vintasend-prisma 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +9 -0
- package/biome.json +34 -0
- package/index.ts +1 -0
- package/package.json +17 -0
- package/prisma-notification-backend.ts +516 -0
- package/schema.prisma.example +49 -0
package/.editorconfig
ADDED
package/biome.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": false,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": false
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"ignoreUnknown": false,
|
|
10
|
+
"ignore": []
|
|
11
|
+
},
|
|
12
|
+
"formatter": {
|
|
13
|
+
"enabled": true,
|
|
14
|
+
"indentStyle": "space",
|
|
15
|
+
"indentWidth": 2,
|
|
16
|
+
"lineWidth": 100
|
|
17
|
+
},
|
|
18
|
+
"organizeImports": {
|
|
19
|
+
"enabled": true
|
|
20
|
+
},
|
|
21
|
+
"linter": {
|
|
22
|
+
"enabled": true,
|
|
23
|
+
"rules": {
|
|
24
|
+
"recommended": true
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"javascript": {
|
|
28
|
+
"formatter": {
|
|
29
|
+
"quoteStyle": "single",
|
|
30
|
+
"semicolons": "always",
|
|
31
|
+
"trailingCommas": "all"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PrismaNotificationBackend } from './prisma-notification-backend';
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vintasend-prisma",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "VintaSend Backend implementation for Prisma",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"generate-prisma-client": "prisma generate"
|
|
9
|
+
},
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@prisma/client": "^6.3.1",
|
|
14
|
+
"prisma": "^6.3.1",
|
|
15
|
+
"vintasend": "^0.1.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import type { BaseNotificationBackend } from 'vintasend/src/services/notification-backends/base-notification-backend';
|
|
2
|
+
import type { ContextGenerator } from 'vintasend/src/services/notification-context-registry';
|
|
3
|
+
import type { InputJsonValue, JsonValue } from 'vintasend/src/types/json-values';
|
|
4
|
+
import type { Notification, NotificationInput } from 'vintasend/src/types/notification';
|
|
5
|
+
import type { NotificationStatus } from 'vintasend/src/types/notification-status';
|
|
6
|
+
import type { NotificationType } from 'vintasend/src/types/notification-type';
|
|
7
|
+
|
|
8
|
+
export const NotificationStatusEnum = {
|
|
9
|
+
PENDING_SEND: 'PENDING_SEND',
|
|
10
|
+
SENT: 'SENT',
|
|
11
|
+
FAILED: 'FAILED',
|
|
12
|
+
READ: 'READ',
|
|
13
|
+
CANCELLED: 'CANCELLED',
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export const NotificationTypeEnum = {
|
|
17
|
+
EMAIL: 'EMAIL',
|
|
18
|
+
PUSH: 'PUSH',
|
|
19
|
+
SMS: 'SMS',
|
|
20
|
+
IN_APP: 'IN_APP',
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
export interface PrismaNotificationModel<IdType, UserId> {
|
|
24
|
+
id: IdType;
|
|
25
|
+
userId: UserId;
|
|
26
|
+
notificationType: NotificationType;
|
|
27
|
+
title: string | null;
|
|
28
|
+
bodyTemplate: string;
|
|
29
|
+
contextName: string;
|
|
30
|
+
contextParameters: JsonValue;
|
|
31
|
+
sendAfter: Date | null;
|
|
32
|
+
subjectTemplate: string | null;
|
|
33
|
+
status: NotificationStatus;
|
|
34
|
+
contextUsed: JsonValue | null;
|
|
35
|
+
extraParams: JsonValue | null;
|
|
36
|
+
adapterUsed: string | null;
|
|
37
|
+
sentAt: Date | null;
|
|
38
|
+
readAt: Date | null;
|
|
39
|
+
createdAt: Date;
|
|
40
|
+
updatedAt: Date;
|
|
41
|
+
user?: {
|
|
42
|
+
email: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface NotificationPrismaClientInterface<NotificationIdType, UserIdType> {
|
|
47
|
+
notification: {
|
|
48
|
+
findMany(args: {
|
|
49
|
+
where?: {
|
|
50
|
+
status?: NotificationStatus | { not: NotificationStatus };
|
|
51
|
+
sendAfter?: { lte: Date } | null;
|
|
52
|
+
userId?: UserIdType;
|
|
53
|
+
readAt?: null;
|
|
54
|
+
};
|
|
55
|
+
skip?: number;
|
|
56
|
+
take?: number;
|
|
57
|
+
include?: { user?: boolean };
|
|
58
|
+
}): Promise<PrismaNotificationModel<NotificationIdType, UserIdType>[]>;
|
|
59
|
+
create(args: {
|
|
60
|
+
data: BaseNotificationCreateInput<UserIdType>;
|
|
61
|
+
include?: { user?: boolean };
|
|
62
|
+
}): Promise<PrismaNotificationModel<NotificationIdType, UserIdType>>;
|
|
63
|
+
update(args: {
|
|
64
|
+
where: { id: NotificationIdType };
|
|
65
|
+
data: Partial<BaseNotificationUpdateInput<UserIdType>>;
|
|
66
|
+
include?: { user?: boolean };
|
|
67
|
+
}): Promise<PrismaNotificationModel<NotificationIdType, UserIdType>>;
|
|
68
|
+
findUnique(args: {
|
|
69
|
+
where: { id: NotificationIdType };
|
|
70
|
+
include?: { user?: boolean };
|
|
71
|
+
}): Promise<PrismaNotificationModel<NotificationIdType, UserIdType> | null>;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// cause typescript not to expand types and preserve names
|
|
76
|
+
type NoExpand<T> = T extends unknown ? T : never;
|
|
77
|
+
|
|
78
|
+
// this type assumes the passed object is entirely optional
|
|
79
|
+
type AtLeast<O extends object, K extends string> = NoExpand<
|
|
80
|
+
O extends unknown
|
|
81
|
+
?
|
|
82
|
+
| (K extends keyof O ? { [P in K]: O[P] } & O : O)
|
|
83
|
+
| ({ [P in keyof O as P extends K ? K : never]-?: O[P] } & O)
|
|
84
|
+
: never
|
|
85
|
+
>;
|
|
86
|
+
|
|
87
|
+
export interface BaseNotificationCreateInput<UserIdType> {
|
|
88
|
+
user: {
|
|
89
|
+
connect?: AtLeast<
|
|
90
|
+
{
|
|
91
|
+
id?: UserIdType;
|
|
92
|
+
email?: string;
|
|
93
|
+
},
|
|
94
|
+
'id' | 'email'
|
|
95
|
+
>;
|
|
96
|
+
};
|
|
97
|
+
notificationType: NotificationType;
|
|
98
|
+
title?: string | null;
|
|
99
|
+
bodyTemplate: string;
|
|
100
|
+
contextName: string;
|
|
101
|
+
contextParameters: InputJsonValue;
|
|
102
|
+
sendAfter?: Date | null;
|
|
103
|
+
subjectTemplate?: string | null;
|
|
104
|
+
status?: NotificationStatus;
|
|
105
|
+
contextUsed?: InputJsonValue;
|
|
106
|
+
extraParams?: InputJsonValue;
|
|
107
|
+
adapterUsed?: string | null;
|
|
108
|
+
sentAt?: Date | null;
|
|
109
|
+
readAt?: Date | null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface BaseNotificationUpdateInput<UserIdType> {
|
|
113
|
+
user: {
|
|
114
|
+
connect?: AtLeast<
|
|
115
|
+
{
|
|
116
|
+
id?: UserIdType;
|
|
117
|
+
email?: string;
|
|
118
|
+
},
|
|
119
|
+
'id' | 'email'
|
|
120
|
+
>;
|
|
121
|
+
};
|
|
122
|
+
notificationType?: NotificationType;
|
|
123
|
+
title?: string | null;
|
|
124
|
+
bodyTemplate?: string;
|
|
125
|
+
contextName?: string;
|
|
126
|
+
contextParameters?: InputJsonValue;
|
|
127
|
+
sendAfter?: Date | null;
|
|
128
|
+
subjectTemplate?: string | null;
|
|
129
|
+
status?: NotificationStatus;
|
|
130
|
+
contextUsed?: InputJsonValue;
|
|
131
|
+
extraParams?: InputJsonValue;
|
|
132
|
+
adapterUsed?: string | null;
|
|
133
|
+
sentAt?: Date | null;
|
|
134
|
+
readAt?: Date | null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function convertJsonValueToRecord(jsonValue: JsonValue): Record<string, string | number | boolean> {
|
|
138
|
+
if (typeof jsonValue === 'object' && !Array.isArray(jsonValue) && jsonValue !== null) {
|
|
139
|
+
return jsonValue as Record<string, string | number | boolean>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
throw new Error('Invalid JSON value. It should be an object.');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export class PrismaNotificationBackend<
|
|
146
|
+
Client extends NotificationPrismaClientInterface<NotificationIdType, UserIdType>,
|
|
147
|
+
AvailableContexts extends Record<string, ContextGenerator>,
|
|
148
|
+
NotificationIdType extends string | number,
|
|
149
|
+
UserIdType extends string | number,
|
|
150
|
+
> implements BaseNotificationBackend<AvailableContexts>
|
|
151
|
+
{
|
|
152
|
+
constructor(private prismaClient: Client) {}
|
|
153
|
+
|
|
154
|
+
serializeNotification(
|
|
155
|
+
notification: NonNullable<
|
|
156
|
+
Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
|
|
157
|
+
>,
|
|
158
|
+
): Notification<AvailableContexts, NotificationIdType, UserIdType> {
|
|
159
|
+
return {
|
|
160
|
+
id: notification.id,
|
|
161
|
+
userId: notification.userId,
|
|
162
|
+
notificationType: notification.notificationType,
|
|
163
|
+
title: notification.title,
|
|
164
|
+
bodyTemplate: notification.bodyTemplate,
|
|
165
|
+
contextName: notification.contextName as keyof AvailableContexts,
|
|
166
|
+
contextParameters: notification.contextParameters
|
|
167
|
+
? (notification.contextParameters as Parameters<
|
|
168
|
+
AvailableContexts[keyof AvailableContexts]['generate']
|
|
169
|
+
>[0])
|
|
170
|
+
: {},
|
|
171
|
+
sendAfter: notification.sendAfter,
|
|
172
|
+
subjectTemplate: notification.subjectTemplate,
|
|
173
|
+
status: notification.status,
|
|
174
|
+
contextUsed: notification.contextUsed as ReturnType<
|
|
175
|
+
AvailableContexts[keyof AvailableContexts]['generate']
|
|
176
|
+
> | null,
|
|
177
|
+
extraParams: notification.extraParams
|
|
178
|
+
? convertJsonValueToRecord(notification.extraParams)
|
|
179
|
+
: null,
|
|
180
|
+
adapterUsed: notification.adapterUsed,
|
|
181
|
+
sentAt: notification.sentAt,
|
|
182
|
+
readAt: notification.readAt,
|
|
183
|
+
createdAt: notification.createdAt,
|
|
184
|
+
updatedAt: notification.updatedAt,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
deserializeNotification(
|
|
189
|
+
notification: NotificationInput<AvailableContexts, UserIdType>,
|
|
190
|
+
): BaseNotificationCreateInput<UserIdType> {
|
|
191
|
+
return {
|
|
192
|
+
user: {
|
|
193
|
+
connect: {
|
|
194
|
+
id: notification.userId,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
notificationType: notification.notificationType,
|
|
198
|
+
title: notification.title,
|
|
199
|
+
bodyTemplate: notification.bodyTemplate,
|
|
200
|
+
contextName: notification.contextName as string,
|
|
201
|
+
contextParameters: notification.contextParameters as InputJsonValue,
|
|
202
|
+
sendAfter: notification.sendAfter,
|
|
203
|
+
subjectTemplate: notification.subjectTemplate,
|
|
204
|
+
extraParams: notification.extraParams as InputJsonValue,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
deserializeNotificationForUpdate(
|
|
209
|
+
notification: Partial<Notification<AvailableContexts, NotificationIdType, UserIdType>>,
|
|
210
|
+
): Partial<Parameters<typeof this.prismaClient.notification.update>[0]['data']> {
|
|
211
|
+
return {
|
|
212
|
+
...(notification.userId ? { user: { connect: { id: notification.userId } } } : {}),
|
|
213
|
+
...(notification.notificationType
|
|
214
|
+
? {
|
|
215
|
+
notificationType: NotificationTypeEnum[
|
|
216
|
+
notification.notificationType as keyof typeof NotificationTypeEnum
|
|
217
|
+
] as NotificationType,
|
|
218
|
+
}
|
|
219
|
+
: {}),
|
|
220
|
+
...(notification.title ? { title: notification.title } : {}),
|
|
221
|
+
...(notification.bodyTemplate ? { bodyTemplate: notification.bodyTemplate } : {}),
|
|
222
|
+
...(notification.contextName ? { contextName: notification.contextName } : {}),
|
|
223
|
+
...(notification.contextParameters
|
|
224
|
+
? {
|
|
225
|
+
contextParameters: notification.contextParameters ? notification.contextParameters : {},
|
|
226
|
+
}
|
|
227
|
+
: {}),
|
|
228
|
+
...(notification.sendAfter ? { sendAfter: notification.sendAfter } : {}),
|
|
229
|
+
...(notification.subjectTemplate ? { subjectTemplate: notification.subjectTemplate } : {}),
|
|
230
|
+
} as Partial<Parameters<typeof this.prismaClient.notification.update>[0]['data']>;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async getAllPendingNotifications(): Promise<
|
|
234
|
+
Notification<AvailableContexts, NotificationIdType, UserIdType>[]
|
|
235
|
+
> {
|
|
236
|
+
const notifications = await this.prismaClient.notification.findMany({
|
|
237
|
+
where: {
|
|
238
|
+
status: NotificationStatusEnum.PENDING_SEND,
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return notifications.map(this.serializeNotification);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async getPendingNotifications(): Promise<
|
|
246
|
+
Notification<AvailableContexts, NotificationIdType, UserIdType>[]
|
|
247
|
+
> {
|
|
248
|
+
const notifications = await this.prismaClient.notification.findMany({
|
|
249
|
+
where: {
|
|
250
|
+
status: NotificationStatusEnum.PENDING_SEND,
|
|
251
|
+
sendAfter: null,
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return notifications.map(this.serializeNotification);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async getAllFutureNotifications(): Promise<
|
|
259
|
+
Notification<AvailableContexts, NotificationIdType, UserIdType>[]
|
|
260
|
+
> {
|
|
261
|
+
const notifications = await this.prismaClient.notification.findMany({
|
|
262
|
+
where: {
|
|
263
|
+
status: {
|
|
264
|
+
not: NotificationStatusEnum.PENDING_SEND,
|
|
265
|
+
},
|
|
266
|
+
sendAfter: {
|
|
267
|
+
lte: new Date(),
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return notifications.map(this.serializeNotification);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async getFutureNotifications(): Promise<
|
|
276
|
+
Notification<AvailableContexts, NotificationIdType, UserIdType>[]
|
|
277
|
+
> {
|
|
278
|
+
const notifications = await this.prismaClient.notification.findMany({
|
|
279
|
+
where: {
|
|
280
|
+
status: {
|
|
281
|
+
not: NotificationStatusEnum.PENDING_SEND,
|
|
282
|
+
},
|
|
283
|
+
sendAfter: {
|
|
284
|
+
lte: new Date(),
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
return notifications.map(this.serializeNotification);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async getAllFutureNotificationsFromUser(
|
|
293
|
+
userId: NonNullable<
|
|
294
|
+
Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
|
|
295
|
+
>['userId'],
|
|
296
|
+
): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>[]> {
|
|
297
|
+
const notifications = await this.prismaClient.notification.findMany({
|
|
298
|
+
where: {
|
|
299
|
+
userId,
|
|
300
|
+
status: {
|
|
301
|
+
not: NotificationStatusEnum.PENDING_SEND,
|
|
302
|
+
},
|
|
303
|
+
sendAfter: {
|
|
304
|
+
lte: new Date(),
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
return notifications.map(this.serializeNotification);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async getFutureNotificationsFromUser(
|
|
313
|
+
userId: NonNullable<
|
|
314
|
+
Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
|
|
315
|
+
>['userId'],
|
|
316
|
+
): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>[]> {
|
|
317
|
+
const notifications = await this.prismaClient.notification.findMany({
|
|
318
|
+
where: {
|
|
319
|
+
userId,
|
|
320
|
+
status: {
|
|
321
|
+
not: NotificationStatusEnum.PENDING_SEND,
|
|
322
|
+
},
|
|
323
|
+
sendAfter: {
|
|
324
|
+
lte: new Date(),
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
return notifications.map(this.serializeNotification);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async persistNotification(
|
|
333
|
+
notification: NotificationInput<AvailableContexts, UserIdType>,
|
|
334
|
+
): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>> {
|
|
335
|
+
return this.serializeNotification(
|
|
336
|
+
await this.prismaClient.notification.create({
|
|
337
|
+
data: this.deserializeNotification(notification),
|
|
338
|
+
}),
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async persistNotificationUpdate(
|
|
343
|
+
notificationId: NonNullable<
|
|
344
|
+
Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
|
|
345
|
+
>['id'],
|
|
346
|
+
notification: Partial<
|
|
347
|
+
Omit<Notification<AvailableContexts, NotificationIdType, UserIdType>, 'id'>
|
|
348
|
+
>,
|
|
349
|
+
): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>> {
|
|
350
|
+
return this.serializeNotification(
|
|
351
|
+
await this.prismaClient.notification.update({
|
|
352
|
+
where: {
|
|
353
|
+
id: notificationId,
|
|
354
|
+
},
|
|
355
|
+
data: this.deserializeNotificationForUpdate(notification),
|
|
356
|
+
}),
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async markPendingAsSent(
|
|
361
|
+
notificationId: NonNullable<
|
|
362
|
+
Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
|
|
363
|
+
>['id'],
|
|
364
|
+
): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>> {
|
|
365
|
+
return this.serializeNotification(
|
|
366
|
+
await this.prismaClient.notification.update({
|
|
367
|
+
where: {
|
|
368
|
+
id: notificationId,
|
|
369
|
+
},
|
|
370
|
+
data: {
|
|
371
|
+
status: NotificationStatusEnum.SENT,
|
|
372
|
+
sentAt: new Date(),
|
|
373
|
+
},
|
|
374
|
+
}),
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async markPendingAsFailed(
|
|
379
|
+
notificationId: NonNullable<
|
|
380
|
+
Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
|
|
381
|
+
>['id'],
|
|
382
|
+
): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>> {
|
|
383
|
+
return this.serializeNotification(
|
|
384
|
+
await this.prismaClient.notification.update({
|
|
385
|
+
where: {
|
|
386
|
+
id: notificationId,
|
|
387
|
+
},
|
|
388
|
+
data: {
|
|
389
|
+
status: NotificationStatusEnum.FAILED,
|
|
390
|
+
sentAt: new Date(),
|
|
391
|
+
},
|
|
392
|
+
}),
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async markSentAsRead(
|
|
397
|
+
notificationId: NonNullable<
|
|
398
|
+
Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
|
|
399
|
+
>['id'],
|
|
400
|
+
): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>> {
|
|
401
|
+
return this.serializeNotification(
|
|
402
|
+
await this.prismaClient.notification.update({
|
|
403
|
+
where: {
|
|
404
|
+
id: notificationId,
|
|
405
|
+
},
|
|
406
|
+
data: {
|
|
407
|
+
status: 'READ',
|
|
408
|
+
readAt: new Date(),
|
|
409
|
+
},
|
|
410
|
+
}),
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async cancelNotification(
|
|
415
|
+
notificationId: NonNullable<
|
|
416
|
+
Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
|
|
417
|
+
>['id'],
|
|
418
|
+
): Promise<void> {
|
|
419
|
+
await this.prismaClient.notification.update({
|
|
420
|
+
where: {
|
|
421
|
+
id: notificationId,
|
|
422
|
+
},
|
|
423
|
+
data: {
|
|
424
|
+
status: NotificationStatusEnum.CANCELLED,
|
|
425
|
+
},
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async getNotification(
|
|
430
|
+
notificationId: NonNullable<
|
|
431
|
+
Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
|
|
432
|
+
>['id'],
|
|
433
|
+
// biome-ignore lint/correctness/noUnusedVariables: <explanation>
|
|
434
|
+
forUpdate: boolean,
|
|
435
|
+
): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType> | null> {
|
|
436
|
+
const notification = await this.prismaClient.notification.findUnique({
|
|
437
|
+
where: {
|
|
438
|
+
id: notificationId,
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
if (!notification) {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return this.serializeNotification(notification);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async filterAllInAppUnreadNotifications(
|
|
449
|
+
userId: NonNullable<
|
|
450
|
+
Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
|
|
451
|
+
>['userId'],
|
|
452
|
+
): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>[]> {
|
|
453
|
+
const notifications = await this.prismaClient.notification.findMany({
|
|
454
|
+
where: {
|
|
455
|
+
userId,
|
|
456
|
+
status: 'SENT',
|
|
457
|
+
readAt: null,
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
return notifications.map(this.serializeNotification);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async filterInAppUnreadNotifications(
|
|
465
|
+
userId: NonNullable<
|
|
466
|
+
Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
|
|
467
|
+
>['userId'],
|
|
468
|
+
page: number,
|
|
469
|
+
pageSize: number,
|
|
470
|
+
): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>[]> {
|
|
471
|
+
const notifications = await this.prismaClient.notification.findMany({
|
|
472
|
+
where: {
|
|
473
|
+
userId,
|
|
474
|
+
status: 'SENT',
|
|
475
|
+
readAt: null,
|
|
476
|
+
},
|
|
477
|
+
skip: page * pageSize,
|
|
478
|
+
take: pageSize,
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
return notifications.map(this.serializeNotification);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async getUserEmailFromNotification(
|
|
485
|
+
notificationId: NonNullable<
|
|
486
|
+
Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
|
|
487
|
+
>['id'],
|
|
488
|
+
): Promise<string | undefined> {
|
|
489
|
+
const notification = await this.prismaClient.notification.findUnique({
|
|
490
|
+
where: {
|
|
491
|
+
id: notificationId,
|
|
492
|
+
},
|
|
493
|
+
include: {
|
|
494
|
+
user: true,
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
return notification?.user?.email;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async storeContextUsed(
|
|
502
|
+
notificationId: NonNullable<
|
|
503
|
+
Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
|
|
504
|
+
>['id'],
|
|
505
|
+
context: InputJsonValue,
|
|
506
|
+
): Promise<void> {
|
|
507
|
+
await this.prismaClient.notification.update({
|
|
508
|
+
where: {
|
|
509
|
+
id: notificationId,
|
|
510
|
+
},
|
|
511
|
+
data: {
|
|
512
|
+
contextUsed: context,
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
datasource db {
|
|
2
|
+
provider = "postgresql"
|
|
3
|
+
url = env("DATABASE_URL")
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
generator client {
|
|
7
|
+
provider = "prisma-client-js"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
model User {
|
|
11
|
+
id Int @id @default(autoincrement())
|
|
12
|
+
email String @unique
|
|
13
|
+
notifications Notification[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
model Notification {
|
|
17
|
+
id Int @id @default(autoincrement())
|
|
18
|
+
user User @relation(fields: [userId], references: [id])
|
|
19
|
+
userId Int
|
|
20
|
+
notificationType NotificationType
|
|
21
|
+
title String?
|
|
22
|
+
bodyTemplate String
|
|
23
|
+
contextName String
|
|
24
|
+
contextParameters Json @default("{}")
|
|
25
|
+
sendAfter DateTime?
|
|
26
|
+
subjectTemplate String?
|
|
27
|
+
status NotificationStatus @default(PENDING_SEND)
|
|
28
|
+
contextUsed Json?
|
|
29
|
+
adapterUsed String?
|
|
30
|
+
sentAt DateTime?
|
|
31
|
+
readAt DateTime?
|
|
32
|
+
createdAt DateTime @default(now())
|
|
33
|
+
updatedAt DateTime @updatedAt
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
enum NotificationType {
|
|
37
|
+
EMAIL
|
|
38
|
+
PUSH
|
|
39
|
+
SMS
|
|
40
|
+
IN_APP
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
enum NotificationStatus {
|
|
44
|
+
PENDING_SEND
|
|
45
|
+
SENT
|
|
46
|
+
FAILED
|
|
47
|
+
READ
|
|
48
|
+
CANCELLED
|
|
49
|
+
}
|