mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-07 19:58:24 +00:00
Merge pull request #5 from Patiun/claude/patiun-rebrand-gjoo9f
Deploy safety and Homebox tags fallback
This commit is contained in:
+3
-1
@@ -39,7 +39,9 @@ cp deploy/compose.yaml "$STACK/compose.yaml"
|
|||||||
set_setting GK_TAG "$TAG"
|
set_setting GK_TAG "$TAG"
|
||||||
[ -n "$CURRENT" ] && set_setting GK_PREVIOUS_TAG "$CURRENT"
|
[ -n "$CURRENT" ] && set_setting GK_PREVIOUS_TAG "$CURRENT"
|
||||||
|
|
||||||
if ! compose up -d --remove-orphans --wait --wait-timeout 120; then
|
# No --remove-orphans: another stack may share the project name, and its
|
||||||
|
# containers must never be touched by this deploy.
|
||||||
|
if ! compose up -d --wait --wait-timeout 120; then
|
||||||
echo "!! $TAG did not come up healthy"
|
echo "!! $TAG did not come up healthy"
|
||||||
compose ps
|
compose ps
|
||||||
compose logs --tail 50 || true
|
compose logs --tail 50 || true
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
* API (Homebox's REST API, v1):
|
* API (Homebox's REST API, v1):
|
||||||
* - `POST /api/v1/users/login` with username and password gives a token
|
* - `POST /api/v1/users/login` with username and password gives a token
|
||||||
* (already prefixed `Bearer `), sent as `Authorization`.
|
* (already prefixed `Bearer `), sent as `Authorization`.
|
||||||
* - `GET /api/v1/labels` and `GET /api/v1/locations` name the ids to filter on.
|
* - `GET /api/v1/labels` (`/api/v1/tags` on newer releases) and `GET /api/v1/locations`
|
||||||
|
* name the ids to filter on.
|
||||||
* - `GET /api/v1/items?labels=<id>&page=&pageSize=` (or `locations=<id>`)
|
* - `GET /api/v1/items?labels=<id>&page=&pageSize=` (or `locations=<id>`)
|
||||||
* lists the items, each with name, quantity and description.
|
* lists the items, each with name, quantity and description.
|
||||||
*
|
*
|
||||||
@@ -84,7 +85,10 @@ export function materialOptions(items: HomeboxItem[]): StockOption[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function call(ctx: SyncContext, url: string, init: RequestInit = {}) {
|
async function call(ctx: SyncContext, url: string, init: RequestInit = {}) {
|
||||||
const res = await ctx.fetch(url, init);
|
return checked(await ctx.fetch(url, init));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checked(res: Response) {
|
||||||
if (res.status === 401 || res.status === 403) throw new Error(`Homebox refused the request (${res.status}); check HOMEBOX_USER and HOMEBOX_PASS`);
|
if (res.status === 401 || res.status === 403) throw new Error(`Homebox refused the request (${res.status}); check HOMEBOX_USER and HOMEBOX_PASS`);
|
||||||
if (!res.ok) throw new Error(`Homebox answered ${res.status}`);
|
if (!res.ok) throw new Error(`Homebox answered ${res.status}`);
|
||||||
return res.json() as Promise<unknown>;
|
return res.json() as Promise<unknown>;
|
||||||
@@ -110,9 +114,14 @@ export function homeboxJob(o: HomeboxOptions): SyncJob {
|
|||||||
...(o.locations ?? []).map((f) => ({ kind: "locations" as const, f })),
|
...(o.locations ?? []).map((f) => ({ kind: "locations" as const, f })),
|
||||||
];
|
];
|
||||||
const ids = { labels: new Map<string, string>(), locations: new Map<string, string>() };
|
const ids = { labels: new Map<string, string>(), locations: new Map<string, string>() };
|
||||||
for (const kind of ["labels", "locations"] as const) {
|
// Newer Homebox releases call labels "tags" (`/api/v1/tags`, `items?tags=`).
|
||||||
if (filters.some((x) => x.kind === kind)) ids[kind] = parseNamed(await call(ctx, api(`/${kind}`), { headers }));
|
let labelParam = "labels";
|
||||||
|
if (filters.some((x) => x.kind === "labels")) {
|
||||||
|
const res = await ctx.fetch(api("/labels"), { headers });
|
||||||
|
if (res.status === 404) labelParam = "tags";
|
||||||
|
ids.labels = parseNamed(res.status === 404 ? await call(ctx, api("/tags"), { headers }) : await checked(res));
|
||||||
}
|
}
|
||||||
|
if (filters.some((x) => x.kind === "locations")) ids.locations = parseNamed(await call(ctx, api("/locations"), { headers }));
|
||||||
|
|
||||||
const byGroup = new Map<string, HomeboxItem[]>();
|
const byGroup = new Map<string, HomeboxItem[]>();
|
||||||
const unknown: string[] = [];
|
const unknown: string[] = [];
|
||||||
@@ -124,7 +133,7 @@ export function homeboxJob(o: HomeboxOptions): SyncJob {
|
|||||||
}
|
}
|
||||||
const items = byGroup.get(f.group) ?? [];
|
const items = byGroup.get(f.group) ?? [];
|
||||||
for (let page = 1; page <= 20; page++) {
|
for (let page = 1; page <= 20; page++) {
|
||||||
const p = new URLSearchParams({ [kind]: id, page: String(page), pageSize: "100" });
|
const p = new URLSearchParams({ [kind === "labels" ? labelParam : kind]: id, page: String(page), pageSize: "100" });
|
||||||
const r = parseItems(await call(ctx, api(`/items?${p}`), { headers }));
|
const r = parseItems(await call(ctx, api(`/items?${p}`), { headers }));
|
||||||
items.push(...r.items);
|
items.push(...r.items);
|
||||||
if (r.items.length < 100 || page * 100 >= r.total) break;
|
if (r.items.length < 100 || page * 100 >= r.total) break;
|
||||||
|
|||||||
@@ -375,6 +375,32 @@ describe("Homebox", () => {
|
|||||||
const missing = await scheduler(homeboxJob({ url: "http://homebox:7745", user: "u", pass: "p", labels: parseFilters("vinyl") }), f.fn).runNow("homebox");
|
const missing = await scheduler(homeboxJob({ url: "http://homebox:7745", user: "u", pass: "p", labels: parseFilters("vinyl") }), f.fn).runNow("homebox");
|
||||||
expect(missing).toMatchObject({ ok: false, message: expect.stringContaining("vinyl") });
|
expect(missing).toMatchObject({ ok: false, message: expect.stringContaining("vinyl") });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("falls back to tags on Homebox releases without /labels", async () => {
|
||||||
|
r.put({
|
||||||
|
type: "product-line",
|
||||||
|
title: "Wallet",
|
||||||
|
category: "Leather",
|
||||||
|
mode: "made to order",
|
||||||
|
channel: "here",
|
||||||
|
summary: "s",
|
||||||
|
slots: { open: 1, total: 2 },
|
||||||
|
priceTiers: [],
|
||||||
|
options: [{ group: "Leather", name: "Chrome tan, Black" }],
|
||||||
|
publish: true,
|
||||||
|
});
|
||||||
|
r.service.scan();
|
||||||
|
const f = fakeFetch((url) => {
|
||||||
|
if (url.endsWith("/api/v1/users/login")) return ok({ token: "Bearer hb-token", expiresAt: "2026-10-12T00:00:00Z" });
|
||||||
|
if (url.endsWith("/api/v1/tags")) return ok(fixture("homebox-labels.sample.json"));
|
||||||
|
if (url.includes("/api/v1/items?")) {
|
||||||
|
return new URL(url).searchParams.get("tags")?.endsWith("01") ? ok(fixture("homebox-items.sample.json")) : ok({ items: [], total: 0 });
|
||||||
|
}
|
||||||
|
return ok({}, 404);
|
||||||
|
});
|
||||||
|
const report = await scheduler(homeboxJob({ url: "http://homebox:7745", user: "u", pass: "p", labels: parseFilters("leather,paper") }), f.fn).runNow("homebox");
|
||||||
|
expect(report).toMatchObject({ ok: true, counts: { lines: 1, leather: 1, paper: 0 } });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------- settings
|
// ---------------------------------------------------------------- settings
|
||||||
|
|||||||
Reference in New Issue
Block a user