LPM-140 The Sprinting Update Adds sprint support across the web UI (board + issue detail), TUI kanban, CLI/REPL, and the Beads-backed storage layer. Changes: Introduces sprint entities in storage and exposes sprint operations via IssueService. Updates web board to support backlog vs. sprint columns, sprint selection, and sprint creation routes. Updates TUI kanban to show backlog + sprint columns and adds sprint selection/creation actions.
122 lines
3.2 KiB
Go
122 lines
3.2 KiB
Go
package server
|
|
|
|
import (
|
|
"embed"
|
|
"io/fs"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/LazyBachelor/LazyPM/internal/models"
|
|
"github.com/LazyBachelor/LazyPM/pkg/web/handler"
|
|
"github.com/NYTimes/gziphandler"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/rs/cors"
|
|
)
|
|
|
|
func (s *Server) RegisterRoutes(assets embed.FS) http.Handler {
|
|
r := chi.NewRouter()
|
|
|
|
if os.Getenv("DEV") == "True" {
|
|
r.Use(middleware.Logger)
|
|
}
|
|
r.Use(middleware.Recoverer)
|
|
r.Use(cors.AllowAll().Handler)
|
|
r.Use(middleware.CleanPath)
|
|
|
|
r.Use(handler.HTMXMiddleware)
|
|
r.Use(handler.AppMiddleware(s.App))
|
|
r.Use(actionLoggingMiddleware(s.App))
|
|
|
|
s.handleAssets(r, assets)
|
|
|
|
r.Get("/", handler.DashboardHandler)
|
|
r.Get("/board/sprint", handler.DashboardHandler)
|
|
r.Post("/board/sprint/new", handler.CreateSprintHandler)
|
|
r.Get("/status", handler.HandleTaskStatus)
|
|
r.Get("/status/modal", handler.HandleTaskStatusModal)
|
|
|
|
r.Route("/issues", func(r chi.Router) {
|
|
r.Get("/", handler.ListIssues)
|
|
r.Post("/", handler.CreateIssue)
|
|
r.Get("/create", handler.CreateIssueFormModal)
|
|
|
|
r.Route("/{id}", func(r chi.Router) {
|
|
r.Use(handler.IssueCtx)
|
|
r.Get("/", handler.GetIssue)
|
|
r.Patch("/", handler.UpdateIssue)
|
|
r.Get("/edit", handler.EditIssueFormModal)
|
|
r.Get("/close", handler.CloseIssueFormModal)
|
|
r.Post("/close", handler.CloseIssue)
|
|
r.Get("/delete", handler.DeleteIssueConfirmModal)
|
|
r.Get("/assignee", handler.AssigneeFormModal)
|
|
r.Patch("/assignee", handler.UpdateAssignee)
|
|
r.Delete("/", handler.DeleteIssue)
|
|
|
|
r.Route("/comments", func(r chi.Router) {
|
|
r.Get("/", handler.ListComments)
|
|
r.Post("/", handler.CreateComment)
|
|
})
|
|
})
|
|
})
|
|
return gziphandler.GzipHandler(r)
|
|
}
|
|
|
|
func actionLoggingMiddleware(app interface{ LogAction(string) }) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if app != nil && shouldLogWebAction(r.URL.Path) {
|
|
app.LogAction(models.EncodeActionEvent(models.ActionEvent{
|
|
Source: "web",
|
|
Action: "request",
|
|
Target: r.Method + " " + r.URL.Path,
|
|
Result: "ok",
|
|
}))
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
func shouldLogWebAction(path string) bool {
|
|
if strings.HasPrefix(path, "/assets/") {
|
|
return false
|
|
}
|
|
|
|
switch path {
|
|
case "/status":
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleAssets(r chi.Router, assets embed.FS) {
|
|
var fileServer http.Handler
|
|
|
|
if os.Getenv("DEV") == "True" {
|
|
log.Println("Running in development mode: serving assets from disk")
|
|
fileServer = http.FileServer(http.Dir("pkg/web/assets"))
|
|
} else {
|
|
subFS, err := fs.Sub(assets, "assets")
|
|
if err != nil {
|
|
log.Fatalf("failed to create sub filesystem: %v", err)
|
|
}
|
|
fileServer = http.FileServer(http.FS(subFS))
|
|
}
|
|
|
|
r.Handle("/assets/*",
|
|
http.StripPrefix("/assets/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
|
fileServer.ServeHTTP(w, r)
|
|
})))
|
|
|
|
r.Get("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Cache-Control", "no-cache")
|
|
http.ServeContent(w, r, "robots.txt", time.Now(), strings.NewReader("User-agent: *\nAllow: /"))
|
|
})
|
|
}
|