Spaces:
Sleeping
Sleeping
Commit ·
ba4bba1
1
Parent(s): 003bc96
Deploy files from GitHub repository
Browse files- bruno/Super Admin/Account Management/Create Account.bru +40 -0
- bruno/Super Admin/Account Management/Delete Account.bru +28 -0
- bruno/Super Admin/Account Management/List Accounts.bru +22 -0
- bruno/Super Admin/Account Management/Update Account.bru +44 -0
- bruno/Super Admin/Account Management/folder.bru +3 -0
- bruno/Super Admin/Accounts/Set User Role.bru +1 -1
- controllers/admin_account_controller.go +215 -0
- models/dto/admin_account_dto.go +32 -0
- provider/controller_provider.go +8 -0
- router/{admin_router.go → admin__procotoring_router.go} +1 -0
- router/super_admin_router.go +6 -0
- services/account_service.go +204 -0
bruno/Super Admin/Account Management/Create Account.bru
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
meta {
|
| 2 |
+
name: Create Account
|
| 3 |
+
type: http
|
| 4 |
+
seq: 2
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
post {
|
| 8 |
+
url: {{base_url}}/api/v1/super-admin/accounts/
|
| 9 |
+
body: json
|
| 10 |
+
auth: inherit
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
headers {
|
| 14 |
+
Content-Type: application/json
|
| 15 |
+
Authorization: Bearer {{access_token}}
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
body:json {
|
| 19 |
+
{
|
| 20 |
+
"email": "newaccount@example.com",
|
| 21 |
+
"username": "newaccount",
|
| 22 |
+
"full_name": "New Account",
|
| 23 |
+
"password": "Password123!",
|
| 24 |
+
"role": "teacher",
|
| 25 |
+
"privileges": {
|
| 26 |
+
"event_management": false,
|
| 27 |
+
"exam_management": true,
|
| 28 |
+
"academy_management": true,
|
| 29 |
+
"content_management": true
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
settings {
|
| 35 |
+
encodeUrl: true
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
docs {
|
| 39 |
+
Create a new account with role and privileges (superadmin only). Hierarchy and FR-19 gates applied.
|
| 40 |
+
}
|
bruno/Super Admin/Account Management/Delete Account.bru
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
meta {
|
| 2 |
+
name: Delete Account
|
| 3 |
+
type: http
|
| 4 |
+
seq: 4
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
delete {
|
| 8 |
+
url: {{base_url}}/api/v1/super-admin/accounts/:account_id
|
| 9 |
+
body: none
|
| 10 |
+
auth: inherit
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
params:path {
|
| 14 |
+
account_id: eab90733-58c9-4631-bf2f-82726ce3c217
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
headers {
|
| 18 |
+
Authorization: Bearer {{access_token}}
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
settings {
|
| 22 |
+
encodeUrl: true
|
| 23 |
+
timeout: 0
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
docs {
|
| 27 |
+
Soft delete an account (superadmin only). Hierarchy and last_superadmin gates applied.
|
| 28 |
+
}
|
bruno/Super Admin/Account Management/List Accounts.bru
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
meta {
|
| 2 |
+
name: List Accounts
|
| 3 |
+
type: http
|
| 4 |
+
seq: 1
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
get {
|
| 8 |
+
url: {{base_url}}/api/v1/super-admin/accounts/?page=1&limit=10&role=&search=
|
| 9 |
+
auth: inherit
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
headers {
|
| 13 |
+
Authorization: Bearer {{access_token}}
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
settings {
|
| 17 |
+
encodeUrl: true
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
docs {
|
| 21 |
+
List all accounts with pagination, optional role filter and search (superadmin only)
|
| 22 |
+
}
|
bruno/Super Admin/Account Management/Update Account.bru
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
meta {
|
| 2 |
+
name: Update Account
|
| 3 |
+
type: http
|
| 4 |
+
seq: 3
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
put {
|
| 8 |
+
url: {{base_url}}/api/v1/super-admin/accounts/:account_id
|
| 9 |
+
body: json
|
| 10 |
+
auth: inherit
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
params:path {
|
| 14 |
+
account_id: 7a731432-8107-4939-9dac-33a50356c36b
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
headers {
|
| 18 |
+
Content-Type: application/json
|
| 19 |
+
Authorization: Bearer {{access_token}}
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
body:json {
|
| 23 |
+
{
|
| 24 |
+
"email": "updated@example.com",
|
| 25 |
+
"username": "updateduser",
|
| 26 |
+
"full_name": "Updated Name",
|
| 27 |
+
"role": "admin",
|
| 28 |
+
"privileges": {
|
| 29 |
+
"event_management": true,
|
| 30 |
+
"exam_management": true,
|
| 31 |
+
"academy_management": true,
|
| 32 |
+
"content_management": true
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
settings {
|
| 38 |
+
encodeUrl: true
|
| 39 |
+
timeout: 0
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
docs {
|
| 43 |
+
Update account profile fields, role, or privileges (superadmin only). Hierarchy, FR-19, and last_superadmin gates applied.
|
| 44 |
+
}
|
bruno/Super Admin/Account Management/folder.bru
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
meta {
|
| 2 |
+
name: Account Management
|
| 3 |
+
}
|
bruno/Super Admin/Accounts/Set User Role.bru
CHANGED
|
@@ -21,7 +21,7 @@ headers {
|
|
| 21 |
|
| 22 |
body:json {
|
| 23 |
{
|
| 24 |
-
"role_name": "
|
| 25 |
"privileges": {
|
| 26 |
|
| 27 |
}
|
|
|
|
| 21 |
|
| 22 |
body:json {
|
| 23 |
{
|
| 24 |
+
"role_name": "event_exam",
|
| 25 |
"privileges": {
|
| 26 |
|
| 27 |
}
|
controllers/admin_account_controller.go
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package controllers
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"strconv"
|
| 5 |
+
|
| 6 |
+
"abdanhafidz.com/go-boilerplate/models/dto"
|
| 7 |
+
entity "abdanhafidz.com/go-boilerplate/models/entity"
|
| 8 |
+
http_error "abdanhafidz.com/go-boilerplate/models/error"
|
| 9 |
+
"abdanhafidz.com/go-boilerplate/services"
|
| 10 |
+
"github.com/gin-gonic/gin"
|
| 11 |
+
"github.com/google/uuid"
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
type AdminAccountController interface {
|
| 15 |
+
ListAccounts(ctx *gin.Context)
|
| 16 |
+
CreateAccount(ctx *gin.Context)
|
| 17 |
+
UpdateAccount(ctx *gin.Context)
|
| 18 |
+
DeleteAccount(ctx *gin.Context)
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
type adminAccountController struct {
|
| 22 |
+
accountService services.AccountService
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
func NewAdminAccountController(accountService services.AccountService) AdminAccountController {
|
| 26 |
+
return &adminAccountController{accountService: accountService}
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
func (c *adminAccountController) ListAccounts(ctx *gin.Context) {
|
| 30 |
+
limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10"))
|
| 31 |
+
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
|
| 32 |
+
role := ctx.DefaultQuery("role", "")
|
| 33 |
+
search := ctx.DefaultQuery("search", "")
|
| 34 |
+
|
| 35 |
+
if limit < 1 {
|
| 36 |
+
limit = 10
|
| 37 |
+
} else if limit > 50 {
|
| 38 |
+
limit = 50
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
if page < 1 {
|
| 42 |
+
page = 1
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
validRoles := map[string]bool{
|
| 46 |
+
"user": true,
|
| 47 |
+
"admin": true,
|
| 48 |
+
"super_admin": true,
|
| 49 |
+
"teacher": true,
|
| 50 |
+
"organizer": true,
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
if role != "" && !validRoles[role] {
|
| 54 |
+
ResponseJSON(ctx, gin.H{}, []dto.AdminAccountResponse{}, http_error.BAD_REQUEST_ERROR)
|
| 55 |
+
return
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
offset := (page - 1) * limit
|
| 59 |
+
p := entity.Pagination{
|
| 60 |
+
Limit: limit,
|
| 61 |
+
Offset: offset,
|
| 62 |
+
Role: role,
|
| 63 |
+
Search: search,
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
callerID := ParseAccountId(ctx)
|
| 67 |
+
|
| 68 |
+
accounts, total, err := c.accountService.AdminListAccounts(ctx.Request.Context(), callerID, p)
|
| 69 |
+
if err != nil {
|
| 70 |
+
ResponseJSON(ctx, gin.H{}, []dto.AdminAccountResponse{}, err)
|
| 71 |
+
return
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
var users []dto.AdminAccountResponse
|
| 75 |
+
for _, acc := range accounts {
|
| 76 |
+
fullName := ""
|
| 77 |
+
if acc.AccountDetail != nil && acc.AccountDetail.FullName != nil {
|
| 78 |
+
fullName = *acc.AccountDetail.FullName
|
| 79 |
+
}
|
| 80 |
+
users = append(users, dto.AdminAccountResponse{
|
| 81 |
+
Id: acc.Id,
|
| 82 |
+
Username: acc.Username,
|
| 83 |
+
Email: acc.Email,
|
| 84 |
+
FullName: fullName,
|
| 85 |
+
Role: acc.Role,
|
| 86 |
+
Privileges: dto.Privileges{
|
| 87 |
+
EventManagement: acc.EventManagement,
|
| 88 |
+
ExamManagement: acc.ExamManagement,
|
| 89 |
+
AcademyManagement: acc.AcademyManagement,
|
| 90 |
+
ContentManagement: acc.ContentManagement,
|
| 91 |
+
},
|
| 92 |
+
IsEmailVerified: acc.IsEmailVerified,
|
| 93 |
+
IsDetailCompleted: acc.IsDetailCompleted,
|
| 94 |
+
CreatedAt: acc.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
| 95 |
+
})
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
var totalPages int
|
| 99 |
+
if total == 0 {
|
| 100 |
+
totalPages = 1
|
| 101 |
+
} else {
|
| 102 |
+
totalPages = int((total + int64(limit) - 1) / int64(limit))
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
if page > totalPages {
|
| 106 |
+
page = totalPages
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
meta := gin.H{
|
| 110 |
+
"totalItems": total,
|
| 111 |
+
"totalPages": totalPages,
|
| 112 |
+
"currentPage": page,
|
| 113 |
+
"limit": limit,
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
ResponseJSON(ctx, meta, users, nil)
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
func (c *adminAccountController) CreateAccount(ctx *gin.Context) {
|
| 120 |
+
req := RequestJSON[dto.AdminCreateAccountRequest](ctx)
|
| 121 |
+
|
| 122 |
+
callerID := ParseAccountId(ctx)
|
| 123 |
+
|
| 124 |
+
account, err := c.accountService.AdminCreateAccount(ctx.Request.Context(), callerID, req)
|
| 125 |
+
if err != nil {
|
| 126 |
+
ResponseJSON(ctx, req, dto.AdminAccountResponse{}, err)
|
| 127 |
+
return
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
fullName := ""
|
| 131 |
+
if account.AccountDetail != nil && account.AccountDetail.FullName != nil {
|
| 132 |
+
fullName = *account.AccountDetail.FullName
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
res := dto.AdminAccountResponse{
|
| 136 |
+
Id: account.Id,
|
| 137 |
+
Username: account.Username,
|
| 138 |
+
Email: account.Email,
|
| 139 |
+
FullName: fullName,
|
| 140 |
+
Role: account.Role,
|
| 141 |
+
Privileges: dto.Privileges{
|
| 142 |
+
EventManagement: account.EventManagement,
|
| 143 |
+
ExamManagement: account.ExamManagement,
|
| 144 |
+
AcademyManagement: account.AcademyManagement,
|
| 145 |
+
ContentManagement: account.ContentManagement,
|
| 146 |
+
},
|
| 147 |
+
IsEmailVerified: account.IsEmailVerified,
|
| 148 |
+
IsDetailCompleted: account.IsDetailCompleted,
|
| 149 |
+
CreatedAt: account.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
ResponseJSON(ctx, req, res, nil)
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
func (c *adminAccountController) UpdateAccount(ctx *gin.Context) {
|
| 156 |
+
idParam := ctx.Param("account_id")
|
| 157 |
+
accountID, err := uuid.Parse(idParam)
|
| 158 |
+
if err != nil {
|
| 159 |
+
ResponseJSON(ctx, gin.H{"id": idParam}, dto.AdminAccountResponse{}, http_error.INVALID_TOKEN)
|
| 160 |
+
return
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
req := RequestJSON[dto.AdminUpdateAccountRequest](ctx)
|
| 164 |
+
|
| 165 |
+
callerID := ParseAccountId(ctx)
|
| 166 |
+
|
| 167 |
+
account, err := c.accountService.AdminUpdateAccount(ctx.Request.Context(), callerID, accountID, req)
|
| 168 |
+
if err != nil {
|
| 169 |
+
ResponseJSON(ctx, req, dto.AdminAccountResponse{}, err)
|
| 170 |
+
return
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
fullName := ""
|
| 174 |
+
if account.AccountDetail != nil && account.AccountDetail.FullName != nil {
|
| 175 |
+
fullName = *account.AccountDetail.FullName
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
res := dto.AdminAccountResponse{
|
| 179 |
+
Id: account.Id,
|
| 180 |
+
Username: account.Username,
|
| 181 |
+
Email: account.Email,
|
| 182 |
+
FullName: fullName,
|
| 183 |
+
Role: account.Role,
|
| 184 |
+
Privileges: dto.Privileges{
|
| 185 |
+
EventManagement: account.EventManagement,
|
| 186 |
+
ExamManagement: account.ExamManagement,
|
| 187 |
+
AcademyManagement: account.AcademyManagement,
|
| 188 |
+
ContentManagement: account.ContentManagement,
|
| 189 |
+
},
|
| 190 |
+
IsEmailVerified: account.IsEmailVerified,
|
| 191 |
+
IsDetailCompleted: account.IsDetailCompleted,
|
| 192 |
+
CreatedAt: account.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
ResponseJSON(ctx, req, res, nil)
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
func (c *adminAccountController) DeleteAccount(ctx *gin.Context) {
|
| 199 |
+
idParam := ctx.Param("account_id")
|
| 200 |
+
accountID, err := uuid.Parse(idParam)
|
| 201 |
+
if err != nil {
|
| 202 |
+
ResponseJSON[any](ctx, gin.H{"id": idParam}, nil, http_error.INVALID_TOKEN)
|
| 203 |
+
return
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
callerID := ParseAccountId(ctx)
|
| 207 |
+
|
| 208 |
+
err = c.accountService.AdminDeleteAccount(ctx.Request.Context(), callerID, accountID)
|
| 209 |
+
if err != nil {
|
| 210 |
+
ResponseJSON[any](ctx, gin.H{"id": accountID}, nil, err)
|
| 211 |
+
return
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
ResponseJSON(ctx, gin.H{"id": accountID}, gin.H{"message": "Account deleted successfully"}, nil)
|
| 215 |
+
}
|
models/dto/admin_account_dto.go
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package dto
|
| 2 |
+
|
| 3 |
+
import "github.com/google/uuid"
|
| 4 |
+
|
| 5 |
+
type AdminCreateAccountRequest struct {
|
| 6 |
+
Email string `json:"email" binding:"required,email"`
|
| 7 |
+
Username string `json:"username" binding:"required"`
|
| 8 |
+
FullName string `json:"full_name" binding:"required"`
|
| 9 |
+
Password string `json:"password" binding:"required"`
|
| 10 |
+
Role string `json:"role" binding:"required"`
|
| 11 |
+
Privileges Privileges `json:"privileges"`
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
type AdminUpdateAccountRequest struct {
|
| 15 |
+
Email string `json:"email"`
|
| 16 |
+
Username string `json:"username"`
|
| 17 |
+
FullName *string `json:"full_name"`
|
| 18 |
+
Role string `json:"role"`
|
| 19 |
+
Privileges *Privileges `json:"privileges"`
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
type AdminAccountResponse struct {
|
| 23 |
+
Id uuid.UUID `json:"id"`
|
| 24 |
+
Username string `json:"username"`
|
| 25 |
+
Email string `json:"email"`
|
| 26 |
+
FullName string `json:"full_name"`
|
| 27 |
+
Role string `json:"role"`
|
| 28 |
+
Privileges Privileges `json:"privileges"`
|
| 29 |
+
IsEmailVerified bool `json:"is_email_verified"`
|
| 30 |
+
IsDetailCompleted bool `json:"is_detail_completed"`
|
| 31 |
+
CreatedAt string `json:"created_at"`
|
| 32 |
+
}
|
provider/controller_provider.go
CHANGED
|
@@ -3,6 +3,7 @@ package provider
|
|
| 3 |
import "abdanhafidz.com/go-boilerplate/controllers"
|
| 4 |
|
| 5 |
type ControllerProvider interface {
|
|
|
|
| 6 |
ProvideAdminAcademyController() controllers.AdminAcademyController
|
| 7 |
ProvideAdminEventController() controllers.AdminEventController
|
| 8 |
ProvideAdminExamController() controllers.AdminExamController
|
|
@@ -27,6 +28,7 @@ type ControllerProvider interface {
|
|
| 27 |
}
|
| 28 |
|
| 29 |
type controllerProvider struct {
|
|
|
|
| 30 |
adminAcademyController controllers.AdminAcademyController
|
| 31 |
adminEventController controllers.AdminEventController
|
| 32 |
adminExamController controllers.AdminExamController
|
|
@@ -68,6 +70,7 @@ func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider
|
|
| 68 |
emailVerificationController := controllers.NewEmailVerificationController(servicesProvider.ProvideEmailVerificationService())
|
| 69 |
eventController := controllers.NewEventController(servicesProvider.ProvideEventService())
|
| 70 |
eventExamController := controllers.NewEventExamController(servicesProvider.ProvideEventExamService())
|
|
|
|
| 71 |
eventExamProctoringController := controllers.NewEventExamProctoringController(servicesProvider.ProvideEventExamProctoringService())
|
| 72 |
forgotPasswordController := controllers.NewForgotPasswordController(servicesProvider.ProvideForgotPasswordService())
|
| 73 |
optionController := controllers.NewOptionController(servicesProvider.ProvideOptionService())
|
|
@@ -81,6 +84,7 @@ func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider
|
|
| 81 |
repairController := controllers.NewRepairController(servicesProvider.ProvideRepairService())
|
| 82 |
superAdminRoleController := controllers.NewSuperAdminRoleController(servicesProvider.ProvideAccountService())
|
| 83 |
return &controllerProvider{
|
|
|
|
| 84 |
adminAcademyController: adminAcademyController,
|
| 85 |
adminEventController: adminEventController,
|
| 86 |
adminExamController: adminExamController,
|
|
@@ -107,6 +111,10 @@ func NewControllerProvider(servicesProvider ServicesProvider) ControllerProvider
|
|
| 107 |
|
| 108 |
// --- Getter Methods ---
|
| 109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
func (c *controllerProvider) ProvideAdminAcademyController() controllers.AdminAcademyController {
|
| 111 |
return c.adminAcademyController
|
| 112 |
}
|
|
|
|
| 3 |
import "abdanhafidz.com/go-boilerplate/controllers"
|
| 4 |
|
| 5 |
type ControllerProvider interface {
|
| 6 |
+
ProvideAdminAccountController() controllers.AdminAccountController
|
| 7 |
ProvideAdminAcademyController() controllers.AdminAcademyController
|
| 8 |
ProvideAdminEventController() controllers.AdminEventController
|
| 9 |
ProvideAdminExamController() controllers.AdminExamController
|
|
|
|
| 28 |
}
|
| 29 |
|
| 30 |
type controllerProvider struct {
|
| 31 |
+
adminAccountController controllers.AdminAccountController
|
| 32 |
adminAcademyController controllers.AdminAcademyController
|
| 33 |
adminEventController controllers.AdminEventController
|
| 34 |
adminExamController controllers.AdminExamController
|
|
|
|
| 70 |
emailVerificationController := controllers.NewEmailVerificationController(servicesProvider.ProvideEmailVerificationService())
|
| 71 |
eventController := controllers.NewEventController(servicesProvider.ProvideEventService())
|
| 72 |
eventExamController := controllers.NewEventExamController(servicesProvider.ProvideEventExamService())
|
| 73 |
+
adminAccountController := controllers.NewAdminAccountController(servicesProvider.ProvideAccountService())
|
| 74 |
eventExamProctoringController := controllers.NewEventExamProctoringController(servicesProvider.ProvideEventExamProctoringService())
|
| 75 |
forgotPasswordController := controllers.NewForgotPasswordController(servicesProvider.ProvideForgotPasswordService())
|
| 76 |
optionController := controllers.NewOptionController(servicesProvider.ProvideOptionService())
|
|
|
|
| 84 |
repairController := controllers.NewRepairController(servicesProvider.ProvideRepairService())
|
| 85 |
superAdminRoleController := controllers.NewSuperAdminRoleController(servicesProvider.ProvideAccountService())
|
| 86 |
return &controllerProvider{
|
| 87 |
+
adminAccountController: adminAccountController,
|
| 88 |
adminAcademyController: adminAcademyController,
|
| 89 |
adminEventController: adminEventController,
|
| 90 |
adminExamController: adminExamController,
|
|
|
|
| 111 |
|
| 112 |
// --- Getter Methods ---
|
| 113 |
|
| 114 |
+
func (c *controllerProvider) ProvideAdminAccountController() controllers.AdminAccountController {
|
| 115 |
+
return c.adminAccountController
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
func (c *controllerProvider) ProvideAdminAcademyController() controllers.AdminAcademyController {
|
| 119 |
return c.adminAcademyController
|
| 120 |
}
|
router/{admin_router.go → admin__procotoring_router.go}
RENAMED
|
@@ -18,4 +18,5 @@ func AdminRouter(router *gin.Engine, middleware provider.MiddlewareProvider, con
|
|
| 18 |
proctoringAdminGroup.PUT("/events/:event_slug/exam/:exam_slug/proctoring/:log_id", proctoringController.UpdateLog)
|
| 19 |
proctoringAdminGroup.DELETE("/events/:event_slug/exam/:exam_slug/proctoring/:log_id", proctoringController.DeleteLog)
|
| 20 |
}
|
|
|
|
| 21 |
}
|
|
|
|
| 18 |
proctoringAdminGroup.PUT("/events/:event_slug/exam/:exam_slug/proctoring/:log_id", proctoringController.UpdateLog)
|
| 19 |
proctoringAdminGroup.DELETE("/events/:event_slug/exam/:exam_slug/proctoring/:log_id", proctoringController.DeleteLog)
|
| 20 |
}
|
| 21 |
+
|
| 22 |
}
|
router/super_admin_router.go
CHANGED
|
@@ -10,6 +10,7 @@ func SuperAdminRouter(router *gin.Engine, middleware provider.MiddlewareProvider
|
|
| 10 |
authorizationMiddleware := middleware.ProvideAuthorizationMiddleware()
|
| 11 |
|
| 12 |
userController := controller.ProvideUserController()
|
|
|
|
| 13 |
injectController := controller.ProvideSuperAdminInjectController()
|
| 14 |
repairController := controller.ProvideRepairController()
|
| 15 |
authController := controller.ProvideAuthenticationController()
|
|
@@ -18,6 +19,10 @@ func SuperAdminRouter(router *gin.Engine, middleware provider.MiddlewareProvider
|
|
| 18 |
// Super Admin Accounts Routes
|
| 19 |
accountsGroup := router.Group("/api/v1/super-admin/accounts", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifySuperAdmin)
|
| 20 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
accountsGroup.PUT("/:account_id/role", authController.SetUserRole)
|
| 22 |
}
|
| 23 |
|
|
@@ -52,6 +57,7 @@ func SuperAdminRouter(router *gin.Engine, middleware provider.MiddlewareProvider
|
|
| 52 |
injectSuperAdminGroup.POST("/zip", injectController.InjectZip)
|
| 53 |
}
|
| 54 |
|
|
|
|
| 55 |
repairSuperAdminGroup := router.Group("/api/v1/super-admin/repair", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifySuperAdmin)
|
| 56 |
{
|
| 57 |
repairSuperAdminGroup.POST("/", repairController.RepairQuestionStoragePath)
|
|
|
|
| 10 |
authorizationMiddleware := middleware.ProvideAuthorizationMiddleware()
|
| 11 |
|
| 12 |
userController := controller.ProvideUserController()
|
| 13 |
+
adminAccountController := controller.ProvideAdminAccountController()
|
| 14 |
injectController := controller.ProvideSuperAdminInjectController()
|
| 15 |
repairController := controller.ProvideRepairController()
|
| 16 |
authController := controller.ProvideAuthenticationController()
|
|
|
|
| 19 |
// Super Admin Accounts Routes
|
| 20 |
accountsGroup := router.Group("/api/v1/super-admin/accounts", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifySuperAdmin)
|
| 21 |
{
|
| 22 |
+
accountsGroup.GET("/", adminAccountController.ListAccounts)
|
| 23 |
+
accountsGroup.POST("/", adminAccountController.CreateAccount)
|
| 24 |
+
accountsGroup.PUT("/:account_id", adminAccountController.UpdateAccount)
|
| 25 |
+
accountsGroup.DELETE("/:account_id", adminAccountController.DeleteAccount)
|
| 26 |
accountsGroup.PUT("/:account_id/role", authController.SetUserRole)
|
| 27 |
}
|
| 28 |
|
|
|
|
| 57 |
injectSuperAdminGroup.POST("/zip", injectController.InjectZip)
|
| 58 |
}
|
| 59 |
|
| 60 |
+
// Super Admin Repair Routes
|
| 61 |
repairSuperAdminGroup := router.Group("/api/v1/super-admin/repair", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifySuperAdmin)
|
| 62 |
{
|
| 63 |
repairSuperAdminGroup.POST("/", repairController.RepairQuestionStoragePath)
|
services/account_service.go
CHANGED
|
@@ -49,6 +49,11 @@ type AccountService interface {
|
|
| 49 |
CountSuperAdmins(ctx context.Context) (int64, error)
|
| 50 |
WriteAuditLog(ctx context.Context, actorID uuid.UUID, action string, targetID *string, detail *string) error
|
| 51 |
GetAuditLogs(ctx context.Context, page, limit int, action, actorAccountID, from, to string) ([]entity.AuditLog, int64, error)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
|
| 54 |
type accountService struct {
|
|
@@ -415,6 +420,205 @@ func (s *accountService) GetAllAccountsWithPagination(ctx context.Context, pagin
|
|
| 415 |
return s.accountRepo.GetAllAccountsWithPagination(ctx, pagination)
|
| 416 |
}
|
| 417 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
func (s *accountService) BulkCreateAccounts(ctx context.Context, requests []dto.CreateUserRequest) (dto.BulkCreateUserResponse, error) {
|
| 419 |
response := dto.BulkCreateUserResponse{
|
| 420 |
SuccessCount: 0,
|
|
|
|
| 49 |
CountSuperAdmins(ctx context.Context) (int64, error)
|
| 50 |
WriteAuditLog(ctx context.Context, actorID uuid.UUID, action string, targetID *string, detail *string) error
|
| 51 |
GetAuditLogs(ctx context.Context, page, limit int, action, actorAccountID, from, to string) ([]entity.AuditLog, int64, error)
|
| 52 |
+
|
| 53 |
+
AdminListAccounts(ctx context.Context, callerID uuid.UUID, pagination entity.Pagination) ([]entity.Account, int64, error)
|
| 54 |
+
AdminCreateAccount(ctx context.Context, callerID uuid.UUID, req dto.AdminCreateAccountRequest) (entity.Account, error)
|
| 55 |
+
AdminUpdateAccount(ctx context.Context, callerID uuid.UUID, targetID uuid.UUID, req dto.AdminUpdateAccountRequest) (entity.Account, error)
|
| 56 |
+
AdminDeleteAccount(ctx context.Context, callerID uuid.UUID, targetID uuid.UUID) error
|
| 57 |
}
|
| 58 |
|
| 59 |
type accountService struct {
|
|
|
|
| 420 |
return s.accountRepo.GetAllAccountsWithPagination(ctx, pagination)
|
| 421 |
}
|
| 422 |
|
| 423 |
+
func (s *accountService) AdminListAccounts(ctx context.Context, callerID uuid.UUID, pagination entity.Pagination) ([]entity.Account, int64, error) {
|
| 424 |
+
caller, err := s.accountRepo.GetAccountById(ctx, callerID)
|
| 425 |
+
if err != nil {
|
| 426 |
+
return nil, 0, err
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
if caller.Role != "super_admin" {
|
| 430 |
+
return nil, 0, http_error.FORBIDDEN_ERROR
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
return s.accountRepo.GetAllAccountsWithPagination(ctx, pagination)
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
func (s *accountService) AdminCreateAccount(ctx context.Context, callerID uuid.UUID, req dto.AdminCreateAccountRequest) (entity.Account, error) {
|
| 437 |
+
caller, err := s.accountRepo.GetAccountById(ctx, callerID)
|
| 438 |
+
if err != nil {
|
| 439 |
+
return entity.Account{}, err
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
targetRole := req.Role
|
| 443 |
+
if targetRole == "" {
|
| 444 |
+
targetRole = "user"
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
if entity.RoleHierarchy(caller.Role) <= entity.RoleHierarchy(targetRole) {
|
| 448 |
+
return entity.Account{}, http_error.HIERARCHY_VIOLATION
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
if caller.Role != "super_admin" {
|
| 452 |
+
callerPrivileges := dto.Privileges{
|
| 453 |
+
EventManagement: caller.EventManagement,
|
| 454 |
+
ExamManagement: caller.ExamManagement,
|
| 455 |
+
AcademyManagement: caller.AcademyManagement,
|
| 456 |
+
ContentManagement: caller.ContentManagement,
|
| 457 |
+
}
|
| 458 |
+
if !req.Privileges.SubsetOf(callerPrivileges) {
|
| 459 |
+
return entity.Account{}, http_error.HIERARCHY_VIOLATION
|
| 460 |
+
}
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
account, err := s.Create(ctx, req.FullName, req.Email, req.Username, req.Password, targetRole)
|
| 464 |
+
if err != nil {
|
| 465 |
+
return entity.Account{}, err
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
account.IsEmailVerified = true
|
| 469 |
+
if targetRole != "" && targetRole != "user" {
|
| 470 |
+
roleName, eventMgt, examMgt, academyMgt, contentMgt := entity.DeriveRoleSnapshot(targetRole)
|
| 471 |
+
account.RoleName = roleName
|
| 472 |
+
account.EventManagement = eventMgt
|
| 473 |
+
account.ExamManagement = examMgt
|
| 474 |
+
account.AcademyManagement = academyMgt
|
| 475 |
+
account.ContentManagement = contentMgt
|
| 476 |
+
}
|
| 477 |
+
if req.Privileges != (dto.Privileges{}) {
|
| 478 |
+
account.EventManagement = req.Privileges.EventManagement
|
| 479 |
+
account.ExamManagement = req.Privileges.ExamManagement
|
| 480 |
+
account.AcademyManagement = req.Privileges.AcademyManagement
|
| 481 |
+
account.ContentManagement = req.Privileges.ContentManagement
|
| 482 |
+
}
|
| 483 |
+
account.TokenVersion++
|
| 484 |
+
account, err = s.accountRepo.UpdateAccount(ctx, account)
|
| 485 |
+
if err != nil {
|
| 486 |
+
return entity.Account{}, err
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
if err := s.SetEmailVerified(ctx, account.Id, true); err != nil {
|
| 490 |
+
return entity.Account{}, err
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
targetIDStr := account.Id.String()
|
| 494 |
+
detailStr := fmt.Sprintf(`{"email":"%s","role":"%s"}`, account.Email, account.Role)
|
| 495 |
+
_ = s.WriteAuditLog(ctx, callerID, "user_created", &targetIDStr, &detailStr)
|
| 496 |
+
|
| 497 |
+
return account, nil
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
func (s *accountService) AdminUpdateAccount(ctx context.Context, callerID uuid.UUID, targetID uuid.UUID, req dto.AdminUpdateAccountRequest) (entity.Account, error) {
|
| 501 |
+
caller, err := s.accountRepo.GetAccountById(ctx, callerID)
|
| 502 |
+
if err != nil {
|
| 503 |
+
return entity.Account{}, err
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
target, err := s.accountRepo.GetAccountById(ctx, targetID)
|
| 507 |
+
if err != nil {
|
| 508 |
+
return entity.Account{}, http_error.ACCOUNT_NOT_FOUND
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
if entity.RoleHierarchy(caller.Role) <= entity.RoleHierarchy(target.Role) {
|
| 512 |
+
return entity.Account{}, http_error.HIERARCHY_VIOLATION
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
if req.Role != "" {
|
| 516 |
+
newTargetHierarchy := entity.RoleHierarchy(req.Role)
|
| 517 |
+
if entity.RoleHierarchy(caller.Role) <= newTargetHierarchy {
|
| 518 |
+
return entity.Account{}, http_error.HIERARCHY_VIOLATION
|
| 519 |
+
}
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
if caller.Role != "super_admin" && req.Privileges != nil {
|
| 523 |
+
callerPrivileges := dto.Privileges{
|
| 524 |
+
EventManagement: caller.EventManagement,
|
| 525 |
+
ExamManagement: caller.ExamManagement,
|
| 526 |
+
AcademyManagement: caller.AcademyManagement,
|
| 527 |
+
ContentManagement: caller.ContentManagement,
|
| 528 |
+
}
|
| 529 |
+
if !req.Privileges.SubsetOf(callerPrivileges) {
|
| 530 |
+
return entity.Account{}, http_error.HIERARCHY_VIOLATION
|
| 531 |
+
}
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
if req.Role != "" && req.Role != "super_admin" && target.Role == "super_admin" {
|
| 535 |
+
count, err := s.accountRepo.CountAccountsByRole(ctx, "super_admin")
|
| 536 |
+
if err != nil {
|
| 537 |
+
return entity.Account{}, err
|
| 538 |
+
}
|
| 539 |
+
if count <= 1 {
|
| 540 |
+
return entity.Account{}, http_error.LAST_SUPERADMIN
|
| 541 |
+
}
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
if req.Email != "" {
|
| 545 |
+
target.Email = req.Email
|
| 546 |
+
}
|
| 547 |
+
if req.Username != "" {
|
| 548 |
+
target.Username = req.Username
|
| 549 |
+
}
|
| 550 |
+
if req.FullName != nil && target.AccountDetail != nil {
|
| 551 |
+
target.AccountDetail.FullName = req.FullName
|
| 552 |
+
_, err = s.accountDetailRepo.UpdateAccountDetail(ctx, *target.AccountDetail)
|
| 553 |
+
if err != nil {
|
| 554 |
+
return entity.Account{}, err
|
| 555 |
+
}
|
| 556 |
+
}
|
| 557 |
+
if req.Role != "" {
|
| 558 |
+
target.Role = req.Role
|
| 559 |
+
roleName, eventMgt, examMgt, academyMgt, contentMgt := entity.DeriveRoleSnapshot(req.Role)
|
| 560 |
+
target.RoleName = roleName
|
| 561 |
+
target.EventManagement = eventMgt
|
| 562 |
+
target.ExamManagement = examMgt
|
| 563 |
+
target.AcademyManagement = academyMgt
|
| 564 |
+
target.ContentManagement = contentMgt
|
| 565 |
+
}
|
| 566 |
+
if req.Privileges != nil {
|
| 567 |
+
target.EventManagement = req.Privileges.EventManagement
|
| 568 |
+
target.ExamManagement = req.Privileges.ExamManagement
|
| 569 |
+
target.AcademyManagement = req.Privileges.AcademyManagement
|
| 570 |
+
target.ContentManagement = req.Privileges.ContentManagement
|
| 571 |
+
}
|
| 572 |
+
target.TokenVersion++
|
| 573 |
+
|
| 574 |
+
updated, err := s.accountRepo.UpdateAccount(ctx, target)
|
| 575 |
+
if err != nil {
|
| 576 |
+
return entity.Account{}, err
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
targetIDStr := target.Id.String()
|
| 580 |
+
detailStr := fmt.Sprintf(`{"email":"%s","role":"%s"}`, target.Email, target.Role)
|
| 581 |
+
_ = s.WriteAuditLog(ctx, callerID, "user_updated", &targetIDStr, &detailStr)
|
| 582 |
+
|
| 583 |
+
return updated, nil
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
func (s *accountService) AdminDeleteAccount(ctx context.Context, callerID uuid.UUID, targetID uuid.UUID) error {
|
| 587 |
+
caller, err := s.accountRepo.GetAccountById(ctx, callerID)
|
| 588 |
+
if err != nil {
|
| 589 |
+
return err
|
| 590 |
+
}
|
| 591 |
+
|
| 592 |
+
target, err := s.accountRepo.GetAccountById(ctx, targetID)
|
| 593 |
+
if err != nil {
|
| 594 |
+
return http_error.ACCOUNT_NOT_FOUND
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
if entity.RoleHierarchy(caller.Role) <= entity.RoleHierarchy(target.Role) {
|
| 598 |
+
return http_error.HIERARCHY_VIOLATION
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
if target.Role == "super_admin" {
|
| 602 |
+
count, err := s.accountRepo.CountAccountsByRole(ctx, "super_admin")
|
| 603 |
+
if err != nil {
|
| 604 |
+
return err
|
| 605 |
+
}
|
| 606 |
+
if count <= 1 {
|
| 607 |
+
return http_error.LAST_SUPERADMIN
|
| 608 |
+
}
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
if err := s.accountRepo.SoftDeleteAccount(ctx, targetID); err != nil {
|
| 612 |
+
return err
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
targetIDStr := targetID.String()
|
| 616 |
+
detailStr := fmt.Sprintf(`{"email":"%s"}`, target.Email)
|
| 617 |
+
_ = s.WriteAuditLog(ctx, callerID, "user_deleted", &targetIDStr, &detailStr)
|
| 618 |
+
|
| 619 |
+
return nil
|
| 620 |
+
}
|
| 621 |
+
|
| 622 |
func (s *accountService) BulkCreateAccounts(ctx context.Context, requests []dto.CreateUserRequest) (dto.BulkCreateUserResponse, error) {
|
| 623 |
response := dto.BulkCreateUserResponse{
|
| 624 |
SuccessCount: 0,
|