route.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { NextRequest, NextResponse } from 'next/server';
  2. import { findUserByEmail, createVerificationToken } from '@/lib/user-service';
  3. import { sendEmail, generateVerificationEmailHtml } from '@/lib/email';
  4. import { isValidEmail } from '@/lib/auth-utils';
  5. import { db } from '@/lib/db';
  6. import { verificationTokens } from '@/lib/schema';
  7. import { and, eq, gt } from 'drizzle-orm';
  8. // 限制重发频率(5分钟内只能发送一次)
  9. const RESEND_COOLDOWN_MINUTES = 5;
  10. export async function POST(request: NextRequest) {
  11. let locale = 'zh'; // 默认语言
  12. try {
  13. const { email, locale: requestLocale = 'zh' } = await request.json();
  14. locale = requestLocale;
  15. // 验证输入
  16. if (!email) {
  17. return NextResponse.json(
  18. { error: locale === 'zh' ? '邮箱地址是必填项' : 'Email address is required' },
  19. { status: 400 }
  20. );
  21. }
  22. if (!isValidEmail(email)) {
  23. return NextResponse.json(
  24. { error: locale === 'zh' ? '邮箱格式不正确' : 'Invalid email format' },
  25. { status: 400 }
  26. );
  27. }
  28. // 查找用户
  29. const user = await findUserByEmail(email.toLowerCase().trim());
  30. if (!user) {
  31. return NextResponse.json(
  32. { error: locale === 'zh' ? '用户不存在' : 'User not found' },
  33. { status: 404 }
  34. );
  35. }
  36. // 检查邮箱是否已验证
  37. if (user.isEmailVerified) {
  38. return NextResponse.json(
  39. { error: locale === 'zh' ? '邮箱已经验证过了' : 'Email is already verified' },
  40. { status: 400 }
  41. );
  42. }
  43. // 检查是否在冷却期内(防止重复发送)
  44. const cooldownTime = new Date(Date.now() - RESEND_COOLDOWN_MINUTES * 60 * 1000);
  45. const recentToken = await db
  46. .select()
  47. .from(verificationTokens)
  48. .where(
  49. and(
  50. eq(verificationTokens.email, user.email),
  51. eq(verificationTokens.type, 'email_verification'),
  52. gt(verificationTokens.createdAt, cooldownTime)
  53. )
  54. )
  55. .limit(1);
  56. if (recentToken.length > 0) {
  57. return NextResponse.json(
  58. {
  59. error: locale === 'zh'
  60. ? `请等待${RESEND_COOLDOWN_MINUTES}分钟后再重新发送验证邮件`
  61. : `Please wait ${RESEND_COOLDOWN_MINUTES} minutes before resending verification email`
  62. },
  63. { status: 429 }
  64. );
  65. }
  66. // 生成新的验证令牌
  67. const verificationToken = await createVerificationToken(
  68. user.email,
  69. 'email_verification',
  70. 24
  71. );
  72. if (!verificationToken) {
  73. return NextResponse.json(
  74. { error: locale === 'zh' ? '生成验证令牌失败' : 'Failed to generate verification token' },
  75. { status: 500 }
  76. );
  77. }
  78. // 发送验证邮件
  79. const verificationUrl = `${process.env.NEXTAUTH_URL}/${locale}/auth/verify-email?token=${verificationToken}`;
  80. const emailHtml = generateVerificationEmailHtml(verificationUrl, locale);
  81. const emailResult = await sendEmail({
  82. to: user.email,
  83. subject: locale === 'zh' ? 'Aiartools - 邮箱验证' : 'Aiartools - Email Verification',
  84. html: emailHtml,
  85. });
  86. if (!emailResult.success) {
  87. console.error('验证邮件发送失败:', emailResult.error);
  88. return NextResponse.json(
  89. { error: locale === 'zh' ? '邮件发送失败,请稍后重试' : 'Failed to send email, please try again later' },
  90. { status: 500 }
  91. );
  92. }
  93. return NextResponse.json({
  94. message: locale === 'zh'
  95. ? '验证邮件已重新发送,请查收邮箱'
  96. : 'Verification email has been resent, please check your inbox',
  97. cooldownMinutes: RESEND_COOLDOWN_MINUTES
  98. });
  99. } catch (error) {
  100. console.error('重发验证邮件错误:', error);
  101. return NextResponse.json(
  102. { error: locale === 'zh' ? '服务器内部错误' : 'Internal server error' },
  103. { status: 500 }
  104. );
  105. }
  106. }
  107. export async function GET() {
  108. return NextResponse.json({
  109. message: '重发邮箱验证API',
  110. version: '1.0.0',
  111. cooldown_minutes: RESEND_COOLDOWN_MINUTES,
  112. usage: 'POST to /api/auth/resend-verification with email and locale'
  113. });
  114. }