Skip to content

Commit

Permalink
feat: add admin crud option to api-feature generator
Browse files Browse the repository at this point in the history
  • Loading branch information
beeman committed Aug 8, 2023
1 parent 376af65 commit 07e1ef7
Show file tree
Hide file tree
Showing 24 changed files with 666 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`api-feature generator should generate the feature libraries 1`] = `
"export * from './lib/api-test-data-access.module';
export * from './lib/api-test.service';
export * from './lib/api-test-admin.service';
export * from './lib/dto/admin-create-test.input';
export * from './lib/dto/admin-update-test.input';
export * from './lib/entity/test.entity';
"
`;

exports[`api-feature generator should generate the feature libraries 2`] = `
"import { Injectable } from '@nestjs/common';
import { ApiCoreService } from '@proj/api/core/data-access';
import { ApiTestAdminService } from './api-test-admin.service';
@Injectable()
export class ApiTestService {
constructor(
private readonly core: ApiCoreService,
readonly admin: ApiTestAdminService
) {}
}
"
`;

exports[`api-feature generator should generate the feature libraries 3`] = `
"import { Injectable } from '@nestjs/common';
import { ApiCoreService } from '@proj/api/core/data-access';
import { AdminCreateTestInput } from './dto/admin-create-test.input';
import { AdminUpdateTestInput } from './dto/admin-update-test.input';
@Injectable()
export class ApiTestAdminService {
constructor(private readonly core: ApiCoreService) {}
async createTest(adminId: string, input: AdminCreateTestInput) {
await this.core.ensureUserAdmin(adminId);
return this.core.data.test.create({ data: input });
}
async deleteTest(adminId: string, testId: string) {
await this.core.ensureUserAdmin(adminId);
return this.core.data.test.delete({ where: { id: testId } });
}
async findManyTests(adminId: string) {
await this.core.ensureUserAdmin(adminId);
return this.core.data.test.findMany();
}
async findOneTest(adminId: string, testId: string) {
await this.core.ensureUserAdmin(adminId);
return this.core.data.test.findUnique({ where: { id: testId } });
}
async updateTest(
adminId: string,
testId: string,
input: AdminUpdateTestInput
) {
await this.core.ensureUserAdmin(adminId);
return this.core.data.test.update({ where: { id: testId }, data: input });
}
}
"
`;

exports[`api-feature generator should generate the feature libraries 4`] = `
"export * from './lib/api-test-feature.module';
"
`;

exports[`api-feature generator should generate the feature libraries 5`] = `
"import { Resolver } from '@nestjs/graphql';
import { ApiTestService } from '@proj/api/test/data-access';
@Resolver()
export class ApiTestResolver {
constructor(private readonly service: ApiTestService) {}
}
"
`;

exports[`api-feature generator should generate the feature libraries 6`] = `
"import { Resolver } from '@nestjs/graphql';
import { ApiTestService } from '@proj/api/test/data-access';
import { ApiAuthGraphqlGuard, CtxUser } from '@proj/api/auth/data-access';
import { User } from '@proj/api/user/data-access';
import { Mutation, Query, Args } from '@nestjs/graphql';
import { UseGuards } from '@nestjs/common';
import {
AdminCreateTestInput,
AdminUpdateTestInput,
Test,
} from '@proj/api/test/data-access';
@Resolver()
@UseGuards(ApiAuthGraphqlGuard)
export class ApiTestAdminResolver {
constructor(private readonly service: ApiTestService) {}
@Mutation(() => Test, { nullable: true })
adminCreateTest(
@CtxUser() user: User,
@Args('input') input: AdminCreateTestInput
) {
return this.service.admin.createTest(user.id, input);
}
@Mutation(() => Test, { nullable: true })
adminDeleteTest(@CtxUser() user: User, @Args('testId') testId: string) {
return this.service.admin.deleteTest(user.id, testId);
}
@Query(() => [Test], { nullable: true })
adminFindManyTests(@CtxUser() user: User) {
return this.service.admin.findManyTests(user.id);
}
@Query(() => Test, { nullable: true })
adminFindOneTest(@CtxUser() user: User, @Args('testId') testId: string) {
return this.service.admin.findOneTest(user.id, testId);
}
@Mutation(() => Test, { nullable: true })
adminUpdateTest(
@CtxUser() user: User,
@Args('testId') testId: string,
@Args('input') input: AdminUpdateTestInput
) {
return this.service.admin.updateTest(user.id, testId, input);
}
}
"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ApiFeatureGeneratorSchema } from './api-feature-schema'

describe('api-feature generator', () => {
let tree: Tree
const options: ApiFeatureGeneratorSchema = { app: 'api', name: 'test' }
const options: ApiFeatureGeneratorSchema = { app: 'api', name: 'test', label: 'name' }

beforeEach(() => {
tree = createTreeWithEmptyWorkspace()
Expand All @@ -24,6 +24,32 @@ describe('api-feature generator', () => {
const config = readProjectConfiguration(tree, `${options.app}-${options.name}-${lib}`)
expect(config).toBeDefined()
})

const basePathDataAccess = `libs/${options.app}/${options.name}/data-access/src`
const dataAccessBarrel = `${basePathDataAccess}/index.ts`
expect(tree.exists(dataAccessBarrel)).toBeTruthy()
expect(tree.read(dataAccessBarrel).toString()).toMatchSnapshot()

const dataAccessService = `${basePathDataAccess}/lib/${options.app}-${options.name}.service.ts`
expect(tree.exists(dataAccessService)).toBeTruthy()
expect(tree.read(dataAccessService).toString()).toMatchSnapshot()

const dataAccessAdminService = `${basePathDataAccess}/lib/${options.app}-${options.name}-admin.service.ts`
expect(tree.exists(dataAccessAdminService)).toBeTruthy()
expect(tree.read(dataAccessAdminService).toString()).toMatchSnapshot()

const basePathFeature = `libs/${options.app}/${options.name}/feature/src`
const featureBarrel = `${basePathFeature}/index.ts`
expect(tree.exists(featureBarrel)).toBeTruthy()
expect(tree.read(featureBarrel).toString()).toMatchSnapshot()

const dataAccessResolver = `${basePathFeature}/lib/${options.app}-${options.name}.resolver.ts`
expect(tree.exists(dataAccessResolver)).toBeTruthy()
expect(tree.read(dataAccessResolver).toString()).toMatchSnapshot()

const dataAccessAdminResolver = `${basePathFeature}/lib/${options.app}-${options.name}-admin.resolver.ts`
expect(tree.exists(dataAccessAdminResolver)).toBeTruthy()
expect(tree.read(dataAccessAdminResolver).toString()).toMatchSnapshot()
})

it('should generate the feature libraries with util lib', async () => {
Expand All @@ -39,6 +65,20 @@ describe('api-feature generator', () => {
})
})

it('should generate the feature with different name', async () => {
const testOptions = { ...options, name: 'company' }
await createMockApp(tree, testOptions.app)

// By default, we generate two libraries: data-access and feature
const libs = ['data-access', 'feature', 'util']
await apiFeatureGenerator(tree, { ...testOptions, skipUtil: false })

libs.forEach((lib) => {
const config = readProjectConfiguration(tree, `${testOptions.app}-${testOptions.name}-${lib}`)
expect(config).toBeDefined()
})
})

it('should generate the feature libraries with custom names', async () => {
await createMockApp(tree, options.app)

Expand Down
4 changes: 4 additions & 0 deletions libs/tools/src/generators/api-feature/api-feature-schema.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
export interface ApiFeatureGeneratorSchema {
app?: string
name: string
label?: string
modelName?: string
pluralModelName?: string
skipAdminCrud?: boolean
skipDataAccess?: boolean
skipFeature?: boolean
skipUtil?: boolean
Expand Down
5 changes: 5 additions & 0 deletions libs/tools/src/generators/api-feature/api-feature-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
"description": "The name of the application you are adding the feature to.",
"default": "api"
},
"skipAdminCrud": {
"type": "boolean",
"description": "Do not create an admin crud library for this feature.",
"default": false
},
"skipDataAccess": {
"type": "boolean",
"description": "Do not create a data access library for this feature.",
Expand Down
27 changes: 24 additions & 3 deletions libs/tools/src/lib/api/generate-api-feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { generateApiLib } from './generate-api-lib'
export interface ApiFeatureSchema {
app?: string
name: string
label?: string
modelName?: string
pluralModelName?: string
skipAdminCrud?: boolean
skipDataAccess?: boolean
skipFeature?: boolean
skipUtil?: boolean
Expand All @@ -12,14 +16,31 @@ export interface ApiFeatureSchema {
export async function generateApiFeature(tree: Tree, options: ApiFeatureSchema) {
const app = options.app ?? 'api'
const name = options.name
const label = options.label ?? 'name'

if (!options.skipDataAccess) {
await generateApiLib(tree, app, name, 'data-access')
await generateApiLib(tree, {
app,
name,
label,
type: 'data-access',
adminCrud: !options.skipAdminCrud,
modelName: options.modelName,
pluralModelName: options.pluralModelName,
})
}
if (!options.skipFeature) {
await generateApiLib(tree, app, name, 'feature')
await generateApiLib(tree, {
app,
name,
label,
type: 'feature',
adminCrud: !options.skipAdminCrud,
modelName: options.modelName,
pluralModelName: options.pluralModelName,
})
}
if (!options.skipUtil) {
await generateApiLib(tree, app, name, 'util')
await generateApiLib(tree, { app, name, label, type: 'util' })
}
}
37 changes: 35 additions & 2 deletions libs/tools/src/lib/api/generate-api-lib-data-access.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
import { Tree } from '@nx/devkit'
import { serviceGenerator } from '@nx/nest'
import { addExport } from '../utils/add-export'
import { apiAddCrudServiceMethods } from '../utils/api-add-crud-service-methods'
import { apiUpdateDataAccessModule } from '../utils/api-update-data-access-module'
import { apiUpdateDataAccessService } from '../utils/api-update-data-access-service'
import { getApiDataAccessModuleInfo } from '../utils/get-api-data-access-module-info'
import { GenerateApiLibOptions } from './generate-api-lib'

export async function generateApiLibDataAccess(tree: Tree, app: string, name: string) {
// We create the service for this data-access module
export async function generateApiLibDataAccess(
tree: Tree,
{ app, name, label, adminCrud, pluralModelName, modelName }: Omit<GenerateApiLibOptions, 'type'>,
) {
// We create the services for this data-access module
await serviceGenerator(tree, {
name: `${app}-${name}`,
project: `${app}-${name}-data-access`,
flat: true,
directory: 'lib',
})

if (adminCrud) {
await serviceGenerator(tree, {
name: `${app}-${name}-admin`,
project: `${app}-${name}-data-access`,
flat: true,
directory: 'lib',
})
}

// We get the module info for the core and target modules
const coreModule = getApiDataAccessModuleInfo(tree, app, 'core')
const targetModule = getApiDataAccessModuleInfo(tree, app, name)
Expand All @@ -30,7 +44,26 @@ export async function generateApiLibDataAccess(tree: Tree, app: string, name: st
importPackage: coreModule.importPath,
importProperty: 'core',
targetClass: targetModule.serviceClassName,
adminServiceFile: adminCrud ? targetModule.adminServiceFile : undefined,
adminServiceClass: adminCrud ? targetModule.adminServiceClassName : undefined,
})

addExport(tree, `${targetModule.project.sourceRoot}/index.ts`, `./lib/${app}-${name}.service`)

if (adminCrud) {
addExport(tree, `${targetModule.project.sourceRoot}/index.ts`, `./lib/${app}-${name}-admin.service`)
apiUpdateDataAccessService(tree, targetModule.adminServicePath, {
importClass: coreModule.serviceClassName,
importPackage: coreModule.importPath,
importProperty: 'core',
targetClass: targetModule.adminServiceClassName,
})

apiAddCrudServiceMethods(tree, targetModule.adminServicePath, {
modelName: modelName ?? '',
pluralModelName: pluralModelName ?? '',
targetClass: targetModule.adminServiceClassName,
label,
})
}
}
Loading

0 comments on commit 07e1ef7

Please sign in to comment.