create-lego-one 2.0.9 → 2.0.12
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/dist/index.cjs +145 -0
- package/dist/index.cjs.map +1 -1
- package/package.json +5 -3
- package/template/host/e2e/auth.spec.ts +38 -0
- package/template/host/e2e/layout.spec.ts +38 -0
- package/template/host/modern.config.ts +19 -0
- package/template/host/package.json +71 -0
- package/template/host/playwright.config.ts +34 -0
- package/template/host/postcss.config.mjs +6 -0
- package/template/host/src/App.tsx +6 -0
- package/template/host/src/bootstrap.tsx +74 -0
- package/template/host/src/global.css +59 -0
- package/template/host/src/index.ts +2 -0
- package/template/host/src/kernel/__tests__/lib-utils.test.ts +32 -0
- package/template/host/src/kernel/__tests__/rbac-hooks.test.tsx +114 -0
- package/template/host/src/kernel/__tests__/rbac-utils.test.ts +108 -0
- package/template/host/src/kernel/auth/ProtectedRoute.tsx +41 -0
- package/template/host/src/kernel/auth/components/LoginForm.tsx +97 -0
- package/template/host/src/kernel/auth/components/LogoutButton.tsx +79 -0
- package/template/host/src/kernel/auth/hooks.ts +174 -0
- package/template/host/src/kernel/auth/index.ts +5 -0
- package/template/host/src/kernel/auth/schemas.ts +27 -0
- package/template/host/src/kernel/auth/service.ts +197 -0
- package/template/host/src/kernel/auth/types.ts +36 -0
- package/template/host/src/kernel/channels/ChannelBus.ts +181 -0
- package/template/host/src/kernel/channels/ChannelProvider.tsx +57 -0
- package/template/host/src/kernel/channels/events.ts +27 -0
- package/template/host/src/kernel/channels/hooks.ts +168 -0
- package/template/host/src/kernel/channels/index.ts +6 -0
- package/template/host/src/kernel/channels/integrations/ToastIntegration.tsx +60 -0
- package/template/host/src/kernel/channels/plugin-hooks.ts +72 -0
- package/template/host/src/kernel/channels/types.ts +112 -0
- package/template/host/src/kernel/components/__tests__/Badge.test.tsx +35 -0
- package/template/host/src/kernel/components/__tests__/Button.test.tsx +63 -0
- package/template/host/src/kernel/components/__tests__/Input.test.tsx +64 -0
- package/template/host/src/kernel/components/index.ts +32 -0
- package/template/host/src/kernel/components/ui/alert.tsx +58 -0
- package/template/host/src/kernel/components/ui/avatar.tsx +47 -0
- package/template/host/src/kernel/components/ui/badge.tsx +35 -0
- package/template/host/src/kernel/components/ui/button.tsx +50 -0
- package/template/host/src/kernel/components/ui/card.tsx +78 -0
- package/template/host/src/kernel/components/ui/dialog.tsx +116 -0
- package/template/host/src/kernel/components/ui/dropdown-menu.tsx +192 -0
- package/template/host/src/kernel/components/ui/index.ts +7 -0
- package/template/host/src/kernel/components/ui/input.tsx +24 -0
- package/template/host/src/kernel/components/ui/label.tsx +21 -0
- package/template/host/src/kernel/components/ui/popover.tsx +28 -0
- package/template/host/src/kernel/components/ui/progress.tsx +25 -0
- package/template/host/src/kernel/components/ui/scroll-area.tsx +45 -0
- package/template/host/src/kernel/components/ui/select.tsx +155 -0
- package/template/host/src/kernel/components/ui/separator.tsx +28 -0
- package/template/host/src/kernel/components/ui/skeleton.tsx +15 -0
- package/template/host/src/kernel/components/ui/switch.tsx +26 -0
- package/template/host/src/kernel/components/ui/table.tsx +116 -0
- package/template/host/src/kernel/components/ui/tabs.tsx +52 -0
- package/template/host/src/kernel/components/ui/toast.tsx +126 -0
- package/template/host/src/kernel/components/ui/toaster.tsx +34 -0
- package/template/host/src/kernel/components/ui/tooltip.tsx +27 -0
- package/template/host/src/kernel/components/ui/use-toast.ts +183 -0
- package/template/host/src/kernel/index.ts +48 -0
- package/template/host/src/kernel/lib/cn.ts +1 -0
- package/template/host/src/kernel/lib/utils.ts +36 -0
- package/template/host/src/kernel/plugins/Slot.tsx +41 -0
- package/template/host/src/kernel/plugins/SlotProvider.tsx +88 -0
- package/template/host/src/kernel/plugins/index.ts +23 -0
- package/template/host/src/kernel/plugins/loader.ts +122 -0
- package/template/host/src/kernel/plugins/schemas.ts +54 -0
- package/template/host/src/kernel/plugins/store.ts +185 -0
- package/template/host/src/kernel/plugins/types.ts +103 -0
- package/template/host/src/kernel/providers/PocketBaseProvider.tsx +70 -0
- package/template/host/src/kernel/providers/QueryProvider.tsx +28 -0
- package/template/host/src/kernel/providers/ThemeProvider.tsx +25 -0
- package/template/host/src/kernel/providers/index.ts +3 -0
- package/template/host/src/kernel/rbac/components/OrganizationSelector.tsx +69 -0
- package/template/host/src/kernel/rbac/components/PermissionGate.tsx +43 -0
- package/template/host/src/kernel/rbac/hooks.ts +379 -0
- package/template/host/src/kernel/rbac/index.ts +6 -0
- package/template/host/src/kernel/rbac/service.ts +504 -0
- package/template/host/src/kernel/rbac/types.ts +164 -0
- package/template/host/src/kernel/rbac/utils.ts +34 -0
- package/template/host/src/kernel/shared-state/bridge.ts +31 -0
- package/template/host/src/kernel/shared-state/index.ts +3 -0
- package/template/host/src/kernel/shared-state/store.ts +62 -0
- package/template/host/src/kernel/shared-state/types.ts +60 -0
- package/template/host/src/kernel/use-migrations.ts +72 -0
- package/template/host/src/layout/MobileMenu.tsx +61 -0
- package/template/host/src/layout/Shell.tsx +42 -0
- package/template/host/src/layout/Sidebar.tsx +178 -0
- package/template/host/src/layout/Topbar.tsx +50 -0
- package/template/host/src/layout/index.ts +4 -0
- package/template/host/src/lib/pocketbase/client.ts +38 -0
- package/template/host/src/lib/pocketbase/collections/audit_logs.ts +87 -0
- package/template/host/src/lib/pocketbase/collections/index.ts +19 -0
- package/template/host/src/lib/pocketbase/collections/organizations.ts +63 -0
- package/template/host/src/lib/pocketbase/collections/permissions.ts +57 -0
- package/template/host/src/lib/pocketbase/collections/roles.ts +55 -0
- package/template/host/src/lib/pocketbase/collections/todos.ts +74 -0
- package/template/host/src/lib/pocketbase/collections/user_roles.ts +57 -0
- package/template/host/src/lib/pocketbase/collections/users.ts +43 -0
- package/template/host/src/lib/pocketbase/index.ts +5 -0
- package/template/host/src/lib/pocketbase/migrations.ts +44 -0
- package/template/host/src/lib/pocketbase/seed/permissions.ts +8 -0
- package/template/host/src/lib/pocketbase/seed/roles.ts +22 -0
- package/template/host/src/lib/pocketbase/seed.ts +113 -0
- package/template/host/src/lib/pocketbase/types.ts +102 -0
- package/template/host/src/modern.runtime.ts +26 -0
- package/template/host/src/plugins.d.ts +9 -0
- package/template/host/src/providers/PocketBaseProvider.tsx +30 -0
- package/template/host/src/routes/_.tsx +6 -0
- package/template/host/src/routes/dashboard._.tsx +41 -0
- package/template/host/src/routes/index.tsx +93 -0
- package/template/host/src/routes/login.tsx +36 -0
- package/template/host/src/saas.config.ts +52 -0
- package/template/host/src/test/setup.ts +65 -0
- package/template/host/src/test/utils.tsx +69 -0
- package/template/host/src/test/vitest-globals.d.ts +19 -0
- package/template/host/src/vite-env.d.ts +16 -0
- package/template/host/tailwind.config.ts +77 -0
- package/template/host/tsconfig.json +19 -0
- package/template/host/vitest.config.ts +30 -0
- package/template/package.json +44 -0
- package/template/packages/plugins/@lego/plugin-dashboard/modern.config.ts +19 -0
- package/template/packages/plugins/@lego/plugin-dashboard/package.json +35 -0
- package/template/packages/plugins/@lego/plugin-dashboard/postcss.config.mjs +6 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/App.tsx +27 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/ActivityFeed.tsx +63 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/QuickActionSlot.tsx +11 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/QuickActions.tsx +68 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/SidebarWidget.tsx +35 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/components/StatCard.tsx +47 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/global.css +24 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useChannelIntegration.ts +43 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useDashboardStats.ts +65 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/usePocketBase.ts +47 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/hooks/useRecentActivity.ts +55 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/lib/utils.ts +6 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/pages/DashboardPage.tsx +105 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/plugin.config.ts +121 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/plugin.ts +18 -0
- package/template/packages/plugins/@lego/plugin-dashboard/src/vite-env.d.ts +32 -0
- package/template/packages/plugins/@lego/plugin-dashboard/tailwind.config.ts +35 -0
- package/template/packages/plugins/@lego/plugin-dashboard/tsconfig.json +18 -0
- package/template/packages/plugins/@lego/plugin-todo/modern.config.ts +18 -0
- package/template/packages/plugins/@lego/plugin-todo/package.json +41 -0
- package/template/packages/plugins/@lego/plugin-todo/postcss.config.mjs +6 -0
- package/template/packages/plugins/@lego/plugin-todo/src/App.tsx +12 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/SidebarWidget.tsx +16 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoDialog.tsx +55 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoFilters.tsx +79 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoForm.tsx +94 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoItem.tsx +121 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/TodoList.tsx +41 -0
- package/template/packages/plugins/@lego/plugin-todo/src/components/index.ts +6 -0
- package/template/packages/plugins/@lego/plugin-todo/src/global.css +59 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/useCreateTodo.ts +62 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/useDeleteTodo.ts +46 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/usePocketBase.ts +38 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/useTodos.ts +64 -0
- package/template/packages/plugins/@lego/plugin-todo/src/hooks/useUpdateTodo.ts +35 -0
- package/template/packages/plugins/@lego/plugin-todo/src/index.tsx +5 -0
- package/template/packages/plugins/@lego/plugin-todo/src/lib/utils.ts +20 -0
- package/template/packages/plugins/@lego/plugin-todo/src/pages/TodoPage.tsx +89 -0
- package/template/packages/plugins/@lego/plugin-todo/src/plugin.config.ts +104 -0
- package/template/packages/plugins/@lego/plugin-todo/src/plugin.ts +13 -0
- package/template/packages/plugins/@lego/plugin-todo/src/schemas.ts +37 -0
- package/template/packages/plugins/@lego/plugin-todo/src/types.ts +42 -0
- package/template/packages/plugins/@lego/plugin-todo/src/vite-env.d.ts +31 -0
- package/template/packages/plugins/@lego/plugin-todo/tailwind.config.ts +51 -0
- package/template/packages/plugins/@lego/plugin-todo/tsconfig.json +18 -0
- package/template/pnpm-workspace.yaml +4 -0
- package/template/tsconfig.json +8 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { Collection } from '../types';
|
|
2
|
+
|
|
3
|
+
export const auditLogs: Collection = {
|
|
4
|
+
type: 'base',
|
|
5
|
+
name: 'audit_logs',
|
|
6
|
+
// Users can see audit logs for their organization
|
|
7
|
+
listRule:
|
|
8
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
9
|
+
viewRule:
|
|
10
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
11
|
+
// System creates audit logs, users cannot manually create
|
|
12
|
+
createRule: null,
|
|
13
|
+
updateRule: null,
|
|
14
|
+
deleteRule: null,
|
|
15
|
+
fields: [
|
|
16
|
+
{
|
|
17
|
+
name: 'action',
|
|
18
|
+
type: 'select',
|
|
19
|
+
required: true,
|
|
20
|
+
values: [
|
|
21
|
+
'create',
|
|
22
|
+
'update',
|
|
23
|
+
'delete',
|
|
24
|
+
'login',
|
|
25
|
+
'logout',
|
|
26
|
+
'invite_user',
|
|
27
|
+
'remove_user',
|
|
28
|
+
'assign_role',
|
|
29
|
+
'revoke_role',
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'entityType',
|
|
34
|
+
type: 'select',
|
|
35
|
+
required: true,
|
|
36
|
+
values: ['user', 'organization', 'role', 'permission', 'plugin'],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'entityId',
|
|
40
|
+
type: 'text',
|
|
41
|
+
required: true,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'entityName',
|
|
45
|
+
type: 'text',
|
|
46
|
+
required: false,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'organizationId',
|
|
50
|
+
type: 'relation',
|
|
51
|
+
required: true,
|
|
52
|
+
maxSelect: 1,
|
|
53
|
+
collectionId: 'organizations',
|
|
54
|
+
cascadeDelete: true,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'userId',
|
|
58
|
+
type: 'relation',
|
|
59
|
+
required: true,
|
|
60
|
+
maxSelect: 1,
|
|
61
|
+
collectionId: 'users',
|
|
62
|
+
cascadeDelete: true,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'ipAddress',
|
|
66
|
+
type: 'text',
|
|
67
|
+
required: false,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'userAgent',
|
|
71
|
+
type: 'text',
|
|
72
|
+
required: false,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'metadata',
|
|
76
|
+
type: 'json',
|
|
77
|
+
required: false,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
indexes: [
|
|
81
|
+
'CREATE INDEX idx_audit_logs_org ON audit_logs (organizationId)',
|
|
82
|
+
'CREATE INDEX idx_audit_logs_user ON audit_logs (userId)',
|
|
83
|
+
'CREATE INDEX idx_audit_logs_entity ON audit_logs (entityType, entityId)',
|
|
84
|
+
'CREATE INDEX idx_audit_logs_action ON audit_logs (action)',
|
|
85
|
+
'CREATE INDEX idx_audit_logs_created ON audit_logs (created DESC)',
|
|
86
|
+
],
|
|
87
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Migration } from '../types';
|
|
2
|
+
import { users } from './users';
|
|
3
|
+
import { organizations } from './organizations';
|
|
4
|
+
import { roles } from './roles';
|
|
5
|
+
import { permissions } from './permissions';
|
|
6
|
+
import { userRoles } from './user_roles';
|
|
7
|
+
import { auditLogs } from './audit_logs';
|
|
8
|
+
import { todos } from './todos';
|
|
9
|
+
|
|
10
|
+
// Wrap each collection in a migration function
|
|
11
|
+
export const collections: Migration[] = [
|
|
12
|
+
() => users,
|
|
13
|
+
() => organizations,
|
|
14
|
+
() => roles,
|
|
15
|
+
() => permissions,
|
|
16
|
+
() => userRoles,
|
|
17
|
+
() => auditLogs,
|
|
18
|
+
() => todos,
|
|
19
|
+
];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Collection } from '../types';
|
|
2
|
+
|
|
3
|
+
export const organizations: Collection = {
|
|
4
|
+
type: 'base',
|
|
5
|
+
name: 'organizations',
|
|
6
|
+
// Multi-tenancy: Users can only see their organization
|
|
7
|
+
listRule:
|
|
8
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
9
|
+
viewRule:
|
|
10
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
11
|
+
// Only org owners/admins can create/update/delete
|
|
12
|
+
createRule:
|
|
13
|
+
'@request.auth.id != "" && @request.auth.roles.role.name = "Owner"',
|
|
14
|
+
updateRule:
|
|
15
|
+
'@request.auth.id != "" && @request.auth.roles.role.name = "Owner"',
|
|
16
|
+
deleteRule:
|
|
17
|
+
'@request.auth.id != "" && @request.auth.roles.role.name = "Owner"',
|
|
18
|
+
fields: [
|
|
19
|
+
{
|
|
20
|
+
name: 'name',
|
|
21
|
+
type: 'text',
|
|
22
|
+
required: true,
|
|
23
|
+
min: 1,
|
|
24
|
+
max: 100,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'slug',
|
|
28
|
+
type: 'text',
|
|
29
|
+
required: true,
|
|
30
|
+
unique: true,
|
|
31
|
+
pattern: '^[a-z0-9-]+$',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'logo',
|
|
35
|
+
type: 'url',
|
|
36
|
+
required: false,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'ownerId',
|
|
40
|
+
type: 'relation',
|
|
41
|
+
required: true,
|
|
42
|
+
maxSelect: 1,
|
|
43
|
+
collectionId: 'users',
|
|
44
|
+
cascadeDelete: true,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'settings',
|
|
48
|
+
type: 'json',
|
|
49
|
+
required: false,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'maxUsers',
|
|
53
|
+
type: 'number',
|
|
54
|
+
required: true,
|
|
55
|
+
default: 10,
|
|
56
|
+
min: 1,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
indexes: [
|
|
60
|
+
'CREATE UNIQUE INDEX idx_organizations_slug ON organizations (slug)',
|
|
61
|
+
'CREATE INDEX idx_organizations_owner ON organizations (ownerId)',
|
|
62
|
+
],
|
|
63
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Collection } from '../types';
|
|
2
|
+
|
|
3
|
+
export const permissions: Collection = {
|
|
4
|
+
type: 'base',
|
|
5
|
+
name: 'permissions',
|
|
6
|
+
// Users can see permissions for their organization
|
|
7
|
+
listRule:
|
|
8
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
9
|
+
viewRule:
|
|
10
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
11
|
+
// Only org owners/admins can manage permissions
|
|
12
|
+
createRule:
|
|
13
|
+
'@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
14
|
+
updateRule:
|
|
15
|
+
'@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
16
|
+
deleteRule:
|
|
17
|
+
'@request.auth.id != "" && @request.auth.roles.role.name = "Owner"',
|
|
18
|
+
fields: [
|
|
19
|
+
{
|
|
20
|
+
name: 'name',
|
|
21
|
+
type: 'text',
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'slug',
|
|
26
|
+
type: 'text',
|
|
27
|
+
required: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'description',
|
|
31
|
+
type: 'text',
|
|
32
|
+
required: false,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'organizationId',
|
|
36
|
+
type: 'relation',
|
|
37
|
+
required: true,
|
|
38
|
+
maxSelect: 1,
|
|
39
|
+
collectionId: 'organizations',
|
|
40
|
+
cascadeDelete: true,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'resource',
|
|
44
|
+
type: 'text',
|
|
45
|
+
required: true,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'action',
|
|
49
|
+
type: 'text',
|
|
50
|
+
required: true,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
indexes: [
|
|
54
|
+
'CREATE UNIQUE INDEX idx_permissions_org_slug ON permissions (organizationId, slug)',
|
|
55
|
+
'CREATE INDEX idx_permissions_resource_action ON permissions (resource, action)',
|
|
56
|
+
],
|
|
57
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Collection } from '../types';
|
|
2
|
+
|
|
3
|
+
export const roles: Collection = {
|
|
4
|
+
type: 'base',
|
|
5
|
+
name: 'roles',
|
|
6
|
+
// Users can see roles in their organization
|
|
7
|
+
listRule:
|
|
8
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
9
|
+
viewRule:
|
|
10
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
11
|
+
// Only org owners/admins can manage roles
|
|
12
|
+
createRule:
|
|
13
|
+
'@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
14
|
+
updateRule:
|
|
15
|
+
'@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
16
|
+
deleteRule:
|
|
17
|
+
'@request.auth.id != "" && @request.auth.roles.role.name = "Owner"',
|
|
18
|
+
fields: [
|
|
19
|
+
{
|
|
20
|
+
name: 'name',
|
|
21
|
+
type: 'text',
|
|
22
|
+
required: true,
|
|
23
|
+
min: 1,
|
|
24
|
+
max: 50,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'slug',
|
|
28
|
+
type: 'text',
|
|
29
|
+
required: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'description',
|
|
33
|
+
type: 'text',
|
|
34
|
+
required: false,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'organizationId',
|
|
38
|
+
type: 'relation',
|
|
39
|
+
required: true,
|
|
40
|
+
maxSelect: 1,
|
|
41
|
+
collectionId: 'organizations',
|
|
42
|
+
cascadeDelete: true,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'isSystem',
|
|
46
|
+
type: 'bool',
|
|
47
|
+
required: true,
|
|
48
|
+
default: false, // System roles (Owner, Admin, Member, Guest) cannot be deleted
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
indexes: [
|
|
52
|
+
'CREATE UNIQUE INDEX idx_roles_org_slug ON roles (organizationId, slug)',
|
|
53
|
+
'CREATE INDEX idx_roles_org ON roles (organizationId)',
|
|
54
|
+
],
|
|
55
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Collection } from '../types';
|
|
2
|
+
|
|
3
|
+
export const todos: Collection = {
|
|
4
|
+
type: 'base',
|
|
5
|
+
name: 'todos',
|
|
6
|
+
// Multi-tenancy: Users can only see todos in their organization
|
|
7
|
+
listRule:
|
|
8
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
9
|
+
viewRule:
|
|
10
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
11
|
+
// Authenticated users can create todos
|
|
12
|
+
createRule:
|
|
13
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
14
|
+
// Users can update their own todos
|
|
15
|
+
updateRule:
|
|
16
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations && owner = @request.auth.id',
|
|
17
|
+
// Users can delete their own todos
|
|
18
|
+
deleteRule:
|
|
19
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations && owner = @request.auth.id',
|
|
20
|
+
fields: [
|
|
21
|
+
{
|
|
22
|
+
name: 'title',
|
|
23
|
+
type: 'text',
|
|
24
|
+
required: true,
|
|
25
|
+
min: 1,
|
|
26
|
+
max: 200,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'description',
|
|
30
|
+
type: 'text',
|
|
31
|
+
required: false,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'completed',
|
|
35
|
+
type: 'bool',
|
|
36
|
+
required: true,
|
|
37
|
+
default: false,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'priority',
|
|
41
|
+
type: 'select',
|
|
42
|
+
required: true,
|
|
43
|
+
values: ['low', 'medium', 'high'],
|
|
44
|
+
default: 'medium',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'dueDate',
|
|
48
|
+
type: 'date',
|
|
49
|
+
required: false,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'owner',
|
|
53
|
+
type: 'relation',
|
|
54
|
+
required: true,
|
|
55
|
+
maxSelect: 1,
|
|
56
|
+
collectionId: 'users',
|
|
57
|
+
cascadeDelete: true,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'organizationId',
|
|
61
|
+
type: 'relation',
|
|
62
|
+
required: true,
|
|
63
|
+
maxSelect: 1,
|
|
64
|
+
collectionId: 'organizations',
|
|
65
|
+
cascadeDelete: true,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
indexes: [
|
|
69
|
+
'CREATE INDEX idx_todos_org ON todos (organizationId)',
|
|
70
|
+
'CREATE INDEX idx_todos_owner ON todos (owner)',
|
|
71
|
+
'CREATE INDEX idx_todos_completed ON todos (completed)',
|
|
72
|
+
'CREATE INDEX idx_todos_priority ON todos (priority)',
|
|
73
|
+
],
|
|
74
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Collection } from '../types';
|
|
2
|
+
|
|
3
|
+
export const userRoles: Collection = {
|
|
4
|
+
type: 'base',
|
|
5
|
+
name: 'user_roles',
|
|
6
|
+
// Users can see who has roles in their org
|
|
7
|
+
listRule:
|
|
8
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
9
|
+
viewRule:
|
|
10
|
+
'@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
11
|
+
// Only org owners/admins can assign roles
|
|
12
|
+
createRule:
|
|
13
|
+
'@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
14
|
+
updateRule:
|
|
15
|
+
'@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
16
|
+
deleteRule:
|
|
17
|
+
'@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
18
|
+
fields: [
|
|
19
|
+
{
|
|
20
|
+
name: 'userId',
|
|
21
|
+
type: 'relation',
|
|
22
|
+
required: true,
|
|
23
|
+
maxSelect: 1,
|
|
24
|
+
collectionId: 'users',
|
|
25
|
+
cascadeDelete: true,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'roleId',
|
|
29
|
+
type: 'relation',
|
|
30
|
+
required: true,
|
|
31
|
+
maxSelect: 1,
|
|
32
|
+
collectionId: 'roles',
|
|
33
|
+
cascadeDelete: true,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'organizationId',
|
|
37
|
+
type: 'relation',
|
|
38
|
+
required: true,
|
|
39
|
+
maxSelect: 1,
|
|
40
|
+
collectionId: 'organizations',
|
|
41
|
+
cascadeDelete: false,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'assignedBy',
|
|
45
|
+
type: 'relation',
|
|
46
|
+
required: true,
|
|
47
|
+
maxSelect: 1,
|
|
48
|
+
collectionId: 'users',
|
|
49
|
+
cascadeDelete: true,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
indexes: [
|
|
53
|
+
'CREATE UNIQUE INDEX idx_user_roles_user_role ON user_roles (userId, roleId)',
|
|
54
|
+
'CREATE INDEX idx_user_roles_org ON user_roles (organizationId)',
|
|
55
|
+
'CREATE INDEX idx_user_roles_user ON user_roles (userId)',
|
|
56
|
+
],
|
|
57
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Collection } from '../types';
|
|
2
|
+
|
|
3
|
+
export const users: Collection = {
|
|
4
|
+
type: 'auth',
|
|
5
|
+
name: 'users',
|
|
6
|
+
// Users can view their own profile
|
|
7
|
+
viewRule: 'id = @request.auth.id',
|
|
8
|
+
// Users can update their own profile
|
|
9
|
+
updateRule: 'id = @request.auth.id',
|
|
10
|
+
// No public list/create/delete - must be done by org admin
|
|
11
|
+
listRule: null,
|
|
12
|
+
createRule: null,
|
|
13
|
+
deleteRule: null,
|
|
14
|
+
fields: [
|
|
15
|
+
{
|
|
16
|
+
name: 'name',
|
|
17
|
+
type: 'text',
|
|
18
|
+
required: true,
|
|
19
|
+
min: 1,
|
|
20
|
+
max: 100,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'avatar',
|
|
24
|
+
type: 'url',
|
|
25
|
+
required: false,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'organizationId',
|
|
29
|
+
type: 'relation',
|
|
30
|
+
required: true,
|
|
31
|
+
maxSelect: 1,
|
|
32
|
+
collectionId: 'organizations',
|
|
33
|
+
cascadeDelete: false, // Don't delete user when org deleted
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'isActive',
|
|
37
|
+
type: 'bool',
|
|
38
|
+
required: true,
|
|
39
|
+
default: true,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
indexes: ['CREATE INDEX idx_users_org ON users (organizationId)'],
|
|
43
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import PocketBase from 'pocketbase';
|
|
2
|
+
import type { Collection, Migration } from './types';
|
|
3
|
+
|
|
4
|
+
export async function runMigrations(pb: PocketBase, migrations: Migration[]) {
|
|
5
|
+
console.log('[Migrations] Starting PocketBase migrations...');
|
|
6
|
+
|
|
7
|
+
for (const migration of migrations) {
|
|
8
|
+
const result = migration();
|
|
9
|
+
const collections = Array.isArray(result) ? result : [result];
|
|
10
|
+
|
|
11
|
+
for (const collection of collections) {
|
|
12
|
+
await ensureCollection(pb, collection);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.log('[Migrations] ✅ All migrations complete');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function ensureCollection(pb: PocketBase, collection: Collection) {
|
|
20
|
+
const existing = await pb.collections.getList(1, 50, {
|
|
21
|
+
filter: `name = "${collection.name}"`,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (existing.totalItems > 0) {
|
|
25
|
+
console.log(`[Migrations] ✓ Collection exists: ${collection.name}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(`[Migrations] → Creating collection: ${collection.name}`);
|
|
30
|
+
|
|
31
|
+
const result = await pb.collections.create({
|
|
32
|
+
type: collection.type,
|
|
33
|
+
name: collection.name,
|
|
34
|
+
listRule: collection.listRule,
|
|
35
|
+
viewRule: collection.viewRule,
|
|
36
|
+
createRule: collection.createRule,
|
|
37
|
+
updateRule: collection.updateRule,
|
|
38
|
+
deleteRule: collection.deleteRule,
|
|
39
|
+
fields: collection.fields,
|
|
40
|
+
indexes: collection.indexes,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log(`[Migrations] ✅ Created: ${result.name} (${result.id})`);
|
|
44
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Collection } from '../types';
|
|
2
|
+
|
|
3
|
+
// System roles that exist for every organization
|
|
4
|
+
// Note: These are the collection definitions, not the role records themselves
|
|
5
|
+
export const systemRoles: Omit<Collection, 'type' | 'indexes'>[] = [
|
|
6
|
+
{
|
|
7
|
+
name: 'roles_owner',
|
|
8
|
+
fields: [] as any,
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: 'roles_admin',
|
|
12
|
+
fields: [] as any,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: 'roles_member',
|
|
16
|
+
fields: [] as any,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'roles_guest',
|
|
20
|
+
fields: [] as any,
|
|
21
|
+
},
|
|
22
|
+
];
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import PocketBase from 'pocketbase';
|
|
2
|
+
|
|
3
|
+
interface SeedData {
|
|
4
|
+
adminEmail: string;
|
|
5
|
+
adminPassword: string;
|
|
6
|
+
adminName: string;
|
|
7
|
+
orgName: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function seedDatabase(pb: PocketBase, seedData: SeedData) {
|
|
11
|
+
console.log('[Seed] Starting database seeding...');
|
|
12
|
+
|
|
13
|
+
// Check if already seeded
|
|
14
|
+
const existingOrgs = await pb.collection('organizations').getList(1, 1);
|
|
15
|
+
if (existingOrgs.totalItems > 0) {
|
|
16
|
+
console.log('[Seed] ℹ Database already seeded, skipping...');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log('[Seed] → Creating admin user...');
|
|
21
|
+
|
|
22
|
+
// Create admin user
|
|
23
|
+
const admin = await pb.collection('users').create({
|
|
24
|
+
email: seedData.adminEmail,
|
|
25
|
+
password: seedData.adminPassword,
|
|
26
|
+
passwordConfirm: seedData.adminPassword,
|
|
27
|
+
name: seedData.adminName,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
console.log('[Seed] → Creating organization...');
|
|
31
|
+
|
|
32
|
+
// Create organization
|
|
33
|
+
const organization = await pb.collection('organizations').create({
|
|
34
|
+
name: seedData.orgName,
|
|
35
|
+
slug: seedData.orgName.toLowerCase().replace(/\s+/g, '-'),
|
|
36
|
+
ownerId: admin.id,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Update admin with organization
|
|
40
|
+
await pb.collection('users').update(admin.id, {
|
|
41
|
+
organizationId: organization.id,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
console.log('[Seed] → Creating system roles...');
|
|
45
|
+
|
|
46
|
+
// Create system roles
|
|
47
|
+
const roleMap = new Map<string, string>();
|
|
48
|
+
|
|
49
|
+
const systemRolesData = [
|
|
50
|
+
{ name: 'Owner', slug: 'owner', description: 'Full access to all resources' },
|
|
51
|
+
{ name: 'Admin', slug: 'admin', description: 'Can manage users, roles, and most settings' },
|
|
52
|
+
{ name: 'Member', slug: 'member', description: 'Standard user with basic access' },
|
|
53
|
+
{ name: 'Guest', slug: 'guest', description: 'Limited access, read-only' },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
for (const roleData of systemRolesData) {
|
|
57
|
+
const role = await pb.collection('roles').create({
|
|
58
|
+
name: roleData.name,
|
|
59
|
+
description: roleData.description,
|
|
60
|
+
organizationId: '', // Empty string for system roles
|
|
61
|
+
isSystem: true,
|
|
62
|
+
});
|
|
63
|
+
roleMap.set(roleData.slug, role.id);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log('[Seed] → Creating sample todos...');
|
|
67
|
+
|
|
68
|
+
// Create sample todos
|
|
69
|
+
await pb.collection('todos').create({
|
|
70
|
+
title: 'Welcome to Lego-One! 👋',
|
|
71
|
+
description:
|
|
72
|
+
'This is your first todo item. Try editing or completing it!',
|
|
73
|
+
completed: false,
|
|
74
|
+
priority: 'medium',
|
|
75
|
+
owner: admin.id,
|
|
76
|
+
organizationId: organization.id,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await pb.collection('todos').create({
|
|
80
|
+
title: 'Explore the Dashboard',
|
|
81
|
+
description:
|
|
82
|
+
'Check out the Dashboard plugin to see your stats and activity.',
|
|
83
|
+
completed: false,
|
|
84
|
+
priority: 'low',
|
|
85
|
+
owner: admin.id,
|
|
86
|
+
organizationId: organization.id,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await pb.collection('todos').create({
|
|
90
|
+
title: 'Manage your Organization',
|
|
91
|
+
description:
|
|
92
|
+
'Go to Settings to invite team members and configure roles.',
|
|
93
|
+
completed: false,
|
|
94
|
+
priority: 'high',
|
|
95
|
+
owner: admin.id,
|
|
96
|
+
organizationId: organization.id,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
console.log('[Seed] → Assigning Owner role to admin...');
|
|
100
|
+
|
|
101
|
+
// Assign Owner role to admin
|
|
102
|
+
await pb.collection('user_roles').create({
|
|
103
|
+
userId: admin.id,
|
|
104
|
+
roleId: roleMap.get('owner')!,
|
|
105
|
+
organizationId: organization.id,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
console.log('[Seed] ✅ Database seeded successfully!');
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log('📧 Login credentials:');
|
|
111
|
+
console.log(` Email: ${seedData.adminEmail}`);
|
|
112
|
+
console.log(` Password: ${seedData.adminPassword}`);
|
|
113
|
+
}
|