implement platform config

This commit is contained in:
2024-10-29 19:06:01 +07:00
parent 075610fb9b
commit e003bcd634
11 changed files with 213 additions and 82 deletions

View File

@@ -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,
},
}

View File

@@ -49,7 +49,7 @@ export class AppConfigSchema extends PothosSchema {
type: [this.appConfig()], type: [this.appConfig()],
description: 'Get all app configs', description: 'Get all app configs',
args: this.builder.generator.findManyArgs('Config'), args: this.builder.generator.findManyArgs('Config'),
resolve: async (query, root, args) => { resolve: async (query, _root, args) => {
return await this.prisma.config.findMany({ return await this.prisma.config.findMany({
...query, ...query,
where: args.filter ?? undefined, where: args.filter ?? undefined,

View File

@@ -1,7 +1,8 @@
import { Injectable, OnModuleInit } from '@nestjs/common' import { Injectable, OnModuleInit } from '@nestjs/common'
// import { ConfigConstant } from 'src/common/constant/config.constant';
import { PrismaService } from 'src/Prisma/prisma.service' import { PrismaService } from 'src/Prisma/prisma.service'
import { ConfigConstants } from './appconfig.constant'
import { Config } from '@prisma/client'
@Injectable() @Injectable()
export class AppConfigService implements OnModuleInit { export class AppConfigService implements OnModuleInit {
@@ -9,21 +10,49 @@ export class AppConfigService implements OnModuleInit {
async onModuleInit() { async onModuleInit() {
// get each config from database, if not exist, create default config // get each config from database, if not exist, create default config
// const configs = await this.prisma.config.findMany(); const configs = await this.prisma.config.findMany()
// if (configs.length === 0) { if (configs.length === 0) {
// await this.prisma.config.createMany({ Object.entries(ConfigConstants).forEach(async ([_key, value]) => {
// data: Object.values(ConfigConstant).map((config) => ({ await this.prisma.config.create({
// ...config, data: {
// })), name: value.name,
// }); key: value.key,
// } value: value.value,
// // regenerate missing config visible: value.visible,
// for (const config of Object.values(ConfigConstant)) { },
// if (!configs.find((c) => c.key === config.key)) { })
// await this.prisma.config.create({ })
// data: config, }
// }); }
// }
// } 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 },
})
} }
} }

View File

@@ -69,7 +69,7 @@ export class CategorySchema extends PothosSchema {
'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) => {
return await this.prisma.category.findMany({ return await this.prisma.category.findMany({
...query, ...query,
skip: args.skip ?? undefined, skip: args.skip ?? undefined,
@@ -83,7 +83,7 @@ export class CategorySchema extends PothosSchema {
description: 'Retrieve a single category by its unique identifier.', description: 'Retrieve a single category by its unique identifier.',
type: this.category(), type: this.category(),
args: this.builder.generator.findUniqueArgs('Category'), args: this.builder.generator.findUniqueArgs('Category'),
resolve: async (query, root, args) => { resolve: async (query, _root, args) => {
return await this.prisma.category.findUnique({ return await this.prisma.category.findUnique({
...query, ...query,
where: args.where ?? undefined, where: args.where ?? undefined,
@@ -95,7 +95,7 @@ export class CategorySchema extends PothosSchema {
'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) => {
return await this.prisma.subCategory.findMany({ return await this.prisma.subCategory.findMany({
...query, ...query,
where: args.filter ?? undefined, where: args.filter ?? undefined,
@@ -118,7 +118,7 @@ export class CategorySchema extends PothosSchema {
required: true, required: true,
}), }),
}, },
resolve: async (query, root, args) => { resolve: async (_query, _root, args) => {
return await this.prisma.category.create({ return await this.prisma.category.create({
data: args.input, data: args.input,
}) })
@@ -133,7 +133,7 @@ export class CategorySchema extends PothosSchema {
required: true, required: true,
}), }),
}, },
resolve: async (query, root, args) => { resolve: async (_query, _root, args) => {
return await this.prisma.category.createManyAndReturn({ return await this.prisma.category.createManyAndReturn({
data: args.data, data: args.data,
skipDuplicates: true, skipDuplicates: true,
@@ -150,7 +150,7 @@ export class CategorySchema extends PothosSchema {
required: true, required: true,
}), }),
}, },
resolve: async (query, root, args) => { resolve: async (_query, _root, args) => {
return await this.prisma.subCategory.create({ return await this.prisma.subCategory.create({
data: args.input, data: args.input,
}) })

View File

@@ -33,6 +33,7 @@ export class MailService {
to: string[], to: string[],
subject: string, subject: string,
template: string, template: string,
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
context: any, context: any,
) { ) {
try { try {

View File

@@ -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

View File

@@ -50,7 +50,7 @@ export class ManagedServiceSchema extends PothosSchema {
managedService: t.field({ managedService: t.field({
type: this.managedService(), type: this.managedService(),
args: this.builder.generator.findUniqueArgs('ManagedService'), args: this.builder.generator.findUniqueArgs('ManagedService'),
resolve: async (parent, args, ctx) => { resolve: async (_parent, args, _ctx) => {
return this.prisma.managedService.findUnique({ return this.prisma.managedService.findUnique({
where: args.where, where: args.where,
}) })
@@ -60,7 +60,7 @@ export class ManagedServiceSchema extends PothosSchema {
managedServices: t.field({ managedServices: t.field({
type: [this.managedService()], type: [this.managedService()],
args: this.builder.generator.findManyArgs('ManagedService'), args: this.builder.generator.findManyArgs('ManagedService'),
resolve: async (parent, args, ctx) => { resolve: async (_parent, args, _ctx) => {
return this.prisma.managedService.findMany({ return this.prisma.managedService.findMany({
where: args.filter ?? undefined, where: args.filter ?? undefined,
orderBy: args.orderBy ?? undefined, orderBy: args.orderBy ?? undefined,
@@ -83,7 +83,7 @@ export class ManagedServiceSchema extends PothosSchema {
description: 'The data for the managed service.', 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({ return await this.prisma.managedService.create({
...query, ...query,
data: args.input, data: args.input,

View File

@@ -58,7 +58,7 @@ export class MilestoneSchema extends PothosSchema {
args: this.builder.generator.findManyArgs('Milestone'), args: this.builder.generator.findManyArgs('Milestone'),
description: description:
'Retrieve a list of milestones with optional filtering, ordering, and pagination.', '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({ return await this.prisma.milestone.findMany({
...query, ...query,
skip: args.skip ?? undefined, skip: args.skip ?? undefined,
@@ -73,7 +73,7 @@ export class MilestoneSchema extends PothosSchema {
type: this.milestone(), type: this.milestone(),
args: this.builder.generator.findUniqueArgs('Milestone'), args: this.builder.generator.findUniqueArgs('Milestone'),
description: 'Retrieve a single milestone by its unique identifier.', 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({ return await this.prisma.milestone.findUnique({
...query, ...query,
where: args.where, where: args.where,

View File

@@ -86,7 +86,7 @@ export class ScheduleSchema extends PothosSchema {
type: this.schedule(), type: this.schedule(),
description: 'Retrieve a single schedule by its unique identifier.', description: 'Retrieve a single schedule by its unique identifier.',
args: this.builder.generator.findUniqueArgs('Schedule'), 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({ return await this.prisma.schedule.findUnique({
...query, ...query,
where: args.where, where: args.where,
@@ -99,7 +99,7 @@ export class ScheduleSchema extends PothosSchema {
args: this.builder.generator.findManyArgs('Schedule'), args: this.builder.generator.findManyArgs('Schedule'),
description: description:
'Retrieve a list of schedules with optional filtering, ordering, and pagination.', '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({ return await this.prisma.schedule.findMany({
...query, ...query,
skip: args.skip ?? undefined, skip: args.skip ?? undefined,

View File

@@ -205,7 +205,7 @@ export class UserSchema extends PothosSchema {
required: true, required: true,
}), }),
}, },
resolve: async (query, root, args) => { resolve: async (query, _root, args) => {
return await this.prisma.user.update({ return await this.prisma.user.update({
...query, ...query,
where: args.where, where: args.where,
@@ -214,22 +214,42 @@ export class UserSchema extends PothosSchema {
}, },
}), }),
sendEmailTest: t.field({ inviteModerator: t.field({
type: 'String', type: 'String',
args: { args: {
to: t.arg({ type: 'String', required: true }), email: t.arg({ type: 'String', required: true }),
}, },
resolve: async (_parent, args) => { 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( await this.mailService.sendTemplateEmail(
[args.to], [args.email],
'Bạn đã được mời làm việc tại Trung tâm băng đĩa lậu hải ngoại', 'Thông báo chọn lựa quản trị viên cho người điều hành',
'MentorInvitation', 'ModeratorInvitation',
{ {
center_name: 'băng đĩa lậu hải ngoại', USER_NAME: user.name,
invite_url: 'https://epess.org',
}, },
) )
return 'Email sent' return 'Invited'
})
}, },
}), }),
})) }))

View File

@@ -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<string, any> = {
// 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,
// },
// };