@rvoh/psychic 0.37.0-beta.9 → 0.37.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/cli/index.js +2 -1
- package/dist/cjs/src/generate/controller.js +5 -2
- package/dist/cjs/src/generate/helpers/generateControllerContent.js +12 -8
- package/dist/cjs/src/generate/helpers/generateResourceControllerSpecContent.js +31 -23
- package/dist/cjs/src/generate/resource.js +1 -0
- package/dist/esm/src/cli/index.js +2 -1
- package/dist/esm/src/generate/controller.js +5 -2
- package/dist/esm/src/generate/helpers/generateControllerContent.js +12 -8
- package/dist/esm/src/generate/helpers/generateResourceControllerSpecContent.js +31 -23
- package/dist/esm/src/generate/resource.js +1 -0
- package/dist/types/src/bin/index.d.ts +1 -0
- package/dist/types/src/generate/controller.d.ts +2 -1
- package/dist/types/src/generate/helpers/generateControllerContent.d.ts +2 -1
- package/dist/types/src/generate/helpers/generateResourceControllerSpecContent.d.ts +2 -1
- package/dist/types/src/generate/resource.d.ts +1 -0
- package/package.json +2 -2
|
@@ -21,7 +21,8 @@ class PsychicCLI {
|
|
|
21
21
|
.command('generate:resource')
|
|
22
22
|
.alias('g:resource')
|
|
23
23
|
.description('create a Dream model, migration, controller, serializer, and spec placeholders')
|
|
24
|
-
.option('--sti-base-serializer', '
|
|
24
|
+
.option('--sti-base-serializer', 'omits the serializer from the dream model, but does create the serializer so it can be extended by STI children')
|
|
25
|
+
.option('--owning-model <modelName>', 'The model class of the object that `associationQuery`/`createAssociation` will be performed on in the created controller and spec (e.g., "Host", "Guest") (simply to save time making changes to the generated code). Defaults to User')
|
|
25
26
|
.argument('<path>', 'URL path from root domain')
|
|
26
27
|
.argument('<modelName>', 'the name of the model to create, e.g. Post or Settings/CommunicationPreferences')
|
|
27
28
|
.argument('[columnsWithTypes...]', 'properties of the model property1:text/string/enum/etc. property2:text/string/enum/etc. ... propertyN:text/string/enum/etc.')
|
|
@@ -37,7 +37,7 @@ const psychicPath_js_1 = __importDefault(require("../helpers/path/psychicPath.js
|
|
|
37
37
|
const generateControllerContent_js_1 = __importDefault(require("./helpers/generateControllerContent.js"));
|
|
38
38
|
const generateControllerSpecContent_js_1 = __importDefault(require("./helpers/generateControllerSpecContent.js"));
|
|
39
39
|
const generateResourceControllerSpecContent_js_1 = __importDefault(require("./helpers/generateResourceControllerSpecContent.js"));
|
|
40
|
-
async function generateController({ fullyQualifiedControllerName, fullyQualifiedModelName, actions, columnsWithTypes = [], resourceSpecs = false, }) {
|
|
40
|
+
async function generateController({ fullyQualifiedControllerName, fullyQualifiedModelName, actions, columnsWithTypes = [], resourceSpecs = false, owningModel, }) {
|
|
41
41
|
fullyQualifiedModelName = fullyQualifiedModelName
|
|
42
42
|
? (0, dream_1.standardizeFullyQualifiedModelName)(fullyQualifiedModelName)
|
|
43
43
|
: fullyQualifiedModelName;
|
|
@@ -89,6 +89,7 @@ async function generateController({ fullyQualifiedControllerName, fullyQualified
|
|
|
89
89
|
fullyQualifiedControllerName,
|
|
90
90
|
fullyQualifiedModelName,
|
|
91
91
|
actions,
|
|
92
|
+
owningModel,
|
|
92
93
|
}));
|
|
93
94
|
}
|
|
94
95
|
catch (error) {
|
|
@@ -106,6 +107,7 @@ async function generateController({ fullyQualifiedControllerName, fullyQualified
|
|
|
106
107
|
fullyQualifiedModelName,
|
|
107
108
|
columnsWithTypes,
|
|
108
109
|
resourceSpecs,
|
|
110
|
+
owningModel,
|
|
109
111
|
});
|
|
110
112
|
}
|
|
111
113
|
function baseAncestorNameAndImport(controllerNameParts, isAdmin, { forBaseController }) {
|
|
@@ -117,7 +119,7 @@ function baseAncestorNameAndImport(controllerNameParts, isAdmin, { forBaseContro
|
|
|
117
119
|
: [`AuthedController`, `import AuthedController from '${dotFiles}/AuthedController.js'`]
|
|
118
120
|
: [maybeAncestorNameForBase, `import ${maybeAncestorNameForBase} from '${dotFiles}/BaseController.js'`];
|
|
119
121
|
}
|
|
120
|
-
async function generateControllerSpec({ fullyQualifiedControllerName, route, fullyQualifiedModelName, columnsWithTypes, resourceSpecs, }) {
|
|
122
|
+
async function generateControllerSpec({ fullyQualifiedControllerName, route, fullyQualifiedModelName, columnsWithTypes, resourceSpecs, owningModel, }) {
|
|
121
123
|
const { relFilePath, absDirPath, absFilePath } = (0, psychicFileAndDirPaths_js_1.default)((0, psychicPath_js_1.default)('controllerSpecs'), fullyQualifiedControllerName + `.spec.ts`);
|
|
122
124
|
try {
|
|
123
125
|
if (!EnvInternal_js_1.default.isTest)
|
|
@@ -129,6 +131,7 @@ async function generateControllerSpec({ fullyQualifiedControllerName, route, ful
|
|
|
129
131
|
route,
|
|
130
132
|
fullyQualifiedModelName,
|
|
131
133
|
columnsWithTypes,
|
|
134
|
+
owningModel,
|
|
132
135
|
})
|
|
133
136
|
: (0, generateControllerSpecContent_js_1.default)(fullyQualifiedControllerName));
|
|
134
137
|
}
|
|
@@ -7,10 +7,14 @@ exports.default = generateControllerContent;
|
|
|
7
7
|
const dream_1 = require("@rvoh/dream");
|
|
8
8
|
const pluralize_esm_1 = __importDefault(require("pluralize-esm"));
|
|
9
9
|
const relativePsychicPath_js_1 = __importDefault(require("../../helpers/path/relativePsychicPath.js"));
|
|
10
|
-
function generateControllerContent({ ancestorName, ancestorImportStatement, fullyQualifiedControllerName, fullyQualifiedModelName, actions = [], omitOpenApi = false, }) {
|
|
10
|
+
function generateControllerContent({ ancestorName, ancestorImportStatement, fullyQualifiedControllerName, fullyQualifiedModelName, actions = [], omitOpenApi = false, owningModel, }) {
|
|
11
11
|
fullyQualifiedControllerName = (0, dream_1.standardizeFullyQualifiedModelName)(fullyQualifiedControllerName);
|
|
12
12
|
const additionalImports = [];
|
|
13
13
|
const controllerClassName = (0, dream_1.globalClassNameFromFullyQualifiedModelName)(fullyQualifiedControllerName);
|
|
14
|
+
// Determine user model variables
|
|
15
|
+
const actualOwningModel = owningModel || 'User';
|
|
16
|
+
const owningModelClassName = (0, dream_1.globalClassNameFromFullyQualifiedModelName)(actualOwningModel);
|
|
17
|
+
const owningModelProperty = `current${owningModelClassName}`;
|
|
14
18
|
let modelClassName;
|
|
15
19
|
let modelAttributeName;
|
|
16
20
|
let pluralizedModelAttributeName;
|
|
@@ -32,7 +36,7 @@ function generateControllerContent({ ancestorName, ancestorImportStatement, full
|
|
|
32
36
|
description: 'Create ${aOrAnDreamModelName(modelClassName)}',
|
|
33
37
|
})
|
|
34
38
|
public async create() {
|
|
35
|
-
// const ${modelAttributeName} = await this.
|
|
39
|
+
// const ${modelAttributeName} = await this.${owningModelProperty}.createAssociation('${pluralizedModelAttributeName}', this.paramsFor(${modelClassName}))
|
|
36
40
|
// this.created(${modelAttributeName})
|
|
37
41
|
}`;
|
|
38
42
|
else
|
|
@@ -55,7 +59,7 @@ function generateControllerContent({ ancestorName, ancestorImportStatement, full
|
|
|
55
59
|
serializerKey: 'summary',
|
|
56
60
|
})
|
|
57
61
|
public async index() {
|
|
58
|
-
// const ${pluralizedModelAttributeName} = await this.
|
|
62
|
+
// const ${pluralizedModelAttributeName} = await this.${owningModelProperty}.associationQuery('${pluralizedModelAttributeName}').all()
|
|
59
63
|
// this.ok(${pluralizedModelAttributeName})
|
|
60
64
|
}`;
|
|
61
65
|
else
|
|
@@ -163,21 +167,21 @@ function generateControllerContent({ ancestorName, ancestorImportStatement, full
|
|
|
163
167
|
${omitOpenApi ? '' : openApiImport + '\n'}${ancestorImportStatement}${additionalImports.length ? '\n' + additionalImports.join('\n') : ''}${omitOpenApi ? '' : '\n\n' + openApiTags}
|
|
164
168
|
|
|
165
169
|
export default class ${controllerClassName} extends ${ancestorName} {
|
|
166
|
-
${methodDefs.join('\n\n')}${modelClassName ? privateMethods(modelClassName, actions) : ''}
|
|
170
|
+
${methodDefs.join('\n\n')}${modelClassName ? privateMethods(modelClassName, actions, owningModelProperty) : ''}
|
|
167
171
|
}
|
|
168
172
|
`;
|
|
169
173
|
}
|
|
170
|
-
function privateMethods(modelClassName, methods) {
|
|
174
|
+
function privateMethods(modelClassName, methods, owningModelProperty) {
|
|
171
175
|
const privateMethods = [];
|
|
172
176
|
if (methods.find(methodName => ['show', 'update', 'destroy'].includes(methodName)))
|
|
173
|
-
privateMethods.push(loadModelStatement(modelClassName));
|
|
177
|
+
privateMethods.push(loadModelStatement(modelClassName, owningModelProperty));
|
|
174
178
|
if (!privateMethods.length)
|
|
175
179
|
return '';
|
|
176
180
|
return `\n\n${privateMethods.join('\n\n')}`;
|
|
177
181
|
}
|
|
178
|
-
function loadModelStatement(modelClassName) {
|
|
182
|
+
function loadModelStatement(modelClassName, owningModelProperty) {
|
|
179
183
|
return ` private async ${(0, dream_1.camelize)(modelClassName)}() {
|
|
180
|
-
// return await this.
|
|
184
|
+
// return await this.${owningModelProperty}.associationQuery('${(0, pluralize_esm_1.default)((0, dream_1.camelize)(modelClassName))}').findOrFail(
|
|
181
185
|
// this.castParam('id', 'string')
|
|
182
186
|
// )
|
|
183
187
|
}`;
|
|
@@ -7,16 +7,26 @@ exports.default = generateResourceControllerSpecContent;
|
|
|
7
7
|
const dream_1 = require("@rvoh/dream");
|
|
8
8
|
const relativePsychicPath_js_1 = __importDefault(require("../../helpers/path/relativePsychicPath.js"));
|
|
9
9
|
const updirsFromPath_js_1 = __importDefault(require("../../helpers/path/updirsFromPath.js"));
|
|
10
|
-
function generateResourceControllerSpecContent({ fullyQualifiedControllerName, route, fullyQualifiedModelName, columnsWithTypes, }) {
|
|
10
|
+
function generateResourceControllerSpecContent({ fullyQualifiedControllerName, route, fullyQualifiedModelName, columnsWithTypes, owningModel, }) {
|
|
11
11
|
fullyQualifiedModelName = (0, dream_1.standardizeFullyQualifiedModelName)(fullyQualifiedModelName);
|
|
12
12
|
const modelClassName = (0, dream_1.globalClassNameFromFullyQualifiedModelName)(fullyQualifiedModelName);
|
|
13
13
|
const modelVariableName = (0, dream_1.camelize)(modelClassName);
|
|
14
|
+
// Always use User for authentication
|
|
15
|
+
const owningModelClassName = 'User';
|
|
16
|
+
const userVariableName = 'user';
|
|
17
|
+
// Determine attached model settings if provided
|
|
18
|
+
const attachedModelClassName = owningModel ? (0, dream_1.globalClassNameFromFullyQualifiedModelName)(owningModel) : null;
|
|
19
|
+
const attachedModelVariableName = attachedModelClassName ? (0, dream_1.camelize)(attachedModelClassName) : null;
|
|
14
20
|
const importStatements = [
|
|
15
21
|
importStatementForModel(fullyQualifiedControllerName, fullyQualifiedModelName),
|
|
16
22
|
importStatementForModel(fullyQualifiedControllerName, 'User'),
|
|
17
23
|
importStatementForModelFactory(fullyQualifiedControllerName, fullyQualifiedModelName),
|
|
18
24
|
importStatementForModelFactory(fullyQualifiedControllerName, 'User'),
|
|
19
25
|
];
|
|
26
|
+
// Add attached model imports if specified
|
|
27
|
+
if (owningModel) {
|
|
28
|
+
importStatements.push(importStatementForModel(fullyQualifiedControllerName, owningModel), importStatementForModelFactory(fullyQualifiedControllerName, owningModel));
|
|
29
|
+
}
|
|
20
30
|
const specUnitUpdirs = (0, updirsFromPath_js_1.default)(fullyQualifiedControllerName);
|
|
21
31
|
const originalStringKeyValues = [];
|
|
22
32
|
const updatedStringKeyValues = [];
|
|
@@ -46,23 +56,23 @@ import { specRequest as request } from '@rvoh/psychic-spec-helpers'${(0, dream_1
|
|
|
46
56
|
import addEndUserAuthHeader from '${specUnitUpdirs}helpers/authentication.js'
|
|
47
57
|
|
|
48
58
|
describe('${fullyQualifiedControllerName}', () => {
|
|
49
|
-
let
|
|
59
|
+
let ${userVariableName}: ${owningModelClassName}${attachedModelVariableName ? `\n let ${attachedModelVariableName}: ${attachedModelClassName}` : ''}
|
|
50
60
|
|
|
51
61
|
beforeEach(async () => {
|
|
52
62
|
await request.init(PsychicServer)
|
|
53
|
-
|
|
63
|
+
${userVariableName} = await createUser()${attachedModelVariableName ? `\n ${attachedModelVariableName} = await create${attachedModelClassName}({ ${userVariableName} })` : ''}
|
|
54
64
|
})
|
|
55
65
|
|
|
56
66
|
describe('GET index', () => {
|
|
57
67
|
const subject = async (expectedStatus: number = 200) => {
|
|
58
68
|
return request.get('/${route}', expectedStatus, {
|
|
59
|
-
headers: await addEndUserAuthHeader(request,
|
|
69
|
+
headers: await addEndUserAuthHeader(request, ${userVariableName}, {}),
|
|
60
70
|
})
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
it('returns the index of ${fullyQualifiedModelName}s', async () => {
|
|
64
74
|
const ${modelVariableName} = await create${modelClassName}({
|
|
65
|
-
|
|
75
|
+
${attachedModelVariableName || userVariableName}${originalStringKeyValues.length ? ',\n ' + originalStringKeyValues.join('\n ') : ''}
|
|
66
76
|
})
|
|
67
77
|
const results = (await subject()).body
|
|
68
78
|
|
|
@@ -73,7 +83,7 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
73
83
|
])
|
|
74
84
|
})
|
|
75
85
|
|
|
76
|
-
context('${modelClassName}s created by another
|
|
86
|
+
context('${modelClassName}s created by another ${owningModelClassName}', () => {
|
|
77
87
|
it('are omitted', async () => {
|
|
78
88
|
await create${modelClassName}()
|
|
79
89
|
const results = (await subject()).body
|
|
@@ -86,13 +96,13 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
86
96
|
describe('GET show', () => {
|
|
87
97
|
const subject = async (${modelVariableName}: ${modelClassName}, expectedStatus: number = 200) => {
|
|
88
98
|
return request.get(\`/${route}/\${${modelVariableName}.id}\`, expectedStatus, {
|
|
89
|
-
headers: await addEndUserAuthHeader(request,
|
|
99
|
+
headers: await addEndUserAuthHeader(request, ${userVariableName}, {}),
|
|
90
100
|
})
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
it('returns the specified ${fullyQualifiedModelName}', async () => {
|
|
94
104
|
const ${modelVariableName} = await create${modelClassName}({
|
|
95
|
-
|
|
105
|
+
${attachedModelVariableName || userVariableName}${originalStringKeyValues.length ? ',\n ' + originalStringKeyValues.join('\n ') : ''}
|
|
96
106
|
})
|
|
97
107
|
const results = (await subject(${modelVariableName})).body
|
|
98
108
|
|
|
@@ -103,10 +113,10 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
103
113
|
)
|
|
104
114
|
})
|
|
105
115
|
|
|
106
|
-
context('${fullyQualifiedModelName} created by another
|
|
116
|
+
context('${fullyQualifiedModelName} created by another ${owningModelClassName}', () => {
|
|
107
117
|
it('is not found', async () => {
|
|
108
|
-
const
|
|
109
|
-
await subject(
|
|
118
|
+
const other${owningModelClassName}${modelClassName} = await create${modelClassName}()
|
|
119
|
+
await subject(other${owningModelClassName}${modelClassName}, 404)
|
|
110
120
|
})
|
|
111
121
|
})
|
|
112
122
|
})
|
|
@@ -115,15 +125,15 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
115
125
|
const subject = async (data: UpdateableProperties<${modelClassName}>, expectedStatus: number = 201) => {
|
|
116
126
|
return request.post('/${route}', expectedStatus, {
|
|
117
127
|
data,
|
|
118
|
-
headers: await addEndUserAuthHeader(request,
|
|
128
|
+
headers: await addEndUserAuthHeader(request, ${userVariableName}, {}),
|
|
119
129
|
})
|
|
120
130
|
}
|
|
121
131
|
|
|
122
|
-
it('creates a ${fullyQualifiedModelName} for this
|
|
132
|
+
it('creates a ${fullyQualifiedModelName} for this ${owningModelClassName}', async () => {
|
|
123
133
|
const results = (await subject({
|
|
124
134
|
${originalStringKeyValues.length ? originalStringKeyValues.join('\n ') : ''}
|
|
125
135
|
})).body
|
|
126
|
-
const ${modelVariableName} = await ${modelClassName}.findOrFailBy({
|
|
136
|
+
const ${modelVariableName} = await ${modelClassName}.findOrFailBy({ ${userVariableName}Id: ${userVariableName}.id })
|
|
127
137
|
|
|
128
138
|
expect(results).toEqual(
|
|
129
139
|
expect.objectContaining({
|
|
@@ -137,13 +147,13 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
137
147
|
const subject = async (${modelVariableName}: ${modelClassName}, data: UpdateableProperties<${modelClassName}>, expectedStatus: number = 204) => {
|
|
138
148
|
return request.patch(\`/${route}/\${${modelVariableName}.id}\`, expectedStatus, {
|
|
139
149
|
data,
|
|
140
|
-
headers: await addEndUserAuthHeader(request,
|
|
150
|
+
headers: await addEndUserAuthHeader(request, ${userVariableName}, {}),
|
|
141
151
|
})
|
|
142
152
|
}
|
|
143
153
|
|
|
144
154
|
it('updates the ${fullyQualifiedModelName}', async () => {
|
|
145
155
|
const ${modelVariableName} = await create${modelClassName}({
|
|
146
|
-
|
|
156
|
+
${attachedModelVariableName || userVariableName}${originalStringKeyValues.length ? ',\n ' + originalStringKeyValues.join('\n ') : ''}
|
|
147
157
|
})
|
|
148
158
|
await subject(${modelVariableName}, {
|
|
149
159
|
${updatedStringKeyValues.length ? updatedStringKeyValues.join('\n ') : ''}
|
|
@@ -153,11 +163,9 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
153
163
|
${updatedStringAttributeChecks.join('\n ')}
|
|
154
164
|
})
|
|
155
165
|
|
|
156
|
-
context('a ${fullyQualifiedModelName} created by another
|
|
166
|
+
context('a ${fullyQualifiedModelName} created by another ${owningModelClassName}', () => {
|
|
157
167
|
it('is not updated', async () => {
|
|
158
|
-
const ${modelVariableName} = await create${modelClassName}(
|
|
159
|
-
${originalStringKeyValues.length ? originalStringKeyValues.join('\n ') : ''}
|
|
160
|
-
})
|
|
168
|
+
const ${modelVariableName} = await create${modelClassName}()
|
|
161
169
|
await subject(${modelVariableName}, {
|
|
162
170
|
${updatedStringKeyValues.length ? updatedStringKeyValues.join('\n ') : ''}
|
|
163
171
|
}, 404)
|
|
@@ -171,18 +179,18 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
171
179
|
describe('DELETE destroy', () => {
|
|
172
180
|
const subject = async (${modelVariableName}: ${modelClassName}, expectedStatus: number = 204) => {
|
|
173
181
|
return request.delete(\`/${route}/\${${modelVariableName}.id}\`, expectedStatus, {
|
|
174
|
-
headers: await addEndUserAuthHeader(request,
|
|
182
|
+
headers: await addEndUserAuthHeader(request, ${userVariableName}, {}),
|
|
175
183
|
})
|
|
176
184
|
}
|
|
177
185
|
|
|
178
186
|
it('deletes the ${fullyQualifiedModelName}', async () => {
|
|
179
|
-
const ${modelVariableName} = await create${modelClassName}({
|
|
187
|
+
const ${modelVariableName} = await create${modelClassName}({ ${attachedModelVariableName || userVariableName} })
|
|
180
188
|
await subject(${modelVariableName})
|
|
181
189
|
|
|
182
190
|
expect(await ${modelClassName}.find(${modelVariableName}.id)).toBeNull()
|
|
183
191
|
})
|
|
184
192
|
|
|
185
|
-
context('a ${fullyQualifiedModelName} created by another
|
|
193
|
+
context('a ${fullyQualifiedModelName} created by another ${owningModelClassName}', () => {
|
|
186
194
|
it('is not deleted', async () => {
|
|
187
195
|
const ${modelVariableName} = await create${modelClassName}()
|
|
188
196
|
await subject(${modelVariableName}, 404)
|
|
@@ -21,6 +21,7 @@ async function generateResource({ route, fullyQualifiedModelName, options, colum
|
|
|
21
21
|
actions: ['create', 'index', 'show', 'update', 'destroy'],
|
|
22
22
|
columnsWithTypes,
|
|
23
23
|
resourceSpecs: true,
|
|
24
|
+
owningModel: options.owningModel,
|
|
24
25
|
});
|
|
25
26
|
await (0, addResourceToRoutes_js_1.default)(route);
|
|
26
27
|
}
|
|
@@ -16,7 +16,8 @@ export default class PsychicCLI {
|
|
|
16
16
|
.command('generate:resource')
|
|
17
17
|
.alias('g:resource')
|
|
18
18
|
.description('create a Dream model, migration, controller, serializer, and spec placeholders')
|
|
19
|
-
.option('--sti-base-serializer', '
|
|
19
|
+
.option('--sti-base-serializer', 'omits the serializer from the dream model, but does create the serializer so it can be extended by STI children')
|
|
20
|
+
.option('--owning-model <modelName>', 'The model class of the object that `associationQuery`/`createAssociation` will be performed on in the created controller and spec (e.g., "Host", "Guest") (simply to save time making changes to the generated code). Defaults to User')
|
|
20
21
|
.argument('<path>', 'URL path from root domain')
|
|
21
22
|
.argument('<modelName>', 'the name of the model to create, e.g. Post or Settings/CommunicationPreferences')
|
|
22
23
|
.argument('[columnsWithTypes...]', 'properties of the model property1:text/string/enum/etc. property2:text/string/enum/etc. ... propertyN:text/string/enum/etc.')
|
|
@@ -8,7 +8,7 @@ import psychicPath from '../helpers/path/psychicPath.js';
|
|
|
8
8
|
import generateControllerContent from './helpers/generateControllerContent.js';
|
|
9
9
|
import generateControllerSpecContent from './helpers/generateControllerSpecContent.js';
|
|
10
10
|
import generateResourceControllerSpecContent from './helpers/generateResourceControllerSpecContent.js';
|
|
11
|
-
export default async function generateController({ fullyQualifiedControllerName, fullyQualifiedModelName, actions, columnsWithTypes = [], resourceSpecs = false, }) {
|
|
11
|
+
export default async function generateController({ fullyQualifiedControllerName, fullyQualifiedModelName, actions, columnsWithTypes = [], resourceSpecs = false, owningModel, }) {
|
|
12
12
|
fullyQualifiedModelName = fullyQualifiedModelName
|
|
13
13
|
? standardizeFullyQualifiedModelName(fullyQualifiedModelName)
|
|
14
14
|
: fullyQualifiedModelName;
|
|
@@ -60,6 +60,7 @@ export default async function generateController({ fullyQualifiedControllerName,
|
|
|
60
60
|
fullyQualifiedControllerName,
|
|
61
61
|
fullyQualifiedModelName,
|
|
62
62
|
actions,
|
|
63
|
+
owningModel,
|
|
63
64
|
}));
|
|
64
65
|
}
|
|
65
66
|
catch (error) {
|
|
@@ -77,6 +78,7 @@ export default async function generateController({ fullyQualifiedControllerName,
|
|
|
77
78
|
fullyQualifiedModelName,
|
|
78
79
|
columnsWithTypes,
|
|
79
80
|
resourceSpecs,
|
|
81
|
+
owningModel,
|
|
80
82
|
});
|
|
81
83
|
}
|
|
82
84
|
function baseAncestorNameAndImport(controllerNameParts, isAdmin, { forBaseController }) {
|
|
@@ -88,7 +90,7 @@ function baseAncestorNameAndImport(controllerNameParts, isAdmin, { forBaseContro
|
|
|
88
90
|
: [`AuthedController`, `import AuthedController from '${dotFiles}/AuthedController.js'`]
|
|
89
91
|
: [maybeAncestorNameForBase, `import ${maybeAncestorNameForBase} from '${dotFiles}/BaseController.js'`];
|
|
90
92
|
}
|
|
91
|
-
async function generateControllerSpec({ fullyQualifiedControllerName, route, fullyQualifiedModelName, columnsWithTypes, resourceSpecs, }) {
|
|
93
|
+
async function generateControllerSpec({ fullyQualifiedControllerName, route, fullyQualifiedModelName, columnsWithTypes, resourceSpecs, owningModel, }) {
|
|
92
94
|
const { relFilePath, absDirPath, absFilePath } = psychicFileAndDirPaths(psychicPath('controllerSpecs'), fullyQualifiedControllerName + `.spec.ts`);
|
|
93
95
|
try {
|
|
94
96
|
if (!EnvInternal.isTest)
|
|
@@ -100,6 +102,7 @@ async function generateControllerSpec({ fullyQualifiedControllerName, route, ful
|
|
|
100
102
|
route,
|
|
101
103
|
fullyQualifiedModelName,
|
|
102
104
|
columnsWithTypes,
|
|
105
|
+
owningModel,
|
|
103
106
|
})
|
|
104
107
|
: generateControllerSpecContent(fullyQualifiedControllerName));
|
|
105
108
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { camelize, globalClassNameFromFullyQualifiedModelName, hyphenize, standardizeFullyQualifiedModelName, } from '@rvoh/dream';
|
|
2
2
|
import pluralize from 'pluralize-esm';
|
|
3
3
|
import relativePsychicPath from '../../helpers/path/relativePsychicPath.js';
|
|
4
|
-
export default function generateControllerContent({ ancestorName, ancestorImportStatement, fullyQualifiedControllerName, fullyQualifiedModelName, actions = [], omitOpenApi = false, }) {
|
|
4
|
+
export default function generateControllerContent({ ancestorName, ancestorImportStatement, fullyQualifiedControllerName, fullyQualifiedModelName, actions = [], omitOpenApi = false, owningModel, }) {
|
|
5
5
|
fullyQualifiedControllerName = standardizeFullyQualifiedModelName(fullyQualifiedControllerName);
|
|
6
6
|
const additionalImports = [];
|
|
7
7
|
const controllerClassName = globalClassNameFromFullyQualifiedModelName(fullyQualifiedControllerName);
|
|
8
|
+
// Determine user model variables
|
|
9
|
+
const actualOwningModel = owningModel || 'User';
|
|
10
|
+
const owningModelClassName = globalClassNameFromFullyQualifiedModelName(actualOwningModel);
|
|
11
|
+
const owningModelProperty = `current${owningModelClassName}`;
|
|
8
12
|
let modelClassName;
|
|
9
13
|
let modelAttributeName;
|
|
10
14
|
let pluralizedModelAttributeName;
|
|
@@ -26,7 +30,7 @@ export default function generateControllerContent({ ancestorName, ancestorImport
|
|
|
26
30
|
description: 'Create ${aOrAnDreamModelName(modelClassName)}',
|
|
27
31
|
})
|
|
28
32
|
public async create() {
|
|
29
|
-
// const ${modelAttributeName} = await this.
|
|
33
|
+
// const ${modelAttributeName} = await this.${owningModelProperty}.createAssociation('${pluralizedModelAttributeName}', this.paramsFor(${modelClassName}))
|
|
30
34
|
// this.created(${modelAttributeName})
|
|
31
35
|
}`;
|
|
32
36
|
else
|
|
@@ -49,7 +53,7 @@ export default function generateControllerContent({ ancestorName, ancestorImport
|
|
|
49
53
|
serializerKey: 'summary',
|
|
50
54
|
})
|
|
51
55
|
public async index() {
|
|
52
|
-
// const ${pluralizedModelAttributeName} = await this.
|
|
56
|
+
// const ${pluralizedModelAttributeName} = await this.${owningModelProperty}.associationQuery('${pluralizedModelAttributeName}').all()
|
|
53
57
|
// this.ok(${pluralizedModelAttributeName})
|
|
54
58
|
}`;
|
|
55
59
|
else
|
|
@@ -157,21 +161,21 @@ export default function generateControllerContent({ ancestorName, ancestorImport
|
|
|
157
161
|
${omitOpenApi ? '' : openApiImport + '\n'}${ancestorImportStatement}${additionalImports.length ? '\n' + additionalImports.join('\n') : ''}${omitOpenApi ? '' : '\n\n' + openApiTags}
|
|
158
162
|
|
|
159
163
|
export default class ${controllerClassName} extends ${ancestorName} {
|
|
160
|
-
${methodDefs.join('\n\n')}${modelClassName ? privateMethods(modelClassName, actions) : ''}
|
|
164
|
+
${methodDefs.join('\n\n')}${modelClassName ? privateMethods(modelClassName, actions, owningModelProperty) : ''}
|
|
161
165
|
}
|
|
162
166
|
`;
|
|
163
167
|
}
|
|
164
|
-
function privateMethods(modelClassName, methods) {
|
|
168
|
+
function privateMethods(modelClassName, methods, owningModelProperty) {
|
|
165
169
|
const privateMethods = [];
|
|
166
170
|
if (methods.find(methodName => ['show', 'update', 'destroy'].includes(methodName)))
|
|
167
|
-
privateMethods.push(loadModelStatement(modelClassName));
|
|
171
|
+
privateMethods.push(loadModelStatement(modelClassName, owningModelProperty));
|
|
168
172
|
if (!privateMethods.length)
|
|
169
173
|
return '';
|
|
170
174
|
return `\n\n${privateMethods.join('\n\n')}`;
|
|
171
175
|
}
|
|
172
|
-
function loadModelStatement(modelClassName) {
|
|
176
|
+
function loadModelStatement(modelClassName, owningModelProperty) {
|
|
173
177
|
return ` private async ${camelize(modelClassName)}() {
|
|
174
|
-
// return await this.
|
|
178
|
+
// return await this.${owningModelProperty}.associationQuery('${pluralize(camelize(modelClassName))}').findOrFail(
|
|
175
179
|
// this.castParam('id', 'string')
|
|
176
180
|
// )
|
|
177
181
|
}`;
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import { camelize, globalClassNameFromFullyQualifiedModelName, standardizeFullyQualifiedModelName, uniq, } from '@rvoh/dream';
|
|
2
2
|
import relativePsychicPath from '../../helpers/path/relativePsychicPath.js';
|
|
3
3
|
import updirsFromPath from '../../helpers/path/updirsFromPath.js';
|
|
4
|
-
export default function generateResourceControllerSpecContent({ fullyQualifiedControllerName, route, fullyQualifiedModelName, columnsWithTypes, }) {
|
|
4
|
+
export default function generateResourceControllerSpecContent({ fullyQualifiedControllerName, route, fullyQualifiedModelName, columnsWithTypes, owningModel, }) {
|
|
5
5
|
fullyQualifiedModelName = standardizeFullyQualifiedModelName(fullyQualifiedModelName);
|
|
6
6
|
const modelClassName = globalClassNameFromFullyQualifiedModelName(fullyQualifiedModelName);
|
|
7
7
|
const modelVariableName = camelize(modelClassName);
|
|
8
|
+
// Always use User for authentication
|
|
9
|
+
const owningModelClassName = 'User';
|
|
10
|
+
const userVariableName = 'user';
|
|
11
|
+
// Determine attached model settings if provided
|
|
12
|
+
const attachedModelClassName = owningModel ? globalClassNameFromFullyQualifiedModelName(owningModel) : null;
|
|
13
|
+
const attachedModelVariableName = attachedModelClassName ? camelize(attachedModelClassName) : null;
|
|
8
14
|
const importStatements = [
|
|
9
15
|
importStatementForModel(fullyQualifiedControllerName, fullyQualifiedModelName),
|
|
10
16
|
importStatementForModel(fullyQualifiedControllerName, 'User'),
|
|
11
17
|
importStatementForModelFactory(fullyQualifiedControllerName, fullyQualifiedModelName),
|
|
12
18
|
importStatementForModelFactory(fullyQualifiedControllerName, 'User'),
|
|
13
19
|
];
|
|
20
|
+
// Add attached model imports if specified
|
|
21
|
+
if (owningModel) {
|
|
22
|
+
importStatements.push(importStatementForModel(fullyQualifiedControllerName, owningModel), importStatementForModelFactory(fullyQualifiedControllerName, owningModel));
|
|
23
|
+
}
|
|
14
24
|
const specUnitUpdirs = updirsFromPath(fullyQualifiedControllerName);
|
|
15
25
|
const originalStringKeyValues = [];
|
|
16
26
|
const updatedStringKeyValues = [];
|
|
@@ -40,23 +50,23 @@ import { specRequest as request } from '@rvoh/psychic-spec-helpers'${uniq(import
|
|
|
40
50
|
import addEndUserAuthHeader from '${specUnitUpdirs}helpers/authentication.js'
|
|
41
51
|
|
|
42
52
|
describe('${fullyQualifiedControllerName}', () => {
|
|
43
|
-
let
|
|
53
|
+
let ${userVariableName}: ${owningModelClassName}${attachedModelVariableName ? `\n let ${attachedModelVariableName}: ${attachedModelClassName}` : ''}
|
|
44
54
|
|
|
45
55
|
beforeEach(async () => {
|
|
46
56
|
await request.init(PsychicServer)
|
|
47
|
-
|
|
57
|
+
${userVariableName} = await createUser()${attachedModelVariableName ? `\n ${attachedModelVariableName} = await create${attachedModelClassName}({ ${userVariableName} })` : ''}
|
|
48
58
|
})
|
|
49
59
|
|
|
50
60
|
describe('GET index', () => {
|
|
51
61
|
const subject = async (expectedStatus: number = 200) => {
|
|
52
62
|
return request.get('/${route}', expectedStatus, {
|
|
53
|
-
headers: await addEndUserAuthHeader(request,
|
|
63
|
+
headers: await addEndUserAuthHeader(request, ${userVariableName}, {}),
|
|
54
64
|
})
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
it('returns the index of ${fullyQualifiedModelName}s', async () => {
|
|
58
68
|
const ${modelVariableName} = await create${modelClassName}({
|
|
59
|
-
|
|
69
|
+
${attachedModelVariableName || userVariableName}${originalStringKeyValues.length ? ',\n ' + originalStringKeyValues.join('\n ') : ''}
|
|
60
70
|
})
|
|
61
71
|
const results = (await subject()).body
|
|
62
72
|
|
|
@@ -67,7 +77,7 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
67
77
|
])
|
|
68
78
|
})
|
|
69
79
|
|
|
70
|
-
context('${modelClassName}s created by another
|
|
80
|
+
context('${modelClassName}s created by another ${owningModelClassName}', () => {
|
|
71
81
|
it('are omitted', async () => {
|
|
72
82
|
await create${modelClassName}()
|
|
73
83
|
const results = (await subject()).body
|
|
@@ -80,13 +90,13 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
80
90
|
describe('GET show', () => {
|
|
81
91
|
const subject = async (${modelVariableName}: ${modelClassName}, expectedStatus: number = 200) => {
|
|
82
92
|
return request.get(\`/${route}/\${${modelVariableName}.id}\`, expectedStatus, {
|
|
83
|
-
headers: await addEndUserAuthHeader(request,
|
|
93
|
+
headers: await addEndUserAuthHeader(request, ${userVariableName}, {}),
|
|
84
94
|
})
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
it('returns the specified ${fullyQualifiedModelName}', async () => {
|
|
88
98
|
const ${modelVariableName} = await create${modelClassName}({
|
|
89
|
-
|
|
99
|
+
${attachedModelVariableName || userVariableName}${originalStringKeyValues.length ? ',\n ' + originalStringKeyValues.join('\n ') : ''}
|
|
90
100
|
})
|
|
91
101
|
const results = (await subject(${modelVariableName})).body
|
|
92
102
|
|
|
@@ -97,10 +107,10 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
97
107
|
)
|
|
98
108
|
})
|
|
99
109
|
|
|
100
|
-
context('${fullyQualifiedModelName} created by another
|
|
110
|
+
context('${fullyQualifiedModelName} created by another ${owningModelClassName}', () => {
|
|
101
111
|
it('is not found', async () => {
|
|
102
|
-
const
|
|
103
|
-
await subject(
|
|
112
|
+
const other${owningModelClassName}${modelClassName} = await create${modelClassName}()
|
|
113
|
+
await subject(other${owningModelClassName}${modelClassName}, 404)
|
|
104
114
|
})
|
|
105
115
|
})
|
|
106
116
|
})
|
|
@@ -109,15 +119,15 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
109
119
|
const subject = async (data: UpdateableProperties<${modelClassName}>, expectedStatus: number = 201) => {
|
|
110
120
|
return request.post('/${route}', expectedStatus, {
|
|
111
121
|
data,
|
|
112
|
-
headers: await addEndUserAuthHeader(request,
|
|
122
|
+
headers: await addEndUserAuthHeader(request, ${userVariableName}, {}),
|
|
113
123
|
})
|
|
114
124
|
}
|
|
115
125
|
|
|
116
|
-
it('creates a ${fullyQualifiedModelName} for this
|
|
126
|
+
it('creates a ${fullyQualifiedModelName} for this ${owningModelClassName}', async () => {
|
|
117
127
|
const results = (await subject({
|
|
118
128
|
${originalStringKeyValues.length ? originalStringKeyValues.join('\n ') : ''}
|
|
119
129
|
})).body
|
|
120
|
-
const ${modelVariableName} = await ${modelClassName}.findOrFailBy({
|
|
130
|
+
const ${modelVariableName} = await ${modelClassName}.findOrFailBy({ ${userVariableName}Id: ${userVariableName}.id })
|
|
121
131
|
|
|
122
132
|
expect(results).toEqual(
|
|
123
133
|
expect.objectContaining({
|
|
@@ -131,13 +141,13 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
131
141
|
const subject = async (${modelVariableName}: ${modelClassName}, data: UpdateableProperties<${modelClassName}>, expectedStatus: number = 204) => {
|
|
132
142
|
return request.patch(\`/${route}/\${${modelVariableName}.id}\`, expectedStatus, {
|
|
133
143
|
data,
|
|
134
|
-
headers: await addEndUserAuthHeader(request,
|
|
144
|
+
headers: await addEndUserAuthHeader(request, ${userVariableName}, {}),
|
|
135
145
|
})
|
|
136
146
|
}
|
|
137
147
|
|
|
138
148
|
it('updates the ${fullyQualifiedModelName}', async () => {
|
|
139
149
|
const ${modelVariableName} = await create${modelClassName}({
|
|
140
|
-
|
|
150
|
+
${attachedModelVariableName || userVariableName}${originalStringKeyValues.length ? ',\n ' + originalStringKeyValues.join('\n ') : ''}
|
|
141
151
|
})
|
|
142
152
|
await subject(${modelVariableName}, {
|
|
143
153
|
${updatedStringKeyValues.length ? updatedStringKeyValues.join('\n ') : ''}
|
|
@@ -147,11 +157,9 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
147
157
|
${updatedStringAttributeChecks.join('\n ')}
|
|
148
158
|
})
|
|
149
159
|
|
|
150
|
-
context('a ${fullyQualifiedModelName} created by another
|
|
160
|
+
context('a ${fullyQualifiedModelName} created by another ${owningModelClassName}', () => {
|
|
151
161
|
it('is not updated', async () => {
|
|
152
|
-
const ${modelVariableName} = await create${modelClassName}(
|
|
153
|
-
${originalStringKeyValues.length ? originalStringKeyValues.join('\n ') : ''}
|
|
154
|
-
})
|
|
162
|
+
const ${modelVariableName} = await create${modelClassName}()
|
|
155
163
|
await subject(${modelVariableName}, {
|
|
156
164
|
${updatedStringKeyValues.length ? updatedStringKeyValues.join('\n ') : ''}
|
|
157
165
|
}, 404)
|
|
@@ -165,18 +173,18 @@ describe('${fullyQualifiedControllerName}', () => {
|
|
|
165
173
|
describe('DELETE destroy', () => {
|
|
166
174
|
const subject = async (${modelVariableName}: ${modelClassName}, expectedStatus: number = 204) => {
|
|
167
175
|
return request.delete(\`/${route}/\${${modelVariableName}.id}\`, expectedStatus, {
|
|
168
|
-
headers: await addEndUserAuthHeader(request,
|
|
176
|
+
headers: await addEndUserAuthHeader(request, ${userVariableName}, {}),
|
|
169
177
|
})
|
|
170
178
|
}
|
|
171
179
|
|
|
172
180
|
it('deletes the ${fullyQualifiedModelName}', async () => {
|
|
173
|
-
const ${modelVariableName} = await create${modelClassName}({
|
|
181
|
+
const ${modelVariableName} = await create${modelClassName}({ ${attachedModelVariableName || userVariableName} })
|
|
174
182
|
await subject(${modelVariableName})
|
|
175
183
|
|
|
176
184
|
expect(await ${modelClassName}.find(${modelVariableName}.id)).toBeNull()
|
|
177
185
|
})
|
|
178
186
|
|
|
179
|
-
context('a ${fullyQualifiedModelName} created by another
|
|
187
|
+
context('a ${fullyQualifiedModelName} created by another ${owningModelClassName}', () => {
|
|
180
188
|
it('is not deleted', async () => {
|
|
181
189
|
const ${modelVariableName} = await create${modelClassName}()
|
|
182
190
|
await subject(${modelVariableName}, 404)
|
|
@@ -15,6 +15,7 @@ export default async function generateResource({ route, fullyQualifiedModelName,
|
|
|
15
15
|
actions: ['create', 'index', 'show', 'update', 'destroy'],
|
|
16
16
|
columnsWithTypes,
|
|
17
17
|
resourceSpecs: true,
|
|
18
|
+
owningModel: options.owningModel,
|
|
18
19
|
});
|
|
19
20
|
await addResourceToRoutes(route);
|
|
20
21
|
}
|
|
@@ -2,6 +2,7 @@ export default class PsychicBin {
|
|
|
2
2
|
static generateController(controllerName: string, actions: string[]): Promise<void>;
|
|
3
3
|
static generateResource(route: string, fullyQualifiedModelName: string, columnsWithTypes: string[], options: {
|
|
4
4
|
stiBaseSerializer: boolean;
|
|
5
|
+
owningModel?: string;
|
|
5
6
|
}): Promise<void>;
|
|
6
7
|
static routes(): Promise<void>;
|
|
7
8
|
static sync({ bypassDreamSync }?: {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export default function generateController({ fullyQualifiedControllerName, fullyQualifiedModelName, actions, columnsWithTypes, resourceSpecs, }: {
|
|
1
|
+
export default function generateController({ fullyQualifiedControllerName, fullyQualifiedModelName, actions, columnsWithTypes, resourceSpecs, owningModel, }: {
|
|
2
2
|
fullyQualifiedControllerName: string;
|
|
3
3
|
fullyQualifiedModelName?: string;
|
|
4
4
|
actions: string[];
|
|
5
5
|
columnsWithTypes?: string[];
|
|
6
6
|
resourceSpecs?: boolean;
|
|
7
|
+
owningModel?: string | undefined;
|
|
7
8
|
}): Promise<void>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export default function generateControllerContent({ ancestorName, ancestorImportStatement, fullyQualifiedControllerName, fullyQualifiedModelName, actions, omitOpenApi, }: {
|
|
1
|
+
export default function generateControllerContent({ ancestorName, ancestorImportStatement, fullyQualifiedControllerName, fullyQualifiedModelName, actions, omitOpenApi, owningModel, }: {
|
|
2
2
|
ancestorName: string;
|
|
3
3
|
ancestorImportStatement: string;
|
|
4
4
|
fullyQualifiedControllerName: string;
|
|
5
5
|
fullyQualifiedModelName?: string | undefined;
|
|
6
6
|
actions?: string[] | undefined;
|
|
7
7
|
omitOpenApi?: boolean | undefined;
|
|
8
|
+
owningModel?: string | undefined;
|
|
8
9
|
}): string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export default function generateResourceControllerSpecContent({ fullyQualifiedControllerName, route, fullyQualifiedModelName, columnsWithTypes, }: {
|
|
1
|
+
export default function generateResourceControllerSpecContent({ fullyQualifiedControllerName, route, fullyQualifiedModelName, columnsWithTypes, owningModel, }: {
|
|
2
2
|
fullyQualifiedControllerName: string;
|
|
3
3
|
route: string;
|
|
4
4
|
fullyQualifiedModelName: string;
|
|
5
5
|
columnsWithTypes: string[];
|
|
6
|
+
owningModel?: string | undefined;
|
|
6
7
|
}): string;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@rvoh/psychic",
|
|
4
4
|
"description": "Typescript web framework",
|
|
5
|
-
"version": "0.37.
|
|
5
|
+
"version": "0.37.2",
|
|
6
6
|
"author": "RVOHealth",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -85,4 +85,4 @@
|
|
|
85
85
|
"winston": "^3.14.2"
|
|
86
86
|
},
|
|
87
87
|
"packageManager": "yarn@4.7.0"
|
|
88
|
-
}
|
|
88
|
+
}
|