feat(profile): add privacy controls and progressive photo gallery
CI / check (push) Failing after 53s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped

This commit is contained in:
Simo committed 2026-09-11 00:31:29 +02:00
1 parent ba9c61d808
commit 74223984dc
13 files changed
+764 -167

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const state = vi.hoisted(() => ({
id: "42" as string | undefined,
values: null as Record<string, unknown> | null,
fail: false,
}));
vi.mock("@/lib/auth", () => ({
auth: async () => ({ user: { id: state.id } }),
}));
vi.mock("@/lib/rate-limit", () => ({ rateLimit: async () => ({ ok: true }) }));
vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
vi.mock("next/navigation", () => ({
redirect: (path: string) => {
throw new Error(path);
},
}));
vi.mock("@/lib/db", () => ({
db: {
insert: () => ({
values: (values: Record<string, unknown>) => {
state.values = values;
return {
onDuplicateKeyUpdate: async () => {
if (state.fail) throw new Error("offline");
},
};
},
}),
},
}));
import { saveProfilePrivacy } from "./profile-privacy";
describe("save profile privacy", () => {
beforeEach(() => {
state.id = "42";
state.values = null;
state.fail = false;
});
it("uses the session owner even when another user is supplied in the form", async () => {
const form = new FormData();
form.set("userId", "99");
form.set("wallet", "on");
await expect(saveProfilePrivacy(form)).rejects.toThrow(
"/settings?privacy=saved",
);
expect(state.values).toEqual({
userId: 42,
wallet: true,
online: false,
friends: false,
photos: false,
registered: false,
});
});
it("rejects unauthenticated writes", async () => {
state.id = undefined;
await expect(saveProfilePrivacy(new FormData())).rejects.toThrow("/login");
expect(state.values).toBeNull();
});
it("never reports success after a database failure", async () => {
state.fail = true;
await expect(saveProfilePrivacy(new FormData())).rejects.toThrow(
"/settings?privacy=error",
);
});
});
+34
View File
@@ -0,0 +1,34 @@
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { WebsiteProfilePrivacy } from "@/db/profile-privacy";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
import { rateLimit } from "@/lib/rate-limit";
export async function saveProfilePrivacy(formData: FormData): Promise<void> {
const session = await auth();
const userId = Number(session?.user?.id);
if (!Number.isSafeInteger(userId) || userId <= 0) redirect("/login");
let outcome = "error";
try {
if ((await rateLimit(`profile-privacy:${userId}`, 10, 60_000)).ok) {
const values = {
wallet: formData.get("wallet") === "on",
online: formData.get("online") === "on",
friends: formData.get("friends") === "on",
photos: formData.get("photos") === "on",
registered: formData.get("registered") === "on",
};
await db
.insert(WebsiteProfilePrivacy)
.values({ userId, ...values })
.onDuplicateKeyUpdate({ set: values });
revalidatePath("/u/[username]", "page");
revalidatePath("/settings");
outcome = "saved";
}
} catch {
outcome = "error";
}
redirect(`/settings?privacy=${outcome}#profile-privacy`);
}