lifedebugger commited on
Commit
ba4bba1
·
1 Parent(s): 003bc96

Deploy files from GitHub repository

Browse files
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": "teacher",
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,