feat(20-01): GetTabloDetailPage handler + GET /tablos/{tabloID} route registration
- tabloDetailRepository interface with ListTasksByTablo
- GetTabloDetailPage: auth check, UUID parse, ownership-scoped tablo lookup, task fetch, vm build, render
- router.go: mux.Get('/tablos/{tabloID}') registered before /edit route
- activePath='/tablos' so sidebar Tablos item stays highlighted
- Threat T-20-01 mitigated: findTabloByID filters by OwnerID from session
This commit is contained in:
parent
9713cbd168
commit
3fc8aae7a0
2 changed files with 84 additions and 0 deletions
83
go-backend/internal/web/handlers/tablo_detail.go
Normal file
83
go-backend/internal/web/handlers/tablo_detail.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"xtablo-backend/internal/web/views"
|
||||
)
|
||||
|
||||
// tabloDetailRepository is the interface for fetching tasks for a specific tablo.
|
||||
type tabloDetailRepository interface {
|
||||
ListTasksByTablo(ctx context.Context, input ListTasksByTabloInput) ([]TaskRecord, error)
|
||||
}
|
||||
|
||||
// GetTabloDetailPage handles GET /tablos/{tabloID}.
|
||||
// It authenticates the user, fetches the tablo (with ownership check), fetches tasks,
|
||||
// and renders the tablo detail page.
|
||||
func (h *AuthHandler) GetTabloDetailPage() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user, ok := h.authenticatedUser(r.Context(), r)
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
tabloID, err := uuid.Parse(r.PathValue("tabloID"))
|
||||
if err != nil {
|
||||
http.Error(w, "invalid tablo id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch all tablos owned by the user — ownership check is implicit via OwnerID filter
|
||||
tablos, err := h.repo.ListTablos(r.Context(), ListTablosInput{OwnerID: user.ID})
|
||||
if err != nil {
|
||||
http.Error(w, "failed to load tablos", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Find the specific tablo (returns 404 if not owned by this user)
|
||||
tablo, ok := findTabloByID(tablos, tabloID)
|
||||
if !ok {
|
||||
http.Error(w, "tablo not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Type-assert to tabloDetailRepository to access ListTasksByTablo
|
||||
taskRepo, ok := h.repo.(tabloDetailRepository)
|
||||
if !ok {
|
||||
http.Error(w, "tasks repository not configured", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tasks, err := taskRepo.ListTasksByTablo(r.Context(), ListTasksByTabloInput{
|
||||
OwnerID: user.ID,
|
||||
TabloID: tabloID,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, "failed to load tasks", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch owner display name (best-effort; falls back to empty string)
|
||||
ownerName := ""
|
||||
if owner, err := h.repo.GetPublicUserByID(r.Context(), user.ID); err == nil {
|
||||
ownerName = owner.DisplayName
|
||||
}
|
||||
|
||||
vm := views.NewTabloDetailViewModel(tablo, tasks, ownerName)
|
||||
content := views.TabloDetailPage(vm)
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
var renderErr error
|
||||
if isHXRequest(r) {
|
||||
renderErr = views.DashboardContentSwapWithMainClass("/tablos", tablos, "flex-1 overflow-auto", content).Render(r.Context(), w)
|
||||
} else {
|
||||
renderErr = views.DashboardPageWithMainClass("/tablos", tablos, "flex-1 overflow-auto", content).Render(r.Context(), w)
|
||||
}
|
||||
if renderErr != nil {
|
||||
http.Error(w, "failed to render page", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@ func newRouterWithHandler(authHandler *handlers.AuthHandler) http.Handler {
|
|||
mux.Get("/files", authHandler.GetFilesPage())
|
||||
mux.Get("/feedback", authHandler.GetFeedbackPage())
|
||||
mux.Post("/tablos", authHandler.PostTablos())
|
||||
mux.Get("/tablos/{tabloID}", authHandler.GetTabloDetailPage())
|
||||
mux.Get("/tablos/{tabloID}/edit", authHandler.GetEditTabloModal())
|
||||
mux.Post("/tablos/{tabloID}", authHandler.PostTabloUpdate())
|
||||
mux.Delete("/tablos/{tabloID}", authHandler.DeleteTablo())
|
||||
|
|
|
|||
Loading…
Reference in a new issue