feat(web): add persistent beam avatars
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createServerSupabaseClient } from "@/lib/supabase/server";
|
||||
import {
|
||||
beamAvatarFromProfile,
|
||||
beamAvatarPatchSchema,
|
||||
createBeamAvatarProfilePatch,
|
||||
} from "@/lib/beam-avatar";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
function selfHostedOnly() {
|
||||
return process.env.AUTH_PROVIDER?.trim() === "self-hosted";
|
||||
}
|
||||
|
||||
export async function PATCH(request: Request) {
|
||||
if (!selfHostedOnly()) {
|
||||
return NextResponse.json({ error: "头像编辑仅在 staging 开放" }, { status: 404 });
|
||||
}
|
||||
|
||||
try {
|
||||
const client = await createServerSupabaseClient();
|
||||
const { data: { user }, error: authError } = await client.auth.getUser();
|
||||
if (authError || !user) {
|
||||
return NextResponse.json({ error: "请先登录" }, { status: 401 });
|
||||
}
|
||||
|
||||
const parsed = beamAvatarPatchSchema.safeParse(
|
||||
await request.json().catch(() => null),
|
||||
);
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({
|
||||
error: "头像设置格式不正确",
|
||||
details: parsed.error.flatten(),
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const values = {
|
||||
...createBeamAvatarProfilePatch(parsed.data),
|
||||
updated_at: new Date().toISOString(),
|
||||
};
|
||||
const { data: profile, error } = await client
|
||||
.from("profiles")
|
||||
.update(values)
|
||||
.eq("id", user.id)
|
||||
.select("avatar_seed,avatar_palette,avatar_renderer_version")
|
||||
.maybeSingle();
|
||||
|
||||
if (error || !profile) {
|
||||
return NextResponse.json({ error: "暂时无法保存头像" }, { status: 500 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ avatar: beamAvatarFromProfile(profile) });
|
||||
} catch {
|
||||
return NextResponse.json({ error: "头像服务暂时不可用" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
applyAccountProfileConcurrencyGuards,
|
||||
resolveAccountBirthTimeApplicationPatch,
|
||||
} from "@/lib/account-profile-patch";
|
||||
import { optionalBeamAvatarFromProfile } from "@/lib/beam-avatar";
|
||||
import { createAdminSupabaseClient, isAdminUser } from "@/lib/supabase/admin";
|
||||
import {
|
||||
isSupabaseConfigurationError,
|
||||
@@ -110,6 +111,23 @@ export async function GET() {
|
||||
if (profileError || !profile) {
|
||||
return NextResponse.json({ error: "暂时无法读取账户余额" }, { status: 500 });
|
||||
}
|
||||
|
||||
let avatar = null;
|
||||
if (process.env.AUTH_PROVIDER?.trim() === "self-hosted") {
|
||||
const { data: avatarProfile, error: avatarError } = await supabase
|
||||
.from("profiles")
|
||||
.select("avatar_seed,avatar_palette,avatar_renderer_version")
|
||||
.eq("id", userId)
|
||||
.single();
|
||||
if (avatarError || !avatarProfile) {
|
||||
return NextResponse.json({ error: "暂时无法读取头像" }, { status: 500 });
|
||||
}
|
||||
avatar = optionalBeamAvatarFromProfile(avatarProfile);
|
||||
if (!avatar) {
|
||||
return NextResponse.json({ error: "头像设置无效" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
const rectificationCase = resolveAccountRectificationV4Case(
|
||||
Array.isArray(rectificationV4CaseRows) ? rectificationV4CaseRows : [],
|
||||
) ?? resolveAccountRectificationCase(
|
||||
@@ -134,6 +152,7 @@ export async function GET() {
|
||||
|
||||
return NextResponse.json({
|
||||
user: { id: user.id, email: user.email ?? null },
|
||||
avatar,
|
||||
credits: profile.credits,
|
||||
isAdmin,
|
||||
adminUrl,
|
||||
|
||||
Reference in New Issue
Block a user