create-mithaq 0.0.0-stage → 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 (396) hide show
  1. package/LICENSE +22 -0
  2. package/LICENSES/openstatus-time-picker.txt +21 -0
  3. package/LICENSES/payload.txt +22 -0
  4. package/LICENSES/ponytail.txt +21 -0
  5. package/index.js +521 -0
  6. package/package.json +19 -4
  7. package/template/apps/asas-backoffice/components.json +25 -0
  8. package/template/apps/asas-backoffice/gitignore +3 -0
  9. package/template/apps/asas-backoffice/package.json +57 -0
  10. package/template/apps/asas-backoffice/public/favicon.svg +1 -0
  11. package/template/apps/asas-backoffice/src/api.ts +147 -0
  12. package/template/apps/asas-backoffice/src/auth-frame.tsx +150 -0
  13. package/template/apps/asas-backoffice/src/components/ui/accordion.tsx +74 -0
  14. package/template/apps/asas-backoffice/src/components/ui/alert-dialog.tsx +185 -0
  15. package/template/apps/asas-backoffice/src/components/ui/avatar.tsx +106 -0
  16. package/template/apps/asas-backoffice/src/components/ui/badge.tsx +48 -0
  17. package/template/apps/asas-backoffice/src/components/ui/breadcrumb.tsx +124 -0
  18. package/template/apps/asas-backoffice/src/components/ui/button-group.tsx +87 -0
  19. package/template/apps/asas-backoffice/src/components/ui/button.tsx +55 -0
  20. package/template/apps/asas-backoffice/src/components/ui/calendar.tsx +223 -0
  21. package/template/apps/asas-backoffice/src/components/ui/card.tsx +102 -0
  22. package/template/apps/asas-backoffice/src/components/ui/chart.tsx +371 -0
  23. package/template/apps/asas-backoffice/src/components/ui/checkbox.tsx +28 -0
  24. package/template/apps/asas-backoffice/src/components/ui/collapsible.tsx +19 -0
  25. package/template/apps/asas-backoffice/src/components/ui/combobox.tsx +308 -0
  26. package/template/apps/asas-backoffice/src/components/ui/dialog.tsx +158 -0
  27. package/template/apps/asas-backoffice/src/components/ui/direction.tsx +6 -0
  28. package/template/apps/asas-backoffice/src/components/ui/directional-icon.tsx +20 -0
  29. package/template/apps/asas-backoffice/src/components/ui/dropdown-menu.tsx +267 -0
  30. package/template/apps/asas-backoffice/src/components/ui/field.tsx +236 -0
  31. package/template/apps/asas-backoffice/src/components/ui/input-group.tsx +155 -0
  32. package/template/apps/asas-backoffice/src/components/ui/input-otp.tsx +84 -0
  33. package/template/apps/asas-backoffice/src/components/ui/input.tsx +19 -0
  34. package/template/apps/asas-backoffice/src/components/ui/label.tsx +19 -0
  35. package/template/apps/asas-backoffice/src/components/ui/popover.tsx +90 -0
  36. package/template/apps/asas-backoffice/src/components/ui/radio-group.tsx +37 -0
  37. package/template/apps/asas-backoffice/src/components/ui/resizable.tsx +47 -0
  38. package/template/apps/asas-backoffice/src/components/ui/scroll-area.tsx +54 -0
  39. package/template/apps/asas-backoffice/src/components/ui/select.tsx +204 -0
  40. package/template/apps/asas-backoffice/src/components/ui/separator.tsx +24 -0
  41. package/template/apps/asas-backoffice/src/components/ui/sheet.tsx +139 -0
  42. package/template/apps/asas-backoffice/src/components/ui/sidebar.tsx +714 -0
  43. package/template/apps/asas-backoffice/src/components/ui/skeleton.tsx +13 -0
  44. package/template/apps/asas-backoffice/src/components/ui/slider.tsx +55 -0
  45. package/template/apps/asas-backoffice/src/components/ui/spinner.tsx +10 -0
  46. package/template/apps/asas-backoffice/src/components/ui/switch.tsx +29 -0
  47. package/template/apps/asas-backoffice/src/components/ui/table.tsx +115 -0
  48. package/template/apps/asas-backoffice/src/components/ui/tabs.tsx +79 -0
  49. package/template/apps/asas-backoffice/src/components/ui/textarea.tsx +17 -0
  50. package/template/apps/asas-backoffice/src/components/ui/toggle-group.tsx +89 -0
  51. package/template/apps/asas-backoffice/src/components/ui/toggle.tsx +44 -0
  52. package/template/apps/asas-backoffice/src/components/ui/tooltip.tsx +63 -0
  53. package/template/apps/asas-backoffice/src/components.ts +35 -0
  54. package/template/apps/asas-backoffice/src/copy.ts +1 -0
  55. package/template/apps/asas-backoffice/src/first-user.ts +14 -0
  56. package/template/apps/asas-backoffice/src/hooks/use-mobile.ts +19 -0
  57. package/template/apps/asas-backoffice/src/lib/utils.ts +1 -0
  58. package/template/apps/asas-backoffice/src/links.ts +38 -0
  59. package/template/apps/asas-backoffice/src/locale-switch.tsx +28 -0
  60. package/template/apps/asas-backoffice/src/no-organization.tsx +5 -0
  61. package/template/apps/asas-backoffice/src/redirect.ts +1 -0
  62. package/template/apps/asas-backoffice/src/request-headers.ts +11 -0
  63. package/template/apps/asas-backoffice/src/routeTree.gen.ts +206 -0
  64. package/template/apps/asas-backoffice/src/router.tsx +18 -0
  65. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice/files.ts +10 -0
  66. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice/screen.json +3971 -0
  67. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice/types.ts +163 -0
  68. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice.$.tsx +17 -0
  69. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice.index.tsx +10 -0
  70. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice.tsx +13 -0
  71. package/template/apps/asas-backoffice/src/routes/__root.tsx +95 -0
  72. package/template/apps/asas-backoffice/src/routes/api/$.ts +34 -0
  73. package/template/apps/asas-backoffice/src/routes/auth.create-first-user.tsx +113 -0
  74. package/template/apps/asas-backoffice/src/routes/auth.sign-in.tsx +77 -0
  75. package/template/apps/asas-backoffice/src/routes/unauthorized.tsx +134 -0
  76. package/template/apps/asas-backoffice/src/screen-form.tsx +36 -0
  77. package/template/apps/asas-backoffice/src/screen-list.tsx +140 -0
  78. package/template/apps/asas-backoffice/src/screen-names.ts +10 -0
  79. package/template/apps/asas-backoffice/src/screens.tsx +888 -0
  80. package/template/apps/asas-backoffice/src/server.ts +30 -0
  81. package/template/apps/asas-backoffice/src/slide.ts +22 -0
  82. package/template/apps/asas-backoffice/src/start.ts +42 -0
  83. package/template/apps/asas-backoffice/src/styles.css +202 -0
  84. package/template/apps/asas-backoffice/src/theme.ts +2 -0
  85. package/template/apps/asas-backoffice/tsconfig.json +19 -0
  86. package/template/apps/asas-backoffice/vite.config.ts +32 -0
  87. package/template/apps/asas-tanstack/components.json +25 -0
  88. package/template/apps/asas-tanstack/gitignore +3 -0
  89. package/template/apps/asas-tanstack/package.json +58 -0
  90. package/template/apps/asas-tanstack/public/favicon.svg +1 -0
  91. package/template/apps/asas-tanstack/src/api.ts +147 -0
  92. package/template/apps/asas-tanstack/src/auth-frame.tsx +150 -0
  93. package/template/apps/asas-tanstack/src/choose-password.tsx +150 -0
  94. package/template/apps/asas-tanstack/src/components/ui/accordion.tsx +10 -0
  95. package/template/apps/asas-tanstack/src/components/ui/alert-dialog.tsx +173 -0
  96. package/template/apps/asas-tanstack/src/components/ui/avatar.tsx +104 -0
  97. package/template/apps/asas-tanstack/src/components/ui/badge.tsx +48 -0
  98. package/template/apps/asas-tanstack/src/components/ui/breadcrumb.tsx +125 -0
  99. package/template/apps/asas-tanstack/src/components/ui/button-group.tsx +87 -0
  100. package/template/apps/asas-tanstack/src/components/ui/button.tsx +56 -0
  101. package/template/apps/asas-tanstack/src/components/ui/calendar.tsx +223 -0
  102. package/template/apps/asas-tanstack/src/components/ui/card.tsx +94 -0
  103. package/template/apps/asas-tanstack/src/components/ui/chart.tsx +369 -0
  104. package/template/apps/asas-tanstack/src/components/ui/checkbox.tsx +27 -0
  105. package/template/apps/asas-tanstack/src/components/ui/collapsible.tsx +19 -0
  106. package/template/apps/asas-tanstack/src/components/ui/combobox.tsx +300 -0
  107. package/template/apps/asas-tanstack/src/components/ui/dialog.tsx +149 -0
  108. package/template/apps/asas-tanstack/src/components/ui/direction.tsx +5 -0
  109. package/template/apps/asas-tanstack/src/components/ui/directional-icon.tsx +20 -0
  110. package/template/apps/asas-tanstack/src/components/ui/dropdown-menu.tsx +267 -0
  111. package/template/apps/asas-tanstack/src/components/ui/field.tsx +225 -0
  112. package/template/apps/asas-tanstack/src/components/ui/input-group.tsx +147 -0
  113. package/template/apps/asas-tanstack/src/components/ui/input-otp.tsx +20 -0
  114. package/template/apps/asas-tanstack/src/components/ui/input.tsx +20 -0
  115. package/template/apps/asas-tanstack/src/components/ui/label.tsx +18 -0
  116. package/template/apps/asas-tanstack/src/components/ui/popover.tsx +50 -0
  117. package/template/apps/asas-tanstack/src/components/ui/radio-group.tsx +10 -0
  118. package/template/apps/asas-tanstack/src/components/ui/resizable.tsx +47 -0
  119. package/template/apps/asas-tanstack/src/components/ui/scroll-area.tsx +52 -0
  120. package/template/apps/asas-tanstack/src/components/ui/select.tsx +190 -0
  121. package/template/apps/asas-tanstack/src/components/ui/separator.tsx +23 -0
  122. package/template/apps/asas-tanstack/src/components/ui/sheet.tsx +127 -0
  123. package/template/apps/asas-tanstack/src/components/ui/sidebar.tsx +716 -0
  124. package/template/apps/asas-tanstack/src/components/ui/skeleton.tsx +13 -0
  125. package/template/apps/asas-tanstack/src/components/ui/slider.tsx +13 -0
  126. package/template/apps/asas-tanstack/src/components/ui/spinner.tsx +11 -0
  127. package/template/apps/asas-tanstack/src/components/ui/switch.tsx +8 -0
  128. package/template/apps/asas-tanstack/src/components/ui/table.tsx +119 -0
  129. package/template/apps/asas-tanstack/src/components/ui/tabs.tsx +80 -0
  130. package/template/apps/asas-tanstack/src/components/ui/textarea.tsx +18 -0
  131. package/template/apps/asas-tanstack/src/components/ui/toggle-group.tsx +8 -0
  132. package/template/apps/asas-tanstack/src/components/ui/toggle.tsx +44 -0
  133. package/template/apps/asas-tanstack/src/components/ui/tooltip.tsx +68 -0
  134. package/template/apps/asas-tanstack/src/components.ts +35 -0
  135. package/template/apps/asas-tanstack/src/copy.ts +1207 -0
  136. package/template/apps/asas-tanstack/src/first-run.ts +16 -0
  137. package/template/apps/asas-tanstack/src/hooks/use-mobile.ts +19 -0
  138. package/template/apps/asas-tanstack/src/lib/utils.ts +6 -0
  139. package/template/apps/asas-tanstack/src/links.ts +38 -0
  140. package/template/apps/asas-tanstack/src/locale-switch.tsx +28 -0
  141. package/template/apps/asas-tanstack/src/no-organization.tsx +108 -0
  142. package/template/apps/asas-tanstack/src/quiet-abort.ts +25 -0
  143. package/template/apps/asas-tanstack/src/redirect.ts +39 -0
  144. package/template/apps/asas-tanstack/src/request-headers.ts +11 -0
  145. package/template/apps/asas-tanstack/src/routeTree.gen.ts +424 -0
  146. package/template/apps/asas-tanstack/src/router.tsx +18 -0
  147. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/files.ts +11 -0
  148. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/screen.json +4242 -0
  149. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/types.ts +160 -0
  150. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.$.tsx +17 -0
  151. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.index.tsx +10 -0
  152. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.tsx +13 -0
  153. package/template/apps/asas-tanstack/src/routes/(screens)/portal/files.ts +9 -0
  154. package/template/apps/asas-tanstack/src/routes/(screens)/portal/screen.json +1564 -0
  155. package/template/apps/asas-tanstack/src/routes/(screens)/portal/types.ts +94 -0
  156. package/template/apps/asas-tanstack/src/routes/(screens)/portal.$.tsx +17 -0
  157. package/template/apps/asas-tanstack/src/routes/(screens)/portal.index.tsx +10 -0
  158. package/template/apps/asas-tanstack/src/routes/(screens)/portal.tsx +13 -0
  159. package/template/apps/asas-tanstack/src/routes/__root.tsx +99 -0
  160. package/template/apps/asas-tanstack/src/routes/api/$.ts +11 -0
  161. package/template/apps/asas-tanstack/src/routes/auth.accept-invitation.tsx +108 -0
  162. package/template/apps/asas-tanstack/src/routes/auth.first-run.tsx +114 -0
  163. package/template/apps/asas-tanstack/src/routes/auth.forgot-password.tsx +94 -0
  164. package/template/apps/asas-tanstack/src/routes/auth.handoff.ts +39 -0
  165. package/template/apps/asas-tanstack/src/routes/auth.reset-password.tsx +16 -0
  166. package/template/apps/asas-tanstack/src/routes/auth.set-password.tsx +16 -0
  167. package/template/apps/asas-tanstack/src/routes/auth.sign-in.tsx +98 -0
  168. package/template/apps/asas-tanstack/src/routes/auth.sign-up.tsx +143 -0
  169. package/template/apps/asas-tanstack/src/routes/index.tsx +146 -0
  170. package/template/apps/asas-tanstack/src/routes/unauthorized.tsx +134 -0
  171. package/template/apps/asas-tanstack/src/screen-form.tsx +36 -0
  172. package/template/apps/asas-tanstack/src/screen-list.tsx +140 -0
  173. package/template/apps/asas-tanstack/src/screen-names.ts +10 -0
  174. package/template/apps/asas-tanstack/src/screens.tsx +888 -0
  175. package/template/apps/asas-tanstack/src/server.ts +26 -0
  176. package/template/apps/asas-tanstack/src/slide.ts +22 -0
  177. package/template/apps/asas-tanstack/src/start.ts +57 -0
  178. package/template/apps/asas-tanstack/src/styles.css +163 -0
  179. package/template/apps/asas-tanstack/src/theme.ts +5 -0
  180. package/template/apps/asas-tanstack/tsconfig.json +19 -0
  181. package/template/apps/asas-tanstack/vite.config.ts +34 -0
  182. package/template/package.json +16 -0
  183. package/template/templates/asas/.claude/skills/add-field/SKILL.md +27 -0
  184. package/template/templates/asas/.claude/skills/add-kayan/SKILL.md +26 -0
  185. package/template/templates/asas/.claude/skills/eject-admin-piece/SKILL.md +28 -0
  186. package/template/templates/asas/.claude/skills/upgrade-mithaq/SKILL.md +35 -0
  187. package/template/templates/asas/.env.example +33 -0
  188. package/template/templates/asas/AGENTS.md +101 -0
  189. package/template/templates/asas/CLAUDE.md +1 -0
  190. package/template/templates/asas/README.md +1056 -0
  191. package/template/templates/asas/mithaq.hints.json +25 -0
  192. package/template/templates/asas/package.json +53 -0
  193. package/template/templates/asas/src/asas.ts +118 -0
  194. package/template/templates/asas/src/backoffice-http.ts +117 -0
  195. package/template/templates/asas/src/dev-setup.ts +112 -0
  196. package/template/templates/asas/src/local-database.ts +22 -0
  197. package/template/templates/asas/src/messages/account.json +132 -0
  198. package/template/templates/asas/src/messages/auditLog.json +304 -0
  199. package/template/templates/asas/src/messages/backofficeLog.json +132 -0
  200. package/template/templates/asas/src/messages/courses.json +312 -0
  201. package/template/templates/asas/src/messages/enrollments.json +60 -0
  202. package/template/templates/asas/src/messages/invitation.json +82 -0
  203. package/template/templates/asas/src/messages/localUsers.json +48 -0
  204. package/template/templates/asas/src/messages/media.json +76 -0
  205. package/template/templates/asas/src/messages/member.json +62 -0
  206. package/template/templates/asas/src/messages/memberRoles.json +48 -0
  207. package/template/templates/asas/src/messages/mithaq.json +214 -0
  208. package/template/templates/asas/src/messages/organization.json +72 -0
  209. package/template/templates/asas/src/messages/organizationRole.json +62 -0
  210. package/template/templates/asas/src/messages/roles.json +72 -0
  211. package/template/templates/asas/src/messages/session.json +102 -0
  212. package/template/templates/asas/src/messages/sessions.json +136 -0
  213. package/template/templates/asas/src/messages/settings.json +64 -0
  214. package/template/templates/asas/src/messages/tags.json +48 -0
  215. package/template/templates/asas/src/messages/user.json +112 -0
  216. package/template/templates/asas/src/messages/verification.json +62 -0
  217. package/template/templates/asas/src/migrations/20260920080701_parched_tombstone/migration.sql +177 -0
  218. package/template/templates/asas/src/migrations/20260920080701_parched_tombstone/snapshot.json +2169 -0
  219. package/template/templates/asas/src/migrations/20260920230505_zippy_ben_urich/migration.sql +1 -0
  220. package/template/templates/asas/src/migrations/20260920230505_zippy_ben_urich/snapshot.json +2182 -0
  221. package/template/templates/asas/src/migrations/20260920231219_slim_retro_girl/migration.sql +20 -0
  222. package/template/templates/asas/src/migrations/20260920231219_slim_retro_girl/snapshot.json +2409 -0
  223. package/template/templates/asas/src/migrations/20260922082945_military_ironclad/migration.sql +15 -0
  224. package/template/templates/asas/src/migrations/20260922082945_military_ironclad/snapshot.json +2627 -0
  225. package/template/templates/asas/src/migrations/20260922112630_bitter_chat/migration.sql +5 -0
  226. package/template/templates/asas/src/migrations/20260922112630_bitter_chat/snapshot.json +2691 -0
  227. package/template/templates/asas/src/migrations/20260923235915_foamy_rachel_grey/migration.sql +8 -0
  228. package/template/templates/asas/src/migrations/20260923235915_foamy_rachel_grey/snapshot.json +2795 -0
  229. package/template/templates/asas/src/migrations/20260926145709_clean_the_initiative/migration.sql +3 -0
  230. package/template/templates/asas/src/migrations/20260926145709_clean_the_initiative/snapshot.json +2834 -0
  231. package/template/templates/asas/src/migrations/20260927212525_bent_prowler/migration.sql +9 -0
  232. package/template/templates/asas/src/migrations/20260927212525_bent_prowler/snapshot.json +2870 -0
  233. package/template/templates/asas/src/migrations/20260927223441_tranquil_bromley/migration.sql +1 -0
  234. package/template/templates/asas/src/migrations/20260927223441_tranquil_bromley/snapshot.json +2881 -0
  235. package/template/templates/asas/src/migrations/20260928030047_outgoing_wraith/migration.sql +15 -0
  236. package/template/templates/asas/src/migrations/20260928030047_outgoing_wraith/snapshot.json +3073 -0
  237. package/template/templates/asas/src/migrations/20260929024407_grey_morlun/migration.sql +21 -0
  238. package/template/templates/asas/src/migrations/20260929024407_grey_morlun/snapshot.json +3098 -0
  239. package/template/templates/asas/src/migrations/20260929130530_bored_jackal/migration.sql +21 -0
  240. package/template/templates/asas/src/migrations/20260929130530_bored_jackal/snapshot.json +3124 -0
  241. package/template/templates/asas/src/migrations/20260930102924_material_newton_destine/migration.sql +32 -0
  242. package/template/templates/asas/src/migrations/20260930102924_material_newton_destine/snapshot.json +3324 -0
  243. package/template/templates/asas/src/migrations/20261003022353_clumsy_taskmaster/migration.sql +2 -0
  244. package/template/templates/asas/src/migrations/20261003022353_clumsy_taskmaster/snapshot.json +3350 -0
  245. package/template/templates/asas/src/migrations/20261003143249_premium_red_shift/migration.sql +21 -0
  246. package/template/templates/asas/src/migrations/20261003143249_premium_red_shift/snapshot.json +3635 -0
  247. package/template/templates/asas/src/migrations/20261008215349_square_norrin_radd/migration.sql +8 -0
  248. package/template/templates/asas/src/migrations/20261008215349_square_norrin_radd/snapshot.json +3635 -0
  249. package/template/templates/asas/src/migrations/20261008230204_many_scarecrow/migration.sql +18 -0
  250. package/template/templates/asas/src/migrations/20261008230204_many_scarecrow/snapshot.json +3635 -0
  251. package/template/templates/asas/src/migrations/20261010005521_elite_giant_girl/migration.sql +18 -0
  252. package/template/templates/asas/src/migrations/20261010005521_elite_giant_girl/snapshot.json +3635 -0
  253. package/template/templates/asas/src/migrations/20261010120822_needy_thunderbolt/migration.sql +11 -0
  254. package/template/templates/asas/src/migrations/20261010120822_needy_thunderbolt/snapshot.json +3667 -0
  255. package/template/templates/asas/src/migrations/20261012000752_organization_ownership/migration.sql +48 -0
  256. package/template/templates/asas/src/migrations/20261012000752_organization_ownership/snapshot.json +3891 -0
  257. package/template/templates/asas/src/migrations/20261014221355_tags_restrict/migration.sql +1 -0
  258. package/template/templates/asas/src/migrations/20261014221355_tags_restrict/snapshot.json +3881 -0
  259. package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/migration.sql +1 -0
  260. package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/snapshot.json +3894 -0
  261. package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/migration.sql +1 -0
  262. package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/snapshot.json +3907 -0
  263. package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/migration.sql +1 -0
  264. package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/snapshot.json +3907 -0
  265. package/template/templates/asas/src/migrations/20261015042444_organization_names/migration.sql +1 -0
  266. package/template/templates/asas/src/migrations/20261015042444_organization_names/snapshot.json +3920 -0
  267. package/template/templates/asas/src/migrations/20261015053212_member_last_seen/migration.sql +1 -0
  268. package/template/templates/asas/src/migrations/20261015053212_member_last_seen/snapshot.json +3933 -0
  269. package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/migration.sql +15 -0
  270. package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/snapshot.json +4113 -0
  271. package/template/templates/asas/src/migrations/20261015095752_portal/migration.sql +2 -0
  272. package/template/templates/asas/src/migrations/20261015095752_portal/snapshot.json +4113 -0
  273. package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/migration.sql +9 -0
  274. package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/snapshot.json +4302 -0
  275. package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/migration.sql +27 -0
  276. package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/snapshot.json +4289 -0
  277. package/template/templates/asas/src/migrations/drizzle.config.ts +11 -0
  278. package/template/templates/asas/src/migrations/schema.ts +30 -0
  279. package/template/templates/asas/src/mithaq.ts +4 -0
  280. package/template/templates/asas/src/organizations.ts +3 -0
  281. package/template/templates/asas/src/seed.ts +427 -0
  282. package/template/templates/asas/src/ui-backoffice/home.tsx +63 -0
  283. package/template/templates/asas/src/ui-backoffice/organization-actions.tsx +121 -0
  284. package/template/templates/asas/src/ui-backoffice/record-page.tsx +30 -0
  285. package/template/templates/asas/src/ui-backoffice/role-cells.tsx +59 -0
  286. package/template/templates/asas/src/ui-backoffice/user-actions.tsx +249 -0
  287. package/template/templates/asas/src/ui-dashboard/dashboard.tsx +300 -0
  288. package/template/templates/asas/src/ui-dashboard/home.tsx +73 -0
  289. package/template/templates/asas/src/ui-dashboard/members-card.tsx +147 -0
  290. package/template/templates/asas/src/ui-dashboard/members.tsx +829 -0
  291. package/template/templates/asas/src/ui-dashboard/theme-provider.tsx +10 -0
  292. package/template/templates/asas/src/ui-dashboard/versions.tsx +446 -0
  293. package/template/templates/asas/src/ui-portal/courses-home.tsx +96 -0
  294. package/template/templates/asas/src/ui-portal/home.tsx +73 -0
  295. package/template/templates/asas/src/ui-portal/page.tsx +11 -0
  296. package/template/templates/features/audit-log/audit-log/audit-log.ts +284 -0
  297. package/template/templates/features/audit-log/backoffice-log/backoffice-log.ts +67 -0
  298. package/template/templates/features/audit-log/index.ts +4 -0
  299. package/template/templates/features/audit-log/package.json +13 -0
  300. package/template/templates/features/auth/index.ts +11 -0
  301. package/template/templates/features/auth/local-users/local-users.ts +143 -0
  302. package/template/templates/features/auth/package.json +13 -0
  303. package/template/templates/features/auth/user/account.access.ts +8 -0
  304. package/template/templates/features/auth/user/account.ts +31 -0
  305. package/template/templates/features/auth/user/index.ts +18 -0
  306. package/template/templates/features/auth/user/invitation.access.ts +4 -0
  307. package/template/templates/features/auth/user/invitation.ts +27 -0
  308. package/template/templates/features/auth/user/member.access.ts +10 -0
  309. package/template/templates/features/auth/user/member.screens.ts +37 -0
  310. package/template/templates/features/auth/user/member.ts +26 -0
  311. package/template/templates/features/auth/user/organization.access.ts +20 -0
  312. package/template/templates/features/auth/user/organization.screens.ts +37 -0
  313. package/template/templates/features/auth/user/organization.ts +25 -0
  314. package/template/templates/features/auth/user/organizationRole.access.ts +8 -0
  315. package/template/templates/features/auth/user/organizationRole.ts +25 -0
  316. package/template/templates/features/auth/user/session.access.ts +8 -0
  317. package/template/templates/features/auth/user/session.ts +28 -0
  318. package/template/templates/features/auth/user/user.access.ts +30 -0
  319. package/template/templates/features/auth/user/user.screens.ts +45 -0
  320. package/template/templates/features/auth/user/user.ts +31 -0
  321. package/template/templates/features/auth/user/verification.access.ts +8 -0
  322. package/template/templates/features/auth/user/verification.ts +23 -0
  323. package/template/templates/features/courses/courses/courses.ts +601 -0
  324. package/template/templates/features/courses/courses/cover-reads.ts +33 -0
  325. package/template/templates/features/courses/courses/ui/archived-badge.tsx +9 -0
  326. package/template/templates/features/courses/courses/ui/fee-cell.tsx +19 -0
  327. package/template/templates/features/courses/courses/ui/no-courses.tsx +13 -0
  328. package/template/templates/features/courses/courses/ui/status-cell.tsx +33 -0
  329. package/template/templates/features/courses/courses/ui/unpublish-button.tsx +15 -0
  330. package/template/templates/features/courses/domain/archive.ts +39 -0
  331. package/template/templates/features/courses/domain/on-offer.ts +6 -0
  332. package/template/templates/features/courses/enrollments/enrollments.ts +309 -0
  333. package/template/templates/features/courses/enrollments/ui/session-cell.tsx +75 -0
  334. package/template/templates/features/courses/enrollments/ui/session-labels.tsx +36 -0
  335. package/template/templates/features/courses/index.ts +8 -0
  336. package/template/templates/features/courses/package.json +24 -0
  337. package/template/templates/features/courses/portal/ui/catalogue.tsx +86 -0
  338. package/template/templates/features/courses/portal/ui/course.tsx +176 -0
  339. package/template/templates/features/courses/portal/ui/enrollments.tsx +170 -0
  340. package/template/templates/features/courses/portal/ui/parts.tsx +95 -0
  341. package/template/templates/features/courses/portal/ui/reads.ts +76 -0
  342. package/template/templates/features/courses/portal/ui/rules.ts +87 -0
  343. package/template/templates/features/courses/portal/ui/shell.tsx +194 -0
  344. package/template/templates/features/courses/sessions/seats.ts +42 -0
  345. package/template/templates/features/courses/sessions/sessions.ts +186 -0
  346. package/template/templates/features/media/index.ts +10 -0
  347. package/template/templates/features/media/media/media.ts +131 -0
  348. package/template/templates/features/media/media/ui/preview.tsx +143 -0
  349. package/template/templates/features/media/package.json +19 -0
  350. package/template/templates/features/roles/index.ts +4 -0
  351. package/template/templates/features/roles/member-roles/member-roles.ts +207 -0
  352. package/template/templates/features/roles/package.json +21 -0
  353. package/template/templates/features/roles/roles/roles.ts +184 -0
  354. package/template/templates/features/roles/roles/ui/role-form.tsx +267 -0
  355. package/template/templates/features/roles/roles/ui/roles.tsx +190 -0
  356. package/template/templates/features/settings/index.ts +3 -0
  357. package/template/templates/features/settings/package.json +13 -0
  358. package/template/templates/features/settings/settings/settings.ts +64 -0
  359. package/template/templates/features/tags/index.ts +3 -0
  360. package/template/templates/features/tags/package.json +14 -0
  361. package/template/templates/features/tags/tags/tags.ts +80 -0
  362. package/template/templates/mithaq/components.json +22 -0
  363. package/template/templates/mithaq/package.json +33 -0
  364. package/template/templates/mithaq/src/mail/index.ts +32 -0
  365. package/template/templates/mithaq/src/mail/mailers.ts +36 -0
  366. package/template/templates/mithaq/src/mithaq/audit-sink.ts +111 -0
  367. package/template/templates/mithaq/src/mithaq/auth/backoffice-roles.ts +108 -0
  368. package/template/templates/mithaq/src/mithaq/auth/index.ts +1859 -0
  369. package/template/templates/mithaq/src/mithaq/auth/organizations.ts +80 -0
  370. package/template/templates/mithaq/src/mithaq/pages.ts +83 -0
  371. package/template/templates/mithaq/src/mithaq/rich-text-nodes.ts +19 -0
  372. package/template/templates/mithaq/src/mithaq.ts +593 -0
  373. package/template/templates/mithaq/src/roles/default-member-roles.ts +29 -0
  374. package/template/templates/mithaq/src/roles/grants.ts +47 -0
  375. package/template/templates/mithaq/src/roles/member-access.ts +11 -0
  376. package/template/templates/mithaq/src/roles/statements.ts +13 -0
  377. package/template/templates/mithaq/src/roles/system-roles.ts +16 -0
  378. package/template/templates/mithaq/src/roles/tenant-access.ts +260 -0
  379. package/template/templates/mithaq/src/roles/tenant-roles.ts +321 -0
  380. package/template/templates/mithaq/src/steps/expected-updated-at.ts +41 -0
  381. package/template/templates/mithaq/src/storage/defaults.ts +156 -0
  382. package/template/templates/mithaq/src/storage/index.ts +97 -0
  383. package/template/templates/mithaq/src/storage/local.ts +143 -0
  384. package/template/templates/mithaq/src/storage/r2.ts +5 -0
  385. package/template/templates/mithaq/src/storage/s3.ts +91 -0
  386. package/template/templates/mithaq/src/storage/uploadthing.ts +59 -0
  387. package/template/templates/mithaq/src/ui/activity-page.tsx +33 -0
  388. package/template/templates/mithaq/src/ui/activity.tsx +527 -0
  389. package/template/templates/mithaq/src/ui/app-shell.tsx +268 -0
  390. package/template/templates/mithaq/src/ui/kpi-card.tsx +25 -0
  391. package/template/templates/mithaq/src/ui/record-page.tsx +50 -0
  392. package/template/templates/mithaq/src/ui/rich-text-nodes.tsx +33 -0
  393. package/template/templates/mithaq/src/ui/screen-nav.tsx +28 -0
  394. package/template/templates/mithaq/src/ui/table-page.tsx +9 -0
  395. package/template/templates/mithaq/src/ui/top-shell.tsx +60 -0
  396. package/README.md +0 -3
@@ -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
@@ -0,0 +1,47 @@
1
+ // What a page asks before it offers a role or a key, measured the way the
2
+ // server measures the write it would make. It imports nothing at runtime, so a
3
+ // page's chunk takes no server module with it, and the server's own guards
4
+ // import the same functions.
5
+ import type { AccessReport } from 'mithaq/admin'
6
+ import type { tenantRoles } from './system-roles.ts'
7
+
8
+ /**
9
+ * Whether the viewer may hand over these keys, as a role or one at a time,
10
+ * measured the way the server's guards measure it: a root granter anything,
11
+ * anyone else only keys it holds, and `'*'` only a root granter. Keys the
12
+ * viewer may not read are not grantable. The server refuses a wrong answer here
13
+ * either way.
14
+ */
15
+ export const grantable = (access: AccessReport, keys: readonly string[] | undefined): boolean => {
16
+ const held = access.permissions
17
+ if (held === '*') return true
18
+ return held !== undefined && keys !== undefined && keys.every((key) => key !== '*' && held.includes(key))
19
+ }
20
+
21
+ /** The keys that let their holder change who holds which role, or what a role holds. */
22
+ const managing = ['*', 'roles.create', 'roles.update', 'roles.delete', 'memberRoles.create', 'memberRoles.update', 'memberRoles.delete']
23
+
24
+ /**
25
+ * Whether a role holding these keys is what lets its holder manage roles. The
26
+ * Members page's sheet disables a member's own such role, and the assignment
27
+ * kayan refuses its removal, so the box and the refusal are one rule.
28
+ */
29
+ export const managesRoles = (keys: '*' | readonly string[]): boolean => keys === '*' || keys.some((key) => managing.includes(key))
30
+
31
+ /**
32
+ * Better Auth's creator role, the organization's owner: `creatorRole`, which
33
+ * this project leaves at the provider's default. The provider keeps it already:
34
+ * nobody but an owner removes or demotes one, and the last one never leaves.
35
+ */
36
+ export const OWNER = 'owner'
37
+
38
+ /** The role of this project's that an owner always holds in its tenant. */
39
+ export const OWNER_ROLE = 'administrator' satisfies keyof typeof tenantRoles
40
+
41
+ /**
42
+ * Whether a Better Auth `member.role` names the owner. Several roles are one
43
+ * column, comma-joined, and the name is matched exactly, untrimmed, the way the
44
+ * provider's own owner checks match it: a spaced or cased name the provider does
45
+ * not treat as an owner is no owner here either.
46
+ */
47
+ export const isOwner = (memberRole: unknown): boolean => typeof memberRole === 'string' && memberRole.split(',').includes(OWNER)
@@ -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 })