@endora-commerce/mod-admin-notifications 0.0.0-stage → 0.100.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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +49 -2
  3. package/dist/backend/entities/admin-notification-read.entity.d.ts +15 -0
  4. package/dist/backend/entities/admin-notification-read.entity.d.ts.map +1 -0
  5. package/dist/backend/entities/admin-notification-read.entity.js +42 -0
  6. package/dist/backend/entities/admin-notification-read.entity.js.map +1 -0
  7. package/dist/backend/entities/admin-notification.entity.d.ts +31 -0
  8. package/dist/backend/entities/admin-notification.entity.d.ts.map +1 -0
  9. package/dist/backend/entities/admin-notification.entity.js +95 -0
  10. package/dist/backend/entities/admin-notification.entity.js.map +1 -0
  11. package/dist/backend/index.d.ts +25 -0
  12. package/dist/backend/index.d.ts.map +1 -0
  13. package/dist/backend/index.js +70 -0
  14. package/dist/backend/index.js.map +1 -0
  15. package/dist/backend/plugin.d.ts +26 -0
  16. package/dist/backend/plugin.d.ts.map +1 -0
  17. package/dist/backend/plugin.js +17 -0
  18. package/dist/backend/plugin.js.map +1 -0
  19. package/dist/backend/routes.admin.d.ts +16 -0
  20. package/dist/backend/routes.admin.d.ts.map +1 -0
  21. package/dist/backend/routes.admin.js +78 -0
  22. package/dist/backend/routes.admin.js.map +1 -0
  23. package/dist/backend/services/admin-notification-port.d.ts +17 -0
  24. package/dist/backend/services/admin-notification-port.d.ts.map +1 -0
  25. package/dist/backend/services/admin-notification-port.js +33 -0
  26. package/dist/backend/services/admin-notification-port.js.map +1 -0
  27. package/dist/backend/services/admin-notification-service.d.ts +78 -0
  28. package/dist/backend/services/admin-notification-service.d.ts.map +1 -0
  29. package/dist/backend/services/admin-notification-service.js +284 -0
  30. package/dist/backend/services/admin-notification-service.js.map +1 -0
  31. package/dist/manifest.d.ts +171 -0
  32. package/dist/manifest.d.ts.map +1 -0
  33. package/dist/manifest.js +46 -0
  34. package/dist/manifest.js.map +1 -0
  35. package/dist/migrations/20260611T140350_admin_notifications_init.d.ts +19 -0
  36. package/dist/migrations/20260611T140350_admin_notifications_init.d.ts.map +1 -0
  37. package/dist/migrations/20260611T140350_admin_notifications_init.js +82 -0
  38. package/dist/migrations/20260611T140350_admin_notifications_init.js.map +1 -0
  39. package/dist/migrations/index.d.ts +27 -0
  40. package/dist/migrations/index.d.ts.map +1 -0
  41. package/dist/migrations/index.js +29 -0
  42. package/dist/migrations/index.js.map +1 -0
  43. package/package.json +63 -3
@@ -0,0 +1,78 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { AdminNotification, type AdminNotificationAudience } from '../entities/admin-notification.entity.js';
3
+ export interface RecordNotificationInput {
4
+ audience: AdminNotificationAudience;
5
+ targetAdminUserId?: string | null;
6
+ kind: string;
7
+ subjectType?: string | null;
8
+ subjectId?: string | null;
9
+ title: string;
10
+ body?: string | null;
11
+ linkPath?: string | null;
12
+ }
13
+ export interface ListNotificationsInput {
14
+ adminUserId: string;
15
+ unread?: boolean;
16
+ limit?: number;
17
+ /** Opaque cursor (currently the `created_at` ISO + id of the last row of the previous page). */
18
+ cursor?: string | null;
19
+ }
20
+ export interface ListedNotification {
21
+ id: string;
22
+ audience: AdminNotificationAudience;
23
+ kind: string;
24
+ subjectType: string | null;
25
+ subjectId: string | null;
26
+ title: string;
27
+ body: string | null;
28
+ linkPath: string | null;
29
+ createdAt: Date;
30
+ isRead: boolean;
31
+ }
32
+ export interface ListNotificationsResult {
33
+ items: ListedNotification[];
34
+ nextCursor: string | null;
35
+ unreadCount: number;
36
+ }
37
+ /**
38
+ * AdminNotificationService — owns the read + write paths for the admin
39
+ * bell feed.
40
+ *
41
+ * `record()` is invoked by domain modules (e.g. organizations registration
42
+ * notifier) to push entries. `listForAdmin()` is the bell's feed query and
43
+ * carries the per-admin `isRead` resolution for `audience='all_admins'`
44
+ * rows. Read marking is split between updating `read_at` (for
45
+ * `audience='admin_user'`) and inserting into `admin_notification_reads`
46
+ * (for `audience='all_admins'`).
47
+ *
48
+ * Bounded growth: `pruneStale()` archives entries older than
49
+ * `maxUnreadPerAudience` per `audience`. Default cap = 50. The pruner is
50
+ * intended to run on a schedule or on each new write; the first iteration
51
+ * runs it inline on every `record()` call which keeps the table small
52
+ * without a separate job.
53
+ */
54
+ export declare class AdminNotificationService {
55
+ private readonly emFactory;
56
+ private readonly maxUnreadPerAudience;
57
+ constructor(emFactory: () => EntityManager, options?: {
58
+ maxUnreadPerAudience?: number;
59
+ });
60
+ record(input: RecordNotificationInput): Promise<AdminNotification>;
61
+ listForAdmin(input: ListNotificationsInput): Promise<ListNotificationsResult>;
62
+ markRead(notificationId: string, adminUserId: string): Promise<void>;
63
+ markAllRead(adminUserId: string): Promise<number>;
64
+ countUnreadForAdmin(adminUserId: string): Promise<number>;
65
+ /**
66
+ * Soft-archive entries beyond `maxUnreadPerAudience` for the given audience.
67
+ * Keeps the most-recent N alive; older ones get `archived_at = now()`.
68
+ */
69
+ private pruneStale;
70
+ private isVisibleToAdmin;
71
+ private serializeCursor;
72
+ private parseCursor;
73
+ }
74
+ export declare class NotificationNotFoundError extends Error {
75
+ readonly notificationId: string;
76
+ constructor(notificationId: string);
77
+ }
78
+ //# sourceMappingURL=admin-notification-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-notification-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/admin-notification-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,KAAK,yBAAyB,EAAE,MAAM,0CAA0C,CAAC;AAG7G,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,yBAAyB,CAAC;IACpC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gGAAgG;IAChG,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,yBAAyB,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,IAAI,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,wBAAwB;IAIjC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAH5B,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;gBAG3B,SAAS,EAAE,MAAM,aAAa,EAC/C,OAAO,CAAC,EAAE;QAAE,oBAAoB,CAAC,EAAE,MAAM,CAAA;KAAE;IAKvC,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA6BlE,YAAY,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAgG7E,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BpE,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA0CjD,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwB/D;;;OAGG;YACW,UAAU;IA8CxB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,WAAW;CAUpB;AAiBD,qBAAa,yBAA0B,SAAQ,KAAK;aACtB,cAAc,EAAE,MAAM;gBAAtB,cAAc,EAAE,MAAM;CAInD"}
@@ -0,0 +1,284 @@
1
+ import { AdminNotification } from '../entities/admin-notification.entity.js';
2
+ import { AdminNotificationRead } from '../entities/admin-notification-read.entity.js';
3
+ /**
4
+ * AdminNotificationService — owns the read + write paths for the admin
5
+ * bell feed.
6
+ *
7
+ * `record()` is invoked by domain modules (e.g. organizations registration
8
+ * notifier) to push entries. `listForAdmin()` is the bell's feed query and
9
+ * carries the per-admin `isRead` resolution for `audience='all_admins'`
10
+ * rows. Read marking is split between updating `read_at` (for
11
+ * `audience='admin_user'`) and inserting into `admin_notification_reads`
12
+ * (for `audience='all_admins'`).
13
+ *
14
+ * Bounded growth: `pruneStale()` archives entries older than
15
+ * `maxUnreadPerAudience` per `audience`. Default cap = 50. The pruner is
16
+ * intended to run on a schedule or on each new write; the first iteration
17
+ * runs it inline on every `record()` call which keeps the table small
18
+ * without a separate job.
19
+ */
20
+ export class AdminNotificationService {
21
+ emFactory;
22
+ maxUnreadPerAudience;
23
+ constructor(emFactory, options) {
24
+ this.emFactory = emFactory;
25
+ this.maxUnreadPerAudience = options?.maxUnreadPerAudience ?? 50;
26
+ }
27
+ async record(input) {
28
+ // command-coverage-ignore: admin notification bookkeeping — system-generated
29
+ // alert record / read-state, not an audited domain-state mutation.
30
+ if (input.audience === 'admin_user' && !input.targetAdminUserId) {
31
+ throw new Error('AdminNotificationService.record: targetAdminUserId is required when audience="admin_user".');
32
+ }
33
+ if (input.audience === 'all_admins' && input.targetAdminUserId) {
34
+ throw new Error('AdminNotificationService.record: targetAdminUserId must be null when audience="all_admins".');
35
+ }
36
+ const em = this.emFactory();
37
+ const entry = em.create(AdminNotification, {
38
+ audience: input.audience,
39
+ targetAdminUserId: input.targetAdminUserId ?? null,
40
+ kind: input.kind,
41
+ subjectType: input.subjectType ?? null,
42
+ subjectId: input.subjectId ?? null,
43
+ title: input.title,
44
+ body: input.body ?? null,
45
+ linkPath: input.linkPath ?? null,
46
+ });
47
+ await em.persistAndFlush(entry);
48
+ await this.pruneStale(input.audience, input.targetAdminUserId ?? null);
49
+ return entry;
50
+ }
51
+ async listForAdmin(input) {
52
+ const limit = Math.min(Math.max(input.limit ?? 25, 1), 100);
53
+ const em = this.emFactory();
54
+ const cursor = input.cursor ? this.parseCursor(input.cursor) : null;
55
+ const whereParts = [];
56
+ // knex `raw` uses positional `?` placeholders (converted to $1..$n for pg
57
+ // in textual order). The SELECT's isRead `?` is textually first, so its
58
+ // binding is prepended when the final params array is assembled below.
59
+ const whereParams = [];
60
+ // Visible to this admin: their personal rows OR broadcasts.
61
+ whereParts.push(`(n."audience" = 'all_admins' or (n."audience" = 'admin_user' and n."target_admin_user_id" = ?))`);
62
+ whereParams.push(input.adminUserId);
63
+ whereParts.push(`n."archived_at" is null`);
64
+ if (input.unread === true) {
65
+ whereParts.push(`(
66
+ (n."audience" = 'admin_user' and n."read_at" is null)
67
+ or (n."audience" = 'all_admins'
68
+ and not exists (
69
+ select 1 from "admin_notification_reads" r
70
+ where r."notification_id" = n."id" and r."admin_user_id" = ?
71
+ ))
72
+ )`);
73
+ whereParams.push(input.adminUserId);
74
+ }
75
+ if (cursor) {
76
+ whereParts.push(`(n."created_at", n."id") < (?, ?)`);
77
+ whereParams.push(cursor.createdAt.toISOString());
78
+ whereParams.push(cursor.id);
79
+ }
80
+ const sql = `
81
+ select
82
+ n."id",
83
+ n."audience",
84
+ n."target_admin_user_id" as "targetAdminUserId",
85
+ n."kind",
86
+ n."subject_type" as "subjectType",
87
+ n."subject_id" as "subjectId",
88
+ n."title",
89
+ n."body",
90
+ n."link_path" as "linkPath",
91
+ n."created_at" as "createdAt",
92
+ n."read_at" as "readAt",
93
+ case
94
+ when n."audience" = 'admin_user' then n."read_at" is not null
95
+ else exists (
96
+ select 1 from "admin_notification_reads" r
97
+ where r."notification_id" = n."id" and r."admin_user_id" = ?
98
+ )
99
+ end as "isRead"
100
+ from "admin_notifications" n
101
+ where ${whereParts.join(' and ')}
102
+ order by n."created_at" desc, n."id" desc
103
+ limit ${limit + 1};
104
+ `;
105
+ // The isRead CASE placeholder appears in the SELECT (textually before the
106
+ // WHERE), so its binding (adminUserId) comes first.
107
+ const params = [input.adminUserId, ...whereParams];
108
+ const knex = em.getConnection().getKnex();
109
+ const rows = (await knex.raw(sql, params)).rows;
110
+ const hasMore = rows.length > limit;
111
+ const visibleRows = hasMore ? rows.slice(0, limit) : rows;
112
+ const items = visibleRows.map((row) => ({
113
+ id: row.id,
114
+ audience: row.audience,
115
+ kind: row.kind,
116
+ subjectType: row.subjectType ?? null,
117
+ subjectId: row.subjectId ?? null,
118
+ title: row.title,
119
+ body: row.body ?? null,
120
+ linkPath: row.linkPath ?? null,
121
+ createdAt: new Date(row.createdAt),
122
+ isRead: row.isRead,
123
+ }));
124
+ const nextCursor = hasMore
125
+ ? this.serializeCursor({ createdAt: new Date(visibleRows[visibleRows.length - 1].createdAt), id: visibleRows[visibleRows.length - 1].id })
126
+ : null;
127
+ const unreadCount = await this.countUnreadForAdmin(input.adminUserId);
128
+ return { items, nextCursor, unreadCount };
129
+ }
130
+ async markRead(notificationId, adminUserId) {
131
+ // command-coverage-ignore: admin notification bookkeeping — system-generated
132
+ // alert record / read-state, not an audited domain-state mutation.
133
+ const em = this.emFactory();
134
+ const entry = await em.findOne(AdminNotification, { id: notificationId });
135
+ if (!entry) {
136
+ throw new NotificationNotFoundError(notificationId);
137
+ }
138
+ if (!this.isVisibleToAdmin(entry, adminUserId)) {
139
+ throw new NotificationNotFoundError(notificationId);
140
+ }
141
+ if (entry.audience === 'admin_user') {
142
+ if (entry.readAt)
143
+ return;
144
+ entry.readAt = new Date();
145
+ await em.persistAndFlush(entry);
146
+ return;
147
+ }
148
+ // Broadcast — record the per-admin read cursor (idempotent).
149
+ const existing = await em.findOne(AdminNotificationRead, {
150
+ notificationId,
151
+ adminUserId,
152
+ });
153
+ if (existing)
154
+ return;
155
+ const cursor = em.create(AdminNotificationRead, {
156
+ notificationId,
157
+ adminUserId,
158
+ });
159
+ await em.persistAndFlush(cursor);
160
+ }
161
+ async markAllRead(adminUserId) {
162
+ const em = this.emFactory();
163
+ const knex = em.getConnection().getKnex();
164
+ let count = 0;
165
+ // Personal rows.
166
+ const personalUpdate = (await knex.raw(`
167
+ update "admin_notifications"
168
+ set "read_at" = now()
169
+ where "audience" = 'admin_user'
170
+ and "target_admin_user_id" = ?
171
+ and "read_at" is null
172
+ and "archived_at" is null
173
+ returning "id";
174
+ `, [adminUserId]));
175
+ count += personalUpdate.rows.length;
176
+ // Broadcasts — insert per-admin read cursors for every visible unread broadcast.
177
+ const broadcastInsert = (await knex.raw(`
178
+ insert into "admin_notification_reads" ("notification_id", "admin_user_id", "read_at")
179
+ select n."id", ?, now()
180
+ from "admin_notifications" n
181
+ where n."audience" = 'all_admins'
182
+ and n."archived_at" is null
183
+ and not exists (
184
+ select 1 from "admin_notification_reads" r
185
+ where r."notification_id" = n."id" and r."admin_user_id" = ?
186
+ )
187
+ on conflict do nothing
188
+ returning "notification_id";
189
+ `, [adminUserId, adminUserId]));
190
+ count += broadcastInsert.rows.length;
191
+ return count;
192
+ }
193
+ async countUnreadForAdmin(adminUserId) {
194
+ const em = this.emFactory();
195
+ const knex = em.getConnection().getKnex();
196
+ const sql = `
197
+ select count(*)::int as "count"
198
+ from "admin_notifications" n
199
+ where n."archived_at" is null
200
+ and (
201
+ (n."audience" = 'admin_user'
202
+ and n."target_admin_user_id" = ?
203
+ and n."read_at" is null)
204
+ or (n."audience" = 'all_admins'
205
+ and not exists (
206
+ select 1 from "admin_notification_reads" r
207
+ where r."notification_id" = n."id" and r."admin_user_id" = ?
208
+ ))
209
+ );
210
+ `;
211
+ const result = (await knex.raw(sql, [adminUserId, adminUserId]));
212
+ return result.rows[0]?.count ?? 0;
213
+ }
214
+ /**
215
+ * Soft-archive entries beyond `maxUnreadPerAudience` for the given audience.
216
+ * Keeps the most-recent N alive; older ones get `archived_at = now()`.
217
+ */
218
+ async pruneStale(audience, targetAdminUserId) {
219
+ const em = this.emFactory();
220
+ const knex = em.getConnection().getKnex();
221
+ if (audience === 'all_admins') {
222
+ await knex.raw(`
223
+ with ranked as (
224
+ select "id",
225
+ row_number() over (order by "created_at" desc, "id" desc) as "rn"
226
+ from "admin_notifications"
227
+ where "audience" = 'all_admins'
228
+ and "archived_at" is null
229
+ )
230
+ update "admin_notifications" n
231
+ set "archived_at" = now()
232
+ from ranked
233
+ where n."id" = ranked."id"
234
+ and ranked."rn" > ?;
235
+ `, [this.maxUnreadPerAudience]);
236
+ }
237
+ else if (targetAdminUserId) {
238
+ await knex.raw(`
239
+ with ranked as (
240
+ select "id",
241
+ row_number() over (order by "created_at" desc, "id" desc) as "rn"
242
+ from "admin_notifications"
243
+ where "audience" = 'admin_user'
244
+ and "target_admin_user_id" = ?
245
+ and "archived_at" is null
246
+ )
247
+ update "admin_notifications" n
248
+ set "archived_at" = now()
249
+ from ranked
250
+ where n."id" = ranked."id"
251
+ and ranked."rn" > ?;
252
+ `, [targetAdminUserId, this.maxUnreadPerAudience]);
253
+ }
254
+ }
255
+ isVisibleToAdmin(entry, adminUserId) {
256
+ if (entry.audience === 'all_admins')
257
+ return true;
258
+ return entry.targetAdminUserId === adminUserId;
259
+ }
260
+ serializeCursor(input) {
261
+ return Buffer.from(`${input.createdAt.toISOString()}|${input.id}`).toString('base64url');
262
+ }
263
+ parseCursor(raw) {
264
+ try {
265
+ const decoded = Buffer.from(raw, 'base64url').toString('utf-8');
266
+ const [iso, id] = decoded.split('|');
267
+ if (!iso || !id)
268
+ return null;
269
+ return { createdAt: new Date(iso), id };
270
+ }
271
+ catch {
272
+ return null;
273
+ }
274
+ }
275
+ }
276
+ export class NotificationNotFoundError extends Error {
277
+ notificationId;
278
+ constructor(notificationId) {
279
+ super(`Admin notification ${notificationId} not found or not visible.`);
280
+ this.notificationId = notificationId;
281
+ this.name = 'NotificationNotFoundError';
282
+ }
283
+ }
284
+ //# sourceMappingURL=admin-notification-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-notification-service.js","sourceRoot":"","sources":["../../../src/backend/services/admin-notification-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAkC,MAAM,0CAA0C,CAAC;AAC7G,OAAO,EAAE,qBAAqB,EAAE,MAAM,+CAA+C,CAAC;AAwCtF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,wBAAwB;IAIhB;IAHF,oBAAoB,CAAS;IAE9C,YACmB,SAA8B,EAC/C,OAA2C;QAD1B,cAAS,GAAT,SAAS,CAAqB;QAG/C,IAAI,CAAC,oBAAoB,GAAG,OAAO,EAAE,oBAAoB,IAAI,EAAE,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAA8B;QACzC,6EAA6E;QAC7E,mEAAmE;QACnE,IAAI,KAAK,CAAC,QAAQ,KAAK,YAAY,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC;YAChE,MAAM,IAAI,KAAK,CACb,4FAA4F,CAC7F,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,CAAC,QAAQ,KAAK,YAAY,IAAI,KAAK,CAAC,iBAAiB,EAAE,CAAC;YAC/D,MAAM,IAAI,KAAK,CACb,6FAA6F,CAC9F,CAAC;QACJ,CAAC;QACD,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,EAAE,CAAC,MAAM,CAAC,iBAAiB,EAAE;YACzC,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,IAAI,IAAI;YAClD,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,IAAI;YACtC,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI;YAClC,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,IAAI;YACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,IAAI;SACjC,CAAC,CAAC;QACH,MAAM,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,iBAAiB,IAAI,IAAI,CAAC,CAAC;QACvE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAA6B;QAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC5D,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAE5B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACpE,MAAM,UAAU,GAAa,EAAE,CAAC;QAChC,0EAA0E;QAC1E,wEAAwE;QACxE,uEAAuE;QACvE,MAAM,WAAW,GAAyC,EAAE,CAAC;QAE7D,4DAA4D;QAC5D,UAAU,CAAC,IAAI,CACb,iGAAiG,CAClG,CAAC;QACF,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAEpC,UAAU,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QAE3C,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;YAC1B,UAAU,CAAC,IAAI,CACb;;;;;;;UAOE,CACH,CAAC;YACF,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACtC,CAAC;QAED,IAAI,MAAM,EAAE,CAAC;YACX,UAAU,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;YACrD,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC;YACjD,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC9B,CAAC;QAED,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;cAqBF,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;;cAExB,KAAK,GAAG,CAAC;KAClB,CAAC;QAEF,0EAA0E;QAC1E,oDAAoD;QACpD,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,WAAW,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,CAAC;QAC1C,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,IAA4B,CAAC;QAExE,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpC,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAE1D,MAAM,KAAK,GAAyB,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC5D,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,QAAQ,EAAE,GAAG,CAAC,QAAqC;YACnD,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,IAAI;YACpC,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,IAAI;YAChC,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,IAAI;YACtB,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,IAAI;YAC9B,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;YAClC,MAAM,EAAE,GAAG,CAAC,MAAM;SACnB,CAAC,CAAC,CAAC;QAEJ,MAAM,UAAU,GAAG,OAAO;YACxB,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,WAAW,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,EAAE,EAAE,CAAC;YAC5I,CAAC,CAAC,IAAI,CAAC;QAET,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAEtE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,cAAsB,EAAE,WAAmB;QACxD,6EAA6E;QAC7E,mEAAmE;QACnE,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;QAC1E,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,yBAAyB,CAAC,cAAc,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,yBAAyB,CAAC,cAAc,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,KAAK,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;YACpC,IAAI,KAAK,CAAC,MAAM;gBAAE,OAAO;YACzB,KAAK,CAAC,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YAC1B,MAAM,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;YAChC,OAAO;QACT,CAAC;QACD,6DAA6D;QAC7D,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,qBAAqB,EAAE;YACvD,cAAc;YACd,WAAW;SACZ,CAAC,CAAC;QACH,IAAI,QAAQ;YAAE,OAAO;QACrB,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,qBAAqB,EAAE;YAC9C,cAAc;YACd,WAAW;SACZ,CAAC,CAAC;QACH,MAAM,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,WAAmB;QACnC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,CAAC;QAC1C,IAAI,KAAK,GAAG,CAAC,CAAC;QAEd,iBAAiB;QACjB,MAAM,cAAc,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CACpC;;;;;;;;KAQD,EACC,CAAC,WAAW,CAAC,CACd,CAAoC,CAAC;QACtC,KAAK,IAAI,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC;QAEpC,iFAAiF;QACjF,MAAM,eAAe,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CACrC;;;;;;;;;;;;KAYD,EACC,CAAC,WAAW,EAAE,WAAW,CAAC,CAC3B,CAAiD,CAAC;QACnD,KAAK,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC;QAErC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,WAAmB;QAC3C,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG;;;;;;;;;;;;;;KAcX,CAAC;QACF,MAAM,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAE9D,CAAC;QACF,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;IACpC,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,UAAU,CACtB,QAAmC,EACnC,iBAAgC;QAEhC,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,CAAC;QAC1C,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;YAC9B,MAAM,IAAI,CAAC,GAAG,CACZ;;;;;;;;;;;;;OAaD,EACC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAC5B,CAAC;QACJ,CAAC;aAAM,IAAI,iBAAiB,EAAE,CAAC;YAC7B,MAAM,IAAI,CAAC,GAAG,CACZ;;;;;;;;;;;;;;OAcD,EACC,CAAC,iBAAiB,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAC/C,CAAC;QACJ,CAAC;IACH,CAAC;IAEO,gBAAgB,CAAC,KAAwB,EAAE,WAAmB;QACpE,IAAI,KAAK,CAAC,QAAQ,KAAK,YAAY;YAAE,OAAO,IAAI,CAAC;QACjD,OAAO,KAAK,CAAC,iBAAiB,KAAK,WAAW,CAAC;IACjD,CAAC;IAEO,eAAe,CAAC,KAAsC;QAC5D,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC3F,CAAC;IAEO,WAAW,CAAC,GAAW;QAC7B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAChE,MAAM,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACrC,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YAC7B,OAAO,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CACF;AAiBD,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IACtB;IAA5B,YAA4B,cAAsB;QAChD,KAAK,CAAC,sBAAsB,cAAc,4BAA4B,CAAC,CAAC;QAD9C,mBAAc,GAAd,cAAc,CAAQ;QAEhD,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF"}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Admin notifications — minimal "bell" surface.
3
+ *
4
+ * Introduced by feature 026 (Organizations) to deliver in-app
5
+ * notifications to admins when a new Organization registers. The
6
+ * module is intentionally generic so future features (low-stock
7
+ * alerts, RFQ awaiting reply, etc.) can write into the same feed.
8
+ *
9
+ * The schema is owned by migration 048_admin_notifications_init.ts.
10
+ */
11
+ export declare const manifest: {
12
+ id: string;
13
+ name: string;
14
+ version: string;
15
+ dependencies: string[];
16
+ description?: string | undefined;
17
+ acknowledgedDependencies?: {
18
+ moduleId: string;
19
+ port: string;
20
+ reason: string;
21
+ }[] | undefined;
22
+ nonBindingDependencies?: {
23
+ moduleId: string;
24
+ name: string;
25
+ kind: "contributes-to" | "degrades-without" | "refuses-without";
26
+ reason: string;
27
+ whenAbsent?: string | undefined;
28
+ }[] | undefined;
29
+ activation?: {
30
+ settingCode: string;
31
+ default: boolean;
32
+ } | {
33
+ nonDeactivatable: true;
34
+ reason: string;
35
+ } | undefined;
36
+ settings?: {
37
+ moduleCode: string;
38
+ groups: {
39
+ code: string;
40
+ name: string;
41
+ salesChannelCodes?: string[] | undefined;
42
+ isSystemProtected?: boolean | undefined;
43
+ }[];
44
+ settings: {
45
+ code: string;
46
+ name: string;
47
+ valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
48
+ defaultValue: unknown;
49
+ description?: string | undefined;
50
+ groupCode?: string | undefined;
51
+ previousDefaultValues?: unknown[] | undefined;
52
+ salesChannelCodes?: string[] | undefined;
53
+ enumOptions?: string[] | undefined;
54
+ configurationType?: string | undefined;
55
+ hidden?: boolean | undefined;
56
+ }[];
57
+ } | undefined;
58
+ i18n?: {
59
+ bundlesDir: string;
60
+ } | undefined;
61
+ docs?: false | {
62
+ dir: string;
63
+ } | undefined;
64
+ demo?: false | {
65
+ summary: string;
66
+ seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
67
+ reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
68
+ after?: readonly string[] | undefined;
69
+ package?: string | undefined;
70
+ } | undefined;
71
+ actions?: {
72
+ id: string;
73
+ labelKey: string;
74
+ icon: "Plus" | "Sparkles" | "Settings" | "Search" | "Boxes" | "Layers" | "Menu" | "PlusCircle" | "PlusSquare" | "FilePlus" | "FolderPlus" | "Upload" | "FileUp" | "CloudUpload" | "Download" | "FileDown" | "FileText" | "BookOpen" | "Rss" | "Package" | "Tag" | "ShoppingCart" | "Receipt" | "CreditCard" | "Users" | "UserPlus" | "Inbox" | "ListChecks" | "ClipboardList" | "Image" | "Video" | "LayoutDashboard" | "PanelLeft" | "KeyRound" | "ShieldCheck" | "Edit" | "Archive" | "Box" | "Truck" | "CircleDollarSign" | "Activity" | "LineChart" | "Smartphone" | "Webhook" | "Scale" | "PlugZap" | "PercentDiamond" | "Newspaper" | "Languages" | "Eraser" | "Warehouse" | "TrendingDown" | "Bell" | "PackageOpen" | "Building2" | "Store" | "ClipboardCheck";
75
+ targetRoute: string;
76
+ keywords: string[];
77
+ weight: number;
78
+ descriptionKey?: string | undefined;
79
+ requiredPermission?: string | undefined;
80
+ }[] | undefined;
81
+ permissions?: {
82
+ code: string;
83
+ label: string;
84
+ module?: string | undefined;
85
+ description?: string | undefined;
86
+ requires?: string[] | undefined;
87
+ }[] | undefined;
88
+ transactionalEmails?: {
89
+ code: string;
90
+ name: string;
91
+ variables: {
92
+ key: string;
93
+ label: string;
94
+ sampleValue?: string | undefined;
95
+ description?: string | undefined;
96
+ }[];
97
+ description?: string | undefined;
98
+ group?: string | undefined;
99
+ }[] | undefined;
100
+ capabilities?: string[] | undefined;
101
+ exclusiveCapabilities?: {
102
+ key: string;
103
+ errorCode: string;
104
+ }[] | undefined;
105
+ errorCodes?: {
106
+ code: string;
107
+ tokens?: string[] | undefined;
108
+ }[] | undefined;
109
+ blocks?: {
110
+ name: string;
111
+ labelKey: string;
112
+ category: string;
113
+ contexts: ("invoice" | "email" | "cms" | "newsletter")[];
114
+ fields: Record<string, {
115
+ type: "number" | "object" | "array" | "text" | "textarea" | "select" | "radio" | "external" | "uuid" | "richtext";
116
+ label?: string | undefined;
117
+ required?: boolean | undefined;
118
+ options?: {
119
+ label: string;
120
+ value: string | number;
121
+ }[] | undefined;
122
+ refKind?: string | undefined;
123
+ }>;
124
+ descriptionKey?: string | undefined;
125
+ defaultProps?: Record<string, unknown> | undefined;
126
+ responsiveFields?: string[] | undefined;
127
+ previewIcon?: string | undefined;
128
+ weight?: number | undefined;
129
+ }[] | undefined;
130
+ blockCategories?: {
131
+ key: string;
132
+ titleKey: string;
133
+ contexts: ("invoice" | "email" | "cms" | "newsletter")[];
134
+ weight?: number | undefined;
135
+ visible?: boolean | undefined;
136
+ }[] | undefined;
137
+ env?: {
138
+ name: string;
139
+ describes: {
140
+ en: string;
141
+ pl: string;
142
+ };
143
+ requirement: {
144
+ kind: "required";
145
+ } | {
146
+ kind: "requiredWhen";
147
+ input: string;
148
+ equals: string;
149
+ } | {
150
+ kind: "optional";
151
+ without: {
152
+ en: string;
153
+ pl: string;
154
+ };
155
+ };
156
+ secret: boolean;
157
+ generable: boolean;
158
+ owner: {
159
+ kind: "platform";
160
+ } | {
161
+ kind: "application";
162
+ application: "admin" | "backend" | "storefront";
163
+ } | {
164
+ kind: "module";
165
+ moduleId: string;
166
+ };
167
+ consumers: ("admin" | "backend" | "storefront")[];
168
+ addressOf: "admin" | "backend" | "storefront" | null;
169
+ }[] | undefined;
170
+ };
171
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCnB,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { defineModuleManifest } from '@endora-commerce/contracts';
2
+ /**
3
+ * Admin notifications — minimal "bell" surface.
4
+ *
5
+ * Introduced by feature 026 (Organizations) to deliver in-app
6
+ * notifications to admins when a new Organization registers. The
7
+ * module is intentionally generic so future features (low-stock
8
+ * alerts, RFQ awaiting reply, etc.) can write into the same feed.
9
+ *
10
+ * The schema is owned by migration 048_admin_notifications_init.ts.
11
+ */
12
+ export const manifest = defineModuleManifest({
13
+ id: 'admin_notifications',
14
+ name: 'Admin Notifications',
15
+ description: 'In-app notification feed surfaced in the admin top-nav bell. Generic so any module can write entries.',
16
+ version: '1.0.0',
17
+ // `auth` added with the conversion: the admin routes are gated by
18
+ // `requireAdmin`, which `auth` owns.
19
+ dependencies: ['admin_users', 'auth'],
20
+ settings: {
21
+ moduleCode: 'admin_notifications',
22
+ groups: [{ code: 'admin_notifications', name: 'Admin notifications' }],
23
+ settings: [
24
+ {
25
+ // Feature 073 — the operator's activation control. Platform-wide.
26
+ code: 'admin_notifications.enabled',
27
+ name: 'Admin notifications enabled',
28
+ description: 'Switches the admin notification centre on or off. While off nothing is recorded and the notification screens and their API are gone. Nothing is deleted — entries already recorded are preserved and reappear when you switch it back on.',
29
+ groupCode: 'admin_notifications',
30
+ valueType: 'boolean',
31
+ defaultValue: true,
32
+ },
33
+ ],
34
+ },
35
+ activation: { settingCode: 'admin_notifications.enabled', default: true },
36
+ // Feature 026 checklist — `routes.admin.ts` has gated the bell on this code
37
+ // since 026 while no manifest declared it, so only a role holding `'*'` could
38
+ // open the notification feed. The code keeps its historical spelling for the
39
+ // same reason `audit_log:read` and `integrations:manage` keep theirs: it is
40
+ // the string the route checks, and `module` is what puts the row under the
41
+ // right heading on the role matrix.
42
+ permissions: [
43
+ { code: 'admin:read', module: 'admin_notifications', label: 'View admin notifications' },
44
+ ],
45
+ });
46
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAElE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,oBAAoB,CAAC;IAC3C,EAAE,EAAE,qBAAqB;IACzB,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EACT,uGAAuG;IACzG,OAAO,EAAE,OAAO;IAChB,kEAAkE;IAClE,qCAAqC;IACrC,YAAY,EAAE,CAAC,aAAa,EAAE,MAAM,CAAC;IACrC,QAAQ,EAAE;QACR,UAAU,EAAE,qBAAqB;QACjC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,qBAAqB,EAAE,CAAC;QACtE,QAAQ,EAAE;YACR;gBACE,kEAAkE;gBAClE,IAAI,EAAE,6BAA6B;gBACnC,IAAI,EAAE,6BAA6B;gBACnC,WAAW,EACT,2OAA2O;gBAC7O,SAAS,EAAE,qBAAqB;gBAChC,SAAS,EAAE,SAAS;gBACpB,YAAY,EAAE,IAAI;aACnB;SACF;KACF;IACD,UAAU,EAAE,EAAE,WAAW,EAAE,6BAA6B,EAAE,OAAO,EAAE,IAAI,EAAE;IACzE,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,2EAA2E;IAC3E,oCAAoC;IACpC,WAAW,EAAE;QACX,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,qBAAqB,EAAE,KAAK,EAAE,0BAA0B,EAAE;KACzF;CACF,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+ /**
3
+ * Admin notifications — minimal "bell" surface introduced by feature 026.
4
+ *
5
+ * One row per notification. `audience='all_admins'` rows are visible to
6
+ * every admin (the read state is tracked per-admin in a future iteration
7
+ * via `admin_notification_reads`; for the first iteration we keep `read_at`
8
+ * on the row itself for `audience='admin_user'` rows only, and rely on the
9
+ * `mark-all-read` endpoint stamping a per-admin "last seen" cursor —
10
+ * implemented in code, not in this schema).
11
+ *
12
+ * Unread cap is enforced by application code (`pruneStale()`), not by the
13
+ * schema.
14
+ */
15
+ export declare class Migration20260611T140350AdminNotificationsInit extends Migration {
16
+ up(): Promise<void>;
17
+ down(): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=20260611T140350_admin_notifications_init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260611T140350_admin_notifications_init.d.ts","sourceRoot":"","sources":["../../src/migrations/20260611T140350_admin_notifications_init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;;;;;GAYG;AACH,qBAAa,8CAA+C,SAAQ,SAAS;IAC5D,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAgEnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAMrC"}