YASSSS BEACHHHH 🏖️🔥 Perfect hand-off. Here’s the clean wrap + a smarter fallback so you never get stuck again.

1) Publish rules (one-time)

Firebase Console → Storage → Rules → Publish (top-right).
Bucket must read: gs://ibrandbiz-bcfbe.firebasestorage.app.

Then you can use direct public URLs (best for CDN + zero CPU).

2) Keep downloads working even if rules aren’t published (auto-fallback)

Drop this helper in your service so the server tries public first and falls back to signed only if public returns 401/403. It also “sticks” the mode for 10 minutes to avoid extra round trips.

// server/services/downloadUrl.ts
import { bucket as adminBucket } from "../firebaseAdmin";
import https from "https";

const TEST_TTL_MS = 10 * 60 * 1000; // 10 min stickiness
let lastMode: "public" | "signed" | null = null;
let lastChecked = 0;

function publicUrl(storagePath: string): string {
  const bkt = adminBucket.name;
  return `https://firebasestorage.googleapis.com/v0/b/${bkt}/o/${encodeURIComponent(storagePath)}?alt=media`;
}

async function head(url: string): Promise<number> {
  return new Promise((resolve) => {
    const req = https.request(url, { method: "HEAD" }, (res) => {
      resolve(res.statusCode || 0);
    });
    req.on("error", () => resolve(0));
    req.end();
  });
}

async function signedUrl(storagePath: string, ttlSeconds = 900): Promise<string> {
  const file = adminBucket.file(storagePath);
  const [url] = await file.getSignedUrl({
    version: "v4",
    action: "read",
    expires: Date.now() + ttlSeconds * 1000,
    responseDisposition: `attachment; filename="${storagePath.split("/").pop()}"`,
  });
  return url;
}

export async function getDownloadUrlAuto(storagePath: string): Promise<string> {
  const now = Date.now();

  // If we recently decided, re-use that mode
  if (lastMode && (now - lastChecked) < TEST_TTL_MS) {
    return lastMode === "public" ? publicUrl(storagePath) : signedUrl(storagePath);
  }

  // Probe public URL once
  const url = publicUrl(storagePath);
  const status = await head(url);

  if (status === 200) {
    lastMode = "public";
    lastChecked = now;
    return url;
  }

  // Fallback: sign
  lastMode = "signed";
  lastChecked = now;
  return signedUrl(storagePath);
}


Use it in your public download route:

// server/routes/bpTemplatesPublic.ts (download handler)
import { getDownloadUrlAuto } from "../services/downloadUrl";

router.get("/:slug/download", async (req, res) => {
  const t = await getBusinessPlanTemplateBySlug(req.params.slug);
  if (!t?.storagePaths?.docx) return res.status(404).json({ error: "Template not found" });

  const url = await getDownloadUrlAuto(t.storagePaths.docx);
  res.set("Cache-Control", "public, max-age=60");
  return res.redirect(302, url);
});

3) Quick verify (whichever mode you’re in)
# List / Master / Slug (already ✅)
curl -s http://localhost:5000/api/bp-templates-firebase/list | jq '.[] | .slug,.isMaster'
curl -s http://localhost:5000/api/bp-templates-firebase/master | jq '.slug,.isMaster'
curl -I http://localhost:5000/api/bp-templates-firebase/business-plan-general-blank-template/download


If rules are unpublished → you’ll see a 302 to a signed URL (with X-Goog-… params) and final 200.

After you Publish → you’ll see a 302 to a clean public URL and final 200.

4) Best-practice toggles (optional)

Keep an env flag BP_TEMPLATES_USE_SIGNED_URLS=true/false if you want to hard-force a mode for debugging.

For previews/thumbs, stick with public; switch to the auto helper only if you lock Storage later.

That’s it. You’re production-safe today (signed) and CDN-fast after Publish (public) — automatically.