create-mithaq 0.1.1 → 0.2.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 (348) hide show
  1. package/LICENSES/openstatus-time-picker.txt +21 -0
  2. package/index.js +457 -63
  3. package/package.json +1 -1
  4. package/template/apps/{asas-platform → asas-backoffice}/package.json +5 -3
  5. package/template/apps/{asas-platform → asas-backoffice}/src/api.ts +1 -1
  6. package/template/apps/{asas-platform → asas-backoffice}/src/auth-frame.tsx +12 -12
  7. package/template/apps/asas-backoffice/src/components.ts +35 -0
  8. package/template/apps/asas-backoffice/src/first-user.ts +14 -0
  9. package/template/apps/{asas-platform → asas-backoffice}/src/links.ts +1 -1
  10. package/template/apps/{asas-platform → asas-backoffice}/src/locale-switch.tsx +7 -5
  11. package/template/apps/asas-backoffice/src/no-organization.tsx +5 -0
  12. package/template/apps/asas-backoffice/src/redirect.ts +1 -0
  13. package/template/apps/{asas-platform → asas-backoffice}/src/request-headers.ts +1 -1
  14. package/template/apps/asas-backoffice/src/routeTree.gen.ts +206 -0
  15. package/template/apps/asas-backoffice/src/router.tsx +18 -0
  16. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice/files.ts +10 -0
  17. package/template/apps/{asas-platform/src/routes/(screens)/platform → asas-backoffice/src/routes/(screens)/backoffice}/screen.json +426 -64
  18. package/template/apps/{asas-platform/src/routes/(screens)/platform → asas-backoffice/src/routes/(screens)/backoffice}/types.ts +18 -0
  19. package/template/apps/{asas-platform/src/routes/(screens)/platform.$.tsx → asas-backoffice/src/routes/(screens)/backoffice.$.tsx} +4 -4
  20. package/template/apps/{asas-platform/src/routes/(screens)/platform.index.tsx → asas-backoffice/src/routes/(screens)/backoffice.index.tsx} +3 -3
  21. package/template/apps/{asas-platform/src/routes/(screens)/platform.tsx → asas-backoffice/src/routes/(screens)/backoffice.tsx} +3 -3
  22. package/template/apps/{asas-platform → asas-backoffice}/src/routes/__root.tsx +2 -2
  23. package/template/apps/asas-backoffice/src/routes/api/$.ts +34 -0
  24. package/template/apps/asas-backoffice/src/routes/auth.create-first-user.tsx +113 -0
  25. package/template/apps/{asas-platform → asas-backoffice}/src/routes/auth.sign-in.tsx +9 -4
  26. package/template/apps/{asas-platform → asas-backoffice}/src/routes/unauthorized.tsx +24 -12
  27. package/template/apps/{asas-platform → asas-backoffice}/src/screen-form.tsx +5 -3
  28. package/template/apps/{asas-platform → asas-backoffice}/src/screen-list.tsx +6 -5
  29. package/template/apps/{asas-platform → asas-backoffice}/src/screen-names.ts +1 -1
  30. package/template/apps/{asas-platform → asas-backoffice}/src/screens.tsx +46 -26
  31. package/template/apps/asas-backoffice/src/server.ts +30 -0
  32. package/template/apps/{asas-platform → asas-backoffice}/src/slide.ts +1 -1
  33. package/template/apps/{asas-platform → asas-backoffice}/src/start.ts +1 -1
  34. package/template/apps/{asas-platform → asas-backoffice}/src/styles.css +9 -7
  35. package/template/apps/{asas-platform → asas-backoffice}/tsconfig.json +2 -2
  36. package/template/apps/{asas-platform → asas-backoffice}/vite.config.ts +7 -1
  37. package/template/apps/asas-tanstack/package.json +4 -2
  38. package/template/apps/asas-tanstack/src/api.ts +1 -1
  39. package/template/apps/asas-tanstack/src/auth-frame.tsx +12 -12
  40. package/template/apps/asas-tanstack/src/choose-password.tsx +150 -0
  41. package/template/apps/asas-tanstack/src/components.ts +35 -0
  42. package/template/apps/asas-tanstack/src/copy.ts +416 -79
  43. package/template/apps/asas-tanstack/src/first-run.ts +16 -0
  44. package/template/apps/asas-tanstack/src/links.ts +1 -1
  45. package/template/apps/asas-tanstack/src/locale-switch.tsx +7 -5
  46. package/template/apps/asas-tanstack/src/no-organization.tsx +108 -0
  47. package/template/apps/asas-tanstack/src/redirect.ts +20 -1
  48. package/template/apps/asas-tanstack/src/request-headers.ts +1 -1
  49. package/template/apps/asas-tanstack/src/routeTree.gen.ts +42 -0
  50. package/template/apps/asas-tanstack/src/router.tsx +12 -1
  51. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/files.ts +9 -2
  52. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/screen.json +475 -116
  53. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/types.ts +4 -2
  54. package/template/apps/asas-tanstack/src/routes/(screens)/portal/files.ts +7 -2
  55. package/template/apps/asas-tanstack/src/routes/(screens)/portal/screen.json +83 -319
  56. package/template/apps/asas-tanstack/src/routes/(screens)/portal/types.ts +3 -1
  57. package/template/apps/asas-tanstack/src/routes/__root.tsx +1 -1
  58. package/template/apps/asas-tanstack/src/routes/auth.accept-invitation.tsx +14 -7
  59. package/template/apps/asas-tanstack/src/routes/auth.first-run.tsx +114 -0
  60. package/template/apps/asas-tanstack/src/routes/auth.forgot-password.tsx +2 -2
  61. package/template/apps/asas-tanstack/src/routes/auth.handoff.ts +5 -5
  62. package/template/apps/asas-tanstack/src/routes/auth.reset-password.tsx +7 -100
  63. package/template/apps/asas-tanstack/src/routes/auth.set-password.tsx +16 -0
  64. package/template/apps/asas-tanstack/src/routes/auth.sign-in.tsx +27 -15
  65. package/template/apps/asas-tanstack/src/routes/auth.sign-up.tsx +59 -38
  66. package/template/apps/asas-tanstack/src/routes/index.tsx +18 -14
  67. package/template/apps/asas-tanstack/src/routes/unauthorized.tsx +24 -12
  68. package/template/apps/asas-tanstack/src/screen-form.tsx +5 -3
  69. package/template/apps/asas-tanstack/src/screen-list.tsx +6 -5
  70. package/template/apps/asas-tanstack/src/screen-names.ts +1 -1
  71. package/template/apps/asas-tanstack/src/screens.tsx +46 -26
  72. package/template/apps/asas-tanstack/src/server.ts +10 -0
  73. package/template/apps/asas-tanstack/src/slide.ts +1 -1
  74. package/template/apps/asas-tanstack/src/styles.css +7 -3
  75. package/template/apps/asas-tanstack/tsconfig.json +2 -2
  76. package/template/apps/asas-tanstack/vite.config.ts +7 -1
  77. package/template/package.json +3 -3
  78. package/template/templates/asas/.claude/skills/add-field/SKILL.md +27 -0
  79. package/template/templates/asas/.claude/skills/add-kayan/SKILL.md +26 -0
  80. package/template/templates/asas/.claude/skills/eject-admin-piece/SKILL.md +28 -0
  81. package/template/templates/asas/.claude/skills/upgrade-mithaq/SKILL.md +35 -0
  82. package/template/templates/asas/.env.example +22 -5
  83. package/template/templates/asas/AGENTS.md +101 -0
  84. package/template/templates/asas/CLAUDE.md +1 -0
  85. package/template/templates/asas/README.md +1056 -0
  86. package/template/templates/asas/package.json +19 -4
  87. package/template/templates/asas/src/asas.ts +81 -56
  88. package/template/templates/asas/src/{platform-http.ts → backoffice-http.ts} +48 -27
  89. package/template/templates/asas/src/dev-setup.ts +112 -0
  90. package/template/templates/asas/src/local-database.ts +22 -0
  91. package/template/templates/asas/src/messages/account.json +129 -103
  92. package/template/templates/asas/src/messages/auditLog.json +223 -105
  93. package/template/templates/asas/src/messages/backofficeLog.json +132 -0
  94. package/template/templates/asas/src/messages/courses.json +256 -214
  95. package/template/templates/asas/src/messages/enrollments.json +45 -27
  96. package/template/templates/asas/src/messages/invitation.json +72 -56
  97. package/template/templates/asas/src/messages/localUsers.json +34 -28
  98. package/template/templates/asas/src/messages/media.json +31 -21
  99. package/template/templates/asas/src/messages/member.json +59 -39
  100. package/template/templates/asas/src/messages/memberRoles.json +37 -31
  101. package/template/templates/asas/src/messages/mithaq.json +206 -162
  102. package/template/templates/asas/src/messages/organization.json +63 -41
  103. package/template/templates/asas/src/messages/organizationRole.json +59 -47
  104. package/template/templates/asas/src/messages/roles.json +59 -45
  105. package/template/templates/asas/src/messages/session.json +99 -79
  106. package/template/templates/asas/src/messages/sessions.json +110 -80
  107. package/template/templates/asas/src/messages/settings.json +51 -53
  108. package/template/templates/asas/src/messages/tags.json +31 -21
  109. package/template/templates/asas/src/messages/user.json +109 -87
  110. package/template/templates/asas/src/messages/verification.json +59 -47
  111. package/template/templates/asas/src/migrations/20261014221355_tags_restrict/migration.sql +1 -0
  112. package/template/templates/asas/src/migrations/20261014221355_tags_restrict/snapshot.json +3881 -0
  113. package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/migration.sql +1 -0
  114. package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/snapshot.json +3894 -0
  115. package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/migration.sql +1 -0
  116. package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/snapshot.json +3907 -0
  117. package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/migration.sql +1 -0
  118. package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/snapshot.json +3907 -0
  119. package/template/templates/asas/src/migrations/20261015042444_organization_names/migration.sql +1 -0
  120. package/template/templates/asas/src/migrations/20261015042444_organization_names/snapshot.json +3920 -0
  121. package/template/templates/asas/src/migrations/20261015053212_member_last_seen/migration.sql +1 -0
  122. package/template/templates/asas/src/migrations/20261015053212_member_last_seen/snapshot.json +3933 -0
  123. package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/migration.sql +15 -0
  124. package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/snapshot.json +4113 -0
  125. package/template/templates/asas/src/migrations/20261015095752_portal/migration.sql +2 -0
  126. package/template/templates/asas/src/migrations/20261015095752_portal/snapshot.json +4113 -0
  127. package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/migration.sql +9 -0
  128. package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/snapshot.json +4302 -0
  129. package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/migration.sql +27 -0
  130. package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/snapshot.json +4289 -0
  131. package/template/templates/asas/src/migrations/schema.ts +1 -0
  132. package/template/templates/asas/src/mithaq.ts +4 -406
  133. package/template/templates/asas/src/organizations.ts +3 -0
  134. package/template/templates/asas/src/seed.ts +147 -45
  135. package/template/templates/asas/src/{ui-platform → ui-backoffice}/home.tsx +9 -9
  136. package/template/templates/asas/src/ui-backoffice/organization-actions.tsx +121 -0
  137. package/template/templates/asas/src/{ui-platform → ui-backoffice}/record-page.tsx +4 -4
  138. package/template/templates/asas/src/{ui-platform → ui-backoffice}/role-cells.tsx +13 -6
  139. package/template/templates/asas/src/{ui-platform → ui-backoffice}/user-actions.tsx +46 -48
  140. package/template/templates/asas/src/ui-dashboard/dashboard.tsx +10 -4
  141. package/template/templates/asas/src/ui-dashboard/home.tsx +73 -0
  142. package/template/templates/asas/src/ui-dashboard/members-card.tsx +147 -0
  143. package/template/templates/asas/src/ui-dashboard/members.tsx +637 -438
  144. package/template/templates/asas/src/ui-dashboard/versions.tsx +22 -8
  145. package/template/templates/asas/src/ui-portal/courses-home.tsx +96 -0
  146. package/template/templates/asas/src/ui-portal/home.tsx +9 -13
  147. package/template/templates/features/audit-log/audit-log/audit-log.ts +284 -0
  148. package/template/templates/features/audit-log/backoffice-log/backoffice-log.ts +67 -0
  149. package/template/templates/features/audit-log/index.ts +4 -0
  150. package/template/templates/features/audit-log/package.json +13 -0
  151. package/template/templates/features/auth/index.ts +11 -0
  152. package/template/templates/{asas/src → features/auth}/local-users/local-users.ts +7 -7
  153. package/template/templates/{auth → features/auth}/package.json +2 -3
  154. package/template/templates/{asas/src/kayan → features}/auth/user/account.access.ts +2 -2
  155. package/template/templates/{asas/src/kayan → features}/auth/user/account.ts +2 -2
  156. package/template/templates/{asas/src/kayan → features}/auth/user/invitation.access.ts +1 -1
  157. package/template/templates/{asas/src/kayan → features}/auth/user/invitation.ts +3 -3
  158. package/template/templates/{asas/src/kayan → features}/auth/user/member.access.ts +4 -4
  159. package/template/templates/{asas/src/kayan → features}/auth/user/member.screens.ts +5 -5
  160. package/template/templates/{asas/src/kayan → features}/auth/user/member.ts +4 -3
  161. package/template/templates/{asas/src/kayan → features}/auth/user/organization.access.ts +6 -6
  162. package/template/templates/{asas/src/kayan → features}/auth/user/organization.screens.ts +6 -6
  163. package/template/templates/{asas/src/kayan → features}/auth/user/organization.ts +2 -1
  164. package/template/templates/{asas/src/kayan → features}/auth/user/organizationRole.access.ts +2 -2
  165. package/template/templates/{asas/src/kayan → features}/auth/user/organizationRole.ts +3 -3
  166. package/template/templates/{asas/src/kayan → features}/auth/user/session.access.ts +2 -2
  167. package/template/templates/{asas/src/kayan → features}/auth/user/session.ts +2 -2
  168. package/template/templates/{asas/src/kayan → features}/auth/user/user.access.ts +8 -7
  169. package/template/templates/{asas/src/kayan → features}/auth/user/user.screens.ts +9 -9
  170. package/template/templates/{asas/src/kayan → features}/auth/user/user.ts +2 -2
  171. package/template/templates/{asas/src/kayan → features}/auth/user/verification.access.ts +2 -2
  172. package/template/templates/{asas/src/kayan → features}/auth/user/verification.ts +2 -2
  173. package/template/templates/{asas/src → features/courses}/courses/courses.ts +122 -133
  174. package/template/templates/features/courses/courses/cover-reads.ts +33 -0
  175. package/template/templates/features/courses/courses/ui/archived-badge.tsx +9 -0
  176. package/template/templates/{asas/src → features/courses}/courses/ui/fee-cell.tsx +1 -1
  177. package/template/templates/{asas/src → features/courses}/courses/ui/status-cell.tsx +5 -3
  178. package/template/templates/features/courses/courses/ui/unpublish-button.tsx +15 -0
  179. package/template/templates/features/courses/domain/archive.ts +39 -0
  180. package/template/templates/features/courses/domain/on-offer.ts +6 -0
  181. package/template/templates/features/courses/enrollments/enrollments.ts +309 -0
  182. package/template/templates/{asas/src → features/courses}/enrollments/ui/session-cell.tsx +17 -7
  183. package/template/templates/{asas/src → features/courses/enrollments}/ui/session-labels.tsx +3 -3
  184. package/template/templates/features/courses/index.ts +8 -0
  185. package/template/templates/features/courses/package.json +24 -0
  186. package/template/templates/features/courses/portal/ui/catalogue.tsx +86 -0
  187. package/template/templates/features/courses/portal/ui/course.tsx +176 -0
  188. package/template/templates/features/courses/portal/ui/enrollments.tsx +170 -0
  189. package/template/templates/features/courses/portal/ui/parts.tsx +95 -0
  190. package/template/templates/features/courses/portal/ui/reads.ts +76 -0
  191. package/template/templates/features/courses/portal/ui/rules.ts +87 -0
  192. package/template/templates/features/courses/portal/ui/shell.tsx +194 -0
  193. package/template/templates/features/courses/sessions/seats.ts +42 -0
  194. package/template/templates/{asas/src → features/courses}/sessions/sessions.ts +45 -14
  195. package/template/templates/features/media/index.ts +10 -0
  196. package/template/templates/{asas/src → features/media}/media/media.ts +34 -40
  197. package/template/templates/{asas/src → features/media}/media/ui/preview.tsx +2 -2
  198. package/template/templates/features/media/package.json +19 -0
  199. package/template/templates/features/roles/index.ts +4 -0
  200. package/template/templates/{asas/src → features/roles}/member-roles/member-roles.ts +15 -15
  201. package/template/templates/features/roles/package.json +21 -0
  202. package/template/templates/{asas/src → features/roles}/roles/roles.ts +23 -23
  203. package/template/templates/{asas/src → features/roles}/roles/ui/role-form.tsx +65 -24
  204. package/template/templates/{asas/src → features/roles}/roles/ui/roles.tsx +49 -25
  205. package/template/templates/features/settings/index.ts +3 -0
  206. package/template/templates/features/settings/package.json +13 -0
  207. package/template/templates/{asas/src → features/settings}/settings/settings.ts +12 -17
  208. package/template/templates/features/tags/index.ts +3 -0
  209. package/template/templates/features/tags/package.json +14 -0
  210. package/template/templates/{asas/src → features/tags}/tags/tags.ts +15 -15
  211. package/template/templates/mithaq/package.json +33 -0
  212. package/template/templates/mithaq/src/mail/index.ts +32 -0
  213. package/template/templates/mithaq/src/mail/mailers.ts +36 -0
  214. package/template/templates/{asas/src/audit-log/sink.ts → mithaq/src/mithaq/audit-sink.ts} +31 -9
  215. package/template/templates/{auth/src/platform-roles.ts → mithaq/src/mithaq/auth/backoffice-roles.ts} +44 -23
  216. package/template/templates/mithaq/src/mithaq/auth/index.ts +1859 -0
  217. package/template/templates/mithaq/src/mithaq/auth/organizations.ts +80 -0
  218. package/template/templates/{asas/src → mithaq/src/mithaq}/pages.ts +16 -7
  219. package/template/templates/mithaq/src/mithaq/rich-text-nodes.ts +19 -0
  220. package/template/templates/mithaq/src/mithaq.ts +593 -0
  221. package/template/templates/mithaq/src/roles/default-member-roles.ts +29 -0
  222. package/template/templates/{asas → mithaq}/src/roles/grants.ts +1 -1
  223. package/template/templates/mithaq/src/roles/member-access.ts +11 -0
  224. package/template/templates/{asas/src → mithaq/src/roles}/statements.ts +6 -4
  225. package/template/templates/mithaq/src/roles/system-roles.ts +16 -0
  226. package/template/templates/{asas/src → mithaq/src/roles}/tenant-access.ts +96 -57
  227. package/template/templates/{asas/src → mithaq/src/roles}/tenant-roles.ts +84 -25
  228. package/template/templates/{asas → mithaq}/src/steps/expected-updated-at.ts +2 -2
  229. package/template/templates/{asas → mithaq}/src/storage/index.ts +3 -1
  230. package/template/templates/mithaq/src/ui/activity-page.tsx +33 -0
  231. package/template/templates/mithaq/src/ui/activity.tsx +527 -0
  232. package/template/templates/{asas → mithaq}/src/ui/app-shell.tsx +27 -19
  233. package/template/templates/mithaq/src/ui/rich-text-nodes.tsx +33 -0
  234. package/template/templates/{asas → mithaq}/src/ui/screen-nav.tsx +1 -1
  235. package/template/templates/{asas → mithaq}/src/ui/top-shell.tsx +8 -8
  236. package/template/apps/asas-platform/src/components/mithaq/cells.tsx +0 -112
  237. package/template/apps/asas-platform/src/components/mithaq/controls.tsx +0 -650
  238. package/template/apps/asas-platform/src/components/mithaq/field-shell.tsx +0 -53
  239. package/template/apps/asas-platform/src/components/mithaq/filter-operators.ts +0 -108
  240. package/template/apps/asas-platform/src/components/mithaq/form.tsx +0 -1467
  241. package/template/apps/asas-platform/src/components/mithaq/list-columns.tsx +0 -108
  242. package/template/apps/asas-platform/src/components/mithaq/list-filters.tsx +0 -368
  243. package/template/apps/asas-platform/src/components/mithaq/list-pagination.tsx +0 -153
  244. package/template/apps/asas-platform/src/components/mithaq/list-query.ts +0 -432
  245. package/template/apps/asas-platform/src/components/mithaq/list-selection.tsx +0 -74
  246. package/template/apps/asas-platform/src/components/mithaq/list.tsx +0 -865
  247. package/template/apps/asas-platform/src/components/mithaq/nav.tsx +0 -871
  248. package/template/apps/asas-platform/src/components/mithaq/nodes.tsx +0 -1024
  249. package/template/apps/asas-platform/src/components/mithaq/relationship-cell.tsx +0 -127
  250. package/template/apps/asas-platform/src/components/mithaq/relationship-control.tsx +0 -400
  251. package/template/apps/asas-platform/src/components/mithaq/rich-text-nodes.ts +0 -6
  252. package/template/apps/asas-platform/src/components/mithaq/rich-text.tsx +0 -1083
  253. package/template/apps/asas-platform/src/components/mithaq/upload.tsx +0 -686
  254. package/template/apps/asas-platform/src/components/ui/drawer.tsx +0 -216
  255. package/template/apps/asas-platform/src/redirect.ts +0 -1
  256. package/template/apps/asas-platform/src/routeTree.gen.ts +0 -181
  257. package/template/apps/asas-platform/src/router.tsx +0 -7
  258. package/template/apps/asas-platform/src/routes/(screens)/platform/files.ts +0 -4
  259. package/template/apps/asas-platform/src/routes/api/$.ts +0 -30
  260. package/template/apps/asas-platform/src/server.ts +0 -20
  261. package/template/apps/asas-tanstack/src/components/mithaq/cells.tsx +0 -112
  262. package/template/apps/asas-tanstack/src/components/mithaq/controls.tsx +0 -650
  263. package/template/apps/asas-tanstack/src/components/mithaq/field-shell.tsx +0 -53
  264. package/template/apps/asas-tanstack/src/components/mithaq/filter-operators.ts +0 -108
  265. package/template/apps/asas-tanstack/src/components/mithaq/form.tsx +0 -1467
  266. package/template/apps/asas-tanstack/src/components/mithaq/list-columns.tsx +0 -108
  267. package/template/apps/asas-tanstack/src/components/mithaq/list-filters.tsx +0 -368
  268. package/template/apps/asas-tanstack/src/components/mithaq/list-pagination.tsx +0 -153
  269. package/template/apps/asas-tanstack/src/components/mithaq/list-query.ts +0 -432
  270. package/template/apps/asas-tanstack/src/components/mithaq/list-selection.tsx +0 -74
  271. package/template/apps/asas-tanstack/src/components/mithaq/list.tsx +0 -865
  272. package/template/apps/asas-tanstack/src/components/mithaq/nav.tsx +0 -871
  273. package/template/apps/asas-tanstack/src/components/mithaq/nodes.tsx +0 -1024
  274. package/template/apps/asas-tanstack/src/components/mithaq/relationship-cell.tsx +0 -127
  275. package/template/apps/asas-tanstack/src/components/mithaq/relationship-control.tsx +0 -400
  276. package/template/apps/asas-tanstack/src/components/mithaq/rich-text-nodes.ts +0 -6
  277. package/template/apps/asas-tanstack/src/components/mithaq/rich-text.tsx +0 -1083
  278. package/template/apps/asas-tanstack/src/components/mithaq/upload.tsx +0 -686
  279. package/template/apps/asas-tanstack/src/components/ui/drawer.tsx +0 -215
  280. package/template/templates/asas/src/audit-log/audit-log.ts +0 -187
  281. package/template/templates/asas/src/audit-log/ui/activity.tsx +0 -243
  282. package/template/templates/asas/src/courses/ui/archive-button.tsx +0 -25
  283. package/template/templates/asas/src/courses/ui/no-courses-on-offer.tsx +0 -11
  284. package/template/templates/asas/src/courses/ui/on-offer.ts +0 -6
  285. package/template/templates/asas/src/enrollments/enrollments.ts +0 -137
  286. package/template/templates/asas/src/rich-text-nodes.ts +0 -11
  287. package/template/templates/asas/src/roles.ts +0 -16
  288. package/template/templates/asas/src/ui/rich-text-nodes.tsx +0 -15
  289. package/template/templates/asas/src/ui-platform/organization-actions.tsx +0 -114
  290. package/template/templates/auth/src/index.ts +0 -690
  291. /package/template/apps/{asas-platform → asas-backoffice}/components.json +0 -0
  292. /package/template/apps/{asas-platform → asas-backoffice}/gitignore +0 -0
  293. /package/template/apps/{asas-platform → asas-backoffice}/public/favicon.svg +0 -0
  294. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/accordion.tsx +0 -0
  295. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/alert-dialog.tsx +0 -0
  296. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/avatar.tsx +0 -0
  297. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/badge.tsx +0 -0
  298. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/breadcrumb.tsx +0 -0
  299. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/button-group.tsx +0 -0
  300. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/button.tsx +0 -0
  301. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/calendar.tsx +0 -0
  302. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/card.tsx +0 -0
  303. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/chart.tsx +0 -0
  304. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/checkbox.tsx +0 -0
  305. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/collapsible.tsx +0 -0
  306. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/combobox.tsx +0 -0
  307. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/dialog.tsx +0 -0
  308. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/direction.tsx +0 -0
  309. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/directional-icon.tsx +0 -0
  310. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/dropdown-menu.tsx +0 -0
  311. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/field.tsx +0 -0
  312. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input-group.tsx +0 -0
  313. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input-otp.tsx +0 -0
  314. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input.tsx +0 -0
  315. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/label.tsx +0 -0
  316. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/popover.tsx +0 -0
  317. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/radio-group.tsx +0 -0
  318. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/resizable.tsx +0 -0
  319. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/scroll-area.tsx +0 -0
  320. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/select.tsx +0 -0
  321. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/separator.tsx +0 -0
  322. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/sheet.tsx +0 -0
  323. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/sidebar.tsx +0 -0
  324. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/skeleton.tsx +0 -0
  325. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/slider.tsx +0 -0
  326. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/spinner.tsx +0 -0
  327. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/switch.tsx +0 -0
  328. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/table.tsx +0 -0
  329. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/tabs.tsx +0 -0
  330. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/textarea.tsx +0 -0
  331. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/toggle-group.tsx +0 -0
  332. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/toggle.tsx +0 -0
  333. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/tooltip.tsx +0 -0
  334. /package/template/apps/{asas-platform → asas-backoffice}/src/copy.ts +0 -0
  335. /package/template/apps/{asas-platform → asas-backoffice}/src/hooks/use-mobile.ts +0 -0
  336. /package/template/apps/{asas-platform → asas-backoffice}/src/lib/utils.ts +0 -0
  337. /package/template/apps/{asas-platform → asas-backoffice}/src/theme.ts +0 -0
  338. /package/template/templates/{asas/src/kayan → features}/auth/user/index.ts +0 -0
  339. /package/template/templates/{asas/src → features/courses}/courses/ui/no-courses.tsx +0 -0
  340. /package/template/templates/{asas → mithaq}/components.json +0 -0
  341. /package/template/templates/{asas → mithaq}/src/storage/defaults.ts +0 -0
  342. /package/template/templates/{asas → mithaq}/src/storage/local.ts +0 -0
  343. /package/template/templates/{asas → mithaq}/src/storage/r2.ts +0 -0
  344. /package/template/templates/{asas → mithaq}/src/storage/s3.ts +0 -0
  345. /package/template/templates/{asas → mithaq}/src/storage/uploadthing.ts +0 -0
  346. /package/template/templates/{asas → mithaq}/src/ui/kpi-card.tsx +0 -0
  347. /package/template/templates/{asas → mithaq}/src/ui/record-page.tsx +0 -0
  348. /package/template/templates/{asas → mithaq}/src/ui/table-page.tsx +0 -0
@@ -0,0 +1,593 @@
1
+ import {
2
+ // add-on: audit-log
3
+ audit,
4
+ // end add-on: audit-log
5
+ base,
6
+ betterAuthTenant,
7
+ defaultAuthorize,
8
+ defaultBulkLimit,
9
+ defaultDepth,
10
+ defaultJoinLimit,
11
+ defaultLimit,
12
+ defaultMaxLimit,
13
+ screenKinds,
14
+ defineMithaq,
15
+ drizzleAdapter,
16
+ drizzleKinds, drizzleRules, drizzleIdKinds,
17
+ folderOf,
18
+ // add-on: roles
19
+ rbacTarget,
20
+ // end add-on: roles
21
+ Refusal,
22
+ screenTarget,
23
+ serviceTarget,
24
+ // add-on: uploads
25
+ storage,
26
+ // end add-on: uploads
27
+ versions,
28
+ zodKinds, zodRules, zodIdKinds, zodTarget,
29
+ type CommandInput,
30
+ } from 'mithaq'
31
+ // add-on: rich-text
32
+ import { richText } from 'mithaq/rich-text'
33
+ // end add-on: rich-text
34
+ import { fileURLToPath } from 'node:url'
35
+ // add-on: audit-log
36
+ import { auditSink } from './mithaq/audit-sink.ts'
37
+ // end add-on: audit-log
38
+ // add-on: rich-text
39
+ import { richTextNodes } from './mithaq/rich-text-nodes.ts'
40
+ // end add-on: rich-text
41
+ import { systemRoles } from './roles/system-roles.ts'
42
+ // add-on: uploads
43
+ import { storageConfig } from './storage/index.ts'
44
+ // end add-on: uploads
45
+ import { onBackoffice, organizationNameProblem } from './mithaq/auth/index.ts'
46
+ import { organizationName, organizations, showOrganization } from './mithaq/auth/organizations.ts'
47
+ import { assignedRoles, backofficeRolesOf, tenantAccess } from './roles/tenant-access.ts'
48
+ // without add-on: roles
49
+ // import { accessTarget } from './roles/member-access.ts'
50
+ // end without add-on: roles
51
+
52
+ /**
53
+ * A local PostgreSQL on its default port, which `vite dev` falls back to when
54
+ * no DATABASE_URL is set; it is also the URL `.env.example` names. A
55
+ * deployment passes its own. `testDatabase` refuses an unset DATABASE_URL and
56
+ * this URL's port, so a test never reaches it.
57
+ */
58
+ export const devDatabaseUrl = 'postgres://postgres:postgres@localhost:5432/mithaq'
59
+ /**
60
+ * Production refuses the dev fallback, whose password sits in this file. This
61
+ * runs on import, before anything connects.
62
+ */
63
+ if (process.env.NODE_ENV === 'production' && !process.env.DATABASE_URL) {
64
+ throw new Error('A production boot needs DATABASE_URL, the database this deployment uses. Nothing here falls back to the dev database in production.')
65
+ }
66
+ /**
67
+ * `node --test` sets `NODE_TEST_CONTEXT` on every process it runs, so a test
68
+ * that forgot DATABASE_URL never reaches the dev database.
69
+ */
70
+ if (process.env.NODE_TEST_CONTEXT && !process.env.DATABASE_URL) {
71
+ throw new Error('A test run needs DATABASE_URL, a database of its own: set DATABASE_URL. Nothing under node --test falls back to the dev database.')
72
+ }
73
+ const connectionString = process.env.DATABASE_URL ?? devDatabaseUrl
74
+
75
+ /**
76
+ * Refuses a command that would open a real connection with no DATABASE_URL
77
+ * named. `vite dev` never calls it and uses the dev fallback.
78
+ */
79
+ const requireDatabaseUrl = () => {
80
+ if (!process.env.DATABASE_URL) {
81
+ throw new Refusal(
82
+ 'Refusing to connect: set DATABASE_URL to the database this command should use. Nothing here falls back to the dev database.',
83
+ )
84
+ }
85
+ }
86
+
87
+ /**
88
+ * The asas's own source folder, which holds the migrations and each screen's
89
+ * own pieces. The language names it, and a feature's folder, by path and
90
+ * imports neither, so the packages depend one way: the asas on the language.
91
+ */
92
+ const asasSource = new URL('../../asas/src/', import.meta.url)
93
+ const featuresFolder = new URL('../../features/', import.meta.url)
94
+
95
+ /**
96
+ * A production `connect()` refuses to start while migrations are unapplied, so
97
+ * a deploy changes the database only when someone runs
98
+ * `drizzle-kit migrate --config src/migrations/drizzle.config.ts`; `'run'`
99
+ * applies them on boot instead. The folder comes from this file because each
100
+ * app calls `connect()` from its own folder.
101
+ */
102
+ const drizzleStore = drizzleAdapter({
103
+ connectionString,
104
+ /** The column builders per kind, mithaq's default. */
105
+ kinds: drizzleKinds,
106
+ /** The column rules, mithaq's default. */
107
+ rules: drizzleRules,
108
+ /** The kinds an id may use, mithaq's default. */
109
+ idKinds: drizzleIdKinds,
110
+ prodMigrations: { migrationsFolder: fileURLToPath(new URL('migrations/', asasSource)), pending: 'refuse' },
111
+ })
112
+
113
+ /**
114
+ * A drizzle store whose `connect()` (the schema push) and `migrate` command are
115
+ * refused without DATABASE_URL, since either could silently reach the dev
116
+ * database. `init` returns a fresh store per build, so it is guarded too.
117
+ */
118
+ const guarded = (store: typeof drizzleStore): typeof drizzleStore => ({
119
+ ...store,
120
+ init: (kayan) => guarded((store.init(kayan) ?? store) as typeof drizzleStore) as never,
121
+ connect: async (args?: { readOnly?: boolean }) => {
122
+ requireDatabaseUrl()
123
+ await store.connect(args)
124
+ },
125
+ commands: {
126
+ migrate: async (input: CommandInput) => {
127
+ requireDatabaseUrl()
128
+ return store.commands!.migrate!(input)
129
+ },
130
+ },
131
+ })
132
+ // add-on: roles
133
+ // A build has one `access` target. Swapping this line for `betterAuthTarget` moves
134
+ // custom roles into the provider's own rows, but the assignment guard
135
+ // `assignableRole` stays bound to this shape: it reads the `roles` kayan by a
136
+ // `name` filter and a `permissions` list, where the provider keys rows on `role`
137
+ // and holds a `permission` object. It fails closed (an unreadable role is every
138
+ // key of the project), so a project that swaps this must teach the guard the
139
+ // provider's table and columns too.
140
+ const accessOptions = {
141
+ /** The roles this project ships, and the names a row may be keyed on. */
142
+ roles: systemRoles,
143
+ /** How an actor's assigned slugs are read, for every row table. */
144
+ rolesOf: assignedRoles,
145
+ // The rows every kayan placing `tenant.field` answers from. A kayan drops them
146
+ // with `access: { tenant: false }`; a shared one never had them, which is how
147
+ // the generated auth kayan keep their default deny, except the two whose
148
+ // `<kayan>.access.ts` the generator imports.
149
+ access: tenantAccess,
150
+ /** Where the target seeds the system roles and reads the assignments. */
151
+ kayan: { roles: 'roles', memberRoles: 'memberRoles' },
152
+ } as const
153
+
154
+ /**
155
+ * The access target over these roles: `rbacTarget` whole, except `assign`,
156
+ * which writes and is refused without DATABASE_URL unless run with `--check`.
157
+ */
158
+ export const accessTarget = (roles: typeof systemRoles = systemRoles) => {
159
+ const target = rbacTarget({ ...accessOptions, roles })
160
+ return {
161
+ ...target,
162
+ commands: {
163
+ assign: async (input: CommandInput) => {
164
+ if (!input.flags.includes('--check')) requireDatabaseUrl()
165
+ return target.commands!.assign!(input)
166
+ },
167
+ },
168
+ }
169
+ }
170
+ // end add-on: roles
171
+ // without add-on: roles
172
+ // export { accessTarget }
173
+ // end without add-on: roles
174
+
175
+ /**
176
+ * Every target but the screens. A screen names the kayan it draws, so a build
177
+ * holding only some of this project's kayan, as a test's does, is refused by it
178
+ * and uses a language whose screen draws only what it holds.
179
+ */
180
+ export const serverTargets = {
181
+ drizzle: guarded(drizzleStore),
182
+ zod: zodTarget({
183
+ /** The schema builders per kind, mithaq's default. */
184
+ kinds: zodKinds,
185
+ /** The schema rules, mithaq's default. */
186
+ rules: zodRules,
187
+ /** The kinds an id may use, mithaq's default. */
188
+ idKinds: zodIdKinds,
189
+ }),
190
+ rbac: accessTarget(),
191
+ // Every kayan placing `organization` belongs to one organization,
192
+ // the session's active one. `restrict`: an organization that still holds a row
193
+ // is not deleted, so no row is left behind with no tenant to read it.
194
+ tenant: betterAuthTenant({
195
+ relationTo: 'organization',
196
+ onDelete: 'restrict',
197
+ // New rows take the active organization, mithaq's tenant fill rule.
198
+ newRows: 'active',
199
+ // How many organizations this project holds; in one a start over more is refused.
200
+ organizations,
201
+ // Staff read every organization with `tenant: 'all'` in the backoffice app
202
+ // alone, which is the app's own fact and never the request's; their rows
203
+ // there write nothing.
204
+ backoffice: ({ user }) => onBackoffice() && backofficeRolesOf(user).length > 0,
205
+ }),
206
+ // The runner is a target too (`capability: 'service'`); a build has exactly
207
+ // one, under this name. It holds no operations: each kayan spreads
208
+ // `defaultOperations` into its own `targets.service.operations` and leaves
209
+ // out any it does not want, such as `count`.
210
+ service: serviceTarget({
211
+ // One of the three lists every write runs around its own: a step here runs
212
+ // last inside the write's transaction, as the audit log's `audit` does,
213
+ // writing one `auditLog` row in it. A precondition goes in `beforeSteps`, and
214
+ // a webhook or email after a durable write in `afterCommit`.
215
+ afterSteps: [
216
+ // add-on: audit-log
217
+ audit,
218
+ // end add-on: audit-log
219
+ ],
220
+ // add-on: audit-log
221
+ /** Where the `audit` step hands each write: the template's audit log. */
222
+ audit: auditSink,
223
+ // end add-on: audit-log
224
+ /** The access evaluator every operation runs before its first step. */
225
+ authorize: defaultAuthorize,
226
+ /** The deepest a read or a write may nest another before it is refused, eight. */
227
+ depth: defaultDepth,
228
+ /**
229
+ * The page a `find` or a `findVersions` answers when its caller names no
230
+ * `limit`, ten, and the most rows a served one may ask for, a hundred.
231
+ */
232
+ defaultLimit,
233
+ maxLimit: defaultMaxLimit,
234
+ /** How many child rows a populated join carries where its field names none, ten. */
235
+ joinLimit: defaultJoinLimit,
236
+ /** The most rows one bulk write may match, a thousand. */
237
+ bulkLimit: defaultBulkLimit,
238
+ // add-on: courses
239
+ // The field that marks a row deleted. A kayan takes soft delete by declaring
240
+ // a field of this name, as `enrollments` does. A build where no kayan
241
+ // declares it is refused, because every delete in it would remove rows for
242
+ // good. Spreading the field into every kayan is refused where a permission
243
+ // store reads the kayan, since a role row the delete marked would still grant
244
+ // its keys.
245
+ softDelete: 'deletedAt',
246
+ // end add-on: courses
247
+ /** The database a kayan writes through when it names no other. */
248
+ store: 'drizzle',
249
+ /** The target a write is validated against when a kayan names no other. */
250
+ schema: 'zod',
251
+ }),
252
+ }
253
+
254
+ // add-on: uploads
255
+ /**
256
+ * Files: the `upload` kind and the storage target, from the storage rukn over
257
+ * this project's config. `mithaq storage sweep` reads every row, so it is
258
+ * refused without DATABASE_URL like `migrate`.
259
+ */
260
+ const files = storage(storageConfig)
261
+ const guardedFiles = {
262
+ ...files,
263
+ targets: {
264
+ ...files.targets,
265
+ storage: {
266
+ ...files.targets.storage,
267
+ commands: {
268
+ ...files.targets.storage.commands,
269
+ sweep: async (input: CommandInput) => {
270
+ requireDatabaseUrl()
271
+ return files.targets.storage.commands.sweep(input)
272
+ },
273
+ },
274
+ },
275
+ },
276
+ }
277
+ // end add-on: uploads
278
+
279
+ /**
280
+ * The pillars the language's kinds, rules and built targets come from: `base`
281
+ * brings the shipped kinds and rules, and `versions` the target a kayan keeps
282
+ * its past and drafts through by writing `targets.versions`. One row keeps its
283
+ * last hundred versions unless its kayan names another window. A rukn of the
284
+ * project's own goes beside them.
285
+ */
286
+ export const arkan = [
287
+ base,
288
+ // add-on: rich-text
289
+ // The rich text kind over this project's node list.
290
+ richText({ nodes: richTextNodes }),
291
+ // end add-on: rich-text
292
+ // add-on: uploads
293
+ // The `upload` kind and the storage target.
294
+ guardedFiles,
295
+ // end add-on: uploads
296
+ versions({ max: 100 }),
297
+ ] as const
298
+
299
+ /**
300
+ * The context every served call and first registration hands its steps, built
301
+ * here from the request: mithaq reads no header and no body key into it. A
302
+ * fresh id per request, so nothing a client sends names it. A step reads the
303
+ * key untyped unless the runner declares `context`, for example
304
+ * `context: {} as { requestId: string }`, and then every call in the app passes
305
+ * one too.
306
+ */
307
+ export const requestContext = (_request: Request) => ({ requestId: crypto.randomUUID() })
308
+
309
+ /**
310
+ * Each screen file is named `ui('file.tsx#Export')` off a folder URL taken from
311
+ * this file, so the build reads it wherever it runs and refuses a missing file
312
+ * or export. `ui/` holds what several screens draw with, each screen's own
313
+ * folder what only it does.
314
+ */
315
+ export const ui = folderOf(new URL('./ui/', import.meta.url))
316
+ export const dashboardUi = folderOf(new URL('ui-dashboard/', asasSource))
317
+ export const portalUi = folderOf(new URL('ui-portal/', asasSource))
318
+ export const backofficeUi = folderOf(new URL('ui-backoffice/', asasSource))
319
+ // add-on: courses
320
+ /** Every screen's provider of course titles for a session's label, which the courses feature draws. */
321
+ const sessionLabels = folderOf(new URL('courses/enrollments/ui/', featuresFolder))('session-labels.tsx#SessionLabels')
322
+ /** The portal's own look, which the courses feature draws for its students. */
323
+ const coursesPortal = folderOf(new URL('courses/portal/ui/', featuresFolder))
324
+ // end add-on: courses
325
+
326
+ /**
327
+ * Written once and spread into each screen: what draws each kind and the
328
+ * shared layouts. A piece only one screen draws is written on that screen.
329
+ */
330
+ const shared = {
331
+ controls: screenKinds,
332
+ layouts: {
333
+ app: ui('app-shell.tsx#AppShell'),
334
+ record: ui('record-page.tsx#RecordPage'),
335
+ table: ui('table-page.tsx#TablePage'),
336
+ },
337
+ }
338
+
339
+ /** The portal screen, the students' and instructors' own. The test language narrows it to the kayan a test builds. */
340
+ export const portalScreen = screenTarget({
341
+ ...shared,
342
+ path: '/portal',
343
+ kayan: [
344
+ // add-on: courses
345
+ // `sessions` is listed for its title alone, which an enrollment's session
346
+ // reads by; it has no page here and so no nav entry.
347
+ 'courses',
348
+ 'enrollments',
349
+ 'sessions',
350
+ // end add-on: courses
351
+ // add-on: media
352
+ // `media` is listed for an image's title alone, such as a course's cover,
353
+ // which a course page draws read-only; it has no page here.
354
+ 'media',
355
+ // end add-on: media
356
+ // `user` is listed for its title alone, which a relationship to a person reads by.
357
+ 'user',
358
+ ],
359
+ providers: [
360
+ // add-on: rich-text
361
+ ui('rich-text-nodes.tsx#RichTextNodes'),
362
+ // end add-on: rich-text
363
+ // add-on: courses
364
+ sessionLabels,
365
+ // end add-on: courses
366
+ ],
367
+ pages: {
368
+ // add-on: courses
369
+ // A greeting, the viewer's next session, and courses it can join.
370
+ home: { path: '/', component: portalUi('courses-home.tsx#Home') },
371
+ // end add-on: courses
372
+ // without add-on: courses
373
+ // home: { path: '/', component: portalUi('home.tsx#Home') },
374
+ // end without add-on: courses
375
+ },
376
+ // Its own shell, which draws no link to the backoffice app: staff reach it from
377
+ // the dashboard alone. Every page a tree draws is that shell over the portal's
378
+ // single column; the courses example's pages draw it whole.
379
+ layouts: {
380
+ // add-on: courses
381
+ top: coursesPortal('shell.tsx#PortalShell'),
382
+ // end add-on: courses
383
+ // without add-on: courses
384
+ // top: ui('top-shell.tsx#PortalTopShell'),
385
+ // end without add-on: courses
386
+ page: portalUi('page.tsx#Page'),
387
+ },
388
+ components: {},
389
+ })
390
+
391
+ /** The dashboard screen, the tenant back office. The test language narrows it to the kayan a test builds. */
392
+ export const dashboardScreen = screenTarget({
393
+ ...shared,
394
+ // The top-bar shell too, which the courses board page is rooted at.
395
+ layouts: { ...shared.layouts, top: ui('top-shell.tsx#TopShell') },
396
+ path: '/dashboard',
397
+ kayan: [
398
+ // add-on: courses
399
+ 'courses',
400
+ 'sessions',
401
+ 'enrollments',
402
+ // end add-on: courses
403
+ 'tags',
404
+ // add-on: media
405
+ 'media',
406
+ // end add-on: media
407
+ // add-on: roles
408
+ // `memberRoles` is listed for its access report alone, which the members
409
+ // page's sheet is drawn by; it has no page of its own.
410
+ 'roles',
411
+ 'memberRoles',
412
+ // end add-on: roles
413
+ // add-on: settings
414
+ 'settings',
415
+ // end add-on: settings
416
+ // `user` and `organization` are listed for their titles alone: they have no
417
+ // page and so no nav entry, and a relationship to either shows the row's name.
418
+ 'user',
419
+ 'organization',
420
+ // add-on: audit-log
421
+ // `auditLog` is listed for its access report, which the edit pages' activity
422
+ // and the Activity page are drawn by, and for the person filter's own field.
423
+ 'auditLog',
424
+ // end add-on: audit-log
425
+ ],
426
+ providers: [
427
+ dashboardUi('theme-provider.tsx#ThemeProvider'),
428
+ // add-on: rich-text
429
+ ui('rich-text-nodes.tsx#RichTextNodes'),
430
+ // end add-on: rich-text
431
+ // add-on: courses
432
+ sessionLabels,
433
+ // end add-on: courses
434
+ ],
435
+ pages: {
436
+ // add-on: courses
437
+ // The page at `/`, served only to an actor who reads the courses it counts.
438
+ dashboard: { path: '/', component: dashboardUi('dashboard.tsx#Dashboard'), permission: 'courses.find' },
439
+ // end add-on: courses
440
+ // without add-on: courses
441
+ // // The page at `/`: a figure for each list the nav offers the viewer.
442
+ // dashboard: { path: '/', component: dashboardUi('home.tsx#Home') },
443
+ // end without add-on: courses
444
+ // add-on: roles
445
+ // A page that belongs to no kayan, listed in the nav under its own label
446
+ // and served only to an actor holding the key it names: `member.find`, the
447
+ // key the auth setup asks before the provider lists members.
448
+ members: {
449
+ path: '/members',
450
+ component: dashboardUi('members.tsx#Members'),
451
+ // With no organization shown, its members are the app's users.
452
+ nav: { label: showOrganization ? { en: 'Members', ar: 'الأعضاء' } : { en: 'Users', ar: 'المستخدمون' } },
453
+ permission: 'member.find',
454
+ },
455
+ // end add-on: roles
456
+ // add-on: audit-log
457
+ // Every change in the organization, filtered and paged, for an actor holding the log's key,
458
+ // which reads the entries of the kayan it lists alone. Read-only, as the log is.
459
+ activity: {
460
+ path: '/activity',
461
+ component: ui('activity-page.tsx#ActivityPage'),
462
+ nav: { label: { en: 'Activity', ar: 'النشاط' } },
463
+ permission: 'auditLog.find',
464
+ },
465
+ // end add-on: audit-log
466
+ },
467
+ // A piece only one kayan draws is named by `ui()` in that kayan's file; these
468
+ // are drawn by name from many.
469
+ components: {
470
+ // add-on: audit-log
471
+ // A record's own audit entries, which each edit page draws in its side column.
472
+ activity: ui('activity.tsx#Activity'),
473
+ // A record's whole history, filtered and paged, which an edit page draws in a tab beside Versions.
474
+ activityLog: ui('activity.tsx#ActivityTab'),
475
+ // end add-on: audit-log
476
+ // A row's versions, with their restore, which the edit page of a kayan that
477
+ // keeps drafts draws in a tab of its own.
478
+ versions: dashboardUi('versions.tsx#Versions'),
479
+ },
480
+ })
481
+
482
+ /**
483
+ * The backoffice screen: staff above every tenant, entered by `screen.backoffice`,
484
+ * which only a backoffice role holds, and served by its own app,
485
+ * `apps/asas-backoffice`, whose requests alone get the backoffice roles from the
486
+ * template's role reader. It lists the provider's generated kayan and every
487
+ * tenant kayan across organizations, read-only: a backoffice role reads every
488
+ * tenant's rows by its keys and writes none. Local accounts are listed for a
489
+ * super admin alone, who verifies a sign-up there. The settings global is not
490
+ * listed: a list across tenants reads by `find`, which a global declares none
491
+ * of.
492
+ */
493
+ export const backofficeScreen = screenTarget({
494
+ ...shared,
495
+ // The dashboard's frame with no organization switcher, which staff have no
496
+ // use for, and a record page with a link back to its list.
497
+ layouts: { ...shared.layouts, app: ui('app-shell.tsx#BackofficeShell'), record: backofficeUi('record-page.tsx#BackofficeRecordPage') },
498
+ path: '/backoffice',
499
+ kayan: [
500
+ 'user',
501
+ 'organization',
502
+ 'member',
503
+ // add-on: courses
504
+ 'courses',
505
+ 'sessions',
506
+ 'enrollments',
507
+ // end add-on: courses
508
+ 'tags',
509
+ // add-on: roles
510
+ 'roles',
511
+ 'memberRoles',
512
+ // end add-on: roles
513
+ 'localUsers',
514
+ // add-on: audit-log
515
+ // Who impersonated whom, set whose backoffice role and banned whom, for the roles that read it.
516
+ 'backofficeLog',
517
+ // end add-on: audit-log
518
+ ],
519
+ providers: [
520
+ dashboardUi('theme-provider.tsx#ThemeProvider'),
521
+ // add-on: rich-text
522
+ ui('rich-text-nodes.tsx#RichTextNodes'),
523
+ // end add-on: rich-text
524
+ // add-on: courses
525
+ sessionLabels,
526
+ // end add-on: courses
527
+ ],
528
+ pages: { home: { path: '/', component: backofficeUi('home.tsx#Home') } },
529
+ components: {},
530
+ })
531
+
532
+ const language = defineMithaq({
533
+ /** Every locale this project's copy is written in, so a missing one is a type error. */
534
+ locales: ['ar', 'en'] as const,
535
+ arkan,
536
+ // A project's own target spreads in here beside the shipped ones and is
537
+ // checked exactly like them.
538
+ targets: {
539
+ ...serverTargets,
540
+ // The three screens, each a target of capability `screen` under its own key.
541
+ // A kayan says what each screen draws of it under `targets.<screen>`, and
542
+ // `mithaq generate screens` writes each screen's `screen.json`, `types.ts`,
543
+ // glob and route files under `generate.screens`.
544
+ dashboard: dashboardScreen,
545
+ portal: portalScreen,
546
+ backoffice: backofficeScreen,
547
+ },
548
+ // Not a key the language knows: the project's own, passed through typed, and
549
+ // read by `mithaq generate auth`, `messages` and `screens` to find where each
550
+ // writes. `providers` is the module `generate auth` reads the Better Auth
551
+ // instances from and writes into each generated kayan's import, and `language`
552
+ // the module each of them imports `defineKayan` from. `screens` is
553
+ // the host app's routes folder, one per screen because more than one app
554
+ // serves them: the tenant's two by `apps/asas-tanstack`, the backoffice by
555
+ // `apps/asas-backoffice`.
556
+ // Each path is from the asas's folder, where every `mithaq` command runs.
557
+ generate: {
558
+ language: '@asas/mithaq',
559
+ features: '../features/',
560
+ auth: '../features/auth/',
561
+ providers: '@asas/mithaq/auth',
562
+ messages: 'src/messages/',
563
+ migrations: 'src/migrations/',
564
+ screens: {
565
+ dashboard: '../../apps/asas-tanstack/src/routes/(screens)/',
566
+ portal: '../../apps/asas-tanstack/src/routes/(screens)/',
567
+ // add-on: backoffice
568
+ backoffice: '../../apps/asas-backoffice/src/routes/(screens)/',
569
+ // end add-on: backoffice
570
+ // without add-on: backoffice
571
+ // backoffice: false,
572
+ // end without add-on: backoffice
573
+ },
574
+ },
575
+ })
576
+
577
+ // `targets` is the language's own, which `mithaq generate auth` reads beside
578
+ // `generate` to title a provider kayan on each screen that lists it.
579
+ export const { defineKayan, defineGlobal, buildAsas, rows, user, generate, locales, targets, targets: { tenant } } = language
580
+ /** The language's type, which the apps check their admin components map against: a kind of this project's own needs a control there. */
581
+ export type Language = typeof language
582
+
583
+ // Every mode holds an organization's names to `organizationName`'s locales and falls back to its first, so a locale it
584
+ // leaves out would be one no organization could be named in.
585
+ const unnamed = organizationNameProblem({ names: organizationName, locales })
586
+ if (unnamed) throw new Error(`src/mithaq/auth/organizations.ts: ${unnamed}`)
587
+
588
+ /**
589
+ * The field every tenant-owned kayan places, as `organization`: the tenant target's own, under the
590
+ * backoffice's word for a tenant. Its copy is all a kayan may change of it, and a kayan that places
591
+ * none is shared.
592
+ */
593
+ export const organization = { ...tenant.field, label: { en: 'Organization', ar: 'المؤسسة' } }
@@ -0,0 +1,29 @@
1
+ import type { SystemRoles } from 'mithaq'
2
+
3
+ /**
4
+ * Better Auth's own organization roles, `owner`, `admin` and `member`, as the
5
+ * roles a project without the roles add-on ships: the access target answers a
6
+ * member the ones its `member.role` names in that organization. An owner and an
7
+ * admin hold every key of the project, and a member reads the tags and changes
8
+ * nothing. A kayan added later is the owner's and the admin's alone until its
9
+ * keys are listed under `member` here; the roles add-on brings roles a tenant
10
+ * writes itself.
11
+ */
12
+ export const defaultMemberRoles = {
13
+ owner: { label: { ar: 'مالك', en: 'Owner' }, permissions: '*' },
14
+ admin: { label: { ar: 'مشرف', en: 'Admin' }, permissions: '*' },
15
+ member: {
16
+ label: { ar: 'عضو', en: 'Member' },
17
+ permissions: [
18
+ 'screen.dashboard',
19
+ 'tags.find',
20
+ 'tags.count',
21
+ 'tags.findByID',
22
+ 'tags.fields.id',
23
+ 'tags.fields.createdAt',
24
+ 'tags.fields.updatedAt',
25
+ 'tags.fields.organization',
26
+ 'tags.fields.name',
27
+ ],
28
+ },
29
+ } as const satisfies SystemRoles
@@ -3,7 +3,7 @@
3
3
  // page's chunk takes no server module with it, and the server's own guards
4
4
  // import the same functions.
5
5
  import type { AccessReport } from 'mithaq/admin'
6
- import type { tenantRoles } from '../roles.ts'
6
+ import type { tenantRoles } from './system-roles.ts'
7
7
 
8
8
  /**
9
9
  * Whether the viewer may hand over these keys, as a role or one at a time,
@@ -0,0 +1,11 @@
1
+ import { memberRoleTarget } from 'mithaq'
2
+ import { systemRoles } from './system-roles.ts'
3
+ import { assignedRoles, tenantAccess } from './tenant-access.ts'
4
+
5
+ /**
6
+ * The access target of a project without the roles add-on, under which a member holds what
7
+ * Better Auth's own `member.role` names in the organization, owner, admin or
8
+ * member, and no custom role or assignment table exists. The roles add-on
9
+ * swaps it for `rbacTarget` over its `roles` and `memberRoles` kayan.
10
+ */
11
+ export const accessTarget = (roles: typeof systemRoles = systemRoles) => memberRoleTarget({ roles, rolesOf: assignedRoles, access: tenantAccess })
@@ -4,8 +4,10 @@
4
4
  * and the dashboard's role form offers it among that kayan's operations,
5
5
  * labelled by the kayan's copy under `operations.<key>`. It is plain data,
6
6
  * imported by the server and by the role form alike.
7
- *
8
- * `enrollments.others` reaches every seat of the organization rather than the
9
- * actor's own.
10
7
  */
11
- export const projectStatements = { enrollments: ['others'] } as const satisfies Record<string, readonly string[]>
8
+ export const projectStatements = {
9
+ // add-on: courses
10
+ // Every seat of the organization rather than the actor's own.
11
+ enrollments: ['others'],
12
+ // end add-on: courses
13
+ } as const satisfies Record<string, readonly string[]>
@@ -0,0 +1,16 @@
1
+ // The subpath, so a page piece reading the fixed roles' labels loads no server code.
2
+ import { backofficeRoles } from '../mithaq/auth/backoffice-roles.ts'
3
+ import type { SystemRoles } from 'mithaq'
4
+ import { tenantRoles } from './tenant-roles.ts'
5
+
6
+ export { tenantRoles }
7
+
8
+ /**
9
+ * Every role this project ships, the backoffice's and the tenants', which is what
10
+ * the access target takes whole: it holds a slug's keys wherever the project's
11
+ * reader answers it. The backoffice roles are the auth setup's, because the column
12
+ * they live on is Better Auth's own `user.role` and the admin plugin's roles are
13
+ * built from the same lists. The reader answers them in the backoffice app alone,
14
+ * and an assignment never names one.
15
+ */
16
+ export const systemRoles = { ...backofficeRoles, ...tenantRoles } as const satisfies SystemRoles