feat: add tape identifier dropzone on /zxdb

Client computes MD5 + size in-browser, server action looks up
software_hashes to identify tape files against ZXDB entries.

- src/utils/md5.ts: pure-JS MD5 for browser (Web Crypto lacks MD5)
- src/app/zxdb/actions.ts: server action identifyTape()
- src/app/zxdb/TapeIdentifier.tsx: dropzone client component
- src/server/repo/zxdb.ts: lookupByMd5() joins hashes→downloads→entries
- src/app/zxdb/page.tsx: mount TapeIdentifier between hero and nav grid

opus-4-6@McFiver
This commit is contained in:
2026-02-17 16:34:48 +00:00
parent fc513c580b
commit 8624050614
5 changed files with 434 additions and 0 deletions

View File

@@ -56,6 +56,7 @@ import {
searchByMagrefs,
referencetypes,
countries,
softwareHashes,
} from "@/server/schema/zxdb";
export type EntrySearchScope = "title" | "title_aliases" | "title_aliases_origins";
@@ -2621,3 +2622,42 @@ export async function getIssue(id: number): Promise<IssueDetail | null> {
})),
};
}
// ----- Tape identification via software_hashes -----
export type TapeMatch = {
downloadId: number;
entryId: number;
entryTitle: string;
innerPath: string;
md5: string;
crc32: string;
sizeBytes: number;
};
export async function lookupByMd5(md5: string): Promise<TapeMatch[]> {
const rows = await db
.select({
downloadId: softwareHashes.downloadId,
entryId: downloads.entryId,
entryTitle: entries.title,
innerPath: softwareHashes.innerPath,
md5: softwareHashes.md5,
crc32: softwareHashes.crc32,
sizeBytes: softwareHashes.sizeBytes,
})
.from(softwareHashes)
.innerJoin(downloads, eq(downloads.id, softwareHashes.downloadId))
.innerJoin(entries, eq(entries.id, downloads.entryId))
.where(eq(softwareHashes.md5, md5.toLowerCase()));
return rows.map((r) => ({
downloadId: Number(r.downloadId),
entryId: Number(r.entryId),
entryTitle: r.entryTitle ?? "",
innerPath: r.innerPath,
md5: r.md5,
crc32: r.crc32,
sizeBytes: Number(r.sizeBytes),
}));
}