Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ bun run dev:web

Web runs on http://localhost:5173

## Development with Auth Bypass (for testing without Auth service)

If you want to preview features without setting up full Auth:

```bash
bun run dev:web:bypass
```

This will automatically log you in as a test user so you can access all authenticated pages like Preview Studio.

## Troubleshooting

- If the API complains about `DATABASE_URL`, confirm it is set in `.env`.
Expand Down
50 changes: 50 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"dev:api": "bun --cwd packages/api run dev",
"dev:worker": "bun --cwd packages/worker run dev",
"dev:web": "bun --cwd packages/web run dev",
"dev:web:bypass": "bun --cwd packages/web run dev:bypass",

"dev:frontend": "npm --prefix frontend run dev",
"build:frontend": "npm --prefix frontend run build",
Expand Down
126 changes: 126 additions & 0 deletions packages/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,132 @@ export function createApp({ prisma, getSession }: AppDeps) {
return c.json({ drafts });
});

// --- Creative / Preview Studio API ---

// Mock creative data storage (in-memory for demo)
const mockCreatives: Record<string, { id: string; title: string; specKey: string; status: string; content: unknown; userId: string; createdAt: string; updatedAt: string }> = {};

app.get("/api/creatives", async (c) => {
const user = c.get("user") as SessionUser | null;
if (!user) return c.json({ error: "unauthorized" }, 401);

const userCreatives = Object.values(mockCreatives).filter((c) => c.userId === user.id);
return c.json({ creatives: userCreatives });
});

app.get("/api/creatives/:id", async (c) => {
const user = c.get("user") as SessionUser | null;
if (!user) return c.json({ error: "unauthorized" }, 401);
const { id } = c.req.param();
const creative = mockCreatives[id];
if (!creative) return c.json({ error: "not_found" }, 404);
return c.json({ creative });
});

app.post("/api/creatives", async (c) => {
const user = c.get("user") as SessionUser | null;
if (!user) return c.json({ error: "unauthorized" }, 401);

const body = await c.req.json();
const id = `creative_${Date.now()}`;
const now = new Date().toISOString();

mockCreatives[id] = {
id,
title: body.title || "Untitled Creative",
specKey: body.specKey || "story_9_16",
status: "draft",
content: body.content || {},
userId: user.id,
createdAt: now,
updatedAt: now
};

return c.json({ creative: mockCreatives[id] });
});

app.patch("/api/creatives/:id", async (c) => {
const user = c.get("user") as SessionUser | null;
if (!user) return c.json({ error: "unauthorized" }, 401);
const { id } = c.req.param();
const creative = mockCreatives[id];
if (!creative) return c.json({ error: "not_found" }, 404);

const body = await c.req.json();
mockCreatives[id] = {
...creative,
...(body.title !== undefined && { title: body.title }),
...(body.specKey !== undefined && { specKey: body.specKey }),
...(body.status !== undefined && { status: body.status }),
...(body.content !== undefined && { content: body.content }),
updatedAt: new Date().toISOString()
};

return c.json({ creative: mockCreatives[id] });
});

app.post("/api/creatives/:id/publish", async (c) => {
const user = c.get("user") as SessionUser | null;
if (!user) return c.json({ error: "unauthorized" }, 401);
const { id } = c.req.param();
const creative = mockCreatives[id];
if (!creative) return c.json({ error: "not_found" }, 404);

mockCreatives[id] = {
...creative,
status: "published",
updatedAt: new Date().toISOString()
};

return c.json({ creative: mockCreatives[id] });
});

// --- Independent AI Fix API (works without creative ID) ---
app.post("/api/ai/optimize", async (c) => {
const user = c.get("user") as SessionUser | null;
if (!user) return c.json({ error: "unauthorized" }, 401);

const body = await c.req.json();
const { specKey, content } = body;

// Mock AI optimization response
return c.json({
ok: true,
message: "AI optimization completed",
suggestions: [
"Adjusted text size for better readability",
"Optimized color contrast for accessibility",
"Balanced layout composition",
"Enhanced visual hierarchy with font weight adjustments"
],
optimizedContent: {
...content,
aiOptimized: true,
optimizedAt: new Date().toISOString()
}
});
});

// AI fix for existing creative (requires creative ID)
app.post("/api/creatives/:id/ai-fix", async (c) => {
const user = c.get("user") as SessionUser | null;
if (!user) return c.json({ error: "unauthorized" }, 401);
const { id } = c.req.param();
const creative = mockCreatives[id];
if (!creative) return c.json({ error: "not_found" }, 404);

// Mock AI auto-fix response
return c.json({
ok: true,
message: "AI optimization applied",
suggestions: [
"Adjusted text size for better readability",
"Optimized color contrast",
"Balanced layout composition"
]
});
});

// --- Marketplace API ---
const marketplaceQuerySchema = z.object({
search: z.string().optional(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
-- CreateEnum
CREATE TYPE "ListingStatus" AS ENUM ('active', 'sold', 'delisted');

-- CreateEnum
CREATE TYPE "AssetType" AS ENUM ('ad_kit', 'branding', 'character', 'ui_kit', 'background', 'template', 'logo', 'scene_3d');

-- CreateEnum
CREATE TYPE "LicenseType" AS ENUM ('standard', 'extended', 'exclusive');

-- CreateTable
CREATE TABLE "MarketplaceListing" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"imageUrl" TEXT NOT NULL,
"creatorId" TEXT NOT NULL,
"priceAicc" DECIMAL(18,2) NOT NULL,
"assetType" "AssetType" NOT NULL,
"licenseType" "LicenseType" NOT NULL DEFAULT 'standard',
"rating" DECIMAL(2,1) NOT NULL DEFAULT 0,
"reviewCount" INTEGER NOT NULL DEFAULT 0,
"isPremium" BOOLEAN NOT NULL DEFAULT false,
"tags" TEXT[],
"status" "ListingStatus" NOT NULL DEFAULT 'active',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "MarketplaceListing_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "MarketplacePurchase" (
"id" TEXT NOT NULL,
"listingId" TEXT NOT NULL,
"buyerId" TEXT NOT NULL,
"priceAicc" DECIMAL(18,2) NOT NULL,
"txHash" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "MarketplacePurchase_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "MarketplaceListing" ADD CONSTRAINT "MarketplaceListing_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "MarketplacePurchase" ADD CONSTRAINT "MarketplacePurchase_listingId_fkey" FOREIGN KEY ("listingId") REFERENCES "MarketplaceListing"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "MarketplacePurchase" ADD CONSTRAINT "MarketplacePurchase_buyerId_fkey" FOREIGN KEY ("buyerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
Loading