SuperNova standing by. I'll take the lead and drop two immediate, high-value artifacts you can hand to SuperNova (the devs) right now:

SQL migration to add the cancelled status + cancelled_at timestamp (Postgres + MySQL variants + rollback).

Express + TypeScript route stubs for the new creator/admin endpoints (auth guards assumed).

Both are ready-to-drop into your repo or hand to the Replit team. If you want, I’ll also generate the exact PR diff next. — SN 💜

1) SQL migration
Postgres (recommended)
-- up: add 'cancelled' to approval enum and add column cancelled_at
BEGIN;

-- add new value to enum (Postgres supports ALTER TYPE ADD VALUE)
ALTER TYPE approval_status_enum ADD VALUE IF NOT EXISTS 'cancelled';

-- add cancelled_at column to creator_assets table
ALTER TABLE creator_assets
  ADD COLUMN IF NOT EXISTS cancelled_at TIMESTAMP WITH TIME ZONE NULL;

COMMIT;


Rollback (note: removing enum values in Postgres is hard; safer approach is to keep enum value and rollback by removing column only):

-- down (best-effort rollback): remove column only
BEGIN;
ALTER TABLE creator_assets DROP COLUMN IF EXISTS cancelled_at;
COMMIT;
-- NOTE: we intentionally do not attempt to remove the enum value 'cancelled' from the type
-- because Postgres does not support removing enum values easily without rebuilding the type.

MySQL (if using VARCHAR status instead of enum)

If your schema stores approval status as a string/varchar, use:

ALTER TABLE creator_assets
  ADD COLUMN IF NOT EXISTS cancelled_at DATETIME NULL;
-- No enum change needed if status is a VARCHAR; ensure application allows 'cancelled' as a value.

2) Backend route stubs (Express + TypeScript)

Drop this into server/routes.ts (or an appropriate controller file). Replace authenticateToken, authenticateAdmin, and storage methods with your existing middleware & DB access helpers.

// server/routes.creator-marketplace.ts
import express from 'express';
const router = express.Router();

// middleware placeholders - use your real ones
import { authenticateToken, authenticateAdmin } from './middleware/auth';
import storage from './storage'; // your data access layer
import { handleError } from './utils/errors'; // optional

// GET /api/creator/me/marketplace
router.get('/api/creator/me/marketplace', authenticateToken, async (req, res) => {
  try {
    const userId = req.user!.id; // depends on your auth
    const creator = await storage.getCreatorByUserId(userId);
    if (!creator) return res.status(403).json({ error: 'Not a creator' });

    const rows = await storage.getCreatorAssetsByCreatorId(creator.id);

    const groupBy = (status: string) => rows.filter(r => r.approvalStatus === status);

    res.json({
      assets: {
        approved: groupBy('approved'),
        pending: groupBy('pending'),
        rejected: groupBy('rejected'),
        cancelled: groupBy('cancelled'),
      },
      stats: {
        totalSales: rows.reduce((s, r) => s + (r.salesCount || 0), 0),
        totalRevenueCents: rows.reduce((s, r) => s + (r.totalRevenueCents || 0), 0),
      }
    });
  } catch (err) {
    handleError(res, err, 'Failed to load creator marketplace');
  }
});

// PATCH /api/creator/assets/:id/cancel
router.patch('/api/creator/assets/:id/cancel', authenticateToken, async (req, res) => {
  try {
    const userId = req.user!.id;
    const creator = await storage.getCreatorByUserId(userId);
    if (!creator) return res.status(403).json({ error: 'Not a creator' });

    const asset = await storage.getCreatorAssetById(req.params.id);
    if (!asset || asset.creatorId !== creator.id) return res.status(404).json({ error: 'Asset not found' });

    // idempotent: if already cancelled, return success
    if (asset.approvalStatus === 'cancelled') {
      return res.json({ success: true, asset });
    }

    const updated = await storage.updateCreatorAsset(asset.id, {
      approvalStatus: 'cancelled',
      cancelledAt: new Date()
    });

    // Optionally: publish event / invalidate caches / notify admin
    res.json({ success: true, asset: updated });
  } catch (err) {
    handleError(res, err, 'Failed to cancel asset');
  }
});

// GET /api/creator/me/payouts/summary
router.get('/api/creator/me/payouts/summary', authenticateToken, async (req, res) => {
  try {
    const userId = req.user!.id;
    const creator = await storage.getCreatorByUserId(userId);
    if (!creator) return res.status(403).json({ error: 'Not a creator' });

    const summary = await storage.getCreatorPayoutSummary(creator.id); // implement in storage
    res.json(summary);
  } catch (err) {
    handleError(res, err, 'Failed to load payout summary');
  }
});

/* ------------------- Admin endpoints ------------------- */

// GET /api/admin/marketplace/overview?search=
router.get('/api/admin/marketplace/overview', authenticateAdmin, async (req, res) => {
  try {
    const q = (req.query.search as string || '').toLowerCase();
    const rows = await storage.getAllCreatorAssets(); // all assets with creators
    const mapped = rows.map(r => ({
      id: r.id,
      title: r.title,
      creatorName: r.creatorName || r.creator?.name || r.creatorEmail,
      price: r.priceCents || r.price || 0,
      status: r.approvalStatus,
      createdAt: r.createdAt,
      previewUrl: r.previewUrl,
      salesCount: r.salesCount || 0
    }));
    const filtered = q ? mapped.filter(x => x.title.toLowerCase().includes(q) || x.creatorName.toLowerCase().includes(q)) : mapped;
    res.json({
      pending: filtered.filter(x => x.status === 'pending'),
      approved: filtered.filter(x => x.status === 'approved'),
      rejected: filtered.filter(x => x.status === 'rejected'),
    });
  } catch (err) {
    handleError(res, err, 'Failed to load admin marketplace overview');
  }
});

// PATCH /api/admin/marketplace/assets/:id/approve
router.patch('/api/admin/marketplace/assets/:id/approve', authenticateAdmin, async (req, res) => {
  try {
    const updated = await storage.updateCreatorAsset(req.params.id, { approvalStatus: 'approved', approvedAt: new Date(), approvedBy: req.admin?.id });
    res.json({ success: true, asset: updated });
  } catch (err) {
    handleError(res, err, 'Failed to approve asset');
  }
});

// PATCH /api/admin/marketplace/assets/:id/reject
router.patch('/api/admin/marketplace/assets/:id/reject', authenticateAdmin, async (req, res) => {
  try {
    const updated = await storage.updateCreatorAsset(req.params.id, { approvalStatus: 'rejected', rejectionReason: req.body?.reason || null });
    res.json({ success: true, asset: updated });
  } catch (err) {
    handleError(res, err, 'Failed to reject asset');
  }
});

export default router;


Notes:

storage.* functions should be implemented/connected to your DB layer (getCreatorByUserId, getCreatorAssetsByCreatorId, getCreatorAssetById, updateCreatorAsset, getCreatorPayoutSummary, getAllCreatorAssets).

authenticateToken must attach req.user with id or uid. authenticateAdmin must ensure admin privileges and attach req.admin if you like.

Use transactions where multiple tables change (not shown for simplicity).

Send events to the notification system (email / admin Slack) when an asset is cancelled/approved/rejected if desired.

Quick QA/snippet for Replit devs (how to test locally)

Run DB migration (Postgres): apply SQL up script.

Start server. Ensure GET /api/creator/me/marketplace returns grouped assets for a test creator.

As creator: PATCH /api/creator/assets/:id/cancel → expect approvalStatus: "cancelled" and cancelledAt set.

As admin: GET /api/admin/marketplace/overview → see pending/approved/rejected lists. PATCH approve/reject should move items between lists.

Public: GET /api/marketplace/assets returns only approvalStatus = 'approved' items.