feat(pos): add GET/PUT /api/v1/pos/slip-layout/[letterheadId]

This commit is contained in:
Dave 2026-09-04 13:24:00 +02:00
parent 3a29ad6a6a
commit 102673d00b
2 changed files with 128 additions and 0 deletions

View file

@ -0,0 +1,72 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { NextRequest } from "next/server";
import { GET, PUT } from "./route";
import { getSlipLayout, saveSlipLayout } from "@/lib/server/pos-slip-layout-service";
vi.mock("@/lib/server/pos-slip-layout-service", () => ({
getSlipLayout: vi.fn(),
saveSlipLayout: vi.fn(),
}));
const POS_API_KEY = "test-pos-key";
function req(method: string, body?: unknown, authHeader: string | null = `Bearer ${POS_API_KEY}`): NextRequest {
const headers: Record<string, string> = { "content-type": "application/json" };
if (authHeader !== null) headers.authorization = authHeader;
return new NextRequest("http://localhost/api/v1/pos/slip-layout/lh-1", {
method,
headers,
body: body !== undefined ? JSON.stringify(body) : undefined,
});
}
const params = Promise.resolve({ letterheadId: "lh-1" });
describe("GET /api/v1/pos/slip-layout/[letterheadId]", () => {
beforeEach(() => {
process.env.POS_API_KEY = POS_API_KEY;
vi.mocked(getSlipLayout).mockReset();
vi.mocked(saveSlipLayout).mockReset();
});
it("401s without a valid key", async () => {
const res = await GET(req("GET", undefined, null), { params });
expect(res.status).toBe(401);
});
it("returns { layout: null } when nothing is saved yet", async () => {
vi.mocked(getSlipLayout).mockResolvedValue(null);
const res = await GET(req("GET"), { params });
expect(res.status).toBe(200);
const body = await res.json();
expect(body.layout).toBeNull();
});
it("returns the saved layout", async () => {
vi.mocked(getSlipLayout).mockResolvedValue({
sale_blocks: [], delivery_blocks: [], cashup_blocks: [], delivery_show_pricing: true,
});
const res = await GET(req("GET"), { params });
const body = await res.json();
expect(body.layout.delivery_show_pricing).toBe(true);
});
});
describe("PUT /api/v1/pos/slip-layout/[letterheadId]", () => {
beforeEach(() => {
process.env.POS_API_KEY = POS_API_KEY;
vi.mocked(saveSlipLayout).mockReset();
});
it("saves and returns the updated layout", async () => {
vi.mocked(saveSlipLayout).mockResolvedValue({
sale_blocks: [{ id: "logo", enabled: false }],
delivery_blocks: [], cashup_blocks: [], delivery_show_pricing: true,
});
const res = await PUT(req("PUT", { sale_blocks: [{ id: "logo", enabled: false }] }), { params });
expect(res.status).toBe(200);
expect(saveSlipLayout).toHaveBeenCalledWith("lh-1", { sale_blocks: [{ id: "logo", enabled: false }] });
const body = await res.json();
expect(body.layout.sale_blocks).toEqual([{ id: "logo", enabled: false }]);
});
});

View file

@ -0,0 +1,56 @@
import { NextRequest, NextResponse } from "next/server";
import { assertPosAuth } from "@/lib/server/pos-auth";
import {
getSlipLayout,
saveSlipLayout,
type PosSlipLayoutInput,
} from "@/lib/server/pos-slip-layout-service";
/**
* GET /api/v1/pos/slip-layout/[letterheadId]
*
* Returns `{ layout: null }` when this letterhead has never had a
* layout saved — the till falls back to its own hardcoded defaults
* in that case (see public/pos/slip-layout.js).
*
* Auth: requires `Authorization: Bearer ${POS_API_KEY}`.
*/
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ letterheadId: string }> },
) {
try {
assertPosAuth(req);
} catch (err) {
const message = err instanceof Error ? err.message : "Unauthorized";
return NextResponse.json({ error: message }, { status: 401 });
}
const { letterheadId } = await params;
const layout = await getSlipLayout(letterheadId);
return NextResponse.json({ layout });
}
/**
* PUT /api/v1/pos/slip-layout/[letterheadId]
*
* Body: any subset of `{ sale_blocks, delivery_blocks, cashup_blocks,
* delivery_show_pricing }` — only the fields given are written.
* Creates the row on first save.
*
* Auth: requires `Authorization: Bearer ${POS_API_KEY}`.
*/
export async function PUT(
req: NextRequest,
{ params }: { params: Promise<{ letterheadId: string }> },
) {
try {
assertPosAuth(req);
} catch (err) {
const message = err instanceof Error ? err.message : "Unauthorized";
return NextResponse.json({ error: message }, { status: 401 });
}
const { letterheadId } = await params;
const body = (await req.json()) as PosSlipLayoutInput;
const layout = await saveSlipLayout(letterheadId, body);
return NextResponse.json({ layout });
}