-
Notifications
You must be signed in to change notification settings - Fork 8
feat(storage): consolidate GCS uploads into @acme/storage package #469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
054015e
474c12f
b534a7e
9eb0e2b
9ef0ab2
44e972f
1a367c4
6e84c51
eef518c
3af08a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,7 +49,6 @@ ENV_FILE_VARS=( | |
| F3_CHANNEL | ||
| OAUTH_CLIENT_ID | ||
| OAUTH_REDIRECT_URI | ||
| GCS_BUCKET | ||
| ) | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import "server-only"; | ||
| import { createPublicImageStorage } from "@acme/storage"; | ||
| import { env } from "~/env"; | ||
|
|
||
| function deriveStorageChannel(channel: string): "staging" | "prod" { | ||
| return channel === "prod" ? "prod" : "staging"; | ||
| } | ||
|
|
||
| export const storage = createPublicImageStorage({ | ||
| channel: deriveStorageChannel(env.F3_CHANNEL), | ||
| credentials: env.GCS_CREDENTIALS, | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { describe, expect, it, vi } from "vitest"; | ||
|
|
||
| describe("map storage bootstrap", () => { | ||
| it("uses prod storage channel when F3_CHANNEL is prod", async () => { | ||
| vi.resetModules(); | ||
|
|
||
| const createPublicImageStorage = vi.fn(() => ({ | ||
| isAllowedPublicImageUrl: () => true, | ||
| })); | ||
|
|
||
| vi.doMock("@acme/storage", () => ({ | ||
| createPublicImageStorage, | ||
| })); | ||
|
|
||
| vi.doMock("~/env", () => ({ | ||
| env: { | ||
| F3_CHANNEL: "prod", | ||
| GCS_CREDENTIALS: "cred-prod", | ||
| }, | ||
| })); | ||
|
|
||
| await import("~/lib/storage"); | ||
|
|
||
| expect(createPublicImageStorage).toHaveBeenCalledWith({ | ||
| channel: "prod", | ||
| credentials: "cred-prod", | ||
| }); | ||
| }); | ||
|
|
||
| it("falls back to staging for non-prod channels", async () => { | ||
| vi.resetModules(); | ||
|
|
||
| const createPublicImageStorage = vi.fn(() => ({ | ||
| isAllowedPublicImageUrl: () => true, | ||
| })); | ||
|
|
||
| vi.doMock("@acme/storage", () => ({ | ||
| createPublicImageStorage, | ||
| })); | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [tests] |
||
| vi.doMock("~/env", () => ({ | ||
| env: { | ||
| F3_CHANNEL: "preview", | ||
| GCS_CREDENTIALS: "cred-staging", | ||
| }, | ||
| })); | ||
|
|
||
| await import("~/lib/storage"); | ||
|
|
||
| expect(createPublicImageStorage).toHaveBeenCalledWith({ | ||
| channel: "staging", | ||
| credentials: "cred-staging", | ||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import { describe, expect, it, vi, afterEach } from "vitest"; | ||
|
|
||
| import { uploadLogo } from "~/utils/image/upload-logo"; | ||
|
|
||
| afterEach(() => { | ||
| vi.restoreAllMocks(); | ||
| }); | ||
|
Comment on lines
+5
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify whether stubbed globals are being reverted globally or per-test.
rg -n --type=ts '\bvi\.stubGlobal\s*\('
rg -n --type=ts '\bvi\.unstubAllGlobals\s*\('
rg -n --type=ts 'unstubGlobals\s*:\s*true'Repository: F3-Nation/f3-nation Length of output: 1260 🏁 Script executed: cat -n apps/map/__tests__/utils/image/upload-logo.test.tsRepository: F3-Nation/f3-nation Length of output: 2367 🏁 Script executed: # Find vitest config files
fd -e 'config.ts' -e 'vitest.config.ts' -e 'vitest.config.js'Repository: F3-Nation/f3-nation Length of output: 662 🏁 Script executed: cat -n apps/map/vitest.config.tsRepository: F3-Nation/f3-nation Length of output: 1998 🏁 Script executed: fd -type f -name 'vitest-config' packages/ && find packages -name '*vitest*' -type f | head -20Repository: F3-Nation/f3-nation Length of output: 234 🏁 Script executed: find packages -name '*vitest*' | head -20Repository: F3-Nation/f3-nation Length of output: 349 🏁 Script executed: rg -l '`@acme/vitest-config`' packages/Repository: F3-Nation/f3-nation Length of output: 45 🏁 Script executed: rg '`@acme/vitest-config`' --type ts --type tsxRepository: F3-Nation/f3-nation Length of output: 91 🏁 Script executed: rg '`@acme/vitest-config`' --type tsRepository: F3-Nation/f3-nation Length of output: 305 🏁 Script executed: find . -path ./node_modules -prune -o -name 'package.json' -type f -exec grep -l '`@acme/vitest-config`' {} \;Repository: F3-Nation/f3-nation Length of output: 164 🏁 Script executed: cat -n tooling/vitest/package.json && echo "---" && ls -la tooling/vitest/ && echo "---" && find tooling/vitest -type f -name '*.ts' -o -name 'index.js'Repository: F3-Nation/f3-nation Length of output: 1404 🏁 Script executed: cat -n tooling/vitest/coverage.tsRepository: F3-Nation/f3-nation Length of output: 1285 Add
Affected test cases
🤖 Prompt for AI Agents |
||
|
|
||
| describe("uploadLogo", () => { | ||
| it("returns url on successful upload", async () => { | ||
| vi.stubGlobal( | ||
| "fetch", | ||
| vi.fn(() => | ||
| Promise.resolve( | ||
| new Response( | ||
| JSON.stringify({ url: "https://example.com/logo.jpg" }), | ||
| { | ||
| status: 200, | ||
| headers: { "Content-Type": "application/json" }, | ||
| }, | ||
| ), | ||
| ), | ||
| ), | ||
| ); | ||
|
|
||
| const result = await uploadLogo({ | ||
| file: new Blob(["abc"], { type: "image/png" }), | ||
| orgId: 42, | ||
| }); | ||
|
|
||
| expect(result).toBe("https://example.com/logo.jpg"); | ||
| }); | ||
|
|
||
| it("throws API-provided error message on failed upload", async () => { | ||
| vi.stubGlobal( | ||
| "fetch", | ||
| vi.fn(() => | ||
| Promise.resolve( | ||
| new Response(JSON.stringify({ error: "Upload denied" }), { | ||
| status: 400, | ||
| headers: { "Content-Type": "application/json" }, | ||
| }), | ||
| ), | ||
| ), | ||
| ); | ||
|
|
||
| await expect( | ||
| uploadLogo({ | ||
| file: new Blob(["abc"], { type: "image/png" }), | ||
| orgId: 42, | ||
| }), | ||
| ).rejects.toThrow("Upload denied"); | ||
| }); | ||
|
|
||
| it("throws fallback error when failed upload body is unreadable", async () => { | ||
| vi.stubGlobal( | ||
| "fetch", | ||
| vi.fn(() => | ||
| Promise.resolve( | ||
| new Response("not json", { | ||
| status: 500, | ||
| headers: { "Content-Type": "text/plain" }, | ||
| }), | ||
| ), | ||
| ), | ||
| ); | ||
|
|
||
| await expect( | ||
| uploadLogo({ | ||
| file: new Blob(["abc"], { type: "image/png" }), | ||
| orgId: 42, | ||
| }), | ||
| ).rejects.toThrow("Failed to upload logo"); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[cleanup] The three
GOOGLE_LOGO_BUCKET_*vars just above are now removed frompackages/envand every appenv.ts, so they're dead config here. Safe to delete lines 36–38.