@jskit-ai/users-core 0.1.21 → 0.1.22

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.
@@ -1,7 +1,7 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/users-core",
4
- version: "0.1.21",
4
+ version: "0.1.22",
5
5
  description: "Users/workspace domain runtime plus HTTP routes for workspace, account, and console features.",
6
6
  dependsOn: [
7
7
  "@jskit-ai/auth-core",
@@ -252,6 +252,15 @@ export default Object.freeze({
252
252
  category: "migration",
253
253
  id: "users-core-console-owner-schema"
254
254
  },
255
+ {
256
+ op: "install-migration",
257
+ from: "templates/migrations/users_core_workspace_settings_single_name_source.cjs",
258
+ toDir: "migrations",
259
+ extension: ".cjs",
260
+ reason: "Remove workspace_settings.name so workspace names come from workspaces only.",
261
+ category: "migration",
262
+ id: "users-core-workspace-settings-single-name-source"
263
+ },
255
264
  {
256
265
  from: "templates/packages/main/src/shared/resources/workspaceSettingsFields.js",
257
266
  to: "packages/main/src/shared/resources/workspaceSettingsFields.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/users-core",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -49,7 +49,7 @@ function createRepository(knex) {
49
49
  const columns = [
50
50
  "w.id",
51
51
  "w.slug",
52
- client.raw("COALESCE(ws.name, w.name) as name"),
52
+ "w.name",
53
53
  "w.owner_user_id",
54
54
  "w.is_personal",
55
55
  client.raw("COALESCE(ws.avatar_url, w.avatar_url) as avatar_url"),
@@ -44,7 +44,6 @@ exports.up = async function up(knex) {
44
44
 
45
45
  await knex.schema.createTable("workspace_settings", (table) => {
46
46
  table.integer("workspace_id").unsigned().primary().references("id").inTable("workspaces").onDelete("CASCADE");
47
- table.string("name", 160).notNullable().defaultTo("Workspace");
48
47
  table.string("avatar_url", 512).notNullable().defaultTo("");
49
48
  table.string("light_primary_color", 7).notNullable().defaultTo("#1867C0");
50
49
  table.string("light_secondary_color", 7).notNullable().defaultTo("#48A9A6");
@@ -0,0 +1,54 @@
1
+ const WORKSPACE_SETTINGS_TABLE = "workspace_settings";
2
+ const WORKSPACES_TABLE = "workspaces";
3
+ const LEGACY_NAME_COLUMN = "name";
4
+
5
+ async function hasTable(knex, tableName) {
6
+ return knex.schema.hasTable(tableName);
7
+ }
8
+
9
+ async function hasColumn(knex, tableName, columnName) {
10
+ return knex.schema.hasColumn(tableName, columnName);
11
+ }
12
+
13
+ exports.up = async function up(knex) {
14
+ const hasWorkspaceSettings = await hasTable(knex, WORKSPACE_SETTINGS_TABLE);
15
+ if (!hasWorkspaceSettings) {
16
+ return;
17
+ }
18
+
19
+ const hasLegacyName = await hasColumn(knex, WORKSPACE_SETTINGS_TABLE, LEGACY_NAME_COLUMN);
20
+ if (!hasLegacyName) {
21
+ return;
22
+ }
23
+
24
+ await knex.schema.alterTable(WORKSPACE_SETTINGS_TABLE, (table) => {
25
+ table.dropColumn(LEGACY_NAME_COLUMN);
26
+ });
27
+ };
28
+
29
+ exports.down = async function down(knex) {
30
+ const hasWorkspaceSettings = await hasTable(knex, WORKSPACE_SETTINGS_TABLE);
31
+ if (!hasWorkspaceSettings) {
32
+ return;
33
+ }
34
+
35
+ const hasLegacyName = await hasColumn(knex, WORKSPACE_SETTINGS_TABLE, LEGACY_NAME_COLUMN);
36
+ if (!hasLegacyName) {
37
+ await knex.schema.alterTable(WORKSPACE_SETTINGS_TABLE, (table) => {
38
+ table.string(LEGACY_NAME_COLUMN, 160).notNullable().defaultTo("Workspace");
39
+ });
40
+ }
41
+
42
+ const hasWorkspaces = await hasTable(knex, WORKSPACES_TABLE);
43
+ if (!hasWorkspaces) {
44
+ return;
45
+ }
46
+
47
+ const workspaceRows = await knex(WORKSPACES_TABLE).select("id", "name");
48
+ for (const workspaceRow of workspaceRows) {
49
+ const normalizedName = String(workspaceRow?.name || "").trim() || "Workspace";
50
+ await knex(WORKSPACE_SETTINGS_TABLE)
51
+ .where({ workspace_id: Number(workspaceRow.id) })
52
+ .update({ name: normalizedName });
53
+ }
54
+ };
@@ -35,26 +35,6 @@ function normalizeHexColor(value) {
35
35
 
36
36
  resetWorkspaceSettingsFields();
37
37
 
38
- defineField({
39
- key: "name",
40
- dbColumn: "name",
41
- required: true,
42
- inputSchema: Type.String({
43
- minLength: 1,
44
- maxLength: 160,
45
- messages: {
46
- required: "Workspace name is required.",
47
- minLength: "Workspace name is required.",
48
- maxLength: "Workspace name must be at most 160 characters.",
49
- default: "Workspace name is required."
50
- }
51
- }),
52
- outputSchema: Type.String({ minLength: 1, maxLength: 160 }),
53
- normalizeInput: (value) => normalizeText(value),
54
- normalizeOutput: (value) => normalizeText(value),
55
- resolveDefault: ({ workspace = {} } = {}) => normalizeText(workspace.name) || "Workspace"
56
- });
57
-
58
38
  defineField({
59
39
  key: "avatarUrl",
60
40
  dbColumn: "avatar_url",
@@ -404,7 +404,7 @@ test("workspace settings route handlers build action input from request.input",
404
404
  createActionRequest({
405
405
  input: {
406
406
  params: { workspaceSlug: "acme" },
407
- body: { name: "Acme Workspace" }
407
+ body: { avatarUrl: "https://example.com/acme.png" }
408
408
  },
409
409
  executeAction
410
410
  }),
@@ -413,7 +413,7 @@ test("workspace settings route handlers build action input from request.input",
413
413
 
414
414
  assert.deepEqual(calls[0], {
415
415
  actionId: "workspace.settings.update",
416
- input: { workspaceSlug: "acme", patch: { name: "Acme Workspace" } }
416
+ input: { workspaceSlug: "acme", patch: { avatarUrl: "https://example.com/acme.png" } }
417
417
  });
418
418
  });
419
419
 
@@ -16,7 +16,6 @@ function createKnexStub(rowOverrides = {}) {
16
16
  updatePayload: null,
17
17
  row: {
18
18
  workspace_id: 1,
19
- name: "Workspace",
20
19
  avatar_url: "",
21
20
  light_primary_color: DEFAULT_WORKSPACE_THEME.light.color,
22
21
  light_secondary_color: DEFAULT_WORKSPACE_THEME.light.secondaryColor,
@@ -41,7 +40,6 @@ function createKnexStub(rowOverrides = {}) {
41
40
  state.insertedRow = { ...payload };
42
41
  state.row = {
43
42
  workspace_id: payload.workspace_id,
44
- name: payload.name,
45
43
  avatar_url: payload.avatar_url,
46
44
  light_primary_color: payload.light_primary_color,
47
45
  light_secondary_color: payload.light_secondary_color,
@@ -69,9 +67,6 @@ function createKnexStub(rowOverrides = {}) {
69
67
  if (Object.hasOwn(payload, "invites_enabled")) {
70
68
  state.row.invites_enabled = payload.invites_enabled;
71
69
  }
72
- if (Object.hasOwn(payload, "name")) {
73
- state.row.name = payload.name;
74
- }
75
70
  if (Object.hasOwn(payload, "avatar_url")) {
76
71
  state.row.avatar_url = payload.avatar_url;
77
72
  }
@@ -122,7 +117,6 @@ test("workspaceSettingsRepository.findByWorkspaceId maps the stored row", async
122
117
 
123
118
  assert.deepEqual(record, {
124
119
  workspaceId: 1,
125
- name: "Workspace",
126
120
  avatarUrl: "",
127
121
  lightPrimaryColor: DEFAULT_WORKSPACE_THEME.light.color,
128
122
  lightSecondaryColor: DEFAULT_WORKSPACE_THEME.light.secondaryColor,
@@ -162,7 +156,6 @@ test("workspaceSettingsRepository.ensureForWorkspaceId inserts the injected defa
162
156
  const record = await repository.ensureForWorkspaceId(5);
163
157
 
164
158
  assert.equal(state.insertedRow.workspace_id, 5);
165
- assert.equal(state.insertedRow.name, "Workspace");
166
159
  assert.equal(state.insertedRow.avatar_url, "");
167
160
  assert.equal(state.insertedRow.light_primary_color, DEFAULT_WORKSPACE_THEME.light.color);
168
161
  assert.equal(state.insertedRow.light_secondary_color, DEFAULT_WORKSPACE_THEME.light.secondaryColor);
@@ -179,7 +172,6 @@ test("workspaceSettingsRepository.ensureForWorkspaceId inserts the injected defa
179
172
  DEFAULT_WORKSPACE_THEME.dark.surfaceVariantColor
180
173
  );
181
174
  assert.equal(state.insertedRow.invites_enabled, false);
182
- assert.equal(record.name, "Workspace");
183
175
  assert.equal(record.avatarUrl, "");
184
176
  assert.equal(record.lightPrimaryColor, DEFAULT_WORKSPACE_THEME.light.color);
185
177
  assert.equal(record.lightSecondaryColor, DEFAULT_WORKSPACE_THEME.light.secondaryColor);
@@ -192,22 +184,19 @@ test("workspaceSettingsRepository.ensureForWorkspaceId inserts the injected defa
192
184
  assert.equal(record.invitesEnabled, false);
193
185
  });
194
186
 
195
- test("workspaceSettingsRepository.updateSettingsByWorkspaceId updates name/avatar/color columns", async () => {
187
+ test("workspaceSettingsRepository.updateSettingsByWorkspaceId updates avatar/color columns", async () => {
196
188
  const { knexStub, state } = createKnexStub();
197
189
  const repository = createRepository(knexStub, {
198
190
  defaultInvitesEnabled: true
199
191
  });
200
192
 
201
193
  const updated = await repository.updateSettingsByWorkspaceId(1, {
202
- name: "New name",
203
194
  avatarUrl: "https://example.com/avatar.png",
204
195
  lightPrimaryColor: "#123abc"
205
196
  });
206
197
 
207
- assert.equal(state.updatePayload.name, "New name");
208
198
  assert.equal(state.updatePayload.avatar_url, "https://example.com/avatar.png");
209
199
  assert.equal(state.updatePayload.light_primary_color, "#123ABC");
210
- assert.equal(updated.name, "New name");
211
200
  assert.equal(updated.avatarUrl, "https://example.com/avatar.png");
212
201
  assert.equal(updated.lightPrimaryColor, "#123ABC");
213
202
  });
@@ -46,7 +46,6 @@ function parseBody(operation, payload = {}) {
46
46
 
47
47
  test("workspace settings patch body normalizes valid payload before validation", () => {
48
48
  const parsed = parseBody(workspaceSettingsResource.operations.patch, {
49
- name: " Team Mercury ",
50
49
  avatarUrl: "https://example.com/avatar.png",
51
50
  lightPrimaryColor: "#0f6b54",
52
51
  lightSecondaryColor: "#0b4d3c",
@@ -62,7 +61,6 @@ test("workspace settings patch body normalizes valid payload before validation",
62
61
  assert.equal(parsed.ok, true);
63
62
  assert.deepEqual(parsed.fieldErrors, {});
64
63
  assert.deepEqual(parsed.value, {
65
- name: "Team Mercury",
66
64
  avatarUrl: "https://example.com/avatar.png",
67
65
  lightPrimaryColor: "#0F6B54",
68
66
  lightSecondaryColor: "#0B4D3C",
@@ -88,19 +86,8 @@ test("workspace settings patch body validates avatar URL protocol", () => {
88
86
  );
89
87
  });
90
88
 
91
- test("workspace settings patch body keeps max-length name rule", () => {
92
- const parsed = parseBody(workspaceSettingsResource.operations.patch, {
93
- name: "x".repeat(161)
94
- });
95
-
96
- assert.equal(parsed.ok, false);
97
- assert.equal(parsed.fieldErrors.name, "Workspace name must be at most 160 characters.");
98
- });
99
-
100
89
  test("workspace settings create body requires full-write fields", () => {
101
- const parsed = parseBody(workspaceSettingsResource.operations.create, {
102
- name: "Mercury Workspace"
103
- });
90
+ const parsed = parseBody(workspaceSettingsResource.operations.create, {});
104
91
 
105
92
  assert.equal(parsed.ok, false);
106
93
  assert.equal(parsed.fieldErrors.lightPrimaryColor, "Light primary color is required.");
@@ -125,7 +112,6 @@ test("workspace settings output normalizes raw service payloads", () => {
125
112
  ownerUserId: "9"
126
113
  },
127
114
  settings: {
128
- name: " Mercury Workspace ",
129
115
  avatarUrl: " https://example.com/avatar.png ",
130
116
  lightPrimaryColor: "#0f6b54",
131
117
  invitesEnabled: false
@@ -140,7 +126,6 @@ test("workspace settings output normalizes raw service payloads", () => {
140
126
  ownerUserId: 9
141
127
  },
142
128
  settings: {
143
- name: "Mercury Workspace",
144
129
  avatarUrl: "https://example.com/avatar.png",
145
130
  lightPrimaryColor: "#0F6B54",
146
131
  lightSecondaryColor: expectedTheme.light.secondaryColor,
@@ -30,7 +30,6 @@ function createFixture({ workspaceInvitationsEnabled = true } = {}) {
30
30
  color: defaultTheme.light.color
31
31
  },
32
32
  settings: {
33
- name: "TonyMobily3",
34
33
  avatarUrl: "",
35
34
  lightPrimaryColor: defaultTheme.light.color,
36
35
  lightSecondaryColor: defaultTheme.light.secondaryColor,
@@ -75,7 +74,6 @@ test("workspaceSettingsService.getWorkspaceSettings returns the stored invitesEn
75
74
  );
76
75
 
77
76
  assert.deepEqual(response.settings, {
78
- name: "TonyMobily3",
79
77
  avatarUrl: "",
80
78
  lightPrimaryColor: "#0F6B54",
81
79
  lightSecondaryColor: "#48A9A6",
@@ -97,18 +95,15 @@ test("workspaceSettingsService.updateWorkspaceSettings writes editable fields th
97
95
  const response = await service.updateWorkspaceSettings(
98
96
  state.workspace,
99
97
  {
100
- name: "New Name",
101
98
  invitesEnabled: false
102
99
  },
103
100
  authorizedOptions(["workspace.settings.update"])
104
101
  );
105
102
 
106
103
  assert.deepEqual(state.settingsPatch, {
107
- name: "New Name",
108
104
  invitesEnabled: false
109
105
  });
110
106
  assert.deepEqual(response.settings, {
111
- name: "New Name",
112
107
  avatarUrl: "",
113
108
  lightPrimaryColor: "#0F6B54",
114
109
  lightSecondaryColor: "#48A9A6",
@@ -135,7 +130,6 @@ test("workspaceSettingsService disables invite settings in output when app polic
135
130
  );
136
131
 
137
132
  assert.deepEqual(response.settings, {
138
- name: "TonyMobily3",
139
133
  avatarUrl: "",
140
134
  lightPrimaryColor: "#0F6B54",
141
135
  lightSecondaryColor: "#48A9A6",