421 lines
11 KiB
Go
421 lines
11 KiB
Go
package handlers
|
|
|
|
import (
|
|
"git.itzana.me/StrafesNET/dev-service/pkg/api/dto"
|
|
"git.itzana.me/StrafesNET/dev-service/pkg/datastore"
|
|
"git.itzana.me/StrafesNET/dev-service/pkg/model"
|
|
"git.itzana.me/StrafesNET/dev-service/pkg/ratelimit"
|
|
"github.com/gin-gonic/gin"
|
|
log "github.com/sirupsen/logrus"
|
|
"net/http"
|
|
"strconv"
|
|
)
|
|
|
|
// AdminHandler handles HTTP requests for admin operations.
|
|
type AdminHandler struct {
|
|
*Handler
|
|
}
|
|
|
|
// NewAdminHandler creates a new AdminHandler with the provided options.
|
|
func NewAdminHandler(options ...HandlerOption) (*AdminHandler, error) {
|
|
baseHandler, err := NewHandler(options...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &AdminHandler{Handler: baseHandler}, nil
|
|
}
|
|
|
|
// --- User management ---
|
|
|
|
// ListUsers returns a paginated list of all users, or searches by username/ID if ?q= is provided
|
|
func (h *AdminHandler) ListUsers(ctx *gin.Context) {
|
|
if q := ctx.Query("q"); q != "" {
|
|
users, err := h.Store.SearchUsers(ctx, q)
|
|
if err != nil {
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
h.RespondWithData(ctx, users, nil)
|
|
return
|
|
}
|
|
|
|
pagination := &datastore.CursorPagination{
|
|
Cursor: ctx.Query("cursor"),
|
|
Limit: DefaultPageLimit,
|
|
}
|
|
|
|
users, err := h.Store.GetAllUsers(ctx, pagination)
|
|
if err != nil {
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
h.RespondWithData(ctx, users, pagination)
|
|
}
|
|
|
|
// GetUser returns a single user by ID including their rate limit status
|
|
func (h *AdminHandler) GetUser(ctx *gin.Context) {
|
|
userID, ok := parseUserID(ctx, h.Handler)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
user, err := h.Store.GetUser(ctx, userID)
|
|
if err != nil {
|
|
h.RespondWithError(ctx, http.StatusNotFound, "User not found")
|
|
return
|
|
}
|
|
|
|
status, err := h.Limiter.GetRateLimitStatus(ctx, user.ID, ratelimit.RateLimitConfig{
|
|
BurstLimit: user.RateLimit.BurstLimit,
|
|
BurstDurationSeconds: user.RateLimit.BurstDuration,
|
|
DailyLimit: user.RateLimit.DailyLimit,
|
|
MonthlyLimit: user.RateLimit.MonthlyLimit,
|
|
})
|
|
if err != nil {
|
|
log.WithError(err).Warn("failed to get rate limit status for user")
|
|
status = &ratelimit.RateLimitStatus{}
|
|
}
|
|
|
|
h.RespondWithData(ctx, &dto.AdminUserResponse{
|
|
ID: user.ID,
|
|
Username: user.Username,
|
|
Active: user.Active,
|
|
RateLimit: user.RateLimit,
|
|
RateLimitStatus: dto.UserRateLimitStatus{
|
|
RemainingBurst: status.RemainingBurst,
|
|
RemainingDaily: status.RemainingDaily,
|
|
RemainingMonthly: status.RemainingMonthly,
|
|
},
|
|
Permissions: user.Permissions,
|
|
}, nil)
|
|
}
|
|
|
|
// UpdateUser updates a user's active status and rate limit class
|
|
func (h *AdminHandler) UpdateUser(ctx *gin.Context) {
|
|
userID, ok := parseUserID(ctx, h.Handler)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var req dto.AdminUpdateUserRequest
|
|
if err := ctx.BindJSON(&req); err != nil {
|
|
return
|
|
}
|
|
|
|
user, err := h.Store.GetUser(ctx, userID)
|
|
if err != nil {
|
|
h.RespondWithError(ctx, http.StatusNotFound, "User not found")
|
|
return
|
|
}
|
|
|
|
user.Active = req.Active
|
|
user.RateLimitID = req.RateLimitID
|
|
|
|
if err := h.Store.UpdateUser(ctx, user); err != nil {
|
|
log.WithError(err).Error("failed to update user")
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, "Failed to update user")
|
|
return
|
|
}
|
|
|
|
// Return refreshed user
|
|
updated, err := h.Store.GetUser(ctx, userID)
|
|
if err != nil {
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, "Failed to fetch updated user")
|
|
return
|
|
}
|
|
|
|
h.RespondWithData(ctx, updated, nil)
|
|
}
|
|
|
|
// AddUserPermission grants a permission to a user
|
|
func (h *AdminHandler) AddUserPermission(ctx *gin.Context) {
|
|
userID, ok := parseUserID(ctx, h.Handler)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
permID, ok := parsePermissionID(ctx, h.Handler)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if err := h.Store.AddPermissionToUser(ctx, userID, permID); err != nil {
|
|
log.WithError(err).Error("failed to add permission to user")
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, "Failed to add permission")
|
|
return
|
|
}
|
|
|
|
h.RespondWithData(ctx, gin.H{"message": "Permission added"}, nil)
|
|
}
|
|
|
|
// RemoveUserPermission revokes a permission from a user
|
|
func (h *AdminHandler) RemoveUserPermission(ctx *gin.Context) {
|
|
userID, ok := parseUserID(ctx, h.Handler)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
permID, ok := parsePermissionID(ctx, h.Handler)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if err := h.Store.RemovePermissionFromUser(ctx, userID, permID); err != nil {
|
|
log.WithError(err).Error("failed to remove permission from user")
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, "Failed to remove permission")
|
|
return
|
|
}
|
|
|
|
h.RespondWithData(ctx, gin.H{"message": "Permission removed"}, nil)
|
|
}
|
|
|
|
// GetUserApplications returns all applications for a given user
|
|
func (h *AdminHandler) GetUserApplications(ctx *gin.Context) {
|
|
userID, ok := parseUserID(ctx, h.Handler)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
apps, err := h.Store.GetApplicationsForUser(ctx, userID)
|
|
if err != nil {
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
responses := make([]dto.ApplicationResponse, len(apps))
|
|
for i, app := range apps {
|
|
responses[i] = dto.ApplicationResponse{
|
|
ID: app.ID,
|
|
Name: app.Name,
|
|
Description: app.Description,
|
|
Permissions: app.Permissions,
|
|
CreatedAt: app.CreatedAt,
|
|
UpdatedAt: app.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
h.RespondWithData(ctx, responses, nil)
|
|
}
|
|
|
|
// --- Permission management ---
|
|
|
|
// ListPermissions returns all permissions
|
|
func (h *AdminHandler) ListPermissions(ctx *gin.Context) {
|
|
perms, err := h.Store.GetAllPermissions(ctx)
|
|
if err != nil {
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
h.RespondWithData(ctx, perms, nil)
|
|
}
|
|
|
|
// CreatePermission creates a new permission
|
|
func (h *AdminHandler) CreatePermission(ctx *gin.Context) {
|
|
var req dto.CreatePermissionRequest
|
|
if err := ctx.BindJSON(&req); err != nil {
|
|
return
|
|
}
|
|
|
|
perm := &model.Permission{
|
|
Service: req.Service,
|
|
PermissionName: req.PermissionName,
|
|
Title: req.Title,
|
|
Description: req.Description,
|
|
IsDefault: req.IsDefault,
|
|
}
|
|
|
|
if err := h.Store.CreatePermission(ctx, perm); err != nil {
|
|
log.WithError(err).Error("failed to create permission")
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, "Failed to create permission")
|
|
return
|
|
}
|
|
|
|
h.RespondWithData(ctx, perm, nil)
|
|
}
|
|
|
|
// UpdatePermission updates an existing permission
|
|
func (h *AdminHandler) UpdatePermission(ctx *gin.Context) {
|
|
permID, ok := parsePermissionID(ctx, h.Handler)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var req dto.UpdatePermissionRequest
|
|
if err := ctx.BindJSON(&req); err != nil {
|
|
return
|
|
}
|
|
|
|
perm, err := h.Store.GetPermission(ctx, permID)
|
|
if err != nil {
|
|
h.RespondWithError(ctx, http.StatusNotFound, "Permission not found")
|
|
return
|
|
}
|
|
|
|
perm.Service = req.Service
|
|
perm.PermissionName = req.PermissionName
|
|
perm.Title = req.Title
|
|
perm.Description = req.Description
|
|
perm.IsDefault = req.IsDefault
|
|
|
|
if err := h.Store.UpdatePermission(ctx, perm); err != nil {
|
|
log.WithError(err).Error("failed to update permission")
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, "Failed to update permission")
|
|
return
|
|
}
|
|
|
|
h.RespondWithData(ctx, perm, nil)
|
|
}
|
|
|
|
// DeletePermission deletes a permission by ID
|
|
func (h *AdminHandler) DeletePermission(ctx *gin.Context) {
|
|
permID, ok := parsePermissionID(ctx, h.Handler)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if err := h.Store.DeletePermission(ctx, permID); err != nil {
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, "Failed to delete permission")
|
|
return
|
|
}
|
|
|
|
h.RespondWithData(ctx, gin.H{"message": "Permission deleted"}, nil)
|
|
}
|
|
|
|
// SetPermissionDefault updates the default status of a permission
|
|
func (h *AdminHandler) SetPermissionDefault(ctx *gin.Context) {
|
|
permID, ok := parsePermissionID(ctx, h.Handler)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var req dto.SetPermissionDefaultRequest
|
|
if err := ctx.BindJSON(&req); err != nil {
|
|
return
|
|
}
|
|
|
|
if err := h.Store.SetPermissionAsDefault(ctx, permID, req.IsDefault); err != nil {
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, "Failed to update permission default")
|
|
return
|
|
}
|
|
|
|
h.RespondWithData(ctx, gin.H{"message": "Permission default updated"}, nil)
|
|
}
|
|
|
|
// --- Rate limit management ---
|
|
|
|
// ListRateLimits returns all rate limit classes
|
|
func (h *AdminHandler) ListRateLimits(ctx *gin.Context) {
|
|
limits, err := h.Store.GetAllRateLimits(ctx)
|
|
if err != nil {
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
h.RespondWithData(ctx, limits, nil)
|
|
}
|
|
|
|
// CreateRateLimit creates a new rate limit class
|
|
func (h *AdminHandler) CreateRateLimit(ctx *gin.Context) {
|
|
var req dto.CreateRateLimitRequest
|
|
if err := ctx.BindJSON(&req); err != nil {
|
|
return
|
|
}
|
|
|
|
rl := &model.RateLimit{
|
|
BurstDuration: req.BurstDuration,
|
|
BurstLimit: req.BurstLimit,
|
|
DailyLimit: req.DailyLimit,
|
|
MonthlyLimit: req.MonthlyLimit,
|
|
MaxApplication: req.MaxApplications,
|
|
}
|
|
|
|
if err := h.Store.CreateRateLimit(ctx, rl); err != nil {
|
|
log.WithError(err).Error("failed to create rate limit")
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, "Failed to create rate limit")
|
|
return
|
|
}
|
|
|
|
h.RespondWithData(ctx, rl, nil)
|
|
}
|
|
|
|
// UpdateRateLimit updates an existing rate limit class
|
|
func (h *AdminHandler) UpdateRateLimit(ctx *gin.Context) {
|
|
rlID, ok := parseRateLimitID(ctx, h.Handler)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var req dto.UpdateRateLimitRequest
|
|
if err := ctx.BindJSON(&req); err != nil {
|
|
return
|
|
}
|
|
|
|
rl, err := h.Store.GetRateLimit(ctx, rlID)
|
|
if err != nil {
|
|
h.RespondWithError(ctx, http.StatusNotFound, "Rate limit not found")
|
|
return
|
|
}
|
|
|
|
rl.BurstDuration = req.BurstDuration
|
|
rl.BurstLimit = req.BurstLimit
|
|
rl.DailyLimit = req.DailyLimit
|
|
rl.MonthlyLimit = req.MonthlyLimit
|
|
rl.MaxApplication = req.MaxApplications
|
|
|
|
if err := h.Store.UpdateRateLimit(ctx, rl); err != nil {
|
|
log.WithError(err).Error("failed to update rate limit")
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, "Failed to update rate limit")
|
|
return
|
|
}
|
|
|
|
h.RespondWithData(ctx, rl, nil)
|
|
}
|
|
|
|
// DeleteRateLimit deletes a rate limit class by ID
|
|
func (h *AdminHandler) DeleteRateLimit(ctx *gin.Context) {
|
|
rlID, ok := parseRateLimitID(ctx, h.Handler)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if err := h.Store.DeleteRateLimit(ctx, rlID); err != nil {
|
|
h.RespondWithError(ctx, http.StatusInternalServerError, "Failed to delete rate limit")
|
|
return
|
|
}
|
|
|
|
h.RespondWithData(ctx, gin.H{"message": "Rate limit deleted"}, nil)
|
|
}
|
|
|
|
// --- Helpers ---
|
|
|
|
func parseUserID(ctx *gin.Context, h *Handler) (uint64, bool) {
|
|
id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
|
|
if err != nil {
|
|
h.RespondWithError(ctx, http.StatusBadRequest, "Invalid user ID")
|
|
return 0, false
|
|
}
|
|
return id, true
|
|
}
|
|
|
|
func parsePermissionID(ctx *gin.Context, h *Handler) (uint32, bool) {
|
|
id, err := strconv.ParseUint(ctx.Param("permId"), 10, 32)
|
|
if err != nil {
|
|
// Try "id" param for permission-specific routes
|
|
id, err = strconv.ParseUint(ctx.Param("id"), 10, 32)
|
|
if err != nil {
|
|
h.RespondWithError(ctx, http.StatusBadRequest, "Invalid permission ID")
|
|
return 0, false
|
|
}
|
|
}
|
|
return uint32(id), true
|
|
}
|
|
|
|
func parseRateLimitID(ctx *gin.Context, h *Handler) (uint32, bool) {
|
|
id, err := strconv.ParseUint(ctx.Param("id"), 10, 32)
|
|
if err != nil {
|
|
h.RespondWithError(ctx, http.StatusBadRequest, "Invalid rate limit ID")
|
|
return 0, false
|
|
}
|
|
return uint32(id), true
|
|
}
|