@jskit-ai/workspaces-core 0.1.30 → 0.1.32
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 +11 -22
- package/package.json +11 -9
- package/src/server/WorkspacesCoreServiceProvider.js +22 -2
- package/src/server/common/repositories/workspaceInvitesRepository.js +233 -78
- package/src/server/common/repositories/workspaceMembershipsRepository.js +177 -86
- package/src/server/common/repositories/workspacesRepository.js +179 -86
- package/src/server/common/services/workspaceContextService.js +26 -24
- package/src/server/common/validators/routeParamsValidator.js +36 -53
- package/src/server/registerWorkspaceCore.js +6 -7
- package/src/server/registerWorkspaceRepositories.js +7 -3
- package/src/server/support/workspaceServerScopeSupport.js +1 -1
- package/src/server/workspaceBootstrapContributor.js +5 -14
- package/src/server/workspaceDirectory/bootWorkspaceDirectoryRoutes.js +54 -27
- package/src/server/workspaceDirectory/workspaceDirectoryActions.js +30 -24
- package/src/server/workspaceMembers/bootWorkspaceMembers.js +70 -32
- package/src/server/workspaceMembers/workspaceMembersActions.js +61 -27
- package/src/server/workspaceMembers/workspaceMembersService.js +43 -7
- package/src/server/workspacePendingInvitations/bootWorkspacePendingInvitations.js +28 -13
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsActions.js +13 -15
- package/src/server/workspacePendingInvitations/workspacePendingInvitationsService.js +33 -10
- package/src/server/workspaceSettings/bootWorkspaceSettings.js +32 -13
- package/src/server/workspaceSettings/registerWorkspaceSettings.js +5 -1
- package/src/server/workspaceSettings/workspaceSettingsActions.js +18 -12
- package/src/server/workspaceSettings/workspaceSettingsRepository.js +104 -91
- package/src/server/workspaceSettings/workspaceSettingsService.js +5 -6
- package/src/shared/jsonApiTransports.js +79 -0
- package/src/shared/resources/workspaceInvitesResource.js +158 -0
- package/src/shared/resources/workspaceMembersResource.js +176 -311
- package/src/shared/resources/workspaceMembershipsResource.js +96 -0
- package/src/shared/resources/workspacePendingInvitationsResource.js +25 -72
- package/src/shared/resources/workspaceResource.js +113 -144
- package/src/shared/resources/workspaceRoleCatalogSchema.js +31 -0
- package/src/shared/resources/workspaceSettingsResource.js +276 -148
- package/test/repositoryContracts.test.js +16 -4
- package/test/resourcesCanonical.test.js +39 -16
- package/test/routeParamsValidator.test.js +37 -19
- package/test/usersRouteResources.test.js +27 -17
- package/test/workspaceActionContextContributor.test.js +1 -1
- package/test/workspaceInternalCrudResources.test.js +98 -0
- package/test/workspaceInvitesRepository.test.js +196 -148
- package/test/workspaceMembersResource.test.js +35 -0
- package/test/workspaceMembershipsRepository.test.js +155 -115
- package/test/workspacePendingInvitationsResource.test.js +18 -23
- package/test/workspacePendingInvitationsService.test.js +2 -1
- package/test/workspaceServerScopeSupport.test.js +21 -3
- package/test/workspaceSettingsActions.test.js +5 -7
- package/test/workspaceSettingsInternalResource.test.js +8 -0
- package/test/workspaceSettingsRepository.test.js +158 -123
- package/test/workspaceSettingsResource.test.js +51 -62
- package/test/workspaceSettingsService.test.js +0 -1
- package/test/workspacesRepository.test.js +318 -174
- package/test/workspacesRouteRequestInputValidator.test.js +25 -11
- package/src/server/common/resources/workspaceInvitesResource.js +0 -207
- package/src/server/common/resources/workspaceMembershipsResource.js +0 -154
- package/src/server/common/resources/workspacesResource.js +0 -170
- package/src/server/common/validators/authenticatedUserValidator.js +0 -43
- package/src/shared/resources/resolveGlobalArrayRegistry.js +0 -6
- package/src/shared/resources/workspaceSettingsFields.js +0 -65
- package/templates/packages/main/src/shared/resources/workspaceSettingsFields.js +0 -197
- package/test/settingsFieldRegistriesSingleton.test.js +0 -14
- package/test-support/registerDefaultSettingsFields.js +0 -1
|
@@ -1,169 +1,297 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
recordIdSchema
|
|
8
|
-
} from "@jskit-ai/kernel/shared/validators";
|
|
9
|
-
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
10
|
-
import { workspaceSettingsFields } from "./workspaceSettingsFields.js";
|
|
11
|
-
import { createWorkspaceRoleCatalog } from "../roles.js";
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
|
|
3
|
+
import { RECORD_ID_PATTERN } from "@jskit-ai/kernel/shared/validators";
|
|
4
|
+
import { defineCrudResource } from "@jskit-ai/resource-crud-core/shared/crudResource";
|
|
5
|
+
import { workspaceRoleCatalogSchema } from "./workspaceRoleCatalogSchema.js";
|
|
6
|
+
import { normalizeWorkspaceHexColor, DEFAULT_WORKSPACE_DARK_PALETTE, DEFAULT_WORKSPACE_LIGHT_PALETTE } from "../settings.js";
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
const WORKSPACE_SETTINGS_FIELD_KEYS = deepFreeze([
|
|
9
|
+
"lightPrimaryColor",
|
|
10
|
+
"lightSecondaryColor",
|
|
11
|
+
"lightSurfaceColor",
|
|
12
|
+
"lightSurfaceVariantColor",
|
|
13
|
+
"darkPrimaryColor",
|
|
14
|
+
"darkSecondaryColor",
|
|
15
|
+
"darkSurfaceColor",
|
|
16
|
+
"darkSurfaceVariantColor",
|
|
17
|
+
"invitesEnabled"
|
|
18
|
+
]);
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const workspaceSettingsBodySchema = createSchema({
|
|
21
|
+
lightPrimaryColor: {
|
|
22
|
+
type: "string",
|
|
23
|
+
required: true,
|
|
24
|
+
minLength: 7,
|
|
25
|
+
maxLength: 7,
|
|
26
|
+
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
21
27
|
messages: {
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
required: "Light primary color is required.",
|
|
29
|
+
pattern: "Light primary color must be a hex color like #1867C0.",
|
|
30
|
+
default: "Light primary color must be a hex color like #1867C0."
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
lightSecondaryColor: {
|
|
34
|
+
type: "string",
|
|
35
|
+
required: true,
|
|
36
|
+
minLength: 7,
|
|
37
|
+
maxLength: 7,
|
|
38
|
+
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
39
|
+
messages: {
|
|
40
|
+
required: "Light secondary color is required.",
|
|
41
|
+
pattern: "Light secondary color must be a hex color like #48A9A6.",
|
|
42
|
+
default: "Light secondary color must be a hex color like #48A9A6."
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
lightSurfaceColor: {
|
|
46
|
+
type: "string",
|
|
47
|
+
required: true,
|
|
48
|
+
minLength: 7,
|
|
49
|
+
maxLength: 7,
|
|
50
|
+
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
51
|
+
messages: {
|
|
52
|
+
required: "Light surface color is required.",
|
|
53
|
+
pattern: "Light surface color must be a hex color like #FFFFFF.",
|
|
54
|
+
default: "Light surface color must be a hex color like #FFFFFF."
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
lightSurfaceVariantColor: {
|
|
58
|
+
type: "string",
|
|
59
|
+
required: true,
|
|
60
|
+
minLength: 7,
|
|
61
|
+
maxLength: 7,
|
|
62
|
+
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
63
|
+
messages: {
|
|
64
|
+
required: "Light surface variant color is required.",
|
|
65
|
+
pattern: "Light surface variant color must be a hex color like #424242.",
|
|
66
|
+
default: "Light surface variant color must be a hex color like #424242."
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
darkPrimaryColor: {
|
|
70
|
+
type: "string",
|
|
71
|
+
required: true,
|
|
72
|
+
minLength: 7,
|
|
73
|
+
maxLength: 7,
|
|
74
|
+
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
75
|
+
messages: {
|
|
76
|
+
required: "Dark primary color is required.",
|
|
77
|
+
pattern: "Dark primary color must be a hex color like #2196F3.",
|
|
78
|
+
default: "Dark primary color must be a hex color like #2196F3."
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
darkSecondaryColor: {
|
|
82
|
+
type: "string",
|
|
83
|
+
required: true,
|
|
84
|
+
minLength: 7,
|
|
85
|
+
maxLength: 7,
|
|
86
|
+
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
87
|
+
messages: {
|
|
88
|
+
required: "Dark secondary color is required.",
|
|
89
|
+
pattern: "Dark secondary color must be a hex color like #54B6B2.",
|
|
90
|
+
default: "Dark secondary color must be a hex color like #54B6B2."
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
darkSurfaceColor: {
|
|
94
|
+
type: "string",
|
|
95
|
+
required: true,
|
|
96
|
+
minLength: 7,
|
|
97
|
+
maxLength: 7,
|
|
98
|
+
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
99
|
+
messages: {
|
|
100
|
+
required: "Dark surface color is required.",
|
|
101
|
+
pattern: "Dark surface color must be a hex color like #212121.",
|
|
102
|
+
default: "Dark surface color must be a hex color like #212121."
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
darkSurfaceVariantColor: {
|
|
106
|
+
type: "string",
|
|
107
|
+
required: true,
|
|
108
|
+
minLength: 7,
|
|
109
|
+
maxLength: 7,
|
|
110
|
+
pattern: "^#[0-9A-Fa-f]{6}$",
|
|
111
|
+
messages: {
|
|
112
|
+
required: "Dark surface variant color is required.",
|
|
113
|
+
pattern: "Dark surface variant color must be a hex color like #C8C8C8.",
|
|
114
|
+
default: "Dark surface variant color must be a hex color like #C8C8C8."
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
invitesEnabled: {
|
|
118
|
+
type: "boolean",
|
|
119
|
+
required: true,
|
|
120
|
+
strictBoolean: true,
|
|
121
|
+
messages: {
|
|
122
|
+
required: "invitesEnabled is required.",
|
|
123
|
+
default: "invitesEnabled must be a boolean."
|
|
24
124
|
}
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function buildSettingsOutputSchema() {
|
|
29
|
-
const properties = {};
|
|
30
|
-
for (const field of workspaceSettingsFields) {
|
|
31
|
-
properties[field.key] = field.outputSchema;
|
|
32
125
|
}
|
|
33
|
-
|
|
34
|
-
properties.invitesEffective = Type.Boolean();
|
|
35
|
-
|
|
36
|
-
return Type.Object(properties, { additionalProperties: false });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function buildResponseRecordSchema() {
|
|
40
|
-
return Type.Object(
|
|
41
|
-
{
|
|
42
|
-
workspace: Type.Object(
|
|
43
|
-
{
|
|
44
|
-
id: recordIdSchema,
|
|
45
|
-
slug: Type.String({ minLength: 1 }),
|
|
46
|
-
ownerUserId: recordIdSchema
|
|
47
|
-
},
|
|
48
|
-
{ additionalProperties: false }
|
|
49
|
-
),
|
|
50
|
-
settings: buildSettingsOutputSchema(),
|
|
51
|
-
roleCatalog: Type.Object(
|
|
52
|
-
{
|
|
53
|
-
collaborationEnabled: Type.Boolean(),
|
|
54
|
-
defaultInviteRole: Type.String(),
|
|
55
|
-
roles: Type.Array(Type.Object({}, { additionalProperties: true })),
|
|
56
|
-
assignableRoleIds: Type.Array(Type.String({ minLength: 1 }))
|
|
57
|
-
},
|
|
58
|
-
{ additionalProperties: true }
|
|
59
|
-
)
|
|
60
|
-
},
|
|
61
|
-
{ additionalProperties: false }
|
|
62
|
-
);
|
|
63
|
-
}
|
|
126
|
+
});
|
|
64
127
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
128
|
+
const workspaceSettingsWorkspaceOutputSchema = createSchema({
|
|
129
|
+
id: { type: "string", required: true, minLength: 1, pattern: RECORD_ID_PATTERN },
|
|
130
|
+
slug: { type: "string", required: true, minLength: 1, maxLength: 120 },
|
|
131
|
+
ownerUserId: { type: "string", required: true, minLength: 1, pattern: RECORD_ID_PATTERN }
|
|
132
|
+
});
|
|
68
133
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
134
|
+
const workspaceSettingsViewSchema = createSchema({
|
|
135
|
+
lightPrimaryColor: { type: "string", required: true, minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" },
|
|
136
|
+
lightSecondaryColor: { type: "string", required: true, minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" },
|
|
137
|
+
lightSurfaceColor: { type: "string", required: true, minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" },
|
|
138
|
+
lightSurfaceVariantColor: { type: "string", required: true, minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" },
|
|
139
|
+
darkPrimaryColor: { type: "string", required: true, minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" },
|
|
140
|
+
darkSecondaryColor: { type: "string", required: true, minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" },
|
|
141
|
+
darkSurfaceColor: { type: "string", required: true, minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" },
|
|
142
|
+
darkSurfaceVariantColor: { type: "string", required: true, minLength: 7, maxLength: 7, pattern: "^#[0-9A-Fa-f]{6}$" },
|
|
143
|
+
invitesEnabled: { type: "boolean", required: true },
|
|
144
|
+
invitesAvailable: { type: "boolean", required: true },
|
|
145
|
+
invitesEffective: { type: "boolean", required: true }
|
|
146
|
+
});
|
|
74
147
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
148
|
+
const workspaceSettingsOutputSchema = createSchema({
|
|
149
|
+
workspace: {
|
|
150
|
+
type: "object",
|
|
151
|
+
required: true,
|
|
152
|
+
schema: workspaceSettingsWorkspaceOutputSchema
|
|
153
|
+
},
|
|
154
|
+
settings: {
|
|
155
|
+
type: "object",
|
|
156
|
+
required: true,
|
|
157
|
+
schema: workspaceSettingsViewSchema
|
|
158
|
+
},
|
|
159
|
+
roleCatalog: {
|
|
160
|
+
type: "object",
|
|
161
|
+
required: true,
|
|
162
|
+
schema: workspaceRoleCatalogSchema
|
|
86
163
|
}
|
|
164
|
+
});
|
|
87
165
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const invitesEffective =
|
|
91
|
-
typeof settings.invitesEffective === "boolean" ? settings.invitesEffective : invitesEnabled;
|
|
92
|
-
normalizedSettings.invitesEnabled = invitesEnabled;
|
|
93
|
-
normalizedSettings.invitesAvailable = invitesAvailable;
|
|
94
|
-
normalizedSettings.invitesEffective = invitesEffective;
|
|
95
|
-
const roleCatalog = normalizeObjectInput(source.roleCatalog);
|
|
96
|
-
const hasRoleCatalog =
|
|
97
|
-
Array.isArray(roleCatalog.roles) &&
|
|
98
|
-
roleCatalog.roles.length > 0 &&
|
|
99
|
-
Array.isArray(roleCatalog.assignableRoleIds);
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
workspace: {
|
|
103
|
-
id: normalizeRecordId(workspace.id, { fallback: "" }),
|
|
104
|
-
slug: normalizeText(workspace.slug),
|
|
105
|
-
ownerUserId: normalizeRecordId(workspace.ownerUserId, { fallback: "" })
|
|
106
|
-
},
|
|
107
|
-
settings: normalizedSettings,
|
|
108
|
-
roleCatalog: hasRoleCatalog ? roleCatalog : createWorkspaceRoleCatalog()
|
|
109
|
-
};
|
|
166
|
+
function normalizeWorkspaceColorInput(value) {
|
|
167
|
+
return normalizeWorkspaceHexColor(value);
|
|
110
168
|
}
|
|
111
169
|
|
|
112
|
-
const
|
|
113
|
-
get schema() {
|
|
114
|
-
return buildResponseRecordSchema();
|
|
115
|
-
},
|
|
116
|
-
normalize: normalizeOutput
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
const resource = {
|
|
170
|
+
const workspaceSettingsResource = defineCrudResource({
|
|
120
171
|
namespace: "workspaceSettings",
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
apiValidation: "Validation failed."
|
|
172
|
+
tableName: "workspace_settings",
|
|
173
|
+
idProperty: "workspace_id",
|
|
174
|
+
searchSchema: {
|
|
175
|
+
id: { type: "id", actualField: "id" }
|
|
126
176
|
},
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
177
|
+
schema: {
|
|
178
|
+
id: {
|
|
179
|
+
type: "id",
|
|
180
|
+
primary: true,
|
|
181
|
+
required: true,
|
|
182
|
+
search: true,
|
|
183
|
+
storage: { column: "workspace_id" }
|
|
184
|
+
},
|
|
185
|
+
lightPrimaryColor: {
|
|
186
|
+
type: "string",
|
|
187
|
+
required: true,
|
|
188
|
+
minLength: 7,
|
|
189
|
+
maxLength: 7,
|
|
190
|
+
defaultTo: DEFAULT_WORKSPACE_LIGHT_PALETTE.color,
|
|
191
|
+
storage: { column: "light_primary_color" },
|
|
192
|
+
setter: (value) => normalizeWorkspaceColorInput(value)
|
|
131
193
|
},
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
194
|
+
lightSecondaryColor: {
|
|
195
|
+
type: "string",
|
|
196
|
+
required: true,
|
|
197
|
+
minLength: 7,
|
|
198
|
+
maxLength: 7,
|
|
199
|
+
defaultTo: DEFAULT_WORKSPACE_LIGHT_PALETTE.secondaryColor,
|
|
200
|
+
storage: { column: "light_secondary_color" },
|
|
201
|
+
setter: (value) => normalizeWorkspaceColorInput(value)
|
|
135
202
|
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
outputValidator: responseRecordValidator
|
|
203
|
+
lightSurfaceColor: {
|
|
204
|
+
type: "string",
|
|
205
|
+
required: true,
|
|
206
|
+
minLength: 7,
|
|
207
|
+
maxLength: 7,
|
|
208
|
+
defaultTo: DEFAULT_WORKSPACE_LIGHT_PALETTE.surfaceColor,
|
|
209
|
+
storage: { column: "light_surface_color" },
|
|
210
|
+
setter: (value) => normalizeWorkspaceColorInput(value)
|
|
145
211
|
},
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
outputValidator: responseRecordValidator
|
|
212
|
+
lightSurfaceVariantColor: {
|
|
213
|
+
type: "string",
|
|
214
|
+
required: true,
|
|
215
|
+
minLength: 7,
|
|
216
|
+
maxLength: 7,
|
|
217
|
+
defaultTo: DEFAULT_WORKSPACE_LIGHT_PALETTE.surfaceVariantColor,
|
|
218
|
+
storage: { column: "light_surface_variant_color" },
|
|
219
|
+
setter: (value) => normalizeWorkspaceColorInput(value)
|
|
155
220
|
},
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
221
|
+
darkPrimaryColor: {
|
|
222
|
+
type: "string",
|
|
223
|
+
required: true,
|
|
224
|
+
minLength: 7,
|
|
225
|
+
maxLength: 7,
|
|
226
|
+
defaultTo: DEFAULT_WORKSPACE_DARK_PALETTE.color,
|
|
227
|
+
storage: { column: "dark_primary_color" },
|
|
228
|
+
setter: (value) => normalizeWorkspaceColorInput(value)
|
|
229
|
+
},
|
|
230
|
+
darkSecondaryColor: {
|
|
231
|
+
type: "string",
|
|
232
|
+
required: true,
|
|
233
|
+
minLength: 7,
|
|
234
|
+
maxLength: 7,
|
|
235
|
+
defaultTo: DEFAULT_WORKSPACE_DARK_PALETTE.secondaryColor,
|
|
236
|
+
storage: { column: "dark_secondary_color" },
|
|
237
|
+
setter: (value) => normalizeWorkspaceColorInput(value)
|
|
238
|
+
},
|
|
239
|
+
darkSurfaceColor: {
|
|
240
|
+
type: "string",
|
|
241
|
+
required: true,
|
|
242
|
+
minLength: 7,
|
|
243
|
+
maxLength: 7,
|
|
244
|
+
defaultTo: DEFAULT_WORKSPACE_DARK_PALETTE.surfaceColor,
|
|
245
|
+
storage: { column: "dark_surface_color" },
|
|
246
|
+
setter: (value) => normalizeWorkspaceColorInput(value)
|
|
247
|
+
},
|
|
248
|
+
darkSurfaceVariantColor: {
|
|
249
|
+
type: "string",
|
|
250
|
+
required: true,
|
|
251
|
+
minLength: 7,
|
|
252
|
+
maxLength: 7,
|
|
253
|
+
defaultTo: DEFAULT_WORKSPACE_DARK_PALETTE.surfaceVariantColor,
|
|
254
|
+
storage: { column: "dark_surface_variant_color" },
|
|
255
|
+
setter: (value) => normalizeWorkspaceColorInput(value)
|
|
256
|
+
},
|
|
257
|
+
invitesEnabled: {
|
|
258
|
+
type: "boolean",
|
|
259
|
+
required: true,
|
|
260
|
+
defaultTo: true,
|
|
261
|
+
storage: { column: "invites_enabled" }
|
|
262
|
+
},
|
|
263
|
+
createdAt: {
|
|
264
|
+
type: "dateTime",
|
|
265
|
+
default: "now()",
|
|
266
|
+
storage: {
|
|
267
|
+
column: "created_at",
|
|
268
|
+
writeSerializer: "datetime-utc"
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
updatedAt: {
|
|
272
|
+
type: "dateTime",
|
|
273
|
+
default: "now()",
|
|
274
|
+
storage: {
|
|
275
|
+
column: "updated_at",
|
|
276
|
+
writeSerializer: "datetime-utc"
|
|
277
|
+
}
|
|
165
278
|
}
|
|
279
|
+
},
|
|
280
|
+
messages: {
|
|
281
|
+
validation: "Fix invalid workspace settings values and try again.",
|
|
282
|
+
saveSuccess: "Workspace settings updated.",
|
|
283
|
+
saveError: "Unable to update workspace settings.",
|
|
284
|
+
apiValidation: "Validation failed."
|
|
285
|
+
},
|
|
286
|
+
crudOperations: ["view", "list", "create", "replace", "patch"],
|
|
287
|
+
crud: {
|
|
288
|
+
output: workspaceSettingsOutputSchema,
|
|
289
|
+
body: workspaceSettingsBodySchema
|
|
166
290
|
}
|
|
167
|
-
};
|
|
291
|
+
});
|
|
168
292
|
|
|
169
|
-
export {
|
|
293
|
+
export {
|
|
294
|
+
WORKSPACE_SETTINGS_FIELD_KEYS,
|
|
295
|
+
workspaceSettingsOutputSchema,
|
|
296
|
+
workspaceSettingsResource
|
|
297
|
+
};
|
|
@@ -17,13 +17,25 @@ function createKnexStub() {
|
|
|
17
17
|
return knex;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function createApiStub() {
|
|
21
|
+
return {
|
|
22
|
+
resources: {
|
|
23
|
+
workspaceInvites: {},
|
|
24
|
+
workspaceMemberships: {},
|
|
25
|
+
workspaces: {},
|
|
26
|
+
workspaceSettings: {}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
test("workspaces-core repositories expose withTransaction", async () => {
|
|
21
32
|
const knex = createKnexStub();
|
|
33
|
+
const api = createApiStub();
|
|
22
34
|
const repositories = [
|
|
23
|
-
createWorkspaceInvitesRepository(knex),
|
|
24
|
-
createWorkspaceMembershipsRepository(knex),
|
|
25
|
-
createWorkspacesRepository(knex),
|
|
26
|
-
createWorkspaceSettingsRepository(knex)
|
|
35
|
+
createWorkspaceInvitesRepository({ api, knex }),
|
|
36
|
+
createWorkspaceMembershipsRepository({ api, knex }),
|
|
37
|
+
createWorkspacesRepository({ api, knex }),
|
|
38
|
+
createWorkspaceSettingsRepository({ api, knex })
|
|
27
39
|
];
|
|
28
40
|
|
|
29
41
|
for (const repository of repositories) {
|
|
@@ -3,8 +3,10 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import "
|
|
6
|
+
import { resolveStructuredSchemaTransportSchema } from "@jskit-ai/kernel/shared/validators";
|
|
7
7
|
import { workspaceMembersResource } from "../src/shared/resources/workspaceMembersResource.js";
|
|
8
|
+
import { workspaceInvitesResource } from "../src/shared/resources/workspaceInvitesResource.js";
|
|
9
|
+
import { workspaceMembershipsResource } from "../src/shared/resources/workspaceMembershipsResource.js";
|
|
8
10
|
import { workspaceResource } from "../src/shared/resources/workspaceResource.js";
|
|
9
11
|
import { workspaceSettingsResource } from "../src/shared/resources/workspaceSettingsResource.js";
|
|
10
12
|
|
|
@@ -28,13 +30,27 @@ function assertResourceOperationMessages(resource, operationName, label) {
|
|
|
28
30
|
|
|
29
31
|
test("workspaces-core resources expose messages for all operations", () => {
|
|
30
32
|
const resources = {
|
|
31
|
-
workspace:
|
|
32
|
-
|
|
33
|
+
workspace: {
|
|
34
|
+
resource: workspaceResource,
|
|
35
|
+
operations: ["view", "list", "create", "replace", "patch"]
|
|
36
|
+
},
|
|
37
|
+
workspaceSettings: {
|
|
38
|
+
resource: workspaceSettingsResource,
|
|
39
|
+
operations: ["view", "list", "create", "replace", "patch"]
|
|
40
|
+
},
|
|
41
|
+
workspaceMemberships: {
|
|
42
|
+
resource: workspaceMembershipsResource,
|
|
43
|
+
operations: ["view", "list", "create", "patch"]
|
|
44
|
+
},
|
|
45
|
+
workspaceInvites: {
|
|
46
|
+
resource: workspaceInvitesResource,
|
|
47
|
+
operations: ["view", "list", "create", "patch"]
|
|
48
|
+
}
|
|
33
49
|
};
|
|
34
50
|
|
|
35
|
-
for (const [label,
|
|
36
|
-
for (const operationName of
|
|
37
|
-
assertResourceOperationMessages(resource, operationName, label);
|
|
51
|
+
for (const [label, spec] of Object.entries(resources)) {
|
|
52
|
+
for (const operationName of spec.operations) {
|
|
53
|
+
assertResourceOperationMessages(spec.resource, operationName, label);
|
|
38
54
|
}
|
|
39
55
|
}
|
|
40
56
|
});
|
|
@@ -53,22 +69,29 @@ test("workspaces-core specialized resource operations expose messages and valida
|
|
|
53
69
|
|
|
54
70
|
for (const { label, operation } of operationSpecs) {
|
|
55
71
|
assert.equal(typeof operation?.messages, "object", `${label}.messages must be an object.`);
|
|
56
|
-
assert.equal(
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
assert.equal(
|
|
73
|
+
typeof resolveStructuredSchemaTransportSchema(operation?.output, {
|
|
74
|
+
context: `${label}.output`,
|
|
75
|
+
defaultMode: "replace"
|
|
76
|
+
}),
|
|
77
|
+
"object",
|
|
78
|
+
`${label}.output transport schema must exist.`
|
|
79
|
+
);
|
|
80
|
+
if (operation?.body) {
|
|
81
|
+
assert.equal(typeof operation.body.schema, "object", `${label}.body.schema must exist.`);
|
|
59
82
|
}
|
|
60
|
-
if (operation?.
|
|
61
|
-
assert.equal(typeof operation.
|
|
83
|
+
if (operation?.params) {
|
|
84
|
+
assert.equal(typeof operation.params.schema, "object", `${label}.params.schema must exist.`);
|
|
62
85
|
}
|
|
63
|
-
if (operation?.
|
|
64
|
-
assert.equal(typeof operation.
|
|
86
|
+
if (operation?.query) {
|
|
87
|
+
assert.equal(typeof operation.query.schema, "object", `${label}.query.schema must exist.`);
|
|
65
88
|
}
|
|
66
89
|
}
|
|
67
90
|
});
|
|
68
91
|
|
|
69
|
-
test("workspaces-core
|
|
92
|
+
test("workspaces-core does not contain src/shared/schema", () => {
|
|
70
93
|
const testFilePath = fileURLToPath(import.meta.url);
|
|
71
94
|
const packageRoot = path.resolve(path.dirname(testFilePath), "..");
|
|
72
|
-
const
|
|
73
|
-
assert.equal(existsSync(
|
|
95
|
+
const sharedSchemaDirPath = path.join(packageRoot, "src", "shared", "schema");
|
|
96
|
+
assert.equal(existsSync(sharedSchemaDirPath), false, "src/shared/schema must not exist.");
|
|
74
97
|
});
|
|
@@ -1,36 +1,53 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import {
|
|
3
|
+
import { createSchema } from "json-rest-schema";
|
|
4
4
|
import { compileRouteValidator } from "@jskit-ai/kernel/_testable";
|
|
5
5
|
import { routeParamsValidator } from "../src/server/common/validators/routeParamsValidator.js";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
function composeSchemaDefinition(...definitions) {
|
|
8
|
+
return Object.freeze({
|
|
9
|
+
schema: createSchema(
|
|
10
|
+
Object.assign({}, ...definitions.map((definition) => definition.schema.getFieldDefinitions()))
|
|
11
|
+
),
|
|
12
|
+
mode: "patch"
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test("routeParamsValidator exposes a shared workspace route params schema definition", () => {
|
|
8
17
|
assert.equal(typeof routeParamsValidator.schema, "object");
|
|
9
|
-
assert.equal(
|
|
18
|
+
assert.equal(routeParamsValidator.mode, "patch");
|
|
10
19
|
});
|
|
11
20
|
|
|
12
|
-
test("workspace route validator pipeline uses the shared params validator
|
|
21
|
+
test("workspace route validator pipeline uses the shared params validator with a composed query schema", () => {
|
|
13
22
|
const paginationQueryValidator = Object.freeze({
|
|
14
|
-
schema:
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
schema: createSchema({
|
|
24
|
+
cursor: {
|
|
25
|
+
type: "string",
|
|
26
|
+
required: false,
|
|
27
|
+
minLength: 1
|
|
18
28
|
},
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
limit: {
|
|
30
|
+
type: "string",
|
|
31
|
+
required: false,
|
|
32
|
+
pattern: "^[0-9]+$"
|
|
33
|
+
}
|
|
34
|
+
}),
|
|
35
|
+
mode: "patch"
|
|
21
36
|
});
|
|
22
37
|
const searchQueryValidator = Object.freeze({
|
|
23
|
-
schema:
|
|
24
|
-
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
schema: createSchema({
|
|
39
|
+
search: {
|
|
40
|
+
type: "string",
|
|
41
|
+
required: false,
|
|
42
|
+
minLength: 1
|
|
43
|
+
}
|
|
44
|
+
}),
|
|
45
|
+
mode: "patch"
|
|
29
46
|
});
|
|
30
47
|
|
|
31
48
|
const compiled = compileRouteValidator({
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
params: routeParamsValidator,
|
|
50
|
+
query: composeSchemaDefinition(paginationQueryValidator, searchQueryValidator)
|
|
34
51
|
});
|
|
35
52
|
|
|
36
53
|
assert.equal(compiled.schema.params.type, "object");
|
|
@@ -39,7 +56,8 @@ test("workspace route validator pipeline uses the shared params validator and me
|
|
|
39
56
|
assert.equal(typeof compiled.schema.params.properties.memberUserId, "object");
|
|
40
57
|
assert.equal(typeof compiled.schema.params.properties.inviteId, "object");
|
|
41
58
|
assert.equal(typeof compiled.schema.params.properties.provider, "object");
|
|
42
|
-
|
|
59
|
+
const normalizedParams = compiled.input.params({ workspaceSlug: "ACME" });
|
|
60
|
+
assert.equal(normalizedParams.workspaceSlug, "acme");
|
|
43
61
|
|
|
44
62
|
assert.equal(compiled.schema.querystring.type, "object");
|
|
45
63
|
assert.equal(compiled.schema.querystring.additionalProperties, false);
|