Skip to content

Commit 5fae5c0

Browse files
feat(backend): Add webhook delivery system for card view events (#590)
* backend * fix: remove conflict markers from schema.prisma * Testcases * resolve ESLint errors * fix: resolve --------- Signed-off-by: Dipti45sktech <kdsofficial.0106@gmail.com>
1 parent 7aabb74 commit 5fae5c0

8 files changed

Lines changed: 947 additions & 139 deletions

File tree

apps/backend/prisma/schema.prisma

Lines changed: 127 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,59 @@
11
generator client {
22
provider = "prisma-client-js"
33
}
4+
45
datasource db {
56
provider = "postgresql"
67
url = env("DATABASE_URL")
7-
88
}
9-
enum Role{
9+
10+
enum Role {
1011
SUPERADMIN
1112
ADMIN
1213
USER
13-
1414
}
15+
1516
model User {
16-
id String @id @default(uuid())
17-
email String @unique
18-
username String @unique
19-
displayName String @map("display_name")
20-
bio String?
21-
pronouns String?
22-
role String?
23-
authRole Role @default(USER)
24-
company String?
25-
avatarUrl String? @map("avatar_url")
26-
accentColor String @default("#6366f1") @map("accent_color")
27-
emailVerified Boolean @default(false) @map("email_verified")
28-
phoneNumber String? @unique @map("phone_number")
29-
lastSignInAt DateTime? @map("last_sign_in_at")
30-
createdAt DateTime @default(now()) @map("created_at")
31-
updatedAt DateTime @updatedAt @map("updated_at")
32-
isActive Boolean @default(false)
33-
34-
identities UserIdentity[]
35-
refreshTokens RefreshToken[]
36-
platformLinks PlatformLink[]
37-
cards Card[]
38-
oauthTokens OAuthToken[]
39-
ownedViews CardView[] @relation("cardOwner")
40-
viewedCards CardView[] @relation("cardViewer")
41-
followLogs FollowLog[]
42-
organizer Event[]
43-
attendedEvents EventAttendee[]
44-
ownedTeams Team[] @relation("TeamOwner")
45-
teamMemberships TeamMember[] @relation("TeamMember")
17+
id String @id @default(uuid())
18+
email String @unique
19+
username String @unique
20+
displayName String @map("display_name")
21+
bio String?
22+
pronouns String?
23+
role String?
24+
authRole Role @default(USER)
25+
company String?
26+
avatarUrl String? @map("avatar_url")
27+
accentColor String @default("#6366f1") @map("accent_color")
28+
emailVerified Boolean @default(false) @map("email_verified")
29+
phoneNumber String? @unique @map("phone_number")
30+
lastSignInAt DateTime? @map("last_sign_in_at")
31+
createdAt DateTime @default(now()) @map("created_at")
32+
updatedAt DateTime @updatedAt @map("updated_at")
33+
isActive Boolean @default(false)
34+
35+
identities UserIdentity[]
36+
refreshTokens RefreshToken[]
37+
platformLinks PlatformLink[]
38+
cards Card[]
39+
oauthTokens OAuthToken[]
40+
ownedViews CardView[] @relation("cardOwner")
41+
viewedCards CardView[] @relation("cardViewer")
42+
followLogs FollowLog[]
43+
organizer Event[]
44+
attendedEvents EventAttendee[]
45+
ownedTeams Team[] @relation("TeamOwner")
46+
teamMemberships TeamMember[] @relation("TeamMember")
47+
webhookEndpoints WebhookEndpoint[]
4648
4749
@@map("users")
4850
}
4951

5052
model UserIdentity {
5153
id String @id @default(uuid())
5254
userId String @map("user_id")
53-
provider String // "google.com" | "apple.com" | "firebase" | "phone"
54-
providerId String @map("provider_id") // Google sub / Apple sub / Firebase UID
55+
provider String
56+
providerId String @map("provider_id")
5557
createdAt DateTime @default(now()) @map("created_at")
5658
5759
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@ -61,17 +63,16 @@ model UserIdentity {
6163
@@map("user_identities")
6264
}
6365

64-
6566
model RefreshToken {
6667
id String @id @default(uuid())
6768
userId String @map("user_id")
68-
tokenHash String @unique @map("token_hash") //SHA-256 hash
69-
family String // token rotation
69+
tokenHash String @unique @map("token_hash")
70+
family String
7071
expiresAt DateTime @map("expires_at")
71-
revokedAt DateTime? @map("revoked_at") // null = still valid
72+
revokedAt DateTime? @map("revoked_at")
7273
createdAt DateTime @default(now()) @map("created_at")
73-
userAgent String? @map("user_agent")
74-
ip String? //hash
74+
userAgent String? @map("user_agent")
75+
ip String?
7576
7677
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
7778
@@ -83,13 +84,13 @@ model RefreshToken {
8384
}
8485

8586
model PlatformLink {
86-
id String @id @default(uuid())
87-
userId String @map("user_id")
87+
id String @id @default(uuid())
88+
userId String @map("user_id")
8889
platform String
8990
username String
9091
url String
91-
displayOrder Int @default(0) @map("display_order")
92-
createdAt DateTime @default(now()) @map("created_at")
92+
displayOrder Int @default(0) @map("display_order")
93+
createdAt DateTime @default(now()) @map("created_at")
9394
9495
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
9596
cardLinks CardLink[]
@@ -170,12 +171,12 @@ model CardView {
170171
viewerId String? @map("viewer_id") // null = anonymous web viewer
171172
viewerIp String? @map("viewer_ip") //hashed
172173
viewerAgent String? @map("viewer_agent")
173-
source String @default("qr") // "qr" | "link" | "web" | "app"
174+
source String @default("qr")
174175
createdAt DateTime @default(now()) @map("created_at")
175176
176-
card Card? @relation(fields: [cardId], references: [id], onDelete: SetNull)
177-
owner User @relation("cardOwner", fields: [ownerId], references: [id], onDelete: Cascade)
178-
viewer User? @relation("cardViewer", fields: [viewerId], references: [id], onDelete: SetNull)
177+
card Card? @relation(fields: [cardId], references: [id], onDelete: SetNull)
178+
owner User @relation("cardOwner", fields: [ownerId], references: [id], onDelete: Cascade)
179+
viewer User? @relation("cardViewer", fields: [viewerId], references: [id], onDelete: SetNull)
179180
180181
@@map("card_views")
181182
@@index([cardId])
@@ -187,8 +188,8 @@ model FollowLog {
187188
followerId String @map("follower_id")
188189
targetUsername String @map("target_username")
189190
platform String
190-
status String @default("success") // "success" | "error"
191-
layer String // "api" | "webview" | "link"
191+
status String @default("success")
192+
layer String
192193
createdAt DateTime @default(now()) @map("created_at")
193194
194195
follower User @relation(fields: [followerId], references: [id], onDelete: Cascade)
@@ -197,31 +198,71 @@ model FollowLog {
197198
}
198199

199200
model Event {
200-
id String @id @default(uuid())
201-
name String
202-
slug String @unique
203-
location String
201+
id String @id @default(uuid())
202+
name String
203+
slug String @unique
204+
location String
204205
description String?
205-
organizerId String
206-
startDate DateTime
207-
endDate DateTime
208-
isPublic Boolean @default(true)
209-
createdAt DateTime @default(now()) @map("created_at")
206+
organizerId String
207+
startDate DateTime
208+
endDate DateTime
209+
isPublic Boolean @default(true)
210+
createdAt DateTime @default(now()) @map("created_at")
211+
210212
attendees EventAttendee[]
213+
organizer User @relation(fields: [organizerId], references: [id])
211214
212-
organizer User @relation(fields: [organizerId], references: [id])
215+
@@map("events")
213216
}
214217

215218
model EventAttendee {
216-
id String @id @default(uuid())
217-
userId String
218-
eventId String
219-
joinedAt DateTime
219+
id String @id @default(uuid())
220+
userId String
221+
eventId String
222+
joinedAt DateTime
220223
221-
event Event @relation(fields: [eventId] , references: [id])
222-
user User @relation(fields: [userId],references: [id])
224+
event Event @relation(fields: [eventId], references: [id])
225+
user User @relation(fields: [userId], references: [id])
223226
224227
@@unique([userId, eventId])
228+
@@map("event_attendees")
229+
}
230+
231+
model WebhookEndpoint {
232+
id String @id @default(uuid())
233+
userId String @map("user_id")
234+
url String
235+
secret String
236+
events String[]
237+
isActive Boolean @default(true) @map("is_active")
238+
createdAt DateTime @default(now()) @map("created_at")
239+
240+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
241+
deliveries WebhookDelivery[]
242+
243+
@@index([userId])
244+
@@map("webhook_endpoints")
245+
}
246+
247+
model WebhookDelivery {
248+
id String @id @default(uuid())
249+
endpointId String @map("endpoint_id")
250+
eventType String @map("event_type")
251+
payload Json
252+
status String @default("pending")
253+
responseCode Int? @map("response_code")
254+
attempts Int @default(0)
255+
nextRetryAt DateTime? @map("next_retry_at")
256+
createdAt DateTime @default(now()) @map("created_at")
257+
updatedAt DateTime @updatedAt @map("updated_at")
258+
errorMessage String? @map("error_message")
259+
deliveredAt DateTime? @map("delivered_at")
260+
261+
endpoint WebhookEndpoint @relation(fields: [endpointId], references: [id], onDelete: Cascade)
262+
263+
@@index([endpointId])
264+
@@index([status, nextRetryAt])
265+
@@map("webhook_deliveries")
225266
}
226267

227268
enum TeamRole {
@@ -230,32 +271,32 @@ enum TeamRole {
230271
MEMBER
231272
}
232273

233-
model Team{
234-
id String @id @default(uuid())
235-
name String
236-
slug String @unique
237-
description String?
238-
avatarUrl String?
239-
ownerId String
240-
createdAt DateTime @default(now())
241-
updatedAt DateTime @updatedAt
274+
model Team {
275+
id String @id @default(uuid())
276+
name String
277+
slug String @unique
278+
description String?
279+
avatarUrl String?
280+
ownerId String
281+
createdAt DateTime @default(now())
282+
updatedAt DateTime @updatedAt
242283
243-
owner User @relation("TeamOwner", fields: [ownerId], references: [id], onDelete: Restrict)
284+
owner User @relation("TeamOwner", fields: [ownerId], references: [id], onDelete: Restrict)
244285
members TeamMember[] @relation("TeamMember")
245286
246-
@@map("teams")
247287
@@index([slug])
288+
@@map("teams")
248289
}
249290

250-
model TeamMember{
251-
id String @id @default(uuid())
252-
teamId String
253-
userId String
254-
role TeamRole
255-
joinedAt DateTime
291+
model TeamMember {
292+
id String @id @default(uuid())
293+
teamId String
294+
userId String
295+
role TeamRole
296+
joinedAt DateTime
256297
257-
team Team @relation("TeamMember",fields: [teamId] , references: [id], onDelete: Cascade)
258-
user User @relation("TeamMember",fields: [userId] , references: [id])
298+
team Team @relation("TeamMember", fields: [teamId], references: [id], onDelete: Cascade)
299+
user User @relation("TeamMember", fields: [userId], references: [id])
259300
260301
@@unique([userId, teamId])
261302
@@index([userId])

apps/backend/src/__tests__/app.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('GET /health', () => {
1818
});
1919

2020
expect(res.statusCode).toBe(200);
21-
expect(JSON.parse(res.body)).toEqual({ status: 'ok' });
21+
expect(JSON.parse(res.body)).toMatchObject({ status: 'ok' });
2222

2323
await app.close();
2424
});

0 commit comments

Comments
 (0)