Refactor Category and Center schemas to improve field descriptions and code clarity; update email templates for consistency in messaging. Add Redis service to CenterSchema for session management. Bump epess-database subproject commit reference to indicate changes. Clean up logging in ClerkService and ServiceSchema.
This commit is contained in:
@@ -1,10 +1,5 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common'
|
import { Inject, Injectable } from '@nestjs/common'
|
||||||
import {
|
import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
|
||||||
Pothos,
|
|
||||||
PothosRef,
|
|
||||||
PothosSchema,
|
|
||||||
SchemaBuilderToken,
|
|
||||||
} from '@smatch-corp/nestjs-pothos'
|
|
||||||
import { Builder } from '../Graphql/graphql.builder'
|
import { Builder } from '../Graphql/graphql.builder'
|
||||||
import { PrismaService } from '../Prisma/prisma.service'
|
import { PrismaService } from '../Prisma/prisma.service'
|
||||||
|
|
||||||
@@ -28,6 +23,9 @@ export class CategorySchema extends PothosSchema {
|
|||||||
name: t.exposeString('name', {
|
name: t.exposeString('name', {
|
||||||
description: 'The name of the category.',
|
description: 'The name of the category.',
|
||||||
}),
|
}),
|
||||||
|
description: t.exposeString('description', {
|
||||||
|
description: 'The description of the category.',
|
||||||
|
}),
|
||||||
subCategory: t.relation('subCategory', {
|
subCategory: t.relation('subCategory', {
|
||||||
description: 'The subcategory of the category.',
|
description: 'The subcategory of the category.',
|
||||||
}),
|
}),
|
||||||
@@ -46,16 +44,17 @@ export class CategorySchema extends PothosSchema {
|
|||||||
name: t.exposeString('name', {
|
name: t.exposeString('name', {
|
||||||
description: 'The name of the subcategory.',
|
description: 'The name of the subcategory.',
|
||||||
}),
|
}),
|
||||||
|
description: t.exposeString('description', {
|
||||||
|
description: 'The description of the subcategory.',
|
||||||
|
}),
|
||||||
categoryId: t.exposeID('categoryId', {
|
categoryId: t.exposeID('categoryId', {
|
||||||
description:
|
description: 'The ID of the category that the subcategory belongs to.',
|
||||||
'The ID of the category that the subcategory belongs to.',
|
|
||||||
}),
|
}),
|
||||||
category: t.relation('category', {
|
category: t.relation('category', {
|
||||||
description: 'The category that the subcategory belongs to.',
|
description: 'The category that the subcategory belongs to.',
|
||||||
}),
|
}),
|
||||||
serviceAndCategory: t.relation('serviceAndCategory', {
|
serviceAndCategory: t.relation('serviceAndCategory', {
|
||||||
description:
|
description: 'The service and category that the subcategory belongs to.',
|
||||||
'The service and category that the subcategory belongs to.',
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -65,8 +64,7 @@ export class CategorySchema extends PothosSchema {
|
|||||||
init(): void {
|
init(): void {
|
||||||
this.builder.queryFields((t) => ({
|
this.builder.queryFields((t) => ({
|
||||||
categories: t.prismaField({
|
categories: t.prismaField({
|
||||||
description:
|
description: 'Retrieve a list of categories with optional filtering, ordering, and pagination.',
|
||||||
'Retrieve a list of categories with optional filtering, ordering, and pagination.',
|
|
||||||
type: [this.category()],
|
type: [this.category()],
|
||||||
args: this.builder.generator.findManyArgs('Category'),
|
args: this.builder.generator.findManyArgs('Category'),
|
||||||
resolve: async (query, _root, args) => {
|
resolve: async (query, _root, args) => {
|
||||||
@@ -91,8 +89,7 @@ export class CategorySchema extends PothosSchema {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
subCategories: t.prismaField({
|
subCategories: t.prismaField({
|
||||||
description:
|
description: 'Retrieve a list of subcategories with optional filtering, ordering, and pagination.',
|
||||||
'Retrieve a list of subcategories with optional filtering, ordering, and pagination.',
|
|
||||||
type: [this.subCategory()],
|
type: [this.subCategory()],
|
||||||
args: this.builder.generator.findManyArgs('SubCategory'),
|
args: this.builder.generator.findManyArgs('SubCategory'),
|
||||||
resolve: async (query, _root, args) => {
|
resolve: async (query, _root, args) => {
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import { Inject, Injectable, Logger } from '@nestjs/common'
|
import { Inject, Injectable, Logger } from '@nestjs/common'
|
||||||
import {
|
import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
|
||||||
Pothos,
|
|
||||||
PothosRef,
|
|
||||||
PothosSchema,
|
|
||||||
SchemaBuilderToken,
|
|
||||||
} from '@smatch-corp/nestjs-pothos'
|
|
||||||
import { Builder } from '../Graphql/graphql.builder'
|
import { Builder } from '../Graphql/graphql.builder'
|
||||||
import { PrismaService } from '../Prisma/prisma.service'
|
import { PrismaService } from '../Prisma/prisma.service'
|
||||||
import { MinioService } from '../Minio/minio.service'
|
import { MinioService } from '../Minio/minio.service'
|
||||||
import { CenterStatus, Role } from '@prisma/client'
|
import { CenterStatus, Role } from '@prisma/client'
|
||||||
import { MailService } from '../Mail/mail.service'
|
import { MailService } from '../Mail/mail.service'
|
||||||
|
import { clerkClient } from '@clerk/express'
|
||||||
|
import { RedisService } from 'src/Redis/redis.service'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CenterSchema extends PothosSchema {
|
export class CenterSchema extends PothosSchema {
|
||||||
@@ -18,6 +15,7 @@ export class CenterSchema extends PothosSchema {
|
|||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly minioService: MinioService,
|
private readonly minioService: MinioService,
|
||||||
private readonly mailService: MailService,
|
private readonly mailService: MailService,
|
||||||
|
private readonly redisService: RedisService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
@@ -54,11 +52,7 @@ export class CenterSchema extends PothosSchema {
|
|||||||
resolve: async (center) => {
|
resolve: async (center) => {
|
||||||
// get file id from logoUrl
|
// get file id from logoUrl
|
||||||
const logoFileId = center.logoUrl?.split('/').pop()?.split('?')[0]
|
const logoFileId = center.logoUrl?.split('/').pop()?.split('?')[0]
|
||||||
return await this.minioService.updatePresignUrl(
|
return await this.minioService.updatePresignUrl(logoFileId ?? '', 'files', center.logoUrl ?? undefined)
|
||||||
logoFileId ?? '',
|
|
||||||
'files',
|
|
||||||
center.logoUrl ?? undefined,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
logoFile: t.relation('logoFile', {
|
logoFile: t.relation('logoFile', {
|
||||||
@@ -103,8 +97,7 @@ export class CenterSchema extends PothosSchema {
|
|||||||
init(): void {
|
init(): void {
|
||||||
this.builder.queryFields((t) => ({
|
this.builder.queryFields((t) => ({
|
||||||
centers: t.prismaField({
|
centers: t.prismaField({
|
||||||
description:
|
description: 'Retrieve a list of centers with optional filtering, ordering, and pagination.',
|
||||||
'Retrieve a list of centers with optional filtering, ordering, and pagination.',
|
|
||||||
type: [this.center()],
|
type: [this.center()],
|
||||||
args: this.builder.generator.findManyArgs('Center'),
|
args: this.builder.generator.findManyArgs('Center'),
|
||||||
resolve: async (query, _root, args) => {
|
resolve: async (query, _root, args) => {
|
||||||
@@ -221,8 +214,14 @@ export class CenterSchema extends PothosSchema {
|
|||||||
required: false,
|
required: false,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
resolve: async (query, _root, args) => {
|
resolve: async (query, _root, args, ctx) => {
|
||||||
return await this.prisma.$transaction(async (prisma) => {
|
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) {
|
||||||
|
throw new Error('Not allowed')
|
||||||
|
}
|
||||||
const center = await prisma.center.findUnique({
|
const center = await prisma.center.findUnique({
|
||||||
...query,
|
...query,
|
||||||
where: {
|
where: {
|
||||||
@@ -249,6 +248,14 @@ export class CenterSchema extends PothosSchema {
|
|||||||
if (!centerOwner) {
|
if (!centerOwner) {
|
||||||
throw new Error('User not found')
|
throw new Error('User not found')
|
||||||
}
|
}
|
||||||
|
// get active clerk session and invalidate cache
|
||||||
|
const sessionList = await clerkClient.sessions.getSessionList({
|
||||||
|
userId: centerOwnerId,
|
||||||
|
})
|
||||||
|
// clear all session cache in redis
|
||||||
|
sessionList.data.forEach(async (session) => {
|
||||||
|
await this.redisService.del(session.id)
|
||||||
|
})
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
id: centerOwnerId,
|
id: centerOwnerId,
|
||||||
@@ -257,6 +264,7 @@ export class CenterSchema extends PothosSchema {
|
|||||||
role: Role.CENTER_OWNER,
|
role: Role.CENTER_OWNER,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// update center status
|
// update center status
|
||||||
const updatedCenter = await prisma.center.update({
|
const updatedCenter = await prisma.center.update({
|
||||||
...query,
|
...query,
|
||||||
@@ -264,9 +272,7 @@ export class CenterSchema extends PothosSchema {
|
|||||||
id: args.centerId,
|
id: args.centerId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
centerStatus: args.approve
|
centerStatus: args.approve ? CenterStatus.APPROVED : CenterStatus.REJECTED,
|
||||||
? CenterStatus.APPROVED
|
|
||||||
: CenterStatus.REJECTED,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// mail to center owner if approved
|
// mail to center owner if approved
|
||||||
|
|||||||
@@ -24,11 +24,10 @@ export class ClerkService {
|
|||||||
// session.removed
|
// session.removed
|
||||||
// session.ended
|
// session.ended
|
||||||
// email.created
|
// email.created
|
||||||
console.log(eventType, data)
|
Logger.log(eventType, data, 'ClerkService')
|
||||||
if (eventType === 'user.created') {
|
if (eventType === 'user.created') {
|
||||||
this.eventUserCreated(data)
|
this.eventUserCreated(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventType === 'user.updated') {
|
if (eventType === 'user.updated') {
|
||||||
this.eventUserUpdated(data)
|
this.eventUserUpdated(data)
|
||||||
}
|
}
|
||||||
@@ -57,7 +56,6 @@ export class ClerkService {
|
|||||||
const primary_email_address_id = data.primary_email_address_id
|
const primary_email_address_id = data.primary_email_address_id
|
||||||
// get primary email address on email_addresses by querying email_addresses with primary_email_address_id
|
// get primary email address on email_addresses by querying email_addresses with primary_email_address_id
|
||||||
let primary_email_address = data.email_addresses.find((email: any) => email.id === primary_email_address_id)
|
let primary_email_address = data.email_addresses.find((email: any) => email.id === primary_email_address_id)
|
||||||
console.log(primary_email_address)
|
|
||||||
if (!primary_email_address) {
|
if (!primary_email_address) {
|
||||||
primary_email_address = ''
|
primary_email_address = ''
|
||||||
}
|
}
|
||||||
@@ -87,7 +85,6 @@ export class ClerkService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async eventUserUpdated(data: any) {
|
async eventUserUpdated(data: any) {
|
||||||
console.log(data)
|
|
||||||
const user_id = data.id
|
const user_id = data.id
|
||||||
const name = `${data.first_name} ${data.last_name}`
|
const name = `${data.first_name} ${data.last_name}`
|
||||||
await this.prisma.user.update({
|
await this.prisma.user.update({
|
||||||
@@ -97,7 +94,6 @@ export class ClerkService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async eventSessionCreated(data: any) {
|
async eventSessionCreated(data: any) {
|
||||||
console.log(data)
|
|
||||||
// check if user exists in database or create user
|
// check if user exists in database or create user
|
||||||
const user = await this.prisma.user.findUnique({
|
const user = await this.prisma.user.findUnique({
|
||||||
where: { id: data.user_id },
|
where: { id: data.user_id },
|
||||||
@@ -105,7 +101,6 @@ export class ClerkService {
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
// get user info from clerk
|
// get user info from clerk
|
||||||
const userInfo = await clerkClient.users.getUser(data.user_id)
|
const userInfo = await clerkClient.users.getUser(data.user_id)
|
||||||
console.log(userInfo)
|
|
||||||
try {
|
try {
|
||||||
await this.prisma.user.create({
|
await this.prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
@@ -123,18 +118,18 @@ export class ClerkService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
eventSessionRevoked(data: any) {
|
eventSessionRevoked(data: any) {
|
||||||
console.log(data)
|
Logger.log(data, 'ClerkService')
|
||||||
}
|
}
|
||||||
|
|
||||||
eventSessionRemoved(data: any) {
|
eventSessionRemoved(data: any) {
|
||||||
console.log(data)
|
Logger.log(data, 'ClerkService')
|
||||||
}
|
}
|
||||||
|
|
||||||
eventSessionEnded(data: any) {
|
eventSessionEnded(data: any) {
|
||||||
console.log(data)
|
Logger.log(data, 'ClerkService')
|
||||||
}
|
}
|
||||||
|
|
||||||
eventEmailCreated(data: any) {
|
eventEmailCreated(data: any) {
|
||||||
console.log(data)
|
Logger.log(data, 'ClerkService')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ html
|
|||||||
body
|
body
|
||||||
.container
|
.container
|
||||||
.header
|
.header
|
||||||
h1 Chúc mừng Trung tâm #{CENTER_NAME} đã được phê duyệt
|
h1 Chúc mừng #{CENTER_NAME} đã được phê duyệt
|
||||||
.content
|
.content
|
||||||
p Kính gửi Quý Trung tâm,
|
p Kính gửi Quý Trung tâm,
|
||||||
p Chúng tôi vui mừng thông báo rằng trung tâm #{CENTER_NAME} của bạn đã được phê duyệt trên nền tảng của chúng tôi.
|
p Chúng tôi vui mừng thông báo rằng trung tâm #{CENTER_NAME} của bạn đã được phê duyệt trên nền tảng của chúng tôi.
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ html
|
|||||||
body
|
body
|
||||||
.container
|
.container
|
||||||
.header
|
.header
|
||||||
h1 Thông báo từ chối Trung tâm #{CENTER_NAME}
|
h1 Thông báo từ chối #{CENTER_NAME}
|
||||||
.content
|
.content
|
||||||
p Kính gửi Quý Trung tâm,
|
p Kính gửi Quý Trung tâm,
|
||||||
p Chúng tôi rất tiếc thông báo rằng trung tâm #{CENTER_NAME} của bạn chưa được phê duyệt trên nền tảng của chúng tôi.
|
p Chúng tôi rất tiếc thông báo rằng trung tâm #{CENTER_NAME} của bạn chưa được phê duyệt trên nền tảng của chúng tôi.
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ html
|
|||||||
body
|
body
|
||||||
.container
|
.container
|
||||||
.header
|
.header
|
||||||
h1 Thư mời làm việc từ Trung tâm #{center_name}
|
h1 Thư mời làm việc từ #{center_name}
|
||||||
.content
|
.content
|
||||||
p Chào bạn,
|
p Chào bạn,
|
||||||
p Chúng tôi rất vui mừng thông báo rằng bạn đã được mời làm người hướng dẫn tại trung tâm #{center_name}.
|
p Chúng tôi rất vui mừng thông báo rằng bạn đã được mời làm người hướng dẫn tại #{center_name}.
|
||||||
p Để tiếp tục quá trình ứng tuyển, vui lòng nhấn vào nút dưới đây để truy cập vào trang nộp resume của bạn.
|
p Để tiếp tục quá trình ứng tuyển, vui lòng nhấn vào nút dưới đây để truy cập vào trang nộp resume của bạn.
|
||||||
a.button(href=invite_url) Nộp Resume
|
a.button(href=invite_url) Nộp Resume
|
||||||
p Nếu bạn có bất kỳ thắc mắc nào, đừng ngần ngại liên hệ với chúng tôi.
|
p Nếu bạn có bất kỳ thắc mắc nào, đừng ngần ngại liên hệ với chúng tôi.
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ html
|
|||||||
h1 Thông báo kết quả ứng tuyển của #{USER_NAME} tại #{CENTER_NAME}
|
h1 Thông báo kết quả ứng tuyển của #{USER_NAME} tại #{CENTER_NAME}
|
||||||
.content
|
.content
|
||||||
p Chào #{USER_NAME},
|
p Chào #{USER_NAME},
|
||||||
p Chúng tôi rất tiếc thông báo rằng bạn chưa được phê duyệt trở thành Mentor tại trung tâm #{CENTER_NAME} lần này.
|
p Chúng tôi rất tiếc thông báo rằng bạn chưa được phê duyệt trở thành Mentor tại #{CENTER_NAME} lần này.
|
||||||
p Chúng tôi khuyến khích bạn tiếp tục nâng cao kỹ năng và kinh nghiệm, và mong đợi đơn ứng tuyển của bạn trong tương lai.
|
p Chúng tôi khuyến khích bạn tiếp tục nâng cao kỹ năng và kinh nghiệm, và mong đợi đơn ứng tuyển của bạn trong tương lai.
|
||||||
p Bạn có thể truy cập trang web của chúng tôi để biết thêm thông tin:
|
p Bạn có thể truy cập trang web của chúng tôi để biết thêm thông tin:
|
||||||
a.button(href="https://center.epess.org") Truy cập Trung tâm
|
a.button(href="https://center.epess.org") Truy cập Trung tâm
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ html
|
|||||||
.header
|
.header
|
||||||
h1 Chúc mừng Dịch vụ #{SERVICE_NAME} đã được phê duyệt
|
h1 Chúc mừng Dịch vụ #{SERVICE_NAME} đã được phê duyệt
|
||||||
.content
|
.content
|
||||||
p Kính gửi Quý Trung tâm #{CENTER_NAME},
|
p Kính gửi #{CENTER_NAME},
|
||||||
p Chúng tôi vui mừng thông báo rằng dịch vụ #{SERVICE_NAME} của bạn đã được phê duyệt trên nền tảng của chúng tôi.
|
p Chúng tôi vui mừng thông báo rằng dịch vụ #{SERVICE_NAME} của bạn đã được phê duyệt trên nền tảng của chúng tôi.
|
||||||
p Vui lòng nhấn vào nút dưới đây để truy cập vào dịch vụ của bạn:
|
p Vui lòng nhấn vào nút dưới đây để truy cập vào dịch vụ của bạn:
|
||||||
a.button(href="https://center.epess.org") Truy cập Dịch vụ
|
a.button(href="https://center.epess.org") Truy cập Dịch vụ
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ html
|
|||||||
.header
|
.header
|
||||||
h1 Thông báo từ chối Dịch vụ #{SERVICE_NAME}
|
h1 Thông báo từ chối Dịch vụ #{SERVICE_NAME}
|
||||||
.content
|
.content
|
||||||
p Kính gửi Quý Trung tâm #{CENTER_NAME},
|
p Kính gửi #{CENTER_NAME},
|
||||||
p Chúng tôi rất tiếc thông báo rằng dịch vụ #{SERVICE_NAME} của bạn chưa được phê duyệt trên nền tảng của chúng tôi.
|
p Chúng tôi rất tiếc thông báo rằng dịch vụ #{SERVICE_NAME} của bạn chưa được phê duyệt trên nền tảng của chúng tôi.
|
||||||
.note
|
.note
|
||||||
p Lý do từ chối:
|
p Lý do từ chối:
|
||||||
|
|||||||
@@ -364,7 +364,6 @@ export class ServiceSchema extends PothosSchema {
|
|||||||
const mentorEmails = await prisma.user.findMany({
|
const mentorEmails = await prisma.user.findMany({
|
||||||
where: { id: { in: mentorIds } },
|
where: { id: { in: mentorIds } },
|
||||||
})
|
})
|
||||||
Logger.log(mentorEmails, 'ServiceSchema')
|
|
||||||
const emails = [centerOwner.email, ...mentorEmails.map((mentor) => mentor.email)]
|
const emails = [centerOwner.email, ...mentorEmails.map((mentor) => mentor.email)]
|
||||||
if (args.approve) {
|
if (args.approve) {
|
||||||
await this.mailService.sendTemplateEmail(emails, 'Thông báo về trạng thái dịch vụ', 'ServiceApproved', {
|
await this.mailService.sendTemplateEmail(emails, 'Thông báo về trạng thái dịch vụ', 'ServiceApproved', {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user