|
|
@@ -1,8 +1,11 @@
|
|
|
import { Injectable, Logger } from '@nestjs/common';
|
|
|
import { ConfigService } from '@nestjs/config';
|
|
|
+import * as nodemailer from 'nodemailer';
|
|
|
import { PrismaService } from '../../prisma/prisma.service';
|
|
|
import { CreateBetaApplicationDto } from './dto/create-beta-application.dto';
|
|
|
|
|
|
+type EmailProvider = 'resend' | 'smtp' | 'disabled';
|
|
|
+
|
|
|
@Injectable()
|
|
|
export class BetaApplicationsService {
|
|
|
private readonly logger = new Logger(BetaApplicationsService.name);
|
|
|
@@ -21,16 +24,9 @@ export class BetaApplicationsService {
|
|
|
.replaceAll("'", ''');
|
|
|
}
|
|
|
|
|
|
- private async sendApplicationEmail(input: CreateBetaApplicationDto) {
|
|
|
- const apiKey = this.configService.get<string>('betaApplicationEmail.apiKey');
|
|
|
+ private buildApplicationEmail(input: CreateBetaApplicationDto) {
|
|
|
const to = this.configService.getOrThrow<string>('betaApplicationEmail.to');
|
|
|
const from = this.configService.getOrThrow<string>('betaApplicationEmail.from');
|
|
|
-
|
|
|
- if (!apiKey) {
|
|
|
- this.logger.warn('RESEND_API_KEY is not configured; beta application email was not sent');
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
const fields = [
|
|
|
['姓名', input.name],
|
|
|
['邮箱', input.email],
|
|
|
@@ -48,6 +44,28 @@ export class BetaApplicationsService {
|
|
|
.map(([label, value]) => `<tr><th align="left">${label}</th><td>${this.escapeHtml(value)}</td></tr>`)
|
|
|
.join('')}</table>`;
|
|
|
|
|
|
+ return {
|
|
|
+ from,
|
|
|
+ to,
|
|
|
+ replyTo: input.email.trim().toLowerCase(),
|
|
|
+ subject: `Aitori Stylist 内测申请:${input.name.trim()}`,
|
|
|
+ html,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private async sendWithResend(
|
|
|
+ message: ReturnType<BetaApplicationsService['buildApplicationEmail']>,
|
|
|
+ ) {
|
|
|
+ const apiKey = this.configService.get<string>(
|
|
|
+ 'betaApplicationEmail.resend.apiKey',
|
|
|
+ );
|
|
|
+ if (!apiKey) {
|
|
|
+ this.logger.warn(
|
|
|
+ 'RESEND_API_KEY is not configured; beta application email was not sent',
|
|
|
+ );
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
const response = await fetch('https://api.resend.com/emails', {
|
|
|
method: 'POST',
|
|
|
headers: {
|
|
|
@@ -55,11 +73,11 @@ export class BetaApplicationsService {
|
|
|
'Content-Type': 'application/json',
|
|
|
},
|
|
|
body: JSON.stringify({
|
|
|
- from,
|
|
|
- to: [to],
|
|
|
- reply_to: input.email.trim().toLowerCase(),
|
|
|
- subject: `Aitori Stylist 内测申请:${input.name.trim()}`,
|
|
|
- html,
|
|
|
+ from: message.from,
|
|
|
+ to: [message.to],
|
|
|
+ reply_to: message.replyTo,
|
|
|
+ subject: message.subject,
|
|
|
+ html: message.html,
|
|
|
}),
|
|
|
signal: AbortSignal.timeout(10000),
|
|
|
});
|
|
|
@@ -71,6 +89,55 @@ export class BetaApplicationsService {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+ private async sendWithSmtp(
|
|
|
+ message: ReturnType<BetaApplicationsService['buildApplicationEmail']>,
|
|
|
+ ) {
|
|
|
+ const host = this.configService.getOrThrow<string>(
|
|
|
+ 'betaApplicationEmail.smtp.host',
|
|
|
+ );
|
|
|
+ const port = this.configService.getOrThrow<number>(
|
|
|
+ 'betaApplicationEmail.smtp.port',
|
|
|
+ );
|
|
|
+ const secure = this.configService.getOrThrow<boolean>(
|
|
|
+ 'betaApplicationEmail.smtp.secure',
|
|
|
+ );
|
|
|
+ const user = this.configService.get<string>('betaApplicationEmail.smtp.user');
|
|
|
+ const password = this.configService.get<string>(
|
|
|
+ 'betaApplicationEmail.smtp.password',
|
|
|
+ );
|
|
|
+
|
|
|
+ const transporter = nodemailer.createTransport({
|
|
|
+ host,
|
|
|
+ port,
|
|
|
+ secure,
|
|
|
+ ...(user && password ? { auth: { user, pass: password } } : {}),
|
|
|
+ connectionTimeout: 10000,
|
|
|
+ greetingTimeout: 10000,
|
|
|
+ socketTimeout: 15000,
|
|
|
+ });
|
|
|
+
|
|
|
+ await transporter.sendMail(message);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private async sendApplicationEmail(input: CreateBetaApplicationDto) {
|
|
|
+ const provider = this.configService.getOrThrow<EmailProvider>(
|
|
|
+ 'betaApplicationEmail.provider',
|
|
|
+ );
|
|
|
+
|
|
|
+ if (provider === 'disabled') {
|
|
|
+ this.logger.log(
|
|
|
+ 'Beta application email is disabled; application was stored without sending',
|
|
|
+ );
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const message = this.buildApplicationEmail(input);
|
|
|
+ return provider === 'smtp'
|
|
|
+ ? this.sendWithSmtp(message)
|
|
|
+ : this.sendWithResend(message);
|
|
|
+ }
|
|
|
+
|
|
|
async create(input: CreateBetaApplicationDto) {
|
|
|
const application = await this.prisma.betaApplication.create({
|
|
|
data: {
|