Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added lint_results_manual.txt
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"cors": "^2.8.6",
"dotenv": "^16.6.1",
"express": "^5.2.1",
"firebase-admin": "^13.8.0",
"helmet": "^8.1.0",
"jsonwebtoken": "^9.0.2",
"morgan": "^1.10.1",
Expand Down
1,088 changes: 1,083 additions & 5 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ model User {
completions Completion[]
credentials Credential[]
transactions Transaction[]
deviceTokens DeviceToken[]
notificationPref NotificationPreference?
notifications NotificationLog[]
syncEvents SyncEvent[]
referralCode ReferralCode?
referrals Referral[] @relation("Referrer")
Expand Down Expand Up @@ -161,3 +164,40 @@ model WebhookDelivery {
lastAttemptAt DateTime?
createdAt DateTime @default(now())
}

model DeviceToken {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
token String @unique
platform String // "ios", "android", "web"
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

model NotificationPreference {
id String @id @default(uuid())
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
rewardReceipt Boolean @default(true)
quizPassFail Boolean @default(true)
streakReminders Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

model NotificationLog {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
type String // "reward", "quiz", "streak"
title String
body String
status String @default("pending") // pending, sent, failed, dead-letter
error String?
attemptCount Int @default(0)
maxAttempts Int @default(5)
nextAttemptAt DateTime? @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
36 changes: 27 additions & 9 deletions src/config/database.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
import { PrismaClient } from '@prisma/client'
import { PrismaPg } from '@prisma/adapter-pg'
import { Pool } from 'pg'

const connectionString =
process.env.DATABASE_URL ??
'postgresql://user:password@localhost:5432/learnault'

const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
prisma: PrismaClient | undefined
pool: Pool | undefined
}

function createPrismaClient(): PrismaClient {
const pool =
globalForPrisma.pool ??
new Pool({
connectionString,
max: 10,
})

if (process.env.NODE_ENV !== 'production') {
globalForPrisma.pool = pool
}

const adapter = new PrismaPg(pool)

return new PrismaClient({ adapter })
}

const prisma = globalForPrisma.prisma ?? new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
})
const prisma = globalForPrisma.prisma ?? createPrismaClient()

if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
globalForPrisma.prisma = prisma
}

export default prisma
Expand Down
12 changes: 6 additions & 6 deletions src/controllers/credential.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class CredentialController {
user: {
select: {
id: true,
name: true,
username: true,
email: true,
},
},
Expand Down Expand Up @@ -178,7 +178,7 @@ export class CredentialController {
user: {
select: {
id: true,
name: true,
username: true,
email: true,
},
},
Expand Down Expand Up @@ -209,7 +209,7 @@ export class CredentialController {
data: {
id: credential.id,
userId: credential.userId,
holderName: credential.user.name,
holderName: credential.user.username,
moduleId: credential.moduleId,
moduleName: credential.module.title,
moduleDescription: credential.module.description,
Expand Down Expand Up @@ -260,7 +260,7 @@ export class CredentialController {
user: {
select: {
id: true,
name: true,
username: true,
},
},
module: {
Expand All @@ -282,7 +282,7 @@ export class CredentialController {
user: {
select: {
id: true,
name: true,
username: true,
},
},
module: {
Expand All @@ -307,7 +307,7 @@ export class CredentialController {
valid: true,
credential: {
id: credential.id,
holderName: credential.user.name,
holderName: credential.user.username,
moduleName: credential.module.title,
moduleCategory: credential.module.category,
moduleDifficulty: credential.module.difficulty,
Expand Down
8 changes: 4 additions & 4 deletions src/controllers/employer.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function locationForCandidate(email: string) {
type CandidateRecord = {
id: string
email: string
name: string
username: string
createdAt: Date
completions: Array<{
score: number
Expand Down Expand Up @@ -123,7 +123,7 @@ function profileFromCandidate(candidate: CandidateRecord) {

return {
id: candidate.id,
name: candidate.name,
name: candidate.username,
location: locationForCandidate(candidate.email),
joinedAt: candidate.createdAt,
skills: derivedSkills(candidate),
Expand Down Expand Up @@ -178,7 +178,7 @@ export const searchTalent = async (req: Request, res: Response) => {
...(search
? {
OR: [
{ name: { contains: search, mode: 'insensitive' } },
{ username: { contains: search, mode: 'insensitive' } },
{ email: { contains: search, mode: 'insensitive' } },
],
}
Expand Down Expand Up @@ -358,7 +358,7 @@ export const contactCandidate = async (req: Request, res: Response) => {
select: {
id: true,
email: true,
name: true,
username: true,
},
})

Expand Down
Loading
Loading