mirror of
https://github.com/Anil-matcha/Open-Generative-AI.git
synced 2026-05-07 01:17:18 +00:00
Slice 2 of the batch feature. Marketing team can now upload the six
trainer images and the studio image once and reuse them across every
future batch.
Backend
-------
- lib/muapiUpload.js: Node-side wrapper around POST
/api/v1/upload_file with FormData + x-api-key. Mirrors
packages/studio/src/muapi.js:131-178 but uses native fetch +
FormData (no XHR).
- lib/batchAuth.js: shared getApiKey() — header > cookie > env fallback.
- lib/localUploadStore.js: writes a local backup of every uploaded
asset to /data/uploads/{trainers,studios}/<id>.<ext> on the
uploads_data volume, so we can re-upload to MuAPI if their CDN
ever expires the URL.
- app/api/trainers/route.js + [id]/route.js: GET list, POST multipart
(uploads to MuAPI then persists), DELETE (refuses if any active job
references the row).
- app/api/studios/route.js + [id]/route.js: identical shape.
UI
--
- app/batch/page.js: leaf route mounting BatchShell.
- components/batch/BatchShell.jsx: 3-tab dark-theme shell
(Batches / Trainers / Studios) with the same MuAPI key gate as
StandaloneShell (reuses ApiKeyModal + the muapi_key cookie).
- components/batch/AssetLibrary.jsx + AddAssetModal.jsx: shared grid +
upload modal driving both tabs from one component.
- components/batch/TrainersTab.jsx, StudiosTab.jsx: thin wrappers.
- components/batch/BatchesTab.jsx: placeholder for slice 3.
Stub packages (upstream submodules unavailable)
-----------------------------------------------
The ai-agent and workflow-ui submodules referenced in .gitmodules
return 404 (Anil-matcha/workflow-ui and jaiprasad04/ai-agent are
deleted/private). next build couldn't resolve their imports.
- Removed the dead .gitmodules entries.
- Added local stubs at packages/ai-agent and packages/workflow-ui
that export no-op components rendering "feature unavailable" so
the build succeeds. The /agents/* and Workflows tab show that
notice; the studios our marketing team actually uses
(Image / Video / Lip Sync / Cinema) keep working since they
live in the studio package which we have.
Docker / dev workflow
---------------------
- Dockerfile: run prisma generate during the builder stage, copy
prisma/ and lib/ into the runner stage so migrations and shared
helpers are present at runtime.
- docker-compose.override.yml (new): dev-mode overrides — mounts
source, runs as root to dodge node_modules permission issues with
the prod nextjs user, runs prisma generate + migrate deploy +
next dev. Worker container is a placeholder until slice 4 lands.
- The full stack (postgres + web + worker) now boots with
`docker compose up -d` and serves /batch on http://localhost:3000.
70 lines
2 KiB
JavaScript
70 lines
2 KiB
JavaScript
import { NextResponse } from 'next/server';
|
|
import prisma from '@/lib/prisma';
|
|
import { getApiKey } from '@/lib/batchAuth';
|
|
import { uploadFileToMuapi } from '@/lib/muapiUpload';
|
|
import { saveLocalBackup } from '@/lib/localUploadStore';
|
|
|
|
export async function GET() {
|
|
const studios = await prisma.studio.findMany({
|
|
orderBy: { createdAt: 'asc' },
|
|
});
|
|
return NextResponse.json({ studios });
|
|
}
|
|
|
|
export async function POST(request) {
|
|
const apiKey = getApiKey(request);
|
|
if (!apiKey) {
|
|
return NextResponse.json(
|
|
{ error: 'MuAPI API key is required. Set it in /studio or pass x-api-key.' },
|
|
{ status: 401 },
|
|
);
|
|
}
|
|
|
|
let form;
|
|
try {
|
|
form = await request.formData();
|
|
} catch {
|
|
return NextResponse.json({ error: 'Invalid multipart body' }, { status: 400 });
|
|
}
|
|
|
|
const name = (form.get('name') || '').toString().trim();
|
|
const csvLabel = (form.get('csvLabel') || '').toString().trim() || null;
|
|
const file = form.get('image');
|
|
|
|
if (!name) return NextResponse.json({ error: 'name is required' }, { status: 400 });
|
|
if (!file || typeof file === 'string') {
|
|
return NextResponse.json({ error: 'image file is required' }, { status: 400 });
|
|
}
|
|
|
|
if (csvLabel) {
|
|
const existing = await prisma.studio.findUnique({ where: { csvLabel } });
|
|
if (existing) {
|
|
return NextResponse.json(
|
|
{ error: `csvLabel "${csvLabel}" is already used by studio "${existing.name}"` },
|
|
{ status: 409 },
|
|
);
|
|
}
|
|
}
|
|
|
|
let muapiUrl;
|
|
try {
|
|
muapiUrl = await uploadFileToMuapi(apiKey, file);
|
|
} catch (err) {
|
|
return NextResponse.json({ error: err.message }, { status: 502 });
|
|
}
|
|
|
|
const studio = await prisma.studio.create({
|
|
data: { name, csvLabel, imageUrl: muapiUrl },
|
|
});
|
|
|
|
const localPath = await saveLocalBackup('studios', studio.id, file);
|
|
if (localPath) {
|
|
await prisma.studio.update({
|
|
where: { id: studio.id },
|
|
data: { localPath },
|
|
});
|
|
studio.localPath = localPath;
|
|
}
|
|
|
|
return NextResponse.json({ studio }, { status: 201 });
|
|
}
|