mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
Refactor R2 key prefix for deterministic identifiers
This commit is contained in:
1 parent
00b0e85438
commit
c02db755ac
6 files changed
+42
-12
No files matched your search
@@ -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) {
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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() || "";
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user