@headless-lms/server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -0
  3. package/dist/app/container.d.ts +124 -0
  4. package/dist/app/container.js +312 -0
  5. package/dist/app/container.js.map +1 -0
  6. package/dist/app/db.d.ts +2 -0
  7. package/dist/app/db.js +2 -0
  8. package/dist/app/integrations.d.ts +7 -0
  9. package/dist/app/integrations.js +44 -0
  10. package/dist/app/integrations.js.map +1 -0
  11. package/dist/app/notifications.d.ts +7 -0
  12. package/dist/app/notifications.js +23 -0
  13. package/dist/app/notifications.js.map +1 -0
  14. package/dist/http/config.d.ts +25 -0
  15. package/dist/http/config.js +1 -0
  16. package/dist/http/fastify.d.ts +35 -0
  17. package/dist/http/plugins/auth.d.ts +11 -0
  18. package/dist/http/plugins/auth.js +47 -0
  19. package/dist/http/plugins/auth.js.map +1 -0
  20. package/dist/http/plugins/cors.d.ts +7 -0
  21. package/dist/http/plugins/cors.js +22 -0
  22. package/dist/http/plugins/cors.js.map +1 -0
  23. package/dist/http/plugins/error-handler.d.ts +6 -0
  24. package/dist/http/plugins/error-handler.js +64 -0
  25. package/dist/http/plugins/error-handler.js.map +1 -0
  26. package/dist/http/plugins/openapi.d.ts +7 -0
  27. package/dist/http/plugins/openapi.js +63 -0
  28. package/dist/http/plugins/openapi.js.map +1 -0
  29. package/dist/http/routes/activities.d.ts +7 -0
  30. package/dist/http/routes/activities.js +163 -0
  31. package/dist/http/routes/activities.js.map +1 -0
  32. package/dist/http/routes/assets.d.ts +7 -0
  33. package/dist/http/routes/assets.js +168 -0
  34. package/dist/http/routes/assets.js.map +1 -0
  35. package/dist/http/routes/automations.d.ts +7 -0
  36. package/dist/http/routes/automations.js +158 -0
  37. package/dist/http/routes/automations.js.map +1 -0
  38. package/dist/http/routes/courses.d.ts +7 -0
  39. package/dist/http/routes/courses.js +130 -0
  40. package/dist/http/routes/courses.js.map +1 -0
  41. package/dist/http/routes/dashboard.d.ts +7 -0
  42. package/dist/http/routes/dashboard.js +42 -0
  43. package/dist/http/routes/dashboard.js.map +1 -0
  44. package/dist/http/routes/discussion.d.ts +7 -0
  45. package/dist/http/routes/discussion.js +122 -0
  46. package/dist/http/routes/discussion.js.map +1 -0
  47. package/dist/http/routes/downloads.d.ts +7 -0
  48. package/dist/http/routes/downloads.js +208 -0
  49. package/dist/http/routes/downloads.js.map +1 -0
  50. package/dist/http/routes/entitlements.d.ts +7 -0
  51. package/dist/http/routes/entitlements.js +69 -0
  52. package/dist/http/routes/entitlements.js.map +1 -0
  53. package/dist/http/routes/identity.d.ts +6 -0
  54. package/dist/http/routes/identity.js +6 -0
  55. package/dist/http/routes/identity.js.map +1 -0
  56. package/dist/http/routes/integrations.d.ts +7 -0
  57. package/dist/http/routes/integrations.js +210 -0
  58. package/dist/http/routes/integrations.js.map +1 -0
  59. package/dist/http/routes/learn/assets.d.ts +8 -0
  60. package/dist/http/routes/learn/assets.js +32 -0
  61. package/dist/http/routes/learn/assets.js.map +1 -0
  62. package/dist/http/routes/learn/comments.d.ts +7 -0
  63. package/dist/http/routes/learn/comments.js +175 -0
  64. package/dist/http/routes/learn/comments.js.map +1 -0
  65. package/dist/http/routes/learn/courses.d.ts +7 -0
  66. package/dist/http/routes/learn/courses.js +75 -0
  67. package/dist/http/routes/learn/courses.js.map +1 -0
  68. package/dist/http/routes/learn/downloads.d.ts +7 -0
  69. package/dist/http/routes/learn/downloads.js +76 -0
  70. package/dist/http/routes/learn/downloads.js.map +1 -0
  71. package/dist/http/routes/learn/index.d.ts +7 -0
  72. package/dist/http/routes/learn/index.js +19 -0
  73. package/dist/http/routes/learn/index.js.map +1 -0
  74. package/dist/http/routes/learn/org.d.ts +7 -0
  75. package/dist/http/routes/learn/org.js +50 -0
  76. package/dist/http/routes/learn/org.js.map +1 -0
  77. package/dist/http/routes/learn/progress.d.ts +7 -0
  78. package/dist/http/routes/learn/progress.js +63 -0
  79. package/dist/http/routes/learn/progress.js.map +1 -0
  80. package/dist/http/routes/organizations.d.ts +7 -0
  81. package/dist/http/routes/organizations.js +219 -0
  82. package/dist/http/routes/organizations.js.map +1 -0
  83. package/dist/http/routes/progress.d.ts +6 -0
  84. package/dist/http/routes/progress.js +6 -0
  85. package/dist/http/routes/progress.js.map +1 -0
  86. package/dist/http/routes/students.d.ts +7 -0
  87. package/dist/http/routes/students.js +125 -0
  88. package/dist/http/routes/students.js.map +1 -0
  89. package/dist/http/routes.d.ts +7 -0
  90. package/dist/http/routes.js +37 -0
  91. package/dist/http/routes.js.map +1 -0
  92. package/dist/http/schemas/activities.d.ts +69 -0
  93. package/dist/http/schemas/activities.js +25 -0
  94. package/dist/http/schemas/activities.js.map +1 -0
  95. package/dist/http/schemas/assets.d.ts +142 -0
  96. package/dist/http/schemas/assets.js +24 -0
  97. package/dist/http/schemas/assets.js.map +1 -0
  98. package/dist/http/schemas/automations.d.ts +154 -0
  99. package/dist/http/schemas/automations.js +26 -0
  100. package/dist/http/schemas/automations.js.map +1 -0
  101. package/dist/http/schemas/content.d.ts +113 -0
  102. package/dist/http/schemas/content.js +29 -0
  103. package/dist/http/schemas/content.js.map +1 -0
  104. package/dist/http/schemas/dashboard.d.ts +26 -0
  105. package/dist/http/schemas/dashboard.js +18 -0
  106. package/dist/http/schemas/dashboard.js.map +1 -0
  107. package/dist/http/schemas/discussion.d.ts +340 -0
  108. package/dist/http/schemas/discussion.js +78 -0
  109. package/dist/http/schemas/discussion.js.map +1 -0
  110. package/dist/http/schemas/downloads.d.ts +110 -0
  111. package/dist/http/schemas/downloads.js +34 -0
  112. package/dist/http/schemas/downloads.js.map +1 -0
  113. package/dist/http/schemas/entitlements.d.ts +97 -0
  114. package/dist/http/schemas/entitlements.js +41 -0
  115. package/dist/http/schemas/entitlements.js.map +1 -0
  116. package/dist/http/schemas/index.d.ts +17 -0
  117. package/dist/http/schemas/index.js +17 -0
  118. package/dist/http/schemas/integrations.d.ts +93 -0
  119. package/dist/http/schemas/integrations.js +47 -0
  120. package/dist/http/schemas/integrations.js.map +1 -0
  121. package/dist/http/schemas/invites.d.ts +95 -0
  122. package/dist/http/schemas/invites.js +62 -0
  123. package/dist/http/schemas/invites.js.map +1 -0
  124. package/dist/http/schemas/learn.d.ts +142 -0
  125. package/dist/http/schemas/learn.js +58 -0
  126. package/dist/http/schemas/learn.js.map +1 -0
  127. package/dist/http/schemas/members.d.ts +88 -0
  128. package/dist/http/schemas/members.js +27 -0
  129. package/dist/http/schemas/members.js.map +1 -0
  130. package/dist/http/schemas/organizations.d.ts +22 -0
  131. package/dist/http/schemas/organizations.js +20 -0
  132. package/dist/http/schemas/organizations.js.map +1 -0
  133. package/dist/http/schemas/settings.d.ts +23 -0
  134. package/dist/http/schemas/settings.js +20 -0
  135. package/dist/http/schemas/settings.js.map +1 -0
  136. package/dist/http/schemas/shared.d.ts +57 -0
  137. package/dist/http/schemas/shared.js +54 -0
  138. package/dist/http/schemas/shared.js.map +1 -0
  139. package/dist/http/schemas/students.d.ts +64 -0
  140. package/dist/http/schemas/students.js +25 -0
  141. package/dist/http/schemas/students.js.map +1 -0
  142. package/dist/http/scope.d.ts +22 -0
  143. package/dist/http/scope.js +28 -0
  144. package/dist/http/scope.js.map +1 -0
  145. package/dist/http/server.d.ts +8 -0
  146. package/dist/http/server.js +33 -0
  147. package/dist/http/server.js.map +1 -0
  148. package/dist/http/web-bridge.d.ts +9 -0
  149. package/dist/http/web-bridge.js +27 -0
  150. package/dist/http/web-bridge.js.map +1 -0
  151. package/dist/index.d.ts +15 -0
  152. package/dist/index.js +16 -0
  153. package/dist/index.js.map +1 -0
  154. package/package.json +56 -0
@@ -0,0 +1,23 @@
1
+ import { entitlementEvents } from "@headless-lms/core/entitlements";
2
+ //#region src/app/notifications.ts
3
+ function registerNotificationSubscribers(bus, mailer, lookups) {
4
+ bus.subscribe(entitlementEvents.entitlementCreated, async (event) => {
5
+ const { orgUserId, contentId } = event.data;
6
+ const [to, content] = await Promise.all([lookups.orgUserEmail(event.orgId, orgUserId), lookups.contentInfo(event.orgId, contentId)]);
7
+ if (!to || !content) throw new Error(`entitlement notification lookups failed: ${event.data.id}`);
8
+ await mailer.send(to, "accessGranted", {
9
+ contentTitle: content.title,
10
+ contentId: content.id
11
+ });
12
+ });
13
+ bus.subscribe(entitlementEvents.entitlementDeleted, async (event) => {
14
+ const { orgUserId, contentId } = event.data;
15
+ const [to, content] = await Promise.all([lookups.orgUserEmail(event.orgId, orgUserId), lookups.contentInfo(event.orgId, contentId)]);
16
+ if (!to || !content) throw new Error(`entitlement notification lookups failed: ${event.data.id}`);
17
+ await mailer.send(to, "accessRevoked", { contentTitle: content.title });
18
+ });
19
+ }
20
+ //#endregion
21
+ export { registerNotificationSubscribers };
22
+
23
+ //# sourceMappingURL=notifications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.js","names":[],"sources":["../../src/app/notifications.ts"],"sourcesContent":["// Email side-effects of domain events, subscribed on the EventBus. Events\n// reach the bus via the outbox relay at-least-once: a mailer failure throws\n// through publish, and the relay retries with backoff — no email is silently\n// dropped.\nimport type { EventBus } from '@headless-lms/core/shared/ports';\nimport type { Mailer, MailerLookups } from '@headless-lms/core/shared/mailer';\nimport { entitlementEvents } from '@headless-lms/core/entitlements';\n\nexport function registerNotificationSubscribers(\n bus: EventBus,\n mailer: Pick<Mailer, 'send'>,\n lookups: MailerLookups,\n): void {\n bus.subscribe(entitlementEvents.entitlementCreated, async (event) => {\n const { orgUserId, contentId } = event.data;\n const [to, content] = await Promise.all([\n lookups.orgUserEmail(event.orgId, orgUserId),\n lookups.contentInfo(event.orgId, contentId),\n ]);\n if (!to || !content) {\n throw new Error(`entitlement notification lookups failed: ${event.data.id}`);\n }\n await mailer.send(to, 'accessGranted', {\n contentTitle: content.title,\n contentId: content.id,\n });\n });\n\n bus.subscribe(entitlementEvents.entitlementDeleted, async (event) => {\n const { orgUserId, contentId } = event.data;\n const [to, content] = await Promise.all([\n lookups.orgUserEmail(event.orgId, orgUserId),\n lookups.contentInfo(event.orgId, contentId),\n ]);\n if (!to || !content) {\n throw new Error(`entitlement notification lookups failed: ${event.data.id}`);\n }\n await mailer.send(to, 'accessRevoked', {\n contentTitle: content.title,\n });\n });\n}\n"],"mappings":";;AAQA,SAAgB,gCACd,KACA,QACA,SACM;CACN,IAAI,UAAU,kBAAkB,oBAAoB,OAAO,UAAU;EACnE,MAAM,EAAE,WAAW,cAAc,MAAM;EACvC,MAAM,CAAC,IAAI,WAAW,MAAM,QAAQ,IAAI,CACtC,QAAQ,aAAa,MAAM,OAAO,SAAS,GAC3C,QAAQ,YAAY,MAAM,OAAO,SAAS,CAC5C,CAAC;EACD,IAAI,CAAC,MAAM,CAAC,SACV,MAAM,IAAI,MAAM,4CAA4C,MAAM,KAAK,IAAI;EAE7E,MAAM,OAAO,KAAK,IAAI,iBAAiB;GACrC,cAAc,QAAQ;GACtB,WAAW,QAAQ;EACrB,CAAC;CACH,CAAC;CAED,IAAI,UAAU,kBAAkB,oBAAoB,OAAO,UAAU;EACnE,MAAM,EAAE,WAAW,cAAc,MAAM;EACvC,MAAM,CAAC,IAAI,WAAW,MAAM,QAAQ,IAAI,CACtC,QAAQ,aAAa,MAAM,OAAO,SAAS,GAC3C,QAAQ,YAAY,MAAM,OAAO,SAAS,CAC5C,CAAC;EACD,IAAI,CAAC,MAAM,CAAC,SACV,MAAM,IAAI,MAAM,4CAA4C,MAAM,KAAK,IAAI;EAE7E,MAAM,OAAO,KAAK,IAAI,iBAAiB,EACrC,cAAc,QAAQ,MACxB,CAAC;CACH,CAAC;AACH"}
@@ -0,0 +1,25 @@
1
+ import { Config } from "../app/container.js";
2
+ //#region src/http/config.d.ts
3
+ interface ServerConfig {
4
+ /** TCP port to listen on. */
5
+ port: number;
6
+ /** Bind address. */
7
+ host: string;
8
+ /** The API's own origin, advertised as the OpenAPI server URL. */
9
+ publicUrl: string;
10
+ /** Browser app origins allowed by CORS. */
11
+ clientOrigins: string[];
12
+ /** Emit Fastify's per-request "incoming request"/"request completed" lines.
13
+ * Unset → on. Turn off for a quiet dev console; app-level logs are
14
+ * unaffected either way. */
15
+ requestLogging?: boolean;
16
+ /** Presigned URL lifetime for entitled download delivery, seconds. Separate
17
+ * from the storage adapter's general download expiry so raising that one
18
+ * can't silently extend paywalled link lifetimes. */
19
+ deliveryExpirySeconds: number;
20
+ /** Everything the composition container needs to wire adapters + services. */
21
+ container: Config;
22
+ }
23
+ //#endregion
24
+ export { ServerConfig };
25
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ import "fastify";
2
+ import { User } from "@headless-lms/core/types";
3
+ //#region src/http/fastify.d.ts
4
+ /** The session's account as the HTTP layer sees it. Re-exported from the
5
+ * package entry point so this module — and its augmentation — gets loaded. */
6
+ type AuthUser = User;
7
+ declare module 'fastify' {
8
+ interface FastifyInstance {
9
+ /** Session guard. Populates `authUser` (and `orgId` when the session has an
10
+ * active org), or throws `UnauthorizedError`. Use for the routes reached
11
+ * before the caller has an org — org creation, invite acceptance. */
12
+ requireSession(request: FastifyRequest): Promise<void>;
13
+ /** As `requireSession`, but also requires an active org on the session. */
14
+ requireOrgSession(request: FastifyRequest): Promise<void>;
15
+ }
16
+ interface FastifyRequest {
17
+ authUser: User;
18
+ /** Domain `organizations.id` for the session's active org — what every
19
+ * domain read keys off. Guaranteed set by `requireOrgSession`; absent under
20
+ * `requireSession` when the session has no active org. */
21
+ orgId: string;
22
+ /** The same org as better-auth knows it (`organizations.external_id`), for
23
+ * writes that go back through the auth provider. Equal to `orgId` today:
24
+ * the org-create hook gives better-auth the domain id and records it as
25
+ * the external id. */
26
+ authOrgId: string;
27
+ /** Domain `users.id` for the session's account — what `org_users.user_id`
28
+ * and every other domain row references. Not `authUser.id`: the two match
29
+ * only for a self-signup, never for a person an admin provisioned first. */
30
+ userId: string;
31
+ }
32
+ }
33
+ //#endregion
34
+ export { AuthUser };
35
+ //# sourceMappingURL=fastify.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { Container } from "../../app/container.js";
2
+ import { FastifyInstance } from "fastify";
3
+ //#region src/http/plugins/auth.d.ts
4
+ /** The request carries no session. The central error handler maps this to 401. */
5
+ declare class UnauthorizedError extends Error {
6
+ constructor(message?: string);
7
+ }
8
+ declare function registerAuth(app: FastifyInstance, appServer: Container): void;
9
+ //#endregion
10
+ export { UnauthorizedError, registerAuth };
11
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1,47 @@
1
+ import { bridgeWebResponse, toWebRequest } from "../web-bridge.js";
2
+ //#region src/http/plugins/auth.ts
3
+ /** The request carries no session. The central error handler maps this to 401. */
4
+ var UnauthorizedError = class extends Error {
5
+ constructor(message = "no session on the request") {
6
+ super(message);
7
+ this.name = "UnauthorizedError";
8
+ }
9
+ };
10
+ function registerAuth(app, appServer) {
11
+ const { auth } = appServer;
12
+ app.route({
13
+ method: ["GET", "POST"],
14
+ url: "/api/auth/*",
15
+ async handler(request, reply) {
16
+ await bridgeWebResponse(await auth.handler(toWebRequest(request)), reply);
17
+ }
18
+ });
19
+ app.decorate("requireSession", async (request) => {
20
+ const session = await appServer.auth.verify(request.headers);
21
+ if (!session) throw new UnauthorizedError();
22
+ request.authUser = session.user;
23
+ request.userId = session.user.id;
24
+ const authOrgId = session.session.activeOrganizationId;
25
+ if (authOrgId) {
26
+ request.authOrgId = authOrgId;
27
+ const org = await appServer.organizations.getByExternalId(authOrgId);
28
+ if (org) request.orgId = org.id;
29
+ }
30
+ });
31
+ app.decorate("requireOrgSession", async (request) => {
32
+ const session = await appServer.auth.verify(request.headers);
33
+ if (!session) throw new UnauthorizedError();
34
+ const authOrgId = session.session.activeOrganizationId;
35
+ if (!authOrgId) throw new UnauthorizedError();
36
+ const org = await appServer.organizations.getByExternalId(authOrgId);
37
+ if (!org) throw new UnauthorizedError("session organization not found");
38
+ request.authUser = session.user;
39
+ request.userId = session.user.id;
40
+ request.orgId = org.id;
41
+ request.authOrgId = authOrgId;
42
+ });
43
+ }
44
+ //#endregion
45
+ export { UnauthorizedError, registerAuth };
46
+
47
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","names":[],"sources":["../../../src/http/plugins/auth.ts"],"sourcesContent":["// Everything better-auth touches on the HTTP surface:\n// - the /api/auth/* catch-all bridged to better-auth's Web handler\n// - the `requireSession` / `requireOrgSession` decorators routes guard with\nimport type { FastifyInstance, FastifyRequest } from 'fastify';\nimport type { Container } from '../../app/container.js';\nimport { bridgeWebResponse, toWebRequest } from '../web-bridge.js';\n\n/** The request carries no session. The central error handler maps this to 401. */\nexport class UnauthorizedError extends Error {\n constructor(message = 'no session on the request') {\n super(message);\n this.name = 'UnauthorizedError';\n }\n}\n\nexport function registerAuth(app: FastifyInstance, appServer: Container): void {\n const { auth } = appServer;\n\n // Mount better-auth: a catch-all that bridges Fastify <-> Web Request/Response.\n app.route({\n method: ['GET', 'POST'],\n url: '/api/auth/*',\n async handler(request, reply) {\n await bridgeWebResponse(await auth.handler(toWebRequest(request)), reply);\n },\n });\n\n // Session only, no active org required. For the routes a caller reaches\n // *before* they have an org: creating their first organization, and accepting\n // an invitation (which is what stamps the active org onto the session).\n app.decorate('requireSession', async (request: FastifyRequest) => {\n const session = await appServer.auth.verify(request.headers);\n if (!session) {\n throw new UnauthorizedError();\n }\n request.authUser = session.user;\n request.userId = session.user.id;\n\n const authOrgId = session.session.activeOrganizationId;\n if (authOrgId) {\n request.authOrgId = authOrgId;\n const org = await appServer.organizations.getByExternalId(authOrgId);\n if (org) {\n request.orgId = org.id;\n }\n }\n });\n\n // Requires a valid session with an active org set — every org-scoped route.\n app.decorate('requireOrgSession', async (request: FastifyRequest) => {\n const session = await appServer.auth.verify(request.headers);\n if (!session) {\n throw new UnauthorizedError();\n }\n const authOrgId = session.session.activeOrganizationId;\n if (!authOrgId) {\n throw new UnauthorizedError();\n }\n\n const org = await appServer.organizations.getByExternalId(authOrgId);\n if (!org) {\n throw new UnauthorizedError('session organization not found');\n }\n request.authUser = session.user;\n request.userId = session.user.id;\n request.orgId = org.id;\n // resolveScope requires the auth-provider id too — routes that write back\n // through better-auth key on it.\n request.authOrgId = authOrgId;\n });\n}\n"],"mappings":";;;AAQA,IAAa,oBAAb,cAAuC,MAAM;CAC3C,YAAY,UAAU,6BAA6B;EACjD,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,aAAa,KAAsB,WAA4B;CAC7E,MAAM,EAAE,SAAS;CAGjB,IAAI,MAAM;EACR,QAAQ,CAAC,OAAO,MAAM;EACtB,KAAK;EACL,MAAM,QAAQ,SAAS,OAAO;GAC5B,MAAM,kBAAkB,MAAM,KAAK,QAAQ,aAAa,OAAO,CAAC,GAAG,KAAK;EAC1E;CACF,CAAC;CAKD,IAAI,SAAS,kBAAkB,OAAO,YAA4B;EAChE,MAAM,UAAU,MAAM,UAAU,KAAK,OAAO,QAAQ,OAAO;EAC3D,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;EAE9B,QAAQ,WAAW,QAAQ;EAC3B,QAAQ,SAAS,QAAQ,KAAK;EAE9B,MAAM,YAAY,QAAQ,QAAQ;EAClC,IAAI,WAAW;GACb,QAAQ,YAAY;GACpB,MAAM,MAAM,MAAM,UAAU,cAAc,gBAAgB,SAAS;GACnE,IAAI,KACF,QAAQ,QAAQ,IAAI;EAExB;CACF,CAAC;CAGD,IAAI,SAAS,qBAAqB,OAAO,YAA4B;EACnE,MAAM,UAAU,MAAM,UAAU,KAAK,OAAO,QAAQ,OAAO;EAC3D,IAAI,CAAC,SACH,MAAM,IAAI,kBAAkB;EAE9B,MAAM,YAAY,QAAQ,QAAQ;EAClC,IAAI,CAAC,WACH,MAAM,IAAI,kBAAkB;EAG9B,MAAM,MAAM,MAAM,UAAU,cAAc,gBAAgB,SAAS;EACnE,IAAI,CAAC,KACH,MAAM,IAAI,kBAAkB,gCAAgC;EAE9D,QAAQ,WAAW,QAAQ;EAC3B,QAAQ,SAAS,QAAQ,KAAK;EAC9B,QAAQ,QAAQ,IAAI;EAGpB,QAAQ,YAAY;CACtB,CAAC;AACH"}
@@ -0,0 +1,7 @@
1
+ import { ServerConfig } from "../config.js";
2
+ import { FastifyInstance } from "fastify";
3
+ //#region src/http/plugins/cors.d.ts
4
+ declare function registerCors(app: FastifyInstance, config: ServerConfig): void;
5
+ //#endregion
6
+ export { registerCors };
7
+ //# sourceMappingURL=cors.d.ts.map
@@ -0,0 +1,22 @@
1
+ import cors from "@fastify/cors";
2
+ //#region src/http/plugins/cors.ts
3
+ function registerCors(app, config) {
4
+ app.register(cors, {
5
+ origin: config.clientOrigins,
6
+ methods: [
7
+ "GET",
8
+ "POST",
9
+ "PUT",
10
+ "PATCH",
11
+ "DELETE",
12
+ "OPTIONS"
13
+ ],
14
+ allowedHeaders: ["Content-Type", "Authorization"],
15
+ credentials: true,
16
+ maxAge: 86400
17
+ });
18
+ }
19
+ //#endregion
20
+ export { registerCors };
21
+
22
+ //# sourceMappingURL=cors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.js","names":[],"sources":["../../../src/http/plugins/cors.ts"],"sourcesContent":["// CORS for the browser apps (student web + admin). Credentials must be allowed\n// so the SPA/admin can carry the better-auth session cookie cross-origin.\nimport type { FastifyInstance } from 'fastify';\nimport cors from '@fastify/cors';\nimport type { ServerConfig } from '../config.js';\n\nexport function registerCors(app: FastifyInstance, config: ServerConfig): void {\n app.register(cors, {\n origin: config.clientOrigins,\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n credentials: true,\n maxAge: 86400,\n });\n}\n"],"mappings":";;AAMA,SAAgB,aAAa,KAAsB,QAA4B;CAC7E,IAAI,SAAS,MAAM;EACjB,QAAQ,OAAO;EACf,SAAS;GAAC;GAAO;GAAQ;GAAO;GAAS;GAAU;EAAS;EAC5D,gBAAgB,CAAC,gBAAgB,eAAe;EAChD,aAAa;EACb,QAAQ;CACV,CAAC;AACH"}
@@ -0,0 +1,6 @@
1
+ import { FastifyReply, FastifyRequest } from "fastify";
2
+ //#region src/http/plugins/error-handler.d.ts
3
+ declare function errorHandler(error: unknown, _request: FastifyRequest, reply: FastifyReply): FastifyReply<import("fastify").RouteGenericInterface, import("fastify").RawServerDefault, import("http").IncomingMessage, import("http").ServerResponse<import("http").IncomingMessage>, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
4
+ //#endregion
5
+ export { errorHandler };
6
+ //# sourceMappingURL=error-handler.d.ts.map
@@ -0,0 +1,64 @@
1
+ import { UnauthorizedError } from "./auth.js";
2
+ import { NoActiveOrgError } from "../scope.js";
3
+ import { OrganizationRuleError } from "@headless-lms/core/organizations";
4
+ import { ActionInvocationError, AlreadyConnectedError, InvalidConfigError, UnknownIntegrationError } from "@headless-lms/core/integrations";
5
+ import { InvalidTriggerError } from "@headless-lms/core/automations";
6
+ import { ConflictError, ForbiddenError, NotFoundError } from "@headless-lms/core/shared/errors";
7
+ //#region src/http/plugins/error-handler.ts
8
+ function errorHandler(error, _request, reply) {
9
+ if (error instanceof NotFoundError) return reply.status(404).send({
10
+ error: "not_found",
11
+ message: error.message
12
+ });
13
+ if (error instanceof ConflictError) return reply.status(409).send({
14
+ error: "conflict",
15
+ message: error.message
16
+ });
17
+ if (error instanceof ForbiddenError) return reply.status(403).send({
18
+ error: "forbidden",
19
+ message: error.message
20
+ });
21
+ if (error instanceof OrganizationRuleError) return reply.status(409).send({
22
+ error: "conflict",
23
+ message: error.message
24
+ });
25
+ if (error instanceof AlreadyConnectedError) return reply.status(409).send({
26
+ error: "already_connected",
27
+ message: error.message
28
+ });
29
+ if (error instanceof UnknownIntegrationError) return reply.status(400).send({
30
+ error: "unknown_integration",
31
+ message: error.message
32
+ });
33
+ if (error instanceof InvalidConfigError) return reply.status(400).send({
34
+ error: "invalid_config",
35
+ message: error.message
36
+ });
37
+ if (error instanceof ActionInvocationError) return reply.status(502).send({
38
+ error: "action_failed",
39
+ message: error.message
40
+ });
41
+ if (error instanceof InvalidTriggerError) return reply.status(400).send({
42
+ error: "invalid_trigger",
43
+ message: error.message
44
+ });
45
+ if (error instanceof NoActiveOrgError) return reply.status(403).send({
46
+ error: "forbidden",
47
+ message: error.message
48
+ });
49
+ if (error instanceof UnauthorizedError) return reply.status(401).send({
50
+ error: "unauthorized",
51
+ message: error.message
52
+ });
53
+ const err = error;
54
+ if (typeof err.statusCode === "number" && err.statusCode >= 400 && err.statusCode < 500) return reply.status(err.statusCode).send({
55
+ error: err.code ?? "bad_request",
56
+ message: err.message ?? "Bad request"
57
+ });
58
+ _request.log.error(error);
59
+ return reply.status(500).send({ error: "internal_error" });
60
+ }
61
+ //#endregion
62
+ export { errorHandler };
63
+
64
+ //# sourceMappingURL=error-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-handler.js","names":[],"sources":["../../../src/http/plugins/error-handler.ts"],"sourcesContent":["import { ConflictError, ForbiddenError, NotFoundError } from '@headless-lms/core/shared/errors';\nimport { OrganizationRuleError } from '@headless-lms/core/organizations';\nimport {\n ActionInvocationError,\n AlreadyConnectedError,\n InvalidConfigError,\n UnknownIntegrationError,\n} from '@headless-lms/core/integrations';\nimport { InvalidTriggerError } from '@headless-lms/core/automations';\nimport { NoActiveOrgError } from '../scope.js';\nimport { UnauthorizedError } from './auth.js';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\n\nexport function errorHandler(error: unknown, _request: FastifyRequest, reply: FastifyReply) {\n if (error instanceof NotFoundError) {\n return reply.status(404).send({ error: 'not_found', message: error.message });\n }\n if (error instanceof ConflictError) {\n return reply.status(409).send({ error: 'conflict', message: error.message });\n }\n if (error instanceof ForbiddenError) {\n return reply.status(403).send({ error: 'forbidden', message: error.message });\n }\n if (error instanceof OrganizationRuleError) {\n return reply.status(409).send({ error: 'conflict', message: error.message });\n }\n if (error instanceof AlreadyConnectedError) {\n return reply.status(409).send({ error: 'already_connected', message: error.message });\n }\n if (error instanceof UnknownIntegrationError) {\n return reply.status(400).send({ error: 'unknown_integration', message: error.message });\n }\n if (error instanceof InvalidConfigError) {\n return reply.status(400).send({ error: 'invalid_config', message: error.message });\n }\n if (error instanceof ActionInvocationError) {\n return reply.status(502).send({ error: 'action_failed', message: error.message });\n }\n if (error instanceof InvalidTriggerError) {\n return reply.status(400).send({ error: 'invalid_trigger', message: error.message });\n }\n if (error instanceof NoActiveOrgError) {\n return reply.status(403).send({ error: 'forbidden', message: error.message });\n }\n // No session at all — 401 so the client bounces to login, not a generic 403.\n if (error instanceof UnauthorizedError) {\n return reply.status(401).send({ error: 'unauthorized', message: error.message });\n }\n const err = error as { statusCode?: number; code?: string; message?: string };\n if (typeof err.statusCode === 'number' && err.statusCode >= 400 && err.statusCode < 500) {\n return reply\n .status(err.statusCode)\n .send({ error: err.code ?? 'bad_request', message: err.message ?? 'Bad request' });\n }\n _request.log.error(error);\n return reply.status(500).send({ error: 'internal_error' });\n}\n"],"mappings":";;;;;;;AAaA,SAAgB,aAAa,OAAgB,UAA0B,OAAqB;CAC1F,IAAI,iBAAiB,eACnB,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;EAAE,OAAO;EAAa,SAAS,MAAM;CAAQ,CAAC;CAE9E,IAAI,iBAAiB,eACnB,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;EAAE,OAAO;EAAY,SAAS,MAAM;CAAQ,CAAC;CAE7E,IAAI,iBAAiB,gBACnB,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;EAAE,OAAO;EAAa,SAAS,MAAM;CAAQ,CAAC;CAE9E,IAAI,iBAAiB,uBACnB,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;EAAE,OAAO;EAAY,SAAS,MAAM;CAAQ,CAAC;CAE7E,IAAI,iBAAiB,uBACnB,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;EAAE,OAAO;EAAqB,SAAS,MAAM;CAAQ,CAAC;CAEtF,IAAI,iBAAiB,yBACnB,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;EAAE,OAAO;EAAuB,SAAS,MAAM;CAAQ,CAAC;CAExF,IAAI,iBAAiB,oBACnB,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;EAAE,OAAO;EAAkB,SAAS,MAAM;CAAQ,CAAC;CAEnF,IAAI,iBAAiB,uBACnB,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;EAAE,OAAO;EAAiB,SAAS,MAAM;CAAQ,CAAC;CAElF,IAAI,iBAAiB,qBACnB,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;EAAE,OAAO;EAAmB,SAAS,MAAM;CAAQ,CAAC;CAEpF,IAAI,iBAAiB,kBACnB,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;EAAE,OAAO;EAAa,SAAS,MAAM;CAAQ,CAAC;CAG9E,IAAI,iBAAiB,mBACnB,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK;EAAE,OAAO;EAAgB,SAAS,MAAM;CAAQ,CAAC;CAEjF,MAAM,MAAM;CACZ,IAAI,OAAO,IAAI,eAAe,YAAY,IAAI,cAAc,OAAO,IAAI,aAAa,KAClF,OAAO,MACJ,OAAO,IAAI,UAAU,CAAC,CACtB,KAAK;EAAE,OAAO,IAAI,QAAQ;EAAe,SAAS,IAAI,WAAW;CAAc,CAAC;CAErF,SAAS,IAAI,MAAM,KAAK;CACxB,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAC3D"}
@@ -0,0 +1,7 @@
1
+ import { ServerConfig } from "../config.js";
2
+ import { FastifyInstance } from "fastify";
3
+ //#region src/http/plugins/openapi.d.ts
4
+ declare function registerOpenApi(app: FastifyInstance, config: ServerConfig): void;
5
+ //#endregion
6
+ export { registerOpenApi };
7
+ //# sourceMappingURL=openapi.d.ts.map
@@ -0,0 +1,63 @@
1
+ import { jsonSchemaTransform } from "fastify-type-provider-zod";
2
+ import swagger from "@fastify/swagger";
3
+ import swaggerUi from "@fastify/swagger-ui";
4
+ //#region src/http/plugins/openapi.ts
5
+ function registerOpenApi(app, config) {
6
+ app.register(swagger, {
7
+ openapi: {
8
+ info: {
9
+ title: "Headless LMS API",
10
+ version: "0.0.0"
11
+ },
12
+ servers: [{ url: config.publicUrl }],
13
+ tags: [
14
+ {
15
+ name: "Organizations",
16
+ description: "Organizations and member management"
17
+ },
18
+ {
19
+ name: "Courses",
20
+ description: "Course authoring: courses, modules, activities"
21
+ },
22
+ {
23
+ name: "Learn",
24
+ description: "Student-facing course delivery"
25
+ },
26
+ {
27
+ name: "Students",
28
+ description: "Students and their enrollments"
29
+ },
30
+ {
31
+ name: "Entitlements",
32
+ description: "Access grants to content"
33
+ },
34
+ {
35
+ name: "Progress",
36
+ description: "Completion tracking"
37
+ },
38
+ {
39
+ name: "Automations",
40
+ description: "Trigger/action workflows"
41
+ },
42
+ {
43
+ name: "Dashboard",
44
+ description: "Back-office overview"
45
+ },
46
+ {
47
+ name: "Assets",
48
+ description: "Media library and uploads"
49
+ },
50
+ {
51
+ name: "Integrations",
52
+ description: "Third-party integration connections"
53
+ }
54
+ ]
55
+ },
56
+ transform: jsonSchemaTransform
57
+ });
58
+ app.register(swaggerUi, { routePrefix: "/docs" });
59
+ }
60
+ //#endregion
61
+ export { registerOpenApi };
62
+
63
+ //# sourceMappingURL=openapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.js","names":[],"sources":["../../../src/http/plugins/openapi.ts"],"sourcesContent":["// OpenAPI documentation. @fastify/swagger reads the Zod route schemas (via the\n// transform) to build the spec the frontend SDK is generated from; it must be\n// registered before the routes so its onRoute hook captures them. UI + JSON are\n// served under /docs.\nimport type { FastifyInstance } from 'fastify';\nimport swagger from '@fastify/swagger';\nimport swaggerUi from '@fastify/swagger-ui';\nimport { jsonSchemaTransform } from 'fastify-type-provider-zod';\nimport type { ServerConfig } from '../config.js';\n\nexport function registerOpenApi(app: FastifyInstance, config: ServerConfig): void {\n app.register(swagger, {\n openapi: {\n info: { title: 'Headless LMS API', version: '0.0.0' },\n servers: [{ url: config.publicUrl }],\n tags: [\n { name: 'Organizations', description: 'Organizations and member management' },\n { name: 'Courses', description: 'Course authoring: courses, modules, activities' },\n { name: 'Learn', description: 'Student-facing course delivery' },\n { name: 'Students', description: 'Students and their enrollments' },\n { name: 'Entitlements', description: 'Access grants to content' },\n { name: 'Progress', description: 'Completion tracking' },\n { name: 'Automations', description: 'Trigger/action workflows' },\n { name: 'Dashboard', description: 'Back-office overview' },\n { name: 'Assets', description: 'Media library and uploads' },\n { name: 'Integrations', description: 'Third-party integration connections' },\n ],\n },\n transform: jsonSchemaTransform,\n });\n app.register(swaggerUi, { routePrefix: '/docs' });\n}\n"],"mappings":";;;;AAUA,SAAgB,gBAAgB,KAAsB,QAA4B;CAChF,IAAI,SAAS,SAAS;EACpB,SAAS;GACP,MAAM;IAAE,OAAO;IAAoB,SAAS;GAAQ;GACpD,SAAS,CAAC,EAAE,KAAK,OAAO,UAAU,CAAC;GACnC,MAAM;IACJ;KAAE,MAAM;KAAiB,aAAa;IAAsC;IAC5E;KAAE,MAAM;KAAW,aAAa;IAAiD;IACjF;KAAE,MAAM;KAAS,aAAa;IAAiC;IAC/D;KAAE,MAAM;KAAY,aAAa;IAAiC;IAClE;KAAE,MAAM;KAAgB,aAAa;IAA2B;IAChE;KAAE,MAAM;KAAY,aAAa;IAAsB;IACvD;KAAE,MAAM;KAAe,aAAa;IAA2B;IAC/D;KAAE,MAAM;KAAa,aAAa;IAAuB;IACzD;KAAE,MAAM;KAAU,aAAa;IAA4B;IAC3D;KAAE,MAAM;KAAgB,aAAa;IAAsC;GAC7E;EACF;EACA,WAAW;CACb,CAAC;CACD,IAAI,SAAS,WAAW,EAAE,aAAa,QAAQ,CAAC;AAClD"}
@@ -0,0 +1,7 @@
1
+ import { Container } from "../../app/container.js";
2
+ import { FastifyInstance } from "fastify";
3
+ //#region src/http/routes/activities.d.ts
4
+ declare function activitiesRoutes(app: FastifyInstance, container: Container): Promise<void>;
5
+ //#endregion
6
+ export { activitiesRoutes };
7
+ //# sourceMappingURL=activities.d.ts.map
@@ -0,0 +1,163 @@
1
+ import { resolveScope } from "../scope.js";
2
+ import { ActivityPathParam, CourseIdPathParam, CreateModule, ModuleList, ModulePathParam, ReorderInput, SaveActivity, UpdateModule } from "../schemas/activities.js";
3
+ import "../schemas/index.js";
4
+ //#region src/http/routes/activities.ts
5
+ async function activitiesRoutes(app, container) {
6
+ const r = app.withTypeProvider();
7
+ const content = container.content;
8
+ const tags = ["Courses"];
9
+ r.route({
10
+ method: "GET",
11
+ url: "/api/courses/:courseId/modules",
12
+ preHandler: app.requireOrgSession,
13
+ schema: {
14
+ operationId: "listModules",
15
+ tags,
16
+ summary: "List a course's modules",
17
+ params: CourseIdPathParam,
18
+ response: { 200: ModuleList }
19
+ },
20
+ handler: async (req) => {
21
+ const scope = await resolveScope(container, req);
22
+ return content.listCourseModules(scope.orgId, req.params.courseId);
23
+ }
24
+ });
25
+ r.route({
26
+ method: "POST",
27
+ url: "/api/courses/:courseId/modules",
28
+ preHandler: app.requireOrgSession,
29
+ schema: {
30
+ operationId: "createModule",
31
+ tags,
32
+ summary: "Add a module",
33
+ params: CourseIdPathParam,
34
+ body: CreateModule,
35
+ response: { 200: ModuleList }
36
+ },
37
+ handler: async (req) => {
38
+ const scope = await resolveScope(container, req);
39
+ return content.createModule(scope.orgId, req.params.courseId, req.body.title);
40
+ }
41
+ });
42
+ r.route({
43
+ method: "POST",
44
+ url: "/api/courses/:courseId/modules/reorder",
45
+ preHandler: app.requireOrgSession,
46
+ schema: {
47
+ operationId: "reorderModules",
48
+ tags,
49
+ summary: "Reorder modules",
50
+ params: CourseIdPathParam,
51
+ body: ReorderInput,
52
+ response: { 200: ModuleList }
53
+ },
54
+ handler: async (req) => {
55
+ const scope = await resolveScope(container, req);
56
+ return content.reorderModules(scope.orgId, req.params.courseId, req.body.orderedIds);
57
+ }
58
+ });
59
+ r.route({
60
+ method: "PATCH",
61
+ url: "/api/courses/:courseId/modules/:moduleId",
62
+ preHandler: app.requireOrgSession,
63
+ schema: {
64
+ operationId: "updateModule",
65
+ tags,
66
+ summary: "Rename a module",
67
+ params: ModulePathParam,
68
+ body: UpdateModule,
69
+ response: { 200: ModuleList }
70
+ },
71
+ handler: async (req) => {
72
+ const scope = await resolveScope(container, req);
73
+ return content.updateModule(scope.orgId, req.params.courseId, req.params.moduleId, req.body.title);
74
+ }
75
+ });
76
+ r.route({
77
+ method: "DELETE",
78
+ url: "/api/courses/:courseId/modules/:moduleId",
79
+ preHandler: app.requireOrgSession,
80
+ schema: {
81
+ operationId: "deleteModule",
82
+ tags,
83
+ summary: "Delete a module",
84
+ params: ModulePathParam,
85
+ response: { 200: ModuleList }
86
+ },
87
+ handler: async (req) => {
88
+ const scope = await resolveScope(container, req);
89
+ return content.deleteModule(scope.orgId, req.params.courseId, req.params.moduleId);
90
+ }
91
+ });
92
+ r.route({
93
+ method: "POST",
94
+ url: "/api/courses/:courseId/modules/:moduleId/activities/reorder",
95
+ preHandler: app.requireOrgSession,
96
+ schema: {
97
+ operationId: "reorderActivities",
98
+ tags,
99
+ summary: "Reorder activities in a module",
100
+ params: ModulePathParam,
101
+ body: ReorderInput,
102
+ response: { 200: ModuleList }
103
+ },
104
+ handler: async (req) => {
105
+ const scope = await resolveScope(container, req);
106
+ return content.reorderActivities(scope.orgId, req.params.courseId, req.params.moduleId, req.body.orderedIds);
107
+ }
108
+ });
109
+ r.route({
110
+ method: "POST",
111
+ url: "/api/courses/:courseId/modules/:moduleId/activities",
112
+ preHandler: app.requireOrgSession,
113
+ schema: {
114
+ operationId: "createActivity",
115
+ tags,
116
+ summary: "Add an activity",
117
+ params: ModulePathParam,
118
+ body: SaveActivity,
119
+ response: { 200: ModuleList }
120
+ },
121
+ handler: async (req) => {
122
+ const scope = await resolveScope(container, req);
123
+ return content.saveActivity(scope.orgId, req.params.courseId, req.params.moduleId, req.body);
124
+ }
125
+ });
126
+ r.route({
127
+ method: "PATCH",
128
+ url: "/api/courses/:courseId/modules/:moduleId/activities/:activityId",
129
+ preHandler: app.requireOrgSession,
130
+ schema: {
131
+ operationId: "updateActivity",
132
+ tags,
133
+ summary: "Update an activity",
134
+ params: ActivityPathParam,
135
+ body: SaveActivity,
136
+ response: { 200: ModuleList }
137
+ },
138
+ handler: async (req) => {
139
+ const scope = await resolveScope(container, req);
140
+ return content.saveActivity(scope.orgId, req.params.courseId, req.params.moduleId, req.body, req.params.activityId);
141
+ }
142
+ });
143
+ r.route({
144
+ method: "DELETE",
145
+ url: "/api/courses/:courseId/modules/:moduleId/activities/:activityId",
146
+ preHandler: app.requireOrgSession,
147
+ schema: {
148
+ operationId: "deleteActivity",
149
+ tags,
150
+ summary: "Delete an activity",
151
+ params: ActivityPathParam,
152
+ response: { 200: ModuleList }
153
+ },
154
+ handler: async (req) => {
155
+ const scope = await resolveScope(container, req);
156
+ return content.deleteActivity(scope.orgId, req.params.courseId, req.params.moduleId, req.params.activityId);
157
+ }
158
+ });
159
+ }
160
+ //#endregion
161
+ export { activitiesRoutes };
162
+
163
+ //# sourceMappingURL=activities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activities.js","names":[],"sources":["../../../src/http/routes/activities.ts"],"sourcesContent":["// HTTP routes for course modules + activities. Write operations return the\n// course's full, reordered module list (how the editor re-renders). Nested under\n// courses. All require a session with an active organization; handlers resolve\n// the session's active org to the domain org id and scope every call by it.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n ActivityPathParam,\n CourseIdPathParam,\n CreateModule,\n ModuleList,\n ModulePathParam,\n ReorderInput,\n SaveActivity,\n UpdateModule,\n} from '../schemas/index.js';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function activitiesRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n // Modules/activities are part of the content aggregate; grouped under the Courses tag.\n const content = container.content;\n const tags = ['Courses'];\n\n r.route({\n method: 'GET',\n url: '/api/courses/:courseId/modules',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listModules',\n tags,\n summary: \"List a course's modules\",\n params: CourseIdPathParam,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listCourseModules(scope.orgId, req.params.courseId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses/:courseId/modules',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createModule',\n tags,\n summary: 'Add a module',\n params: CourseIdPathParam,\n body: CreateModule,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.createModule(scope.orgId, req.params.courseId, req.body.title);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses/:courseId/modules/reorder',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reorderModules',\n tags,\n summary: 'Reorder modules',\n params: CourseIdPathParam,\n body: ReorderInput,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.reorderModules(scope.orgId, req.params.courseId, req.body.orderedIds);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/courses/:courseId/modules/:moduleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateModule',\n tags,\n summary: 'Rename a module',\n params: ModulePathParam,\n body: UpdateModule,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.updateModule(\n scope.orgId,\n req.params.courseId,\n req.params.moduleId,\n req.body.title,\n );\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/courses/:courseId/modules/:moduleId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteModule',\n tags,\n summary: 'Delete a module',\n params: ModulePathParam,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.deleteModule(scope.orgId, req.params.courseId, req.params.moduleId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses/:courseId/modules/:moduleId/activities/reorder',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reorderActivities',\n tags,\n summary: 'Reorder activities in a module',\n params: ModulePathParam,\n body: ReorderInput,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.reorderActivities(\n scope.orgId,\n req.params.courseId,\n req.params.moduleId,\n req.body.orderedIds,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses/:courseId/modules/:moduleId/activities',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createActivity',\n tags,\n summary: 'Add an activity',\n params: ModulePathParam,\n body: SaveActivity,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.saveActivity(scope.orgId, req.params.courseId, req.params.moduleId, req.body);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/courses/:courseId/modules/:moduleId/activities/:activityId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateActivity',\n tags,\n summary: 'Update an activity',\n params: ActivityPathParam,\n body: SaveActivity,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.saveActivity(\n scope.orgId,\n req.params.courseId,\n req.params.moduleId,\n req.body,\n req.params.activityId,\n );\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/courses/:courseId/modules/:moduleId/activities/:activityId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteActivity',\n tags,\n summary: 'Delete an activity',\n params: ActivityPathParam,\n response: { 200: ModuleList },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.deleteActivity(\n scope.orgId,\n req.params.courseId,\n req.params.moduleId,\n req.params.activityId,\n );\n },\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Container } from "../../app/container.js";
2
+ import { FastifyInstance } from "fastify";
3
+ //#region src/http/routes/assets.d.ts
4
+ declare function assetsRoutes(app: FastifyInstance, container: Container): Promise<void>;
5
+ //#endregion
6
+ export { assetsRoutes };
7
+ //# sourceMappingURL=assets.d.ts.map