implement platform config
This commit is contained in:
46
src/AppConfig/appconfig.constant.ts
Normal file
46
src/AppConfig/appconfig.constant.ts
Normal 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,
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -33,6 +33,7 @@ export class MailService {
|
||||
to: string[],
|
||||
subject: string,
|
||||
template: string,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
context: any,
|
||||
) {
|
||||
try {
|
||||
|
||||
72
src/Mail/templates/ModeratorInvitation.pug
Normal file
72
src/Mail/templates/ModeratorInvitation.pug
Normal 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
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
})
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
@@ -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,
|
||||
// },
|
||||
// };
|
||||
Reference in New Issue
Block a user