vintasend-prisma 0.2.2 → 0.3.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.
@@ -1,6 +1,7 @@
1
1
  import type { BaseNotificationBackend } from 'vintasend/dist/services/notification-backends/base-notification-backend';
2
2
  import type { InputJsonValue, JsonValue } from 'vintasend/dist/types/json-values';
3
- import type { DatabaseNotification, Notification, NotificationInput } from 'vintasend/dist/types/notification';
3
+ import type { AnyNotificationInput, DatabaseNotification, Notification, NotificationInput } from 'vintasend/dist/types/notification';
4
+ import type { DatabaseOneOffNotification, OneOffNotificationInput, AnyNotification, AnyDatabaseNotification } from 'vintasend/dist/types/notification';
4
5
  import type { NotificationStatus } from 'vintasend/dist/types/notification-status';
5
6
  import type { NotificationType } from 'vintasend/dist/types/notification-type';
6
7
  import type { BaseNotificationTypeConfig } from 'vintasend/dist/types/notification-type-config';
@@ -19,7 +20,10 @@ export declare const NotificationTypeEnum: {
19
20
  };
20
21
  export interface PrismaNotificationModel<IdType, UserId> {
21
22
  id: IdType;
22
- userId: UserId;
23
+ userId: UserId | null;
24
+ emailOrPhone: string | null;
25
+ firstName: string | null;
26
+ lastName: string | null;
23
27
  notificationType: NotificationType;
24
28
  title: string | null;
25
29
  bodyTemplate: string;
@@ -49,8 +53,11 @@ export interface NotificationPrismaClientInterface<NotificationIdType, UserIdTyp
49
53
  sendAfter?: {
50
54
  lte: Date;
51
55
  } | null;
52
- userId?: UserIdType;
56
+ userId?: UserIdType | null;
53
57
  readAt?: null;
58
+ emailOrPhone?: string | {
59
+ not: null;
60
+ };
54
61
  };
55
62
  skip?: number;
56
63
  take?: number;
@@ -93,12 +100,16 @@ type AtLeast<O extends object, K extends string> = NoExpand<O extends unknown ?
93
100
  [P in keyof O as P extends K ? K : never]-?: O[P];
94
101
  } & O) : never>;
95
102
  export interface BaseNotificationCreateInput<UserIdType> {
96
- user: {
103
+ user?: {
97
104
  connect?: AtLeast<{
98
105
  id?: UserIdType;
99
106
  email?: string;
100
107
  }, 'id' | 'email'>;
101
108
  };
109
+ userId?: UserIdType | null;
110
+ emailOrPhone?: string | null;
111
+ firstName?: string | null;
112
+ lastName?: string | null;
102
113
  notificationType: NotificationType;
103
114
  title?: string | null;
104
115
  bodyTemplate: string;
@@ -114,12 +125,15 @@ export interface BaseNotificationCreateInput<UserIdType> {
114
125
  readAt?: Date | null;
115
126
  }
116
127
  export interface BaseNotificationUpdateInput<UserIdType> {
117
- user: {
128
+ user?: {
118
129
  connect?: AtLeast<{
119
130
  id?: UserIdType;
120
131
  email?: string;
121
132
  }, 'id' | 'email'>;
122
133
  };
134
+ emailOrPhone?: string | null;
135
+ firstName?: string | null;
136
+ lastName?: string | null;
123
137
  notificationType?: NotificationType;
124
138
  title?: string | null;
125
139
  bodyTemplate?: string;
@@ -137,29 +151,72 @@ export interface BaseNotificationUpdateInput<UserIdType> {
137
151
  export declare class PrismaNotificationBackend<Client extends NotificationPrismaClientInterface<Config['NotificationIdType'], Config['UserIdType']>, Config extends BaseNotificationTypeConfig> implements BaseNotificationBackend<Config> {
138
152
  private prismaClient;
139
153
  constructor(prismaClient: Client);
140
- serializeNotification(notification: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>): DatabaseNotification<Config>;
141
- deserializeNotification(notification: NotificationInput<Config>): BaseNotificationCreateInput<Config['UserIdType']>;
154
+ /**
155
+ * Build a where clause for status-based updates
156
+ */
157
+ private buildStatusWhere;
158
+ /**
159
+ * Serialize a Prisma notification model to either DatabaseNotification or DatabaseOneOffNotification
160
+ * based on whether it has a userId or not (internal implementation)
161
+ */
162
+ private serializeAnyNotification;
163
+ /**
164
+ * Serialize a Prisma notification model to DatabaseNotification
165
+ */
166
+ private serializeRegularNotification;
167
+ /**
168
+ * Serialize a Prisma notification model to DatabaseOneOffNotification
169
+ */
170
+ private serializeOneOffNotification;
171
+ /**
172
+ * Public accessor for serialization - primarily for testing
173
+ * @internal
174
+ */
175
+ serializeNotification(notification: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>): AnyDatabaseNotification<Config>;
176
+ /**
177
+ * Core internal builder for creating notification data
178
+ * Validates that notification has either userId or emailOrPhone (but not neither)
179
+ */
180
+ private buildCreateData;
181
+ /**
182
+ * Deserialize a regular notification input for creation
183
+ */
184
+ private deserializeRegularNotification;
185
+ /**
186
+ * Build one-off notification data for creation
187
+ */
188
+ private buildOneOffNotificationData;
189
+ /**
190
+ * Core internal builder for update data (supports both regular and one-off)
191
+ */
192
+ private buildUpdateData;
193
+ deserializeNotification(notification: AnyNotificationInput<Config> | Omit<AnyNotificationInput<Config>, 'id'>): BaseNotificationCreateInput<Config['UserIdType']>;
142
194
  deserializeNotificationForUpdate(notification: Partial<Notification<Config>>): Partial<Parameters<typeof this.prismaClient.notification.update>[0]['data']>;
143
- getAllPendingNotifications(): Promise<DatabaseNotification<Config>[]>;
144
- getPendingNotifications(): Promise<DatabaseNotification<Config>[]>;
145
- getAllFutureNotifications(): Promise<DatabaseNotification<Config>[]>;
146
- getFutureNotifications(): Promise<DatabaseNotification<Config>[]>;
195
+ getAllPendingNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
196
+ getPendingNotifications(page?: number, pageSize?: number): Promise<AnyDatabaseNotification<Config>[]>;
197
+ getAllFutureNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
198
+ getFutureNotifications(page?: number, pageSize?: number): Promise<AnyDatabaseNotification<Config>[]>;
147
199
  getAllFutureNotificationsFromUser(userId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['userId']): Promise<DatabaseNotification<Config>[]>;
148
200
  getFutureNotificationsFromUser(userId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['userId'], page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
149
- getAllNotifications(): Promise<DatabaseNotification<Config>[]>;
150
- getNotifications(page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
201
+ getAllNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
202
+ getNotifications(page: number, pageSize: number): Promise<AnyDatabaseNotification<Config>[]>;
151
203
  persistNotification(notification: NotificationInput<Config>): Promise<DatabaseNotification<Config>>;
152
204
  persistNotificationUpdate(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], notification: Partial<Omit<DatabaseNotification<Config>, 'id'>>): Promise<DatabaseNotification<Config>>;
153
- markAsSent(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], checkIsPending?: boolean): Promise<DatabaseNotification<Config>>;
154
- markAsFailed(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], checkIsPending?: boolean): Promise<DatabaseNotification<Config>>;
205
+ persistOneOffNotification(notification: OneOffNotificationInput<Config>): Promise<DatabaseOneOffNotification<Config>>;
206
+ persistOneOffNotificationUpdate(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], notification: Partial<Omit<DatabaseOneOffNotification<Config>, 'id'>>): Promise<DatabaseOneOffNotification<Config>>;
207
+ getOneOffNotification(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], _forUpdate: boolean): Promise<DatabaseOneOffNotification<Config> | null>;
208
+ getAllOneOffNotifications(): Promise<DatabaseOneOffNotification<Config>[]>;
209
+ getOneOffNotifications(page: number, pageSize: number): Promise<DatabaseOneOffNotification<Config>[]>;
210
+ markAsSent(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], checkIsPending?: boolean): Promise<AnyDatabaseNotification<Config>>;
211
+ markAsFailed(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], checkIsPending?: boolean): Promise<AnyDatabaseNotification<Config>>;
155
212
  markAsRead(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], checkIsSent?: boolean): Promise<DatabaseNotification<Config>>;
156
213
  cancelNotification(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id']): Promise<void>;
157
- getNotification(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], _forUpdate: boolean): Promise<DatabaseNotification<Config> | null>;
214
+ getNotification(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], _forUpdate: boolean): Promise<AnyDatabaseNotification<Config> | null>;
158
215
  filterAllInAppUnreadNotifications(userId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['userId']): Promise<DatabaseNotification<Config>[]>;
159
216
  filterInAppUnreadNotifications(userId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['userId'], page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
160
217
  getUserEmailFromNotification(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id']): Promise<string | undefined>;
161
218
  storeContextUsed(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], context: InputJsonValue): Promise<void>;
162
- bulkPersistNotifications(notifications: Omit<Notification<Config>, 'id'>[]): Promise<Config['NotificationIdType'][]>;
219
+ bulkPersistNotifications(notifications: Omit<AnyNotification<Config>, 'id'>[]): Promise<Config['NotificationIdType'][]>;
163
220
  }
164
221
  export declare class PrismaNotificationBackendFactory<Config extends BaseNotificationTypeConfig> {
165
222
  create<Client extends NotificationPrismaClientInterface<Config['NotificationIdType'], Config['UserIdType']>>(prismaClient: Client): PrismaNotificationBackend<Client, Config>;
@@ -24,10 +24,25 @@ class PrismaNotificationBackend {
24
24
  constructor(prismaClient) {
25
25
  this.prismaClient = prismaClient;
26
26
  }
27
- serializeNotification(notification) {
28
- return {
27
+ /**
28
+ * Build a where clause for status-based updates
29
+ */
30
+ buildStatusWhere(id, opts = {}) {
31
+ const where = {
32
+ id: id,
33
+ };
34
+ if (opts.checkStatus) {
35
+ where.status = opts.checkStatus;
36
+ }
37
+ return where;
38
+ }
39
+ /**
40
+ * Serialize a Prisma notification model to either DatabaseNotification or DatabaseOneOffNotification
41
+ * based on whether it has a userId or not (internal implementation)
42
+ */
43
+ serializeAnyNotification(notification) {
44
+ const baseData = {
29
45
  id: notification.id,
30
- userId: notification.userId,
31
46
  notificationType: notification.notificationType,
32
47
  title: notification.title,
33
48
  bodyTemplate: notification.bodyTemplate,
@@ -48,14 +63,60 @@ class PrismaNotificationBackend {
48
63
  createdAt: notification.createdAt,
49
64
  updatedAt: notification.updatedAt,
50
65
  };
51
- }
52
- deserializeNotification(notification) {
66
+ // Check if this is a one-off notification (has emailOrPhone but no userId)
67
+ // Use explicit null checks to avoid misclassification with empty strings or other falsy values
68
+ if (notification.userId == null && notification.emailOrPhone != null) {
69
+ return {
70
+ ...baseData,
71
+ emailOrPhone: notification.emailOrPhone,
72
+ firstName: notification.firstName || '',
73
+ lastName: notification.lastName || '',
74
+ };
75
+ }
76
+ // Regular notification with userId
77
+ if (notification.userId == null) {
78
+ throw new Error('Invalid notification: missing both userId and emailOrPhone');
79
+ }
53
80
  return {
54
- user: {
55
- connect: {
56
- id: notification.userId,
57
- },
58
- },
81
+ ...baseData,
82
+ userId: notification.userId,
83
+ };
84
+ }
85
+ /**
86
+ * Serialize a Prisma notification model to DatabaseNotification
87
+ */
88
+ serializeRegularNotification(notification) {
89
+ return this.serializeAnyNotification(notification);
90
+ }
91
+ /**
92
+ * Serialize a Prisma notification model to DatabaseOneOffNotification
93
+ */
94
+ serializeOneOffNotification(notification) {
95
+ return this.serializeAnyNotification(notification);
96
+ }
97
+ /**
98
+ * Public accessor for serialization - primarily for testing
99
+ * @internal
100
+ */
101
+ serializeNotification(notification) {
102
+ return this.serializeAnyNotification(notification);
103
+ }
104
+ /**
105
+ * Core internal builder for creating notification data
106
+ * Validates that notification has either userId or emailOrPhone (but not neither)
107
+ */
108
+ buildCreateData(notification) {
109
+ var _a, _b;
110
+ const hasUserId = 'userId' in notification && notification.userId != null;
111
+ const hasEmailOrPhone = 'emailOrPhone' in notification && notification.emailOrPhone != null;
112
+ // Validate: must have either userId or emailOrPhone
113
+ if (!hasUserId && !hasEmailOrPhone) {
114
+ throw new Error('Invalid notification: missing both userId and emailOrPhone');
115
+ }
116
+ // Determine if this is a one-off notification
117
+ // When both are provided, userId takes precedence (regular notification)
118
+ const isOneOff = !hasUserId && hasEmailOrPhone;
119
+ const base = {
59
120
  notificationType: notification.notificationType,
60
121
  title: notification.title,
61
122
  bodyTemplate: notification.bodyTemplate,
@@ -65,6 +126,122 @@ class PrismaNotificationBackend {
65
126
  subjectTemplate: notification.subjectTemplate,
66
127
  extraParams: notification.extraParams,
67
128
  };
129
+ if (isOneOff) {
130
+ return {
131
+ ...base,
132
+ userId: null,
133
+ emailOrPhone: 'emailOrPhone' in notification ? notification.emailOrPhone : '',
134
+ firstName: (_a = ('firstName' in notification ? notification.firstName : null)) !== null && _a !== void 0 ? _a : null,
135
+ lastName: (_b = ('lastName' in notification ? notification.lastName : null)) !== null && _b !== void 0 ? _b : null,
136
+ status: exports.NotificationStatusEnum.PENDING_SEND,
137
+ };
138
+ }
139
+ // At this point we know hasUserId is true, so userId exists and is not null
140
+ const userId = ('userId' in notification ? notification.userId : null);
141
+ return {
142
+ ...base,
143
+ user: {
144
+ connect: { id: userId },
145
+ },
146
+ };
147
+ }
148
+ /**
149
+ * Deserialize a regular notification input for creation
150
+ */
151
+ deserializeRegularNotification(notification) {
152
+ return this.buildCreateData(notification);
153
+ }
154
+ /**
155
+ * Build one-off notification data for creation
156
+ */
157
+ buildOneOffNotificationData(notification) {
158
+ return this.buildCreateData(notification);
159
+ }
160
+ /**
161
+ * Core internal builder for update data (supports both regular and one-off)
162
+ */
163
+ buildUpdateData(notification) {
164
+ const data = {};
165
+ // Determine if this is transitioning between regular and one-off notification types
166
+ const hasUserId = 'userId' in notification && notification.userId !== undefined;
167
+ const hasOneOffFields = 'emailOrPhone' in notification && notification.emailOrPhone !== undefined;
168
+ // Handle user / one-off fields with mutual exclusion
169
+ if (hasUserId) {
170
+ // Converting to regular notification: set user and clear one-off fields
171
+ data.user = { connect: { id: notification.userId } };
172
+ // Clear one-off specific fields when transitioning to regular notification
173
+ data.emailOrPhone = null;
174
+ data.firstName = null;
175
+ data.lastName = null;
176
+ }
177
+ else if (hasOneOffFields) {
178
+ // Converting to one-off notification: set one-off fields
179
+ // Note: We cannot explicitly clear the user relationship via update,
180
+ // but setting emailOrPhone indicates this is now a one-off notification
181
+ data.emailOrPhone = notification.emailOrPhone;
182
+ if ('firstName' in notification && notification.firstName !== undefined) {
183
+ data.firstName = notification.firstName;
184
+ }
185
+ if ('lastName' in notification && notification.lastName !== undefined) {
186
+ data.lastName = notification.lastName;
187
+ }
188
+ }
189
+ else {
190
+ // No type transition, just update individual fields if provided
191
+ if ('emailOrPhone' in notification && notification.emailOrPhone !== undefined) {
192
+ data.emailOrPhone = notification.emailOrPhone;
193
+ }
194
+ if ('firstName' in notification && notification.firstName !== undefined) {
195
+ data.firstName = notification.firstName;
196
+ }
197
+ if ('lastName' in notification && notification.lastName !== undefined) {
198
+ data.lastName = notification.lastName;
199
+ }
200
+ }
201
+ // Handle common fields
202
+ if (notification.notificationType !== undefined) {
203
+ data.notificationType = notification.notificationType;
204
+ }
205
+ if (notification.title !== undefined) {
206
+ data.title = notification.title;
207
+ }
208
+ if (notification.bodyTemplate !== undefined) {
209
+ data.bodyTemplate = notification.bodyTemplate;
210
+ }
211
+ if (notification.contextName !== undefined) {
212
+ data.contextName = notification.contextName;
213
+ }
214
+ if (notification.contextParameters !== undefined) {
215
+ data.contextParameters = notification.contextParameters;
216
+ }
217
+ if (notification.sendAfter !== undefined) {
218
+ data.sendAfter = notification.sendAfter;
219
+ }
220
+ if (notification.subjectTemplate !== undefined) {
221
+ data.subjectTemplate = notification.subjectTemplate;
222
+ }
223
+ if (notification.status !== undefined) {
224
+ data.status = notification.status;
225
+ }
226
+ if (notification.contextUsed !== undefined) {
227
+ data.contextUsed = notification.contextUsed;
228
+ }
229
+ if (notification.extraParams !== undefined) {
230
+ data.extraParams = notification.extraParams;
231
+ }
232
+ if (notification.adapterUsed !== undefined) {
233
+ data.adapterUsed = notification.adapterUsed;
234
+ }
235
+ if (notification.sentAt !== undefined) {
236
+ data.sentAt = notification.sentAt;
237
+ }
238
+ if (notification.readAt !== undefined) {
239
+ data.readAt = notification.readAt;
240
+ }
241
+ return data;
242
+ }
243
+ deserializeNotification(notification) {
244
+ return this.buildCreateData(notification);
68
245
  }
69
246
  deserializeNotificationForUpdate(notification) {
70
247
  return {
@@ -88,46 +265,40 @@ class PrismaNotificationBackend {
88
265
  }
89
266
  async getAllPendingNotifications() {
90
267
  const notifications = await this.prismaClient.notification.findMany({
91
- where: {
92
- status: exports.NotificationStatusEnum.PENDING_SEND,
93
- },
268
+ where: { status: exports.NotificationStatusEnum.PENDING_SEND },
94
269
  });
95
- return notifications.map(this.serializeNotification);
270
+ return notifications.map((n) => this.serializeAnyNotification(n));
96
271
  }
97
- async getPendingNotifications() {
272
+ async getPendingNotifications(page = 0, pageSize = 100) {
98
273
  const notifications = await this.prismaClient.notification.findMany({
99
274
  where: {
100
275
  status: exports.NotificationStatusEnum.PENDING_SEND,
101
276
  sendAfter: null,
102
277
  },
278
+ skip: page * pageSize,
279
+ take: pageSize,
103
280
  });
104
- return notifications.map(this.serializeNotification);
281
+ return notifications.map((n) => this.serializeAnyNotification(n));
105
282
  }
106
283
  async getAllFutureNotifications() {
107
284
  const notifications = await this.prismaClient.notification.findMany({
108
285
  where: {
109
- status: {
110
- not: exports.NotificationStatusEnum.PENDING_SEND,
111
- },
112
- sendAfter: {
113
- lte: new Date(),
114
- },
286
+ status: { not: exports.NotificationStatusEnum.PENDING_SEND },
287
+ sendAfter: { lte: new Date() },
115
288
  },
116
289
  });
117
- return notifications.map(this.serializeNotification);
290
+ return notifications.map((n) => this.serializeAnyNotification(n));
118
291
  }
119
- async getFutureNotifications() {
292
+ async getFutureNotifications(page = 0, pageSize = 100) {
120
293
  const notifications = await this.prismaClient.notification.findMany({
121
294
  where: {
122
- status: {
123
- not: exports.NotificationStatusEnum.PENDING_SEND,
124
- },
125
- sendAfter: {
126
- lte: new Date(),
127
- },
295
+ status: { not: exports.NotificationStatusEnum.PENDING_SEND },
296
+ sendAfter: { lte: new Date() },
128
297
  },
298
+ skip: page * pageSize,
299
+ take: pageSize,
129
300
  });
130
- return notifications.map(this.serializeNotification);
301
+ return notifications.map((n) => this.serializeAnyNotification(n));
131
302
  }
132
303
  async getAllFutureNotificationsFromUser(userId) {
133
304
  const notifications = await this.prismaClient.notification.findMany({
@@ -141,7 +312,7 @@ class PrismaNotificationBackend {
141
312
  },
142
313
  },
143
314
  });
144
- return notifications.map(this.serializeNotification);
315
+ return notifications.map((n) => this.serializeRegularNotification(n));
145
316
  }
146
317
  async getFutureNotificationsFromUser(userId, page, pageSize) {
147
318
  const notifications = await this.prismaClient.notification.findMany({
@@ -157,67 +328,124 @@ class PrismaNotificationBackend {
157
328
  skip: page * pageSize,
158
329
  take: pageSize,
159
330
  });
160
- return notifications.map(this.serializeNotification);
331
+ return notifications.map((n) => this.serializeRegularNotification(n));
161
332
  }
162
333
  async getAllNotifications() {
163
334
  const notifications = await this.prismaClient.notification.findMany({});
164
- return notifications.map(this.serializeNotification);
335
+ return notifications.map((n) => this.serializeAnyNotification(n));
165
336
  }
166
337
  async getNotifications(page, pageSize) {
167
338
  const notifications = await this.prismaClient.notification.findMany({
168
339
  skip: page * pageSize,
169
340
  take: pageSize,
170
341
  });
171
- return notifications.map(this.serializeNotification);
342
+ return notifications.map((n) => this.serializeAnyNotification(n));
172
343
  }
173
344
  async persistNotification(notification) {
174
- return this.serializeNotification(await this.prismaClient.notification.create({
175
- data: this.deserializeNotification(notification),
176
- }));
345
+ const created = await this.prismaClient.notification.create({
346
+ data: this.deserializeRegularNotification(notification),
347
+ });
348
+ return this.serializeRegularNotification(created);
177
349
  }
178
350
  async persistNotificationUpdate(notificationId, notification) {
179
- return this.serializeNotification(await this.prismaClient.notification.update({
351
+ const updated = await this.prismaClient.notification.update({
180
352
  where: {
181
353
  id: notificationId,
182
354
  },
183
- data: this.deserializeNotificationForUpdate(notification),
184
- }));
355
+ data: this.buildUpdateData(notification),
356
+ });
357
+ return this.serializeRegularNotification(updated);
185
358
  }
186
- async markAsSent(notificationId, checkIsPending = true) {
187
- return this.serializeNotification(await this.prismaClient.notification.update({
359
+ /* One-off notification persistence and query methods */
360
+ async persistOneOffNotification(notification) {
361
+ const created = await this.prismaClient.notification.create({
362
+ data: this.buildOneOffNotificationData(notification),
363
+ });
364
+ return this.serializeOneOffNotification(created);
365
+ }
366
+ async persistOneOffNotificationUpdate(notificationId, notification) {
367
+ const updated = await this.prismaClient.notification.update({
368
+ where: { id: notificationId },
369
+ data: this.buildUpdateData(notification),
370
+ });
371
+ return this.serializeOneOffNotification(updated);
372
+ }
373
+ async getOneOffNotification(notificationId, _forUpdate) {
374
+ const notification = await this.prismaClient.notification.findUnique({
188
375
  where: {
189
376
  id: notificationId,
190
- ...(checkIsPending ? { status: exports.NotificationStatusEnum.PENDING_SEND } : {}),
191
377
  },
378
+ });
379
+ if (!notification || notification.emailOrPhone == null || notification.userId !== null) {
380
+ return null;
381
+ }
382
+ return this.serializeOneOffNotification(notification);
383
+ }
384
+ async getAllOneOffNotifications() {
385
+ const notifications = await this.prismaClient.notification.findMany({
386
+ where: {
387
+ userId: null,
388
+ emailOrPhone: { not: null },
389
+ },
390
+ });
391
+ return notifications.map((n) => this.serializeOneOffNotification(n));
392
+ }
393
+ async getOneOffNotifications(page, pageSize) {
394
+ const notifications = await this.prismaClient.notification.findMany({
395
+ where: {
396
+ userId: null,
397
+ emailOrPhone: { not: null },
398
+ },
399
+ skip: page * pageSize,
400
+ take: pageSize,
401
+ });
402
+ return notifications.map((n) => this.serializeOneOffNotification(n));
403
+ }
404
+ async markAsSent(notificationId, checkIsPending = true) {
405
+ const updated = await this.prismaClient.notification.update({
406
+ where: this.buildStatusWhere(notificationId, {
407
+ checkStatus: checkIsPending ? exports.NotificationStatusEnum.PENDING_SEND : undefined,
408
+ }),
192
409
  data: {
193
410
  status: exports.NotificationStatusEnum.SENT,
194
411
  sentAt: new Date(),
195
412
  },
196
- }));
413
+ });
414
+ return this.serializeAnyNotification(updated);
197
415
  }
198
416
  async markAsFailed(notificationId, checkIsPending = true) {
199
- return this.serializeNotification(await this.prismaClient.notification.update({
200
- where: {
201
- id: notificationId,
202
- ...(checkIsPending ? { status: exports.NotificationStatusEnum.PENDING_SEND } : {}),
203
- },
417
+ const updated = await this.prismaClient.notification.update({
418
+ where: this.buildStatusWhere(notificationId, {
419
+ checkStatus: checkIsPending ? exports.NotificationStatusEnum.PENDING_SEND : undefined,
420
+ }),
204
421
  data: {
205
422
  status: exports.NotificationStatusEnum.FAILED,
206
423
  sentAt: new Date(),
207
424
  },
208
- }));
425
+ });
426
+ return this.serializeAnyNotification(updated);
209
427
  }
210
428
  async markAsRead(notificationId, checkIsSent = true) {
211
- return this.serializeNotification(await this.prismaClient.notification.update({
212
- where: {
213
- id: notificationId,
214
- ...(checkIsSent ? { status: exports.NotificationStatusEnum.SENT } : {}),
215
- },
429
+ // First fetch to validate it's a regular notification
430
+ const notification = await this.prismaClient.notification.findUnique({
431
+ where: { id: notificationId },
432
+ });
433
+ if (!notification) {
434
+ throw new Error('Notification not found');
435
+ }
436
+ if (notification.userId == null) {
437
+ throw new Error('Cannot mark one-off notification as read');
438
+ }
439
+ const updated = await this.prismaClient.notification.update({
440
+ where: this.buildStatusWhere(notificationId, {
441
+ checkStatus: checkIsSent ? exports.NotificationStatusEnum.SENT : undefined,
442
+ }),
216
443
  data: {
217
- status: 'READ',
444
+ status: exports.NotificationStatusEnum.READ,
218
445
  readAt: new Date(),
219
446
  },
220
- }));
447
+ });
448
+ return this.serializeRegularNotification(updated);
221
449
  }
222
450
  async cancelNotification(notificationId) {
223
451
  await this.prismaClient.notification.update({
@@ -231,14 +459,11 @@ class PrismaNotificationBackend {
231
459
  }
232
460
  async getNotification(notificationId, _forUpdate) {
233
461
  const notification = await this.prismaClient.notification.findUnique({
234
- where: {
235
- id: notificationId,
236
- },
462
+ where: { id: notificationId },
237
463
  });
238
- if (!notification) {
464
+ if (!notification)
239
465
  return null;
240
- }
241
- return this.serializeNotification(notification);
466
+ return this.serializeAnyNotification(notification);
242
467
  }
243
468
  async filterAllInAppUnreadNotifications(userId) {
244
469
  const notifications = await this.prismaClient.notification.findMany({
@@ -248,7 +473,7 @@ class PrismaNotificationBackend {
248
473
  readAt: null,
249
474
  },
250
475
  });
251
- return notifications.map(this.serializeNotification);
476
+ return notifications.map((n) => this.serializeRegularNotification(n));
252
477
  }
253
478
  async filterInAppUnreadNotifications(userId, page, pageSize) {
254
479
  const notifications = await this.prismaClient.notification.findMany({
@@ -260,7 +485,7 @@ class PrismaNotificationBackend {
260
485
  skip: page * pageSize,
261
486
  take: pageSize,
262
487
  });
263
- return notifications.map(this.serializeNotification);
488
+ return notifications.map((n) => this.serializeRegularNotification(n));
264
489
  }
265
490
  async getUserEmailFromNotification(notificationId) {
266
491
  var _a;
@@ -276,12 +501,8 @@ class PrismaNotificationBackend {
276
501
  }
277
502
  async storeContextUsed(notificationId, context) {
278
503
  await this.prismaClient.notification.update({
279
- where: {
280
- id: notificationId,
281
- },
282
- data: {
283
- contextUsed: context,
284
- },
504
+ where: { id: notificationId },
505
+ data: { contextUsed: context }
285
506
  });
286
507
  }
287
508
  async bulkPersistNotifications(notifications) {