package admin import ( "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/mattnite/forgejo-tickets/internal/models" "github.com/rs/zerolog/log" ) type TicketHandler struct { deps Dependencies } type ticketListRow struct { models.Ticket RepoName string RepoSlug string UserEmail string UserName string } func (h *TicketHandler) List(c *gin.Context) { statusFilter := c.Query("status") var tickets []ticketListRow query := h.deps.DB.Table("tickets"). Select("tickets.*, repos.name as repo_name, repos.slug as repo_slug, users.email as user_email, users.name as user_name"). Joins("JOIN repos ON repos.id = tickets.repo_id"). Joins("JOIN users ON users.id = tickets.user_id") if statusFilter != "" { query = query.Where("tickets.status = ?", statusFilter) } if err := query.Order("tickets.created_at DESC").Limit(100).Scan(&tickets).Error; err != nil { log.Error().Err(err).Msg("list tickets error") h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusInternalServerError, "Failed to load tickets") return } h.deps.Renderer.Render(c.Writer, c.Request, "admin/tickets/list", map[string]interface{}{ "Tickets": tickets, "StatusFilter": statusFilter, }) } func (h *TicketHandler) Detail(c *gin.Context) { ticketID, err := uuid.Parse(c.Param("id")) if err != nil { h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusBadRequest, "Invalid ticket ID") return } var ticket models.Ticket if err := h.deps.DB.First(&ticket, "id = ?", ticketID).Error; err != nil { h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusNotFound, "Ticket not found") return } var user models.User h.deps.DB.First(&user, "id = ?", ticket.UserID) var repo models.Repo h.deps.DB.First(&repo, "id = ?", ticket.RepoID) var comments []struct { models.TicketComment UserName string UserEmail string } h.deps.DB.Table("ticket_comments"). Select("ticket_comments.*, users.name as user_name, users.email as user_email"). Joins("JOIN users ON users.id = ticket_comments.user_id"). Where("ticket_comments.ticket_id = ?", ticket.ID). Order("ticket_comments.created_at ASC"). Scan(&comments) h.deps.Renderer.Render(c.Writer, c.Request, "admin/tickets/detail", map[string]interface{}{ "Ticket": ticket, "User": user, "Repo": repo, "Comments": comments, }) } func (h *TicketHandler) UpdateStatus(c *gin.Context) { ticketID, err := uuid.Parse(c.Param("id")) if err != nil { h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusBadRequest, "Invalid ticket ID") return } status := models.TicketStatus(c.PostForm("status")) if err := h.deps.DB.Model(&models.Ticket{}).Where("id = ?", ticketID).Update("status", status).Error; err != nil { log.Error().Err(err).Msg("update ticket status error") h.deps.Renderer.RenderError(c.Writer, c.Request, http.StatusInternalServerError, "Failed to update status") return } c.Redirect(http.StatusSeeOther, "/tickets/"+ticketID.String()) }