Files
kandev/apps/backend/internal/system/database/maintenance.go
T

92 lines
2.7 KiB
Go

package database
import (
"context"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
// Vacuum runs VACUUM via a tracked job and returns the job ID. The job
// result includes "size_before" and "size_after" bytes; "reclaimed_bytes"
// is the difference (>= 0).
func (s *Service) Vacuum(ctx context.Context) string {
return s.jobs.Start(ctx, "vacuum", func(jobCtx context.Context) (map[string]interface{}, error) {
return s.runVacuum(jobCtx)
})
}
func (s *Service) runVacuum(_ context.Context) (map[string]interface{}, error) {
if s.pool == nil {
return nil, fmt.Errorf("vacuum: no database pool")
}
before, _ := readDBSize(s.pool.Reader())
if _, err := s.pool.Writer().Exec("VACUUM"); err != nil {
return nil, fmt.Errorf("vacuum: %w", err)
}
after, _ := readDBSize(s.pool.Reader())
reclaimed := before - after
if reclaimed < 0 {
reclaimed = 0
}
return map[string]interface{}{
"size_before": before,
"size_after": after,
"reclaimed_bytes": reclaimed,
}, nil
}
// Optimize runs PRAGMA optimize via a tracked job. PRAGMA optimize is
// cheap and idempotent; we still track it so the UI can show progress
// consistently with VACUUM.
func (s *Service) Optimize(ctx context.Context) string {
return s.jobs.Start(ctx, "optimize", func(jobCtx context.Context) (map[string]interface{}, error) {
return s.runOptimize(jobCtx)
})
}
func (s *Service) runOptimize(_ context.Context) (map[string]interface{}, error) {
if s.pool == nil {
return nil, fmt.Errorf("optimize: no database pool")
}
if _, err := s.pool.Writer().Exec("PRAGMA optimize"); err != nil {
return nil, fmt.Errorf("pragma optimize: %w", err)
}
return map[string]interface{}{"status": "ok"}, nil
}
// HandleVacuum returns a gin handler for POST /api/v1/system/database/vacuum.
func HandleVacuum(s *Service) gin.HandlerFunc {
return func(c *gin.Context) {
respondAccepted(c, s.Vacuum(ctxOrBackground(c)))
}
}
// HandleOptimize returns a gin handler for POST /api/v1/system/database/optimize.
func HandleOptimize(s *Service) gin.HandlerFunc {
return func(c *gin.Context) {
respondAccepted(c, s.Optimize(ctxOrBackground(c)))
}
}
// resetRequest is the JSON body for POST /api/v1/system/database/reset.
type resetRequest struct {
Confirm string `json:"confirm"`
}
// HandleReset returns a gin handler for POST /api/v1/system/database/reset.
// Validates the body's confirm == "RESET" before spawning the reset job.
func HandleReset(s *Service) gin.HandlerFunc {
return func(c *gin.Context) {
var req resetRequest
_ = c.ShouldBindJSON(&req)
jobID, err := s.FactoryReset(ctxOrBackground(c), req.Confirm)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
respondAccepted(c, jobID)
}
}