feat: implement proto-gen platform

- Config loader with env var expansion and validation
- SQLite database with tasks and toolchain_versions tables
- Gitea REST API client for repository and branch operations
- K8s client for Job lifecycle management (with noop fallback for local dev)
- REST API: tasks CRUD, retry, plugins, version management
- Worker/scheduler with polling, concurrency control, and job tracking
- Static frontend: generate, tasks, settings pages
- Toolchain images: Go and Node.js Dockerfiles with entrypoint scripts
- Helm chart with deployment, service, configmap, ingress, PVC
- Multi-stage Dockerfile for service
- Unit tests for config, database, gitclient, k8s, api, worker
- E2E tests for idempotency, version switch, failure recovery
This commit is contained in:
2026-08-10 16:54:30 +08:00
parent cc478bdad4
commit 4e467e1136
43 changed files with 3179 additions and 1 deletions
+97
View File
@@ -0,0 +1,97 @@
package database
import (
"database/sql"
"fmt"
"time"
"proto-gen/internal/models"
)
func (db *DB) CreateTask(task *models.Task) error {
now := time.Now().UTC().Format(time.RFC3339)
_, err := db.conn.Exec(`
INSERT INTO tasks (id, status, language, proto_repo, proto_branch, target_repo, toolchain_image, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
task.ID, task.Status, task.Language, task.ProtoRepo, task.ProtoBranch,
task.TargetRepo, task.ToolchainImage, now, now,
)
return err
}
func (db *DB) GetTask(id string) (*models.Task, error) {
var t models.Task
var createdAt, updatedAt string
err := db.conn.QueryRow(`
SELECT id, status, language, proto_repo, proto_branch, proto_commit, target_repo,
toolchain_image, image_digest, result_commit, error, log, retries, created_at, updated_at
FROM tasks WHERE id = ?`, id,
).Scan(&t.ID, &t.Status, &t.Language, &t.ProtoRepo, &t.ProtoBranch, &t.ProtoCommit,
&t.TargetRepo, &t.ToolchainImage, &t.ImageDigest, &t.ResultCommit,
&t.Error, &t.Log, &t.Retries, &createdAt, &updatedAt)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("task not found: %s", id)
}
if err != nil {
return nil, err
}
t.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
t.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)
return &t, nil
}
func (db *DB) UpdateTask(task *models.Task) error {
now := time.Now().UTC().Format(time.RFC3339)
_, err := db.conn.Exec(`
UPDATE tasks SET status=?, proto_commit=?, image_digest=?, result_commit=?,
error=?, log=?, retries=?, updated_at=?
WHERE id=?`,
task.Status, task.ProtoCommit, task.ImageDigest, task.ResultCommit,
task.Error, task.Log, task.Retries, now, task.ID,
)
return err
}
func (db *DB) ListTasks(status string, limit, offset int) ([]*models.Task, error) {
query := `SELECT id, status, language, proto_repo, proto_branch, proto_commit, target_repo,
toolchain_image, image_digest, result_commit, error, log, retries, created_at, updated_at
FROM tasks`
var args []interface{}
if status != "" {
query += ` WHERE status = ?`
args = append(args, status)
}
query += ` ORDER BY created_at DESC LIMIT ? OFFSET ?`
args = append(args, limit, offset)
rows, err := db.conn.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var tasks []*models.Task
for rows.Next() {
var t models.Task
var createdAt, updatedAt string
if err := rows.Scan(&t.ID, &t.Status, &t.Language, &t.ProtoRepo, &t.ProtoBranch,
&t.ProtoCommit, &t.TargetRepo, &t.ToolchainImage, &t.ImageDigest,
&t.ResultCommit, &t.Error, &t.Log, &t.Retries, &createdAt, &updatedAt); err != nil {
return nil, err
}
t.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
t.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)
tasks = append(tasks, &t)
}
return tasks, nil
}
func (db *DB) GetPendingTasks(limit int) ([]*models.Task, error) {
return db.ListTasks(string(models.StatusPending), limit, 0)
}
func (db *DB) HasRunningTask(targetRepo string) (bool, error) {
var count int
err := db.conn.QueryRow(`SELECT COUNT(*) FROM tasks WHERE target_repo = ? AND status = 'running'`, targetRepo).Scan(&count)
return count > 0, err
}