implement redis cache for context
This commit is contained in:
88
package-lock.json
generated
88
package-lock.json
generated
@@ -48,6 +48,7 @@
|
|||||||
"graphql-tools": "^9.0.1",
|
"graphql-tools": "^9.0.1",
|
||||||
"graphql-upload": "15.0.2",
|
"graphql-upload": "15.0.2",
|
||||||
"graphql-ws": "^5.16.0",
|
"graphql-ws": "^5.16.0",
|
||||||
|
"ioredis": "^5.4.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"minio": "^8.0.1",
|
"minio": "^8.0.1",
|
||||||
"nestjs-minio": "^2.6.2",
|
"nestjs-minio": "^2.6.2",
|
||||||
@@ -3390,6 +3391,12 @@
|
|||||||
"url": "https://github.com/sponsors/nzakas"
|
"url": "https://github.com/sponsors/nzakas"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ioredis/commands": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
@@ -7836,6 +7843,15 @@
|
|||||||
"node": ">=0.8"
|
"node": ">=0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cluster-key-slot": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/co": {
|
"node_modules/co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
@@ -8358,6 +8374,15 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/denque": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@@ -10779,6 +10804,30 @@
|
|||||||
"loose-envify": "^1.0.0"
|
"loose-envify": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ioredis": {
|
||||||
|
"version": "5.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz",
|
||||||
|
"integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ioredis/commands": "^1.1.1",
|
||||||
|
"cluster-key-slot": "^1.1.0",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"denque": "^2.1.0",
|
||||||
|
"lodash.defaults": "^4.2.0",
|
||||||
|
"lodash.isarguments": "^3.1.0",
|
||||||
|
"redis-errors": "^1.2.0",
|
||||||
|
"redis-parser": "^3.0.0",
|
||||||
|
"standard-as-callback": "^2.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.22.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/ioredis"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
|
||||||
@@ -12463,6 +12512,12 @@
|
|||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.defaults": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.get": {
|
"node_modules/lodash.get": {
|
||||||
"version": "4.4.2",
|
"version": "4.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||||
@@ -12475,6 +12530,12 @@
|
|||||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.isarguments": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.isboolean": {
|
"node_modules/lodash.isboolean": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||||
@@ -14993,6 +15054,27 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redis-errors": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/redis-parser": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"redis-errors": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reflect-metadata": {
|
"node_modules/reflect-metadata": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
||||||
@@ -15816,6 +15898,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/standard-as-callback": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
"graphql-tools": "^9.0.1",
|
"graphql-tools": "^9.0.1",
|
||||||
"graphql-upload": "15.0.2",
|
"graphql-upload": "15.0.2",
|
||||||
"graphql-ws": "^5.16.0",
|
"graphql-ws": "^5.16.0",
|
||||||
|
"ioredis": "^5.4.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"minio": "^8.0.1",
|
"minio": "^8.0.1",
|
||||||
"nestjs-minio": "^2.6.2",
|
"nestjs-minio": "^2.6.2",
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Global, Module } from '@nestjs/common';
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { AppConfigSchema } from './appconfig.schema';
|
import { AppConfigSchema } from './appconfig.schema';
|
||||||
|
import { AppConfigService } from './appconfig.service';
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [AppConfigSchema],
|
providers: [AppConfigSchema, AppConfigService],
|
||||||
providers: [AppConfigSchema],
|
exports: [AppConfigSchema, AppConfigService],
|
||||||
exports: [AppConfigSchema],
|
|
||||||
})
|
})
|
||||||
export class AppConfigModule {}
|
export class AppConfigModule {}
|
||||||
|
|||||||
@@ -26,8 +26,12 @@ export class AppConfigSchema extends PothosSchema {
|
|||||||
id: t.exposeID('id', {
|
id: t.exposeID('id', {
|
||||||
description: 'The unique identifier for the config',
|
description: 'The unique identifier for the config',
|
||||||
}),
|
}),
|
||||||
name: t.exposeString('name', { description: 'The name of the config' }),
|
name: t.exposeString('name', {
|
||||||
key: t.exposeString('key', { description: 'The key of the config' }),
|
description: 'The name of the config',
|
||||||
|
}),
|
||||||
|
key: t.exposeString('key', {
|
||||||
|
description: 'The key of the config',
|
||||||
|
}),
|
||||||
value: t.exposeString('value', {
|
value: t.exposeString('value', {
|
||||||
description: 'The value of the config',
|
description: 'The value of the config',
|
||||||
}),
|
}),
|
||||||
@@ -45,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, ctx, info) => {
|
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,
|
||||||
@@ -56,6 +60,53 @@ export class AppConfigSchema extends PothosSchema {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
appConfig: t.prismaField({
|
||||||
|
type: this.appConfig(),
|
||||||
|
description: 'Get an app config by key',
|
||||||
|
args: this.builder.generator.findUniqueArgs('Config'),
|
||||||
|
resolve: async (query, root, args) => {
|
||||||
|
return await this.prisma.config.findUnique({
|
||||||
|
...query,
|
||||||
|
where: args.where ?? undefined,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mutations
|
||||||
|
this.builder.mutationFields((t) => ({
|
||||||
|
createAppConfig: t.prismaField({
|
||||||
|
type: this.appConfig(),
|
||||||
|
description: 'Create an app config',
|
||||||
|
args: {
|
||||||
|
input: t.arg({
|
||||||
|
type: this.builder.generator.getCreateInput('Config'),
|
||||||
|
required: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
resolve: async (query, root, args) => {
|
||||||
|
return await this.prisma.config.create({
|
||||||
|
...query,
|
||||||
|
data: args.input,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
createAppConfigs: t.prismaField({
|
||||||
|
type: [this.appConfig()],
|
||||||
|
description: 'Create multiple app configs',
|
||||||
|
args: {
|
||||||
|
input: t.arg({
|
||||||
|
type: this.builder.generator.getCreateManyInput('Config'),
|
||||||
|
required: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
resolve: async (query, root, args) => {
|
||||||
|
return await this.prisma.config.createManyAndReturn({
|
||||||
|
...query,
|
||||||
|
data: args.input,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ export class CenterSchema extends PothosSchema {
|
|||||||
if (args.approve) {
|
if (args.approve) {
|
||||||
try {
|
try {
|
||||||
await this.mailService.sendTemplateEmail(
|
await this.mailService.sendTemplateEmail(
|
||||||
centerOwner.email,
|
[centerOwner.email],
|
||||||
'Thông báo phê duyệt đăng ký trung tâm',
|
'Thông báo phê duyệt đăng ký trung tâm',
|
||||||
'CenterApproved',
|
'CenterApproved',
|
||||||
{
|
{
|
||||||
@@ -277,7 +277,7 @@ export class CenterSchema extends PothosSchema {
|
|||||||
// mail to center owner if rejected
|
// mail to center owner if rejected
|
||||||
try {
|
try {
|
||||||
await this.mailService.sendTemplateEmail(
|
await this.mailService.sendTemplateEmail(
|
||||||
centerOwner.email,
|
[centerOwner.email],
|
||||||
'Thông báo từ chối đăng ký trung tâm',
|
'Thông báo từ chối đăng ký trung tâm',
|
||||||
'CenterRejected',
|
'CenterRejected',
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export class CenterMentorSchema extends PothosSchema {
|
|||||||
@Pothos()
|
@Pothos()
|
||||||
init(): void {
|
init(): void {
|
||||||
this.builder.queryFields((t) => ({
|
this.builder.queryFields((t) => ({
|
||||||
centerMentor: t.prismaField({
|
centerMentors: t.prismaField({
|
||||||
description:
|
description:
|
||||||
'Retrieve a list of center mentors with optional filtering, ordering, and pagination.',
|
'Retrieve a list of center mentors with optional filtering, ordering, and pagination.',
|
||||||
type: [this.centerMentor()],
|
type: [this.centerMentor()],
|
||||||
@@ -159,7 +159,7 @@ export class CenterMentorSchema extends PothosSchema {
|
|||||||
const inviteUrl = `${process.env.CENTER_BASE_URL}/invite?token=${token}`;
|
const inviteUrl = `${process.env.CENTER_BASE_URL}/invite?token=${token}`;
|
||||||
// mail to user with params centerId, email
|
// mail to user with params centerId, email
|
||||||
await this.mailService.sendTemplateEmail(
|
await this.mailService.sendTemplateEmail(
|
||||||
args.email,
|
[args.email],
|
||||||
'Invite to center',
|
'Invite to center',
|
||||||
'MentorInvitation',
|
'MentorInvitation',
|
||||||
{
|
{
|
||||||
@@ -189,7 +189,7 @@ export class CenterMentorSchema extends PothosSchema {
|
|||||||
const inviteUrl = `${process.env.CENTER_BASE_URL}/invite?token=${token}`;
|
const inviteUrl = `${process.env.CENTER_BASE_URL}/invite?token=${token}`;
|
||||||
// mail to user with params centerId, email
|
// mail to user with params centerId, email
|
||||||
await this.mailService.sendTemplateEmail(
|
await this.mailService.sendTemplateEmail(
|
||||||
args.email,
|
[args.email],
|
||||||
'Invite to center',
|
'Invite to center',
|
||||||
'MentorInvitation',
|
'MentorInvitation',
|
||||||
{
|
{
|
||||||
@@ -251,7 +251,7 @@ export class CenterMentorSchema extends PothosSchema {
|
|||||||
if (args.approved) {
|
if (args.approved) {
|
||||||
// send mail to user
|
// send mail to user
|
||||||
await this.mailService.sendTemplateEmail(
|
await this.mailService.sendTemplateEmail(
|
||||||
email.email,
|
[email.email],
|
||||||
'Thông báo về việc được chấp nhận làm mentor',
|
'Thông báo về việc được chấp nhận làm mentor',
|
||||||
'MentorApproved',
|
'MentorApproved',
|
||||||
{
|
{
|
||||||
@@ -291,7 +291,7 @@ export class CenterMentorSchema extends PothosSchema {
|
|||||||
}
|
}
|
||||||
// if rejected, update adminNote
|
// if rejected, update adminNote
|
||||||
await this.mailService.sendTemplateEmail(
|
await this.mailService.sendTemplateEmail(
|
||||||
email.email,
|
[email.email],
|
||||||
'Thông báo về việc không được chấp nhận làm mentor',
|
'Thông báo về việc không được chấp nhận làm mentor',
|
||||||
'MentorRejected',
|
'MentorRejected',
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Global, MiddlewareConsumer, Module } from '@nestjs/common';
|
|||||||
|
|
||||||
import { AdminNoteModule } from '../AdminNote/adminnote.module';
|
import { AdminNoteModule } from '../AdminNote/adminnote.module';
|
||||||
import { ApolloDriverConfig } from '@nestjs/apollo';
|
import { ApolloDriverConfig } from '@nestjs/apollo';
|
||||||
|
import { AppConfigModule } from '../AppConfig/appconfig.module';
|
||||||
import { Builder } from './graphql.builder';
|
import { Builder } from './graphql.builder';
|
||||||
import { CategoryModule } from '../Category/category.module';
|
import { CategoryModule } from '../Category/category.module';
|
||||||
import { CenterMentorModule } from '../CenterMentor/centermentor.module';
|
import { CenterMentorModule } from '../CenterMentor/centermentor.module';
|
||||||
@@ -21,6 +22,8 @@ import { PothosModule } from '@smatch-corp/nestjs-pothos';
|
|||||||
import { PrismaCrudGenerator } from './graphql.generator';
|
import { PrismaCrudGenerator } from './graphql.generator';
|
||||||
import { PrismaModule } from '../Prisma/prisma.module';
|
import { PrismaModule } from '../Prisma/prisma.module';
|
||||||
import { PrismaService } from '../Prisma/prisma.service';
|
import { PrismaService } from '../Prisma/prisma.service';
|
||||||
|
import { RedisModule } from 'src/Redis/redis.module';
|
||||||
|
import { RedisService } from 'src/Redis/redis.service';
|
||||||
import { RefundTicketModule } from '../RefundTicket/refundticket.module';
|
import { RefundTicketModule } from '../RefundTicket/refundticket.module';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { ResumeModule } from '../Resume/resume.module';
|
import { ResumeModule } from '../Resume/resume.module';
|
||||||
@@ -42,6 +45,8 @@ import { initContextCache } from '@pothos/core';
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
PrismaModule,
|
PrismaModule,
|
||||||
|
RedisModule,
|
||||||
|
AppConfigModule,
|
||||||
UserModule,
|
UserModule,
|
||||||
CenterModule,
|
CenterModule,
|
||||||
ServiceModule,
|
ServiceModule,
|
||||||
@@ -93,8 +98,9 @@ import { initContextCache } from '@pothos/core';
|
|||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: GraphqlService,
|
provide: GraphqlService,
|
||||||
useFactory: (prisma: PrismaService) => new GraphqlService(prisma),
|
useFactory: (prisma: PrismaService, redis: RedisService) =>
|
||||||
inject: [PrismaService],
|
new GraphqlService(prisma, redis),
|
||||||
|
inject: [PrismaService, 'REDIS_CLIENT'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: Builder,
|
provide: Builder,
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
import { Injectable, Logger, UnauthorizedException } from '@nestjs/common';
|
import {
|
||||||
|
Inject,
|
||||||
|
Injectable,
|
||||||
|
Logger,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
|
||||||
import { PrismaService } from '../Prisma/prisma.service';
|
import { PrismaService } from '../Prisma/prisma.service';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { clerkClient } from '@clerk/express';
|
import { clerkClient } from '@clerk/express';
|
||||||
|
|
||||||
|
import { RedisService } from '../Redis/redis.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlService {
|
export class GraphqlService {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
@Inject('REDIS_CLIENT') private readonly redis: RedisService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async acquireContext(req: Request) {
|
async acquireContext(req: Request) {
|
||||||
// get x-session-id from headers
|
// get x-session-id from headers
|
||||||
@@ -24,6 +34,11 @@ export class GraphqlService {
|
|||||||
if (disableAuth) {
|
if (disableAuth) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
// redis context cache
|
||||||
|
const cachedUser = await this.redis.getUser(sessionId);
|
||||||
|
if (cachedUser) {
|
||||||
|
return cachedUser;
|
||||||
|
}
|
||||||
// check if the token is valid
|
// check if the token is valid
|
||||||
const session = await clerkClient.sessions.getSession(sessionId as string);
|
const session = await clerkClient.sessions.getSession(sessionId as string);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
@@ -35,7 +50,7 @@ export class GraphqlService {
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
throw new UnauthorizedException('User not found');
|
throw new UnauthorizedException('User not found');
|
||||||
}
|
}
|
||||||
Logger.log(`User ${user.name} with id ${user.id} acquired context`);
|
await this.redis.setUser(sessionId, user, session.expireAt);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class MailService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async sendTemplateEmail(
|
async sendTemplateEmail(
|
||||||
to: string,
|
to: string[],
|
||||||
subject: string,
|
subject: string,
|
||||||
template: string,
|
template: string,
|
||||||
context: any,
|
context: any,
|
||||||
|
|||||||
72
src/Mail/templates/ServiceApproved.pug
Normal file
72
src/Mail/templates/ServiceApproved.pug
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
doctype html
|
||||||
|
html
|
||||||
|
head
|
||||||
|
meta(charset="UTF-8")
|
||||||
|
title Thông báo phê duyệt Dịch vụ #{SERVICE_NAME} của Trung tâm #{CENTER_NAME}
|
||||||
|
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 Chúc mừng Dịch vụ #{SERVICE_NAME} đã được phê duyệt
|
||||||
|
.content
|
||||||
|
p Kính gửi Quý Trung tâm #{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 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ụ
|
||||||
|
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.
|
||||||
|
.footer
|
||||||
|
p Trân trọng,
|
||||||
|
p EPESS
|
||||||
|
p Nền tảng hỗ trợ viết luận
|
||||||
84
src/Mail/templates/ServiceRejected.pug
Normal file
84
src/Mail/templates/ServiceRejected.pug
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
doctype html
|
||||||
|
html
|
||||||
|
head
|
||||||
|
meta(charset="UTF-8")
|
||||||
|
title Thông báo từ chối Dịch vụ #{SERVICE_NAME} của Trung tâm #{CENTER_NAME}
|
||||||
|
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: #d9534f; /* Red color for rejection */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
.note {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
}
|
||||||
|
.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 từ chối Dịch vụ #{SERVICE_NAME}
|
||||||
|
.content
|
||||||
|
p Kính gửi Quý Trung tâm #{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.
|
||||||
|
.note
|
||||||
|
p Lý do từ chối:
|
||||||
|
p #{ADMIN_NOTE}
|
||||||
|
p Chúng tôi khuyến khích bạn xem xét lại thông tin và nộp đơn đăng ký lại 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:
|
||||||
|
a.button(href="https://center.epess.org") Truy cập Trung tâm
|
||||||
|
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.
|
||||||
|
.footer
|
||||||
|
p Trân trọng,
|
||||||
|
p EPESS
|
||||||
|
p Nền tảng hỗ trợ viết luận
|
||||||
33
src/Mail/templates/Signature.pug
Normal file
33
src/Mail/templates/Signature.pug
Normal file
File diff suppressed because one or more lines are too long
@@ -14,7 +14,7 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
|
|||||||
super({
|
super({
|
||||||
log: [
|
log: [
|
||||||
{
|
{
|
||||||
emit: 'event',
|
emit: 'stdout',
|
||||||
level: 'query',
|
level: 'query',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
15
src/Redis/redis.module.ts
Normal file
15
src/Redis/redis.module.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { RedisService } from './redis.service';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: 'REDIS_CLIENT',
|
||||||
|
useClass: RedisService,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
exports: ['REDIS_CLIENT'],
|
||||||
|
})
|
||||||
|
export class RedisModule {}
|
||||||
41
src/Redis/redis.service.ts
Normal file
41
src/Redis/redis.service.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Redis } from 'ioredis';
|
||||||
|
import { User } from '@prisma/client';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RedisService {
|
||||||
|
private readonly redis: Redis;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.redis = new Redis(process.env.REDIS_URL as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(key: string) {
|
||||||
|
return await this.redis.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(key: string, value: string, expireAt: number) {
|
||||||
|
return await this.redis.set(key, value, 'EXAT', expireAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
async del(key: string) {
|
||||||
|
return await this.redis.del(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
return await this.redis.quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUser(sessionId: string) {
|
||||||
|
const userData = await this.get(sessionId);
|
||||||
|
if (!userData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const retrievedUser: User = JSON.parse(userData);
|
||||||
|
return retrievedUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setUser(sessionId: string, user: User, expireAt: number) {
|
||||||
|
return await this.set(sessionId, JSON.stringify(user), expireAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
Pothos,
|
Pothos,
|
||||||
PothosRef,
|
PothosRef,
|
||||||
@@ -237,6 +237,10 @@ export class ServiceSchema extends PothosSchema {
|
|||||||
type: 'Boolean',
|
type: 'Boolean',
|
||||||
required: true,
|
required: true,
|
||||||
}),
|
}),
|
||||||
|
adminNote: t.arg({
|
||||||
|
type: 'String',
|
||||||
|
required: false,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
resolve: async (query, root, args, ctx, info) => {
|
resolve: async (query, root, args, ctx, info) => {
|
||||||
return await this.prisma.$transaction(async (prisma) => {
|
return await this.prisma.$transaction(async (prisma) => {
|
||||||
@@ -258,9 +262,15 @@ export class ServiceSchema extends PothosSchema {
|
|||||||
status: args.approve
|
status: args.approve
|
||||||
? ServiceStatus.APPROVED
|
? ServiceStatus.APPROVED
|
||||||
: ServiceStatus.REJECTED,
|
: ServiceStatus.REJECTED,
|
||||||
|
adminNote: {
|
||||||
|
create: {
|
||||||
|
content: args.adminNote ?? '',
|
||||||
|
notedByUserId: ctx.me.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// mail to center owner and mentor who requested the service
|
// mail to all mentor or center owner for the center
|
||||||
const center = await prisma.center.findUnique({
|
const center = await prisma.center.findUnique({
|
||||||
where: { id: service.centerId },
|
where: { id: service.centerId },
|
||||||
});
|
});
|
||||||
@@ -276,15 +286,36 @@ export class ServiceSchema extends PothosSchema {
|
|||||||
const centerMentor = await prisma.centerMentor.findMany({
|
const centerMentor = await prisma.centerMentor.findMany({
|
||||||
where: { centerId: service.centerId },
|
where: { centerId: service.centerId },
|
||||||
});
|
});
|
||||||
const mentorEmails = centerMentor.map((mentor) => mentor.mentorId);
|
const mentorIds = centerMentor.map((mentor) => mentor.mentorId);
|
||||||
const emails = [centerOwner.email, ...mentorEmails];
|
// get mentor emails
|
||||||
for (const email of emails) {
|
const mentorEmails = await prisma.user.findMany({
|
||||||
await this.mailService.sendEmail(
|
where: { id: { in: mentorIds } },
|
||||||
email,
|
});
|
||||||
args.approve
|
Logger.log(mentorEmails, 'ServiceSchema');
|
||||||
? 'Your service has been approved'
|
const emails = [
|
||||||
: 'Your service has been rejected',
|
centerOwner.email,
|
||||||
args.approve ? 'service-approved' : 'service-rejected',
|
...mentorEmails.map((mentor) => mentor.email),
|
||||||
|
];
|
||||||
|
if (args.approve) {
|
||||||
|
await this.mailService.sendTemplateEmail(
|
||||||
|
emails,
|
||||||
|
'Thông báo về trạng thái dịch vụ',
|
||||||
|
'ServiceApproved',
|
||||||
|
{
|
||||||
|
SERVICE_NAME: service.name,
|
||||||
|
CENTER_NAME: center.name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await this.mailService.sendTemplateEmail(
|
||||||
|
emails,
|
||||||
|
'Thông báo về trạng thái dịch vụ',
|
||||||
|
'ServiceRejected',
|
||||||
|
{
|
||||||
|
SERVICE_NAME: service.name,
|
||||||
|
CENTER_NAME: center.name,
|
||||||
|
ADMIN_NOTE: args.adminNote ?? 'Không có lý do',
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return updatedService;
|
return updatedService;
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export class UserSchema extends PothosSchema {
|
|||||||
me: t.prismaField({
|
me: t.prismaField({
|
||||||
description: 'Retrieve the current user by token.',
|
description: 'Retrieve the current user by token.',
|
||||||
type: this.user(),
|
type: this.user(),
|
||||||
resolve: async (query, root, args, ctx, info) => {
|
resolve: async (query, root, args, ctx) => {
|
||||||
// get session id from X-Session-Id
|
// get session id from X-Session-Id
|
||||||
const sessionId = ctx.req.headers['x-session-id'];
|
const sessionId = ctx.req.headers['x-session-id'];
|
||||||
if (!sessionId)
|
if (!sessionId)
|
||||||
@@ -155,7 +155,7 @@ export class UserSchema extends PothosSchema {
|
|||||||
'Retrieve a list of users with optional filtering, ordering, and pagination.',
|
'Retrieve a list of users with optional filtering, ordering, and pagination.',
|
||||||
type: [this.user()],
|
type: [this.user()],
|
||||||
args: this.builder.generator.findManyArgs('User'),
|
args: this.builder.generator.findManyArgs('User'),
|
||||||
resolve: async (query, root, args, ctx, info) => {
|
resolve: async (query, root, args) => {
|
||||||
return await this.prisma.user.findMany({
|
return await this.prisma.user.findMany({
|
||||||
...query,
|
...query,
|
||||||
take: args.take ?? undefined,
|
take: args.take ?? undefined,
|
||||||
@@ -170,7 +170,7 @@ export class UserSchema extends PothosSchema {
|
|||||||
description: 'Retrieve a single user by their unique identifier.',
|
description: 'Retrieve a single user by their unique identifier.',
|
||||||
type: this.user(),
|
type: this.user(),
|
||||||
args: this.builder.generator.findUniqueArgs('User'),
|
args: this.builder.generator.findUniqueArgs('User'),
|
||||||
resolve: async (query, root, args, ctx, info) => {
|
resolve: async (query, root, args) => {
|
||||||
return await this.prisma.user.findUniqueOrThrow({
|
return await this.prisma.user.findUniqueOrThrow({
|
||||||
...query,
|
...query,
|
||||||
where: args.where,
|
where: args.where,
|
||||||
@@ -183,7 +183,7 @@ export class UserSchema extends PothosSchema {
|
|||||||
args: {
|
args: {
|
||||||
sessionId: t.arg({ type: 'String', required: true }),
|
sessionId: t.arg({ type: 'String', required: true }),
|
||||||
},
|
},
|
||||||
resolve: async (query, root, args, ctx, info) => {
|
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');
|
||||||
@@ -212,7 +212,7 @@ export class UserSchema extends PothosSchema {
|
|||||||
required: true,
|
required: true,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
resolve: async (query, root, args, ctx, info) => {
|
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,
|
||||||
@@ -221,29 +221,14 @@ export class UserSchema extends PothosSchema {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// banUser: t.prismaField({
|
|
||||||
// description: 'Ban a user.',
|
|
||||||
// type: this.user(),
|
|
||||||
// args: {
|
|
||||||
// userId: t.arg({ type: 'String', required: true }),
|
|
||||||
// },
|
|
||||||
// resolve: async (query, root, args, ctx, info) => {
|
|
||||||
// return await this.prisma.user.update({
|
|
||||||
// ...query,
|
|
||||||
// where: { id: args.userId },
|
|
||||||
// data: { banned: true },
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
|
|
||||||
sendEmailTest: t.field({
|
sendEmailTest: t.field({
|
||||||
type: 'String',
|
type: 'String',
|
||||||
args: {
|
args: {
|
||||||
to: t.arg({ type: 'String', required: true }),
|
to: t.arg({ type: 'String', required: true }),
|
||||||
},
|
},
|
||||||
resolve: async (_parent, args, _context, _info) => {
|
resolve: async (_parent, args) => {
|
||||||
await this.mailService.sendTemplateEmail(
|
await this.mailService.sendTemplateEmail(
|
||||||
args.to,
|
[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',
|
'Bạn đã được mời làm việc tại Trung tâm băng đĩa lậu hải ngoại',
|
||||||
'MentorInvitation',
|
'MentorInvitation',
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export class WorkshopSubscriptionSchema extends PothosSchema {
|
|||||||
args: this.builder.generator.findUniqueArgs('WorkshopSubscription'),
|
args: this.builder.generator.findUniqueArgs('WorkshopSubscription'),
|
||||||
description:
|
description:
|
||||||
'Retrieve a single workshop subscription by its unique identifier.',
|
'Retrieve a single workshop subscription by its unique identifier.',
|
||||||
resolve: async (query, root, args, ctx, info) => {
|
resolve: async (query, root, args) => {
|
||||||
return await this.prisma.workshopSubscription.findUnique({
|
return await this.prisma.workshopSubscription.findUnique({
|
||||||
...query,
|
...query,
|
||||||
where: args.where,
|
where: args.where,
|
||||||
@@ -62,7 +62,7 @@ export class WorkshopSubscriptionSchema extends PothosSchema {
|
|||||||
args: this.builder.generator.findManyArgs('WorkshopSubscription'),
|
args: this.builder.generator.findManyArgs('WorkshopSubscription'),
|
||||||
description:
|
description:
|
||||||
'Retrieve a list of workshop subscriptions with optional filtering, ordering, and pagination.',
|
'Retrieve a list of workshop subscriptions with optional filtering, ordering, and pagination.',
|
||||||
resolve: async (query, root, args, ctx, info) => {
|
resolve: async (query, root, args) => {
|
||||||
return await this.prisma.workshopSubscription.findMany({
|
return await this.prisma.workshopSubscription.findMany({
|
||||||
...query,
|
...query,
|
||||||
skip: args.skip ?? undefined,
|
skip: args.skip ?? undefined,
|
||||||
|
|||||||
Reference in New Issue
Block a user