feat(delete): warn when a hosted file is left behind by locked storage

Deleting a video removes the Nostr note(s) but cleanup of the underlying R2/S3
object was silently skipped when storage was locked — leaving the file publicly
downloadable with no indication to the user. Now the delete flow surfaces a
clear heads-up when that happens, telling the user to unlock storage and delete
again (or remove the object from their bucket manually).

To make the signal accurate, deleteVideoStorage now resolves the affected object
keys BEFORE the credentials check (publicBaseUrl is available even while
locked). So "storage-locked" is reported only when there are genuinely hosted
objects to remove; an external-URL video (nothing on the user's bucket) returns
"no-matching-objects" and never triggers a false warning.

Tests (deleteVideoStorage via injected credentials/deleteObject): locked +
hosted object -> storage-locked; locked + external URL -> no-matching-objects
(no false alarm); unlocked -> actually deletes the object. Mutation-verified —
restoring the old credentials-first ordering misreports the external-URL case.

Build + full lint green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-20 12:21:49 -04:00
1 parent 3f4299a885
commit f0eecec09d
3 files changed
+108 -5

No files matched your search

+17 -5
View File
@@ -154,19 +154,31 @@ export class R2Service {
""
).trim();
if (!accessKeyId || !secretAccessKey) {
// Storage is locked / keys unavailable — can't delete without them.
return skip("storage-locked");
}
if (!bucket || !publicBaseUrl) {
if (!publicBaseUrl) {
// Without the public base URL we can't tell which objects belong to this
// user's bucket, so there's nothing safe to remove.
return skip("missing-bucket-or-base-url");
}
// Determine the affected objects BEFORE the credentials check. publicBaseUrl
// is available even while storage is locked, so this lets us report
// "storage-locked" only when there are genuinely hosted objects to remove —
// an external-URL video (no matching objects) is never a false alarm.
const keys = collectVideoStorageKeys({ videos: list, publicBaseUrl });
if (!keys.length) {
return skip("no-matching-objects");
}
if (!accessKeyId || !secretAccessKey) {
// There ARE objects to remove but storage is locked / keys unavailable —
// surface this distinctly so the caller can warn the user the hosted
// files were left behind.
return skip("storage-locked");
}
if (!bucket) {
return skip("missing-bucket-or-base-url");
}
let s3;
try {
await ensureS3SdkLoaded();