diff --git a/internal/admin/sessions.go b/internal/admin/sessions.go index bd7226f..e47cd67 100644 --- a/internal/admin/sessions.go +++ b/internal/admin/sessions.go @@ -3,17 +3,20 @@ package admin import ( "encoding/json" "log" + "math" "net/http" "strconv" "gitea.local/admin/hspguard/internal/repository" "gitea.local/admin/hspguard/internal/types" "gitea.local/admin/hspguard/internal/web" + "github.com/go-chi/chi/v5" + "github.com/google/uuid" ) type GetSessionsParams struct { - Limit int32 `json:"limit"` - Offset int32 `json:"offset"` + PageSize int `json:"size"` + Page int `json:"page"` // TODO: More filtering possibilities like onlyActive, expired, not-expired etc. } @@ -22,17 +25,22 @@ func (h *AdminHandler) GetUserSessions(w http.ResponseWriter, r *http.Request) { params := GetSessionsParams{} - if limit, err := strconv.Atoi(q.Get("limit")); err == nil { - params.Limit = int32(limit) + if pageSize, err := strconv.Atoi(q.Get("size")); err == nil { + params.PageSize = pageSize + } else { + params.PageSize = 15 } - if offset, err := strconv.Atoi(q.Get("offset")); err == nil { - params.Offset = int32(offset) + if page, err := strconv.Atoi(q.Get("page")); err == nil { + params.Page = page + } else { + web.Error(w, "page is required", http.StatusBadRequest) + return } sessions, err := h.repo.GetUserSessions(r.Context(), repository.GetUserSessionsParams{ - Limit: params.Limit, - Offset: params.Offset, + Limit: int32(params.PageSize), + Offset: int32(params.Page-1) * int32(params.PageSize), }) if err != nil { log.Println("ERR: Failed to read user sessions from db:", err) @@ -40,35 +48,77 @@ func (h *AdminHandler) GetUserSessions(w http.ResponseWriter, r *http.Request) { return } - var mapped []*types.UserSessionDTO + totalSessions, err := h.repo.GetUserSessionsCount(r.Context()) + if err != nil { + log.Println("ERR: Failed to get total count of user sessions:", err) + web.Error(w, "failed to retrieve sessions", http.StatusInternalServerError) + return + } + + mapped := make([]*types.UserSessionDTO, 0) for _, session := range sessions { mapped = append(mapped, types.NewUserSessionDTO(&session)) } - if err := json.NewEncoder(w).Encode(mapped); err != nil { + type Response struct { + Items []*types.UserSessionDTO `json:"items"` + Page int `json:"page"` + TotalPages int `json:"total_pages"` + } + + response := Response{ + Items: mapped, + Page: params.Page, + TotalPages: int(math.Ceil(float64(totalSessions) / float64(params.PageSize))), + } + + if err := json.NewEncoder(w).Encode(response); err != nil { log.Println("ERR: Failed to encode sessions in response:", err) web.Error(w, "failed to encode sessions", http.StatusInternalServerError) return } } +func (h *AdminHandler) RevokeUserSession(w http.ResponseWriter, r *http.Request) { + sessionId := chi.URLParam(r, "id") + parsed, err := uuid.Parse(sessionId) + if err != nil { + web.Error(w, "provided service id is not valid", http.StatusBadRequest) + return + } + + if err := h.repo.RevokeUserSession(r.Context(), parsed); err != nil { + log.Println("ERR: Failed to revoke user session:", err) + web.Error(w, "failed to revoke user session", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte("{\"success\":true}")) +} + func (h *AdminHandler) GetServiceSessions(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() params := GetSessionsParams{} - if limit, err := strconv.Atoi(q.Get("limit")); err == nil { - params.Limit = int32(limit) + if pageSize, err := strconv.Atoi(q.Get("size")); err == nil { + params.PageSize = pageSize + } else { + params.PageSize = 15 } - if offset, err := strconv.Atoi(q.Get("offset")); err == nil { - params.Offset = int32(offset) + if page, err := strconv.Atoi(q.Get("page")); err == nil { + params.Page = page + } else { + web.Error(w, "page is required", http.StatusBadRequest) + return } sessions, err := h.repo.GetServiceSessions(r.Context(), repository.GetServiceSessionsParams{ - Limit: params.Limit, - Offset: params.Offset, + Limit: int32(params.PageSize), + Offset: int32(params.Page-1) * int32(params.PageSize), }) if err != nil { log.Println("ERR: Failed to read api sessions from db:", err) @@ -76,13 +126,32 @@ func (h *AdminHandler) GetServiceSessions(w http.ResponseWriter, r *http.Request return } - var mapped []*types.ServiceSessionDTO + totalSessions, err := h.repo.GetServiceSessionsCount(r.Context()) + if err != nil { + log.Println("ERR: Failed to get total count of service sessions:", err) + web.Error(w, "failed to retrieve sessions", http.StatusInternalServerError) + return + } + + mapped := make([]*types.ServiceSessionDTO, 0) for _, session := range sessions { mapped = append(mapped, types.NewServiceSessionDTO(&session)) } - if err := json.NewEncoder(w).Encode(sessions); err != nil { + type Response struct { + Items []*types.ServiceSessionDTO `json:"items"` + Page int `json:"page"` + TotalPages int `json:"total_pages"` + } + + response := Response{ + Items: mapped, + Page: params.Page, + TotalPages: int(math.Ceil(float64(totalSessions) / float64(params.PageSize))), + } + + if err := json.NewEncoder(w).Encode(response); err != nil { log.Println("ERR: Failed to encode sessions in response:", err) web.Error(w, "failed to encode sessions", http.StatusInternalServerError) } diff --git a/queries/service_sessions.sql b/queries/service_sessions.sql index 64d384c..dedd6b1 100644 --- a/queries/service_sessions.sql +++ b/queries/service_sessions.sql @@ -31,8 +31,7 @@ WHERE access_token_id = $1 -- name: GetServiceSessionByRefreshJTI :one SELECT * FROM service_sessions -WHERE refresh_token_id = $1 - AND is_active = TRUE; +WHERE refresh_token_id = $1; -- name: RevokeServiceSession :exec UPDATE service_sessions @@ -59,3 +58,6 @@ JOIN api_services AS service ON service.id = session.service_id JOIN users AS u ON u.id = session.user_id ORDER BY session.issued_at DESC LIMIT $1 OFFSET $2; + +-- name: GetServiceSessionsCount :one +SELECT COUNT(*) FROM service_sessions; diff --git a/queries/user_sessions.sql b/queries/user_sessions.sql index bc73d1c..61c7dca 100644 --- a/queries/user_sessions.sql +++ b/queries/user_sessions.sql @@ -23,8 +23,7 @@ WHERE access_token_id = $1 -- name: GetUserSessionByRefreshJTI :one SELECT * FROM user_sessions -WHERE refresh_token_id = $1 - AND is_active = TRUE; +WHERE refresh_token_id = $1; -- name: RevokeUserSession :exec UPDATE user_sessions @@ -56,3 +55,6 @@ FROM user_sessions AS session JOIN users AS u ON u.id = session.user_id ORDER BY session.issued_at DESC LIMIT $1 OFFSET $2; + +-- name: GetUserSessionsCount :one +SELECT COUNT(*) FROM user_sessions;