CI / check (push) Canceled after 0s
CI / tests-unit (push) Canceled after 0s
CI / tests-integration (push) Canceled after 0s
CI / tests-ui (push) Canceled after 0s
CI / preflight (push) Canceled after 0s
CI / deploy (push) Canceled after 0s
Gitea Runner Clean Test / test-job (push) Canceled after 0s
Gitea Actions Runner Test / test-job (push) Canceled after 0s
- P1: new src/lib/admin/rank-guard.ts with tryGuardRank/guardRank + RankGuardTarget/Result; extracted from users.ts; applied to admin-bans (create/lift + rank redirect) and admin-badges (caps + rank guard + error surfacing); import-url-safety.ts SSRF guard wired into clone save+icon fetch; logAudit await in help-tickets; antiddos mutations SETTINGS_EDIT; markAllAlertsRead NOTIFICATIONS_EDIT; rankError/saveError in all 25 message files; tests rew/new for bans/badges/rank-guard/import-url-safety - P2: ticket-inbox merged pagination reworked to single UNION ALL SQL with LIMIT/OFFSET (avoids O(offset) materialization); getCachedAdminCount with cacheFiltered option applied to devops/errors/photos/items/guilds/help-tickets/moderation pages; admin-theme catches→?error=1+banner+25 locales; logger in silent catches (rare-values/writeable-boxes); catalog-items-loader CAST→plain equality; count caching helper extended with opt-in cacheFiltered - P3: removed dead modules is-staff/notice/recent-furni-resync/log-filters plus load-user-sanctions/user-sanction-timeline; removed orphaned CATALOG_PAGE_COLUMNS re-export; removed getFurniIconUrl from studio-furni-icons Build: lint 0, typecheck 0, 3480 tests pass, i18n:check exit 0, colors exit 0. Circular chunk warning (9452/10535/66463) is webpack code-splitting artifact; build succeeds.
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
// @ts-nocheck
|
|
// Bulk-import live run: imports every cloneable item from the sources that
|
|
// reliably serve .nitro assets (SodaStudios, Hubbly). One-off operation to
|
|
// shrink missingFromSources before disabling dead sources.
|
|
import { appendFileSync } from "node:fs";
|
|
import { beforeAll, describe, expect, it } from "vitest";
|
|
import { loadEnvForLiveTests } from "@/test/live-env";
|
|
|
|
const runLive = process.env.RUN_CLONE_BULK_LIVE === "1";
|
|
const LOG = "/tmp/clone-bulk.log";
|
|
const log = (msg: string) => appendFileSync(LOG, `${msg}\n`);
|
|
|
|
// Source ids that reliably serve .nitro files (verified 2026-09).
|
|
const IMPORT_IDS = ["default-sodastudios", "default-hubbly"];
|
|
|
|
describe("clone bulk import", {
|
|
skip: !runLive,
|
|
}, () => {
|
|
beforeAll(async () => {
|
|
loadEnvForLiveTests();
|
|
});
|
|
|
|
it("imports clonable items from delivering sources", async () => {
|
|
expect(process.env.DATABASE_URL).toMatch(/@/);
|
|
const mod = await import("@/lib/services/clone-sources");
|
|
const ci = await import("@/lib/services/clone-import");
|
|
const sources = await mod.listSources();
|
|
log(`START bulk import ${new Date().toISOString()}`);
|
|
|
|
for (const id of IMPORT_IDS) {
|
|
const source = sources.find((s) => s.id === id);
|
|
if (!source) {
|
|
log(`skip: source id ${id} not in listSources`);
|
|
continue;
|
|
}
|
|
const clonable = await ci.getClonableClassnames(source);
|
|
const all = await ci.fetchSourceFurnidata(source.furnidataUrl);
|
|
const entryFor = (classname: string) =>
|
|
all.find((e) => e.classname === classname);
|
|
log(
|
|
`SOURCE ${source.name}: ${clonable.length} clonable (fetched ${all.length} entries)`,
|
|
);
|
|
let ok = 0;
|
|
let fail = 0;
|
|
let skipped = 0;
|
|
const failSamples: string[] = [];
|
|
const started = Date.now();
|
|
for (let i = 0; i < clonable.length; i++) {
|
|
const classname = clonable[i];
|
|
const entry = entryFor(classname);
|
|
if (!entry) {
|
|
skipped++;
|
|
continue;
|
|
}
|
|
const res = await ci.cloneSingleFurni({ source, entry });
|
|
if (res.ok) ok++;
|
|
else if ("skipped" in res && res.skipped) skipped++;
|
|
else {
|
|
fail++;
|
|
if (failSamples.length < 30) {
|
|
failSamples.push(`${classname}: ${res.error}`);
|
|
}
|
|
}
|
|
if ((i + 1) % 50 === 0) {
|
|
const elapsed = ((Date.now() - started) / 1000).toFixed(0);
|
|
log(
|
|
` ${source.name} ${i + 1}/${clonable.length} ok=${ok} fail=${fail} skipped=${skipped} (${elapsed}s)`,
|
|
);
|
|
}
|
|
}
|
|
const secs = ((Date.now() - started) / 1000).toFixed(0);
|
|
log(
|
|
`DONE ${source.name}: ok=${ok} fail=${fail} skipped=${skipped} in ${secs}s`,
|
|
);
|
|
if (failSamples.length) {
|
|
log(` fail samples:\n ${failSamples.join("\n ")}`);
|
|
}
|
|
}
|
|
log(`END bulk import ${new Date().toISOString()}`);
|
|
}, 1800000);
|
|
});
|