Add quak helper list-missing-thumbnails and fix-missing-thumbnails
Some checks failed
check / check (push) Failing after 8s

list-missing-thumbnails: iterates all files across all collections,
fetches each thumbnail from the CDN, reports any that are missing or
empty. Deduplicates by file ID across collections.

fix-missing-thumbnails: for each missing thumbnail, downloads the
original file, generates a 720px JPEG thumbnail via sharp, encrypts
it with secretstream push (encryptBlob), uploads to a presigned URL,
and registers the new thumbnail via PUT /files/thumbnail.

New crypto: encryptBlob (secretstream push, single chunk TAG_FINAL).
New ApiClient methods: getUploadURL, putFile, putJSON, updateThumbnail.
New Client method: getApiClient() for modules that need raw API access.

Deps: sharp 0.34.5 (image processing), @types/sharp 0.32.0.
This commit is contained in:
2026-05-13 21:00:35 -07:00
parent fbd5099d49
commit e9a56d5c8d
8 changed files with 551 additions and 2 deletions

View File

@@ -8,6 +8,23 @@ export const STREAM_CHUNK_SIZE = 4 * 1024 * 1024;
// 16 bytes of Poly1305 tag plus 1 byte of secretstream tag.
export const STREAM_CHUNK_OVERHEAD = 17;
// Encrypt a small blob as a single secretstream chunk with TAG_FINAL.
// Returns the header and ciphertext. Used for encrypting thumbnails
// and metadata before upload.
export const encryptBlob = (
plaintext: Uint8Array,
key: Uint8Array,
): { header: Uint8Array; ciphertext: Uint8Array } => {
const push = sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
const ciphertext = sodium.crypto_secretstream_xchacha20poly1305_push(
push.state,
plaintext,
null,
sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL,
);
return { header: push.header, ciphertext };
};
// Opaque handle to libsodium's secretstream pull state. Threaded through
// successive pullStreamChunk calls.
export type StreamPullState = sodium.StateAddress;