update service status

This commit is contained in:
2024-10-23 15:43:50 +07:00
parent 7e55f4093b
commit 2872ac69ef
14 changed files with 212 additions and 32 deletions

22
package-lock.json generated
View File

@@ -25,6 +25,8 @@
"@nestjs/swagger": "^7.4.2", "@nestjs/swagger": "^7.4.2",
"@pothos/core": "^4.2.0", "@pothos/core": "^4.2.0",
"@pothos/plugin-add-graphql": "^4.1.0", "@pothos/plugin-add-graphql": "^4.1.0",
"@pothos/plugin-authz": "^3.5.10",
"@pothos/plugin-errors": "^4.2.0",
"@pothos/plugin-prisma": "^4.2.1", "@pothos/plugin-prisma": "^4.2.1",
"@pothos/plugin-prisma-utils": "^1.2.0", "@pothos/plugin-prisma-utils": "^1.2.0",
"@pothos/plugin-relay": "^4.3.0", "@pothos/plugin-relay": "^4.3.0",
@@ -4860,6 +4862,26 @@
"graphql": ">=16.6.0" "graphql": ">=16.6.0"
} }
}, },
"node_modules/@pothos/plugin-authz": {
"version": "3.5.10",
"resolved": "https://registry.npmjs.org/@pothos/plugin-authz/-/plugin-authz-3.5.10.tgz",
"integrity": "sha512-HIrz72+KnbvJjRKEtuy+c/J69jxET/yiSKLx/q3IV8FC4GyPKnPxlwuGlMczxlpK6+UG0gE0ziCAwY6LWEd2Yg==",
"license": "ISC",
"peerDependencies": {
"@graphql-authz/core": "*",
"graphql": ">=15.1.0"
}
},
"node_modules/@pothos/plugin-errors": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@pothos/plugin-errors/-/plugin-errors-4.2.0.tgz",
"integrity": "sha512-Yt87hn7z0+1bv7bhrJCD5GH9+NTO/5CJEkbebyM4scnnmQ0rpqyO/yViIirtzCXk/NKBHEiLrthL2frQxUmJ5w==",
"license": "ISC",
"peerDependencies": {
"@pothos/core": "*",
"graphql": ">=16.6.0"
}
},
"node_modules/@pothos/plugin-prisma": { "node_modules/@pothos/plugin-prisma": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/@pothos/plugin-prisma/-/plugin-prisma-4.2.1.tgz", "resolved": "https://registry.npmjs.org/@pothos/plugin-prisma/-/plugin-prisma-4.2.1.tgz",

View File

@@ -43,6 +43,8 @@
"@nestjs/swagger": "^7.4.2", "@nestjs/swagger": "^7.4.2",
"@pothos/core": "^4.2.0", "@pothos/core": "^4.2.0",
"@pothos/plugin-add-graphql": "^4.1.0", "@pothos/plugin-add-graphql": "^4.1.0",
"@pothos/plugin-authz": "^3.5.10",
"@pothos/plugin-errors": "^4.2.0",
"@pothos/plugin-prisma": "^4.2.1", "@pothos/plugin-prisma": "^4.2.1",
"@pothos/plugin-prisma-utils": "^1.2.0", "@pothos/plugin-prisma-utils": "^1.2.0",
"@pothos/plugin-relay": "^4.3.0", "@pothos/plugin-relay": "^4.3.0",

View File

@@ -18,11 +18,17 @@ import SmartSubscriptionPlugin, {
subscribeOptionsFromIterator, subscribeOptionsFromIterator,
} from '@pothos/plugin-smart-subscriptions'; } from '@pothos/plugin-smart-subscriptions';
import RelayPlugin from '@pothos/plugin-relay'; import RelayPlugin from '@pothos/plugin-relay';
import ErrorsPlugin from '@pothos/plugin-errors';
import AuthzPlugin from '@pothos/plugin-authz';
import { User } from '@prisma/client';
// import { rules } from '../common/graphql/common.graphql.auth-rule';
export interface SchemaContext { export interface SchemaContext {
req: Request; req: Request;
res: Response; res: Response;
generator: PrismaCrudGenerator<BuilderTypes>; me: User;
pubSub: PubSub; pubSub: PubSub;
generator: PrismaCrudGenerator<BuilderTypes>;
} }
export interface SchemaBuilderOption { export interface SchemaBuilderOption {
@@ -32,6 +38,7 @@ export interface SchemaBuilderOption {
Connection: { Connection: {
totalCount: number | (() => number | Promise<number>); totalCount: number | (() => number | Promise<number>);
}; };
// AuthZRule: keyof typeof rules;
Scalars: { Scalars: {
DateTime: { DateTime: {
Input: Date; Input: Date;
@@ -60,6 +67,8 @@ export class Builder extends SchemaBuilder<SchemaBuilderOption> {
SimpleObjectPlugin, SimpleObjectPlugin,
SmartSubscriptionPlugin, SmartSubscriptionPlugin,
RelayPlugin, RelayPlugin,
ErrorsPlugin,
AuthzPlugin,
], ],
smartSubscriptions: { smartSubscriptions: {
debounceDelay: 1000, debounceDelay: 1000,
@@ -75,6 +84,9 @@ export class Builder extends SchemaBuilder<SchemaBuilderOption> {
onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn', onUnusedQuery: process.env.NODE_ENV === 'production' ? null : 'warn',
dmmf: getDatamodel(), dmmf: getDatamodel(),
}, },
errors: {
defaultTypes: [],
},
}); });
this.generator = new PrismaCrudGenerator<BuilderTypes>(this); this.generator = new PrismaCrudGenerator<BuilderTypes>(this);
this.addScalarType('DateTime', DateTimeResolver); this.addScalarType('DateTime', DateTimeResolver);
@@ -83,7 +95,7 @@ export class Builder extends SchemaBuilder<SchemaBuilderOption> {
this.queryType({}); this.queryType({});
this.mutationType({}); this.mutationType({});
// this.subscriptionType({}); this.subscriptionType({});
this.globalConnectionField('totalCount', (t) => this.globalConnectionField('totalCount', (t) =>
t.int({ t.int({

View File

@@ -6,6 +6,7 @@ import { PothosApolloDriver } from '@smatch-corp/nestjs-pothos-apollo-driver';
import { Builder } from './graphql.builder'; import { Builder } from './graphql.builder';
import { PrismaService } from '../Prisma/prisma.service'; import { PrismaService } from '../Prisma/prisma.service';
import { GraphQLValidationMiddleware } from '../middlewares/graphql.middleware'; import { GraphQLValidationMiddleware } from '../middlewares/graphql.middleware';
import { CommonModule } from '../common/common.module';
import { PrismaModule } from '../Prisma/prisma.module'; import { PrismaModule } from '../Prisma/prisma.module';
import { UserModule } from '../User/user.module'; import { UserModule } from '../User/user.module';
import { CenterModule } from '../Center/center.module'; import { CenterModule } from '../Center/center.module';
@@ -33,6 +34,7 @@ import { ManagedServiceModule } from '../ManagedService/managedservice.module';
@Global() @Global()
@Module({ @Module({
imports: [ imports: [
CommonModule,
PrismaModule, PrismaModule,
UserModule, UserModule,
CenterModule, CenterModule,
@@ -64,9 +66,9 @@ import { ManagedServiceModule } from '../ManagedService/managedservice.module';
GraphQLModule.forRoot<ApolloDriverConfig>({ GraphQLModule.forRoot<ApolloDriverConfig>({
driver: PothosApolloDriver, driver: PothosApolloDriver,
path: process.env.API_PATH + '/graphql', path: process.env.API_PATH + '/graphql',
debug: process.env.NODE_ENV === 'development', debug: process.env.NODE_ENV === 'development' || false,
playground: true, playground: process.env.NODE_ENV === 'development' || false,
introspection: true, introspection: process.env.NODE_ENV === 'development' || false,
installSubscriptionHandlers: true, installSubscriptionHandlers: true,
subscriptions: { subscriptions: {
'graphql-ws': true, 'graphql-ws': true,

View File

@@ -79,18 +79,66 @@ export class MessageSchema extends PothosSchema {
}); });
}, },
}), }),
messagesByChatRoomId: t.prismaField({
type: [this.message()],
description: 'Retrieve a list of messages by chat room ID.',
args: this.builder.generator.findManyArgs('Message'),
resolve: async (query, root, args) => {
return await this.prisma.message.findMany({
...query,
where: args.filter ?? undefined,
});
},
}),
})); }));
// mutations // mutations
this.builder.mutationFields((t) => ({
sendMessage: t.prismaField({
type: this.message(),
description: 'Send a message to a chat room.',
args: {
input: t.arg({
type: this.builder.generator.getCreateInput('Message'),
description: 'The message to send.',
required: true,
}),
},
resolve: async (query, root, args, ctx, info) => {
const message = await this.prisma.message.create({
...query,
data: args.input,
});
ctx.pubSub.publish('MESSAGE_SENT', message);
return message;
},
}),
}));
// subscriptions // subscriptions
// this.builder.subscriptionFields((t) => ({ /* The code snippet `subscriptions` is currently commented out in the provided TypeScript class. It
// messageSent: t.field({ appears to be a placeholder or a section where subscription-related logic or fields could be
// subscribe: (_parent, _args, ctx) => { defined. In GraphQL, subscriptions are used to listen for real-time events or changes in data
// return ctx.pubSub.asyncIterator('MESSAGE_SENT'); and receive updates when those events occur. */
// },
// resolve: (payload) => payload as any, this.builder.subscriptionFields((t) => ({
// }), messageSent: t.field({
// })); subscribe: (_, __, ctx) => {
return {
[Symbol.asyncIterator]: () =>
ctx.pubSub.asyncIterator('MESSAGE_SENT'),
};
},
type: this.message(), // Add the type property
resolve: (payload) =>
payload as {
message: 'Json';
id: string;
senderId: string;
chatRoomId: string;
sentAt: Date;
},
}),
}));
} }
} }

View File

@@ -36,7 +36,24 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() { async onModuleInit() {
this.logger.log('Try to connect database...'); this.logger.log('Try to connect database...');
for (let attempt = 1; attempt <= 3; attempt++) {
try {
await this.$connect(); await this.$connect();
break; // Exit loop if connection is successful
} catch (error) {
if (attempt < 3) {
this.logger.warn(
`Connection attempt ${attempt} failed. Retrying in 5000ms...`,
);
await new Promise((resolve) => setTimeout(resolve, 5000));
} else {
this.logger.error(
'Failed to connect to the database after 3 attempts.',
);
throw error; // Rethrow the error after 3 failed attempts
}
}
}
this.logger.log('Connected.'); this.logger.log('Connected.');
} }

View File

@@ -8,6 +8,7 @@ import {
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 { MinioService } from '../Minio/minio.service'; import { MinioService } from '../Minio/minio.service';
import { ServiceStatus } from '@prisma/client';
@Injectable() @Injectable()
export class ServiceSchema extends PothosSchema { export class ServiceSchema extends PothosSchema {
constructor( constructor(
@@ -55,6 +56,11 @@ export class ServiceSchema extends PothosSchema {
imageFileUrl: t.exposeString('imageFileUrl', { imageFileUrl: t.exposeString('imageFileUrl', {
description: 'The URL of the image file for the service.', description: 'The URL of the image file for the service.',
}), }),
status: t.expose('status', {
type: ServiceStatus,
nullable: true,
description: 'The status of the service.',
}),
createdAt: t.expose('createdAt', { createdAt: t.expose('createdAt', {
type: 'DateTime', type: 'DateTime',
nullable: true, nullable: true,
@@ -125,6 +131,7 @@ export class ServiceSchema extends PothosSchema {
description: description:
'Retrieve a list of services with optional filtering, ordering, and pagination.', 'Retrieve a list of services with optional filtering, ordering, and pagination.',
type: [this.service()], type: [this.service()],
args: this.builder.generator.findManyArgs('Service'), args: this.builder.generator.findManyArgs('Service'),
resolve: async (query, root, args, ctx, info) => { resolve: async (query, root, args, ctx, info) => {
return await this.prisma.service.findMany({ return await this.prisma.service.findMany({

View File

@@ -8,6 +8,9 @@ import {
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 { clerkClient } from '@clerk/express'; import { clerkClient } from '@clerk/express';
import type { Session } from '@clerk/express';
import { UnauthorizedException } from '@nestjs/common';
@Injectable() @Injectable()
export class UserSchema extends PothosSchema { export class UserSchema extends PothosSchema {
constructor( constructor(
@@ -89,17 +92,51 @@ export class UserSchema extends PothosSchema {
@Pothos() @Pothos()
init(): void { init(): void {
this.builder.queryFields((t) => ({ this.builder.queryFields((t) => ({
// me: t.prismaField({ session: t.field({
// description: 'Retrieve the current user.', type: 'Json',
// type: this.user(), args: {
// resolve: async (query, root, args, ctx, info) => { sessionId: t.arg({ type: 'String', required: true }),
// const sessionId = ctx.req.headers.sessionId; },
// const userId = await clerkClient.users.getUser() resolve: async (_, { sessionId }) => {
// return await this.prisma.user.findUnique({ const session = await clerkClient.sessions.getSession(sessionId);
// where: { id: ctx.req.headers.authorization }, return JSON.parse(JSON.stringify(session));
// }); },
// }, }),
// }), newSession: t.field({
type: 'String',
args: {
userId: t.arg({
type: 'String',
required: true,
}),
},
resolve: async (_, { userId }) => {
const session = await clerkClient.signInTokens.createSignInToken({
userId,
expiresInSeconds: 60 * 60 * 24,
});
return session.id;
},
}),
me: t.prismaField({
description: 'Retrieve the current user.',
type: this.user(),
resolve: async (query, root, args, ctx, info) => {
const sessionCookie = ctx.req.headers.cookie
?.split('; ')
.find((row) => row.startsWith('__session='))
?.split('=')[1];
if (!sessionCookie)
throw new UnauthorizedException({
message: 'No session cookie found',
});
const session = await clerkClient.sessions.getSession(sessionCookie);
if (!session) throw new UnauthorizedException();
return await this.prisma.user.findUnique({
where: { id: session.userId },
});
},
}),
users: t.prismaField({ users: t.prismaField({
description: description:

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { CommonGraphqlError } from './graphql/common.graphql.error';
@Module({
imports: [],
providers: [CommonGraphqlError],
exports: [CommonGraphqlError],
})
export class CommonModule {}

View File

@@ -0,0 +1,24 @@
import { Inject, Injectable } from '@nestjs/common';
import { Builder } from '../../Graphql/graphql.builder';
import {
PothosRef,
PothosSchema,
SchemaBuilderToken,
} from '@smatch-corp/nestjs-pothos';
@Injectable()
export class CommonGraphqlError extends PothosSchema {
constructor(@Inject(SchemaBuilderToken) private readonly builder: Builder) {
super();
}
@PothosRef()
error() {
return this.builder.objectType(Error, {
name: 'Error',
fields: (t) => ({
message: t.exposeString('message'),
}),
});
}
}

View File

@@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.js'; import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.js';
import { clerkMiddleware } from '@clerk/express';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
async function bootstrap() { async function bootstrap() {
@@ -11,11 +12,7 @@ async function bootstrap() {
app.enableCors({ app.enableCors({
origin: corsOrigin, origin: corsOrigin,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: [ allowedHeaders: ['Content-Type', '*', 'x-apollo-operation-name'],
'Content-Type',
'Authorization',
'x-apollo-operation-name',
],
credentials: true, credentials: true,
}); });
@@ -46,6 +43,9 @@ async function bootstrap() {
}, },
}; };
// clerk middleware
app.use(clerkMiddleware({}));
// graphql upload // graphql upload
app.use( app.use(
graphqlUploadExpress({ graphqlUploadExpress({

File diff suppressed because one or more lines are too long