Here’s the exact “SN fix pack” with what to change and where. This locks the mockups upload + preview back in.

Files to review (and the precise fixes)

server/routes.ts
Remove the temporary compatibility alias that overrides the real mockups endpoint and returns an empty list. Delete this block:
app.get('/api/stock/mockups', … res.json({ mockups: [] })).
(We already mount /api/mockups and /api/stock above it, so this alias steals the route.)

client/src/pages/business-assets/stock/Mockups.tsx
Make the list call hit the canonical route:
Change fetch("/api/stock/mockups") → fetch("/api/mockups/list").
(Our mockup router serves /api/mockups/list with correct url/previewUrl values.)

server/routes/mockupLibraryRoutes.js (keep this as source-of-truth for mockups)

✅ Already good: it returns each mockup with previewUrl = /api/mockups/:id/preview and implements that preview route.

✅ Download route enforces license/quota and streams original or preview.

✅ Upload route watermarks previews and saves locally if cloud fails.

server/routes/stockLibraryRoutes.js (clean up duplicates and the multer bug)

A. Remove/disable the duplicate list for /api/stock/mockups or make it proxy to /api/mockups/list. Right now it publishes previewUrl: /api/stock/:id/mockup-preview (a route we don’t serve). That’s why you saw broken images. If you keep it for compatibility, change the URLs it emits to /api/mockups/:id/preview.

B. Fix the multer mismatch in the POST /api/stock/mockups handler. It uses upload.array("files") but then references req.file.* (singular) throughout. Replace with something like:

router.post("/mockups", upload.array("files", 10), async (req, res) => {
  try {
    if (!req.files?.length) return res.status(400).json({ error: "No files uploaded" });
    const results = [];
    for (const f of req.files) {
      const dbRow = await insertStockDBRecord({
        originalName: f.originalname,
        mimeType: f.mimetype,
        uploaderId: req.user?.id || "admin",
      });
      const tenDigit = toTenDigitCode(dbRow.id);

      // ORIGINAL → object storage (best effort)
      const originalBuf = await fs.readFile(f.path);
      const objectStorage = new ObjectStorageService();
      const publicPaths = objectStorage.getPublicObjectSearchPaths();
      const originalPath = `${publicPaths[0]}/mockups/original/${dbRow.id}/${f.filename}`;
      try { await objectStorage.uploadObject(originalPath, originalBuf); } catch {}

      // PREVIEW (watermark) → public
      const wmBuf = await applyWatermark(originalBuf, { stockSeed: dbRow.id, opacity: 0.15 });
      const previewPath = `${publicPaths[0]}/mockups/preview/${dbRow.id}/${f.filename}`;
      try {
        await objectStorage.uploadObject(previewPath, wmBuf);
      } catch {
        const localPreviewDir = path.join(process.cwd(), "uploads", "mockup-previews", dbRow.id);
        await fs.mkdir(localPreviewDir, { recursive: true });
        await fs.writeFile(path.join(localPreviewDir, f.filename), wmBuf);
      }

      await fs.unlink(f.path).catch(() => {});
      await saveMockupToDB({ id: dbRow.id, name: f.originalname, filename: f.filename, mimeType: f.mimetype, uploadedAt: new Date().toISOString(), uploaderId: req.user?.id || "admin" });

      results.push({
        id: dbRow.id,
        code: tenDigit,
        name: f.originalname,
        mimeType: f.mimetype,
        previewUrl: `/api/mockups/${dbRow.id}/preview`,
      });
    }
    res.json({ uploaded: results });
  } catch (err) {
    console.error("Mockup upload error:", err);
    res.status(500).json({ error: "Upload failed" });
  }
});


(Optional) If you keep a mockups list under /api/stock/mockups
Make it emit the mockups preview URL that actually exists (/api/mockups/:id/preview) instead of /api/stock/:id/mockup-preview—or just remove this list entirely so there’s one canonical list at /api/mockups/list.

Why this breaks & why this fixes it (super quick)

The alias in routes.ts was returning {mockups: []} and masking the real list.

The FE was requesting /api/stock/mockups instead of the canonical /api/mockups/list.

The preview URL coming from the stock router pointed at a route that doesn’t exist (/api/stock/:id/mockup-preview), while the existing preview route lives at /api/mockups/:id/preview.

The upload handler declared array("files") but read req.file everywhere, so multer blew up or saved nothing.

Do these, and you’ll get: uploads succeed (with watermark), list shows real items, and card images render from the working preview route.