Fixing file name casing

This commit is contained in:
2024-10-12 23:15:06 +07:00
parent 5c40a2fd53
commit 0e3aa751ce
29 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
import SchemaBuilder from '@pothos/core';
import PrismaPlugin, {
PothosPrismaDatamodel,
PrismaClient,
} from '@pothos/plugin-prisma';
import PrismaUtils from '@pothos/plugin-prisma-utils';
import { Request, Response } from 'express';
import type PrismaTypes from '../types/pothos.generated';
import { getDatamodel } from '../types/pothos.generated';
import { DateTimeResolver, JSONObjectResolver } from 'graphql-scalars';
import { Injectable } from '@nestjs/common';
import { PrismaCrudGenerator } from './graphql.generator';
export interface SchemaContext {
req: Request;
res: Response;
generator: PrismaCrudGenerator<BuilderTypes>;
}
export interface SchemaBuilderOption {
Context: SchemaContext;
PrismaTypes: PrismaTypes;
DataModel: PothosPrismaDatamodel;
Scalars: {
DateTime: {
Input: Date;
Output: Date;
};
Json: {
Input: JSON;
Output: JSON;
};
};
}
@Injectable()
export class Builder extends SchemaBuilder<SchemaBuilderOption> {
public generator: PrismaCrudGenerator<BuilderTypes>;
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<BuilderTypes>(this);
this.addScalarType('DateTime', DateTimeResolver);
this.addScalarType('Json', JSONObjectResolver);
this.queryType({});
this.mutationType({});
}
}
export type BuilderTypes =
PothosSchemaTypes.ExtendDefaultTypes<SchemaBuilderOption>;

View File

@@ -0,0 +1,645 @@
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<Types extends SchemaTypes> {
private refCache = new Map<
InputType<Types> | string,
Map<string, InputObjectRef<Types, unknown>>
>();
private enumRefs = new Map<string, EnumRef<Types, unknown>>();
constructor(
@Inject(SchemaBuilderToken)
private builder: PothosSchemaTypes.SchemaBuilder<Types>,
) {}
findManyArgs<Name extends string & keyof Types['PrismaTypes']>(
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,
}),
}));
}
findUniqueArgs<Name extends string & keyof Types['PrismaTypes']>(
modelName: Name,
) {
return this.builder.args((t) => ({
where: t.field({ type: this.getWhereUnique(modelName), required: true }),
}));
}
getWhere<Name extends string & keyof Types['PrismaTypes']>(
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<string, InputType<Types>> = {};
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: <explanation>
let type;
switch (field.kind) {
case 'scalar':
type = field.isList
? this.getScalarListFilter(
this.mapScalarType(field.type) as InputType<Types>,
)
: this.getFilter(
this.mapScalarType(field.type) as InputType<Types>,
);
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<Name extends string & keyof Types['PrismaTypes']>(
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<string, InputType<Types>> = {};
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: <explanation>
let type;
switch (field.kind) {
case 'scalar':
type = this.mapScalarType(field.type) as InputType<Types>;
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<Name extends string & keyof Types['PrismaTypes']>(
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<string, InputType<Types> | boolean> = {};
model.fields.forEach((field) => {
// biome-ignore lint/suspicious/noImplicitAnyLet: <explanation>
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<Name extends string & keyof Types['PrismaTypes']>(
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<string, InputTypeParam<Types>> = {};
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: <explanation>
let type;
switch (field.kind) {
case 'scalar':
type = this.mapScalarType(field.type) as InputType<Types>;
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<Model['Create'][Relation & keyof Model['Update']]>
>;
},
);
}
getCreateManyInput<Name extends string & keyof Types['PrismaTypes']>(
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<string, InputTypeParam<Types>> = {};
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: <explanation>
let type;
switch (field.kind) {
case 'scalar':
type = this.mapScalarType(field.type) as InputType<Types>;
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<Name extends string & keyof Types['PrismaTypes']>(
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<string, InputTypeParam<Types>> = {};
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: <explanation>
let type;
switch (field.kind) {
case 'scalar':
type = this.mapScalarType(field.type) as InputType<Types>;
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<Model['Update'][Relation & keyof Model['Update']]>
>;
},
);
}
private getFilter(type: InputType<Types>) {
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<Types>) {
return this.getRef(type, `${String(type)}ListFilter`, () =>
this.builder.prismaScalarListFilter(type, {
ops: scalarListOps,
}),
);
}
private getListFilter(type: InputType<Types>) {
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<string, BaseEnum>)[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<T extends InputObjectRef<Types, unknown>>(
key: InputType<Types> | 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<Types, unknown>(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);
}

View File

@@ -0,0 +1,85 @@
import { ApolloDriverConfig } from '@nestjs/apollo';
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 { Builder } from './graphql.builder';
import { PrismaService } from '../Prisma/prisma.service';
import { GraphQLValidationMiddleware } from '../middlewares/graphql.middleware';
import { PrismaModule } from '../Prisma/prisma.module';
import { UserModule } from '../User/user.module';
import { CenterModule } from '../Center/center.module';
import { ServiceModule } from '../Service/service.module';
import { ChatroomModule } from '../ChatRoom/chatroom.module';
import { CenterStaffModule } from '../CenterStaff/centerstaff.module';
import { ResumeModule } from '../Resume/resume.module';
import { WorkshopModule } from '../Workshop/workshop.module';
import { WorkshopOrganizationModule } from '../WorkshopOrganization/workshoporganization.module';
import { WorkshopSubscriptionModule } from '../WorkshopSubscription/workshopsubscription.module';
import { PrismaCrudGenerator } from './graphql.generator';
import { OrderModule } from '../Order/order.module';
import { PaymentModule } from '../Payment/payment.module';
import { RefundTicketModule } from '../RefundTicket/refundticket.module';
import { ServiceAndCategoryModule } from '../ServiceAndCategory/serviceandcategory.module';
import { CategoryModule } from '../Category/category.module';
import { ServiceFeedbackModule } from '../ServiceFeedback/servicefeedback.module';
import { MilestoneModule } from '../Milestone/milestone.module';
import { ScheduleModule } from '../Schedule/schedule.module';
import { MessageModule } from '../Message/message.module';
import { ServiceMeetingRoomModule } from '../ServiceMeetingRoom/servicemeetingroom.module';
import { UploadedDocumentModule } from '../UploadedDocument/uploadeddocument.module';
@Global()
@Module({
imports: [
PrismaModule,
UserModule,
CenterModule,
ServiceModule,
ChatroomModule,
CenterStaffModule,
ResumeModule,
WorkshopModule,
WorkshopOrganizationModule,
WorkshopSubscriptionModule,
PaymentModule,
OrderModule,
RefundTicketModule,
ServiceAndCategoryModule,
CategoryModule,
ServiceFeedbackModule,
MilestoneModule,
ScheduleModule,
MessageModule,
ServiceMeetingRoomModule,
UploadedDocumentModule,
PothosModule.forRoot({
builder: {
inject: [PrismaService],
useFactory: (prisma: PrismaService) => new Builder(prisma),
},
}),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: PothosApolloDriver,
}),
],
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) {
consumer
.apply(GraphQLValidationMiddleware) // Apply the custom middleware
.forRoutes('graphql'); // Ensure it only applies to the /graphql endpoint
}
}

View File

@@ -0,0 +1,4 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class GraphqlService {}