Implement Backend API, MCP Server, and Gitea integration
All checks were successful
Build and Push Backend / build (push) Successful in 5s
All checks were successful
Build and Push Backend / build (push) Successful in 5s
- Add REST API routes for projects, tasks, and agents (CRUD operations) - Implement MCP Server with 4 core tools: - get_next_task: Assign tasks to agents - update_task_status: Update task states - create_branch: Create Git branches via Gitea API - create_pull_request: Create PRs via Gitea API - Add Gitea API client for repository operations - Fix database migration error handling for existing tables - Connect all routes to Bun.serve() main server Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
247
src/api/routes/projects.ts
Normal file
247
src/api/routes/projects.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Projects API Routes
|
||||
* CRUD operations for projects
|
||||
*/
|
||||
|
||||
import { db } from '../../db/client'
|
||||
import { projects } from '../../db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { randomUUID } from 'crypto'
|
||||
|
||||
/**
|
||||
* Handle all project routes
|
||||
*/
|
||||
export async function handleProjectRoutes(req: Request, url: URL): Promise<Response> {
|
||||
const method = req.method
|
||||
const pathParts = url.pathname.split('/').filter(Boolean)
|
||||
|
||||
// GET /api/projects - List all projects
|
||||
if (method === 'GET' && pathParts.length === 2) {
|
||||
return await listProjects()
|
||||
}
|
||||
|
||||
// GET /api/projects/:id - Get single project
|
||||
if (method === 'GET' && pathParts.length === 3) {
|
||||
const projectId = pathParts[2]
|
||||
return await getProject(projectId)
|
||||
}
|
||||
|
||||
// POST /api/projects - Create project
|
||||
if (method === 'POST' && pathParts.length === 2) {
|
||||
return await createProject(req)
|
||||
}
|
||||
|
||||
// PATCH /api/projects/:id - Update project
|
||||
if (method === 'PATCH' && pathParts.length === 3) {
|
||||
const projectId = pathParts[2]
|
||||
return await updateProject(projectId, req)
|
||||
}
|
||||
|
||||
// DELETE /api/projects/:id - Delete project
|
||||
if (method === 'DELETE' && pathParts.length === 3) {
|
||||
const projectId = pathParts[2]
|
||||
return await deleteProject(projectId)
|
||||
}
|
||||
|
||||
return new Response('Not Found', { status: 404 })
|
||||
}
|
||||
|
||||
/**
|
||||
* List all projects
|
||||
*/
|
||||
async function listProjects(): Promise<Response> {
|
||||
try {
|
||||
const allProjects = await db.select().from(projects)
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
data: allProjects,
|
||||
count: allProjects.length,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error listing projects:', error)
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: 'Failed to list projects',
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single project
|
||||
*/
|
||||
async function getProject(projectId: string): Promise<Response> {
|
||||
try {
|
||||
const project = await db
|
||||
.select()
|
||||
.from(projects)
|
||||
.where(eq(projects.id, projectId))
|
||||
.limit(1)
|
||||
|
||||
if (project.length === 0) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: 'Project not found',
|
||||
}, { status: 404 })
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
data: project[0],
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error getting project:', error)
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: 'Failed to get project',
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create project
|
||||
*/
|
||||
async function createProject(req: Request): Promise<Response> {
|
||||
try {
|
||||
const body = await req.json()
|
||||
|
||||
// Validate required fields
|
||||
if (!body.name) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: 'Name is required',
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
// Generate k8s namespace name from project name
|
||||
const k8sNamespace = `proj-${body.name.toLowerCase().replace(/[^a-z0-9-]/g, '-')}`
|
||||
|
||||
const newProject = {
|
||||
id: randomUUID(),
|
||||
name: body.name,
|
||||
description: body.description || null,
|
||||
k8sNamespace,
|
||||
giteaRepoId: body.giteaRepoId || null,
|
||||
giteaRepoUrl: body.giteaRepoUrl || null,
|
||||
giteaOwner: body.giteaOwner || null,
|
||||
giteaRepoName: body.giteaRepoName || null,
|
||||
defaultBranch: body.defaultBranch || 'main',
|
||||
dockerImage: body.dockerImage || null,
|
||||
envVars: body.envVars || null,
|
||||
replicas: body.replicas || 1,
|
||||
cpuLimit: body.cpuLimit || '500m',
|
||||
memoryLimit: body.memoryLimit || '512Mi',
|
||||
status: body.status || 'active',
|
||||
}
|
||||
|
||||
await db.insert(projects).values(newProject)
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
data: newProject,
|
||||
}, { status: 201 })
|
||||
} catch (error) {
|
||||
console.error('Error creating project:', error)
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: 'Failed to create project',
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update project
|
||||
*/
|
||||
async function updateProject(projectId: string, req: Request): Promise<Response> {
|
||||
try {
|
||||
const body = await req.json()
|
||||
|
||||
// Check if project exists
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(projects)
|
||||
.where(eq(projects.id, projectId))
|
||||
.limit(1)
|
||||
|
||||
if (existing.length === 0) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: 'Project not found',
|
||||
}, { status: 404 })
|
||||
}
|
||||
|
||||
// Update only provided fields
|
||||
const updateData: any = {}
|
||||
|
||||
if (body.name !== undefined) updateData.name = body.name
|
||||
if (body.description !== undefined) updateData.description = body.description
|
||||
if (body.giteaRepoId !== undefined) updateData.giteaRepoId = body.giteaRepoId
|
||||
if (body.giteaRepoUrl !== undefined) updateData.giteaRepoUrl = body.giteaRepoUrl
|
||||
if (body.giteaOwner !== undefined) updateData.giteaOwner = body.giteaOwner
|
||||
if (body.giteaRepoName !== undefined) updateData.giteaRepoName = body.giteaRepoName
|
||||
if (body.defaultBranch !== undefined) updateData.defaultBranch = body.defaultBranch
|
||||
if (body.dockerImage !== undefined) updateData.dockerImage = body.dockerImage
|
||||
if (body.envVars !== undefined) updateData.envVars = body.envVars
|
||||
if (body.replicas !== undefined) updateData.replicas = body.replicas
|
||||
if (body.cpuLimit !== undefined) updateData.cpuLimit = body.cpuLimit
|
||||
if (body.memoryLimit !== undefined) updateData.memoryLimit = body.memoryLimit
|
||||
if (body.status !== undefined) updateData.status = body.status
|
||||
|
||||
await db
|
||||
.update(projects)
|
||||
.set(updateData)
|
||||
.where(eq(projects.id, projectId))
|
||||
|
||||
// Get updated project
|
||||
const updated = await db
|
||||
.select()
|
||||
.from(projects)
|
||||
.where(eq(projects.id, projectId))
|
||||
.limit(1)
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
data: updated[0],
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error updating project:', error)
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: 'Failed to update project',
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete project
|
||||
*/
|
||||
async function deleteProject(projectId: string): Promise<Response> {
|
||||
try {
|
||||
// Check if project exists
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(projects)
|
||||
.where(eq(projects.id, projectId))
|
||||
.limit(1)
|
||||
|
||||
if (existing.length === 0) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: 'Project not found',
|
||||
}, { status: 404 })
|
||||
}
|
||||
|
||||
await db.delete(projects).where(eq(projects.id, projectId))
|
||||
|
||||
return Response.json({
|
||||
success: true,
|
||||
message: 'Project deleted',
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error deleting project:', error)
|
||||
return Response.json({
|
||||
success: false,
|
||||
error: 'Failed to delete project',
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user