Initial miaojingAI project with image resolution guard

This commit is contained in:
FengLee
2026-05-09 11:32:34 +08:00
commit d499020d4e
264 changed files with 54160 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
import { NextRequest, NextResponse } from 'next/server';
import { ensureEmailSchema, getRequestBaseUrl, isValidEmail, normalizeEmail, sendTemplatedEmail, verifyEmailCode } from '@/lib/email-service';
import { getDbClient } from '@/storage/database/local-db';
export const runtime = 'nodejs';
function passwordStrongEnough(value: string): boolean {
return value.length >= 8 && /[a-zA-Z]/.test(value) && /\d/.test(value);
}
function friendlyError(error: unknown) {
return error instanceof Error ? error.message : '密码重置失败,请稍后再试';
}
export async function POST(request: NextRequest) {
const client = await getDbClient();
try {
await ensureEmailSchema(client);
const body = await request.json();
const email = normalizeEmail(body.email);
const code = typeof body.code === 'string' ? body.code.trim() : '';
const newPassword = typeof body.newPassword === 'string' ? body.newPassword : '';
if (!isValidEmail(email) || !/^[a-z0-9]{4,10}$/i.test(code)) {
return NextResponse.json({ error: '邮箱或验证码格式不正确' }, { status: 400 });
}
if (!passwordStrongEnough(newPassword)) {
return NextResponse.json({ error: '新密码至少 8 位,并同时包含字母和数字' }, { status: 400 });
}
await client.query('BEGIN');
await verifyEmailCode(client, { email, type: 'reset_password', code });
const user = await client.query(
`SELECT p.id, p.nickname
FROM profiles p
JOIN auth.users u ON u.id = p.id
WHERE LOWER(p.email) = LOWER($1) AND p.email_verified = true
LIMIT 1`,
[email],
);
if (user.rows.length === 0) {
await client.query('ROLLBACK');
return NextResponse.json({ error: '该邮箱尚未绑定或未完成验证' }, { status: 400 });
}
await client.query(
`UPDATE auth.users
SET password_hash = crypt($1, gen_salt('bf'))
WHERE id = $2`,
[newPassword, user.rows[0].id],
);
await client.query('COMMIT');
await sendTemplatedEmail(client, {
to: email,
type: 'password_reset_success',
subject: '【妙境】密码已重置',
title: '密码重置成功',
intro: '你的妙境账号密码已成功重置。请使用新密码重新登录。',
note: '若非本人操作,请立即联系管理员并检查账号安全。',
assetBaseUrl: getRequestBaseUrl(request) || undefined,
}).catch(() => undefined);
return NextResponse.json({ success: true, message: '密码已重置,请重新登录' });
} catch (error) {
await client.query('ROLLBACK').catch(() => undefined);
return NextResponse.json({ error: friendlyError(error) }, { status: 400 });
} finally {
client.release();
}
}

View File

@@ -0,0 +1,62 @@
import { NextRequest, NextResponse } from 'next/server';
import { requireAdmin } from '@/lib/admin-auth';
import { getRequestBaseUrl, isValidEmail, normalizeEmail, sendTemplatedEmail, type EmailMessageType } from '@/lib/email-service';
import { getDbClient } from '@/storage/database/local-db';
export const runtime = 'nodejs';
const ALLOWED_TYPES: EmailMessageType[] = [
'register_success',
'email_verified',
'password_reset_success',
'security_login',
'announcement',
'order',
'business',
];
export async function POST(request: NextRequest) {
const adminError = await requireAdmin(request);
if (adminError) return adminError;
const client = await getDbClient();
try {
const body = await request.json();
const to = normalizeEmail(body.to);
const type = ALLOWED_TYPES.includes(body.type) ? body.type : 'business';
const title = typeof body.title === 'string' ? body.title.trim().slice(0, 120) : '';
const bodyText = typeof body.body === 'string' ? body.body.trim().slice(0, 4000) : '';
const buttonText = typeof body.buttonText === 'string' ? body.buttonText.trim().slice(0, 40) : '';
const buttonUrl = typeof body.buttonUrl === 'string' ? body.buttonUrl.trim().slice(0, 500) : '';
if (!isValidEmail(to)) {
return NextResponse.json({ error: '请输入正确的收件邮箱' }, { status: 400 });
}
if (!title || !bodyText) {
return NextResponse.json({ error: '请填写邮件标题和正文' }, { status: 400 });
}
if (buttonUrl && !/^https?:\/\/[^\s"'<>]+$/i.test(buttonUrl)) {
return NextResponse.json({ error: '按钮链接必须是 HTTP(S) 地址' }, { status: 400 });
}
await sendTemplatedEmail(client, {
to,
type,
subject: `【妙境】${title}`,
title,
body: bodyText,
buttonText: buttonText || undefined,
buttonUrl: buttonUrl || undefined,
note: '这是一封系统通知邮件,请勿直接回复。',
ipAddress: 'admin',
assetBaseUrl: getRequestBaseUrl(request) || undefined,
});
return NextResponse.json({ success: true, message: '邮件已发送' });
} catch (error) {
const message = error instanceof Error ? error.message : '邮件发送失败';
return NextResponse.json({ error: message }, { status: 400 });
} finally {
client.release();
}
}

View File

@@ -0,0 +1,47 @@
import { NextRequest, NextResponse } from 'next/server';
import { ensureEmailSchema, isValidEmail, normalizeEmail, sendVerificationCode } from '@/lib/email-service';
import { getDbClient } from '@/storage/database/local-db';
import { getAuthenticatedUserId } from '@/lib/session-auth';
export const runtime = 'nodejs';
function friendlyError(error: unknown) {
return error instanceof Error ? error.message : '验证码发送失败,请稍后再试';
}
export async function POST(request: NextRequest) {
const userId = await getAuthenticatedUserId(request);
if (!userId) {
return NextResponse.json({ error: '请先登录后再验证邮箱' }, { status: 401 });
}
const client = await getDbClient();
try {
await ensureEmailSchema(client);
const body = await request.json();
const email = normalizeEmail(body.email);
if (!isValidEmail(email)) {
return NextResponse.json({ error: '请输入正确的邮箱地址' }, { status: 400 });
}
const user = await client.query('SELECT id, email FROM profiles WHERE id = $1 LIMIT 1', [userId]);
if (user.rows.length === 0) {
return NextResponse.json({ error: '账号不存在,请重新登录' }, { status: 404 });
}
const duplicate = await client.query(
'SELECT id FROM profiles WHERE LOWER(email) = LOWER($1) AND id <> $2 LIMIT 1',
[email, userId],
);
if (duplicate.rows.length > 0) {
return NextResponse.json({ error: '该邮箱已被其他账号绑定' }, { status: 400 });
}
const result = await sendVerificationCode(client, request, { email, type: 'verify_email', userId });
return NextResponse.json({ ...result, message: '验证码已发送,请查收邮箱' });
} catch (error) {
return NextResponse.json({ error: friendlyError(error) }, { status: 400 });
} finally {
client.release();
}
}

View File

@@ -0,0 +1,36 @@
import { NextRequest, NextResponse } from 'next/server';
import { sendVerificationCode, normalizeEmail, isValidEmail } from '@/lib/email-service';
import { getDbClient } from '@/storage/database/local-db';
export const runtime = 'nodejs';
function friendlyError(error: unknown) {
return error instanceof Error ? error.message : '验证码发送失败,请稍后再试';
}
export async function POST(request: NextRequest) {
const client = await getDbClient();
try {
const body = await request.json();
const email = normalizeEmail(body.email);
if (!isValidEmail(email)) {
return NextResponse.json({ error: '请输入正确的邮箱地址' }, { status: 400 });
}
const existing = await client.query(
'SELECT id FROM profiles WHERE LOWER(email) = LOWER($1) LIMIT 1',
[email],
);
if (existing.rows.length > 0) {
return NextResponse.json({ error: '该邮箱已注册,请直接登录' }, { status: 400 });
}
const result = await sendVerificationCode(client, request, { email, type: 'register' });
return NextResponse.json({ ...result, message: '验证码已发送,请查收邮箱' });
} catch (error) {
return NextResponse.json({ error: friendlyError(error) }, { status: 400 });
} finally {
client.release();
}
}

View File

@@ -0,0 +1,47 @@
import { NextRequest, NextResponse } from 'next/server';
import { ensureEmailSchema, isValidEmail, normalizeEmail, sendVerificationCode } from '@/lib/email-service';
import { getDbClient } from '@/storage/database/local-db';
export const runtime = 'nodejs';
export async function POST(request: NextRequest) {
const client = await getDbClient();
try {
await ensureEmailSchema(client);
const body = await request.json();
const email = normalizeEmail(body.email);
if (!isValidEmail(email)) {
return NextResponse.json({ error: '请输入正确的邮箱地址' }, { status: 400 });
}
const user = await client.query(
`SELECT p.id
FROM profiles p
JOIN auth.users u ON u.id = p.id
WHERE LOWER(p.email) = LOWER($1) AND p.email_verified = true AND u.password_hash IS NOT NULL
LIMIT 1`,
[email],
);
if (user.rows.length > 0) {
try {
await sendVerificationCode(client, request, {
email,
type: 'reset_password',
userId: user.rows[0].id,
});
} catch (error) {
const message = error instanceof Error ? error.message : '验证码发送失败,请稍后再试';
return NextResponse.json({ error: message }, { status: 400 });
}
}
return NextResponse.json({
success: true,
cooldown: 60,
message: '如果该邮箱已绑定并验证,我们已发送重置验证码',
});
} finally {
client.release();
}
}

View File

@@ -0,0 +1,73 @@
import { NextRequest, NextResponse } from 'next/server';
import { ensureEmailSchema, getRequestBaseUrl, isValidEmail, normalizeEmail, sendTemplatedEmail, verifyEmailCode } from '@/lib/email-service';
import { getDbClient } from '@/storage/database/local-db';
import { getAuthenticatedUserId } from '@/lib/session-auth';
export const runtime = 'nodejs';
function friendlyError(error: unknown) {
return error instanceof Error ? error.message : '邮箱验证失败,请稍后再试';
}
export async function POST(request: NextRequest) {
const userId = await getAuthenticatedUserId(request);
if (!userId) {
return NextResponse.json({ error: '请先登录后再验证邮箱' }, { status: 401 });
}
const client = await getDbClient();
try {
await ensureEmailSchema(client);
const body = await request.json();
const email = normalizeEmail(body.email);
const code = typeof body.code === 'string' ? body.code.trim() : '';
if (!isValidEmail(email) || !/^[a-z0-9]{4,10}$/i.test(code)) {
return NextResponse.json({ error: '邮箱或验证码格式不正确' }, { status: 400 });
}
await client.query('BEGIN');
await verifyEmailCode(client, { email, type: 'verify_email', code });
const duplicate = await client.query(
'SELECT id FROM profiles WHERE LOWER(email) = LOWER($1) AND id <> $2 LIMIT 1',
[email, userId],
);
if (duplicate.rows.length > 0) {
await client.query('ROLLBACK');
return NextResponse.json({ error: '该邮箱已被其他账号绑定' }, { status: 400 });
}
const domain = email.includes('@') ? email.split('@')[1] : null;
const profile = await client.query(
`UPDATE profiles
SET email = $1,
email_verified = true,
email_verified_at = NOW(),
email_bound_at = COALESCE(email_bound_at, NOW()),
email_sender_domain = $2,
updated_at = NOW()
WHERE id = $3
RETURNING id, email, nickname, phone, role, membership_tier, credits_balance, daily_quota_used, daily_quota_limit, avatar_url, created_at, email_verified, email_verified_at, email_bound_at`,
[email, domain, userId],
);
await client.query('UPDATE auth.users SET email = $1 WHERE id = $2', [email, userId]);
await client.query('COMMIT');
await sendTemplatedEmail(client, {
to: email,
type: 'email_verified',
subject: '【妙境】邮箱验证成功',
title: '邮箱验证成功',
intro: '你的账号邮箱已完成验证,后续可用于找回密码和安全通知。',
note: '若非本人操作,请尽快修改账号密码。',
assetBaseUrl: getRequestBaseUrl(request) || undefined,
}).catch(() => undefined);
return NextResponse.json({ success: true, profile: profile.rows[0], message: '邮箱验证成功' });
} catch (error) {
await client.query('ROLLBACK').catch(() => undefined);
return NextResponse.json({ error: friendlyError(error) }, { status: 400 });
} finally {
client.release();
}
}