@headless-lms/core 0.3.0 → 0.5.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/dist/assets/events.d.ts +24 -18
- package/dist/assets/file-urls.d.ts +11 -7
- package/dist/assets/file-urls.js +14 -4
- package/dist/assets/file-urls.js.map +1 -1
- package/dist/assets/index.d.ts +5 -4
- package/dist/assets/index.js +4 -3
- package/dist/assets/jobs.d.ts +15 -0
- package/dist/assets/jobs.js +30 -0
- package/dist/assets/jobs.js.map +1 -0
- package/dist/assets/ports.d.ts +37 -17
- package/dist/assets/service.d.ts +30 -15
- package/dist/assets/service.js +229 -59
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.js +2 -63
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.d.ts +1 -2
- package/dist/automations/catalog.js +137 -65
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/community-derivations.js +20 -20
- package/dist/automations/community-derivations.js.map +1 -1
- package/dist/automations/community-mail.d.ts +36 -20
- package/dist/automations/community-mail.js +32 -24
- package/dist/automations/community-mail.js.map +1 -1
- package/dist/automations/event-permissions.d.ts +12 -0
- package/dist/automations/event-permissions.js +38 -0
- package/dist/automations/event-permissions.js.map +1 -0
- package/dist/automations/events.d.ts +0 -10
- package/dist/automations/index.d.ts +12 -4
- package/dist/automations/index.js +9 -1
- package/dist/automations/notifications/applicable.d.ts +8 -0
- package/dist/automations/notifications/applicable.js +12 -0
- package/dist/automations/notifications/applicable.js.map +1 -0
- package/dist/automations/notifications/debounce.d.ts +25 -0
- package/dist/automations/notifications/debounce.js +93 -0
- package/dist/automations/notifications/debounce.js.map +1 -0
- package/dist/automations/notifications/defaults.d.ts +8 -0
- package/dist/automations/notifications/defaults.js +346 -0
- package/dist/automations/notifications/defaults.js.map +1 -0
- package/dist/automations/notifications/delivery.d.ts +28 -0
- package/dist/automations/notifications/delivery.js +88 -0
- package/dist/automations/notifications/delivery.js.map +1 -0
- package/dist/automations/notifications/processor.d.ts +64 -0
- package/dist/automations/notifications/processor.js +270 -0
- package/dist/automations/notifications/processor.js.map +1 -0
- package/dist/automations/notifications/rules/content.d.ts +6 -0
- package/dist/automations/notifications/rules/content.js +96 -0
- package/dist/automations/notifications/rules/content.js.map +1 -0
- package/dist/automations/notifications/rules/discussion.d.ts +7 -0
- package/dist/automations/notifications/rules/discussion.js +678 -0
- package/dist/automations/notifications/rules/discussion.js.map +1 -0
- package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
- package/dist/automations/notifications/rules/entitlements.js +276 -0
- package/dist/automations/notifications/rules/entitlements.js.map +1 -0
- package/dist/automations/notifications/rules/events.d.ts +9 -0
- package/dist/automations/notifications/rules/events.js +27 -0
- package/dist/automations/notifications/rules/events.js.map +1 -0
- package/dist/automations/notifications/rules/index.d.ts +6 -0
- package/dist/automations/notifications/rules/index.js +15 -0
- package/dist/automations/notifications/rules/index.js.map +1 -0
- package/dist/automations/notifications/rules/organizations.d.ts +6 -0
- package/dist/automations/notifications/rules/organizations.js +35 -0
- package/dist/automations/notifications/rules/organizations.js.map +1 -0
- package/dist/automations/notifications/rules/recipients.d.ts +32 -0
- package/dist/automations/notifications/rules/recipients.js +98 -0
- package/dist/automations/notifications/rules/recipients.js.map +1 -0
- package/dist/automations/notifications/service.d.ts +29 -0
- package/dist/automations/notifications/service.js +45 -0
- package/dist/automations/notifications/service.js.map +1 -0
- package/dist/automations/notifications/types.d.ts +7 -0
- package/dist/automations/notifications/types.js +560 -0
- package/dist/automations/notifications/types.js.map +1 -0
- package/dist/automations/ports.d.ts +134 -21
- package/dist/automations/service.d.ts +30 -30
- package/dist/automations/service.js +111 -144
- package/dist/automations/service.js.map +1 -1
- package/dist/content/event-sight.d.ts +11 -0
- package/dist/content/event-sight.js +11 -0
- package/dist/content/event-sight.js.map +1 -0
- package/dist/content/events.d.ts +437 -46
- package/dist/content/events.js +45 -1
- package/dist/content/events.js.map +1 -1
- package/dist/content/index.d.ts +8 -4
- package/dist/content/index.js +5 -1
- package/dist/content/jobs.d.ts +16 -0
- package/dist/content/jobs.js +37 -0
- package/dist/content/jobs.js.map +1 -0
- package/dist/content/model.d.ts +2 -2
- package/dist/content/notification-categories.d.ts +37 -0
- package/dist/content/notification-categories.js +73 -0
- package/dist/content/notification-categories.js.map +1 -0
- package/dist/content/ports.d.ts +132 -68
- package/dist/content/service.d.ts +100 -68
- package/dist/content/service.js +703 -146
- package/dist/content/service.js.map +1 -1
- package/dist/content/titles.d.ts +25 -0
- package/dist/content/titles.js +82 -0
- package/dist/content/titles.js.map +1 -0
- package/dist/content/types.d.ts +2 -2
- package/dist/discussion/authors.d.ts +4 -2
- package/dist/discussion/authors.js +9 -5
- package/dist/discussion/authors.js.map +1 -1
- package/dist/discussion/chats.d.ts +63 -39
- package/dist/discussion/chats.js +455 -190
- package/dist/discussion/chats.js.map +1 -1
- package/dist/discussion/digests.d.ts +24 -0
- package/dist/discussion/digests.js +104 -0
- package/dist/discussion/digests.js.map +1 -0
- package/dist/discussion/events.d.ts +227 -23
- package/dist/discussion/events.js +114 -19
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/index.d.ts +12 -4
- package/dist/discussion/index.js +9 -2
- package/dist/discussion/jobs.d.ts +16 -0
- package/dist/discussion/jobs.js +40 -0
- package/dist/discussion/jobs.js.map +1 -0
- package/dist/discussion/live.d.ts +106 -0
- package/dist/discussion/live.js +642 -0
- package/dist/discussion/live.js.map +1 -0
- package/dist/discussion/mentions.d.ts +11 -0
- package/dist/discussion/mentions.js +35 -0
- package/dist/discussion/mentions.js.map +1 -0
- package/dist/discussion/ports.d.ts +279 -114
- package/dist/discussion/ports.js.map +1 -1
- package/dist/discussion/posts.d.ts +60 -44
- package/dist/discussion/posts.js +453 -287
- package/dist/discussion/posts.js.map +1 -1
- package/dist/discussion/rules.d.ts +18 -0
- package/dist/discussion/rules.js +22 -0
- package/dist/discussion/rules.js.map +1 -0
- package/dist/discussion/service.d.ts +50 -35
- package/dist/discussion/service.js +333 -205
- package/dist/discussion/service.js.map +1 -1
- package/dist/discussion/subscriptions.d.ts +37 -0
- package/dist/discussion/subscriptions.js +115 -0
- package/dist/discussion/subscriptions.js.map +1 -0
- package/dist/discussion/titles.d.ts +28 -0
- package/dist/discussion/titles.js +98 -0
- package/dist/discussion/titles.js.map +1 -0
- package/dist/entitlements/events.d.ts +20 -20
- package/dist/entitlements/index.d.ts +5 -4
- package/dist/entitlements/index.js +3 -2
- package/dist/entitlements/jobs.d.ts +12 -0
- package/dist/entitlements/jobs.js +18 -0
- package/dist/entitlements/jobs.js.map +1 -0
- package/dist/entitlements/model.d.ts +2 -2
- package/dist/entitlements/ports.d.ts +68 -38
- package/dist/entitlements/rules.d.ts +3 -6
- package/dist/entitlements/rules.js +5 -10
- package/dist/entitlements/rules.js.map +1 -1
- package/dist/entitlements/service.d.ts +43 -28
- package/dist/entitlements/service.js +387 -160
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/devices.d.ts +30 -0
- package/dist/identity/devices.js +109 -0
- package/dist/identity/devices.js.map +1 -0
- package/dist/identity/index.d.ts +6 -5
- package/dist/identity/index.js +4 -3
- package/dist/identity/model.d.ts +2 -2
- package/dist/identity/notification-settings.d.ts +28 -0
- package/dist/identity/notification-settings.js +114 -0
- package/dist/identity/notification-settings.js.map +1 -0
- package/dist/identity/ports.d.ts +80 -56
- package/dist/identity/service.d.ts +8 -7
- package/dist/identity/service.js +17 -13
- package/dist/identity/service.js.map +1 -1
- package/dist/integrations/ports.d.ts +9 -8
- package/dist/integrations/service.d.ts +9 -8
- package/dist/integrations/service.js +33 -16
- package/dist/integrations/service.js.map +1 -1
- package/dist/organizations/default-roles.d.ts +22 -0
- package/dist/organizations/default-roles.js +89 -0
- package/dist/organizations/default-roles.js.map +1 -0
- package/dist/organizations/effective-permissions.d.ts +45 -0
- package/dist/organizations/effective-permissions.js +113 -0
- package/dist/organizations/effective-permissions.js.map +1 -0
- package/dist/organizations/events.d.ts +320 -181
- package/dist/organizations/events.js +90 -18
- package/dist/organizations/events.js.map +1 -1
- package/dist/organizations/index.d.ts +15 -10
- package/dist/organizations/index.js +8 -3
- package/dist/organizations/members.d.ts +66 -8
- package/dist/organizations/members.js.map +1 -1
- package/dist/organizations/notification-catalog.d.ts +31 -0
- package/dist/organizations/notification-catalog.js +32 -0
- package/dist/organizations/notification-catalog.js.map +1 -0
- package/dist/organizations/notification-categories.d.ts +34 -0
- package/dist/organizations/notification-categories.js +66 -0
- package/dist/organizations/notification-categories.js.map +1 -0
- package/dist/organizations/org-roles.d.ts +33 -0
- package/dist/organizations/org-roles.js +49 -0
- package/dist/organizations/org-roles.js.map +1 -0
- package/dist/organizations/permissions.d.ts +311 -0
- package/dist/organizations/permissions.js +327 -0
- package/dist/organizations/permissions.js.map +1 -0
- package/dist/organizations/ports.d.ts +187 -65
- package/dist/organizations/service.d.ts +104 -35
- package/dist/organizations/service.js +1247 -323
- package/dist/organizations/service.js.map +1 -1
- package/dist/organizations/testing.d.ts +18 -0
- package/dist/organizations/testing.js +37 -0
- package/dist/organizations/testing.js.map +1 -0
- package/dist/organizations/types.d.ts +5 -16
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/ports.d.ts +11 -3
- package/dist/progress/service.d.ts +6 -5
- package/dist/progress/service.js +23 -8
- package/dist/progress/service.js.map +1 -1
- package/dist/reporting/admin/index.d.ts +6 -4
- package/dist/reporting/admin/model.d.ts +36 -3
- package/dist/reporting/admin/ports.d.ts +39 -14
- package/dist/reporting/admin/service.d.ts +32 -11
- package/dist/reporting/admin/service.js +102 -18
- package/dist/reporting/admin/service.js.map +1 -1
- package/dist/reporting/events/index.d.ts +27 -0
- package/dist/reporting/events/index.js +20 -0
- package/dist/reporting/events/index.js.map +1 -0
- package/dist/reporting/member/calendar.d.ts +11 -0
- package/dist/reporting/member/calendar.js +75 -0
- package/dist/reporting/member/calendar.js.map +1 -0
- package/dist/reporting/member/index.d.ts +3 -2
- package/dist/reporting/member/model.d.ts +80 -26
- package/dist/reporting/member/ports.d.ts +50 -28
- package/dist/reporting/member/service.d.ts +81 -35
- package/dist/reporting/member/service.js +438 -145
- package/dist/reporting/member/service.js.map +1 -1
- package/dist/shared/actor.d.ts +22 -3
- package/dist/shared/errors.d.ts +15 -2
- package/dist/shared/errors.js +21 -2
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/id.d.ts +7 -0
- package/dist/shared/id.js +8 -1
- package/dist/shared/id.js.map +1 -1
- package/dist/shared/logger.d.ts +2 -1
- package/dist/shared/logger.js +10 -1
- package/dist/shared/logger.js.map +1 -1
- package/dist/shared/mailer.d.ts +11 -1
- package/dist/shared/mailer.js +32 -5
- package/dist/shared/mailer.js.map +1 -1
- package/dist/shared/notification-categories.d.ts +16 -0
- package/dist/shared/notification-categories.js +94 -0
- package/dist/shared/notification-categories.js.map +1 -0
- package/dist/shared/notification-settings.d.ts +14 -0
- package/dist/shared/notification-settings.js +61 -0
- package/dist/shared/notification-settings.js.map +1 -0
- package/dist/shared/ports.d.ts +68 -2
- package/dist/shared/settings.d.ts +2 -1
- package/dist/shared/settings.js +1 -0
- package/dist/shared/settings.js.map +1 -1
- package/dist/types/automations.d.ts +8 -2
- package/dist/types/content.d.ts +2 -2
- package/dist/types/discussion.d.ts +4 -5
- package/dist/types/email-templates.d.ts +119 -6
- package/dist/types/entitlements.d.ts +2 -2
- package/dist/types/identity.d.ts +14 -17
- package/dist/types/index.d.ts +13 -12
- package/dist/types/index.js +2 -1
- package/dist/types/notifications.d.ts +157 -0
- package/dist/types/notifications.js +1 -0
- package/dist/types/organizations.d.ts +3 -2
- package/dist/types/ports.d.ts +8 -1
- package/dist/types/ports.js +7 -1
- package/dist/types/ports.js.map +1 -0
- package/dist/types/schemas/assets.d.ts +5 -5
- package/dist/types/schemas/assets.js +4 -3
- package/dist/types/schemas/assets.js.map +1 -1
- package/dist/types/schemas/automations.d.ts +1 -10
- package/dist/types/schemas/automations.js +1 -3
- package/dist/types/schemas/automations.js.map +1 -1
- package/dist/types/schemas/content.d.ts +596 -11
- package/dist/types/schemas/content.js +225 -9
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +362 -66
- package/dist/types/schemas/discussion.js +123 -15
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/entitlements.d.ts +14 -1
- package/dist/types/schemas/entitlements.js +11 -4
- package/dist/types/schemas/entitlements.js.map +1 -1
- package/dist/types/schemas/identity.d.ts +1 -85
- package/dist/types/schemas/identity.js +1 -42
- package/dist/types/schemas/identity.js.map +1 -1
- package/dist/types/schemas/index.d.ts +8 -7
- package/dist/types/schemas/index.js +8 -7
- package/dist/types/schemas/notifications.d.ts +96 -0
- package/dist/types/schemas/notifications.js +57 -0
- package/dist/types/schemas/notifications.js.map +1 -0
- package/dist/types/schemas/organizations.d.ts +96 -60
- package/dist/types/schemas/organizations.js +78 -27
- package/dist/types/schemas/organizations.js.map +1 -1
- package/dist/types/schemas/progress.d.ts +0 -1
- package/dist/types/schemas/progress.js +0 -1
- package/dist/types/schemas/progress.js.map +1 -1
- package/package.json +1 -1
- package/dist/automations/community-notifications.d.ts +0 -16
- package/dist/automations/community-notifications.js +0 -84
- package/dist/automations/community-notifications.js.map +0 -1
- package/dist/automations/seeds.d.ts +0 -17
- package/dist/automations/seeds.js +0 -235
- package/dist/automations/seeds.js.map +0 -1
- package/dist/identity/notifications.d.ts +0 -27
- package/dist/identity/notifications.js +0 -101
- package/dist/identity/notifications.js.map +0 -1
- package/dist/organizations/access-scopes.d.ts +0 -19
- package/dist/organizations/access-scopes.js +0 -89
- package/dist/organizations/access-scopes.js.map +0 -1
- package/dist/organizations/roles.d.ts +0 -19
- package/dist/organizations/roles.js +0 -82
- package/dist/organizations/roles.js.map +0 -1
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { notificationKindSchema } from "../types/schemas/identity.js";
|
|
2
|
-
import { genId } from "../shared/id.js";
|
|
3
|
-
import { NotFoundError } from "../shared/errors.js";
|
|
4
|
-
import { noopLogger } from "../shared/logger.js";
|
|
5
|
-
//#region src/identity/notifications.ts
|
|
6
|
-
function defaultPreference(kind) {
|
|
7
|
-
return {
|
|
8
|
-
kind,
|
|
9
|
-
inApp: true,
|
|
10
|
-
email: true
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
var NotificationServiceImpl = class {
|
|
14
|
-
repo;
|
|
15
|
-
logger;
|
|
16
|
-
constructor(params) {
|
|
17
|
-
this.repo = params.repo;
|
|
18
|
-
this.logger = params.logger ?? noopLogger;
|
|
19
|
-
}
|
|
20
|
-
async notify(orgId, notifications) {
|
|
21
|
-
if (notifications.length === 0) return;
|
|
22
|
-
const now = /* @__PURE__ */ new Date();
|
|
23
|
-
const rows = notifications.map((notification) => ({
|
|
24
|
-
...notification,
|
|
25
|
-
orgId,
|
|
26
|
-
id: genId("notification"),
|
|
27
|
-
createdAt: now,
|
|
28
|
-
readAt: null
|
|
29
|
-
}));
|
|
30
|
-
await this.repo.insert(orgId, rows);
|
|
31
|
-
this.logger.info("notifications added", {
|
|
32
|
-
orgId,
|
|
33
|
-
kinds: [...new Set(rows.map((row) => row.kind))],
|
|
34
|
-
count: rows.length
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
async preferencesFor(orgId, userIds, kind) {
|
|
38
|
-
const unique = [...new Set(userIds)];
|
|
39
|
-
const stored = await this.repo.preferences(orgId, unique);
|
|
40
|
-
const result = {};
|
|
41
|
-
for (const userId of unique) result[userId] = defaultPreference(kind);
|
|
42
|
-
for (const row of stored) if (row.kind === kind) result[row.userId] = {
|
|
43
|
-
kind,
|
|
44
|
-
inApp: row.inApp,
|
|
45
|
-
email: row.email
|
|
46
|
-
};
|
|
47
|
-
return result;
|
|
48
|
-
}
|
|
49
|
-
async listNotifications(orgId, userId, query) {
|
|
50
|
-
const page = await this.repo.page(orgId, userId, query);
|
|
51
|
-
this.logger.debug("notifications listed", {
|
|
52
|
-
orgId,
|
|
53
|
-
userId,
|
|
54
|
-
count: page.rows.length
|
|
55
|
-
});
|
|
56
|
-
return {
|
|
57
|
-
...page,
|
|
58
|
-
page: query.page,
|
|
59
|
-
pageSize: query.pageSize
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
async markNotificationRead(orgId, userId, notificationId) {
|
|
63
|
-
const row = await this.repo.markRead(orgId, userId, notificationId, /* @__PURE__ */ new Date());
|
|
64
|
-
if (!row) throw new NotFoundError("Notification", notificationId);
|
|
65
|
-
return row;
|
|
66
|
-
}
|
|
67
|
-
async markAllNotificationsRead(orgId, userId) {
|
|
68
|
-
const updated = await this.repo.markAllRead(orgId, userId, /* @__PURE__ */ new Date());
|
|
69
|
-
this.logger.debug("notifications marked read", {
|
|
70
|
-
orgId,
|
|
71
|
-
userId,
|
|
72
|
-
updated
|
|
73
|
-
});
|
|
74
|
-
return { updated };
|
|
75
|
-
}
|
|
76
|
-
async notificationPreferences(orgId, userId) {
|
|
77
|
-
const stored = await this.repo.preferences(orgId, [userId]);
|
|
78
|
-
const byKind = new Map(stored.map((row) => [row.kind, row]));
|
|
79
|
-
return notificationKindSchema.options.map((kind) => {
|
|
80
|
-
const row = byKind.get(kind);
|
|
81
|
-
return row ? {
|
|
82
|
-
kind,
|
|
83
|
-
inApp: row.inApp,
|
|
84
|
-
email: row.email
|
|
85
|
-
} : defaultPreference(kind);
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
async setNotificationPreferences(orgId, userId, preferences) {
|
|
89
|
-
await this.repo.upsertPreferences(orgId, userId, preferences);
|
|
90
|
-
this.logger.info("notification preferences updated", {
|
|
91
|
-
orgId,
|
|
92
|
-
userId,
|
|
93
|
-
kinds: preferences.map((pref) => pref.kind)
|
|
94
|
-
});
|
|
95
|
-
return this.notificationPreferences(orgId, userId);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
//#endregion
|
|
99
|
-
export { NotificationServiceImpl, defaultPreference };
|
|
100
|
-
|
|
101
|
-
//# sourceMappingURL=notifications.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.js","names":[],"sources":["../../src/identity/notifications.ts"],"sourcesContent":["import { NotFoundError } from '../shared/errors.js';\nimport { genId } from '../shared/id.js';\nimport { noopLogger } from '../shared/logger.js';\nimport type { Logger } from '../shared/ports.js';\nimport { notificationKindSchema } from '../types/schemas/identity.js';\nimport type { Notification, NotificationKind, NotificationPreference } from './model.js';\nimport type {\n NewNotification,\n NotificationPage,\n NotificationService,\n NotificationsQuery,\n NotificationsRepository,\n} from './ports.js';\n\nexport function defaultPreference(kind: NotificationKind): NotificationPreference {\n return { kind, inApp: true, email: true };\n}\n\nexport class NotificationServiceImpl implements NotificationService {\n private readonly repo: NotificationsRepository;\n private readonly logger: Logger;\n\n constructor(params: { repo: NotificationsRepository; logger?: Logger }) {\n this.repo = params.repo;\n this.logger = params.logger ?? noopLogger;\n }\n\n async notify(orgId: string, notifications: NewNotification[]): Promise<void> {\n if (notifications.length === 0) {\n return;\n }\n const now = new Date();\n const rows: Notification[] = notifications.map((notification) => ({\n ...notification,\n orgId,\n id: genId('notification'),\n createdAt: now,\n readAt: null,\n }));\n await this.repo.insert(orgId, rows);\n this.logger.info('notifications added', {\n orgId,\n kinds: [...new Set(rows.map((row) => row.kind))],\n count: rows.length,\n });\n }\n\n async preferencesFor(\n orgId: string,\n userIds: string[],\n kind: NotificationKind,\n ): Promise<Record<string, NotificationPreference>> {\n const unique = [...new Set(userIds)];\n const stored = await this.repo.preferences(orgId, unique);\n const result: Record<string, NotificationPreference> = {};\n for (const userId of unique) {\n result[userId] = defaultPreference(kind);\n }\n for (const row of stored) {\n if (row.kind === kind) {\n result[row.userId] = { kind, inApp: row.inApp, email: row.email };\n }\n }\n return result;\n }\n\n async listNotifications(\n orgId: string,\n userId: string,\n query: NotificationsQuery,\n ): Promise<NotificationPage> {\n const page = await this.repo.page(orgId, userId, query);\n this.logger.debug('notifications listed', { orgId, userId, count: page.rows.length });\n return { ...page, page: query.page, pageSize: query.pageSize };\n }\n\n async markNotificationRead(\n orgId: string,\n userId: string,\n notificationId: string,\n ): Promise<Notification> {\n const row = await this.repo.markRead(orgId, userId, notificationId, new Date());\n if (!row) {\n throw new NotFoundError('Notification', notificationId);\n }\n return row;\n }\n\n async markAllNotificationsRead(orgId: string, userId: string): Promise<{ updated: number }> {\n const updated = await this.repo.markAllRead(orgId, userId, new Date());\n this.logger.debug('notifications marked read', { orgId, userId, updated });\n return { updated };\n }\n\n async notificationPreferences(orgId: string, userId: string): Promise<NotificationPreference[]> {\n const stored = await this.repo.preferences(orgId, [userId]);\n const byKind = new Map(stored.map((row) => [row.kind, row]));\n return notificationKindSchema.options.map((kind) => {\n const row = byKind.get(kind);\n return row ? { kind, inApp: row.inApp, email: row.email } : defaultPreference(kind);\n });\n }\n\n async setNotificationPreferences(\n orgId: string,\n userId: string,\n preferences: NotificationPreference[],\n ): Promise<NotificationPreference[]> {\n await this.repo.upsertPreferences(orgId, userId, preferences);\n this.logger.info('notification preferences updated', {\n orgId,\n userId,\n kinds: preferences.map((pref) => pref.kind),\n });\n return this.notificationPreferences(orgId, userId);\n }\n}\n"],"mappings":";;;;;AAcA,SAAgB,kBAAkB,MAAgD;CAChF,OAAO;EAAE;EAAM,OAAO;EAAM,OAAO;CAAK;AAC1C;AAEA,IAAa,0BAAb,MAAoE;CAClE;CACA;CAEA,YAAY,QAA4D;EACtE,KAAK,OAAO,OAAO;EACnB,KAAK,SAAS,OAAO,UAAU;CACjC;CAEA,MAAM,OAAO,OAAe,eAAiD;EAC3E,IAAI,cAAc,WAAW,GAC3B;EAEF,MAAM,sBAAM,IAAI,KAAK;EACrB,MAAM,OAAuB,cAAc,KAAK,kBAAkB;GAChE,GAAG;GACH;GACA,IAAI,MAAM,cAAc;GACxB,WAAW;GACX,QAAQ;EACV,EAAE;EACF,MAAM,KAAK,KAAK,OAAO,OAAO,IAAI;EAClC,KAAK,OAAO,KAAK,uBAAuB;GACtC;GACA,OAAO,CAAC,GAAG,IAAI,IAAI,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC;GAC/C,OAAO,KAAK;EACd,CAAC;CACH;CAEA,MAAM,eACJ,OACA,SACA,MACiD;EACjD,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC;EACnC,MAAM,SAAS,MAAM,KAAK,KAAK,YAAY,OAAO,MAAM;EACxD,MAAM,SAAiD,CAAC;EACxD,KAAK,MAAM,UAAU,QACnB,OAAO,UAAU,kBAAkB,IAAI;EAEzC,KAAK,MAAM,OAAO,QAChB,IAAI,IAAI,SAAS,MACf,OAAO,IAAI,UAAU;GAAE;GAAM,OAAO,IAAI;GAAO,OAAO,IAAI;EAAM;EAGpE,OAAO;CACT;CAEA,MAAM,kBACJ,OACA,QACA,OAC2B;EAC3B,MAAM,OAAO,MAAM,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK;EACtD,KAAK,OAAO,MAAM,wBAAwB;GAAE;GAAO;GAAQ,OAAO,KAAK,KAAK;EAAO,CAAC;EACpF,OAAO;GAAE,GAAG;GAAM,MAAM,MAAM;GAAM,UAAU,MAAM;EAAS;CAC/D;CAEA,MAAM,qBACJ,OACA,QACA,gBACuB;EACvB,MAAM,MAAM,MAAM,KAAK,KAAK,SAAS,OAAO,QAAQ,gCAAgB,IAAI,KAAK,CAAC;EAC9E,IAAI,CAAC,KACH,MAAM,IAAI,cAAc,gBAAgB,cAAc;EAExD,OAAO;CACT;CAEA,MAAM,yBAAyB,OAAe,QAA8C;EAC1F,MAAM,UAAU,MAAM,KAAK,KAAK,YAAY,OAAO,wBAAQ,IAAI,KAAK,CAAC;EACrE,KAAK,OAAO,MAAM,6BAA6B;GAAE;GAAO;GAAQ;EAAQ,CAAC;EACzE,OAAO,EAAE,QAAQ;CACnB;CAEA,MAAM,wBAAwB,OAAe,QAAmD;EAC9F,MAAM,SAAS,MAAM,KAAK,KAAK,YAAY,OAAO,CAAC,MAAM,CAAC;EAC1D,MAAM,SAAS,IAAI,IAAI,OAAO,KAAK,QAAQ,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC;EAC3D,OAAO,uBAAuB,QAAQ,KAAK,SAAS;GAClD,MAAM,MAAM,OAAO,IAAI,IAAI;GAC3B,OAAO,MAAM;IAAE;IAAM,OAAO,IAAI;IAAO,OAAO,IAAI;GAAM,IAAI,kBAAkB,IAAI;EACpF,CAAC;CACH;CAEA,MAAM,2BACJ,OACA,QACA,aACmC;EACnC,MAAM,KAAK,KAAK,kBAAkB,OAAO,QAAQ,WAAW;EAC5D,KAAK,OAAO,KAAK,oCAAoC;GACnD;GACA;GACA,OAAO,YAAY,KAAK,SAAS,KAAK,IAAI;EAC5C,CAAC;EACD,OAAO,KAAK,wBAAwB,OAAO,MAAM;CACnD;AACF"}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Role } from "../types/schemas/organizations.js";
|
|
2
|
-
import { Permission } from "./roles.js";
|
|
3
|
-
//#region src/organizations/access-scopes.d.ts
|
|
4
|
-
declare const ACCESS_SCOPES: readonly ["courses:read", "courses:write", "courses:delete", "downloads:read", "downloads:write", "downloads:delete", "bundles:read", "bundles:write", "bundles:delete", "media:read", "students:read", "students:invite", "access:read", "access:write", "analytics:read", "automations:read", "automations:write", "automations:delete", "comments:read", "comments:moderate", "communities:read", "communities:write", "communities:delete", "community_members:read", "community_members:write", "community_moderation:read", "community_moderation:write"];
|
|
5
|
-
type AccessScope = (typeof ACCESS_SCOPES)[number];
|
|
6
|
-
declare const SCOPE_PERMISSION: Record<AccessScope, Permission | null>;
|
|
7
|
-
type ScopeOption = {
|
|
8
|
-
scope: AccessScope;
|
|
9
|
-
allowed: boolean;
|
|
10
|
-
};
|
|
11
|
-
declare function isAccessScope(value: string): value is AccessScope;
|
|
12
|
-
declare function isDeleteScope(scope: AccessScope): boolean;
|
|
13
|
-
declare function roleHoldsScope(role: Role, scope: AccessScope): boolean;
|
|
14
|
-
declare function scopesForRole(role: Role): AccessScope[];
|
|
15
|
-
declare function scopeOptions(role: Role): ScopeOption[];
|
|
16
|
-
declare function assertRoleHoldsScopes(role: Role, scopes: readonly AccessScope[]): void;
|
|
17
|
-
//#endregion
|
|
18
|
-
export { ACCESS_SCOPES, AccessScope, SCOPE_PERMISSION, ScopeOption, assertRoleHoldsScopes, isAccessScope, isDeleteScope, roleHoldsScope, scopeOptions, scopesForRole };
|
|
19
|
-
//# sourceMappingURL=access-scopes.d.ts.map
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { ForbiddenError } from "../shared/errors.js";
|
|
2
|
-
import { capability, isStaffRole } from "./roles.js";
|
|
3
|
-
//#region src/organizations/access-scopes.ts
|
|
4
|
-
const ACCESS_SCOPES = [
|
|
5
|
-
"courses:read",
|
|
6
|
-
"courses:write",
|
|
7
|
-
"courses:delete",
|
|
8
|
-
"downloads:read",
|
|
9
|
-
"downloads:write",
|
|
10
|
-
"downloads:delete",
|
|
11
|
-
"bundles:read",
|
|
12
|
-
"bundles:write",
|
|
13
|
-
"bundles:delete",
|
|
14
|
-
"media:read",
|
|
15
|
-
"students:read",
|
|
16
|
-
"students:invite",
|
|
17
|
-
"access:read",
|
|
18
|
-
"access:write",
|
|
19
|
-
"analytics:read",
|
|
20
|
-
"automations:read",
|
|
21
|
-
"automations:write",
|
|
22
|
-
"automations:delete",
|
|
23
|
-
"comments:read",
|
|
24
|
-
"comments:moderate",
|
|
25
|
-
"communities:read",
|
|
26
|
-
"communities:write",
|
|
27
|
-
"communities:delete",
|
|
28
|
-
"community_members:read",
|
|
29
|
-
"community_members:write",
|
|
30
|
-
"community_moderation:read",
|
|
31
|
-
"community_moderation:write"
|
|
32
|
-
];
|
|
33
|
-
const SCOPE_PERMISSION = {
|
|
34
|
-
"courses:read": null,
|
|
35
|
-
"courses:write": "create_course",
|
|
36
|
-
"courses:delete": "create_course",
|
|
37
|
-
"downloads:read": null,
|
|
38
|
-
"downloads:write": "create_course",
|
|
39
|
-
"downloads:delete": "create_course",
|
|
40
|
-
"bundles:read": null,
|
|
41
|
-
"bundles:write": "create_course",
|
|
42
|
-
"bundles:delete": "create_course",
|
|
43
|
-
"media:read": null,
|
|
44
|
-
"students:read": "view_student_progress",
|
|
45
|
-
"students:invite": "manage_users",
|
|
46
|
-
"access:read": "view_student_progress",
|
|
47
|
-
"access:write": "manage_users",
|
|
48
|
-
"analytics:read": "view_student_progress",
|
|
49
|
-
"automations:read": null,
|
|
50
|
-
"automations:write": "manage_org_settings",
|
|
51
|
-
"automations:delete": "manage_org_settings",
|
|
52
|
-
"comments:read": null,
|
|
53
|
-
"comments:moderate": "edit_assigned_course",
|
|
54
|
-
"communities:read": null,
|
|
55
|
-
"communities:write": "create_course",
|
|
56
|
-
"communities:delete": "create_course",
|
|
57
|
-
"community_members:read": null,
|
|
58
|
-
"community_members:write": "moderate",
|
|
59
|
-
"community_moderation:read": null,
|
|
60
|
-
"community_moderation:write": "moderate"
|
|
61
|
-
};
|
|
62
|
-
function isAccessScope(value) {
|
|
63
|
-
return ACCESS_SCOPES.includes(value);
|
|
64
|
-
}
|
|
65
|
-
function isDeleteScope(scope) {
|
|
66
|
-
return scope.endsWith(":delete");
|
|
67
|
-
}
|
|
68
|
-
function roleHoldsScope(role, scope) {
|
|
69
|
-
if (!isStaffRole(role)) return false;
|
|
70
|
-
const permission = SCOPE_PERMISSION[scope];
|
|
71
|
-
return permission === null || capability(role, permission) === true;
|
|
72
|
-
}
|
|
73
|
-
function scopesForRole(role) {
|
|
74
|
-
return ACCESS_SCOPES.filter((scope) => roleHoldsScope(role, scope));
|
|
75
|
-
}
|
|
76
|
-
function scopeOptions(role) {
|
|
77
|
-
return ACCESS_SCOPES.map((scope) => ({
|
|
78
|
-
scope,
|
|
79
|
-
allowed: roleHoldsScope(role, scope)
|
|
80
|
-
}));
|
|
81
|
-
}
|
|
82
|
-
function assertRoleHoldsScopes(role, scopes) {
|
|
83
|
-
const missing = scopes.filter((scope) => !roleHoldsScope(role, scope));
|
|
84
|
-
if (missing.length > 0) throw new ForbiddenError(`Your role cannot grant ${missing.join(", ")}`);
|
|
85
|
-
}
|
|
86
|
-
//#endregion
|
|
87
|
-
export { ACCESS_SCOPES, SCOPE_PERMISSION, assertRoleHoldsScopes, isAccessScope, isDeleteScope, roleHoldsScope, scopeOptions, scopesForRole };
|
|
88
|
-
|
|
89
|
-
//# sourceMappingURL=access-scopes.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"access-scopes.js","names":[],"sources":["../../src/organizations/access-scopes.ts"],"sourcesContent":["import { ForbiddenError } from '../shared/errors.js';\nimport { capability, isStaffRole, type Permission, type Role } from './roles.js';\n\nexport const ACCESS_SCOPES = [\n 'courses:read',\n 'courses:write',\n 'courses:delete',\n 'downloads:read',\n 'downloads:write',\n 'downloads:delete',\n 'bundles:read',\n 'bundles:write',\n 'bundles:delete',\n 'media:read',\n 'students:read',\n 'students:invite',\n 'access:read',\n 'access:write',\n 'analytics:read',\n 'automations:read',\n 'automations:write',\n 'automations:delete',\n 'comments:read',\n 'comments:moderate',\n 'communities:read',\n 'communities:write',\n 'communities:delete',\n 'community_members:read',\n 'community_members:write',\n 'community_moderation:read',\n 'community_moderation:write',\n] as const;\n\nexport type AccessScope = (typeof ACCESS_SCOPES)[number];\n\nexport const SCOPE_PERMISSION: Record<AccessScope, Permission | null> = {\n 'courses:read': null,\n 'courses:write': 'create_course',\n 'courses:delete': 'create_course',\n 'downloads:read': null,\n 'downloads:write': 'create_course',\n 'downloads:delete': 'create_course',\n 'bundles:read': null,\n 'bundles:write': 'create_course',\n 'bundles:delete': 'create_course',\n 'media:read': null,\n 'students:read': 'view_student_progress',\n 'students:invite': 'manage_users',\n 'access:read': 'view_student_progress',\n 'access:write': 'manage_users',\n 'analytics:read': 'view_student_progress',\n 'automations:read': null,\n 'automations:write': 'manage_org_settings',\n 'automations:delete': 'manage_org_settings',\n 'comments:read': null,\n 'comments:moderate': 'edit_assigned_course',\n 'communities:read': null,\n 'communities:write': 'create_course',\n 'communities:delete': 'create_course',\n 'community_members:read': null,\n 'community_members:write': 'moderate',\n 'community_moderation:read': null,\n 'community_moderation:write': 'moderate',\n};\n\nexport type ScopeOption = { scope: AccessScope; allowed: boolean };\n\nexport function isAccessScope(value: string): value is AccessScope {\n return (ACCESS_SCOPES as readonly string[]).includes(value);\n}\n\nexport function isDeleteScope(scope: AccessScope): boolean {\n return scope.endsWith(':delete');\n}\n\nexport function roleHoldsScope(role: Role, scope: AccessScope): boolean {\n if (!isStaffRole(role)) {\n return false;\n }\n const permission = SCOPE_PERMISSION[scope];\n return permission === null || capability(role, permission) === true;\n}\n\nexport function scopesForRole(role: Role): AccessScope[] {\n return ACCESS_SCOPES.filter((scope) => roleHoldsScope(role, scope));\n}\n\nexport function scopeOptions(role: Role): ScopeOption[] {\n return ACCESS_SCOPES.map((scope) => ({ scope, allowed: roleHoldsScope(role, scope) }));\n}\n\nexport function assertRoleHoldsScopes(role: Role, scopes: readonly AccessScope[]): void {\n const missing = scopes.filter((scope) => !roleHoldsScope(role, scope));\n if (missing.length > 0) {\n throw new ForbiddenError(`Your role cannot grant ${missing.join(', ')}`);\n }\n}\n"],"mappings":";;;AAGA,MAAa,gBAAgB;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAIA,MAAa,mBAA2D;CACtE,gBAAgB;CAChB,iBAAiB;CACjB,kBAAkB;CAClB,kBAAkB;CAClB,mBAAmB;CACnB,oBAAoB;CACpB,gBAAgB;CAChB,iBAAiB;CACjB,kBAAkB;CAClB,cAAc;CACd,iBAAiB;CACjB,mBAAmB;CACnB,eAAe;CACf,gBAAgB;CAChB,kBAAkB;CAClB,oBAAoB;CACpB,qBAAqB;CACrB,sBAAsB;CACtB,iBAAiB;CACjB,qBAAqB;CACrB,oBAAoB;CACpB,qBAAqB;CACrB,sBAAsB;CACtB,0BAA0B;CAC1B,2BAA2B;CAC3B,6BAA6B;CAC7B,8BAA8B;AAChC;AAIA,SAAgB,cAAc,OAAqC;CACjE,OAAQ,cAAoC,SAAS,KAAK;AAC5D;AAEA,SAAgB,cAAc,OAA6B;CACzD,OAAO,MAAM,SAAS,SAAS;AACjC;AAEA,SAAgB,eAAe,MAAY,OAA6B;CACtE,IAAI,CAAC,YAAY,IAAI,GACnB,OAAO;CAET,MAAM,aAAa,iBAAiB;CACpC,OAAO,eAAe,QAAQ,WAAW,MAAM,UAAU,MAAM;AACjE;AAEA,SAAgB,cAAc,MAA2B;CACvD,OAAO,cAAc,QAAQ,UAAU,eAAe,MAAM,KAAK,CAAC;AACpE;AAEA,SAAgB,aAAa,MAA2B;CACtD,OAAO,cAAc,KAAK,WAAW;EAAE;EAAO,SAAS,eAAe,MAAM,KAAK;CAAE,EAAE;AACvF;AAEA,SAAgB,sBAAsB,MAAY,QAAsC;CACtF,MAAM,UAAU,OAAO,QAAQ,UAAU,CAAC,eAAe,MAAM,KAAK,CAAC;CACrE,IAAI,QAAQ,SAAS,GACnB,MAAM,IAAI,eAAe,0BAA0B,QAAQ,KAAK,IAAI,GAAG;AAE3E"}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Role } from "../types/schemas/organizations.js";
|
|
2
|
-
import "../types/index.js";
|
|
3
|
-
//#region src/organizations/roles.d.ts
|
|
4
|
-
declare const ROLES: readonly ["owner", "admin", "instructor", "moderator", "student"];
|
|
5
|
-
type StaffRole = 'owner' | 'admin' | 'instructor';
|
|
6
|
-
declare const STAFF_ROLES: readonly ["owner", "admin", "instructor"];
|
|
7
|
-
type TeamRole = Exclude<Role, 'student'>;
|
|
8
|
-
declare function isRole(value: string): value is Role;
|
|
9
|
-
declare function isStaffRole(value: string): value is StaffRole;
|
|
10
|
-
declare function parseRole(value: string): Role;
|
|
11
|
-
type Permission = 'manage_billing' | 'manage_org_settings' | 'manage_users' | 'create_course' | 'edit_assigned_course' | 'view_student_progress' | 'moderate' | 'consume_content';
|
|
12
|
-
type Capability = true | 'assigned' | 'enrolled';
|
|
13
|
-
declare function capability(role: Role, permission: Permission): Capability | false;
|
|
14
|
-
declare function canModerate(role: Role): boolean;
|
|
15
|
-
declare function normalizeRole(raw: string): Role;
|
|
16
|
-
declare const STUDENT_ROLE = "student";
|
|
17
|
-
//#endregion
|
|
18
|
-
export { Capability, Permission, ROLES, type Role, STAFF_ROLES, STUDENT_ROLE, StaffRole, TeamRole, canModerate, capability, isRole, isStaffRole, normalizeRole, parseRole };
|
|
19
|
-
//# sourceMappingURL=roles.d.ts.map
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
//#region src/organizations/roles.ts
|
|
2
|
-
const ROLES = [
|
|
3
|
-
"owner",
|
|
4
|
-
"admin",
|
|
5
|
-
"instructor",
|
|
6
|
-
"moderator",
|
|
7
|
-
"student"
|
|
8
|
-
];
|
|
9
|
-
const STAFF_ROLES = [
|
|
10
|
-
"owner",
|
|
11
|
-
"admin",
|
|
12
|
-
"instructor"
|
|
13
|
-
];
|
|
14
|
-
function isRole(value) {
|
|
15
|
-
return ROLES.includes(value);
|
|
16
|
-
}
|
|
17
|
-
function isStaffRole(value) {
|
|
18
|
-
return STAFF_ROLES.includes(value);
|
|
19
|
-
}
|
|
20
|
-
function parseRole(value) {
|
|
21
|
-
if (!isRole(value)) throw new Error(`unknown role: ${value}`);
|
|
22
|
-
return value;
|
|
23
|
-
}
|
|
24
|
-
const MATRIX = {
|
|
25
|
-
owner: {
|
|
26
|
-
manage_billing: true,
|
|
27
|
-
manage_org_settings: true,
|
|
28
|
-
manage_users: true,
|
|
29
|
-
create_course: true,
|
|
30
|
-
edit_assigned_course: true,
|
|
31
|
-
view_student_progress: true,
|
|
32
|
-
moderate: true
|
|
33
|
-
},
|
|
34
|
-
admin: {
|
|
35
|
-
manage_org_settings: true,
|
|
36
|
-
manage_users: true,
|
|
37
|
-
create_course: true,
|
|
38
|
-
edit_assigned_course: true,
|
|
39
|
-
view_student_progress: true,
|
|
40
|
-
moderate: true
|
|
41
|
-
},
|
|
42
|
-
instructor: {
|
|
43
|
-
edit_assigned_course: "assigned",
|
|
44
|
-
view_student_progress: "assigned",
|
|
45
|
-
moderate: true
|
|
46
|
-
},
|
|
47
|
-
moderator: {
|
|
48
|
-
moderate: true,
|
|
49
|
-
consume_content: "enrolled"
|
|
50
|
-
},
|
|
51
|
-
student: { consume_content: "enrolled" }
|
|
52
|
-
};
|
|
53
|
-
function capability(role, permission) {
|
|
54
|
-
return MATRIX[role][permission] ?? false;
|
|
55
|
-
}
|
|
56
|
-
function canModerate(role) {
|
|
57
|
-
return capability(role, "moderate") === true;
|
|
58
|
-
}
|
|
59
|
-
const RANK = {
|
|
60
|
-
owner: 4,
|
|
61
|
-
admin: 3,
|
|
62
|
-
instructor: 2,
|
|
63
|
-
moderator: 1,
|
|
64
|
-
student: 0
|
|
65
|
-
};
|
|
66
|
-
function normalizeRole(raw) {
|
|
67
|
-
let best = "instructor";
|
|
68
|
-
let bestRank = -1;
|
|
69
|
-
for (const token of raw.split(",").map((t) => t.trim())) {
|
|
70
|
-
const r = isRole(token) ? token : token === "member" ? "instructor" : null;
|
|
71
|
-
if (r && RANK[r] > bestRank) {
|
|
72
|
-
best = r;
|
|
73
|
-
bestRank = RANK[r];
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return best;
|
|
77
|
-
}
|
|
78
|
-
const STUDENT_ROLE = "student";
|
|
79
|
-
//#endregion
|
|
80
|
-
export { ROLES, STAFF_ROLES, STUDENT_ROLE, canModerate, capability, isRole, isStaffRole, normalizeRole, parseRole };
|
|
81
|
-
|
|
82
|
-
//# sourceMappingURL=roles.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"roles.js","names":[],"sources":["../../src/organizations/roles.ts"],"sourcesContent":["// organizations context — roles and the authorization matrix.\n// Defined in code (no DB enum). The DB stores role as text; the domain narrows\n// it to Role and answers authorization questions here. The Role type itself is\n// owned by @headless-lms/core/types; RANK/MATRIX below are Record<Role, …>, so the\n// compiler forces this file to cover every role the published type declares.\nimport type { Role } from '../types/index.js';\n\nexport type { Role };\nexport const ROLES = [\n 'owner',\n 'admin',\n 'instructor',\n 'moderator',\n 'student',\n] as const satisfies readonly Role[];\n\nexport type StaffRole = 'owner' | 'admin' | 'instructor';\nexport const STAFF_ROLES = ['owner', 'admin', 'instructor'] as const satisfies readonly StaffRole[];\n\nexport type TeamRole = Exclude<Role, 'student'>;\n\nexport function isRole(value: string): value is Role {\n return (ROLES as readonly string[]).includes(value);\n}\n\nexport function isStaffRole(value: string): value is StaffRole {\n return (STAFF_ROLES as readonly string[]).includes(value);\n}\n\nexport function parseRole(value: string): Role {\n if (!isRole(value)) {\n throw new Error(`unknown role: ${value}`);\n }\n return value;\n}\n\nexport type Permission =\n | 'manage_billing'\n | 'manage_org_settings'\n | 'manage_users'\n | 'create_course'\n | 'edit_assigned_course'\n | 'view_student_progress'\n | 'moderate'\n | 'consume_content';\n\n// Unconditional (true), course-scoped (\"assigned\"), or enrollment-scoped\n// (\"enrolled\" — access owned by entitlements). Absent ⇒ denied.\n//\n// \"assigned\" is a granted-but-narrowed answer: the role holds the permission,\n// but only over some subset of courses. Nothing narrows it today — there is no\n// course-assignment store — so callers must decide what a narrowed grant means\n// for them. `authorize` treats it as denied; `tools.ts` treats it as granted.\nexport type Capability = true | 'assigned' | 'enrolled';\n\nconst MATRIX: Record<Role, Partial<Record<Permission, Capability>>> = {\n owner: {\n manage_billing: true,\n manage_org_settings: true,\n manage_users: true,\n create_course: true,\n edit_assigned_course: true,\n view_student_progress: true,\n moderate: true,\n },\n admin: {\n manage_org_settings: true,\n manage_users: true,\n create_course: true,\n edit_assigned_course: true,\n view_student_progress: true,\n moderate: true,\n },\n instructor: {\n edit_assigned_course: 'assigned',\n view_student_progress: 'assigned',\n moderate: true,\n },\n moderator: {\n moderate: true,\n consume_content: 'enrolled',\n },\n student: {\n consume_content: 'enrolled',\n },\n};\n\nexport function capability(role: Role, permission: Permission): Capability | false {\n return MATRIX[role][permission] ?? false;\n}\n\nexport function canModerate(role: Role): boolean {\n return capability(role, 'moderate') === true;\n}\n\n// Better Auth's role model is broader than ours: it keeps a built-in `member`\n// role and allows comma-joined multi-role strings. Normalize any incoming role\n// string to a single domain Role before persisting: `member` -> `instructor`, a\n// multi-role string -> its highest-privilege known role, unknown -> `instructor`.\nconst RANK: Record<Role, number> = { owner: 4, admin: 3, instructor: 2, moderator: 1, student: 0 };\n\nexport function normalizeRole(raw: string): Role {\n let best: Role = 'instructor';\n let bestRank = -1;\n for (const token of raw.split(',').map((t) => t.trim())) {\n const r: Role | null = isRole(token) ? token : token === 'member' ? 'instructor' : null;\n if (r && RANK[r] > bestRank) {\n best = r;\n bestRank = RANK[r];\n }\n }\n return best;\n}\n\n// Invites carry either a staff Role or this marker: the invited account is\n// a portal student, not an org member. Owned by the organizations domain — the\n// invite provider adapter reuses it to branch links, emails, and grants.\nexport const STUDENT_ROLE = 'student';\n"],"mappings":";AAQA,MAAa,QAAQ;CACnB;CACA;CACA;CACA;CACA;AACF;AAGA,MAAa,cAAc;CAAC;CAAS;CAAS;AAAY;AAI1D,SAAgB,OAAO,OAA8B;CACnD,OAAQ,MAA4B,SAAS,KAAK;AACpD;AAEA,SAAgB,YAAY,OAAmC;CAC7D,OAAQ,YAAkC,SAAS,KAAK;AAC1D;AAEA,SAAgB,UAAU,OAAqB;CAC7C,IAAI,CAAC,OAAO,KAAK,GACf,MAAM,IAAI,MAAM,iBAAiB,OAAO;CAE1C,OAAO;AACT;AAqBA,MAAM,SAAgE;CACpE,OAAO;EACL,gBAAgB;EAChB,qBAAqB;EACrB,cAAc;EACd,eAAe;EACf,sBAAsB;EACtB,uBAAuB;EACvB,UAAU;CACZ;CACA,OAAO;EACL,qBAAqB;EACrB,cAAc;EACd,eAAe;EACf,sBAAsB;EACtB,uBAAuB;EACvB,UAAU;CACZ;CACA,YAAY;EACV,sBAAsB;EACtB,uBAAuB;EACvB,UAAU;CACZ;CACA,WAAW;EACT,UAAU;EACV,iBAAiB;CACnB;CACA,SAAS,EACP,iBAAiB,WACnB;AACF;AAEA,SAAgB,WAAW,MAAY,YAA4C;CACjF,OAAO,OAAO,KAAK,CAAC,eAAe;AACrC;AAEA,SAAgB,YAAY,MAAqB;CAC/C,OAAO,WAAW,MAAM,UAAU,MAAM;AAC1C;AAMA,MAAM,OAA6B;CAAE,OAAO;CAAG,OAAO;CAAG,YAAY;CAAG,WAAW;CAAG,SAAS;AAAE;AAEjG,SAAgB,cAAc,KAAmB;CAC/C,IAAI,OAAa;CACjB,IAAI,WAAW;CACf,KAAK,MAAM,SAAS,IAAI,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,KAAK,CAAC,GAAG;EACvD,MAAM,IAAiB,OAAO,KAAK,IAAI,QAAQ,UAAU,WAAW,eAAe;EACnF,IAAI,KAAK,KAAK,KAAK,UAAU;GAC3B,OAAO;GACP,WAAW,KAAK;EAClB;CACF;CACA,OAAO;AACT;AAKA,MAAa,eAAe"}
|