chore: combine context

This commit is contained in:
2024-12-20 18:30:50 +07:00
parent 461f2653e3
commit 776881f961
23 changed files with 532 additions and 694 deletions

View File

@@ -171,19 +171,16 @@ export class AnalyticSchema extends PothosSchema {
type: this.customerAnalytic(),
description: 'Retrieve a single customer analytic.',
resolve: async (_parent, _args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
if (ctx.http.me.role !== Role.CUSTOMER) {
if (ctx.me.role !== Role.CUSTOMER) {
throw new Error('Only customers can access this data')
}
// calculate analytic
const activeServiceCount = await this.prisma.order.count({
where: {
userId: ctx.http.me.id,
userId: ctx.me.id,
status: OrderStatus.PAID,
schedule: {
dates: {
@@ -198,12 +195,12 @@ export class AnalyticSchema extends PothosSchema {
})
const totalServiceCount = await this.prisma.order.count({
where: {
userId: ctx.http.me.id,
userId: ctx.me.id,
},
})
const totalSpent = await this.prisma.order.aggregate({
where: {
userId: ctx.http.me.id,
userId: ctx.me.id,
status: OrderStatus.PAID,
},
_sum: {
@@ -211,7 +208,7 @@ export class AnalyticSchema extends PothosSchema {
},
})
return {
userId: ctx.http.me.id,
userId: ctx.me.id,
activeServiceCount: activeServiceCount,
totalServiceCount: totalServiceCount,
totalSpent: totalSpent._sum.total,
@@ -223,18 +220,15 @@ export class AnalyticSchema extends PothosSchema {
type: this.mentorAnalytic(),
description: 'Retrieve a single mentor analytic.',
resolve: async (_parent, _args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
if (ctx.http.me.role !== Role.CENTER_MENTOR) {
if (ctx.me.role !== Role.CENTER_MENTOR) {
throw new Error('Only center mentors can access this data')
}
// calculate analytic
return {
userId: ctx.http.me.id,
userId: ctx.me.id,
}
},
}),
@@ -242,19 +236,16 @@ export class AnalyticSchema extends PothosSchema {
type: this.centerAnalytic(),
description: 'Retrieve a single center analytic.',
resolve: async (_parent, _args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
if (ctx.http.me.role !== Role.CENTER_OWNER) {
if (ctx.me.role !== Role.CENTER_OWNER) {
throw new Error('Only center owners can access this data')
}
// get center by owner id
const center = await this.prisma.center.findUnique({
where: {
centerOwnerId: ctx.http.me.id,
centerOwnerId: ctx.me.id,
},
})
if (!center) {
@@ -266,7 +257,7 @@ export class AnalyticSchema extends PothosSchema {
const activeMentorCount = await this.prisma.user.count({
where: {
center: {
centerOwnerId: ctx.http.me.id,
centerOwnerId: ctx.me.id,
},
banned: false,
},
@@ -340,13 +331,10 @@ export class AnalyticSchema extends PothosSchema {
},
description: 'Retrieve a single platform analytic.',
resolve: async (_parent, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
if (ctx.http.me.role !== Role.ADMIN && ctx.http.me.role !== Role.MODERATOR) {
if (ctx.me.role !== Role.ADMIN && ctx.me.role !== Role.MODERATOR) {
throw new Error('Only admins and moderators can access this data')
}
// calculate analytic for services sorted by args.serviceSortBy and args.timeframes

View File

@@ -154,16 +154,13 @@ export class CenterSchema extends PothosSchema {
}),
},
resolve: async (query, _root, args, ctx) => {
if (ctx.isSubscription) {
throw new Error('Not allowed in subscription')
}
if (ctx.http.me?.role !== Role.CUSTOMER) {
if (ctx.me?.role !== Role.CUSTOMER) {
throw new Error('Not allowed')
}
// check if user has already created a center
const existingCenter = await this.prisma.center.findFirst({
where: {
centerOwnerId: ctx.http.me?.id,
centerOwnerId: ctx.me?.id,
},
})
if (existingCenter) {
@@ -239,10 +236,7 @@ export class CenterSchema extends PothosSchema {
},
resolve: async (query, _root, args, ctx) => {
return await this.prisma.$transaction(async (prisma) => {
if (ctx.isSubscription) {
throw new Error('Not allowed in subscription')
}
if (ctx.http.me?.role !== Role.ADMIN && ctx.http.me?.role !== Role.MODERATOR) {
if (ctx.me?.role !== Role.ADMIN && ctx.me?.role !== Role.MODERATOR) {
throw new Error('Not allowed')
}
const center = await prisma.center.findUnique({

View File

@@ -139,11 +139,11 @@ export class CenterMentorSchema extends PothosSchema {
},
resolve: async (_query, _root, args, ctx) => {
return this.prisma.$transaction(async (prisma) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
if (!ctx.me) {
throw new Error('User not found')
}
// get centerId by user id from context
const userId = ctx.http.me?.id
const userId = ctx.me.id
if (!userId) {
throw new Error('User ID is required')
}
@@ -205,8 +205,8 @@ export class CenterMentorSchema extends PothosSchema {
adminNote: t.arg({ type: 'String', required: false }),
},
resolve: async (_query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
if (!ctx.me) {
throw new Error('User not found')
}
return this.prisma.$transaction(async (prisma) => {
// get mentor info
@@ -255,7 +255,7 @@ export class CenterMentorSchema extends PothosSchema {
data: {
content: args.adminNote ?? '',
mentorId: mentor.id,
notedByUserId: ctx.http.me?.id ?? '',
notedByUserId: ctx.me.id,
},
})
// update user role
@@ -310,7 +310,7 @@ export class CenterMentorSchema extends PothosSchema {
adminNote: {
create: {
content: args.adminNote ?? '',
notedByUserId: ctx.http.me?.id ?? '',
notedByUserId: ctx.me.id,
updatedAt: new Date(),
},
},

View File

@@ -1,88 +1,123 @@
import { Inject, Injectable } from '@nestjs/common'
import { ChatRoomType } from '@prisma/client'
import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
import { Builder } from '../Graphql/graphql.builder'
import { PrismaService } from '../Prisma/prisma.service'
import { Inject, Injectable } from "@nestjs/common";
import { ChatRoomType } from "@prisma/client";
import {
Pothos,
PothosRef,
PothosSchema,
SchemaBuilderToken,
} from "@smatch-corp/nestjs-pothos";
import { Builder } from "../Graphql/graphql.builder";
import { PrismaService } from "../Prisma/prisma.service";
@Injectable()
export class ChatroomSchema extends PothosSchema {
constructor(
@Inject(SchemaBuilderToken) private readonly builder: Builder,
private readonly prisma: PrismaService,
private readonly prisma: PrismaService
) {
super()
super();
}
@PothosRef()
chatRoom() {
return this.builder.prismaObject('ChatRoom', {
description: 'A chat room in the system.',
return this.builder.prismaObject("ChatRoom", {
description: "A chat room in the system.",
fields: (t) => ({
id: t.exposeID('id', {
description: 'The ID of the chat room.',
id: t.exposeID("id", {
description: "The ID of the chat room.",
}),
type: t.expose('type', {
type: t.expose("type", {
type: ChatRoomType,
description: 'The type of the chat room.',
description: "The type of the chat room.",
}),
customerId: t.exposeID('customerId', {
description: 'The ID of the customer.',
customerId: t.exposeID("customerId", {
description: "The ID of the customer.",
}),
centerId: t.exposeID('centerId', {
description: 'The ID of the center.',
centerId: t.exposeID("centerId", {
description: "The ID of the center.",
}),
mentorId: t.exposeID('mentorId', {
description: 'The ID of the mentor.',
mentorId: t.exposeID("mentorId", {
description: "The ID of the mentor.",
}),
createdAt: t.expose('createdAt', {
type: 'DateTime',
description: 'The date and time the chat room was created.',
createdAt: t.expose("createdAt", {
type: "DateTime",
description: "The date and time the chat room was created.",
}),
message: t.relation('message', {
description: 'The messages in the chat room.',
message: t.relation("message", {
description: "The messages in the chat room.",
}),
customer: t.relation('customer', {
description: 'The customer.',
customer: t.relation("customer", {
description: "The customer.",
}),
center: t.relation('center', {
description: 'The center.',
center: t.relation("center", {
description: "The center.",
}),
mentor: t.relation('mentor', {
description: 'The mentor.',
mentor: t.relation("mentor", {
description: "The mentor.",
}),
collaborationSession: t.relation('CollaborationSession', {
description: 'The collaboration session.',
collaborationSession: t.relation("CollaborationSession", {
description: "The collaboration session.",
}),
lastActivity: t.expose('lastActivity', {
type: 'DateTime',
description: 'The last activity date and time.',
lastActivity: t.expose("lastActivity", {
type: "DateTime",
description: "The last activity date and time.",
}),
order: t.relation('Order', {
description: 'The order.',
order: t.relation("Order", {
description: "The order.",
}),
}),
})
});
}
// @PothosRef()
// chatUser() {
// return this.builder.simpleObject("ChatUser", {
// description: "A user in a chat room.",
// fields: (t) => ({
// user: t.field({
// type: this.userSchema.user(),
// description: "The user.",
// }),
// chatRoom: t.field({
// type: this.chatRoom(),
// description: "The chat room.",
// }),
// lastMessage: t.field({
// type: this.messageSchema.message(),
// description: "The last message.",
// }),
// lastMessageAt: t.field({
// type: "DateTime",
// description: "The date and time of the last message.",
// }),
// unreadCount: t.field({
// type: "Int",
// description: "The number of unread messages.",
// }),
// }),
// });
// }
@Pothos()
init(): void {
this.builder.queryFields((t) => ({
chatRoom: t.prismaField({
type: this.chatRoom(),
description: 'Retrieve a single chat room by its unique identifier.',
args: this.builder.generator.findUniqueArgs('ChatRoom'),
description: "Retrieve a single chat room by its unique identifier.",
args: this.builder.generator.findUniqueArgs("ChatRoom"),
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.chatRoom.findUnique({
...query,
where: args.where,
})
});
},
}),
chatRooms: t.prismaField({
type: [this.chatRoom()],
description: 'Retrieve a list of chat rooms with optional filtering, ordering, and pagination.',
args: this.builder.generator.findManyArgs('ChatRoom'),
description:
"Retrieve a list of chat rooms with optional filtering, ordering, and pagination.",
args: this.builder.generator.findManyArgs("ChatRoom"),
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.chatRoom.findMany({
...query,
@@ -90,9 +125,30 @@ export class ChatroomSchema extends PothosSchema {
take: args.take ?? undefined,
orderBy: args.orderBy ?? undefined,
where: args.filter ?? undefined,
})
});
},
}),
}))
// chatUsers: t.prismaField({
// type: [this.chatUser()],
// args: {
// chatRoomId: t.arg({
// type: "ID",
// description: "The ID of the chat room.",
// }),
// },
// description: "Retrieve a list of chat users.",
// args: this.builder.generator.findManyArgs("ChatUser"),
// resolve: async (query, _root, args, ctx, _info) => {
// if (ctx.isSubscription) {
// throw new Error("Not allowed");
// }
// if (!ctx.user) {
// throw new Error("Unauthorized");
// }
// // TODO: get chat users for the chat room
// return [];
// },
// }),
}));
}
}

View File

@@ -73,10 +73,7 @@ export class CollaborationSessionSchema extends PothosSchema {
},
description: 'Retrieve a single collaboration session by its unique identifier.',
resolve: async (_query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Cannot get your info')
}
const scheduleDate = await this.prisma.scheduleDate.findUnique({
@@ -94,9 +91,9 @@ export class CollaborationSessionSchema extends PothosSchema {
},
})
/* ---------- use case 1 : customer get collaboration session by id --------- */
if (ctx.http.me?.role === Role.CUSTOMER && collaborationSession) {
if (ctx.me?.role === Role.CUSTOMER && collaborationSession) {
// check if user is participant
if (!collaborationSession.collaboratorsIds.includes(ctx.http.me.id)) {
if (!collaborationSession.collaboratorsIds.includes(ctx.me.id)) {
throw new Error('User not allowed')
}
// update schedule date status
@@ -111,14 +108,14 @@ export class CollaborationSessionSchema extends PothosSchema {
return collaborationSession
}
/* ---------- use case 2 : center mentor get collaboration session by schedule date id --------- */
if (ctx.http.me.role !== Role.CENTER_MENTOR && ctx.http.me.role !== Role.CENTER_OWNER) {
if (ctx.me.role !== Role.CENTER_MENTOR && ctx.me.role !== Role.CENTER_OWNER) {
if (!collaborationSession) {
throw new Error('Mentor does not created collaboration session yet')
}
throw new Error('User not allowed')
}
// check if user is participant
if (!scheduleDate.participantIds.includes(ctx.http.me.id)) {
if (!scheduleDate.participantIds.includes(ctx.me.id)) {
throw new Error('User not allowed')
}
// check if order is exist in schedule date
@@ -218,16 +215,13 @@ export class CollaborationSessionSchema extends PothosSchema {
liveKitToken: t.field({
type: 'String',
resolve: async (_, _args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http?.me?.id) {
if (!ctx.me?.id) {
throw new Error('Unauthorized')
}
// check if participantId is in meetingRoomCollaborators
const meetingRoomCollaborator = await this.prisma.meetingRoomCollaborator.findFirst({
where: {
userId: ctx.http.me.id,
userId: ctx.me.id,
},
})
if (!meetingRoomCollaborator) {
@@ -241,7 +235,7 @@ export class CollaborationSessionSchema extends PothosSchema {
if (!meetingRoom) {
throw new Error('Meeting room not found')
}
const token = await this.liveKitService.createToken(ctx.http.me, meetingRoom.id)
const token = await this.liveKitService.createToken(ctx.me, meetingRoom.id)
return token
},
}),
@@ -263,10 +257,7 @@ export class CollaborationSessionSchema extends PothosSchema {
},
description: 'Update the active document ID for a collaboration session.',
resolve: async (_query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Cannot get your info')
}
// check permission
@@ -281,7 +272,7 @@ export class CollaborationSessionSchema extends PothosSchema {
if (!collaborationSession) {
throw new Error('Collaboration session not found')
}
if (!collaborationSession.scheduleDate.participantIds.includes(ctx.http.me.id)) {
if (!collaborationSession.scheduleDate.participantIds.includes(ctx.me.id)) {
throw new Error('User not allowed')
}
const updatedCollaborationSession = await this.prisma.collaborationSession.update({
@@ -292,7 +283,7 @@ export class CollaborationSessionSchema extends PothosSchema {
activeDocumentId: args.activeDocumentId,
},
})
ctx.http.pubSub.publish(`collaborationSessionUpdated:${collaborationSession.id}`, updatedCollaborationSession)
ctx.pubSub.publish(`collaborationSessionUpdated:${collaborationSession.id}`, updatedCollaborationSession)
Logger.log(`Collaboration session updated: ${updatedCollaborationSession.id}`, 'updateActiveDocumentId')
return updatedCollaborationSession
},
@@ -310,10 +301,7 @@ export class CollaborationSessionSchema extends PothosSchema {
}),
},
subscribe: async (_parent, args, ctx) => {
if (!ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.websocket.me) {
if (!ctx.me) {
throw new Error('Cannot get your info')
}
const collaborationSession = await this.prisma.collaborationSession.findUnique({
@@ -324,7 +312,7 @@ export class CollaborationSessionSchema extends PothosSchema {
if (!collaborationSession) {
throw new Error('Collaboration session not found')
}
return ctx.websocket.pubSub.asyncIterator([
return ctx.pubSub.asyncIterator([
`collaborationSessionUpdated:${collaborationSession.id}`,
]) as unknown as AsyncIterable<CollaborationSession>
},

View File

@@ -206,10 +206,7 @@ export class DocumentSchema extends PothosSchema {
type: [this.document()],
args: this.builder.generator.findManyArgs("Document"),
resolve: async (query, _parent, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error("Not allowed");
}
if (!ctx.http?.me?.id) {
if (!ctx.me?.id) {
throw new Error("User not found");
}
return await this.prisma.document.findMany({
@@ -217,8 +214,8 @@ export class DocumentSchema extends PothosSchema {
orderBy: args.orderBy ?? undefined,
where: {
OR: [
{ ownerId: ctx.http.me.id },
{ collaborators: { some: { userId: ctx.http.me.id } } },
{ ownerId: ctx.me.id },
{ collaborators: { some: { userId: ctx.me.id } } },
],
},
});
@@ -252,10 +249,7 @@ export class DocumentSchema extends PothosSchema {
type: this.document(),
args: {},
resolve: async (query, _args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error("Not allowed");
}
const userId = ctx.http?.me?.id;
const userId = ctx.me?.id;
if (!userId) {
throw new Error("User not found");
}
@@ -271,33 +265,6 @@ export class DocumentSchema extends PothosSchema {
},
}),
testCheckGrammar: t.field({
type: "Boolean",
args: {
documentId: t.arg({ type: "String", required: true }),
pageId: t.arg({ type: "Int", required: true }),
promptType: t.arg({
type: this.builder.enumType("PromptType", {
values: [
"CHECK_GRAMMAR",
"REWRITE_TEXT",
"SUMMARIZE",
"TRANSLATE",
"EXPAND_CONTENT",
] as const,
}),
required: true,
}),
},
resolve: async (_query, args, _ctx: SchemaContext) => {
await this.documentService.checkGrammarForPage(
args.documentId,
args.pageId,
args.promptType as PromptType
);
return true;
},
}),
// exportDocument: t.field({
// type: this.DocumentExportObject(),
@@ -344,10 +311,7 @@ export class DocumentSchema extends PothosSchema {
pageIndex: t.arg({ type: "Int", required: true }),
},
resolve: async (_, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error("Not allowed");
}
if (!ctx.http?.me?.id) {
if (!ctx.me?.id) {
throw new Error("User not found");
}
if (!args.documentId) {
@@ -371,7 +335,7 @@ export class DocumentSchema extends PothosSchema {
pageIndex: args.pageIndex,
delta,
totalPage,
senderId: ctx.http?.me?.id,
senderId: ctx.me?.id,
eventType: DocumentEvent.CLIENT_REQUEST_SYNC,
};
},
@@ -398,10 +362,7 @@ export class DocumentSchema extends PothosSchema {
}),
},
resolve: async (query, _parent, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error("Not allowed");
}
const userId = ctx.http?.me?.id;
const userId = ctx.me?.id;
if (!userId) {
throw new Error("Unauthorized");
}
@@ -430,13 +391,10 @@ export class DocumentSchema extends PothosSchema {
}),
},
resolve: async (_, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error("Not allowed");
}
const {
http: { pubSub },
pubSub,
} = ctx;
const senderId = ctx.http?.me?.id;
const senderId = ctx.me?.id;
if (!senderId) {
throw new Error("User not found");
}
@@ -466,10 +424,7 @@ export class DocumentSchema extends PothosSchema {
data: t.arg({ type: this.documentDeltaInput(), required: true }),
},
resolve: async (_, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error("Not allowed");
}
const senderId = ctx.http?.me?.id;
const senderId = ctx.me?.id;
if (!args.data.documentId) {
throw new Error("Document id not found");
}
@@ -522,10 +477,7 @@ export class DocumentSchema extends PothosSchema {
}),
},
resolve: async (query, _parent, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error("Not allowed");
}
if (!ctx.http?.me?.id) {
if (!ctx.me?.id) {
throw new Error("Unauthorized");
}
// check if user is owner or collaborator
@@ -541,9 +493,9 @@ export class DocumentSchema extends PothosSchema {
if (
!document.isPublic &&
!document.collaborators.some(
(c) => c.userId === ctx.http?.me?.id && c.writable
(c) => c.userId === ctx.me?.id && c.writable
) &&
document.ownerId !== ctx.http?.me?.id
document.ownerId !== ctx.me?.id
) {
throw new Error("User is not owner or collaborator of document");
}
@@ -563,9 +515,6 @@ export class DocumentSchema extends PothosSchema {
writable: t.arg({ type: "Boolean", required: true }),
},
resolve: async (_, __, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error("Not allowed");
}
// check if ctx user is owner of document
const document = await this.prisma.document.findUnique({
where: { id: args.documentId },
@@ -573,7 +522,7 @@ export class DocumentSchema extends PothosSchema {
if (!document) {
throw new Error("Document not found");
}
if (document.ownerId !== ctx.http?.me?.id) {
if (document.ownerId !== ctx.me?.id) {
throw new Error("User is not owner of document");
}
return await this.prisma.documentCollaborator.create({
@@ -593,9 +542,6 @@ export class DocumentSchema extends PothosSchema {
userId: t.arg({ type: "String", required: true }),
},
resolve: async (_, __, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error("Not allowed");
}
// check if ctx user is owner of document
const document = await this.prisma.document.findUnique({
where: { id: args.documentId },
@@ -603,7 +549,7 @@ export class DocumentSchema extends PothosSchema {
if (!document) {
throw new Error("Document not found");
}
if (document.ownerId !== ctx.http?.me?.id) {
if (document.ownerId !== ctx.me?.id) {
throw new Error("User is not owner of document");
}
return await this.prisma.documentCollaborator.delete({
@@ -625,9 +571,6 @@ export class DocumentSchema extends PothosSchema {
writable: t.arg({ type: "Boolean", required: true }),
},
resolve: async (_, __, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error("Not allowed");
}
// check if ctx user is owner of document
const document = await this.prisma.document.findUnique({
where: { id: args.documentId },
@@ -635,7 +578,7 @@ export class DocumentSchema extends PothosSchema {
if (!document) {
throw new Error("Document not found");
}
if (document.ownerId !== ctx.http?.me?.id) {
if (document.ownerId !== ctx.me?.id) {
throw new Error("User is not owner of document");
}
return await this.prisma.documentCollaborator.update({
@@ -656,9 +599,6 @@ export class DocumentSchema extends PothosSchema {
imageId: t.arg({ type: "String", required: true }),
},
resolve: async (query, _parent, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error("Not allowed");
}
const document = await this.prisma.document.findUnique({
where: { id: args.documentId },
include: {
@@ -669,9 +609,9 @@ export class DocumentSchema extends PothosSchema {
throw new Error("Document not found");
}
if (
document.ownerId !== ctx.http?.me?.id &&
document.ownerId !== ctx.me?.id &&
!document.collaborators.some(
(c) => c.userId === ctx.http?.me?.id && (c.writable || c.readable)
(c) => c.userId === ctx.me?.id && (c.writable || c.readable)
)
) {
throw new Error("User is not owner or collaborator of document");
@@ -702,9 +642,6 @@ export class DocumentSchema extends PothosSchema {
}),
},
subscribe: async (_, args, ctx: SchemaContext) => {
if (!ctx.isSubscription) {
throw new Error("Not allowed");
}
const documentId = args.documentId;
// check user permission
const document = await this.prisma.document.findUnique({
@@ -718,17 +655,17 @@ export class DocumentSchema extends PothosSchema {
}
if (!document.isPublic) {
if (
document.ownerId !== ctx.websocket?.me?.id &&
document.ownerId !== ctx.me?.id &&
!document.collaborators.some(
(c) =>
c.userId === ctx.websocket?.me?.id &&
c.userId === ctx.me?.id &&
(c.writable || c.readable)
)
) {
throw new Error("User is not owner or collaborator of document");
}
}
return ctx.websocket.pubSub.asyncIterator([
return ctx.pubSub.asyncIterator([
`${DocumentEvent.CHANGED}.${documentId}`,
`${DocumentEvent.DELETED}.${documentId}`,
`${DocumentEvent.SAVED}.${documentId}`,
@@ -739,11 +676,7 @@ export class DocumentSchema extends PothosSchema {
`${DocumentEvent.CURSOR_MOVED}.${documentId}`,
]) as unknown as AsyncIterable<DocumentDelta>;
},
resolve: async (payload: DocumentDelta, _args, ctx: SchemaContext) => {
if (!ctx.isSubscription) {
throw new Error("Not allowed");
}
resolve: async (payload: DocumentDelta, _args, _ctx: SchemaContext) => {
// If there's an explicit sync request, pass it through immediately
if (payload.requestSync) {
return payload;
@@ -797,9 +730,6 @@ export class DocumentSchema extends PothosSchema {
pageIndex: number,
ctx: SchemaContext
): Promise<void> {
if (!ctx.isSubscription) {
throw new Error("Not allowed");
}
try {
const syncKey = `document:sync:${documentId}:${pageIndex}`;
const currentTime = DateTimeUtils.now().toMillis().toString();
@@ -823,7 +753,7 @@ export class DocumentSchema extends PothosSchema {
}
// Optionally publish AI suggestion if needed
ctx.websocket.pubSub.publish(
ctx.pubSub.publish(
`${DocumentEvent.AI_SUGGESTION}.${documentId}`,
{
documentId,

View File

@@ -1,93 +1,84 @@
import { Injectable, Logger } from '@nestjs/common'
import SchemaBuilder from '@pothos/core'
import AuthzPlugin from '@pothos/plugin-authz'
import ErrorsPlugin from '@pothos/plugin-errors'
import PrismaPlugin, { PothosPrismaDatamodel, PrismaClient } from '@pothos/plugin-prisma'
import PrismaUtils from '@pothos/plugin-prisma-utils'
import RelayPlugin from '@pothos/plugin-relay'
import SimpleObjectPlugin from '@pothos/plugin-simple-objects'
import SmartSubscriptionPlugin, { subscribeOptionsFromIterator } from '@pothos/plugin-smart-subscriptions'
import ZodPlugin from '@pothos/plugin-zod'
import { User } from '@prisma/client'
import { JsonValue } from '@prisma/client/runtime/library'
import { Request, Response } from 'express'
import { Kind, ValueNode } from 'graphql'
import { RedisPubSub } from 'graphql-redis-subscriptions'
import { JSONObjectResolver } from 'graphql-scalars'
import { Injectable, Logger } from "@nestjs/common";
import SchemaBuilder from "@pothos/core";
import AuthzPlugin from "@pothos/plugin-authz";
import ErrorsPlugin from "@pothos/plugin-errors";
import PrismaPlugin, {
PothosPrismaDatamodel,
PrismaClient,
} from "@pothos/plugin-prisma";
import PrismaUtils from "@pothos/plugin-prisma-utils";
import RelayPlugin from "@pothos/plugin-relay";
import SimpleObjectPlugin from "@pothos/plugin-simple-objects";
import SmartSubscriptionPlugin, {
subscribeOptionsFromIterator,
} from "@pothos/plugin-smart-subscriptions";
import ZodPlugin from "@pothos/plugin-zod";
import { User } from "@prisma/client";
import { JsonValue } from "@prisma/client/runtime/library";
import { Request, Response } from "express";
import { Kind, ValueNode } from "graphql";
import { RedisPubSub } from "graphql-redis-subscriptions";
import { JSONObjectResolver } from "graphql-scalars";
// @ts-expect-error
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'
import GraphQLUpload from "graphql-upload/GraphQLUpload.mjs";
// @ts-expect-error
import type { FileUpload } from 'graphql-upload/processRequest.mjs'
import { DateTime } from 'luxon'
import Delta from 'quill-delta'
import { DateTimeUtils } from '../common/utils/datetime.utils'
import type PrismaTypes from '../types/pothos.generated'
import { getDatamodel } from '../types/pothos.generated'
import { PrismaCrudGenerator } from './graphql.generator'
export type SchemaContext =
| {
isSubscription: true
websocket: {
req: Request
pubSub: RedisPubSub
sessionId: string
me: User
generator: PrismaCrudGenerator<BuilderTypes>
}
}
| {
isSubscription: false
http: {
req: Request
res: Response
me: User | null
pubSub: RedisPubSub
invalidateCache: () => Promise<void>
generator: PrismaCrudGenerator<BuilderTypes>
}
}
import type { FileUpload } from "graphql-upload/processRequest.mjs";
import { DateTime } from "luxon";
import Delta from "quill-delta";
import { DateTimeUtils } from "../common/utils/datetime.utils";
import type PrismaTypes from "../types/pothos.generated";
import { getDatamodel } from "../types/pothos.generated";
import { PrismaCrudGenerator } from "./graphql.generator";
export type SchemaContext = {
req: Request;
pubSub: RedisPubSub;
sessionId: string;
me: User;
generator: PrismaCrudGenerator<BuilderTypes>;
res: Response;
invalidateCache: () => Promise<void>;
};
// extend prisma types to contain string type
export interface SchemaBuilderOption {
Context: SchemaContext
PrismaTypes: PrismaTypes
DataModel: PothosPrismaDatamodel
Context: SchemaContext;
PrismaTypes: PrismaTypes;
DataModel: PothosPrismaDatamodel;
Connection: {
totalCount: number | (() => number | Promise<number>)
}
totalCount: number | (() => number | Promise<number>);
};
// AuthZRule: keyof typeof rules;
Scalars: {
DateTime: {
Input: string | DateTime | Date
Output: string | DateTime | Date
}
Input: string | DateTime | Date;
Output: string | DateTime | Date;
};
Json: {
Input: JsonValue
Output: JsonValue
}
Input: JsonValue;
Output: JsonValue;
};
Upload: {
Input: FileUpload
Output: FileUpload
}
Input: FileUpload;
Output: FileUpload;
};
Int: {
Input: number
Output: number | bigint | string
}
Input: number;
Output: number | bigint | string;
};
Delta: {
Input: Delta
Output: Delta
}
Input: Delta;
Output: Delta;
};
JsonList: {
Input: JsonValue[]
Output: JsonValue[]
}
}
Input: JsonValue[];
Output: JsonValue[];
};
};
}
@Injectable()
export class Builder extends SchemaBuilder<SchemaBuilderOption> {
public generator: PrismaCrudGenerator<BuilderTypes>
public generator: PrismaCrudGenerator<BuilderTypes>;
constructor(private readonly prisma: PrismaClient) {
super({
@@ -104,17 +95,15 @@ export class Builder extends SchemaBuilder<SchemaBuilderOption> {
smartSubscriptions: {
debounceDelay: 1000,
...subscribeOptionsFromIterator((name, context) => {
return context.isSubscription
? context.websocket.pubSub.asyncIterator(name)
: context.http.pubSub.asyncIterator(name)
return context.pubSub.asyncIterator(name);
}),
},
zod: {
// optionally customize how errors are formatted
validationError: (zodError, _args, _context, _info) => {
// the default behavior is to just throw the zod error directly
Logger.error(zodError.message, 'Zod Error')
return zodError
Logger.error(zodError.message, "Zod Error");
return zodError;
},
},
relay: {},
@@ -123,65 +112,70 @@ export class Builder extends SchemaBuilder<SchemaBuilderOption> {
exposeDescriptions: true,
filterConnectionTotalCount: true,
onUnusedQuery: (info) => {
Logger.log(`Unused query: ${info.fieldName}`, 'GraphQL')
Logger.log(`Unused query: ${info.fieldName}`, "GraphQL");
},
dmmf: getDatamodel(),
},
errors: {
defaultTypes: [],
},
})
this.generator = new PrismaCrudGenerator<BuilderTypes>(this)
this.scalarType('DateTime', {
});
this.generator = new PrismaCrudGenerator<BuilderTypes>(this);
this.scalarType("DateTime", {
serialize: (value) => {
if (typeof value === 'string') {
return value
if (typeof value === "string") {
return value;
}
if (typeof value === 'object' && value !== null && 'toISO' in value) {
return value
if (typeof value === "object" && value !== null && "toISO" in value) {
return value;
}
if (value instanceof Date) {
return DateTimeUtils.toIsoString(DateTimeUtils.fromDate(value))
return DateTimeUtils.toIsoString(DateTimeUtils.fromDate(value));
}
throw new Error('Invalid DateTime')
throw new Error("Invalid DateTime");
},
parseValue: (value) => {
if (typeof value === 'string') {
return DateTimeUtils.fromIsoString(value)
if (typeof value === "string") {
return DateTimeUtils.fromIsoString(value);
}
throw new Error('Invalid DateTime')
throw new Error("Invalid DateTime");
},
parseLiteral: (ast) => {
if (ast.kind === Kind.STRING) {
return DateTimeUtils.fromIsoString(ast.value)
return DateTimeUtils.fromIsoString(ast.value);
}
throw new Error('Invalid DateTime')
throw new Error("Invalid DateTime");
},
})
this.scalarType('Delta', {
});
this.scalarType("Delta", {
serialize: (value) => JSON.stringify(value),
parseValue: (value: unknown) => JSON.parse(value as string) as Delta,
parseLiteral: (ast: ValueNode) => ast as unknown as Delta,
})
});
this.scalarType('JsonList', {
this.scalarType("JsonList", {
serialize: (value) => JSON.stringify(value),
parseValue: (value: unknown) => JSON.parse(value as string) as JsonValue[],
parseValue: (value: unknown) =>
JSON.parse(value as string) as JsonValue[],
parseLiteral: (ast: ValueNode) => ast as unknown as JsonValue[],
})
});
this.addScalarType('Json', JSONObjectResolver)
this.addScalarType('Upload', GraphQLUpload)
this.addScalarType("Json", JSONObjectResolver);
this.addScalarType("Upload", GraphQLUpload);
this.queryType({})
this.mutationType({})
this.subscriptionType({})
this.globalConnectionField('totalCount', (t) =>
this.queryType({});
this.mutationType({});
this.subscriptionType({});
this.globalConnectionField("totalCount", (t) =>
t.int({
nullable: true,
resolve: (parent) => (typeof parent.totalCount === 'function' ? parent.totalCount() : parent.totalCount),
}),
)
resolve: (parent) =>
typeof parent.totalCount === "function"
? parent.totalCount()
: parent.totalCount,
})
);
}
}
export type BuilderTypes = PothosSchemaTypes.ExtendDefaultTypes<SchemaBuilderOption>
export type BuilderTypes =
PothosSchemaTypes.ExtendDefaultTypes<SchemaBuilderOption>;

View File

@@ -1,53 +1,53 @@
import { Global, Logger, Module } from '@nestjs/common'
import { Global, Logger, Module } from "@nestjs/common";
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default'
import { ApolloDriverConfig } from '@nestjs/apollo'
import { ConfigModule } from '@nestjs/config'
import { GraphQLModule } from '@nestjs/graphql'
import { initContextCache } from '@pothos/core'
import { PothosModule } from '@smatch-corp/nestjs-pothos'
import { PothosApolloDriver } from '@smatch-corp/nestjs-pothos-apollo-driver'
import { Request } from 'express'
import { RedisPubSub } from 'graphql-redis-subscriptions'
import { CloseCode, Context, WebSocket } from 'graphql-ws'
import { PersonalMilestoneModule } from 'src/PersonalMilestone/personalmilestone.module'
import { AdminNoteModule } from '../AdminNote/adminnote.module'
import { AnalyticModule } from '../Analytic/analytic.module'
import { AppConfigModule } from '../AppConfig/appconfig.module'
import { CategoryModule } from '../Category/category.module'
import { CenterModule } from '../Center/center.module'
import { CenterMentorModule } from '../CenterMentor/centermentor.module'
import { ChatroomModule } from '../ChatRoom/chatroom.module'
import { CollaborationSessionModule } from '../CollaborationSession/collaborationsession.module'
import { DocumentModule } from '../Document/document.module'
import { ManagedServiceModule } from '../ManagedService/managedservice.module'
import { MeetingRoomModule } from '../MeetingRoom/meetingroom.module'
import { MessageModule } from '../Message/message.module'
import { OrderModule } from '../Order/order.module'
import { PaymentModule } from '../Payment/payment.module'
import { PrismaModule } from '../Prisma/prisma.module'
import { PrismaService } from '../Prisma/prisma.service'
import { PubSubModule } from '../PubSub/pubsub.module'
import { PubSubService } from '../PubSub/pubsub.service'
import { QuizModule } from '../Quiz/quiz.module'
import { RedisModule } from '../Redis/redis.module'
import { RedisService } from '../Redis/redis.service'
import { RefundTicketModule } from '../RefundTicket/refundticket.module'
import { ResumeModule } from '../Resume/resume.module'
import { ScheduleModule } from '../Schedule/schedule.module'
import { ServiceModule } from '../Service/service.module'
import { ServiceAndCategoryModule } from '../ServiceAndCategory/serviceandcategory.module'
import { ServiceFeedbackModule } from '../ServiceFeedback/servicefeedback.module'
import { UploadedFileModule } from '../UploadedFile/uploadedfile.module'
import { UserModule } from '../User/user.module'
import { WorkshopModule } from '../Workshop/workshop.module'
import { WorkshopMeetingRoomModule } from '../WorkshopMeetingRoom/workshopmeetingroom.module'
import { WorkshopOrganizationModule } from '../WorkshopOrganization/workshoporganization.module'
import { WorkshopSubscriptionModule } from '../WorkshopSubscription/workshopsubscription.module'
import { CommonModule } from '../common/common.module'
import { Builder } from './graphql.builder'
import { PrismaCrudGenerator } from './graphql.generator'
import { GraphqlService } from './graphql.service'
import { ApolloServerPluginLandingPageLocalDefault } from "@apollo/server/plugin/landingPage/default";
import { ApolloDriverConfig } from "@nestjs/apollo";
import { ConfigModule } from "@nestjs/config";
import { GraphQLModule } from "@nestjs/graphql";
import { initContextCache } from "@pothos/core";
import { PothosModule } from "@smatch-corp/nestjs-pothos";
import { PothosApolloDriver } from "@smatch-corp/nestjs-pothos-apollo-driver";
import { Request } from "express";
import { RedisPubSub } from "graphql-redis-subscriptions";
import { CloseCode, Context, WebSocket } from "graphql-ws";
import { PersonalMilestoneModule } from "src/PersonalMilestone/personalmilestone.module";
import { AdminNoteModule } from "../AdminNote/adminnote.module";
import { AnalyticModule } from "../Analytic/analytic.module";
import { AppConfigModule } from "../AppConfig/appconfig.module";
import { CategoryModule } from "../Category/category.module";
import { CenterModule } from "../Center/center.module";
import { CenterMentorModule } from "../CenterMentor/centermentor.module";
import { ChatroomModule } from "../ChatRoom/chatroom.module";
import { CollaborationSessionModule } from "../CollaborationSession/collaborationsession.module";
import { DocumentModule } from "../Document/document.module";
import { ManagedServiceModule } from "../ManagedService/managedservice.module";
import { MeetingRoomModule } from "../MeetingRoom/meetingroom.module";
import { MessageModule } from "../Message/message.module";
import { OrderModule } from "../Order/order.module";
import { PaymentModule } from "../Payment/payment.module";
import { PrismaModule } from "../Prisma/prisma.module";
import { PrismaService } from "../Prisma/prisma.service";
import { PubSubModule } from "../PubSub/pubsub.module";
import { PubSubService } from "../PubSub/pubsub.service";
import { QuizModule } from "../Quiz/quiz.module";
import { RedisModule } from "../Redis/redis.module";
import { RedisService } from "../Redis/redis.service";
import { RefundTicketModule } from "../RefundTicket/refundticket.module";
import { ResumeModule } from "../Resume/resume.module";
import { ScheduleModule } from "../Schedule/schedule.module";
import { ServiceModule } from "../Service/service.module";
import { ServiceAndCategoryModule } from "../ServiceAndCategory/serviceandcategory.module";
import { ServiceFeedbackModule } from "../ServiceFeedback/servicefeedback.module";
import { UploadedFileModule } from "../UploadedFile/uploadedfile.module";
import { UserModule } from "../User/user.module";
import { WorkshopModule } from "../Workshop/workshop.module";
import { WorkshopMeetingRoomModule } from "../WorkshopMeetingRoom/workshopmeetingroom.module";
import { WorkshopOrganizationModule } from "../WorkshopOrganization/workshoporganization.module";
import { WorkshopSubscriptionModule } from "../WorkshopSubscription/workshopsubscription.module";
import { CommonModule } from "../common/common.module";
import { Builder } from "./graphql.builder";
import { PrismaCrudGenerator } from "./graphql.generator";
import { GraphqlService } from "./graphql.service";
@Global()
@Module({
imports: [
@@ -94,36 +94,48 @@ import { GraphqlService } from './graphql.service'
}),
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: PothosApolloDriver,
inject: [GraphqlService, 'PUB_SUB_REDIS'],
useFactory: async (graphqlService: GraphqlService, pubsub: RedisPubSub) => ({
path: process.env.API_PATH + '/graphql',
inject: [GraphqlService, "PUB_SUB_REDIS"],
useFactory: async (
graphqlService: GraphqlService,
pubsub: RedisPubSub
) => ({
path: process.env.API_PATH + "/graphql",
debug: true,
playground: false,
allowBatchedHttpRequests: true,
includeStacktraceInErrorResponses: false,
introspection: process.env.NODE_ENV === 'development' || false,
introspection: process.env.NODE_ENV === "development" || false,
logger: {
debug: (...args) => Logger.debug(...args, 'GraphqlModule'),
info: (...args) => Logger.log(...args, 'GraphqlModule'),
warn: (...args) => Logger.warn(...args, 'GraphqlModule'),
error: (...args) => Logger.error(...args, 'GraphqlModule'),
debug: (...args) => Logger.debug(...args, "GraphqlModule"),
info: (...args) => Logger.log(...args, "GraphqlModule"),
warn: (...args) => Logger.warn(...args, "GraphqlModule"),
error: (...args) => Logger.error(...args, "GraphqlModule"),
},
plugins: [ApolloServerPluginLandingPageLocalDefault()],
installSubscriptionHandlers: true,
subscriptions: {
'graphql-ws': {
"graphql-ws": {
onSubscribe(ctx, message) {
console.log('onSubscribe', ctx, message)
console.log("onSubscribe", ctx, message);
},
onConnect: (ctx: Context<Record<string, unknown>>) => {
if (!ctx.connectionParams) {
Logger.log('No connectionParams provided', 'GraphqlModule')
Logger.log("No connectionParams provided", "GraphqlModule");
}
if (!ctx.connectionParams?.["x-session-id"]) {
Logger.log("No sessionId provided", "GraphqlModule");
}
if (!ctx.extra) {
Logger.log('No extra provided', 'GraphqlModule')
Logger.log("No extra provided", "GraphqlModule");
}
// @ts-expect-error: Request is not typed
ctx.extra.request.headers['x-session-id'] = ctx.connectionParams['x-session-id']
const sessionId: string = ctx.connectionParams?.[
"x-session-id"
] as string;
if (!sessionId) {
Logger.log("No sessionId provided", "GraphqlModule");
}
// @ts-expect-error: extra is not typed
ctx.extra.request.headers["x-session-id"] = sessionId;
},
},
},
@@ -132,37 +144,34 @@ import { GraphqlService } from './graphql.service'
subscriptions,
extra,
}: {
req?: Request
subscriptions?: Record<string, never>
extra?: Record<string, never>
req?: Request;
subscriptions?: Record<string, never>;
extra?: Record<string, never>;
}) => {
initContextCache()
initContextCache();
if (subscriptions) {
// @ts-expect-error: TODO
if (!extra?.request?.headers['x-session-id']) {
throw new Error('No sessionId provided')
if (!extra?.request?.headers["x-session-id"]) {
throw new Error("No sessionId provided");
}
return {
isSubscription: true,
websocket: {
req: extra?.request,
pubSub: pubsub,
me: await graphqlService.acquireContextFromSessionId(
// @ts-expect-error: TODO
extra.request.headers['x-session-id'],
extra.request.headers["x-session-id"]
),
},
}
};
}
return {
isSubscription: false,
http: {
req,
me: req ? await graphqlService.acquireContext(req) : null,
pubSub: pubsub,
invalidateCache: () => graphqlService.invalidateCache(req?.headers['x-session-id'] as string),
},
}
invalidateCache: () =>
graphqlService.invalidateCache(
req?.headers["x-session-id"] as string
),
};
},
}),
}),
@@ -171,8 +180,9 @@ import { GraphqlService } from './graphql.service'
RedisService,
{
provide: GraphqlService,
useFactory: (prisma: PrismaService, redis: RedisService) => new GraphqlService(prisma, redis),
inject: [PrismaService, 'REDIS_CLIENT'],
useFactory: (prisma: PrismaService, redis: RedisService) =>
new GraphqlService(prisma, redis),
inject: [PrismaService, "REDIS_CLIENT"],
},
{
provide: Builder,

View File

@@ -75,10 +75,7 @@ export class MeetingRoomSchema extends PothosSchema {
required: true,
}),
},
resolve: async (_query, _parent, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
resolve: async (_query, _parent, args, _ctx: SchemaContext) => {
const collaborationSession = await this.prisma.collaborationSession.findUnique({
where: {
scheduleDateId: args.scheduleDateId,
@@ -112,10 +109,7 @@ export class MeetingRoomSchema extends PothosSchema {
}),
},
resolve: async (_, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
const meetingRoom = await this.prisma.meetingRoom.findUnique({
@@ -131,11 +125,11 @@ export class MeetingRoomSchema extends PothosSchema {
if (!collaborationSession) {
throw new Error('Collaboration session not found')
}
if (!collaborationSession.collaboratorsIds.includes(ctx.http.me.id)) {
if (!collaborationSession.collaboratorsIds.includes(ctx.me.id)) {
throw new Error('User is not collaborator')
}
// create new token
const token = await this.livekitService.createToken(ctx.http.me, meetingRoom.id)
const token = await this.livekitService.createToken(ctx.me, meetingRoom.id)
return {
id: meetingRoom.id,
token,
@@ -151,13 +145,10 @@ export class MeetingRoomSchema extends PothosSchema {
}),
},
resolve: async (_, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
const token = await this.livekitService.createToken(ctx.http.me, args.scheduleId)
const token = await this.livekitService.createToken(ctx.me, args.scheduleId)
return {
id: args.scheduleId,
token,
@@ -181,10 +172,7 @@ export class MeetingRoomSchema extends PothosSchema {
}),
},
resolve: async (query, _parent, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
return await this.prisma.meetingRoom.create({
@@ -207,10 +195,7 @@ export class MeetingRoomSchema extends PothosSchema {
}),
},
resolve: async (query, _parent, args, ctx: SchemaContext) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
return await this.prisma.meetingRoom.update({

View File

@@ -20,7 +20,7 @@ import { DateTimeUtils } from "../common/utils/datetime.utils";
export class MessageSchema extends PothosSchema {
constructor(
@Inject(SchemaBuilderToken) private readonly builder: Builder,
private readonly prisma: PrismaService
private readonly prisma: PrismaService,
) {
super();
}
@@ -92,9 +92,6 @@ export class MessageSchema extends PothosSchema {
"Retrieve a list of messages with optional filtering, ordering, and pagination.",
args: this.builder.generator.findManyArgs("Message"),
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error("Not allowed");
}
if (args.filter?.context && typeof args.filter.context === "object") {
// if args.context is NOTIFICATION or SYSTEM, filter by recipientId
if (
@@ -105,7 +102,7 @@ export class MessageSchema extends PothosSchema {
?.toString()
.includes(MessageContextType.SYSTEM)
) {
args.filter.recipientId = ctx.http.me?.id;
args.filter.recipientId = ctx.me?.id;
}
}
return await this.prisma.message.findMany({
@@ -151,14 +148,11 @@ export class MessageSchema extends PothosSchema {
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error("Not allowed");
}
const messageContext = MessageContextType.CHAT;
// get the sender from the context and add it to the input
args.input.sender = {
connect: {
id: ctx.http.me?.id,
id: ctx.me?.id,
},
};
if (!args.input.sender) {
@@ -206,13 +200,13 @@ export class MessageSchema extends PothosSchema {
});
return message;
});
ctx.http.pubSub.publish(
ctx.pubSub.publish(
`${PubSubEvent.MESSAGE_SENT}.${message.chatRoomId}`,
message
);
// publish to new message subscribers
userIds.forEach((userId: string) => {
ctx.http.pubSub.publish(
ctx.pubSub.publish(
`${PubSubEvent.NEW_MESSAGE}.${userId}`,
message
);
@@ -233,10 +227,7 @@ export class MessageSchema extends PothosSchema {
}),
},
subscribe: (_, args, ctx: SchemaContext) => {
if (!ctx.isSubscription) {
throw new Error("Not allowed");
}
return ctx.websocket.pubSub.asyncIterator([
return ctx.pubSub.asyncIterator([
`${PubSubEvent.MESSAGE_SENT}.${args.chatRoomId}`,
]) as unknown as AsyncIterable<Message>;
},

View File

@@ -154,10 +154,7 @@ export class OrderSchema extends PothosSchema {
description: 'Retrieve a list of completed orders',
args: this.builder.generator.findManyArgs('Order'),
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Orders cannot be retrieved in subscription context')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
// return orders where user is the one who made the order and status is PAID and schedule.dates is in the past
@@ -167,7 +164,7 @@ export class OrderSchema extends PothosSchema {
AND: [
...(args.filter ? [args.filter] : []),
{
userId: ctx.http.me.id,
userId: ctx.me.id,
status: OrderStatus.PAID,
schedule: {
OR: [
@@ -202,14 +199,11 @@ export class OrderSchema extends PothosSchema {
description: 'Retrieve a list of completed orders for moderator',
args: this.builder.generator.findManyArgs('Order'),
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Orders cannot be retrieved in subscription context')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
// only for role moderator
if (ctx.http.me.role !== Role.MODERATOR) {
if (ctx.me.role !== Role.MODERATOR) {
throw new Error('Unauthorized')
}
// return completed order list where schedule status is COMPLETED
@@ -239,13 +233,10 @@ export class OrderSchema extends PothosSchema {
},
description: 'Retrieve a list of completed orders details',
resolve: async (_query, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Orders cannot be retrieved in subscription context')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
if (ctx.http.me.role !== Role.MODERATOR) {
if (ctx.me.role !== Role.MODERATOR) {
throw new Error('Unauthorized')
}
// get order details
@@ -367,10 +358,7 @@ export class OrderSchema extends PothosSchema {
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Subscription is not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
if (!args.data.service.connect?.id) {
@@ -388,7 +376,7 @@ export class OrderSchema extends PothosSchema {
where: {
AND: [
{
customerId: ctx.http.me?.id,
customerId: ctx.me?.id,
},
{
managedService: {
@@ -419,7 +407,7 @@ export class OrderSchema extends PothosSchema {
data: {
status: OrderStatus.PENDING,
total: service.price,
userId: ctx.http.me?.id ?? '',
userId: ctx.me?.id ?? '',
serviceId: service.id,
scheduleId: args.data.schedule.connect?.id ?? '',
commission: service.commission ?? 0.0,
@@ -460,8 +448,8 @@ export class OrderSchema extends PothosSchema {
orderCode: paymentCode,
amount: service.price,
description: _name,
buyerName: ctx.http.me?.name ?? '',
buyerEmail: ctx.http.me?.email ?? '',
buyerName: ctx.me?.name ?? '',
buyerEmail: ctx.me?.email ?? '',
returnUrl: `${process.env.PAYOS_RETURN_URL}`.replace('<serviceId>', service.id),
cancelUrl: `${process.env.PAYOS_RETURN_URL}`.replace('<serviceId>', service.id),
expiredAt: DateTimeUtils.now().plus({ minutes: 15 }).toUnixInteger(),
@@ -519,13 +507,10 @@ export class OrderSchema extends PothosSchema {
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Subscription is not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
if (ctx.http.me.role !== Role.MODERATOR) {
if (ctx.me.role !== Role.MODERATOR) {
throw new Error('Unauthorized')
}
return await this.prisma.order.update({

View File

@@ -65,10 +65,7 @@ export class PersonalMilestoneSchema extends PothosSchema {
args: this.builder.generator.findUniqueArgs('PersonalMilestone'),
description: 'Retrieve a single personal milestone by its unique identifier.',
resolve: async (_query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Cannot get your info')
}
return this.prisma.personalMilestone.findUnique({
@@ -81,10 +78,7 @@ export class PersonalMilestoneSchema extends PothosSchema {
args: this.builder.generator.findManyArgs('PersonalMilestone'),
description: 'Retrieve multiple personal milestones.',
resolve: async (_query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Cannot get your info')
}
return this.prisma.personalMilestone.findMany({
@@ -119,13 +113,10 @@ export class PersonalMilestoneSchema extends PothosSchema {
},
description: 'Create multiple personal milestones.',
resolve: async (_query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Cannot get your info')
}
const userId = ctx.http.me.id
const userId = ctx.me.id
const result = await this.prisma.personalMilestone.createManyAndReturn({
data: args.data.map((data) => ({
...data,
@@ -158,13 +149,10 @@ export class PersonalMilestoneSchema extends PothosSchema {
},
description: 'Update a personal milestone.',
resolve: async (_query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Cannot get your info')
}
const userId = ctx.http.me.id
const userId = ctx.me.id
return this.prisma.personalMilestone.update({
where: {
...args.where,
@@ -185,10 +173,7 @@ export class PersonalMilestoneSchema extends PothosSchema {
},
description: 'Delete a personal milestone.',
resolve: async (_query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Cannot get your info')
}
// check if the user is mentor of the schedule where the personal milestone belongs to
@@ -202,7 +187,7 @@ export class PersonalMilestoneSchema extends PothosSchema {
const managedService = await this.prisma.managedService.findUnique({
where: { id: schedule.managedServiceId },
})
if (managedService?.mentorId !== ctx.http.me.id) {
if (managedService?.mentorId !== ctx.me.id) {
throw new Error('Unauthorized')
}
}

View File

@@ -145,10 +145,7 @@ export class QuizSchema extends PothosSchema {
type: this.quiz(),
args: this.builder.generator.findUniqueArgs('Quiz'),
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Subscription is not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
return await this.prisma.quiz.findUnique({ ...query, where: { id: args.where.id } })
@@ -167,17 +164,14 @@ export class QuizSchema extends PothosSchema {
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Subscription is not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
// use case 1: customer
if (ctx.http.me.role === Role.CUSTOMER) {
if (ctx.me.role === Role.CUSTOMER) {
// using pseudo random to get amount of quizzes based on userid as seed
const random = getRandomWithSeed(
parseInt(crypto.createHash('sha256').update(ctx.http.me.id).digest('hex'), 16),
parseInt(crypto.createHash('sha256').update(ctx.me.id).digest('hex'), 16),
)
if (!args.scheduleId) {
throw new Error('Schedule ID is required')
@@ -192,7 +186,7 @@ export class QuizSchema extends PothosSchema {
if (!schedule) {
throw new Error('Schedule not found')
}
if (schedule.customerId !== ctx.http.me.id) {
if (schedule.customerId !== ctx.me.id) {
throw new Error('Unauthorized')
}
// get centerMentorId from schedule
@@ -211,7 +205,7 @@ export class QuizSchema extends PothosSchema {
// check if user has already taken the quiz
const quizAttempt = await this.prisma.quizAttempt.findFirst({
where: {
userId: ctx.http.me.id,
userId: ctx.me.id,
quizId: quizzes[0]?.id,
},
})
@@ -226,9 +220,9 @@ export class QuizSchema extends PothosSchema {
}
// use case 2: center mentor or center owner
if (ctx.http.me.role === Role.CENTER_MENTOR || ctx.http.me.role === Role.CENTER_OWNER) {
if (ctx.me.role === Role.CENTER_MENTOR || ctx.me.role === Role.CENTER_OWNER) {
const centerMentor = await this.prisma.centerMentor.findUnique({
where: { mentorId: ctx.http.me.id },
where: { mentorId: ctx.me.id },
})
if (!centerMentor) {
throw new Error('Center mentor not found')
@@ -252,10 +246,7 @@ export class QuizSchema extends PothosSchema {
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
if (!args.id) {
@@ -272,10 +263,10 @@ export class QuizSchema extends PothosSchema {
}
// check if user is the owner of the quiz attempt
if (
result.userId !== ctx.http.me.id &&
ctx.http.me.role !== Role.CENTER_OWNER &&
ctx.http.me.role !== Role.CENTER_MENTOR &&
ctx.http.me.role !== Role.CUSTOMER
result.userId !== ctx.me.id &&
ctx.me.role !== Role.CENTER_OWNER &&
ctx.me.role !== Role.CENTER_MENTOR &&
ctx.me.role !== Role.CUSTOMER
) {
throw new Error('Unauthorized')
}
@@ -299,16 +290,13 @@ export class QuizSchema extends PothosSchema {
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
// use case 1: center mentor or center owner
if (ctx.http.me.role === Role.CENTER_MENTOR || ctx.http.me.role === Role.CENTER_OWNER) {
if (ctx.me.role === Role.CENTER_MENTOR || ctx.me.role === Role.CENTER_OWNER) {
const centerMentor = await this.prisma.centerMentor.findUnique({
where: { mentorId: ctx.http.me.id },
where: { mentorId: ctx.me.id },
})
if (!centerMentor) {
throw new Error('Center mentor not found')
@@ -331,11 +319,11 @@ export class QuizSchema extends PothosSchema {
})
}
// use case 2: customer
if (ctx.http.me.role === Role.CUSTOMER) {
if (ctx.me.role === Role.CUSTOMER) {
return await this.prisma.quizAttempt.findMany({
...query,
where: {
userId: ctx.http.me.id,
userId: ctx.me.id,
...(args.quizId ? [{ quizId: args.quizId }] : []),
},
orderBy: {
@@ -356,10 +344,7 @@ export class QuizSchema extends PothosSchema {
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Subscription is not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
if (!args.data) {
@@ -370,7 +355,7 @@ export class QuizSchema extends PothosSchema {
}
args.data.centerMentor = {
connect: {
mentorId: ctx.http.me.id,
mentorId: ctx.me.id,
},
}
return await this.prisma.quiz.create({
@@ -394,10 +379,7 @@ export class QuizSchema extends PothosSchema {
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Subscription is not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
return await this.prisma.quiz.update({
@@ -422,10 +404,7 @@ export class QuizSchema extends PothosSchema {
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Subscription is not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
if (!args.data) {
@@ -450,7 +429,7 @@ export class QuizSchema extends PothosSchema {
data: {
...args.data,
quiz: { connect: { id: args.data.quiz.connect.id } },
user: { connect: { id: ctx.http.me.id } },
user: { connect: { id: ctx.me.id } },
},
})
// update schedule status to WAITING_INTERVIEW

View File

@@ -91,10 +91,10 @@ export class RefundTicketSchema extends PothosSchema {
id: t.arg({ type: 'String', required: true }),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Subscription is not allowed')
if (!ctx.me) {
throw new Error('User not found')
}
if (ctx.http.me?.role !== Role.MODERATOR) {
if (ctx.me.role !== Role.MODERATOR) {
throw new Error('Only moderators can retrieve refund tickets')
}
return await this.prisma.refundTicket.findUnique({ ...query, where: { id: args.id } })
@@ -132,24 +132,21 @@ export class RefundTicketSchema extends PothosSchema {
}),
},
resolve: async (_query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Subscription is not allowed')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
// Check if the user is a customer or a center mentor
if (
ctx.http.me?.role !== Role.CUSTOMER &&
ctx.http.me?.role !== Role.CENTER_MENTOR &&
ctx.http.me?.role !== Role.CENTER_OWNER
ctx.me?.role !== Role.CUSTOMER &&
ctx.me?.role !== Role.CENTER_MENTOR &&
ctx.me?.role !== Role.CENTER_OWNER
) {
throw new Error('Only customers and center mentors can request refund')
}
// Check bank details for non-center mentors
if (ctx.http.me?.role !== Role.CENTER_MENTOR) {
if (!ctx.http.me?.bankBin || !ctx.http.me?.bankAccountNumber) {
if (ctx.me?.role !== Role.CENTER_MENTOR) {
if (!ctx.me?.bankBin || !ctx.me?.bankAccountNumber) {
throw new Error('Bank bin and bank account number are required, please update your profile first')
}
}
@@ -188,7 +185,7 @@ export class RefundTicketSchema extends PothosSchema {
let refundAmount = 0
// Special handling for center mentors - full refund always allowed
if (ctx.http.me?.role === Role.CENTER_MENTOR) {
if (ctx.me?.role === Role.CENTER_MENTOR) {
refundAmount = order.total
} else {
// Existing refund logic for customers
@@ -204,12 +201,12 @@ export class RefundTicketSchema extends PothosSchema {
}
// Prepare bank details
let bankBin = ctx.http.me?.bankBin
let bankAccountNumber = ctx.http.me?.bankAccountNumber
let bankBin = ctx.me?.bankBin
let bankAccountNumber = ctx.me?.bankAccountNumber
let bankName = ''
// For center mentors, use a default or system bank account
if (ctx.http.me?.role === Role.CENTER_MENTOR) {
if (ctx.me?.role === Role.CENTER_MENTOR) {
// You might want to replace this with a specific system bank account
bankBin = 'SYSTEM_MENTOR_REFUND'
bankAccountNumber = 'SYSTEM_MENTOR_ACCOUNT'
@@ -234,7 +231,7 @@ export class RefundTicketSchema extends PothosSchema {
bankBin: bankBin,
bankAccountNumber: bankAccountNumber,
bankName: bankName,
requesterId: ctx.http.me.id,
requesterId: ctx.me.id,
},
})
// notify all Moderator
@@ -244,15 +241,15 @@ export class RefundTicketSchema extends PothosSchema {
for (const moderator of moderators) {
const message = await this.prisma.message.create({
data: {
senderId: ctx.http.me?.id ?? '',
senderId: ctx.me.id,
recipientId: moderator.id,
type: MessageType.TEXT,
content: `Có yêu cầu hoàn tiền mới từ ${ctx.http.me?.name}`,
content: `Có yêu cầu hoàn tiền mới từ ${ctx.me.name}`,
sentAt: DateTimeUtils.nowAsJSDate(),
context: MessageContextType.NOTIFICATION,
},
})
ctx.http.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${moderator.id}`, message)
ctx.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${moderator.id}`, message)
}
return refundTicket
},
@@ -275,10 +272,10 @@ export class RefundTicketSchema extends PothosSchema {
}),
},
resolve: async (_query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Subscription is not allowed')
if (!ctx.me) {
throw new Error('User not found')
}
if (ctx.http.me?.role !== Role.MODERATOR) {
if (ctx.me.role !== Role.MODERATOR) {
throw new Error('Only moderators can process refund tickets')
}
// if action is REJECT, reason is required
@@ -291,7 +288,7 @@ export class RefundTicketSchema extends PothosSchema {
data: {
status: args.action === 'APPROVE' ? RefundTicketStatus.APPROVED : RefundTicketStatus.REJECTED,
rejectedReason: args.action === 'REJECT' ? args.reason : undefined,
moderatorId: ctx.http.me.id,
moderatorId: ctx.me.id,
},
include: {
order: true,
@@ -332,7 +329,7 @@ export class RefundTicketSchema extends PothosSchema {
}
const message = await this.prisma.message.create({
data: {
senderId: ctx.http.me.id,
senderId: ctx.me.id,
recipientId: requester.id,
type: MessageType.TEXT,
content: `Yêu cầu hoàn tiền của bạn đã được ${args.action === 'APPROVE' ? 'chấp thuận' : 'từ chối'}`,
@@ -340,7 +337,7 @@ export class RefundTicketSchema extends PothosSchema {
context: MessageContextType.NOTIFICATION,
},
})
ctx.http.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${requester.id}`, message)
ctx.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${requester.id}`, message)
return refundTicket
},
}),

View File

@@ -108,13 +108,13 @@ export class ResumeSchema extends PothosSchema {
},
resolve: async (query, _root, args, ctx, _info) => {
try {
if (ctx.isSubscription) {
throw new Error('Not allowed')
if (!ctx.me) {
throw new Error('User not found')
}
const resumes = await this.prisma.resume.findMany({
...query,
where: {
userId: ctx.http.me?.id ?? '',
userId: ctx.me.id,
status: args.status ?? undefined,
},
})
@@ -205,10 +205,10 @@ export class ResumeSchema extends PothosSchema {
}),
},
resolve: async (_query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
if (!ctx.me) {
throw new Error('User not found')
}
if (ctx.http.me?.role !== Role.CUSTOMER && ctx.http.me?.role !== Role.CENTER_OWNER) {
if (ctx.me.role !== Role.CUSTOMER && ctx.me.role !== Role.CENTER_OWNER) {
throw new Error('Not allowed')
}
const { resumeFile } = args
@@ -251,15 +251,15 @@ export class ResumeSchema extends PothosSchema {
for (const moderator of moderators) {
const message = await this.prisma.message.create({
data: {
senderId: ctx.http.me?.id ?? '',
senderId: ctx.me?.id ?? '',
recipientId: moderator.id,
type: MessageType.TEXT,
content: `Có yêu cầu hồ sơ mới từ ${ctx.http.me?.name}`,
content: `Có yêu cầu hồ sơ mới từ ${ctx.me?.name}`,
sentAt: DateTimeUtils.nowAsJSDate(),
context: MessageContextType.NOTIFICATION,
},
})
ctx.http.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${moderator.id}`, message)
ctx.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${moderator.id}`, message)
}
return resume
}
@@ -278,15 +278,15 @@ export class ResumeSchema extends PothosSchema {
for (const moderator of moderators) {
const message = await this.prisma.message.create({
data: {
senderId: ctx.http.me?.id ?? '',
senderId: ctx.me?.id ?? '',
recipientId: moderator.id,
type: MessageType.TEXT,
content: `Có yêu cầu hồ sơ mới từ ${ctx.http.me?.name}`,
content: `Có yêu cầu hồ sơ mới từ ${ctx.me?.name}`,
sentAt: DateTimeUtils.nowAsJSDate(),
context: MessageContextType.NOTIFICATION,
},
})
ctx.http.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${moderator.id}`, message)
ctx.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${moderator.id}`, message)
}
return resume
},
@@ -310,10 +310,10 @@ export class ResumeSchema extends PothosSchema {
}),
},
resolve: async (_query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
if (!ctx.me) {
throw new Error('User not found')
}
if (ctx.http.me?.role !== Role.MODERATOR) {
if (ctx.me.role !== Role.MODERATOR) {
throw new Error('Not allowed')
}
const { resumeId, status, adminNote } = args
@@ -348,7 +348,7 @@ export class ResumeSchema extends PothosSchema {
_adminNote = await tx.adminNote.create({
data: {
content: adminNote,
notedByUserId: ctx.http.me?.id ?? '',
notedByUserId: ctx.me?.id ?? '',
resumeId,
},
})

View File

@@ -225,10 +225,7 @@ export class ScheduleSchema extends PothosSchema {
description: 'Retrieve a single schedule by its unique identifier.',
args: this.builder.generator.findUniqueArgs('Schedule'),
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Cannot retrieve schedule in subscription')
}
if (!ctx.http?.me?.id) {
if (!ctx.me) {
throw new Error('User not found')
}
// only return schedule belong to center
@@ -236,7 +233,7 @@ export class ScheduleSchema extends PothosSchema {
const center = await this.prisma.center.findFirst({
where: {
AND: [
{ OR: [{ centerOwnerId: ctx.http.me.id }, { centerMentors: { some: { mentorId: ctx.http.me.id } } }] },
{ OR: [{ centerOwnerId: ctx.me.id }, { centerMentors: { some: { mentorId: ctx.me.id } } }] },
{ centerStatus: CenterStatus.APPROVED },
],
},
@@ -260,14 +257,11 @@ export class ScheduleSchema extends PothosSchema {
args: this.builder.generator.findManyArgs('Schedule'),
description: 'Retrieve a list of schedules with optional filtering, ordering, and pagination.',
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Cannot retrieve schedules in subscription')
}
if (!ctx.http?.me?.id) {
if (!ctx.me) {
throw new Error('User not found')
}
// use case 1: customer query schedules where customer is participant
if (ctx.http.me.role === Role.CUSTOMER) {
if (ctx.me.role === Role.CUSTOMER) {
const schedules = await this.prisma.schedule.findMany({
...query,
orderBy: args.orderBy ?? undefined,
@@ -278,12 +272,12 @@ export class ScheduleSchema extends PothosSchema {
return schedules
}
// use case 2: center mentor or center owner query schedules where center mentor or center owner is mentor
if (ctx.http.me.role === Role.CENTER_MENTOR) {
if (ctx.me.role === Role.CENTER_MENTOR) {
const center = await this.prisma.center.findFirst({
where: {
centerMentors: {
some: {
mentorId: ctx.http.me.id,
mentorId: ctx.me.id,
},
},
},
@@ -299,7 +293,7 @@ export class ScheduleSchema extends PothosSchema {
orderBy: args.orderBy ?? undefined,
where: {
AND: [
{ managedService: { service: { centerId: center.id }, mentorId: ctx.http.me.id } },
{ managedService: { service: { centerId: center.id }, mentorId: ctx.me.id } },
...(args.filter ? [args.filter] : []),
],
},
@@ -307,9 +301,9 @@ export class ScheduleSchema extends PothosSchema {
return schedules
}
// use case 3: Center owner query all schedules belong to center
if (ctx.http.me.role === Role.CENTER_OWNER) {
if (ctx.me.role === Role.CENTER_OWNER) {
const center = await this.prisma.center.findFirst({
where: { centerOwnerId: ctx.http.me.id },
where: { centerOwnerId: ctx.me.id },
})
if (!center) {
throw new Error('Center not found')
@@ -333,10 +327,7 @@ export class ScheduleSchema extends PothosSchema {
description: 'Retrieve a list of schedule dates.',
args: this.builder.generator.findManyArgs('ScheduleDate'),
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Cannot retrieve schedule dates in subscription')
}
if (!ctx.http?.me?.id) {
if (!ctx.me) {
throw new Error('User not found')
}
return await this.prisma.scheduleDate.findMany({
@@ -345,7 +336,7 @@ export class ScheduleSchema extends PothosSchema {
take: args.take ?? undefined,
orderBy: args.orderBy ?? undefined,
where: {
AND: [{ participantIds: { has: ctx.http.me.id } }, ...(args.filter ? [args.filter] : [])],
AND: [{ participantIds: { has: ctx.me.id } }, ...(args.filter ? [args.filter] : [])],
},
})
},
@@ -404,8 +395,8 @@ d72a864e-2f41-45ab-9c9b-bf0512a31883,e9be51fd-2382-4e43-9988-74e76fde4b56,2024-1
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Cannot create schedule in subscription')
if (!ctx.me) {
throw new Error('User not found')
}
Logger.log('args.schedule', args.schedule)
// reject schedule if start date is today or in the past

View File

@@ -118,16 +118,13 @@ export class ServiceSchema extends PothosSchema {
description: 'Whether the user has already provided feedback for the service.',
nullable: true,
resolve: async (service, _args, ctx) => {
if (ctx.isSubscription) {
return null
}
if (!ctx.http.me) {
if (!ctx.me) {
return false
}
const serviceFeedbacks = await this.prisma.serviceFeedback.findMany({
where: {
serviceId: service.id,
userId: ctx.http.me.id,
userId: ctx.me.id,
},
})
return serviceFeedbacks.length > 0
@@ -164,18 +161,15 @@ export class ServiceSchema extends PothosSchema {
args: this.builder.generator.findManyArgs('Service'),
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
// check role if user is mentor or center owner
const role = ctx.http.me?.role
const role = ctx.me?.role
if (role !== Role.CENTER_MENTOR && role !== Role.CENTER_OWNER) {
throw new Error('Not allowed')
throw new Error('User not found')
}
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.me.id },
})
if (!managedServices) {
throw new Error('Managed services not found')
@@ -188,7 +182,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.me.id },
})
if (!center) {
throw new Error('Center not found')
@@ -239,11 +233,11 @@ export class ServiceSchema extends PothosSchema {
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
if (!ctx.me) {
throw new Error('User not found')
}
// replace userId with current user id
args.input.user = { connect: { id: ctx.http.me?.id ?? '' } }
args.input.user = { connect: { id: ctx.me.id } }
const service = await this.prisma.service.create({
...query,
data: args.input,
@@ -279,7 +273,7 @@ export class ServiceSchema extends PothosSchema {
})
const messages = await this.prisma.message.createManyAndReturn({
data: moderatorIds.map((moderator) => ({
senderId: ctx.http.me?.id ?? '',
senderId: ctx.me.id,
recipientId: moderator.id,
type: MessageType.TEXT,
content: `Có một dịch vụ mới với tên ${service.name} được đăng tải bởi ${center.name}`,
@@ -288,7 +282,7 @@ export class ServiceSchema extends PothosSchema {
})),
})
messages.forEach((message) => {
ctx.http.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${message.recipientId}`, message)
ctx.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${message.recipientId}`, message)
})
return service
},
@@ -355,8 +349,8 @@ export class ServiceSchema extends PothosSchema {
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
if (!ctx.me) {
throw new Error('User not found')
}
return await this.prisma.$transaction(async (prisma) => {
// check if service is already approved or rejected
@@ -382,7 +376,7 @@ export class ServiceSchema extends PothosSchema {
adminNote: {
create: {
content: args.adminNote ?? '',
notedByUserId: ctx.http.me?.id ?? '',
notedByUserId: ctx.me.id,
},
},
commission: commission ?? 0,
@@ -427,7 +421,7 @@ export class ServiceSchema extends PothosSchema {
// add message to database
const message = await this.prisma.message.create({
data: {
senderId: ctx.http.me?.id ?? '',
senderId: ctx.me.id,
recipientId: id,
type: MessageType.TEXT,
content: `Dịch vụ ${service.name} của bạn đã được chấp thuận`,
@@ -438,7 +432,7 @@ export class ServiceSchema extends PothosSchema {
},
},
})
ctx.http.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${id}`, message)
ctx.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${id}`, message)
})
} else {
await this.mailService.sendTemplateEmail(emails, 'Thông báo về trạng thái dịch vụ', 'ServiceRejected', {
@@ -455,7 +449,7 @@ export class ServiceSchema extends PothosSchema {
// add message to database
const message = await this.prisma.message.create({
data: {
senderId: ctx.http.me?.id ?? '',
senderId: ctx.me.id,
recipientId: id,
type: MessageType.TEXT,
content: `Dịch vụ ${service.name} của bạn đã bị từ chối`,
@@ -466,7 +460,7 @@ export class ServiceSchema extends PothosSchema {
},
},
})
ctx.http.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${id}`, message)
ctx.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${id}`, message)
})
}
return updatedService

View File

@@ -90,21 +90,18 @@ export class ServiceFeedbackSchema extends PothosSchema {
},
description: 'Create a new service feedback.',
resolve: async (_, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http?.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
// allow only when user is CUSTOMER and order is completed
if (ctx.http?.me?.role !== Role.CUSTOMER) {
if (ctx.me.role !== Role.CUSTOMER) {
throw new Error('Unauthorized')
}
Logger.log(`args: ${JSON.stringify(args)}`)
const order = await this.prisma.order.findFirst({
where: {
id: args.orderId,
userId: ctx.http?.me?.id,
userId: ctx.me.id,
status: OrderStatus.PAID,
schedule: {
dates: {
@@ -118,7 +115,7 @@ export class ServiceFeedbackSchema extends PothosSchema {
if (!order) {
throw new Error('Order not found')
}
if (order.userId !== ctx.http?.me?.id) {
if (order.userId !== ctx.me.id) {
throw new Error('Unauthorized')
}
if (order.status !== OrderStatus.PAID) {
@@ -134,7 +131,7 @@ export class ServiceFeedbackSchema extends PothosSchema {
}
return await this.prisma.serviceFeedback.create({
data: {
userId: ctx.http?.me?.id,
userId: ctx.me.id,
serviceId: order.serviceId,
rating: args.rating,
comments: args.comments,

View File

@@ -1,10 +1,11 @@
import { Module } from '@nestjs/common'
import { ChatroomModule } from '../ChatRoom/chatroom.module'
import { MessageModule } from '../Message/message.module'
import { ChatroomSchema } from 'src/ChatRoom/chatroom.schema'
import { MessageSchema } from 'src/Message/message.schema'
import { UserSchema } from './user.schema'
@Module({
imports: [MessageModule, ChatroomModule],
providers: [UserSchema],
providers: [UserSchema, ChatroomSchema, MessageSchema],
exports: [UserSchema],
})
export class UserModule {}

View File

@@ -170,10 +170,10 @@ export class UserSchema extends PothosSchema {
description: 'Retrieve the current user in context.',
type: this.user(),
resolve: async (_query, _root, _args, ctx) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
if (!ctx.me) {
throw new Error('User not found')
}
return ctx.http.me
return ctx.me
},
}),
@@ -184,18 +184,13 @@ export class UserSchema extends PothosSchema {
take: t.arg({ type: 'Int', required: false }),
},
resolve: async (_parent, args, ctx) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
const me = ctx.http.me
if (!me) {
if (!ctx.me) {
throw new Error('User not found')
}
// get chat rooms that the user is a part of
const chatRooms = await this.prisma.chatRoom.findMany({
where: {
OR: [{ customerId: me.id }, { mentorId: me.id }],
OR: [{ customerId: ctx.me.id }, { mentorId: ctx.me.id }],
},
orderBy: {
lastActivity: 'desc',
@@ -357,10 +352,10 @@ export class UserSchema extends PothosSchema {
}),
},
resolve: async (_query, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
if (!ctx.me) {
throw new Error('User not found')
}
const id = ctx.http.me?.id
const id = ctx.me.id
if (!id) {
throw new Error('User not found')
}
@@ -420,7 +415,7 @@ export class UserSchema extends PothosSchema {
})
}
// invalidate cache
await ctx.http.invalidateCache()
await ctx.invalidateCache()
return await this.prisma.user.findUniqueOrThrow({
where: { id: clerkUser.id },
})
@@ -434,12 +429,12 @@ export class UserSchema extends PothosSchema {
},
resolve: async (_parent, args, ctx) => {
// check context
if (ctx.isSubscription) {
if (!ctx.me) {
throw new Error('Not allowed')
}
// check context is admin
if (ctx.http.me?.role !== Role.ADMIN) {
if (ctx.me?.role !== Role.ADMIN) {
throw new Error(`Only admin can invite moderator`)
}
return this.prisma.$transaction(async (tx) => {
@@ -477,11 +472,7 @@ export class UserSchema extends PothosSchema {
}),
},
resolve: async (_, args, ctx) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
const me = ctx.http.me
if (!me) {
if (!ctx.me) {
throw new Error('User not found')
}
// create message
@@ -489,7 +480,7 @@ export class UserSchema extends PothosSchema {
data: {
type: args.input.type,
content: args.input.content,
senderId: me.id,
senderId: ctx.me.id,
recipientId: args.input.recipient?.connect?.id ?? null,
chatRoomId: args.input.chatRoom?.connect?.id ?? null,
sentAt: DateTimeUtils.nowAsJSDate(),
@@ -498,7 +489,7 @@ export class UserSchema extends PothosSchema {
},
})
// publish message
await ctx.http.pubSub.publish(`${PubSubEvent.NEW_MESSAGE}.${message.recipientId}`, message)
await ctx.pubSub.publish(`${PubSubEvent.NEW_MESSAGE}.${message.recipientId}`, message)
return message
},
}),
@@ -508,13 +499,10 @@ export class UserSchema extends PothosSchema {
userId: t.arg({ type: 'String', required: true }),
},
resolve: async (_parent, args, ctx) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (ctx.http.me?.role !== Role.ADMIN && ctx.http.me?.role !== Role.MODERATOR) {
if (ctx.me?.role !== Role.ADMIN && ctx.me?.role !== Role.MODERATOR) {
throw new Error(`Only admin or moderator can ban user`)
}
if (args.userId === ctx.http.me?.id) {
if (args.userId === ctx.me?.id) {
throw new Error(`Cannot ban yourself`)
}
// get banning user info
@@ -531,7 +519,7 @@ export class UserSchema extends PothosSchema {
// ban user from clerk
await clerkClient.users.banUser(args.userId)
// invalidate cache
await ctx.http.invalidateCache()
await ctx.invalidateCache()
// update user banned status
await this.prisma.user.update({
where: { id: args.userId },
@@ -547,13 +535,13 @@ export class UserSchema extends PothosSchema {
userScopedMessage: t.field({
type: this.messageSchema.message(),
subscribe: async (_, _args, ctx) => {
if (!ctx.isSubscription) {
if (!ctx.me) {
throw new Error('Not allowed')
}
return ctx.websocket.pubSub.asyncIterator([
`${PubSubEvent.NEW_MESSAGE}.${ctx.websocket.me?.id}`,
`${PubSubEvent.NOTIFICATION}.${ctx.websocket.me?.id}`,
return ctx.pubSub.asyncIterator([
`${PubSubEvent.NEW_MESSAGE}.${ctx.me?.id}`,
`${PubSubEvent.NOTIFICATION}.${ctx.me?.id}`,
]) as unknown as AsyncIterable<Message>
},
resolve: async (payload: Message) => payload,

View File

@@ -136,13 +136,10 @@ export class WorkshopSchema extends PothosSchema {
},
description: 'Create a new workshop.',
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Workshops cannot be created in subscription context')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('User is not authenticated to create a workshop')
}
if (ctx.http.me.role !== Role.CENTER_OWNER) {
if (ctx.me.role !== Role.CENTER_OWNER) {
throw new Error('Only center owners can create workshops')
}
if (!args.input.service.connect) {
@@ -186,7 +183,7 @@ export class WorkshopSchema extends PothosSchema {
for (const customer of customers) {
const message = await this.prisma.message.create({
data: {
senderId: ctx.http.me?.id ?? '',
senderId: ctx.me?.id ?? '',
recipientId: customer.id,
type: MessageType.TEXT,
content: `Workshop ${workshop.title} đã được lên lịch do ${service.center.name} tổ chức. Nhanh tay đăng kí ngay!`,
@@ -194,7 +191,7 @@ export class WorkshopSchema extends PothosSchema {
context: MessageContextType.NOTIFICATION,
},
})
ctx.http.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${customer.id}`, message)
ctx.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${customer.id}`, message)
}
return workshop
},

View File

@@ -72,10 +72,7 @@ export class WorkshopMeetingRoomSchema extends PothosSchema {
}),
},
resolve: async (_, args, ctx) => {
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
if (!ctx.http?.me) {
if (!ctx.me) {
throw new Error('Unauthorized')
}
const meetingRoom = await this.prisma.workshopMeetingRoom.findUnique({
@@ -96,7 +93,7 @@ export class WorkshopMeetingRoomSchema extends PothosSchema {
const serverUrl = this.livekitService.getServerUrl()
return {
id: meetingRoom.id,
token: await this.livekitService.createToken(ctx.http?.me, meetingRoom.id),
token: await this.livekitService.createToken(ctx.me, meetingRoom.id),
serverUrl,
chatRoomId: chatRoom?.id,
}

View File

@@ -58,10 +58,7 @@ export class WorkshopSubscriptionSchema extends PothosSchema {
type: [this.workshopSubscription()],
args: this.builder.generator.findManyArgs('WorkshopSubscription'),
description: 'Retrieve a list of workshop subscriptions with optional filtering, ordering, and pagination.',
resolve: async (query, _root, args, ctx) => {
if (ctx.isSubscription) {
throw new Error('Workshops cannot be retrieved in subscription context')
}
resolve: async (query, _root, args, _ctx) => {
return await this.prisma.workshopSubscription.findMany({
...query,
skip: args.skip ?? undefined,
@@ -75,16 +72,13 @@ export class WorkshopSubscriptionSchema extends PothosSchema {
type: [this.workshopSubscription()],
description: 'Retrieve a list of workshops that the current user is subscribed to.',
resolve: async (query, _root, _args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Workshops cannot be retrieved in subscription context')
}
if (!ctx.http.me) {
if (!ctx.me) {
throw new Error('User is not authenticated')
}
return await this.prisma.workshopSubscription.findMany({
...query,
where: {
userId: ctx.http.me.id,
userId: ctx.me.id,
},
})
},
@@ -100,10 +94,7 @@ export class WorkshopSubscriptionSchema extends PothosSchema {
}),
},
resolve: async (_query, _root, args, ctx) => {
if (ctx.isSubscription) {
throw new Error('Not allowed in subscription')
}
const userId = ctx.http.me?.id
const userId = ctx.me?.id
// retrieve the workshop
const workshop = await this.prisma.workshop.findUnique({
where: { id: args.workshopId },