From 52376decd936f56ac213854dfff7bebe734c6a70 Mon Sep 17 00:00:00 2001 From: Bram Borggreve Date: Mon, 22 Jan 2024 00:28:32 +0000 Subject: [PATCH] feat: add fields to api-crud and web-crud generatorsa --- .../api-crud-generator.spec.ts.snap | 14 ++ .../api-crud/api-crud-generator.spec.ts | 2 +- .../generators/api-crud/api-crud-schema.d.ts | 1 + .../api-feature-generator.spec.ts.snap | 1 + .../api-feature/api-feature-generator.spec.ts | 22 +-- .../api-feature/api-feature-schema.d.ts | 2 +- .../api-feature/api-feature-schema.json | 9 - .../web-crud/web-crud-generator.spec.ts | 2 +- .../generators/web-crud/web-crud-schema.d.ts | 1 + .../web-feature-generator.spec.ts.snap | 9 +- .../web-feature/web-feature-generator.spec.ts | 6 +- .../web-feature/web-feature-schema.d.ts | 1 - ...create-__modelFileName__.input.ts.template | 6 +- ...update-__modelFileName__.input.ts.template | 4 +- .../__modelFileName__.entity.ts.template | 6 +- .../src/lib/api-crud/generate-api-crud.ts | 8 +- .../{api => api-crud}/generate-sdk-file.ts | 8 +- .../api-crud/get-api-crud-substitutions.ts | 1 + .../lib/api-crud/normalize-api-crud-schema.ts | 4 + ...__modelFileName__-feature.spec.ts.template | 171 ------------------ .../tools/src/lib/api-e2e/generate-api-e2e.ts | 10 - libs/tools/src/lib/api/create-mock-api-app.ts | 15 ++ .../tools/src/lib/api/generate-api-feature.ts | 4 +- .../lib/api/generate-api-lib-data-access.ts | 2 +- libs/tools/src/lib/api/generate-api-lib.ts | 4 +- .../lib/api/get-api-feature-module-info.ts | 2 +- .../lib/api/normalize-api-feature-schema.ts | 3 +- .../lib/prisma/create-mock-prisma-schema.ts | 1 - .../tools/src/lib/prisma/get-prisma-models.ts | 35 ++++ .../src/lib/prisma/get-prisma-schema-file.ts | 2 +- .../tools/src/lib/prisma/get-prisma-schema.ts | 1 - ...odelFileName__-ui-create-form.tsx.template | 8 +- ...odelFileName__-ui-update-form.tsx.template | 8 +- .../web-crud/get-web-crud-substitutions.ts | 3 +- .../lib/web-crud/normalize-web-crud-schema.ts | 4 + .../tools/src/lib/web/generate-web-feature.ts | 4 +- libs/tools/src/lib/web/generate-web-lib.ts | 4 +- .../lib/web/normalize-web-feature-schema.ts | 3 +- 38 files changed, 148 insertions(+), 243 deletions(-) rename libs/tools/src/lib/{api => api-crud}/generate-sdk-file.ts (92%) delete mode 100644 libs/tools/src/lib/api-e2e/files/api-__modelFileName__-feature.spec.ts.template delete mode 100644 libs/tools/src/lib/api-e2e/generate-api-e2e.ts diff --git a/libs/tools/src/generators/api-crud/__snapshots__/api-crud-generator.spec.ts.snap b/libs/tools/src/generators/api-crud/__snapshots__/api-crud-generator.spec.ts.snap index 4b53917..96bb679 100644 --- a/libs/tools/src/generators/api-crud/__snapshots__/api-crud-generator.spec.ts.snap +++ b/libs/tools/src/generators/api-crud/__snapshots__/api-crud-generator.spec.ts.snap @@ -155,6 +155,10 @@ exports[`api-crud generator should run successfully 1`] = ` "export class AdminCreateCompanyInput {", "@Field()", "name!: string;", + "@Field()", + "location!: string;", + "@Field({ nullable: true })", + "phone?: string;", "}", ], "isBinary": false, @@ -180,6 +184,10 @@ exports[`api-crud generator should run successfully 1`] = ` "export class AdminUpdateCompanyInput {", "@Field({ nullable: true })", "name?: string;", + "@Field({ nullable: true })", + "location?: string;", + "@Field({ nullable: true })", + "phone?: string;", "}", ], "isBinary": false, @@ -214,6 +222,10 @@ exports[`api-crud generator should run successfully 1`] = ` "updatedAt?: Date;", "@Field()", "name!: string;", + "@Field()", + "location!: string;", + "@Field({ nullable: true })", + "phone?: string;", "}", ], "isBinary": false, @@ -1137,6 +1149,8 @@ exports[`api-crud generator should run successfully 1`] = ` "createdAt", "id", "name", + "location", + "phone", "updatedAt", "}", "query adminFindManyCompany($input: AdminFindManyCompanyInput!) {", diff --git a/libs/tools/src/generators/api-crud/api-crud-generator.spec.ts b/libs/tools/src/generators/api-crud/api-crud-generator.spec.ts index 1f54641..b184ef0 100644 --- a/libs/tools/src/generators/api-crud/api-crud-generator.spec.ts +++ b/libs/tools/src/generators/api-crud/api-crud-generator.spec.ts @@ -13,7 +13,7 @@ describe('api-crud generator', () => { beforeEach(async () => { tree = createTreeWithEmptyWorkspace() await createMockApiApp(tree, options.app) - await apiFeatureGenerator(tree, { app: options.app, model: options.model, name: options.model }) + await apiFeatureGenerator(tree, { app: options.app, model: options.model }) }) it('should run successfully', async () => { diff --git a/libs/tools/src/generators/api-crud/api-crud-schema.d.ts b/libs/tools/src/generators/api-crud/api-crud-schema.d.ts index 8c08a95..39b90bf 100644 --- a/libs/tools/src/generators/api-crud/api-crud-schema.d.ts +++ b/libs/tools/src/generators/api-crud/api-crud-schema.d.ts @@ -8,4 +8,5 @@ export interface ApiCrudGeneratorSchema { export interface NormalizedApiCrudSchema extends ApiCrudGeneratorSchema { label: string npmScope: string + fields?: PrismaModelField[] } diff --git a/libs/tools/src/generators/api-feature/__snapshots__/api-feature-generator.spec.ts.snap b/libs/tools/src/generators/api-feature/__snapshots__/api-feature-generator.spec.ts.snap index bd6b0c7..5213d33 100644 --- a/libs/tools/src/generators/api-feature/__snapshots__/api-feature-generator.spec.ts.snap +++ b/libs/tools/src/generators/api-feature/__snapshots__/api-feature-generator.spec.ts.snap @@ -222,6 +222,7 @@ export class Test { createdAt?: Date; @Field({ nullable: true }) updatedAt?: Date; + @Field() name!: string; } diff --git a/libs/tools/src/generators/api-feature/api-feature-generator.spec.ts b/libs/tools/src/generators/api-feature/api-feature-generator.spec.ts index 6f418ea..15aaaef 100644 --- a/libs/tools/src/generators/api-feature/api-feature-generator.spec.ts +++ b/libs/tools/src/generators/api-feature/api-feature-generator.spec.ts @@ -9,7 +9,7 @@ import { ApiFeatureGeneratorSchema } from './api-feature-schema' describe('api-feature generator', () => { let tree: Tree - const options: ApiFeatureGeneratorSchema = { app: 'api', name: 'test', label: 'name' } + const options: ApiFeatureGeneratorSchema = { app: 'api', label: 'name', model: 'test' } beforeEach(() => { tree = createTreeWithEmptyWorkspace() @@ -23,12 +23,12 @@ describe('api-feature generator', () => { await apiFeatureGenerator(tree, options) libs.forEach((lib) => { - const config = readProjectConfiguration(tree, `${options.app}-${options.name}-${lib}`) + const config = readProjectConfiguration(tree, `${options.app}-${options.model}-${lib}`) expect(config).toBeDefined() }) - const basePathDataAccess = `libs/${options.app}/${options.name}/data-access/src` - const basePathFeature = `libs/${options.app}/${options.name}/feature/src` + const basePathDataAccess = `libs/${options.app}/${options.model}/data-access/src` + const basePathFeature = `libs/${options.app}/${options.model}/feature/src` const sourceFilesDataAccess = getRecursiveFileNames({ tree, path: basePathDataAccess }) const sourceFilesFeature = getRecursiveFileNames({ tree, path: basePathFeature }) @@ -64,12 +64,12 @@ describe('api-feature generator', () => { await apiFeatureGenerator(tree, { ...options, crud: 'admin,user' }) libs.forEach((lib) => { - const config = readProjectConfiguration(tree, `${options.app}-${options.name}-${lib}`) + const config = readProjectConfiguration(tree, `${options.app}-${options.model}-${lib}`) expect(config).toBeDefined() }) - const basePathDataAccess = `libs/${options.app}/${options.name}/data-access/src` - const basePathFeature = `libs/${options.app}/${options.name}/feature/src` + const basePathDataAccess = `libs/${options.app}/${options.model}/data-access/src` + const basePathFeature = `libs/${options.app}/${options.model}/feature/src` const sourceFilesDataAccess = getRecursiveFileNames({ tree, path: basePathDataAccess }) const sourceFilesFeature = getRecursiveFileNames({ tree, path: basePathFeature }) @@ -118,13 +118,13 @@ describe('api-feature generator', () => { await apiFeatureGenerator(tree, { ...options, skipUtil: false }) libs.forEach((lib) => { - const config = readProjectConfiguration(tree, `${options.app}-${options.name}-${lib}`) + const config = readProjectConfiguration(tree, `${options.app}-${options.model}-${lib}`) expect(config).toBeDefined() }) }) it('should generate the feature with different name', async () => { - const testOptions = { ...options, name: 'company' } + const testOptions = { ...options, model: 'company' } await createMockApiApp(tree, testOptions.app) // By default, we generate two libraries: data-access and feature @@ -132,7 +132,7 @@ describe('api-feature generator', () => { await apiFeatureGenerator(tree, { ...testOptions, skipUtil: false }) libs.forEach((lib) => { - const config = readProjectConfiguration(tree, `${testOptions.app}-${testOptions.name}-${lib}`) + const config = readProjectConfiguration(tree, `${testOptions.app}-${testOptions.model}-${lib}`) expect(config).toBeDefined() }) }) @@ -143,7 +143,7 @@ describe('api-feature generator', () => { // By default, we generate two libraries: data-access and feature const libs = ['data-access', 'feature'] const customName = 'custom' - await apiFeatureGenerator(tree, { ...options, name: customName }) + await apiFeatureGenerator(tree, { ...options, model: customName }) libs.forEach((lib) => { const config = readProjectConfiguration(tree, `${options.app}-${customName}-${lib}`) diff --git a/libs/tools/src/generators/api-feature/api-feature-schema.d.ts b/libs/tools/src/generators/api-feature/api-feature-schema.d.ts index f28c32d..d9c8b10 100644 --- a/libs/tools/src/generators/api-feature/api-feature-schema.d.ts +++ b/libs/tools/src/generators/api-feature/api-feature-schema.d.ts @@ -1,11 +1,11 @@ export type ApiFeatureGeneratorSchema = Partial> & { crud?: string + model: string } export interface NormalizedApiFeatureSchema { app: string crud: string[] - name: string label: string model: string npmScope: string diff --git a/libs/tools/src/generators/api-feature/api-feature-schema.json b/libs/tools/src/generators/api-feature/api-feature-schema.json index fb581fb..1909508 100644 --- a/libs/tools/src/generators/api-feature/api-feature-schema.json +++ b/libs/tools/src/generators/api-feature/api-feature-schema.json @@ -4,15 +4,6 @@ "title": "", "type": "object", "properties": { - "name": { - "type": "string", - "description": "The name of the feature.", - "$default": { - "$source": "argv", - "index": 0 - }, - "x-prompt": "What name would you like to use?" - }, "app": { "type": "string", "description": "The name of the application you are adding the feature to.", diff --git a/libs/tools/src/generators/web-crud/web-crud-generator.spec.ts b/libs/tools/src/generators/web-crud/web-crud-generator.spec.ts index 00b97fc..ecc9337 100644 --- a/libs/tools/src/generators/web-crud/web-crud-generator.spec.ts +++ b/libs/tools/src/generators/web-crud/web-crud-generator.spec.ts @@ -12,7 +12,7 @@ describe('web-crud generator', () => { tree = createTreeWithEmptyWorkspace() await createMockWebApp(tree, options.app) - await webFeatureGenerator(tree, { app: options.app, model: options.model, name: options.model }) + await webFeatureGenerator(tree, { app: options.app, model: options.model }) }) it('should run successfully', async () => { diff --git a/libs/tools/src/generators/web-crud/web-crud-schema.d.ts b/libs/tools/src/generators/web-crud/web-crud-schema.d.ts index fd652ef..c5ae695 100644 --- a/libs/tools/src/generators/web-crud/web-crud-schema.d.ts +++ b/libs/tools/src/generators/web-crud/web-crud-schema.d.ts @@ -8,4 +8,5 @@ export interface WebCrudGeneratorSchema { export interface NormalizedWebCrudSchema extends WebCrudGeneratorSchema { label: string npmScope: string + fields?: PrismaModelField[] } diff --git a/libs/tools/src/generators/web-feature/__snapshots__/web-feature-generator.spec.ts.snap b/libs/tools/src/generators/web-feature/__snapshots__/web-feature-generator.spec.ts.snap index 6888fb3..c07ad06 100644 --- a/libs/tools/src/generators/web-feature/__snapshots__/web-feature-generator.spec.ts.snap +++ b/libs/tools/src/generators/web-feature/__snapshots__/web-feature-generator.spec.ts.snap @@ -213,6 +213,7 @@ export function useUserFindOneTest({ testId }: { testId: string }) { exports[`web-feature generator should run successfully with crud 9`] = ` "export const AdminTestFeature = lazy(() => import('./lib/admin-test.routes')); import { lazy } from 'react'; + export const UserTestFeature = lazy(() => import('./lib/user-test.routes')); " `; @@ -686,7 +687,7 @@ export function AdminTestUiCreateForm({ }; const fields: UiFormField[] = [ - formFieldText('name', { label: 'Name', required: true }), + formFieldText('name', { label: 'name', required: true }), ]; return ( [] = [ - formFieldText('name', { label: 'Name' }), + formFieldText('name', { label: 'name' }), ]; return ( [] = [ - formFieldText('name', { label: 'Name', required: true }), + formFieldText('name', { label: 'name', required: true }), ]; return ( [] = [ - formFieldText('name', { label: 'Name' }), + formFieldText('name', { label: 'name' }), ]; return ( { let tree: Tree - const rawOptions: WebFeatureGeneratorSchema = { app: 'web', name: 'test' } + const rawOptions: WebFeatureGeneratorSchema = { app: 'web', model: 'test' } let options: NormalizedWebFeatureSchema beforeEach(async () => { tree = createTreeWithEmptyWorkspace() options = normalizeWebFeatureSchema(tree, rawOptions) + await createMockApiApp(tree, 'api') + await apiFeatureGenerator(tree, { app: 'api', crud: 'admin,user', model: 'company' }) await createMockWebApp(tree, options.app) }) diff --git a/libs/tools/src/generators/web-feature/web-feature-schema.d.ts b/libs/tools/src/generators/web-feature/web-feature-schema.d.ts index 35ee91a..a31b0f3 100644 --- a/libs/tools/src/generators/web-feature/web-feature-schema.d.ts +++ b/libs/tools/src/generators/web-feature/web-feature-schema.d.ts @@ -5,7 +5,6 @@ export type WebFeatureGeneratorSchema = PartialCreate<%= model.className %>Input { - @Field() - <%= label.propertyName %>!: string +<% for (let field of fields){ %> + @Field(<% if(field.optional){ %>{ nullable: true }<% } %>) + <%= field.name %><% if(field.optional){ %>?<% } else { %>!<% } %>: <%= field.type %> +<% } %> } diff --git a/libs/tools/src/lib/api-crud/files/data-access/lib/dto/__actorFileName__-update-__modelFileName__.input.ts.template b/libs/tools/src/lib/api-crud/files/data-access/lib/dto/__actorFileName__-update-__modelFileName__.input.ts.template index 2169e59..08874e7 100644 --- a/libs/tools/src/lib/api-crud/files/data-access/lib/dto/__actorFileName__-update-__modelFileName__.input.ts.template +++ b/libs/tools/src/lib/api-crud/files/data-access/lib/dto/__actorFileName__-update-__modelFileName__.input.ts.template @@ -2,6 +2,8 @@ import { Field, InputType } from '@nestjs/graphql' @InputType() export class <%= actor.className %>Update<%= model.className %>Input { +<% for (let field of fields){ %> @Field({ nullable: true }) - <%= label.propertyName %>?: string + <%= field.name %>?: <%= field.type %> +<% } %> } diff --git a/libs/tools/src/lib/api-crud/files/entity/lib/entity/__modelFileName__.entity.ts.template b/libs/tools/src/lib/api-crud/files/entity/lib/entity/__modelFileName__.entity.ts.template index 748029b..6704266 100644 --- a/libs/tools/src/lib/api-crud/files/entity/lib/entity/__modelFileName__.entity.ts.template +++ b/libs/tools/src/lib/api-crud/files/entity/lib/entity/__modelFileName__.entity.ts.template @@ -8,6 +8,8 @@ export class <%= model.className %> { createdAt?: Date @Field({ nullable: true }) updatedAt?: Date - @Field() - <%= label.propertyName %>!: string + <% for (let field of fields){ %> + @Field(<% if(field.optional){ %>{ nullable: true }<% } %>) + <%= field.name %><% if(field.optional){ %>?<% } else { %>!<% } %>: <%= field.type %> + <% } %> } diff --git a/libs/tools/src/lib/api-crud/generate-api-crud.ts b/libs/tools/src/lib/api-crud/generate-api-crud.ts index 31badfe..b9fae9c 100644 --- a/libs/tools/src/lib/api-crud/generate-api-crud.ts +++ b/libs/tools/src/lib/api-crud/generate-api-crud.ts @@ -1,10 +1,10 @@ import { generateFiles, getProjects, type ProjectConfiguration, Tree } from '@nx/devkit' import type { NormalizedApiCrudSchema } from '../../generators/api-crud/api-crud-schema' -import { generateSdkFile } from '../api/generate-sdk-file' import { addExports } from '../utils/add-export' import { ensureNxProjectExists } from '../utils/ensure-nx-project-exists' import { addServiceToClassConstructor } from './add-service-to-class-constructor' import { addServiceToModuleDecorator } from './add-service-to-module-decorator' +import { generateSdkFile } from './generate-sdk-file' import { getApiCrudSubstitutions } from './get-api-crud-substitutions' export function generateApiCrud(tree: Tree, options: NormalizedApiCrudSchema) { @@ -71,7 +71,11 @@ export function generateApiCrud(tree: Tree, options: NormalizedApiCrudSchema) { generateFiles(tree, `${__dirname}/files/feature`, feature.sourceRoot, { ...vars }) // Generate the SDK file - generateSdkFile(tree, options) + generateSdkFile( + tree, + options, + options.fields?.map((f) => f.name.toString()), + ) const e2e = getProjects(tree).get(`${options.app}-e2e`) if (!e2e) { diff --git a/libs/tools/src/lib/api/generate-sdk-file.ts b/libs/tools/src/lib/api-crud/generate-sdk-file.ts similarity index 92% rename from libs/tools/src/lib/api/generate-sdk-file.ts rename to libs/tools/src/lib/api-crud/generate-sdk-file.ts index c990cdf..e419268 100644 --- a/libs/tools/src/lib/api/generate-sdk-file.ts +++ b/libs/tools/src/lib/api-crud/generate-sdk-file.ts @@ -1,6 +1,6 @@ import { getProjects, names, Tree } from '@nx/devkit' -export function generateSdkFile(tree: Tree, options: { actor: string; model: string; label: string }) { +export function generateSdkFile(tree: Tree, options: { actor: string; model: string }, fields: string[]) { const project = getProjects(tree).get('sdk') if (!project) { @@ -20,7 +20,7 @@ export function generateSdkFile(tree: Tree, options: { actor: string; model: str if (!exists) { // Write the fragment to the file if it doesn't exist. - tree.write(target, sdkTemplateFragment(options.model, options.label)) + tree.write(target, sdkTemplateFragment(options.model, fields)) } const content = tree.read(target)?.toString() ?? '' @@ -30,12 +30,12 @@ export function generateSdkFile(tree: Tree, options: { actor: string; model: str } } -function sdkTemplateFragment(name: string, label: string) { +function sdkTemplateFragment(name: string, fields: string[]) { const { className } = names(name) return `fragment ${className}Details on ${className} { createdAt id - ${label} + ${fields.join('\n ')} updatedAt } ` diff --git a/libs/tools/src/lib/api-crud/get-api-crud-substitutions.ts b/libs/tools/src/lib/api-crud/get-api-crud-substitutions.ts index 7759def..3e6531b 100644 --- a/libs/tools/src/lib/api-crud/get-api-crud-substitutions.ts +++ b/libs/tools/src/lib/api-crud/get-api-crud-substitutions.ts @@ -12,6 +12,7 @@ export function getApiCrudSubstitutions(options: NormalizedApiCrudSchema) { actorFileName: actor.fileName, app, appFileName: app.fileName, + fields: options.fields, label: names(options.label), model, modelFileName: model.fileName, diff --git a/libs/tools/src/lib/api-crud/normalize-api-crud-schema.ts b/libs/tools/src/lib/api-crud/normalize-api-crud-schema.ts index c89767a..77fdac5 100644 --- a/libs/tools/src/lib/api-crud/normalize-api-crud-schema.ts +++ b/libs/tools/src/lib/api-crud/normalize-api-crud-schema.ts @@ -1,14 +1,18 @@ import { Tree } from '@nx/devkit' import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope' import type { ApiCrudGeneratorSchema, NormalizedApiCrudSchema } from '../../generators/api-crud/api-crud-schema' +import { getPrismaModelFields } from '../prisma/get-prisma-models' export function normalizeApiCrudSchema(tree: Tree, schema: ApiCrudGeneratorSchema): NormalizedApiCrudSchema { const npmScope = getNpmScope(tree) + const fields = getPrismaModelFields(tree, schema.model) ?? [{ name: 'name', type: 'string', optional: false }] + return { app: schema.app ?? 'api', actor: schema.actor, label: schema.label ?? 'name', model: schema.model, npmScope, + fields, } } diff --git a/libs/tools/src/lib/api-e2e/files/api-__modelFileName__-feature.spec.ts.template b/libs/tools/src/lib/api-e2e/files/api-__modelFileName__-feature.spec.ts.template deleted file mode 100644 index 96ad1da..0000000 --- a/libs/tools/src/lib/api-e2e/files/api-__modelFileName__-feature.spec.ts.template +++ /dev/null @@ -1,171 +0,0 @@ -import { AdminCreate<%= model.className %>Input, AdminFindMany<%= model.className %>Input, AdminUpdate<%= model.className %>Input, <%= model.className %> } from '@<%= npmScope %>/sdk' -import { getAliceCookie, getBobCookie, sdk, uniqueId } from '../support' - -describe('api-<%= model.fileName %>-feature', () => { - describe('api-<%= model.fileName %>-admin-resolver', () => { - const <%= model.fileName %>Name = uniqueId('acme-<%= model.fileName %>') - let <%= model.propertyName %>Id: string - let cookie: string - - beforeAll(async () => { - cookie = await getAliceCookie() - const created = await sdk.adminCreate<%= model.className %>({ input: { name: <%= model.fileName %>Name } }, { cookie }) - <%= model.propertyName %>Id = created.data.created.id - }) - - describe('authorized', () => { - beforeAll(async () => { - cookie = await getAliceCookie() - }) - - it('should create a <%= model.fileName %>', async () => { - const input: AdminCreate<%= model.className %>Input = { - name: uniqueId('<%= model.fileName %>'), - } - - const res = await sdk.adminCreate<%= model.className %>({ input }, { cookie }) - - const item: <%= model.className %> = res.data.created - expect(item.name).toBe(input.name) - expect(item.id).toBeDefined() - expect(item.createdAt).toBeDefined() - expect(item.updatedAt).toBeDefined() - }) - - it('should update a <%= model.fileName %>', async () => { - const createInput: AdminCreate<%= model.className %>Input = { - name: uniqueId('<%= model.fileName %>'), - } - const createdRes = await sdk.adminCreate<%= model.className %>({ input: createInput }, { cookie }) - const <%= model.propertyName %>Id = createdRes.data.created.id - const input: AdminUpdate<%= model.className %>Input = { - name: uniqueId('<%= model.fileName %>'), - } - - const res = await sdk.adminUpdate<%= model.className %>({ <%= model.propertyName %>Id, input }, { cookie }) - - const item: <%= model.className %> = res.data.updated - expect(item.name).toBe(input.name) - }) - - it('should find a list of <%= modelPropertyNamePlural %> (find all)', async () => { - const createInput: AdminCreate<%= model.className %>Input = { - name: uniqueId('<%= model.fileName %>'), - } - const createdRes = await sdk.adminCreate<%= model.className %>({ input: createInput }, { cookie }) - const <%= model.propertyName %>Id = createdRes.data.created.id - - const input: AdminFindMany<%= model.className %>Input = {} - - const res = await sdk.adminFindMany<%= model.className %>({ input }, { cookie }) - - expect(res.data.paging.meta.totalCount).toBeGreaterThan(1) - expect(res.data.paging.data.length).toBeGreaterThan(1) - // First item should be the one we created above - expect(res.data.paging.data[0].id).toBe(<%= model.propertyName %>Id) - }) - - it('should find a list of <%= modelPropertyNamePlural %> (find new one)', async () => { - const createInput: AdminCreate<%= model.className %>Input = { - name: uniqueId('<%= model.fileName %>'), - } - const createdRes = await sdk.adminCreate<%= model.className %>({ input: createInput }, { cookie }) - const <%= model.propertyName %>Id = createdRes.data.created.id - - const input: AdminFindMany<%= model.className %>Input = { - search: <%= model.propertyName %>Id, - } - - const res = await sdk.adminFindMany<%= model.className %>({ input }, { cookie }) - - expect(res.data.paging.meta.totalCount).toBe(1) - expect(res.data.paging.data.length).toBe(1) - expect(res.data.paging.data[0].id).toBe(<%= model.propertyName %>Id) - }) - - it('should find a <%= model.fileName %> by id', async () => { - const createInput: AdminCreate<%= model.className %>Input = { - name: uniqueId('<%= model.fileName %>'), - } - const createdRes = await sdk.adminCreate<%= model.className %>({ input: createInput }, { cookie }) - const <%= model.propertyName %>Id = createdRes.data.created.id - - const res = await sdk.adminFindOne<%= model.className %>({ <%= model.propertyName %>Id }, { cookie }) - - expect(res.data.item.id).toBe(<%= model.propertyName %>Id) - }) - - it('should delete a <%= model.fileName %>', async () => { - const createInput: AdminCreate<%= model.className %>Input = { - name: uniqueId('<%= model.fileName %>'), - } - const createdRes = await sdk.adminCreate<%= model.className %>({ input: createInput }, { cookie }) - const <%= model.propertyName %>Id = createdRes.data.created.id - - const res = await sdk.adminDelete<%= model.className %>({ <%= model.propertyName %>Id }, { cookie }) - - expect(res.data.deleted).toBe(true) - - const findRes = await sdk.adminFindMany<%= model.className %>({ input: { search: <%= model.propertyName %>Id } }, { cookie }) - expect(findRes.data.paging.meta.totalCount).toBe(0) - expect(findRes.data.paging.data.length).toBe(0) - }) - }) - - describe('unauthorized', () => { - let cookie: string - beforeAll(async () => { - cookie = await getBobCookie() - }) - - it('should not create a <%= model.fileName %>', async () => { - expect.assertions(1) - const input: AdminCreate<%= model.className %>Input = { - name: uniqueId('<%= model.fileName %>'), - } - - try { - await sdk.adminCreate<%= model.className %>({ input }, { cookie }) - } catch (e) { - expect(e.message).toBe('Unauthorized: User is not Admin') - } - }) - - it('should not update a <%= model.fileName %>', async () => { - expect.assertions(1) - try { - await sdk.adminUpdate<%= model.className %>({ <%= model.propertyName %>Id, input: {} }, { cookie }) - } catch (e) { - expect(e.message).toBe('Unauthorized: User is not Admin') - } - }) - - it('should not find a list of <%= modelPropertyNamePlural %> (find all)', async () => { - expect.assertions(1) - try { - await sdk.adminFindMany<%= model.className %>({ input: {} }, { cookie }) - } catch (e) { - expect(e.message).toBe('Unauthorized: User is not Admin') - } - }) - - it('should not find a <%= model.fileName %> by id', async () => { - expect.assertions(1) - try { - await sdk.adminFindOne<%= model.className %>({ <%= model.propertyName %>Id }, { cookie }) - } catch (e) { - expect(e.message).toBe('Unauthorized: User is not Admin') - } - }) - - it('should not delete a <%= model.fileName %>', async () => { - expect.assertions(1) - try { - await sdk.adminDelete<%= model.className %>({ <%= model.propertyName %>Id }, { cookie }) - } catch (e) { - expect(e.message).toBe('Unauthorized: User is not Admin') - } - }) - }) - }) -}) diff --git a/libs/tools/src/lib/api-e2e/generate-api-e2e.ts b/libs/tools/src/lib/api-e2e/generate-api-e2e.ts deleted file mode 100644 index 37133a3..0000000 --- a/libs/tools/src/lib/api-e2e/generate-api-e2e.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { generateFiles, Tree } from '@nx/devkit' -import { NormalizedApiFeatureSchema } from '../../generators/api-feature/api-feature-schema' -import { getApiSubstitutions } from '../api/get-api-substitutions' - -export async function generateApiE2e(tree: Tree, options: NormalizedApiFeatureSchema) { - const substitutions = getApiSubstitutions(options) - generateFiles(tree, `${__dirname}/files`, `apps/${options.app}-e2e/src/api/`, { ...substitutions }) - - return true -} diff --git a/libs/tools/src/lib/api/create-mock-api-app.ts b/libs/tools/src/lib/api/create-mock-api-app.ts index 95f4ab7..99a9aaf 100644 --- a/libs/tools/src/lib/api/create-mock-api-app.ts +++ b/libs/tools/src/lib/api/create-mock-api-app.ts @@ -38,4 +38,19 @@ export async function createMockApiApp(tree: Tree, app: string) { projectNameAndRootFormat: 'as-provided', skipFormat: true, }) + + tree.write( + 'prisma/schema.prisma', + ` + model User { + id Int @id + } + model Company { + id Int @id + name String + location String + phone String? + } + `, + ) } diff --git a/libs/tools/src/lib/api/generate-api-feature.ts b/libs/tools/src/lib/api/generate-api-feature.ts index fa68be3..7da17dc 100644 --- a/libs/tools/src/lib/api/generate-api-feature.ts +++ b/libs/tools/src/lib/api/generate-api-feature.ts @@ -1,6 +1,6 @@ import { getProjects, Tree } from '@nx/devkit' +import apiCrudGenerator from '../../generators/api-crud/api-crud-generator' import { NormalizedApiFeatureSchema } from '../../generators/api-feature/api-feature-schema' -import { generateApiCrud } from '../api-crud/generate-api-crud' import { generateApiLib } from './generate-api-lib' export async function generateApiFeature(tree: Tree, options: NormalizedApiFeatureSchema) { @@ -22,7 +22,7 @@ export async function generateApiFeature(tree: Tree, options: NormalizedApiFeatu } if (!options.skipDataAccess && !options.skipFeature && options.crud?.length) { for (const actor of options.crud) { - generateApiCrud(tree, { actor, ...options }) + await apiCrudGenerator(tree, { actor, ...options }) } } } diff --git a/libs/tools/src/lib/api/generate-api-lib-data-access.ts b/libs/tools/src/lib/api/generate-api-lib-data-access.ts index e8e7f73..d462d47 100644 --- a/libs/tools/src/lib/api/generate-api-lib-data-access.ts +++ b/libs/tools/src/lib/api/generate-api-lib-data-access.ts @@ -17,7 +17,7 @@ export async function generateApiLibDataAccess(tree: Tree, options: NormalizedAp generateFiles(tree, `${__dirname}/files/data-access`, dataAccess.sourceRoot, { ...substitutions }) // Add the exports to the barrel file addExports(tree, `${dataAccess.sourceRoot}/index.ts`, [ - `./lib/${options.app}-${options.name}.service`, + `./lib/${options.app}-${options.model}.service`, `./lib/entity/${substitutions.model.fileName}.entity`, ]) } diff --git a/libs/tools/src/lib/api/generate-api-lib.ts b/libs/tools/src/lib/api/generate-api-lib.ts index 6978756..e8ee9dd 100644 --- a/libs/tools/src/lib/api/generate-api-lib.ts +++ b/libs/tools/src/lib/api/generate-api-lib.ts @@ -7,9 +7,9 @@ import { generateApiLibFeature } from './generate-api-lib-feature' export async function generateApiLib(tree: Tree, type: ApiLibType, options: NormalizedApiFeatureSchema) { const generated = await libraryGenerator(tree, { - name: `${options.app}-${options.name}-${type}`, + name: `${options.app}-${options.model}-${type}`, projectNameAndRootFormat: 'as-provided', - directory: `libs/${options.app}/${options.name}/${type}`, + directory: `libs/${options.app}/${options.model}/${type}`, tags: `app:${options.app},type:${type}`, skipFormat: true, }) diff --git a/libs/tools/src/lib/api/get-api-feature-module-info.ts b/libs/tools/src/lib/api/get-api-feature-module-info.ts index 60000c9..f4ef442 100644 --- a/libs/tools/src/lib/api/get-api-feature-module-info.ts +++ b/libs/tools/src/lib/api/get-api-feature-module-info.ts @@ -3,7 +3,7 @@ import { NormalizedApiFeatureSchema } from '../../generators/api-feature/api-fea import { getImportPath } from '../utils/get-import-path' export function getApiFeatureModuleInfo(tree: Tree, options: NormalizedApiFeatureSchema) { - const project = readProjectConfiguration(tree, `${options.app}-${options.name}-feature`) + const project = readProjectConfiguration(tree, `${options.app}-${options.model}-feature`) const featureProjectRoot = project.sourceRoot const featureImportPath = getImportPath(tree, `${featureProjectRoot}/index.ts`) diff --git a/libs/tools/src/lib/api/normalize-api-feature-schema.ts b/libs/tools/src/lib/api/normalize-api-feature-schema.ts index b19d803..d2ac3e2 100644 --- a/libs/tools/src/lib/api/normalize-api-feature-schema.ts +++ b/libs/tools/src/lib/api/normalize-api-feature-schema.ts @@ -3,12 +3,11 @@ import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope' import { ApiFeatureGeneratorSchema, NormalizedApiFeatureSchema } from '../../generators/api-feature/api-feature-schema' export function normalizeApiFeatureSchema(tree: Tree, schema: ApiFeatureGeneratorSchema): NormalizedApiFeatureSchema { - const model = schema.name + const model = schema.model const npmScope = getNpmScope(tree) return { app: schema.app ?? 'api', - name: schema.name, label: schema.label ?? 'name', crud: schema.crud?.length ? schema.crud.split(',') : [], model, diff --git a/libs/tools/src/lib/prisma/create-mock-prisma-schema.ts b/libs/tools/src/lib/prisma/create-mock-prisma-schema.ts index 598a281..f8fe81b 100644 --- a/libs/tools/src/lib/prisma/create-mock-prisma-schema.ts +++ b/libs/tools/src/lib/prisma/create-mock-prisma-schema.ts @@ -46,7 +46,6 @@ export function createMockPrismaSchema(tree: Tree) { const sorted = printSchema(schema, { sort: true }) if (sorted.trim() !== baseScheme.trim()) { - console.log(sorted) throw new Error('createMockPrismaSchema: Prisma baseScheme not properly sorted') } } diff --git a/libs/tools/src/lib/prisma/get-prisma-models.ts b/libs/tools/src/lib/prisma/get-prisma-models.ts index 2d904b7..86f5942 100644 --- a/libs/tools/src/lib/prisma/get-prisma-models.ts +++ b/libs/tools/src/lib/prisma/get-prisma-models.ts @@ -1,3 +1,4 @@ +import type { Field } from '@mrleebo/prisma-ast' import { Model } from '@mrleebo/prisma-ast' import { Tree } from '@nx/devkit' import { getPrismaSchema } from './get-prisma-schema' @@ -7,3 +8,37 @@ export function getPrismaModels(tree: Tree, schemaPath = 'prisma/schema.prisma') return schema.list.filter((item) => item.type === 'model') as Model[] } + +interface PrismaModelField { + name: string + type: string + optional: boolean +} + +function getField(type: string) { + switch (type) { + case 'String': + return 'string' + case 'Int': + return 'number' + case 'Boolean': + return 'boolean' + case 'DateTime': + return 'Date' + default: + return 'string' + } +} + +export function getPrismaModelFields(tree: Tree, name: string): PrismaModelField[] { + const model = getPrismaModels(tree).find((m) => m.name.toLowerCase() === name.toLowerCase()) + + return model?.properties + .filter((p) => p.type === 'field') + .filter((p: Field) => !['id', 'createdAt', 'updatedAt'].includes(p.name)) + .map((p: Field) => ({ + name: p.name, + type: getField(p.fieldType.toString()), + optional: p.optional, + })) +} diff --git a/libs/tools/src/lib/prisma/get-prisma-schema-file.ts b/libs/tools/src/lib/prisma/get-prisma-schema-file.ts index 3fd4b5a..22e6e26 100644 --- a/libs/tools/src/lib/prisma/get-prisma-schema-file.ts +++ b/libs/tools/src/lib/prisma/get-prisma-schema-file.ts @@ -1,5 +1,5 @@ import { Tree } from '@nx/devkit' export function getPrismaSchemaFile(tree: Tree, schemaPath = 'prisma/schema.prisma'): string { - return tree.read(schemaPath).toString('utf-8') + return tree.read(schemaPath)?.toString('utf-8') } diff --git a/libs/tools/src/lib/prisma/get-prisma-schema.ts b/libs/tools/src/lib/prisma/get-prisma-schema.ts index 74ca257..945f10e 100644 --- a/libs/tools/src/lib/prisma/get-prisma-schema.ts +++ b/libs/tools/src/lib/prisma/get-prisma-schema.ts @@ -3,6 +3,5 @@ import { Tree } from '@nx/devkit' import { getPrismaSchemaFile } from './get-prisma-schema-file' export function getPrismaSchema(tree: Tree, schemaPath = 'prisma/schema.prisma'): Schema { - // console.log(getSchema(``)) return getSchema(getPrismaSchemaFile(tree, schemaPath)) } diff --git a/libs/tools/src/lib/web-crud/files/ui/lib/__actorFileName__-__modelFileName__-ui-create-form.tsx.template b/libs/tools/src/lib/web-crud/files/ui/lib/__actorFileName__-__modelFileName__-ui-create-form.tsx.template index 9d33a17..e143798 100644 --- a/libs/tools/src/lib/web-crud/files/ui/lib/__actorFileName__-__modelFileName__-ui-create-form.tsx.template +++ b/libs/tools/src/lib/web-crud/files/ui/lib/__actorFileName__-__modelFileName__-ui-create-form.tsx.template @@ -5,11 +5,15 @@ import { ReactNode } from 'react' export function <%= actor.className %><%= model.className %>UiCreateForm({ submit }: { submit: (res: <%= actor.className %>Create<%= model.className %>Input) => Promise }) { const model: <%= actor.className %>Create<%= model.className %>Input = { - <%= label.propertyName %>: '', + <% for (let field of fields){ %> + <%= field.name %>: '', + <% } %> } const fields: UiFormField<<%= actor.className %>Create<%= model.className %>Input>[] = [ - formFieldText('<%= label.propertyName %>', { label: '<%= label.className %>', required: true, }), + <% for (let field of fields){ %> + formFieldText('<%= field.name %>', { label: '<%= field.name %>', required: true, }), + <% } %> ] return ( submit(res as <%= actor.className %>Create<%= model.className %>Input)}> diff --git a/libs/tools/src/lib/web-crud/files/ui/lib/__actorFileName__-__modelFileName__-ui-update-form.tsx.template b/libs/tools/src/lib/web-crud/files/ui/lib/__actorFileName__-__modelFileName__-ui-update-form.tsx.template index 2363c96..f76d744 100644 --- a/libs/tools/src/lib/web-crud/files/ui/lib/__actorFileName__-__modelFileName__-ui-update-form.tsx.template +++ b/libs/tools/src/lib/web-crud/files/ui/lib/__actorFileName__-__modelFileName__-ui-update-form.tsx.template @@ -10,11 +10,15 @@ export function <%= actor.className %><%= model.className %>UiUpdateForm({ <%= model.propertyName %>: <%= model.className %> }) { const model: <%= actor.className %>Update<%= model.className %>Input = { - <%= label.propertyName %>: <%= model.propertyName %>.<%= label.propertyName %> ?? '', + <% for (let field of fields){ %> + <%= field.name %>: <%= model.propertyName %>.<%= field.name %> ?? '', + <% } %> } const fields: UiFormField<<%= actor.className %>Update<%= model.className %>Input>[] = [ - formFieldText('<%= label.propertyName %>', { label: '<%= label.className %>' }), + <% for (let field of fields){ %> + formFieldText('<%= field.name %>', { label: '<%= field.name %>', }), + <% } %> ] return ( submit(res as <%= actor.className %>Update<%= model.className %>Input)}> diff --git a/libs/tools/src/lib/web-crud/get-web-crud-substitutions.ts b/libs/tools/src/lib/web-crud/get-web-crud-substitutions.ts index eddfbb2..1e5cfd7 100644 --- a/libs/tools/src/lib/web-crud/get-web-crud-substitutions.ts +++ b/libs/tools/src/lib/web-crud/get-web-crud-substitutions.ts @@ -10,10 +10,11 @@ export function getWebCrudSubstitutions(options: NormalizedApiCrudSchema) { return { actor, - actorFileName: actor.fileName, actorAdmin: actor.className === 'Admin', + actorFileName: actor.fileName, app, appFileName: app.fileName, + fields: options.fields, label: names(options.label), model, modelFileName: model.fileName, diff --git a/libs/tools/src/lib/web-crud/normalize-web-crud-schema.ts b/libs/tools/src/lib/web-crud/normalize-web-crud-schema.ts index 4fa297b..981ef0b 100644 --- a/libs/tools/src/lib/web-crud/normalize-web-crud-schema.ts +++ b/libs/tools/src/lib/web-crud/normalize-web-crud-schema.ts @@ -1,14 +1,18 @@ import { Tree } from '@nx/devkit' import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope' import type { NormalizedWebCrudSchema, WebCrudGeneratorSchema } from '../../generators/web-crud/web-crud-schema' +import { getPrismaModelFields } from '../prisma/get-prisma-models' export function normalizeWebCrudSchema(tree: Tree, schema: WebCrudGeneratorSchema): NormalizedWebCrudSchema { const npmScope = getNpmScope(tree) + const fields = getPrismaModelFields(tree, schema.model) ?? [{ name: 'name', type: 'string', optional: false }] + return { app: schema.app ?? 'web', actor: schema.actor, label: schema.label ?? 'name', model: schema.model, npmScope, + fields, } } diff --git a/libs/tools/src/lib/web/generate-web-feature.ts b/libs/tools/src/lib/web/generate-web-feature.ts index 9fb6aec..02ea686 100644 --- a/libs/tools/src/lib/web/generate-web-feature.ts +++ b/libs/tools/src/lib/web/generate-web-feature.ts @@ -1,6 +1,6 @@ import { getProjects, Tree } from '@nx/devkit' +import webCrudGenerator from '../../generators/web-crud/web-crud-generator' import { NormalizedWebFeatureSchema } from '../../generators/web-feature/web-feature-schema' -import { generateWebCrud } from '../web-crud/generate-web-crud' import { generateWebLib } from './generate-web-lib' export async function generateWebFeature(tree: Tree, options: NormalizedWebFeatureSchema) { @@ -22,7 +22,7 @@ export async function generateWebFeature(tree: Tree, options: NormalizedWebFeatu } if (!options.skipDataAccess && !options.skipFeature && !options.skipUi && options.crud?.length) { for (const actor of options.crud) { - generateWebCrud(tree, { actor, ...options }) + await webCrudGenerator(tree, { actor, ...options }) } } } diff --git a/libs/tools/src/lib/web/generate-web-lib.ts b/libs/tools/src/lib/web/generate-web-lib.ts index 0c9f33f..a19468d 100644 --- a/libs/tools/src/lib/web/generate-web-lib.ts +++ b/libs/tools/src/lib/web/generate-web-lib.ts @@ -7,9 +7,9 @@ import { WebLibType } from '../types/web-feature' export async function generateWebLib(tree: Tree, type: WebLibType, options: NormalizedWebFeatureSchema) { const generated = await libraryGenerator(tree, { - name: `${options.app}-${options.name}-${type}`, + name: `${options.app}-${options.model}-${type}`, projectNameAndRootFormat: 'as-provided', - directory: `libs/${options.app}/${options.name}/${type}`, + directory: `libs/${options.app}/${options.model}/${type}`, tags: `app:${options.app},type:${type}`, skipFormat: true, linter: Linter.EsLint, diff --git a/libs/tools/src/lib/web/normalize-web-feature-schema.ts b/libs/tools/src/lib/web/normalize-web-feature-schema.ts index 8c4539d..0a92d69 100644 --- a/libs/tools/src/lib/web/normalize-web-feature-schema.ts +++ b/libs/tools/src/lib/web/normalize-web-feature-schema.ts @@ -3,12 +3,11 @@ import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope' import { NormalizedWebFeatureSchema, WebFeatureGeneratorSchema } from '../../generators/web-feature/web-feature-schema' export function normalizeWebFeatureSchema(tree: Tree, schema: WebFeatureGeneratorSchema): NormalizedWebFeatureSchema { - const model = schema.name + const model = schema.model const npmScope = getNpmScope(tree) return { app: schema.app ?? 'web', - name: schema.name, label: schema.label ?? 'name', crud: schema.crud?.length ? schema.crud.split(',') : [], model,