@jskit-ai/crud-server-generator 0.1.26 → 0.1.28
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 +19 -14
- package/package.json +9 -7
- package/src/server/{CrudServiceProvider.js → CrudProvider.js} +2 -2
- package/src/server/buildTemplateContext.js +278 -32
- package/src/server/subcommands/addField.js +238 -0
- package/src/server/subcommands/resourceAst.js +632 -0
- package/src/shared/crud/crudResource.js +93 -98
- package/templates/migrations/crud_initial.cjs +1 -0
- package/templates/src/local-package/package.descriptor.mjs +2 -2
- package/templates/src/local-package/server/{CrudServiceProvider.js → CrudProvider.js} +13 -8
- package/templates/src/local-package/server/actions.js +24 -10
- package/templates/src/local-package/server/registerRoutes.js +32 -33
- package/templates/src/local-package/server/repository.js +33 -132
- package/templates/src/local-package/server/service.js +88 -47
- package/templates/src/local-package/shared/crudResource.js +77 -45
- package/test/addFieldSubcommand.test.js +167 -0
- package/test/buildTemplateContext.test.js +198 -4
- package/test/crudResource.test.js +6 -0
- package/test/crudServerGuards.test.js +43 -49
- package/test/crudService.test.js +93 -5
- package/test/routeInputContracts.test.js +144 -41
- package/test-support/templateServerFixture.js +169 -0
- package/src/server/actionIds.js +0 -22
- package/src/server/actions.js +0 -152
- package/src/server/registerRoutes.js +0 -234
- package/src/server/repository.js +0 -162
- package/src/server/service.js +0 -96
|
@@ -7,76 +7,13 @@ import {
|
|
|
7
7
|
normalizeObjectInput,
|
|
8
8
|
createCursorListValidator
|
|
9
9
|
} from "@jskit-ai/kernel/shared/validators";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
throw new TypeError(`${fieldLabel} must be a valid number.`);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return normalized;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function normalizeDateTimeField(value, { fieldLabel = "Date field" } = {}) {
|
|
22
|
-
try {
|
|
23
|
-
return toIsoString(value);
|
|
24
|
-
} catch {
|
|
25
|
-
throw new TypeError(`${fieldLabel} must be a valid date/time.`);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function normalizeDatabaseDateTimeField(value, { fieldLabel = "Date field" } = {}) {
|
|
30
|
-
try {
|
|
31
|
-
return toDatabaseDateTimeUtc(value);
|
|
32
|
-
} catch {
|
|
33
|
-
throw new TypeError(`${fieldLabel} must be a valid date/time.`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function normalizeRecordInput(payload = {}) {
|
|
38
|
-
const source = normalizeObjectInput(payload);
|
|
39
|
-
const normalized = {};
|
|
40
|
-
|
|
41
|
-
if (Object.hasOwn(source, "textField")) {
|
|
42
|
-
normalized.textField = normalizeText(source.textField);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (Object.hasOwn(source, "dateField")) {
|
|
46
|
-
normalized.dateField = normalizeDatabaseDateTimeField(source.dateField, {
|
|
47
|
-
fieldLabel: "Date field"
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (Object.hasOwn(source, "numberField")) {
|
|
52
|
-
normalized.numberField = normalizeNumberField(source.numberField, {
|
|
53
|
-
fieldLabel: "Number field"
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return normalized;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function normalizeRecordOutput(payload = {}) {
|
|
61
|
-
const source = normalizeObjectInput(payload);
|
|
10
|
+
import {
|
|
11
|
+
normalizeText,
|
|
12
|
+
normalizeFiniteNumber,
|
|
13
|
+
normalizeIfPresent
|
|
14
|
+
} from "@jskit-ai/kernel/shared/support/normalize";
|
|
62
15
|
|
|
63
|
-
|
|
64
|
-
id: Number(source.id),
|
|
65
|
-
textField: normalizeText(source.textField),
|
|
66
|
-
dateField: normalizeDateTimeField(source.dateField, {
|
|
67
|
-
fieldLabel: "Date field"
|
|
68
|
-
}),
|
|
69
|
-
numberField: normalizeNumberField(source.numberField, {
|
|
70
|
-
fieldLabel: "Number field"
|
|
71
|
-
}),
|
|
72
|
-
createdAt: normalizeDateTimeField(source.createdAt, {
|
|
73
|
-
fieldLabel: "Created at"
|
|
74
|
-
}),
|
|
75
|
-
updatedAt: normalizeDateTimeField(source.updatedAt, {
|
|
76
|
-
fieldLabel: "Updated at"
|
|
77
|
-
})
|
|
78
|
-
};
|
|
79
|
-
}
|
|
16
|
+
const RESOURCE_LOOKUP_CONTAINER_KEY = "lookups";
|
|
80
17
|
|
|
81
18
|
const recordOutputSchema = Type.Object(
|
|
82
19
|
{
|
|
@@ -85,7 +22,8 @@ const recordOutputSchema = Type.Object(
|
|
|
85
22
|
dateField: Type.String({ minLength: 1 }),
|
|
86
23
|
numberField: Type.Number(),
|
|
87
24
|
createdAt: Type.String({ minLength: 1 }),
|
|
88
|
-
updatedAt: Type.String({ minLength: 1 })
|
|
25
|
+
updatedAt: Type.String({ minLength: 1 }),
|
|
26
|
+
[RESOURCE_LOOKUP_CONTAINER_KEY]: Type.Optional(Type.Record(Type.String(), Type.Unknown()))
|
|
89
27
|
},
|
|
90
28
|
{ additionalProperties: false }
|
|
91
29
|
);
|
|
@@ -126,13 +64,79 @@ const recordBodySchema = Type.Object(
|
|
|
126
64
|
}
|
|
127
65
|
);
|
|
128
66
|
|
|
67
|
+
const patchBodySchema = Type.Partial(recordBodySchema, { additionalProperties: false });
|
|
68
|
+
|
|
129
69
|
const recordOutputValidator = Object.freeze({
|
|
130
70
|
schema: recordOutputSchema,
|
|
131
|
-
normalize
|
|
71
|
+
normalize(payload = {}) {
|
|
72
|
+
const source = normalizeObjectInput(payload);
|
|
73
|
+
const normalized = {
|
|
74
|
+
id: Number(source.id),
|
|
75
|
+
textField: normalizeText(source.textField),
|
|
76
|
+
dateField: toIsoString(source.dateField),
|
|
77
|
+
numberField: normalizeFiniteNumber(source.numberField),
|
|
78
|
+
createdAt: normalizeIfPresent(source.createdAt, toIsoString),
|
|
79
|
+
updatedAt: normalizeIfPresent(source.updatedAt, toIsoString)
|
|
80
|
+
};
|
|
81
|
+
if (Object.hasOwn(source, RESOURCE_LOOKUP_CONTAINER_KEY)) {
|
|
82
|
+
normalized[RESOURCE_LOOKUP_CONTAINER_KEY] = source[RESOURCE_LOOKUP_CONTAINER_KEY];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return normalized;
|
|
86
|
+
}
|
|
132
87
|
});
|
|
133
88
|
|
|
89
|
+
const listOutputValidator = createCursorListValidator(recordOutputValidator);
|
|
90
|
+
|
|
91
|
+
const createBodyValidator = Object.freeze({
|
|
92
|
+
schema: recordBodySchema,
|
|
93
|
+
normalize(payload = {}) {
|
|
94
|
+
const source = normalizeObjectInput(payload);
|
|
95
|
+
const normalized = {};
|
|
96
|
+
|
|
97
|
+
if (Object.hasOwn(source, "textField")) {
|
|
98
|
+
normalized.textField = normalizeText(source.textField);
|
|
99
|
+
}
|
|
100
|
+
if (Object.hasOwn(source, "dateField")) {
|
|
101
|
+
normalized.dateField = toDatabaseDateTimeUtc(source.dateField);
|
|
102
|
+
}
|
|
103
|
+
if (Object.hasOwn(source, "numberField")) {
|
|
104
|
+
normalized.numberField = normalizeFiniteNumber(source.numberField);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return normalized;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const patchBodyValidator = Object.freeze({
|
|
112
|
+
schema: patchBodySchema,
|
|
113
|
+
normalize: createBodyValidator.normalize
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const deleteOutputValidator = Object.freeze({
|
|
117
|
+
schema: Type.Object(
|
|
118
|
+
{
|
|
119
|
+
id: Type.Integer({ minimum: 1 }),
|
|
120
|
+
deleted: Type.Literal(true)
|
|
121
|
+
},
|
|
122
|
+
{ additionalProperties: false }
|
|
123
|
+
),
|
|
124
|
+
normalize(payload = {}) {
|
|
125
|
+
const source = normalizeObjectInput(payload);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
id: Number(source.id),
|
|
129
|
+
deleted: true
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const CRUD_RESOURCE_FIELD_META = [];
|
|
135
|
+
|
|
134
136
|
const crudResource = {
|
|
135
137
|
resource: "crud",
|
|
138
|
+
tableName: "crud",
|
|
139
|
+
idColumn: "id",
|
|
136
140
|
messages: {
|
|
137
141
|
validation: "Fix invalid CRUD values and try again.",
|
|
138
142
|
saveSuccess: "Record saved.",
|
|
@@ -140,10 +144,20 @@ const crudResource = {
|
|
|
140
144
|
deleteSuccess: "Record deleted.",
|
|
141
145
|
deleteError: "Unable to delete record."
|
|
142
146
|
},
|
|
147
|
+
contract: {
|
|
148
|
+
lookup: {
|
|
149
|
+
containerKey: RESOURCE_LOOKUP_CONTAINER_KEY,
|
|
150
|
+
defaultInclude: "*", // Set "none" to disable lookup hydration unless include=... is passed.
|
|
151
|
+
maxDepth: 3 // Lower this to limit nested lookup hydration depth.
|
|
152
|
+
}
|
|
153
|
+
},
|
|
143
154
|
operations: {
|
|
144
155
|
list: {
|
|
156
|
+
realtime: {
|
|
157
|
+
events: ["crud.record.changed"] // Add more events e.g. for lookup records
|
|
158
|
+
},
|
|
145
159
|
method: "GET",
|
|
146
|
-
outputValidator:
|
|
160
|
+
outputValidator: listOutputValidator
|
|
147
161
|
},
|
|
148
162
|
view: {
|
|
149
163
|
method: "GET",
|
|
@@ -151,41 +165,22 @@ const crudResource = {
|
|
|
151
165
|
},
|
|
152
166
|
create: {
|
|
153
167
|
method: "POST",
|
|
154
|
-
bodyValidator:
|
|
155
|
-
schema: recordBodySchema,
|
|
156
|
-
normalize: normalizeRecordInput
|
|
157
|
-
},
|
|
168
|
+
bodyValidator: createBodyValidator,
|
|
158
169
|
outputValidator: recordOutputValidator
|
|
159
170
|
},
|
|
160
171
|
patch: {
|
|
161
172
|
method: "PATCH",
|
|
162
|
-
bodyValidator:
|
|
163
|
-
schema: Type.Partial(recordBodySchema, { additionalProperties: false }),
|
|
164
|
-
normalize: normalizeRecordInput
|
|
165
|
-
},
|
|
173
|
+
bodyValidator: patchBodyValidator,
|
|
166
174
|
outputValidator: recordOutputValidator
|
|
167
175
|
},
|
|
168
176
|
delete: {
|
|
169
177
|
method: "DELETE",
|
|
170
|
-
outputValidator:
|
|
171
|
-
schema: Type.Object(
|
|
172
|
-
{
|
|
173
|
-
id: Type.Integer({ minimum: 1 }),
|
|
174
|
-
deleted: Type.Literal(true)
|
|
175
|
-
},
|
|
176
|
-
{ additionalProperties: false }
|
|
177
|
-
),
|
|
178
|
-
normalize(payload = {}) {
|
|
179
|
-
const source = normalizeObjectInput(payload);
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
id: Number(source.id),
|
|
183
|
-
deleted: true
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
}
|
|
178
|
+
outputValidator: deleteOutputValidator
|
|
187
179
|
}
|
|
188
|
-
}
|
|
180
|
+
},
|
|
181
|
+
fieldMeta: CRUD_RESOURCE_FIELD_META
|
|
189
182
|
};
|
|
190
183
|
|
|
184
|
+
void CRUD_RESOURCE_FIELD_META;
|
|
185
|
+
|
|
191
186
|
export { crudResource };
|
|
@@ -27,8 +27,8 @@ export default Object.freeze({
|
|
|
27
27
|
server: {
|
|
28
28
|
providers: [
|
|
29
29
|
{
|
|
30
|
-
entrypoint: "src/server/${option:namespace|pascal}
|
|
31
|
-
export: "${option:namespace|pascal}
|
|
30
|
+
entrypoint: "src/server/${option:namespace|pascal}Provider.js",
|
|
31
|
+
export: "${option:namespace|pascal}Provider"
|
|
32
32
|
}
|
|
33
33
|
]
|
|
34
34
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
|
|
2
2
|
import { resolveCrudSurfacePolicyFromAppConfig } from "@jskit-ai/crud-core/server/crudModuleConfig";
|
|
3
|
+
import {
|
|
4
|
+
createCrudLookupProviderResolver,
|
|
5
|
+
createCrudLookupProvider
|
|
6
|
+
} from "@jskit-ai/crud-core/server/lookupProviders";
|
|
3
7
|
import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
|
|
4
8
|
import { createRepository } from "./repository.js";
|
|
5
9
|
import {
|
|
@@ -8,8 +12,6 @@ import {
|
|
|
8
12
|
} from "./service.js";
|
|
9
13
|
import { createActions } from "./actions.js";
|
|
10
14
|
import { registerRoutes } from "./registerRoutes.js";
|
|
11
|
-
const NAMESPACE_${option:namespace|snake|upper}_TABLE_NAME = __JSKIT_CRUD_TABLE_NAME__;
|
|
12
|
-
const NAMESPACE_${option:namespace|snake|upper}_ID_COLUMN = __JSKIT_CRUD_ID_COLUMN__;
|
|
13
15
|
const CRUD_MODULE_CONFIG = Object.freeze({
|
|
14
16
|
namespace: "${option:namespace|snake}",
|
|
15
17
|
surface: "${option:surface|lower}",
|
|
@@ -19,18 +21,18 @@ const CRUD_MODULE_CONFIG = Object.freeze({
|
|
|
19
21
|
|
|
20
22
|
function resolveCrudPolicyFromApp(app) {
|
|
21
23
|
return resolveCrudSurfacePolicyFromAppConfig(CRUD_MODULE_CONFIG, resolveAppConfig(app), {
|
|
22
|
-
context: "${option:namespace|pascal}
|
|
24
|
+
context: "${option:namespace|pascal}Provider"
|
|
23
25
|
});
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
class ${option:namespace|pascal}
|
|
28
|
+
class ${option:namespace|pascal}Provider {
|
|
27
29
|
static id = "crud.${option:namespace|snake}";
|
|
28
30
|
|
|
29
31
|
static dependsOn = ["runtime.actions", "runtime.database", "auth.policy.fastify", "local.main", "users.core"];
|
|
30
32
|
|
|
31
33
|
register(app) {
|
|
32
34
|
if (!app || typeof app.singleton !== "function" || typeof app.service !== "function" || typeof app.actions !== "function") {
|
|
33
|
-
throw new Error("${option:namespace|pascal}
|
|
35
|
+
throw new Error("${option:namespace|pascal}Provider requires application singleton()/service()/actions().");
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
const crudPolicy = resolveCrudPolicyFromApp(app);
|
|
@@ -38,11 +40,14 @@ class ${option:namespace|pascal}ServiceProvider {
|
|
|
38
40
|
app.singleton("repository.${option:namespace|snake}", (scope) => {
|
|
39
41
|
const knex = scope.make("jskit.database.knex");
|
|
40
42
|
return createRepository(knex, {
|
|
41
|
-
|
|
42
|
-
idColumn: NAMESPACE_${option:namespace|snake|upper}_ID_COLUMN
|
|
43
|
+
resolveLookupProvider: createCrudLookupProviderResolver(scope)
|
|
43
44
|
});
|
|
44
45
|
});
|
|
45
46
|
|
|
47
|
+
app.singleton("crud.lookup.${option:namespace|snake}", (scope) => {
|
|
48
|
+
return createCrudLookupProvider(scope.make("repository.${option:namespace|snake}"));
|
|
49
|
+
});
|
|
50
|
+
|
|
46
51
|
app.service(
|
|
47
52
|
"crud.${option:namespace|snake}",
|
|
48
53
|
(scope) => {
|
|
@@ -81,4 +86,4 @@ class ${option:namespace|pascal}ServiceProvider {
|
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
|
|
84
|
-
export { ${option:namespace|pascal}
|
|
89
|
+
export { ${option:namespace|pascal}Provider };
|
|
@@ -2,10 +2,17 @@ import {
|
|
|
2
2
|
cursorPaginationQueryValidator,
|
|
3
3
|
recordIdParamsValidator
|
|
4
4
|
} from "@jskit-ai/kernel/shared/validators";
|
|
5
|
+
import {
|
|
6
|
+
listSearchQueryValidator,
|
|
7
|
+
lookupIncludeQueryValidator,
|
|
8
|
+
createCrudParentFilterQueryValidator
|
|
9
|
+
} from "@jskit-ai/crud-core/server/listQueryValidators";
|
|
5
10
|
import { workspaceSlugParamsValidator } from "@jskit-ai/users-core/server/validators/routeParamsValidator";
|
|
6
|
-
import {
|
|
11
|
+
import { resource } from "../shared/${option:namespace|singular|camel}Resource.js";
|
|
7
12
|
import { actionIds } from "./actionIds.js";
|
|
8
13
|
|
|
14
|
+
const listParentFilterQueryValidator = createCrudParentFilterQueryValidator(resource);
|
|
15
|
+
|
|
9
16
|
function requireActionSurface(surface = "") {
|
|
10
17
|
const normalizedSurface = String(surface || "").trim().toLowerCase();
|
|
11
18
|
if (!normalizedSurface) {
|
|
@@ -28,8 +35,14 @@ function createActions({ surface = "" } = {}) {
|
|
|
28
35
|
permission: {
|
|
29
36
|
require: "authenticated"
|
|
30
37
|
},
|
|
31
|
-
inputValidator: [
|
|
32
|
-
|
|
38
|
+
inputValidator: [
|
|
39
|
+
workspaceSlugParamsValidator,
|
|
40
|
+
cursorPaginationQueryValidator,
|
|
41
|
+
listSearchQueryValidator,
|
|
42
|
+
listParentFilterQueryValidator,
|
|
43
|
+
lookupIncludeQueryValidator
|
|
44
|
+
],
|
|
45
|
+
outputValidator: resource.operations.list.outputValidator,
|
|
33
46
|
idempotency: "none",
|
|
34
47
|
audit: {
|
|
35
48
|
actionName: actionIds.list
|
|
@@ -51,7 +64,7 @@ function createActions({ surface = "" } = {}) {
|
|
|
51
64
|
permission: {
|
|
52
65
|
require: "authenticated"
|
|
53
66
|
},
|
|
54
|
-
inputValidator: [workspaceSlugParamsValidator, recordIdParamsValidator],
|
|
67
|
+
inputValidator: [workspaceSlugParamsValidator, recordIdParamsValidator, lookupIncludeQueryValidator],
|
|
55
68
|
outputValidator: ${option:namespace|singular|camel}Resource.operations.view.outputValidator,
|
|
56
69
|
idempotency: "none",
|
|
57
70
|
audit: {
|
|
@@ -61,7 +74,8 @@ function createActions({ surface = "" } = {}) {
|
|
|
61
74
|
async execute(input, context, deps) {
|
|
62
75
|
return deps.${option:namespace|camel}Service.getRecord(input.recordId, {
|
|
63
76
|
context,
|
|
64
|
-
visibilityContext: context?.visibilityContext
|
|
77
|
+
visibilityContext: context?.visibilityContext,
|
|
78
|
+
include: input.include
|
|
65
79
|
});
|
|
66
80
|
}
|
|
67
81
|
},
|
|
@@ -77,10 +91,10 @@ function createActions({ surface = "" } = {}) {
|
|
|
77
91
|
inputValidator: [
|
|
78
92
|
workspaceSlugParamsValidator,
|
|
79
93
|
{
|
|
80
|
-
payload:
|
|
94
|
+
payload: resource.operations.create.bodyValidator
|
|
81
95
|
}
|
|
82
96
|
],
|
|
83
|
-
outputValidator:
|
|
97
|
+
outputValidator: resource.operations.create.outputValidator,
|
|
84
98
|
idempotency: "optional",
|
|
85
99
|
audit: {
|
|
86
100
|
actionName: actionIds.create
|
|
@@ -106,10 +120,10 @@ function createActions({ surface = "" } = {}) {
|
|
|
106
120
|
workspaceSlugParamsValidator,
|
|
107
121
|
recordIdParamsValidator,
|
|
108
122
|
{
|
|
109
|
-
patch:
|
|
123
|
+
patch: resource.operations.patch.bodyValidator
|
|
110
124
|
}
|
|
111
125
|
],
|
|
112
|
-
outputValidator:
|
|
126
|
+
outputValidator: resource.operations.patch.outputValidator,
|
|
113
127
|
idempotency: "optional",
|
|
114
128
|
audit: {
|
|
115
129
|
actionName: actionIds.update
|
|
@@ -132,7 +146,7 @@ function createActions({ surface = "" } = {}) {
|
|
|
132
146
|
require: "authenticated"
|
|
133
147
|
},
|
|
134
148
|
inputValidator: [workspaceSlugParamsValidator, recordIdParamsValidator],
|
|
135
|
-
outputValidator:
|
|
149
|
+
outputValidator: resource.operations.delete.outputValidator,
|
|
136
150
|
idempotency: "optional",
|
|
137
151
|
audit: {
|
|
138
152
|
actionName: actionIds.delete
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
|
|
2
2
|
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
3
|
+
import {
|
|
4
|
+
listSearchQueryValidator,
|
|
5
|
+
lookupIncludeQueryValidator,
|
|
6
|
+
createCrudParentFilterQueryValidator
|
|
7
|
+
} from "@jskit-ai/crud-core/server/listQueryValidators";
|
|
3
8
|
import {
|
|
4
9
|
cursorPaginationQueryValidator,
|
|
5
10
|
recordIdParamsValidator
|
|
6
11
|
} from "@jskit-ai/kernel/shared/validators";
|
|
7
12
|
import { routeParamsValidator } from "@jskit-ai/users-core/server/validators/routeParamsValidator";
|
|
8
|
-
import {
|
|
13
|
+
import { checkRouteVisibility } from "@jskit-ai/users-core/shared/support/usersVisibility";
|
|
9
14
|
import { buildWorkspaceInputFromRouteParams } from "@jskit-ai/users-core/server/support/workspaceRouteInput";
|
|
10
15
|
import { resolveApiBasePath } from "@jskit-ai/users-core/shared/support/usersApiPaths";
|
|
11
16
|
import { actionIds } from "./actionIds.js";
|
|
12
|
-
import {
|
|
17
|
+
import { resource } from "../shared/${option:namespace|singular|camel}Resource.js";
|
|
18
|
+
|
|
19
|
+
const listParentFilterQueryValidator = createCrudParentFilterQueryValidator(resource);
|
|
13
20
|
|
|
14
21
|
function registerRoutes(
|
|
15
22
|
app,
|
|
@@ -20,17 +27,7 @@ function registerRoutes(
|
|
|
20
27
|
routeRelativePath = ""
|
|
21
28
|
} = {}
|
|
22
29
|
) {
|
|
23
|
-
if (!app || typeof app.make !== "function") {
|
|
24
|
-
throw new Error("registerRoutes requires application make().");
|
|
25
|
-
}
|
|
26
|
-
if (!String(routeRelativePath || "").trim()) {
|
|
27
|
-
throw new Error("registerRoutes requires routeRelativePath.");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
30
|
const router = app.make("jskit.http.router");
|
|
31
|
-
const routeVisibility = normalizeScopedRouteVisibility(routeOwnershipFilter, {
|
|
32
|
-
fallback: "public"
|
|
33
|
-
});
|
|
34
31
|
const normalizedRouteSurface = normalizeSurfaceId(routeSurface);
|
|
35
32
|
const routeBase = resolveApiBasePath({
|
|
36
33
|
surfaceRequiresWorkspace: routeSurfaceRequiresWorkspace === true,
|
|
@@ -43,27 +40,27 @@ function registerRoutes(
|
|
|
43
40
|
{
|
|
44
41
|
auth: "required",
|
|
45
42
|
surface: normalizedRouteSurface,
|
|
46
|
-
visibility:
|
|
43
|
+
visibility: checkRouteVisibility(routeOwnershipFilter),
|
|
47
44
|
meta: {
|
|
48
45
|
tags: ["crud"],
|
|
49
46
|
summary: "List records."
|
|
50
47
|
},
|
|
51
48
|
paramsValidator: routeParamsValidator,
|
|
52
|
-
queryValidator:
|
|
49
|
+
queryValidator: [
|
|
50
|
+
cursorPaginationQueryValidator,
|
|
51
|
+
listSearchQueryValidator,
|
|
52
|
+
listParentFilterQueryValidator,
|
|
53
|
+
lookupIncludeQueryValidator
|
|
54
|
+
],
|
|
53
55
|
responseValidators: withStandardErrorResponses({
|
|
54
|
-
200:
|
|
56
|
+
200: resource.operations.list.outputValidator
|
|
55
57
|
})
|
|
56
58
|
},
|
|
57
59
|
async function (request, reply) {
|
|
58
60
|
const listInput = {
|
|
59
|
-
...buildWorkspaceInputFromRouteParams(request.input.params)
|
|
61
|
+
...buildWorkspaceInputFromRouteParams(request.input.params),
|
|
62
|
+
...(request.input.query || {})
|
|
60
63
|
};
|
|
61
|
-
if (request.input.query.cursor != null) {
|
|
62
|
-
listInput.cursor = request.input.query.cursor;
|
|
63
|
-
}
|
|
64
|
-
if (request.input.query.limit != null) {
|
|
65
|
-
listInput.limit = request.input.query.limit;
|
|
66
|
-
}
|
|
67
64
|
const response = await request.executeAction({
|
|
68
65
|
actionId: actionIds.list,
|
|
69
66
|
input: listInput
|
|
@@ -78,14 +75,15 @@ function registerRoutes(
|
|
|
78
75
|
{
|
|
79
76
|
auth: "required",
|
|
80
77
|
surface: normalizedRouteSurface,
|
|
81
|
-
visibility:
|
|
78
|
+
visibility: checkRouteVisibility(routeOwnershipFilter),
|
|
82
79
|
meta: {
|
|
83
80
|
tags: ["crud"],
|
|
84
81
|
summary: "View a record."
|
|
85
82
|
},
|
|
86
83
|
paramsValidator: [routeParamsValidator, recordIdParamsValidator],
|
|
84
|
+
queryValidator: [lookupIncludeQueryValidator],
|
|
87
85
|
responseValidators: withStandardErrorResponses({
|
|
88
|
-
200:
|
|
86
|
+
200: resource.operations.view.outputValidator
|
|
89
87
|
})
|
|
90
88
|
},
|
|
91
89
|
async function (request, reply) {
|
|
@@ -93,7 +91,8 @@ function registerRoutes(
|
|
|
93
91
|
actionId: actionIds.view,
|
|
94
92
|
input: {
|
|
95
93
|
...buildWorkspaceInputFromRouteParams(request.input.params),
|
|
96
|
-
recordId: request.input.params.recordId
|
|
94
|
+
recordId: request.input.params.recordId,
|
|
95
|
+
...(request.input.query || {})
|
|
97
96
|
}
|
|
98
97
|
});
|
|
99
98
|
reply.code(200).send(response);
|
|
@@ -106,16 +105,16 @@ function registerRoutes(
|
|
|
106
105
|
{
|
|
107
106
|
auth: "required",
|
|
108
107
|
surface: normalizedRouteSurface,
|
|
109
|
-
visibility:
|
|
108
|
+
visibility: checkRouteVisibility(routeOwnershipFilter),
|
|
110
109
|
meta: {
|
|
111
110
|
tags: ["crud"],
|
|
112
111
|
summary: "Create a record."
|
|
113
112
|
},
|
|
114
113
|
paramsValidator: routeParamsValidator,
|
|
115
|
-
bodyValidator:
|
|
114
|
+
bodyValidator: resource.operations.create.bodyValidator,
|
|
116
115
|
responseValidators: withStandardErrorResponses(
|
|
117
116
|
{
|
|
118
|
-
201:
|
|
117
|
+
201: resource.operations.create.outputValidator
|
|
119
118
|
},
|
|
120
119
|
{ includeValidation400: true }
|
|
121
120
|
)
|
|
@@ -138,16 +137,16 @@ function registerRoutes(
|
|
|
138
137
|
{
|
|
139
138
|
auth: "required",
|
|
140
139
|
surface: normalizedRouteSurface,
|
|
141
|
-
visibility:
|
|
140
|
+
visibility: checkRouteVisibility(routeOwnershipFilter),
|
|
142
141
|
meta: {
|
|
143
142
|
tags: ["crud"],
|
|
144
143
|
summary: "Update a record."
|
|
145
144
|
},
|
|
146
145
|
paramsValidator: [routeParamsValidator, recordIdParamsValidator],
|
|
147
|
-
bodyValidator:
|
|
146
|
+
bodyValidator: resource.operations.patch.bodyValidator,
|
|
148
147
|
responseValidators: withStandardErrorResponses(
|
|
149
148
|
{
|
|
150
|
-
200:
|
|
149
|
+
200: resource.operations.patch.outputValidator
|
|
151
150
|
},
|
|
152
151
|
{ includeValidation400: true }
|
|
153
152
|
)
|
|
@@ -171,14 +170,14 @@ function registerRoutes(
|
|
|
171
170
|
{
|
|
172
171
|
auth: "required",
|
|
173
172
|
surface: normalizedRouteSurface,
|
|
174
|
-
visibility:
|
|
173
|
+
visibility: checkRouteVisibility(routeOwnershipFilter),
|
|
175
174
|
meta: {
|
|
176
175
|
tags: ["crud"],
|
|
177
176
|
summary: "Delete a record."
|
|
178
177
|
},
|
|
179
178
|
paramsValidator: [routeParamsValidator, recordIdParamsValidator],
|
|
180
179
|
responseValidators: withStandardErrorResponses({
|
|
181
|
-
200:
|
|
180
|
+
200: resource.operations.delete.outputValidator
|
|
182
181
|
})
|
|
183
182
|
},
|
|
184
183
|
async function (request, reply) {
|