122 lines
3.4 KiB
Go
122 lines
3.4 KiB
Go
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
|
|
}
|