super bug

This commit is contained in:
2024-11-18 15:34:32 +07:00
parent 3430971449
commit 88941394eb
6 changed files with 186 additions and 382 deletions

View File

@@ -1,10 +1,5 @@
import { Inject, Injectable } from '@nestjs/common' import { Inject, Injectable } from '@nestjs/common'
import { import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
Pothos,
PothosRef,
PothosSchema,
SchemaBuilderToken,
} from '@smatch-corp/nestjs-pothos'
import { Builder } from '../Graphql/graphql.builder' import { Builder } from '../Graphql/graphql.builder'
import { PrismaService } from '../Prisma/prisma.service' import { PrismaService } from '../Prisma/prisma.service'
import { ChatRoomType } from '@prisma/client' import { ChatRoomType } from '@prisma/client'
@@ -58,6 +53,10 @@ export class ChatroomSchema extends PothosSchema {
meetingRoom: t.relation('meetingRoom', { meetingRoom: t.relation('meetingRoom', {
description: 'The meeting room.', description: 'The meeting room.',
}), }),
lastActivity: t.expose('lastActivity', {
type: 'DateTime',
description: 'The last activity date and time.',
}),
}), }),
}) })
} }
@@ -79,8 +78,7 @@ export class ChatroomSchema extends PothosSchema {
chatRooms: t.prismaField({ chatRooms: t.prismaField({
type: [this.chatRoom()], type: [this.chatRoom()],
description: description: 'Retrieve a list of chat rooms with optional filtering, ordering, and pagination.',
'Retrieve a list of chat rooms with optional filtering, ordering, and pagination.',
args: this.builder.generator.findManyArgs('ChatRoom'), args: this.builder.generator.findManyArgs('ChatRoom'),
resolve: async (query, _root, args, _ctx, _info) => { resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.chatRoom.findMany({ return await this.prisma.chatRoom.findMany({

View File

@@ -1,12 +1,7 @@
import { JSONObjectResolver } from 'graphql-scalars' import { JSONObjectResolver } from 'graphql-scalars'
import PrismaPlugin, { import PrismaPlugin, { PothosPrismaDatamodel, PrismaClient } from '@pothos/plugin-prisma'
PothosPrismaDatamodel,
PrismaClient,
} from '@pothos/plugin-prisma'
import { Request, Response } from 'express' import { Request, Response } from 'express'
import SmartSubscriptionPlugin, { import SmartSubscriptionPlugin, { subscribeOptionsFromIterator } from '@pothos/plugin-smart-subscriptions'
subscribeOptionsFromIterator,
} from '@pothos/plugin-smart-subscriptions'
import ZodPlugin from '@pothos/plugin-zod' import ZodPlugin from '@pothos/plugin-zod'
import AuthzPlugin from '@pothos/plugin-authz' import AuthzPlugin from '@pothos/plugin-authz'
import ErrorsPlugin from '@pothos/plugin-errors' import ErrorsPlugin from '@pothos/plugin-errors'
@@ -90,22 +85,11 @@ export class Builder extends SchemaBuilder<SchemaBuilderOption> {
constructor(private readonly prisma: PrismaClient) { constructor(private readonly prisma: PrismaClient) {
super({ super({
plugins: [ plugins: [PrismaPlugin, PrismaUtils, SimpleObjectPlugin, SmartSubscriptionPlugin, RelayPlugin, ErrorsPlugin, AuthzPlugin, ZodPlugin],
PrismaPlugin,
PrismaUtils,
SimpleObjectPlugin,
SmartSubscriptionPlugin,
RelayPlugin,
ErrorsPlugin,
AuthzPlugin,
ZodPlugin,
],
smartSubscriptions: { smartSubscriptions: {
debounceDelay: 1000, debounceDelay: 1000,
...subscribeOptionsFromIterator((name, context) => { ...subscribeOptionsFromIterator((name, context) => {
return context.isSubscription return context.isSubscription ? context.websocket.pubSub.asyncIterator(name) : context.http.pubSub.asyncIterator(name)
? context.websocket.pubSub.asyncIterator(name)
: context.http.pubSub.asyncIterator(name)
}), }),
}, },
zod: { zod: {
@@ -174,13 +158,9 @@ export class Builder extends SchemaBuilder<SchemaBuilderOption> {
this.globalConnectionField('totalCount', (t) => this.globalConnectionField('totalCount', (t) =>
t.int({ t.int({
nullable: true, nullable: true,
resolve: (parent) => resolve: (parent) => (typeof parent.totalCount === 'function' ? parent.totalCount() : parent.totalCount),
typeof parent.totalCount === 'function'
? parent.totalCount()
: parent.totalCount,
}), }),
) )
} }
} }
export type BuilderTypes = export type BuilderTypes = PothosSchemaTypes.ExtendDefaultTypes<SchemaBuilderOption>
PothosSchemaTypes.ExtendDefaultTypes<SchemaBuilderOption>

View File

@@ -1,13 +1,6 @@
import { Inject, Injectable } from '@nestjs/common' import { Inject, Injectable } from '@nestjs/common'
import { import { type BaseEnum, type EnumRef, InputObjectRef, type InputType, type InputTypeParam, type SchemaTypes } from '@pothos/core'
type BaseEnum,
type EnumRef,
InputObjectRef,
type InputType,
type InputTypeParam,
type SchemaTypes,
} from '@pothos/core'
import { type PrismaModelTypes, getModel } from '@pothos/plugin-prisma' import { type PrismaModelTypes, getModel } from '@pothos/plugin-prisma'
import type { FilterOps } from '@pothos/plugin-prisma-utils' import type { FilterOps } from '@pothos/plugin-prisma-utils'
import * as Prisma from '@prisma/client' import * as Prisma from '@prisma/client'
@@ -15,31 +8,15 @@ import { SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
const filterOps = ['equals', 'in', 'notIn', 'not'] as const const filterOps = ['equals', 'in', 'notIn', 'not'] as const
const sortableFilterProps = ['lt', 'lte', 'gt', 'gte'] as const const sortableFilterProps = ['lt', 'lte', 'gt', 'gte'] as const
const stringFilterOps = [ const stringFilterOps = [...filterOps, 'contains', 'startsWith', 'endsWith', 'mode', 'search'] as const
...filterOps,
'contains',
'startsWith',
'endsWith',
'mode',
'search',
] as const
const sortableTypes = ['String', 'Int', 'Float', 'DateTime', 'BigInt'] as const const sortableTypes = ['String', 'Int', 'Float', 'DateTime', 'BigInt'] as const
const listOps = ['every', 'some', 'none'] as const const listOps = ['every', 'some', 'none'] as const
const scalarListOps = [ const scalarListOps = ['has', 'hasSome', 'hasEvery', 'isEmpty', 'equals'] as const
'has',
'hasSome',
'hasEvery',
'isEmpty',
'equals',
] as const
const JsonFilterOps = ['equals', 'in', 'notIn', 'not'] as const const JsonFilterOps = ['equals', 'in', 'notIn', 'not'] as const
const EnumFilterOps = ['equals', 'not'] as const const EnumFilterOps = ['equals', 'not'] as const
@Injectable() @Injectable()
export class PrismaCrudGenerator<Types extends SchemaTypes> { export class PrismaCrudGenerator<Types extends SchemaTypes> {
private refCache = new Map< private refCache = new Map<InputType<Types> | string, Map<string, InputObjectRef<Types, unknown>>>()
InputType<Types> | string,
Map<string, InputObjectRef<Types, unknown>>
>()
private enumRefs = new Map<string, EnumRef<Types, unknown>>() private enumRefs = new Map<string, EnumRef<Types, unknown>>()
@@ -49,9 +26,7 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
private builder: PothosSchemaTypes.SchemaBuilder<Types>, private builder: PothosSchemaTypes.SchemaBuilder<Types>,
) {} ) {}
findManyArgs<Name extends string & keyof Types['PrismaTypes']>( findManyArgs<Name extends string & keyof Types['PrismaTypes']>(modelName: Name) {
modelName: Name,
) {
return this.builder.args((t) => ({ return this.builder.args((t) => ({
filter: t.field({ filter: t.field({
type: this.getWhere(modelName), type: this.getWhere(modelName),
@@ -78,9 +53,7 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
})) }))
} }
findUniqueArgs<Name extends string & keyof Types['PrismaTypes']>( findUniqueArgs<Name extends string & keyof Types['PrismaTypes']>(modelName: Name) {
modelName: Name,
) {
return this.builder.args((t) => ({ return this.builder.args((t) => ({
where: t.field({ where: t.field({
type: this.getWhereUnique(modelName), type: this.getWhereUnique(modelName),
@@ -89,13 +62,8 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
})) }))
} }
getWhere<Name extends string & keyof Types['PrismaTypes']>( getWhere<Name extends string & keyof Types['PrismaTypes']>(modelName: Name, without?: string[]) {
modelName: Name, const withoutName = (without ?? []).map((name) => `Without${capitalize(name)}`).join('')
without?: string[],
) {
const withoutName = (without ?? [])
.map((name) => `Without${capitalize(name)}`)
.join('')
const fullName = `${modelName}${withoutName}Filter` const fullName = `${modelName}${withoutName}Filter`
return this.getRef(modelName, fullName, () => { return this.getRef(modelName, fullName, () => {
@@ -105,40 +73,21 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
name: fullName, name: fullName,
fields: (() => { fields: (() => {
const fields: Record<string, InputType<Types>> = {} const fields: Record<string, InputType<Types>> = {}
const withoutFields = model.fields.filter((field) => const withoutFields = model.fields.filter((field) => without?.includes(field.name))
without?.includes(field.name),
)
model.fields model.fields
.filter( .filter((field) => !withoutFields.some((f) => f.name === field.name || f.relationFromFields?.includes(field.name)))
(field) =>
!withoutFields.some(
(f) =>
f.name === field.name ||
f.relationFromFields?.includes(field.name),
),
)
.forEach((field) => { .forEach((field) => {
let type let type
switch (field.kind) { switch (field.kind) {
case 'scalar': case 'scalar':
type = field.isList type = field.isList ? this.getScalarListFilter(this.mapScalarType(field.type) as InputType<Types>) : this.getFilter(this.mapScalarType(field.type) as InputType<Types>)
? this.getScalarListFilter(
this.mapScalarType(field.type) as InputType<Types>,
)
: this.getFilter(
this.mapScalarType(field.type) as InputType<Types>,
)
break break
case 'enum': case 'enum':
type = field.isList type = field.isList ? this.getScalarListFilter(this.getEnum(field.type)) : this.getFilter(this.getEnum(field.type))
? this.getScalarListFilter(this.getEnum(field.type))
: this.getFilter(this.getEnum(field.type))
break break
case 'object': case 'object':
type = field.isList type = field.isList ? this.getListFilter(this.getWhere(field.type as Name)) : this.getWhere(field.type as Name)
? this.getListFilter(this.getWhere(field.type as Name))
: this.getWhere(field.type as Name)
break break
case 'unsupported': case 'unsupported':
break break
@@ -154,15 +103,10 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
return fields return fields
}) as never, }) as never,
}) as InputObjectRef< }) as InputObjectRef<Types, (PrismaModelTypes & Types['PrismaTypes'][Name])['Where']>
Types,
(PrismaModelTypes & Types['PrismaTypes'][Name])['Where']
>
}) })
} }
getWhereUnique<Name extends string & keyof Types['PrismaTypes']>( getWhereUnique<Name extends string & keyof Types['PrismaTypes']>(modelName: Name) {
modelName: Name,
) {
const name = `${modelName}UniqueFilter` const name = `${modelName}UniqueFilter`
return this.getRef(modelName, name, () => { return this.getRef(modelName, name, () => {
@@ -173,15 +117,7 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
const fields: Record<string, InputType<Types>> = {} const fields: Record<string, InputType<Types>> = {}
model.fields model.fields
.filter( .filter((field) => field.isUnique || field.isId || model.uniqueIndexes.some((index) => index.fields.includes(field.name)) || model.primaryKey?.fields.includes(field.name))
(field) =>
field.isUnique ||
field.isId ||
model.uniqueIndexes.some((index) =>
index.fields.includes(field.name),
) ||
model.primaryKey?.fields.includes(field.name),
)
.forEach((field) => { .forEach((field) => {
let type let type
switch (field.kind) { switch (field.kind) {
@@ -207,15 +143,10 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
return fields return fields
}) as never, }) as never,
}) as InputObjectRef< }) as InputObjectRef<Types, (PrismaModelTypes & Types['PrismaTypes'][Name])['WhereUnique']>
Types,
(PrismaModelTypes & Types['PrismaTypes'][Name])['WhereUnique']
>
}) })
} }
getOrderBy<Name extends string & keyof Types['PrismaTypes']>( getOrderBy<Name extends string & keyof Types['PrismaTypes']>(modelName: Name) {
modelName: Name,
) {
const name = `${modelName}OrderBy` const name = `${modelName}OrderBy`
return this.getRef(modelName, name, () => { return this.getRef(modelName, name, () => {
const model = getModel(modelName, this.builder) const model = getModel(modelName, this.builder)
@@ -252,13 +183,8 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
}) })
} }
getCreateInput<Name extends string & keyof Types['PrismaTypes']>( getCreateInput<Name extends string & keyof Types['PrismaTypes']>(modelName: Name, without?: string[]) {
modelName: Name, const withoutName = (without ?? []).map((name) => `Without${capitalize(name)}`).join('')
without?: string[],
) {
const withoutName = (without ?? [])
.map((name) => `Without${capitalize(name)}`)
.join('')
const fullName = `${modelName}Create${withoutName}Input` const fullName = `${modelName}Create${withoutName}Input`
return this.getRef(modelName, fullName, () => { return this.getRef(modelName, fullName, () => {
@@ -267,22 +193,11 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
name: fullName, name: fullName,
fields: (() => { fields: (() => {
const fields: Record<string, InputTypeParam<Types>> = {} const fields: Record<string, InputTypeParam<Types>> = {}
const withoutFields = model.fields.filter((field) => const withoutFields = model.fields.filter((field) => without?.includes(field.name))
without?.includes(field.name), const relationIds = model.fields.flatMap((field) => field.relationFromFields ?? [])
)
const relationIds = model.fields.flatMap(
(field) => field.relationFromFields ?? [],
)
model.fields model.fields
.filter( .filter((field) => !withoutFields.some((f) => f.name === field.name || f.relationFromFields?.includes(field.name)) && !relationIds.includes(field.name))
(field) =>
!withoutFields.some(
(f) =>
f.name === field.name ||
f.relationFromFields?.includes(field.name),
) && !relationIds.includes(field.name),
)
.forEach((field) => { .forEach((field) => {
let type let type
switch (field.kind) { switch (field.kind) {
@@ -308,58 +223,33 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
return fields return fields
}) as never, }) as never,
}) as InputObjectRef< }) as InputObjectRef<Types, (PrismaModelTypes & Types['PrismaTypes'][Name])['Create']>
Types,
(PrismaModelTypes & Types['PrismaTypes'][Name])['Create']
>
}) })
} }
getCreateRelationInput< getCreateRelationInput<Name extends string & keyof Types['PrismaTypes'], Relation extends Model['RelationName'], Model extends PrismaModelTypes = Types['PrismaTypes'][Name] extends PrismaModelTypes ? Types['PrismaTypes'][Name] : never>(
Name extends string & keyof Types['PrismaTypes'], modelName: Name,
Relation extends Model['RelationName'], relation: Relation,
Model extends ) {
PrismaModelTypes = Types['PrismaTypes'][Name] extends PrismaModelTypes return this.getRef(`${modelName}${capitalize(relation)}`, 'CreateRelationInput', () => {
? Types['PrismaTypes'][Name] const model = getModel(modelName, this.builder)
: never, return this.builder.prismaCreateRelation(modelName, relation, {
>(modelName: Name, relation: Relation) { fields: () => {
return this.getRef( const relationField = model.fields.find((field) => field.name === relation)!
`${modelName}${capitalize(relation)}`, const relatedModel = getModel(relationField.type, this.builder)
'CreateRelationInput', const relatedFieldName = relatedModel.fields.find((field) => field.relationName === relationField.relationName)!
() => {
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 { return {
create: this.getCreateInput(relationField.type as Name, [ create: this.getCreateInput(relationField.type as Name, [relatedFieldName.name]),
relatedFieldName.name, connect: this.getWhereUnique(relationField.type as Name),
]), }
connect: this.getWhereUnique(relationField.type as Name), },
} } as never) as InputObjectRef<Types, NonNullable<Model['Create'][Relation & keyof Model['Update']]>>
}, })
} as never) as InputObjectRef<
Types,
NonNullable<Model['Create'][Relation & keyof Model['Update']]>
>
},
)
} }
getCreateManyInput<Name extends string & keyof Types['PrismaTypes']>( getCreateManyInput<Name extends string & keyof Types['PrismaTypes']>(modelName: Name, without?: string[]) {
modelName: Name, const withoutName = (without ?? []).map((name) => `Without${capitalize(name)}`).join('')
without?: string[],
) {
const withoutName = (without ?? [])
.map((name) => `Without${capitalize(name)}`)
.join('')
const fullName = `${modelName}Create${withoutName}Input` const fullName = `${modelName}Create${withoutName}Input`
return this.getRef(modelName, fullName, () => { return this.getRef(modelName, fullName, () => {
@@ -369,22 +259,11 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
name: fullName, name: fullName,
fields: (() => { fields: (() => {
const fields: Record<string, InputTypeParam<Types>> = {} const fields: Record<string, InputTypeParam<Types>> = {}
const withoutFields = model.fields.filter((field) => const withoutFields = model.fields.filter((field) => without?.includes(field.name))
without?.includes(field.name), const relationIds = model.fields.flatMap((field) => field.relationFromFields ?? [])
)
const relationIds = model.fields.flatMap(
(field) => field.relationFromFields ?? [],
)
model.fields model.fields
.filter( .filter((field) => !withoutFields.some((f) => f.name === field.name || f.relationFromFields?.includes(field.name)) && !relationIds.includes(field.name))
(field) =>
!withoutFields.some(
(f) =>
f.name === field.name ||
f.relationFromFields?.includes(field.name),
) && !relationIds.includes(field.name),
)
.forEach((field) => { .forEach((field) => {
let type let type
switch (field.kind) { switch (field.kind) {
@@ -407,20 +286,12 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
return fields return fields
}) as never, }) as never,
}) as InputObjectRef< }) as InputObjectRef<Types, (PrismaModelTypes & Types['PrismaTypes'][Name])['Create']>
Types,
(PrismaModelTypes & Types['PrismaTypes'][Name])['Create']
>
}) })
} }
getUpdateInput<Name extends string & keyof Types['PrismaTypes']>( getUpdateInput<Name extends string & keyof Types['PrismaTypes']>(modelName: Name, without?: string[]) {
modelName: Name, const withoutName = (without ?? []).map((name) => `Without${capitalize(name)}`).join('')
without?: string[],
) {
const withoutName = (without ?? [])
.map((name) => `Without${capitalize(name)}`)
.join('')
const fullName = `${modelName}Update${withoutName}Input` const fullName = `${modelName}Update${withoutName}Input`
return this.getRef(modelName, fullName, () => { return this.getRef(modelName, fullName, () => {
@@ -429,22 +300,11 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
name: fullName, name: fullName,
fields: (() => { fields: (() => {
const fields: Record<string, InputTypeParam<Types>> = {} const fields: Record<string, InputTypeParam<Types>> = {}
const withoutFields = model.fields.filter((field) => const withoutFields = model.fields.filter((field) => without?.includes(field.name))
without?.includes(field.name), const relationIds = model.fields.flatMap((field) => field.relationFromFields ?? [])
)
const relationIds = model.fields.flatMap(
(field) => field.relationFromFields ?? [],
)
model.fields model.fields
.filter( .filter((field) => !withoutFields.some((f) => f.name === field.name || f.relationFromFields?.includes(field.name)) && !relationIds.includes(field.name))
(field) =>
!withoutFields.some(
(f) =>
f.name === field.name ||
f.relationFromFields?.includes(field.name),
) && !relationIds.includes(field.name),
)
.forEach((field) => { .forEach((field) => {
let type let type
switch (field.kind) { switch (field.kind) {
@@ -470,88 +330,54 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
return fields return fields
}) as never, }) as never,
}) as InputObjectRef< }) as InputObjectRef<Types, (PrismaModelTypes & Types['PrismaTypes'][Name])['Update']>
Types,
(PrismaModelTypes & Types['PrismaTypes'][Name])['Update']
>
}) })
} }
getUpdateRelationInput< getUpdateRelationInput<Name extends string & keyof Types['PrismaTypes'], Relation extends Model['RelationName'], Model extends PrismaModelTypes = Types['PrismaTypes'][Name] extends PrismaModelTypes ? Types['PrismaTypes'][Name] : never>(
Name extends string & keyof Types['PrismaTypes'], modelName: Name,
Relation extends Model['RelationName'], relation: Relation,
Model extends ) {
PrismaModelTypes = Types['PrismaTypes'][Name] extends PrismaModelTypes return this.getRef(`${modelName}${capitalize(relation)}`, 'UpdateRelationInput', () => {
? Types['PrismaTypes'][Name] const model = getModel(modelName, this.builder)
: never, return this.builder.prismaUpdateRelation(modelName, relation, {
>(modelName: Name, relation: Relation) { fields: () => {
return this.getRef( const relationField = model.fields.find((field) => field.name === relation)!
`${modelName}${capitalize(relation)}`, const relatedModel = getModel(relationField.type, this.builder)
'UpdateRelationInput', const relatedFieldName = relatedModel.fields.find((field) => field.relationName === relationField.relationName)!.name
() => {
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,
]),
}
}
if (relationField.isList) {
return { return {
create: this.getCreateInput(relationField.type as Name, [ create: this.getCreateInput(relationField.type as Name, [relatedFieldName]),
relatedFieldName, createMany: {
]), skipDuplicates: 'Boolean',
update: this.getUpdateInput(relationField.type as Name, [ data: this.getCreateInput(relationField.type as Name, [relatedFieldName]),
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), connect: this.getWhereUnique(relationField.type as Name),
disconnect: relationField.isRequired ? undefined : 'Boolean', update: {
delete: relationField.isRequired ? undefined : 'Boolean', 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]),
} }
}, }
} as never) as InputObjectRef<
Types, return {
NonNullable<Model['Update'][Relation & keyof Model['Update']]> 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>) { private getFilter(type: InputType<Types>) {
@@ -596,12 +422,9 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
private getEnum(name: string) { private getEnum(name: string) {
if (!this.enumRefs.has(name)) { if (!this.enumRefs.has(name)) {
const enumRef = this.builder.enumType( const enumRef = this.builder.enumType((Prisma as unknown as Record<string, BaseEnum>)[name], {
(Prisma as unknown as Record<string, BaseEnum>)[name], name,
{ })
name,
},
)
this.enumRefs.set(name, enumRef) this.enumRefs.set(name, enumRef)
} }
@@ -623,11 +446,7 @@ export class PrismaCrudGenerator<Types extends SchemaTypes> {
} }
} }
private getRef<T extends InputObjectRef<Types, unknown>>( private getRef<T extends InputObjectRef<Types, unknown>>(key: InputType<Types> | string, name: string, create: () => T): T {
key: InputType<Types> | string,
name: string,
create: () => T,
): T {
if (!this.refCache.has(key)) { if (!this.refCache.has(key)) {
this.refCache.set(key, new Map()) this.refCache.set(key, new Map())
} }

View File

@@ -1,18 +1,8 @@
import { Inject, Injectable, Logger } from '@nestjs/common' import { Inject, Injectable, Logger } from '@nestjs/common'
import { import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
Pothos,
PothosRef,
PothosSchema,
SchemaBuilderToken,
} from '@smatch-corp/nestjs-pothos'
import { Builder, SchemaContext } from '../Graphql/graphql.builder' import { Builder, SchemaContext } from '../Graphql/graphql.builder'
import { PrismaService } from '../Prisma/prisma.service' import { PrismaService } from '../Prisma/prisma.service'
import { import { ChatRoomType, Message, MessageContextType, MessageType } from '@prisma/client'
ChatRoomType,
Message,
MessageContextType,
MessageType,
} from '@prisma/client'
import { DateTimeUtils } from '../common/utils/datetime.utils' import { DateTimeUtils } from '../common/utils/datetime.utils'
import { PubSubEvent } from '../common/pubsub/pubsub-event' import { PubSubEvent } from '../common/pubsub/pubsub-event'
@@ -85,8 +75,7 @@ export class MessageSchema extends PothosSchema {
}), }),
messages: t.prismaField({ messages: t.prismaField({
type: [this.message()], type: [this.message()],
description: description: 'Retrieve a list of messages with optional filtering, ordering, and pagination.',
'Retrieve a list of messages with optional filtering, ordering, and pagination.',
args: this.builder.generator.findManyArgs('Message'), args: this.builder.generator.findManyArgs('Message'),
resolve: async (query, _root, args) => { resolve: async (query, _root, args) => {
return await this.prisma.message.findMany({ return await this.prisma.message.findMany({
@@ -118,15 +107,7 @@ export class MessageSchema extends PothosSchema {
description: 'Send a message to a chat room.', description: 'Send a message to a chat room.',
args: { args: {
input: t.arg({ input: t.arg({
type: this.builder.generator.getCreateInput('Message', [ type: this.builder.generator.getCreateInput('Message', ['id', 'senderId', 'sender', 'sentAt', 'context', 'recipient', 'recipientId']),
'id',
'senderId',
'sender',
'sentAt',
'context',
'recipient',
'recipientId',
]),
description: 'The message to send.', description: 'The message to send.',
required: true, required: true,
}), }),
@@ -174,20 +155,26 @@ export class MessageSchema extends PothosSchema {
throw new Error('Content cannot be longer than 1024 characters') throw new Error('Content cannot be longer than 1024 characters')
} }
args.input.context = messageContext args.input.context = messageContext
const message = await this.prisma.message.create({ const lastActivity = DateTimeUtils.now()
...query, const message = await this.prisma.$transaction(async (tx) => {
data: args.input, const message = await tx.message.create({
...query,
data: args.input,
})
await tx.chatRoom.update({
where: {
id: message.chatRoomId!,
},
data: {
lastActivity: lastActivity.toJSDate(),
},
})
return message
}) })
ctx.http.pubSub.publish( ctx.http.pubSub.publish(`${PubSubEvent.MESSAGE_SENT}.${message.chatRoomId}`, message)
`${PubSubEvent.MESSAGE_SENT}.${message.chatRoomId}`,
message,
)
// publish to new message subscribers // publish to new message subscribers
userIds.forEach((userId: string) => { userIds.forEach((userId: string) => {
ctx.http.pubSub.publish( ctx.http.pubSub.publish(`${PubSubEvent.NEW_MESSAGE}.${userId}`, message)
`${PubSubEvent.NEW_MESSAGE}.${userId}`,
message,
)
}) })
return message return message
}, },
@@ -209,9 +196,7 @@ export class MessageSchema extends PothosSchema {
const { const {
websocket: { pubSub }, websocket: { pubSub },
} = ctx } = ctx
return pubSub.asyncIterator([ return pubSub.asyncIterator([`${PubSubEvent.MESSAGE_SENT}.${args.chatRoomId}`]) as unknown as AsyncIterable<Message>
`${PubSubEvent.MESSAGE_SENT}.${args.chatRoomId}`,
]) as unknown as AsyncIterable<Message>
}, },
resolve: (payload: Message) => payload, resolve: (payload: Message) => payload,
}), }),

View File

@@ -1,21 +1,33 @@
import { Inject, Injectable, Logger } from '@nestjs/common' import { Inject, Injectable, Logger } from '@nestjs/common'
import { import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
Pothos,
PothosRef,
PothosSchema,
SchemaBuilderToken,
} from '@smatch-corp/nestjs-pothos'
import { Builder, SchemaContext } from '../Graphql/graphql.builder' import { Builder, SchemaContext } from '../Graphql/graphql.builder'
import { PrismaService } from '../Prisma/prisma.service' import { PrismaService } from '../Prisma/prisma.service'
import { clerkClient } from '@clerk/express' import { clerkClient } from '@clerk/express'
import { UnauthorizedException } from '@nestjs/common' import { UnauthorizedException } from '@nestjs/common'
import { MailService } from '../Mail/mail.service' import { MailService } from '../Mail/mail.service'
import { MessageSchema } from 'src/Message/message.schema' import { MessageSchema } from 'src/Message/message.schema'
import { Message, MessageContextType, MessageType } from '@prisma/client' import { ChatRoom, Message, MessageContextType, MessageType, Role } from '@prisma/client'
import { PubSubEvent } from 'src/common/pubsub/pubsub-event' import { PubSubEvent } from 'src/common/pubsub/pubsub-event'
import { DateTimeUtils } from 'src/common/utils/datetime.utils' import { DateTimeUtils } from 'src/common/utils/datetime.utils'
import { JsonValue } from '@prisma/client/runtime/library' import { JsonValue } from '@prisma/client/runtime/library'
import { z } from 'zod' import { z } from 'zod'
type UserWithChatRooms = {
name: string
id: string
email: string
phoneNumber: string | null
bankBin: string | null
bankAccountNumber: string | null
packageValue: number
role: Role
avatarUrl: string | null
createdAt: Date
updatedAt: Date
customerChatRoom?: ChatRoom[]
mentorChatRoom?: ChatRoom[]
}
@Injectable() @Injectable()
export class UserSchema extends PothosSchema { export class UserSchema extends PothosSchema {
constructor( constructor(
@@ -148,18 +160,39 @@ export class UserSchema extends PothosSchema {
}), }),
me: t.prismaField({ me: t.prismaField({
description: 'Retrieve the current user in context.', description: 'Retrieve the current user in context.',
type: this.user(), type: this.user() || 'Json',
args: {
includeChatRoom: t.arg({ type: 'Boolean', required: false }),
},
resolve: async (_query, _root, _args, ctx) => { resolve: async (_query, _root, _args, ctx) => {
if (ctx.isSubscription) { if (ctx.isSubscription) {
throw new Error('Not allowed') throw new Error('Not allowed')
} }
return ctx.http.me let user = ctx.http.me as UserWithChatRooms
if (!user?.name) {
throw new Error('User not found')
}
if (_args.includeChatRoom) {
const customerChatRoom = await this.prisma.chatRoom.findMany({
where: {
OR: [{ customerId: ctx.http.me?.id }, { mentorId: ctx.http.me?.id }],
},
distinct: ['id'],
orderBy: {
lastActivity: 'desc',
},
})
user = {
...user,
customerChatRoom,
}
}
return user
}, },
}), }),
users: t.prismaField({ users: t.prismaField({
description: description: 'Retrieve a list of users with optional filtering, ordering, and pagination.',
'Retrieve a list of users with optional filtering, ordering, and pagination.',
type: [this.user()], type: [this.user()],
args: this.builder.generator.findManyArgs('User'), args: this.builder.generator.findManyArgs('User'),
resolve: async (query, _root, args) => { resolve: async (query, _root, args) => {
@@ -291,10 +324,9 @@ export class UserSchema extends PothosSchema {
} }
const buffer = Buffer.concat(chunks) const buffer = Buffer.concat(chunks)
const { id: userId, imageUrl } = const { id: userId, imageUrl } = await clerkClient.users.updateUserProfileImage(id, {
await clerkClient.users.updateUserProfileImage(id, { file: new Blob([buffer]),
file: new Blob([buffer]), })
})
await this.prisma.user.update({ await this.prisma.user.update({
where: { id: userId }, where: { id: userId },
data: { data: {
@@ -371,14 +403,9 @@ export class UserSchema extends PothosSchema {
throw new Error(`User ${args.email} not found`) throw new Error(`User ${args.email} not found`)
} }
// send email // send email
await this.mailService.sendTemplateEmail( await this.mailService.sendTemplateEmail([args.email], 'Thông báo chọn lựa quản trị viên cho người điều hành', 'ModeratorInvitation', {
[args.email], USER_NAME: user.name,
'Thông báo chọn lựa quản trị viên cho người điều hành', })
'ModeratorInvitation',
{
USER_NAME: user.name,
},
)
return 'Invited' return 'Invited'
}) })
}, },
@@ -415,10 +442,7 @@ export class UserSchema extends PothosSchema {
}, },
}) })
// publish message // publish message
await ctx.http.pubSub.publish( await ctx.http.pubSub.publish(`${PubSubEvent.NEW_MESSAGE}.${message.recipientId}`, message)
`${PubSubEvent.NEW_MESSAGE}.${message.recipientId}`,
message,
)
return message return message
}, },
}), }),
@@ -433,9 +457,7 @@ export class UserSchema extends PothosSchema {
const { const {
websocket: { pubSub }, websocket: { pubSub },
} = ctx } = ctx
return pubSub.asyncIterator([ return pubSub.asyncIterator([`${PubSubEvent.NEW_MESSAGE}.${ctx.websocket.me?.id}`]) as unknown as AsyncIterable<Message>
`${PubSubEvent.NEW_MESSAGE}.${ctx.websocket.me?.id}`,
]) as unknown as AsyncIterable<Message>
}, },
resolve: async (payload: Message) => payload, resolve: async (payload: Message) => payload,
}), }),

File diff suppressed because one or more lines are too long