create-lego-one 2.0.12 → 2.0.14
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 +150 -15
- package/dist/index.cjs.map +1 -1
- package/package.json +1 -1
- package/template/.cursor/rules/rules.mdc +639 -0
- package/template/.dockerignore +58 -0
- package/template/.env.example +18 -0
- package/template/.eslintignore +5 -0
- package/template/.eslintrc.js +28 -0
- package/template/.prettierignore +6 -0
- package/template/.prettierrc +11 -0
- package/template/CLAUDE.md +634 -0
- package/template/Dockerfile +67 -0
- package/template/PROMPT.md +457 -0
- package/template/README.md +325 -0
- package/template/docker-compose.yml +48 -0
- package/template/docker-entrypoint.sh +23 -0
- package/template/docs/checkpoints/.template.md +64 -0
- package/template/docs/checkpoints/framework/01-infrastructure-setup.md +132 -0
- package/template/docs/checkpoints/framework/02-pocketbase-setup.md +155 -0
- package/template/docs/checkpoints/framework/03-host-kernel.md +170 -0
- package/template/docs/checkpoints/framework/04-auth-system.md +163 -0
- package/template/docs/checkpoints/framework/phase-05-multitenancy-rbac.md +223 -0
- package/template/docs/checkpoints/framework/phase-06-ui-components.md +260 -0
- package/template/docs/checkpoints/framework/phase-07-communication-system.md +276 -0
- package/template/docs/checkpoints/framework/phase-08-plugin-system.md +91 -0
- package/template/docs/checkpoints/framework/phase-09-dashboard-plugin.md +111 -0
- package/template/docs/checkpoints/framework/phase-10-todo-plugin.md +169 -0
- package/template/docs/checkpoints/framework/phase-11-testing.md +264 -0
- package/template/docs/checkpoints/framework/phase-12-deployment.md +294 -0
- package/template/docs/checkpoints/framework/phase-13-documentation.md +312 -0
- package/template/docs/framework/plans/00-index.md +164 -0
- package/template/docs/framework/plans/01-infrastructure-setup.md +855 -0
- package/template/docs/framework/plans/02-pocketbase-setup.md +1374 -0
- package/template/docs/framework/plans/03-host-kernel.md +1518 -0
- package/template/docs/framework/plans/04-auth-system.md +1466 -0
- package/template/docs/framework/plans/05-multitenancy-rbac.md +1527 -0
- package/template/docs/framework/plans/06-ui-components.md +1478 -0
- package/template/docs/framework/plans/07-communication-system.md +1106 -0
- package/template/docs/framework/plans/08-plugin-system.md +1179 -0
- package/template/docs/framework/plans/09-dashboard-plugin.md +1137 -0
- package/template/docs/framework/plans/10-todo-plugin.md +1343 -0
- package/template/docs/framework/plans/11-testing.md +935 -0
- package/template/docs/framework/plans/12-deployment.md +896 -0
- package/template/docs/framework/prompts/0-boilerplate-modernjs.md +151 -0
- package/template/docs/framework/research/00-modernjs-audit.md +488 -0
- package/template/docs/framework/research/01-system-blueprint.md +721 -0
- package/template/docs/framework/research/02-data-migration-protocol.md +699 -0
- package/template/docs/framework/research/03-host-setup.md +714 -0
- package/template/docs/framework/research/04-plugin-architecture.md +645 -0
- package/template/docs/framework/research/05-slot-injection-pattern.md +671 -0
- package/template/docs/framework/research/06-cli-strategy.md +615 -0
- package/template/docs/framework/research/07-deployment.md +629 -0
- package/template/docs/framework/research/README.md +282 -0
- package/template/docs/framework/setup/00-index.md +210 -0
- package/template/docs/framework/setup/01-framework-structure.md +308 -0
- package/template/docs/framework/setup/02-development-workflow.md +405 -0
- package/template/docs/framework/setup/03-environment-setup.md +215 -0
- package/template/docs/framework/setup/04-kernel-architecture.md +499 -0
- package/template/docs/framework/setup/05-plugin-system.md +620 -0
- package/template/docs/framework/setup/06-communication-patterns.md +451 -0
- package/template/docs/framework/setup/07-plugin-development.md +582 -0
- package/template/docs/framework/setup/08-component-library.md +658 -0
- package/template/docs/framework/setup/09-data-integration.md +609 -0
- package/template/docs/framework/setup/10-auth-rbac.md +497 -0
- package/template/docs/framework/setup/11-hooks-api.md +393 -0
- package/template/docs/framework/setup/12-components-api.md +665 -0
- package/template/docs/framework/setup/13-deployment-guide.md +566 -0
- package/template/docs/framework/setup/README.md +548 -0
- package/template/host/package.json +1 -1
- package/template/nginx.conf +72 -0
- package/template/package.json +1 -1
- package/template/packages/plugins/@lego/plugin-dashboard/package.json +1 -1
- package/template/packages/plugins/@lego/plugin-todo/package.json +1 -1
- package/template/pocketbase/CHANGELOG.md +911 -0
- package/template/pocketbase/LICENSE.md +17 -0
- package/template/scripts/create-plugin.js +221 -0
- package/template/scripts/deploy.sh +56 -0
- package/template/tsconfig.base.json +26 -0
|
@@ -0,0 +1,1374 @@
|
|
|
1
|
+
# PocketBase Setup
|
|
2
|
+
|
|
3
|
+
> **For AI:** Complete all tasks in order. This sets up the entire backend with multitenancy, RBAC, and seed data.
|
|
4
|
+
|
|
5
|
+
**Goal:** Install PocketBase, create all collections with API rules for multi-tenancy, implement migration system, and add seed data.
|
|
6
|
+
|
|
7
|
+
**Architecture:** PocketBase as backend with proper API rules ensuring each organization's data is isolated. Collections: users, organizations, roles, permissions, user_roles, audit_logs, todos.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** PocketBase v0.22+, JavaScript migrations, admin API for programmatic collection creation
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Task 1: Download and Setup PocketBase
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Create: `pocketbase/`
|
|
17
|
+
- Download: PocketBase binary
|
|
18
|
+
|
|
19
|
+
**Step 1: Create pocketbase directory and download binary**
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Create directory
|
|
23
|
+
mkdir -p pocketbase
|
|
24
|
+
cd pocketbase
|
|
25
|
+
|
|
26
|
+
# Download PocketBase v0.22.0 for your platform
|
|
27
|
+
# Linux (adjust URL for Mac/Windows if needed)
|
|
28
|
+
wget https://github.com/pocketbase/pocketbase/releases/download/v0.22.0/pocketbase_0.22.0_linux_amd64.zip
|
|
29
|
+
|
|
30
|
+
# Extract
|
|
31
|
+
unzip pocketbase_0.22.0_linux_amd64.zip
|
|
32
|
+
|
|
33
|
+
# Make executable
|
|
34
|
+
chmod +x pocketbase
|
|
35
|
+
|
|
36
|
+
# Verify
|
|
37
|
+
./pocketbase --version
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Expected: Output shows version 0.22.0
|
|
41
|
+
|
|
42
|
+
**Step 2: Create .gitignore for pocketbase**
|
|
43
|
+
|
|
44
|
+
Create `pocketbase/.gitignore`:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
# Ignore data directory
|
|
48
|
+
pb_data/
|
|
49
|
+
|
|
50
|
+
# Ignore public uploads
|
|
51
|
+
pb_public/
|
|
52
|
+
|
|
53
|
+
# Keep binary and migrations
|
|
54
|
+
!pocketbase
|
|
55
|
+
!pb_migrations/
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Step 3: Create migrations directory**
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
mkdir -p pb_migrations
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Step 4: Commit**
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
git add pocketbase/
|
|
68
|
+
git commit -m "chore: add PocketBase binary and migrations structure"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Task 2: Create Migration Types
|
|
74
|
+
|
|
75
|
+
**Files:**
|
|
76
|
+
- Create: `host/src/lib/pocketbase/types.ts`
|
|
77
|
+
- Create: `host/src/lib/pocketbase/migrations.ts`
|
|
78
|
+
|
|
79
|
+
**Step 1: Create PocketBase types**
|
|
80
|
+
|
|
81
|
+
Create `host/src/lib/pocketbase/types.ts`:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// PocketBase field types
|
|
85
|
+
export interface BoolField {
|
|
86
|
+
name: string;
|
|
87
|
+
type: 'bool';
|
|
88
|
+
required?: boolean;
|
|
89
|
+
default?: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface NumberField {
|
|
93
|
+
name: string;
|
|
94
|
+
type: 'number';
|
|
95
|
+
required?: boolean;
|
|
96
|
+
min?: number;
|
|
97
|
+
max?: number;
|
|
98
|
+
default?: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface TextField {
|
|
102
|
+
name: string;
|
|
103
|
+
type: 'text';
|
|
104
|
+
required?: boolean;
|
|
105
|
+
min?: number;
|
|
106
|
+
max?: number;
|
|
107
|
+
default?: string;
|
|
108
|
+
pattern?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface EmailField {
|
|
112
|
+
name: string;
|
|
113
|
+
type: 'email';
|
|
114
|
+
required?: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface UrlField {
|
|
118
|
+
name: string;
|
|
119
|
+
type: 'url';
|
|
120
|
+
required?: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface DateField {
|
|
124
|
+
name: string;
|
|
125
|
+
type: 'date';
|
|
126
|
+
required?: boolean;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface AutodateField {
|
|
130
|
+
name: string;
|
|
131
|
+
type: 'autodate';
|
|
132
|
+
required?: boolean;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface SelectField {
|
|
136
|
+
name: string;
|
|
137
|
+
type: 'select';
|
|
138
|
+
required?: boolean;
|
|
139
|
+
values: string[];
|
|
140
|
+
default?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface JsonField {
|
|
144
|
+
name: string;
|
|
145
|
+
type: 'json';
|
|
146
|
+
required?: boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface RelationField {
|
|
150
|
+
name: string;
|
|
151
|
+
type: 'relation';
|
|
152
|
+
required?: boolean;
|
|
153
|
+
maxSelect?: number;
|
|
154
|
+
collectionId: string;
|
|
155
|
+
cascadeDelete?: boolean;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type Field = BoolField | NumberField | TextField | EmailField | UrlField | DateField | AutodateField | SelectField | JsonField | RelationField;
|
|
159
|
+
|
|
160
|
+
// Collection definition
|
|
161
|
+
export interface Collection {
|
|
162
|
+
type: 'base' | 'auth' | 'view';
|
|
163
|
+
name: string;
|
|
164
|
+
listRule?: string | null;
|
|
165
|
+
viewRule?: string | null;
|
|
166
|
+
createRule?: string | null;
|
|
167
|
+
updateRule?: string | null;
|
|
168
|
+
deleteRule?: string | null;
|
|
169
|
+
fields: Field[];
|
|
170
|
+
indexes?: string[];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Migration function type
|
|
174
|
+
export type Migration = () => Collection | Collection[];
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Step 2: Create migration utilities**
|
|
178
|
+
|
|
179
|
+
Create `host/src/lib/pocketbase/migrations.ts`:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import PocketBase from 'pocketbase';
|
|
183
|
+
import type { Collection, Migration } from './types';
|
|
184
|
+
|
|
185
|
+
export async function runMigrations(pb: PocketBase, migrations: Migration[]) {
|
|
186
|
+
console.log('[Migrations] Starting PocketBase migrations...');
|
|
187
|
+
|
|
188
|
+
for (const migration of migrations) {
|
|
189
|
+
const collections = Array.isArray(migration()) ? migration() : [migration()];
|
|
190
|
+
|
|
191
|
+
for (const collection of collections) {
|
|
192
|
+
await ensureCollection(pb, collection);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log('[Migrations] ✅ All migrations complete');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function ensureCollection(pb: PocketBase, collection: Collection) {
|
|
200
|
+
const existing = await pb.collections.getList(1, 50, {
|
|
201
|
+
filter: `name = "${collection.name}"`,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (existing.totalItems > 0) {
|
|
205
|
+
console.log(`[Migrations] ✓ Collection exists: ${collection.name}`);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log(`[Migrations] → Creating collection: ${collection.name}`);
|
|
210
|
+
|
|
211
|
+
const result = await pb.collections.create({
|
|
212
|
+
type: collection.type,
|
|
213
|
+
name: collection.name,
|
|
214
|
+
listRule: collection.listRule,
|
|
215
|
+
viewRule: collection.viewRule,
|
|
216
|
+
createRule: collection.createRule,
|
|
217
|
+
updateRule: collection.updateRule,
|
|
218
|
+
deleteRule: collection.deleteRule,
|
|
219
|
+
fields: collection.fields,
|
|
220
|
+
indexes: collection.indexes,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
console.log(`[Migrations] ✅ Created: ${result.name} (${result.id})`);
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Step 3: Commit**
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
git add host/src/lib/pocketbase/
|
|
231
|
+
git commit -m "feat: add PocketBase migration utilities"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Task 3: Define All Collection Migrations
|
|
237
|
+
|
|
238
|
+
**Files:**
|
|
239
|
+
- Create: `host/src/lib/pocketbase/collections/index.ts`
|
|
240
|
+
- Create: `host/src/lib/pocketbase/collections/users.ts`
|
|
241
|
+
- Create: `host/src/lib/pocketbase/collections/organizations.ts`
|
|
242
|
+
- Create: `host/src/lib/pocketbase/collections/roles.ts`
|
|
243
|
+
- Create: `host/src/lib/pocketbase/collections/permissions.ts`
|
|
244
|
+
- Create: `host/src/lib/pocketbase/collections/user_roles.ts`
|
|
245
|
+
- Create: `host/src/lib/pocketbase/collections/audit_logs.ts`
|
|
246
|
+
- Create: `host/src/lib/pocketbase/collections/todos.ts`
|
|
247
|
+
|
|
248
|
+
**Step 1: Create collections index**
|
|
249
|
+
|
|
250
|
+
Create `host/src/lib/pocketbase/collections/index.ts`:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import { users } from './users';
|
|
254
|
+
import { organizations } from './organizations';
|
|
255
|
+
import { roles } from './roles';
|
|
256
|
+
import { permissions } from './permissions';
|
|
257
|
+
import { userRoles } from './user_roles';
|
|
258
|
+
import { auditLogs } from './audit_logs';
|
|
259
|
+
import { todos } from './todos';
|
|
260
|
+
|
|
261
|
+
export const collections = [
|
|
262
|
+
users,
|
|
263
|
+
organizations,
|
|
264
|
+
roles,
|
|
265
|
+
permissions,
|
|
266
|
+
userRoles,
|
|
267
|
+
auditLogs,
|
|
268
|
+
todos,
|
|
269
|
+
];
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Step 2: Create users collection**
|
|
273
|
+
|
|
274
|
+
Create `host/src/lib/pocketbase/collections/users.ts`:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import type { Collection } from '../types';
|
|
278
|
+
|
|
279
|
+
export const users: Collection = {
|
|
280
|
+
type: 'auth',
|
|
281
|
+
name: 'users',
|
|
282
|
+
// Users can view their own profile
|
|
283
|
+
viewRule: 'id = @request.auth.id',
|
|
284
|
+
// Users can update their own profile
|
|
285
|
+
updateRule: 'id = @request.auth.id',
|
|
286
|
+
// No public list/create/delete - must be done by org admin
|
|
287
|
+
listRule: null,
|
|
288
|
+
createRule: null,
|
|
289
|
+
deleteRule: null,
|
|
290
|
+
fields: [
|
|
291
|
+
{
|
|
292
|
+
name: 'name',
|
|
293
|
+
type: 'text',
|
|
294
|
+
required: true,
|
|
295
|
+
min: 1,
|
|
296
|
+
max: 100,
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: 'avatar',
|
|
300
|
+
type: 'url',
|
|
301
|
+
required: false,
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: 'organizationId',
|
|
305
|
+
type: 'relation',
|
|
306
|
+
required: true,
|
|
307
|
+
maxSelect: 1,
|
|
308
|
+
collectionId: 'organizations',
|
|
309
|
+
cascadeDelete: false, // Don't delete user when org deleted
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: 'isActive',
|
|
313
|
+
type: 'bool',
|
|
314
|
+
required: true,
|
|
315
|
+
default: true,
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
indexes: ['CREATE INDEX idx_users_org ON users (organizationId)'],
|
|
319
|
+
};
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Step 3: Create organizations collection**
|
|
323
|
+
|
|
324
|
+
Create `host/src/lib/pocketbase/collections/organizations.ts`:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import type { Collection } from '../types';
|
|
328
|
+
|
|
329
|
+
export const organizations: Collection = {
|
|
330
|
+
type: 'base',
|
|
331
|
+
name: 'organizations',
|
|
332
|
+
// Multi-tenancy: Users can only see their organization
|
|
333
|
+
listRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
334
|
+
viewRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
335
|
+
// Only org owners/admins can create/update/delete
|
|
336
|
+
createRule: '@request.auth.id != "" && @request.auth.roles.role.name = "Owner"',
|
|
337
|
+
updateRule: '@request.auth.id != "" && @request.auth.roles.role.name = "Owner"',
|
|
338
|
+
deleteRule: '@request.auth.id != "" && @request.auth.roles.role.name = "Owner"',
|
|
339
|
+
fields: [
|
|
340
|
+
{
|
|
341
|
+
name: 'name',
|
|
342
|
+
type: 'text',
|
|
343
|
+
required: true,
|
|
344
|
+
min: 1,
|
|
345
|
+
max: 100,
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: 'slug',
|
|
349
|
+
type: 'text',
|
|
350
|
+
required: true,
|
|
351
|
+
unique: true,
|
|
352
|
+
pattern: '^[a-z0-9-]+$',
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: 'logo',
|
|
356
|
+
type: 'url',
|
|
357
|
+
required: false,
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: 'ownerId',
|
|
361
|
+
type: 'relation',
|
|
362
|
+
required: true,
|
|
363
|
+
maxSelect: 1,
|
|
364
|
+
collectionId: 'users',
|
|
365
|
+
cascadeDelete: true,
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
name: 'settings',
|
|
369
|
+
type: 'json',
|
|
370
|
+
required: false,
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: 'maxUsers',
|
|
374
|
+
type: 'number',
|
|
375
|
+
required: true,
|
|
376
|
+
default: 10,
|
|
377
|
+
min: 1,
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
indexes: [
|
|
381
|
+
'CREATE UNIQUE INDEX idx_organizations_slug ON organizations (slug)',
|
|
382
|
+
'CREATE INDEX idx_organizations_owner ON organizations (ownerId)',
|
|
383
|
+
],
|
|
384
|
+
};
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Step 4: Create roles collection**
|
|
388
|
+
|
|
389
|
+
Create `host/src/lib/pocketbase/collections/roles.ts`:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import type { Collection } from '../types';
|
|
393
|
+
|
|
394
|
+
export const roles: Collection = {
|
|
395
|
+
type: 'base',
|
|
396
|
+
name: 'roles',
|
|
397
|
+
// Users can see roles in their organization
|
|
398
|
+
listRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
399
|
+
viewRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
400
|
+
// Only org owners/admins can manage roles
|
|
401
|
+
createRule: '@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
402
|
+
updateRule: '@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
403
|
+
deleteRule: '@request.auth.id != "" && @request.auth.roles.role.name = "Owner"',
|
|
404
|
+
fields: [
|
|
405
|
+
{
|
|
406
|
+
name: 'name',
|
|
407
|
+
type: 'text',
|
|
408
|
+
required: true,
|
|
409
|
+
min: 1,
|
|
410
|
+
max: 50,
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
name: 'slug',
|
|
414
|
+
type: 'text',
|
|
415
|
+
required: true,
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
name: 'description',
|
|
419
|
+
type: 'text',
|
|
420
|
+
required: false,
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: 'organizationId',
|
|
424
|
+
type: 'relation',
|
|
425
|
+
required: true,
|
|
426
|
+
maxSelect: 1,
|
|
427
|
+
collectionId: 'organizations',
|
|
428
|
+
cascadeDelete: true,
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
name: 'isSystem',
|
|
432
|
+
type: 'bool',
|
|
433
|
+
required: true,
|
|
434
|
+
default: false, // System roles (Owner, Admin, Member, Guest) cannot be deleted
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
indexes: [
|
|
438
|
+
'CREATE UNIQUE INDEX idx_roles_org_slug ON roles (organizationId, slug)',
|
|
439
|
+
'CREATE INDEX idx_roles_org ON roles (organizationId)',
|
|
440
|
+
],
|
|
441
|
+
};
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**Step 5: Create permissions collection**
|
|
445
|
+
|
|
446
|
+
Create `host/src/lib/pocketbase/collections/permissions.ts`:
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
import type { Collection } from '../types';
|
|
450
|
+
|
|
451
|
+
export const permissions: Collection = {
|
|
452
|
+
type: 'base',
|
|
453
|
+
name: 'permissions',
|
|
454
|
+
// Users can see permissions for their organization
|
|
455
|
+
listRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
456
|
+
viewRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
457
|
+
// Only org owners/admins can manage permissions
|
|
458
|
+
createRule: '@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
459
|
+
updateRule: '@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
460
|
+
deleteRule: '@request.auth.id != "" && @request.auth.roles.role.name = "Owner"',
|
|
461
|
+
fields: [
|
|
462
|
+
{
|
|
463
|
+
name: 'name',
|
|
464
|
+
type: 'text',
|
|
465
|
+
required: true,
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: 'slug',
|
|
469
|
+
type: 'text',
|
|
470
|
+
required: true,
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: 'description',
|
|
474
|
+
type: 'text',
|
|
475
|
+
required: false,
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
name: 'organizationId',
|
|
479
|
+
type: 'relation',
|
|
480
|
+
required: true,
|
|
481
|
+
maxSelect: 1,
|
|
482
|
+
collectionId: 'organizations',
|
|
483
|
+
cascadeDelete: true,
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: 'resource',
|
|
487
|
+
type: 'text',
|
|
488
|
+
required: true,
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: 'action',
|
|
492
|
+
type: 'text',
|
|
493
|
+
required: true,
|
|
494
|
+
},
|
|
495
|
+
],
|
|
496
|
+
indexes: [
|
|
497
|
+
'CREATE UNIQUE INDEX idx_permissions_org_slug ON permissions (organizationId, slug)',
|
|
498
|
+
'CREATE INDEX idx_permissions_resource_action ON permissions (resource, action)',
|
|
499
|
+
],
|
|
500
|
+
};
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**Step 6: Create user_roles junction collection**
|
|
504
|
+
|
|
505
|
+
Create `host/src/lib/pocketbase/collections/user_roles.ts`:
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
import type { Collection } from '../types';
|
|
509
|
+
|
|
510
|
+
export const userRoles: Collection = {
|
|
511
|
+
type: 'base',
|
|
512
|
+
name: 'user_roles',
|
|
513
|
+
// Users can see who has roles in their org
|
|
514
|
+
listRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
515
|
+
viewRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
516
|
+
// Only org owners/admins can assign roles
|
|
517
|
+
createRule: '@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
518
|
+
updateRule: '@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
519
|
+
deleteRule: '@request.auth.id != "" && (@request.auth.roles.role.name = "Owner" || @request.auth.roles.role.name = "Admin")',
|
|
520
|
+
fields: [
|
|
521
|
+
{
|
|
522
|
+
name: 'userId',
|
|
523
|
+
type: 'relation',
|
|
524
|
+
required: true,
|
|
525
|
+
maxSelect: 1,
|
|
526
|
+
collectionId: 'users',
|
|
527
|
+
cascadeDelete: true,
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: 'roleId',
|
|
531
|
+
type: 'relation',
|
|
532
|
+
required: true,
|
|
533
|
+
maxSelect: 1,
|
|
534
|
+
collectionId: 'roles',
|
|
535
|
+
cascadeDelete: true,
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
name: 'organizationId',
|
|
539
|
+
type: 'relation',
|
|
540
|
+
required: true,
|
|
541
|
+
maxSelect: 1,
|
|
542
|
+
collectionId: 'organizations',
|
|
543
|
+
cascadeDelete: false,
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: 'assignedBy',
|
|
547
|
+
type: 'relation',
|
|
548
|
+
required: true,
|
|
549
|
+
maxSelect: 1,
|
|
550
|
+
collectionId: 'users',
|
|
551
|
+
cascadeDelete: true,
|
|
552
|
+
},
|
|
553
|
+
],
|
|
554
|
+
indexes: [
|
|
555
|
+
'CREATE UNIQUE INDEX idx_user_roles_user_role ON user_roles (userId, roleId)',
|
|
556
|
+
'CREATE INDEX idx_user_roles_org ON user_roles (organizationId)',
|
|
557
|
+
'CREATE INDEX idx_user_roles_user ON user_roles (userId)',
|
|
558
|
+
],
|
|
559
|
+
};
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
**Step 7: Create audit_logs collection**
|
|
563
|
+
|
|
564
|
+
Create `host/src/lib/pocketbase/collections/audit_logs.ts`:
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import type { Collection } from '../types';
|
|
568
|
+
|
|
569
|
+
export const auditLogs: Collection = {
|
|
570
|
+
type: 'base',
|
|
571
|
+
name: 'audit_logs',
|
|
572
|
+
// Users can see audit logs for their organization
|
|
573
|
+
listRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
574
|
+
viewRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
575
|
+
// System creates audit logs, users cannot manually create
|
|
576
|
+
createRule: null,
|
|
577
|
+
updateRule: null,
|
|
578
|
+
deleteRule: null,
|
|
579
|
+
fields: [
|
|
580
|
+
{
|
|
581
|
+
name: 'action',
|
|
582
|
+
type: 'select',
|
|
583
|
+
required: true,
|
|
584
|
+
values: ['create', 'update', 'delete', 'login', 'logout', 'invite_user', 'remove_user', 'assign_role', 'revoke_role'],
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
name: 'entityType',
|
|
588
|
+
type: 'select',
|
|
589
|
+
required: true,
|
|
590
|
+
values: ['user', 'organization', 'role', 'permission', 'plugin'],
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
name: 'entityId',
|
|
594
|
+
type: 'text',
|
|
595
|
+
required: true,
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
name: 'entityName',
|
|
599
|
+
type: 'text',
|
|
600
|
+
required: false,
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
name: 'organizationId',
|
|
604
|
+
type: 'relation',
|
|
605
|
+
required: true,
|
|
606
|
+
maxSelect: 1,
|
|
607
|
+
collectionId: 'organizations',
|
|
608
|
+
cascadeDelete: true,
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
name: 'userId',
|
|
612
|
+
type: 'relation',
|
|
613
|
+
required: true,
|
|
614
|
+
maxSelect: 1,
|
|
615
|
+
collectionId: 'users',
|
|
616
|
+
cascadeDelete: true,
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
name: 'ipAddress',
|
|
620
|
+
type: 'text',
|
|
621
|
+
required: false,
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
name: 'userAgent',
|
|
625
|
+
type: 'text',
|
|
626
|
+
required: false,
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
name: 'metadata',
|
|
630
|
+
type: 'json',
|
|
631
|
+
required: false,
|
|
632
|
+
},
|
|
633
|
+
],
|
|
634
|
+
indexes: [
|
|
635
|
+
'CREATE INDEX idx_audit_logs_org ON audit_logs (organizationId)',
|
|
636
|
+
'CREATE INDEX idx_audit_logs_user ON audit_logs (userId)',
|
|
637
|
+
'CREATE INDEX idx_audit_logs_entity ON audit_logs (entityType, entityId)',
|
|
638
|
+
'CREATE INDEX idx_audit_logs_action ON audit_logs (action)',
|
|
639
|
+
'CREATE INDEX idx_audit_logs_created ON audit_logs (created DESC)',
|
|
640
|
+
],
|
|
641
|
+
};
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
**Step 8: Create todos collection**
|
|
645
|
+
|
|
646
|
+
Create `host/src/lib/pocketbase/collections/todos.ts`:
|
|
647
|
+
|
|
648
|
+
```typescript
|
|
649
|
+
import type { Collection } from '../types';
|
|
650
|
+
|
|
651
|
+
export const todos: Collection = {
|
|
652
|
+
type: 'base',
|
|
653
|
+
name: 'todos',
|
|
654
|
+
// Multi-tenancy: Users can only see todos in their organization
|
|
655
|
+
listRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
656
|
+
viewRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
657
|
+
// Authenticated users can create todos
|
|
658
|
+
createRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations',
|
|
659
|
+
// Users can update their own todos
|
|
660
|
+
updateRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations && owner = @request.auth.id',
|
|
661
|
+
// Users can delete their own todos
|
|
662
|
+
deleteRule: '@request.auth.id != "" && organization = @request.auth.membership.organizations && owner = @request.auth.id',
|
|
663
|
+
fields: [
|
|
664
|
+
{
|
|
665
|
+
name: 'title',
|
|
666
|
+
type: 'text',
|
|
667
|
+
required: true,
|
|
668
|
+
min: 1,
|
|
669
|
+
max: 200,
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
name: 'description',
|
|
673
|
+
type: 'text',
|
|
674
|
+
required: false,
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
name: 'completed',
|
|
678
|
+
type: 'bool',
|
|
679
|
+
required: true,
|
|
680
|
+
default: false,
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
name: 'priority',
|
|
684
|
+
type: 'select',
|
|
685
|
+
required: true,
|
|
686
|
+
values: ['low', 'medium', 'high'],
|
|
687
|
+
default: 'medium',
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
name: 'dueDate',
|
|
691
|
+
type: 'date',
|
|
692
|
+
required: false,
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
name: 'owner',
|
|
696
|
+
type: 'relation',
|
|
697
|
+
required: true,
|
|
698
|
+
maxSelect: 1,
|
|
699
|
+
collectionId: 'users',
|
|
700
|
+
cascadeDelete: true,
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
name: 'organizationId',
|
|
704
|
+
type: 'relation',
|
|
705
|
+
required: true,
|
|
706
|
+
maxSelect: 1,
|
|
707
|
+
collectionId: 'organizations',
|
|
708
|
+
cascadeDelete: true,
|
|
709
|
+
},
|
|
710
|
+
],
|
|
711
|
+
indexes: [
|
|
712
|
+
'CREATE INDEX idx_todos_org ON todos (organizationId)',
|
|
713
|
+
'CREATE INDEX idx_todos_owner ON todos (owner)',
|
|
714
|
+
'CREATE INDEX idx_todos_completed ON todos (completed)',
|
|
715
|
+
'CREATE INDEX idx_todos_priority ON todos (priority)',
|
|
716
|
+
],
|
|
717
|
+
};
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
**Step 9: Commit**
|
|
721
|
+
|
|
722
|
+
```bash
|
|
723
|
+
git add host/src/lib/pocketbase/
|
|
724
|
+
git commit -m "feat: add all PocketBase collection definitions with multi-tenancy API rules"
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
---
|
|
728
|
+
|
|
729
|
+
## Task 4: Create Seed Data System
|
|
730
|
+
|
|
731
|
+
**Files:**
|
|
732
|
+
- Create: `host/src/lib/pocketbase/seed.ts`
|
|
733
|
+
- Create: `host/src/lib/pocketbase/seed/roles.ts`
|
|
734
|
+
- Create: `host/src/lib/pocketbase/seed/permissions.ts`
|
|
735
|
+
|
|
736
|
+
**Step 1: Create seed roles**
|
|
737
|
+
|
|
738
|
+
Create `host/src/lib/pocketbase/seed/roles.ts`:
|
|
739
|
+
|
|
740
|
+
```typescript
|
|
741
|
+
import type { Collection } from '../types';
|
|
742
|
+
|
|
743
|
+
// System roles that exist for every organization
|
|
744
|
+
export const systemRoles: Omit<Collection, 'type' | 'indexes'>[] = [
|
|
745
|
+
{
|
|
746
|
+
name: 'roles_owner',
|
|
747
|
+
slug: 'owner',
|
|
748
|
+
description: 'Full access to all resources',
|
|
749
|
+
isSystem: true,
|
|
750
|
+
fields: [] as any,
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
name: 'roles_admin',
|
|
754
|
+
slug: 'admin',
|
|
755
|
+
description: 'Can manage users, roles, and most settings',
|
|
756
|
+
isSystem: true,
|
|
757
|
+
fields: [] as any,
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
name: 'roles_member',
|
|
761
|
+
slug: 'member',
|
|
762
|
+
description: 'Standard user with basic access',
|
|
763
|
+
isSystem: true,
|
|
764
|
+
fields: [] as any,
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
name: 'roles_guest',
|
|
768
|
+
slug: 'guest',
|
|
769
|
+
description: 'Limited access, read-only',
|
|
770
|
+
isSystem: true,
|
|
771
|
+
fields: [] as any,
|
|
772
|
+
},
|
|
773
|
+
];
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
**Step 2: Create seed permissions**
|
|
777
|
+
|
|
778
|
+
Create `host/src/lib/pocketbase/seed/permissions.ts`:
|
|
779
|
+
|
|
780
|
+
```typescript
|
|
781
|
+
import type { Collection } from '../types';
|
|
782
|
+
|
|
783
|
+
export const systemPermissions: Omit<Collection, 'type' | 'indexes'>[] = [
|
|
784
|
+
// User management
|
|
785
|
+
{
|
|
786
|
+
name: 'permissions_users_view',
|
|
787
|
+
slug: 'users.view',
|
|
788
|
+
description: 'View users in organization',
|
|
789
|
+
resource: 'users',
|
|
790
|
+
action: 'view',
|
|
791
|
+
fields: [] as any,
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
name: 'permissions_users_create',
|
|
795
|
+
slug: 'users.create',
|
|
796
|
+
description: 'Invite users to organization',
|
|
797
|
+
resource: 'users',
|
|
798
|
+
action: 'create',
|
|
799
|
+
fields: [] as any,
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
name: 'permissions_users_update',
|
|
803
|
+
slug: 'users.update',
|
|
804
|
+
description: 'Update user information',
|
|
805
|
+
resource: 'users',
|
|
806
|
+
action: 'update',
|
|
807
|
+
fields: [] as any,
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
name: 'permissions_users_delete',
|
|
811
|
+
slug: 'users.delete',
|
|
812
|
+
description: 'Remove users from organization',
|
|
813
|
+
resource: 'users',
|
|
814
|
+
action: 'delete',
|
|
815
|
+
fields: [] as any,
|
|
816
|
+
},
|
|
817
|
+
|
|
818
|
+
// Role management
|
|
819
|
+
{
|
|
820
|
+
name: 'permissions_roles_view',
|
|
821
|
+
slug: 'roles.view',
|
|
822
|
+
description: 'View roles in organization',
|
|
823
|
+
resource: 'roles',
|
|
824
|
+
action: 'view',
|
|
825
|
+
fields: [] as any,
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
name: 'permissions_roles_create',
|
|
829
|
+
slug: 'roles.create',
|
|
830
|
+
description: 'Create new roles',
|
|
831
|
+
resource: 'roles',
|
|
832
|
+
action: 'create',
|
|
833
|
+
fields: [] as any,
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
name: 'permissions_roles_update',
|
|
837
|
+
slug: 'roles.update',
|
|
838
|
+
description: 'Update roles',
|
|
839
|
+
resource: 'roles',
|
|
840
|
+
action: 'update',
|
|
841
|
+
fields: [] as any,
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
name: 'permissions_roles_delete',
|
|
845
|
+
slug: 'roles.delete',
|
|
846
|
+
description: 'Delete roles',
|
|
847
|
+
resource: 'roles',
|
|
848
|
+
action: 'delete',
|
|
849
|
+
fields: [] as any,
|
|
850
|
+
},
|
|
851
|
+
|
|
852
|
+
// Permission management
|
|
853
|
+
{
|
|
854
|
+
name: 'permissions_permissions_view',
|
|
855
|
+
slug: 'permissions.view',
|
|
856
|
+
description: 'View permissions',
|
|
857
|
+
resource: 'permissions',
|
|
858
|
+
action: 'view',
|
|
859
|
+
fields: [] as any,
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
name: 'permissions_permissions_create',
|
|
863
|
+
slug: 'permissions.create',
|
|
864
|
+
description: 'Create permissions',
|
|
865
|
+
resource: 'permissions',
|
|
866
|
+
action: 'create',
|
|
867
|
+
fields: [] as any,
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
name: 'permissions_permissions_delete',
|
|
871
|
+
slug: 'permissions.delete',
|
|
872
|
+
description: 'Delete permissions',
|
|
873
|
+
resource: 'permissions',
|
|
874
|
+
action: 'delete',
|
|
875
|
+
fields: [] as any,
|
|
876
|
+
},
|
|
877
|
+
|
|
878
|
+
// Organization management
|
|
879
|
+
{
|
|
880
|
+
name: 'permissions_org_update',
|
|
881
|
+
slug: 'org.update',
|
|
882
|
+
description: 'Update organization settings',
|
|
883
|
+
resource: 'organization',
|
|
884
|
+
action: 'update',
|
|
885
|
+
fields: [] as any,
|
|
886
|
+
},
|
|
887
|
+
|
|
888
|
+
// Audit logs
|
|
889
|
+
{
|
|
890
|
+
name: 'permissions_audit_view',
|
|
891
|
+
slug: 'audit.view',
|
|
892
|
+
description: 'View audit logs',
|
|
893
|
+
resource: 'audit_logs',
|
|
894
|
+
action: 'view',
|
|
895
|
+
fields: [] as any,
|
|
896
|
+
},
|
|
897
|
+
|
|
898
|
+
// Todo management
|
|
899
|
+
{
|
|
900
|
+
name: 'permissions_todos_view',
|
|
901
|
+
slug: 'todos.view',
|
|
902
|
+
description: 'View todos',
|
|
903
|
+
resource: 'todos',
|
|
904
|
+
action: 'view',
|
|
905
|
+
fields: [] as any,
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
name: 'permissions_todos_manage',
|
|
909
|
+
slug: 'todos.manage',
|
|
910
|
+
description: 'Manage all todos (create, update, delete any)',
|
|
911
|
+
resource: 'todos',
|
|
912
|
+
action: 'manage',
|
|
913
|
+
fields: [] as any,
|
|
914
|
+
},
|
|
915
|
+
|
|
916
|
+
// Plugin management
|
|
917
|
+
{
|
|
918
|
+
name: 'permissions_plugins_view',
|
|
919
|
+
slug: 'plugins.view',
|
|
920
|
+
description: 'View enabled plugins',
|
|
921
|
+
resource: 'plugins',
|
|
922
|
+
action: 'view',
|
|
923
|
+
fields: [] as any,
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
name: 'permissions_plugins_manage',
|
|
927
|
+
slug: 'plugins.manage',
|
|
928
|
+
description: 'Enable/disable plugins',
|
|
929
|
+
resource: 'plugins',
|
|
930
|
+
action: 'manage',
|
|
931
|
+
fields: [] as any,
|
|
932
|
+
},
|
|
933
|
+
];
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
**Step 3: Create seed function**
|
|
937
|
+
|
|
938
|
+
Create `host/src/lib/pocketbase/seed.ts`:
|
|
939
|
+
|
|
940
|
+
```typescript
|
|
941
|
+
import PocketBase from 'pocketbase';
|
|
942
|
+
import { env } from '../../env/env.schema';
|
|
943
|
+
import { systemRoles } from './seed/roles';
|
|
944
|
+
import { systemPermissions } from './seed/permissions';
|
|
945
|
+
|
|
946
|
+
interface SeedData {
|
|
947
|
+
adminEmail: string;
|
|
948
|
+
adminPassword: string;
|
|
949
|
+
adminName: string;
|
|
950
|
+
orgName: string;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
export async function seedDatabase(pb: PocketBase, seedData: SeedData) {
|
|
954
|
+
console.log('[Seed] Starting database seeding...');
|
|
955
|
+
|
|
956
|
+
// Check if already seeded
|
|
957
|
+
const orgs = await pb.collections.getList(1, 1, {
|
|
958
|
+
filter: 'name = "organizations"',
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
if (orgs.totalItems > 0) {
|
|
962
|
+
const existingOrgs = await pb.records.getList('organizations', 1, 1);
|
|
963
|
+
if (existingOrgs.totalItems > 0) {
|
|
964
|
+
console.log('[Seed] ℹ Database already seeded, skipping...');
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
console.log('[Seed] → Creating admin user...');
|
|
970
|
+
|
|
971
|
+
// Create admin user
|
|
972
|
+
const admin = await pb.users.create({
|
|
973
|
+
email: seedData.adminEmail,
|
|
974
|
+
password: seedData.adminPassword,
|
|
975
|
+
passwordConfirm: seedData.adminPassword,
|
|
976
|
+
name: seedData.adminName,
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
console.log('[Seed] → Creating organization...');
|
|
980
|
+
|
|
981
|
+
// Create organization
|
|
982
|
+
const organization = await pb.records.create('organizations', {
|
|
983
|
+
name: seedData.orgName,
|
|
984
|
+
slug: seedData.orgName.toLowerCase().replace(/\s+/g, '-'),
|
|
985
|
+
ownerId: admin.id,
|
|
986
|
+
maxUsers: 10,
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
// Update admin with organization
|
|
990
|
+
await pb.users.update(admin.id, {
|
|
991
|
+
organizationId: organization.id,
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
console.log('[Seed] → Creating system roles...');
|
|
995
|
+
|
|
996
|
+
// Create system roles
|
|
997
|
+
const roleMap = new Map<string, string>();
|
|
998
|
+
|
|
999
|
+
for (const roleData of systemRoles) {
|
|
1000
|
+
const role = await pb.records.create('roles', {
|
|
1001
|
+
name: roleData.name,
|
|
1002
|
+
slug: roleData.slug,
|
|
1003
|
+
description: roleData.description,
|
|
1004
|
+
organizationId: organization.id,
|
|
1005
|
+
isSystem: roleData.isSystem,
|
|
1006
|
+
});
|
|
1007
|
+
roleMap.set(roleData.slug, role.id);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
console.log('[Seed] → Creating system permissions...');
|
|
1011
|
+
|
|
1012
|
+
// Create system permissions
|
|
1013
|
+
for (const permData of systemPermissions) {
|
|
1014
|
+
await pb.records.create('permissions', {
|
|
1015
|
+
name: permData.name,
|
|
1016
|
+
slug: permData.slug,
|
|
1017
|
+
description: permData.description,
|
|
1018
|
+
organizationId: organization.id,
|
|
1019
|
+
resource: permData.resource,
|
|
1020
|
+
action: permData.action,
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
console.log('[Seed] → Assigning Owner role to admin...');
|
|
1025
|
+
|
|
1026
|
+
// Assign Owner role to admin
|
|
1027
|
+
await pb.records.create('user_roles', {
|
|
1028
|
+
userId: admin.id,
|
|
1029
|
+
roleId: roleMap.get('owner')!,
|
|
1030
|
+
organizationId: organization.id,
|
|
1031
|
+
assignedBy: admin.id,
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
console.log('[Seed] → Creating sample todos...');
|
|
1035
|
+
|
|
1036
|
+
// Create sample todos
|
|
1037
|
+
await pb.records.create('todos', {
|
|
1038
|
+
title: 'Welcome to Lego-One! 👋',
|
|
1039
|
+
description: 'This is your first todo item. Try editing or completing it!',
|
|
1040
|
+
completed: false,
|
|
1041
|
+
priority: 'medium',
|
|
1042
|
+
owner: admin.id,
|
|
1043
|
+
organizationId: organization.id,
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
await pb.records.create('todos', {
|
|
1047
|
+
title: 'Explore the Dashboard',
|
|
1048
|
+
description: 'Check out the Dashboard plugin to see your stats and activity.',
|
|
1049
|
+
completed: false,
|
|
1050
|
+
priority: 'low',
|
|
1051
|
+
owner: admin.id,
|
|
1052
|
+
organizationId: organization.id,
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
await pb.records.create('todos', {
|
|
1056
|
+
title: 'Manage your Organization',
|
|
1057
|
+
description: 'Go to Settings to invite team members and configure roles.',
|
|
1058
|
+
completed: false,
|
|
1059
|
+
priority: 'high',
|
|
1060
|
+
owner: admin.id,
|
|
1061
|
+
organizationId: organization.id,
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
console.log('[Seed] ✅ Database seeded successfully!');
|
|
1065
|
+
console.log('');
|
|
1066
|
+
console.log('📧 Login credentials:');
|
|
1067
|
+
console.log(` Email: ${seedData.adminEmail}`);
|
|
1068
|
+
console.log(` Password: ${seedData.adminPassword}`);
|
|
1069
|
+
}
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
**Step 4: Commit**
|
|
1073
|
+
|
|
1074
|
+
```bash
|
|
1075
|
+
git add host/src/lib/pocketbase/seed/
|
|
1076
|
+
git commit -m "feat: add database seeding system with roles, permissions, and sample data"
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
---
|
|
1080
|
+
|
|
1081
|
+
## Task 5: Create PocketBase Client Initialization
|
|
1082
|
+
|
|
1083
|
+
**Files:**
|
|
1084
|
+
- Create: `host/src/lib/pocketbase/client.ts`
|
|
1085
|
+
- Create: `host/src/lib/pocketbase/index.ts`
|
|
1086
|
+
|
|
1087
|
+
**Step 1: Create PocketBase client**
|
|
1088
|
+
|
|
1089
|
+
Create `host/src/lib/pocketbase/client.ts`:
|
|
1090
|
+
|
|
1091
|
+
```typescript
|
|
1092
|
+
import PocketBase from 'pocketbase';
|
|
1093
|
+
import { env } from '../../env/env.schema';
|
|
1094
|
+
|
|
1095
|
+
let pbInstance: PocketBase | null = null;
|
|
1096
|
+
|
|
1097
|
+
export function getPocketBase(): PocketBase {
|
|
1098
|
+
if (pbInstance) {
|
|
1099
|
+
return pbInstance;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
const pb = new PocketBase(env.VITE_POCKETBASE_URL);
|
|
1103
|
+
|
|
1104
|
+
// Auto-authenticate as admin for server-side operations
|
|
1105
|
+
if (typeof window === 'undefined') {
|
|
1106
|
+
pb.admins.authViaPassword(
|
|
1107
|
+
env.VITE_POCKETBASE_ADMIN_EMAIL,
|
|
1108
|
+
env.VITE_POCKETBASE_ADMIN_PASSWORD
|
|
1109
|
+
).catch((err) => {
|
|
1110
|
+
console.error('Failed to authenticate with PocketBase:', err);
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
pbInstance = pb;
|
|
1115
|
+
return pb;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
export function getPocketBaseAdmin(): PocketBase {
|
|
1119
|
+
const pb = new PocketBase(env.VITE_POCKETBASE_URL);
|
|
1120
|
+
|
|
1121
|
+
// This must be called with admin credentials
|
|
1122
|
+
return pb;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// Reset singleton (useful for testing)
|
|
1126
|
+
export function resetPocketBase(): void {
|
|
1127
|
+
pbInstance = null;
|
|
1128
|
+
}
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
**Step 2: Create barrel export**
|
|
1132
|
+
|
|
1133
|
+
Create `host/src/lib/pocketbase/index.ts`:
|
|
1134
|
+
|
|
1135
|
+
```typescript
|
|
1136
|
+
export * from './client';
|
|
1137
|
+
export * from './types';
|
|
1138
|
+
export * from './migrations';
|
|
1139
|
+
export * from './collections';
|
|
1140
|
+
export * from './seed';
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
**Step 3: Commit**
|
|
1144
|
+
|
|
1145
|
+
```bash
|
|
1146
|
+
git add host/src/lib/pocketbase/
|
|
1147
|
+
git commit -m "feat: add PocketBase client initialization and exports"
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
---
|
|
1151
|
+
|
|
1152
|
+
## Task 6: Create Startup Migration Hook
|
|
1153
|
+
|
|
1154
|
+
**Files:**
|
|
1155
|
+
- Create: `host/src/kernel/use-migrations.ts`
|
|
1156
|
+
|
|
1157
|
+
**Step 1: Create migration hook**
|
|
1158
|
+
|
|
1159
|
+
Create `host/src/kernel/use-migrations.ts`:
|
|
1160
|
+
|
|
1161
|
+
```typescript
|
|
1162
|
+
import { useEffect, useState } from 'react';
|
|
1163
|
+
import { getPocketBaseAdmin } from '@lib/pocketbase';
|
|
1164
|
+
import { runMigrations } from '@lib/pocketbase/migrations';
|
|
1165
|
+
import { collections } from '@lib/pocketbase/collections';
|
|
1166
|
+
import { seedDatabase } from '@lib/pocketbase/seed';
|
|
1167
|
+
import { env } from '@env/env.schema';
|
|
1168
|
+
|
|
1169
|
+
interface MigrationState {
|
|
1170
|
+
status: 'pending' | 'running' | 'success' | 'error';
|
|
1171
|
+
message: string | null;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
export function useMigrations() {
|
|
1175
|
+
const [state, setState] = useState<MigrationState>({
|
|
1176
|
+
status: 'pending',
|
|
1177
|
+
message: null,
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
useEffect(() => {
|
|
1181
|
+
let cancelled = false;
|
|
1182
|
+
|
|
1183
|
+
async function run() {
|
|
1184
|
+
if (cancelled) return;
|
|
1185
|
+
|
|
1186
|
+
setState({ status: 'running', message: 'Running database migrations...' });
|
|
1187
|
+
|
|
1188
|
+
try {
|
|
1189
|
+
const pb = getPocketBaseAdmin();
|
|
1190
|
+
|
|
1191
|
+
// Authenticate as admin
|
|
1192
|
+
await pb.admins.authWithPassword(
|
|
1193
|
+
env.VITE_POCKETBASE_ADMIN_EMAIL,
|
|
1194
|
+
env.VITE_POCKETBASE_ADMIN_PASSWORD
|
|
1195
|
+
);
|
|
1196
|
+
|
|
1197
|
+
if (cancelled) return;
|
|
1198
|
+
|
|
1199
|
+
// Run migrations
|
|
1200
|
+
await runMigrations(pb, collections);
|
|
1201
|
+
|
|
1202
|
+
if (cancelled) return;
|
|
1203
|
+
|
|
1204
|
+
// Seed database (only if in development or explicitly requested)
|
|
1205
|
+
if (env.VITE_SEED_ADMIN_EMAIL) {
|
|
1206
|
+
setState({ status: 'running', message: 'Seeding database...' });
|
|
1207
|
+
|
|
1208
|
+
await seedDatabase(pb, {
|
|
1209
|
+
adminEmail: env.VITE_SEED_ADMIN_EMAIL,
|
|
1210
|
+
adminPassword: env.VITE_SEED_ADMIN_PASSWORD || 'admin123',
|
|
1211
|
+
adminName: env.VITE_SEED_ADMIN_NAME || 'Super Admin',
|
|
1212
|
+
orgName: env.VITE_SEED_ORG_NAME || 'Demo Organization',
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if (cancelled) return;
|
|
1217
|
+
|
|
1218
|
+
setState({ status: 'success', message: 'Database ready!' });
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
1221
|
+
setState({ status: 'error', message });
|
|
1222
|
+
console.error('[Migrations] Error:', error);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
run();
|
|
1227
|
+
|
|
1228
|
+
return () => {
|
|
1229
|
+
cancelled = true;
|
|
1230
|
+
};
|
|
1231
|
+
}, []);
|
|
1232
|
+
|
|
1233
|
+
return state;
|
|
1234
|
+
}
|
|
1235
|
+
```
|
|
1236
|
+
|
|
1237
|
+
**Step 2: Commit**
|
|
1238
|
+
|
|
1239
|
+
```bash
|
|
1240
|
+
git add host/src/kernel/use-migrations.ts
|
|
1241
|
+
git commit -m "feat: add database migration hook for startup"
|
|
1242
|
+
```
|
|
1243
|
+
|
|
1244
|
+
---
|
|
1245
|
+
|
|
1246
|
+
## Task 7: Create PocketBase Provider
|
|
1247
|
+
|
|
1248
|
+
**Files:**
|
|
1249
|
+
- Create: `host/src/providers/PocketBaseProvider.tsx`
|
|
1250
|
+
|
|
1251
|
+
**Step 1: Create PocketBase context provider**
|
|
1252
|
+
|
|
1253
|
+
Create `host/src/providers/PocketBaseProvider.tsx`:
|
|
1254
|
+
|
|
1255
|
+
```typescript
|
|
1256
|
+
import { createContext, useContext, ReactNode } from 'react';
|
|
1257
|
+
import { getPocketBase, resetPocketBase } from '@lib/pocketbase';
|
|
1258
|
+
import type { PocketBase } from 'pocketbase';
|
|
1259
|
+
|
|
1260
|
+
const PocketBaseContext = createContext<PocketBase | null>(null);
|
|
1261
|
+
|
|
1262
|
+
export function PocketBaseProvider({ children }: { children: ReactNode }) {
|
|
1263
|
+
const pb = getPocketBase();
|
|
1264
|
+
|
|
1265
|
+
return (
|
|
1266
|
+
<PocketBaseContext.Provider value={pb}>
|
|
1267
|
+
{children}
|
|
1268
|
+
</PocketBaseContext.Provider>
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
export function usePocketBase(): PocketBase {
|
|
1273
|
+
const pb = useContext(PocketBaseContext);
|
|
1274
|
+
|
|
1275
|
+
if (!pb) {
|
|
1276
|
+
throw new Error('usePocketBase must be used within PocketBaseProvider');
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
return pb;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
export function usePocketBaseAdmin() {
|
|
1283
|
+
return getPocketBaseAdmin();
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// For testing
|
|
1287
|
+
export { resetPocketBase as resetPocketBaseForTesting };
|
|
1288
|
+
```
|
|
1289
|
+
|
|
1290
|
+
**Step 2: Commit**
|
|
1291
|
+
|
|
1292
|
+
```bash
|
|
1293
|
+
git add host/src/providers/
|
|
1294
|
+
git commit -m "feat: add PocketBase context provider"
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
---
|
|
1298
|
+
|
|
1299
|
+
## Task 8: Create Initial saas.config.ts
|
|
1300
|
+
|
|
1301
|
+
**Files:**
|
|
1302
|
+
- Create: `saas.config.ts`
|
|
1303
|
+
|
|
1304
|
+
**Step 1: Create SaaS configuration**
|
|
1305
|
+
|
|
1306
|
+
Create `saas.config.ts`:
|
|
1307
|
+
|
|
1308
|
+
```typescript
|
|
1309
|
+
import { defineConfig } from '@lego/kernel/config';
|
|
1310
|
+
|
|
1311
|
+
export default defineConfig({
|
|
1312
|
+
plugins: [
|
|
1313
|
+
{
|
|
1314
|
+
name: '@lego/plugin-dashboard',
|
|
1315
|
+
enabled: true,
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
name: '@lego/plugin-todo',
|
|
1319
|
+
enabled: true,
|
|
1320
|
+
},
|
|
1321
|
+
],
|
|
1322
|
+
});
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
**Step 2: Commit**
|
|
1326
|
+
|
|
1327
|
+
```bash
|
|
1328
|
+
git add saas.config.ts
|
|
1329
|
+
git commit -m "chore: add initial SaaS plugin configuration"
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
---
|
|
1333
|
+
|
|
1334
|
+
## Verification
|
|
1335
|
+
|
|
1336
|
+
After completing all tasks:
|
|
1337
|
+
|
|
1338
|
+
**Step 1: Verify file structure**
|
|
1339
|
+
|
|
1340
|
+
Run:
|
|
1341
|
+
```bash
|
|
1342
|
+
ls -la host/src/lib/pocketbase/
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
Expected: `types.ts`, `migrations.ts`, `collections/index.ts`, `seed.ts`, `client.ts`, `seed/` folder
|
|
1346
|
+
|
|
1347
|
+
**Step 2: Verify imports compile**
|
|
1348
|
+
|
|
1349
|
+
Run:
|
|
1350
|
+
```bash
|
|
1351
|
+
cd host && pnpm install && pnpm typecheck
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
Expected: Will fail because workspace doesn't exist yet, but should not have TypeScript errors in created files.
|
|
1355
|
+
|
|
1356
|
+
**Step 3: Verify migrations compile**
|
|
1357
|
+
|
|
1358
|
+
No errors in TypeScript files created.
|
|
1359
|
+
|
|
1360
|
+
---
|
|
1361
|
+
|
|
1362
|
+
## Summary
|
|
1363
|
+
|
|
1364
|
+
After completing this document, you have:
|
|
1365
|
+
|
|
1366
|
+
✅ PocketBase downloaded and configured
|
|
1367
|
+
✅ All 7 collections defined (users, organizations, roles, permissions, user_roles, audit_logs, todos)
|
|
1368
|
+
✅ Multi-tenancy API rules on all collections
|
|
1369
|
+
✅ Migration system to create collections on startup
|
|
1370
|
+
✅ Seed data system with admin + org + roles + permissions + sample todos
|
|
1371
|
+
✅ PocketBase client with singleton pattern
|
|
1372
|
+
✅ PocketBase context provider for React
|
|
1373
|
+
|
|
1374
|
+
**Next:** → [`03-host-kernel.md`](./03-host-kernel.md)
|