@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.
@@ -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', 'Omits the serializer from the dream model, but does create the serializer so it can be extended by STI children')
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.currentUser.createAssociation('${pluralizedModelAttributeName}', this.paramsFor(${modelClassName}))
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.currentUser.associationQuery('${pluralizedModelAttributeName}').all()
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.currentUser.associationQuery('${(0, pluralize_esm_1.default)((0, dream_1.camelize)(modelClassName))}').findOrFail(
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 user: User
59
+ let ${userVariableName}: ${owningModelClassName}${attachedModelVariableName ? `\n let ${attachedModelVariableName}: ${attachedModelClassName}` : ''}
50
60
 
51
61
  beforeEach(async () => {
52
62
  await request.init(PsychicServer)
53
- user = await createUser()
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, user, {}),
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
- user${originalStringKeyValues.length ? ',\n ' + originalStringKeyValues.join('\n ') : ''}
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 User', () => {
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, user, {}),
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
- user${originalStringKeyValues.length ? ',\n ' + originalStringKeyValues.join('\n ') : ''}
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 User', () => {
116
+ context('${fullyQualifiedModelName} created by another ${owningModelClassName}', () => {
107
117
  it('is not found', async () => {
108
- const otherUser${modelClassName} = await create${modelClassName}()
109
- await subject(otherUser${modelClassName}, 404)
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, user, {}),
128
+ headers: await addEndUserAuthHeader(request, ${userVariableName}, {}),
119
129
  })
120
130
  }
121
131
 
122
- it('creates a ${fullyQualifiedModelName} for this User', async () => {
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({ userId: user.id })
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, user, {}),
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
- user${originalStringKeyValues.length ? ',\n ' + originalStringKeyValues.join('\n ') : ''}
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 User', () => {
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, user, {}),
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}({ user })
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 User', () => {
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', 'Omits the serializer from the dream model, but does create the serializer so it can be extended by STI children')
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.currentUser.createAssociation('${pluralizedModelAttributeName}', this.paramsFor(${modelClassName}))
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.currentUser.associationQuery('${pluralizedModelAttributeName}').all()
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.currentUser.associationQuery('${pluralize(camelize(modelClassName))}').findOrFail(
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 user: User
53
+ let ${userVariableName}: ${owningModelClassName}${attachedModelVariableName ? `\n let ${attachedModelVariableName}: ${attachedModelClassName}` : ''}
44
54
 
45
55
  beforeEach(async () => {
46
56
  await request.init(PsychicServer)
47
- user = await createUser()
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, user, {}),
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
- user${originalStringKeyValues.length ? ',\n ' + originalStringKeyValues.join('\n ') : ''}
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 User', () => {
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, user, {}),
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
- user${originalStringKeyValues.length ? ',\n ' + originalStringKeyValues.join('\n ') : ''}
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 User', () => {
110
+ context('${fullyQualifiedModelName} created by another ${owningModelClassName}', () => {
101
111
  it('is not found', async () => {
102
- const otherUser${modelClassName} = await create${modelClassName}()
103
- await subject(otherUser${modelClassName}, 404)
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, user, {}),
122
+ headers: await addEndUserAuthHeader(request, ${userVariableName}, {}),
113
123
  })
114
124
  }
115
125
 
116
- it('creates a ${fullyQualifiedModelName} for this User', async () => {
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({ userId: user.id })
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, user, {}),
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
- user${originalStringKeyValues.length ? ',\n ' + originalStringKeyValues.join('\n ') : ''}
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 User', () => {
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, user, {}),
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}({ user })
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 User', () => {
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;
@@ -3,6 +3,7 @@ export default function generateResource({ route, fullyQualifiedModelName, optio
3
3
  fullyQualifiedModelName: string;
4
4
  options: {
5
5
  stiBaseSerializer: boolean;
6
+ owningModel?: string;
6
7
  };
7
8
  columnsWithTypes: string[];
8
9
  }): Promise<void>;
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.0-beta.9",
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
+ }