Skip to content

Commit da2eaa5

Browse files
authored
Merge pull request #55 from prodbycorne/feat/issue-53-push-notification-api
feat: Push Notification API for Learning Events
2 parents a1e1ead + 79e71f6 commit da2eaa5

27 files changed

Lines changed: 2363 additions & 508 deletions

lint_results_manual.txt

3.14 KB
Binary file not shown.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"cors": "^2.8.6",
4242
"dotenv": "^16.6.1",
4343
"express": "^5.2.1",
44+
"firebase-admin": "^13.8.0",
4445
"helmet": "^8.1.0",
4546
"jsonwebtoken": "^9.0.2",
4647
"morgan": "^1.10.1",

pnpm-lock.yaml

Lines changed: 1083 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

prisma/schema.prisma

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ model User {
1919
completions Completion[]
2020
credentials Credential[]
2121
transactions Transaction[]
22+
deviceTokens DeviceToken[]
23+
notificationPref NotificationPreference?
24+
notifications NotificationLog[]
2225
syncEvents SyncEvent[]
2326
referralCode ReferralCode?
2427
referrals Referral[] @relation("Referrer")
@@ -161,3 +164,40 @@ model WebhookDelivery {
161164
lastAttemptAt DateTime?
162165
createdAt DateTime @default(now())
163166
}
167+
168+
model DeviceToken {
169+
id String @id @default(uuid())
170+
userId String
171+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
172+
token String @unique
173+
platform String // "ios", "android", "web"
174+
createdAt DateTime @default(now())
175+
updatedAt DateTime @updatedAt
176+
}
177+
178+
model NotificationPreference {
179+
id String @id @default(uuid())
180+
userId String @unique
181+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
182+
rewardReceipt Boolean @default(true)
183+
quizPassFail Boolean @default(true)
184+
streakReminders Boolean @default(true)
185+
createdAt DateTime @default(now())
186+
updatedAt DateTime @updatedAt
187+
}
188+
189+
model NotificationLog {
190+
id String @id @default(uuid())
191+
userId String
192+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
193+
type String // "reward", "quiz", "streak"
194+
title String
195+
body String
196+
status String @default("pending") // pending, sent, failed, dead-letter
197+
error String?
198+
attemptCount Int @default(0)
199+
maxAttempts Int @default(5)
200+
nextAttemptAt DateTime? @default(now())
201+
createdAt DateTime @default(now())
202+
updatedAt DateTime @updatedAt
203+
}

src/config/database.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,37 @@
11
import { PrismaClient } from '@prisma/client'
2+
import { PrismaPg } from '@prisma/adapter-pg'
3+
import { Pool } from 'pg'
4+
5+
const connectionString =
6+
process.env.DATABASE_URL ??
7+
'postgresql://user:password@localhost:5432/learnault'
28

39
const globalForPrisma = globalThis as unknown as {
4-
prisma: PrismaClient | undefined
10+
prisma: PrismaClient | undefined
11+
pool: Pool | undefined
12+
}
13+
14+
function createPrismaClient(): PrismaClient {
15+
const pool =
16+
globalForPrisma.pool ??
17+
new Pool({
18+
connectionString,
19+
max: 10,
20+
})
21+
22+
if (process.env.NODE_ENV !== 'production') {
23+
globalForPrisma.pool = pool
24+
}
25+
26+
const adapter = new PrismaPg(pool)
27+
28+
return new PrismaClient({ adapter })
529
}
630

7-
const prisma = globalForPrisma.prisma ?? new PrismaClient({
8-
datasources: {
9-
db: {
10-
url: process.env.DATABASE_URL,
11-
},
12-
},
13-
})
31+
const prisma = globalForPrisma.prisma ?? createPrismaClient()
1432

1533
if (process.env.NODE_ENV !== 'production') {
16-
globalForPrisma.prisma = prisma
34+
globalForPrisma.prisma = prisma
1735
}
1836

1937
export default prisma

src/controllers/credential.controller.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export class CredentialController {
9696
user: {
9797
select: {
9898
id: true,
99-
name: true,
99+
username: true,
100100
email: true,
101101
},
102102
},
@@ -178,7 +178,7 @@ export class CredentialController {
178178
user: {
179179
select: {
180180
id: true,
181-
name: true,
181+
username: true,
182182
email: true,
183183
},
184184
},
@@ -209,7 +209,7 @@ export class CredentialController {
209209
data: {
210210
id: credential.id,
211211
userId: credential.userId,
212-
holderName: credential.user.name,
212+
holderName: credential.user.username,
213213
moduleId: credential.moduleId,
214214
moduleName: credential.module.title,
215215
moduleDescription: credential.module.description,
@@ -260,7 +260,7 @@ export class CredentialController {
260260
user: {
261261
select: {
262262
id: true,
263-
name: true,
263+
username: true,
264264
},
265265
},
266266
module: {
@@ -282,7 +282,7 @@ export class CredentialController {
282282
user: {
283283
select: {
284284
id: true,
285-
name: true,
285+
username: true,
286286
},
287287
},
288288
module: {
@@ -307,7 +307,7 @@ export class CredentialController {
307307
valid: true,
308308
credential: {
309309
id: credential.id,
310-
holderName: credential.user.name,
310+
holderName: credential.user.username,
311311
moduleName: credential.module.title,
312312
moduleCategory: credential.module.category,
313313
moduleDifficulty: credential.module.difficulty,

src/controllers/employer.controller.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ function locationForCandidate(email: string) {
7070
type CandidateRecord = {
7171
id: string
7272
email: string
73-
name: string
73+
username: string
7474
createdAt: Date
7575
completions: Array<{
7676
score: number
@@ -123,7 +123,7 @@ function profileFromCandidate(candidate: CandidateRecord) {
123123

124124
return {
125125
id: candidate.id,
126-
name: candidate.name,
126+
name: candidate.username,
127127
location: locationForCandidate(candidate.email),
128128
joinedAt: candidate.createdAt,
129129
skills: derivedSkills(candidate),
@@ -178,7 +178,7 @@ export const searchTalent = async (req: Request, res: Response) => {
178178
...(search
179179
? {
180180
OR: [
181-
{ name: { contains: search, mode: 'insensitive' } },
181+
{ username: { contains: search, mode: 'insensitive' } },
182182
{ email: { contains: search, mode: 'insensitive' } },
183183
],
184184
}
@@ -358,7 +358,7 @@ export const contactCandidate = async (req: Request, res: Response) => {
358358
select: {
359359
id: true,
360360
email: true,
361-
name: true,
361+
username: true,
362362
},
363363
})
364364

0 commit comments

Comments
 (0)