mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-03 03:12:31 +00:00
feat(my-videos): add pure video-health classifier (Phase 1, step 1)
First piece of the "My Videos" management tab: classifyVideoHealth decides the maintenance status for one of the user's own videos from note-side data plus an optional URL probe result. Pure/synchronous so it's deterministic and testable; the controller will do the actual HEAD probe and pass the result in. Statuses: no-source (no url AND no magnet) -> error; hosted-URL-unreachable -> warning; deleted (tombstoned) -> info; otherwise ok. Key guardrail: only URLs under the user's storage publicBaseUrl can be flagged dead — external/CORS-opaque links are never cried wolf on. Deleted is checked before no-source since soft-delete scrubs url/magnet. Tests + mutation-verified: dropping the hosted guard (external false-alarm) and removing deleted-precedence each fail a scenario. Build + full lint green. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
73a06069ad
commit
8cb03d23cc
2 files changed
+145
No files matched your search
@@ -0,0 +1,71 @@
|
||||
// Pure health classification for the "My Videos" management tab.
|
||||
//
|
||||
// Given a parsed video note (and optionally a URL probe result), decide the
|
||||
// maintenance status shown to the user. Deliberately no network/IO here so it
|
||||
// stays deterministic and testable; the controller performs the actual HEAD
|
||||
// probe and passes the result in. Phase 2 (bucket reconciliation) layers
|
||||
// storage-side statuses on top of this note-side classification.
|
||||
|
||||
export const VIDEO_HEALTH_STATUS = Object.freeze({
|
||||
DELETED: "deleted",
|
||||
NO_SOURCE: "no-source",
|
||||
DEAD_URL: "dead-url",
|
||||
OK: "ok",
|
||||
});
|
||||
|
||||
function trimStr(value) {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Does `url` live under the user's storage public base URL? Only hosted URLs are
|
||||
* ones we can meaningfully probe / reconcile against the bucket; third-party
|
||||
* links are out of our control (and CORS-opaque), so they must never be flagged
|
||||
* red just because we can't verify them.
|
||||
*/
|
||||
export function isUrlUnderBase(url, publicBaseUrl) {
|
||||
const u = trimStr(url);
|
||||
const base = trimStr(publicBaseUrl).replace(/\/+$/, "");
|
||||
if (!u || !base) {
|
||||
return false;
|
||||
}
|
||||
return u === base || u.startsWith(`${base}/`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify the maintenance health of one of the user's own videos.
|
||||
*
|
||||
* @param {object} video parsed video note ({ url, magnet, deleted, ... })
|
||||
* @param {object} [opts]
|
||||
* @param {string} [opts.publicBaseUrl] the user's storage public base URL
|
||||
* @param {{ ok: boolean }|null} [opts.urlProbe] result of a HEAD probe on
|
||||
* `video.url`, if one was performed (null = not probed / unverifiable)
|
||||
* @returns {{ status: string, severity: "error"|"warning"|"info"|"ok",
|
||||
* label: string, hosted: boolean }}
|
||||
*/
|
||||
export function classifyVideoHealth(video, { publicBaseUrl = "", urlProbe = null } = {}) {
|
||||
const v = video && typeof video === "object" ? video : {};
|
||||
const url = trimStr(v.url);
|
||||
const magnet = trimStr(v.magnet);
|
||||
const hosted = isUrlUnderBase(url, publicBaseUrl);
|
||||
|
||||
// A deleted (tombstoned) note is intentional, not a problem to fix here — it's
|
||||
// a candidate for storage cleanup (Phase 2). Checked first because soft-delete
|
||||
// scrubs url/magnet, which would otherwise read as "no source".
|
||||
if (v.deleted === true) {
|
||||
return { status: VIDEO_HEALTH_STATUS.DELETED, severity: "info", label: "Deleted on Nostr", hosted };
|
||||
}
|
||||
|
||||
if (!url && !magnet) {
|
||||
return { status: VIDEO_HEALTH_STATUS.NO_SOURCE, severity: "error", label: "No playable source", hosted };
|
||||
}
|
||||
|
||||
// A dead URL is only a hard signal when it's a hosted URL we could actually
|
||||
// probe. External links that fail (or can't be probed) stay "ok" so we don't
|
||||
// cry wolf on CORS-opaque third-party hosts.
|
||||
if (hosted && url && urlProbe && urlProbe.ok === false) {
|
||||
return { status: VIDEO_HEALTH_STATUS.DEAD_URL, severity: "warning", label: "Hosted file unreachable", hosted };
|
||||
}
|
||||
|
||||
return { status: VIDEO_HEALTH_STATUS.OK, severity: "ok", label: "OK", hosted };
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// The "My Videos" management tab flags maintenance issues per video so a user can
|
||||
// keep a large library clean. classifyVideoHealth is the pure decision core:
|
||||
// no-source -> error (red), hosted-and-unreachable -> warning, deleted -> info,
|
||||
// otherwise ok. It must NOT flag external/unverifiable URLs as broken.
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import {
|
||||
classifyVideoHealth,
|
||||
isUrlUnderBase,
|
||||
VIDEO_HEALTH_STATUS,
|
||||
} from "../js/ui/profileModal/myVideosHealth.js";
|
||||
|
||||
const BASE = "https://cdn.example.com";
|
||||
|
||||
test("a video with neither URL nor magnet is a red no-source error", () => {
|
||||
const r = classifyVideoHealth({ url: "", magnet: "" }, { publicBaseUrl: BASE });
|
||||
assert.equal(r.status, VIDEO_HEALTH_STATUS.NO_SOURCE);
|
||||
assert.equal(r.severity, "error");
|
||||
});
|
||||
|
||||
test("a magnet-only video is OK (no hosted URL needed)", () => {
|
||||
const r = classifyVideoHealth({ url: "", magnet: "magnet:?xt=urn:btih:abc" }, { publicBaseUrl: BASE });
|
||||
assert.equal(r.status, VIDEO_HEALTH_STATUS.OK);
|
||||
assert.equal(r.severity, "ok");
|
||||
});
|
||||
|
||||
test("a hosted URL with no probe is OK", () => {
|
||||
const r = classifyVideoHealth({ url: `${BASE}/u/npub1/h/v.mp4` }, { publicBaseUrl: BASE });
|
||||
assert.equal(r.status, VIDEO_HEALTH_STATUS.OK);
|
||||
assert.equal(r.hosted, true);
|
||||
});
|
||||
|
||||
test("a deleted note is classified as deleted (info), not no-source", () => {
|
||||
// Soft-delete scrubs url/magnet; deleted must win over no-source.
|
||||
const r = classifyVideoHealth({ url: "", magnet: "", deleted: true }, { publicBaseUrl: BASE });
|
||||
assert.equal(r.status, VIDEO_HEALTH_STATUS.DELETED);
|
||||
assert.equal(r.severity, "info");
|
||||
});
|
||||
|
||||
test("a hosted URL that fails its probe is a dead-url warning", () => {
|
||||
const r = classifyVideoHealth(
|
||||
{ url: `${BASE}/u/npub1/h/v.mp4` },
|
||||
{ publicBaseUrl: BASE, urlProbe: { ok: false } },
|
||||
);
|
||||
assert.equal(r.status, VIDEO_HEALTH_STATUS.DEAD_URL);
|
||||
assert.equal(r.severity, "warning");
|
||||
});
|
||||
|
||||
test("an EXTERNAL URL that fails its probe is NOT flagged (unverifiable, no false alarm)", () => {
|
||||
const r = classifyVideoHealth(
|
||||
{ url: "https://third-party.example/v.mp4" },
|
||||
{ publicBaseUrl: BASE, urlProbe: { ok: false } },
|
||||
);
|
||||
assert.equal(r.status, VIDEO_HEALTH_STATUS.OK);
|
||||
assert.equal(r.hosted, false);
|
||||
});
|
||||
|
||||
test("a hosted URL whose probe succeeds is OK", () => {
|
||||
const r = classifyVideoHealth(
|
||||
{ url: `${BASE}/u/npub1/h/v.mp4` },
|
||||
{ publicBaseUrl: BASE, urlProbe: { ok: true } },
|
||||
);
|
||||
assert.equal(r.status, VIDEO_HEALTH_STATUS.OK);
|
||||
});
|
||||
|
||||
test("isUrlUnderBase handles trailing slashes, external hosts, and empty input", () => {
|
||||
assert.equal(isUrlUnderBase(`${BASE}/a/b.mp4`, BASE), true);
|
||||
assert.equal(isUrlUnderBase(`${BASE}/a/b.mp4`, `${BASE}/`), true, "trailing slash on base");
|
||||
assert.equal(isUrlUnderBase("https://evil.example/a.mp4", BASE), false);
|
||||
assert.equal(isUrlUnderBase(`${BASE}-lookalike.com/a.mp4`, BASE), false, "prefix must be a path boundary");
|
||||
assert.equal(isUrlUnderBase("", BASE), false);
|
||||
assert.equal(isUrlUnderBase(`${BASE}/a.mp4`, ""), false);
|
||||
});
|
||||
Reference in new issue
Block a user