diff --git a/app/RegisterSW.tsx b/app/RegisterSW.tsx
new file mode 100644
index 00000000..0af4afe5
--- /dev/null
+++ b/app/RegisterSW.tsx
@@ -0,0 +1,16 @@
+"use client";
+
+import { useEffect } from "react";
+
+export default function RegisterSW() {
+ useEffect(() => {
+ if ("serviceWorker" in navigator) {
+ navigator.serviceWorker
+ .register("/sw.js")
+ .then(() => console.log("SW registered"))
+ .catch((err) => console.error("SW registration failed:", err));
+ }
+ }, []);
+
+ return null;
+}
diff --git a/app/api/github/check-repo/[repo_name]/route.ts b/app/api/github/check-repo/[repo_name]/route.ts
index a16e6c5a..78a8d1dd 100644
--- a/app/api/github/check-repo/[repo_name]/route.ts
+++ b/app/api/github/check-repo/[repo_name]/route.ts
@@ -1,31 +1,33 @@
-import { NextResponse } from 'next/server';
-import { checkRepositoryAvailability } from '@/lib/services/github';
+import { NextResponse } from "next/server";
+import type { NextRequest } from "next/server";
+import { checkRepositoryAvailability } from "@/lib/github-api";
+import { getPlainServiceToken } from "@/lib/services/tokens";
-interface RouteContext {
- params: Promise<{ repo_name: string }>;
-}
+export const runtime = "nodejs";
+export const dynamic = "force-dynamic";
-export async function GET(_request: Request, { params }: RouteContext) {
+export async function GET(
+ _req: NextRequest,
+ { params }: { params: Promise<{ repo_name: string }> }
+) {
try {
- const { repo_name } = await params;
- const result = await checkRepositoryAvailability(repo_name);
- if (result.exists) {
- return NextResponse.json({ available: false, username: result.username }, { status: 409 });
+ const { repo_name } = await params; // π₯ μ¬κΈ°μ await ν΄μΌ ν¨
+
+ const token = await getPlainServiceToken("github");
+ if (!token) {
+ return NextResponse.json({ error: "No GitHub token" }, { status: 401 });
}
- return NextResponse.json({ available: true, username: result.username });
- } catch (error) {
- console.error('[API] Failed to check repository availability:', error);
- const status = error instanceof Error && 'status' in error ? (error as any).status ?? 500 : 500;
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to check repository availability',
- message: error instanceof Error ? error.message : 'Unknown error',
- },
- { status },
+
+ const owner = process.env.GITHUB_OWNER!;
+ const result = await checkRepositoryAvailability(
+ token,
+ owner,
+ repo_name
);
+
+ return NextResponse.json(result);
+ } catch (err) {
+ console.error(err);
+ return NextResponse.json({ error: "Failed" }, { status: 500 });
}
}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/github/create-repo/route.ts b/app/api/github/create-repo/route.ts
index f3e2d827..ce1cbda7 100644
--- a/app/api/github/create-repo/route.ts
+++ b/app/api/github/create-repo/route.ts
@@ -1,28 +1,70 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { createRepository, getGithubUser } from '@/lib/services/github';
+import { NextRequest, NextResponse } from "next/server";
+import { getPlainServiceToken } from "@/lib/services/tokens";
+
+export const runtime = "nodejs";
+export const dynamic = "force-dynamic";
export async function POST(request: NextRequest) {
try {
const body = await request.json();
- if (!body || typeof body !== 'object') {
- return NextResponse.json({ success: false, error: 'Invalid payload' }, { status: 400 });
+
+ const repoName = body?.repo_name;
+ const description = body?.description ?? "";
+ const isPrivate = body?.private ?? false;
+
+ if (!repoName || typeof repoName !== "string") {
+ return NextResponse.json(
+ { success: false, error: "repo_name is required" },
+ { status: 400 }
+ );
}
- const repoName = typeof body.repo_name === 'string' ? body.repo_name : undefined;
- if (!repoName) {
- return NextResponse.json({ success: false, error: 'repo_name is required' }, { status: 400 });
+ const token = await getPlainServiceToken("github");
+ if (!token) {
+ return NextResponse.json(
+ { success: false, error: "GitHub token not configured" },
+ { status: 401 }
+ );
}
- const description = typeof body.description === 'string' ? body.description : '';
- const isPrivate = typeof body.private === 'boolean' ? body.private : false;
+ // 1οΈβ£ μ¬μ©μ μ 보 κ°μ Έμ€κΈ°
+ const userRes = await fetch("https://api.github.com/user", {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ Accept: "application/vnd.github+json",
+ },
+ });
+
+ if (!userRes.ok) {
+ throw new Error("Failed to fetch GitHub user");
+ }
- const repo = await createRepository({
- repoName,
- description,
- private: isPrivate,
+ const user = await userRes.json();
+
+ // 2οΈβ£ λ ν¬ μμ±
+ const repoRes = await fetch("https://api.github.com/user/repos", {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "application/json",
+ Accept: "application/vnd.github+json",
+ },
+ body: JSON.stringify({
+ name: repoName,
+ description,
+ private: isPrivate,
+ }),
});
- const user = await getGithubUser();
+ if (!repoRes.ok) {
+ const err = await repoRes.json().catch(() => ({}));
+ return NextResponse.json(
+ { success: false, error: err?.message ?? "Failed to create repo" },
+ { status: repoRes.status }
+ );
+ }
+
+ const repo = await repoRes.json();
return NextResponse.json({
success: true,
@@ -33,18 +75,14 @@ export async function POST(request: NextRequest) {
owner: user.login,
});
} catch (error) {
- console.error('[API] Failed to create GitHub repository:', error);
- const status = error instanceof Error && 'status' in error ? (error as any).status ?? 500 : 500;
+ console.error("[API] Failed to create GitHub repository:", error);
+
return NextResponse.json(
{
success: false,
- error: 'Failed to create GitHub repository',
- message: error instanceof Error ? error.message : 'Unknown error',
+ error: "Failed to create GitHub repository",
},
- { status },
+ { status: 500 }
);
}
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
+}
\ No newline at end of file
diff --git a/app/api/projects/[project_id]/files/content/route.ts b/app/api/projects/[project_id]/files/content/route.ts
deleted file mode 100644
index c9d36140..00000000
--- a/app/api/projects/[project_id]/files/content/route.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-/**
- * GET /api/projects/[id]/files/content - Get file content
- */
-
-import { NextRequest, NextResponse } from 'next/server';
-import {
- readProjectFileContent,
- writeProjectFileContent,
- FileBrowserError,
-} from '@/lib/services/file-browser';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-export async function GET(request: NextRequest, { params }: RouteContext) {
- try {
- const { project_id } = await params;
- const url = new URL(request.url);
- const filePath = url.searchParams.get('path');
-
- if (!filePath) {
- return NextResponse.json(
- { success: false, error: 'path query parameter is required' },
- { status: 400 }
- );
- }
-
- const file = await readProjectFileContent(project_id, filePath);
-
- return NextResponse.json({
- success: true,
- data: file,
- });
- } catch (error) {
- if (error instanceof FileBrowserError) {
- return NextResponse.json(
- { success: false, error: error.message },
- { status: error.status }
- );
- }
-
- console.error('[API] Failed to read project file:', error);
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to read project file',
- },
- { status: 500 }
- );
- }
-}
-
-export async function PUT(request: NextRequest, { params }: RouteContext) {
- try {
- const { project_id } = await params;
- const body = await request.json();
- const filePath = body.path;
- const content = body.content;
-
- if (!filePath || typeof filePath !== 'string') {
- return NextResponse.json(
- { success: false, error: 'path is required' },
- { status: 400 }
- );
- }
-
- if (typeof content !== 'string') {
- return NextResponse.json(
- { success: false, error: 'content must be a string' },
- { status: 400 }
- );
- }
-
- await writeProjectFileContent(project_id, filePath, content);
-
- return NextResponse.json({
- success: true,
- data: { path: filePath },
- });
- } catch (error) {
- if (error instanceof FileBrowserError) {
- return NextResponse.json(
- { success: false, error: error.message },
- { status: error.status }
- );
- }
-
- console.error('[API] Failed to write project file:', error);
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to write project file',
- },
- { status: 500 }
- );
- }
-}
diff --git a/app/api/projects/[project_id]/files/route.ts b/app/api/projects/[project_id]/files/route.ts
deleted file mode 100644
index 2d4ea18b..00000000
--- a/app/api/projects/[project_id]/files/route.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * GET /api/projects/[id]/files - Get project directory list
- */
-
-import { NextRequest, NextResponse } from 'next/server';
-import { listProjectDirectory, FileBrowserError } from '@/lib/services/file-browser';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-export async function GET(request: NextRequest, { params }: RouteContext) {
- try {
- const { project_id } = await params;
- const url = new URL(request.url);
- const dir = url.searchParams.get('path') ?? '.';
-
- const entries = await listProjectDirectory(project_id, dir);
-
- return NextResponse.json({
- success: true,
- data: {
- entries,
- },
- });
- } catch (error) {
- if (error instanceof FileBrowserError) {
- return NextResponse.json(
- { success: false, error: error.message },
- { status: error.status }
- );
- }
-
- console.error('[API] Failed to list project files:', error);
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to list project files',
- },
- { status: 500 }
- );
- }
-}
diff --git a/app/api/projects/[project_id]/github/connect/route.ts b/app/api/projects/[project_id]/github/connect/route.ts
deleted file mode 100644
index 56556250..00000000
--- a/app/api/projects/[project_id]/github/connect/route.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { connectProjectToGitHub } from '@/lib/services/github';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-export async function POST(request: NextRequest, { params }: RouteContext) {
- try {
- const { project_id } = await params;
- const body = await request.json();
- if (!body || typeof body !== 'object') {
- return NextResponse.json({ success: false, error: 'Invalid payload' }, { status: 400 });
- }
-
- const repoName = typeof body.repo_name === 'string' ? body.repo_name : undefined;
- if (!repoName) {
- return NextResponse.json({ success: false, error: 'repo_name is required' }, { status: 400 });
- }
-
- const description = typeof body.description === 'string' ? body.description : '';
- const isPrivate = typeof body.private === 'boolean' ? body.private : false;
-
- const result = await connectProjectToGitHub(project_id, {
- repoName,
- description,
- private: isPrivate,
- });
-
- return NextResponse.json({
- success: true,
- repo_url: result.repo_url,
- clone_url: result.clone_url,
- default_branch: result.default_branch,
- owner: result.owner,
- message: 'GitHub repository created and connected',
- });
- } catch (error) {
- console.error('[API] Failed to connect GitHub repository:', error);
- const status = error instanceof Error && 'status' in error ? (error as any).status ?? 500 : 500;
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to connect GitHub repository',
- message: error instanceof Error ? error.message : 'Unknown error',
- },
- { status },
- );
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/projects/[project_id]/github/push/route.ts b/app/api/projects/[project_id]/github/push/route.ts
deleted file mode 100644
index 0858a12a..00000000
--- a/app/api/projects/[project_id]/github/push/route.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { NextResponse } from 'next/server';
-import { pushProjectToGitHub } from '@/lib/services/github';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-export async function POST(_request: Request, { params }: RouteContext) {
- try {
- const { project_id } = await params;
- await pushProjectToGitHub(project_id);
- return NextResponse.json({ success: true, message: 'Changes pushed to GitHub' });
- } catch (error) {
- console.error('[API] Failed to push to GitHub:', error);
- const status = error instanceof Error && 'status' in error ? (error as any).status ?? 500 : 500;
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to push to GitHub',
- message: error instanceof Error ? error.message : 'Unknown error',
- },
- { status },
- );
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/projects/[project_id]/install-dependencies/route.ts b/app/api/projects/[project_id]/install-dependencies/route.ts
deleted file mode 100644
index b5bfaffa..00000000
--- a/app/api/projects/[project_id]/install-dependencies/route.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * POST /api/projects/[project_id]/install-dependencies
- * Run npm install (or equivalent) for a project workspace.
- */
-
-import { NextResponse } from 'next/server';
-import { previewManager } from '@/lib/services/preview';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-export async function POST(
- _request: Request,
- { params }: RouteContext
-) {
- try {
- const { project_id } = await params;
- const result = await previewManager.installDependencies(project_id);
-
- return NextResponse.json({
- success: true,
- logs: result.logs,
- });
- } catch (error) {
- console.error('[API] Failed to install dependencies:', error);
- return NextResponse.json(
- {
- success: false,
- error:
- error instanceof Error
- ? error.message
- : 'Failed to install dependencies',
- },
- { status: 500 }
- );
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/projects/[project_id]/preview/start/route.ts b/app/api/projects/[project_id]/preview/start/route.ts
deleted file mode 100644
index e36399c2..00000000
--- a/app/api/projects/[project_id]/preview/start/route.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * POST /api/projects/[id]/preview/start
- * Launches the development server for a project and returns the preview URL.
- */
-
-import { NextResponse } from 'next/server';
-import { previewManager } from '@/lib/services/preview';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-export async function POST(
- _request: Request,
- { params }: RouteContext
-) {
- try {
- const { project_id } = await params;
- const preview = await previewManager.start(project_id);
-
- return NextResponse.json({
- success: true,
- data: preview,
- });
- } catch (error) {
- console.error('[API] Failed to start preview:', error);
- return NextResponse.json(
- {
- success: false,
- error:
- error instanceof Error ? error.message : 'Failed to start preview',
- },
- { status: 500 }
- );
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/projects/[project_id]/preview/status/route.ts b/app/api/projects/[project_id]/preview/status/route.ts
deleted file mode 100644
index 8e54b56a..00000000
--- a/app/api/projects/[project_id]/preview/status/route.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * GET /api/projects/[id]/preview/status
- * Returns the current preview status for the project.
- */
-
-import { NextResponse } from 'next/server';
-import { previewManager } from '@/lib/services/preview';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-export async function GET(
- _request: Request,
- { params }: RouteContext
-) {
- try {
- const { project_id } = await params;
- const preview = previewManager.getStatus(project_id);
-
- return NextResponse.json({
- success: true,
- data: preview,
- });
- } catch (error) {
- console.error('[API] Failed to fetch preview status:', error);
- return NextResponse.json(
- {
- success: false,
- error:
- error instanceof Error
- ? error.message
- : 'Failed to fetch preview status',
- },
- { status: 500 }
- );
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/projects/[project_id]/preview/stop/route.ts b/app/api/projects/[project_id]/preview/stop/route.ts
deleted file mode 100644
index 6cfb5d55..00000000
--- a/app/api/projects/[project_id]/preview/stop/route.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * POST /api/projects/[id]/preview/stop
- * Stops the development server for the project if it is running.
- */
-
-import { NextResponse } from 'next/server';
-import { previewManager } from '@/lib/services/preview';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-export async function POST(
- _request: Request,
- { params }: RouteContext
-) {
- try {
- const { project_id } = await params;
- const preview = await previewManager.stop(project_id);
-
- return NextResponse.json({
- success: true,
- data: preview,
- });
- } catch (error) {
- console.error('[API] Failed to stop preview:', error);
- return NextResponse.json(
- {
- success: false,
- error:
- error instanceof Error ? error.message : 'Failed to stop preview',
- },
- { status: 500 }
- );
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/projects/[project_id]/route.ts b/app/api/projects/[project_id]/route.ts
deleted file mode 100644
index 59c0c612..00000000
--- a/app/api/projects/[project_id]/route.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-/**
- * Single Project API Routes
- * GET /api/projects/[project_id] - Retrieve project
- * PUT /api/projects/[project_id] - Update project
- * DELETE /api/projects/[project_id] - Delete project
- */
-
-import { NextRequest, NextResponse } from 'next/server';
-import {
- getProjectById,
- updateProject,
- deleteProject,
-} from '@/lib/services/project';
-import type { UpdateProjectInput } from '@/types/backend';
-import { serializeProject } from '@/lib/serializers/project';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-/**
- * GET /api/projects/[project_id]
- * Retrieve specific project
- */
-export async function GET(
- request: NextRequest,
- { params }: RouteContext
-) {
- try {
- const { project_id } = await params;
- const project = await getProjectById(project_id);
-
- if (!project) {
- return NextResponse.json(
- { success: false, error: 'Project not found' },
- { status: 404 }
- );
- }
-
- return NextResponse.json({ success: true, data: serializeProject(project) });
- } catch (error) {
- console.error('[API] Failed to get project:', error);
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to fetch project',
- message: error instanceof Error ? error.message : 'Unknown error',
- },
- { status: 500 }
- );
- }
-}
-
-/**
- * PUT /api/projects/[project_id]
- * Update project
- */
-export async function PUT(
- request: NextRequest,
- { params }: RouteContext
-) {
- try {
- const { project_id } = await params;
- const body = await request.json();
-
- const input: UpdateProjectInput = {
- name: body.name,
- description: body.description,
- status: body.status,
- previewUrl: body.previewUrl,
- previewPort: body.previewPort,
- preferredCli: body.preferredCli,
- selectedModel: body.selectedModel,
- settings: body.settings,
- };
-
- const project = await updateProject(project_id, input);
- return NextResponse.json({ success: true, data: serializeProject(project) });
- } catch (error) {
- console.error('[API] Failed to update project:', error);
-
- // Distinguish between different error types
- if (error instanceof Error) {
- if (error.message.includes('not found')) {
- return NextResponse.json(
- { success: false, error: 'Project not found' },
- { status: 404 }
- );
- }
- if (error.message.includes('validation') || error.message.includes('invalid')) {
- return NextResponse.json(
- { success: false, error: 'Invalid input', message: error.message },
- { status: 400 }
- );
- }
- }
-
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to update project',
- message: error instanceof Error ? error.message : 'Unknown error',
- },
- { status: 500 }
- );
- }
-}
-
-/**
- * DELETE /api/projects/[project_id]
- * Delete project
- */
-export async function DELETE(
- request: NextRequest,
- { params }: RouteContext
-) {
- try {
- const { project_id } = await params;
- await deleteProject(project_id);
-
- return NextResponse.json({
- success: true,
- message: 'Project deleted successfully',
- });
- } catch (error) {
- console.error('[API] Failed to delete project:', error);
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to delete project',
- message: error instanceof Error ? error.message : 'Unknown error',
- },
- { status: 500 }
- );
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/projects/[project_id]/services/[service_id]/route.ts b/app/api/projects/[project_id]/services/[service_id]/route.ts
deleted file mode 100644
index 17973b03..00000000
--- a/app/api/projects/[project_id]/services/[service_id]/route.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { NextResponse } from 'next/server';
-import { deleteProjectService } from '@/lib/services/project-services';
-
-interface RouteContext {
- params: Promise<{ project_id: string; service_id: string }>;
-}
-
-export async function DELETE(_request: Request, { params }: RouteContext) {
- try {
- const { service_id } = await params;
- const deleted = await deleteProjectService(service_id);
- if (!deleted) {
- return NextResponse.json({ success: false, error: 'Service not found' }, { status: 404 });
- }
-
- return NextResponse.json({ success: true, message: 'Service disconnected' });
- } catch (error) {
- console.error('[API] Failed to delete project service:', error);
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to delete project service',
- message: error instanceof Error ? error.message : 'Unknown error',
- },
- { status: 500 },
- );
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/projects/[project_id]/services/route.ts b/app/api/projects/[project_id]/services/route.ts
deleted file mode 100644
index 260fb2c7..00000000
--- a/app/api/projects/[project_id]/services/route.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { NextResponse } from 'next/server';
-import { listProjectServices } from '@/lib/services/project-services';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-export async function GET(_request: Request, { params }: RouteContext) {
- try {
- const { project_id } = await params;
- const services = await listProjectServices(project_id);
- const payload = services.map((service) => ({
- ...service,
- service_data: service.serviceData,
- }));
- return NextResponse.json(payload);
- } catch (error) {
- console.error('[API] Failed to load project services:', error);
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to load project services',
- message: error instanceof Error ? error.message : 'Unknown error',
- },
- { status: 500 },
- );
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/projects/[project_id]/supabase/connect/route.ts b/app/api/projects/[project_id]/supabase/connect/route.ts
deleted file mode 100644
index 75c6b523..00000000
--- a/app/api/projects/[project_id]/supabase/connect/route.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { connectExistingSupabase } from '@/lib/services/supabase';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-export async function POST(request: NextRequest, { params }: RouteContext) {
- try {
- const { project_id } = await params;
- const body = await request.json();
- const supabaseProjectId =
- typeof body?.project_id === 'string'
- ? body.project_id
- : typeof body?.supabase_project_id === 'string'
- ? body.supabase_project_id
- : undefined;
- const projectUrl = typeof body?.project_url === 'string' ? body.project_url : undefined;
- if (!supabaseProjectId || !projectUrl) {
- return NextResponse.json(
- { success: false, error: 'project_id and project_url are required' },
- { status: 400 },
- );
- }
-
- const result = await connectExistingSupabase(project_id, {
- projectId: supabaseProjectId,
- projectUrl,
- projectName: typeof body?.project_name === 'string' ? body.project_name : undefined,
- region: typeof body?.region === 'string' ? body.region : undefined,
- });
- return NextResponse.json({ success: true, data: result });
- } catch (error) {
- console.error('[API] Failed to connect Supabase project:', error);
- const status = error instanceof Error && 'status' in error ? (error as any).status ?? 500 : 500;
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to connect Supabase project',
- message: error instanceof Error ? error.message : 'Unknown error',
- },
- { status },
- );
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/projects/[project_id]/vercel/connect/route.ts b/app/api/projects/[project_id]/vercel/connect/route.ts
deleted file mode 100644
index a3e6ab1a..00000000
--- a/app/api/projects/[project_id]/vercel/connect/route.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { connectVercelProject } from '@/lib/services/vercel';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-export async function POST(request: NextRequest, { params }: RouteContext) {
- try {
- const { project_id } = await params;
- const body = await request.json();
- const projectName = typeof body?.project_name === 'string' ? body.project_name : undefined;
- if (!projectName) {
- return NextResponse.json({ success: false, error: 'project_name is required' }, { status: 400 });
- }
-
- const teamId =
- typeof body?.team_id === 'string'
- ? body.team_id
- : typeof body?.teamId === 'string'
- ? body.teamId
- : undefined;
-
- const result = await connectVercelProject(project_id, projectName, {
- githubRepo: typeof body?.github_repo === 'string' ? body.github_repo : undefined,
- teamId,
- });
- return NextResponse.json({
- success: true,
- data: result,
- message: `Connected Vercel project ${projectName}`,
- });
- } catch (error) {
- console.error('[API] Failed to connect Vercel project:', error);
- const status = error instanceof Error && 'status' in error ? (error as any).status ?? 500 : 500;
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to connect Vercel project',
- message: error instanceof Error ? error.message : 'Unknown error',
- },
- { status },
- );
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/projects/[project_id]/vercel/deploy/route.ts b/app/api/projects/[project_id]/vercel/deploy/route.ts
deleted file mode 100644
index b1af3916..00000000
--- a/app/api/projects/[project_id]/vercel/deploy/route.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { NextResponse } from 'next/server';
-import { triggerVercelDeployment } from '@/lib/services/vercel';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-export async function POST(_request: Request, { params }: RouteContext) {
- try {
- const { project_id } = await params;
- const result = await triggerVercelDeployment(project_id);
- return NextResponse.json({
- success: true,
- deployment_id: result.deploymentId ?? null,
- deployment_url: result.deploymentUrl ?? null,
- status: result.status ?? null,
- });
- } catch (error) {
- console.error('[API] Failed to trigger Vercel deployment:', error);
- const status = error instanceof Error && 'status' in error ? (error as any).status ?? 500 : 500;
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to trigger Vercel deployment',
- message: error instanceof Error ? error.message : 'Unknown error',
- },
- { status },
- );
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/projects/[project_id]/vercel/deployment/current/route.ts b/app/api/projects/[project_id]/vercel/deployment/current/route.ts
deleted file mode 100644
index 24720bae..00000000
--- a/app/api/projects/[project_id]/vercel/deployment/current/route.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { NextResponse } from 'next/server';
-import { getCurrentDeploymentStatus } from '@/lib/services/vercel';
-
-interface RouteContext {
- params: Promise<{ project_id: string }>;
-}
-
-export async function GET(_request: Request, { params }: RouteContext) {
- try {
- const { project_id } = await params;
- const status = await getCurrentDeploymentStatus(project_id);
- return NextResponse.json(status);
- } catch (error) {
- console.error('[API] Failed to get deployment status:', error);
- const statusCode = error instanceof Error && 'status' in error ? (error as any).status ?? 500 : 500;
- return NextResponse.json(
- {
- success: false,
- error: 'Failed to get deployment status',
- message: error instanceof Error ? error.message : 'Unknown error',
- },
- { status: statusCode },
- );
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/projects/route.ts b/app/api/projects/route.ts
deleted file mode 100644
index 3b097ae5..00000000
--- a/app/api/projects/route.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * Projects API Routes
- * GET /api/projects - Get all projects
- * POST /api/projects - Create new project
- */
-
-import { NextRequest } from 'next/server';
-import { getAllProjects, createProject } from '@/lib/services/project';
-import type { CreateProjectInput } from '@/types/backend';
-import { serializeProjects, serializeProject } from '@/lib/serializers/project';
-import { getDefaultModelForCli, normalizeModelId } from '@/lib/constants/cliModels';
-import { createSuccessResponse, createErrorResponse, handleApiError } from '@/lib/utils/api-response';
-
-/**
- * GET /api/projects
- * Get all projects list
- */
-export async function GET() {
- try {
- const projects = await getAllProjects();
- return createSuccessResponse(serializeProjects(projects));
- } catch (error) {
- return handleApiError(error, 'API', 'Failed to fetch projects');
- }
-}
-
-/**
- * POST /api/projects
- * Create new project
- */
-export async function POST(request: NextRequest) {
- try {
- const body = await request.json();
- const preferredCli = String(body.preferredCli || body.preferred_cli || 'claude').toLowerCase();
- const requestedModel = body.selectedModel || body.selected_model;
-
- const input: CreateProjectInput = {
- project_id: body.project_id,
- name: body.name,
- initialPrompt: body.initialPrompt || body.initial_prompt,
- preferredCli,
- selectedModel: normalizeModelId(preferredCli, requestedModel ?? getDefaultModelForCli(preferredCli)),
- description: body.description,
- };
-
- // Validation
- if (!input.project_id || !input.name) {
- return createErrorResponse('project_id and name are required', undefined, 400);
- }
-
- const project = await createProject(input);
- return createSuccessResponse(serializeProject(project), 201);
- } catch (error) {
- return handleApiError(error, 'API', 'Failed to create project');
- }
-}
-
-export const runtime = 'nodejs';
-export const dynamic = 'force-dynamic';
diff --git a/app/api/recipe/route.ts b/app/api/recipe/route.ts
new file mode 100644
index 00000000..368a37df
--- /dev/null
+++ b/app/api/recipe/route.ts
@@ -0,0 +1,70 @@
+import { NextResponse } from "next/server";
+
+export const runtime = "nodejs";
+
+export async function POST(req: Request) {
+ try {
+ const { ingredients } = await req.json();
+
+ if (!ingredients) {
+ return NextResponse.json(
+ { recipes: "μ¬λ£λ₯Ό μ
λ ₯ν΄μ£ΌμΈμ π" },
+ { status: 400 }
+ );
+ }
+
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
+ },
+ body: JSON.stringify({
+ model: "gpt-4o-mini",
+ messages: [
+ { role: "system", content: "λλ μ리 μ λ¬Έκ°μΌ." },
+ {
+ role: "user",
+ content: `λ€μ μ¬λ£λ‘ λ§λ€ μ μλ μ리λ₯Ό 3κ° μΆμ²ν΄μ€: ${ingredients}`,
+ },
+ ],
+ }),
+ });
+
+ const data = await response.json();
+
+ console.log("OPENAI RAW RESPONSE π", data);
+
+ // π΄ OpenAIμμ μλ¬κ° μμ κ²½μ° (μΏΌν° μ΄κ³Ό λ±)
+ if (!response.ok) {
+ console.error("OpenAI Error:", data);
+
+ return NextResponse.json({
+ recipes: `β οΈ νμ¬ AI μ¬μ©λμ΄ μ΄κ³Όλμ΄ μμ μΆμ²μ 보μ¬λ립λλ€.
+
+1. ${ingredients} λ³Άμ
+2. ${ingredients} μ€λ―λ
+3. ${ingredients} μλ¬λ`,
+ });
+ }
+
+ // π΄ choicesκ° μλ κ²½μ° λ°©μ΄
+ if (!data.choices || !data.choices[0]) {
+ return NextResponse.json({
+ recipes: "AI μλ΅μ΄ λΉμ΄ μμ΅λλ€. λ€μ μλν΄ μ£ΌμΈμ.",
+ });
+ }
+
+ // β
μ μ μλ΅
+ return NextResponse.json({
+ recipes: data.choices[0].message.content,
+ });
+
+ } catch (error) {
+ console.error("SERVER ERROR:", error);
+
+ return NextResponse.json({
+ recipes: "μλ² μ€λ₯κ° λ°μνμ΅λλ€. μ μ ν λ€μ μλν΄μ£ΌμΈμ.",
+ });
+ }
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 73487b0b..0ebd0bfe 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -4,10 +4,12 @@ import GlobalSettingsProvider from '@/contexts/GlobalSettingsContext'
import { AuthProvider } from '@/contexts/AuthContext'
import Header from '@/components/layout/Header'
import { Metadata } from 'next'
+import RegisterSW from './RegisterSW'
export const metadata: Metadata = {
title: 'Claudable',
description: 'Claudable Application',
+ manifest: '/manifest.json',
icons: {
icon: '/Claudable_Icon.png',
},
@@ -24,6 +26,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
{children}
+
+ {/* π μ΄ μ€ μΆκ° */}
+