AI da dat ten cho dong song
This commit is contained in:
2833
package-lock.json
generated
2833
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@
|
|||||||
"@graphql-codegen/typescript": "^4.0.9",
|
"@graphql-codegen/typescript": "^4.0.9",
|
||||||
"@graphql-codegen/typescript-operations": "^4.2.3",
|
"@graphql-codegen/typescript-operations": "^4.2.3",
|
||||||
"@graphql-codegen/typescript-resolvers": "^4.2.1",
|
"@graphql-codegen/typescript-resolvers": "^4.2.1",
|
||||||
|
"@nestjs-modules/mailer": "^2.0.2",
|
||||||
"@nestjs/apollo": "^12.2.0",
|
"@nestjs/apollo": "^12.2.0",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/config": "^3.2.3",
|
"@nestjs/config": "^3.2.3",
|
||||||
@@ -69,6 +70,8 @@
|
|||||||
"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",
|
||||||
|
"nodemailer": "^6.9.15",
|
||||||
|
"openai": "^4.68.4",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.0",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
@@ -92,6 +95,7 @@
|
|||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/jest": "^29.5.2",
|
"@types/jest": "^29.5.2",
|
||||||
"@types/node": "^20.3.1",
|
"@types/node": "^20.3.1",
|
||||||
|
"@types/nodemailer": "^6.4.16",
|
||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^4.0.1",
|
||||||
"@types/supertest": "^6.0.0",
|
"@types/supertest": "^6.0.0",
|
||||||
"@types/ws": "^8.5.12",
|
"@types/ws": "^8.5.12",
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ import { OrderModule } from '../Order/order.module';
|
|||||||
import { PaymentModule } from '../Payment/payment.module';
|
import { PaymentModule } from '../Payment/payment.module';
|
||||||
import { PothosApolloDriver } from '@smatch-corp/nestjs-pothos-apollo-driver';
|
import { PothosApolloDriver } from '@smatch-corp/nestjs-pothos-apollo-driver';
|
||||||
import { PothosModule } from '@smatch-corp/nestjs-pothos';
|
import { PothosModule } from '@smatch-corp/nestjs-pothos';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
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 { RefundTicketModule } from '../RefundTicket/refundticket.module';
|
import { RefundTicketModule } from '../RefundTicket/refundticket.module';
|
||||||
|
import { Request } from 'express';
|
||||||
import { ResumeModule } from '../Resume/resume.module';
|
import { ResumeModule } from '../Resume/resume.module';
|
||||||
import { ScheduleModule } from '../Schedule/schedule.module';
|
import { ScheduleModule } from '../Schedule/schedule.module';
|
||||||
import { ServiceAndCategoryModule } from '../ServiceAndCategory/serviceandcategory.module';
|
import { ServiceAndCategoryModule } from '../ServiceAndCategory/serviceandcategory.module';
|
||||||
@@ -32,6 +34,7 @@ import { WorkshopMeetingRoomModule } from '../WorkshopMeetingRoom/workshopmeetin
|
|||||||
import { WorkshopModule } from '../Workshop/workshop.module';
|
import { WorkshopModule } from '../Workshop/workshop.module';
|
||||||
import { WorkshopOrganizationModule } from '../WorkshopOrganization/workshoporganization.module';
|
import { WorkshopOrganizationModule } from '../WorkshopOrganization/workshoporganization.module';
|
||||||
import { WorkshopSubscriptionModule } from '../WorkshopSubscription/workshopsubscription.module';
|
import { WorkshopSubscriptionModule } from '../WorkshopSubscription/workshopsubscription.module';
|
||||||
|
import { initContextCache } from '@pothos/core';
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
@@ -76,6 +79,9 @@ import { WorkshopSubscriptionModule } from '../WorkshopSubscription/workshopsubs
|
|||||||
subscriptions: {
|
subscriptions: {
|
||||||
'graphql-ws': true,
|
'graphql-ws': true,
|
||||||
},
|
},
|
||||||
|
context: async () => ({
|
||||||
|
...initContextCache(),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
45
src/Mail/mail.module.ts
Normal file
45
src/Mail/mail.module.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { MailService } from './mail.service';
|
||||||
|
import { MailerModule } from '@nestjs-modules/mailer';
|
||||||
|
import { OpenaiModule } from '../OpenAI/openai.module';
|
||||||
|
import { PugAdapter } from '@nestjs-modules/mailer/dist/adapters/pug.adapter';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
MailerModule.forRootAsync({
|
||||||
|
useFactory: () => ({
|
||||||
|
transport: {
|
||||||
|
host: process.env.MAILU_HOST,
|
||||||
|
port: parseInt(process.env.MAILU_PORT || '587'),
|
||||||
|
secure: false,
|
||||||
|
pool: true,
|
||||||
|
authMethod: 'login',
|
||||||
|
path: '/',
|
||||||
|
auth: {
|
||||||
|
user: process.env.MAILU_USER,
|
||||||
|
pass: process.env.MAILU_PASSWORD,
|
||||||
|
},
|
||||||
|
verify: true,
|
||||||
|
},
|
||||||
|
defaults: {
|
||||||
|
from: process.env.MAILU_FROM,
|
||||||
|
},
|
||||||
|
// template: {
|
||||||
|
// dir: path.join(__dirname, 'templates'),
|
||||||
|
// adapter: new PugAdapter(),
|
||||||
|
// options: {
|
||||||
|
// strict: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
OpenaiModule,
|
||||||
|
],
|
||||||
|
providers: [MailService],
|
||||||
|
exports: [MailService],
|
||||||
|
})
|
||||||
|
export class MailModule {}
|
||||||
51
src/Mail/mail.service.ts
Normal file
51
src/Mail/mail.service.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { MailerService } from '@nestjs-modules/mailer';
|
||||||
|
import { OpenaiService } from '../OpenAI/openai.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MailService {
|
||||||
|
constructor(
|
||||||
|
private readonly mailerService: MailerService,
|
||||||
|
private readonly openaiService: OpenaiService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async sendEmail(to: string, subject: string, text: string) {
|
||||||
|
try {
|
||||||
|
const mailContent =
|
||||||
|
await this.openaiService.generateInvitationMailContent(
|
||||||
|
to,
|
||||||
|
'John Doe',
|
||||||
|
'https://epess.org',
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await this.mailerService.sendMail({
|
||||||
|
to,
|
||||||
|
subject,
|
||||||
|
text: mailContent ?? text,
|
||||||
|
});
|
||||||
|
Logger.log(result, 'MailService');
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(error, 'MailService');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendTemplateEmail(
|
||||||
|
to: string,
|
||||||
|
subject: string,
|
||||||
|
template: string,
|
||||||
|
context: any,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const result = await this.mailerService.sendMail({
|
||||||
|
to,
|
||||||
|
subject,
|
||||||
|
template,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
Logger.log(result, 'MailService');
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(error, 'MailService');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/OpenAI/openai.module.ts
Normal file
23
src/OpenAI/openai.module.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ClientOptions, OpenAI } from 'openai';
|
||||||
|
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { OpenaiService } from './openai.service';
|
||||||
|
|
||||||
|
const openaiOptions: ClientOptions = {
|
||||||
|
apiKey: process.env.OPENAI_API_KEY,
|
||||||
|
baseURL: process.env.OPENAI_BASE_URL,
|
||||||
|
maxRetries: parseInt(process.env.OPENAI_MAX_RETRIES as string) ?? 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [OpenAI],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: OpenAI,
|
||||||
|
useFactory: () => new OpenAI(openaiOptions),
|
||||||
|
},
|
||||||
|
OpenaiService,
|
||||||
|
],
|
||||||
|
exports: [OpenaiService],
|
||||||
|
})
|
||||||
|
export class OpenaiModule {}
|
||||||
24
src/OpenAI/openai.service.ts
Normal file
24
src/OpenAI/openai.service.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { OpenAI } from 'openai';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class OpenaiService {
|
||||||
|
constructor(private openai: OpenAI) {}
|
||||||
|
|
||||||
|
async generateInvitationMailContent(
|
||||||
|
mail: string,
|
||||||
|
username: string,
|
||||||
|
url: string,
|
||||||
|
) {
|
||||||
|
const prompt = `
|
||||||
|
give me mail content for invitation to a workshop to EPESS and replace {{ mail }} with ${mail}, {{ username }} with ${username} and {{ url }} with ${url}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await this.openai.chat.completions.create({
|
||||||
|
model: 'gpt-4o',
|
||||||
|
messages: [{ role: 'user', content: prompt }],
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.choices[0].message.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,11 +42,11 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
|
|||||||
await this.$connect();
|
await this.$connect();
|
||||||
break; // Exit loop if connection is successful
|
break; // Exit loop if connection is successful
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (attempt < 3) {
|
if (attempt < (parseInt(process.env.PRISMA_MAX_RETRY as string) ?? 3)) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`Connection attempt ${attempt} failed. Retrying in 5000ms...`,
|
`Connection attempt ${attempt} failed. Retrying in 10000ms...`,
|
||||||
);
|
);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||||
} else {
|
} else {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
'Failed to connect to the database after 3 attempts.',
|
'Failed to connect to the database after 3 attempts.',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
Pothos,
|
Pothos,
|
||||||
PothosRef,
|
PothosRef,
|
||||||
@@ -97,6 +97,31 @@ export class ResumeSchema extends PothosSchema {
|
|||||||
@Pothos()
|
@Pothos()
|
||||||
init(): void {
|
init(): void {
|
||||||
this.builder.queryFields((t) => ({
|
this.builder.queryFields((t) => ({
|
||||||
|
myResumes: t.prismaField({
|
||||||
|
description: 'Retrieve a list of resumes for the current user.',
|
||||||
|
type: [this.resume()],
|
||||||
|
args: {
|
||||||
|
status: t.arg({
|
||||||
|
type: ResumeStatus,
|
||||||
|
required: false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
resolve: async (query, root, args, ctx, info) => {
|
||||||
|
try {
|
||||||
|
const resumes = await this.prisma.resume.findMany({
|
||||||
|
...query,
|
||||||
|
where: {
|
||||||
|
userId: ctx.me.id,
|
||||||
|
status: args.status ?? undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return resumes;
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(error, 'myResumes');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
resumes: t.prismaField({
|
resumes: t.prismaField({
|
||||||
description:
|
description:
|
||||||
'Retrieve a list of resumes with optional filtering, ordering, and pagination.',
|
'Retrieve a list of resumes with optional filtering, ordering, and pagination.',
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ import { Builder } from '../Graphql/graphql.builder';
|
|||||||
import { PrismaService } from '../Prisma/prisma.service';
|
import { PrismaService } from '../Prisma/prisma.service';
|
||||||
import { clerkClient } from '@clerk/express';
|
import { clerkClient } from '@clerk/express';
|
||||||
import { UnauthorizedException } from '@nestjs/common';
|
import { UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { MailService } from '../Mail/mail.service';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserSchema extends PothosSchema {
|
export class UserSchema extends PothosSchema {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(SchemaBuilderToken) private readonly builder: Builder,
|
@Inject(SchemaBuilderToken) private readonly builder: Builder,
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly mailService: MailService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -123,22 +124,26 @@ export class UserSchema extends PothosSchema {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
me: t.prismaField({
|
me: t.prismaField({
|
||||||
description: 'Retrieve the current user.',
|
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, info) => {
|
||||||
const sessionCookie = ctx.req.headers.cookie
|
// get session id from X-Session-Id
|
||||||
?.split('; ')
|
const sessionId = ctx.req.headers['x-session-id'];
|
||||||
.find((row) => row.startsWith('__session='))
|
if (!sessionId)
|
||||||
?.split('=')[1];
|
|
||||||
if (!sessionCookie)
|
|
||||||
throw new UnauthorizedException({
|
throw new UnauthorizedException({
|
||||||
message: 'No session cookie found',
|
message: 'No session ID found',
|
||||||
});
|
});
|
||||||
const session = await clerkClient.sessions.getSession(sessionCookie);
|
// verify the token
|
||||||
|
const session = await clerkClient.sessions.getSession(
|
||||||
|
sessionId as string,
|
||||||
|
);
|
||||||
if (!session) throw new UnauthorizedException();
|
if (!session) throw new UnauthorizedException();
|
||||||
return await this.prisma.user.findUnique({
|
const user = await this.prisma.user.findUnique({
|
||||||
where: { id: session.userId },
|
where: { id: session.userId },
|
||||||
});
|
});
|
||||||
|
if (!user) throw new UnauthorizedException();
|
||||||
|
ctx.me = user;
|
||||||
|
return user;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -227,6 +232,17 @@ export class UserSchema extends PothosSchema {
|
|||||||
// });
|
// });
|
||||||
// },
|
// },
|
||||||
// }),
|
// }),
|
||||||
|
|
||||||
|
sendEmailTest: t.field({
|
||||||
|
type: 'String',
|
||||||
|
args: {
|
||||||
|
to: t.arg({ type: 'String', required: true }),
|
||||||
|
},
|
||||||
|
resolve: async (_parent, args, _context, _info) => {
|
||||||
|
await this.mailService.sendEmail(args.to, 'Test', 'Test');
|
||||||
|
return 'Email sent';
|
||||||
|
},
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { ClerkModule } from './Clerk/clerk.module';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { GraphqlModule } from './Graphql/graphql.module';
|
import { GraphqlModule } from './Graphql/graphql.module';
|
||||||
import { ClerkModule } from './Clerk/clerk.module';
|
import { MailModule } from './Mail/mail.module';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
import { RestfulModule } from './Restful/restful.module';
|
import { RestfulModule } from './Restful/restful.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({
|
ConfigModule.forRoot({
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
}),
|
}),
|
||||||
ClerkModule,
|
ClerkModule,
|
||||||
|
MailModule,
|
||||||
GraphqlModule,
|
GraphqlModule,
|
||||||
RestfulModule,
|
RestfulModule,
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user