Mirror pipeline creatives into each client's Drive folder tree.
On upload, create Pipeline-creatividades_{slug}/YYYY/MM under the linked client folder and name files DD-MM-YYYY_formato_hook[-N].
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,15 +9,20 @@ import {
|
||||
sanitizeFileName,
|
||||
serviceSupabase,
|
||||
} from '@/lib/assets';
|
||||
import { syncPipelineAssetToDrive } from '@/lib/pipeline-drive';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
export const runtime = 'nodejs';
|
||||
export const maxDuration = 90;
|
||||
|
||||
const ALLOWED_MIME = new Set(['image/jpeg', 'image/png', 'image/webp']);
|
||||
|
||||
/**
|
||||
* POST multipart: sube portada o slide de una pieza del pipeline.
|
||||
* fields: file, itemId (requerido), slot ('cover' | 'slide'), index? (para slide, 0-based)
|
||||
*
|
||||
* También espeja el archivo en Drive del cliente:
|
||||
* {carpeta-cliente}/Pipeline-creatividades_{slug}/YYYY/MM/DD-MM-YYYY_formato_hook[-N].ext
|
||||
*/
|
||||
export async function POST(req: NextRequest) {
|
||||
const supabase = await createClient();
|
||||
@@ -36,13 +41,16 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
const ctx = await resolveActiveTenant(user);
|
||||
if (!ctx.tenantId || ctx.portfolioWide) {
|
||||
return NextResponse.json({ error: 'Selecciona un cliente en Contexto' }, { status: 400 });
|
||||
return NextResponse.json(
|
||||
{ error: 'Selecciona un cliente en Contexto' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const form = await req.formData();
|
||||
const file = form.get('file');
|
||||
const itemId = String(form.get('itemId') || '').trim();
|
||||
const slot = String(form.get('slot') || 'cover').trim();
|
||||
const slot = String(form.get('slot') || 'cover').trim() as 'cover' | 'slide';
|
||||
const index = Math.max(0, parseInt(String(form.get('index') || '0'), 10) || 0);
|
||||
|
||||
if (!(file instanceof File)) {
|
||||
@@ -80,19 +88,32 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: 'Service role required' }, { status: 503 });
|
||||
}
|
||||
|
||||
// Verifica que la pieza existe y pertenece al tenant activo.
|
||||
const curRes = await fetch(
|
||||
`${supabaseUrl()}/rest/v1/creative_pipeline?id=eq.${encodeURIComponent(
|
||||
itemId
|
||||
)}&tenant_id=eq.${encodeURIComponent(ctx.tenantId)}&select=id,slide_paths`,
|
||||
)}&tenant_id=eq.${encodeURIComponent(
|
||||
ctx.tenantId
|
||||
)}&select=id,title,format,caption,notes,scheduled_at,created_at,slide_paths`,
|
||||
{ headers, cache: 'no-store' }
|
||||
);
|
||||
const curRows = curRes.ok ? await curRes.json() : [];
|
||||
if (!curRows?.[0]) {
|
||||
return NextResponse.json({ error: 'Pieza no encontrada' }, { status: 404 });
|
||||
}
|
||||
const item = curRows[0] as {
|
||||
id: string;
|
||||
title?: string | null;
|
||||
format?: string | null;
|
||||
caption?: string | null;
|
||||
notes?: string | null;
|
||||
scheduled_at?: string | null;
|
||||
created_at?: string | null;
|
||||
slide_paths?: string[];
|
||||
};
|
||||
|
||||
const ext = (sanitizeFileName(file.name || 'img.jpg').split('.').pop() || 'jpg').toLowerCase();
|
||||
const ext = (
|
||||
sanitizeFileName(file.name || 'img.jpg').split('.').pop() || 'jpg'
|
||||
).toLowerCase();
|
||||
const fileName = slot === 'cover' ? `cover.${ext}` : `slide-${index + 1}.${ext}`;
|
||||
const storagePath = `${ctx.tenantId}/pipeline/${itemId}/${fileName}`;
|
||||
const buf = Buffer.from(await file.arrayBuffer());
|
||||
@@ -110,8 +131,8 @@ export async function POST(req: NextRequest) {
|
||||
patch.cover_path = storagePath;
|
||||
patch.cover_mime = mime;
|
||||
} else {
|
||||
const slides: string[] = Array.isArray(curRows[0].slide_paths)
|
||||
? [...curRows[0].slide_paths]
|
||||
const slides: string[] = Array.isArray(item.slide_paths)
|
||||
? [...item.slide_paths]
|
||||
: [];
|
||||
slides[index] = storagePath;
|
||||
patch.slide_paths = slides;
|
||||
@@ -134,8 +155,28 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: await patchRes.text() }, { status: 500 });
|
||||
}
|
||||
const data = await patchRes.json();
|
||||
|
||||
const drive = await syncPipelineAssetToDrive({
|
||||
tenantId: ctx.tenantId,
|
||||
clientLabel: ctx.label,
|
||||
item,
|
||||
buffer: buf,
|
||||
mimeType: mime,
|
||||
ext,
|
||||
slot: slot === 'slide' ? 'slide' : 'cover',
|
||||
slideIndex: index,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
item: Array.isArray(data) ? data[0] : data,
|
||||
storage_path: storagePath,
|
||||
drive: {
|
||||
ok: drive.ok,
|
||||
skipped: drive.skipped || false,
|
||||
path: drive.drivePath || null,
|
||||
fileName: drive.fileName || null,
|
||||
fileId: drive.driveFileId || null,
|
||||
warning: drive.warning || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user