package handlers import ( "database/sql" "net/http" "strconv" "github.com/go-chi/chi/v5" "github.com/leolionad58/ticketbot/internal/db" panelauth "github.com/leolionad58/ticketbot/internal/panel/auth" ) // SessionsHandler serves /sessions routes (superadmin only). type SessionsHandler struct { SessionRepo *db.PanelSessionRepo AdminRepo *db.PanelAdminRepo AuditLog *db.AuditLogRepo Renderer *Renderer } // sessionWithAdmin joins a PanelSession with the admin's username. type sessionWithAdmin struct { *db.PanelSession DiscordUsername string IsCurrent bool } type sessionsData struct { baseData Sessions []*sessionWithAdmin } // HandleList serves GET /sessions. func (h *SessionsHandler) HandleList(w http.ResponseWriter, r *http.Request) { ctx := r.Context() base := h.Renderer.base(r, "sessions") currentSess := panelauth.SessionFromContext(ctx) currentToken := "" if currentSess != nil { currentToken = currentSess.Token } sessions, err := h.SessionRepo.List(ctx) if err != nil { http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError) return } var enriched []*sessionWithAdmin for _, s := range sessions { swa := &sessionWithAdmin{ PanelSession: s, IsCurrent: s.Token == currentToken, } if admin, err := h.AdminRepo.GetByID(ctx, s.AdminID); err == nil && admin != nil { swa.DiscordUsername = admin.DiscordUsername } enriched = append(enriched, swa) } h.Renderer.Page(w, "sessions", sessionsData{ baseData: base, Sessions: enriched, }) } // HandleRevoke serves POST /sessions/{id}/revoke. func (h *SessionsHandler) HandleRevoke(w http.ResponseWriter, r *http.Request) { ctx := r.Context() id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { http.Error(w, "invalid id", http.StatusBadRequest) return } if err := h.SessionRepo.DeleteByID(ctx, id); err != nil { http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError) return } // Audit log if h.AuditLog != nil { admin := panelauth.AdminFromContext(ctx) if admin != nil { _ = h.AuditLog.Insert(ctx, &db.AuditLogEntry{ AdminID: admin.ID, Action: db.AuditRevokeSession, EntityType: "panel_session", EntityID: sql.NullInt64{Int64: id, Valid: true}, IPAddress: clientIP(r), }) } } http.Redirect(w, r, "/sessions?flash=Session+révoquée", http.StatusSeeOther) } // HandleRevokeAll serves POST /sessions/revoke-all. func (h *SessionsHandler) HandleRevokeAll(w http.ResponseWriter, r *http.Request) { ctx := r.Context() admin := panelauth.AdminFromContext(ctx) if admin == nil { http.Error(w, "unauthorized", http.StatusUnauthorized) return } currentSess := panelauth.SessionFromContext(ctx) currentToken := "" if currentSess != nil { currentToken = currentSess.Token } if err := h.SessionRepo.DeleteAllExcept(ctx, admin.ID, currentToken); err != nil { http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError) return } // Audit log if h.AuditLog != nil { _ = h.AuditLog.Insert(ctx, &db.AuditLogEntry{ AdminID: admin.ID, Action: db.AuditRevokeAllSess, EntityType: "panel_session", IPAddress: clientIP(r), }) } http.Redirect(w, r, "/sessions?flash=Toutes+les+sessions+révoquées", http.StatusSeeOther) }