feat: Phase 1 — 项目骨架搭建,集成 Gin、SQLite 和设置页面

This commit is contained in:
2026-06-18 22:48:16 +08:00
parent 46f1bbf0f1
commit 1e9b1a2129
26 changed files with 2518 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
package handlers
import (
"database/sql"
"net/http"
"os"
"path/filepath"
"github.com/gin-gonic/gin"
)
type ReposHandler struct {
db *sql.DB
reposDir string
}
func NewReposHandler(db *sql.DB, reposDir string) *ReposHandler {
return &ReposHandler{db: db, reposDir: reposDir}
}
func (h *ReposHandler) ListRepos(c *gin.Context) {
rows, err := h.db.Query(`SELECT id, url, local_path, size_bytes, cloned_at, last_used FROM repositories ORDER BY last_used DESC`)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
defer rows.Close()
var repos []gin.H
for rows.Next() {
var id int64
var url, localPath string
var sizeBytes int64
var clonedAt, lastUsed string
if rows.Scan(&id, &url, &localPath, &sizeBytes, &clonedAt, &lastUsed) == nil {
repos = append(repos, gin.H{
"id": id, "url": url, "local_path": localPath,
"size_bytes": sizeBytes, "cloned_at": clonedAt, "last_used": lastUsed,
})
}
}
if repos == nil {
repos = []gin.H{}
}
c.JSON(http.StatusOK, repos)
}
func (h *ReposHandler) DeleteRepo(c *gin.Context) {
id := c.Param("id")
var localPath string
err := h.db.QueryRow(`SELECT local_path FROM repositories WHERE id = ?`, id).Scan(&localPath)
if err == sql.ErrNoRows {
c.JSON(http.StatusNotFound, gin.H{"error": "repository not found"})
return
}
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Remove from filesystem
os.RemoveAll(localPath)
// Remove from database
h.db.Exec(`DELETE FROM analyses WHERE repo_id = ?`, id)
h.db.Exec(`DELETE FROM repositories WHERE id = ?`, id)
c.JSON(http.StatusOK, gin.H{"ok": true})
}
func (h *ReposHandler) CleanupRepos(c *gin.Context) {
// Get max age from settings
var maxAgeDays string
h.db.QueryRow(`SELECT value FROM settings WHERE key = 'cache.max_age_days'`).Scan(&maxAgeDays)
if maxAgeDays == "" {
maxAgeDays = "7"
}
// Find expired repos
rows, err := h.db.Query(`SELECT id, local_path FROM repositories WHERE last_used < datetime('now', '-' || ? || ' days')`, maxAgeDays)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
defer rows.Close()
var cleaned []int64
for rows.Next() {
var id int64
var localPath string
if rows.Scan(&id, &localPath) == nil {
os.RemoveAll(localPath)
h.db.Exec(`DELETE FROM analyses WHERE repo_id = ?`, id)
h.db.Exec(`DELETE FROM repositories WHERE id = ?`, id)
cleaned = append(cleaned, id)
}
}
c.JSON(http.StatusOK, gin.H{"cleaned": cleaned})
}
// CloneRepo handles POST /api/repos — stub for Phase 2
func (h *ReposHandler) CloneRepo(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"error": "clone not implemented yet — coming in Phase 2"})
}
// GetGraph handles GET /api/repos/:id/graph — stub for Phase 2
func (h *ReposHandler) GetGraph(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"error": "graph not implemented yet — coming in Phase 2"})
}
// GetDiff handles GET /api/repos/:id/diff — stub for Phase 2
func (h *ReposHandler) GetDiff(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{"error": "diff not implemented yet — coming in Phase 2"})
}
// dirSize returns the total size of a directory in bytes
func dirSize(path string) int64 {
var size int64
filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return nil
}
size += info.Size()
return nil
})
return size
}