feat: prisma typed select (#347)

* feat: wip prisma gql select

* feat: stronger api using decorator

* feat: add PrismaSelect everywhere

* fix: remove unused

* fix: remove seed debug
This commit is contained in:
Jérémy M 2023-06-22 11:17:31 +02:00 committed by GitHub
parent eb9be6894e
commit ca283a2196
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1081 additions and 578 deletions

View File

@ -19,7 +19,8 @@
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"prisma:generate-client": "npx prisma generate --generator client",
"prisma:generate-client": "npx prisma generate --generator client && yarn prisma:generate-gql-select",
"prisma:generate-gql-select": "node scripts/generate-model-select-map.js",
"prisma:generate-nest-graphql": "npx prisma generate --generator nestgraphql && eslint \"src/core/@generated/**\" --fix",
"prisma:migrate": "npx prisma migrate deploy",
"prisma:seed": "npx prisma db seed",
@ -39,6 +40,7 @@
"@nestjs/platform-express": "^9.0.0",
"@nestjs/serve-static": "^3.0.0",
"@nestjs/terminus": "^9.2.2",
"@paljs/plugins": "^5.3.3",
"@prisma/client": "^4.13.0",
"apollo-server-express": "^3.12.0",
"bcrypt": "^5.1.0",

View File

@ -0,0 +1,32 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require('fs');
const path = require('path');
const { getDMMF, getSchemaPath } = require('@prisma/internals');
async function generateTypes() {
const schemaPath = await getSchemaPath();
const dmmf = await getDMMF({
datamodel: fs.readFileSync(schemaPath, 'utf-8'),
});
let content =
'// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n';
content += "import { Prisma } from '@prisma/client';\n\n";
content += 'export type ModelSelectMap = {\n';
for (const model of dmmf.datamodel.models) {
content += ` ${model.name}: Prisma.${model.name}Select;\n`;
}
content += '};\n';
fs.writeFileSync(
path.join(__dirname, '../src/utils/prisma-select/model-select-map.ts'),
content,
);
}
generateTypes().catch((e) => {
console.error(e);
process.exit(1);
});

View File

@ -1,4 +1,4 @@
import { PureAbility, AbilityBuilder, subject } from '@casl/ability';
import { PureAbility, AbilityBuilder } from '@casl/ability';
import { createPrismaAbility, PrismaQuery, Subjects } from '@casl/prisma';
import { Injectable } from '@nestjs/common';
import {

View File

@ -1,10 +1,8 @@
import { Module } from '@nestjs/common';
import { CommentService } from './services/comment.service';
import { CommentResolver } from './resolvers/comment.resolver';
import { CommentRelationsResolver } from './resolvers/comment-relations.resolver';
import { CommentThreadTargetService } from './services/comment-thread-target.service';
import { CommentThreadResolver } from './resolvers/comment-thread.resolver';
import { CommentThreadRelationsResolver } from './resolvers/comment-thread-relations.resolver';
import { CommentThreadService } from './services/comment-thread.service';
@Module({
@ -13,9 +11,7 @@ import { CommentThreadService } from './services/comment-thread.service';
CommentThreadService,
CommentThreadTargetService,
CommentResolver,
CommentRelationsResolver,
CommentThreadResolver,
CommentThreadRelationsResolver,
],
exports: [CommentService, CommentThreadService, CommentThreadTargetService],
})

View File

@ -1,25 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CommentRelationsResolver } from './comment-relations.resolver';
import { CommentService } from '../services/comment.service';
describe('CommentRelationsResolver', () => {
let resolver: CommentRelationsResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CommentRelationsResolver,
{
provide: CommentService,
useValue: {},
},
],
}).compile();
resolver = module.get<CommentRelationsResolver>(CommentRelationsResolver);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -1,22 +0,0 @@
import * as TypeGraphQL from '@nestjs/graphql';
import { User } from 'src/core/@generated/user/user.model';
import { Comment } from 'src/core/@generated/comment/comment.model';
import { CommentService } from '../services/comment.service';
@TypeGraphQL.Resolver(() => Comment)
export class CommentRelationsResolver {
constructor(private readonly commentService: CommentService) {}
@TypeGraphQL.ResolveField(() => User, {
nullable: true,
})
async author(@TypeGraphQL.Parent() comment: Comment): Promise<User | null> {
return await this.commentService
.findFirst({
where: {
id: comment.id,
},
})
.author({});
}
}

View File

@ -1,32 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CommentThreadRelationsResolver } from './comment-thread-relations.resolver';
import { CommentService } from '../services/comment.service';
import { CommentThreadTargetService } from '../services/comment-thread-target.service';
describe('CommentThreadRelationsResolver', () => {
let resolver: CommentThreadRelationsResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CommentThreadRelationsResolver,
{
provide: CommentService,
useValue: {},
},
{
provide: CommentThreadTargetService,
useValue: {},
},
],
}).compile();
resolver = module.get<CommentThreadRelationsResolver>(
CommentThreadRelationsResolver,
);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -1,48 +0,0 @@
import * as TypeGraphQL from '@nestjs/graphql';
import { CommentThreadTarget } from 'src/core/@generated/comment-thread-target/comment-thread-target.model';
import { CommentThread } from 'src/core/@generated/comment-thread/comment-thread.model';
import { Comment } from 'src/core/@generated/comment/comment.model';
import { CommentService } from '../services/comment.service';
import { CommentThreadTargetService } from '../services/comment-thread-target.service';
@TypeGraphQL.Resolver(() => CommentThread)
export class CommentThreadRelationsResolver {
constructor(
private readonly commentService: CommentService,
private readonly commentThreadTargetService: CommentThreadTargetService,
) {}
@TypeGraphQL.ResolveField(() => [Comment], {
nullable: false,
})
async comments(
@TypeGraphQL.Root() commentThread: CommentThread,
): Promise<Comment[]> {
return this.commentService.findMany({
where: {
commentThreadId: commentThread.id,
},
orderBy: {
// TODO: find a way to pass it in the query
createdAt: 'desc',
},
});
}
@TypeGraphQL.ResolveField(() => [CommentThreadTarget], {
nullable: true,
})
async commentThreadTargets(
@TypeGraphQL.Root() commentThread: CommentThread,
): Promise<CommentThreadTarget[]> {
return this.commentThreadTargetService.findMany({
where: {
commentThreadId: commentThread.id,
},
orderBy: {
// TODO: find a way to pass it in the query
createdAt: 'desc',
},
});
}
}

View File

@ -11,6 +11,10 @@ import { CommentThreadService } from '../services/comment-thread.service';
import { prepareFindManyArgs } from 'src/utils/prepare-find-many';
import { UpdateOneCommentThreadArgs } from 'src/core/@generated/comment-thread/update-one-comment-thread.args';
import { Prisma } from '@prisma/client';
import {
PrismaSelector,
PrismaSelect,
} from 'src/decorators/prisma-select.decorator';
@UseGuards(JwtAuthGuard)
@Resolver(() => CommentThread)
@ -24,7 +28,9 @@ export class CommentThreadResolver {
async createOneCommentThread(
@Args() args: CreateOneCommentThreadArgs,
@AuthWorkspace() workspace: Workspace,
): Promise<CommentThread> {
@PrismaSelector({ modelName: 'CommentThread' })
prismaSelect: PrismaSelect<'CommentThread'>,
): Promise<Partial<CommentThread>> {
const newCommentData = args.data.comments?.createMany?.data
? args.data.comments?.createMany?.data?.map((comment) => ({
...comment,
@ -38,6 +44,7 @@ export class CommentThreadResolver {
...{ comments: { createMany: { data: newCommentData } } },
...{ workspace: { connect: { id: workspace.id } } },
},
select: prismaSelect.value,
});
return createdCommentThread;
@ -48,10 +55,13 @@ export class CommentThreadResolver {
})
async updateOneCommentThread(
@Args() args: UpdateOneCommentThreadArgs,
): Promise<CommentThread> {
const updatedCommentThread = await this.commentThreadService.update(
args satisfies UpdateOneCommentThreadArgs as Prisma.CommentThreadUpdateArgs,
);
@PrismaSelector({ modelName: 'CommentThread' })
prismaSelect: PrismaSelect<'CommentThread'>,
): Promise<Partial<CommentThread>> {
const updatedCommentThread = await this.commentThreadService.update({
...args,
select: prismaSelect.value,
} as Prisma.CommentThreadUpdateArgs);
return updatedCommentThread;
}
@ -60,13 +70,18 @@ export class CommentThreadResolver {
async findManyCommentThreads(
@Args() args: FindManyCommentThreadArgs,
@AuthWorkspace() workspace: Workspace,
) {
@PrismaSelector({ modelName: 'CommentThread' })
prismaSelect: PrismaSelect<'CommentThread'>,
): Promise<Partial<CommentThread>[]> {
const preparedArgs = prepareFindManyArgs<FindManyCommentThreadArgs>(
args,
workspace,
);
const result = await this.commentThreadService.findMany(preparedArgs);
const result = await this.commentThreadService.findMany({
...preparedArgs,
select: prismaSelect.value,
});
return result;
}

View File

@ -8,6 +8,10 @@ import { Comment } from '../../../core/@generated/comment/comment.model';
import { CreateOneCommentGuard } from '../../../guards/create-one-comment.guard';
import { Prisma } from '@prisma/client';
import { CommentService } from '../services/comment.service';
import {
PrismaSelector,
PrismaSelect,
} from 'src/decorators/prisma-select.decorator';
@UseGuards(JwtAuthGuard)
@Resolver(() => Comment)
@ -21,12 +25,15 @@ export class CommentResolver {
async createOneComment(
@Args() args: CreateOneCommentArgs,
@AuthWorkspace() workspace: Workspace,
): Promise<Comment> {
@PrismaSelector({ modelName: 'Comment' })
prismaSelect: PrismaSelect<'Comment'>,
): Promise<Partial<Comment>> {
return this.commentService.create({
data: {
...args.data,
...{ workspace: { connect: { id: workspace.id } } },
},
} satisfies CreateOneCommentArgs as Prisma.CommentCreateArgs);
select: prismaSelect.value,
} as Prisma.CommentCreateArgs);
}
}

View File

@ -2,40 +2,28 @@ import * as TypeGraphQL from '@nestjs/graphql';
import { CommentThread } from 'src/core/@generated/comment-thread/comment-thread.model';
import { Comment } from 'src/core/@generated/comment/comment.model';
import { Company } from 'src/core/@generated/company/company.model';
import { User } from 'src/core/@generated/user/user.model';
import { CompanyService } from './company.service';
import { CommentThreadService } from '../comment/services/comment-thread.service';
import { CommentService } from '../comment/services/comment.service';
import {
PrismaSelect,
PrismaSelector,
} from 'src/decorators/prisma-select.decorator';
@TypeGraphQL.Resolver(() => Company)
export class CompanyRelationsResolver {
constructor(
private readonly companyService: CompanyService,
private readonly commentThreadService: CommentThreadService,
private readonly commentService: CommentService,
) {}
@TypeGraphQL.ResolveField(() => User, {
nullable: true,
})
async accountOwner(
@TypeGraphQL.Parent() company: Company,
): Promise<User | null> {
return this.companyService
.findUniqueOrThrow({
where: {
id: company.id,
},
})
.accountOwner({});
}
@TypeGraphQL.ResolveField(() => [CommentThread], {
nullable: false,
})
async commentThreads(
@TypeGraphQL.Root() company: Company,
): Promise<CommentThread[]> {
@PrismaSelector({ modelName: 'CommentThread' })
prismaSelect: PrismaSelect<'CommentThread'>,
): Promise<Partial<CommentThread>[]> {
return this.commentThreadService.findMany({
where: {
commentThreadTargets: {
@ -45,13 +33,18 @@ export class CompanyRelationsResolver {
},
},
},
select: prismaSelect.value,
});
}
@TypeGraphQL.ResolveField(() => [Comment], {
nullable: false,
})
async comments(@TypeGraphQL.Root() company: Company): Promise<Comment[]> {
async comments(
@TypeGraphQL.Root() company: Company,
@PrismaSelector({ modelName: 'Comment' })
prismaSelect: PrismaSelect<'Comment'>,
): Promise<Partial<Comment>[]> {
return this.commentService.findMany({
where: {
commentThread: {
@ -63,6 +56,7 @@ export class CompanyRelationsResolver {
},
},
},
select: prismaSelect.value,
});
}

View File

@ -15,6 +15,10 @@ import { DeleteManyGuard } from '../../guards/delete-many.guard';
import { CreateOneGuard } from '../../guards/create-one.guard';
import { CompanyService } from './company.service';
import { prepareFindManyArgs } from 'src/utils/prepare-find-many';
import {
PrismaSelect,
PrismaSelector,
} from 'src/decorators/prisma-select.decorator';
@UseGuards(JwtAuthGuard)
@Resolver(() => Company)
@ -25,12 +29,17 @@ export class CompanyResolver {
async findManyCompany(
@Args() args: FindManyCompanyArgs,
@AuthWorkspace() workspace: Workspace,
) {
@PrismaSelector({ modelName: 'Company' })
prismaSelect: PrismaSelect<'Company'>,
): Promise<Partial<Company>[]> {
const preparedArgs = prepareFindManyArgs<FindManyCompanyArgs>(
args,
workspace,
);
return this.companyService.findMany(preparedArgs);
return this.companyService.findMany({
...preparedArgs,
select: prismaSelect.value,
});
}
@UseGuards(UpdateOneGuard)
@ -39,14 +48,17 @@ export class CompanyResolver {
})
async updateOneCompany(
@Args() args: UpdateOneCompanyArgs,
): Promise<Company | null> {
@PrismaSelector({ modelName: 'Company' })
prismaSelect: PrismaSelect<'Company'>,
): Promise<Partial<Company> | null> {
if (!args.data.accountOwner?.connect?.id) {
args.data.accountOwner = { disconnect: true };
}
return this.companyService.update({
...args,
} satisfies UpdateOneCompanyArgs as Prisma.CompanyUpdateArgs);
select: prismaSelect.value,
} as Prisma.CompanyUpdateArgs);
}
@UseGuards(DeleteManyGuard)
@ -68,12 +80,15 @@ export class CompanyResolver {
async createOneCompany(
@Args() args: CreateOneCompanyArgs,
@AuthWorkspace() workspace: Workspace,
): Promise<Company> {
@PrismaSelector({ modelName: 'Company' })
prismaSelect: PrismaSelect<'Company'>,
): Promise<Partial<Company>> {
return this.companyService.create({
data: {
...args.data,
...{ workspace: { connect: { id: workspace.id } } },
},
} satisfies CreateOneCompanyArgs as Prisma.CompanyCreateArgs);
select: prismaSelect.value,
} as Prisma.CompanyCreateArgs);
}
}

View File

@ -1,39 +1,29 @@
import * as TypeGraphQL from '@nestjs/graphql';
import { CommentThread } from 'src/core/@generated/comment-thread/comment-thread.model';
import { Comment } from 'src/core/@generated/comment/comment.model';
import { Company } from 'src/core/@generated/company/company.model';
import { Person } from 'src/core/@generated/person/person.model';
import { PersonService } from './person.service';
import { CommentThreadService } from '../comment/services/comment-thread.service';
import { CommentService } from '../comment/services/comment.service';
import {
PrismaSelect,
PrismaSelector,
} from 'src/decorators/prisma-select.decorator';
@TypeGraphQL.Resolver(() => Person)
export class PersonRelationsResolver {
constructor(
private readonly personService: PersonService,
private readonly commentThreadService: CommentThreadService,
private readonly commentService: CommentService,
) {}
@TypeGraphQL.ResolveField(() => Company, {
nullable: true,
})
async company(@TypeGraphQL.Parent() person: Person): Promise<Company | null> {
return await this.personService
.findUniqueOrThrow({
where: {
id: person.id,
},
})
.company({});
}
@TypeGraphQL.ResolveField(() => [CommentThread], {
nullable: false,
})
async commentThreads(
@TypeGraphQL.Root() person: Person,
): Promise<CommentThread[]> {
@PrismaSelector({ modelName: 'CommentThread' })
prismaSelect: PrismaSelect<'CommentThread'>,
): Promise<Partial<CommentThread>[]> {
return await this.commentThreadService.findMany({
where: {
commentThreadTargets: {
@ -43,13 +33,18 @@ export class PersonRelationsResolver {
},
},
},
select: prismaSelect.value,
});
}
@TypeGraphQL.ResolveField(() => [Comment], {
nullable: false,
})
async comments(@TypeGraphQL.Root() person: Person): Promise<Comment[]> {
async comments(
@TypeGraphQL.Root() person: Person,
@PrismaSelector({ modelName: 'Comment' })
prismaSelect: PrismaSelect<'Comment'>,
): Promise<Partial<Comment>[]> {
return this.commentService.findMany({
where: {
commentThread: {
@ -61,6 +56,7 @@ export class PersonRelationsResolver {
},
},
},
select: prismaSelect.value,
});
}

View File

@ -15,6 +15,10 @@ import { DeleteManyGuard } from '../../guards/delete-many.guard';
import { CreateOneGuard } from '../../guards/create-one.guard';
import { PersonService } from './person.service';
import { prepareFindManyArgs } from 'src/utils/prepare-find-many';
import {
PrismaSelect,
PrismaSelector,
} from 'src/decorators/prisma-select.decorator';
@UseGuards(JwtAuthGuard)
@Resolver(() => Person)
@ -27,13 +31,17 @@ export class PersonResolver {
async findManyPerson(
@Args() args: FindManyPersonArgs,
@AuthWorkspace() workspace: Workspace,
): Promise<Person[]> {
@PrismaSelector({ modelName: 'Person' })
prismaSelect: PrismaSelect<'Person'>,
): Promise<Partial<Person>[]> {
const preparedArgs = prepareFindManyArgs<FindManyPersonArgs>(
args,
workspace,
);
return this.personService.findMany({
...preparedArgs,
select: prismaSelect.value,
});
}
@ -43,14 +51,17 @@ export class PersonResolver {
})
async updateOnePerson(
@Args() args: UpdateOnePersonArgs,
): Promise<Person | null> {
@PrismaSelector({ modelName: 'Person' })
prismaSelect: PrismaSelect<'Person'>,
): Promise<Partial<Person> | null> {
if (!args.data.company?.connect?.id) {
args.data.company = { disconnect: true };
}
return this.personService.update({
...args,
} satisfies UpdateOnePersonArgs as Prisma.PersonUpdateArgs);
select: prismaSelect.value,
} as Prisma.PersonUpdateArgs);
}
@UseGuards(DeleteManyGuard)
@ -72,12 +83,15 @@ export class PersonResolver {
async createOnePerson(
@Args() args: CreateOnePersonArgs,
@AuthWorkspace() workspace: Workspace,
): Promise<Person> {
@PrismaSelector({ modelName: 'Person' })
prismaSelect: PrismaSelect<'Person'>,
): Promise<Partial<Person>> {
return this.personService.create({
data: {
...args.data,
...{ workspace: { connect: { id: workspace.id } } },
},
} satisfies CreateOnePersonArgs as Prisma.PersonCreateArgs);
select: prismaSelect.value,
} as Prisma.PersonCreateArgs);
}
}

View File

@ -1,11 +1,8 @@
import { Module } from '@nestjs/common';
import { PipelineService } from './services/pipeline.service';
import { PipelineResolver } from './resolvers/pipeline.resolver';
import { PipelineRelationsResolver } from './resolvers/pipeline-relations.resolver';
import { PipelineStageResolver } from './resolvers/pipeline-stage.resolver';
import { PipelineStageRelationsResolver } from './resolvers/pipeline-stage-relations.resolver';
import { PipelineProgressResolver } from './resolvers/pipeline-progress.resolver';
import { PipelineProgressRelationsResolver } from './resolvers/pipeline-progress-relations.resolver';
import { PipelineStageService } from './services/pipeline-stage.service';
import { PipelineProgressService } from './services/pipeline-progress.service';
@ -16,11 +13,8 @@ import { PipelineProgressService } from './services/pipeline-progress.service';
PipelineStageService,
PipelineProgressService,
PipelineResolver,
PipelineRelationsResolver,
PipelineStageResolver,
PipelineStageRelationsResolver,
PipelineProgressResolver,
PipelineProgressRelationsResolver,
],
exports: [PipelineService, PipelineStageService, PipelineProgressService],
})

View File

@ -1,32 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PipelineProgressRelationsResolver } from './pipeline-progress-relations.resolver';
import { PipelineStageService } from '../services/pipeline-stage.service';
import { PipelineService } from '../services/pipeline.service';
describe('PipelineProgressRelationsResolver', () => {
let resolver: PipelineProgressRelationsResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PipelineProgressRelationsResolver,
{
provide: PipelineStageService,
useValue: {},
},
{
provide: PipelineService,
useValue: {},
},
],
}).compile();
resolver = module.get<PipelineProgressRelationsResolver>(
PipelineProgressRelationsResolver,
);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -1,40 +0,0 @@
import * as TypeGraphQL from '@nestjs/graphql';
import { PipelineProgress } from 'src/core/@generated/pipeline-progress/pipeline-progress.model';
import { PipelineStage } from 'src/core/@generated/pipeline-stage/pipeline-stage.model';
import { Pipeline } from 'src/core/@generated/pipeline/pipeline.model';
import { PipelineStageService } from '../services/pipeline-stage.service';
import { PipelineService } from '../services/pipeline.service';
@TypeGraphQL.Resolver(() => PipelineProgress)
export class PipelineProgressRelationsResolver {
constructor(
private readonly pipelineStageService: PipelineStageService,
private readonly pipelineService: PipelineService,
) {}
@TypeGraphQL.ResolveField(() => PipelineStage, {
nullable: false,
})
async pipelineStage(
@TypeGraphQL.Root() pipelineStage: PipelineProgress,
): Promise<PipelineStage> {
return this.pipelineStageService.findUniqueOrThrow({
where: {
id: pipelineStage.pipelineStageId,
},
});
}
@TypeGraphQL.ResolveField(() => Pipeline, {
nullable: false,
})
async pipeline(
@TypeGraphQL.Root() pipelineStage: PipelineProgress,
): Promise<Pipeline> {
return this.pipelineService.findUniqueOrThrow({
where: {
id: pipelineStage.pipelineId,
},
});
}
}

View File

@ -22,6 +22,10 @@ import {
} from 'src/ability/handlers/pipeline-progress.ability-handler';
import { UserAbility } from 'src/decorators/user-ability.decorator';
import { AppAbility } from 'src/ability/ability.factory';
import {
PrismaSelector,
PrismaSelect,
} from 'src/decorators/prisma-select.decorator';
@UseGuards(JwtAuthGuard)
@Resolver(() => PipelineProgress)
@ -36,13 +40,16 @@ export class PipelineProgressResolver {
async findManyPipelineProgress(
@Args() args: FindManyPipelineProgressArgs,
@UserAbility() ability: AppAbility,
) {
@PrismaSelector({ modelName: 'PipelineProgress' })
prismaSelect: PrismaSelect<'PipelineProgress'>,
): Promise<Partial<PipelineProgress>[]> {
return this.pipelineProgressService.findMany({
...args,
where: {
...args.where,
AND: [accessibleBy(ability).PipelineProgress],
},
select: prismaSelect.value,
});
}
@ -53,10 +60,13 @@ export class PipelineProgressResolver {
@CheckAbilities(UpdatePipelineProgressAbilityHandler)
async updateOnePipelineProgress(
@Args() args: UpdateOnePipelineProgressArgs,
): Promise<PipelineProgress | null> {
@PrismaSelector({ modelName: 'PipelineProgress' })
prismaSelect: PrismaSelect<'PipelineProgress'>,
): Promise<Partial<PipelineProgress> | null> {
return this.pipelineProgressService.update({
...args,
} satisfies UpdateOnePipelineProgressArgs as Prisma.PipelineProgressUpdateArgs);
select: prismaSelect.value,
} as Prisma.PipelineProgressUpdateArgs);
}
@Mutation(() => AffectedRows, {
@ -80,12 +90,15 @@ export class PipelineProgressResolver {
async createOnePipelineProgress(
@Args() args: CreateOnePipelineProgressArgs,
@AuthWorkspace() workspace: Workspace,
): Promise<PipelineProgress> {
@PrismaSelector({ modelName: 'PipelineProgress' })
prismaSelect: PrismaSelect<'PipelineProgress'>,
): Promise<Partial<PipelineProgress>> {
return this.pipelineProgressService.create({
data: {
...args.data,
...{ workspace: { connect: { id: workspace.id } } },
},
} satisfies CreateOnePipelineProgressArgs as Prisma.PipelineProgressCreateArgs);
select: prismaSelect.value,
} as Prisma.PipelineProgressCreateArgs);
}
}

View File

@ -1,25 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PipelineRelationsResolver } from './pipeline-relations.resolver';
import { PipelineStageService } from '../services/pipeline-stage.service';
describe('PipelineRelationsResolver', () => {
let resolver: PipelineRelationsResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PipelineRelationsResolver,
{
provide: PipelineStageService,
useValue: {},
},
],
}).compile();
resolver = module.get<PipelineRelationsResolver>(PipelineRelationsResolver);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -1,24 +0,0 @@
import * as TypeGraphQL from '@nestjs/graphql';
import { PipelineStage } from 'src/core/@generated/pipeline-stage/pipeline-stage.model';
import { Pipeline } from 'src/core/@generated/pipeline/pipeline.model';
import { PipelineStageService } from '../services/pipeline-stage.service';
@TypeGraphQL.Resolver(() => Pipeline)
export class PipelineRelationsResolver {
constructor(private readonly pipelineStageService: PipelineStageService) {}
@TypeGraphQL.ResolveField(() => [PipelineStage], {
nullable: false,
})
async pipelineStages(
@TypeGraphQL.Root() pipeline: Pipeline,
): Promise<PipelineStage[]> {
return this.pipelineStageService.findMany({
where: {
pipelineId: {
equals: pipeline.id,
},
},
});
}
}

View File

@ -1,27 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PipelineStageRelationsResolver } from './pipeline-stage-relations.resolver';
import { PipelineProgressService } from '../services/pipeline-progress.service';
describe('PipelineStageRelationsResolver', () => {
let resolver: PipelineStageRelationsResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PipelineStageRelationsResolver,
{
provide: PipelineProgressService,
useValue: {},
},
],
}).compile();
resolver = module.get<PipelineStageRelationsResolver>(
PipelineStageRelationsResolver,
);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -1,26 +0,0 @@
import * as TypeGraphQL from '@nestjs/graphql';
import { PipelineProgress } from 'src/core/@generated/pipeline-progress/pipeline-progress.model';
import { PipelineStage } from 'src/core/@generated/pipeline-stage/pipeline-stage.model';
import { PipelineProgressService } from '../services/pipeline-progress.service';
@TypeGraphQL.Resolver(() => PipelineStage)
export class PipelineStageRelationsResolver {
constructor(
private readonly pipelineProgressService: PipelineProgressService,
) {}
@TypeGraphQL.ResolveField(() => [PipelineProgress], {
nullable: false,
})
async pipelineProgresses(
@TypeGraphQL.Root() pipelineStage: PipelineStage,
): Promise<PipelineProgress[]> {
return this.pipelineProgressService.findMany({
where: {
pipelineStageId: {
equals: pipelineStage.id,
},
},
});
}
}

View File

@ -10,6 +10,10 @@ import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
import { ReadPipelineStageAbilityHandler } from 'src/ability/handlers/pipeline-stage.ability-handler';
import { UserAbility } from 'src/decorators/user-ability.decorator';
import { AppAbility } from 'src/ability/ability.factory';
import {
PrismaSelector,
PrismaSelect,
} from 'src/decorators/prisma-select.decorator';
@UseGuards(JwtAuthGuard)
@Resolver(() => PipelineStage)
@ -22,13 +26,16 @@ export class PipelineStageResolver {
async findManyPipelineStage(
@Args() args: FindManyPipelineStageArgs,
@UserAbility() ability: AppAbility,
) {
@PrismaSelector({ modelName: 'PipelineStage' })
prismaSelect: PrismaSelect<'PipelineStage'>,
): Promise<Partial<PipelineStage>[]> {
return this.pipelineStageService.findMany({
...args,
where: {
...args.where,
AND: [accessibleBy(ability).PipelineStage],
},
select: prismaSelect.value,
});
}
}

View File

@ -10,6 +10,10 @@ import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
import { ReadPipelineAbilityHandler } from 'src/ability/handlers/pipeline.ability-handler';
import { UserAbility } from 'src/decorators/user-ability.decorator';
import { AppAbility } from 'src/ability/ability.factory';
import {
PrismaSelector,
PrismaSelect,
} from 'src/decorators/prisma-select.decorator';
@UseGuards(JwtAuthGuard)
@Resolver(() => Pipeline)
@ -22,13 +26,16 @@ export class PipelineResolver {
async findManyPipeline(
@Args() args: FindManyPipelineArgs,
@UserAbility() ability: AppAbility,
) {
@PrismaSelector({ modelName: 'Pipeline' })
prismaSelect: PrismaSelect<'Pipeline'>,
): Promise<Partial<Pipeline>[]> {
return this.pipelineService.findMany({
...args,
where: {
...args.where,
AND: [accessibleBy(ability).Pipeline],
},
select: prismaSelect.value,
});
}
}

View File

@ -1,25 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UserRelationsResolver } from './user-relations.resolver';
import { UserService } from './user.service';
describe('UserRelationsResolver', () => {
let resolver: UserRelationsResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserRelationsResolver,
{
provide: UserService,
useValue: {},
},
],
}).compile();
resolver = module.get<UserRelationsResolver>(UserRelationsResolver);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -1,46 +0,0 @@
import * as TypeGraphQL from '@nestjs/graphql';
import type { GraphQLResolveInfo } from 'graphql';
import { WorkspaceMember } from 'src/core/@generated/workspace-member/workspace-member.model';
import { User } from 'src/core/@generated/user/user.model';
import { Company } from 'src/core/@generated/company/company.model';
import { FindManyCompanyArgs } from 'src/core/@generated/company/find-many-company.args';
import { UserService } from './user.service';
@TypeGraphQL.Resolver(() => User)
export class UserRelationsResolver {
constructor(private readonly userService: UserService) {}
@TypeGraphQL.ResolveField(() => WorkspaceMember, {
nullable: true,
})
async workspaceMember(
@TypeGraphQL.Parent() user: User,
): Promise<WorkspaceMember | null> {
return await this.userService
.findFirst({
where: {
id: user.id,
},
})
.workspaceMember({});
}
@TypeGraphQL.ResolveField(() => [Company], {
nullable: false,
})
async companies(
@TypeGraphQL.Parent() user: User,
@TypeGraphQL.Info() info: GraphQLResolveInfo,
@TypeGraphQL.Args() args: FindManyCompanyArgs,
): Promise<Company[]> {
return this.userService
.findUniqueOrThrow({
where: {
id: user.id,
},
})
.companies({
...args,
});
}
}

View File

@ -1,12 +1,11 @@
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserResolver } from './user.resolver';
import { UserRelationsResolver } from './user-relations.resolver';
import { WorkspaceModule } from '../workspace/workspace.module';
@Module({
imports: [WorkspaceModule],
providers: [UserService, UserResolver, UserRelationsResolver],
providers: [UserService, UserResolver],
exports: [UserService],
})
export class UserModule {}

View File

@ -7,6 +7,10 @@ import { User } from 'src/core/@generated/user/user.model';
import { ExceptionFilter } from 'src/filters/exception.filter';
import { UseFilters, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
import {
PrismaSelect,
PrismaSelector,
} from 'src/decorators/prisma-select.decorator';
@UseGuards(JwtAuthGuard)
@Resolver(() => User)
@ -20,7 +24,9 @@ export class UserResolver {
async findManyUser(
@Args() args: FindManyUserArgs,
@AuthWorkspace() workspace: Workspace,
): Promise<User[]> {
@PrismaSelector({ modelName: 'User' })
prismaSelect: PrismaSelect<'User'>,
): Promise<Partial<User>[]> {
return await this.userService.findMany({
...args,
where: {
@ -29,6 +35,7 @@ export class UserResolver {
is: { workspace: { is: { id: { equals: workspace.id } } } },
},
},
select: prismaSelect.value,
});
}
}

View File

@ -1,27 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { WorkspaceMemberRelationsResolver } from './workspace-member-relations.resolver';
import { WorkspaceMemberService } from '../services/workspace-member.service';
describe('WorkspaceMemberRelationsResolver', () => {
let resolver: WorkspaceMemberRelationsResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
WorkspaceMemberRelationsResolver,
{
provide: WorkspaceMemberService,
useValue: {},
},
],
}).compile();
resolver = module.get<WorkspaceMemberRelationsResolver>(
WorkspaceMemberRelationsResolver,
);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -1,42 +0,0 @@
import * as TypeGraphQL from '@nestjs/graphql';
import { User } from 'src/core/@generated/user/user.model';
import { WorkspaceMember } from 'src/core/@generated/workspace-member/workspace-member.model';
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
import { WorkspaceMemberService } from '../services/workspace-member.service';
@TypeGraphQL.Resolver(() => WorkspaceMember)
export class WorkspaceMemberRelationsResolver {
constructor(
private readonly workspaceMemberSercice: WorkspaceMemberService,
) {}
@TypeGraphQL.ResolveField(() => User, {
nullable: false,
})
async user(
@TypeGraphQL.Parent() workspaceMember: WorkspaceMember,
): Promise<User> {
return await this.workspaceMemberSercice
.findUniqueOrThrow({
where: {
id: workspaceMember.id,
},
})
.user({});
}
@TypeGraphQL.ResolveField(() => Workspace, {
nullable: false,
})
async workspace(
@TypeGraphQL.Parent() workspaceMember: WorkspaceMember,
): Promise<Workspace> {
return this.workspaceMemberSercice
.findUniqueOrThrow({
where: {
id: workspaceMember.id,
},
})
.workspace({});
}
}

View File

@ -2,14 +2,12 @@ import { Module } from '@nestjs/common';
import { WorkspaceService } from './services/workspace.service';
import { WorkspaceMemberService } from './services/workspace-member.service';
import { WorkspaceMemberResolver } from './resolvers/workspace-member.resolver';
import { WorkspaceMemberRelationsResolver } from './resolvers/workspace-member-relations.resolver';
@Module({
providers: [
WorkspaceService,
WorkspaceMemberService,
WorkspaceMemberResolver,
WorkspaceMemberRelationsResolver,
],
exports: [WorkspaceService, WorkspaceMemberService],
})

View File

@ -0,0 +1,26 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import {
PrismaSelect,
ModelSelectMap,
DefaultFieldsMap,
} from 'src/utils/prisma-select';
export { PrismaSelect };
export const PrismaSelector = createParamDecorator(
(
data: {
modelName: keyof ModelSelectMap;
defaultFields?: DefaultFieldsMap;
},
ctx: ExecutionContext,
): PrismaSelect<keyof ModelSelectMap> => {
const gqlCtx = GqlExecutionContext.create(ctx);
const info = gqlCtx.getInfo();
return new PrismaSelect(data.modelName, info, {
defaultFields: data.defaultFields,
});
},
);

View File

@ -0,0 +1,56 @@
import { PrismaSelect as PalJSPrismaSelect } from '@paljs/plugins';
import { DMMF } from '@prisma/client/runtime';
import { GraphQLResolveInfo } from 'graphql';
import { ModelSelectMap } from './model-select-map';
export type DefaultFieldsMap = {
readonly [K in keyof ModelSelectMap]?:
| ModelSelectMap[K]
| ((select: any) => ModelSelectMap[K]);
};
export { ModelSelectMap };
export class PrismaSelect<
K extends keyof ModelSelectMap,
> extends PalJSPrismaSelect {
private modelName: K;
constructor(
modelName: K,
info: GraphQLResolveInfo,
options?: {
readonly defaultFields?: DefaultFieldsMap;
readonly dmmf?: readonly Pick<DMMF.Document, 'datamodel'>[];
},
) {
super(info, options as any);
this.modelName = modelName;
}
get value(): ModelSelectMap[K] {
return super.value;
}
valueOf(field: string, mergeObject?: any): ModelSelectMap[K];
valueOf<SubKey extends keyof ModelSelectMap>(
field: string,
filterBy: SubKey,
mergeObject?: any,
): ModelSelectMap[SubKey];
valueOf(
field: string,
filterByOrMergeObject?: keyof ModelSelectMap | any,
mergeObject?: any,
) {
if (typeof filterByOrMergeObject === 'string') {
return super.valueOf(field, filterByOrMergeObject, mergeObject);
} else {
return super.valueOf(field, this.modelName, filterByOrMergeObject);
}
}
valueWithFilter(modelName: K): ModelSelectMap[K] {
return super.valueWithFilter(modelName).select;
}
}

View File

@ -0,0 +1,17 @@
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
import { Prisma } from '@prisma/client';
export type ModelSelectMap = {
User: Prisma.UserSelect;
Workspace: Prisma.WorkspaceSelect;
WorkspaceMember: Prisma.WorkspaceMemberSelect;
Company: Prisma.CompanySelect;
Person: Prisma.PersonSelect;
RefreshToken: Prisma.RefreshTokenSelect;
CommentThread: Prisma.CommentThreadSelect;
Comment: Prisma.CommentSelect;
CommentThreadTarget: Prisma.CommentThreadTargetSelect;
Pipeline: Prisma.PipelineSelect;
PipelineStage: Prisma.PipelineStageSelect;
PipelineProgress: Prisma.PipelineProgressSelect;
};

File diff suppressed because it is too large Load Diff