commit expose quizAttempt
This commit is contained in:
@@ -1,14 +1,14 @@
|
|||||||
import { clerkClient } from '@clerk/express'
|
import { clerkClient } from '@clerk/express';
|
||||||
import { Inject, Injectable, Logger } from '@nestjs/common'
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
import { Message, Role } from '@prisma/client'
|
import { Message, Role } from '@prisma/client';
|
||||||
import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
|
import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos';
|
||||||
import { ChatroomSchema } from '../ChatRoom/chatroom.schema'
|
import { ChatroomSchema } from '../ChatRoom/chatroom.schema';
|
||||||
import { Builder, SchemaContext } from '../Graphql/graphql.builder'
|
import { Builder, SchemaContext } from '../Graphql/graphql.builder';
|
||||||
import { MailService } from '../Mail/mail.service'
|
import { MailService } from '../Mail/mail.service';
|
||||||
import { MessageSchema } from '../Message/message.schema'
|
import { MessageSchema } from '../Message/message.schema';
|
||||||
import { PrismaService } from '../Prisma/prisma.service'
|
import { PrismaService } from '../Prisma/prisma.service';
|
||||||
import { PubSubEvent } from '../common/pubsub/pubsub-event'
|
import { PubSubEvent } from '../common/pubsub/pubsub-event';
|
||||||
import { DateTimeUtils } from '../common/utils/datetime.utils'
|
import { DateTimeUtils } from '../common/utils/datetime.utils';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserSchema extends PothosSchema {
|
export class UserSchema extends PothosSchema {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -16,9 +16,9 @@ export class UserSchema extends PothosSchema {
|
|||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly mailService: MailService,
|
private readonly mailService: MailService,
|
||||||
private readonly messageSchema: MessageSchema,
|
private readonly messageSchema: MessageSchema,
|
||||||
private readonly chatRoomSchema: ChatroomSchema,
|
private readonly chatRoomSchema: ChatroomSchema
|
||||||
) {
|
) {
|
||||||
super()
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Types section
|
// Types section
|
||||||
@@ -109,8 +109,11 @@ export class UserSchema extends PothosSchema {
|
|||||||
banned: t.exposeBoolean('banned', {
|
banned: t.exposeBoolean('banned', {
|
||||||
description: 'The banned status of the user.',
|
description: 'The banned status of the user.',
|
||||||
}),
|
}),
|
||||||
|
quizAttempt: t.relation('QuizAttempt', {
|
||||||
|
description: 'The quiz attempt of the user',
|
||||||
}),
|
}),
|
||||||
})
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@PothosRef()
|
@PothosRef()
|
||||||
@@ -134,7 +137,7 @@ export class UserSchema extends PothosSchema {
|
|||||||
description: 'The last message of the chat room.',
|
description: 'The last message of the chat room.',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query section
|
// Query section
|
||||||
@@ -147,8 +150,8 @@ export class UserSchema extends PothosSchema {
|
|||||||
sessionId: t.arg({ type: 'String', required: true }),
|
sessionId: t.arg({ type: 'String', required: true }),
|
||||||
},
|
},
|
||||||
resolve: async (_, { sessionId }) => {
|
resolve: async (_, { sessionId }) => {
|
||||||
const session = await clerkClient.sessions.getSession(sessionId)
|
const session = await clerkClient.sessions.getSession(sessionId);
|
||||||
return JSON.parse(JSON.stringify(session))
|
return JSON.parse(JSON.stringify(session));
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
newSession: t.field({
|
newSession: t.field({
|
||||||
@@ -163,8 +166,8 @@ export class UserSchema extends PothosSchema {
|
|||||||
const session = await clerkClient.signInTokens.createSignInToken({
|
const session = await clerkClient.signInTokens.createSignInToken({
|
||||||
userId,
|
userId,
|
||||||
expiresInSeconds: 60 * 60 * 24,
|
expiresInSeconds: 60 * 60 * 24,
|
||||||
})
|
});
|
||||||
return session.id
|
return session.id;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
me: t.prismaField({
|
me: t.prismaField({
|
||||||
@@ -172,9 +175,9 @@ export class UserSchema extends PothosSchema {
|
|||||||
type: this.user(),
|
type: this.user(),
|
||||||
resolve: async (_query, _root, _args, ctx) => {
|
resolve: async (_query, _root, _args, ctx) => {
|
||||||
if (ctx.isSubscription) {
|
if (ctx.isSubscription) {
|
||||||
throw new Error('Not allowed')
|
throw new Error('Not allowed');
|
||||||
}
|
}
|
||||||
return ctx.http.me
|
return ctx.http.me;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -186,11 +189,11 @@ export class UserSchema extends PothosSchema {
|
|||||||
},
|
},
|
||||||
resolve: async (_parent, args, ctx) => {
|
resolve: async (_parent, args, ctx) => {
|
||||||
if (ctx.isSubscription) {
|
if (ctx.isSubscription) {
|
||||||
throw new Error('Not allowed')
|
throw new Error('Not allowed');
|
||||||
}
|
}
|
||||||
const me = ctx.http.me
|
const me = ctx.http.me;
|
||||||
if (!me) {
|
if (!me) {
|
||||||
throw new Error('User not found')
|
throw new Error('User not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// get chat rooms that the user is a part of
|
// get chat rooms that the user is a part of
|
||||||
@@ -202,7 +205,7 @@ export class UserSchema extends PothosSchema {
|
|||||||
lastActivity: 'desc',
|
lastActivity: 'desc',
|
||||||
},
|
},
|
||||||
take: args.take ?? 10,
|
take: args.take ?? 10,
|
||||||
})
|
});
|
||||||
|
|
||||||
// get the last message for each chat room
|
// get the last message for each chat room
|
||||||
const lastMessages = await Promise.all(
|
const lastMessages = await Promise.all(
|
||||||
@@ -214,28 +217,28 @@ export class UserSchema extends PothosSchema {
|
|||||||
orderBy: {
|
orderBy: {
|
||||||
sentAt: 'desc',
|
sentAt: 'desc',
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (!lastMessage) {
|
if (!lastMessage) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sender = lastMessage.senderId
|
const sender = lastMessage.senderId
|
||||||
? await this.prisma.user.findUnique({
|
? await this.prisma.user.findUnique({
|
||||||
where: { id: lastMessage.senderId },
|
where: { id: lastMessage.senderId },
|
||||||
})
|
})
|
||||||
: undefined
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chatRoom: chatRoom,
|
chatRoom: chatRoom,
|
||||||
lastActivity: lastMessage.sentAt,
|
lastActivity: lastMessage.sentAt,
|
||||||
sender: sender,
|
sender: sender,
|
||||||
message: lastMessage,
|
message: lastMessage,
|
||||||
}
|
};
|
||||||
}),
|
})
|
||||||
)
|
);
|
||||||
|
|
||||||
return lastMessages.filter((msg) => msg !== null)
|
return lastMessages.filter((msg) => msg !== null);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -250,7 +253,7 @@ export class UserSchema extends PothosSchema {
|
|||||||
skip: args.skip ?? undefined,
|
skip: args.skip ?? undefined,
|
||||||
orderBy: args.orderBy ?? undefined,
|
orderBy: args.orderBy ?? undefined,
|
||||||
where: args.filter ?? undefined,
|
where: args.filter ?? undefined,
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -262,11 +265,11 @@ export class UserSchema extends PothosSchema {
|
|||||||
const user = await this.prisma.user.findUnique({
|
const user = await this.prisma.user.findUnique({
|
||||||
...query,
|
...query,
|
||||||
where: args.where,
|
where: args.where,
|
||||||
})
|
});
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('User not found')
|
throw new Error('User not found');
|
||||||
}
|
}
|
||||||
return user
|
return user;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
userBySession: t.prismaField({
|
userBySession: t.prismaField({
|
||||||
@@ -277,17 +280,17 @@ export class UserSchema extends PothosSchema {
|
|||||||
},
|
},
|
||||||
resolve: async (query, _root, args) => {
|
resolve: async (query, _root, args) => {
|
||||||
// check if the token is valid
|
// check if the token is valid
|
||||||
const session = await clerkClient.sessions.getSession(args.sessionId)
|
const session = await clerkClient.sessions.getSession(args.sessionId);
|
||||||
Logger.log(session, 'Session')
|
Logger.log(session, 'Session');
|
||||||
return await this.prisma.user.findFirstOrThrow({
|
return await this.prisma.user.findFirstOrThrow({
|
||||||
...query,
|
...query,
|
||||||
where: {
|
where: {
|
||||||
id: session.userId,
|
id: session.userId,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
||||||
// Mutation section
|
// Mutation section
|
||||||
this.builder.mutationFields((t) => ({
|
this.builder.mutationFields((t) => ({
|
||||||
@@ -309,7 +312,7 @@ export class UserSchema extends PothosSchema {
|
|||||||
...query,
|
...query,
|
||||||
where: args.where,
|
where: args.where,
|
||||||
data: args.input,
|
data: args.input,
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -359,32 +362,32 @@ export class UserSchema extends PothosSchema {
|
|||||||
},
|
},
|
||||||
resolve: async (_query, args, ctx, _info) => {
|
resolve: async (_query, args, ctx, _info) => {
|
||||||
if (ctx.isSubscription) {
|
if (ctx.isSubscription) {
|
||||||
throw new Error('Not allowed')
|
throw new Error('Not allowed');
|
||||||
}
|
}
|
||||||
const id = ctx.http.me?.id
|
const id = ctx.http.me?.id;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new Error('User not found')
|
throw new Error('User not found');
|
||||||
}
|
}
|
||||||
if (args.imageBlob) {
|
if (args.imageBlob) {
|
||||||
const { mimetype, createReadStream } = await args.imageBlob
|
const { mimetype, createReadStream } = await args.imageBlob;
|
||||||
if (mimetype && createReadStream) {
|
if (mimetype && createReadStream) {
|
||||||
const stream = createReadStream()
|
const stream = createReadStream();
|
||||||
const chunks: Uint8Array[] = []
|
const chunks: Uint8Array[] = [];
|
||||||
|
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
chunks.push(chunk)
|
chunks.push(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = Buffer.concat(chunks)
|
const buffer = Buffer.concat(chunks);
|
||||||
const { id: userId, imageUrl } = await clerkClient.users.updateUserProfileImage(id, {
|
const { id: userId, imageUrl } = await clerkClient.users.updateUserProfileImage(id, {
|
||||||
file: new Blob([buffer]),
|
file: new Blob([buffer]),
|
||||||
})
|
});
|
||||||
await this.prisma.user.update({
|
await this.prisma.user.update({
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
data: {
|
data: {
|
||||||
avatarUrl: imageUrl,
|
avatarUrl: imageUrl,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +395,7 @@ export class UserSchema extends PothosSchema {
|
|||||||
const clerkUser = await clerkClient.users.updateUser(id, {
|
const clerkUser = await clerkClient.users.updateUser(id, {
|
||||||
firstName: args.firstName as string,
|
firstName: args.firstName as string,
|
||||||
lastName: args.lastName as string,
|
lastName: args.lastName as string,
|
||||||
})
|
});
|
||||||
// update bank account number and bank bin to database
|
// update bank account number and bank bin to database
|
||||||
if (args.input?.bankAccountNumber) {
|
if (args.input?.bankAccountNumber) {
|
||||||
await this.prisma.user.update({
|
await this.prisma.user.update({
|
||||||
@@ -400,7 +403,7 @@ export class UserSchema extends PothosSchema {
|
|||||||
data: {
|
data: {
|
||||||
bankAccountNumber: args.input.bankAccountNumber,
|
bankAccountNumber: args.input.bankAccountNumber,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.input?.bankBin) {
|
if (args.input?.bankBin) {
|
||||||
@@ -409,7 +412,7 @@ export class UserSchema extends PothosSchema {
|
|||||||
data: {
|
data: {
|
||||||
bankBin: args.input.bankBin,
|
bankBin: args.input.bankBin,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.firstName || args.lastName) {
|
if (args.firstName || args.lastName) {
|
||||||
@@ -418,13 +421,13 @@ export class UserSchema extends PothosSchema {
|
|||||||
data: {
|
data: {
|
||||||
name: `${args.firstName || ''} ${args.lastName || ''}`.trim(),
|
name: `${args.firstName || ''} ${args.lastName || ''}`.trim(),
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
// invalidate cache
|
// invalidate cache
|
||||||
await ctx.http.invalidateCache()
|
await ctx.http.invalidateCache();
|
||||||
return await this.prisma.user.findUniqueOrThrow({
|
return await this.prisma.user.findUniqueOrThrow({
|
||||||
where: { id: clerkUser.id },
|
where: { id: clerkUser.id },
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -436,35 +439,30 @@ export class UserSchema extends PothosSchema {
|
|||||||
resolve: async (_parent, args, ctx) => {
|
resolve: async (_parent, args, ctx) => {
|
||||||
// check context
|
// check context
|
||||||
if (ctx.isSubscription) {
|
if (ctx.isSubscription) {
|
||||||
throw new Error('Not allowed')
|
throw new Error('Not allowed');
|
||||||
}
|
}
|
||||||
// check context is admin
|
// check context is admin
|
||||||
|
|
||||||
if (ctx.http.me?.role !== Role.ADMIN) {
|
if (ctx.http.me?.role !== Role.ADMIN) {
|
||||||
throw new Error(`Only admin can invite moderator`)
|
throw new Error(`Only admin can invite moderator`);
|
||||||
}
|
}
|
||||||
return this.prisma.$transaction(async (tx) => {
|
return this.prisma.$transaction(async (tx) => {
|
||||||
let user
|
let user;
|
||||||
// perform update role
|
// perform update role
|
||||||
try {
|
try {
|
||||||
user = await tx.user.update({
|
user = await tx.user.update({
|
||||||
where: { email: args.email },
|
where: { email: args.email },
|
||||||
data: { role: 'MODERATOR' },
|
data: { role: 'MODERATOR' },
|
||||||
})
|
});
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
throw new Error(`User ${args.email} not found`)
|
throw new Error(`User ${args.email} not found`);
|
||||||
}
|
}
|
||||||
// send email
|
// send email
|
||||||
await this.mailService.sendTemplateEmail(
|
await this.mailService.sendTemplateEmail([args.email], 'Thông báo chọn lựa quản trị viên cho người điều hành', 'ModeratorInvitation', {
|
||||||
[args.email],
|
|
||||||
'Thông báo chọn lựa quản trị viên cho người điều hành',
|
|
||||||
'ModeratorInvitation',
|
|
||||||
{
|
|
||||||
USER_NAME: user.name,
|
USER_NAME: user.name,
|
||||||
},
|
});
|
||||||
)
|
return 'Invited';
|
||||||
return 'Invited'
|
});
|
||||||
})
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
// send test notification
|
// send test notification
|
||||||
@@ -479,11 +477,11 @@ export class UserSchema extends PothosSchema {
|
|||||||
},
|
},
|
||||||
resolve: async (_, args, ctx) => {
|
resolve: async (_, args, ctx) => {
|
||||||
if (ctx.isSubscription) {
|
if (ctx.isSubscription) {
|
||||||
throw new Error('Not allowed')
|
throw new Error('Not allowed');
|
||||||
}
|
}
|
||||||
const me = ctx.http.me
|
const me = ctx.http.me;
|
||||||
if (!me) {
|
if (!me) {
|
||||||
throw new Error('User not found')
|
throw new Error('User not found');
|
||||||
}
|
}
|
||||||
// create message
|
// create message
|
||||||
const message = await this.prisma.message.create({
|
const message = await this.prisma.message.create({
|
||||||
@@ -497,10 +495,10 @@ export class UserSchema extends PothosSchema {
|
|||||||
context: args.input.context ?? undefined,
|
context: args.input.context ?? undefined,
|
||||||
metadata: args.input.metadata ?? undefined,
|
metadata: args.input.metadata ?? undefined,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
// publish message
|
// publish message
|
||||||
await ctx.http.pubSub.publish(`${PubSubEvent.NEW_MESSAGE}.${message.recipientId}`, message)
|
await ctx.http.pubSub.publish(`${PubSubEvent.NEW_MESSAGE}.${message.recipientId}`, message);
|
||||||
return message
|
return message;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
banUser: t.field({
|
banUser: t.field({
|
||||||
@@ -510,38 +508,38 @@ export class UserSchema extends PothosSchema {
|
|||||||
},
|
},
|
||||||
resolve: async (_parent, args, ctx) => {
|
resolve: async (_parent, args, ctx) => {
|
||||||
if (ctx.isSubscription) {
|
if (ctx.isSubscription) {
|
||||||
throw new Error('Not allowed')
|
throw new Error('Not allowed');
|
||||||
}
|
}
|
||||||
if (ctx.http.me?.role !== Role.ADMIN && ctx.http.me?.role !== Role.MODERATOR) {
|
if (ctx.http.me?.role !== Role.ADMIN && ctx.http.me?.role !== Role.MODERATOR) {
|
||||||
throw new Error(`Only admin or moderator can ban user`)
|
throw new Error(`Only admin or moderator can ban user`);
|
||||||
}
|
}
|
||||||
if (args.userId === ctx.http.me?.id) {
|
if (args.userId === ctx.http.me?.id) {
|
||||||
throw new Error(`Cannot ban yourself`)
|
throw new Error(`Cannot ban yourself`);
|
||||||
}
|
}
|
||||||
// get banning user info
|
// get banning user info
|
||||||
const banningUser = await this.prisma.user.findUnique({
|
const banningUser = await this.prisma.user.findUnique({
|
||||||
where: { id: args.userId },
|
where: { id: args.userId },
|
||||||
})
|
});
|
||||||
if (!banningUser) {
|
if (!banningUser) {
|
||||||
throw new Error(`User ${args.userId} not found`)
|
throw new Error(`User ${args.userId} not found`);
|
||||||
}
|
}
|
||||||
// if banning user is moderator or admin, throw error
|
// if banning user is moderator or admin, throw error
|
||||||
if (banningUser.role === Role.MODERATOR || banningUser.role === Role.ADMIN) {
|
if (banningUser.role === Role.MODERATOR || banningUser.role === Role.ADMIN) {
|
||||||
throw new Error(`Cannot ban moderator or admin`)
|
throw new Error(`Cannot ban moderator or admin`);
|
||||||
}
|
}
|
||||||
// ban user from clerk
|
// ban user from clerk
|
||||||
await clerkClient.users.banUser(args.userId)
|
await clerkClient.users.banUser(args.userId);
|
||||||
// invalidate cache
|
// invalidate cache
|
||||||
await ctx.http.invalidateCache()
|
await ctx.http.invalidateCache();
|
||||||
// update user banned status
|
// update user banned status
|
||||||
await this.prisma.user.update({
|
await this.prisma.user.update({
|
||||||
where: { id: args.userId },
|
where: { id: args.userId },
|
||||||
data: { banned: true },
|
data: { banned: true },
|
||||||
})
|
});
|
||||||
return 'Banned'
|
return 'Banned';
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
||||||
// Subscription section
|
// Subscription section
|
||||||
this.builder.subscriptionFields((t) => ({
|
this.builder.subscriptionFields((t) => ({
|
||||||
@@ -549,16 +547,13 @@ export class UserSchema extends PothosSchema {
|
|||||||
type: this.messageSchema.message(),
|
type: this.messageSchema.message(),
|
||||||
subscribe: async (_, _args, ctx) => {
|
subscribe: async (_, _args, ctx) => {
|
||||||
if (!ctx.isSubscription) {
|
if (!ctx.isSubscription) {
|
||||||
throw new Error('Not allowed')
|
throw new Error('Not allowed');
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.websocket.pubSub.asyncIterator([
|
return ctx.websocket.pubSub.asyncIterator([`${PubSubEvent.NEW_MESSAGE}.${ctx.websocket.me?.id}`, `${PubSubEvent.NOTIFICATION}.${ctx.websocket.me?.id}`]) as unknown as AsyncIterable<Message>;
|
||||||
`${PubSubEvent.NEW_MESSAGE}.${ctx.websocket.me?.id}`,
|
|
||||||
`${PubSubEvent.NOTIFICATION}.${ctx.websocket.me?.id}`,
|
|
||||||
]) as unknown as AsyncIterable<Message>
|
|
||||||
},
|
},
|
||||||
resolve: async (payload: Message) => payload,
|
resolve: async (payload: Message) => payload,
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user