Initial commit: Backend with Bun.serve() + Drizzle ORM

- Bun 1.3.6 server setup
- MariaDB schema (projects, agents, tasks)
- Auto-migrations on startup
- WebSocket support
- Health check endpoint

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hector Ros
2026-01-19 22:46:21 +01:00
parent 0a2f6a4e4f
commit 0f44ec34ba
17 changed files with 1960 additions and 2 deletions

147
src/db/schema.ts Normal file
View File

@@ -0,0 +1,147 @@
/**
* Database Schema - Drizzle ORM
* MariaDB 11.4 LTS
*/
import { relations } from 'drizzle-orm'
import {
mysqlTable,
varchar,
text,
timestamp,
json,
int,
mysqlEnum,
boolean,
bigint,
index,
} from 'drizzle-orm/mysql-core'
// ============================================
// PROJECTS TABLE
// ============================================
export const projects = mysqlTable('projects', {
id: varchar('id', { length: 36 }).primaryKey(),
name: varchar('name', { length: 255 }).notNull(),
description: text('description'),
// Gitea
giteaRepoId: int('gitea_repo_id'),
giteaRepoUrl: varchar('gitea_repo_url', { length: 512 }),
giteaOwner: varchar('gitea_owner', { length: 100 }),
giteaRepoName: varchar('gitea_repo_name', { length: 100 }),
defaultBranch: varchar('default_branch', { length: 100 }).default('main'),
// K8s
k8sNamespace: varchar('k8s_namespace', { length: 63 }).notNull().unique(),
// Infrastructure
dockerImage: varchar('docker_image', { length: 512 }),
envVars: json('env_vars').$type<Record<string, string>>(),
replicas: int('replicas').default(1),
cpuLimit: varchar('cpu_limit', { length: 20 }).default('500m'),
memoryLimit: varchar('memory_limit', { length: 20 }).default('512Mi'),
// Status
status: mysqlEnum('status', ['active', 'paused', 'archived']).default('active'),
// Timestamps
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow().onUpdateNow(),
}, (table) => ({
statusIdx: index('idx_status').on(table.status),
}))
// ============================================
// AGENTS TABLE
// ============================================
export const agents = mysqlTable('agents', {
id: varchar('id', { length: 36 }).primaryKey(),
// K8s
podName: varchar('pod_name', { length: 253 }).notNull().unique(),
k8sNamespace: varchar('k8s_namespace', { length: 63 }).default('agents'),
// Status
status: mysqlEnum('status', ['idle', 'busy', 'error', 'offline']).default('idle'),
currentTaskId: varchar('current_task_id', { length: 36 }),
// Metrics
tasksCompleted: int('tasks_completed').default(0),
lastHeartbeat: timestamp('last_heartbeat'),
// Timestamps
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow().onUpdateNow(),
}, (table) => ({
statusIdx: index('idx_status').on(table.status),
}))
// ============================================
// TASKS TABLE
// ============================================
export const tasks = mysqlTable('tasks', {
id: varchar('id', { length: 36 }).primaryKey(),
projectId: varchar('project_id', { length: 36 }).notNull().references(() => projects.id, { onDelete: 'cascade' }),
// Task info
title: varchar('title', { length: 255 }).notNull(),
description: text('description'),
priority: mysqlEnum('priority', ['low', 'medium', 'high', 'urgent']).default('medium'),
// State
state: mysqlEnum('state', [
'backlog',
'in_progress',
'needs_input',
'ready_to_test',
'approved',
'staging',
'production',
]).default('backlog'),
// Assignment
assignedAgentId: varchar('assigned_agent_id', { length: 36 }).references(() => agents.id, { onDelete: 'set null' }),
// Git
branchName: varchar('branch_name', { length: 255 }),
prUrl: varchar('pr_url', { length: 512 }),
// Preview
previewUrl: varchar('preview_url', { length: 512 }),
// Timestamps
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow().onUpdateNow(),
}, (table) => ({
projectStateIdx: index('idx_project_state').on(table.projectId, table.state),
}))
// ============================================
// RELATIONS
// ============================================
export const projectsRelations = relations(projects, ({ many }) => ({
tasks: many(tasks),
}))
export const tasksRelations = relations(tasks, ({ one }) => ({
project: one(projects, {
fields: [tasks.projectId],
references: [projects.id],
}),
assignedAgent: one(agents, {
fields: [tasks.assignedAgentId],
references: [agents.id],
}),
}))
export const agentsRelations = relations(agents, ({ one }) => ({
currentTask: one(tasks, {
fields: [agents.currentTaskId],
references: [tasks.id],
}),
}))