Merge lane/import in feature/craftvia-mvp
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { moduleGuard } from "@/server/action-guard";
|
||||
import { ForbiddenError } from "@/server/rbac";
|
||||
import { ctxFromGuard, ServiceError } from "@/server/services/context";
|
||||
import { confirmImport, discardImport, retryImport } from "@/server/services/imports/confirm";
|
||||
import { searchCustomers } from "@/server/services/imports/queries";
|
||||
|
||||
/**
|
||||
* Server actions of the import module (thin adapters: guard → service → revalidate).
|
||||
* Uploads do NOT go through a server action (1 MB body limit) but through
|
||||
* POST /api/v1/work-orders/import with upload progress.
|
||||
*/
|
||||
const guard = moduleGuard("imports");
|
||||
|
||||
export type ConfirmState =
|
||||
| { status: "idle" }
|
||||
| { status: "error"; code: string; issues?: Array<{ path: string; message: string }> };
|
||||
|
||||
export type SimpleState = { status: "idle" } | { status: "error"; code: string };
|
||||
|
||||
function errorCode(err: unknown): string {
|
||||
if (err instanceof ServiceError) return err.code === "invalid" || err.code === "conflict" ? err.message : err.code;
|
||||
if (err instanceof ForbiddenError) return "forbidden";
|
||||
console.error("[actions/imports]", err);
|
||||
return "error";
|
||||
}
|
||||
|
||||
export async function confirmImportAction(importId: string, _prev: ConfirmState, formData: FormData): Promise<ConfirmState> {
|
||||
const ctx = ctxFromGuard(await guard("import:write", "work_order:write"));
|
||||
let payload: unknown;
|
||||
try {
|
||||
payload = JSON.parse(String(formData.get("payload") ?? ""));
|
||||
} catch {
|
||||
return { status: "error", code: "form_invalid" };
|
||||
}
|
||||
try {
|
||||
await confirmImport(ctx, importId, payload);
|
||||
} catch (err) {
|
||||
const issues = err instanceof ServiceError && err.code === "invalid" ? (err.details as Array<{ path: string; message: string }> | undefined) : undefined;
|
||||
return { status: "error", code: errorCode(err), issues };
|
||||
}
|
||||
revalidatePath("/imports");
|
||||
redirect(`/imports/${importId}`);
|
||||
}
|
||||
|
||||
// Bound as (prev, formData) action for useActionState; the extra arguments are not needed.
|
||||
export async function discardImportAction(importId: string): Promise<SimpleState> {
|
||||
const ctx = ctxFromGuard(await guard("import:write"));
|
||||
try {
|
||||
await discardImport(ctx, importId);
|
||||
} catch (err) {
|
||||
return { status: "error", code: errorCode(err) };
|
||||
}
|
||||
revalidatePath("/imports");
|
||||
redirect("/imports");
|
||||
}
|
||||
|
||||
export async function retryImportAction(importId: string): Promise<SimpleState> {
|
||||
const ctx = ctxFromGuard(await guard("import:write"));
|
||||
try {
|
||||
await retryImport(ctx, importId);
|
||||
} catch (err) {
|
||||
return { status: "error", code: errorCode(err) };
|
||||
}
|
||||
revalidatePath("/imports");
|
||||
revalidatePath(`/imports/${importId}`);
|
||||
return { status: "idle" };
|
||||
}
|
||||
|
||||
export async function searchCustomersAction(q: string) {
|
||||
const ctx = ctxFromGuard(await guard("import:write"));
|
||||
const rows = await searchCustomers(ctx, String(q ?? "").slice(0, 100));
|
||||
return rows;
|
||||
}
|
||||
Reference in New Issue
Block a user