Skip to content

Commit 45033e7

Browse files
authored
Merge pull request #25 from applirank/fix/typescript-problems
feat: enhance seed script with environment variable support and error handling
2 parents 2ab046f + b74fb9e commit 45033e7

8 files changed

Lines changed: 92 additions & 26 deletions

File tree

app/pages/index.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,9 @@ async function tryDemo() {
8787
// Activate the demo org before navigating
8888
const orgsResult = await authClient.organization.list()
8989
const orgs = orgsResult.data
90-
if (orgs && orgs.length > 0) {
91-
await authClient.organization.setActive({ organizationId: orgs[0].id })
90+
const firstOrg = orgs?.[0]
91+
if (firstOrg) {
92+
await authClient.organization.setActive({ organizationId: firstOrg.id })
9293
}
9394
9495
// Hard navigation to avoid hydration mismatches between dark landing and light dashboard

app/pages/onboarding/create-org.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@ const autoSwitched = ref(false)
2929
watch([orgs, isOrgsLoading], async ([orgList, loading]) => {
3030
if (loading || autoSwitched.value || showCreateForm.value) return
3131
if (orgList.length === 1) {
32+
const firstOrg = orgList[0]
33+
if (!firstOrg) return
34+
3235
autoSwitched.value = true
3336
isLoading.value = true
3437
try {
35-
await switchOrg(orgList[0].id)
38+
await switchOrg(firstOrg.id)
3639
}
3740
catch {
3841
isLoading.value = false

server/api/__sitemap__/urls.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { eq } from 'drizzle-orm'
2+
import { queryCollection } from '@nuxt/content/server'
23
import { job } from '../../database/schema'
34

45
/**

server/middleware/demo-guard.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,16 @@ export default defineEventHandler(async (event) => {
5252

5353
// Check if the current session belongs to the demo org
5454
const session = await auth.api.getSession({ headers: event.headers })
55-
if (!session?.session.activeOrganizationId) return
55+
const activeOrganizationId = session
56+
? (session.session as { activeOrganizationId?: string }).activeOrganizationId
57+
: undefined
58+
59+
if (!activeOrganizationId) return
5660

5761
const guardedOrgId = await getDemoOrgId()
5862
if (!guardedOrgId) return
5963

60-
if (session.session.activeOrganizationId === guardedOrgId) {
64+
if (activeOrganizationId === guardedOrgId) {
6165
throw createError({
6266
statusCode: 403,
6367
statusMessage: 'Demo mode — this action is disabled. Deploy your own instance to get full access.',

server/plugins/migrations.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ export default defineNitroPlugin(async () => {
1818

1919
try {
2020
// pg_try_advisory_lock returns true if lock acquired, false if another process holds it
21-
const [{ locked }] = await db.execute<{ locked: boolean }>(
21+
const lockResult = await db.execute<{ locked: boolean }>(
2222
`SELECT pg_try_advisory_lock(${MIGRATION_LOCK_ID}) as locked`
2323
)
24+
const locked = lockResult[0]?.locked ?? false
2425

2526
if (!locked) {
2627
console.log('[Applirank] Another instance is running migrations, skipping')

server/scripts/seed.ts

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ if (!DATABASE_URL) {
3333
}
3434

3535
const DEMO_EMAIL = 'demo@applirank.com'
36-
const DEMO_PASSWORD = 'demo1234'
36+
const DEMO_PASSWORD = process.env.DEMO_PASSWORD ?? 'demo1234'
3737
const DEMO_ORG_NAME = 'Applirank Demo'
3838
const DEMO_ORG_SLUG = 'applirank-demo'
3939

@@ -58,8 +58,12 @@ function daysAgo(n: number): Date {
5858
return d
5959
}
6060

61-
function randomItem<T>(arr: T[]): T {
62-
return arr[Math.floor(Math.random() * arr.length)]
61+
function getArrayItemOrThrow<T>(arr: readonly T[], index: number, context: string): T {
62+
const value = arr[index]
63+
if (value === undefined) {
64+
throw new Error(`Missing ${context} at index ${index}`)
65+
}
66+
return value
6367
}
6468

6569
function generateSlug(title: string, uuid: string): string {
@@ -246,6 +250,11 @@ const JOB_APPLICATIONS = [JOB_0_APPS, JOB_1_APPS, JOB_2_APPS, JOB_3_APPS, []]
246250

247251
// Sample responses for questions
248252
function generateResponses(jobIndex: number, candidateIndex: number): Record<string, string | string[] | boolean> {
253+
const candidate = CANDIDATES_DATA[candidateIndex]
254+
if (!candidate) {
255+
return {}
256+
}
257+
249258
if (jobIndex === 0) {
250259
const years = ['3', '4', '5', '6', '7', '8+']
251260
const frameworks = ['Vue', 'React', 'Svelte', 'Vue', 'React', 'Vue']
@@ -259,12 +268,16 @@ function generateResponses(jobIndex: number, candidateIndex: number): Record<str
259268
'Created a type-safe API client generator from OpenAPI specs that eliminated an entire class of runtime errors.',
260269
]
261270
const i = candidateIndex % years.length
271+
const year = getArrayItemOrThrow(years, i, 'TypeScript years response')
272+
const framework = getArrayItemOrThrow(frameworks, i, 'framework response')
273+
const problem = getArrayItemOrThrow(problems, i, 'problem response')
274+
const start = getArrayItemOrThrow(starts, i, 'start date response')
262275
return {
263-
'Years of TypeScript experience': years[i],
264-
'Preferred frontend framework': frameworks[i],
265-
'Describe a challenging technical problem you solved recently': problems[i],
266-
'Link to your GitHub profile or portfolio': `https://github.com/${CANDIDATES_DATA[candidateIndex].firstName.toLowerCase()}${CANDIDATES_DATA[candidateIndex].lastName.toLowerCase().replace(/['\s]/g, '')}`,
267-
'When can you start?': starts[i],
276+
'Years of TypeScript experience': year,
277+
'Preferred frontend framework': framework,
278+
'Describe a challenging technical problem you solved recently': problem,
279+
'Link to your GitHub profile or portfolio': `https://github.com/${candidate.firstName.toLowerCase()}${candidate.lastName.toLowerCase().replace(/['\s]/g, '')}`,
280+
'When can you start?': start,
268281
}
269282
}
270283
if (jobIndex === 1) {
@@ -276,10 +289,12 @@ function generateResponses(jobIndex: number, candidateIndex: number): Record<str
276289
'I believe in rapid prototyping. Quick sketches → Figma prototypes → guerrilla testing → iteration. Speed of learning beats perfection.',
277290
]
278291
const i = candidateIndex % tools.length
292+
const tool = getArrayItemOrThrow(tools, i, 'design tool response')
293+
const process = getArrayItemOrThrow(processes, i, 'design process response')
279294
return {
280-
'Link to your portfolio': `https://dribbble.com/${CANDIDATES_DATA[candidateIndex].firstName.toLowerCase()}${CANDIDATES_DATA[candidateIndex].lastName.toLowerCase().charAt(0)}`,
281-
'Primary design tool': tools[i],
282-
'Walk us through your design process for a recent project': processes[i],
295+
'Link to your portfolio': `https://dribbble.com/${candidate.firstName.toLowerCase()}${candidate.lastName.toLowerCase().charAt(0)}`,
296+
'Primary design tool': tool,
297+
'Walk us through your design process for a recent project': process,
283298
'I have experience with design systems': candidateIndex % 2 === 0,
284299
}
285300
}
@@ -288,10 +303,13 @@ function generateResponses(jobIndex: number, candidateIndex: number): Record<str
288303
const ciPlatforms = ['GitHub Actions', 'GitLab CI', 'GitHub Actions', 'Jenkins', 'GitHub Actions', 'CircleCI']
289304
const dockerYears = ['3', '4', '5', '6', '2', '7']
290305
const i = candidateIndex % platforms.length
306+
const platformList = getArrayItemOrThrow(platforms, i, 'cloud platform response')
307+
const ciPlatform = getArrayItemOrThrow(ciPlatforms, i, 'CI/CD platform response')
308+
const dockerYear = getArrayItemOrThrow(dockerYears, i, 'docker years response')
291309
return {
292-
'Which cloud platforms have you worked with?': platforms[i],
293-
'Years of Docker experience': dockerYears[i],
294-
'Preferred CI/CD platform': ciPlatforms[i],
310+
'Which cloud platforms have you worked with?': platformList,
311+
'Years of Docker experience': dockerYear,
312+
'Preferred CI/CD platform': ciPlatform,
295313
}
296314
}
297315
return {}
@@ -418,17 +436,26 @@ async function seed() {
418436

419437
for (let jobIndex = 0; jobIndex < questionSets.length; jobIndex++) {
420438
const questions = questionSets[jobIndex]
439+
const jobId = jobIds[jobIndex]
440+
if (!questions || !jobId) {
441+
throw new Error(`Invalid seed configuration for questions at job index ${jobIndex}`)
442+
}
443+
421444
const questionIds: { questionId: string; label: string }[] = []
422445

423446
for (let qi = 0; qi < questions.length; qi++) {
424447
const q = questions[qi]
448+
if (!q) {
449+
continue
450+
}
451+
425452
const questionId = id()
426453
questionIds.push({ questionId, label: q.label })
427454

428455
await db.insert(schema.jobQuestion).values({
429456
id: questionId,
430457
organizationId: orgId,
431-
jobId: jobIds[jobIndex],
458+
jobId,
432459
type: q.type,
433460
label: q.label,
434461
required: q.required,
@@ -449,16 +476,25 @@ async function seed() {
449476

450477
for (let jobIndex = 0; jobIndex < JOB_APPLICATIONS.length; jobIndex++) {
451478
const apps = JOB_APPLICATIONS[jobIndex]
479+
const jobId = jobIds[jobIndex]
480+
if (!apps || !jobId) {
481+
throw new Error(`Invalid seed configuration for applications at job index ${jobIndex}`)
482+
}
452483

453484
for (const app of apps) {
485+
const candidateId = candidateIds[app.candidateIndex]
486+
if (!candidateId) {
487+
throw new Error(`Missing candidate ID for candidate index ${app.candidateIndex}`)
488+
}
489+
454490
const applicationId = id()
455491
const createdDaysAgo = 1 + Math.floor(Math.random() * 15)
456492

457493
await db.insert(schema.application).values({
458494
id: applicationId,
459495
organizationId: orgId,
460-
candidateId: candidateIds[app.candidateIndex],
461-
jobId: jobIds[jobIndex],
496+
candidateId,
497+
jobId,
462498
status: app.status,
463499
score: app.score ?? null,
464500
notes: app.notes ?? null,

server/utils/db.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ function getDB(): DB {
4141
export const db: DB = new Proxy({} as DB, {
4242
get(_, prop) {
4343
const instance = getDB()
44-
const value = (instance as Record<string, unknown>)[prop]
44+
45+
if (typeof prop !== 'string') {
46+
return Reflect.get(instance as object, prop)
47+
}
48+
49+
const value = (instance as unknown as Record<string, unknown>)[prop]
4550
// Bind methods so they keep the correct `this` context
4651
return typeof value === 'function' ? value.bind(instance) : value
4752
},

server/utils/requireAuth.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import type { H3Event } from 'h3'
22

3+
type AuthSession = NonNullable<Awaited<ReturnType<typeof auth.api.getSession>>>
4+
type AuthSessionWithActiveOrg = Omit<AuthSession, 'session'> & {
5+
session: AuthSession['session'] & {
6+
activeOrganizationId: string
7+
}
8+
}
9+
310
/**
411
* Require an authenticated session with an active organization.
512
* Throws 401 if not authenticated, 403 if no active organization selected.
@@ -16,9 +23,17 @@ export async function requireAuth(event: H3Event) {
1623
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
1724
}
1825

19-
if (!session.session.activeOrganizationId) {
26+
const activeOrganizationId = (session.session as { activeOrganizationId?: string }).activeOrganizationId
27+
28+
if (!activeOrganizationId) {
2029
throw createError({ statusCode: 403, statusMessage: 'No active organization' })
2130
}
2231

23-
return session
32+
return {
33+
...session,
34+
session: {
35+
...session.session,
36+
activeOrganizationId,
37+
},
38+
} as AuthSessionWithActiveOrg
2439
}

0 commit comments

Comments
 (0)