@jskit-ai/users-core 0.1.33 → 0.1.35
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/package.descriptor.mjs +16 -245
- package/package.json +7 -7
- package/src/server/UsersCoreServiceProvider.js +4 -28
- package/src/server/UsersWorkspacesServiceProvider.js +44 -0
- package/src/server/common/registerCommonRepositories.js +0 -19
- package/src/server/common/repositories/userSettingsRepository.js +1 -12
- package/src/server/registerUsersBootstrap.js +22 -0
- package/src/server/registerUsersCore.js +30 -0
- package/src/server/registerWorkspaceBootstrap.js +2 -5
- package/src/server/registerWorkspaceCore.js +1 -16
- package/src/server/registerWorkspaceRepositories.js +26 -0
- package/src/server/usersBootstrapContributor.js +248 -0
- package/src/server/workspaceBootstrapContributor.js +63 -257
- package/src/shared/settings.js +1 -2
- package/templates/migrations/users_core_generic_initial.cjs +69 -0
- package/test/registerUsersCore.test.js +42 -0
- package/test/usersBootstrapContributor.test.js +172 -0
- package/test/usersRouteRequestInputValidator.test.js +7 -390
- package/test/workspaceBootstrapContributor.test.js +31 -343
- package/test-support/registerDefaultSettingsFields.js +1 -1
- package/templates/config/roles.js +0 -27
- package/templates/migrations/users_core_initial.cjs +0 -123
- package/templates/migrations/users_core_workspace_settings_single_name_source.cjs +0 -71
- package/templates/migrations/users_core_workspaces_drop_color.cjs +0 -85
- package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +0 -197
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { createWorkspaceBootstrapContributor } from "../src/server/workspaceBootstrapContributor.js";
|
|
4
|
-
import {
|
|
5
|
-
TENANCY_MODE_PERSONAL,
|
|
6
|
-
WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
|
|
7
|
-
} from "../src/shared/tenancyProfile.js";
|
|
8
4
|
|
|
9
5
|
function createAuthenticatedProfile(overrides = {}) {
|
|
10
6
|
return {
|
|
@@ -40,26 +36,10 @@ test("workspace bootstrap contributor passes actor context to pending invites se
|
|
|
40
36
|
}
|
|
41
37
|
},
|
|
42
38
|
usersRepository: {
|
|
43
|
-
async
|
|
39
|
+
async findById() {
|
|
44
40
|
return profile;
|
|
45
41
|
}
|
|
46
42
|
},
|
|
47
|
-
userSettingsRepository: {
|
|
48
|
-
async ensureForUserId() {
|
|
49
|
-
return {
|
|
50
|
-
theme: "system",
|
|
51
|
-
locale: "en",
|
|
52
|
-
timeZone: "UTC",
|
|
53
|
-
dateFormat: "YYYY-MM-DD",
|
|
54
|
-
numberFormat: "1,234.56",
|
|
55
|
-
currencyCode: "USD",
|
|
56
|
-
avatarSize: 64,
|
|
57
|
-
productUpdates: true,
|
|
58
|
-
accountActivity: true,
|
|
59
|
-
securityAlerts: true
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
43
|
workspaceInvitationsEnabled: true,
|
|
64
44
|
appConfig: {
|
|
65
45
|
tenancyMode: "workspaces"
|
|
@@ -67,15 +47,12 @@ test("workspace bootstrap contributor passes actor context to pending invites se
|
|
|
67
47
|
});
|
|
68
48
|
|
|
69
49
|
await contributor.contribute({
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
profile
|
|
75
|
-
};
|
|
50
|
+
payload: {
|
|
51
|
+
session: {
|
|
52
|
+
authenticated: true,
|
|
53
|
+
userId: profile.id
|
|
76
54
|
}
|
|
77
|
-
}
|
|
78
|
-
reply: {}
|
|
55
|
+
}
|
|
79
56
|
});
|
|
80
57
|
|
|
81
58
|
assert.equal(pendingServiceCalls.length, 1);
|
|
@@ -83,133 +60,6 @@ test("workspace bootstrap contributor passes actor context to pending invites se
|
|
|
83
60
|
assert.equal(pendingServiceCalls[0].options?.context?.actor?.id, profile.id);
|
|
84
61
|
});
|
|
85
62
|
|
|
86
|
-
test("workspace bootstrap contributor seeds the initial console owner on authenticated bootstrap", async () => {
|
|
87
|
-
const profile = createAuthenticatedProfile({ id: 12 });
|
|
88
|
-
const consoleOwnerSeeds = [];
|
|
89
|
-
|
|
90
|
-
const contributor = createWorkspaceBootstrapContributor({
|
|
91
|
-
workspaceService: {
|
|
92
|
-
async listWorkspacesForUser() {
|
|
93
|
-
return [];
|
|
94
|
-
},
|
|
95
|
-
async resolveWorkspaceContextForUserBySlug() {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
workspacePendingInvitationsService: {
|
|
100
|
-
async listPendingInvitesForUser() {
|
|
101
|
-
return [];
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
usersRepository: {
|
|
105
|
-
async findByIdentity() {
|
|
106
|
-
return profile;
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
userSettingsRepository: {
|
|
110
|
-
async ensureForUserId() {
|
|
111
|
-
return {
|
|
112
|
-
theme: "system",
|
|
113
|
-
locale: "en",
|
|
114
|
-
timeZone: "UTC",
|
|
115
|
-
dateFormat: "YYYY-MM-DD",
|
|
116
|
-
numberFormat: "1,234.56",
|
|
117
|
-
currencyCode: "USD",
|
|
118
|
-
avatarSize: 64,
|
|
119
|
-
productUpdates: true,
|
|
120
|
-
accountActivity: true,
|
|
121
|
-
securityAlerts: true
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
workspaceInvitationsEnabled: false,
|
|
126
|
-
consoleService: {
|
|
127
|
-
async ensureInitialConsoleMember(userId) {
|
|
128
|
-
consoleOwnerSeeds.push(Number(userId));
|
|
129
|
-
return Number(userId);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
const payload = await contributor.contribute({
|
|
135
|
-
request: {
|
|
136
|
-
async executeAction() {
|
|
137
|
-
return {
|
|
138
|
-
authenticated: true,
|
|
139
|
-
profile
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
reply: {}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
assert.deepEqual(consoleOwnerSeeds, [12]);
|
|
147
|
-
assert.equal(payload.surfaceAccess?.consoleowner, true);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test("workspace bootstrap contributor emits canonical tenancy profile from users-core", async () => {
|
|
151
|
-
const contributor = createWorkspaceBootstrapContributor({
|
|
152
|
-
workspaceService: {
|
|
153
|
-
async listWorkspacesForUser() {
|
|
154
|
-
return [];
|
|
155
|
-
},
|
|
156
|
-
async resolveWorkspaceContextForUserBySlug() {
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
workspacePendingInvitationsService: {
|
|
161
|
-
async listPendingInvitesForUser() {
|
|
162
|
-
return [];
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
usersRepository: {
|
|
166
|
-
async findByIdentity() {
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
userSettingsRepository: {
|
|
171
|
-
async ensureForUserId() {
|
|
172
|
-
return {};
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
workspaceInvitationsEnabled: false,
|
|
176
|
-
tenancyProfile: {
|
|
177
|
-
mode: TENANCY_MODE_PERSONAL,
|
|
178
|
-
workspace: {
|
|
179
|
-
enabled: true,
|
|
180
|
-
autoProvision: true,
|
|
181
|
-
allowSelfCreate: false,
|
|
182
|
-
slugPolicy: WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
appConfig: {
|
|
186
|
-
tenancyMode: "none"
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const payload = await contributor.contribute({
|
|
191
|
-
request: {
|
|
192
|
-
async executeAction() {
|
|
193
|
-
return {
|
|
194
|
-
authenticated: false
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
reply: {}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
assert.deepEqual(payload.tenancy, {
|
|
202
|
-
mode: TENANCY_MODE_PERSONAL,
|
|
203
|
-
workspace: {
|
|
204
|
-
enabled: true,
|
|
205
|
-
autoProvision: true,
|
|
206
|
-
allowSelfCreate: false,
|
|
207
|
-
slugPolicy: WORKSPACE_SLUG_POLICY_IMMUTABLE_USERNAME
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
assert.equal(payload.app.tenancyMode, undefined);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
63
|
test("workspace bootstrap contributor resolves workspace slug from bootstrap query", async () => {
|
|
214
64
|
const profile = createAuthenticatedProfile();
|
|
215
65
|
const calls = [];
|
|
@@ -229,162 +79,56 @@ test("workspace bootstrap contributor resolves workspace slug from bootstrap que
|
|
|
229
79
|
}
|
|
230
80
|
},
|
|
231
81
|
usersRepository: {
|
|
232
|
-
async
|
|
82
|
+
async findById() {
|
|
233
83
|
return profile;
|
|
234
84
|
}
|
|
235
85
|
},
|
|
236
|
-
userSettingsRepository: {
|
|
237
|
-
async ensureForUserId() {
|
|
238
|
-
return {
|
|
239
|
-
theme: "system",
|
|
240
|
-
locale: "en",
|
|
241
|
-
timeZone: "UTC",
|
|
242
|
-
dateFormat: "YYYY-MM-DD",
|
|
243
|
-
numberFormat: "1,234.56",
|
|
244
|
-
currencyCode: "USD",
|
|
245
|
-
avatarSize: 64,
|
|
246
|
-
productUpdates: true,
|
|
247
|
-
accountActivity: true,
|
|
248
|
-
securityAlerts: true
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
},
|
|
252
86
|
workspaceInvitationsEnabled: true,
|
|
253
87
|
appConfig: {
|
|
254
88
|
tenancyMode: "workspaces"
|
|
255
89
|
}
|
|
256
90
|
});
|
|
257
91
|
|
|
258
|
-
await contributor.contribute({
|
|
92
|
+
const payload = await contributor.contribute({
|
|
259
93
|
query: {
|
|
260
94
|
workspaceSlug: " AcMe "
|
|
261
95
|
},
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
profile
|
|
267
|
-
};
|
|
96
|
+
payload: {
|
|
97
|
+
session: {
|
|
98
|
+
authenticated: true,
|
|
99
|
+
userId: profile.id
|
|
268
100
|
}
|
|
269
|
-
},
|
|
270
|
-
reply: {}
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
assert.deepEqual(calls, ["acme"]);
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
test("workspace bootstrap contributor returns global payload with requestedWorkspace=forbidden when slug access is denied", async () => {
|
|
277
|
-
const profile = createAuthenticatedProfile();
|
|
278
|
-
const contributor = createWorkspaceBootstrapContributor({
|
|
279
|
-
workspaceService: {
|
|
280
|
-
async listWorkspacesForUser() {
|
|
281
|
-
return [{ id: 3, slug: "chiara", name: "Chiara Workspace" }];
|
|
282
|
-
},
|
|
283
|
-
async resolveWorkspaceContextForUserBySlug() {
|
|
284
|
-
const error = new Error("Forbidden.");
|
|
285
|
-
error.status = 403;
|
|
286
|
-
throw error;
|
|
287
|
-
}
|
|
288
|
-
},
|
|
289
|
-
workspacePendingInvitationsService: {
|
|
290
|
-
async listPendingInvitesForUser() {
|
|
291
|
-
return [];
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
usersRepository: {
|
|
295
|
-
async findByIdentity() {
|
|
296
|
-
return profile;
|
|
297
|
-
}
|
|
298
|
-
},
|
|
299
|
-
userSettingsRepository: {
|
|
300
|
-
async ensureForUserId() {
|
|
301
|
-
return {
|
|
302
|
-
theme: "system",
|
|
303
|
-
locale: "en",
|
|
304
|
-
timeZone: "UTC",
|
|
305
|
-
dateFormat: "YYYY-MM-DD",
|
|
306
|
-
numberFormat: "1,234.56",
|
|
307
|
-
currencyCode: "USD",
|
|
308
|
-
avatarSize: 64,
|
|
309
|
-
productUpdates: true,
|
|
310
|
-
accountActivity: true,
|
|
311
|
-
securityAlerts: true
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
},
|
|
315
|
-
workspaceInvitationsEnabled: true,
|
|
316
|
-
appConfig: {
|
|
317
|
-
tenancyMode: "workspaces"
|
|
318
101
|
}
|
|
319
102
|
});
|
|
320
103
|
|
|
321
|
-
|
|
322
|
-
query: {
|
|
323
|
-
workspaceSlug: "tonymobily"
|
|
324
|
-
},
|
|
325
|
-
request: {
|
|
326
|
-
async executeAction() {
|
|
327
|
-
return {
|
|
328
|
-
authenticated: true,
|
|
329
|
-
profile
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
},
|
|
333
|
-
reply: {}
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
assert.equal(payload.session.authenticated, true);
|
|
337
|
-
assert.deepEqual(payload.workspaces, [{ id: 3, slug: "chiara", name: "Chiara Workspace" }]);
|
|
104
|
+
assert.deepEqual(calls, ["acme"]);
|
|
338
105
|
assert.deepEqual(payload.requestedWorkspace, {
|
|
339
|
-
slug: "
|
|
340
|
-
status: "
|
|
106
|
+
slug: "acme",
|
|
107
|
+
status: "resolved"
|
|
341
108
|
});
|
|
342
|
-
assert.equal(payload.activeWorkspace, null);
|
|
343
|
-
assert.equal(payload.membership, null);
|
|
344
|
-
assert.deepEqual(payload.permissions, []);
|
|
345
|
-
assert.equal(payload.workspaceSettings, null);
|
|
346
109
|
});
|
|
347
110
|
|
|
348
|
-
test("workspace bootstrap contributor
|
|
349
|
-
const profile = createAuthenticatedProfile();
|
|
111
|
+
test("workspace bootstrap contributor reports unauthenticated requested workspace without generic bootstrap work", async () => {
|
|
350
112
|
const contributor = createWorkspaceBootstrapContributor({
|
|
351
113
|
workspaceService: {
|
|
352
114
|
async listWorkspacesForUser() {
|
|
353
|
-
|
|
115
|
+
assert.fail("listWorkspacesForUser should not run for unauthenticated payloads");
|
|
354
116
|
},
|
|
355
117
|
async resolveWorkspaceContextForUserBySlug() {
|
|
356
|
-
|
|
357
|
-
error.status = 404;
|
|
358
|
-
throw error;
|
|
118
|
+
assert.fail("resolveWorkspaceContextForUserBySlug should not run for unauthenticated payloads");
|
|
359
119
|
}
|
|
360
120
|
},
|
|
361
121
|
workspacePendingInvitationsService: {
|
|
362
122
|
async listPendingInvitesForUser() {
|
|
363
|
-
|
|
123
|
+
assert.fail("listPendingInvitesForUser should not run for unauthenticated payloads");
|
|
364
124
|
}
|
|
365
125
|
},
|
|
366
126
|
usersRepository: {
|
|
367
|
-
async
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
},
|
|
371
|
-
userSettingsRepository: {
|
|
372
|
-
async ensureForUserId() {
|
|
373
|
-
return {
|
|
374
|
-
theme: "system",
|
|
375
|
-
locale: "en",
|
|
376
|
-
timeZone: "UTC",
|
|
377
|
-
dateFormat: "YYYY-MM-DD",
|
|
378
|
-
numberFormat: "1,234.56",
|
|
379
|
-
currencyCode: "USD",
|
|
380
|
-
avatarSize: 64,
|
|
381
|
-
productUpdates: true,
|
|
382
|
-
accountActivity: true,
|
|
383
|
-
securityAlerts: true
|
|
384
|
-
};
|
|
127
|
+
async findById() {
|
|
128
|
+
assert.fail("findById should not run for unauthenticated payloads");
|
|
385
129
|
}
|
|
386
130
|
},
|
|
387
|
-
workspaceInvitationsEnabled:
|
|
131
|
+
workspaceInvitationsEnabled: true,
|
|
388
132
|
appConfig: {
|
|
389
133
|
tenancyMode: "workspaces"
|
|
390
134
|
}
|
|
@@ -392,75 +136,19 @@ test("workspace bootstrap contributor returns requestedWorkspace=not_found when
|
|
|
392
136
|
|
|
393
137
|
const payload = await contributor.contribute({
|
|
394
138
|
query: {
|
|
395
|
-
workspaceSlug: "
|
|
139
|
+
workspaceSlug: "AcMe"
|
|
396
140
|
},
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
authenticated: true,
|
|
401
|
-
profile
|
|
402
|
-
};
|
|
141
|
+
payload: {
|
|
142
|
+
session: {
|
|
143
|
+
authenticated: false
|
|
403
144
|
}
|
|
404
|
-
},
|
|
405
|
-
reply: {}
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
assert.deepEqual(payload.requestedWorkspace, {
|
|
409
|
-
slug: "missing-workspace",
|
|
410
|
-
status: "not_found"
|
|
411
|
-
});
|
|
412
|
-
assert.deepEqual(payload.workspaces, [{ id: 1, slug: "acme", name: "Acme Workspace" }]);
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
test("workspace bootstrap contributor returns requestedWorkspace=unauthenticated for anonymous workspace slug query", async () => {
|
|
416
|
-
const contributor = createWorkspaceBootstrapContributor({
|
|
417
|
-
workspaceService: {
|
|
418
|
-
async listWorkspacesForUser() {
|
|
419
|
-
return [];
|
|
420
|
-
},
|
|
421
|
-
async resolveWorkspaceContextForUserBySlug() {
|
|
422
|
-
return null;
|
|
423
|
-
}
|
|
424
|
-
},
|
|
425
|
-
workspacePendingInvitationsService: {
|
|
426
|
-
async listPendingInvitesForUser() {
|
|
427
|
-
return [];
|
|
428
|
-
}
|
|
429
|
-
},
|
|
430
|
-
usersRepository: {
|
|
431
|
-
async findByIdentity() {
|
|
432
|
-
return null;
|
|
433
|
-
}
|
|
434
|
-
},
|
|
435
|
-
userSettingsRepository: {
|
|
436
|
-
async ensureForUserId() {
|
|
437
|
-
return {};
|
|
438
|
-
}
|
|
439
|
-
},
|
|
440
|
-
workspaceInvitationsEnabled: false,
|
|
441
|
-
appConfig: {
|
|
442
|
-
tenancyMode: "workspaces"
|
|
443
145
|
}
|
|
444
146
|
});
|
|
445
147
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
async executeAction() {
|
|
452
|
-
return {
|
|
453
|
-
authenticated: false
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
},
|
|
457
|
-
reply: {}
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
assert.equal(payload.session.authenticated, false);
|
|
461
|
-
assert.deepEqual(payload.requestedWorkspace, {
|
|
462
|
-
slug: "tonymobily",
|
|
463
|
-
status: "unauthenticated"
|
|
148
|
+
assert.deepEqual(payload, {
|
|
149
|
+
requestedWorkspace: {
|
|
150
|
+
slug: "acme",
|
|
151
|
+
status: "unauthenticated"
|
|
152
|
+
}
|
|
464
153
|
});
|
|
465
|
-
assert.deepEqual(payload.workspaces, []);
|
|
466
154
|
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "
|
|
1
|
+
import "../../workspaces-core/templates/packages/main/src/shared/resources/workspaceSettingsFields.js";
|
|
2
2
|
import "../templates/packages/main/src/shared/resources/consoleSettingsFields.js";
|
|
3
3
|
import "../templates/packages/main/src/shared/resources/userSettingsFields.js";
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export const roleCatalog = {
|
|
2
|
-
workspace: {
|
|
3
|
-
defaultInviteRole: "member"
|
|
4
|
-
},
|
|
5
|
-
roles: {
|
|
6
|
-
owner: {
|
|
7
|
-
assignable: false,
|
|
8
|
-
permissions: ["*"]
|
|
9
|
-
},
|
|
10
|
-
admin: {
|
|
11
|
-
assignable: true,
|
|
12
|
-
inherits: "member",
|
|
13
|
-
permissions: [
|
|
14
|
-
"workspace.roles.view",
|
|
15
|
-
"workspace.settings.update",
|
|
16
|
-
"workspace.members.view",
|
|
17
|
-
"workspace.members.invite",
|
|
18
|
-
"workspace.members.manage",
|
|
19
|
-
"workspace.invites.revoke"
|
|
20
|
-
]
|
|
21
|
-
},
|
|
22
|
-
member: {
|
|
23
|
-
assignable: true,
|
|
24
|
-
permissions: ["workspace.settings.view"]
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
};
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @param {import('knex').Knex} knex
|
|
3
|
-
*/
|
|
4
|
-
exports.up = async function up(knex) {
|
|
5
|
-
await knex.schema.createTable("users", (table) => {
|
|
6
|
-
table.increments("id").primary();
|
|
7
|
-
table.string("auth_provider", 64).notNullable();
|
|
8
|
-
table.string("auth_provider_user_sid", 191).notNullable();
|
|
9
|
-
table.string("email", 255).notNullable();
|
|
10
|
-
table.string("username", 120).notNullable();
|
|
11
|
-
table.string("display_name", 160).notNullable();
|
|
12
|
-
table.string("avatar_storage_key", 512).nullable();
|
|
13
|
-
table.string("avatar_version", 64).nullable();
|
|
14
|
-
table.timestamp("avatar_updated_at", { useTz: false }).nullable();
|
|
15
|
-
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
16
|
-
table.unique(["auth_provider", "auth_provider_user_sid"], "uq_users_identity");
|
|
17
|
-
table.unique(["email"], "uq_users_email");
|
|
18
|
-
table.unique(["username"], "uq_users_username");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
await knex.schema.createTable("workspaces", (table) => {
|
|
22
|
-
table.increments("id").primary();
|
|
23
|
-
table.string("slug", 120).notNullable().unique();
|
|
24
|
-
table.string("name", 160).notNullable();
|
|
25
|
-
table.integer("owner_user_id").unsigned().notNullable().references("id").inTable("users").onDelete("CASCADE");
|
|
26
|
-
table.boolean("is_personal").notNullable().defaultTo(true);
|
|
27
|
-
table.string("avatar_url", 512).notNullable().defaultTo("");
|
|
28
|
-
table.string("color", 7).notNullable().defaultTo("#1867C0");
|
|
29
|
-
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
30
|
-
table.timestamp("updated_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
31
|
-
table.timestamp("deleted_at", { useTz: false }).nullable();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
await knex.schema.createTable("workspace_memberships", (table) => {
|
|
35
|
-
table.increments("id").primary();
|
|
36
|
-
table.integer("workspace_id").unsigned().notNullable().references("id").inTable("workspaces").onDelete("CASCADE");
|
|
37
|
-
table.integer("user_id").unsigned().notNullable().references("id").inTable("users").onDelete("CASCADE");
|
|
38
|
-
table.string("role_sid", 64).notNullable().defaultTo("member");
|
|
39
|
-
table.string("status", 32).notNullable().defaultTo("active");
|
|
40
|
-
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
41
|
-
table.timestamp("updated_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
42
|
-
table.unique(["workspace_id", "user_id"], "uq_workspace_memberships_workspace_user");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
await knex.schema.createTable("workspace_settings", (table) => {
|
|
46
|
-
table.integer("workspace_id").unsigned().primary().references("id").inTable("workspaces").onDelete("CASCADE");
|
|
47
|
-
table.string("name", 160).notNullable().defaultTo("Workspace");
|
|
48
|
-
table.string("avatar_url", 512).notNullable().defaultTo("");
|
|
49
|
-
table.string("light_primary_color", 7).notNullable().defaultTo("#1867C0");
|
|
50
|
-
table.string("light_secondary_color", 7).notNullable().defaultTo("#48A9A6");
|
|
51
|
-
table.string("light_surface_color", 7).notNullable().defaultTo("#FFFFFF");
|
|
52
|
-
table.string("light_surface_variant_color", 7).notNullable().defaultTo("#424242");
|
|
53
|
-
table.string("dark_primary_color", 7).notNullable().defaultTo("#2196F3");
|
|
54
|
-
table.string("dark_secondary_color", 7).notNullable().defaultTo("#54B6B2");
|
|
55
|
-
table.string("dark_surface_color", 7).notNullable().defaultTo("#212121");
|
|
56
|
-
table.string("dark_surface_variant_color", 7).notNullable().defaultTo("#C8C8C8");
|
|
57
|
-
table.boolean("invites_enabled").notNullable().defaultTo(true);
|
|
58
|
-
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
59
|
-
table.timestamp("updated_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
await knex.schema.createTable("workspace_invites", (table) => {
|
|
63
|
-
table.increments("id").primary();
|
|
64
|
-
table.integer("workspace_id").unsigned().notNullable().references("id").inTable("workspaces").onDelete("CASCADE");
|
|
65
|
-
table.string("email", 255).notNullable();
|
|
66
|
-
table.string("role_sid", 64).notNullable().defaultTo("member");
|
|
67
|
-
table.string("status", 32).notNullable().defaultTo("pending");
|
|
68
|
-
table.string("token_hash", 191).notNullable();
|
|
69
|
-
table.integer("invited_by_user_id").unsigned().nullable().references("id").inTable("users").onDelete("SET NULL");
|
|
70
|
-
table.timestamp("expires_at", { useTz: false }).nullable();
|
|
71
|
-
table.timestamp("accepted_at", { useTz: false }).nullable();
|
|
72
|
-
table.timestamp("revoked_at", { useTz: false }).nullable();
|
|
73
|
-
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
74
|
-
table.timestamp("updated_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
75
|
-
table.unique(["token_hash"], "uq_workspace_invites_token_hash");
|
|
76
|
-
table.index(["workspace_id", "status"], "idx_workspace_invites_workspace_status");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
await knex.schema.createTable("user_settings", (table) => {
|
|
80
|
-
table.integer("user_id").unsigned().primary().references("id").inTable("users").onDelete("CASCADE");
|
|
81
|
-
table.integer("last_active_workspace_id").unsigned().nullable().references("id").inTable("workspaces").onDelete("SET NULL");
|
|
82
|
-
table.string("theme", 32).notNullable().defaultTo("system");
|
|
83
|
-
table.string("locale", 24).notNullable().defaultTo("en");
|
|
84
|
-
table.string("time_zone", 64).notNullable().defaultTo("UTC");
|
|
85
|
-
table.string("date_format", 32).notNullable().defaultTo("yyyy-mm-dd");
|
|
86
|
-
table.string("number_format", 32).notNullable().defaultTo("1,234.56");
|
|
87
|
-
table.string("currency_code", 3).notNullable().defaultTo("USD");
|
|
88
|
-
table.integer("avatar_size").notNullable().defaultTo(64);
|
|
89
|
-
table.boolean("password_sign_in_enabled").notNullable().defaultTo(true);
|
|
90
|
-
table.boolean("password_setup_required").notNullable().defaultTo(false);
|
|
91
|
-
table.boolean("notify_product_updates").notNullable().defaultTo(true);
|
|
92
|
-
table.boolean("notify_account_activity").notNullable().defaultTo(true);
|
|
93
|
-
table.boolean("notify_security_alerts").notNullable().defaultTo(true);
|
|
94
|
-
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
95
|
-
table.timestamp("updated_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
await knex.schema.createTable("console_settings", (table) => {
|
|
99
|
-
table.integer("id").primary();
|
|
100
|
-
table.integer("owner_user_id").unsigned().nullable().references("id").inTable("users").onDelete("SET NULL");
|
|
101
|
-
table.timestamp("created_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
102
|
-
table.timestamp("updated_at", { useTz: false }).notNullable().defaultTo(knex.fn.now());
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
await knex("console_settings").insert({
|
|
106
|
-
id: 1,
|
|
107
|
-
created_at: knex.fn.now(),
|
|
108
|
-
updated_at: knex.fn.now()
|
|
109
|
-
});
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* @param {import('knex').Knex} knex
|
|
114
|
-
*/
|
|
115
|
-
exports.down = async function down(knex) {
|
|
116
|
-
await knex.schema.dropTableIfExists("console_settings");
|
|
117
|
-
await knex.schema.dropTableIfExists("user_settings");
|
|
118
|
-
await knex.schema.dropTableIfExists("workspace_invites");
|
|
119
|
-
await knex.schema.dropTableIfExists("workspace_settings");
|
|
120
|
-
await knex.schema.dropTableIfExists("workspace_memberships");
|
|
121
|
-
await knex.schema.dropTableIfExists("workspaces");
|
|
122
|
-
await knex.schema.dropTableIfExists("users");
|
|
123
|
-
};
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
const WORKSPACE_SETTINGS_TABLE = "workspace_settings";
|
|
2
|
-
const WORKSPACES_TABLE = "workspaces";
|
|
3
|
-
const LEGACY_NAME_COLUMN = "name";
|
|
4
|
-
const LEGACY_AVATAR_COLUMN = "avatar_url";
|
|
5
|
-
|
|
6
|
-
async function hasTable(knex, tableName) {
|
|
7
|
-
return knex.schema.hasTable(tableName);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
async function hasColumn(knex, tableName, columnName) {
|
|
11
|
-
return knex.schema.hasColumn(tableName, columnName);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
exports.up = async function up(knex) {
|
|
15
|
-
const hasWorkspaceSettings = await hasTable(knex, WORKSPACE_SETTINGS_TABLE);
|
|
16
|
-
if (!hasWorkspaceSettings) {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const hasLegacyName = await hasColumn(knex, WORKSPACE_SETTINGS_TABLE, LEGACY_NAME_COLUMN);
|
|
21
|
-
const hasLegacyAvatarUrl = await hasColumn(knex, WORKSPACE_SETTINGS_TABLE, LEGACY_AVATAR_COLUMN);
|
|
22
|
-
if (!hasLegacyName && !hasLegacyAvatarUrl) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
await knex.schema.alterTable(WORKSPACE_SETTINGS_TABLE, (table) => {
|
|
27
|
-
if (hasLegacyName) {
|
|
28
|
-
table.dropColumn(LEGACY_NAME_COLUMN);
|
|
29
|
-
}
|
|
30
|
-
if (hasLegacyAvatarUrl) {
|
|
31
|
-
table.dropColumn(LEGACY_AVATAR_COLUMN);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
exports.down = async function down(knex) {
|
|
37
|
-
const hasWorkspaceSettings = await hasTable(knex, WORKSPACE_SETTINGS_TABLE);
|
|
38
|
-
if (!hasWorkspaceSettings) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const hasLegacyName = await hasColumn(knex, WORKSPACE_SETTINGS_TABLE, LEGACY_NAME_COLUMN);
|
|
43
|
-
const hasLegacyAvatarUrl = await hasColumn(knex, WORKSPACE_SETTINGS_TABLE, LEGACY_AVATAR_COLUMN);
|
|
44
|
-
if (!hasLegacyName || !hasLegacyAvatarUrl) {
|
|
45
|
-
await knex.schema.alterTable(WORKSPACE_SETTINGS_TABLE, (table) => {
|
|
46
|
-
if (!hasLegacyName) {
|
|
47
|
-
table.string(LEGACY_NAME_COLUMN, 160).notNullable().defaultTo("Workspace");
|
|
48
|
-
}
|
|
49
|
-
if (!hasLegacyAvatarUrl) {
|
|
50
|
-
table.string(LEGACY_AVATAR_COLUMN, 512).notNullable().defaultTo("");
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const hasWorkspaces = await hasTable(knex, WORKSPACES_TABLE);
|
|
56
|
-
if (!hasWorkspaces) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const workspaceRows = await knex(WORKSPACES_TABLE).select("id", "name", "avatar_url");
|
|
61
|
-
for (const workspaceRow of workspaceRows) {
|
|
62
|
-
const normalizedName = String(workspaceRow?.name || "").trim() || "Workspace";
|
|
63
|
-
const normalizedAvatarUrl = String(workspaceRow?.avatar_url || "").trim();
|
|
64
|
-
await knex(WORKSPACE_SETTINGS_TABLE)
|
|
65
|
-
.where({ workspace_id: Number(workspaceRow.id) })
|
|
66
|
-
.update({
|
|
67
|
-
name: normalizedName,
|
|
68
|
-
avatar_url: normalizedAvatarUrl
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
};
|