Spaces:
Sleeping
Sleeping
| package controllers | |
| import ( | |
| "strconv" | |
| "abdanhafidz.com/go-boilerplate/models/dto" | |
| entity "abdanhafidz.com/go-boilerplate/models/entity" | |
| http_error "abdanhafidz.com/go-boilerplate/models/error" | |
| "abdanhafidz.com/go-boilerplate/services" | |
| "github.com/gin-gonic/gin" | |
| ) | |
| type SuperAdminRoleController interface { | |
| ListRoles(ctx *gin.Context) | |
| CreateRole(ctx *gin.Context) | |
| UpdateRole(ctx *gin.Context) | |
| DeleteRole(ctx *gin.Context) | |
| ListAuditLogs(ctx *gin.Context) | |
| } | |
| type superAdminRoleController struct { | |
| accountService services.AccountService | |
| } | |
| func NewSuperAdminRoleController(accountService services.AccountService) SuperAdminRoleController { | |
| return &superAdminRoleController{accountService: accountService} | |
| } | |
| // ListRoles godoc | |
| // @Summary List All Roles | |
| // @Description Get all system and named custom roles with their privilege flags | |
| // @Tags Super Admin Roles | |
| // @Accept json | |
| // @Produce json | |
| // @Security BearerAuth | |
| // @Success 200 {object} dto.SuccessResponse[[]dto.RoleResponse] | |
| // @Failure 401 {object} dto.ErrorResponse | |
| // @Failure 403 {object} dto.ErrorResponse | |
| // @Router /api/v1/super-admin/roles [get] | |
| func (c *superAdminRoleController) ListRoles(ctx *gin.Context) { | |
| roles, err := c.accountService.GetAllRoles(ctx.Request.Context()) | |
| if err != nil { | |
| ResponseJSON(ctx, gin.H{}, []dto.RoleResponse{}, err) | |
| return | |
| } | |
| var res []dto.RoleResponse | |
| for _, r := range roles { | |
| res = append(res, dto.RoleResponse{ | |
| Id: r.Id, | |
| Name: r.Name, | |
| IsSystem: r.IsSystem, | |
| EventManagement: r.EventManagement, | |
| ExamManagement: r.ExamManagement, | |
| AcademyManagement: r.AcademyManagement, | |
| ContentManagement: r.ContentManagement, | |
| }) | |
| } | |
| ResponseJSON(ctx, gin.H{}, res, nil) | |
| } | |
| // CreateRole godoc | |
| // @Summary Create Named Role | |
| // @Description Create a new named custom role with privilege flags. Superadmin only. | |
| // @Tags Super Admin Roles | |
| // @Accept json | |
| // @Produce json | |
| // @Security BearerAuth | |
| // @Param request body dto.CreateRoleRequest true "Create Role Request" | |
| // @Success 200 {object} dto.SuccessResponse[dto.RoleResponse] | |
| // @Failure 400 {object} dto.ErrorResponse | |
| // @Failure 401 {object} dto.ErrorResponse | |
| // @Failure 403 {object} dto.ErrorResponse | |
| // @Failure 409 {object} dto.ErrorResponse | |
| // @Router /api/v1/super-admin/roles [post] | |
| func (c *superAdminRoleController) CreateRole(ctx *gin.Context) { | |
| req := RequestJSON[dto.CreateRoleRequest](ctx) | |
| role := entity.Role{ | |
| Name: req.Name, | |
| IsSystem: false, | |
| EventManagement: req.EventManagement, | |
| ExamManagement: req.ExamManagement, | |
| AcademyManagement: req.AcademyManagement, | |
| ContentManagement: req.ContentManagement, | |
| } | |
| created, err := c.accountService.CreateRole(ctx.Request.Context(), role) | |
| if err != nil { | |
| ResponseJSON(ctx, req, dto.RoleResponse{}, err) | |
| return | |
| } | |
| callerID := ParseAccountId(ctx) | |
| roleIDStr := created.Id.String() | |
| detailStr := `{"role_name":"` + created.Name + `"}` | |
| _ = c.accountService.WriteAuditLog(ctx.Request.Context(), callerID, "role_created", &roleIDStr, &detailStr) | |
| res := dto.RoleResponse{ | |
| Id: created.Id, | |
| Name: created.Name, | |
| IsSystem: created.IsSystem, | |
| EventManagement: created.EventManagement, | |
| ExamManagement: created.ExamManagement, | |
| AcademyManagement: created.AcademyManagement, | |
| ContentManagement: created.ContentManagement, | |
| } | |
| ResponseJSON(ctx, req, res, nil) | |
| } | |
| // UpdateRole godoc | |
| // @Summary Update Named Role | |
| // @Description Update a named custom role's privilege flags. System roles are immutable. | |
| // @Tags Super Admin Roles | |
| // @Accept json | |
| // @Produce json | |
| // @Security BearerAuth | |
| // @Param role_id path string true "Role ID" | |
| // @Param request body dto.UpdateRoleRequest true "Update Role Request" | |
| // @Success 200 {object} dto.SuccessResponse[dto.RoleResponse] | |
| // @Failure 400 {object} dto.ErrorResponse | |
| // @Failure 401 {object} dto.ErrorResponse | |
| // @Failure 403 {object} dto.ErrorResponse | |
| // @Router /api/v1/super-admin/roles/{role_id} [put] | |
| func (c *superAdminRoleController) UpdateRole(ctx *gin.Context) { | |
| roleID := ParseUUID(ctx, "role_id") | |
| req := RequestJSON[dto.UpdateRoleRequest](ctx) | |
| existing, err := c.accountService.GetRoleByID(ctx.Request.Context(), roleID) | |
| if err != nil { | |
| ResponseJSON(ctx, req, dto.RoleResponse{}, err) | |
| return | |
| } | |
| if existing.IsSystem { | |
| ResponseJSON(ctx, req, dto.RoleResponse{}, http_error.ROLE_IMMUTABLE) | |
| return | |
| } | |
| if req.Name != nil { | |
| existing.Name = *req.Name | |
| } | |
| if req.EventManagement != nil { | |
| existing.EventManagement = *req.EventManagement | |
| } | |
| if req.ExamManagement != nil { | |
| existing.ExamManagement = *req.ExamManagement | |
| } | |
| if req.AcademyManagement != nil { | |
| existing.AcademyManagement = *req.AcademyManagement | |
| } | |
| if req.ContentManagement != nil { | |
| existing.ContentManagement = *req.ContentManagement | |
| } | |
| updated, err := c.accountService.UpdateRole(ctx.Request.Context(), existing) | |
| if err != nil { | |
| ResponseJSON(ctx, req, dto.RoleResponse{}, err) | |
| return | |
| } | |
| callerID := ParseAccountId(ctx) | |
| roleIDStr := updated.Id.String() | |
| detailStr := `{"role_name":"` + updated.Name + `"}` | |
| _ = c.accountService.WriteAuditLog(ctx.Request.Context(), callerID, "role_updated", &roleIDStr, &detailStr) | |
| res := dto.RoleResponse{ | |
| Id: updated.Id, | |
| Name: updated.Name, | |
| IsSystem: updated.IsSystem, | |
| EventManagement: updated.EventManagement, | |
| ExamManagement: updated.ExamManagement, | |
| AcademyManagement: updated.AcademyManagement, | |
| ContentManagement: updated.ContentManagement, | |
| } | |
| ResponseJSON(ctx, req, res, nil) | |
| } | |
| // DeleteRole godoc | |
| // @Summary Delete Named Role | |
| // @Description Delete a named custom role. System roles are immutable. Cannot delete if assigned to any account. | |
| // @Tags Super Admin Roles | |
| // @Accept json | |
| // @Produce json | |
| // @Security BearerAuth | |
| // @Param role_id path string true "Role ID" | |
| // @Success 200 {object} dto.SuccessResponse[any] | |
| // @Failure 401 {object} dto.ErrorResponse | |
| // @Failure 403 {object} dto.ErrorResponse | |
| // @Failure 409 {object} dto.ErrorResponse | |
| // @Router /api/v1/super-admin/roles/{role_id} [delete] | |
| func (c *superAdminRoleController) DeleteRole(ctx *gin.Context) { | |
| roleID := ParseUUID(ctx, "role_id") | |
| existing, err := c.accountService.GetRoleByID(ctx.Request.Context(), roleID) | |
| if err != nil { | |
| ResponseJSON[any](ctx, gin.H{}, nil, err) | |
| return | |
| } | |
| if existing.IsSystem { | |
| ResponseJSON[any](ctx, gin.H{}, nil, http_error.ROLE_IMMUTABLE) | |
| return | |
| } | |
| inUse, err := c.accountService.CountRoleAssignments(ctx.Request.Context(), roleID) | |
| if err != nil { | |
| ResponseJSON[any](ctx, gin.H{}, nil, err) | |
| return | |
| } | |
| if inUse > 0 { | |
| ResponseJSON[any](ctx, gin.H{}, nil, http_error.ROLE_IN_USE) | |
| return | |
| } | |
| if err := c.accountService.DeleteRole(ctx.Request.Context(), roleID); err != nil { | |
| ResponseJSON[any](ctx, gin.H{}, nil, err) | |
| return | |
| } | |
| callerID := ParseAccountId(ctx) | |
| roleIDStr := roleID.String() | |
| detailStr := `{"role_name":"` + existing.Name + `"}` | |
| _ = c.accountService.WriteAuditLog(ctx.Request.Context(), callerID, "role_deleted", &roleIDStr, &detailStr) | |
| ResponseJSON(ctx, gin.H{}, gin.H{"message": "Role deleted successfully"}, nil) | |
| } | |
| // ListAuditLogs godoc | |
| // @Summary List Audit Logs | |
| // @Description Get paginated audit logs with optional filters. Superadmin only. | |
| // @Tags Super Admin Audit | |
| // @Accept json | |
| // @Produce json | |
| // @Security BearerAuth | |
| // @Param page query int false "Page number (default 1)" | |
| // @Param limit query int false "Items per page (default 10, max 50)" | |
| // @Param action query string false "Filter by action" | |
| // @Param actor_account_id query string false "Filter by actor account ID" | |
| // @Param from query string false "Start time (RFC3339)" | |
| // @Param to query string false "End time (RFC3339)" | |
| // @Success 200 {object} dto.SuccessResponse[[]dto.AuditLogResponse] | |
| // @Failure 401 {object} dto.ErrorResponse | |
| // @Failure 403 {object} dto.ErrorResponse | |
| // @Router /api/v1/super-admin/logs [get] | |
| func (c *superAdminRoleController) ListAuditLogs(ctx *gin.Context) { | |
| limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10")) | |
| page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1")) | |
| action := ctx.DefaultQuery("action", "") | |
| actorID := ctx.DefaultQuery("actor_account_id", "") | |
| from := ctx.DefaultQuery("from", "") | |
| to := ctx.DefaultQuery("to", "") | |
| if limit < 1 { | |
| limit = 10 | |
| } else if limit > 50 { | |
| limit = 50 | |
| } | |
| if page < 1 { | |
| page = 1 | |
| } | |
| logs, total, err := c.accountService.GetAuditLogs(ctx.Request.Context(), page, limit, action, actorID, from, to) | |
| if err != nil { | |
| ResponseJSON(ctx, gin.H{}, []dto.AuditLogResponse{}, err) | |
| return | |
| } | |
| var res []dto.AuditLogResponse | |
| for _, l := range logs { | |
| res = append(res, dto.AuditLogResponse{ | |
| Id: l.Id, | |
| ActorAccountID: l.ActorAccountID, | |
| Action: l.Action, | |
| TargetID: l.TargetID, | |
| Detail: l.Detail, | |
| CreatedAt: l.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), | |
| }) | |
| } | |
| var totalPages int | |
| if total == 0 { | |
| totalPages = 1 | |
| } else { | |
| totalPages = int((total + int64(limit) - 1) / int64(limit)) | |
| } | |
| if page > totalPages { | |
| page = totalPages | |
| } | |
| meta := gin.H{ | |
| "totalItems": total, | |
| "totalPages": totalPages, | |
| "currentPage": page, | |
| "limit": limit, | |
| } | |
| ResponseJSON(ctx, meta, res, nil) | |
| } | |