feat(backend): complete AI and business migration

This commit is contained in:
poyrazavsever
2026-07-17 09:12:45 +03:00
parent 42f165b27e
commit 64a5f96872
23 changed files with 1286 additions and 409 deletions
+41 -72
View File
@@ -1,84 +1,53 @@
import { generateText } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
import { createGoogleGenerativeAI } from '@ai-sdk/google';
import { createClient } from '@/lib/supabase/server';
import { buildProjectRiskContext } from "@/server/ai/context";
import { getAiRuntime } from "@/server/ai/provider";
import { aiJsonError } from "@/server/ai/responses";
import { domainActorFromSession } from "@/server/auth/domain-actor";
import { getSessionContextFromHeaders } from "@/server/auth/session";
import { DomainError } from "@/server/domain/errors";
import { getDomainService } from "@/server/services/runtime";
import { generateText } from "ai";
import { NextResponse } from "next/server";
import { z } from "zod";
export const maxDuration = 30;
export const maxDuration = 120;
export async function POST(req: Request) {
const requestSchema = z.object({
projectId: z.string().trim().min(1).max(160).optional(),
}).strict();
export async function POST(request: Request) {
try {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return new Response(JSON.stringify({ error: 'Yetkisiz erişim' }), { status: 401 });
const context = await getSessionContextFromHeaders(new Headers(request.headers));
if (!context) {
throw new DomainError("UNAUTHENTICATED", "Oturum gerekli.");
}
if (context.profile.role !== "freelancer") {
throw new DomainError("FORBIDDEN", "Bu işlem yalnızca freelancer hesabına açıktır.");
}
const { projectId } = await req.json();
const { data: appSettings } = await supabase
.from("app_settings")
.select("*")
.eq("user_id", user.id)
.single();
const provider = appSettings?.ai_provider || "openai";
const apiKey = appSettings?.api_key;
if (!apiKey) {
return new Response(JSON.stringify({ error: 'Ayarlardan AI Sağlayıcı ve API Anahtarı seçmelisiniz.' }), { status: 400 });
}
let model;
if (provider === 'gemini') {
const google = createGoogleGenerativeAI({ apiKey });
model = google('gemini-1.5-pro-latest');
} else if (provider === 'groq') {
const groq = createOpenAI({ apiKey, baseURL: 'https://api.groq.com/openai/v1' });
model = groq('llama-3.1-8b-instant');
} else {
const openai = createOpenAI({ apiKey });
model = openai('gpt-4o');
}
// Fetch project details
let projectDataStr = "";
if (projectId) {
const { data: project } = await supabase.from('projects').select('*, clients(name)').eq('id', projectId).single();
if (!project) return new Response(JSON.stringify({ error: 'Proje bulunamadı.' }), { status: 404 });
const { data: tasks } = await supabase.from('tasks').select('status').eq('project_id', projectId);
const completedTasks = tasks?.filter(t => t.status === 'completed').length || 0;
const totalTasks = tasks?.length || 0;
projectDataStr = `Proje Adı: ${project.name}
Müşteri: ${project.clients?.name || 'Bilinmiyor'}
Durum: ${project.status}
Bütçe: ${project.budget_amount || 0} ${project.currency}
İlerleme: %${project.progress}
Başlangıç: ${project.start_date || 'Bilinmiyor'}
Bitiş (Deadline): ${project.due_date || 'Bilinmiyor'}
Görevler: ${totalTasks} adet (${completedTasks} tamamlandı)`;
} else {
// Analyze all active projects
const { data: projects } = await supabase.from('projects').select('name, status, due_date, progress').eq('user_id', user.id).eq('status', 'active');
if (!projects || projects.length === 0) return new Response(JSON.stringify({ error: 'Aktif proje bulunamadı.' }), { status: 404 });
projectDataStr = `Aktif Projeler:\n${projects.map(p => `- ${p.name} | İlerleme: %${p.progress} | Deadline: ${p.due_date || 'Yok'}`).join('\n')}`;
const parsed = requestSchema.safeParse(await request.json());
if (!parsed.success) {
throw new DomainError("VALIDATION_ERROR", "Proje risk isteği geçersiz.");
}
const actor = domainActorFromSession(context);
const projectContext = buildProjectRiskContext(
getDomainService(),
actor,
parsed.data.projectId,
);
const runtime = getAiRuntime(actor);
const { text } = await generateText({
model,
system: `Sen bir Proje Yönetim Uzmanısın. Verilen proje bilgilerini analiz ederek kısa, net ve aksiyon odaklı bir "Risk ve Durum Raporu" oluşturmalısın. Türkçe yanıt ver.`,
prompt: `Lütfen aşağıdaki proje verilerine göre riskleri ve önerilerini belirt:\n\n${projectDataStr}`,
model: runtime.model,
timeout: runtime.timeout,
system: `Sen bir proje yönetim uzmanısın.
Verilen proje bilgilerini analiz ederek kısa, net ve aksiyon odaklı bir risk ve durum raporu oluştur.
Türkçe yanıt ver; yalnızca sağlanan verilere dayan ve belirsizlikleri açıkça belirt.`,
prompt: `Aşağıdaki server-side proje bağlamındaki riskleri ve önerileri belirt:\n\n${projectContext}`,
});
return new Response(JSON.stringify({ text }), {
headers: { 'Content-Type': 'application/json' },
});
} catch (error: any) {
console.error("AI Project Risk Error:", error);
return new Response(JSON.stringify({ error: error.message }), { status: 500 });
return NextResponse.json({ text });
} catch (error) {
return aiJsonError(error);
}
}