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(); if (!intro && (status || activityType || whyJoin || region || availableTime)) { const extra = [status && `状态:${status}`, activityType && `活动偏好:${activityType}`, whyJoin && `原因:${whyJoin}`, region && `区域:${region}`, availableTime && `时间:${availableTime}`].filter(Boolean).join(" | "); intro = extra; } const isDigitalNomad = session === "digital-nomad"; const contact = wechatOrPhone || wechat || phone; if (!nickname || !intro || !session || !agreeRules || !xiaohongshuUrl) { return NextResponse.json( { ok: false, error: "请填写必填项(含小红书主页地址、自我介绍)" }, { status: 400 } ); } if (!contact) { return NextResponse.json( { ok: false, error: "请填写微信号或手机号" }, { status: 400 } ); } if (isDigitalNomad && (!education || !graduationYear)) { return NextResponse.json( { ok: false, error: "请选择学历和毕业时间" }, { status: 400 } ); } const userId = getUserIdFromCookie(request) || generateAnonId(); const EDUCATION_GRAD_AGE: Record = { "高中及以下": 18, 大专: 21, 本科: 22, 硕士: 25, 博士: 28, }; const gradAge = EDUCATION_GRAD_AGE[education] ?? 22; const estimatedAge = new Date().getFullYear() - parseInt(graduationYear, 10) + gradAge; const ageRangeForRecord = isDigitalNomad && education && graduationYear ? `推算约${estimatedAge}岁(学历${education}+${graduationYear}毕业)` : ageRange; const record = { user_id: userId, nickname, occupation: isDigitalNomad ? profession : city, reason: intro, wechatId: wechat || wechatOrPhone, xiaohongshu_url: xiaohongshuUrl, gender, education: education, gradYear: graduationYear, phone: phone || wechatOrPhone, single: "", media: xiaohongshuAvatarUrl, type: session, order_id: "", amount: 0, age_range: ageRangeForRecord, first_time: firstTime, }; 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 url = `${base}/api/collections/${pb.joinCollection}/records`; const reasonParts = [record.reason, record.age_range, record.first_time]; if (record.xiaohongshu_url) reasonParts.push(`小红书:${record.xiaohongshu_url}`); const body: Record = { user_id: record.user_id, nickname: record.nickname, occupation: record.occupation ?? "", reason: reasonParts.filter(Boolean).join(" | "), wechatId: record.wechatId ?? "", gender: record.gender ?? "", education: record.education ?? "", gradYear: record.gradYear ?? "", phone: record.phone ?? "", single: record.single ?? "", media: record.media ?? "", type: record.type ?? "", order_id: record.order_id ?? "", amount: record.amount ?? 0, }; 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 } ); } }