diff --git a/package-lock.json b/package-lock.json index c4b475c..3a58c5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "epess-web-backend": "file:", "graphql": "^16.9.0", "graphql-scalars": "^1.23.0", "graphql-tools": "^9.0.1", @@ -8812,6 +8813,10 @@ "node": ">=6" } }, + "node_modules/epess-web-backend": { + "resolved": "", + "link": true + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", diff --git a/package.json b/package.json index e31f3e4..c538032 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", - "start:dev": "npm run prisma:generate && nest start --watch", + "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "prisma:generate": "npx prisma generate --schema=./epess-database/prisma/schema.prisma", @@ -50,6 +50,7 @@ "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "epess-web-backend": "file:", "graphql": "^16.9.0", "graphql-scalars": "^1.23.0", "graphql-tools": "^9.0.1", diff --git a/src/app.module.ts b/src/app.module.ts index fe089d7..100be33 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,10 +4,6 @@ import { ClerkModule } from './clerk/clerk.module'; import { RestfulModule } from './restful/restful.module'; @Module({ - imports: [ - GraphqlModule, // GraphQL setup - ClerkModule, // Clerk setup - RestfulModule, - ], + imports: [GraphqlModule, ClerkModule, RestfulModule], }) export class AppModule {} diff --git a/src/center/center.schema.ts b/src/center/center.schema.ts index 7e0494d..d9163a0 100644 --- a/src/center/center.schema.ts +++ b/src/center/center.schema.ts @@ -27,11 +27,11 @@ export class CenterSchema extends PothosSchema { description: t.exposeString('description'), location: t.exposeString('location'), individual: t.exposeBoolean('individual'), - createdAt: t.expose('createdAt', { type: 'Date' }), - updatedAt: t.expose('updatedAt', { type: 'Date' }), + createdAt: t.expose('createdAt', { type: 'DateTime' }), + updatedAt: t.expose('updatedAt', { type: 'DateTime' }), services: t.relation('services'), centerOwner: t.relation('centerOwner'), - chatRoom: t.relation('chatRoom'), + // chatRoom: t.relation('chatRoom'), centerStaff: t.relation('CenterStaff'), resume: t.relation('Resume'), }), diff --git a/src/graphql/graphql.builder.ts b/src/graphql/graphql.builder.ts index a0c9744..8491291 100644 --- a/src/graphql/graphql.builder.ts +++ b/src/graphql/graphql.builder.ts @@ -1,14 +1,20 @@ import SchemaBuilder from '@pothos/core'; -import PrismaPlugin, { PothosPrismaDatamodel } from '@pothos/plugin-prisma'; -import { PrismaClient } from '@prisma/client'; +import PrismaPlugin, { + PothosPrismaDatamodel, + PrismaClient, +} from '@pothos/plugin-prisma'; import PrismaUtils from '@pothos/plugin-prisma-utils'; -import { Request } from 'express'; +import { Request, Response } from 'express'; import type PrismaTypes from '../types/pothos.generated'; import { getDatamodel } from '../types/pothos.generated'; -import { DateTimeResolver } from 'graphql-scalars'; +import { DateTimeResolver, JSONResolver } from 'graphql-scalars'; +import { Injectable } from '@nestjs/common'; +import { PrismaCrudGenerator } from './graphql.generator'; export interface SchemaContext { req: Request; + res: Response; + generator: PrismaCrudGenerator; } export interface SchemaBuilderOption { @@ -16,30 +22,38 @@ export interface SchemaBuilderOption { PrismaTypes: PrismaTypes; DataModel: PothosPrismaDatamodel; Scalars: { - Date: { + DateTime: { Input: Date; Output: Date; }; + Json: { + Input: JSON; + Output: JSON; + }; }; } -export function createBuilder(client: PrismaClient) { - const builder = new SchemaBuilder({ - plugins: [PrismaPlugin, PrismaUtils], - prisma: { - client, - exposeDescriptions: true, - filterConnectionTotalCount: true, - onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn', - dmmf: getDatamodel(), - }, - }); - builder.queryType({}); - // builder.mutationType({}); - // builder.subscriptionType({}); +@Injectable() +export class Builder extends SchemaBuilder { + public generator: PrismaCrudGenerator; - builder.addScalarType('Date', DateTimeResolver, {}); - return builder; + constructor(private readonly prisma: PrismaClient) { + super({ + plugins: [PrismaPlugin, PrismaUtils], + prisma: { + client: prisma, + exposeDescriptions: true, + filterConnectionTotalCount: true, + onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn', + dmmf: getDatamodel(), + }, + }); + this.generator = new PrismaCrudGenerator(this); + this.addScalarType('DateTime', DateTimeResolver); + this.addScalarType('Json', JSONResolver); + this.queryType({}); + // this.mutationType({}); + } } - -export type Builder = ReturnType; +export type BuilderTypes = + PothosSchemaTypes.ExtendDefaultTypes; diff --git a/src/graphql/graphql.generator.ts b/src/graphql/graphql.generator.ts new file mode 100644 index 0000000..8616bcc --- /dev/null +++ b/src/graphql/graphql.generator.ts @@ -0,0 +1,637 @@ +import { Inject, Injectable } from '@nestjs/common'; + +import { + type BaseEnum, + type EnumRef, + InputObjectRef, + type InputType, + type InputTypeParam, + type SchemaTypes, +} from '@pothos/core'; +import { type PrismaModelTypes, getModel } from '@pothos/plugin-prisma'; +import type { FilterOps } from '@pothos/plugin-prisma-utils'; +import * as Prisma from '@prisma/client'; +import { SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'; + +// +const filterOps = ['equals', 'in', 'notIn', 'not', 'is', 'isNot'] as const; +const sortableFilterProps = ['lt', 'lte', 'gt', 'gte'] as const; +const stringFilterOps = [ + ...filterOps, + 'contains', + 'startsWith', + 'endsWith', +] as const; +const sortableTypes = ['String', 'Int', 'Float', 'DateTime', 'BigInt'] as const; +const listOps = ['every', 'some', 'none'] as const; +const scalarListOps = [ + 'has', + 'hasSome', + 'hasEvery', + 'isEmpty', + 'equals', +] as const; +const JsonFilterOps = ['equals', 'in', 'notIn', 'not', 'is', 'isNot'] as const; +const EnumFilterOps = ['equals', 'not'] as const; + +@Injectable() +export class PrismaCrudGenerator { + private refCache = new Map< + InputType | string, + Map> + >(); + + private enumRefs = new Map>(); + + constructor( + @Inject(SchemaBuilderToken) + private builder: PothosSchemaTypes.SchemaBuilder, + ) {} + + findManyArgs( + modelName: Name, + ) { + return this.builder.args((t) => ({ + filter: t.field({ + type: this.getWhere(modelName), + required: false, + }), + orderBy: t.field({ + type: this.getOrderBy(modelName), + required: false, + }), + cursor: t.field({ + type: this.getWhereUnique(modelName), + required: false, + }), + take: t.field({ + type: 'Int', + required: false, + }), + skip: t.field({ + type: 'Int', + required: false, + }), + })); + } + + getWhere( + modelName: Name, + without?: string[], + ) { + const withoutName = (without ?? []) + .map((name) => `Without${capitalize(name)}`) + .join(''); + const fullName = `${modelName}${withoutName}Filter`; + + return this.getRef(modelName, fullName, () => { + const model = getModel(modelName, this.builder); + + return this.builder.prismaWhere(modelName, { + name: fullName, + fields: (() => { + const fields: Record> = {}; + const withoutFields = model.fields.filter((field) => + without?.includes(field.name), + ); + + model.fields + .filter( + (field) => + !withoutFields.some( + (f) => + f.name === field.name || + f.relationFromFields?.includes(field.name), + ), + ) + .forEach((field) => { + // biome-ignore lint/suspicious/noImplicitAnyLet: + let type; + switch (field.kind) { + case 'scalar': + type = field.isList + ? this.getScalarListFilter( + this.mapScalarType(field.type) as InputType, + ) + : this.getFilter( + this.mapScalarType(field.type) as InputType, + ); + break; + case 'enum': + type = field.isList + ? this.getScalarListFilter(this.getEnum(field.type)) + : this.getFilter(this.getEnum(field.type)); + break; + case 'object': + type = field.isList + ? this.getListFilter(this.getWhere(field.type as Name)) + : this.getWhere(field.type as Name); + break; + case 'unsupported': + break; + default: + throw new Error(`Unknown field kind ${field.kind}`); + } + + if (!type) { + return; + } + fields[field.name] = type; + }); + + return fields; + }) as never, + }) as InputObjectRef< + Types, + (PrismaModelTypes & Types['PrismaTypes'][Name])['Where'] + >; + }); + } + getWhereUnique( + modelName: Name, + ) { + const name = `${modelName}UniqueFilter`; + + return this.getRef(modelName, name, () => { + const model = getModel(modelName, this.builder); + return this.builder.prismaWhereUnique(modelName, { + name, + fields: (() => { + const fields: Record> = {}; + + model.fields + .filter( + (field) => + field.isUnique || + field.isId || + model.uniqueIndexes.some((index) => + index.fields.includes(field.name), + ) || + model.primaryKey?.fields.includes(field.name), + ) + .forEach((field) => { + // biome-ignore lint/suspicious/noImplicitAnyLet: + let type; + switch (field.kind) { + case 'scalar': + type = this.mapScalarType(field.type) as InputType; + break; + case 'enum': + type = this.getEnum(field.type); + break; + case 'object': + case 'unsupported': + break; + default: + throw new Error(`Unknown field kind ${field.kind}`); + } + + if (!type) { + return; + } + + fields[field.name] = type; + }); + + return fields; + }) as never, + }) as InputObjectRef< + Types, + (PrismaModelTypes & Types['PrismaTypes'][Name])['WhereUnique'] + >; + }); + } + getOrderBy( + modelName: Name, + ) { + const name = `${modelName}OrderBy`; + return this.getRef(modelName, name, () => { + const model = getModel(modelName, this.builder); + + return this.builder.prismaOrderBy(modelName, { + name, + fields: () => { + const fields: Record | boolean> = {}; + + model.fields.forEach((field) => { + // biome-ignore lint/suspicious/noImplicitAnyLet: + let type; + switch (field.kind) { + case 'scalar': + case 'enum': + type = true; + break; + case 'object': + type = this.getOrderBy(field.type as Name); + break; + case 'unsupported': + break; + default: + throw new Error(`Unknown field kind ${field.kind}`); + } + + if (type) { + fields[field.name] = type; + } + }); + + return fields as {}; + }, + }); + }); + } + + getCreateInput( + modelName: Name, + without?: string[], + ) { + const withoutName = (without ?? []) + .map((name) => `Without${capitalize(name)}`) + .join(''); + const fullName = `${modelName}Create${withoutName}Input`; + + return this.getRef(modelName, fullName, () => { + const model = getModel(modelName, this.builder); + return this.builder.prismaCreate(modelName, { + name: fullName, + fields: (() => { + const fields: Record> = {}; + const withoutFields = model.fields.filter((field) => + without?.includes(field.name), + ); + const relationIds = model.fields.flatMap( + (field) => field.relationFromFields ?? [], + ); + + model.fields + .filter( + (field) => + !withoutFields.some( + (f) => + f.name === field.name || + f.relationFromFields?.includes(field.name), + ) && !relationIds.includes(field.name), + ) + .forEach((field) => { + // biome-ignore lint/suspicious/noImplicitAnyLet: + let type; + switch (field.kind) { + case 'scalar': + type = this.mapScalarType(field.type) as InputType; + break; + case 'enum': + type = this.getEnum(field.type); + break; + case 'object': + type = this.getCreateRelationInput(modelName, field.name); + break; + case 'unsupported': + break; + default: + throw new Error(`Unknown field kind ${field.kind}`); + } + + if (type) { + fields[field.name] = type; + } + }); + + return fields; + }) as never, + }) as InputObjectRef< + Types, + (PrismaModelTypes & Types['PrismaTypes'][Name])['Create'] + >; + }); + } + + getCreateRelationInput< + Name extends string & keyof Types['PrismaTypes'], + Relation extends Model['RelationName'], + Model extends + PrismaModelTypes = Types['PrismaTypes'][Name] extends PrismaModelTypes + ? Types['PrismaTypes'][Name] + : never, + >(modelName: Name, relation: Relation) { + return this.getRef( + `${modelName}${capitalize(relation)}`, + 'CreateRelationInput', + () => { + const model = getModel(modelName, this.builder); + return this.builder.prismaCreateRelation(modelName, relation, { + fields: () => { + const relationField = model.fields.find( + (field) => field.name === relation, + )!; + const relatedModel = getModel(relationField.type, this.builder); + const relatedFieldName = relatedModel.fields.find( + (field) => field.relationName === relationField.relationName, + )!; + + return { + create: this.getCreateInput(relationField.type as Name, [ + relatedFieldName.name, + ]), + connect: this.getWhereUnique(relationField.type as Name), + }; + }, + } as never) as InputObjectRef< + Types, + NonNullable + >; + }, + ); + } + + getCreateManyInput( + modelName: Name, + without?: string[], + ) { + const withoutName = (without ?? []) + .map((name) => `Without${capitalize(name)}`) + .join(''); + const fullName = `${modelName}Create${withoutName}Input`; + + return this.getRef(modelName, fullName, () => { + const model = getModel(modelName, this.builder); + + return this.builder.prismaCreateMany(modelName, { + name: fullName, + fields: (() => { + const fields: Record> = {}; + const withoutFields = model.fields.filter((field) => + without?.includes(field.name), + ); + const relationIds = model.fields.flatMap( + (field) => field.relationFromFields ?? [], + ); + + model.fields + .filter( + (field) => + !withoutFields.some( + (f) => + f.name === field.name || + f.relationFromFields?.includes(field.name), + ) && !relationIds.includes(field.name), + ) + .forEach((field) => { + // biome-ignore lint/suspicious/noImplicitAnyLet: + let type; + switch (field.kind) { + case 'scalar': + type = this.mapScalarType(field.type) as InputType; + break; + case 'enum': + type = this.getEnum(field.type); + break; + case 'unsupported': + break; + default: + throw new Error(`Unknown field kind ${field.kind}`); + } + + if (type) { + fields[field.name] = type; + } + }); + + return fields; + }) as never, + }) as InputObjectRef< + Types, + (PrismaModelTypes & Types['PrismaTypes'][Name])['Create'] + >; + }); + } + + getUpdateInput( + modelName: Name, + without?: string[], + ) { + const withoutName = (without ?? []) + .map((name) => `Without${capitalize(name)}`) + .join(''); + const fullName = `${modelName}Update${withoutName}Input`; + + return this.getRef(modelName, fullName, () => { + const model = getModel(modelName, this.builder); + return this.builder.prismaUpdate(modelName, { + name: fullName, + fields: (() => { + const fields: Record> = {}; + const withoutFields = model.fields.filter((field) => + without?.includes(field.name), + ); + const relationIds = model.fields.flatMap( + (field) => field.relationFromFields ?? [], + ); + + model.fields + .filter( + (field) => + !withoutFields.some( + (f) => + f.name === field.name || + f.relationFromFields?.includes(field.name), + ) && !relationIds.includes(field.name), + ) + .forEach((field) => { + // biome-ignore lint/suspicious/noImplicitAnyLet: + let type; + switch (field.kind) { + case 'scalar': + type = this.mapScalarType(field.type) as InputType; + break; + case 'enum': + type = this.getEnum(field.type); + break; + case 'object': + type = this.getUpdateRelationInput(modelName, field.name); + break; + case 'unsupported': + break; + default: + throw new Error(`Unknown field kind ${field.kind}`); + } + + if (type) { + fields[field.name] = type; + } + }); + + return fields; + }) as never, + }) as InputObjectRef< + Types, + (PrismaModelTypes & Types['PrismaTypes'][Name])['Update'] + >; + }); + } + getUpdateRelationInput< + Name extends string & keyof Types['PrismaTypes'], + Relation extends Model['RelationName'], + Model extends + PrismaModelTypes = Types['PrismaTypes'][Name] extends PrismaModelTypes + ? Types['PrismaTypes'][Name] + : never, + >(modelName: Name, relation: Relation) { + return this.getRef( + `${modelName}${capitalize(relation)}`, + 'UpdateRelationInput', + () => { + const model = getModel(modelName, this.builder); + return this.builder.prismaUpdateRelation(modelName, relation, { + fields: () => { + const relationField = model.fields.find( + (field) => field.name === relation, + )!; + const relatedModel = getModel(relationField.type, this.builder); + const relatedFieldName = relatedModel.fields.find( + (field) => field.relationName === relationField.relationName, + )!.name; + + if (relationField.isList) { + return { + create: this.getCreateInput(relationField.type as Name, [ + relatedFieldName, + ]), + createMany: { + skipDuplicates: 'Boolean', + data: this.getCreateInput(relationField.type as Name, [ + relatedFieldName, + ]), + }, + set: this.getWhereUnique(relationField.type as Name), + disconnect: this.getWhereUnique(relationField.type as Name), + delete: this.getWhereUnique(relationField.type as Name), + connect: this.getWhereUnique(relationField.type as Name), + update: { + where: this.getWhereUnique(relationField.type as Name), + data: this.getUpdateInput(relationField.type as Name, [ + relatedFieldName, + ]), + }, + updateMany: { + where: this.getWhere(relationField.type as Name, [ + relatedFieldName, + ]), + data: this.getUpdateInput(relationField.type as Name, [ + relatedFieldName, + ]), + }, + deleteMany: this.getWhere(relationField.type as Name, [ + relatedFieldName, + ]), + }; + } + + return { + create: this.getCreateInput(relationField.type as Name, [ + relatedFieldName, + ]), + update: this.getUpdateInput(relationField.type as Name, [ + relatedFieldName, + ]), + connect: this.getWhereUnique(relationField.type as Name), + disconnect: relationField.isRequired ? undefined : 'Boolean', + delete: relationField.isRequired ? undefined : 'Boolean', + }; + }, + } as never) as InputObjectRef< + Types, + NonNullable + >; + }, + ); + } + + private getFilter(type: InputType) { + return this.getRef(type, `${String(type)}Filter`, () => { + const ops: FilterOps[] = [...filterOps]; + + if (type === 'String') { + ops.push(...stringFilterOps); + } + if (sortableTypes.includes(type as never)) { + ops.push(...sortableFilterProps); + } + + return this.builder.prismaFilter(type, { + ops, + }); + }); + } + + private getScalarListFilter(type: InputType) { + return this.getRef(type, `${String(type)}ListFilter`, () => + this.builder.prismaScalarListFilter(type, { + ops: scalarListOps, + }), + ); + } + + private getListFilter(type: InputType) { + return this.getRef(type, `${String(type)}ListFilter`, () => + this.builder.prismaListFilter(type, { + ops: listOps, + }), + ); + } + + private getEnum(name: string) { + if (!this.enumRefs.has(name)) { + const enumRef = this.builder.enumType( + (Prisma as unknown as Record)[name], + { + name, + }, + ); + + this.enumRefs.set(name, enumRef); + } + + return this.enumRefs.get(name)!; + } + + private mapScalarType(type: string) { + switch (type) { + case 'String': + case 'Boolean': + case 'Int': + case 'Float': + case 'DateTime': + case 'Json': + return type; + default: + return null; + } + } + + private getRef>( + key: InputType | string, + name: string, + create: () => T, + ): T { + if (!this.refCache.has(key)) { + this.refCache.set(key, new Map()); + } + const cache = this.refCache.get(key)!; + + if (cache.has(name)) { + return cache.get(name)! as T; + } + + const ref = new InputObjectRef(name); + + cache.set(name, ref); + + this.builder.configStore.associateParamWithRef(ref, create()); + + return ref as T; + } +} + +function capitalize(str: string) { + return str[0].toUpperCase() + str.slice(1); +} diff --git a/src/graphql/graphql.module.ts b/src/graphql/graphql.module.ts index 0b97427..e247e5f 100644 --- a/src/graphql/graphql.module.ts +++ b/src/graphql/graphql.module.ts @@ -1,15 +1,14 @@ import { ApolloDriverConfig } from '@nestjs/apollo'; -import { MiddlewareConsumer, Module } from '@nestjs/common'; +import { Global, MiddlewareConsumer, Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; import { PothosModule } from '@smatch-corp/nestjs-pothos'; import { PothosApolloDriver } from '@smatch-corp/nestjs-pothos-apollo-driver'; -import { createBuilder } from './graphql.builder'; +import { Builder } from './graphql.builder'; import { PrismaService } from '../prisma/prisma.service'; import { GraphQLValidationMiddleware } from 'src/middlewares/graphql.middleware'; import { PrismaModule } from 'src/prisma/prisma.module'; import { UserModule } from 'src/user/user.module'; import { CenterModule } from 'src/center/center.module'; -import { GraphqlService } from './graphql.service'; import { ServiceModule } from 'src/service/service.module'; import { ChatroomModule } from 'src/chatroom/chatroom.module'; import { CenterStaffModule } from 'src/centerstaff/centerstaff.module'; @@ -17,6 +16,9 @@ import { ResumeModule } from 'src/resume/resume.module'; import { WorkshopModule } from 'src/workshop/workshop.module'; import { WorkshopOrganizationModule } from 'src/workshoporganization/workshoporganization.module'; import { WorkshopSubscriptionModule } from 'src/workshopsubscription/workshopsubscription.module'; +import { PrismaCrudGenerator } from './graphql.generator'; + +@Global() @Module({ imports: [ PrismaModule, @@ -32,14 +34,26 @@ import { WorkshopSubscriptionModule } from 'src/workshopsubscription/workshopsub PothosModule.forRoot({ builder: { inject: [PrismaService], - useFactory: (prisma: PrismaService) => createBuilder(prisma), + useFactory: (prisma: PrismaService) => new Builder(prisma), }, }), GraphQLModule.forRoot({ driver: PothosApolloDriver, }), ], - providers: [GraphqlService], + providers: [ + { + provide: Builder, + useFactory: (prisma: PrismaService) => new Builder(prisma), + inject: [PrismaService], + }, + { + provide: PrismaCrudGenerator, + useFactory: (builder: Builder) => new PrismaCrudGenerator(builder), + inject: [Builder], + }, + ], + exports: [Builder, PrismaCrudGenerator], }) export class GraphqlModule { configure(consumer: MiddlewareConsumer) { diff --git a/src/main.ts b/src/main.ts index 4007938..2992e70 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,8 +2,6 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { cors } from './common/utils/cors.utils'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; -// Import DateTime scalar if necessary -require('./common/utils/datetime.utils'); async function bootstrap() { const app = await NestFactory.create(AppModule); diff --git a/src/middlewares/graphql.middleware.ts b/src/middlewares/graphql.middleware.ts index a231765..d5de9ce 100644 --- a/src/middlewares/graphql.middleware.ts +++ b/src/middlewares/graphql.middleware.ts @@ -4,7 +4,7 @@ import { Request, Response, NextFunction } from 'express'; @Injectable() export class GraphQLValidationMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { - // Only handle POST requests + // handle post request if ( req.method === 'POST' && req.headers['content-type'] === 'application/json' @@ -12,18 +12,14 @@ export class GraphQLValidationMiddleware implements NestMiddleware { const { query, mutation, subscription } = req.body; // If none of these are present, return a custom error response - if (!query && !mutation && !subscription) { - return res.status(400).json({ - errors: [ - { - message: - 'Must provide a valid GraphQL query, mutation, or subscription.', - }, - ], - }); - } - // handle query only contain \n - if (query.trim() === '') { + if ( + !query && + !mutation && + !subscription && + query.trim() === '' && + mutation.trim() === '' && + subscription.trim() === '' + ) { return res.status(400).json({ errors: [ { diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts index a7da05b..acb9109 100644 --- a/src/prisma/prisma.service.ts +++ b/src/prisma/prisma.service.ts @@ -12,7 +12,24 @@ export class PrismaService extends PrismaClient implements OnModuleInit { constructor() { super({ - log: ['query', 'info', 'warn', 'error'], + log: [ + { + emit: 'event', + level: 'query', + }, + { + emit: 'stdout', + level: 'error', + }, + { + emit: 'event', + level: 'info', + }, + { + emit: 'stdout', + level: 'warn', + }, + ], }); } diff --git a/src/resume/resume.schema.ts b/src/resume/resume.schema.ts index 9dac94d..9ce6e28 100644 --- a/src/resume/resume.schema.ts +++ b/src/resume/resume.schema.ts @@ -26,11 +26,11 @@ export class ResumeSchema extends PothosSchema { centerId: t.exposeID('centerId'), status: t.exposeString('status'), createdAt: t.expose('createdAt', { - type: 'Date', + type: 'DateTime', nullable: true, }), updatedAt: t.expose('updatedAt', { - type: 'Date', + type: 'DateTime', nullable: true, }), center: t.relation('center'), @@ -38,13 +38,6 @@ export class ResumeSchema extends PothosSchema { }); } - @PothosRef() - resumeStatus() { - return this.builder.enumType('ResumeStatus', { - values: ['PENDING', 'APPROVED', 'REJECTED'], - }); - } - @Pothos() init() { this.builder.queryField('resumes', (t) => diff --git a/src/service/service.module.ts b/src/service/service.module.ts index 992752a..0f6da83 100644 --- a/src/service/service.module.ts +++ b/src/service/service.module.ts @@ -1,9 +1,10 @@ import { Global, Module } from '@nestjs/common'; import { ServiceSchema } from './service.schema'; +import { PrismaCrudGenerator } from 'src/graphql/graphql.generator'; @Global() @Module({ - providers: [ServiceSchema], + providers: [ServiceSchema, PrismaCrudGenerator], exports: [ServiceSchema], }) export class ServiceModule {} diff --git a/src/service/service.schema.ts b/src/service/service.schema.ts index 96d5010..0fca557 100644 --- a/src/service/service.schema.ts +++ b/src/service/service.schema.ts @@ -5,8 +5,9 @@ import { PothosSchema, SchemaBuilderToken, } from '@smatch-corp/nestjs-pothos'; -import { Builder } from '../graphql/graphql.builder'; +import { Builder, BuilderTypes } from '../graphql/graphql.builder'; import { PrismaService } from 'src/prisma/prisma.service'; +import { PrismaCrudGenerator } from 'src/graphql/graphql.generator'; @Injectable() export class ServiceSchema extends PothosSchema { @@ -27,11 +28,11 @@ export class ServiceSchema extends PothosSchema { price: t.exposeFloat('price'), rating: t.exposeFloat('rating'), createdAt: t.expose('createdAt', { - type: 'Date', + type: 'DateTime', nullable: true, }), updatedAt: t.expose('updatedAt', { - type: 'Date', + type: 'DateTime', nullable: true, }), }), @@ -79,4 +80,26 @@ export class ServiceSchema extends PothosSchema { }), })); } + + // @Pothos() + // initMutation() { + // this.builder.mutationFields((t) => ({ + // createService: t.prismaField({ + // type: this.service(), + // args: { + // input: t.arg({ + // type: this.generator.getCreateInput('Service'), + // required: true, + // }), + // }, + // resolve: async (query, root, args, ctx, info) => { + // const { input } = args; + // const service = await this.prisma.service.create({ + // data: input, + // }); + // return service; + // }, + // }), + // })); + // } } diff --git a/src/types/scalars/common.scalar.ts b/src/types/scalars/common.scalar.ts deleted file mode 100644 index 4e8512e..0000000 --- a/src/types/scalars/common.scalar.ts +++ /dev/null @@ -1 +0,0 @@ -import { DateTimeResolver } from 'graphql-scalars'; diff --git a/src/user/user.module.ts b/src/user/user.module.ts index fe13202..7ee9dc1 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -1,9 +1,30 @@ import { Global, Module } from '@nestjs/common'; import { UserSchema } from './user.schema'; - +import { Builder, BuilderTypes } from '../graphql/graphql.builder'; +import { PrismaCrudGenerator } from 'src/graphql/graphql.generator'; +import { PrismaService } from 'src/prisma/prisma.service'; @Global() @Module({ - providers: [UserSchema], + providers: [ + PrismaService, + UserSchema, + { + provide: Builder, + useFactory: (prisma: PrismaService) => new Builder(prisma), + inject: [PrismaService], + }, + { + provide: PrismaCrudGenerator, + inject: [ + { + token: Builder, + optional: false, + }, + ], + useFactory: (builder: Builder) => + new PrismaCrudGenerator(builder), + }, + ], exports: [UserSchema], }) export class UserModule {} diff --git a/src/user/user.schema.ts b/src/user/user.schema.ts index eeb6535..fbf8ebd 100644 --- a/src/user/user.schema.ts +++ b/src/user/user.schema.ts @@ -27,9 +27,9 @@ export class UserSchema extends PothosSchema { email: t.exposeString('email'), phoneNumber: t.exposeString('phoneNumber'), oauthToken: t.exposeString('oauthToken', { nullable: true }), - role: t.exposeString('role'), - createdAt: t.expose('createdAt', { type: 'Date' }), - updatedAt: t.expose('updatedAt', { type: 'Date' }), + // role: t.exposeString('role'), + createdAt: t.expose('createdAt', { type: 'DateTime' }), + updatedAt: t.expose('updatedAt', { type: 'DateTime' }), center: t.relation('center'), }), }); @@ -41,25 +41,12 @@ export class UserSchema extends PothosSchema { this.builder.queryFields((t) => ({ users: t.prismaField({ type: [this.user()], - args: { - skip: t.arg.int(), - take: t.arg.int(), - cursor: t.arg.string(), - where: t.arg.string(), - orderBy: t.arg.string(), - }, + args: this.builder.generator.findManyArgs('User'), resolve: async (query, root, args, ctx, info) => { - const { skip, take, cursor, where, orderBy } = args; - - const users = await this.prisma.user.findMany({ - skip: skip || 0, - take: take || 10, - cursor: cursor ? { id: cursor } : undefined, - where: where ? JSON.parse(where) : undefined, - orderBy: orderBy ? JSON.parse(orderBy) : undefined, + return await this.prisma.user.findMany({ + ...query, + take: args.take ?? 10, }); - - return users; }, }), diff --git a/src/workshop/workshop.schema.ts b/src/workshop/workshop.schema.ts index 1bc23ba..a1ce9d9 100644 --- a/src/workshop/workshop.schema.ts +++ b/src/workshop/workshop.schema.ts @@ -27,13 +27,13 @@ export class WorkshopSchema extends PothosSchema { staffId: t.exposeID('staffId'), serviceId: t.exposeID('serviceId'), date: t.expose('date', { - type: 'Date', + type: 'DateTime', }), createdAt: t.expose('createdAt', { - type: 'Date', + type: 'DateTime', }), updatedAt: t.expose('updatedAt', { - type: 'Date', + type: 'DateTime', }), service: t.relation('service'), workshopOrganization: t.relation('workshopOrganization'),