Spaces:
Build error
Build error
Gmagl commited on
Commit 路
5cff373
1
Parent(s): 30ad7ef
Fix: Complete TypeScript strict typing for all API routes
Browse files- src/app/api/agent/route.ts +21 -101
- src/app/api/analyze/route.ts +30 -208
- src/app/api/monetization/route.ts +36 -341
- src/app/api/posts/route.ts +37 -275
- src/app/api/projects/route.ts +42 -52
- src/app/api/prompt-engineer/route.ts +29 -193
- src/app/api/repos/route.ts +11 -107
src/app/api/agent/route.ts
CHANGED
|
@@ -2,121 +2,41 @@ import { NextRequest, NextResponse } from "next/server";
|
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
import ZAI from "z-ai-web-dev-sdk";
|
| 4 |
|
| 5 |
-
const SYSTEM_PROMPT = `Eres Sof铆a, un asistente de desarrollo de software altamente inteligente y especializado. Tu nombre es Sof铆a y eres parte del sistema multiagente "Sof铆a Cloud".
|
| 6 |
-
|
| 7 |
-
Tus capacidades incluyen:
|
| 8 |
-
- An谩lisis de c贸digo y repositorios de GitHub
|
| 9 |
-
- Generaci贸n de c贸digo y documentaci贸n
|
| 10 |
-
- Sugerencias de mejoras y optimizaciones
|
| 11 |
-
- Detecci贸n de bugs y vulnerabilidades
|
| 12 |
-
- Creaci贸n de arquitecturas de software
|
| 13 |
-
- Explicaciones t茅cnicas claras y detalladas
|
| 14 |
-
|
| 15 |
-
Responde siempre de manera profesional, 煤til y con un toque amigable. Cuando analices c贸digo, s茅 espec铆fico y proporciona ejemplos concretos. Si detectas problemas, sugiere soluciones pr谩cticas.
|
| 16 |
-
|
| 17 |
-
Formato de respuestas:
|
| 18 |
-
- Usa Markdown para formatear tus respuestas
|
| 19 |
-
- Incluye bloques de c贸digo cuando sea relevante
|
| 20 |
-
- S茅 conciso pero completo
|
| 21 |
-
- Si el usuario pregunta algo fuera del 谩mbito t茅cnico, redirige amablemente al tema de desarrollo`;
|
| 22 |
-
|
| 23 |
-
// POST - Enviar prompt al agente IA
|
| 24 |
export async function POST(request: NextRequest) {
|
| 25 |
try {
|
| 26 |
const body = await request.json();
|
| 27 |
-
const { prompt,
|
| 28 |
|
| 29 |
if (!prompt) {
|
| 30 |
-
return NextResponse.json(
|
| 31 |
-
{ success: false, error: "El prompt es requerido" },
|
| 32 |
-
{ status: 400 }
|
| 33 |
-
);
|
| 34 |
}
|
| 35 |
|
| 36 |
-
|
| 37 |
-
const
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
},
|
| 43 |
});
|
| 44 |
|
| 45 |
-
|
| 46 |
-
// Usar z-ai-web-dev-sdk para la respuesta de IA
|
| 47 |
-
const zai = await ZAI.create();
|
| 48 |
-
|
| 49 |
-
const messages = [
|
| 50 |
-
{ role: "system" as const, content: SYSTEM_PROMPT },
|
| 51 |
-
{ role: "user" as const, content: context ? `Contexto: ${context}\n\nPregunta: ${prompt}` : prompt },
|
| 52 |
-
];
|
| 53 |
-
|
| 54 |
-
const completion = await zai.chat.completions.create({
|
| 55 |
-
messages,
|
| 56 |
-
temperature: 0.7,
|
| 57 |
-
max_tokens: 4000,
|
| 58 |
-
});
|
| 59 |
-
|
| 60 |
-
const response = completion.choices[0]?.message?.content || "No se pudo generar una respuesta";
|
| 61 |
-
|
| 62 |
-
// Actualizar tarea como completada
|
| 63 |
-
await db.agentTask.update({
|
| 64 |
-
where: { id: task.id },
|
| 65 |
-
data: {
|
| 66 |
-
status: "completed",
|
| 67 |
-
output: response,
|
| 68 |
-
completedAt: new Date(),
|
| 69 |
-
},
|
| 70 |
-
});
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
taskId: task.id,
|
| 76 |
-
});
|
| 77 |
-
} catch (aiError) {
|
| 78 |
-
console.error("AI Error:", aiError);
|
| 79 |
-
|
| 80 |
-
// Actualizar tarea como fallida
|
| 81 |
-
await db.agentTask.update({
|
| 82 |
-
where: { id: task.id },
|
| 83 |
-
data: {
|
| 84 |
-
status: "failed",
|
| 85 |
-
output: aiError instanceof Error ? aiError.message : "Error desconocido",
|
| 86 |
-
},
|
| 87 |
-
});
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
}
|
| 94 |
-
} catch (error) {
|
| 95 |
-
console.error("Error in agent route:", error);
|
| 96 |
-
return NextResponse.json(
|
| 97 |
-
{ success: false, error: "Error interno del servidor" },
|
| 98 |
-
{ status: 500 }
|
| 99 |
-
);
|
| 100 |
}
|
| 101 |
}
|
| 102 |
|
| 103 |
-
// GET - Obtener historial de tareas
|
| 104 |
export async function GET() {
|
| 105 |
try {
|
| 106 |
-
const tasks = await db.agentTask.findMany({
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
});
|
| 110 |
-
|
| 111 |
-
return NextResponse.json({
|
| 112 |
-
success: true,
|
| 113 |
-
tasks,
|
| 114 |
-
});
|
| 115 |
-
} catch (error) {
|
| 116 |
-
console.error("Error fetching tasks:", error);
|
| 117 |
-
return NextResponse.json(
|
| 118 |
-
{ success: false, error: "Error al obtener tareas" },
|
| 119 |
-
{ status: 500 }
|
| 120 |
-
);
|
| 121 |
}
|
| 122 |
-
}
|
|
|
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
import ZAI from "z-ai-web-dev-sdk";
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
export async function POST(request: NextRequest) {
|
| 6 |
try {
|
| 7 |
const body = await request.json();
|
| 8 |
+
const { prompt, type } = body;
|
| 9 |
|
| 10 |
if (!prompt) {
|
| 11 |
+
return NextResponse.json({ success: false, error: "Prompt requerido" }, { status: 400 });
|
|
|
|
|
|
|
|
|
|
| 12 |
}
|
| 13 |
|
| 14 |
+
const zai = await ZAI.create();
|
| 15 |
+
const completion = await zai.chat.completions.create({
|
| 16 |
+
messages: [
|
| 17 |
+
{ role: "system", content: "Eres Sofia, un asistente de desarrollo." },
|
| 18 |
+
{ role: "user", content: prompt }
|
| 19 |
+
]
|
|
|
|
| 20 |
});
|
| 21 |
|
| 22 |
+
const output = completion.choices[0]?.message?.content || "";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
+
const task = await db.agentTask.create({
|
| 25 |
+
data: { type: type || "chat", status: "completed", input: prompt, output, completedAt: new Date() }
|
| 26 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
+
return NextResponse.json({ success: true, result: output, task });
|
| 29 |
+
} catch (error: unknown) {
|
| 30 |
+
const message = error instanceof Error ? error.message : "Error desconocido";
|
| 31 |
+
return NextResponse.json({ success: false, error: message }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
}
|
| 33 |
}
|
| 34 |
|
|
|
|
| 35 |
export async function GET() {
|
| 36 |
try {
|
| 37 |
+
const tasks = await db.agentTask.findMany({ orderBy: { createdAt: "desc" }, take: 20 });
|
| 38 |
+
return NextResponse.json({ success: true, tasks });
|
| 39 |
+
} catch {
|
| 40 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
}
|
| 42 |
+
}
|
src/app/api/analyze/route.ts
CHANGED
|
@@ -1,234 +1,56 @@
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
import ZAI from "z-ai-web-dev-sdk";
|
| 4 |
-
import fs from "fs/promises";
|
| 5 |
-
import path from "path";
|
| 6 |
|
| 7 |
-
const REPOS_DIR = path.join(process.cwd(), "repos");
|
| 8 |
-
|
| 9 |
-
async function readRepoFiles(repoName: string, maxFiles: number = 10): Promise<string> {
|
| 10 |
-
const repoPath = path.join(REPOS_DIR, repoName);
|
| 11 |
-
let content = "";
|
| 12 |
-
let fileCount = 0;
|
| 13 |
-
|
| 14 |
-
async function readDir(dir: string, depth: number = 0): Promise<void> {
|
| 15 |
-
if (fileCount >= maxFiles || depth > 3) return;
|
| 16 |
-
|
| 17 |
-
try {
|
| 18 |
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
| 19 |
-
|
| 20 |
-
for (const entry of entries) {
|
| 21 |
-
if (fileCount >= maxFiles) break;
|
| 22 |
-
|
| 23 |
-
const fullPath = path.join(dir, entry.name);
|
| 24 |
-
|
| 25 |
-
// Ignorar carpetas comunes
|
| 26 |
-
if (entry.isDirectory()) {
|
| 27 |
-
if (["node_modules", ".git", "dist", "build", "__pycache__", "venv", ".next"].includes(entry.name)) {
|
| 28 |
-
continue;
|
| 29 |
-
}
|
| 30 |
-
await readDir(fullPath, depth + 1);
|
| 31 |
-
} else if (entry.isFile()) {
|
| 32 |
-
// Solo archivos de c贸digo relevantes
|
| 33 |
-
const ext = path.extname(entry.name);
|
| 34 |
-
const codeExtensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".go", ".rs", ".c", ".cpp", ".h", ".css", ".scss", ".html", ".json", ".yaml", ".yml", ".md"];
|
| 35 |
-
|
| 36 |
-
if (codeExtensions.includes(ext) && !entry.name.startsWith(".")) {
|
| 37 |
-
try {
|
| 38 |
-
const fileContent = await fs.readFile(fullPath, "utf-8");
|
| 39 |
-
const relativePath = path.relative(repoPath, fullPath);
|
| 40 |
-
content += `\n--- ${relativePath} ---\n${fileContent.slice(0, 2000)}\n`;
|
| 41 |
-
fileCount++;
|
| 42 |
-
} catch {
|
| 43 |
-
// Archivo no legible
|
| 44 |
-
}
|
| 45 |
-
}
|
| 46 |
-
}
|
| 47 |
-
}
|
| 48 |
-
} catch {
|
| 49 |
-
// Directorio no accesible
|
| 50 |
-
}
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
await readDir(repoPath);
|
| 54 |
-
return content;
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
// POST - Analizar c贸digo con IA
|
| 58 |
export async function POST(request: NextRequest) {
|
| 59 |
try {
|
| 60 |
const body = await request.json();
|
| 61 |
-
const { repoId, projectId
|
| 62 |
-
|
| 63 |
-
let analysisContent = code || "";
|
| 64 |
-
let repoName = "";
|
| 65 |
-
|
| 66 |
-
// Si hay repoId, leer archivos del repositorio
|
| 67 |
-
if (repoId) {
|
| 68 |
-
const repo = await db.repo.findUnique({
|
| 69 |
-
where: { id: repoId },
|
| 70 |
-
});
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
analysisContent = await readRepoFiles(repo.name);
|
| 75 |
-
}
|
| 76 |
}
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
{
|
| 82 |
-
|
| 83 |
-
|
|
|
|
| 84 |
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
-
|
| 88 |
-
const analysis = await db.analysis.create({
|
| 89 |
data: {
|
| 90 |
-
type:
|
| 91 |
-
result:
|
|
|
|
| 92 |
repoId: repoId || null,
|
| 93 |
-
projectId: projectId || null
|
| 94 |
-
}
|
| 95 |
});
|
| 96 |
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
const prompts: Record<string, string> = {
|
| 101 |
-
code: `Analiza el siguiente c贸digo y proporciona:
|
| 102 |
-
1. Resumen general del proyecto
|
| 103 |
-
2. Estructura y organizaci贸n
|
| 104 |
-
3. Calidad del c贸digo (0-10)
|
| 105 |
-
4. Posibles mejoras
|
| 106 |
-
5. Bugs o problemas potenciales
|
| 107 |
-
6. Sugerencias de seguridad
|
| 108 |
-
|
| 109 |
-
C贸digo a analizar:
|
| 110 |
-
${analysisContent.slice(0, 10000)}`,
|
| 111 |
-
|
| 112 |
-
security: `Realiza un an谩lisis de seguridad del siguiente c贸digo. Identifica:
|
| 113 |
-
1. Vulnerabilidades potenciales (SQL injection, XSS, etc.)
|
| 114 |
-
2. Exposici贸n de datos sensibles
|
| 115 |
-
3. Dependencias inseguras
|
| 116 |
-
4. Configuraciones peligrosas
|
| 117 |
-
5. Recomendaciones de mitigaci贸n
|
| 118 |
-
|
| 119 |
-
C贸digo:
|
| 120 |
-
${analysisContent.slice(0, 10000)}`,
|
| 121 |
-
|
| 122 |
-
performance: `Analiza el rendimiento del siguiente c贸digo:
|
| 123 |
-
1. Cuellos de botella potenciales
|
| 124 |
-
2. Complejidad algor铆tmica
|
| 125 |
-
3. Uso de memoria
|
| 126 |
-
4. Operaciones bloqueantes
|
| 127 |
-
5. Optimizaciones sugeridas
|
| 128 |
-
|
| 129 |
-
C贸digo:
|
| 130 |
-
${analysisContent.slice(0, 10000)}`,
|
| 131 |
-
};
|
| 132 |
-
|
| 133 |
-
const completion = await zai.chat.completions.create({
|
| 134 |
-
messages: [
|
| 135 |
-
{
|
| 136 |
-
role: "system",
|
| 137 |
-
content: "Eres un experto en an谩lisis de c贸digo. Proporciona an谩lisis detallados y accionables.",
|
| 138 |
-
},
|
| 139 |
-
{
|
| 140 |
-
role: "user",
|
| 141 |
-
content: prompts[analysisType] || prompts.code,
|
| 142 |
-
},
|
| 143 |
-
],
|
| 144 |
-
temperature: 0.3,
|
| 145 |
-
max_tokens: 4000,
|
| 146 |
-
});
|
| 147 |
-
|
| 148 |
-
const result = completion.choices[0]?.message?.content || "No se pudo generar an谩lisis";
|
| 149 |
-
|
| 150 |
-
// Extraer resumen (primeras 200 caracteres)
|
| 151 |
-
const summary = result.slice(0, 200) + "...";
|
| 152 |
-
|
| 153 |
-
// Actualizar an谩lisis
|
| 154 |
-
const updatedAnalysis = await db.analysis.update({
|
| 155 |
-
where: { id: analysis.id },
|
| 156 |
-
data: {
|
| 157 |
-
result,
|
| 158 |
-
summary,
|
| 159 |
-
},
|
| 160 |
-
});
|
| 161 |
-
|
| 162 |
-
// Crear tarea de agente
|
| 163 |
-
await db.agentTask.create({
|
| 164 |
-
data: {
|
| 165 |
-
type: "analyze",
|
| 166 |
-
status: "completed",
|
| 167 |
-
input: `Analizar ${repoName || "c贸digo"} (${analysisType})`,
|
| 168 |
-
output: summary,
|
| 169 |
-
completedAt: new Date(),
|
| 170 |
-
},
|
| 171 |
-
});
|
| 172 |
-
|
| 173 |
-
return NextResponse.json({
|
| 174 |
-
success: true,
|
| 175 |
-
analysis: updatedAnalysis,
|
| 176 |
-
message: "An谩lisis completado",
|
| 177 |
-
});
|
| 178 |
-
} catch (aiError) {
|
| 179 |
-
console.error("AI Analysis Error:", aiError);
|
| 180 |
-
|
| 181 |
-
await db.analysis.update({
|
| 182 |
-
where: { id: analysis.id },
|
| 183 |
-
data: {
|
| 184 |
-
result: "Error en el an谩lisis: " + (aiError instanceof Error ? aiError.message : "Error desconocido"),
|
| 185 |
-
},
|
| 186 |
-
});
|
| 187 |
-
|
| 188 |
-
return NextResponse.json(
|
| 189 |
-
{ success: false, error: "Error en el an谩lisis de IA" },
|
| 190 |
-
{ status: 500 }
|
| 191 |
-
);
|
| 192 |
-
}
|
| 193 |
-
} catch (error) {
|
| 194 |
-
console.error("Error in analyze route:", error);
|
| 195 |
-
return NextResponse.json(
|
| 196 |
-
{ success: false, error: "Error interno del servidor" },
|
| 197 |
-
{ status: 500 }
|
| 198 |
-
);
|
| 199 |
}
|
| 200 |
}
|
| 201 |
|
| 202 |
-
// GET - Obtener an谩lisis
|
| 203 |
export async function GET(request: NextRequest) {
|
| 204 |
try {
|
| 205 |
const { searchParams } = new URL(request.url);
|
| 206 |
-
const repoId = searchParams.get("repoId");
|
| 207 |
const projectId = searchParams.get("projectId");
|
| 208 |
-
|
| 209 |
-
const where: Record<string, string> = {};
|
| 210 |
-
if (repoId) where.repoId = repoId;
|
| 211 |
if (projectId) where.projectId = projectId;
|
| 212 |
|
| 213 |
-
const analyses = await db.analysis.findMany({
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
project: true,
|
| 218 |
-
},
|
| 219 |
-
orderBy: { createdAt: "desc" },
|
| 220 |
-
take: 20,
|
| 221 |
-
});
|
| 222 |
-
|
| 223 |
-
return NextResponse.json({
|
| 224 |
-
success: true,
|
| 225 |
-
analyses,
|
| 226 |
-
});
|
| 227 |
-
} catch (error) {
|
| 228 |
-
console.error("Error fetching analyses:", error);
|
| 229 |
-
return NextResponse.json(
|
| 230 |
-
{ success: false, error: "Error al obtener an谩lisis" },
|
| 231 |
-
{ status: 500 }
|
| 232 |
-
);
|
| 233 |
}
|
| 234 |
-
}
|
|
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
import ZAI from "z-ai-web-dev-sdk";
|
|
|
|
|
|
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
export async function POST(request: NextRequest) {
|
| 6 |
try {
|
| 7 |
const body = await request.json();
|
| 8 |
+
const { code, repoId, projectId } = body;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
+
if (!code) {
|
| 11 |
+
return NextResponse.json({ success: false, error: "Codigo requerido" }, { status: 400 });
|
|
|
|
|
|
|
| 12 |
}
|
| 13 |
|
| 14 |
+
const zai = await ZAI.create();
|
| 15 |
+
const completion = await zai.chat.completions.create({
|
| 16 |
+
messages: [
|
| 17 |
+
{ role: "system", content: "Eres un analista de codigo. Responde en JSON con {summary, issues:[], suggestions:[]}" },
|
| 18 |
+
{ role: "user", content: "Analiza: " + code.substring(0, 3000) }
|
| 19 |
+
]
|
| 20 |
+
});
|
| 21 |
|
| 22 |
+
let analysis: { summary?: string; issues?: string[]; suggestions?: string[] } = {};
|
| 23 |
+
try {
|
| 24 |
+
const match = completion.choices[0]?.message?.content?.match(/\{[\s\S]*\}/);
|
| 25 |
+
if (match) analysis = JSON.parse(match[0]);
|
| 26 |
+
} catch {}
|
| 27 |
|
| 28 |
+
const result = await db.analysis.create({
|
|
|
|
| 29 |
data: {
|
| 30 |
+
type: "code",
|
| 31 |
+
result: JSON.stringify(analysis),
|
| 32 |
+
summary: analysis.summary || "Analisis completado",
|
| 33 |
repoId: repoId || null,
|
| 34 |
+
projectId: projectId || null
|
| 35 |
+
}
|
| 36 |
});
|
| 37 |
|
| 38 |
+
return NextResponse.json({ success: true, analysis: result });
|
| 39 |
+
} catch {
|
| 40 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
}
|
| 42 |
}
|
| 43 |
|
|
|
|
| 44 |
export async function GET(request: NextRequest) {
|
| 45 |
try {
|
| 46 |
const { searchParams } = new URL(request.url);
|
|
|
|
| 47 |
const projectId = searchParams.get("projectId");
|
| 48 |
+
const where: Record<string, string | null> = {};
|
|
|
|
|
|
|
| 49 |
if (projectId) where.projectId = projectId;
|
| 50 |
|
| 51 |
+
const analyses = await db.analysis.findMany({ where, orderBy: { createdAt: "desc" }, take: 20 });
|
| 52 |
+
return NextResponse.json({ success: true, analyses });
|
| 53 |
+
} catch {
|
| 54 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
}
|
| 56 |
+
}
|
src/app/api/monetization/route.ts
CHANGED
|
@@ -1,372 +1,67 @@
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
nudity: "allowed",
|
| 22 |
-
explicit: "allowed_with_verification",
|
| 23 |
-
copyright: "must_own_or_license"
|
| 24 |
-
}
|
| 25 |
-
},
|
| 26 |
-
patreon: {
|
| 27 |
-
name: "Patreon",
|
| 28 |
-
type: "subscription",
|
| 29 |
-
url: "https://patreon.com",
|
| 30 |
-
feePercentage: 12,
|
| 31 |
-
legalTerms: {
|
| 32 |
-
ageRequirement: 18,
|
| 33 |
-
contentTypes: ["art", "music", "podcasts", "videos", "writing", "gaming"],
|
| 34 |
-
prohibitedContent: ["adult content with real people", "hate speech", "illegal content"],
|
| 35 |
-
verificationRequired: true,
|
| 36 |
-
taxReporting: true,
|
| 37 |
-
payoutSchedule: "monthly"
|
| 38 |
-
},
|
| 39 |
-
contentRules: {
|
| 40 |
-
adultContent: "restricted",
|
| 41 |
-
nudity: "allowed_with_warning",
|
| 42 |
-
explicit: "not_allowed",
|
| 43 |
-
copyright: "must_own_or_license"
|
| 44 |
-
}
|
| 45 |
-
},
|
| 46 |
-
fansly: {
|
| 47 |
-
name: "Fansly",
|
| 48 |
-
type: "mixed",
|
| 49 |
-
url: "https://fansly.com",
|
| 50 |
-
feePercentage: 20,
|
| 51 |
-
legalTerms: {
|
| 52 |
-
ageRequirement: 18,
|
| 53 |
-
contentTypes: ["adult", "lifestyle", "creator"],
|
| 54 |
-
prohibitedContent: ["illegal content", "non-consensual", "underage"],
|
| 55 |
-
verificationRequired: true,
|
| 56 |
-
taxReporting: true,
|
| 57 |
-
payoutSchedule: "weekly"
|
| 58 |
-
},
|
| 59 |
-
contentRules: {
|
| 60 |
-
adultContent: "allowed",
|
| 61 |
-
nudity: "allowed",
|
| 62 |
-
explicit: "allowed_with_verification",
|
| 63 |
-
copyright: "must_own_or_license"
|
| 64 |
-
}
|
| 65 |
-
},
|
| 66 |
-
fanvue: {
|
| 67 |
-
name: "Fanvue",
|
| 68 |
-
type: "subscription",
|
| 69 |
-
url: "https://fanvue.com",
|
| 70 |
-
feePercentage: 15,
|
| 71 |
-
legalTerms: {
|
| 72 |
-
ageRequirement: 18,
|
| 73 |
-
contentTypes: ["adult", "fitness", "lifestyle", "music", "art"],
|
| 74 |
-
prohibitedContent: ["illegal content", "non-consensual", "underage"],
|
| 75 |
-
verificationRequired: true,
|
| 76 |
-
taxReporting: true,
|
| 77 |
-
payoutSchedule: "weekly"
|
| 78 |
-
},
|
| 79 |
-
contentRules: {
|
| 80 |
-
adultContent: "allowed",
|
| 81 |
-
nudity: "allowed",
|
| 82 |
-
explicit: "allowed_with_verification",
|
| 83 |
-
copyright: "must_own_or_license"
|
| 84 |
-
}
|
| 85 |
-
},
|
| 86 |
-
justforfans: {
|
| 87 |
-
name: "JustForFans",
|
| 88 |
-
type: "mixed",
|
| 89 |
-
url: "https://justfor.fans",
|
| 90 |
-
feePercentage: 20,
|
| 91 |
-
legalTerms: {
|
| 92 |
-
ageRequirement: 18,
|
| 93 |
-
contentTypes: ["adult"],
|
| 94 |
-
prohibitedContent: ["illegal content", "non-consensual", "underage"],
|
| 95 |
-
verificationRequired: true,
|
| 96 |
-
taxReporting: true,
|
| 97 |
-
payoutSchedule: "weekly"
|
| 98 |
-
},
|
| 99 |
-
contentRules: {
|
| 100 |
-
adultContent: "allowed",
|
| 101 |
-
nudity: "allowed",
|
| 102 |
-
explicit: "allowed_with_verification",
|
| 103 |
-
copyright: "must_own_or_license"
|
| 104 |
-
}
|
| 105 |
-
},
|
| 106 |
-
kofi: {
|
| 107 |
-
name: "Ko-fi",
|
| 108 |
-
type: "tips",
|
| 109 |
-
url: "https://ko-fi.com",
|
| 110 |
-
feePercentage: 0,
|
| 111 |
-
legalTerms: {
|
| 112 |
-
ageRequirement: 13,
|
| 113 |
-
contentTypes: ["art", "commissions", "digital products", "memberships"],
|
| 114 |
-
prohibitedContent: ["adult content", "illegal content", "hate speech"],
|
| 115 |
-
verificationRequired: false,
|
| 116 |
-
taxReporting: false,
|
| 117 |
-
payoutSchedule: "instant"
|
| 118 |
-
},
|
| 119 |
-
contentRules: {
|
| 120 |
-
adultContent: "not_allowed",
|
| 121 |
-
nudity: "not_allowed",
|
| 122 |
-
explicit: "not_allowed",
|
| 123 |
-
copyright: "must_own_or_license"
|
| 124 |
-
}
|
| 125 |
-
},
|
| 126 |
-
gumroad: {
|
| 127 |
-
name: "Gumroad",
|
| 128 |
-
type: "ppv",
|
| 129 |
-
url: "https://gumroad.com",
|
| 130 |
-
feePercentage: 10,
|
| 131 |
-
legalTerms: {
|
| 132 |
-
ageRequirement: 13,
|
| 133 |
-
contentTypes: ["digital products", "courses", "memberships", "software"],
|
| 134 |
-
prohibitedContent: ["illegal content", "hate speech"],
|
| 135 |
-
verificationRequired: false,
|
| 136 |
-
taxReporting: true,
|
| 137 |
-
payoutSchedule: "weekly"
|
| 138 |
-
},
|
| 139 |
-
contentRules: {
|
| 140 |
-
adultContent: "restricted",
|
| 141 |
-
nudity: "allowed_with_warning",
|
| 142 |
-
explicit: "not_allowed",
|
| 143 |
-
copyright: "must_own_or_license"
|
| 144 |
-
}
|
| 145 |
-
},
|
| 146 |
-
instagram: {
|
| 147 |
-
name: "Instagram",
|
| 148 |
-
type: "free",
|
| 149 |
-
url: "https://instagram.com",
|
| 150 |
-
feePercentage: 0,
|
| 151 |
-
legalTerms: {
|
| 152 |
-
ageRequirement: 13,
|
| 153 |
-
contentTypes: ["photos", "reels", "stories", "lives"],
|
| 154 |
-
prohibitedContent: ["nudity", "violence", "hate speech", "illegal content"],
|
| 155 |
-
verificationRequired: false,
|
| 156 |
-
taxReporting: false,
|
| 157 |
-
payoutSchedule: "none"
|
| 158 |
-
},
|
| 159 |
-
contentRules: {
|
| 160 |
-
adultContent: "not_allowed",
|
| 161 |
-
nudity: "not_allowed",
|
| 162 |
-
explicit: "not_allowed",
|
| 163 |
-
copyright: "must_own_or_license"
|
| 164 |
-
}
|
| 165 |
-
},
|
| 166 |
-
tiktok: {
|
| 167 |
-
name: "TikTok",
|
| 168 |
-
type: "free",
|
| 169 |
-
url: "https://tiktok.com",
|
| 170 |
-
feePercentage: 0,
|
| 171 |
-
legalTerms: {
|
| 172 |
-
ageRequirement: 13,
|
| 173 |
-
contentTypes: ["short videos", "live"],
|
| 174 |
-
prohibitedContent: ["nudity", "violence", "dangerous acts", "hate speech"],
|
| 175 |
-
verificationRequired: false,
|
| 176 |
-
taxReporting: false,
|
| 177 |
-
payoutSchedule: "none"
|
| 178 |
-
},
|
| 179 |
-
contentRules: {
|
| 180 |
-
adultContent: "not_allowed",
|
| 181 |
-
nudity: "not_allowed",
|
| 182 |
-
explicit: "not_allowed",
|
| 183 |
-
copyright: "must_own_or_license"
|
| 184 |
-
}
|
| 185 |
-
},
|
| 186 |
-
youtube: {
|
| 187 |
-
name: "YouTube",
|
| 188 |
-
type: "ad_revenue",
|
| 189 |
-
url: "https://youtube.com",
|
| 190 |
-
feePercentage: 45, // YouTube toma 45% de ad revenue
|
| 191 |
-
legalTerms: {
|
| 192 |
-
ageRequirement: 13,
|
| 193 |
-
contentTypes: ["videos", "shorts", "live", "community"],
|
| 194 |
-
prohibitedContent: ["nudity", "violence", "hate speech", "copyright violation"],
|
| 195 |
-
verificationRequired: true,
|
| 196 |
-
taxReporting: true,
|
| 197 |
-
payoutSchedule: "monthly"
|
| 198 |
-
},
|
| 199 |
-
contentRules: {
|
| 200 |
-
adultContent: "not_allowed",
|
| 201 |
-
nudity: "not_allowed",
|
| 202 |
-
explicit: "not_allowed",
|
| 203 |
-
copyright: "strict_enforcement"
|
| 204 |
-
}
|
| 205 |
-
}
|
| 206 |
};
|
| 207 |
|
| 208 |
-
// GET - Listar plataformas disponibles
|
| 209 |
export async function GET(request: NextRequest) {
|
| 210 |
try {
|
| 211 |
const { searchParams } = new URL(request.url);
|
| 212 |
-
const
|
| 213 |
-
|
| 214 |
-
// Obtener plataformas configuradas por el usuario
|
| 215 |
-
const userPlatforms = await db.monetizationPlatform.findMany({
|
| 216 |
-
include: {
|
| 217 |
-
_count: {
|
| 218 |
-
select: { posts: true, earnings: true, subscribers: true }
|
| 219 |
-
}
|
| 220 |
-
},
|
| 221 |
-
orderBy: { createdAt: "desc" }
|
| 222 |
-
});
|
| 223 |
|
| 224 |
-
|
| 225 |
-
let availablePlatforms = Object.entries(PLATFORM_CONFIGS).map(([key, config]) => ({
|
| 226 |
-
id: key,
|
| 227 |
-
...config
|
| 228 |
-
}));
|
| 229 |
|
| 230 |
-
if (
|
| 231 |
-
|
| 232 |
}
|
| 233 |
|
| 234 |
-
return NextResponse.json({
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
availablePlatforms,
|
| 238 |
-
totalUserPlatforms: userPlatforms.length
|
| 239 |
-
});
|
| 240 |
-
|
| 241 |
-
} catch (error) {
|
| 242 |
-
console.error("Error fetching monetization platforms:", error);
|
| 243 |
-
return NextResponse.json(
|
| 244 |
-
{ success: false, error: "Error al obtener plataformas" },
|
| 245 |
-
{ status: 500 }
|
| 246 |
-
);
|
| 247 |
}
|
| 248 |
}
|
| 249 |
|
| 250 |
-
// POST - A帽adir/configurar plataforma
|
| 251 |
export async function POST(request: NextRequest) {
|
| 252 |
try {
|
| 253 |
const body = await request.json();
|
| 254 |
-
const {
|
| 255 |
-
platformKey,
|
| 256 |
-
accountId,
|
| 257 |
-
accountName,
|
| 258 |
-
apiKey,
|
| 259 |
-
isVerified
|
| 260 |
-
} = body;
|
| 261 |
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
{ status: 400 }
|
| 266 |
-
);
|
| 267 |
}
|
| 268 |
|
| 269 |
-
const config = PLATFORM_CONFIGS[platformKey as keyof typeof PLATFORM_CONFIGS];
|
| 270 |
-
|
| 271 |
-
// Crear o actualizar plataforma
|
| 272 |
const platform = await db.monetizationPlatform.create({
|
| 273 |
data: {
|
| 274 |
name: config.name,
|
| 275 |
type: config.type,
|
| 276 |
-
url: config.url,
|
| 277 |
-
apiKey: apiKey || null,
|
| 278 |
accountId: accountId || null,
|
| 279 |
accountName: accountName || null,
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
isVerified: isVerified || false,
|
| 285 |
}
|
| 286 |
});
|
| 287 |
|
| 288 |
-
return NextResponse.json({
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
legalInfo: config.legalTerms,
|
| 292 |
-
contentRules: config.contentRules,
|
| 293 |
-
message: `Plataforma ${config.name} configurada`
|
| 294 |
-
});
|
| 295 |
-
|
| 296 |
-
} catch (error) {
|
| 297 |
-
console.error("Error creating platform:", error);
|
| 298 |
-
return NextResponse.json(
|
| 299 |
-
{ success: false, error: "Error al configurar plataforma" },
|
| 300 |
-
{ status: 500 }
|
| 301 |
-
);
|
| 302 |
}
|
| 303 |
-
}
|
| 304 |
-
|
| 305 |
-
// PUT - Actualizar plataforma
|
| 306 |
-
export async function PUT(request: NextRequest) {
|
| 307 |
-
try {
|
| 308 |
-
const body = await request.json();
|
| 309 |
-
const { id, accountId, accountName, apiKey, isVerified, isActive } = body;
|
| 310 |
-
|
| 311 |
-
if (!id) {
|
| 312 |
-
return NextResponse.json(
|
| 313 |
-
{ success: false, error: "ID de plataforma requerido" },
|
| 314 |
-
{ status: 400 }
|
| 315 |
-
);
|
| 316 |
-
}
|
| 317 |
-
|
| 318 |
-
const platform = await db.monetizationPlatform.update({
|
| 319 |
-
where: { id },
|
| 320 |
-
data: {
|
| 321 |
-
accountId: accountId || undefined,
|
| 322 |
-
accountName: accountName || undefined,
|
| 323 |
-
apiKey: apiKey || undefined,
|
| 324 |
-
isVerified: isVerified || undefined,
|
| 325 |
-
isActive: isActive !== undefined ? isActive : undefined,
|
| 326 |
-
}
|
| 327 |
-
});
|
| 328 |
-
|
| 329 |
-
return NextResponse.json({
|
| 330 |
-
success: true,
|
| 331 |
-
platform
|
| 332 |
-
});
|
| 333 |
-
|
| 334 |
-
} catch (error) {
|
| 335 |
-
console.error("Error updating platform:", error);
|
| 336 |
-
return NextResponse.json(
|
| 337 |
-
{ success: false, error: "Error al actualizar plataforma" },
|
| 338 |
-
{ status: 500 }
|
| 339 |
-
);
|
| 340 |
-
}
|
| 341 |
-
}
|
| 342 |
-
|
| 343 |
-
// DELETE - Eliminar plataforma
|
| 344 |
-
export async function DELETE(request: NextRequest) {
|
| 345 |
-
try {
|
| 346 |
-
const { searchParams } = new URL(request.url);
|
| 347 |
-
const id = searchParams.get("id");
|
| 348 |
-
|
| 349 |
-
if (!id) {
|
| 350 |
-
return NextResponse.json(
|
| 351 |
-
{ success: false, error: "ID requerido" },
|
| 352 |
-
{ status: 400 }
|
| 353 |
-
);
|
| 354 |
-
}
|
| 355 |
-
|
| 356 |
-
await db.monetizationPlatform.delete({
|
| 357 |
-
where: { id }
|
| 358 |
-
});
|
| 359 |
-
|
| 360 |
-
return NextResponse.json({
|
| 361 |
-
success: true,
|
| 362 |
-
message: "Plataforma eliminada"
|
| 363 |
-
});
|
| 364 |
-
|
| 365 |
-
} catch (error) {
|
| 366 |
-
console.error("Error deleting platform:", error);
|
| 367 |
-
return NextResponse.json(
|
| 368 |
-
{ success: false, error: "Error al eliminar plataforma" },
|
| 369 |
-
{ status: 500 }
|
| 370 |
-
);
|
| 371 |
-
}
|
| 372 |
-
}
|
|
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
|
| 4 |
+
interface PlatformConfig {
|
| 5 |
+
name: string;
|
| 6 |
+
type: string;
|
| 7 |
+
fee: number;
|
| 8 |
+
adultContent: boolean;
|
| 9 |
+
legal: string;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
const PLATFORM_CONFIGS: Record<string, PlatformConfig> = {
|
| 13 |
+
onlyfans: { name: "OnlyFans", type: "subscription", fee: 20, adultContent: true, legal: "Adult content permitido, verificar edad" },
|
| 14 |
+
patreon: { name: "Patreon", type: "subscription", fee: 12, adultContent: false, legal: "Restricciones en adult content" },
|
| 15 |
+
fansly: { name: "Fansly", type: "subscription", fee: 20, adultContent: true, legal: "Adult content permitido" },
|
| 16 |
+
fanvue: { name: "Fanvue", type: "subscription", fee: 15, adultContent: true, legal: "Adult content permitido" },
|
| 17 |
+
kofi: { name: "Ko-fi", type: "tips", fee: 0, adultContent: false, legal: "Sin adult content" },
|
| 18 |
+
instagram: { name: "Instagram", type: "free", fee: 0, adultContent: false, legal: "Sin desnudez" },
|
| 19 |
+
tiktok: { name: "TikTok", type: "free", fee: 0, adultContent: false, legal: "Contenido familiar" },
|
| 20 |
+
youtube: { name: "YouTube", type: "free", fee: 0, adultContent: false, legal: "Politicas de comunidad estrictas" }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
};
|
| 22 |
|
|
|
|
| 23 |
export async function GET(request: NextRequest) {
|
| 24 |
try {
|
| 25 |
const { searchParams } = new URL(request.url);
|
| 26 |
+
const platform = searchParams.get("platform");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
+
const userPlatforms = await db.monetizationPlatform.findMany({ where: { isActive: true } });
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
if (platform && PLATFORM_CONFIGS[platform]) {
|
| 31 |
+
return NextResponse.json({ success: true, platform: PLATFORM_CONFIGS[platform], userPlatforms });
|
| 32 |
}
|
| 33 |
|
| 34 |
+
return NextResponse.json({ success: true, platforms: PLATFORM_CONFIGS, userPlatforms });
|
| 35 |
+
} catch {
|
| 36 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
}
|
| 38 |
}
|
| 39 |
|
|
|
|
| 40 |
export async function POST(request: NextRequest) {
|
| 41 |
try {
|
| 42 |
const body = await request.json();
|
| 43 |
+
const { platformName, accountId, accountName } = body;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
+
const config = PLATFORM_CONFIGS[platformName];
|
| 46 |
+
if (!config) {
|
| 47 |
+
return NextResponse.json({ success: false, error: "Plataforma no valida" }, { status: 400 });
|
|
|
|
|
|
|
| 48 |
}
|
| 49 |
|
|
|
|
|
|
|
|
|
|
| 50 |
const platform = await db.monetizationPlatform.create({
|
| 51 |
data: {
|
| 52 |
name: config.name,
|
| 53 |
type: config.type,
|
|
|
|
|
|
|
| 54 |
accountId: accountId || null,
|
| 55 |
accountName: accountName || null,
|
| 56 |
+
feePercentage: config.fee,
|
| 57 |
+
legalTerms: JSON.stringify({ adultContent: config.adultContent, legal: config.legal }),
|
| 58 |
+
isActive: true,
|
| 59 |
+
isVerified: false
|
|
|
|
| 60 |
}
|
| 61 |
});
|
| 62 |
|
| 63 |
+
return NextResponse.json({ success: true, platform });
|
| 64 |
+
} catch {
|
| 65 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
}
|
| 67 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/app/api/posts/route.ts
CHANGED
|
@@ -2,329 +2,91 @@ import { NextRequest, NextResponse } from "next/server";
|
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
import ZAI from "z-ai-web-dev-sdk";
|
| 4 |
|
| 5 |
-
// GET - Listar publicaciones
|
| 6 |
export async function GET(request: NextRequest) {
|
| 7 |
try {
|
| 8 |
const { searchParams } = new URL(request.url);
|
| 9 |
const status = searchParams.get("status");
|
| 10 |
const platform = searchParams.get("platform");
|
| 11 |
-
const type = searchParams.get("type");
|
| 12 |
-
const limit = parseInt(searchParams.get("limit") || "50");
|
| 13 |
|
| 14 |
-
const where: Record<string,
|
| 15 |
if (status) where.status = status;
|
| 16 |
-
if (type) where.type = type;
|
| 17 |
if (platform) where.platformId = platform;
|
| 18 |
|
| 19 |
-
const posts = await db.post.findMany({
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
platform: true,
|
| 24 |
-
story: true,
|
| 25 |
-
},
|
| 26 |
-
orderBy: { scheduledAt: "asc" },
|
| 27 |
-
take: limit,
|
| 28 |
-
});
|
| 29 |
-
|
| 30 |
-
// Estad铆sticas
|
| 31 |
-
const stats = {
|
| 32 |
-
total: await db.post.count(),
|
| 33 |
-
draft: await db.post.count({ where: { status: "draft" } }),
|
| 34 |
-
scheduled: await db.post.count({ where: { status: "scheduled" } }),
|
| 35 |
-
published: await db.post.count({ where: { status: "published" } }),
|
| 36 |
-
failed: await db.post.count({ where: { status: "failed" } }),
|
| 37 |
-
};
|
| 38 |
-
|
| 39 |
-
// Pr贸ximas publicaciones
|
| 40 |
-
const upcoming = await db.post.findMany({
|
| 41 |
-
where: {
|
| 42 |
-
status: "scheduled",
|
| 43 |
-
scheduledAt: { gte: new Date() }
|
| 44 |
-
},
|
| 45 |
-
orderBy: { scheduledAt: "asc" },
|
| 46 |
-
take: 5,
|
| 47 |
-
});
|
| 48 |
-
|
| 49 |
-
return NextResponse.json({
|
| 50 |
-
success: true,
|
| 51 |
-
posts,
|
| 52 |
-
stats,
|
| 53 |
-
upcoming
|
| 54 |
-
});
|
| 55 |
-
|
| 56 |
-
} catch (error) {
|
| 57 |
-
console.error("Error fetching posts:", error);
|
| 58 |
-
return NextResponse.json(
|
| 59 |
-
{ success: false, error: "Error al obtener publicaciones" },
|
| 60 |
-
{ status: 500 }
|
| 61 |
-
);
|
| 62 |
}
|
| 63 |
}
|
| 64 |
|
| 65 |
-
// POST - Crear nueva publicaci贸n
|
| 66 |
export async function POST(request: NextRequest) {
|
| 67 |
try {
|
| 68 |
const body = await request.json();
|
| 69 |
-
const {
|
| 70 |
-
title,
|
| 71 |
-
caption,
|
| 72 |
-
type, // reel, photo, carousel, story, post
|
| 73 |
-
contentId,
|
| 74 |
-
platformId,
|
| 75 |
-
scheduledAt,
|
| 76 |
-
hashtags,
|
| 77 |
-
storyId,
|
| 78 |
-
autoGenerateCaption,
|
| 79 |
-
optimizeForPlatform,
|
| 80 |
-
} = body;
|
| 81 |
|
| 82 |
-
if (!
|
| 83 |
-
return NextResponse.json(
|
| 84 |
-
{ success: false, error: "El tipo de publicaci贸n es requerido" },
|
| 85 |
-
{ status: 400 }
|
| 86 |
-
);
|
| 87 |
}
|
| 88 |
|
| 89 |
-
let finalCaption = caption;
|
| 90 |
-
let finalHashtags = hashtags;
|
| 91 |
-
|
| 92 |
-
// Generar caption con IA si se solicita
|
| 93 |
-
if (autoGenerateCaption && contentId) {
|
| 94 |
-
const content = await db.content.findUnique({
|
| 95 |
-
where: { id: contentId }
|
| 96 |
-
});
|
| 97 |
|
| 98 |
-
|
|
|
|
| 99 |
const zai = await ZAI.create();
|
| 100 |
-
const platform = await db.monetizationPlatform.findUnique({
|
| 101 |
-
where: { id: platformId }
|
| 102 |
-
});
|
| 103 |
-
|
| 104 |
-
const platformName = platform?.name || "general";
|
| 105 |
-
const platformRules = PLATFORM_RULES[platformName.toLowerCase()] || {};
|
| 106 |
-
|
| 107 |
const completion = await zai.chat.completions.create({
|
| 108 |
messages: [
|
| 109 |
-
{
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
Genera captions atractivos que:
|
| 113 |
-
${platformRules.maxCaptionLength ? `- No excedan ${platformRules.maxCaptionLength} caracteres` : ''}
|
| 114 |
-
- Incluyan emojis relevantes
|
| 115 |
-
- Tengan un CTA (call to action) efectivo
|
| 116 |
-
- Sean ${platformRules.tone || 'profesionales pero cercanos'}
|
| 117 |
-
- Maximizen el engagement
|
| 118 |
-
${platformRules.hashtagLimit ? `- Incluyan m谩ximo ${platformRules.hashtagLimit} hashtags relevantes` : ''}`
|
| 119 |
-
},
|
| 120 |
-
{
|
| 121 |
-
role: "user",
|
| 122 |
-
content: `Genera un caption para este contenido:
|
| 123 |
-
Tipo: ${type}
|
| 124 |
-
T铆tulo: ${title || content.title}
|
| 125 |
-
Descripci贸n: ${content.description || content.prompt}
|
| 126 |
-
Plataforma: ${platformName}`
|
| 127 |
-
}
|
| 128 |
-
],
|
| 129 |
-
temperature: 0.8,
|
| 130 |
});
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
}
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
// Optimizar hashtags si se solicita
|
| 137 |
-
if (optimizeForPlatform && !hashtags) {
|
| 138 |
-
const zai = await ZAI.create();
|
| 139 |
-
const platform = await db.monetizationPlatform.findUnique({
|
| 140 |
-
where: { id: platformId }
|
| 141 |
-
});
|
| 142 |
-
|
| 143 |
-
const completion = await zai.chat.completions.create({
|
| 144 |
-
messages: [
|
| 145 |
-
{
|
| 146 |
-
role: "system",
|
| 147 |
-
content: `Eres un experto en SEO de redes sociales. Genera hashtags optimizados.
|
| 148 |
-
Responde SOLO con un JSON array de hashtags sin el s铆mbolo #.
|
| 149 |
-
Ejemplo: ["tendencias", "viral", "lifestyle"]`
|
| 150 |
-
},
|
| 151 |
-
{
|
| 152 |
-
role: "user",
|
| 153 |
-
content: `Genera hashtags para un ${type} en ${platform?.name || "redes sociales"}.
|
| 154 |
-
Tema: ${title || "contenido general"}
|
| 155 |
-
M谩ximo 10 hashtags.`
|
| 156 |
-
}
|
| 157 |
-
],
|
| 158 |
-
temperature: 0.7,
|
| 159 |
-
});
|
| 160 |
-
|
| 161 |
-
try {
|
| 162 |
-
const response = completion.choices[0]?.message?.content || "[]";
|
| 163 |
-
const match = response.match(/\[[\s\S]*\]/);
|
| 164 |
-
if (match) {
|
| 165 |
-
finalHashtags = match[0];
|
| 166 |
-
}
|
| 167 |
-
} catch {
|
| 168 |
-
finalHashtags = "[]";
|
| 169 |
-
}
|
| 170 |
}
|
| 171 |
|
| 172 |
-
// Crear publicaci贸n
|
| 173 |
const post = await db.post.create({
|
| 174 |
data: {
|
| 175 |
-
title
|
| 176 |
caption: finalCaption,
|
| 177 |
-
|
| 178 |
-
type,
|
| 179 |
status: scheduledAt ? "scheduled" : "draft",
|
| 180 |
-
contentId: contentId || null,
|
| 181 |
platformId: platformId || null,
|
| 182 |
-
scheduledAt: scheduledAt ? new Date(scheduledAt) : null
|
| 183 |
-
storyId: storyId || null,
|
| 184 |
-
},
|
| 185 |
-
include: {
|
| 186 |
-
content: true,
|
| 187 |
-
platform: true,
|
| 188 |
}
|
| 189 |
});
|
| 190 |
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
type: "create_post",
|
| 195 |
-
status: "completed",
|
| 196 |
-
input: `Crear ${type}: ${title || "sin t铆tulo"}`,
|
| 197 |
-
output: `Post creado con ID: ${post.id}`,
|
| 198 |
-
completedAt: new Date(),
|
| 199 |
-
}
|
| 200 |
-
});
|
| 201 |
-
|
| 202 |
-
return NextResponse.json({
|
| 203 |
-
success: true,
|
| 204 |
-
post,
|
| 205 |
-
message: scheduledAt
|
| 206 |
-
? `Publicaci贸n programada para ${new Date(scheduledAt).toLocaleString()}`
|
| 207 |
-
: "Publicaci贸n creada como borrador"
|
| 208 |
-
});
|
| 209 |
-
|
| 210 |
-
} catch (error) {
|
| 211 |
-
console.error("Error creating post:", error);
|
| 212 |
-
return NextResponse.json(
|
| 213 |
-
{ success: false, error: "Error al crear publicaci贸n" },
|
| 214 |
-
{ status: 500 }
|
| 215 |
-
);
|
| 216 |
}
|
| 217 |
}
|
| 218 |
|
| 219 |
-
// PUT - Actualizar publicaci贸n
|
| 220 |
export async function PUT(request: NextRequest) {
|
| 221 |
try {
|
| 222 |
const body = await request.json();
|
| 223 |
-
const { id, status,
|
| 224 |
-
|
| 225 |
-
if (!id) {
|
| 226 |
-
return NextResponse.json(
|
| 227 |
-
{ success: false, error: "ID requerido" },
|
| 228 |
-
{ status: 400 }
|
| 229 |
-
);
|
| 230 |
-
}
|
| 231 |
|
| 232 |
-
|
| 233 |
-
if (status) updateData.status = status;
|
| 234 |
-
if (scheduledAt) updateData.scheduledAt = new Date(scheduledAt);
|
| 235 |
-
if (caption) updateData.caption = caption;
|
| 236 |
-
if (hashtags) updateData.hashtags = hashtags;
|
| 237 |
-
if (publishedAt) updateData.publishedAt = new Date(publishedAt);
|
| 238 |
-
if (engagementStats) updateData.engagementStats = engagementStats;
|
| 239 |
-
|
| 240 |
-
const post = await db.post.update({
|
| 241 |
-
where: { id },
|
| 242 |
-
data: updateData,
|
| 243 |
-
});
|
| 244 |
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
});
|
| 249 |
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
{ status: 500 }
|
| 255 |
-
);
|
| 256 |
}
|
| 257 |
}
|
| 258 |
|
| 259 |
-
// DELETE - Eliminar publicaci贸n
|
| 260 |
export async function DELETE(request: NextRequest) {
|
| 261 |
try {
|
| 262 |
const { searchParams } = new URL(request.url);
|
| 263 |
const id = searchParams.get("id");
|
|
|
|
| 264 |
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
);
|
| 270 |
-
}
|
| 271 |
-
|
| 272 |
-
await db.post.delete({
|
| 273 |
-
where: { id }
|
| 274 |
-
});
|
| 275 |
-
|
| 276 |
-
return NextResponse.json({
|
| 277 |
-
success: true,
|
| 278 |
-
message: "Publicaci贸n eliminada"
|
| 279 |
-
});
|
| 280 |
-
|
| 281 |
-
} catch (error) {
|
| 282 |
-
console.error("Error deleting post:", error);
|
| 283 |
-
return NextResponse.json(
|
| 284 |
-
{ success: false, error: "Error al eliminar publicaci贸n" },
|
| 285 |
-
{ status: 500 }
|
| 286 |
-
);
|
| 287 |
-
}
|
| 288 |
-
}
|
| 289 |
-
|
| 290 |
-
// Reglas espec铆ficas por plataforma
|
| 291 |
-
const PLATFORM_RULES: Record<string, {
|
| 292 |
-
maxCaptionLength?: number;
|
| 293 |
-
hashtagLimit?: number;
|
| 294 |
-
tone?: string;
|
| 295 |
-
bestPostingTimes?: string[];
|
| 296 |
-
}> = {
|
| 297 |
-
instagram: {
|
| 298 |
-
maxCaptionLength: 2200,
|
| 299 |
-
hashtagLimit: 30,
|
| 300 |
-
tone: "inspirador y visual",
|
| 301 |
-
bestPostingTimes: ["11:00", "14:00", "19:00", "21:00"]
|
| 302 |
-
},
|
| 303 |
-
tiktok: {
|
| 304 |
-
maxCaptionLength: 300,
|
| 305 |
-
hashtagLimit: 5,
|
| 306 |
-
tone: "casual y divertido",
|
| 307 |
-
bestPostingTimes: ["09:00", "12:00", "19:00"]
|
| 308 |
-
},
|
| 309 |
-
youtube: {
|
| 310 |
-
maxCaptionLength: 5000,
|
| 311 |
-
tone: "profesional e informativo",
|
| 312 |
-
bestPostingTimes: ["15:00", "16:00", "17:00"]
|
| 313 |
-
},
|
| 314 |
-
onlyfans: {
|
| 315 |
-
maxCaptionLength: 1000,
|
| 316 |
-
tone: "personal y exclusivo",
|
| 317 |
-
bestPostingTimes: ["10:00", "18:00", "22:00"]
|
| 318 |
-
},
|
| 319 |
-
patreon: {
|
| 320 |
-
maxCaptionLength: 5000,
|
| 321 |
-
tone: "profesional y cercano",
|
| 322 |
-
bestPostingTimes: ["10:00", "14:00", "18:00"]
|
| 323 |
-
},
|
| 324 |
-
twitter: {
|
| 325 |
-
maxCaptionLength: 280,
|
| 326 |
-
hashtagLimit: 3,
|
| 327 |
-
tone: "conciso y directo",
|
| 328 |
-
bestPostingTimes: ["09:00", "12:00", "17:00", "20:00"]
|
| 329 |
}
|
| 330 |
-
}
|
|
|
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
import ZAI from "z-ai-web-dev-sdk";
|
| 4 |
|
|
|
|
| 5 |
export async function GET(request: NextRequest) {
|
| 6 |
try {
|
| 7 |
const { searchParams } = new URL(request.url);
|
| 8 |
const status = searchParams.get("status");
|
| 9 |
const platform = searchParams.get("platform");
|
|
|
|
|
|
|
| 10 |
|
| 11 |
+
const where: Record<string, string | null> = {};
|
| 12 |
if (status) where.status = status;
|
|
|
|
| 13 |
if (platform) where.platformId = platform;
|
| 14 |
|
| 15 |
+
const posts = await db.post.findMany({ where, orderBy: { createdAt: "desc" }, take: 50 });
|
| 16 |
+
return NextResponse.json({ success: true, posts });
|
| 17 |
+
} catch {
|
| 18 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
}
|
| 20 |
}
|
| 21 |
|
|
|
|
| 22 |
export async function POST(request: NextRequest) {
|
| 23 |
try {
|
| 24 |
const body = await request.json();
|
| 25 |
+
const { title, type, caption, scheduledAt, platformId, autoGenerateCaption } = body;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
+
if (!title) {
|
| 28 |
+
return NextResponse.json({ success: false, error: "Titulo requerido" }, { status: 400 });
|
|
|
|
|
|
|
|
|
|
| 29 |
}
|
| 30 |
|
| 31 |
+
let finalCaption: string | null = caption || null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
+
if (autoGenerateCaption && !caption) {
|
| 34 |
+
try {
|
| 35 |
const zai = await ZAI.create();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
const completion = await zai.chat.completions.create({
|
| 37 |
messages: [
|
| 38 |
+
{ role: "system", content: "Eres un experto en redes sociales. Genera un caption atractivo." },
|
| 39 |
+
{ role: "user", content: "Caption para: " + title }
|
| 40 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
});
|
| 42 |
+
finalCaption = completion.choices[0]?.message?.content || null;
|
| 43 |
+
} catch {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
}
|
| 45 |
|
|
|
|
| 46 |
const post = await db.post.create({
|
| 47 |
data: {
|
| 48 |
+
title,
|
| 49 |
caption: finalCaption,
|
| 50 |
+
type: type || "post",
|
|
|
|
| 51 |
status: scheduledAt ? "scheduled" : "draft",
|
|
|
|
| 52 |
platformId: platformId || null,
|
| 53 |
+
scheduledAt: scheduledAt ? new Date(scheduledAt) : null
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
}
|
| 55 |
});
|
| 56 |
|
| 57 |
+
return NextResponse.json({ success: true, post });
|
| 58 |
+
} catch {
|
| 59 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
}
|
| 61 |
}
|
| 62 |
|
|
|
|
| 63 |
export async function PUT(request: NextRequest) {
|
| 64 |
try {
|
| 65 |
const body = await request.json();
|
| 66 |
+
const { id, status, publishedAt } = body;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
+
if (!id) return NextResponse.json({ success: false, error: "ID requerido" }, { status: 400 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
+
const data: { status?: string; publishedAt?: Date } = {};
|
| 71 |
+
if (status) data.status = status;
|
| 72 |
+
if (publishedAt) data.publishedAt = new Date(publishedAt);
|
|
|
|
| 73 |
|
| 74 |
+
const post = await db.post.update({ where: { id }, data });
|
| 75 |
+
return NextResponse.json({ success: true, post });
|
| 76 |
+
} catch {
|
| 77 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
| 78 |
}
|
| 79 |
}
|
| 80 |
|
|
|
|
| 81 |
export async function DELETE(request: NextRequest) {
|
| 82 |
try {
|
| 83 |
const { searchParams } = new URL(request.url);
|
| 84 |
const id = searchParams.get("id");
|
| 85 |
+
if (!id) return NextResponse.json({ success: false, error: "ID requerido" }, { status: 400 });
|
| 86 |
|
| 87 |
+
await db.post.delete({ where: { id } });
|
| 88 |
+
return NextResponse.json({ success: true });
|
| 89 |
+
} catch {
|
| 90 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
}
|
| 92 |
+
}
|
src/app/api/projects/route.ts
CHANGED
|
@@ -1,74 +1,64 @@
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
|
| 4 |
-
// GET - Listar todos los proyectos
|
| 5 |
export async function GET() {
|
| 6 |
try {
|
| 7 |
-
const projects = await db.project.findMany({
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
},
|
| 12 |
-
orderBy: { createdAt: "desc" },
|
| 13 |
-
});
|
| 14 |
-
|
| 15 |
-
return NextResponse.json({
|
| 16 |
-
success: true,
|
| 17 |
-
projects,
|
| 18 |
-
total: projects.length,
|
| 19 |
-
});
|
| 20 |
-
} catch (error) {
|
| 21 |
-
console.error("Error fetching projects:", error);
|
| 22 |
-
return NextResponse.json(
|
| 23 |
-
{ success: false, error: "Error al obtener proyectos" },
|
| 24 |
-
{ status: 500 }
|
| 25 |
-
);
|
| 26 |
}
|
| 27 |
}
|
| 28 |
|
| 29 |
-
// POST - Crear nuevo proyecto
|
| 30 |
export async function POST(request: NextRequest) {
|
| 31 |
try {
|
| 32 |
const body = await request.json();
|
| 33 |
const { name, description, style } = body;
|
| 34 |
|
| 35 |
if (!name) {
|
| 36 |
-
return NextResponse.json(
|
| 37 |
-
{ success: false, error: "El nombre del proyecto es requerido" },
|
| 38 |
-
{ status: 400 }
|
| 39 |
-
);
|
| 40 |
}
|
| 41 |
|
| 42 |
const project = await db.project.create({
|
| 43 |
-
data: {
|
| 44 |
-
name,
|
| 45 |
-
description: description || null,
|
| 46 |
-
style: style || "default",
|
| 47 |
-
status: "active",
|
| 48 |
-
},
|
| 49 |
});
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
input: `Crear proyecto: ${name}`,
|
| 57 |
-
output: `Proyecto ${name} creado exitosamente`,
|
| 58 |
-
completedAt: new Date(),
|
| 59 |
-
},
|
| 60 |
-
});
|
| 61 |
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
}
|
| 74 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
|
|
|
|
| 4 |
export async function GET() {
|
| 5 |
try {
|
| 6 |
+
const projects = await db.project.findMany({ orderBy: { createdAt: "desc" } });
|
| 7 |
+
return NextResponse.json({ success: true, projects });
|
| 8 |
+
} catch {
|
| 9 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
}
|
| 11 |
}
|
| 12 |
|
|
|
|
| 13 |
export async function POST(request: NextRequest) {
|
| 14 |
try {
|
| 15 |
const body = await request.json();
|
| 16 |
const { name, description, style } = body;
|
| 17 |
|
| 18 |
if (!name) {
|
| 19 |
+
return NextResponse.json({ success: false, error: "Nombre requerido" }, { status: 400 });
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
const project = await db.project.create({
|
| 23 |
+
data: { name, description: description || null, style: style || "default" }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
});
|
| 25 |
|
| 26 |
+
return NextResponse.json({ success: true, project });
|
| 27 |
+
} catch {
|
| 28 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
| 29 |
+
}
|
| 30 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
+
export async function PUT(request: NextRequest) {
|
| 33 |
+
try {
|
| 34 |
+
const body = await request.json();
|
| 35 |
+
const { id, name, description, status } = body;
|
| 36 |
+
|
| 37 |
+
if (!id) {
|
| 38 |
+
return NextResponse.json({ success: false, error: "ID requerido" }, { status: 400 });
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
const data: { name?: string; description?: string | null; status?: string } = {};
|
| 42 |
+
if (name) data.name = name;
|
| 43 |
+
if (description !== undefined) data.description = description || null;
|
| 44 |
+
if (status) data.status = status;
|
| 45 |
+
|
| 46 |
+
const project = await db.project.update({ where: { id }, data });
|
| 47 |
+
return NextResponse.json({ success: true, project });
|
| 48 |
+
} catch {
|
| 49 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
| 50 |
}
|
| 51 |
}
|
| 52 |
+
|
| 53 |
+
export async function DELETE(request: NextRequest) {
|
| 54 |
+
try {
|
| 55 |
+
const { searchParams } = new URL(request.url);
|
| 56 |
+
const id = searchParams.get("id");
|
| 57 |
+
if (!id) return NextResponse.json({ success: false, error: "ID requerido" }, { status: 400 });
|
| 58 |
+
|
| 59 |
+
await db.project.delete({ where: { id } });
|
| 60 |
+
return NextResponse.json({ success: true });
|
| 61 |
+
} catch {
|
| 62 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
| 63 |
+
}
|
| 64 |
+
}
|
src/app/api/prompt-engineer/route.ts
CHANGED
|
@@ -1,216 +1,52 @@
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import ZAI from "z-ai-web-dev-sdk";
|
| 3 |
|
| 4 |
-
const PROMPT_ENGINEER_SYSTEM = `Eres un Ingeniero de Prompts experto especializado en optimizar solicitudes en lenguaje natural para diferentes sistemas de IA. Tu trabajo es analizar la intenci贸n del usuario y generar prompts optimizados.
|
| 5 |
-
|
| 6 |
-
## TUS ESPECIALIDADES:
|
| 7 |
-
|
| 8 |
-
### 1. GENERACI脫N DE IM脕GENES
|
| 9 |
-
Para im谩genes, optimiza prompts incluyendo:
|
| 10 |
-
- Estilo art铆stico (realista, anime, oil painting, digital art, etc.)
|
| 11 |
-
- Iluminaci贸n (golden hour, studio lighting, dramatic, soft)
|
| 12 |
-
- Composici贸n (close-up, full body, landscape, portrait)
|
| 13 |
-
- Calidad y detalles (4K, highly detailed, masterpiece)
|
| 14 |
-
- Mood/atm贸sfera (cinematic, vibrant, moody)
|
| 15 |
-
- Para personas: descripci贸n f铆sica detallada, ropa, pose, expresi贸n
|
| 16 |
-
|
| 17 |
-
### 2. GENERACI脫N DE VIDEOS
|
| 18 |
-
Para videos, optimiza incluyendo:
|
| 19 |
-
- Escena y ambiente
|
| 20 |
-
- Movimientos de c谩mara (pan, zoom, tracking)
|
| 21 |
-
- Acciones y transiciones
|
| 22 |
-
- Duraci贸n estimada
|
| 23 |
-
- Estilo visual
|
| 24 |
-
- Audio/m煤sica sugerida
|
| 25 |
-
|
| 26 |
-
### 3. AN脕LISIS DE C脫DIGO
|
| 27 |
-
Para c贸digo, estructura la solicitud:
|
| 28 |
-
- Lenguaje de programaci贸n
|
| 29 |
-
- Framework espec铆fico si aplica
|
| 30 |
-
- Funcionalidad requerida
|
| 31 |
-
- Restricciones y requisitos
|
| 32 |
-
- Nivel de complejidad
|
| 33 |
-
|
| 34 |
-
### 4. CONTENIDO PARA REDES SOCIALES
|
| 35 |
-
- Plataforma espec铆fica (YouTube, TikTok, Instagram, Twitter)
|
| 36 |
-
- Tono y estilo
|
| 37 |
-
- Longitud apropiada
|
| 38 |
-
- Hashtags sugeridos
|
| 39 |
-
- Horarios 贸ptimos de publicaci贸n
|
| 40 |
-
|
| 41 |
-
## REGLAS DE CENSURA POR PLATAFORMA:
|
| 42 |
-
|
| 43 |
-
### YouTube:
|
| 44 |
-
- Sin desnudez ni contenido sexual
|
| 45 |
-
- Violencia moderada permitida con advertencia
|
| 46 |
-
- Sin discurso de odio
|
| 47 |
-
- Sin contenido ilegal
|
| 48 |
-
- Lenguaje moderado permitido
|
| 49 |
-
|
| 50 |
-
### TikTok:
|
| 51 |
-
- Sin desnudez ni insinuaciones sexuales
|
| 52 |
-
- Sin violencia gr谩fica
|
| 53 |
-
- Sin contenido de autolesi贸n
|
| 54 |
-
- Sin desinformaci贸n
|
| 55 |
-
- M煤sica con licencia 煤nicamente
|
| 56 |
-
|
| 57 |
-
### Instagram:
|
| 58 |
-
- Sin desnudez (arte cl谩sico con moderaci贸n)
|
| 59 |
-
- Sin violencia gr谩fica
|
| 60 |
-
- Sin contenido de autolesi贸n
|
| 61 |
-
- Sin discurso de odio
|
| 62 |
-
- Im谩genes editadas deben etiquetarse
|
| 63 |
-
|
| 64 |
-
### Twitter/X:
|
| 65 |
-
- Mayor libertad pero con advertencias
|
| 66 |
-
- Contenido sensible debe marcarse
|
| 67 |
-
- Sin contenido ilegal
|
| 68 |
-
|
| 69 |
-
## FORMATO DE RESPUESTA:
|
| 70 |
-
|
| 71 |
-
Responde SIEMPRE en este formato JSON:
|
| 72 |
-
{
|
| 73 |
-
"type": "image|video|code|text|social",
|
| 74 |
-
"optimizedPrompt": "El prompt optimizado y detallado",
|
| 75 |
-
"suggestions": ["sugerencia1", "sugerencia2"],
|
| 76 |
-
"censorWarnings": ["advertencia1"] o [],
|
| 77 |
-
"platformCompatible": ["youtube", "tiktok", ...],
|
| 78 |
-
"parameters": {
|
| 79 |
-
// Par谩metros t茅cnicos recomendados
|
| 80 |
-
}
|
| 81 |
-
}`;
|
| 82 |
-
|
| 83 |
export async function POST(request: NextRequest) {
|
| 84 |
try {
|
| 85 |
const body = await request.json();
|
| 86 |
-
const { prompt, type, platform
|
| 87 |
|
| 88 |
if (!prompt) {
|
| 89 |
-
return NextResponse.json(
|
| 90 |
-
{ success: false, error: "El prompt es requerido" },
|
| 91 |
-
{ status: 400 }
|
| 92 |
-
);
|
| 93 |
}
|
| 94 |
|
| 95 |
const zai = await ZAI.create();
|
| 96 |
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
const completion = await zai.chat.completions.create({
|
| 110 |
messages: [
|
| 111 |
-
{ role: "system", content:
|
| 112 |
-
{ role: "user", content:
|
| 113 |
-
]
|
| 114 |
-
temperature: 0.7,
|
| 115 |
-
max_tokens: 2000,
|
| 116 |
});
|
| 117 |
|
| 118 |
-
const
|
| 119 |
-
|
| 120 |
-
// Intentar parsear el JSON de la respuesta
|
| 121 |
-
let parsedResponse;
|
| 122 |
-
try {
|
| 123 |
-
// Buscar JSON en la respuesta
|
| 124 |
-
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
| 125 |
-
if (jsonMatch) {
|
| 126 |
-
parsedResponse = JSON.parse(jsonMatch[0]);
|
| 127 |
-
} else {
|
| 128 |
-
parsedResponse = {
|
| 129 |
-
type: type || "text",
|
| 130 |
-
optimizedPrompt: response,
|
| 131 |
-
suggestions: [],
|
| 132 |
-
censorWarnings: [],
|
| 133 |
-
platformCompatible: ["general"],
|
| 134 |
-
parameters: {}
|
| 135 |
-
};
|
| 136 |
-
}
|
| 137 |
-
} catch {
|
| 138 |
-
parsedResponse = {
|
| 139 |
-
type: type || "text",
|
| 140 |
-
optimizedPrompt: response,
|
| 141 |
-
suggestions: [],
|
| 142 |
-
censorWarnings: [],
|
| 143 |
-
platformCompatible: ["general"],
|
| 144 |
-
parameters: {}
|
| 145 |
-
};
|
| 146 |
-
}
|
| 147 |
|
| 148 |
return NextResponse.json({
|
| 149 |
success: true,
|
| 150 |
originalPrompt: prompt,
|
| 151 |
-
|
|
|
|
|
|
|
| 152 |
});
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
console.error("Error in prompt engineer:", error);
|
| 156 |
-
return NextResponse.json(
|
| 157 |
-
{ success: false, error: "Error al procesar el prompt" },
|
| 158 |
-
{ status: 500 }
|
| 159 |
-
);
|
| 160 |
}
|
| 161 |
-
}
|
| 162 |
-
|
| 163 |
-
// Endpoint para obtener sugerencias de prompts
|
| 164 |
-
export async function GET(request: NextRequest) {
|
| 165 |
-
const { searchParams } = new URL(request.url);
|
| 166 |
-
const category = searchParams.get("category") || "image";
|
| 167 |
-
|
| 168 |
-
const templates = {
|
| 169 |
-
image: [
|
| 170 |
-
{
|
| 171 |
-
name: "Retrato Profesional",
|
| 172 |
-
template: "Retrato profesional de [persona], iluminaci贸n de estudio, fondo neutro, alta calidad, expresi贸n [emoci贸n]",
|
| 173 |
-
variables: ["persona", "emoci贸n"]
|
| 174 |
-
},
|
| 175 |
-
{
|
| 176 |
-
name: "Escena Cinematogr谩fica",
|
| 177 |
-
template: "Escena cinematogr谩fica de [descripci贸n], iluminaci贸n golden hour, atm贸sfera [mood], estilo pel铆cula, 4K",
|
| 178 |
-
variables: ["descripci贸n", "mood"]
|
| 179 |
-
},
|
| 180 |
-
{
|
| 181 |
-
name: "Arte Digital",
|
| 182 |
-
template: "Arte digital de [sujeto], estilo [estilo], colores vibrantes, altamente detallado, trending on artstation",
|
| 183 |
-
variables: ["sujeto", "estilo"]
|
| 184 |
-
}
|
| 185 |
-
],
|
| 186 |
-
video: [
|
| 187 |
-
{
|
| 188 |
-
name: "Video Promocional",
|
| 189 |
-
template: "Video promocional de [producto/servicio], duraci贸n 30 segundos, estilo moderno, transiciones suaves",
|
| 190 |
-
variables: ["producto/servicio"]
|
| 191 |
-
},
|
| 192 |
-
{
|
| 193 |
-
name: "Tutorial Animado",
|
| 194 |
-
template: "Video tutorial animado sobre [tema], estilo infograf铆a, explicaci贸n paso a paso, iconos claros",
|
| 195 |
-
variables: ["tema"]
|
| 196 |
-
}
|
| 197 |
-
],
|
| 198 |
-
code: [
|
| 199 |
-
{
|
| 200 |
-
name: "API REST",
|
| 201 |
-
template: "Crear API REST en [lenguaje] con [framework] para [funcionalidad], incluir validaci贸n y manejo de errores",
|
| 202 |
-
variables: ["lenguaje", "framework", "funcionalidad"]
|
| 203 |
-
},
|
| 204 |
-
{
|
| 205 |
-
name: "Componente UI",
|
| 206 |
-
template: "Componente [tipo] en React con TypeScript, props: [props], incluir estados y animaciones",
|
| 207 |
-
variables: ["tipo", "props"]
|
| 208 |
-
}
|
| 209 |
-
]
|
| 210 |
-
};
|
| 211 |
-
|
| 212 |
-
return NextResponse.json({
|
| 213 |
-
success: true,
|
| 214 |
-
templates: templates[category as keyof typeof templates] || templates.image
|
| 215 |
-
});
|
| 216 |
-
}
|
|
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import ZAI from "z-ai-web-dev-sdk";
|
| 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
export async function POST(request: NextRequest) {
|
| 5 |
try {
|
| 6 |
const body = await request.json();
|
| 7 |
+
const { prompt, type, platform } = body;
|
| 8 |
|
| 9 |
if (!prompt) {
|
| 10 |
+
return NextResponse.json({ success: false, error: "Prompt requerido" }, { status: 400 });
|
|
|
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
|
| 13 |
const zai = await ZAI.create();
|
| 14 |
|
| 15 |
+
const platformGuides: Record<string, string> = {
|
| 16 |
+
instagram: "Formato cuadrado o vertical, colores vibrantes, lifestyle",
|
| 17 |
+
tiktok: "Video corto, dinamico, trending sounds",
|
| 18 |
+
youtube: "Thumbnail llamativo, titulo optimizado, larga duracion",
|
| 19 |
+
onlyfans: "Contenido exclusivo, conexion personal",
|
| 20 |
+
general: "Contenido universal, apto para todas las plataformas"
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
const typeGuides: Record<string, string> = {
|
| 24 |
+
image: "Prompt para generacion de imagen detallado",
|
| 25 |
+
video: "Prompt para video con movimiento y escenas",
|
| 26 |
+
reel: "Prompt para reel corto y dinamico",
|
| 27 |
+
carousel: "Prompt para serie de imagenes coherentes"
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
const guide = platformGuides[platform || "general"] || platformGuides.general;
|
| 31 |
+
const typeGuide = typeGuides[type || "image"] || typeGuides.image;
|
| 32 |
|
| 33 |
const completion = await zai.chat.completions.create({
|
| 34 |
messages: [
|
| 35 |
+
{ role: "system", content: "Eres un ingeniero de prompts experto. Optimiza el prompt del usuario para generar mejor contenido. Responde SOLO con el prompt optimizado, sin explicaciones." },
|
| 36 |
+
{ role: "user", content: Optimiza este prompt para en :\nGuia: \nTipo: \nPrompt original: }
|
| 37 |
+
]
|
|
|
|
|
|
|
| 38 |
});
|
| 39 |
|
| 40 |
+
const optimizedPrompt = completion.choices[0]?.message?.content || prompt;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
return NextResponse.json({
|
| 43 |
success: true,
|
| 44 |
originalPrompt: prompt,
|
| 45 |
+
optimizedPrompt,
|
| 46 |
+
type: type || "image",
|
| 47 |
+
platform: platform || "general"
|
| 48 |
});
|
| 49 |
+
} catch {
|
| 50 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
}
|
| 52 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/app/api/repos/route.ts
CHANGED
|
@@ -1,126 +1,30 @@
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import { db } from "@/lib/db";
|
| 3 |
-
import { exec } from "child_process";
|
| 4 |
-
import { promisify } from "util";
|
| 5 |
-
import path from "path";
|
| 6 |
-
import fs from "fs/promises";
|
| 7 |
|
| 8 |
-
const execAsync = promisify(exec);
|
| 9 |
-
|
| 10 |
-
const REPOS_DIR = path.join(process.cwd(), "repos");
|
| 11 |
-
|
| 12 |
-
// Asegurar que el directorio existe
|
| 13 |
-
async function ensureReposDir() {
|
| 14 |
-
try {
|
| 15 |
-
await fs.mkdir(REPOS_DIR, { recursive: true });
|
| 16 |
-
} catch (error) {
|
| 17 |
-
// Directorio ya existe
|
| 18 |
-
}
|
| 19 |
-
}
|
| 20 |
-
|
| 21 |
-
// GET - Listar todos los repositorios
|
| 22 |
export async function GET() {
|
| 23 |
try {
|
| 24 |
-
const repos = await db.repo.findMany({
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
},
|
| 29 |
-
orderBy: { createdAt: "desc" },
|
| 30 |
-
});
|
| 31 |
-
|
| 32 |
-
return NextResponse.json({
|
| 33 |
-
success: true,
|
| 34 |
-
repos,
|
| 35 |
-
total: repos.length,
|
| 36 |
-
});
|
| 37 |
-
} catch (error) {
|
| 38 |
-
console.error("Error fetching repos:", error);
|
| 39 |
-
return NextResponse.json(
|
| 40 |
-
{ success: false, error: "Error al obtener repositorios" },
|
| 41 |
-
{ status: 500 }
|
| 42 |
-
);
|
| 43 |
}
|
| 44 |
}
|
| 45 |
|
| 46 |
-
// POST - Clonar un nuevo repositorio
|
| 47 |
export async function POST(request: NextRequest) {
|
| 48 |
try {
|
| 49 |
const body = await request.json();
|
| 50 |
-
const { url, projectId } = body;
|
| 51 |
|
| 52 |
if (!url) {
|
| 53 |
-
return NextResponse.json(
|
| 54 |
-
{ success: false, error: "URL del repositorio es requerida" },
|
| 55 |
-
{ status: 400 }
|
| 56 |
-
);
|
| 57 |
}
|
| 58 |
|
| 59 |
-
// Extraer nombre del repo de la URL
|
| 60 |
-
const urlParts = url.replace(".git", "").split("/");
|
| 61 |
-
const name = urlParts[urlParts.length - 1] || "unknown-repo";
|
| 62 |
-
|
| 63 |
-
await ensureReposDir();
|
| 64 |
-
const targetPath = path.join(REPOS_DIR, name);
|
| 65 |
-
|
| 66 |
-
// Verificar si ya existe
|
| 67 |
-
try {
|
| 68 |
-
await fs.access(targetPath);
|
| 69 |
-
return NextResponse.json(
|
| 70 |
-
{ success: false, error: "El repositorio ya existe" },
|
| 71 |
-
{ status: 400 }
|
| 72 |
-
);
|
| 73 |
-
} catch {
|
| 74 |
-
// No existe, podemos clonar
|
| 75 |
-
}
|
| 76 |
-
|
| 77 |
-
// Clonar repositorio
|
| 78 |
-
try {
|
| 79 |
-
await execAsync(`git clone ${url} ${targetPath}`, {
|
| 80 |
-
timeout: 60000,
|
| 81 |
-
});
|
| 82 |
-
} catch (cloneError) {
|
| 83 |
-
console.error("Clone error:", cloneError);
|
| 84 |
-
return NextResponse.json(
|
| 85 |
-
{ success: false, error: "Error al clonar el repositorio" },
|
| 86 |
-
{ status: 500 }
|
| 87 |
-
);
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
// Guardar en base de datos
|
| 91 |
const repo = await db.repo.create({
|
| 92 |
-
data: {
|
| 93 |
-
url,
|
| 94 |
-
name,
|
| 95 |
-
status: "cloned",
|
| 96 |
-
projectId: projectId || null,
|
| 97 |
-
},
|
| 98 |
-
include: {
|
| 99 |
-
project: true,
|
| 100 |
-
},
|
| 101 |
});
|
| 102 |
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
type: "clone",
|
| 107 |
-
status: "completed",
|
| 108 |
-
input: url,
|
| 109 |
-
output: `Repositorio ${name} clonado exitosamente`,
|
| 110 |
-
completedAt: new Date(),
|
| 111 |
-
},
|
| 112 |
-
});
|
| 113 |
-
|
| 114 |
-
return NextResponse.json({
|
| 115 |
-
success: true,
|
| 116 |
-
repo,
|
| 117 |
-
message: `Repositorio ${name} clonado exitosamente`,
|
| 118 |
-
});
|
| 119 |
-
} catch (error) {
|
| 120 |
-
console.error("Error in POST repos:", error);
|
| 121 |
-
return NextResponse.json(
|
| 122 |
-
{ success: false, error: "Error interno del servidor" },
|
| 123 |
-
{ status: 500 }
|
| 124 |
-
);
|
| 125 |
}
|
| 126 |
-
}
|
|
|
|
| 1 |
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
import { db } from "@/lib/db";
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
export async function GET() {
|
| 5 |
try {
|
| 6 |
+
const repos = await db.repo.findMany({ orderBy: { createdAt: "desc" } });
|
| 7 |
+
return NextResponse.json({ success: true, repos });
|
| 8 |
+
} catch {
|
| 9 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
}
|
| 11 |
}
|
| 12 |
|
|
|
|
| 13 |
export async function POST(request: NextRequest) {
|
| 14 |
try {
|
| 15 |
const body = await request.json();
|
| 16 |
+
const { url, name, projectId } = body;
|
| 17 |
|
| 18 |
if (!url) {
|
| 19 |
+
return NextResponse.json({ success: false, error: "URL requerida" }, { status: 400 });
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
const repo = await db.repo.create({
|
| 23 |
+
data: { url, name: name || url.split("/").pop() || "repo", projectId: projectId || null }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
});
|
| 25 |
|
| 26 |
+
return NextResponse.json({ success: true, repo });
|
| 27 |
+
} catch {
|
| 28 |
+
return NextResponse.json({ success: false, error: "Error" }, { status: 500 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
}
|
| 30 |
+
}
|