Files
bookhoard/internal/handlers/processing_issues.go
T
john-okeefe a87c8afc22 refactor(admin): remove separate sidebar, integrate admin nav into main sidebar
- Delete AdminSidebar component entirely
- Add Administration section (Dashboard, Libraries, Users, Settings) to
  header sidebar, visible only for admin users
- Remove Admin Panel link from user dropdown
- Strip admin chrome (sidebar wrapper, back buttons) from all 5 admin
  page templates
- Fix activeClass to handle trailing-slash routes correctly
- Add isUserVisible helper for library visibility toggles
- Fix processing issues page: remove dead Alpine JS, wire HTMX dismiss
  with proper mediaItemId, add issue ID swap targets
- Add processing issue resolve/delete routes to library router
- Add GetProcessingIssueStatsData context-based method
- Fix users page: remove broken hx-headers auth, simplify role select
- Fix settings page: remove dead adminSettings Alpine ref
2026-08-07 09:35:30 -04:00

148 lines
4.6 KiB
Go

package handlers
import (
"bookhoard/internal/database"
"context"
"net/http"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/labstack/echo/v5"
)
type ProcessingIssuesHandler struct {
db *database.Queries
}
func NewProcessingIssuesHandler(db *database.Queries) *ProcessingIssuesHandler {
return &ProcessingIssuesHandler{db: db}
}
type ProcessingIssueResponse struct {
ID string `json:"id"`
MediaItemID string `json:"media_item_id"`
Title string `json:"title"`
FilePath string `json:"file_path"`
FormatGroup string `json:"format_group"`
LibraryTypeName string `json:"library_type_name"`
IssueType string `json:"issue_type"`
IssueDescription string `json:"issue_description"`
Severity string `json:"severity"`
CreatedAt time.Time `json:"created_at"`
}
type ProcessingIssueStats struct {
ErrorCount int64 `json:"error_count"`
WarningCount int64 `json:"warning_count"`
InfoCount int64 `json:"info_count"`
}
// ListProcessingIssues returns all processing issues for a library
func (h *ProcessingIssuesHandler) ListProcessingIssues(c *echo.Context) error {
libraryID, err := uuid.Parse(c.Param("id"))
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid library ID"})
}
issues, err := h.db.ListProcessingIssuesByLibrary(
c.Request().Context(),
pgtype.UUID{Bytes: libraryID, Valid: true},
)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch issues"})
}
response := make([]ProcessingIssueResponse, len(issues))
for i, issue := range issues {
response[i] = ProcessingIssueResponse{
ID: issue.ID.String(),
MediaItemID: issue.MediaItemID.String(),
Title: issue.Title,
FilePath: issue.FilePath,
FormatGroup: issue.FormatGroup,
LibraryTypeName: issue.LibraryTypeName,
IssueType: issue.IssueType,
IssueDescription: issue.IssueDescription,
Severity: issue.Severity,
CreatedAt: issue.CreatedAt.Time,
}
}
return c.JSON(http.StatusOK, response)
}
// GetProcessingIssueStats returns statistics about processing issues
func (h *ProcessingIssuesHandler) GetProcessingIssueStats(c *echo.Context) error {
libraryID, err := uuid.Parse(c.Param("id"))
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid library ID"})
}
stats, err := h.db.GetProcessingIssueStats(
c.Request().Context(),
pgtype.UUID{Bytes: libraryID, Valid: true},
)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch stats"})
}
return c.JSON(http.StatusOK, ProcessingIssueStats{
ErrorCount: stats.ErrorCount,
WarningCount: stats.WarningCount,
InfoCount: stats.InfoCount,
})
}
// ResolveProcessingIssue marks an issue as resolved
func (h *ProcessingIssuesHandler) ResolveProcessingIssue(c *echo.Context) error {
issueID, err := uuid.Parse(c.Param("issueId"))
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid issue ID"})
}
mediaItemID, err := uuid.Parse(c.Param("mediaItemId"))
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid media item ID"})
}
_, err = h.db.ResolveProcessingIssue(
c.Request().Context(),
database.ResolveProcessingIssueParams{
ID: pgtype.UUID{Bytes: issueID, Valid: true},
MediaItemID: pgtype.UUID{Bytes: mediaItemID, Valid: true},
},
)
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to resolve issue"})
}
return c.JSON(http.StatusOK, map[string]interface{}{
"success": true,
"message": "Issue marked as resolved",
})
}
// DeleteProcessingIssue permanently deletes an issue record
func (h *ProcessingIssuesHandler) DeleteProcessingIssue(c *echo.Context) error {
issueID, err := uuid.Parse(c.Param("issueId"))
if err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid issue ID"})
}
_, err = h.db.DeleteProcessingIssue(c.Request().Context(), pgtype.UUID{Bytes: issueID, Valid: true})
if err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to delete issue"})
}
return c.JSON(http.StatusOK, map[string]any{
"success": true,
"message": "Issue deleted",
})
}
// GetProcessingIssueStatsData returns stats for SSR (not JSON response)
func (h *ProcessingIssuesHandler) GetProcessingIssueStatsData(ctx context.Context, libraryID pgtype.UUID) (database.GetProcessingIssueStatsRow, error) {
return h.db.GetProcessingIssueStats(ctx, libraryID)
}