CI / test (push) Successful in 1m4s
A creator deletes a product: it's no longer listed and nobody new can get it, while people who have it keep it (its page, files and library entries stay, and rooms keep showing it), as old revisions stay available. Moderation removes one everywhere, with a reason: its page and files answer 410, and it leaves libraries and rooms. Both are on the product's page and in the audit log. Co-Authored-By: Claude Opus 5.5 <[email protected]>
97 lines
3.1 KiB
TypeScript
97 lines
3.1 KiB
TypeScript
"use server";
|
|
import { revalidatePath } from "next/cache";
|
|
import { redirect } from "next/navigation";
|
|
import { api, ApiError, unwrap } from "@/lib/api";
|
|
import { attempt, field, type ActionState } from "@/lib/actions";
|
|
|
|
const done = (id: string) => {
|
|
revalidatePath(`/products/${id}`);
|
|
revalidatePath("/library");
|
|
};
|
|
|
|
/** The person's library and a product: get it, remove it, stay on a revision or follow the latest. */
|
|
export async function libraryAction(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const id = field(form, "id");
|
|
return attempt(async () => {
|
|
const lib = api.library({ id });
|
|
switch (field(form, "intent")) {
|
|
case "get":
|
|
unwrap(await lib.put());
|
|
break;
|
|
case "remove":
|
|
unwrap(await lib.delete());
|
|
break;
|
|
case "stay":
|
|
unwrap(await lib.patch({ stayOn: Number(field(form, "revision")) }));
|
|
break;
|
|
case "follow":
|
|
unwrap(await lib.patch({ stayOn: null }));
|
|
break;
|
|
}
|
|
done(id);
|
|
});
|
|
}
|
|
|
|
/** Its creator deletes the product. */
|
|
export async function deleteProduct(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const id = field(form, "id");
|
|
const state = await attempt(async () => {
|
|
unwrap(await api.products({ id }).delete());
|
|
done(id);
|
|
revalidatePath("/products");
|
|
});
|
|
if (state?.error) return state;
|
|
redirect(`/products/${id}`);
|
|
}
|
|
|
|
/** Moderation removes the product. */
|
|
export async function removeProduct(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const id = field(form, "id");
|
|
const state = await attempt(async () => {
|
|
unwrap(await api.products({ id }).remove.post({ reason: field(form, "reason") }));
|
|
done(id);
|
|
revalidatePath("/products");
|
|
});
|
|
if (state?.error) return state;
|
|
redirect("/products");
|
|
}
|
|
|
|
export async function retract(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const id = field(form, "id");
|
|
return attempt(async () => {
|
|
unwrap(await api.products({ id }).revisions({ n: Number(field(form, "revision")) }).retract.post());
|
|
done(id);
|
|
});
|
|
}
|
|
|
|
export async function saveProduct(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const id = field(form, "id");
|
|
const rating = field(form, "rating");
|
|
const state = await attempt(async () => {
|
|
unwrap(
|
|
await api.products({ id }).patch({
|
|
name: field(form, "name"),
|
|
description: typeof form.get("description") === "string" ? String(form.get("description")) : "",
|
|
...(rating === "adult" ? { rating: "adult" as const } : {}),
|
|
}),
|
|
);
|
|
done(id);
|
|
revalidatePath("/products");
|
|
});
|
|
if (state?.error) return state;
|
|
redirect(`/products/${id}`);
|
|
}
|
|
|
|
/** An upload token and the catalog's upload address, for the browser to send the file there itself. */
|
|
export async function startUpload(
|
|
catalog: string,
|
|
product?: string,
|
|
): Promise<{ token: string; url: string } | { error: string }> {
|
|
try {
|
|
return unwrap(await api.products.upload.post({ catalog, ...(product ? { product } : {}) }));
|
|
} catch (e) {
|
|
if (e instanceof ApiError) return { error: e.message };
|
|
throw e;
|
|
}
|
|
}
|