create-mithaq 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/LICENSES/payload.txt +22 -0
- package/LICENSES/ponytail.txt +21 -0
- package/index.js +127 -0
- package/package.json +19 -4
- package/template/apps/asas-platform/components.json +25 -0
- package/template/apps/asas-platform/gitignore +3 -0
- package/template/apps/asas-platform/package.json +55 -0
- package/template/apps/asas-platform/public/favicon.svg +1 -0
- package/template/apps/asas-platform/src/api.ts +147 -0
- package/template/apps/asas-platform/src/auth-frame.tsx +150 -0
- package/template/apps/asas-platform/src/components/mithaq/cells.tsx +112 -0
- package/template/apps/asas-platform/src/components/mithaq/controls.tsx +650 -0
- package/template/apps/asas-platform/src/components/mithaq/field-shell.tsx +53 -0
- package/template/apps/asas-platform/src/components/mithaq/filter-operators.ts +108 -0
- package/template/apps/asas-platform/src/components/mithaq/form.tsx +1467 -0
- package/template/apps/asas-platform/src/components/mithaq/list-columns.tsx +108 -0
- package/template/apps/asas-platform/src/components/mithaq/list-filters.tsx +368 -0
- package/template/apps/asas-platform/src/components/mithaq/list-pagination.tsx +153 -0
- package/template/apps/asas-platform/src/components/mithaq/list-query.ts +432 -0
- package/template/apps/asas-platform/src/components/mithaq/list-selection.tsx +74 -0
- package/template/apps/asas-platform/src/components/mithaq/list.tsx +865 -0
- package/template/apps/asas-platform/src/components/mithaq/nav.tsx +871 -0
- package/template/apps/asas-platform/src/components/mithaq/nodes.tsx +1024 -0
- package/template/apps/asas-platform/src/components/mithaq/relationship-cell.tsx +127 -0
- package/template/apps/asas-platform/src/components/mithaq/relationship-control.tsx +400 -0
- package/template/apps/asas-platform/src/components/mithaq/rich-text-nodes.ts +6 -0
- package/template/apps/asas-platform/src/components/mithaq/rich-text.tsx +1083 -0
- package/template/apps/asas-platform/src/components/mithaq/upload.tsx +686 -0
- package/template/apps/asas-platform/src/components/ui/accordion.tsx +74 -0
- package/template/apps/asas-platform/src/components/ui/alert-dialog.tsx +185 -0
- package/template/apps/asas-platform/src/components/ui/avatar.tsx +106 -0
- package/template/apps/asas-platform/src/components/ui/badge.tsx +48 -0
- package/template/apps/asas-platform/src/components/ui/breadcrumb.tsx +124 -0
- package/template/apps/asas-platform/src/components/ui/button-group.tsx +87 -0
- package/template/apps/asas-platform/src/components/ui/button.tsx +55 -0
- package/template/apps/asas-platform/src/components/ui/calendar.tsx +223 -0
- package/template/apps/asas-platform/src/components/ui/card.tsx +102 -0
- package/template/apps/asas-platform/src/components/ui/chart.tsx +371 -0
- package/template/apps/asas-platform/src/components/ui/checkbox.tsx +28 -0
- package/template/apps/asas-platform/src/components/ui/collapsible.tsx +19 -0
- package/template/apps/asas-platform/src/components/ui/combobox.tsx +308 -0
- package/template/apps/asas-platform/src/components/ui/dialog.tsx +158 -0
- package/template/apps/asas-platform/src/components/ui/direction.tsx +6 -0
- package/template/apps/asas-platform/src/components/ui/directional-icon.tsx +20 -0
- package/template/apps/asas-platform/src/components/ui/drawer.tsx +216 -0
- package/template/apps/asas-platform/src/components/ui/dropdown-menu.tsx +267 -0
- package/template/apps/asas-platform/src/components/ui/field.tsx +236 -0
- package/template/apps/asas-platform/src/components/ui/input-group.tsx +155 -0
- package/template/apps/asas-platform/src/components/ui/input-otp.tsx +84 -0
- package/template/apps/asas-platform/src/components/ui/input.tsx +19 -0
- package/template/apps/asas-platform/src/components/ui/label.tsx +19 -0
- package/template/apps/asas-platform/src/components/ui/popover.tsx +90 -0
- package/template/apps/asas-platform/src/components/ui/radio-group.tsx +37 -0
- package/template/apps/asas-platform/src/components/ui/resizable.tsx +47 -0
- package/template/apps/asas-platform/src/components/ui/scroll-area.tsx +54 -0
- package/template/apps/asas-platform/src/components/ui/select.tsx +204 -0
- package/template/apps/asas-platform/src/components/ui/separator.tsx +24 -0
- package/template/apps/asas-platform/src/components/ui/sheet.tsx +139 -0
- package/template/apps/asas-platform/src/components/ui/sidebar.tsx +714 -0
- package/template/apps/asas-platform/src/components/ui/skeleton.tsx +13 -0
- package/template/apps/asas-platform/src/components/ui/slider.tsx +55 -0
- package/template/apps/asas-platform/src/components/ui/spinner.tsx +10 -0
- package/template/apps/asas-platform/src/components/ui/switch.tsx +29 -0
- package/template/apps/asas-platform/src/components/ui/table.tsx +115 -0
- package/template/apps/asas-platform/src/components/ui/tabs.tsx +79 -0
- package/template/apps/asas-platform/src/components/ui/textarea.tsx +17 -0
- package/template/apps/asas-platform/src/components/ui/toggle-group.tsx +89 -0
- package/template/apps/asas-platform/src/components/ui/toggle.tsx +44 -0
- package/template/apps/asas-platform/src/components/ui/tooltip.tsx +63 -0
- package/template/apps/asas-platform/src/copy.ts +1 -0
- package/template/apps/asas-platform/src/hooks/use-mobile.ts +19 -0
- package/template/apps/asas-platform/src/lib/utils.ts +1 -0
- package/template/apps/asas-platform/src/links.ts +38 -0
- package/template/apps/asas-platform/src/locale-switch.tsx +26 -0
- package/template/apps/asas-platform/src/redirect.ts +1 -0
- package/template/apps/asas-platform/src/request-headers.ts +11 -0
- package/template/apps/asas-platform/src/routeTree.gen.ts +181 -0
- package/template/apps/asas-platform/src/router.tsx +7 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform/files.ts +4 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform/screen.json +3609 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform/types.ts +145 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform.$.tsx +17 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform.index.tsx +10 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform.tsx +13 -0
- package/template/apps/asas-platform/src/routes/__root.tsx +95 -0
- package/template/apps/asas-platform/src/routes/api/$.ts +30 -0
- package/template/apps/asas-platform/src/routes/auth.sign-in.tsx +72 -0
- package/template/apps/asas-platform/src/routes/unauthorized.tsx +122 -0
- package/template/apps/asas-platform/src/screen-form.tsx +34 -0
- package/template/apps/asas-platform/src/screen-list.tsx +139 -0
- package/template/apps/asas-platform/src/screen-names.ts +10 -0
- package/template/apps/asas-platform/src/screens.tsx +868 -0
- package/template/apps/asas-platform/src/server.ts +20 -0
- package/template/apps/asas-platform/src/slide.ts +22 -0
- package/template/apps/asas-platform/src/start.ts +42 -0
- package/template/apps/asas-platform/src/styles.css +200 -0
- package/template/apps/asas-platform/src/theme.ts +2 -0
- package/template/apps/asas-platform/tsconfig.json +19 -0
- package/template/apps/asas-platform/vite.config.ts +26 -0
- package/template/apps/asas-tanstack/components.json +25 -0
- package/template/apps/asas-tanstack/gitignore +3 -0
- package/template/apps/asas-tanstack/package.json +56 -0
- package/template/apps/asas-tanstack/public/favicon.svg +1 -0
- package/template/apps/asas-tanstack/src/api.ts +147 -0
- package/template/apps/asas-tanstack/src/auth-frame.tsx +150 -0
- package/template/apps/asas-tanstack/src/components/mithaq/cells.tsx +112 -0
- package/template/apps/asas-tanstack/src/components/mithaq/controls.tsx +650 -0
- package/template/apps/asas-tanstack/src/components/mithaq/field-shell.tsx +53 -0
- package/template/apps/asas-tanstack/src/components/mithaq/filter-operators.ts +108 -0
- package/template/apps/asas-tanstack/src/components/mithaq/form.tsx +1467 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-columns.tsx +108 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-filters.tsx +368 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-pagination.tsx +153 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-query.ts +432 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-selection.tsx +74 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list.tsx +865 -0
- package/template/apps/asas-tanstack/src/components/mithaq/nav.tsx +871 -0
- package/template/apps/asas-tanstack/src/components/mithaq/nodes.tsx +1024 -0
- package/template/apps/asas-tanstack/src/components/mithaq/relationship-cell.tsx +127 -0
- package/template/apps/asas-tanstack/src/components/mithaq/relationship-control.tsx +400 -0
- package/template/apps/asas-tanstack/src/components/mithaq/rich-text-nodes.ts +6 -0
- package/template/apps/asas-tanstack/src/components/mithaq/rich-text.tsx +1083 -0
- package/template/apps/asas-tanstack/src/components/mithaq/upload.tsx +686 -0
- package/template/apps/asas-tanstack/src/components/ui/accordion.tsx +10 -0
- package/template/apps/asas-tanstack/src/components/ui/alert-dialog.tsx +173 -0
- package/template/apps/asas-tanstack/src/components/ui/avatar.tsx +104 -0
- package/template/apps/asas-tanstack/src/components/ui/badge.tsx +48 -0
- package/template/apps/asas-tanstack/src/components/ui/breadcrumb.tsx +125 -0
- package/template/apps/asas-tanstack/src/components/ui/button-group.tsx +87 -0
- package/template/apps/asas-tanstack/src/components/ui/button.tsx +56 -0
- package/template/apps/asas-tanstack/src/components/ui/calendar.tsx +223 -0
- package/template/apps/asas-tanstack/src/components/ui/card.tsx +94 -0
- package/template/apps/asas-tanstack/src/components/ui/chart.tsx +369 -0
- package/template/apps/asas-tanstack/src/components/ui/checkbox.tsx +27 -0
- package/template/apps/asas-tanstack/src/components/ui/collapsible.tsx +19 -0
- package/template/apps/asas-tanstack/src/components/ui/combobox.tsx +300 -0
- package/template/apps/asas-tanstack/src/components/ui/dialog.tsx +149 -0
- package/template/apps/asas-tanstack/src/components/ui/direction.tsx +5 -0
- package/template/apps/asas-tanstack/src/components/ui/directional-icon.tsx +20 -0
- package/template/apps/asas-tanstack/src/components/ui/drawer.tsx +215 -0
- package/template/apps/asas-tanstack/src/components/ui/dropdown-menu.tsx +267 -0
- package/template/apps/asas-tanstack/src/components/ui/field.tsx +225 -0
- package/template/apps/asas-tanstack/src/components/ui/input-group.tsx +147 -0
- package/template/apps/asas-tanstack/src/components/ui/input-otp.tsx +20 -0
- package/template/apps/asas-tanstack/src/components/ui/input.tsx +20 -0
- package/template/apps/asas-tanstack/src/components/ui/label.tsx +18 -0
- package/template/apps/asas-tanstack/src/components/ui/popover.tsx +50 -0
- package/template/apps/asas-tanstack/src/components/ui/radio-group.tsx +10 -0
- package/template/apps/asas-tanstack/src/components/ui/resizable.tsx +47 -0
- package/template/apps/asas-tanstack/src/components/ui/scroll-area.tsx +52 -0
- package/template/apps/asas-tanstack/src/components/ui/select.tsx +190 -0
- package/template/apps/asas-tanstack/src/components/ui/separator.tsx +23 -0
- package/template/apps/asas-tanstack/src/components/ui/sheet.tsx +127 -0
- package/template/apps/asas-tanstack/src/components/ui/sidebar.tsx +716 -0
- package/template/apps/asas-tanstack/src/components/ui/skeleton.tsx +13 -0
- package/template/apps/asas-tanstack/src/components/ui/slider.tsx +13 -0
- package/template/apps/asas-tanstack/src/components/ui/spinner.tsx +11 -0
- package/template/apps/asas-tanstack/src/components/ui/switch.tsx +8 -0
- package/template/apps/asas-tanstack/src/components/ui/table.tsx +119 -0
- package/template/apps/asas-tanstack/src/components/ui/tabs.tsx +80 -0
- package/template/apps/asas-tanstack/src/components/ui/textarea.tsx +18 -0
- package/template/apps/asas-tanstack/src/components/ui/toggle-group.tsx +8 -0
- package/template/apps/asas-tanstack/src/components/ui/toggle.tsx +44 -0
- package/template/apps/asas-tanstack/src/components/ui/tooltip.tsx +68 -0
- package/template/apps/asas-tanstack/src/copy.ts +870 -0
- package/template/apps/asas-tanstack/src/hooks/use-mobile.ts +19 -0
- package/template/apps/asas-tanstack/src/lib/utils.ts +6 -0
- package/template/apps/asas-tanstack/src/links.ts +38 -0
- package/template/apps/asas-tanstack/src/locale-switch.tsx +26 -0
- package/template/apps/asas-tanstack/src/quiet-abort.ts +25 -0
- package/template/apps/asas-tanstack/src/redirect.ts +20 -0
- package/template/apps/asas-tanstack/src/request-headers.ts +11 -0
- package/template/apps/asas-tanstack/src/routeTree.gen.ts +382 -0
- package/template/apps/asas-tanstack/src/router.tsx +7 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/files.ts +4 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/screen.json +3883 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/types.ts +158 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.$.tsx +17 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.index.tsx +10 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.tsx +13 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal/files.ts +4 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal/screen.json +1800 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal/types.ts +92 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal.$.tsx +17 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal.index.tsx +10 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal.tsx +13 -0
- package/template/apps/asas-tanstack/src/routes/__root.tsx +99 -0
- package/template/apps/asas-tanstack/src/routes/api/$.ts +11 -0
- package/template/apps/asas-tanstack/src/routes/auth.accept-invitation.tsx +101 -0
- package/template/apps/asas-tanstack/src/routes/auth.forgot-password.tsx +94 -0
- package/template/apps/asas-tanstack/src/routes/auth.handoff.ts +39 -0
- package/template/apps/asas-tanstack/src/routes/auth.reset-password.tsx +109 -0
- package/template/apps/asas-tanstack/src/routes/auth.sign-in.tsx +86 -0
- package/template/apps/asas-tanstack/src/routes/auth.sign-up.tsx +122 -0
- package/template/apps/asas-tanstack/src/routes/index.tsx +142 -0
- package/template/apps/asas-tanstack/src/routes/unauthorized.tsx +122 -0
- package/template/apps/asas-tanstack/src/screen-form.tsx +34 -0
- package/template/apps/asas-tanstack/src/screen-list.tsx +139 -0
- package/template/apps/asas-tanstack/src/screen-names.ts +10 -0
- package/template/apps/asas-tanstack/src/screens.tsx +868 -0
- package/template/apps/asas-tanstack/src/server.ts +16 -0
- package/template/apps/asas-tanstack/src/slide.ts +22 -0
- package/template/apps/asas-tanstack/src/start.ts +57 -0
- package/template/apps/asas-tanstack/src/styles.css +159 -0
- package/template/apps/asas-tanstack/src/theme.ts +5 -0
- package/template/apps/asas-tanstack/tsconfig.json +19 -0
- package/template/apps/asas-tanstack/vite.config.ts +28 -0
- package/template/package.json +16 -0
- package/template/templates/asas/.env.example +16 -0
- package/template/templates/asas/components.json +22 -0
- package/template/templates/asas/mithaq.hints.json +25 -0
- package/template/templates/asas/package.json +38 -0
- package/template/templates/asas/src/asas.ts +93 -0
- package/template/templates/asas/src/audit-log/audit-log.ts +187 -0
- package/template/templates/asas/src/audit-log/sink.ts +89 -0
- package/template/templates/asas/src/audit-log/ui/activity.tsx +243 -0
- package/template/templates/asas/src/courses/courses.ts +612 -0
- package/template/templates/asas/src/courses/ui/archive-button.tsx +25 -0
- package/template/templates/asas/src/courses/ui/fee-cell.tsx +19 -0
- package/template/templates/asas/src/courses/ui/no-courses-on-offer.tsx +11 -0
- package/template/templates/asas/src/courses/ui/no-courses.tsx +13 -0
- package/template/templates/asas/src/courses/ui/on-offer.ts +6 -0
- package/template/templates/asas/src/courses/ui/status-cell.tsx +31 -0
- package/template/templates/asas/src/enrollments/enrollments.ts +137 -0
- package/template/templates/asas/src/enrollments/ui/session-cell.tsx +65 -0
- package/template/templates/asas/src/kayan/auth/user/account.access.ts +8 -0
- package/template/templates/asas/src/kayan/auth/user/account.ts +31 -0
- package/template/templates/asas/src/kayan/auth/user/index.ts +18 -0
- package/template/templates/asas/src/kayan/auth/user/invitation.access.ts +4 -0
- package/template/templates/asas/src/kayan/auth/user/invitation.ts +27 -0
- package/template/templates/asas/src/kayan/auth/user/member.access.ts +10 -0
- package/template/templates/asas/src/kayan/auth/user/member.screens.ts +37 -0
- package/template/templates/asas/src/kayan/auth/user/member.ts +25 -0
- package/template/templates/asas/src/kayan/auth/user/organization.access.ts +20 -0
- package/template/templates/asas/src/kayan/auth/user/organization.screens.ts +37 -0
- package/template/templates/asas/src/kayan/auth/user/organization.ts +24 -0
- package/template/templates/asas/src/kayan/auth/user/organizationRole.access.ts +8 -0
- package/template/templates/asas/src/kayan/auth/user/organizationRole.ts +25 -0
- package/template/templates/asas/src/kayan/auth/user/session.access.ts +8 -0
- package/template/templates/asas/src/kayan/auth/user/session.ts +28 -0
- package/template/templates/asas/src/kayan/auth/user/user.access.ts +29 -0
- package/template/templates/asas/src/kayan/auth/user/user.screens.ts +45 -0
- package/template/templates/asas/src/kayan/auth/user/user.ts +31 -0
- package/template/templates/asas/src/kayan/auth/user/verification.access.ts +8 -0
- package/template/templates/asas/src/kayan/auth/user/verification.ts +23 -0
- package/template/templates/asas/src/local-users/local-users.ts +143 -0
- package/template/templates/asas/src/media/media.ts +137 -0
- package/template/templates/asas/src/media/ui/preview.tsx +143 -0
- package/template/templates/asas/src/member-roles/member-roles.ts +207 -0
- package/template/templates/asas/src/messages/account.json +106 -0
- package/template/templates/asas/src/messages/auditLog.json +186 -0
- package/template/templates/asas/src/messages/courses.json +270 -0
- package/template/templates/asas/src/messages/enrollments.json +42 -0
- package/template/templates/asas/src/messages/invitation.json +66 -0
- package/template/templates/asas/src/messages/localUsers.json +42 -0
- package/template/templates/asas/src/messages/media.json +66 -0
- package/template/templates/asas/src/messages/member.json +42 -0
- package/template/templates/asas/src/messages/memberRoles.json +42 -0
- package/template/templates/asas/src/messages/mithaq.json +170 -0
- package/template/templates/asas/src/messages/organization.json +50 -0
- package/template/templates/asas/src/messages/organizationRole.json +50 -0
- package/template/templates/asas/src/messages/roles.json +58 -0
- package/template/templates/asas/src/messages/session.json +82 -0
- package/template/templates/asas/src/messages/sessions.json +106 -0
- package/template/templates/asas/src/messages/settings.json +66 -0
- package/template/templates/asas/src/messages/tags.json +38 -0
- package/template/templates/asas/src/messages/user.json +90 -0
- package/template/templates/asas/src/messages/verification.json +50 -0
- package/template/templates/asas/src/migrations/20260920080701_parched_tombstone/migration.sql +177 -0
- package/template/templates/asas/src/migrations/20260920080701_parched_tombstone/snapshot.json +2169 -0
- package/template/templates/asas/src/migrations/20260920230505_zippy_ben_urich/migration.sql +1 -0
- package/template/templates/asas/src/migrations/20260920230505_zippy_ben_urich/snapshot.json +2182 -0
- package/template/templates/asas/src/migrations/20260920231219_slim_retro_girl/migration.sql +20 -0
- package/template/templates/asas/src/migrations/20260920231219_slim_retro_girl/snapshot.json +2409 -0
- package/template/templates/asas/src/migrations/20260922082945_military_ironclad/migration.sql +15 -0
- package/template/templates/asas/src/migrations/20260922082945_military_ironclad/snapshot.json +2627 -0
- package/template/templates/asas/src/migrations/20260922112630_bitter_chat/migration.sql +5 -0
- package/template/templates/asas/src/migrations/20260922112630_bitter_chat/snapshot.json +2691 -0
- package/template/templates/asas/src/migrations/20260923235915_foamy_rachel_grey/migration.sql +8 -0
- package/template/templates/asas/src/migrations/20260923235915_foamy_rachel_grey/snapshot.json +2795 -0
- package/template/templates/asas/src/migrations/20260926145709_clean_the_initiative/migration.sql +3 -0
- package/template/templates/asas/src/migrations/20260926145709_clean_the_initiative/snapshot.json +2834 -0
- package/template/templates/asas/src/migrations/20260927212525_bent_prowler/migration.sql +9 -0
- package/template/templates/asas/src/migrations/20260927212525_bent_prowler/snapshot.json +2870 -0
- package/template/templates/asas/src/migrations/20260927223441_tranquil_bromley/migration.sql +1 -0
- package/template/templates/asas/src/migrations/20260927223441_tranquil_bromley/snapshot.json +2881 -0
- package/template/templates/asas/src/migrations/20260928030047_outgoing_wraith/migration.sql +15 -0
- package/template/templates/asas/src/migrations/20260928030047_outgoing_wraith/snapshot.json +3073 -0
- package/template/templates/asas/src/migrations/20260929024407_grey_morlun/migration.sql +21 -0
- package/template/templates/asas/src/migrations/20260929024407_grey_morlun/snapshot.json +3098 -0
- package/template/templates/asas/src/migrations/20260929130530_bored_jackal/migration.sql +21 -0
- package/template/templates/asas/src/migrations/20260929130530_bored_jackal/snapshot.json +3124 -0
- package/template/templates/asas/src/migrations/20260930102924_material_newton_destine/migration.sql +32 -0
- package/template/templates/asas/src/migrations/20260930102924_material_newton_destine/snapshot.json +3324 -0
- package/template/templates/asas/src/migrations/20261003022353_clumsy_taskmaster/migration.sql +2 -0
- package/template/templates/asas/src/migrations/20261003022353_clumsy_taskmaster/snapshot.json +3350 -0
- package/template/templates/asas/src/migrations/20261003143249_premium_red_shift/migration.sql +21 -0
- package/template/templates/asas/src/migrations/20261003143249_premium_red_shift/snapshot.json +3635 -0
- package/template/templates/asas/src/migrations/20261008215349_square_norrin_radd/migration.sql +8 -0
- package/template/templates/asas/src/migrations/20261008215349_square_norrin_radd/snapshot.json +3635 -0
- package/template/templates/asas/src/migrations/20261008230204_many_scarecrow/migration.sql +18 -0
- package/template/templates/asas/src/migrations/20261008230204_many_scarecrow/snapshot.json +3635 -0
- package/template/templates/asas/src/migrations/20261010005521_elite_giant_girl/migration.sql +18 -0
- package/template/templates/asas/src/migrations/20261010005521_elite_giant_girl/snapshot.json +3635 -0
- package/template/templates/asas/src/migrations/20261010120822_needy_thunderbolt/migration.sql +11 -0
- package/template/templates/asas/src/migrations/20261010120822_needy_thunderbolt/snapshot.json +3667 -0
- package/template/templates/asas/src/migrations/20261012000752_organization_ownership/migration.sql +48 -0
- package/template/templates/asas/src/migrations/20261012000752_organization_ownership/snapshot.json +3891 -0
- package/template/templates/asas/src/migrations/drizzle.config.ts +11 -0
- package/template/templates/asas/src/migrations/schema.ts +29 -0
- package/template/templates/asas/src/mithaq.ts +406 -0
- package/template/templates/asas/src/pages.ts +74 -0
- package/template/templates/asas/src/platform-http.ts +96 -0
- package/template/templates/asas/src/rich-text-nodes.ts +11 -0
- package/template/templates/asas/src/roles/grants.ts +47 -0
- package/template/templates/asas/src/roles/roles.ts +184 -0
- package/template/templates/asas/src/roles/ui/role-form.tsx +226 -0
- package/template/templates/asas/src/roles/ui/roles.tsx +166 -0
- package/template/templates/asas/src/roles.ts +16 -0
- package/template/templates/asas/src/seed.ts +325 -0
- package/template/templates/asas/src/sessions/sessions.ts +155 -0
- package/template/templates/asas/src/settings/settings.ts +69 -0
- package/template/templates/asas/src/statements.ts +11 -0
- package/template/templates/asas/src/steps/expected-updated-at.ts +41 -0
- package/template/templates/asas/src/storage/defaults.ts +156 -0
- package/template/templates/asas/src/storage/index.ts +95 -0
- package/template/templates/asas/src/storage/local.ts +143 -0
- package/template/templates/asas/src/storage/r2.ts +5 -0
- package/template/templates/asas/src/storage/s3.ts +91 -0
- package/template/templates/asas/src/storage/uploadthing.ts +59 -0
- package/template/templates/asas/src/tags/tags.ts +80 -0
- package/template/templates/asas/src/tenant-access.ts +221 -0
- package/template/templates/asas/src/tenant-roles.ts +262 -0
- package/template/templates/asas/src/ui/app-shell.tsx +260 -0
- package/template/templates/asas/src/ui/kpi-card.tsx +25 -0
- package/template/templates/asas/src/ui/record-page.tsx +50 -0
- package/template/templates/asas/src/ui/rich-text-nodes.tsx +15 -0
- package/template/templates/asas/src/ui/screen-nav.tsx +28 -0
- package/template/templates/asas/src/ui/session-labels.tsx +36 -0
- package/template/templates/asas/src/ui/table-page.tsx +9 -0
- package/template/templates/asas/src/ui/top-shell.tsx +60 -0
- package/template/templates/asas/src/ui-dashboard/dashboard.tsx +294 -0
- package/template/templates/asas/src/ui-dashboard/members.tsx +630 -0
- package/template/templates/asas/src/ui-dashboard/theme-provider.tsx +10 -0
- package/template/templates/asas/src/ui-dashboard/versions.tsx +432 -0
- package/template/templates/asas/src/ui-platform/home.tsx +63 -0
- package/template/templates/asas/src/ui-platform/organization-actions.tsx +114 -0
- package/template/templates/asas/src/ui-platform/record-page.tsx +30 -0
- package/template/templates/asas/src/ui-platform/role-cells.tsx +52 -0
- package/template/templates/asas/src/ui-platform/user-actions.tsx +251 -0
- package/template/templates/asas/src/ui-portal/home.tsx +77 -0
- package/template/templates/asas/src/ui-portal/page.tsx +11 -0
- package/template/templates/auth/package.json +14 -0
- package/template/templates/auth/src/index.ts +690 -0
- package/template/templates/auth/src/platform-roles.ts +87 -0
- package/README.md +0 -3
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// What the courses list draws when it holds no row, and what the board page
|
|
2
|
+
// draws by path: the same file under a registered name and an inline path.
|
|
3
|
+
import type { EmptyProps } from 'mithaq/admin'
|
|
4
|
+
import { useKayan, useLocale } from 'mithaq/admin/react'
|
|
5
|
+
|
|
6
|
+
export function NoCourses({ page }: EmptyProps) {
|
|
7
|
+
const { t } = useLocale()
|
|
8
|
+
return (
|
|
9
|
+
<p data-empty={`${useKayan().name}${page.path}`} className="text-muted-foreground text-sm">
|
|
10
|
+
{t('courses.none', 'No courses yet.')}
|
|
11
|
+
</p>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The courses the portal offers: every course not archived. The portal lists
|
|
2
|
+
// these alone, as its screen's `baseFilter` for the kayan, and its home counts
|
|
3
|
+
// these alone. Plain data in a file of its own, so the page that counts reads it
|
|
4
|
+
// without the kayan's declaration. A course with no `archived` value is not
|
|
5
|
+
// archived either.
|
|
6
|
+
export const onOffer = { or: [{ archived: { exists: false } }, { archived: { equals: false } }] }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// The list reads each row's latest draft, so a published course whose newest version is a draft
|
|
2
|
+
// and a never-published course both arrive as `draft`. The plain read finds only courses with a
|
|
3
|
+
// published copy, which tells the two apart.
|
|
4
|
+
import { use } from 'react'
|
|
5
|
+
import { useQuery } from '@tanstack/react-query'
|
|
6
|
+
import type { CellProps, Row } from 'mithaq/admin'
|
|
7
|
+
import { ListContext, useClient, useLocale, useScreen } from 'mithaq/admin/react'
|
|
8
|
+
import { Badge } from '@/components/ui/badge'
|
|
9
|
+
import { noValue } from '@/components/mithaq/controls.tsx'
|
|
10
|
+
import { coursesOf } from '../../enrollments/ui/session-cell.tsx'
|
|
11
|
+
|
|
12
|
+
const pills = {
|
|
13
|
+
draft: 'bg-muted text-muted-foreground',
|
|
14
|
+
published: 'bg-emerald-500/15 text-emerald-700 dark:text-emerald-400',
|
|
15
|
+
changed: 'bg-amber-500/15 text-amber-700 dark:text-amber-400',
|
|
16
|
+
}
|
|
17
|
+
const words = { draft: 'Draft', published: 'Published', changed: 'Changed' }
|
|
18
|
+
|
|
19
|
+
export function StatusCell({ value, row }: CellProps) {
|
|
20
|
+
const client = useClient()
|
|
21
|
+
const { t } = useLocale()
|
|
22
|
+
const { maxLimit } = useScreen()
|
|
23
|
+
// Sorted, so the same rows in another order are the same query.
|
|
24
|
+
const ids = [...new Set((use(ListContext)?.page.docs ?? [row]).flatMap((one: Row) => (one._status === 'draft' ? [String(one.id)] : [])))].sort()
|
|
25
|
+
const { data: published, isError } = useQuery({ queryKey: ['status-cell', ids], queryFn: () => coursesOf({ client, ids, maxLimit }), enabled: ids.length > 0, staleTime: 30_000 })
|
|
26
|
+
if (value !== 'draft' && value !== 'published') return noValue
|
|
27
|
+
// Loading until the read answers, and a draft where it failed, which is what the row itself holds.
|
|
28
|
+
if (value === 'draft' && !published && !isError) return t('admin.loading', 'Loading…')
|
|
29
|
+
const status = value === 'draft' && published?.has(String(row.id)) ? 'changed' : value
|
|
30
|
+
return <Badge data-status={status} className={pills[status]}>{t(`version.${status}`, words[status])}</Badge>
|
|
31
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { defaultFields, defaultOperations, folderOf } from 'mithaq'
|
|
2
|
+
import { defineKayan, organization } from '../mithaq.ts'
|
|
3
|
+
import { readsPast, user as actor } from '../tenant-access.ts'
|
|
4
|
+
import { activity, appPage, danger, platformList, platformRecord, topPage } from '../pages.ts'
|
|
5
|
+
import { user } from '../kayan/auth/user/user.ts'
|
|
6
|
+
import { sessions } from '../sessions/sessions.ts'
|
|
7
|
+
|
|
8
|
+
const ui = folderOf(new URL('./ui/', import.meta.url))
|
|
9
|
+
const sessionCell = ui('session-cell.tsx#SessionCell')
|
|
10
|
+
const enrollment = [{ section: 'enrollment', label: { en: 'Enrollment', ar: 'التسجيل' }, fields: ['session', 'student'] }] as const
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The seats an actor reads, edits and deletes, inside its active organization:
|
|
14
|
+
* every seat of its organization for an actor holding
|
|
15
|
+
* `enrollments.others` and for platform staff, its own seats alone for anyone
|
|
16
|
+
* else. That is the student, whose enrollment keys would otherwise reach every
|
|
17
|
+
* other student's seat, and a custom role a tenant writes without the key.
|
|
18
|
+
* Another's seat answers 404, as a missing row does.
|
|
19
|
+
*
|
|
20
|
+
* The id is read off the actor as an own key, and an actor carrying none reaches
|
|
21
|
+
* no seat. Every read answers from it, a session's join of its enrollments
|
|
22
|
+
* included.
|
|
23
|
+
*/
|
|
24
|
+
const ownSeats = ({ user }: { user: unknown }) => {
|
|
25
|
+
if (readsPast(user, 'enrollments.others')) return true
|
|
26
|
+
const actor = user && typeof user === 'object' ? (user as Record<string, unknown>) : undefined
|
|
27
|
+
const id = actor && Object.hasOwn(actor, 'id') ? actor.id : undefined
|
|
28
|
+
return typeof id === 'string' && id ? { student: { equals: id } } : false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const enrollments = defineKayan({
|
|
32
|
+
name: 'enrollments',
|
|
33
|
+
access: { read: ownSeats, update: ownSeats, delete: ownSeats },
|
|
34
|
+
fields: {
|
|
35
|
+
...defaultFields,
|
|
36
|
+
organization,
|
|
37
|
+
// Read as the session's course and its start on every screen, where a
|
|
38
|
+
// session alone is titled by its start.
|
|
39
|
+
//
|
|
40
|
+
// Both are required, so no write makes a seat for nobody or in nothing, and
|
|
41
|
+
// a session or a person that holds one is not deleted under it: a required
|
|
42
|
+
// relationship restricts.
|
|
43
|
+
session: {
|
|
44
|
+
kind: 'relationship', label: { en: 'Session', ar: 'الحصة' }, relationTo: () => sessions,
|
|
45
|
+
rules: { required: true },
|
|
46
|
+
targets: { dashboard: { cell: sessionCell }, portal: { cell: sessionCell }, platform: { cell: sessionCell } },
|
|
47
|
+
},
|
|
48
|
+
// A create that names no student enrolls the person making it: the runner's
|
|
49
|
+
// defaults step fills it from the actor, and a field with a default is one a
|
|
50
|
+
// create may leave out, at the HTTP boundary too. A role without the column's
|
|
51
|
+
// key is refused the filled value, so today only the instructor's and the
|
|
52
|
+
// administrator's API create fills it; the student reads its own seats and
|
|
53
|
+
// creates none.
|
|
54
|
+
student: {
|
|
55
|
+
kind: 'relationship', label: { en: 'Student', ar: 'الطالب' }, relationTo: () => user,
|
|
56
|
+
rules: { required: true, defaultValue: actor.id },
|
|
57
|
+
},
|
|
58
|
+
/**
|
|
59
|
+
* The field the language names as its soft-delete mark, so a student who
|
|
60
|
+
* leaves a session is a row this kayan keeps rather than one it removes: an
|
|
61
|
+
* enrollment is the record that the student was there. It is the one kayan
|
|
62
|
+
* of this template that takes the mark. Nothing points at an enrollment, so
|
|
63
|
+
* no child outlives it, and nothing refers to it through a `restrict`
|
|
64
|
+
* foreign key that a delete would have to release. Nobody supplies the
|
|
65
|
+
* mark: the runner writes it at the delete.
|
|
66
|
+
*
|
|
67
|
+
* `session` and `student` are required, so each restricts, and a session or
|
|
68
|
+
* a person a marked enrollment still names is not deleted under it.
|
|
69
|
+
*/
|
|
70
|
+
deletedAt: {
|
|
71
|
+
kind: 'datetime',
|
|
72
|
+
label: { en: 'Deleted at', ar: 'تاريخ الحذف' },
|
|
73
|
+
rules: { providedBy: { create: 'never', update: 'never' } },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
targets: {
|
|
77
|
+
// Every organization's rows on the platform, read-only, and each row's
|
|
78
|
+
// record, which only reads. A record is titled by its student, a person the
|
|
79
|
+
// platform reads, where a session is titled only by its start.
|
|
80
|
+
platform: {
|
|
81
|
+
useAsTitle: 'student',
|
|
82
|
+
pages: {
|
|
83
|
+
...platformList('/enrollments', { columns: ['session', 'student', 'organization', 'createdAt'], filters: ['organization', 'session', 'student'], sort: '-createdAt' }),
|
|
84
|
+
...platformRecord({
|
|
85
|
+
path: '/enrollments/:id',
|
|
86
|
+
main: [{ section: 'enrollment', label: { en: 'Enrollment', ar: 'التسجيل' }, fields: ['session', 'student', 'deletedAt'] }],
|
|
87
|
+
}),
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
service: { operations: defaultOperations },
|
|
91
|
+
// Both screens list enrollments; the portal's create opens as a drawer over
|
|
92
|
+
// its list. Titled by the student on the dashboard, as on the platform, so a
|
|
93
|
+
// tab and a heading tell one seat from another; on the portal, where the
|
|
94
|
+
// student is most often the viewer, by the session.
|
|
95
|
+
dashboard: {
|
|
96
|
+
useAsTitle: 'student',
|
|
97
|
+
description: {
|
|
98
|
+
en: 'Which student holds a seat in which session.',
|
|
99
|
+
ar: 'أي طالب يشغل مقعدا في أي حصة.',
|
|
100
|
+
},
|
|
101
|
+
pages: {
|
|
102
|
+
// The same table every kayan's list is. Searched by the student's name,
|
|
103
|
+
// the one column of a person a tenant reads, and filtered by session,
|
|
104
|
+
// student and when the student enrolled, as the course list is.
|
|
105
|
+
list: { path: '/enrollments', page: appPage([{ layout: 'table', main: [{ list: {
|
|
106
|
+
columns: ['session', 'student', 'createdAt'],
|
|
107
|
+
filters: ['session', 'student', 'createdAt'],
|
|
108
|
+
search: ['student.name'],
|
|
109
|
+
} }] }]) },
|
|
110
|
+
create: { path: '/enrollments/new', page: appPage([{ layout: 'record', main: [{ form: enrollment }] }]) },
|
|
111
|
+
// The editor layout, and the one timestamp worth reading here, named and
|
|
112
|
+
// read-only: when the student enrolled, beside the organization it
|
|
113
|
+
// belongs to, read by its name.
|
|
114
|
+
edit: {
|
|
115
|
+
path: '/enrollments/:id',
|
|
116
|
+
page: appPage([{
|
|
117
|
+
layout: 'record',
|
|
118
|
+
main: [{ form: enrollment }],
|
|
119
|
+
side: [{ section: 'record', label: { en: 'Record', ar: 'السجل' }, fields: [{ field: 'organization', readOnly: true }, { field: 'createdAt', readOnly: true }] }, activity],
|
|
120
|
+
danger: danger(),
|
|
121
|
+
}]),
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
portal: {
|
|
126
|
+
useAsTitle: 'session',
|
|
127
|
+
pages: {
|
|
128
|
+
list: { path: '/enrollments', page: topPage([{ layout: 'page', main: [{ list: { columns: ['session', 'createdAt'] } }] }]) },
|
|
129
|
+
// The student too, as the dashboard draws it, so an instructor picks
|
|
130
|
+
// whom it enrolls. A role without `enrollments.fields.student` is not
|
|
131
|
+
// projected the field and is drawn the session alone.
|
|
132
|
+
create: { path: '/enrollments/new', mode: 'drawer', page: topPage([{ layout: 'page', main: [{ form: ['session', 'student'] }] }]) },
|
|
133
|
+
edit: { path: '/enrollments/:id', page: topPage([{ layout: 'page', main: [{ form: ['session'] }] }]) },
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// What an enrollment's session column draws: the session's course by its title, then when it starts, which tells two sessions of one course apart.
|
|
2
|
+
// A session is titled by its start and a title is one field, so the course is a second read.
|
|
3
|
+
//
|
|
4
|
+
// The list's own `useRelationshipTitles` keeps only each session's title, the start as text, and not the course it points at, so this cell reads them again:
|
|
5
|
+
// one read of every session the page's rows point at and one of their courses, shared by every cell of the page through the query client rather than two per cell,
|
|
6
|
+
// and read again once it is as old as the list's own pages. A read that failed is not kept, so the next one tries again.
|
|
7
|
+
// Each read is the viewer's own, so a session or a course the viewer may not read is left out, and its cell keeps what it can say.
|
|
8
|
+
import { use } from 'react'
|
|
9
|
+
import { useQuery } from '@tanstack/react-query'
|
|
10
|
+
import type { CellProps, FetchClient, Row } from 'mithaq/admin'
|
|
11
|
+
import { ListContext, useClient, useLocale, useScreen } from 'mithaq/admin/react'
|
|
12
|
+
import { formatDate, noValue, refusalKeyOf, type FoundPage } from '@/components/mithaq/controls.tsx'
|
|
13
|
+
import { findByIds, nameOfRow, titleFieldOf } from '@/components/mithaq/relationship-control.tsx'
|
|
14
|
+
|
|
15
|
+
/** A refusal is the viewer's access answering, so it reads as no rows; anything else is a failure, and throws for the query to try again. */
|
|
16
|
+
const unread = (thrown: unknown): never[] => {
|
|
17
|
+
if (refusalKeyOf(thrown)) return []
|
|
18
|
+
throw thrown
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const of = (client: FetchClient, kayan: string) => ({ find: (input: Record<string, unknown>) => client({ kayan, operation: 'find', input }) as Promise<FoundPage> })
|
|
22
|
+
|
|
23
|
+
/** Some courses by id, as the viewer reads them, which the session picker's label reads too. */
|
|
24
|
+
export const coursesOf = async ({ client, ids, maxLimit }: { client: FetchClient; ids: readonly string[]; maxLimit: number }): Promise<ReadonlyMap<string, Row>> => {
|
|
25
|
+
const courses = ids.length ? await findByIds({ reads: of(client, 'courses'), ids, maxLimit }).catch(unread) : []
|
|
26
|
+
return new Map(courses.map((one) => [String(one.id), one]))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const sessionsOf = async ({ client, ids, maxLimit }: { client: FetchClient; ids: readonly string[]; maxLimit: number }): Promise<ReadonlyMap<string, Row>> => {
|
|
30
|
+
const sessions = await findByIds({ reads: of(client, 'sessions'), ids, maxLimit }).catch(unread)
|
|
31
|
+
const courseIds = [...new Set(sessions.flatMap((one) => (typeof one.course === 'string' ? [one.course] : [])))]
|
|
32
|
+
const course = await coursesOf({ client, ids: courseIds, maxLimit })
|
|
33
|
+
return new Map(sessions.map((one) => [String(one.id), { ...one, course: course.get(String(one.course)) }]))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function SessionCell({ field, value, row }: CellProps) {
|
|
37
|
+
const client = useClient()
|
|
38
|
+
const { locale, t } = useLocale()
|
|
39
|
+
const { kayan: { courses }, maxLimit } = useScreen()
|
|
40
|
+
// A list's page, or the one row a record page draws.
|
|
41
|
+
// Sorted, so the same rows in another order are the same query.
|
|
42
|
+
const ids = [...new Set((use(ListContext)?.page.docs ?? [row]).flatMap((one: Row) => (typeof one[field.name] === 'string' ? [one[field.name] as string] : [])))].sort()
|
|
43
|
+
const { data: sessions, isError } = useQuery({ queryKey: ['session-cell', ids], queryFn: () => sessionsOf({ client, ids, maxLimit }), staleTime: 30_000 })
|
|
44
|
+
if (typeof value !== 'string') return noValue
|
|
45
|
+
const session = sessions?.get(value)
|
|
46
|
+
const when = formatDate({ value: session?.startsAt, locale })
|
|
47
|
+
const course = session?.course ? nameOfRow({ row: session.course as Row, title: titleFieldOf(courses), locale }) : undefined
|
|
48
|
+
// Never the id the column holds, as the relationship cell draws none:
|
|
49
|
+
// loading until the read answers, the server's render among it, a dash
|
|
50
|
+
// where it failed, and a row the viewer cannot see for a session it may
|
|
51
|
+
// not read or one of which it may read neither the course nor the start.
|
|
52
|
+
if (!when && !course) {
|
|
53
|
+
if (sessions) return t('admin.cannotSee', 'You can\'t see this')
|
|
54
|
+
return isError ? '—' : t('admin.loading', 'Loading…')
|
|
55
|
+
}
|
|
56
|
+
// Two lines, the course over its start, so a narrow column wraps each
|
|
57
|
+
// whole rather than one into the other.
|
|
58
|
+
return (
|
|
59
|
+
<span className="grid">
|
|
60
|
+
{course ? <span className="font-medium">{course}</span> : null}
|
|
61
|
+
{/* One unbroken run, so a narrow column breaks between the course and the date and never inside the date. */}
|
|
62
|
+
{when ? <span className="text-muted-foreground text-xs"><bdi className="whitespace-nowrap">{when}</bdi></span> : null}
|
|
63
|
+
</span>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// This project's access for the generated `account` kayan, which `mithaq generate
|
|
2
|
+
// auth` imports into `./account.ts` because this file sits beside it.
|
|
3
|
+
//
|
|
4
|
+
// Better Auth alone writes and reads this table, through its own adapter, so
|
|
5
|
+
// every managed operation on it is refused to every actor. Written out rather
|
|
6
|
+
// than left to the administrator's default, which a super admin passes in the
|
|
7
|
+
// platform app.
|
|
8
|
+
export { providerOnly as access } from '../../../tenant-access.ts'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Written by `mithaq generate auth` from the Better Auth options generate.providers names. Do not edit.
|
|
2
|
+
// This kayan's access and what each screen draws of it are the project's:
|
|
3
|
+
// `account.access.ts` exporting `access` and `account.screens.ts` exporting
|
|
4
|
+
// `screens`, beside this file, are imported here on the next run. A kayan
|
|
5
|
+
// built under this one's name elsewhere is refused by every relationship to it.
|
|
6
|
+
import { defaultFields, defaultOperations } from 'mithaq'
|
|
7
|
+
import { defineKayan } from '../../../mithaq.ts'
|
|
8
|
+
import { user } from './user.ts'
|
|
9
|
+
import { access } from './account.access.ts'
|
|
10
|
+
|
|
11
|
+
export const account = defineKayan({
|
|
12
|
+
name: 'account',
|
|
13
|
+
targets: { service: { operations: defaultOperations } },
|
|
14
|
+
access,
|
|
15
|
+
fields: {
|
|
16
|
+
...defaultFields,
|
|
17
|
+
id: { kind: 'text' },
|
|
18
|
+
accountId: { kind: 'text', rules: { required: true } },
|
|
19
|
+
providerId: { kind: 'text', rules: { required: true } },
|
|
20
|
+
userId: { kind: 'relationship', relationTo: () => user, dbName: 'user_id', rules: { required: true, onDelete: 'cascade' } },
|
|
21
|
+
accessToken: { kind: 'text' },
|
|
22
|
+
refreshToken: { kind: 'text' },
|
|
23
|
+
idToken: { kind: 'text' },
|
|
24
|
+
accessTokenExpiresAt: { kind: 'datetime' },
|
|
25
|
+
refreshTokenExpiresAt: { kind: 'datetime' },
|
|
26
|
+
scope: { kind: 'text' },
|
|
27
|
+
password: { kind: 'text' },
|
|
28
|
+
createdAt: { kind: 'datetime', rules: { required: true } },
|
|
29
|
+
updatedAt: { kind: 'datetime', rules: { required: true, nowOnUpdate: true, providedBy: { update: 'application' } } },
|
|
30
|
+
},
|
|
31
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Written by `mithaq generate auth` from the Better Auth options generate.providers names. Do not edit.
|
|
2
|
+
// One key per plugin that brought tables of its own, and `core` for the tables
|
|
3
|
+
// Better Auth ships without one, so a project spreads these lists into
|
|
4
|
+
// `buildAsas` instead of naming every file. A plugin that leaves takes its key
|
|
5
|
+
// with it, and `mithaq generate auth --check` reports the drift.
|
|
6
|
+
import { account } from './account.ts'
|
|
7
|
+
import { invitation } from './invitation.ts'
|
|
8
|
+
import { member } from './member.ts'
|
|
9
|
+
import { organization } from './organization.ts'
|
|
10
|
+
import { organizationRole } from './organizationRole.ts'
|
|
11
|
+
import { session } from './session.ts'
|
|
12
|
+
import { user } from './user.ts'
|
|
13
|
+
import { verification } from './verification.ts'
|
|
14
|
+
|
|
15
|
+
export const betterAuth = {
|
|
16
|
+
core: [user, session, account, verification],
|
|
17
|
+
organization: [organization, organizationRole, member, invitation],
|
|
18
|
+
} as const
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Better Auth alone writes and reads this table, through its own adapter, so every managed
|
|
2
|
+
// operation on it is refused to every actor. Written out rather than left to the administrator's
|
|
3
|
+
// default, which a super admin passes.
|
|
4
|
+
export { providerOnly as access } from '../../../tenant-access.ts'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Written by `mithaq generate auth` from the Better Auth options generate.providers names. Do not edit.
|
|
2
|
+
// This kayan's access and what each screen draws of it are the project's:
|
|
3
|
+
// `invitation.access.ts` exporting `access` and `invitation.screens.ts` exporting
|
|
4
|
+
// `screens`, beside this file, are imported here on the next run. A kayan
|
|
5
|
+
// built under this one's name elsewhere is refused by every relationship to it.
|
|
6
|
+
import { defaultFields, defaultOperations } from 'mithaq'
|
|
7
|
+
import { defineKayan } from '../../../mithaq.ts'
|
|
8
|
+
import { organization } from './organization.ts'
|
|
9
|
+
import { user } from './user.ts'
|
|
10
|
+
import { access } from './invitation.access.ts'
|
|
11
|
+
|
|
12
|
+
export const invitation = defineKayan({
|
|
13
|
+
name: 'invitation',
|
|
14
|
+
targets: { service: { operations: defaultOperations } },
|
|
15
|
+
access,
|
|
16
|
+
fields: {
|
|
17
|
+
...defaultFields,
|
|
18
|
+
id: { kind: 'text' },
|
|
19
|
+
organizationId: { kind: 'relationship', relationTo: () => organization, dbName: 'organization_id', rules: { required: true, onDelete: 'cascade' } },
|
|
20
|
+
email: { kind: 'text', rules: { required: true } },
|
|
21
|
+
role: { kind: 'text' },
|
|
22
|
+
status: { kind: 'text', rules: { required: true } },
|
|
23
|
+
expiresAt: { kind: 'datetime', rules: { required: true } },
|
|
24
|
+
createdAt: { kind: 'datetime', rules: { required: true } },
|
|
25
|
+
inviterId: { kind: 'relationship', relationTo: () => user, dbName: 'inviter_id', rules: { required: true, onDelete: 'cascade' } },
|
|
26
|
+
},
|
|
27
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// This project's access for the generated `member` kayan, which `mithaq
|
|
2
|
+
// generate auth` imports into `./member.ts` because this file sits beside it. A
|
|
3
|
+
// platform role reads every organization's members in the platform app, by the
|
|
4
|
+
// keys it holds, and no one writes through it. There is no `'*'` row, so a
|
|
5
|
+
// tenant's actor takes no row here and reads nothing: a tenant lists its
|
|
6
|
+
// members through the provider's own `list-members`.
|
|
7
|
+
import { rows } from '../../../mithaq.ts'
|
|
8
|
+
import { platformReads } from '../../../tenant-access.ts'
|
|
9
|
+
|
|
10
|
+
export const access = rows(platformReads)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// What the platform draws of the generated `member` kayan, which `mithaq
|
|
2
|
+
// generate auth` imports into `./member.ts` because this file sits beside it:
|
|
3
|
+
// every organization's members, each by the person's and the organization's name,
|
|
4
|
+
// filtered to one organization from that organization's row.
|
|
5
|
+
import { platformUi } from '../../../mithaq.ts'
|
|
6
|
+
import { appPage } from '../../../pages.ts'
|
|
7
|
+
|
|
8
|
+
/** The provider's governance role by the project's word for it, never its slug. */
|
|
9
|
+
const role = { field: 'role', cell: platformUi('role-cells.tsx#MembershipRole') } as const
|
|
10
|
+
|
|
11
|
+
export const screens = {
|
|
12
|
+
platform: {
|
|
13
|
+
// A membership's record is titled by its person.
|
|
14
|
+
useAsTitle: 'userId',
|
|
15
|
+
pages: {
|
|
16
|
+
list: {
|
|
17
|
+
path: '/members',
|
|
18
|
+
page: appPage([{ layout: 'table', main: [{ list: {
|
|
19
|
+
columns: ['userId', 'organizationId', role, 'createdAt'],
|
|
20
|
+
filters: ['organizationId', 'userId', 'role', 'createdAt'],
|
|
21
|
+
// A membership is looked for by its person.
|
|
22
|
+
search: ['userId.name', 'userId.email'],
|
|
23
|
+
sort: '-createdAt',
|
|
24
|
+
} }] }]),
|
|
25
|
+
},
|
|
26
|
+
// A membership's record, which a row opens and which only reads.
|
|
27
|
+
view: {
|
|
28
|
+
path: '/members/:id',
|
|
29
|
+
page: appPage([{
|
|
30
|
+
layout: 'record',
|
|
31
|
+
main: [{ section: 'membership', label: { en: 'Membership', ar: 'العضوية' }, fields: ['userId', 'organizationId', role] }],
|
|
32
|
+
side: [{ section: 'record', label: { en: 'Record', ar: 'السجل' }, fields: ['createdAt', 'updatedAt'] }],
|
|
33
|
+
}]),
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
} as const
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Written by `mithaq generate auth` from the Better Auth options generate.providers names. Do not edit.
|
|
2
|
+
// This kayan's access and what each screen draws of it are the project's:
|
|
3
|
+
// `member.access.ts` exporting `access` and `member.screens.ts` exporting
|
|
4
|
+
// `screens`, beside this file, are imported here on the next run. A kayan
|
|
5
|
+
// built under this one's name elsewhere is refused by every relationship to it.
|
|
6
|
+
import { defaultFields, defaultOperations } from 'mithaq'
|
|
7
|
+
import { defineKayan } from '../../../mithaq.ts'
|
|
8
|
+
import { organization } from './organization.ts'
|
|
9
|
+
import { user } from './user.ts'
|
|
10
|
+
import { access } from './member.access.ts'
|
|
11
|
+
import { screens } from './member.screens.ts'
|
|
12
|
+
|
|
13
|
+
export const member = defineKayan({
|
|
14
|
+
name: 'member',
|
|
15
|
+
targets: { ...screens, service: { operations: defaultOperations } },
|
|
16
|
+
access,
|
|
17
|
+
fields: {
|
|
18
|
+
...defaultFields,
|
|
19
|
+
id: { kind: 'text' },
|
|
20
|
+
organizationId: { kind: 'relationship', relationTo: () => organization, dbName: 'organization_id', rules: { required: true, onDelete: 'cascade' } },
|
|
21
|
+
userId: { kind: 'relationship', relationTo: () => user, dbName: 'user_id', rules: { required: true, onDelete: 'cascade' } },
|
|
22
|
+
role: { kind: 'text', rules: { required: true } },
|
|
23
|
+
createdAt: { kind: 'datetime', rules: { required: true } },
|
|
24
|
+
},
|
|
25
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// This project's access for the generated `organization` kayan, which `mithaq
|
|
2
|
+
// generate auth` imports into `./organization.ts` because this file sits
|
|
3
|
+
// beside it. The table is written here, beside the kayan it is for, and never
|
|
4
|
+
// in a file every kayan shares.
|
|
5
|
+
//
|
|
6
|
+
// An organization is written through no managed operation: Better Auth writes
|
|
7
|
+
// it through its own adapter, and the platform renames one through its own route.
|
|
8
|
+
// In the tenant app a member reads its active organization, the one every tenant-owned
|
|
9
|
+
// row belongs to, and by its id and its name alone, so a tenant
|
|
10
|
+
// relationship shows a name on the dashboard. In the platform app a
|
|
11
|
+
// platform role reads every organization, each column where it holds that
|
|
12
|
+
// column's key. The id and the name are held by `namesInTenant` for the
|
|
13
|
+
// reason `./user.access.ts` gives.
|
|
14
|
+
import { rows } from '../../../mithaq.ts'
|
|
15
|
+
import { namesInTenant, platformReads, user } from '../../../tenant-access.ts'
|
|
16
|
+
|
|
17
|
+
export const access = rows(
|
|
18
|
+
{ '*': { read: { id: { equals: user.activeOrganizationId } } }, ...platformReads },
|
|
19
|
+
{ holds: namesInTenant('organization') },
|
|
20
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// What each screen draws of the generated `organization` kayan, which `mithaq
|
|
2
|
+
// generate auth` imports into `./organization.ts` because this file sits beside
|
|
3
|
+
// it, spreading it into the kayan's `targets` after the generated titles of the
|
|
4
|
+
// screens it does not name, so the platform's block here is that screen's whole
|
|
5
|
+
// block, its title included, and the dashboard keeps the generated one. The
|
|
6
|
+
// platform lists every organization with the registry's list and renames it or
|
|
7
|
+
// opens its members from the slug column.
|
|
8
|
+
import { platformUi } from '../../../mithaq.ts'
|
|
9
|
+
import { appPage } from '../../../pages.ts'
|
|
10
|
+
|
|
11
|
+
export const screens = {
|
|
12
|
+
platform: {
|
|
13
|
+
useAsTitle: 'name',
|
|
14
|
+
pages: {
|
|
15
|
+
list: {
|
|
16
|
+
path: '/organizations',
|
|
17
|
+
page: appPage([{ layout: 'table', main: [{ list: {
|
|
18
|
+
columns: ['name', { field: 'slug', cell: platformUi('organization-actions.tsx#OrganizationActions') }, 'createdAt'],
|
|
19
|
+
filters: ['name', 'slug', 'createdAt'],
|
|
20
|
+
search: ['name', 'slug'],
|
|
21
|
+
sort: 'name',
|
|
22
|
+
} }] }]),
|
|
23
|
+
},
|
|
24
|
+
// An organization's record, which a row opens and which only reads; its
|
|
25
|
+
// rename and its members stay on the list's slug column.
|
|
26
|
+
view: {
|
|
27
|
+
path: '/organizations/:id',
|
|
28
|
+
page: appPage([{
|
|
29
|
+
layout: 'record',
|
|
30
|
+
main: [{ section: 'organization', label: { en: 'Organization', ar: 'المؤسسة' }, fields: ['name', 'slug', 'logo', 'metadata'] }],
|
|
31
|
+
// No id: the record is read by its name.
|
|
32
|
+
side: [{ section: 'record', label: { en: 'Record', ar: 'السجل' }, fields: ['createdAt', 'updatedAt'] }],
|
|
33
|
+
}]),
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
} as const
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Written by `mithaq generate auth` from the Better Auth options generate.providers names. Do not edit.
|
|
2
|
+
// This kayan's access and what each screen draws of it are the project's:
|
|
3
|
+
// `organization.access.ts` exporting `access` and `organization.screens.ts` exporting
|
|
4
|
+
// `screens`, beside this file, are imported here on the next run. A kayan
|
|
5
|
+
// built under this one's name elsewhere is refused by every relationship to it.
|
|
6
|
+
import { defaultFields, defaultOperations } from 'mithaq'
|
|
7
|
+
import { defineKayan } from '../../../mithaq.ts'
|
|
8
|
+
import { access } from './organization.access.ts'
|
|
9
|
+
import { screens } from './organization.screens.ts'
|
|
10
|
+
|
|
11
|
+
export const organization = defineKayan({
|
|
12
|
+
name: 'organization',
|
|
13
|
+
targets: { dashboard: { useAsTitle: 'name' }, ...screens, service: { operations: defaultOperations } },
|
|
14
|
+
access,
|
|
15
|
+
fields: {
|
|
16
|
+
...defaultFields,
|
|
17
|
+
id: { kind: 'text' },
|
|
18
|
+
name: { kind: 'text', rules: { required: true } },
|
|
19
|
+
slug: { kind: 'text', rules: { required: true, unique: true } },
|
|
20
|
+
logo: { kind: 'text' },
|
|
21
|
+
createdAt: { kind: 'datetime', rules: { required: true } },
|
|
22
|
+
metadata: { kind: 'text' },
|
|
23
|
+
},
|
|
24
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// This project's access for the generated `organizationRole` kayan, which `mithaq generate
|
|
2
|
+
// auth` imports into `./organizationRole.ts` because this file sits beside it.
|
|
3
|
+
//
|
|
4
|
+
// Better Auth alone writes and reads this table, through its own adapter,
|
|
5
|
+
// so every managed operation on it is refused to every actor. Written out
|
|
6
|
+
// rather than left to the administrator's default, which a super admin
|
|
7
|
+
// passes in the platform app.
|
|
8
|
+
export { providerOnly as access } from '../../../tenant-access.ts'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Written by `mithaq generate auth` from the Better Auth options generate.providers names. Do not edit.
|
|
2
|
+
// This kayan's access and what each screen draws of it are the project's:
|
|
3
|
+
// `organizationRole.access.ts` exporting `access` and `organizationRole.screens.ts` exporting
|
|
4
|
+
// `screens`, beside this file, are imported here on the next run. A kayan
|
|
5
|
+
// built under this one's name elsewhere is refused by every relationship to it.
|
|
6
|
+
import { defaultFields, defaultOperations } from 'mithaq'
|
|
7
|
+
import { defineKayan } from '../../../mithaq.ts'
|
|
8
|
+
import { organization } from './organization.ts'
|
|
9
|
+
import { access } from './organizationRole.access.ts'
|
|
10
|
+
|
|
11
|
+
export const organizationRole = defineKayan({
|
|
12
|
+
name: 'organizationRole',
|
|
13
|
+
dbName: 'organization_role',
|
|
14
|
+
targets: { service: { operations: defaultOperations } },
|
|
15
|
+
access,
|
|
16
|
+
fields: {
|
|
17
|
+
...defaultFields,
|
|
18
|
+
id: { kind: 'text' },
|
|
19
|
+
organizationId: { kind: 'relationship', relationTo: () => organization, dbName: 'organization_id', rules: { required: true, onDelete: 'cascade' } },
|
|
20
|
+
role: { kind: 'text', rules: { required: true } },
|
|
21
|
+
permission: { kind: 'text', rules: { required: true } },
|
|
22
|
+
createdAt: { kind: 'datetime', rules: { required: true } },
|
|
23
|
+
updatedAt: { kind: 'datetime', rules: { nowOnUpdate: true, providedBy: { update: 'application' } } },
|
|
24
|
+
},
|
|
25
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// This project's access for the generated `session` kayan, which `mithaq generate
|
|
2
|
+
// auth` imports into `./session.ts` because this file sits beside it.
|
|
3
|
+
//
|
|
4
|
+
// Better Auth alone writes and reads this table, through its own adapter, so
|
|
5
|
+
// every managed operation on it is refused to every actor. Written out rather
|
|
6
|
+
// than left to the administrator's default, which a super admin passes in the
|
|
7
|
+
// platform app.
|
|
8
|
+
export { providerOnly as access } from '../../../tenant-access.ts'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Written by `mithaq generate auth` from the Better Auth options generate.providers names. Do not edit.
|
|
2
|
+
// This kayan's access and what each screen draws of it are the project's:
|
|
3
|
+
// `session.access.ts` exporting `access` and `session.screens.ts` exporting
|
|
4
|
+
// `screens`, beside this file, are imported here on the next run. A kayan
|
|
5
|
+
// built under this one's name elsewhere is refused by every relationship to it.
|
|
6
|
+
import { defaultFields, defaultOperations } from 'mithaq'
|
|
7
|
+
import { defineKayan } from '../../../mithaq.ts'
|
|
8
|
+
import { user } from './user.ts'
|
|
9
|
+
import { access } from './session.access.ts'
|
|
10
|
+
|
|
11
|
+
export const session = defineKayan({
|
|
12
|
+
name: 'session',
|
|
13
|
+
targets: { service: { operations: defaultOperations } },
|
|
14
|
+
access,
|
|
15
|
+
fields: {
|
|
16
|
+
...defaultFields,
|
|
17
|
+
id: { kind: 'text' },
|
|
18
|
+
expiresAt: { kind: 'datetime', rules: { required: true } },
|
|
19
|
+
token: { kind: 'text', rules: { required: true, unique: true } },
|
|
20
|
+
createdAt: { kind: 'datetime', rules: { required: true } },
|
|
21
|
+
updatedAt: { kind: 'datetime', rules: { required: true, nowOnUpdate: true, providedBy: { update: 'application' } } },
|
|
22
|
+
ipAddress: { kind: 'text' },
|
|
23
|
+
userAgent: { kind: 'text' },
|
|
24
|
+
userId: { kind: 'relationship', relationTo: () => user, dbName: 'user_id', rules: { required: true, onDelete: 'cascade' } },
|
|
25
|
+
impersonatedBy: { kind: 'text' },
|
|
26
|
+
activeOrganizationId: { kind: 'text' },
|
|
27
|
+
},
|
|
28
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// This project's access for the generated `user` kayan, which `mithaq generate
|
|
2
|
+
// auth` imports into `./user.ts` because this file sits beside it. The table is
|
|
3
|
+
// written here, beside the kayan it is for, and never in a file every kayan
|
|
4
|
+
// shares.
|
|
5
|
+
//
|
|
6
|
+
// A person is written through no managed operation: Better Auth writes it
|
|
7
|
+
// through its own adapter. The two sides of the platform line read it
|
|
8
|
+
// differently, and neither row reaches the other's side. In the tenant app a
|
|
9
|
+
// member reads its active organization's members by their id and their name
|
|
10
|
+
// alone, so a relationship to a person shows a name in the dashboard's cell and
|
|
11
|
+
// picker, and no email, role or ban reaches a tenant. In the platform app a
|
|
12
|
+
// platform role reads every person, each column where it holds that column's key.
|
|
13
|
+
//
|
|
14
|
+
// The id and the name are held by `namesInTenant` rather than by a field list
|
|
15
|
+
// on the `'*'` row, because every actor with a role takes the `'*'` row, the
|
|
16
|
+
// platform's included, and a column one row's list leaves out is hidden
|
|
17
|
+
// whatever the other rows say.
|
|
18
|
+
//
|
|
19
|
+
// The `'*'` row reads every person, and `membersOf` narrows it to the active
|
|
20
|
+
// organization's members: a person carries no tenant column, and a row filter
|
|
21
|
+
// reaches one relationship hop from the kayan's own fields, which `user` has
|
|
22
|
+
// none of toward `member`.
|
|
23
|
+
import { rows, serverTargets } from '../../../mithaq.ts'
|
|
24
|
+
import { membersOf, namesInTenant, platformReads } from '../../../tenant-access.ts'
|
|
25
|
+
|
|
26
|
+
export const access = {
|
|
27
|
+
...rows({ '*': { read: true }, ...platformReads }, { holds: namesInTenant('user') }),
|
|
28
|
+
read: membersOf(serverTargets.drizzle),
|
|
29
|
+
}
|