diff --git a/epess-database b/epess-database index e6f3f9a..fdb993e 160000 --- a/epess-database +++ b/epess-database @@ -1 +1 @@ -Subproject commit e6f3f9a84f9da18ee3bb1005ff1fdfad91ff4e9e +Subproject commit fdb993eb658de73413a2256ed33b3b496c7ea23f diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..3a88062 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,54 @@ +import typescriptEslintEslintPlugin from '@typescript-eslint/eslint-plugin'; +import globals from 'globals'; +import tsParser from '@typescript-eslint/parser'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import js from '@eslint/js'; +import { FlatCompat } from '@eslint/eslintrc'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}); + +export default [ + { + ignores: ['**/.eslintrc.js'], + }, + ...compat.extends( + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ), + { + plugins: { + '@typescript-eslint': typescriptEslintEslintPlugin, + }, + + languageOptions: { + globals: { + ...globals.node, + ...globals.jest, + ...globals.browser, + }, + + parser: tsParser, + ecmaVersion: 5, + sourceType: 'module', + + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: 'C:\\Users\\AliensVN\\EPESS\\epess-web-backend', + }, + }, + + rules: { + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + }, + }, +]; diff --git a/package-lock.json b/package-lock.json index 7fb68e2..c4b475c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,8 @@ "@pothos/plugin-prisma-utils": "^1.2.0", "@pothos/plugin-scope-auth": "^4.1.0", "@prisma/client": "^5.20.0", + "@smatch-corp/nestjs-pothos": "^0.3.0", + "@smatch-corp/nestjs-pothos-apollo-driver": "^0.1.0", "apollo-server-express": "^3.13.0", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", @@ -46,6 +48,8 @@ }, "devDependencies": { "@clerk/types": "^4.23.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "^9.12.0", "@graphql-codegen/cli": "5.0.2", "@graphql-codegen/typescript": "4.0.9", "@graphql-codegen/typescript-resolvers": "4.2.1", @@ -64,6 +68,7 @@ "eslint": "9.11.1", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", + "globals": "^15.10.0", "jest": "^29.5.0", "prettier": "^3.0.0", "prisma": "^5.20.0", @@ -2105,6 +2110,19 @@ "concat-map": "0.0.1" } }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2126,9 +2144,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.11.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.1.tgz", - "integrity": "sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==", + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", + "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", "dev": true, "license": "MIT", "engines": { @@ -5747,6 +5765,35 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@smatch-corp/nestjs-pothos": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@smatch-corp/nestjs-pothos/-/nestjs-pothos-0.3.0.tgz", + "integrity": "sha512-MRWLe+jCKu3q2Nr5gc2o4vU4R2LDd0Fwk5Il6HOUgMAJ7yxUjvRDf7C0tCYLme2wESjEyCPTjMyoQr7yj2Ju1g==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^7 || ^8 || ^9", + "@nestjs/core": "^7 || ^8 || ^9", + "@nestjs/graphql": "*", + "@pothos/core": "^3", + "rxjs": "*" + } + }, + "node_modules/@smatch-corp/nestjs-pothos-apollo-driver": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@smatch-corp/nestjs-pothos-apollo-driver/-/nestjs-pothos-apollo-driver-0.1.0.tgz", + "integrity": "sha512-Xcqz6v4pOBMvJR5jm7L05EMHiOn+ZDmvKIRVfNmvK8Ttoo/fmzRIrCO3WHVJn0VdCk5nFvqgins2omHifzUwTg==", + "license": "MIT", + "dependencies": { + "@smatch-corp/nestjs-pothos": "^0.3.0" + }, + "peerDependencies": { + "@nestjs/apollo": "^10", + "@nestjs/common": "^7 || ^8 || ^9", + "@nestjs/core": "^7 || ^8 || ^9", + "@nestjs/graphql": "^10", + "rxjs": "*" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -8968,6 +9015,16 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.1.tgz", + "integrity": "sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/eslint/node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -9844,9 +9901,9 @@ "license": "BSD-2-Clause" }, "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "15.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.10.0.tgz", + "integrity": "sha512-tqFIbz83w4Y5TCbtgjZjApohbuh7K9BxGYFm7ifwDR240tvdb7P9x+/9VvUKlmkPoiknoJtanI8UOrqxS3a7lQ==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 4a5361d..e31f3e4 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,8 @@ "@pothos/plugin-prisma-utils": "^1.2.0", "@pothos/plugin-scope-auth": "^4.1.0", "@prisma/client": "^5.20.0", + "@smatch-corp/nestjs-pothos": "^0.3.0", + "@smatch-corp/nestjs-pothos-apollo-driver": "^0.1.0", "apollo-server-express": "^3.13.0", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", @@ -60,6 +62,8 @@ }, "devDependencies": { "@clerk/types": "^4.23.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "^9.12.0", "@graphql-codegen/cli": "5.0.2", "@graphql-codegen/typescript": "4.0.9", "@graphql-codegen/typescript-resolvers": "4.2.1", @@ -78,6 +82,7 @@ "eslint": "9.11.1", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", + "globals": "^15.10.0", "jest": "^29.5.0", "prettier": "^3.0.0", "prisma": "^5.20.0", diff --git a/src/app.module.ts b/src/app.module.ts index 1ebf343..fe089d7 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,9 +5,9 @@ import { RestfulModule } from './restful/restful.module'; @Module({ imports: [ - GraphqlModule, // GraphQL setup - ClerkModule, // Clerk setup - RestfulModule, // RESTful API module + GraphqlModule, // GraphQL setup + ClerkModule, // Clerk setup + RestfulModule, ], }) export class AppModule {} diff --git a/src/common/custom_types/schedule.date.type.d.ts b/src/common/custom_types/schedule.date.type.d.ts index 80d3c48..e69de29 100644 --- a/src/common/custom_types/schedule.date.type.d.ts +++ b/src/common/custom_types/schedule.date.type.d.ts @@ -1,21 +0,0 @@ -export type ScheduleDateJSON = {}; - -export type ScheduleDate = Date | ScheduleDateJSON; - -/* - -[id:startTimeStamp - endTimeStamp, id:startTimeStamp - endTimeStamp, ...] - -*/ - - -1728206200 - 1728206300 -1728206400 - 1728206500 -1728206600 - 1728206700 -1728206800 - 1728206900 -1728207000 - 1728207100 -1728207200 - 1728207300 -1728207400 - 1728207500 -1728207600 - 1728207700 - - diff --git a/src/common/utils/datetime.utils.ts b/src/common/utils/datetime.utils.ts index d4290a3..5391525 100644 --- a/src/common/utils/datetime.utils.ts +++ b/src/common/utils/datetime.utils.ts @@ -2,7 +2,7 @@ function getOverlapRange( startA: number, endA: number, startB: number, - endB: number + endB: number, ) { const overlapStart = Math.max(startA, startB); const overlapEnd = Math.min(endA, endB); @@ -10,12 +10,7 @@ function getOverlapRange( return overlapStart < overlapEnd ? { overlapStart, overlapEnd } : null; } -function isOverlap( - startA: number, - endA: number, - startB: number, - endB: number -) { +function isOverlap(startA: number, endA: number, startB: number, endB: number) { return getOverlapRange(startA, endA, startB, endB) !== null; } @@ -57,13 +52,13 @@ const endB = new Date('2024-10-06T13:00:00Z').getTime(); const overlapRange = getOverlapRange(startA, endA, startB, endB); if (overlapRange) { console.log( - `Overlap Start: ${new Date(overlapRange.overlapStart).toISOString()}` + `Overlap Start: ${new Date(overlapRange.overlapStart).toISOString()}`, ); console.log( - `Overlap End: ${new Date(overlapRange.overlapEnd).toISOString()}` + `Overlap End: ${new Date(overlapRange.overlapEnd).toISOString()}`, ); - + console.log('Is overlap: true'); } else { console.log('No overlap'); -} \ No newline at end of file +} diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index d3234b8..40b9f1b 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -1,19 +1,36 @@ -import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql'; +import { + GraphQLResolveInfo, + GraphQLScalarType, + GraphQLScalarTypeConfig, +} from 'graphql'; export type Maybe = T | null; export type InputMaybe = Maybe; -export type Exact = { [K in keyof T]: T[K] }; -export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; -export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; -export type MakeEmpty = { [_ in K]?: never }; -export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +export type Exact = { + [K in keyof T]: T[K]; +}; +export type MakeOptional = Omit & { + [SubKey in K]?: Maybe; +}; +export type MakeMaybe = Omit & { + [SubKey in K]: Maybe; +}; +export type MakeEmpty< + T extends { [key: string]: unknown }, + K extends keyof T, +> = { [_ in K]?: never }; +export type Incremental = + | T + | { + [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never; + }; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { - ID: { input: string; output: string; } - String: { input: string; output: string; } - Boolean: { input: boolean; output: boolean; } - Int: { input: number; output: number; } - Float: { input: number; output: number; } - DateTime: { input: any; output: any; } + ID: { input: string; output: string }; + String: { input: string; output: string }; + Boolean: { input: boolean; output: boolean }; + Int: { input: number; output: number }; + Float: { input: number; output: number }; + DateTime: { input: any; output: any }; }; export type Center = { @@ -103,40 +120,55 @@ export type SendingMessage = { userId?: Maybe; }; - - export type ResolverTypeWrapper = Promise | T; - export type ResolverWithResolve = { resolve: ResolverFn; }; -export type Resolver = ResolverFn | ResolverWithResolve; +export type Resolver = + | ResolverFn + | ResolverWithResolve; export type ResolverFn = ( parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => Promise | TResult; export type SubscriptionSubscribeFn = ( parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => AsyncIterable | Promise>; export type SubscriptionResolveFn = ( parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => TResult | Promise; -export interface SubscriptionSubscriberObject { - subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>; - resolve?: SubscriptionResolveFn; +export interface SubscriptionSubscriberObject< + TResult, + TKey extends string, + TParent, + TContext, + TArgs, +> { + subscribe: SubscriptionSubscribeFn< + { [key in TKey]: TResult }, + TParent, + TContext, + TArgs + >; + resolve?: SubscriptionResolveFn< + TResult, + { [key in TKey]: TResult }, + TContext, + TArgs + >; } export interface SubscriptionResolverObject { @@ -144,34 +176,55 @@ export interface SubscriptionResolverObject { resolve: SubscriptionResolveFn; } -export type SubscriptionObject = +export type SubscriptionObject< + TResult, + TKey extends string, + TParent, + TContext, + TArgs, +> = | SubscriptionSubscriberObject | SubscriptionResolverObject; -export type SubscriptionResolver = - | ((...args: any[]) => SubscriptionObject) +export type SubscriptionResolver< + TResult, + TKey extends string, + TParent = {}, + TContext = {}, + TArgs = {}, +> = + | (( + ...args: any[] + ) => SubscriptionObject) | SubscriptionObject; export type TypeResolveFn = ( parent: TParent, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => Maybe | Promise>; -export type IsTypeOfResolverFn = (obj: T, context: TContext, info: GraphQLResolveInfo) => boolean | Promise; +export type IsTypeOfResolverFn = ( + obj: T, + context: TContext, + info: GraphQLResolveInfo, +) => boolean | Promise; export type NextResolverFn = () => Promise; -export type DirectiveResolverFn = ( +export type DirectiveResolverFn< + TResult = {}, + TParent = {}, + TContext = {}, + TArgs = {}, +> = ( next: NextResolverFn, parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo + info: GraphQLResolveInfo, ) => TResult | Promise; - - /** Mapping between all available schema types and the resolvers types */ export type ResolversTypes = { Boolean: ResolverTypeWrapper; @@ -216,91 +269,196 @@ export type ResolversParentTypes = { sendingMessage: SendingMessage; }; -export type CenterResolvers = { +export type CenterResolvers< + ContextType = any, + ParentType extends + ResolversParentTypes['Center'] = ResolversParentTypes['Center'], +> = { id?: Resolver, ParentType, ContextType>; userId?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; -export type CenterStaffResolvers = { +export type CenterStaffResolvers< + ContextType = any, + ParentType extends + ResolversParentTypes['CenterStaff'] = ResolversParentTypes['CenterStaff'], +> = { centerId?: Resolver, ParentType, ContextType>; serviceId?: Resolver, ParentType, ContextType>; staffId?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; -export type ChatRoomResolvers = { +export type ChatRoomResolvers< + ContextType = any, + ParentType extends + ResolversParentTypes['ChatRoom'] = ResolversParentTypes['ChatRoom'], +> = { id?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; -export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig { +export interface DateTimeScalarConfig + extends GraphQLScalarTypeConfig { name: 'DateTime'; } -export type OrderResolvers = { +export type OrderResolvers< + ContextType = any, + ParentType extends + ResolversParentTypes['Order'] = ResolversParentTypes['Order'], +> = { id?: Resolver, ParentType, ContextType>; userId?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; -export type QueryResolvers = { - users?: Resolver>, ParentType, ContextType>; +export type QueryResolvers< + ContextType = any, + ParentType extends + ResolversParentTypes['Query'] = ResolversParentTypes['Query'], +> = { + users?: Resolver< + Maybe>, + ParentType, + ContextType + >; }; -export type ServiceResolvers = { +export type ServiceResolvers< + ContextType = any, + ParentType extends + ResolversParentTypes['Service'] = ResolversParentTypes['Service'], +> = { id?: Resolver, ParentType, ContextType>; userId?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; -export type ServiceFeedbackResolvers = { +export type ServiceFeedbackResolvers< + ContextType = any, + ParentType extends + ResolversParentTypes['ServiceFeedback'] = ResolversParentTypes['ServiceFeedback'], +> = { id?: Resolver, ParentType, ContextType>; userId?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; -export type UserResolvers = { - CenterStaff?: Resolver, ParentType, ContextType>; - Service?: Resolver>, ParentType, ContextType>; - WorkshopSubscription?: Resolver>, ParentType, ContextType>; +export type UserResolvers< + ContextType = any, + ParentType extends + ResolversParentTypes['User'] = ResolversParentTypes['User'], +> = { + CenterStaff?: Resolver< + Maybe, + ParentType, + ContextType + >; + Service?: Resolver< + Maybe>, + ParentType, + ContextType + >; + WorkshopSubscription?: Resolver< + Maybe>, + ParentType, + ContextType + >; center?: Resolver, ParentType, ContextType>; - centerStaffChatRoom?: Resolver>, ParentType, ContextType>; - createdAt?: Resolver, ParentType, ContextType>; - customerChatRoom?: Resolver>, ParentType, ContextType>; - documents?: Resolver>, ParentType, ContextType>; + centerStaffChatRoom?: Resolver< + Maybe>, + ParentType, + ContextType + >; + createdAt?: Resolver< + Maybe, + ParentType, + ContextType + >; + customerChatRoom?: Resolver< + Maybe>, + ParentType, + ContextType + >; + documents?: Resolver< + Maybe>, + ParentType, + ContextType + >; email?: Resolver, ParentType, ContextType>; id?: Resolver, ParentType, ContextType>; name?: Resolver, ParentType, ContextType>; - order?: Resolver>, ParentType, ContextType>; - phoneNumber?: Resolver, ParentType, ContextType>; + order?: Resolver< + Maybe>, + ParentType, + ContextType + >; + phoneNumber?: Resolver< + Maybe, + ParentType, + ContextType + >; role?: Resolver, ParentType, ContextType>; - sendingMessage?: Resolver>, ParentType, ContextType>; - serviceFeedbacks?: Resolver>, ParentType, ContextType>; - updatedAt?: Resolver, ParentType, ContextType>; + sendingMessage?: Resolver< + Maybe>, + ParentType, + ContextType + >; + serviceFeedbacks?: Resolver< + Maybe>, + ParentType, + ContextType + >; + updatedAt?: Resolver< + Maybe, + ParentType, + ContextType + >; __isTypeOf?: IsTypeOfResolverFn; }; -export type WorkshopResolvers = { +export type WorkshopResolvers< + ContextType = any, + ParentType extends + ResolversParentTypes['Workshop'] = ResolversParentTypes['Workshop'], +> = { id?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; -export type WorkshopSubscriptionResolvers = { +export type WorkshopSubscriptionResolvers< + ContextType = any, + ParentType extends + ResolversParentTypes['WorkshopSubscription'] = ResolversParentTypes['WorkshopSubscription'], +> = { user?: Resolver, ParentType, ContextType>; userId?: Resolver, ParentType, ContextType>; - workshop?: Resolver, ParentType, ContextType>; + workshop?: Resolver< + Maybe, + ParentType, + ContextType + >; workshopId?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; -export type DocumentsResolvers = { +export type DocumentsResolvers< + ContextType = any, + ParentType extends + ResolversParentTypes['documents'] = ResolversParentTypes['documents'], +> = { id?: Resolver, ParentType, ContextType>; userId?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }; -export type SendingMessageResolvers = { +export type SendingMessageResolvers< + ContextType = any, + ParentType extends + ResolversParentTypes['sendingMessage'] = ResolversParentTypes['sendingMessage'], +> = { id?: Resolver, ParentType, ContextType>; userId?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -321,4 +479,3 @@ export type Resolvers = { documents?: DocumentsResolvers; sendingMessage?: SendingMessageResolvers; }; - diff --git a/src/graphql/graphql.builder.ts b/src/graphql/graphql.builder.ts index dbd6b1d..4e9be0a 100644 --- a/src/graphql/graphql.builder.ts +++ b/src/graphql/graphql.builder.ts @@ -1,36 +1,45 @@ import SchemaBuilder from '@pothos/core'; -import PrismaPlugin from '@pothos/plugin-prisma'; +import PrismaPlugin, { PothosPrismaDatamodel } from '@pothos/plugin-prisma'; +import { PrismaClient } from '@prisma/client'; import PrismaUtils from '@pothos/plugin-prisma-utils'; -import { DateTimeResolver } from 'graphql-scalars'; -import { GraphQLInt } from 'graphql'; -import { PrismaService } from '../prisma/prisma.service'; +import { Request } from 'express'; import type PrismaTypes from '../types/pothos.generated'; import { getDatamodel } from '../types/pothos.generated'; +import { DateTimeResolver } from 'graphql-scalars'; -export const prisma = new PrismaService({ - log: ['query', 'info', 'warn', 'error'], - errorFormat: 'pretty', -}); -export const builder = new SchemaBuilder<{ +export interface SchemaContext { + req: Request; +} + +interface SchemaBuilderOption { + Context: SchemaContext; + PrismaTypes: PrismaTypes; + DataModel: PothosPrismaDatamodel; Scalars: { - DateTime: { + Date: { Input: Date; Output: Date; }; }; - PrismaTypes: PrismaTypes; -}>({ - plugins: [PrismaPlugin, PrismaUtils], - prisma: { - client: prisma, - exposeDescriptions: true, - filterConnectionTotalCount: true, - onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn', - dmmf: getDatamodel(), - }, -}); +} +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(), + }, + }); -export const SortOrder = builder.enumType('SortOrder', { - values: ['asc', 'desc'], -}); -builder.addScalarType('DateTime', DateTimeResolver, {}); + builder.queryType({}); + // builder.mutationType({}); + // builder.subscriptionType({}); + + builder.addScalarType('Date', DateTimeResolver, {}); + return builder; +} + +export type Builder = ReturnType; diff --git a/src/graphql/graphql.module.ts b/src/graphql/graphql.module.ts index 5d1f06a..6d5cc0b 100644 --- a/src/graphql/graphql.module.ts +++ b/src/graphql/graphql.module.ts @@ -1,20 +1,29 @@ +import { ApolloDriverConfig } from '@nestjs/apollo'; import { MiddlewareConsumer, Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; -import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; +import { PothosModule } from '@smatch-corp/nestjs-pothos'; +import { PothosApolloDriver } from '@smatch-corp/nestjs-pothos-apollo-driver'; +import { createBuilder } from './graphql.builder'; import { PrismaService } from '../prisma/prisma.service'; -import { schema } from './schema'; import { GraphQLValidationMiddleware } from 'src/middlewares/graphql.middleware'; +import { PrismaModule } from 'src/prisma/prisma.module'; +import { UserModule } from 'src/user/user.module'; +import { GraphqlService } from './graphql.service'; @Module({ imports: [ + PrismaModule, + UserModule, + PothosModule.forRoot({ + builder: { + inject: [PrismaService], + useFactory: (prisma: PrismaService) => createBuilder(prisma), + }, + }), GraphQLModule.forRoot({ - driver: ApolloDriver, - schema: schema, - debug: true, - allowBatchedHttpRequests: true, - introspection: true, + driver: PothosApolloDriver, }), ], - providers: [PrismaService], + providers: [GraphqlService], }) export class GraphqlModule { configure(consumer: MiddlewareConsumer) { diff --git a/src/graphql/graphql.service.spec.ts b/src/graphql/graphql.service.spec.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/graphql/mutation.graphql.ts b/src/graphql/mutation.graphql.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/graphql/query.graphql.ts b/src/graphql/query.graphql.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/graphql/schema.ts b/src/graphql/schema.ts index ab93d7f..b4e284b 100644 --- a/src/graphql/schema.ts +++ b/src/graphql/schema.ts @@ -1,448 +1,448 @@ -import { builder, prisma, SortOrder } from './graphql.builder'; -import { Prisma } from '@prisma/client'; +// import { builder, prisma, SortOrder } from './graphql.builder'; +// import { Prisma } from '@prisma/client'; -builder.prismaObject('User', { - fields: (t) => ({ - id: t.exposeID('id'), - name: t.exposeString('name'), - email: t.exposeString('email'), - phoneNumber: t.exposeString('phoneNumber'), - role: t.exposeString('role'), - createdAt: t.expose('createdAt', { - type: 'DateTime', - nullable: true, - }), - updatedAt: t.expose('updatedAt', { - type: 'DateTime', - nullable: true, - }), - order: t.relation('orders'), - serviceFeedbacks: t.relation('serviceFeedbacks'), - documents: t.relation('documents'), - sendingMessage: t.relation('sendingMessage'), - Service: t.relation('Service'), - center: t.relation('center'), - customerChatRoom: t.relation('customerChatRoom'), - centerStaffChatRoom: t.relation('centerStaffChatRoom'), - CenterStaff: t.relation('CenterStaff'), - WorkshopSubscription: t.relation('WorkshopSubscription'), - }), -}); - -builder.prismaObject('Order', { - fields: (t) => ({ - id: t.exposeID('id'), - paymentId: t.exposeString('paymentId'), - userId: t.exposeID('userId'), - serviceId: t.exposeID('serviceId'), - status: t.exposeString('status'), - total: t.expose('total', { - type: 'Int', - }), - createdAt: t.expose('createdAt', { - type: 'DateTime', - nullable: true, - }), - updatedAt: t.expose('updatedAt', { - type: 'DateTime', - nullable: true, - }), - user: t.relation('user'), - payment: t.relation('payment'), - service: t.relation('service'), - refundTicket: t.relation('refundTicket'), - }), -}); - -builder.prismaObject('ServiceFeedback', { - fields: (t) => ({ - id: t.exposeID('id'), - userId: t.exposeID('userId'), - }), -}); - -builder.prismaObject('UploadedDocument', { - name: 'documents', - fields: (t) => ({ - id: t.exposeID('id'), - userId: t.exposeID('userId'), - }), -}); - -builder.prismaObject('Message', { - name: 'sendingMessage', - fields: (t) => ({ - id: t.exposeID('id'), - userId: t.exposeID('senderId'), - }), -}); - -builder.prismaObject('Payment', { - fields: (t) => ({ - id: t.exposeID('id'), - amount: t.expose('amount', { - type: 'Float', - }), - status: t.exposeString('status'), - createdAt: t.expose('createdAt', { - type: 'DateTime', - nullable: true, - }), - updatedAt: t.expose('updatedAt', { - type: 'DateTime', - nullable: true, - }), - order: t.relation('Order'), - }), -}); - -builder.prismaObject('RefundTicket', { - fields: (t) => ({ - id: t.exposeID('id'), - orderId: t.exposeID('orderId'), - amount: t.expose('amount', { - type: 'Float', - }), - status: t.exposeString('status'), - createdAt: t.expose('createdAt', { - type: 'DateTime', - nullable: true, - }), - updatedAt: t.expose('updatedAt', { - type: 'DateTime', - nullable: true, - }), - order: t.relation('order'), - }), -}); - -builder.prismaObject('Service', { - fields: (t) => ({ - id: t.exposeID('id'), - name: t.exposeString('name'), - description: t.exposeString('description'), - centerId: t.exposeID('centerId'), - price: t.expose('price', { - type: 'Float', - }), - rating: t.expose('rating', { - type: 'Float', - }), - createdAt: t.expose('createdAt', { - type: 'DateTime', - nullable: true, - }), - updatedAt: t.expose('updatedAt', { - type: 'DateTime', - nullable: true, - }), - managedBy: t.relation('managedBy'), - feedbacks: t.relation('feedbacks'), - order: t.relation('order'), - workshop: t.relation('workshop'), - milestone: t.relation('milestone'), - schedule: t.relation('schedule'), - serviceAndCategory: t.relation('serviceAndCategory'), - workshopOrganization: t.relation('workshopOrganization'), - user: t.relation('user'), - userId: t.exposeID('userId'), - }), -}); - -builder.prismaObject('Milestone', { - fields: (t) => ({ - id: t.exposeID('id'), - serviceId: t.exposeID('serviceId'), - }), -}); - -builder.prismaObject('Schedule', { - fields: (t) => ({ - id: t.exposeID('id'), - serviceId: t.exposeID('serviceId'), - }), -}); - -builder.prismaObject('ServiceAndCategory', { - fields: (t) => ({ - serviceId: t.exposeID('serviceId'), - categoryId: t.exposeID('categoryId'), - service: t.relation('service'), - category: t.relation('category'), - }), -}); - -builder.prismaObject('WorkshopOrganization', { - fields: (t) => ({ - serviceId: t.exposeID('serviceId'), - workshopId: t.exposeID('workshopId'), - service: t.relation('service'), - workshop: t.relation('workshop'), - }), -}); - -builder.prismaObject('Category', { - fields: (t) => ({ - id: t.exposeID('id'), - }), -}); - -builder.prismaObject('Center', { - fields: (t) => ({ - id: t.exposeID('id'), - userId: t.exposeID('centerOwnerId'), - }), -}); - -builder.prismaObject('ChatRoom', { - fields: (t) => ({ - id: t.exposeID('id'), - }), -}); - -builder.prismaObject('CenterStaff', { - fields: (t) => ({ - staffId: t.exposeID('staffId'), - staff: t.relation('staff'), - centerId: t.exposeID('centerId'), - serviceId: t.exposeID('serviceId'), - }), -}); - -builder.prismaObject('WorkshopSubscription', { - fields: (t) => ({ - userId: t.exposeID('userId'), - workshopId: t.exposeID('workshopId'), - user: t.relation('user'), - workshop: t.relation('workshop'), - }), -}); - -builder.prismaObject('Workshop', { - fields: (t) => ({ - id: t.exposeID('id'), - }), -}); - -// Query section -builder.queryType({ - fields: (t) => ({ - users: t.prismaField({ - type: ['User'], // Return type is a list of 'User' - resolve: (query, root, args, ctx, info) => { - return prisma.user.findMany({ - ...query, - // Include related posts in the query - }); - }, - }), - user: t.prismaField({ - type: 'User', - args: { - id: t.arg.string(), - }, - resolve: (query, root, args, ctx, info) => { - return prisma.user.findUnique({ - where: { - id: args.id?.toString(), - }, - }); - }, - }), - orders: t.prismaField({ - type: ['Order'], - resolve: (query, root, args, ctx, info) => { - return prisma.order.findMany({ - ...query, - }); - }, - }), - serviceFeedbacks: t.prismaField({ - type: ['ServiceFeedback'], - resolve: (query, root, args, ctx, info) => { - return prisma.serviceFeedback.findMany({ - ...query, - }); - }, - }), - documents: t.prismaField({ - type: ['UploadedDocument'], - resolve: (query, root, args, ctx, info) => { - return prisma.uploadedDocument.findMany({ - ...query, - }); - }, - }), - messages: t.prismaField({ - type: ['Message'], - resolve: (query, root, args, ctx, info) => { - return prisma.message.findMany({ - ...query, - }); - }, - }), - services: t.prismaField({ - type: ['Service'], - args: { - where: t.arg({ - type: builder.inputType('ServiceWhereInput', { - fields: (t) => ({ - // search by name contains - nameContain: t.string(), - // search by name starts with - nameStartsWith: t.string(), - // search by name ends with - nameEndsWith: t.string(), - }), - }), - }), - orderBy: t.arg({ - type: builder.inputType('ServiceOrderByInput', { - fields: (t) => ({ - rating: t.field({ - type: SortOrder, - }), - price: t.field({ - type: SortOrder, - }), - }), - }), - }), - cursor: t.arg({ - type: builder.inputType('ServiceWhereUniqueInput', { - fields: (t) => ({ - // Define fields to match your `ServiceWhereUniqueInput` structure. - name: t.string(), - }), - }), - }), - take: t.arg.int(), - skip: t.arg.int(), - }, - resolve: async (query, root, args, ctx, info) => { - return prisma.service.findMany({ - // handle where condition - where: { - name: { - contains: args.where?.nameContain as string | undefined, - startsWith: args.where?.nameStartsWith as string | undefined, - endsWith: args.where?.nameEndsWith as string | undefined, - }, - }, - // handle orderBy condition - orderBy: { - rating: args.orderBy?.rating as Prisma.SortOrder, - price: args.orderBy?.price as Prisma.SortOrder, - }, - // handle pagination - cursor: args.cursor as Prisma.ServiceWhereUniqueInput | undefined, - take: args.take as number | undefined, - skip: args.skip as number | undefined, - }); - }, - }), - service: t.prismaField({ - type: 'Service', - args: { - id: t.arg.string(), - }, - resolve: (query, root, args, ctx, info) => { - return prisma.service.findUnique({ - where: { - id: args.id?.toString(), - }, - }); - }, - }), - centers: t.prismaField({ - type: ['Center'], - resolve: (query, root, args, ctx, info) => { - return prisma.center.findMany({ - ...query, - }); - }, - }), - chatRooms: t.prismaField({ - type: ['ChatRoom'], - resolve: (query, root, args, ctx, info) => { - return prisma.chatRoom.findMany({ - ...query, - }); - }, - }), - centerStaffs: t.prismaField({ - type: ['CenterStaff'], - resolve: (query, root, args, ctx, info) => { - return prisma.centerStaff.findMany({ - ...query, - }); - }, - }), - workshopSubscriptions: t.prismaField({ - type: ['WorkshopSubscription'], - resolve: (query, root, args, ctx, info) => { - return prisma.workshopSubscription.findMany({ - ...query, - }); - }, - }), - workshops: t.prismaField({ - type: ['Workshop'], - resolve: (query, root, args, ctx, info) => { - return prisma.workshop.findMany({ - ...query, - }); - }, - }), - }), -}); - -// Mutation section -// builder.mutationType({ +// builder.prismaObject('User', { // fields: (t) => ({ -// createUser: t.prismaField({ +// id: t.exposeID('id'), +// name: t.exposeString('name'), +// email: t.exposeString('email'), +// phoneNumber: t.exposeString('phoneNumber'), +// role: t.exposeString('role'), +// createdAt: t.expose('createdAt', { +// type: 'DateTime', +// nullable: true, +// }), +// updatedAt: t.expose('updatedAt', { +// type: 'DateTime', +// nullable: true, +// }), +// order: t.relation('orders'), +// serviceFeedbacks: t.relation('serviceFeedbacks'), +// documents: t.relation('documents'), +// sendingMessage: t.relation('sendingMessage'), +// Service: t.relation('Service'), +// center: t.relation('center'), +// customerChatRoom: t.relation('customerChatRoom'), +// centerStaffChatRoom: t.relation('centerStaffChatRoom'), +// CenterStaff: t.relation('CenterStaff'), +// WorkshopSubscription: t.relation('WorkshopSubscription'), +// }), +// }); + +// builder.prismaObject('Order', { +// fields: (t) => ({ +// id: t.exposeID('id'), +// paymentId: t.exposeString('paymentId'), +// userId: t.exposeID('userId'), +// serviceId: t.exposeID('serviceId'), +// status: t.exposeString('status'), +// total: t.expose('total', { +// type: 'Int', +// }), +// createdAt: t.expose('createdAt', { +// type: 'DateTime', +// nullable: true, +// }), +// updatedAt: t.expose('updatedAt', { +// type: 'DateTime', +// nullable: true, +// }), +// user: t.relation('user'), +// payment: t.relation('payment'), +// service: t.relation('service'), +// refundTicket: t.relation('refundTicket'), +// }), +// }); + +// builder.prismaObject('ServiceFeedback', { +// fields: (t) => ({ +// id: t.exposeID('id'), +// userId: t.exposeID('userId'), +// }), +// }); + +// builder.prismaObject('UploadedDocument', { +// name: 'documents', +// fields: (t) => ({ +// id: t.exposeID('id'), +// userId: t.exposeID('userId'), +// }), +// }); + +// builder.prismaObject('Message', { +// name: 'sendingMessage', +// fields: (t) => ({ +// id: t.exposeID('id'), +// userId: t.exposeID('senderId'), +// }), +// }); + +// builder.prismaObject('Payment', { +// fields: (t) => ({ +// id: t.exposeID('id'), +// amount: t.expose('amount', { +// type: 'Float', +// }), +// status: t.exposeString('status'), +// createdAt: t.expose('createdAt', { +// type: 'DateTime', +// nullable: true, +// }), +// updatedAt: t.expose('updatedAt', { +// type: 'DateTime', +// nullable: true, +// }), +// order: t.relation('Order'), +// }), +// }); + +// builder.prismaObject('RefundTicket', { +// fields: (t) => ({ +// id: t.exposeID('id'), +// orderId: t.exposeID('orderId'), +// amount: t.expose('amount', { +// type: 'Float', +// }), +// status: t.exposeString('status'), +// createdAt: t.expose('createdAt', { +// type: 'DateTime', +// nullable: true, +// }), +// updatedAt: t.expose('updatedAt', { +// type: 'DateTime', +// nullable: true, +// }), +// order: t.relation('order'), +// }), +// }); + +// builder.prismaObject('Service', { +// fields: (t) => ({ +// id: t.exposeID('id'), +// name: t.exposeString('name'), +// description: t.exposeString('description'), +// centerId: t.exposeID('centerId'), +// price: t.expose('price', { +// type: 'Float', +// }), +// rating: t.expose('rating', { +// type: 'Float', +// }), +// createdAt: t.expose('createdAt', { +// type: 'DateTime', +// nullable: true, +// }), +// updatedAt: t.expose('updatedAt', { +// type: 'DateTime', +// nullable: true, +// }), +// managedBy: t.relation('managedBy'), +// feedbacks: t.relation('feedbacks'), +// order: t.relation('order'), +// workshop: t.relation('workshop'), +// milestone: t.relation('milestone'), +// schedule: t.relation('schedule'), +// serviceAndCategory: t.relation('serviceAndCategory'), +// workshopOrganization: t.relation('workshopOrganization'), +// user: t.relation('user'), +// userId: t.exposeID('userId'), +// }), +// }); + +// builder.prismaObject('Milestone', { +// fields: (t) => ({ +// id: t.exposeID('id'), +// serviceId: t.exposeID('serviceId'), +// }), +// }); + +// builder.prismaObject('Schedule', { +// fields: (t) => ({ +// id: t.exposeID('id'), +// serviceId: t.exposeID('serviceId'), +// }), +// }); + +// builder.prismaObject('ServiceAndCategory', { +// fields: (t) => ({ +// serviceId: t.exposeID('serviceId'), +// categoryId: t.exposeID('categoryId'), +// service: t.relation('service'), +// category: t.relation('category'), +// }), +// }); + +// builder.prismaObject('WorkshopOrganization', { +// fields: (t) => ({ +// serviceId: t.exposeID('serviceId'), +// workshopId: t.exposeID('workshopId'), +// service: t.relation('service'), +// workshop: t.relation('workshop'), +// }), +// }); + +// builder.prismaObject('Category', { +// fields: (t) => ({ +// id: t.exposeID('id'), +// }), +// }); + +// builder.prismaObject('Center', { +// fields: (t) => ({ +// id: t.exposeID('id'), +// userId: t.exposeID('centerOwnerId'), +// }), +// }); + +// builder.prismaObject('ChatRoom', { +// fields: (t) => ({ +// id: t.exposeID('id'), +// }), +// }); + +// builder.prismaObject('CenterStaff', { +// fields: (t) => ({ +// staffId: t.exposeID('staffId'), +// staff: t.relation('staff'), +// centerId: t.exposeID('centerId'), +// serviceId: t.exposeID('serviceId'), +// }), +// }); + +// builder.prismaObject('WorkshopSubscription', { +// fields: (t) => ({ +// userId: t.exposeID('userId'), +// workshopId: t.exposeID('workshopId'), +// user: t.relation('user'), +// workshop: t.relation('workshop'), +// }), +// }); + +// builder.prismaObject('Workshop', { +// fields: (t) => ({ +// id: t.exposeID('id'), +// }), +// }); + +// // Query section +// builder.queryType({ +// fields: (t) => ({ +// users: t.prismaField({ +// type: ['User'], // Return type is a list of 'User' +// resolve: (query, root, args, ctx, info) => { +// return prisma.user.findMany({ +// ...query, +// // Include related posts in the query +// }); +// }, +// }), +// user: t.prismaField({ // type: 'User', // args: { -// data: t.arg({ -// type: builder.inputType('UserCreateInput', { -// fields: (t) => ({ -// email: t.string({ required: true }), -// name: t.string(), -// // Include other fields as per your schema -// }), -// }), -// required: true, // Make the data argument required -// }), +// id: t.arg.string(), // }, // resolve: (query, root, args, ctx, info) => { -// if (!args.data) { -// throw new Error('Data input is required'); -// } - -// return prisma.user.create({ +// return prisma.user.findUnique({ +// where: { +// id: args.id?.toString(), +// }, +// }); +// }, +// }), +// orders: t.prismaField({ +// type: ['Order'], +// resolve: (query, root, args, ctx, info) => { +// return prisma.order.findMany({ +// ...query, +// }); +// }, +// }), +// serviceFeedbacks: t.prismaField({ +// type: ['ServiceFeedback'], +// resolve: (query, root, args, ctx, info) => { +// return prisma.serviceFeedback.findMany({ +// ...query, +// }); +// }, +// }), +// documents: t.prismaField({ +// type: ['UploadedDocument'], +// resolve: (query, root, args, ctx, info) => { +// return prisma.uploadedDocument.findMany({ +// ...query, +// }); +// }, +// }), +// messages: t.prismaField({ +// type: ['Message'], +// resolve: (query, root, args, ctx, info) => { +// return prisma.message.findMany({ +// ...query, +// }); +// }, +// }), +// services: t.prismaField({ +// type: ['Service'], +// args: { +// where: t.arg({ +// type: builder.inputType('ServiceWhereInput', { +// fields: (t) => ({ +// // search by name contains +// nameContain: t.string(), +// // search by name starts with +// nameStartsWith: t.string(), +// // search by name ends with +// nameEndsWith: t.string(), +// }), +// }), +// }), +// orderBy: t.arg({ +// type: builder.inputType('ServiceOrderByInput', { +// fields: (t) => ({ +// rating: t.field({ +// type: SortOrder, +// }), +// price: t.field({ +// type: SortOrder, +// }), +// }), +// }), +// }), +// cursor: t.arg({ +// type: builder.inputType('ServiceWhereUniqueInput', { +// fields: (t) => ({ +// // Define fields to match your `ServiceWhereUniqueInput` structure. +// name: t.string(), +// }), +// }), +// }), +// take: t.arg.int(), +// skip: t.arg.int(), +// }, +// resolve: async (query, root, args, ctx, info) => { +// return prisma.service.findMany({ +// // handle where condition +// where: { +// name: { +// contains: args.where?.nameContain as string | undefined, +// startsWith: args.where?.nameStartsWith as string | undefined, +// endsWith: args.where?.nameEndsWith as string | undefined, +// }, +// }, +// // handle orderBy condition +// orderBy: { +// rating: args.orderBy?.rating as Prisma.SortOrder, +// price: args.orderBy?.price as Prisma.SortOrder, +// }, +// // handle pagination +// cursor: args.cursor as Prisma.ServiceWhereUniqueInput | undefined, +// take: args.take as number | undefined, +// skip: args.skip as number | undefined, +// }); +// }, +// }), +// service: t.prismaField({ +// type: 'Service', +// args: { +// id: t.arg.string(), +// }, +// resolve: (query, root, args, ctx, info) => { +// return prisma.service.findUnique({ +// where: { +// id: args.id?.toString(), +// }, +// }); +// }, +// }), +// centers: t.prismaField({ +// type: ['Center'], +// resolve: (query, root, args, ctx, info) => { +// return prisma.center.findMany({ +// ...query, +// }); +// }, +// }), +// chatRooms: t.prismaField({ +// type: ['ChatRoom'], +// resolve: (query, root, args, ctx, info) => { +// return prisma.chatRoom.findMany({ +// ...query, +// }); +// }, +// }), +// centerStaffs: t.prismaField({ +// type: ['CenterStaff'], +// resolve: (query, root, args, ctx, info) => { +// return prisma.centerStaff.findMany({ +// ...query, +// }); +// }, +// }), +// workshopSubscriptions: t.prismaField({ +// type: ['WorkshopSubscription'], +// resolve: (query, root, args, ctx, info) => { +// return prisma.workshopSubscription.findMany({ +// ...query, +// }); +// }, +// }), +// workshops: t.prismaField({ +// type: ['Workshop'], +// resolve: (query, root, args, ctx, info) => { +// return prisma.workshop.findMany({ // ...query, -// data: args.data as Prisma.UserCreateInput, // Explicit type casting to match Prisma's expectation // }); // }, // }), // }), // }); -// Subscription section -// builder.subscriptionType({ -// fields: (t) => ({ -// userCreated: t.prismaField({ -// type: 'User', -// subscribe: (query, root, args, ctx, info) => { -// return prisma.$subscribe.user({ -// mutation_in: ['CREATED'], -// }); -// }, -// resolve: (payload) => { -// return payload; -// }, -// }), -// }), -// }); +// // Mutation section +// // builder.mutationType({ +// // fields: (t) => ({ +// // createUser: t.prismaField({ +// // type: 'User', +// // args: { +// // data: t.arg({ +// // type: builder.inputType('UserCreateInput', { +// // fields: (t) => ({ +// // email: t.string({ required: true }), +// // name: t.string(), +// // // Include other fields as per your schema +// // }), +// // }), +// // required: true, // Make the data argument required +// // }), +// // }, +// // resolve: (query, root, args, ctx, info) => { +// // if (!args.data) { +// // throw new Error('Data input is required'); +// // } -export const schema = builder.toSchema(); +// // return prisma.user.create({ +// // ...query, +// // data: args.data as Prisma.UserCreateInput, // Explicit type casting to match Prisma's expectation +// // }); +// // }, +// // }), +// // }), +// // }); + +// // Subscription section +// // builder.subscriptionType({ +// // fields: (t) => ({ +// // userCreated: t.prismaField({ +// // type: 'User', +// // subscribe: (query, root, args, ctx, info) => { +// // return prisma.$subscribe.user({ +// // mutation_in: ['CREATED'], +// // }); +// // }, +// // resolve: (payload) => { +// // return payload; +// // }, +// // }), +// // }), +// // }); + +// export const schema = builder.toSchema(); diff --git a/src/graphql/subscription.graphql.ts b/src/graphql/subscription.graphql.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/main.ts b/src/main.ts index 1f0f43b..4007938 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,7 +22,8 @@ async function bootstrap() { document.paths['/graphql'] = { get: { summary: 'GraphQL Playground', - description: 'Access the GraphQL Playground to interact with the GraphQL API.', + description: + 'Access the GraphQL Playground to interact with the GraphQL API.', responses: { '200': { description: 'GraphQL Playground', @@ -31,6 +32,7 @@ async function bootstrap() { }, }; - await app.listen(3000); + const port = process.env.LISTEN_PORT ?? 3000; // Default to 3000 if LISTEN_PORT is not set + await app.listen(port); } bootstrap(); diff --git a/src/middlewares/graphql.middleware.ts b/src/middlewares/graphql.middleware.ts index 9370761..5d042bf 100644 --- a/src/middlewares/graphql.middleware.ts +++ b/src/middlewares/graphql.middleware.ts @@ -5,7 +5,10 @@ import { Request, Response, NextFunction } from 'express'; export class GraphQLValidationMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { // Only handle POST requests - if (req.method === 'POST' && req.headers['content-type'] === 'application/json') { + if ( + req.method === 'POST' && + req.headers['content-type'] === 'application/json' + ) { const { query, mutation, subscription } = req.body; // If none of these are present, return a custom error response @@ -13,7 +16,8 @@ export class GraphQLValidationMiddleware implements NestMiddleware { return res.status(400).json({ errors: [ { - message: 'Must provide a valid GraphQL query, mutation, or subscription.', + message: + 'Must provide a valid GraphQL query, mutation, or subscription.', }, ], }); diff --git a/src/prisma/prisma.module.ts b/src/prisma/prisma.module.ts index 7d69436..7207426 100644 --- a/src/prisma/prisma.module.ts +++ b/src/prisma/prisma.module.ts @@ -1,8 +1,9 @@ -import { Module } from '@nestjs/common'; +import { Global, Module } from '@nestjs/common'; import { PrismaService } from './prisma.service'; +@Global() @Module({ providers: [PrismaService], - exports: [PrismaService], // Export PrismaService so other modules can use it + exports: [PrismaService], }) export class PrismaModule {} diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts index bb6565f..a7da05b 100644 --- a/src/prisma/prisma.service.ts +++ b/src/prisma/prisma.service.ts @@ -1,13 +1,32 @@ -import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; +import { + ConsoleLogger, + INestApplication, + Injectable, + OnModuleInit, +} from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() -export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { - async onModuleInit() { - await this.$connect(); +export class PrismaService extends PrismaClient implements OnModuleInit { + private readonly logger = new ConsoleLogger(PrismaService.name); + + constructor() { + super({ + log: ['query', 'info', 'warn', 'error'], + }); } - async onModuleDestroy() { - await this.$disconnect(); + async onModuleInit() { + this.logger.log('Try to connect database...'); + + await this.$connect(); + this.logger.log('Connected.'); + } + + async enableShutdownHooks(app: INestApplication) { + this.$on('beforeExit' as never, async () => { + this.logger.log('Wait for application closing...'); + await app.close(); + }); } } diff --git a/src/restful/restful.controller.ts b/src/restful/restful.controller.ts index 99c4305..daa0a98 100644 --- a/src/restful/restful.controller.ts +++ b/src/restful/restful.controller.ts @@ -1,4 +1,12 @@ -import { Controller, Get, Post, Put, Delete, Param, Body } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Put, + Delete, + Param, + Body, +} from '@nestjs/common'; import { RestfulService } from './restful.service'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; @@ -7,40 +15,52 @@ import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; export class RestfulController { constructor(private readonly restfulService: RestfulService) {} - @Get() - @ApiOperation({ summary: 'Get all items' }) - @ApiResponse({ status: 200, description: 'Returns all items.' }) - getAllItems() { - return this.restfulService.getAllItems(); - } + // @Get() + // @ApiOperation({ summary: 'Get all items' }) + // @ApiResponse({ status: 200, description: 'Returns all items.' }) + // getAllItems() { + // return this.restfulService.getAllItems(); + // } - @Get(':id') - @ApiOperation({ summary: 'Get an item by ID' }) - @ApiResponse({ status: 200, description: 'Returns the item with the given ID.' }) - getItem(@Param('id') id: string) { - return this.restfulService.getItem(id); - } + // @Get(':id') + // @ApiOperation({ summary: 'Get an item by ID' }) + // @ApiResponse({ + // status: 200, + // description: 'Returns the item with the given ID.', + // }) + // getItem(@Param('id') id: string) { + // return this.restfulService.getItem(id); + // } - @Post() - @ApiOperation({ summary: 'Create a new item' }) - @ApiResponse({ status: 201, description: 'The item has been successfully created.' }) - createItem(@Body() createDto: any) { - return this.restfulService.createItem(createDto); - } + // @Post() + // @ApiOperation({ summary: 'Create a new item' }) + // @ApiResponse({ + // status: 201, + // description: 'The item has been successfully created.', + // }) + // createItem(@Body() createDto: any) { + // return this.restfulService.createItem(createDto); + // } - @Put(':id') - @ApiOperation({ summary: 'Update an item' }) - @ApiResponse({ status: 200, description: 'The item has been successfully updated.' }) - updateItem(@Param('id') id: string, @Body() updateDto: any) { - return this.restfulService.updateItem(id, updateDto); - } + // @Put(':id') + // @ApiOperation({ summary: 'Update an item' }) + // @ApiResponse({ + // status: 200, + // description: 'The item has been successfully updated.', + // }) + // updateItem(@Param('id') id: string, @Body() updateDto: any) { + // return this.restfulService.updateItem(id, updateDto); + // } - @Delete(':id') - @ApiOperation({ summary: 'Delete an item' }) - @ApiResponse({ status: 200, description: 'The item has been successfully deleted.' }) - deleteItem(@Param('id') id: string) { - return this.restfulService.deleteItem(id); - } + // @Delete(':id') + // @ApiOperation({ summary: 'Delete an item' }) + // @ApiResponse({ + // status: 200, + // description: 'The item has been successfully deleted.', + // }) + // deleteItem(@Param('id') id: string) { + // return this.restfulService.deleteItem(id); + // } } export default RestfulController; diff --git a/src/restful/restful.module.ts b/src/restful/restful.module.ts index dbe92f1..00fc437 100644 --- a/src/restful/restful.module.ts +++ b/src/restful/restful.module.ts @@ -6,4 +6,4 @@ import { RestfulService } from './restful.service'; controllers: [RestfulController], providers: [RestfulService], }) -export class RestfulModule {} \ No newline at end of file +export class RestfulModule {} diff --git a/src/restful/restful.service.ts b/src/restful/restful.service.ts index f8fd0f2..eb6c581 100644 --- a/src/restful/restful.service.ts +++ b/src/restful/restful.service.ts @@ -1,31 +1,4 @@ import { Injectable } from '@nestjs/common'; @Injectable() -export class RestfulService { - getAllItems() { - // Implementation to get all items - return []; // Replace with actual logic - } - - getItem(id: string) { - // Implementation to get a single item by id - return {}; // Replace with actual logic - } - - createItem(createDto: any) { - // Implementation to create a new item - return {}; // Replace with actual logic - } - - updateItem(id: string, updateDto: any) { - // Implementation to update an item by id - return {}; // Replace with actual logic - } - - deleteItem(id: string) { - // Implementation to delete an item by id - return {}; // Replace with actual logic - } -} - -export default RestfulService; +export class RestfulService {} diff --git a/src/service/service.module.ts b/src/service/service.module.ts new file mode 100644 index 0000000..3e78ead --- /dev/null +++ b/src/service/service.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class ServiceModule {} diff --git a/src/types/scalars/common.scalar.ts b/src/types/scalars/common.scalar.ts new file mode 100644 index 0000000..4e8512e --- /dev/null +++ b/src/types/scalars/common.scalar.ts @@ -0,0 +1 @@ +import { DateTimeResolver } from 'graphql-scalars'; diff --git a/src/user/user.module.ts b/src/user/user.module.ts new file mode 100644 index 0000000..fe13202 --- /dev/null +++ b/src/user/user.module.ts @@ -0,0 +1,9 @@ +import { Global, Module } from '@nestjs/common'; +import { UserSchema } from './user.schema'; + +@Global() +@Module({ + providers: [UserSchema], + exports: [UserSchema], +}) +export class UserModule {} diff --git a/src/user/user.schema.ts b/src/user/user.schema.ts new file mode 100644 index 0000000..d43c1e3 --- /dev/null +++ b/src/user/user.schema.ts @@ -0,0 +1,59 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { + Pothos, + PothosRef, + PothosSchema, + SchemaBuilderToken, +} from '@smatch-corp/nestjs-pothos'; +import { Builder } from '../graphql/graphql.builder'; +import { PrismaService } from 'src/prisma/prisma.service'; + +@Injectable() +export class UserSchema extends PothosSchema { + constructor( + @Inject(SchemaBuilderToken) private readonly builder: Builder, + private readonly prisma: PrismaService, + ) { + super(); + } + + // Types section + @PothosRef() + user(): any { + return this.builder.prismaObject('User', { + fields: (t) => ({ + id: t.exposeID('id'), + name: t.exposeString('name'), + email: t.exposeString('email'), + phoneNumber: t.exposeString('phoneNumber'), + oauthToken: t.exposeString('oauthToken'), + role: t.exposeString('role'), + createdAt: t.expose('createdAt', { + type: 'Date', + nullable: true, + }), + updatedAt: t.expose('updatedAt', { + type: 'Date', + nullable: true, + }), + }), + }); + } + + // Query section + @Pothos() + init(): void { + this.builder.queryFields((t) => ({ + users: t.prismaField({ + type: 'User', + resolve: async (query) => this.prisma.user.findMany() as any, + }), + })); + } + + // Mutation section + @Pothos() + createUser() { + return 'createUser'; + } +} diff --git a/src/users/dto/create-user.dto.ts b/src/users/dto/create-user.dto.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/users/dto/update-user.dto.ts b/src/users/dto/update-user.dto.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/users/users.module.ts b/src/users/users.module.ts deleted file mode 100644 index 44aa801..0000000 --- a/src/users/users.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { UsersService } from './users.service'; -import { UsersResolver } from './users.resolver'; -import { PrismaModule } from '../prisma/prisma.module'; // PrismaModule to access database - -@Module({ - imports: [PrismaModule], - providers: [UsersService, UsersResolver], -}) -export class UsersModule {} diff --git a/src/users/users.resolver.ts b/src/users/users.resolver.ts deleted file mode 100644 index bbd0bdf..0000000 --- a/src/users/users.resolver.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Resolver, Query } from '@nestjs/graphql'; -import { UsersService } from './users.service'; - -@Resolver('User') -export class UsersResolver { - constructor(private readonly usersService: UsersService) {} -} diff --git a/src/users/users.service.ts b/src/users/users.service.ts deleted file mode 100644 index 78338c1..0000000 --- a/src/users/users.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Injectable } from '@nestjs/common'; -@Injectable() -export class UsersService { - async findOneById(id: string) { - return { id }; - } -}