day len server theo loi Khoi noi, toi xin tuyen bo mien tru trach nhiem

This commit is contained in:
2024-11-05 15:02:53 +07:00
parent 0f68b51d75
commit 56ba2808c8
22 changed files with 482 additions and 63 deletions

225
package-lock.json generated
View File

@@ -15,6 +15,7 @@
"@graphql-codegen/typescript": "^4.0.9",
"@graphql-codegen/typescript-operations": "^4.2.3",
"@graphql-codegen/typescript-resolvers": "^4.2.1",
"@livekit/rtc-node": "^0.11.0",
"@nestjs-modules/mailer": "^2.0.2",
"@nestjs/apollo": "^12.2.0",
"@nestjs/axios": "^3.1.1",
@@ -53,6 +54,7 @@
"graphql-ws": "^5.16.0",
"ioredis": "^5.4.1",
"jsonwebtoken": "^9.0.2",
"livekit-server-sdk": "^2.7.3",
"luxon": "^3.5.0",
"minio": "^8.0.1",
"nestjs-minio": "^2.6.2",
@@ -88,6 +90,7 @@
"@types/nodemailer": "^6.4.16",
"@types/passport-jwt": "^4.0.1",
"@types/supertest": "^6.0.0",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.5.12",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
@@ -1913,6 +1916,12 @@
"node": ">=14.21.3"
}
},
"node_modules/@bufbuild/protobuf": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.2.tgz",
"integrity": "sha512-UNtPCbrwrenpmrXuRwn9jYpPoweNXj8X5sMvYgsqYyaH8jQ6LfUJSk3dJLnBK+6sfYPrF4iAIo5sd5HQ+tg75A==",
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@clerk/backend": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-1.15.2.tgz",
@@ -4139,6 +4148,134 @@
"dev": true,
"license": "MIT"
},
"node_modules/@livekit/mutex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@livekit/mutex/-/mutex-1.0.0.tgz",
"integrity": "sha512-aiUhoThBNF9UyGTxEURFzJLhhPLIVTnQiEVMjRhPnfHNKLfo2JY9xovHKIus7B78UD5hsP6DlgpmAsjrz4U0Iw==",
"license": "Apache-2.0"
},
"node_modules/@livekit/protocol": {
"version": "1.27.1",
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.27.1.tgz",
"integrity": "sha512-ISEp7uWdV82mtCR1eyHFTzdRZTVbe2+ZztjmjiMPzR/KPrI1Ma/u5kLh87NNuY3Rn8wv1VlEvGHHsFjQ+dKVUw==",
"license": "Apache-2.0",
"dependencies": {
"@bufbuild/protobuf": "^1.10.0"
}
},
"node_modules/@livekit/protocol/node_modules/@bufbuild/protobuf": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz",
"integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==",
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@livekit/rtc-node": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@livekit/rtc-node/-/rtc-node-0.11.0.tgz",
"integrity": "sha512-HRnoGsNJC+okhzV9IlljaWskOYIGi1xLD9NzwJwRkzYRdPFwEMf5QU5bmELNCfwDw8bMOpMQB4M8wIkVhCrcxg==",
"license": "Apache-2.0",
"dependencies": {
"@bufbuild/protobuf": "^2.2.0",
"@livekit/mutex": "^1.0.0",
"@livekit/typed-emitter": "^3.0.0"
},
"engines": {
"node": ">= 18"
},
"optionalDependencies": {
"@livekit/rtc-node-darwin-arm64": "0.11.0",
"@livekit/rtc-node-darwin-x64": "0.11.0",
"@livekit/rtc-node-linux-arm64-gnu": "0.11.0",
"@livekit/rtc-node-linux-x64-gnu": "0.11.0",
"@livekit/rtc-node-win32-x64-msvc": "0.11.0"
}
},
"node_modules/@livekit/rtc-node-darwin-arm64": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@livekit/rtc-node-darwin-arm64/-/rtc-node-darwin-arm64-0.11.0.tgz",
"integrity": "sha512-ZepFYFO984NnkM6h29KvGagGpQ7/Q/7WdtVr68rhque8YyP/SLPfW1AA73jSn7+FwIVQiaUUERtrOlSlEWe8hg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@livekit/rtc-node-darwin-x64": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@livekit/rtc-node-darwin-x64/-/rtc-node-darwin-x64-0.11.0.tgz",
"integrity": "sha512-JsqwV6XVDFYFUV67QZ3CFxD3OeK6+eZZzWuDjR7ELam5cDyqYjqi2bWEVDPnv+4zA+w6Xx9g7IPRDzkMdA60kg==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@livekit/rtc-node-linux-arm64-gnu": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@livekit/rtc-node-linux-arm64-gnu/-/rtc-node-linux-arm64-gnu-0.11.0.tgz",
"integrity": "sha512-3d1WtyMKhQwLbiZQuw6MnpazPq2nOSChFiePoIuZhcrDyZrN5Tte/QBkIL8G+dCXwyD1jfknTL4D0FeI+GRMww==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@livekit/rtc-node-linux-x64-gnu": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@livekit/rtc-node-linux-x64-gnu/-/rtc-node-linux-x64-gnu-0.11.0.tgz",
"integrity": "sha512-iwi1FpnJgI0JNq1pNe6jgqIXa16bujBQPhEF0ul47uyp7bOnSpVzVAnbAdkrACnaalB106YWZ6cc1L37gACYPg==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@livekit/rtc-node-win32-x64-msvc": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@livekit/rtc-node-win32-x64-msvc/-/rtc-node-win32-x64-msvc-0.11.0.tgz",
"integrity": "sha512-tLrdolxU+0PBxssIrSFNJie5XaCi1X/GN6GIbp/ZnSOZ+nwaNxFBmYolGjxRuW7ks1HGR7z/zIIpJey5CnFkWg==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@livekit/typed-emitter": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@livekit/typed-emitter/-/typed-emitter-3.0.0.tgz",
"integrity": "sha512-9bl0k4MgBPZu3Qu3R3xy12rmbW17e3bE9yf4YY85gJIQ3ezLEj/uzpKHWBsLaDoL5Mozz8QCgggwIBudYQWeQg==",
"license": "MIT"
},
"node_modules/@ljharb/through": {
"version": "2.3.13",
"resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz",
@@ -5759,6 +5896,13 @@
"@types/superagent": "^8.1.0"
}
},
"node_modules/@types/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/validator": {
"version": "13.12.2",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz",
@@ -7686,6 +7830,60 @@
"node": ">=6"
}
},
"node_modules/camelcase-keys": {
"version": "9.1.3",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-9.1.3.tgz",
"integrity": "sha512-Rircqi9ch8AnZscQcsA1C47NFdaO3wukpmIRzYcDOrmvgt78hM/sj5pZhZNec2NM12uk5vTwRHZ4anGcrC4ZTg==",
"license": "MIT",
"dependencies": {
"camelcase": "^8.0.0",
"map-obj": "5.0.0",
"quick-lru": "^6.1.1",
"type-fest": "^4.3.2"
},
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/camelcase-keys/node_modules/camelcase": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
"integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==",
"license": "MIT",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/camelcase-keys/node_modules/map-obj": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-5.0.0.tgz",
"integrity": "sha512-2L3MIgJynYrZ3TYMriLDLWocz15okFakV6J12HXvMXDHui2x/zgChzg1u9mFFGbbGWE+GsLpQByt4POb9Or+uA==",
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/camelcase-keys/node_modules/type-fest": {
"version": "4.26.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz",
"integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001676",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001676.tgz",
@@ -12237,7 +12435,6 @@
"version": "5.9.6",
"resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz",
"integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
@@ -12759,6 +12956,20 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/livekit-server-sdk": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/livekit-server-sdk/-/livekit-server-sdk-2.7.3.tgz",
"integrity": "sha512-dBiyMJ2o3Adw7aBVuFxVOlYHmiZtGGS9zVksMuv/wiEVHY+6XSDzo0X67pZVkyGlq1moF4YZAReVY2Dbxve8NQ==",
"license": "Apache-2.0",
"dependencies": {
"@livekit/protocol": "^1.27.0",
"camelcase-keys": "^9.0.0",
"jose": "^5.1.2"
},
"engines": {
"node": ">=19"
}
},
"node_modules/loader-runner": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
@@ -15241,6 +15452,18 @@
],
"license": "MIT"
},
"node_modules/quick-lru": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.2.tgz",
"integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",

View File

@@ -37,6 +37,7 @@
"@graphql-codegen/typescript": "^4.0.9",
"@graphql-codegen/typescript-operations": "^4.2.3",
"@graphql-codegen/typescript-resolvers": "^4.2.1",
"@livekit/rtc-node": "^0.11.0",
"@nestjs-modules/mailer": "^2.0.2",
"@nestjs/apollo": "^12.2.0",
"@nestjs/axios": "^3.1.1",
@@ -75,6 +76,7 @@
"graphql-ws": "^5.16.0",
"ioredis": "^5.4.1",
"jsonwebtoken": "^9.0.2",
"livekit-server-sdk": "^2.7.3",
"luxon": "^3.5.0",
"minio": "^8.0.1",
"nestjs-minio": "^2.6.2",
@@ -110,6 +112,7 @@
"@types/nodemailer": "^6.4.16",
"@types/passport-jwt": "^4.0.1",
"@types/supertest": "^6.0.0",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.5.12",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",

View File

@@ -142,7 +142,7 @@ export class CenterMentorSchema extends PothosSchema {
throw new Error('Not allowed')
}
// get centerId by user id from context
const userId = ctx.http.me.id
const userId = ctx.http.me?.id
if (!userId) {
throw new Error('User ID is required')
}
@@ -266,7 +266,7 @@ export class CenterMentorSchema extends PothosSchema {
data: {
content: args.adminNote ?? '',
mentorId: mentor.id,
notedByUserId: ctx.http.me.id,
notedByUserId: ctx.http.me?.id ?? '',
},
})
// update user role
@@ -312,7 +312,7 @@ export class CenterMentorSchema extends PothosSchema {
adminNote: {
create: {
content: args.adminNote ?? '',
notedByUserId: ctx.http.me.id,
notedByUserId: ctx.http.me?.id ?? '',
updatedAt: new Date(),
},
},

View File

@@ -43,7 +43,7 @@ export type SchemaContext =
http: {
req: Request
res: Response
me: User
me: User | null
pubSub: PubSub
invalidateCache: () => Promise<void>
generator: PrismaCrudGenerator<BuilderTypes>

View File

@@ -30,6 +30,10 @@ export class GraphqlService {
if (disableAuth) {
return null
}
// check if the sessionId is valid
if (!sessionId) {
return null
}
// redis context cache
const cachedUser = await this.redis.getUser(sessionId)
if (cachedUser) {

View File

@@ -0,0 +1,7 @@
// import { Module } from '@nestjs/common'
// import { LiveKitService } from './livekit.service'
// @Module({
// providers: [LiveKitService],
// exports: [LiveKitService],
// })
// export class LiveKitModule {}

View File

@@ -0,0 +1,24 @@
import { Injectable } from '@nestjs/common'
// @ts-ignore
import { AccessToken } from 'livekit-server-sdk'
@Injectable()
export class LiveKitParticipantService {
async createAccessToken(participantId: string) {
return new AccessToken(
process.env.LIVEKIT_API_KEY,
process.env.LIVEKIT_API_SECRET,
{
identity: participantId,
},
)
}
async grantRoomJoinPermission(token: AccessToken, roomName: string) {
token.addGrant({ roomJoin: true, room: roomName })
}
async toJWT(token: AccessToken) {
return token.toJwt()
}
}

View File

@@ -0,0 +1,36 @@
// import {
// Room,
// RoomServiceClient,
// RoomCompositeOptions,
// // @ts-ignore
// } from 'livekit-server-sdk'
// import { v4 as uuidv4 } from 'uuid'
// export class LiveKitRoomService {
// private roomServiceClient: RoomServiceClient
// constructor() {
// this.roomServiceClient = new RoomServiceClient(
// process.env.LIVEKIT_URL as string,
// process.env.LIVEKIT_API_KEY as string,
// process.env.LIVEKIT_API_SECRET as string,
// )
// }
// async createServiceMeetingRoom(chattingRoomId: string) {
// const room = await this.roomServiceClient.createRoom({
// maxParticipants: 3,
// name: chattingRoomId,
// })
// return room
// }
// async createWorkshopMeetingRoom(workshopId: string, maxParticipants: number) {
// const room = await this.roomServiceClient.createRoom({
// maxParticipants: maxParticipants,
// name: workshopId,
// })
// return room
// }
// }

View File

@@ -0,0 +1,11 @@
// import { Injectable, OnModuleInit } from '@nestjs/common'
// import { LiveKitRoomService } from './livekit.room.service'
// @Injectable()
// export class LiveKitService implements OnModuleInit {
// private liveKitRoomService: LiveKitRoomService
// async onModuleInit() {
// // init livekit room service
// this.liveKitRoomService = new LiveKitRoomService()
// }
// }

View File

@@ -0,0 +1,5 @@
// @ts-ignore
import { TrackInfo } from 'livekit-server-sdk'
export class LiveKitTrackService {
}

View File

@@ -57,7 +57,7 @@ export class MessageSchema extends PothosSchema {
type: this.message(),
description: 'Retrieve a single message by its unique identifier.',
args: this.builder.generator.findUniqueArgs('Message'),
resolve: async (query, root, args) => {
resolve: async (query, _root, args) => {
return await this.prisma.message.findUnique({
...query,
where: args.where,
@@ -69,7 +69,7 @@ export class MessageSchema extends PothosSchema {
description:
'Retrieve a list of messages with optional filtering, ordering, and pagination.',
args: this.builder.generator.findManyArgs('Message'),
resolve: async (query, root, args) => {
resolve: async (query, _root, args) => {
return await this.prisma.message.findMany({
...query,
skip: args.skip ?? undefined,
@@ -83,7 +83,7 @@ export class MessageSchema extends PothosSchema {
type: [this.message()],
description: 'Retrieve a list of messages by chat room ID.',
args: this.builder.generator.findManyArgs('Message'),
resolve: async (query, root, args) => {
resolve: async (query, _root, args) => {
return await this.prisma.message.findMany({
...query,
where: args.filter ?? undefined,
@@ -94,6 +94,19 @@ export class MessageSchema extends PothosSchema {
// mutations
this.builder.mutationFields((t) => ({
testSendMessage: t.field({
type: 'String',
description: 'Test sending a message.',
resolve: async (_, __, ctx) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
ctx.http.pubSub.publish('MESSAGE_SENT', {
message: 'Hello, world!',
})
return 'Message sent'
},
}),
sendMessage: t.prismaField({
type: this.message(),
description: 'Send a message to a chat room.',
@@ -104,7 +117,7 @@ export class MessageSchema extends PothosSchema {
required: true,
}),
},
resolve: async (query, root, args, ctx, info) => {
resolve: async (query, _root, args, ctx, _info) => {
const message = await this.prisma.message.create({
...query,
data: args.input,

View File

@@ -8,8 +8,8 @@ import {
import { Builder } from '../Graphql/graphql.builder'
import { PrismaService } from '../Prisma/prisma.service'
import { OrderStatus } from '@prisma/client'
import { DateTimeUtils } from 'src/common/utils/datetime.utils'
import { PayosService } from 'src/Payos/payos.service'
import { DateTimeUtils } from '../common/utils/datetime.utils'
import { PayosService } from '../Payos/payos.service'
@Injectable()
export class OrderSchema extends PothosSchema {
constructor(
@@ -45,6 +45,9 @@ export class OrderSchema extends PothosSchema {
scheduleId: t.exposeID('scheduleId', {
description: 'The ID of the schedule.',
}),
schedule: t.relation('schedule', {
description: 'The schedule of the order.',
}),
createdAt: t.expose('createdAt', {
type: 'DateTime',
description: 'The date and time the order was created.',
@@ -140,7 +143,7 @@ export class OrderSchema extends PothosSchema {
}
// check if input schedule has order id then throw error
const schedule = await this.prisma.schedule.findUnique({
where: { id: args.data.scheduleId },
where: { id: args.data.schedule.connect?.id ?? '' },
})
if (schedule?.orderId) {
// check if order status is PAID OR PENDING
@@ -159,9 +162,9 @@ export class OrderSchema extends PothosSchema {
data: {
status: OrderStatus.PENDING,
total: service.price,
userId: ctx.http.me.id,
userId: ctx.http.me?.id ?? '',
serviceId: service.id,
scheduleId: args.data.scheduleId,
scheduleId: args.data.schedule.connect?.id ?? '',
},
})
// check if service is valid
@@ -173,7 +176,7 @@ export class OrderSchema extends PothosSchema {
if (order.total === 0) {
// assign schedule
await this.prisma.schedule.update({
where: { id: args.data.scheduleId },
where: { id: args.data.schedule.connect?.id ?? '' },
data: {
orderId: order.id,
},
@@ -197,8 +200,8 @@ export class OrderSchema extends PothosSchema {
orderCode: paymentCode,
amount: service.price,
description: service.name,
buyerName: ctx.http.me.name,
buyerEmail: ctx.http.me.email,
buyerName: ctx.http.me?.name ?? '',
buyerEmail: ctx.http.me?.email ?? '',
returnUrl: `${process.env.PAYOS_RETURN_URL}`.replace(
'<serviceId>',
service.id,

View File

@@ -10,7 +10,12 @@ import type {
CancelPaymentLinkRequestType,
DataType,
} from '@payos/node/lib/type'
import { OrderStatus, PaymentStatus, ScheduleStatus } from '@prisma/client'
import {
ChatRoomType,
OrderStatus,
PaymentStatus,
ScheduleStatus,
} from '@prisma/client'
export type CreatePaymentBody = CheckoutRequestType
export type CreatePaymentResponse = CheckoutResponseDataType
@Injectable()
@@ -52,7 +57,7 @@ export class PayosService {
status: orderStatus,
},
})
const order = await this.prisma.order.findUnique({
const order = await this.prisma.order.findUniqueOrThrow({
where: { id: payment.orderId },
})
const schedule = await this.prisma.schedule.findUnique({
@@ -62,16 +67,36 @@ export class PayosService {
await this.prisma.schedule.update({
where: { id: schedule?.id },
data: {
customerId: order?.userId,
orderId: order?.id,
status: ScheduleStatus.IN_PROGRESS,
},
})
// get mentor id from managed service
const managedService = await this.prisma.managedService.findUniqueOrThrow({
where: { id: schedule?.managedServiceId },
})
const mentorId = managedService.mentorId
// get center id from order service
const orderService = await this.prisma.service.findUniqueOrThrow({
where: { id: order?.serviceId },
})
const centerId = orderService.centerId
// create chatroom for service meeting room
await this.prisma.chatRoom.create({
data: {
type: ChatRoomType.SUPPORT,
customerId: order.userId,
centerId: centerId,
mentorId: mentorId,
},
})
return {
message: 'Payment received',
}
}
async createPaymentURL(body: any) {
async createPaymentURL(body: CheckoutRequestType) {
return await this.payos.createPaymentLink(body)
}
@@ -90,6 +115,7 @@ export class PayosService {
return await this.payos.cancelPaymentLink(orderId, cancellationReason)
}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
async refundPayment(body: any) {
return body
}

View File

@@ -117,7 +117,7 @@ export class ResumeSchema extends PothosSchema {
const resumes = await this.prisma.resume.findMany({
...query,
where: {
userId: ctx.http.me.id,
userId: ctx.http.me?.id ?? '',
status: args.status ?? undefined,
},
})
@@ -274,7 +274,7 @@ export class ResumeSchema extends PothosSchema {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (ctx.http.me.role !== Role.MODERATOR) {
if (ctx.http.me?.role !== Role.MODERATOR) {
throw new Error('Not allowed')
}
const { resumeId, status, adminNote } = args
@@ -315,7 +315,7 @@ export class ResumeSchema extends PothosSchema {
_adminNote = await tx.adminNote.create({
data: {
content: adminNote,
notedByUserId: ctx.http.me.id,
notedByUserId: ctx.http.me?.id ?? '',
resumeId,
},
})

View File

@@ -11,6 +11,7 @@ import { ScheduleStatus } from '@prisma/client'
import { ScheduleService } from './schedule.service'
import { AppConfigService } from '../AppConfig/appconfig.service'
import { ScheduleConfigType } from './schedule'
import { DateTimeUtils } from 'src/common/utils/datetime.utils'
@Injectable()
export class ScheduleSchema extends PothosSchema {
@@ -290,36 +291,40 @@ d72a864e-2f41-45ab-9c9b-bf0512a31883,e9be51fd-2382-4e43-9988-74e76fde4b56,2024-1
required: true,
}),
},
resolve: async (query, _root, args, _ctx, _info) => {
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Cannot create schedule in subscription')
}
Logger.log('args.schedule', args.schedule)
// check if there is any overlapping schedule
const overlappingSchedules = await this.prisma.schedule.findMany({
where: {
OR: [
{
scheduleStart: {
gte: args.schedule.scheduleStart,
},
scheduleEnd: {
lte: args.schedule.scheduleEnd,
},
},
],
},
// generate preview and check if there is any overlapping with other schedules date in same service
const previewSchedule =
await this.scheduleService.createSchedulePreviewForCenter({
startDate: args.schedule.scheduleStart as string,
endDate: args.schedule.scheduleEnd as string,
slots: args.schedule.slots as number[],
days: args.schedule.daysOfWeek as number[],
})
// check if is same managedServiceId
if (
overlappingSchedules.some(
(schedule) =>
schedule.managedServiceId ===
args.schedule.managedService.connect?.id,
const existingScheduleDates = await this.prisma.scheduleDate.findMany(
{
where: {
serviceId: args.schedule.managedService.connect?.id,
},
},
)
) {
throw new Error(
`Overlapping schedule with ${JSON.stringify(
overlappingSchedules.map((schedule) => schedule.id),
)}`,
// check if there is any overlapping with existing schedule dates in same service using DateTimeUtils
const isOverlapping = DateTimeUtils.isOverlaps(
previewSchedule.slots.map((slot) => ({
start: DateTimeUtils.fromIsoString(slot.start),
end: DateTimeUtils.fromIsoString(slot.end),
})),
existingScheduleDates.map((date) => ({
start: DateTimeUtils.fromDate(date.start),
end: DateTimeUtils.fromDate(date.end),
})),
)
if (isOverlapping) {
Logger.error('Overlapping schedule', 'ScheduleSchema')
throw new Error('Overlapping schedule')
}
const schedule = await this.prisma.schedule.create({
...query,

View File

@@ -102,7 +102,6 @@ export class ScheduleService {
}
}
}
const scheduleDatesCreated =
await this.prisma.scheduleDate.createManyAndReturn({
data: scheduleDates,

View File

@@ -159,14 +159,14 @@ export class ServiceSchema extends PothosSchema {
throw new Error('Not allowed')
}
// check role if user is mentor or center owner
const role = ctx.http.me.role
const role = ctx.http.me?.role
if (role !== Role.CENTER_MENTOR && role !== Role.CENTER_OWNER) {
throw new Error('Not allowed')
}
if (role === Role.CENTER_MENTOR) {
// load only service belong to center of current user
const managedServices = await this.prisma.managedService.findMany({
where: { mentorId: ctx.http.me.id },
where: { mentorId: ctx.http.me?.id ?? '' },
})
if (!managedServices) {
throw new Error('Managed services not found')
@@ -179,7 +179,7 @@ export class ServiceSchema extends PothosSchema {
// if role is center owner, load all services belong to center of current user
if (role === Role.CENTER_OWNER) {
const center = await this.prisma.center.findUnique({
where: { centerOwnerId: ctx.http.me.id },
where: { centerOwnerId: ctx.http.me?.id ?? '' },
})
if (!center) {
throw new Error('Center not found')
@@ -234,7 +234,7 @@ export class ServiceSchema extends PothosSchema {
throw new Error('Not allowed')
}
// replace userId with current user id
args.input.user = { connect: { id: ctx.http.me.id } }
args.input.user = { connect: { id: ctx.http.me?.id ?? '' } }
return await this.prisma.service.create({
...query,
data: args.input,
@@ -321,7 +321,7 @@ export class ServiceSchema extends PothosSchema {
adminNote: {
create: {
content: args.adminNote ?? '',
notedByUserId: ctx.http.me.id,
notedByUserId: ctx.http.me?.id ?? '',
},
},
},

View File

@@ -7,12 +7,15 @@ import {
} from '@smatch-corp/nestjs-pothos'
import { Builder } from '../Graphql/graphql.builder'
import { PrismaService } from '../Prisma/prisma.service'
// import { LiveKitRoomService } from 'src/LiveKit/livekit.room.service'
import { v4 as uuidv4 } from 'uuid'
@Injectable()
export class ServiceMeetingRoomSchema extends PothosSchema {
constructor(
@Inject(SchemaBuilderToken) private readonly builder: Builder,
private readonly prisma: PrismaService,
// private readonly liveKitRoomService: LiveKitRoomService,
) {
super()
}
@@ -66,5 +69,33 @@ export class ServiceMeetingRoomSchema extends PothosSchema {
},
}),
}))
this.builder.mutationFields((t) => ({
createServiceMeetingRoom: t.prismaField({
type: this.serviceMeetingRoom(),
args: {
input: t.arg({
type: this.builder.generator.getCreateInput('ServiceMeetingRoom'),
required: true,
}),
},
description: 'Create a new service meeting room.',
resolve: async (query, _root, args, _ctx, _info) => {
// for test only !!!
if (args.input.chattingRoom.create) {
args.input.chattingRoom.create.id = uuidv4()
}
// call livekit room service to create room
// this.liveKitRoomService.createServiceMeetingRoom(
// args.input.chattingRoom.create?.id ?? '',
// )
return await this.prisma.serviceMeetingRoom.create({
...query,
data: args.input,
})
},
}),
}))
}
}

View File

@@ -262,7 +262,10 @@ export class UserSchema extends PothosSchema {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
const id = ctx.http.me.id
const id = ctx.http.me?.id
if (!id) {
throw new Error('User not found')
}
if (args.imageBlob) {
const { mimetype, createReadStream } = await args.imageBlob
if (mimetype && createReadStream) {
@@ -333,7 +336,7 @@ export class UserSchema extends PothosSchema {
throw new Error('Not allowed')
}
// check context is admin
if (ctx.http.me.role !== 'ADMIN') {
if (ctx.http.me?.role !== 'ADMIN') {
throw new UnauthorizedException(`Only admin can invite moderator`)
}
let user

View File

@@ -4,6 +4,7 @@ import { GraphqlModule } from './Graphql/graphql.module'
import { MailModule } from './Mail/mail.module'
import { Module } from '@nestjs/common'
import { RestfulModule } from './Restful/restful.module'
// import { LiveKitModule } from './LiveKit/livekit.module'
@Module({
imports: [
@@ -14,6 +15,7 @@ import { RestfulModule } from './Restful/restful.module'
MailModule,
GraphqlModule,
RestfulModule,
// LiveKitModule,
],
})
export class AppModule {}

View File

@@ -60,6 +60,20 @@ export class DateTimeUtils {
)
}
static isOverlaps(
listA: { start: DateTime; end: DateTime }[],
listB: { start: DateTime; end: DateTime }[],
): boolean {
for (const a of listA) {
for (const b of listB) {
if (this.isOverlap(a.start, a.end, b.start, b.end)) {
return true
}
}
}
return false
}
static fromIsoString(isoString: string): DateTime {
const dateTime = DateTime.fromISO(isoString)
if (!dateTime.isValid) {

File diff suppressed because one or more lines are too long