mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
[storage] #39: fix profile image/banner upload (resolveConnection rejected hex pubkeys)
The Edit Profile image + banner upload was wired (resolve connection -> uploadFile -> auto-fill URL) but silently broken: ProfileEditController passes a HEX pubkey to r2Service.resolveConnection(), which only decoded npub1... strings via safeDecodeNpub. A hex pubkey therefore resolved to null -> "Storage configuration missing" and the Upload button stayed disabled even with storage configured. (Thumbnails worked because mediaUploader calls listConnections(pubkey) directly.) Fix: resolveConnection() now accepts a hex pubkey OR an npub. The rest of the chain is already provider-agnostic (resolveConnection returns the defaultForUploads connection's endpoint/baseDomain/forcePathStyle, uploadFile routes R2 vs generic-S3 by provider, buildPublicUrl builds the URL), so profile/banner upload now works for R2 / B2 / Custom S3. Test: tests/r2-resolve-connection-hex.test.mjs (3 — nip19.decode throws on hex to prove the hex path is used, plus npub still works and empty returns null). Build + lint clean; r2/storage suites green. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
f0beab2713
commit
a673ee96aa
3 files changed
+109
-8
No files matched your search
@@ -447,8 +447,13 @@ export class R2Service {
|
||||
async resolveConnection(npub) {
|
||||
if (!npub) return null;
|
||||
|
||||
// 1. Try StorageService
|
||||
const pubkey = safeDecodeNpub(npub);
|
||||
// 1. Try StorageService. Accept either a hex pubkey or an npub — some callers
|
||||
// (e.g. ProfileEditController's image upload) pass hex, and safeDecodeNpub only
|
||||
// decodes npub1… strings, so passing hex used to silently resolve no connection.
|
||||
const trimmed = String(npub).trim();
|
||||
const pubkey = /^[0-9a-f]{64}$/i.test(trimmed)
|
||||
? trimmed.toLowerCase()
|
||||
: safeDecodeNpub(trimmed);
|
||||
if (pubkey && storageService) {
|
||||
try {
|
||||
const connections = await storageService.listConnections(pubkey);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// #39: profile image/banner upload silently found "no storage" because
|
||||
// ProfileEditController passes a HEX pubkey to r2Service.resolveConnection(), which
|
||||
// only decoded npub1… strings (safeDecodeNpub) — so a hex pubkey resolved to null and
|
||||
// the upload button stayed disabled even with storage configured. resolveConnection
|
||||
// must accept a hex pubkey OR an npub.
|
||||
|
||||
import { describe, it, beforeEach, afterEach, mock } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import r2ServiceDefault from "../js/services/r2Service.js";
|
||||
import storageService from "../js/services/storageService.js";
|
||||
|
||||
const R2Service = r2ServiceDefault.constructor;
|
||||
const HEX = "a".repeat(64);
|
||||
|
||||
let originalWindow;
|
||||
|
||||
beforeEach(() => {
|
||||
originalWindow = globalThis.window;
|
||||
// nip19.decode THROWS on anything that isn't a real npub — exactly how the live
|
||||
// decoder behaves for a hex string. So if resolveConnection still works with hex,
|
||||
// it's using the hex path, not npub decoding.
|
||||
globalThis.window = {
|
||||
location: { origin: "" },
|
||||
NostrTools: {
|
||||
nip19: {
|
||||
decode: (value) => {
|
||||
if (typeof value === "string" && value.startsWith("npub1")) {
|
||||
return { type: "npub", data: HEX };
|
||||
}
|
||||
throw new Error("not an npub");
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
mock.method(storageService, "listConnections", async () => [
|
||||
{ id: "backblaze_b2", provider: "backblaze_b2", meta: { defaultForUploads: true } },
|
||||
]);
|
||||
mock.method(storageService, "isUnlocked", () => true);
|
||||
mock.method(storageService, "getConnection", async () => ({
|
||||
provider: "backblaze_b2",
|
||||
accessKeyId: "b2-key",
|
||||
secretAccessKey: "b2-secret",
|
||||
endpoint: "https://s3.us-west-004.backblazeb2.com",
|
||||
region: "us-west-004",
|
||||
forcePathStyle: false,
|
||||
meta: {
|
||||
bucket: "bitvid",
|
||||
provider: "backblaze_b2",
|
||||
publicBaseUrl: "https://bitvid.s3.us-west-004.backblazeb2.com",
|
||||
defaultForUploads: true,
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restoreAll();
|
||||
globalThis.window = originalWindow;
|
||||
});
|
||||
|
||||
describe("r2Service.resolveConnection accepts hex or npub", () => {
|
||||
it("resolves the configured connection when passed a HEX pubkey (the upload bug)", async () => {
|
||||
const svc = new R2Service();
|
||||
const conn = await svc.resolveConnection(HEX);
|
||||
assert.ok(conn, "hex pubkey must resolve a connection, not null");
|
||||
assert.equal(conn.provider, "backblaze_b2");
|
||||
assert.equal(conn.bucket, "bitvid");
|
||||
assert.equal(conn.baseDomain, "https://bitvid.s3.us-west-004.backblazeb2.com");
|
||||
assert.equal(conn.accessKeyId, "b2-key");
|
||||
});
|
||||
|
||||
it("still resolves when passed an npub", async () => {
|
||||
const svc = new R2Service();
|
||||
const conn = await svc.resolveConnection("npub1anything");
|
||||
assert.ok(conn);
|
||||
assert.equal(conn.provider, "backblaze_b2");
|
||||
});
|
||||
|
||||
it("returns null for empty / non-key input", async () => {
|
||||
const svc = new R2Service();
|
||||
assert.equal(await svc.resolveConnection(""), null);
|
||||
});
|
||||
});
|
||||
@@ -903,12 +903,25 @@ Reported 2026-06-25. Relates to #17 (NIP-71 interop) / the bitvid→NIP-71 mirro
|
||||
the export carries both). Storage + profile-modal suites 69/69; build + lint clean.
|
||||
|
||||
### 39. Image uploads should prefer uploading to configured storage (UX)
|
||||
- [ ] Anywhere the user adds an image, prefer letting them **upload an image file to
|
||||
their configured storage** (Cloudflare R2 / S3 / B2 / Blossom) and auto-grab the
|
||||
resulting URL — rather than pasting a link. First target: the **profile image +
|
||||
banner** in Edit Profile (upload → store → auto-fill the URL). Reuse the existing
|
||||
upload pipeline (`s3UploadService` / thumbnail upload) and apply the same pattern
|
||||
to other image inputs (thumbnails already do some of this).
|
||||
- [x] **Profile image + banner upload — FIXED 2026-06-30.** The UI was already wired
|
||||
(`ProfileEditController.handleUpload`: resolve connection → `uploadFile` → auto-fill
|
||||
the URL field), but it was **broken**: it passed a **hex** pubkey to
|
||||
`r2Service.resolveConnection()`, which only decoded `npub1…` strings
|
||||
(`safeDecodeNpub`) → hex resolved to `null` → "Storage configuration missing" and the
|
||||
Upload button stayed disabled even with storage configured. (Thumbnails worked
|
||||
because `mediaUploader` uses `listConnections(pubkey)` directly.)
|
||||
**Fix:** `resolveConnection` now accepts a hex pubkey OR an npub. The rest of the
|
||||
chain is already provider-agnostic — `resolveConnection` returns the
|
||||
`defaultForUploads` connection (any provider) with its `endpoint`/`baseDomain`/
|
||||
`forcePathStyle`, `uploadFile` routes R2 vs generic-S3 by provider, and
|
||||
`buildPublicUrl` builds the right URL — so this now works for R2 / B2 / Custom S3.
|
||||
Test: `tests/r2-resolve-connection-hex.test.mjs` (3, with `nip19.decode` throwing on
|
||||
hex to prove the hex path, not npub decoding). Build + lint clean.
|
||||
- [ ] **VERIFY live:** unlock storage, Edit Profile → upload a picture/banner file →
|
||||
uploads to the active provider's bucket and the URL auto-fills.
|
||||
- [ ] **Extend the pattern** to any remaining image-URL inputs that still only accept a
|
||||
pasted link (video thumbnail in upload/edit already uploads via `mediaUploader`).
|
||||
Audit for others (e.g. future channel/branding image fields) and reuse the helper.
|
||||
|
||||
## Open — lower priority / infra
|
||||
|
||||
|
||||
Reference in new issue
Block a user