Files
PR-Helper/main.go
T

132 lines
3.6 KiB
Go

package main
import (
"context"
"fmt"
"html/template"
"log"
"net/http"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/gin-gonic/gin"
"github.com/HoHD/PR-Helper/config"
"github.com/HoHD/PR-Helper/database"
"github.com/HoHD/PR-Helper/handlers"
)
func main() {
cfg := config.Load()
db, err := database.New(cfg.DBPath())
if err != nil {
log.Fatalf("failed to initialize database: %v", err)
}
defer db.Close()
gin.SetMode(cfg.GinMode)
r := gin.Default()
// Custom template functions
funcMap := template.FuncMap{
"add": func(a, b int) int { return a + b },
"formatSize": func(bytes int64) string {
if bytes < 1024 {
return fmt.Sprintf("%d B", bytes)
}
if bytes < 1024*1024 {
return fmt.Sprintf("%.1f KB", float64(bytes)/1024)
}
return fmt.Sprintf("%.1f MB", float64(bytes)/1024/1024)
},
}
// Load templates: base layout first, then pages and partials
tmpl := template.New("").Funcs(funcMap)
// Walk all template files and parse them together
filepath.Walk("templates", func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() || !strings.HasSuffix(path, ".html") {
return nil
}
data, readErr := os.ReadFile(path)
if readErr != nil {
return readErr
}
// Use the relative path as template name (e.g. "layouts/base.html")
name := strings.TrimPrefix(path, "templates/")
t, parseErr := tmpl.New(name).Parse(string(data))
if parseErr != nil {
log.Fatalf("failed to parse template %s: %v", name, parseErr)
}
tmpl = t
return nil
})
r.SetHTMLTemplate(tmpl)
// Static files
r.Static("/static", "./static")
// Handlers
pages := handlers.NewPageHandler(db.Conn())
settings := handlers.NewSettingsHandler(db.Conn())
repos := handlers.NewReposHandler(db.Conn(), cfg.ReposDir())
generate := handlers.NewGenerateHandler(db.Conn())
review := handlers.NewReviewHandler(db.Conn())
// Page routes
r.GET("/", pages.Index)
r.GET("/repo/:id", pages.Repo)
r.GET("/repo/:id/generate", pages.Generate)
r.GET("/repo/:id/review", pages.Review)
r.GET("/settings", pages.Settings)
// API routes
r.GET("/api/settings", settings.GetSettings)
r.PUT("/api/settings", settings.UpdateSettings)
r.GET("/api/repos", repos.ListRepos)
r.POST("/api/repos", repos.CloneRepo)
r.DELETE("/api/repos/:id", repos.DeleteRepo)
r.POST("/api/repos/:id/cleanup", repos.CleanupRepos)
r.GET("/api/repos/:id/graph", repos.GetGraph)
r.GET("/api/repos/:id/refs", repos.GetRefs)
r.GET("/api/repos/:id/commits", repos.GetCommits)
r.GET("/api/repos/:id/diff", repos.GetDiff)
r.POST("/api/repos/:id/generate", generate.Generate)
r.POST("/api/repos/:id/review", review.Review)
r.GET("/api/repos/:id/review/analyses", review.ListReviews)
r.GET("/api/repos/:id/review/analyses/:aid", review.GetReview)
r.POST("/api/repos/:id/review/notes", review.SaveNotes)
r.GET("/api/repos/:id/review/notes", review.GetNotes)
r.POST("/api/repos/:id/review/pdf", review.GeneratePDF)
// Graceful shutdown with signal handling
srv := &http.Server{
Addr: ":" + cfg.Port,
Handler: r,
}
go func() {
log.Printf("PR-Helper starting on :%s", cfg.Port)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("failed to start server: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("server forced to shutdown: %v", err)
}
log.Println("server exited")
}