@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,168 @@
1
+ import { ErrorBody } from "../schemas/shared.js";
2
+ import { Asset, AssetIdParam, AssetsPage, AssetsQuery, DownloadTicket, RequestDownload, RequestUpload, UploadTicket } from "../schemas/assets.js";
3
+ import "../schemas/index.js";
4
+ import { NotFoundError } from "@headless-lms/core/shared/errors";
5
+ import { z } from "zod";
6
+ //#region src/http/routes/assets.ts
7
+ /** Resolve the session's active org to the domain org id, or 400 and return null. */
8
+ async function resolveOrgId(req, reply) {
9
+ if (!req.orgId) {
10
+ await reply.code(400).send({
11
+ error: "no_active_org",
12
+ message: "No active organization"
13
+ });
14
+ return null;
15
+ }
16
+ return req.orgId;
17
+ }
18
+ async function assetsRoutes(app, container) {
19
+ const r = app.withTypeProvider();
20
+ const assets = container.assets;
21
+ const tags = ["Assets"];
22
+ r.route({
23
+ method: "POST",
24
+ url: "/api/uploads",
25
+ preHandler: app.requireOrgSession,
26
+ schema: {
27
+ operationId: "requestUpload",
28
+ tags,
29
+ summary: "Register an asset and get a presigned upload URL",
30
+ body: RequestUpload,
31
+ response: {
32
+ 201: UploadTicket,
33
+ 400: ErrorBody,
34
+ 401: ErrorBody
35
+ }
36
+ },
37
+ handler: async (req, reply) => {
38
+ const orgId = await resolveOrgId(req, reply);
39
+ if (!orgId) return;
40
+ const ticket = await assets.requestUpload(orgId, {
41
+ uploadedBy: req.userId,
42
+ ...req.body
43
+ });
44
+ return reply.code(201).send(ticket);
45
+ }
46
+ });
47
+ r.route({
48
+ method: "POST",
49
+ url: "/api/assets/:id/confirm",
50
+ preHandler: app.requireOrgSession,
51
+ schema: {
52
+ operationId: "confirmAsset",
53
+ tags,
54
+ summary: "Confirm an upload completed (captures size + content type)",
55
+ params: AssetIdParam,
56
+ response: {
57
+ 200: Asset,
58
+ 400: ErrorBody,
59
+ 401: ErrorBody,
60
+ 404: ErrorBody
61
+ }
62
+ },
63
+ handler: async (req, reply) => {
64
+ const orgId = await resolveOrgId(req, reply);
65
+ if (!orgId) return;
66
+ const asset = await assets.confirm(orgId, req.params.id);
67
+ if (!asset) throw new NotFoundError("Asset", req.params.id);
68
+ return asset;
69
+ }
70
+ });
71
+ r.route({
72
+ method: "GET",
73
+ url: "/api/assets",
74
+ preHandler: app.requireOrgSession,
75
+ schema: {
76
+ operationId: "listAssets",
77
+ tags,
78
+ summary: "Browse the organization's media library",
79
+ querystring: AssetsQuery,
80
+ response: {
81
+ 200: AssetsPage,
82
+ 400: ErrorBody,
83
+ 401: ErrorBody
84
+ }
85
+ },
86
+ handler: async (req, reply) => {
87
+ const orgId = await resolveOrgId(req, reply);
88
+ if (!orgId) return;
89
+ return assets.list(orgId, req.query);
90
+ }
91
+ });
92
+ r.route({
93
+ method: "GET",
94
+ url: "/api/assets/:id",
95
+ preHandler: app.requireOrgSession,
96
+ schema: {
97
+ operationId: "getAsset",
98
+ tags,
99
+ summary: "Get an asset's metadata",
100
+ params: AssetIdParam,
101
+ response: {
102
+ 200: Asset,
103
+ 400: ErrorBody,
104
+ 401: ErrorBody,
105
+ 404: ErrorBody
106
+ }
107
+ },
108
+ handler: async (req, reply) => {
109
+ const orgId = await resolveOrgId(req, reply);
110
+ if (!orgId) return;
111
+ const asset = await assets.get(orgId, req.params.id);
112
+ if (!asset) throw new NotFoundError("Asset", req.params.id);
113
+ return asset;
114
+ }
115
+ });
116
+ r.route({
117
+ method: "POST",
118
+ url: "/api/assets/:id/download-url",
119
+ preHandler: app.requireOrgSession,
120
+ schema: {
121
+ operationId: "requestAssetDownload",
122
+ tags,
123
+ summary: "Get a short-lived presigned URL to download/serve an asset",
124
+ params: AssetIdParam,
125
+ body: RequestDownload,
126
+ response: {
127
+ 200: DownloadTicket,
128
+ 400: ErrorBody,
129
+ 401: ErrorBody,
130
+ 404: ErrorBody
131
+ }
132
+ },
133
+ handler: async (req, reply) => {
134
+ const orgId = await resolveOrgId(req, reply);
135
+ if (!orgId) return;
136
+ const ticket = await assets.requestDownload(orgId, req.params.id, req.body.filename);
137
+ if (!ticket) throw new NotFoundError("Asset", req.params.id);
138
+ return ticket;
139
+ }
140
+ });
141
+ r.route({
142
+ method: "DELETE",
143
+ url: "/api/assets/:id",
144
+ preHandler: app.requireOrgSession,
145
+ schema: {
146
+ operationId: "deleteAsset",
147
+ tags,
148
+ summary: "Delete an asset (removes the object from storage)",
149
+ params: AssetIdParam,
150
+ response: {
151
+ 204: z.void(),
152
+ 400: ErrorBody,
153
+ 401: ErrorBody,
154
+ 404: ErrorBody
155
+ }
156
+ },
157
+ handler: async (req, reply) => {
158
+ const orgId = await resolveOrgId(req, reply);
159
+ if (!orgId) return;
160
+ if (!await assets.remove(orgId, req.params.id)) throw new NotFoundError("Asset", req.params.id);
161
+ return reply.code(204).send();
162
+ }
163
+ });
164
+ }
165
+ //#endregion
166
+ export { assetsRoutes };
167
+
168
+ //# sourceMappingURL=assets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.js","names":[],"sources":["../../../src/http/routes/assets.ts"],"sourcesContent":["// HTTP routes for the assets media library + content delivery. All require a\n// session with an active organization; assets are stored under the org's\n// private prefix and served only through short-lived presigned URLs.\n//\n// `req.orgId` is already the domain organization id these tables key on — the\n// session guard translates it from better-auth's id once per request.\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n Asset,\n AssetIdParam,\n AssetsPage,\n AssetsQuery,\n DownloadTicket,\n ErrorBody,\n RequestDownload,\n RequestUpload,\n UploadTicket,\n} from '../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\n\n/** Resolve the session's active org to the domain org id, or 400 and return null. */\nasync function resolveOrgId(\n req: FastifyRequest,\n reply: FastifyReply,\n): Promise<string | null> {\n if (!req.orgId) {\n await reply.code(400).send({ error: 'no_active_org', message: 'No active organization' });\n return null;\n }\n return req.orgId;\n}\n\nexport async function assetsRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const assets = container.assets;\n const tags = ['Assets'];\n\n // Register an asset + get a presigned upload URL. Kept at /api/uploads as the\n // \"start an upload\" action; the resulting asset lives in the library below.\n r.route({\n method: 'POST',\n url: '/api/uploads',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'requestUpload',\n tags,\n summary: 'Register an asset and get a presigned upload URL',\n body: RequestUpload,\n response: { 201: UploadTicket, 400: ErrorBody, 401: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const ticket = await assets.requestUpload(orgId, {\n // Domain `users.id`, like every other org-scoped row — not the auth id.\n uploadedBy: req.userId,\n ...req.body,\n });\n return reply.code(201).send(ticket);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/assets/:id/confirm',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'confirmAsset',\n tags,\n summary: 'Confirm an upload completed (captures size + content type)',\n params: AssetIdParam,\n response: { 200: Asset, 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const asset = await assets.confirm(orgId, req.params.id);\n if (!asset) {\n throw new NotFoundError('Asset', req.params.id);\n }\n return asset;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/assets',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAssets',\n tags,\n summary: \"Browse the organization's media library\",\n querystring: AssetsQuery,\n response: { 200: AssetsPage, 400: ErrorBody, 401: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n return assets.list(orgId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/assets/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getAsset',\n tags,\n summary: \"Get an asset's metadata\",\n params: AssetIdParam,\n response: { 200: Asset, 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const asset = await assets.get(orgId, req.params.id);\n if (!asset) {\n throw new NotFoundError('Asset', req.params.id);\n }\n return asset;\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/assets/:id/download-url',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'requestAssetDownload',\n tags,\n summary: 'Get a short-lived presigned URL to download/serve an asset',\n params: AssetIdParam,\n body: RequestDownload,\n response: { 200: DownloadTicket, 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const ticket = await assets.requestDownload(orgId, req.params.id, req.body.filename);\n if (!ticket) {\n throw new NotFoundError('Asset', req.params.id);\n }\n return ticket;\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/assets/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteAsset',\n tags,\n summary: 'Delete an asset (removes the object from storage)',\n params: AssetIdParam,\n response: { 204: z.void(), 400: ErrorBody, 401: ErrorBody, 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const orgId = await resolveOrgId(req, reply);\n if (!orgId) {\n return;\n }\n const removed = await assets.remove(orgId, req.params.id);\n if (!removed) {\n throw new NotFoundError('Asset', req.params.id);\n }\n return reply.code(204).send();\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/automations.d.ts
4
+ declare function automationsRoutes(app: FastifyInstance, container: Container): Promise<void>;
5
+ //#endregion
6
+ export { automationsRoutes };
7
+ //# sourceMappingURL=automations.d.ts.map
@@ -0,0 +1,158 @@
1
+ import { resolveScope } from "../scope.js";
2
+ import { ErrorBody } from "../schemas/shared.js";
3
+ import { Automation, AutomationIdParam, AutomationRunsPage, AutomationRunsQuery, AvailableAction, AvailableTriggers, CreateAutomationBody, UpdateAutomationBody } from "../schemas/automations.js";
4
+ import "../schemas/index.js";
5
+ import { NotFoundError } from "@headless-lms/core/shared/errors";
6
+ import { z } from "zod";
7
+ //#region src/http/routes/automations.ts
8
+ async function automationsRoutes(app, container) {
9
+ const r = app.withTypeProvider();
10
+ const automations = container.automations;
11
+ const tags = ["Automations"];
12
+ r.route({
13
+ method: "GET",
14
+ url: "/api/automations",
15
+ preHandler: app.requireOrgSession,
16
+ schema: {
17
+ operationId: "listAutomations",
18
+ tags,
19
+ summary: "List automations",
20
+ response: { 200: z.array(Automation) }
21
+ },
22
+ handler: async (req) => {
23
+ const scope = await resolveScope(container, req);
24
+ return automations.list(scope.orgId);
25
+ }
26
+ });
27
+ r.route({
28
+ method: "GET",
29
+ url: "/api/automations/actions",
30
+ preHandler: app.requireOrgSession,
31
+ schema: {
32
+ operationId: "listAutomationActions",
33
+ tags,
34
+ summary: "List the actions automations can use",
35
+ response: { 200: z.array(AvailableAction) }
36
+ },
37
+ handler: async (req) => {
38
+ await resolveScope(container, req);
39
+ return automations.availableActions();
40
+ }
41
+ });
42
+ r.route({
43
+ method: "GET",
44
+ url: "/api/automations/triggers",
45
+ preHandler: app.requireOrgSession,
46
+ schema: {
47
+ operationId: "listAutomationTriggers",
48
+ tags,
49
+ summary: "List the domain events automations can react to",
50
+ response: { 200: AvailableTriggers }
51
+ },
52
+ handler: async (req) => {
53
+ await resolveScope(container, req);
54
+ return automations.availableTriggers();
55
+ }
56
+ });
57
+ r.route({
58
+ method: "POST",
59
+ url: "/api/automations",
60
+ preHandler: app.requireOrgSession,
61
+ schema: {
62
+ operationId: "createAutomation",
63
+ tags,
64
+ summary: "Create an automation",
65
+ body: CreateAutomationBody,
66
+ response: { 201: Automation }
67
+ },
68
+ handler: async (req, reply) => {
69
+ const scope = await resolveScope(container, req);
70
+ const automation = await automations.create(scope.orgId, req.body);
71
+ return reply.code(201).send(automation);
72
+ }
73
+ });
74
+ r.route({
75
+ method: "GET",
76
+ url: "/api/automations/:id",
77
+ preHandler: app.requireOrgSession,
78
+ schema: {
79
+ operationId: "getAutomation",
80
+ tags,
81
+ summary: "Get an automation by id",
82
+ params: AutomationIdParam,
83
+ response: {
84
+ 200: Automation,
85
+ 404: ErrorBody
86
+ }
87
+ },
88
+ handler: async (req) => {
89
+ const scope = await resolveScope(container, req);
90
+ const automation = await automations.get(scope.orgId, req.params.id);
91
+ if (!automation) throw new NotFoundError("Automation", req.params.id);
92
+ return automation;
93
+ }
94
+ });
95
+ r.route({
96
+ method: "PATCH",
97
+ url: "/api/automations/:id",
98
+ preHandler: app.requireOrgSession,
99
+ schema: {
100
+ operationId: "updateAutomation",
101
+ tags,
102
+ summary: "Update an automation",
103
+ params: AutomationIdParam,
104
+ body: UpdateAutomationBody,
105
+ response: {
106
+ 200: Automation,
107
+ 404: ErrorBody
108
+ }
109
+ },
110
+ handler: async (req) => {
111
+ const scope = await resolveScope(container, req);
112
+ const automation = await automations.update(scope.orgId, req.params.id, req.body);
113
+ if (!automation) throw new NotFoundError("Automation", req.params.id);
114
+ return automation;
115
+ }
116
+ });
117
+ r.route({
118
+ method: "DELETE",
119
+ url: "/api/automations/:id",
120
+ preHandler: app.requireOrgSession,
121
+ schema: {
122
+ operationId: "deleteAutomation",
123
+ tags,
124
+ summary: "Delete an automation",
125
+ params: AutomationIdParam,
126
+ response: {
127
+ 204: z.void(),
128
+ 404: ErrorBody
129
+ }
130
+ },
131
+ handler: async (req, reply) => {
132
+ const scope = await resolveScope(container, req);
133
+ if (!await automations.delete(scope.orgId, req.params.id)) throw new NotFoundError("Automation", req.params.id);
134
+ return reply.code(204).send();
135
+ }
136
+ });
137
+ r.route({
138
+ method: "GET",
139
+ url: "/api/automations/:id/runs",
140
+ preHandler: app.requireOrgSession,
141
+ schema: {
142
+ operationId: "listAutomationRuns",
143
+ tags,
144
+ summary: "List an automation's runs — a deleted automation's runs remain reachable (audit trail)",
145
+ params: AutomationIdParam,
146
+ querystring: AutomationRunsQuery,
147
+ response: { 200: AutomationRunsPage }
148
+ },
149
+ handler: async (req) => {
150
+ const scope = await resolveScope(container, req);
151
+ return automations.listRuns(scope.orgId, req.params.id, req.query);
152
+ }
153
+ });
154
+ }
155
+ //#endregion
156
+ export { automationsRoutes };
157
+
158
+ //# sourceMappingURL=automations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"automations.js","names":[],"sources":["../../../src/http/routes/automations.ts"],"sourcesContent":["// HTTP routes for the automations context: rules that match a trigger (a\n// domain event type) against enabled automations and run an ordered list of\n// actions, plus the run history each automation accumulates.\nimport type { FastifyInstance } from 'fastify';\nimport { z } from 'zod';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n Automation,\n AutomationIdParam,\n AutomationRunsPage,\n AutomationRunsQuery,\n AvailableAction,\n AvailableTriggers,\n CreateAutomationBody,\n ErrorBody,\n UpdateAutomationBody,\n} from '../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function automationsRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const automations = container.automations;\n const tags = ['Automations'];\n\n r.route({\n method: 'GET',\n url: '/api/automations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAutomations',\n tags,\n summary: 'List automations',\n response: { 200: z.array(Automation) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return automations.list(scope.orgId);\n },\n });\n\n // Before `/api/automations/:id` — otherwise `:id` would swallow these path segments.\n r.route({\n method: 'GET',\n url: '/api/automations/actions',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAutomationActions',\n tags,\n summary: 'List the actions automations can use',\n response: { 200: z.array(AvailableAction) },\n },\n handler: async (req) => {\n await resolveScope(container, req);\n return automations.availableActions();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/automations/triggers',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAutomationTriggers',\n tags,\n summary: 'List the domain events automations can react to',\n response: { 200: AvailableTriggers },\n },\n handler: async (req) => {\n await resolveScope(container, req);\n return automations.availableTriggers();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/automations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createAutomation',\n tags,\n summary: 'Create an automation',\n body: CreateAutomationBody,\n response: { 201: Automation },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const automation = await automations.create(scope.orgId, req.body);\n return reply.code(201).send(automation);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/automations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getAutomation',\n tags,\n summary: 'Get an automation by id',\n params: AutomationIdParam,\n response: { 200: Automation, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const automation = await automations.get(scope.orgId, req.params.id);\n if (!automation) {\n throw new NotFoundError('Automation', req.params.id);\n }\n return automation;\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/automations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateAutomation',\n tags,\n summary: 'Update an automation',\n params: AutomationIdParam,\n body: UpdateAutomationBody,\n response: { 200: Automation, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const automation = await automations.update(scope.orgId, req.params.id, req.body);\n if (!automation) {\n throw new NotFoundError('Automation', req.params.id);\n }\n return automation;\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/automations/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteAutomation',\n tags,\n summary: 'Delete an automation',\n params: AutomationIdParam,\n response: { 204: z.void(), 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const deleted = await automations.delete(scope.orgId, req.params.id);\n if (!deleted) {\n throw new NotFoundError('Automation', req.params.id);\n }\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/automations/:id/runs',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listAutomationRuns',\n tags,\n summary: \"List an automation's runs — a deleted automation's runs remain reachable (audit trail)\",\n params: AutomationIdParam,\n querystring: AutomationRunsQuery,\n response: { 200: AutomationRunsPage },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n // No existence pre-check: runs deliberately survive automation deletion (audit trail).\n return automations.listRuns(scope.orgId, req.params.id, req.query);\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/courses.d.ts
4
+ declare function coursesRoutes(app: FastifyInstance, container: Container): Promise<void>;
5
+ //#endregion
6
+ export { coursesRoutes };
7
+ //# sourceMappingURL=courses.d.ts.map
@@ -0,0 +1,130 @@
1
+ import { resolveScope } from "../scope.js";
2
+ import { ErrorBody } from "../schemas/shared.js";
3
+ import { Course, CourseIdParam, CourseSettings, CoursesPage, CoursesQuery, CreateCourse, PatchCourseSettings, UpdateCourse } from "../schemas/content.js";
4
+ import "../schemas/index.js";
5
+ import { NotFoundError } from "@headless-lms/core/shared/errors";
6
+ import { z } from "zod";
7
+ //#region src/http/routes/courses.ts
8
+ async function coursesRoutes(app, container) {
9
+ const r = app.withTypeProvider();
10
+ const courses = container.content;
11
+ r.route({
12
+ method: "GET",
13
+ url: "/api/courses",
14
+ preHandler: app.requireOrgSession,
15
+ schema: {
16
+ operationId: "listCourses",
17
+ tags: ["Courses"],
18
+ summary: "List courses",
19
+ querystring: CoursesQuery,
20
+ response: { 200: CoursesPage }
21
+ },
22
+ handler: async (req) => {
23
+ const scope = await resolveScope(container, req);
24
+ return courses.list(scope.orgId, req.query);
25
+ }
26
+ });
27
+ r.route({
28
+ method: "GET",
29
+ url: "/api/courses/:id",
30
+ preHandler: app.requireOrgSession,
31
+ schema: {
32
+ operationId: "getCourse",
33
+ tags: ["Courses"],
34
+ summary: "Get a course by id",
35
+ params: CourseIdParam,
36
+ response: {
37
+ 200: Course,
38
+ 404: ErrorBody
39
+ }
40
+ },
41
+ handler: async (req) => {
42
+ const scope = await resolveScope(container, req);
43
+ const course = await courses.getCourse(scope.orgId, req.params.id);
44
+ if (!course) throw new NotFoundError("Course", req.params.id);
45
+ return course;
46
+ }
47
+ });
48
+ r.route({
49
+ method: "POST",
50
+ url: "/api/courses",
51
+ preHandler: app.requireOrgSession,
52
+ schema: {
53
+ operationId: "createCourse",
54
+ tags: ["Courses"],
55
+ summary: "Create a course",
56
+ body: CreateCourse,
57
+ response: { 201: Course }
58
+ },
59
+ handler: async (req, reply) => {
60
+ const scope = await resolveScope(container, req);
61
+ const course = await courses.createCourse(scope.orgId, req.body);
62
+ return reply.code(201).send(course);
63
+ }
64
+ });
65
+ r.route({
66
+ method: "PATCH",
67
+ url: "/api/courses/:id",
68
+ preHandler: app.requireOrgSession,
69
+ schema: {
70
+ operationId: "updateCourse",
71
+ tags: ["Courses"],
72
+ summary: "Update a course",
73
+ params: CourseIdParam,
74
+ body: UpdateCourse,
75
+ response: {
76
+ 200: Course,
77
+ 404: ErrorBody
78
+ }
79
+ },
80
+ handler: async (req) => {
81
+ const scope = await resolveScope(container, req);
82
+ return courses.updateCourse(scope.orgId, req.params.id, req.body);
83
+ }
84
+ });
85
+ r.route({
86
+ method: "PATCH",
87
+ url: "/api/courses/:id/settings",
88
+ preHandler: app.requireOrgSession,
89
+ schema: {
90
+ operationId: "updateCourseSettings",
91
+ tags: ["Courses"],
92
+ summary: "Update a course settings",
93
+ params: CourseIdParam,
94
+ body: PatchCourseSettings,
95
+ response: {
96
+ 200: CourseSettings,
97
+ 404: ErrorBody
98
+ }
99
+ },
100
+ handler: async (req) => {
101
+ const scope = await resolveScope(container, req);
102
+ req.log.info({ body: req.body }, "update course settings");
103
+ return courses.patchSettings(scope.orgId, req.params.id, req.body);
104
+ }
105
+ });
106
+ r.route({
107
+ method: "DELETE",
108
+ url: "/api/courses/:id",
109
+ preHandler: app.requireOrgSession,
110
+ schema: {
111
+ operationId: "deleteCourse",
112
+ tags: ["Courses"],
113
+ summary: "Delete a course",
114
+ params: CourseIdParam,
115
+ response: {
116
+ 204: z.void(),
117
+ 404: ErrorBody
118
+ }
119
+ },
120
+ handler: async (req, reply) => {
121
+ const scope = await resolveScope(container, req);
122
+ await courses.deleteCourse(scope.orgId, req.params.id);
123
+ return reply.code(204).send();
124
+ }
125
+ });
126
+ }
127
+ //#endregion
128
+ export { coursesRoutes };
129
+
130
+ //# sourceMappingURL=courses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"courses.js","names":[],"sources":["../../../src/http/routes/courses.ts"],"sourcesContent":["// HTTP routes for the courses context. Request + response are validated against\n// the shared contract schemas by the Zod type provider, and @fastify/swagger\n// reads the same schemas to build the OpenAPI spec the SDK is generated from.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n Course,\n CourseIdParam,\n CourseSettings,\n CoursesPage,\n CoursesQuery,\n CreateCourse,\n ErrorBody,\n PatchCourseSettings,\n UpdateCourse,\n} from '../schemas/index.js';\nimport { z } from 'zod';\nimport type { Container } from '../../app/container.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport { resolveScope } from '../scope.js';\n\nexport async function coursesRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const courses = container.content;\n\n r.route({\n method: 'GET',\n url: '/api/courses',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listCourses',\n tags: ['Courses'],\n summary: 'List courses',\n querystring: CoursesQuery,\n response: { 200: CoursesPage },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return courses.list(scope.orgId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/courses/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getCourse',\n tags: ['Courses'],\n summary: 'Get a course by id',\n params: CourseIdParam,\n response: { 200: Course, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const course = await courses.getCourse(scope.orgId, req.params.id);\n if (!course) {\n throw new NotFoundError('Course', req.params.id);\n }\n return course;\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/courses',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createCourse',\n tags: ['Courses'],\n summary: 'Create a course',\n body: CreateCourse,\n response: { 201: Course },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const course = await courses.createCourse(scope.orgId, req.body);\n return reply.code(201).send(course);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/courses/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateCourse',\n tags: ['Courses'],\n summary: 'Update a course',\n params: CourseIdParam,\n body: UpdateCourse,\n response: { 200: Course, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return courses.updateCourse(scope.orgId, req.params.id, req.body);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/courses/:id/settings',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateCourseSettings',\n tags: ['Courses'],\n summary: 'Update a course settings',\n params: CourseIdParam,\n body: PatchCourseSettings,\n response: { 200: CourseSettings, 404: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n req.log.info( { body: req.body },'update course settings',);\n return courses.patchSettings(scope.orgId, req.params.id, req.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/courses/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteCourse',\n tags: ['Courses'],\n summary: 'Delete a course',\n params: CourseIdParam,\n response: { 204: z.void(), 404: ErrorBody },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await courses.deleteCourse(scope.orgId, req.params.id);\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":";;;;;;;AAqBA,eAAsB,cAAc,KAAsB,WAAqC;CAC7F,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,UAAU,UAAU;CAE1B,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,aAAa;GACb,UAAU,EAAE,KAAK,YAAY;EAC/B;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,QAAQ,KAAK,MAAM,OAAO,IAAI,KAAK;EAC5C;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK;IAAQ,KAAK;GAAU;EAC1C;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,SAAS,MAAM,QAAQ,UAAU,MAAM,OAAO,IAAI,OAAO,EAAE;GACjE,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,EAAE;GAEjD,OAAO;EACT;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,MAAM;GACN,UAAU,EAAE,KAAK,OAAO;EAC1B;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,SAAS,MAAM,QAAQ,aAAa,MAAM,OAAO,IAAI,IAAI;GAC/D,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,MAAM;EACpC;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,QAAQ;GACR,MAAM;GACN,UAAU;IAAE,KAAK;IAAQ,KAAK;GAAU;EAC1C;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,QAAQ,aAAa,MAAM,OAAO,IAAI,OAAO,IAAI,IAAI,IAAI;EAClE;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,QAAQ;GACR,MAAM;GACN,UAAU;IAAE,KAAK;IAAgB,KAAK;GAAU;EAClD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,IAAI,IAAI,KAAM,EAAE,MAAM,IAAI,KAAK,GAAE,wBAAyB;GAC1D,OAAO,QAAQ,cAAc,MAAM,OAAO,IAAI,OAAO,IAAI,IAAI,IAAI;EACnE;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,SAAS;GAChB,SAAS;GACT,QAAQ;GACR,UAAU;IAAE,KAAK,EAAE,KAAK;IAAG,KAAK;GAAU;EAC5C;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,QAAQ,aAAa,MAAM,OAAO,IAAI,OAAO,EAAE;GACrD,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;CACF,CAAC;AACH"}
@@ -0,0 +1,7 @@
1
+ import { Container } from "../../app/container.js";
2
+ import { FastifyInstance } from "fastify";
3
+ //#region src/http/routes/dashboard.d.ts
4
+ declare function dashboardRoutes(app: FastifyInstance, container: Container): Promise<void>;
5
+ //#endregion
6
+ export { dashboardRoutes };
7
+ //# sourceMappingURL=dashboard.d.ts.map
@@ -0,0 +1,42 @@
1
+ import { resolveScope } from "../scope.js";
2
+ import { EnrollmentSeries, EnrollmentSeriesQuery, OverviewStats } from "../schemas/dashboard.js";
3
+ import "../schemas/index.js";
4
+ //#region src/http/routes/dashboard.ts
5
+ async function dashboardRoutes(app, container) {
6
+ const r = app.withTypeProvider();
7
+ r.route({
8
+ method: "GET",
9
+ url: "/api/overview",
10
+ preHandler: app.requireOrgSession,
11
+ schema: {
12
+ operationId: "getOverview",
13
+ tags: ["Dashboard"],
14
+ summary: "Back-office overview stats",
15
+ response: { 200: OverviewStats }
16
+ },
17
+ handler: async (req) => {
18
+ const scope = await resolveScope(container, req);
19
+ return container.reporting.dashboard.overview(scope.orgId);
20
+ }
21
+ });
22
+ r.route({
23
+ method: "GET",
24
+ url: "/api/overview/enrollments",
25
+ preHandler: app.requireOrgSession,
26
+ schema: {
27
+ operationId: "getEnrollmentSeries",
28
+ tags: ["Dashboard"],
29
+ summary: "Enrollments granted per day over a trailing window",
30
+ querystring: EnrollmentSeriesQuery,
31
+ response: { 200: EnrollmentSeries }
32
+ },
33
+ handler: async (req) => {
34
+ const scope = await resolveScope(container, req);
35
+ return container.reporting.dashboard.enrollments(scope.orgId, req.query.days);
36
+ }
37
+ });
38
+ }
39
+ //#endregion
40
+ export { dashboardRoutes };
41
+
42
+ //# sourceMappingURL=dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard.js","names":[],"sources":["../../../src/http/routes/dashboard.ts"],"sourcesContent":["// HTTP routes for the dashboard (overview) context.\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { EnrollmentSeries, EnrollmentSeriesQuery, OverviewStats } from '../schemas/index.js';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function dashboardRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n\n r.route({\n method: 'GET',\n url: '/api/overview',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getOverview',\n tags: ['Dashboard'],\n summary: 'Back-office overview stats',\n response: { 200: OverviewStats },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.reporting.dashboard.overview(scope.orgId);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/overview/enrollments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getEnrollmentSeries',\n tags: ['Dashboard'],\n summary: 'Enrollments granted per day over a trailing window',\n querystring: EnrollmentSeriesQuery,\n response: { 200: EnrollmentSeries },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.reporting.dashboard.enrollments(scope.orgId, req.query.days);\n },\n });\n}\n"],"mappings":";;;;AAOA,eAAsB,gBAAgB,KAAsB,WAAqC;CAC/F,MAAM,IAAI,IAAI,iBAAkC;CAEhD,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,WAAW;GAClB,SAAS;GACT,UAAU,EAAE,KAAK,cAAc;EACjC;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,UAAU,UAAU,SAAS,MAAM,KAAK;EAC3D;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,WAAW;GAClB,SAAS;GACT,aAAa;GACb,UAAU,EAAE,KAAK,iBAAiB;EACpC;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,UAAU,UAAU,UAAU,YAAY,MAAM,OAAO,IAAI,MAAM,IAAI;EAC9E;CACF,CAAC;AACH"}
@@ -0,0 +1,7 @@
1
+ import { Container } from "../../app/container.js";
2
+ import { FastifyInstance } from "fastify";
3
+ //#region src/http/routes/discussion.d.ts
4
+ declare function discussionRoutes(app: FastifyInstance, container: Container): Promise<void>;
5
+ //#endregion
6
+ export { discussionRoutes };
7
+ //# sourceMappingURL=discussion.d.ts.map