@headless-lms/core 0.1.0 → 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.
- package/dist/assets/events.d.ts +90 -9
- package/dist/assets/events.js +19 -3
- package/dist/assets/events.js.map +1 -1
- package/dist/assets/file-urls.d.ts +20 -0
- package/dist/assets/file-urls.js +25 -0
- package/dist/assets/file-urls.js.map +1 -0
- package/dist/assets/index.d.ts +5 -4
- package/dist/assets/index.js +3 -2
- package/dist/assets/model.d.ts +2 -2
- package/dist/assets/ports.d.ts +45 -15
- package/dist/assets/service.d.ts +39 -7
- package/dist/assets/service.js +292 -37
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.d.ts +12 -15
- package/dist/automations/actions.js +188 -30
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.d.ts +5 -1
- package/dist/automations/catalog.js +514 -197
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/community-derivations.d.ts +14 -0
- package/dist/automations/community-derivations.js +439 -0
- package/dist/automations/community-derivations.js.map +1 -0
- package/dist/automations/community-mail.d.ts +112 -0
- package/dist/automations/community-mail.js +90 -0
- package/dist/automations/community-mail.js.map +1 -0
- package/dist/automations/community-notifications.d.ts +16 -0
- package/dist/automations/community-notifications.js +84 -0
- package/dist/automations/community-notifications.js.map +1 -0
- package/dist/automations/events.d.ts +74 -24
- package/dist/automations/events.js +5 -5
- package/dist/automations/events.js.map +1 -1
- package/dist/automations/index.d.ts +7 -5
- package/dist/automations/index.js +3 -2
- package/dist/automations/model.d.ts +20 -3
- package/dist/automations/model.js +36 -3
- package/dist/automations/model.js.map +1 -1
- package/dist/automations/ports.d.ts +27 -8
- package/dist/automations/schedule.d.ts +6 -0
- package/dist/automations/schedule.js +41 -0
- package/dist/automations/schedule.js.map +1 -0
- package/dist/automations/seeds.d.ts +17 -0
- package/dist/automations/seeds.js +235 -0
- package/dist/automations/seeds.js.map +1 -0
- package/dist/automations/service.d.ts +41 -6
- package/dist/automations/service.js +542 -59
- package/dist/automations/service.js.map +1 -1
- package/dist/automations/types.d.ts +2 -2
- package/dist/automations/webhook-signing.d.ts +7 -0
- package/dist/automations/webhook-signing.js +22 -0
- package/dist/automations/webhook-signing.js.map +1 -0
- package/dist/content/events.d.ts +1013 -25
- package/dist/content/events.js +168 -19
- package/dist/content/events.js.map +1 -1
- package/dist/content/index.d.ts +5 -5
- package/dist/content/index.js +2 -2
- package/dist/content/model.d.ts +2 -2
- package/dist/content/ports.d.ts +109 -25
- package/dist/content/service.d.ts +68 -23
- package/dist/content/service.js +647 -60
- package/dist/content/service.js.map +1 -1
- package/dist/content/types.d.ts +2 -2
- package/dist/discussion/authors.d.ts +10 -0
- package/dist/discussion/authors.js +23 -0
- package/dist/discussion/authors.js.map +1 -0
- package/dist/discussion/chats.d.ts +104 -0
- package/dist/discussion/chats.js +798 -0
- package/dist/discussion/chats.js.map +1 -0
- package/dist/discussion/events.d.ts +633 -14
- package/dist/discussion/events.js +134 -5
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/index.d.ts +7 -3
- package/dist/discussion/index.js +4 -1
- package/dist/discussion/model.d.ts +1 -2
- package/dist/discussion/ports.d.ts +288 -27
- package/dist/discussion/ports.js +6 -1
- package/dist/discussion/ports.js.map +1 -0
- package/dist/discussion/posts.d.ts +103 -0
- package/dist/discussion/posts.js +723 -0
- package/dist/discussion/posts.js.map +1 -0
- package/dist/discussion/service.d.ts +33 -11
- package/dist/discussion/service.js +297 -81
- package/dist/discussion/service.js.map +1 -1
- package/dist/entitlements/events.d.ts +264 -13
- package/dist/entitlements/events.js +49 -5
- package/dist/entitlements/events.js.map +1 -1
- package/dist/entitlements/index.d.ts +5 -4
- package/dist/entitlements/index.js +2 -1
- package/dist/entitlements/model.d.ts +3 -2
- package/dist/entitlements/ports.d.ts +124 -11
- package/dist/entitlements/rules.d.ts +12 -0
- package/dist/entitlements/rules.js +17 -0
- package/dist/entitlements/rules.js.map +1 -0
- package/dist/entitlements/service.d.ts +54 -3
- package/dist/entitlements/service.js +473 -2
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/access.d.ts +6 -0
- package/dist/identity/access.js +7 -0
- package/dist/identity/access.js.map +1 -0
- package/dist/identity/events.d.ts +17 -3
- package/dist/identity/events.js +12 -2
- package/dist/identity/events.js.map +1 -1
- package/dist/identity/index.d.ts +6 -4
- package/dist/identity/index.js +3 -1
- package/dist/identity/model.d.ts +2 -1
- package/dist/identity/notifications.d.ts +27 -0
- package/dist/identity/notifications.js +101 -0
- package/dist/identity/notifications.js.map +1 -0
- package/dist/identity/ports.d.ts +76 -3
- package/dist/identity/service.d.ts +17 -3
- package/dist/identity/service.js +73 -5
- package/dist/identity/service.js.map +1 -1
- package/dist/integrations/events.d.ts +27 -3
- package/dist/integrations/events.js +15 -3
- package/dist/integrations/events.js.map +1 -1
- package/dist/integrations/model.js +1 -1
- package/dist/integrations/model.js.map +1 -1
- package/dist/integrations/service.js +2 -2
- package/dist/integrations/service.js.map +1 -1
- package/dist/organizations/access-scopes.d.ts +19 -0
- package/dist/organizations/access-scopes.js +89 -0
- package/dist/organizations/access-scopes.js.map +1 -0
- package/dist/organizations/events.d.ts +234 -25
- package/dist/organizations/events.js +58 -9
- package/dist/organizations/events.js.map +1 -1
- package/dist/organizations/index.d.ts +8 -7
- package/dist/organizations/index.js +4 -3
- package/dist/organizations/members.d.ts +8 -5
- package/dist/organizations/members.js.map +1 -1
- package/dist/organizations/ports.d.ts +20 -21
- package/dist/organizations/roles.d.ts +6 -10
- package/dist/organizations/roles.js +19 -7
- package/dist/organizations/roles.js.map +1 -1
- package/dist/organizations/service.d.ts +28 -14
- package/dist/organizations/service.js +274 -105
- package/dist/organizations/service.js.map +1 -1
- package/dist/organizations/types.d.ts +2 -11
- package/dist/progress/events.d.ts +34 -2
- package/dist/progress/events.js +15 -2
- package/dist/progress/events.js.map +1 -1
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/ports.d.ts +6 -1
- package/dist/progress/service.d.ts +5 -3
- package/dist/progress/service.js +9 -3
- package/dist/progress/service.js.map +1 -1
- package/dist/reporting/admin/index.d.ts +4 -0
- package/dist/reporting/admin/index.js +2 -0
- package/dist/reporting/admin/model.d.ts +108 -0
- package/dist/reporting/admin/ports.d.ts +27 -0
- package/dist/reporting/admin/service.d.ts +22 -0
- package/dist/reporting/admin/service.js +38 -0
- package/dist/reporting/admin/service.js.map +1 -0
- package/dist/reporting/member/index.d.ts +5 -0
- package/dist/reporting/member/index.js +2 -0
- package/dist/reporting/member/model.d.ts +124 -0
- package/dist/reporting/member/ports.d.ts +45 -0
- package/dist/reporting/member/service.d.ts +73 -0
- package/dist/reporting/member/service.js +286 -0
- package/dist/reporting/member/service.js.map +1 -0
- package/dist/shared/actor.d.ts +9 -0
- package/dist/shared/errors.d.ts +7 -1
- package/dist/shared/errors.js +10 -1
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/events.d.ts +5 -3
- package/dist/shared/events.js +1 -0
- package/dist/shared/events.js.map +1 -1
- package/dist/shared/id.d.ts +12 -0
- package/dist/shared/id.js +13 -1
- package/dist/shared/id.js.map +1 -1
- package/dist/shared/mailer.d.ts +6 -1
- package/dist/shared/mailer.js.map +1 -1
- package/dist/shared/network.d.ts +10 -0
- package/dist/shared/network.js +211 -0
- package/dist/shared/network.js.map +1 -0
- package/dist/shared/settings.js.map +1 -1
- package/dist/types/assets.d.ts +2 -2
- package/dist/types/automations.d.ts +21 -2
- package/dist/types/content.d.ts +2 -2
- package/dist/types/discussion.d.ts +12 -3
- package/dist/types/email-templates.d.ts +124 -1
- package/dist/types/entitlements.d.ts +2 -2
- package/dist/types/events.d.ts +3 -3
- package/dist/types/identity.d.ts +48 -2
- package/dist/types/index.d.ts +10 -10
- package/dist/types/organizations.d.ts +2 -2
- package/dist/types/ports.d.ts +7 -1
- package/dist/types/schemas/assets.d.ts +67 -36
- package/dist/types/schemas/assets.js +19 -13
- package/dist/types/schemas/assets.js.map +1 -1
- package/dist/types/schemas/automations.d.ts +88 -4
- package/dist/types/schemas/automations.js +46 -6
- package/dist/types/schemas/automations.js.map +1 -1
- package/dist/types/schemas/content.d.ts +385 -20
- package/dist/types/schemas/content.js +178 -15
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/credentials.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +379 -4
- package/dist/types/schemas/discussion.js +209 -7
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/entitlements.d.ts +44 -4
- package/dist/types/schemas/entitlements.js +32 -4
- package/dist/types/schemas/entitlements.js.map +1 -1
- package/dist/types/schemas/identity.d.ts +115 -5
- package/dist/types/schemas/identity.js +59 -6
- package/dist/types/schemas/identity.js.map +1 -1
- package/dist/types/schemas/index.d.ts +9 -9
- package/dist/types/schemas/index.js +9 -9
- package/dist/types/schemas/integrations.js.map +1 -1
- package/dist/types/schemas/organizations.d.ts +27 -5
- package/dist/types/schemas/organizations.js +14 -7
- package/dist/types/schemas/organizations.js.map +1 -1
- package/dist/types/schemas/outbox.d.ts +1 -0
- package/dist/types/schemas/outbox.js +1 -0
- package/dist/types/schemas/outbox.js.map +1 -1
- package/dist/types/schemas/progress.d.ts +1 -0
- package/dist/types/schemas/progress.js +1 -0
- package/dist/types/schemas/progress.js.map +1 -1
- package/dist/types/schemas/settings.js.map +1 -1
- package/dist/types/schemas/shared.d.ts +4 -1
- package/dist/types/schemas/shared.js +7 -1
- package/dist/types/schemas/shared.js.map +1 -1
- package/dist/types/shared.d.ts +1 -1
- package/package.json +2 -2
- package/dist/reporting/dashboard/index.d.ts +0 -4
- package/dist/reporting/dashboard/index.js +0 -2
- package/dist/reporting/dashboard/model.d.ts +0 -14
- package/dist/reporting/dashboard/ports.d.ts +0 -13
- package/dist/reporting/dashboard/service.d.ts +0 -19
- package/dist/reporting/dashboard/service.js +0 -20
- package/dist/reporting/dashboard/service.js.map +0 -1
- package/dist/reporting/learn/index.d.ts +0 -5
- package/dist/reporting/learn/index.js +0 -2
- package/dist/reporting/learn/model.d.ts +0 -23
- package/dist/reporting/learn/ports.d.ts +0 -42
- package/dist/reporting/learn/service.d.ts +0 -48
- package/dist/reporting/learn/service.js +0 -111
- package/dist/reporting/learn/service.js.map +0 -1
- package/dist/reporting/students/index.d.ts +0 -4
- package/dist/reporting/students/index.js +0 -2
- package/dist/reporting/students/model.d.ts +0 -31
- package/dist/reporting/students/ports.d.ts +0 -13
- package/dist/reporting/students/ports.js +0 -1
- package/dist/reporting/students/service.d.ts +0 -19
- package/dist/reporting/students/service.js +0 -20
- package/dist/reporting/students/service.js.map +0 -1
- /package/dist/reporting/{dashboard → admin}/model.js +0 -0
- /package/dist/reporting/{dashboard → admin}/ports.js +0 -0
- /package/dist/reporting/{learn → member}/model.js +0 -0
- /package/dist/reporting/{learn → member}/ports.js +0 -0
- /package/dist/{reporting/students/model.js → shared/actor.js} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ProvisionUserInput } from "../types/schemas/identity.js";
|
|
2
|
-
import { AcceptInviteInput, AddOrgUserInput, CreateInviteInput, CreateOrgUserInput, InviteRole, NewOrganizationInput, OrgUserStatus, Organization } from "../types/schemas/organizations.js";
|
|
2
|
+
import { AcceptInviteInput, AddOrgUserInput, CreateInviteInput, CreateOrgUserInput, InviteRole, NewOrganizationInput, OrgUserStatus, Organization, UpdateOrgUserInput } from "../types/schemas/organizations.js";
|
|
3
3
|
import { InviteId, OrgUserId, OrganizationId } from "../types/organizations.js";
|
|
4
4
|
import "../types/index.js";
|
|
5
5
|
//#region src/organizations/types.d.ts
|
|
@@ -20,20 +20,11 @@ interface UnlinkOrgUserInput {
|
|
|
20
20
|
orgExternalId: string;
|
|
21
21
|
userExternalId: string;
|
|
22
22
|
}
|
|
23
|
-
interface UpdatePersonInput {
|
|
24
|
-
firstName?: string;
|
|
25
|
-
lastName?: string;
|
|
26
|
-
email?: string;
|
|
27
|
-
}
|
|
28
|
-
interface UpdateStudentInput extends UpdatePersonInput {
|
|
29
|
-
orgId: string;
|
|
30
|
-
orgUserId: string;
|
|
31
|
-
}
|
|
32
23
|
interface ResendStudentInviteInput {
|
|
33
24
|
orgId: string;
|
|
34
25
|
orgUserId: string;
|
|
35
26
|
inviterUserId: string;
|
|
36
27
|
}
|
|
37
28
|
//#endregion
|
|
38
|
-
export { type AcceptInviteInput, type AddOrgUserInput, type CreateInviteInput, type CreateOrgUserInput, CreateOrganizationInput, type InviteId, type InviteRole, LinkOrgUserInput, type NewOrganizationInput, type OrgUserId, type OrgUserStatus, type OrganizationId, type ProvisionUserInput, ResendStudentInviteInput, UnlinkOrgUserInput,
|
|
29
|
+
export { type AcceptInviteInput, type AddOrgUserInput, type CreateInviteInput, type CreateOrgUserInput, CreateOrganizationInput, type InviteId, type InviteRole, LinkOrgUserInput, type NewOrganizationInput, type OrgUserId, type OrgUserStatus, type OrganizationId, type ProvisionUserInput, ResendStudentInviteInput, UnlinkOrgUserInput, type UpdateOrgUserInput, UpdateOrganizationInput };
|
|
39
30
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -12,6 +12,7 @@ declare const progressEvents: {
|
|
|
12
12
|
startedAt: Date;
|
|
13
13
|
position: JsonValue;
|
|
14
14
|
completedAt: Date | null;
|
|
15
|
+
createdAt: Date;
|
|
15
16
|
updatedAt: Date;
|
|
16
17
|
}, "progress.record.started", 1>, {
|
|
17
18
|
id: string;
|
|
@@ -22,8 +23,23 @@ declare const progressEvents: {
|
|
|
22
23
|
startedAt: unknown;
|
|
23
24
|
position: unknown;
|
|
24
25
|
completedAt: unknown;
|
|
26
|
+
createdAt: unknown;
|
|
25
27
|
updatedAt: unknown;
|
|
26
|
-
}
|
|
28
|
+
}, import("zod").ZodObject<{
|
|
29
|
+
orgId: import("zod").ZodString;
|
|
30
|
+
id: import("zod").ZodString;
|
|
31
|
+
createdAt: import("zod").ZodCoercedDate<unknown>;
|
|
32
|
+
updatedAt: import("zod").ZodCoercedDate<unknown>;
|
|
33
|
+
startedAt: import("zod").ZodCoercedDate<unknown>;
|
|
34
|
+
orgUserId: import("zod").ZodString;
|
|
35
|
+
targetType: import("zod").ZodEnum<{
|
|
36
|
+
course: "course";
|
|
37
|
+
activity: "activity";
|
|
38
|
+
module: "module";
|
|
39
|
+
}>;
|
|
40
|
+
targetId: import("zod").ZodString;
|
|
41
|
+
completedAt: import("zod").ZodNullable<import("zod").ZodCoercedDate<unknown>>;
|
|
42
|
+
}, import("zod/v4/core").$strip>>;
|
|
27
43
|
progressCompleted: RuntimeEventDefinition<VersionedDomainEvent<{
|
|
28
44
|
id: string;
|
|
29
45
|
orgId: string;
|
|
@@ -33,6 +49,7 @@ declare const progressEvents: {
|
|
|
33
49
|
startedAt: Date;
|
|
34
50
|
position: JsonValue;
|
|
35
51
|
completedAt: Date | null;
|
|
52
|
+
createdAt: Date;
|
|
36
53
|
updatedAt: Date;
|
|
37
54
|
}, "progress.record.completed", 1>, {
|
|
38
55
|
id: string;
|
|
@@ -43,8 +60,23 @@ declare const progressEvents: {
|
|
|
43
60
|
startedAt: unknown;
|
|
44
61
|
position: unknown;
|
|
45
62
|
completedAt: unknown;
|
|
63
|
+
createdAt: unknown;
|
|
46
64
|
updatedAt: unknown;
|
|
47
|
-
}
|
|
65
|
+
}, import("zod").ZodObject<{
|
|
66
|
+
orgId: import("zod").ZodString;
|
|
67
|
+
id: import("zod").ZodString;
|
|
68
|
+
createdAt: import("zod").ZodCoercedDate<unknown>;
|
|
69
|
+
updatedAt: import("zod").ZodCoercedDate<unknown>;
|
|
70
|
+
startedAt: import("zod").ZodCoercedDate<unknown>;
|
|
71
|
+
orgUserId: import("zod").ZodString;
|
|
72
|
+
targetType: import("zod").ZodEnum<{
|
|
73
|
+
course: "course";
|
|
74
|
+
activity: "activity";
|
|
75
|
+
module: "module";
|
|
76
|
+
}>;
|
|
77
|
+
targetId: import("zod").ZodString;
|
|
78
|
+
completedAt: import("zod").ZodNullable<import("zod").ZodCoercedDate<unknown>>;
|
|
79
|
+
}, import("zod/v4/core").$strip>>;
|
|
48
80
|
};
|
|
49
81
|
type ProgressStarted = EventOf<typeof progressEvents.progressStarted>;
|
|
50
82
|
type ProgressCompleted = EventOf<typeof progressEvents.progressCompleted>;
|
package/dist/progress/events.js
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import { progressRecordSchema } from "../types/schemas/progress.js";
|
|
2
2
|
import { defineEvent } from "../shared/events.js";
|
|
3
3
|
//#region src/progress/events.ts
|
|
4
|
+
const publicProgressRecord = progressRecordSchema.pick({
|
|
5
|
+
id: true,
|
|
6
|
+
orgId: true,
|
|
7
|
+
orgUserId: true,
|
|
8
|
+
targetType: true,
|
|
9
|
+
targetId: true,
|
|
10
|
+
startedAt: true,
|
|
11
|
+
completedAt: true,
|
|
12
|
+
createdAt: true,
|
|
13
|
+
updatedAt: true
|
|
14
|
+
}).strip();
|
|
4
15
|
const progressEvents = {
|
|
5
16
|
progressStarted: defineEvent({
|
|
6
17
|
type: "progress.record.started",
|
|
7
18
|
version: 1,
|
|
8
|
-
data: progressRecordSchema
|
|
19
|
+
data: progressRecordSchema,
|
|
20
|
+
publicData: publicProgressRecord
|
|
9
21
|
}),
|
|
10
22
|
progressCompleted: defineEvent({
|
|
11
23
|
type: "progress.record.completed",
|
|
12
24
|
version: 1,
|
|
13
|
-
data: progressRecordSchema
|
|
25
|
+
data: progressRecordSchema,
|
|
26
|
+
publicData: publicProgressRecord
|
|
14
27
|
})
|
|
15
28
|
};
|
|
16
29
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","names":[],"sources":["../../src/progress/events.ts"],"sourcesContent":["import { progressRecordSchema } from '../types/schemas/index.js';\nimport {\n defineEvent,\n type EventOf,\n type EventOfValues,\n type NewDomainEvent,\n} from '../shared/ports.js';\n\nexport const progressEvents = {\n progressStarted: defineEvent({\n type: 'progress.record.started',\n version: 1,\n data: progressRecordSchema,\n }),\n progressCompleted: defineEvent({\n type: 'progress.record.completed',\n version: 1,\n data: progressRecordSchema,\n }),\n};\n\nexport type ProgressStarted = EventOf<typeof progressEvents.progressStarted>;\nexport type ProgressCompleted = EventOf<typeof progressEvents.progressCompleted>;\nexport type ProgressEvent = EventOfValues<typeof progressEvents>;\nexport type NewProgressEvent = NewDomainEvent<ProgressEvent>;\n"],"mappings":";;;AAQA,MAAa,iBAAiB;CAC5B,iBAAiB,YAAY;EAC3B,MAAM;EACN,SAAS;EACT,MAAM;
|
|
1
|
+
{"version":3,"file":"events.js","names":[],"sources":["../../src/progress/events.ts"],"sourcesContent":["import { progressRecordSchema } from '../types/schemas/index.js';\nimport {\n defineEvent,\n type EventOf,\n type EventOfValues,\n type NewDomainEvent,\n} from '../shared/ports.js';\n\nconst publicProgressRecord = progressRecordSchema\n .pick({\n id: true,\n orgId: true,\n orgUserId: true,\n targetType: true,\n targetId: true,\n startedAt: true,\n completedAt: true,\n createdAt: true,\n updatedAt: true,\n })\n .strip();\n\nexport const progressEvents = {\n progressStarted: defineEvent({\n type: 'progress.record.started',\n version: 1,\n data: progressRecordSchema,\n publicData: publicProgressRecord,\n }),\n progressCompleted: defineEvent({\n type: 'progress.record.completed',\n version: 1,\n data: progressRecordSchema,\n publicData: publicProgressRecord,\n }),\n};\n\nexport type ProgressStarted = EventOf<typeof progressEvents.progressStarted>;\nexport type ProgressCompleted = EventOf<typeof progressEvents.progressCompleted>;\nexport type ProgressEvent = EventOfValues<typeof progressEvents>;\nexport type NewProgressEvent = NewDomainEvent<ProgressEvent>;\n"],"mappings":";;;AAQA,MAAM,uBAAuB,qBAC1B,KAAK;CACJ,IAAI;CACJ,OAAO;CACP,WAAW;CACX,YAAY;CACZ,UAAU;CACV,WAAW;CACX,aAAa;CACb,WAAW;CACX,WAAW;AACb,CAAC,CAAC,CACD,MAAM;AAET,MAAa,iBAAiB;CAC5B,iBAAiB,YAAY;EAC3B,MAAM;EACN,SAAS;EACT,MAAM;EACN,YAAY;CACd,CAAC;CACD,mBAAmB,YAAY;EAC7B,MAAM;EACN,SAAS;EACT,MAAM;EACN,YAAY;CACd,CAAC;AACH"}
|
package/dist/progress/index.d.ts
CHANGED
|
@@ -3,6 +3,6 @@ import { ProgressId } from "../types/progress.js";
|
|
|
3
3
|
import { ProgressCompleted, ProgressEvent, ProgressStarted, progressEvents } from "./events.js";
|
|
4
4
|
import "./model.js";
|
|
5
5
|
import "./types.js";
|
|
6
|
-
import { ProgressRepository, ProgressService, ProgressUnitOfWork } from "./ports.js";
|
|
6
|
+
import { ContentAccessReader, ProgressRepository, ProgressService, ProgressUnitOfWork } from "./ports.js";
|
|
7
7
|
import { ProgressServiceImpl } from "./service.js";
|
|
8
|
-
export { type ProgressCompleted, type ProgressEvent, type ProgressId, type ProgressRecord, type ProgressReportItem, type ProgressRepository, type ProgressService, ProgressServiceImpl, type ProgressStarted, type ProgressTarget, type ProgressTargetType, type ProgressUnitOfWork, type ReportProgressInput, progressEvents };
|
|
8
|
+
export { type ContentAccessReader, type ProgressCompleted, type ProgressEvent, type ProgressId, type ProgressRecord, type ProgressReportItem, type ProgressRepository, type ProgressService, ProgressServiceImpl, type ProgressStarted, type ProgressTarget, type ProgressTargetType, type ProgressUnitOfWork, type ReportProgressInput, progressEvents };
|
package/dist/progress/ports.d.ts
CHANGED
|
@@ -25,6 +25,11 @@ interface ProgressRepository {
|
|
|
25
25
|
}): Promise<ProgressRecord[]>;
|
|
26
26
|
update(orgId: string, id: string, patch: Partial<Pick<ProgressRecord, 'position' | 'completedAt'>>): Promise<ProgressRecord | null>;
|
|
27
27
|
}
|
|
28
|
+
/** Access is resolved by entitlements — a report is only taken from a student
|
|
29
|
+
* who can open the course. */
|
|
30
|
+
interface ContentAccessReader {
|
|
31
|
+
hasAccess(orgId: string, orgUserId: string, contentId: string): Promise<boolean>;
|
|
32
|
+
}
|
|
28
33
|
/** Writes that emit events run through this scope so row + outbox entry commit
|
|
29
34
|
* in one transaction. */
|
|
30
35
|
interface ProgressWriteScope {
|
|
@@ -33,5 +38,5 @@ interface ProgressWriteScope {
|
|
|
33
38
|
}
|
|
34
39
|
type ProgressUnitOfWork = UnitOfWork<ProgressWriteScope>;
|
|
35
40
|
//#endregion
|
|
36
|
-
export { ProgressRepository, ProgressService, ProgressUnitOfWork, ProgressWriteScope };
|
|
41
|
+
export { ContentAccessReader, ProgressRepository, ProgressService, ProgressUnitOfWork, ProgressWriteScope };
|
|
37
42
|
//# sourceMappingURL=ports.d.ts.map
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { Logger } from "../types/ports.js";
|
|
2
2
|
import { ProgressRecord, ProgressTarget, ReportProgressInput } from "../types/schemas/progress.js";
|
|
3
3
|
import "../shared/ports.js";
|
|
4
|
-
import {
|
|
4
|
+
import { CourseManagementService } from "../content/ports.js";
|
|
5
5
|
import "../content/index.js";
|
|
6
6
|
import "./model.js";
|
|
7
7
|
import "./types.js";
|
|
8
|
-
import { ProgressRepository, ProgressService, ProgressUnitOfWork } from "./ports.js";
|
|
8
|
+
import { ContentAccessReader, ProgressRepository, ProgressService, ProgressUnitOfWork } from "./ports.js";
|
|
9
9
|
//#region src/progress/service.d.ts
|
|
10
10
|
type ProgressServiceParams = {
|
|
11
11
|
repo: ProgressRepository;
|
|
12
|
-
content:
|
|
12
|
+
content: CourseManagementService;
|
|
13
|
+
access: ContentAccessReader;
|
|
13
14
|
uow: ProgressUnitOfWork;
|
|
14
15
|
logger?: Logger;
|
|
15
16
|
};
|
|
16
17
|
declare class ProgressServiceImpl implements ProgressService {
|
|
17
18
|
private readonly repo;
|
|
18
19
|
private readonly content;
|
|
20
|
+
private readonly access;
|
|
19
21
|
private readonly uow;
|
|
20
22
|
private readonly logger;
|
|
21
23
|
constructor(params: ProgressServiceParams);
|
package/dist/progress/service.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { genId } from "../shared/id.js";
|
|
2
|
-
import { noopLogger } from "../shared/logger.js";
|
|
3
2
|
import { NotFoundError } from "../shared/errors.js";
|
|
3
|
+
import { noopLogger } from "../shared/logger.js";
|
|
4
4
|
import { progressEvents } from "./events.js";
|
|
5
5
|
//#region src/progress/service.ts
|
|
6
|
-
/** Mirrors reporting/
|
|
6
|
+
/** Mirrors reporting/member: `settings.published === false` is the only draft signal. */
|
|
7
7
|
function isActivityPublished(settings) {
|
|
8
8
|
return settings?.published !== false;
|
|
9
9
|
}
|
|
@@ -39,11 +39,13 @@ function completionSatisfied(settings, claimed) {
|
|
|
39
39
|
var ProgressServiceImpl = class {
|
|
40
40
|
repo;
|
|
41
41
|
content;
|
|
42
|
+
access;
|
|
42
43
|
uow;
|
|
43
44
|
logger;
|
|
44
45
|
constructor(params) {
|
|
45
46
|
this.repo = params.repo;
|
|
46
47
|
this.content = params.content;
|
|
48
|
+
this.access = params.access;
|
|
47
49
|
this.uow = params.uow;
|
|
48
50
|
this.logger = params.logger ?? noopLogger;
|
|
49
51
|
}
|
|
@@ -51,6 +53,8 @@ var ProgressServiceImpl = class {
|
|
|
51
53
|
const activity = await this.content.getActivity(orgId, input.activityId);
|
|
52
54
|
if (!activity || !isActivityPublished(activity.settings)) throw new NotFoundError("Activity", input.activityId);
|
|
53
55
|
const courseId = activity.courseId;
|
|
56
|
+
const course = await this.content.getCourse(orgId, courseId);
|
|
57
|
+
if (!course || course.status !== "published" || !await this.access.hasAccess(orgId, input.orgUserId, courseId)) throw new NotFoundError("Activity", input.activityId);
|
|
54
58
|
const modules = await this.content.listCourseModules(orgId, courseId);
|
|
55
59
|
const courseActivities = await this.content.listCourseActivities(orgId, courseId);
|
|
56
60
|
return this.uow.run(async (scope) => {
|
|
@@ -63,7 +67,7 @@ var ProgressServiceImpl = class {
|
|
|
63
67
|
const events = [];
|
|
64
68
|
let record = await this.ensureActivityRecord(orgId, input, scope, events);
|
|
65
69
|
const state = mergeReports(record.position, input.reports);
|
|
66
|
-
if (state.changed
|
|
70
|
+
if (state.changed) record = await scope.progress.update(orgId, record.id, { position: state.map }) ?? record;
|
|
67
71
|
if (!record.completedAt && completionSatisfied(activity.settings, state.claimed)) {
|
|
68
72
|
record = await scope.progress.update(orgId, record.id, { completedAt: /* @__PURE__ */ new Date() }) ?? record;
|
|
69
73
|
events.push(progressEvents.progressCompleted.make({
|
|
@@ -98,6 +102,7 @@ var ProgressServiceImpl = class {
|
|
|
98
102
|
startedAt: now,
|
|
99
103
|
position: null,
|
|
100
104
|
completedAt: null,
|
|
105
|
+
createdAt: now,
|
|
101
106
|
updatedAt: now
|
|
102
107
|
});
|
|
103
108
|
if (!record) {
|
|
@@ -149,6 +154,7 @@ var ProgressServiceImpl = class {
|
|
|
149
154
|
startedAt: now,
|
|
150
155
|
position: null,
|
|
151
156
|
completedAt: now,
|
|
157
|
+
createdAt: now,
|
|
152
158
|
updatedAt: now
|
|
153
159
|
});
|
|
154
160
|
if (inserted) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","names":[],"sources":["../../src/progress/service.ts"],"sourcesContent":["// progress context — service implementation (inbound port).\n// The frontend only reports usage; this service makes every completion\n// decision. One report batch runs in one UoW transaction: ensure the record,\n// merge item payloads into the per-subject state map, evaluate the activity's\n// completion rule, then module/course against current published structure —\n// appending events with the writes. The container is resolved from the\n// hierarchy (activity → module → course), never supplied by the caller.\n// Percentage/resume are derived by readers, never stored here.\nimport { genId } from '../shared/id.js';\nimport { NotFoundError } from '../shared/errors.js';\nimport type { ProgressRecord } from './model.js';\nimport type {\n ProgressRepository,\n ProgressService,\n ProgressUnitOfWork,\n ProgressWriteScope,\n} from './ports.js';\nimport type { ProgressReportItem, ProgressTarget, ReportProgressInput } from './types.js';\nimport { progressEvents, type NewProgressEvent } from './events.js';\nimport type { Activity, ContentService, Module } from '../content/index.js';\nimport type { JsonValue } from '../types/index.js';\nimport type { Logger } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\n\n/** Mirrors reporting/learn: `settings.published === false` is the only draft signal. */\nfunction isActivityPublished(settings: unknown): boolean {\n return (settings as { published?: boolean } | null)?.published !== false;\n}\n\n/** Fold a report batch into the record's per-subject state map. Reserved keys\n * (`asset` = subject, `completed` = claim) are stripped; the rest is the asset\n * type's own vocabulary, stored opaquely — latest report per subject wins. */\nfunction mergeReports(\n position: unknown,\n items: ProgressReportItem[],\n): { map: Record<string, JsonValue>; changed: boolean; claimed: boolean } {\n const map = { ...((position as Record<string, JsonValue> | null) ?? {}) };\n let changed = false;\n let claimed = false;\n for (const item of items) {\n const { asset, completed, ...payload } = item;\n if (completed === true) {\n claimed = true;\n }\n if (Object.keys(payload).length > 0) {\n map[asset ?? 'self'] = payload;\n changed = true;\n }\n }\n return { map, changed, claimed };\n}\n\n/** The completion-rule seam. Only `manual` ships: no authored rule → the\n * learner's claim decides. Authored rules are never satisfied until their\n * evaluators exist. Future rules evaluate against the accumulated state map. */\nfunction completionSatisfied(settings: unknown, claimed: boolean): boolean {\n const rule = (settings as { completion?: { rule?: string } } | null)?.completion?.rule;\n if (!rule || rule === 'manual') {\n return claimed;\n }\n return false;\n}\n\nexport type ProgressServiceParams = {\n repo: ProgressRepository;\n content: ContentService;\n uow: ProgressUnitOfWork;\n logger?: Logger;\n};\n\nexport class ProgressServiceImpl implements ProgressService {\n private readonly repo: ProgressRepository;\n private readonly content: ContentService;\n private readonly uow: ProgressUnitOfWork;\n private readonly logger: Logger;\n\n constructor(params: ProgressServiceParams) {\n this.repo = params.repo;\n this.content = params.content;\n this.uow = params.uow;\n this.logger = params.logger ?? noopLogger;\n }\n\n async report(orgId: string, input: ReportProgressInput): Promise<ProgressRecord> {\n const activity = await this.content.getActivity(orgId, input.activityId);\n if (!activity || !isActivityPublished(activity.settings)) {\n throw new NotFoundError('Activity', input.activityId);\n }\n const courseId = activity.courseId;\n const modules = await this.content.listCourseModules(orgId, courseId);\n const courseActivities = await this.content.listCourseActivities(orgId, courseId);\n return this.uow.run(async (scope) => {\n // Serializes concurrent reports for this student+course (locks are tx-scoped;\n // both racers lock in the same order, the loser waits and then sees committed state).\n const lockIds = [\n ...courseActivities.filter((a) => isActivityPublished(a.settings)).map((a) => a.id),\n ...modules.map((m) => m.id),\n courseId,\n ];\n await scope.progress.findByTargets(orgId, input.orgUserId, lockIds, { forUpdate: true });\n const events: NewProgressEvent[] = [];\n let record = await this.ensureActivityRecord(orgId, input, scope, events);\n const state = mergeReports(record.position, input.reports);\n if (state.changed && !record.completedAt) {\n record = (await scope.progress.update(orgId, record.id, { position: state.map })) ?? record;\n }\n if (!record.completedAt && completionSatisfied(activity.settings, state.claimed)) {\n record =\n (await scope.progress.update(orgId, record.id, {\n completedAt: new Date(),\n })) ?? record;\n events.push(\n progressEvents.progressCompleted.make({ orgId, data: record }),\n );\n await this.completeContainers(orgId, input, courseId, modules, courseActivities, scope, events);\n this.logger.info('progress completed', { orgId, recordId: record.id });\n }\n if (events.length > 0) {\n await scope.outbox.append(events);\n }\n return record;\n });\n }\n\n private async ensureActivityRecord(\n orgId: string,\n input: ReportProgressInput,\n scope: ProgressWriteScope,\n events: NewProgressEvent[],\n ): Promise<ProgressRecord> {\n const target: ProgressTarget = {\n orgUserId: input.orgUserId,\n targetType: 'activity',\n targetId: input.activityId,\n };\n const existing = await scope.progress.findByTarget(orgId, target);\n if (existing) {\n return existing;\n }\n const now = new Date();\n const record = await scope.progress.insert(orgId, {\n id: genId('progress'),\n orgId,\n orgUserId: input.orgUserId,\n targetType: 'activity',\n targetId: input.activityId,\n startedAt: now,\n position: null,\n completedAt: null,\n updatedAt: now,\n });\n if (!record) {\n // Lost a concurrent first-touch insert: the winner owns the row and emitted\n // progress.started. Return its row without re-emitting.\n const winner = await scope.progress.findByTarget(orgId, target);\n if (!winner) {\n throw new Error('progress record vanished after insert conflict');\n }\n return winner;\n }\n events.push(progressEvents.progressStarted.make({ orgId, data: record }));\n this.logger.info('progress started', { orgId, recordId: record.id });\n return record;\n }\n\n /** After an activity completes: newly-complete containers get their records\n * (created complete) and a progress.completed event — same transaction. */\n private async completeContainers(\n orgId: string,\n input: ReportProgressInput,\n courseId: string,\n modules: Module[],\n courseActivities: Activity[],\n scope: ProgressWriteScope,\n events: NewProgressEvent[],\n ): Promise<void> {\n const published = courseActivities.filter((a) => isActivityPublished(a.settings));\n const byModule = modules.map((m) => ({\n id: m.id,\n activityIds: published.filter((a) => a.moduleId === m.id).map((a) => a.id),\n }));\n const allIds = byModule.flatMap((m) => m.activityIds);\n const records = await scope.progress.findByTargets(orgId, input.orgUserId, allIds);\n const done = new Set(\n records.filter((r) => r.targetType === 'activity' && r.completedAt).map((r) => r.targetId),\n );\n const containing = byModule.find((m) => m.activityIds.includes(input.activityId));\n if (containing && containing.activityIds.every((id) => done.has(id))) {\n await this.ensureContainerComplete(orgId, input, 'module', containing.id, scope, events);\n }\n if (allIds.length > 0 && allIds.every((id) => done.has(id))) {\n await this.ensureContainerComplete(orgId, input, 'course', courseId, scope, events);\n }\n }\n\n private async ensureContainerComplete(\n orgId: string,\n input: ReportProgressInput,\n targetType: 'module' | 'course',\n targetId: string,\n scope: ProgressWriteScope,\n events: NewProgressEvent[],\n ): Promise<void> {\n const target: ProgressTarget = { orgUserId: input.orgUserId, targetType, targetId };\n let existing = await scope.progress.findByTarget(orgId, target);\n if (existing?.completedAt) {\n return;\n }\n if (!existing) {\n const now = new Date();\n const inserted = await scope.progress.insert(orgId, {\n id: genId('progress'),\n orgId,\n orgUserId: input.orgUserId,\n targetType,\n targetId,\n startedAt: now,\n position: null,\n completedAt: now,\n updatedAt: now,\n });\n if (inserted) {\n events.push(\n progressEvents.progressCompleted.make({ orgId, data: inserted }),\n );\n return;\n }\n // Lost a concurrent insert: re-read the winner's row.\n existing = await scope.progress.findByTarget(orgId, target);\n if (existing?.completedAt) {\n return;\n }\n if (!existing) {\n throw new Error('progress container record vanished after insert conflict');\n }\n }\n const record =\n (await scope.progress.update(orgId, existing.id, {\n completedAt: new Date(),\n })) ?? existing;\n events.push(progressEvents.progressCompleted.make({ orgId, data: record }));\n }\n\n get(orgId: string, target: ProgressTarget): Promise<ProgressRecord | null> {\n return this.repo.findByTarget(orgId, target);\n }\n\n listByTargets(orgId: string, orgUserId: string, targetIds: string[]): Promise<ProgressRecord[]> {\n return this.repo.findByTargets(orgId, orgUserId, targetIds);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service.js","names":[],"sources":["../../src/progress/service.ts"],"sourcesContent":["// progress context — service implementation (inbound port).\n// The frontend only reports usage; this service makes every completion\n// decision. One report batch runs in one UoW transaction: ensure the record,\n// merge item payloads into the per-subject state map, evaluate the activity's\n// completion rule, then module/course against current published structure —\n// appending events with the writes. The container is resolved from the\n// hierarchy (activity → module → course), never supplied by the caller.\n// Percentage/resume are derived by readers, never stored here.\nimport { genId } from '../shared/id.js';\nimport { NotFoundError } from '../shared/errors.js';\nimport type { ProgressRecord } from './model.js';\nimport type {\n ContentAccessReader,\n ProgressRepository,\n ProgressService,\n ProgressUnitOfWork,\n ProgressWriteScope,\n} from './ports.js';\nimport type { ProgressReportItem, ProgressTarget, ReportProgressInput } from './types.js';\nimport { progressEvents, type NewProgressEvent } from './events.js';\nimport type { Activity, CourseManagementService, Module } from '../content/index.js';\nimport type { JsonValue } from '../types/index.js';\nimport type { Logger } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\n\n/** Mirrors reporting/member: `settings.published === false` is the only draft signal. */\nfunction isActivityPublished(settings: unknown): boolean {\n return (settings as { published?: boolean } | null)?.published !== false;\n}\n\n/** Fold a report batch into the record's per-subject state map. Reserved keys\n * (`asset` = subject, `completed` = claim) are stripped; the rest is the asset\n * type's own vocabulary, stored opaquely — latest report per subject wins. */\nfunction mergeReports(\n position: unknown,\n items: ProgressReportItem[],\n): { map: Record<string, JsonValue>; changed: boolean; claimed: boolean } {\n const map = { ...((position as Record<string, JsonValue> | null) ?? {}) };\n let changed = false;\n let claimed = false;\n for (const item of items) {\n const { asset, completed, ...payload } = item;\n if (completed === true) {\n claimed = true;\n }\n if (Object.keys(payload).length > 0) {\n map[asset ?? 'self'] = payload;\n changed = true;\n }\n }\n return { map, changed, claimed };\n}\n\n/** The completion-rule seam. Only `manual` ships: no authored rule → the\n * learner's claim decides. Authored rules are never satisfied until their\n * evaluators exist. Future rules evaluate against the accumulated state map. */\nfunction completionSatisfied(settings: unknown, claimed: boolean): boolean {\n const rule = (settings as { completion?: { rule?: string } } | null)?.completion?.rule;\n if (!rule || rule === 'manual') {\n return claimed;\n }\n return false;\n}\n\nexport type ProgressServiceParams = {\n repo: ProgressRepository;\n content: CourseManagementService;\n access: ContentAccessReader;\n uow: ProgressUnitOfWork;\n logger?: Logger;\n};\n\nexport class ProgressServiceImpl implements ProgressService {\n private readonly repo: ProgressRepository;\n private readonly content: CourseManagementService;\n private readonly access: ContentAccessReader;\n private readonly uow: ProgressUnitOfWork;\n private readonly logger: Logger;\n\n constructor(params: ProgressServiceParams) {\n this.repo = params.repo;\n this.content = params.content;\n this.access = params.access;\n this.uow = params.uow;\n this.logger = params.logger ?? noopLogger;\n }\n\n async report(orgId: string, input: ReportProgressInput): Promise<ProgressRecord> {\n const activity = await this.content.getActivity(orgId, input.activityId);\n if (!activity || !isActivityPublished(activity.settings)) {\n throw new NotFoundError('Activity', input.activityId);\n }\n const courseId = activity.courseId;\n const course = await this.content.getCourse(orgId, courseId);\n if (\n !course ||\n course.status !== 'published' ||\n !(await this.access.hasAccess(orgId, input.orgUserId, courseId))\n ) {\n throw new NotFoundError('Activity', input.activityId);\n }\n const modules = await this.content.listCourseModules(orgId, courseId);\n const courseActivities = await this.content.listCourseActivities(orgId, courseId);\n return this.uow.run(async (scope) => {\n // Serializes concurrent reports for this student+course (locks are tx-scoped;\n // both racers lock in the same order, the loser waits and then sees committed state).\n const lockIds = [\n ...courseActivities.filter((a) => isActivityPublished(a.settings)).map((a) => a.id),\n ...modules.map((m) => m.id),\n courseId,\n ];\n await scope.progress.findByTargets(orgId, input.orgUserId, lockIds, { forUpdate: true });\n const events: NewProgressEvent[] = [];\n let record = await this.ensureActivityRecord(orgId, input, scope, events);\n const state = mergeReports(record.position, input.reports);\n if (state.changed) {\n record = (await scope.progress.update(orgId, record.id, { position: state.map })) ?? record;\n }\n if (!record.completedAt && completionSatisfied(activity.settings, state.claimed)) {\n record =\n (await scope.progress.update(orgId, record.id, {\n completedAt: new Date(),\n })) ?? record;\n events.push(progressEvents.progressCompleted.make({ orgId, data: record }));\n await this.completeContainers(\n orgId,\n input,\n courseId,\n modules,\n courseActivities,\n scope,\n events,\n );\n this.logger.info('progress completed', { orgId, recordId: record.id });\n }\n if (events.length > 0) {\n await scope.outbox.append(events);\n }\n return record;\n });\n }\n\n private async ensureActivityRecord(\n orgId: string,\n input: ReportProgressInput,\n scope: ProgressWriteScope,\n events: NewProgressEvent[],\n ): Promise<ProgressRecord> {\n const target: ProgressTarget = {\n orgUserId: input.orgUserId,\n targetType: 'activity',\n targetId: input.activityId,\n };\n const existing = await scope.progress.findByTarget(orgId, target);\n if (existing) {\n return existing;\n }\n const now = new Date();\n const record = await scope.progress.insert(orgId, {\n id: genId('progress'),\n orgId,\n orgUserId: input.orgUserId,\n targetType: 'activity',\n targetId: input.activityId,\n startedAt: now,\n position: null,\n completedAt: null,\n createdAt: now,\n updatedAt: now,\n });\n if (!record) {\n // Lost a concurrent first-touch insert: the winner owns the row and emitted\n // progress.started. Return its row without re-emitting.\n const winner = await scope.progress.findByTarget(orgId, target);\n if (!winner) {\n throw new Error('progress record vanished after insert conflict');\n }\n return winner;\n }\n events.push(progressEvents.progressStarted.make({ orgId, data: record }));\n this.logger.info('progress started', { orgId, recordId: record.id });\n return record;\n }\n\n /** After an activity completes: newly-complete containers get their records\n * (created complete) and a progress.completed event — same transaction. */\n private async completeContainers(\n orgId: string,\n input: ReportProgressInput,\n courseId: string,\n modules: Module[],\n courseActivities: Activity[],\n scope: ProgressWriteScope,\n events: NewProgressEvent[],\n ): Promise<void> {\n const published = courseActivities.filter((a) => isActivityPublished(a.settings));\n const byModule = modules.map((m) => ({\n id: m.id,\n activityIds: published.filter((a) => a.moduleId === m.id).map((a) => a.id),\n }));\n const allIds = byModule.flatMap((m) => m.activityIds);\n const records = await scope.progress.findByTargets(orgId, input.orgUserId, allIds);\n const done = new Set(\n records.filter((r) => r.targetType === 'activity' && r.completedAt).map((r) => r.targetId),\n );\n const containing = byModule.find((m) => m.activityIds.includes(input.activityId));\n if (containing && containing.activityIds.every((id) => done.has(id))) {\n await this.ensureContainerComplete(orgId, input, 'module', containing.id, scope, events);\n }\n if (allIds.length > 0 && allIds.every((id) => done.has(id))) {\n await this.ensureContainerComplete(orgId, input, 'course', courseId, scope, events);\n }\n }\n\n private async ensureContainerComplete(\n orgId: string,\n input: ReportProgressInput,\n targetType: 'module' | 'course',\n targetId: string,\n scope: ProgressWriteScope,\n events: NewProgressEvent[],\n ): Promise<void> {\n const target: ProgressTarget = { orgUserId: input.orgUserId, targetType, targetId };\n let existing = await scope.progress.findByTarget(orgId, target);\n if (existing?.completedAt) {\n return;\n }\n if (!existing) {\n const now = new Date();\n const inserted = await scope.progress.insert(orgId, {\n id: genId('progress'),\n orgId,\n orgUserId: input.orgUserId,\n targetType,\n targetId,\n startedAt: now,\n position: null,\n completedAt: now,\n createdAt: now,\n updatedAt: now,\n });\n if (inserted) {\n events.push(progressEvents.progressCompleted.make({ orgId, data: inserted }));\n return;\n }\n // Lost a concurrent insert: re-read the winner's row.\n existing = await scope.progress.findByTarget(orgId, target);\n if (existing?.completedAt) {\n return;\n }\n if (!existing) {\n throw new Error('progress container record vanished after insert conflict');\n }\n }\n const record =\n (await scope.progress.update(orgId, existing.id, {\n completedAt: new Date(),\n })) ?? existing;\n events.push(progressEvents.progressCompleted.make({ orgId, data: record }));\n }\n\n get(orgId: string, target: ProgressTarget): Promise<ProgressRecord | null> {\n return this.repo.findByTarget(orgId, target);\n }\n\n listByTargets(orgId: string, orgUserId: string, targetIds: string[]): Promise<ProgressRecord[]> {\n return this.repo.findByTargets(orgId, orgUserId, targetIds);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { CommunityAnalytics, CommunityMember, CommunityMembersQuery, CourseActivityEngagement, CourseAnalytics, CourseEnrollmentPoint, EnrollmentPoint, OverviewStats, Page, Student, StudentAnalytics, StudentCourseProgress, StudentsQuery } from "./model.js";
|
|
2
|
+
import { AdminReportRepository, AdminReportService } from "./ports.js";
|
|
3
|
+
import { AdminReportServiceImpl } from "./service.js";
|
|
4
|
+
export { type AdminReportRepository, type AdminReportService, AdminReportServiceImpl, type CommunityAnalytics, type CommunityMember, type CommunityMembersQuery, type CourseActivityEngagement, type CourseAnalytics, type CourseEnrollmentPoint, type EnrollmentPoint, type OverviewStats, type Page, type Student, type StudentAnalytics, type StudentCourseProgress, type StudentsQuery };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { OrgUserProfile, OrgUserStatus, Role } from "../../types/schemas/organizations.js";
|
|
2
|
+
import "../../types/index.js";
|
|
3
|
+
//#region src/reporting/admin/model.d.ts
|
|
4
|
+
interface SpacePostCount {
|
|
5
|
+
spaceId: string;
|
|
6
|
+
count: number;
|
|
7
|
+
}
|
|
8
|
+
interface PostCommentCount {
|
|
9
|
+
postId: string;
|
|
10
|
+
count: number;
|
|
11
|
+
}
|
|
12
|
+
interface CommunityAnalytics {
|
|
13
|
+
memberCount: number;
|
|
14
|
+
activeMembers: number;
|
|
15
|
+
postsPerSpace: SpacePostCount[];
|
|
16
|
+
commentsPerPost: PostCommentCount[];
|
|
17
|
+
messagesSent: number;
|
|
18
|
+
flagsRaised: number;
|
|
19
|
+
flagsResolved: number;
|
|
20
|
+
grantsLapsed: number;
|
|
21
|
+
grantsRenewed: number;
|
|
22
|
+
}
|
|
23
|
+
interface OverviewStats {
|
|
24
|
+
publishedCourses: number;
|
|
25
|
+
draftCourses: number;
|
|
26
|
+
activeStudents: number;
|
|
27
|
+
expiringSoon: number;
|
|
28
|
+
}
|
|
29
|
+
interface EnrollmentPoint {
|
|
30
|
+
date: string;
|
|
31
|
+
count: number;
|
|
32
|
+
}
|
|
33
|
+
interface CourseActivityEngagement {
|
|
34
|
+
activityId: string;
|
|
35
|
+
title: string;
|
|
36
|
+
moduleId: string;
|
|
37
|
+
moduleTitle: string;
|
|
38
|
+
started: number;
|
|
39
|
+
completed: number;
|
|
40
|
+
}
|
|
41
|
+
interface CourseAnalytics {
|
|
42
|
+
enrolled: number;
|
|
43
|
+
started: number;
|
|
44
|
+
completed: number;
|
|
45
|
+
/** Mean of each cohort member's completed-activities percentage (0–100). */
|
|
46
|
+
avgProgress: number;
|
|
47
|
+
activities: CourseActivityEngagement[];
|
|
48
|
+
}
|
|
49
|
+
interface CourseEnrollmentPoint {
|
|
50
|
+
date: string;
|
|
51
|
+
count: number;
|
|
52
|
+
}
|
|
53
|
+
interface Student extends OrgUserProfile {
|
|
54
|
+
/** As typed on the invite form. `name` is the composed rendering name and
|
|
55
|
+
* drifts once the person edits their own profile; these are what the admin
|
|
56
|
+
* edits. Null for anyone who arrived by self-signup. */
|
|
57
|
+
firstName: string | null;
|
|
58
|
+
lastName: string | null;
|
|
59
|
+
entitlementCount: number;
|
|
60
|
+
avgProgress: number;
|
|
61
|
+
/** `invited` until they accept — the admin added them, they have not arrived. */
|
|
62
|
+
status: OrgUserStatus;
|
|
63
|
+
joinedAt: string;
|
|
64
|
+
lastActiveAt: string | null;
|
|
65
|
+
}
|
|
66
|
+
interface StudentCourseProgress {
|
|
67
|
+
courseId: string;
|
|
68
|
+
title: string;
|
|
69
|
+
/** Published activities in the course. */
|
|
70
|
+
totalActivities: number;
|
|
71
|
+
completedActivities: number;
|
|
72
|
+
/** 0–100, completed published activities over the total. */
|
|
73
|
+
progress: number;
|
|
74
|
+
startedAt: string | null;
|
|
75
|
+
lastActivityAt: string | null;
|
|
76
|
+
completedAt: string | null;
|
|
77
|
+
}
|
|
78
|
+
interface StudentAnalytics {
|
|
79
|
+
enrolled: number;
|
|
80
|
+
started: number;
|
|
81
|
+
completed: number;
|
|
82
|
+
/** Mean of the per-course progress percentages (0–100). */
|
|
83
|
+
avgProgress: number;
|
|
84
|
+
courses: StudentCourseProgress[];
|
|
85
|
+
}
|
|
86
|
+
interface CommunityMember extends OrgUserProfile {
|
|
87
|
+
role: Role;
|
|
88
|
+
}
|
|
89
|
+
interface CommunityMembersQuery {
|
|
90
|
+
page: number;
|
|
91
|
+
pageSize: number;
|
|
92
|
+
search?: string | undefined;
|
|
93
|
+
}
|
|
94
|
+
interface StudentsQuery {
|
|
95
|
+
page: number;
|
|
96
|
+
pageSize: number;
|
|
97
|
+
search?: string | undefined;
|
|
98
|
+
sort?: string | undefined;
|
|
99
|
+
}
|
|
100
|
+
interface Page<T> {
|
|
101
|
+
rows: T[];
|
|
102
|
+
total: number;
|
|
103
|
+
page: number;
|
|
104
|
+
pageSize: number;
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { CommunityAnalytics, CommunityMember, CommunityMembersQuery, CourseActivityEngagement, CourseAnalytics, CourseEnrollmentPoint, EnrollmentPoint, OverviewStats, Page, PostCommentCount, SpacePostCount, Student, StudentAnalytics, StudentCourseProgress, StudentsQuery };
|
|
108
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CommunityAnalytics, CommunityMember, CommunityMembersQuery, CourseAnalytics, CourseEnrollmentPoint, EnrollmentPoint, OverviewStats, Page, Student, StudentAnalytics, StudentsQuery } from "./model.js";
|
|
2
|
+
//#region src/reporting/admin/ports.d.ts
|
|
3
|
+
interface AdminReportService {
|
|
4
|
+
overview(orgId: string): Promise<OverviewStats>;
|
|
5
|
+
enrollments(orgId: string, days: number): Promise<EnrollmentPoint[]>;
|
|
6
|
+
courseAnalytics(orgId: string, courseId: string): Promise<CourseAnalytics | null>;
|
|
7
|
+
courseEnrollments(orgId: string, courseId: string, days: number): Promise<CourseEnrollmentPoint[] | null>;
|
|
8
|
+
listStudents(orgId: string, query: StudentsQuery): Promise<Page<Student>>;
|
|
9
|
+
getStudent(orgId: string, id: string): Promise<Student | null>;
|
|
10
|
+
studentAnalytics(orgId: string, id: string): Promise<StudentAnalytics | null>;
|
|
11
|
+
communityAnalytics(orgId: string, communityId: string): Promise<CommunityAnalytics | null>;
|
|
12
|
+
communityMembers(orgId: string, communityId: string, query: CommunityMembersQuery): Promise<Page<CommunityMember> | null>;
|
|
13
|
+
}
|
|
14
|
+
interface AdminReportRepository {
|
|
15
|
+
overview(orgId: string): Promise<OverviewStats>;
|
|
16
|
+
enrollments(orgId: string, days: number): Promise<EnrollmentPoint[]>;
|
|
17
|
+
courseAnalytics(orgId: string, courseId: string): Promise<CourseAnalytics | null>;
|
|
18
|
+
courseEnrollments(orgId: string, courseId: string, days: number): Promise<CourseEnrollmentPoint[] | null>;
|
|
19
|
+
listStudents(orgId: string, query: StudentsQuery): Promise<Page<Student>>;
|
|
20
|
+
getStudent(orgId: string, id: string): Promise<Student | null>;
|
|
21
|
+
studentAnalytics(orgId: string, id: string): Promise<StudentAnalytics | null>;
|
|
22
|
+
communityAnalytics(orgId: string, communityId: string): Promise<CommunityAnalytics | null>;
|
|
23
|
+
communityMembers(orgId: string, communityId: string, query: CommunityMembersQuery): Promise<Page<CommunityMember> | null>;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { AdminReportRepository, AdminReportService };
|
|
27
|
+
//# sourceMappingURL=ports.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CommunityAnalytics, CommunityMember, CommunityMembersQuery, CourseAnalytics, CourseEnrollmentPoint, EnrollmentPoint, OverviewStats, Page, Student, StudentAnalytics, StudentsQuery } from "./model.js";
|
|
2
|
+
import { AdminReportRepository, AdminReportService } from "./ports.js";
|
|
3
|
+
//#region src/reporting/admin/service.d.ts
|
|
4
|
+
type AdminReportServiceParams = {
|
|
5
|
+
repo: AdminReportRepository;
|
|
6
|
+
};
|
|
7
|
+
declare class AdminReportServiceImpl implements AdminReportService {
|
|
8
|
+
private readonly repo;
|
|
9
|
+
constructor(params: AdminReportServiceParams);
|
|
10
|
+
overview(orgId: string): Promise<OverviewStats>;
|
|
11
|
+
enrollments(orgId: string, days: number): Promise<EnrollmentPoint[]>;
|
|
12
|
+
courseAnalytics(orgId: string, courseId: string): Promise<CourseAnalytics | null>;
|
|
13
|
+
courseEnrollments(orgId: string, courseId: string, days: number): Promise<CourseEnrollmentPoint[] | null>;
|
|
14
|
+
listStudents(orgId: string, query: StudentsQuery): Promise<Page<Student>>;
|
|
15
|
+
getStudent(orgId: string, id: string): Promise<Student | null>;
|
|
16
|
+
studentAnalytics(orgId: string, id: string): Promise<StudentAnalytics | null>;
|
|
17
|
+
communityAnalytics(orgId: string, communityId: string): Promise<CommunityAnalytics | null>;
|
|
18
|
+
communityMembers(orgId: string, communityId: string, query: CommunityMembersQuery): Promise<Page<CommunityMember> | null>;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { AdminReportServiceImpl, AdminReportServiceParams };
|
|
22
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/reporting/admin/service.ts
|
|
2
|
+
var AdminReportServiceImpl = class {
|
|
3
|
+
repo;
|
|
4
|
+
constructor(params) {
|
|
5
|
+
this.repo = params.repo;
|
|
6
|
+
}
|
|
7
|
+
overview(orgId) {
|
|
8
|
+
return this.repo.overview(orgId);
|
|
9
|
+
}
|
|
10
|
+
enrollments(orgId, days) {
|
|
11
|
+
return this.repo.enrollments(orgId, days);
|
|
12
|
+
}
|
|
13
|
+
courseAnalytics(orgId, courseId) {
|
|
14
|
+
return this.repo.courseAnalytics(orgId, courseId);
|
|
15
|
+
}
|
|
16
|
+
courseEnrollments(orgId, courseId, days) {
|
|
17
|
+
return this.repo.courseEnrollments(orgId, courseId, days);
|
|
18
|
+
}
|
|
19
|
+
listStudents(orgId, query) {
|
|
20
|
+
return this.repo.listStudents(orgId, query);
|
|
21
|
+
}
|
|
22
|
+
getStudent(orgId, id) {
|
|
23
|
+
return this.repo.getStudent(orgId, id);
|
|
24
|
+
}
|
|
25
|
+
studentAnalytics(orgId, id) {
|
|
26
|
+
return this.repo.studentAnalytics(orgId, id);
|
|
27
|
+
}
|
|
28
|
+
communityAnalytics(orgId, communityId) {
|
|
29
|
+
return this.repo.communityAnalytics(orgId, communityId);
|
|
30
|
+
}
|
|
31
|
+
communityMembers(orgId, communityId, query) {
|
|
32
|
+
return this.repo.communityMembers(orgId, communityId, query);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { AdminReportServiceImpl };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","names":[],"sources":["../../../src/reporting/admin/service.ts"],"sourcesContent":["import type {\n CommunityAnalytics,\n CommunityMember,\n CommunityMembersQuery,\n CourseAnalytics,\n CourseEnrollmentPoint,\n EnrollmentPoint,\n OverviewStats,\n Page,\n Student,\n StudentAnalytics,\n StudentsQuery,\n} from './model.js';\nimport type { AdminReportRepository, AdminReportService } from './ports.js';\n\nexport type AdminReportServiceParams = {\n repo: AdminReportRepository;\n};\n\nexport class AdminReportServiceImpl implements AdminReportService {\n private readonly repo: AdminReportRepository;\n\n constructor(params: AdminReportServiceParams) {\n this.repo = params.repo;\n }\n\n overview(orgId: string): Promise<OverviewStats> {\n return this.repo.overview(orgId);\n }\n\n enrollments(orgId: string, days: number): Promise<EnrollmentPoint[]> {\n return this.repo.enrollments(orgId, days);\n }\n\n courseAnalytics(orgId: string, courseId: string): Promise<CourseAnalytics | null> {\n return this.repo.courseAnalytics(orgId, courseId);\n }\n\n courseEnrollments(\n orgId: string,\n courseId: string,\n days: number,\n ): Promise<CourseEnrollmentPoint[] | null> {\n return this.repo.courseEnrollments(orgId, courseId, days);\n }\n\n listStudents(orgId: string, query: StudentsQuery): Promise<Page<Student>> {\n return this.repo.listStudents(orgId, query);\n }\n\n getStudent(orgId: string, id: string): Promise<Student | null> {\n return this.repo.getStudent(orgId, id);\n }\n\n studentAnalytics(orgId: string, id: string): Promise<StudentAnalytics | null> {\n return this.repo.studentAnalytics(orgId, id);\n }\n\n communityAnalytics(orgId: string, communityId: string): Promise<CommunityAnalytics | null> {\n return this.repo.communityAnalytics(orgId, communityId);\n }\n\n communityMembers(\n orgId: string,\n communityId: string,\n query: CommunityMembersQuery,\n ): Promise<Page<CommunityMember> | null> {\n return this.repo.communityMembers(orgId, communityId, query);\n }\n}\n"],"mappings":";AAmBA,IAAa,yBAAb,MAAkE;CAChE;CAEA,YAAY,QAAkC;EAC5C,KAAK,OAAO,OAAO;CACrB;CAEA,SAAS,OAAuC;EAC9C,OAAO,KAAK,KAAK,SAAS,KAAK;CACjC;CAEA,YAAY,OAAe,MAA0C;EACnE,OAAO,KAAK,KAAK,YAAY,OAAO,IAAI;CAC1C;CAEA,gBAAgB,OAAe,UAAmD;EAChF,OAAO,KAAK,KAAK,gBAAgB,OAAO,QAAQ;CAClD;CAEA,kBACE,OACA,UACA,MACyC;EACzC,OAAO,KAAK,KAAK,kBAAkB,OAAO,UAAU,IAAI;CAC1D;CAEA,aAAa,OAAe,OAA8C;EACxE,OAAO,KAAK,KAAK,aAAa,OAAO,KAAK;CAC5C;CAEA,WAAW,OAAe,IAAqC;EAC7D,OAAO,KAAK,KAAK,WAAW,OAAO,EAAE;CACvC;CAEA,iBAAiB,OAAe,IAA8C;EAC5E,OAAO,KAAK,KAAK,iBAAiB,OAAO,EAAE;CAC7C;CAEA,mBAAmB,OAAe,aAAyD;EACzF,OAAO,KAAK,KAAK,mBAAmB,OAAO,WAAW;CACxD;CAEA,iBACE,OACA,aACA,OACuC;EACvC,OAAO,KAAK,KAAK,iBAAiB,OAAO,aAAa,KAAK;CAC7D;AACF"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { CourseView, Module } from "../../types/schemas/content.js";
|
|
2
|
+
import { CommunityHome, CommunityHomeSpace, CourseProgressView, DirectoryEntry, DirectoryQuery, DirectoryRow, LockedCommunitySpace, NotificationView, NotificationsView, OpenCommunitySpace } from "./model.js";
|
|
3
|
+
import { MemberReportRepository, MemberReportService } from "./ports.js";
|
|
4
|
+
import { MemberReportServiceImpl } from "./service.js";
|
|
5
|
+
export { type CommunityHome, type CommunityHomeSpace, type CourseProgressView, type CourseView, type DirectoryEntry, type DirectoryQuery, type DirectoryRow, type LockedCommunitySpace, type MemberReportRepository, type MemberReportService, MemberReportServiceImpl, type Module, type NotificationView, type NotificationsView, type OpenCommunitySpace };
|