import { NextRequest, NextResponse } from "next/server"; import { postSalonApi } from "@/app/lib/salonApi"; import { getAdminToken } from "@/app/lib/pocketbase/admin"; import { getPocketBaseConfig } from "@/config/services.config"; import { COOKIE_NAME } from "@/app/lib/auth-cookie"; function getUserIdFromCookie(request: NextRequest): string | null { const cookie = request.cookies.get(COOKIE_NAME)?.value; if (!cookie) return null; try { const payload = JSON.parse(Buffer.from(cookie, "base64url").toString("utf-8")); return payload?.userId ?? null; } catch { return null; } } function generateAnonId(): string { return "anon_" + crypto.randomUUID().replace(/-/g, ""); } export async function POST(request: NextRequest) { try { const body = await request.json(); const formData = body && typeof body === "object" ? body : {}; const nickname = String(formData.nickname || "").trim(); const city = String(formData.city || "").trim(); const session = String(formData.session || "").trim(); const ageRange = String(formData.age_range || "").trim(); const wechatOrPhone = String(formData.wechat_or_phone || formData.wechat || "").trim(); const wechat = String(formData.wechat || "").trim(); const phone = String(formData.phone || "").trim(); const profession = String(formData.profession || "").trim(); const education = String(formData.education || "").trim(); const graduationYear = String(formData.graduation_year || formData.gradYear || "").trim(); const xiaohongshuUrl = String(formData.xiaohongshu_url || "").trim(); const xiaohongshuAvatarUrl = String(formData.xiaohongshu_avatar_url || formData.media || "").trim(); const gender = String(formData.gender || "").trim() || "\u65e0"; let intro = String(formData.intro || "").trim(); const firstTime = String(formData.first_time || "").trim(); const agreeRules = !!formData.agree_rules; const status = String(formData.status || "").trim(); const activityType = String(formData.activity_type || "").trim(); const whyJoin = String(formData.why_join || "").trim(); const region = String(formData.region || "").trim(); const availableTime = String(formData.available_time || "").trim(); const isVolunteer = !!formData.is_volunteer; const qualify = !!formData.qualify; // 女性且小红书帖子>10 时自动通过审核 /** reason 仅存自我介绍,不合并其他字段 */ const contact = wechatOrPhone || wechat || phone; if (!nickname || !session || !agreeRules) { return NextResponse.json( { ok: false, error: "请填写必填项" }, { status: 400 } ); } if (!contact) { return NextResponse.json( { ok: false, error: "请填写微信号" }, { status: 400 } ); } const userId = getUserIdFromCookie(request) || generateAnonId(); const ageRangeForRecord = ageRange || ""; /** occupation: 职业/城市,reason: 仅自我介绍 */ const occupationValue = profession || city; const record: Record = { user_id: userId, nickname, occupation: occupationValue, reason: intro, wechatId: wechat || wechatOrPhone, xiaohongshu_url: xiaohongshuUrl, gender, education: education, gradYear: graduationYear, single: "", media: xiaohongshuAvatarUrl, type: session, order_id: "", amount: 0, age_range: ageRangeForRecord, first_time: firstTime, is_volunteer: isVolunteer, qualify, }; if (phone.trim()) { record.phone = phone.trim(); } try { const { status, data } = await postSalonApi(request, "/api/salon/join", record); if (status >= 500) { const payload = typeof data === "object" && data !== null ? (data as Record) : {}; return NextResponse.json( { ok: false, error: (payload.detail as string) || (payload.error as string) || "提交失败" }, { status: 503 } ); } if (status >= 400) { const payload = typeof data === "object" && data !== null ? (data as Record) : {}; const errMsg = (payload.error as string) || (payload.detail as string) || "提交失败"; if (status === 401) { return tryPocketBaseDirect(record, errMsg); } return NextResponse.json({ ok: false, error: errMsg }, { status }); } return NextResponse.json({ ok: true }); } catch { return tryPocketBaseDirect(record, "服务暂时不可用"); } } catch (e) { const msg = e instanceof Error ? e.message : String(e); console.error("Join API error:", e); return NextResponse.json( { ok: false, error: `提交失败: ${msg}` }, { status: 500 } ); } } async function tryPocketBaseDirect( record: Record, fallbackError: string ): Promise { const token = await getAdminToken(); if (!token) { return NextResponse.json({ ok: false, error: fallbackError }, { status: 503 }); } const pb = getPocketBaseConfig(); const base = pb.url.replace(/\/$/, ""); const wechatId = String(record.wechatId ?? "").trim(); if (wechatId) { const escaped = wechatId.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); const filter = encodeURIComponent(`wechatId = "${escaped}"`); const checkRes = await fetch( `${base}/api/collections/${pb.joinCollection}/records?filter=${filter}&perPage=1`, { headers: { Authorization: `Bearer ${token}` }, cache: "no-store", } ); if (checkRes.ok) { const checkData = await checkRes.json(); if (Array.isArray((checkData as { items?: unknown[] }).items) && (checkData as { items: unknown[] }).items.length > 0) { return NextResponse.json( { ok: false, error: "该微信号已报名,请勿重复提交" }, { status: 400 } ); } } } const url = `${base}/api/collections/${pb.joinCollection}/records`; const qualify = !!(record.qualify ?? false); const body: Record = { user_id: record.user_id, nickname: record.nickname, occupation: record.occupation ?? "", reason: record.reason ?? "", wechatId: record.wechatId ?? "", gender: record.gender ?? "", education: record.education ?? "", gradYear: record.gradYear ?? "", single: record.single ?? "", media: record.media ?? "", type: record.type ?? "", order_id: record.order_id ?? "", amount: record.amount ?? 0, xiaohongshu_url: record.xiaohongshu_url ?? "", is_volunteer: record.is_volunteer ?? false, is_approved: qualify, age_range: record.age_range ?? "", first_time: record.first_time ?? "", }; if (record.phone != null && String(record.phone).trim()) { body.phone = record.phone; } try { const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify(body), }); if (!res.ok) { const errData = await res.json().catch(() => ({})); const msg = (errData as { message?: string }).message || fallbackError; return NextResponse.json({ ok: false, error: msg }, { status: res.status }); } return NextResponse.json({ ok: true }); } catch (e) { console.error("PocketBase direct write error:", e); return NextResponse.json( { ok: false, error: fallbackError }, { status: 503 } ); } }