package handler import ( "encoding/json" "net/http" "strconv" "github.com/go-chi/chi/v5" "github.com/johnzastrow/actalog/internal/service" "github.com/johnzastrow/actalog/pkg/logger" "github.com/johnzastrow/actalog/pkg/middleware" ) // SessionHandler handles session management operations type SessionHandler struct { userService *service.UserService logger *logger.Logger } // NewSessionHandler creates a new session handler func NewSessionHandler(userService *service.UserService, logger *logger.Logger) *SessionHandler { return &SessionHandler{ userService: userService, logger: logger, } } // ListSessions handles GET /api/sessions // @Summary List active sessions // @Description Get all active sessions for the current user // @Tags sessions // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} map[string]interface{} "List of active sessions" // @Failure 401 {object} ErrorResponse "Unauthorized" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /sessions [get] func (h *SessionHandler) ListSessions(w http.ResponseWriter, r *http.Request) { // Get user ID from context userID, ok := middleware.GetUserID(r.Context()) if !ok { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // Get all active sessions sessions, err := h.userService.GetActiveSessions(userID) if err != nil { h.logger.Error("Failed to get sessions: user_id=%d error=%v", userID, err) http.Error(w, "Failed to retrieve sessions", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "sessions": sessions, }) } // RevokeSession handles DELETE /api/sessions/:id // @Summary Revoke session // @Description Revoke a specific session (log out that device) // @Tags sessions // @Accept json // @Produce json // @Security BearerAuth // @Param id path int true "Session ID" // @Success 200 {object} MessageResponse "Session revoked" // @Failure 400 {object} ErrorResponse "Invalid session ID" // @Failure 401 {object} ErrorResponse "Unauthorized" // @Router /sessions/{id} [delete] func (h *SessionHandler) RevokeSession(w http.ResponseWriter, r *http.Request) { // Get user ID from context userID, ok := middleware.GetUserID(r.Context()) if !ok { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // Get session ID from URL sessionIDStr := chi.URLParam(r, "id") sessionID, err := strconv.ParseInt(sessionIDStr, 10, 64) if err != nil { http.Error(w, "Invalid session ID", http.StatusBadRequest) return } // Revoke the session if err := h.userService.RevokeSession(userID, sessionID); err != nil { h.logger.Error("Failed to revoke session: user_id=%d session_id=%d error=%v", userID, sessionID, err) http.Error(w, err.Error(), http.StatusBadRequest) return } h.logger.Info("Session revoked: user_id=%d session_id=%d", userID, sessionID) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "message": "Session revoked successfully", }) } // RevokeAllSessions handles POST /api/sessions/revoke-all // @Summary Revoke all sessions // @Description Revoke all sessions for the current user (log out everywhere) // @Tags sessions // @Accept json // @Produce json // @Security BearerAuth // @Param request body object false "Options (except_current_session)" // @Success 200 {object} MessageResponse "All sessions revoked" // @Failure 401 {object} ErrorResponse "Unauthorized" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /sessions/revoke-all [post] func (h *SessionHandler) RevokeAllSessions(w http.ResponseWriter, r *http.Request) { // Get user ID from context userID, ok := middleware.GetUserID(r.Context()) if !ok { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // Parse request body for optional except_token_id var request struct { ExceptCurrentSession bool `json:"except_current_session"` } if err := json.NewDecoder(r.Body).Decode(&request); err != nil { // If body is empty or invalid, treat as revoke all request.ExceptCurrentSession = false } // Revoke all sessions var exceptTokenID *int64 // Note: In a real implementation, you'd track the current session's token ID // For now, we'll just support revoking all if err := h.userService.RevokeAllSessions(userID, exceptTokenID); err != nil { h.logger.Error("Failed to revoke all sessions: user_id=%d error=%v", userID, err) http.Error(w, err.Error(), http.StatusInternalServerError) return } h.logger.Info("All sessions revoked: user_id=%d", userID) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "message": "All sessions revoked successfully", }) }