From dfc69936f0c803c2362efa2b0d84b271de32dc4d Mon Sep 17 00:00:00 2001 From: shanoysinc Date: Mon, 6 Jul 2026 17:08:36 -0500 Subject: [PATCH 1/2] fix: Update Sponsor Selector logic to display companies without work experience records --- .../app/routes/api.companies.search.tsx | 3 ++- .../employment/queries/list-companies.ts | 25 +++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/apps/admin-dashboard/app/routes/api.companies.search.tsx b/apps/admin-dashboard/app/routes/api.companies.search.tsx index a2dc41f13..c6a208d98 100644 --- a/apps/admin-dashboard/app/routes/api.companies.search.tsx +++ b/apps/admin-dashboard/app/routes/api.companies.search.tsx @@ -13,9 +13,10 @@ export async function loader({ request }: LoaderFunctionArgs) { const search = url.searchParams.get('search') || ''; const { companies } = await listCompanies({ + includeCompaniesWithoutEmployeesOrOpportunities: true, orderBy: 'most_employees', pagination: { - limit: 100, + limit: 50, page: 1, }, select: ['companies.id', 'companies.name'], diff --git a/packages/core/src/modules/employment/queries/list-companies.ts b/packages/core/src/modules/employment/queries/list-companies.ts index aa5e0259b..d37f871dd 100644 --- a/packages/core/src/modules/employment/queries/list-companies.ts +++ b/packages/core/src/modules/employment/queries/list-companies.ts @@ -10,6 +10,7 @@ import { import { type PaginationSearchParams } from '@/shared/types'; type ListCompaniesOptions = { + includeCompaniesWithoutEmployeesOrOpportunities?: boolean; orderBy: ListCompaniesOrderBy; pagination: PaginationSearchParams; select: Selection[]; @@ -18,7 +19,13 @@ type ListCompaniesOptions = { export async function listCompanies< Selection extends SelectExpression, ->({ orderBy, pagination, select, where }: ListCompaniesOptions) { +>({ + includeCompaniesWithoutEmployeesOrOpportunities = false, + orderBy, + pagination, + select, + where, +}: ListCompaniesOptions) { const query = db .selectFrom('companies') .leftJoin('opportunities', (join) => { @@ -33,13 +40,15 @@ export async function listCompanies< 'workExperiences.id' ) .where('workExperiences.deletedAt', 'is', null) - .where((eb) => { - // We only want to return companies that have at least one employee (past - // or present) or opportunity. - return eb.or([ - eb('opportunities.companyId', 'is not', null), - eb('workExperiences.companyId', 'is not', null), - ]); + .$if(!includeCompaniesWithoutEmployeesOrOpportunities, (qb) => { + // We only want to return companies that have at least one employee + // (past or present) or opportunity. + return qb.where((eb) => { + return eb.or([ + eb('opportunities.companyId', 'is not', null), + eb('workExperiences.companyId', 'is not', null), + ]); + }); }) .$if(!!where.search, (qb) => { const { search } = where; From 44db02bd7b7659c740db0b81896e7932b4306f09 Mon Sep 17 00:00:00 2001 From: shanoysinc Date: Tue, 7 Jul 2026 14:03:01 -0500 Subject: [PATCH 2/2] fix: duplicate Airtable table handling when creating resume books --- .../routes/_dashboard.resume-books.create.tsx | 9 ++++- packages/core/src/modules/airtable.ts | 38 +++++++++++++++++++ .../src/modules/resume-books/resume-books.ts | 19 +++++++++- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/apps/admin-dashboard/app/routes/_dashboard.resume-books.create.tsx b/apps/admin-dashboard/app/routes/_dashboard.resume-books.create.tsx index f3de4922c..fc3c2c9e1 100644 --- a/apps/admin-dashboard/app/routes/_dashboard.resume-books.create.tsx +++ b/apps/admin-dashboard/app/routes/_dashboard.resume-books.create.tsx @@ -54,7 +54,14 @@ export async function action({ request }: ActionFunctionArgs) { return data(result, { status: 400 }); } - await createResumeBook(result.data); + const createResult = await createResumeBook(result.data); + + if (!createResult.ok) { + return data( + { errors: { name: createResult.error } }, + { status: createResult.code } + ); + } toast(session, { message: 'Created resume book.', diff --git a/packages/core/src/modules/airtable.ts b/packages/core/src/modules/airtable.ts index 07e6210b6..c0b09c414 100644 --- a/packages/core/src/modules/airtable.ts +++ b/packages/core/src/modules/airtable.ts @@ -319,11 +319,49 @@ type CreateAirtableTableInput = { name: string; }; +/** + * @see https://airtable.com/developers/web/api/list-tables + */ +async function getAirtableTableByName({ + baseId, + name, +}: { + baseId: string; + name: string; +}) { + await airtableRateLimiter.process(); + + const response = await fetch( + `${AIRTABLE_API_URI}/meta/bases/${baseId}/tables`, + { + headers: getAirtableHeaders(), + } + ); + + const json = await response.json(); + + if (!response.ok) { + throw new ColorStackError() + .withMessage('Failed to list Airtable tables.') + .withContext({ baseId, response: json }); + } + + const tables = json.tables as { id: string; name: string }[]; + + return tables.find((table) => table.name === name); +} + export async function createAirtableTable({ baseId, fields, name, }: CreateAirtableTableInput) { + const existingTable = await getAirtableTableByName({ baseId, name }); + + if (existingTable) { + return existingTable.id; + } + await airtableRateLimiter.process(); // The Airtable API doesn't automatically assign colors to select fields, that diff --git a/packages/core/src/modules/resume-books/resume-books.ts b/packages/core/src/modules/resume-books/resume-books.ts index e2b3feff0..422172c54 100644 --- a/packages/core/src/modules/resume-books/resume-books.ts +++ b/packages/core/src/modules/resume-books/resume-books.ts @@ -28,7 +28,7 @@ import { type UpdateResumeBookInput, } from '@/modules/resume-books/resume-books.types'; import { ColorStackError } from '@/shared/errors'; -import { success } from '@/shared/utils/core'; +import { fail, success } from '@/shared/utils/core'; // Environment Variables @@ -45,6 +45,7 @@ type GetResumeBookOptions = { where: Partial<{ hidden: false; id: string; + name: string; status: 'active'; }>; }; @@ -61,6 +62,9 @@ export async function getResumeBook< .$if(!!where.id, (eb) => { return eb.where('id', '=', where.id!); }) + .$if(!!where.name, (eb) => { + return eb.where('name', 'ilike', where.name!); + }) .$if(where.status === 'active', (eb) => { return eb .where('startDate', '<', new Date()) @@ -147,6 +151,19 @@ export async function createResumeBook({ sponsors, startDate, }: CreateResumeBookInput) { + const existingResumeBook = await getResumeBook({ + select: ['id'], + where: { name }, + }); + + if (existingResumeBook) { + return fail({ + code: 409, + context: { name }, + error: 'A resume book with this name already exists.', + }); + } + const [airtableTableId, googleDriveFolderId] = await Promise.all([ createAirtableTable({ baseId: AIRTABLE_RESUME_BOOKS_BASE_ID,