Initial commit
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
"use server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { rawDb, uniqueSlug } from "@/lib/db/client";
|
||||
|
||||
type EntityTable = "actresses" | "studios" | "series";
|
||||
|
||||
function createEntity(table: EntityTable, name: string): { id: number; slug: string } | null {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) return null;
|
||||
const existing = rawDb.prepare(`SELECT id, slug FROM ${table} WHERE name = ?`).get(trimmed) as
|
||||
| { id: number; slug: string }
|
||||
| undefined;
|
||||
if (existing) return existing;
|
||||
const slug = uniqueSlug(rawDb, table, trimmed);
|
||||
const row = rawDb.prepare(`INSERT INTO ${table} (name, slug) VALUES (?, ?) RETURNING id`).get(trimmed, slug) as {
|
||||
id: number;
|
||||
};
|
||||
return { id: row.id, slug };
|
||||
}
|
||||
|
||||
export async function createActressAction(formData: FormData) {
|
||||
createEntity("actresses", String(formData.get("name") ?? ""));
|
||||
revalidatePath("/actress");
|
||||
}
|
||||
|
||||
export async function createStudioAction(formData: FormData) {
|
||||
const created = createEntity("studios", String(formData.get("name") ?? ""));
|
||||
revalidatePath("/studios");
|
||||
if (created) redirect(`/studios/${created.slug}`);
|
||||
}
|
||||
|
||||
export async function createSeriesAction(formData: FormData) {
|
||||
const created = createEntity("series", String(formData.get("name") ?? ""));
|
||||
revalidatePath("/series");
|
||||
if (created) redirect(`/series/${created.slug}`);
|
||||
}
|
||||
|
||||
/** Deletes a studio. Any covers referencing it have studio_id set to NULL. */
|
||||
export async function deleteStudio(id: number) {
|
||||
rawDb.prepare(`UPDATE images SET studio_id = NULL WHERE studio_id = ?`).run(id);
|
||||
rawDb.prepare(`DELETE FROM studios WHERE id = ?`).run(id);
|
||||
revalidatePath("/studios");
|
||||
revalidatePath("/");
|
||||
redirect("/studios");
|
||||
}
|
||||
|
||||
/** Deletes a series. Any covers referencing it have series_id set to NULL. */
|
||||
export async function deleteSeries(id: number) {
|
||||
rawDb.prepare(`UPDATE images SET series_id = NULL WHERE series_id = ?`).run(id);
|
||||
rawDb.prepare(`DELETE FROM series WHERE id = ?`).run(id);
|
||||
revalidatePath("/series");
|
||||
revalidatePath("/");
|
||||
redirect("/series");
|
||||
}
|
||||
|
||||
/** Rename a studio. Returns the new slug (which may be re-uniquified) or null. */
|
||||
export async function renameStudio(id: number, name: string): Promise<{ slug: string } | null> {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) return null;
|
||||
const row = rawDb.prepare(`SELECT name, slug FROM studios WHERE id = ?`).get(id) as { name: string; slug: string } | undefined;
|
||||
if (!row) return null;
|
||||
if (trimmed === row.name) return { slug: row.slug };
|
||||
const slug = uniqueSlug(rawDb, "studios", trimmed, id);
|
||||
rawDb.prepare(`UPDATE studios SET name = ?, slug = ? WHERE id = ?`).run(trimmed, slug, id);
|
||||
revalidatePath("/studios");
|
||||
revalidatePath(`/studios/${row.slug}`);
|
||||
if (slug !== row.slug) revalidatePath(`/studios/${slug}`);
|
||||
return { slug };
|
||||
}
|
||||
|
||||
/** Rename a series. Returns the new slug or null. */
|
||||
export async function renameSeries(id: number, name: string): Promise<{ slug: string } | null> {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) return null;
|
||||
const row = rawDb.prepare(`SELECT name, slug FROM series WHERE id = ?`).get(id) as { name: string; slug: string } | undefined;
|
||||
if (!row) return null;
|
||||
if (trimmed === row.name) return { slug: row.slug };
|
||||
const slug = uniqueSlug(rawDb, "series", trimmed, id);
|
||||
rawDb.prepare(`UPDATE series SET name = ?, slug = ? WHERE id = ?`).run(trimmed, slug, id);
|
||||
revalidatePath("/series");
|
||||
revalidatePath(`/series/${row.slug}`);
|
||||
if (slug !== row.slug) revalidatePath(`/series/${slug}`);
|
||||
return { slug };
|
||||
}
|
||||
|
||||
/** Rename a tag (keyed by name). Returns the new name or null. */
|
||||
export async function renameTag(oldName: string, newName: string): Promise<{ name: string } | null> {
|
||||
// Tags are stored lowercased (see tags.ts createTag/addTagToImage).
|
||||
// Lookup by exact match must use the same casing or it silently misses.
|
||||
const oldTrim = oldName.trim().toLowerCase();
|
||||
const newTrim = newName.trim().toLowerCase();
|
||||
if (!oldTrim || !newTrim) return null;
|
||||
if (oldTrim === newTrim) return { name: oldTrim };
|
||||
const row = rawDb.prepare(`SELECT id FROM tags WHERE name = ?`).get(oldTrim) as { id: number } | undefined;
|
||||
if (!row) return null;
|
||||
// Wrap merge in a transaction so a concurrent INSERT into image_tags
|
||||
// for the old tag_id can't slip between the UPDATE re-point and the
|
||||
// DELETE — both run atomically against a consistent snapshot.
|
||||
const merge = rawDb.transaction(() => {
|
||||
const conflict = rawDb.prepare(`SELECT id FROM tags WHERE name = ?`).get(newTrim) as { id: number } | undefined;
|
||||
if (conflict && conflict.id !== row.id) {
|
||||
rawDb.prepare(`UPDATE OR IGNORE image_tags SET tag_id = ? WHERE tag_id = ?`).run(conflict.id, row.id);
|
||||
rawDb.prepare(`DELETE FROM image_tags WHERE tag_id = ?`).run(row.id);
|
||||
rawDb.prepare(`DELETE FROM tags WHERE id = ?`).run(row.id);
|
||||
} else {
|
||||
rawDb.prepare(`UPDATE tags SET name = ? WHERE id = ?`).run(newTrim, row.id);
|
||||
}
|
||||
});
|
||||
merge();
|
||||
revalidatePath("/tag");
|
||||
revalidatePath(`/tag/${encodeURIComponent(oldTrim)}`);
|
||||
revalidatePath(`/tag/${encodeURIComponent(newTrim)}`);
|
||||
return { name: newTrim };
|
||||
}
|
||||
|
||||
/** Deletes a tag. Any image_tags rows referencing it are removed. */
|
||||
export async function deleteTag(name: string) {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) return;
|
||||
const row = rawDb.prepare(`SELECT id FROM tags WHERE name = ?`).get(trimmed) as { id: number } | undefined;
|
||||
if (!row) return;
|
||||
rawDb.prepare(`DELETE FROM image_tags WHERE tag_id = ?`).run(row.id);
|
||||
rawDb.prepare(`DELETE FROM tags WHERE id = ?`).run(row.id);
|
||||
revalidatePath("/tag");
|
||||
revalidatePath("/");
|
||||
redirect("/tag");
|
||||
}
|
||||
Reference in New Issue
Block a user