Refactor R2 key prefix for deterministic identifiers

This commit is contained in:
thePR0M3TH3AN committed 2026-01-26 12:47:05 -05:00
1 parent 00b0e85438
commit c02db755ac
6 files changed
+42 -12

No files matched your search

+6 -5
View File
@@ -211,13 +211,13 @@ function guessExtension(file) {
}
}
export function buildR2Key(npub, file) {
const now = new Date();
const year = now.getUTCFullYear();
const month = String(now.getUTCMonth() + 1).padStart(2, "0");
export function buildR2Key(npub, file, identifier = "") {
const safeNpub = String(npub || "")
.toLowerCase()
.replace(/[^a-z0-9]/g, "");
const safeIdentifier = String(identifier || "")
.toLowerCase()
.replace(/[^a-z0-9]/g, "");
const baseName = typeof file?.name === "string" ? file.name : "video";
const withoutExt = baseName.replace(/\.[^/.]+$/, "");
const slug = withoutExt
@@ -226,7 +226,8 @@ export function buildR2Key(npub, file) {
.replace(/^-+|-+$/g, "");
const safeSlug = slug || "video";
const ext = guessExtension(file);
return `u/${safeNpub}/${year}/${month}/${safeSlug}.${ext}`;
const namespace = safeIdentifier || "uploads";
return `u/${safeNpub}/${namespace}/${safeSlug}.${ext}`;
}
export function buildPublicUrl(baseUrl, key) {
+2 -1
View File
@@ -356,7 +356,8 @@ export async function uploadAttachment({
}
const prepared = await prepareAttachmentUpload({ file, encrypt });
const key = typeof buildKey === "function" ? buildKey(pubkey, file) : "";
const key =
typeof buildKey === "function" ? buildKey(pubkey, file, prepared.sha256) : "";
if (!key) {
throw new Error("Unable to derive storage key for attachment.");
}
+6 -2
View File
@@ -844,6 +844,11 @@ class R2Service {
this.updateCloudflareProgress(0);
this.setCloudflareUploading(true);
const normalizedInfoHash = normalizeInfoHash(infoHash);
const keyIdentifier = isValidInfoHash(normalizedInfoHash)
? normalizedInfoHash
: "";
let bucketResult = null;
try {
bucketResult = await this.ensureBucketConfigForNpub(npub, {
@@ -880,7 +885,7 @@ class R2Service {
this.setCloudflareUploadStatus(statusMessage, "info");
// Use forced keys if provided, otherwise generate them
const key = forcedVideoKey || buildR2Key(npub, file);
const key = forcedVideoKey || buildR2Key(npub, file, keyIdentifier);
const publicUrl = forcedVideoUrl || buildPublicUrl(bucketEntry.publicBaseUrl, key);
const buildTorrentKey = () => {
@@ -978,7 +983,6 @@ class R2Service {
// - `xs` (eXact Source): The URL to the .torrent file on R2. This allows
// clients to fetch metadata (file structure, piece hashes) instantly via HTTP
// instead of waiting to fetch it from the DHT (which can take minutes).
const normalizedInfoHash = normalizeInfoHash(infoHash);
const hasValidInfoHash = isValidInfoHash(normalizedInfoHash);
let generatedMagnet = "";
let generatedWs = "";
+6 -1
View File
@@ -215,6 +215,11 @@ class S3UploadService {
try {
await ensureS3SdkLoaded();
const normalizedInfoHash = normalizeInfoHash(infoHash);
const keyIdentifier = isValidInfoHash(normalizedInfoHash)
? normalizedInfoHash
: "";
const s3 = makeS3Client({
endpoint: normalized.endpoint,
region: normalized.region,
@@ -223,7 +228,7 @@ class S3UploadService {
forcePathStyle: Boolean(normalized.forcePathStyle),
});
const key = forcedVideoKey || buildR2Key(npub, file);
const key = forcedVideoKey || buildR2Key(npub, file, keyIdentifier);
const publicUrl =
forcedVideoUrl ||
buildS3ObjectUrl({
+19 -2
View File
@@ -6,7 +6,10 @@ import {
getVideoNoteErrorMessage,
normalizeVideoNotePayload,
} from "../../services/videoNotePayload.js";
import { createTorrentMetadata } from "../../utils/torrentHash.js";
import {
calculateTorrentInfoHash,
createTorrentMetadata,
} from "../../utils/torrentHash.js";
import { sanitizeBucketName } from "../../storage/r2-mgmt.js";
import { buildR2Key, buildPublicUrl } from "../../r2.js";
import { buildS3ObjectUrl } from "../../services/s3Service.js";
@@ -524,7 +527,8 @@ export class UploadModal {
if (this.videoUploadId !== currentUploadId) return;
// 2. Determine Keys
const videoKey = buildR2Key(npub, file);
const identifier = await this.resolveUploadIdentifier(file);
const videoKey = buildR2Key(npub, file, identifier);
const baseDomain = bucketEntry.publicBaseUrl;
// Note: buildPublicUrl works for both if the base is clean
@@ -1088,6 +1092,19 @@ export class UploadModal {
};
}
async resolveUploadIdentifier(file) {
try {
const infoHash = await calculateTorrentInfoHash(file);
const normalized = normalizeInfoHash(infoHash);
if (isValidInfoHash(normalized)) {
return normalized;
}
} catch (hashErr) {
userLogger.warn("Failed to precompute info hash for storage key:", hashErr);
}
return "";
}
async handleExternalFlow(metadata) {
metadata.url = this.inputs.url?.value?.trim() || "";
metadata.magnet = this.inputs.magnet?.value?.trim() || "";
+3 -1
View File
@@ -8914,7 +8914,9 @@ export class ProfileModalController {
return;
}
const key = buildR2Key(pubkey, file);
const keyIdentifier =
type === "picture" ? "profile-picture" : "profile-banner";
const key = buildR2Key(pubkey, file, keyIdentifier);
await r2Service.uploadFile({
file,
...credentials,