From 2cc989c9be87abefe18d67fa4465ca26ffea769d Mon Sep 17 00:00:00 2001 From: yibo Date: Wed, 2 Sep 2026 02:16:43 +0800 Subject: [PATCH] Add admin-characters/garments/dance-refs.html (T3-T5) --- admin-characters.html | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ admin-dance-refs.html | 237 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ admin-garments.html | 231 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ admin-pages-flow.test.js | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ admin.html | 3 +++ 5 files changed, 836 insertions(+), 0 deletions(-) create mode 100644 admin-characters.html create mode 100644 admin-dance-refs.html create mode 100644 admin-garments.html create mode 100644 admin-pages-flow.test.js diff --git a/admin-characters.html b/admin-characters.html new file mode 100644 index 0000000..7ca5099 --- /dev/null +++ b/admin-characters.html @@ -0,0 +1,234 @@ + + + + +人物库管理 + + + + +
+

人物库管理

+
+ 视频库 + 衣服库 + 参考跳舞视频库 + ← 返回直播频道 +
+ +
+
+
+
+ + + + +
+
+ +
+ + +
+ +
+ +
+
+ +
+ + + + + + + + + + + + + + + +
图片名称外观描述标签默认来源状态创建时间操作
+
+
+ + + + diff --git a/admin-dance-refs.html b/admin-dance-refs.html new file mode 100644 index 0000000..3f86722 --- /dev/null +++ b/admin-dance-refs.html @@ -0,0 +1,237 @@ + + + + +参考跳舞视频库管理 + + + + +
+

参考跳舞视频库管理

+
+ 视频库 + 人物库 + 衣服库 + ← 返回直播频道 +
+ +
+
+
+
+ + + + +
+
+ +
动作描述是必填项:这条视频一旦录入,后续所有引用它生成的视频都复用这段描述,不能留空事后补
+
+ + +
+ +
+ +
+
+ +
+ + + + + + + + + + + + + + +
预览名称动作描述风格标签版权来源上架创建时间操作
+
+
+ + + + diff --git a/admin-garments.html b/admin-garments.html new file mode 100644 index 0000000..d781e7a --- /dev/null +++ b/admin-garments.html @@ -0,0 +1,231 @@ + + + + +衣服库管理 + + + + +
+

衣服库管理

+
+ 视频库 + 人物库 + 参考跳舞视频库 + ← 返回直播频道 +
+ +
+
+
+
+ + + + + +
+
+ +
+ + +
+ +
+ +
+
+ +
+ + + + + + + + + + + + + + +
图片名称分类外观描述上架来源创建时间操作
+
+
+ + + + diff --git a/admin-pages-flow.test.js b/admin-pages-flow.test.js new file mode 100644 index 0000000..0c61bec --- /dev/null +++ b/admin-pages-flow.test.js @@ -0,0 +1,131 @@ +// End-to-end check of the exact sequence admin-characters.html / +// admin-garments.html / admin-dance-refs.html perform in the browser: +// login -> upload a real file -> create the record with the returned URL +// -> list -> confirm the shape has everything the page renders. Doesn't +// render actual HTML/JS (no browser tooling available here), but exercises +// the same API calls in the same order with real files, which is where a +// field-name mismatch between a page's JS and the backend would surface. +const test = require("node:test"); +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); +const os = require("node:os"); +const { spawn } = require("node:child_process"); + +const PORT = 21934 + Math.floor(Math.random() * 1000); +const BASE = `http://localhost:${PORT}`; +const ADMIN_PASSWORD = "test-password-only-for-this-run"; +let cookie; +let child; +let cwd; + +const TINY_VALID_PNG = Buffer.from( + "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAEklEQVR4nGPkEpFjYGBgYgADAALmAEAUQs4PAAAAAElFTkSuQmCC", + "base64" +); + +function waitForServer(url, timeoutMs = 10000) { + const deadline = Date.now() + timeoutMs; + return (async function poll() { + while (Date.now() < deadline) { + try { + await fetch(url); + return; + } catch { + await new Promise((r) => setTimeout(r, 100)); + } + } + throw new Error("server did not come up in time"); + })(); +} + +test.before(async () => { + cwd = fs.mkdtempSync(path.join(os.tmpdir(), "admin-pages-flow-")); + for (const f of ["server.js", "ingest.js", "uploads.js", "store.js", "dressup-store.js"]) { + fs.copyFileSync(path.join(__dirname, f), path.join(cwd, f)); + } + + child = spawn(process.execPath, ["server.js"], { + cwd, + env: { ...process.env, PORT: String(PORT), H3_CONSOLE_BASE: "", H3_CONSOLE_TOKEN: "", ADMIN_PASSWORD }, + stdio: "pipe", + }); + await waitForServer(BASE); + + const loginRes = await fetch(`${BASE}/api/admin/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ password: ADMIN_PASSWORD }), + }); + cookie = (loginRes.headers.get("set-cookie") || "").split(";")[0]; +}); + +test.after(() => { + child.kill(); + fs.rmSync(cwd, { recursive: true, force: true }); +}); + +test("admin-characters.html flow: upload image then create, list shows imageUrl reachable via GET /uploads/*", async () => { + const fd = new FormData(); + fd.append("file", new Blob([TINY_VALID_PNG], { type: "image/png" }), "person.png"); + const uploadRes = await fetch(`${BASE}/api/admin/upload`, { method: "POST", headers: { Cookie: cookie }, body: fd }); + assert.equal(uploadRes.status, 200); + const { url: imageUrl } = await uploadRes.json(); + + const createRes = await fetch(`${BASE}/api/admin/characters`, { + method: "POST", + headers: { "Content-Type": "application/json", Cookie: cookie }, + body: JSON.stringify({ name: "小美", imageUrl, appearanceDescriptor: "black hair", tags: ["seed"], isDefault: true }), + }); + assert.equal(createRes.status, 200); + const created = await createRes.json(); + + const list = await fetch(`${BASE}/api/admin/characters`, { headers: { Cookie: cookie } }).then((r) => r.json()); + const row = list.find((c) => c.id === created.id); + assert.ok(row); + assert.equal(row.imageUrl, imageUrl); + + const served = await fetch(`${BASE}${row.imageUrl}`); + assert.equal(served.status, 200); +}); + +test("admin-garments.html flow: upload image then create, thumbnailUrl falls back to imageUrl for the table's ", async () => { + const fd = new FormData(); + fd.append("file", new Blob([TINY_VALID_PNG], { type: "image/png" }), "garment.png"); + const uploadRes = await fetch(`${BASE}/api/admin/upload`, { method: "POST", headers: { Cookie: cookie }, body: fd }); + const { url: imageUrl } = await uploadRes.json(); + + const createRes = await fetch(`${BASE}/api/admin/garments`, { + method: "POST", + headers: { "Content-Type": "application/json", Cookie: cookie }, + body: JSON.stringify({ name: "红旗袍", category: "古风", imageUrl, appearanceDescriptor: "red qipao" }), + }); + const created = await createRes.json(); + assert.equal(created.thumbnailUrl, imageUrl); + + const served = await fetch(`${BASE}${created.thumbnailUrl}`); + assert.equal(served.status, 200); +}); + +test("admin-dance-refs.html flow: upload real mp4 then create, videoUrl reachable and motionDescriptor round-trips", async () => { + const realMp4 = fs.readFileSync(path.join(__dirname, "spike", "s1-s2-s4-2026-09-02", "danceref.mp4")); + const fd = new FormData(); + fd.append("file", new Blob([realMp4], { type: "video/mp4" }), "dance.mp4"); + const uploadRes = await fetch(`${BASE}/api/admin/upload-video`, { method: "POST", headers: { Cookie: cookie }, body: fd }); + assert.equal(uploadRes.status, 200); + const { url: videoUrl } = await uploadRes.json(); + + const createRes = await fetch(`${BASE}/api/admin/dance-refs`, { + method: "POST", + headers: { "Content-Type": "application/json", Cookie: cookie }, + body: JSON.stringify({ name: "拉丁舞", videoUrl, motionDescriptor: "hip sways and raised-arm turns", styleTags: ["latin"] }), + }); + assert.equal(createRes.status, 200); + const created = await createRes.json(); + assert.equal(created.videoUrl, videoUrl); + assert.equal(created.motionDescriptor, "hip sways and raised-arm turns"); + + const served = await fetch(`${BASE}${created.videoUrl}`); + assert.equal(served.status, 200); + assert.equal(served.headers.get("content-type"), "video/mp4"); +}); diff --git a/admin.html b/admin.html index 36bf5e8..d760b51 100644 --- a/admin.html +++ b/admin.html @@ -29,6 +29,9 @@

视频库管理

+ 人物库 + 衣服库 + 参考跳舞视频库 ← 返回直播频道
-- libgit2 0.21.4