fix time geneate logic and replace default datetime by luxon

This commit is contained in:
2024-11-01 17:27:25 +07:00
parent 24a49d9412
commit ec77f07de1
14 changed files with 253 additions and 52 deletions

View File

@@ -3,6 +3,9 @@ import { Injectable, Logger } from '@nestjs/common'
import { PrismaService } from '../Prisma/prisma.service'
import { clerkClient } from '@clerk/express'
export interface ClerkResponse {
}
@Injectable()
export class ClerkService {
constructor(private readonly prisma: PrismaService) {}

View File

@@ -41,6 +41,7 @@ export type SchemaContext =
res: Response
me: User
pubSub: PubSub
invalidateCache: () => Promise<void>
generator: PrismaCrudGenerator<BuilderTypes>
}
}

View File

@@ -95,7 +95,12 @@ import { initContextCache } from '@pothos/core'
...initContextCache(),
isSubscription: false,
http: {
req,
me: await graphqlService.acquireContext(req),
invalidateCache: () =>
graphqlService.invalidateCache(
req.headers['x-session-id'] as string,
),
},
}),
}),

View File

@@ -37,6 +37,7 @@ export class GraphqlService {
// redis context cache
const cachedUser = await this.redis.getUser(sessionId)
if (cachedUser) {
Logger.log(`Cache hit for sessionId: ${sessionId}`)
return cachedUser
}
// check if the token is valid
@@ -53,4 +54,10 @@ export class GraphqlService {
await this.redis.setUser(sessionId, user, session.expireAt)
return user
}
async invalidateCache(sessionId: string) {
// invalidate redis cache for sessionId
await this.redis.del(sessionId)
Logger.log(`Invalidated cache for sessionId: ${sessionId}`)
}
}

View File

@@ -32,7 +32,7 @@ export class MessageSchema extends PothosSchema {
description: 'The ID of the chat room.',
}),
message: t.expose('message', {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
type: 'Json' as any,
description: 'The message content.',
}),

View File

@@ -81,7 +81,7 @@ export class OrderSchema extends PothosSchema {
description:
'Retrieve a list of orders with optional filtering, ordering, and pagination.',
args: this.builder.generator.findManyArgs('Order'),
resolve: async (query, root, args, ctx, info) => {
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.order.findMany({
...query,
take: args.take ?? undefined,
@@ -95,7 +95,7 @@ export class OrderSchema extends PothosSchema {
type: this.order(),
args: this.builder.generator.findUniqueArgs('Order'),
description: 'Retrieve a single order by its unique identifier.',
resolve: async (query, root, args, ctx, info) => {
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.order.findUnique({
...query,
where: args.where,
@@ -124,7 +124,7 @@ export class OrderSchema extends PothosSchema {
required: true,
}),
},
resolve: async (query, root, args, ctx, info) => {
resolve: async (query, _root, args, _ctx, _info) => {
return this.prisma.$transaction(async (prisma) => {
const order = await prisma.order.create({
...query,
@@ -162,7 +162,7 @@ export class OrderSchema extends PothosSchema {
required: true,
}),
},
resolve: async (query, root, args, ctx, info) => {
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.order.delete({
...query,
where: args.where,
@@ -185,7 +185,7 @@ export class OrderSchema extends PothosSchema {
required: true,
}),
},
resolve: async (query, root, args, ctx, info) => {
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.order.update({
...query,
data: args.data,

View File

@@ -23,6 +23,16 @@ export type ScheduleConfigType =
| null
| undefined
export type ScheduleConfigTypeForCenter =
| {
startDate?: string | null | undefined
endDate?: string | null | undefined
slots?: number[] | null | undefined
days?: number[] | null | undefined
}
| null
| undefined
export type ScheduleSlotType = {
slot: string
start: string
@@ -144,6 +154,18 @@ export class ScheduleSchema extends PothosSchema {
})
}
@PothosRef()
scheduleConfigInputForCenter() {
return this.builder.inputType('ScheduleConfigInputForCenter', {
fields: (t) => ({
startDate: t.string(),
endDate: t.string(),
slots: t.intList(),
days: t.intList(),
}),
})
}
@Pothos()
init(): void {
this.builder.queryFields((t) => ({
@@ -175,6 +197,21 @@ export class ScheduleSchema extends PothosSchema {
},
}),
// centerPreviewSchedule: t.field({
// type: this.previewSchedule(),
// description: 'Preview a schedule for center mentor.',
// args: {
// scheduleConfig: t.arg({
// type: this.scheduleConfigInputForCenter(),
// }),
// },
// resolve: async (_parent, args, _context, _info) => {
// return await this.scheduleService.createSchedulePreviewForCenter(
// args.scheduleConfig,
// )
// },
// }),
adminPreviewSchedule: t.field({
type: this.previewSchedule(),
description: 'Preview a schedule for admin.',

View File

@@ -7,11 +7,21 @@ import { AppConfigService } from 'src/AppConfig/appconfig.service'
import {
PreviewScheduleType,
ScheduleConfigType,
ScheduleConfigTypeForCenter,
ScheduleSlotType,
} from './schedule.schema'
import { Config } from '@prisma/client'
import { DateTime, Settings, Zone } from 'luxon'
import * as _ from 'lodash'
Settings.defaultLocale = 'en-US'
Settings.defaultZone = 'utc'
// Settings.defaultWeekSettings = {
// firstDay: 2,
// minimalDays: 1,
// weekend: [6, 7],
// }
@Injectable()
export class ScheduleService {
constructor(
@@ -38,6 +48,18 @@ export class ScheduleService {
}
}
// async createSchedulePreviewForCenter(
// scheduleConfig: ScheduleConfigTypeForCenter,
// ): Promise<PreviewScheduleType> {
// const config: Config[] = await this.appConfigService.getVisibleConfigs()
// Logger.log(config)
// // process scheduleConfig input by filling with default values from config
// const scheduleConfigFilled = this.processScheduleConfig(
// scheduleConfig,
// config,
// )
// }
generateSlots(scheduleConfigFilled: ScheduleConfigType): ScheduleSlotType[] {
Logger.log(`Generating slots with config: ${scheduleConfigFilled}`)
const slots: ScheduleSlotType[] = []
@@ -66,11 +88,11 @@ export class ScheduleService {
!this.isOverLapping(
startTime,
endTime,
this.getSpecificDateWithTime(
DateTime.fromISO(
// @ts-ignore
scheduleConfigFilled?.midDayBreakTimeStart,
),
this.getSpecificDateWithTime(
DateTime.fromISO(
// @ts-ignore
scheduleConfigFilled?.midDayBreakTimeEnd,
),
@@ -78,8 +100,8 @@ export class ScheduleService {
) {
slots.push({
slot: i.toString(),
start: startTime.toISOString(),
end: endTime.toISOString(),
start: startTime.toString(),
end: endTime.toString(),
})
}
}
@@ -87,14 +109,14 @@ export class ScheduleService {
}
isOverLapping(
startTime1: Date,
endTime1: Date,
startTime2: Date,
endTime2: Date,
startTime1: DateTime,
endTime1: DateTime,
startTime2: DateTime,
endTime2: DateTime,
) {
return (
Math.max(startTime1.getTime(), startTime2.getTime()) <
Math.min(endTime1.getTime(), endTime2.getTime())
Math.max(startTime1.toMillis(), startTime2.toMillis()) <
Math.min(endTime1.toMillis(), endTime2.toMillis())
)
}
@@ -104,10 +126,11 @@ export class ScheduleService {
slotDuration: string,
slotBreakDuration: string,
) {
const startDate = new Date(startTime)
const endDate = new Date(endTime)
const startDate = DateTime.fromISO(startTime)
const endDate = DateTime.fromISO(endTime)
const totalMinutes = (endDate.getTime() - startDate.getTime()) / (60 * 1000)
const totalMinutes =
(endDate.toMillis() - startDate.toMillis()) / (60 * 1000)
const numberOfSlots = Math.floor(
totalMinutes / (parseInt(slotDuration) + parseInt(slotBreakDuration)),
)
@@ -120,13 +143,14 @@ export class ScheduleService {
slotBreakDuration: string,
slotStartTime: string,
) {
const startTime = new Date(slotStartTime)
startTime.setUTCMinutes(
startTime.getUTCMinutes() +
slotNumber * (parseInt(slotDuration) + parseInt(slotBreakDuration)),
)
const endTime = new Date(startTime)
endTime.setUTCMinutes(endTime.getUTCMinutes() + parseInt(slotDuration))
const durationInMinutes = parseInt(slotDuration);
const breakDurationInMinutes = parseInt(slotBreakDuration);
const startTime = DateTime.fromISO(slotStartTime).plus({
minutes: (slotNumber - 1) * (durationInMinutes + breakDurationInMinutes),
});
const endTime = startTime.plus({ minutes: durationInMinutes })
return { startTime, endTime }
}
@@ -169,26 +193,23 @@ export class ScheduleService {
return _.camelCase(str.toLowerCase())
}
getTodayWithTime(date: Date) {
const today = new Date()
today.setUTCHours(
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds(),
0,
)
getTodayWithTime(date: DateTime) {
let today = DateTime.now()
today = today.set({
hour: date.hour,
minute: date.minute,
second: date.second,
})
return today
}
getSpecificDateWithTime(date: Date) {
const specificDate = new Date(date)
date = new Date(date)
specificDate.setUTCHours(
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds(),
0,
)
getSpecificDateWithTime(date: DateTime) {
let specificDate = DateTime.now()
specificDate = specificDate.set({
hour: date.hour,
minute: date.minute,
second: date.second,
})
return specificDate
}
}

View File

@@ -43,7 +43,7 @@ export class ServiceMeetingRoomSchema extends PothosSchema {
args: this.builder.generator.findUniqueArgs('ServiceMeetingRoom'),
description:
'Retrieve a single service meeting room by its unique identifier.',
resolve: async (query, root, args, ctx, info) => {
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.serviceMeetingRoom.findUnique({
...query,
where: args.where,
@@ -55,7 +55,7 @@ export class ServiceMeetingRoomSchema extends PothosSchema {
args: this.builder.generator.findManyArgs('ServiceMeetingRoom'),
description:
'Retrieve a list of service meeting rooms with optional filtering, ordering, and pagination.',
resolve: async (query, root, args, ctx, info) => {
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.serviceMeetingRoom.findMany({
...query,
skip: args.skip ?? undefined,

View File

@@ -73,7 +73,7 @@ export class UploadedFileSchema extends PothosSchema {
'Retrieve a single uploaded file by its unique identifier.',
type: this.uploadedFile(),
args: this.builder.generator.findUniqueArgs('UploadedFile'),
resolve: async (query, root, args) => {
resolve: async (query, _root, args) => {
const file = await this.prisma.uploadedFile.findUnique({
...query,
where: args.where,
@@ -94,7 +94,7 @@ export class UploadedFileSchema extends PothosSchema {
'Retrieve a list of uploaded files with optional filtering, ordering, and pagination.',
type: [this.uploadedFile()],
args: this.builder.generator.findManyArgs('UploadedFile'),
resolve: async (query, root, args) => {
resolve: async (query, _root, args) => {
const files = await this.prisma.uploadedFile.findMany({
...query,
skip: args.skip ?? undefined,
@@ -132,7 +132,7 @@ export class UploadedFileSchema extends PothosSchema {
required: true,
}),
},
resolve: async (query, root, args) => {
resolve: async (_query, _root, args) => {
const user = await this.prisma.user.findUnique({
where: {
id: args.userId,
@@ -182,7 +182,7 @@ export class UploadedFileSchema extends PothosSchema {
required: true,
}),
},
resolve: async (query, root, args) => {
resolve: async (_query, _root, args) => {
const user = await this.prisma.user.findUnique({
where: {
id: args.userId,
@@ -230,7 +230,7 @@ export class UploadedFileSchema extends PothosSchema {
required: true,
}),
},
resolve: async (query, root, args) => {
resolve: async (_query, _root, args) => {
const file = await this.prisma.uploadedFile.findUnique({
where: {
id: args.id,
@@ -259,7 +259,7 @@ export class UploadedFileSchema extends PothosSchema {
required: true,
}),
},
resolve: async (query, root, args) => {
resolve: async (_query, _root, args) => {
const files = await this.prisma.uploadedFile.findMany({
where: {
id: {

View File

@@ -214,6 +214,113 @@ export class UserSchema extends PothosSchema {
},
}),
updateMe: t.field({
type: this.user(),
description: 'Update the current user in context.',
args: {
input: t.arg({
type: this.builder.generator.getUpdateInput('User', [
'id',
'adminNote',
'center',
'customerChatRoom',
'avatarUrl',
// 'bankAccountNumber',
// 'bankBin',
'email',
'name',
'phoneNumber',
'role',
'createdAt',
'updatedAt',
'files',
'orders',
'sendingMessage',
'mentor',
'mentorChatRoom',
'resume',
'service',
'serviceFeedbacks',
'workshopSubscription',
]),
required: false,
}),
imageBlob: t.arg({
type: 'Upload',
required: false,
}),
firstName: t.arg({
type: 'String',
required: false,
}),
lastName: t.arg({
type: 'String',
required: false,
}),
},
resolve: async (_query, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
const id = ctx.http.me.id
if (args.imageBlob) {
const { mimetype, createReadStream } = await args.imageBlob
if (mimetype && createReadStream) {
const stream = createReadStream()
const chunks: Uint8Array[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
const buffer = Buffer.concat(chunks)
await clerkClient.users.updateUserProfileImage(id, {
file: new Blob([buffer]),
})
}
}
// update info to clerk
const clerkUser = await clerkClient.users.updateUser(id, {
firstName: args.firstName as string,
lastName: args.lastName as string,
})
Logger.log(clerkUser, 'Clerk User')
// update bank account number and bank bin to database
if (args.input?.bankAccountNumber) {
await this.prisma.user.update({
where: { id: clerkUser.id },
data: {
bankAccountNumber: args.input.bankAccountNumber,
},
})
}
if (args.input?.bankBin) {
await this.prisma.user.update({
where: { id: clerkUser.id },
data: {
bankBin: args.input.bankBin,
},
})
}
if (args.firstName || args.lastName) {
await this.prisma.user.update({
where: { id: clerkUser.id },
data: {
name: `${args.firstName || ''} ${args.lastName || ''}`.trim(),
},
})
}
// invalidate cache
await ctx.http.invalidateCache()
return await this.prisma.user.findUniqueOrThrow({
where: { id: clerkUser.id },
})
},
}),
inviteModerator: t.field({
type: 'String',
args: {