AI da dat ten cho dong song

This commit is contained in:
2024-10-26 11:10:14 +07:00
parent 2b6d3869f9
commit 48305a3948
11 changed files with 2999 additions and 63 deletions

View File

@@ -16,10 +16,12 @@ import { OrderModule } from '../Order/order.module';
import { PaymentModule } from '../Payment/payment.module';
import { PothosApolloDriver } from '@smatch-corp/nestjs-pothos-apollo-driver';
import { PothosModule } from '@smatch-corp/nestjs-pothos';
import { PrismaClient } from '@prisma/client';
import { PrismaCrudGenerator } from './graphql.generator';
import { PrismaModule } from '../Prisma/prisma.module';
import { PrismaService } from '../Prisma/prisma.service';
import { RefundTicketModule } from '../RefundTicket/refundticket.module';
import { Request } from 'express';
import { ResumeModule } from '../Resume/resume.module';
import { ScheduleModule } from '../Schedule/schedule.module';
import { ServiceAndCategoryModule } from '../ServiceAndCategory/serviceandcategory.module';
@@ -32,6 +34,7 @@ import { WorkshopMeetingRoomModule } from '../WorkshopMeetingRoom/workshopmeetin
import { WorkshopModule } from '../Workshop/workshop.module';
import { WorkshopOrganizationModule } from '../WorkshopOrganization/workshoporganization.module';
import { WorkshopSubscriptionModule } from '../WorkshopSubscription/workshopsubscription.module';
import { initContextCache } from '@pothos/core';
@Global()
@Module({
@@ -76,6 +79,9 @@ import { WorkshopSubscriptionModule } from '../WorkshopSubscription/workshopsubs
subscriptions: {
'graphql-ws': true,
},
context: async () => ({
...initContextCache(),
}),
}),
],
providers: [

45
src/Mail/mail.module.ts Normal file
View 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
View 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');
}
}
}

View 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 {}

View 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;
}
}

View File

@@ -42,11 +42,11 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
await this.$connect();
break; // Exit loop if connection is successful
} catch (error) {
if (attempt < 3) {
if (attempt < (parseInt(process.env.PRISMA_MAX_RETRY as string) ?? 3)) {
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 {
this.logger.error(
'Failed to connect to the database after 3 attempts.',

View File

@@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common';
import { Inject, Injectable, Logger } from '@nestjs/common';
import {
Pothos,
PothosRef,
@@ -97,6 +97,31 @@ export class ResumeSchema extends PothosSchema {
@Pothos()
init(): void {
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({
description:
'Retrieve a list of resumes with optional filtering, ordering, and pagination.',

View File

@@ -9,12 +9,13 @@ import { Builder } from '../Graphql/graphql.builder';
import { PrismaService } from '../Prisma/prisma.service';
import { clerkClient } from '@clerk/express';
import { UnauthorizedException } from '@nestjs/common';
import { MailService } from '../Mail/mail.service';
@Injectable()
export class UserSchema extends PothosSchema {
constructor(
@Inject(SchemaBuilderToken) private readonly builder: Builder,
private readonly prisma: PrismaService,
private readonly mailService: MailService,
) {
super();
}
@@ -123,22 +124,26 @@ export class UserSchema extends PothosSchema {
},
}),
me: t.prismaField({
description: 'Retrieve the current user.',
description: 'Retrieve the current user by token.',
type: this.user(),
resolve: async (query, root, args, ctx, info) => {
const sessionCookie = ctx.req.headers.cookie
?.split('; ')
.find((row) => row.startsWith('__session='))
?.split('=')[1];
if (!sessionCookie)
// get session id from X-Session-Id
const sessionId = ctx.req.headers['x-session-id'];
if (!sessionId)
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();
return await this.prisma.user.findUnique({
const user = await this.prisma.user.findUnique({
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';
},
}),
}));
}
}

View File

@@ -1,15 +1,16 @@
import { Module } from '@nestjs/common';
import { ClerkModule } from './Clerk/clerk.module';
import { ConfigModule } from '@nestjs/config';
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';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
ClerkModule,
MailModule,
GraphqlModule,
RestfulModule,
],