diff --git a/src/AppConfig/appconfig.constant.ts b/src/AppConfig/appconfig.constant.ts new file mode 100644 index 0000000..008ef6d --- /dev/null +++ b/src/AppConfig/appconfig.constant.ts @@ -0,0 +1,46 @@ +export const ConfigConstants: Record< + string, + { + name: string + key: string + value: string + visible: boolean + } +> = { + SLOT_DURATION: { + name: 'Slot Duration', + key: 'SLOT_DURATION', + value: '60', + visible: true, + }, + SLOT_BREAK_DURATION: { + name: 'Slot Break Duration', + key: 'SLOT_BREAK_DURATION', + value: '15', + visible: true, + }, + MID_DAY_BREAK_TIME_START: { + name: 'Mid Day Break Time Start', + key: 'MID_DAY_BREAK_TIME_START', + value: new Date(new Date().setHours(12, 0, 0, 0)).toISOString(), + visible: true, + }, + MID_DAY_BREAK_TIME_END: { + name: 'Mid Day Break Time End', + key: 'MID_DAY_BREAK_TIME_END', + value: new Date(new Date().setHours(13, 0, 0, 0)).toISOString(), + visible: true, + }, + SLOT_START_TIME: { + name: 'Slot Start Time', + key: 'SLOT_START_TIME', + value: new Date(new Date().setHours(8, 0, 0, 0)).toISOString(), + visible: true, + }, + SLOT_END_TIME: { + name: 'Slot End Time', + key: 'SLOT_END_TIME', + value: new Date(new Date().setHours(22, 0, 0, 0)).toISOString(), + visible: true, + }, +} diff --git a/src/AppConfig/appconfig.schema.ts b/src/AppConfig/appconfig.schema.ts index 14f0136..982e673 100644 --- a/src/AppConfig/appconfig.schema.ts +++ b/src/AppConfig/appconfig.schema.ts @@ -49,7 +49,7 @@ export class AppConfigSchema extends PothosSchema { type: [this.appConfig()], description: 'Get all app configs', args: this.builder.generator.findManyArgs('Config'), - resolve: async (query, root, args) => { + resolve: async (query, _root, args) => { return await this.prisma.config.findMany({ ...query, where: args.filter ?? undefined, diff --git a/src/AppConfig/appconfig.service.ts b/src/AppConfig/appconfig.service.ts index f2f6dfa..2d36c9d 100644 --- a/src/AppConfig/appconfig.service.ts +++ b/src/AppConfig/appconfig.service.ts @@ -1,7 +1,8 @@ import { Injectable, OnModuleInit } from '@nestjs/common' -// import { ConfigConstant } from 'src/common/constant/config.constant'; import { PrismaService } from 'src/Prisma/prisma.service' +import { ConfigConstants } from './appconfig.constant' +import { Config } from '@prisma/client' @Injectable() export class AppConfigService implements OnModuleInit { @@ -9,21 +10,49 @@ export class AppConfigService implements OnModuleInit { async onModuleInit() { // get each config from database, if not exist, create default config - // const configs = await this.prisma.config.findMany(); - // if (configs.length === 0) { - // await this.prisma.config.createMany({ - // data: Object.values(ConfigConstant).map((config) => ({ - // ...config, - // })), - // }); - // } - // // regenerate missing config - // for (const config of Object.values(ConfigConstant)) { - // if (!configs.find((c) => c.key === config.key)) { - // await this.prisma.config.create({ - // data: config, - // }); - // } - // } + const configs = await this.prisma.config.findMany() + if (configs.length === 0) { + Object.entries(ConfigConstants).forEach(async ([_key, value]) => { + await this.prisma.config.create({ + data: { + name: value.name, + key: value.key, + value: value.value, + visible: value.visible, + }, + }) + }) + } + } + + async getConfig(key: string) { + return await this.prisma.config.findUnique({ + where: { key }, + }) + } + + async getVisibleConfigs() { + return await this.prisma.config.findMany({ + where: { visible: true }, + }) + } + + async updateConfig(key: string, value: string) { + return await this.prisma.config.update({ + where: { key }, + data: { value }, + }) + } + + async updateVisibleConfigs(configs: Config[]) { + return await this.prisma.config.updateMany({ + data: configs, + }) + } + + async deleteConfig(key: string) { + return await this.prisma.config.delete({ + where: { key }, + }) } } diff --git a/src/Category/category.schema.ts b/src/Category/category.schema.ts index 86e08e2..c9eff59 100644 --- a/src/Category/category.schema.ts +++ b/src/Category/category.schema.ts @@ -69,7 +69,7 @@ export class CategorySchema extends PothosSchema { 'Retrieve a list of categories with optional filtering, ordering, and pagination.', type: [this.category()], args: this.builder.generator.findManyArgs('Category'), - resolve: async (query, root, args) => { + resolve: async (query, _root, args) => { return await this.prisma.category.findMany({ ...query, skip: args.skip ?? undefined, @@ -83,7 +83,7 @@ export class CategorySchema extends PothosSchema { description: 'Retrieve a single category by its unique identifier.', type: this.category(), args: this.builder.generator.findUniqueArgs('Category'), - resolve: async (query, root, args) => { + resolve: async (query, _root, args) => { return await this.prisma.category.findUnique({ ...query, where: args.where ?? undefined, @@ -95,7 +95,7 @@ export class CategorySchema extends PothosSchema { 'Retrieve a list of subcategories with optional filtering, ordering, and pagination.', type: [this.subCategory()], args: this.builder.generator.findManyArgs('SubCategory'), - resolve: async (query, root, args) => { + resolve: async (query, _root, args) => { return await this.prisma.subCategory.findMany({ ...query, where: args.filter ?? undefined, @@ -118,7 +118,7 @@ export class CategorySchema extends PothosSchema { required: true, }), }, - resolve: async (query, root, args) => { + resolve: async (_query, _root, args) => { return await this.prisma.category.create({ data: args.input, }) @@ -133,7 +133,7 @@ export class CategorySchema extends PothosSchema { required: true, }), }, - resolve: async (query, root, args) => { + resolve: async (_query, _root, args) => { return await this.prisma.category.createManyAndReturn({ data: args.data, skipDuplicates: true, @@ -150,7 +150,7 @@ export class CategorySchema extends PothosSchema { required: true, }), }, - resolve: async (query, root, args) => { + resolve: async (_query, _root, args) => { return await this.prisma.subCategory.create({ data: args.input, }) diff --git a/src/Mail/mail.service.ts b/src/Mail/mail.service.ts index 6aa35c3..eb07b77 100644 --- a/src/Mail/mail.service.ts +++ b/src/Mail/mail.service.ts @@ -33,6 +33,7 @@ export class MailService { to: string[], subject: string, template: string, + // biome-ignore lint/suspicious/noExplicitAny: context: any, ) { try { diff --git a/src/Mail/templates/ModeratorInvitation.pug b/src/Mail/templates/ModeratorInvitation.pug new file mode 100644 index 0000000..40bc1f1 --- /dev/null +++ b/src/Mail/templates/ModeratorInvitation.pug @@ -0,0 +1,72 @@ +doctype html +html + head + meta(charset="UTF-8") + title Thông báo chọn lựa quản trị viên cho người điều hành + style. + body { + font-family: Arial, sans-serif; + background-color: #f0f8ff; + color: #333; + margin: 0; + padding: 0; + } + .container { + max-width: 600px; + margin: 20px auto; + padding: 20px; + background-color: #ffffff; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + } + .header { + text-align: center; + background-color: #457D84; /* Medium teal */ + color: #ffffff; + padding: 15px; + border-radius: 8px 8px 0 0; + } + .header h1 { + margin: 0; + font-size: 24px; + } + .content { + padding: 20px; + color: #333; + } + .content p { + font-size: 16px; + line-height: 1.5; + } + .button { + display: inline-block; + padding: 12px 20px; + background-color: #2BD4E2; /* Bright aqua */ + color: #ffffff; + text-decoration: none; + font-size: 16px; + border-radius: 5px; + text-align: center; + margin: 20px 0; + } + .footer { + text-align: center; + font-size: 14px; + color: #555; + padding: 10px; + border-top: 1px solid #e0e0e0; + } + body + .container + .header + h1 Thông báo chọn lựa quản trị viên + .content + p Chào #{USER_NAME}, + p Chúng tôi vui mừng thông báo rằng bạn đã được chọn làm người điều hành cho nền tảng của chúng tôi. + p Để bắt đầu, vui lòng nhấn vào nút dưới đây để truy cập vào trang quản lý của bạn. + a.button(href="https://admin.epess.org") Truy cập Trang Quản Lý + p Nếu bạn có bất kỳ câu hỏi nào, đừng ngần ngại liên hệ với chúng tôi. + .footer + p Trân trọng, + p EPESS + p Nền tảng hỗ trợ viết luận \ No newline at end of file diff --git a/src/ManagedService/managedservice.schema.ts b/src/ManagedService/managedservice.schema.ts index 752c487..9ca5a99 100644 --- a/src/ManagedService/managedservice.schema.ts +++ b/src/ManagedService/managedservice.schema.ts @@ -50,7 +50,7 @@ export class ManagedServiceSchema extends PothosSchema { managedService: t.field({ type: this.managedService(), args: this.builder.generator.findUniqueArgs('ManagedService'), - resolve: async (parent, args, ctx) => { + resolve: async (_parent, args, _ctx) => { return this.prisma.managedService.findUnique({ where: args.where, }) @@ -60,7 +60,7 @@ export class ManagedServiceSchema extends PothosSchema { managedServices: t.field({ type: [this.managedService()], args: this.builder.generator.findManyArgs('ManagedService'), - resolve: async (parent, args, ctx) => { + resolve: async (_parent, args, _ctx) => { return this.prisma.managedService.findMany({ where: args.filter ?? undefined, orderBy: args.orderBy ?? undefined, @@ -83,7 +83,7 @@ export class ManagedServiceSchema extends PothosSchema { description: 'The data for the managed service.', }), }, - resolve: async (query, root, args, ctx, info) => { + resolve: async (query, _root, args, _ctx, _info) => { return await this.prisma.managedService.create({ ...query, data: args.input, diff --git a/src/Milestone/milestone.schema.ts b/src/Milestone/milestone.schema.ts index c63ede8..f5e51e6 100644 --- a/src/Milestone/milestone.schema.ts +++ b/src/Milestone/milestone.schema.ts @@ -58,7 +58,7 @@ export class MilestoneSchema extends PothosSchema { args: this.builder.generator.findManyArgs('Milestone'), description: 'Retrieve a list of milestones with optional filtering, ordering, and pagination.', - resolve: async (query, root, args, ctx, info) => { + resolve: async (query, _root, args, _ctx, _info) => { return await this.prisma.milestone.findMany({ ...query, skip: args.skip ?? undefined, @@ -73,7 +73,7 @@ export class MilestoneSchema extends PothosSchema { type: this.milestone(), args: this.builder.generator.findUniqueArgs('Milestone'), description: 'Retrieve a single milestone by its unique identifier.', - resolve: async (query, root, args, ctx, info) => { + resolve: async (query, _root, args, _ctx, _info) => { return await this.prisma.milestone.findUnique({ ...query, where: args.where, diff --git a/src/Schedule/schedule.schema.ts b/src/Schedule/schedule.schema.ts index 5bee6a1..3278d5b 100644 --- a/src/Schedule/schedule.schema.ts +++ b/src/Schedule/schedule.schema.ts @@ -86,7 +86,7 @@ export class ScheduleSchema extends PothosSchema { type: this.schedule(), description: 'Retrieve a single schedule by its unique identifier.', args: this.builder.generator.findUniqueArgs('Schedule'), - resolve: async (query, root, args, ctx, info) => { + resolve: async (query, _root, args, _ctx, _info) => { return await this.prisma.schedule.findUnique({ ...query, where: args.where, @@ -99,7 +99,7 @@ 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) => { + resolve: async (query, _root, args, _ctx, _info) => { return await this.prisma.schedule.findMany({ ...query, skip: args.skip ?? undefined, diff --git a/src/User/user.schema.ts b/src/User/user.schema.ts index 25dd213..a468b0a 100644 --- a/src/User/user.schema.ts +++ b/src/User/user.schema.ts @@ -205,7 +205,7 @@ export class UserSchema extends PothosSchema { required: true, }), }, - resolve: async (query, root, args) => { + resolve: async (query, _root, args) => { return await this.prisma.user.update({ ...query, where: args.where, @@ -214,22 +214,42 @@ export class UserSchema extends PothosSchema { }, }), - sendEmailTest: t.field({ + inviteModerator: t.field({ type: 'String', args: { - to: t.arg({ type: 'String', required: true }), + email: t.arg({ type: 'String', required: true }), }, - resolve: async (_parent, args) => { - await this.mailService.sendTemplateEmail( - [args.to], - 'Bạn đã được mời làm việc tại Trung tâm băng đĩa lậu hải ngoại', - 'MentorInvitation', - { - center_name: 'băng đĩa lậu hải ngoại', - invite_url: 'https://epess.org', - }, - ) - return 'Email sent' + resolve: async (_parent, args, ctx) => { + return this.prisma.$transaction(async (tx) => { + // check context + if (ctx.isSubscription) { + throw new Error('Not allowed') + } + // check context is admin + if (ctx.http.me.role !== 'ADMIN') { + throw new UnauthorizedException(`Only admin can invite moderator`) + } + let user + // perform update role + try { + user = await tx.user.update({ + where: { email: args.email }, + data: { role: 'MODERATOR' }, + }) + } catch (_error) { + throw new Error(`User ${args.email} not found`) + } + // send email + 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', + { + USER_NAME: user.name, + }, + ) + return 'Invited' + }) }, }), })) diff --git a/src/common/constant/config.constant.ts b/src/common/constant/config.constant.ts deleted file mode 100644 index 02265e8..0000000 --- a/src/common/constant/config.constant.ts +++ /dev/null @@ -1,37 +0,0 @@ -// export class ConfigConstant { -// static SLOT_DURATION: number = 60; -// static SLOT_BREAK_DURATION: number = 15; -// static MID_DAY_BREAK_TIME_START: Date = new Date().setHours(12, 0, 0, 0); -// static MID_DAY_BREAK_TIME_END: Date = new Date().setHours(13, 0, 0, 0); -// static SLOT_START_TIME: Date = new Date().setHours(8, 0, 0, 0); -// static SLOT_END_TIME: Date = new Date().setHours(22, 0, 0, 0); -// } - -// export const ConfigConstant: Record = { -// SLOT_DURATION: 60, -// SLOT_BREAK_DURATION: 15, -// MID_DAY_BREAK_TIME_START: { -// hour: 12, -// minute: 0, -// second: 0, -// millisecond: 0, -// }, -// MID_DAY_BREAK_TIME_END: { -// hour: 13, -// minute: 0, -// second: 0, -// millisecond: 0, -// }, -// SLOT_START_TIME: { -// hour: 8, -// minute: 0, -// second: 0, -// millisecond: 0, -// }, -// SLOT_END_TIME: { -// hour: 22, -// minute: 0, -// second: 0, -// millisecond: 0, -// }, -// };