lifedebugger commited on
Commit
17fa17b
·
1 Parent(s): be83b4a

Deploy files from GitHub repository

Browse files
Quzuu_API_Collection.postman_collection.json CHANGED
@@ -1,11 +1,11 @@
1
  {
2
  "info": {
3
- "_postman_id": "e33fddc6-52e9-4005-9c5f-a144547d5bbc",
4
  "name": "Quzuu API Collection",
5
  "description": "Complete API collection for Quzuu Platform with all endpoints organized by modules.",
6
  "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
7
  "_exporter_id": "43539486",
8
- "_collection_link": "https://go.postman.co/collection/43539486-e33fddc6-52e9-4005-9c5f-a144547d5bbc?source=collection_link"
9
  },
10
  "item": [
11
  {
@@ -4157,6 +4157,61 @@
4157
  "description": "Delete academy (admin)"
4158
  },
4159
  "response": []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4160
  }
4161
  ]
4162
  },
 
1
  {
2
  "info": {
3
+ "_postman_id": "5114459b-535e-4021-bd3c-6145a5d8fc61",
4
  "name": "Quzuu API Collection",
5
  "description": "Complete API collection for Quzuu Platform with all endpoints organized by modules.",
6
  "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
7
  "_exporter_id": "43539486",
8
+ "_collection_link": "https://go.postman.co/collection/43539486-5114459b-535e-4021-bd3c-6145a5d8fc61?source=collection_link"
9
  },
10
  "item": [
11
  {
 
4157
  "description": "Delete academy (admin)"
4158
  },
4159
  "response": []
4160
+ },
4161
+ {
4162
+ "name": "Academy - List",
4163
+ "request": {
4164
+ "method": "GET",
4165
+ "header": [
4166
+ {
4167
+ "key": "Authorization",
4168
+ "value": "Bearer {{access_token}}",
4169
+ "type": "text"
4170
+ }
4171
+ ],
4172
+ "url": {
4173
+ "raw": "{{base_url}}/api/v1/admin/academy/?page=1&limit=10&search=&sortBy=created_at&orderBy=desc",
4174
+ "host": [
4175
+ "{{base_url}}"
4176
+ ],
4177
+ "path": [
4178
+ "api",
4179
+ "v1",
4180
+ "admin",
4181
+ "academy",
4182
+ ""
4183
+ ],
4184
+ "query": [
4185
+ {
4186
+ "key": "page",
4187
+ "value": "1",
4188
+ "description": "Page number"
4189
+ },
4190
+ {
4191
+ "key": "limit",
4192
+ "value": "10",
4193
+ "description": "Items per page (max 100)"
4194
+ },
4195
+ {
4196
+ "key": "search",
4197
+ "value": "",
4198
+ "description": "Search by title / slug / code"
4199
+ },
4200
+ {
4201
+ "key": "sortBy",
4202
+ "value": "created_at",
4203
+ "description": "Sort field (title, slug, code, created_at, is_public)"
4204
+ },
4205
+ {
4206
+ "key": "orderBy",
4207
+ "value": "desc",
4208
+ "description": "Sort direction (asc / desc)"
4209
+ }
4210
+ ]
4211
+ },
4212
+ "description": "List all academies (private and public) with pagination (admin)"
4213
+ },
4214
+ "response": []
4215
  }
4216
  ]
4217
  },
controllers/admin_academy_controller.go CHANGED
@@ -12,6 +12,7 @@ import (
12
  )
13
 
14
  type AdminAcademyController interface {
 
15
  CreateAcademy(ctx *gin.Context)
16
  GetAcademyDetail(ctx *gin.Context)
17
  UpdateAcademy(ctx *gin.Context)
@@ -46,6 +47,66 @@ func NewAdminAcademyController(service services.AdminAcademyService) AdminAcadem
46
  return &adminAcademyController{service: service}
47
  }
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  // CreateAcademy godoc
50
  // @Summary Create Academy
51
  // @Description Create a new academy
 
12
  )
13
 
14
  type AdminAcademyController interface {
15
+ ListAcademies(ctx *gin.Context)
16
  CreateAcademy(ctx *gin.Context)
17
  GetAcademyDetail(ctx *gin.Context)
18
  UpdateAcademy(ctx *gin.Context)
 
47
  return &adminAcademyController{service: service}
48
  }
49
 
50
+ // ListAcademies godoc
51
+ // @Summary Admin: List Academies
52
+ // @Description List all academies (private and public)
53
+ // @Tags Admin Academy
54
+ // @Accept json
55
+ // @Produce json
56
+ // @Security BearerAuth
57
+ // @Param limit query int false "Items per page" default(10)
58
+ // @Param page query int false "Page number" default(1)
59
+ // @Param search query string false "Search by title / slug / code"
60
+ // @Param sortBy query string false "Sort field (title, slug, code, created_at, is_public)"
61
+ // @Param orderBy query string false "Sort direction (asc / desc)"
62
+ // @Success 200 {object} dto.SuccessResponse[[]entity.Academy]
63
+ // @Failure 400 {object} dto.ErrorResponse
64
+ // @Router /api/v1/admin/academy [get]
65
+ func (c *adminAcademyController) ListAcademies(ctx *gin.Context) {
66
+ limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10"))
67
+ page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
68
+ search := ctx.DefaultQuery("search", "")
69
+ sortBy := ctx.DefaultQuery("sortBy", "")
70
+ order := ctx.DefaultQuery("orderBy", "")
71
+ if order == "" {
72
+ order = ctx.DefaultQuery("order", "")
73
+ }
74
+
75
+ if limit < 1 {
76
+ limit = 10
77
+ } else if limit > 100 {
78
+ limit = 100
79
+ }
80
+ if page < 1 {
81
+ page = 1
82
+ }
83
+
84
+ offset := (page - 1) * limit
85
+ p := entity.Pagination{Limit: limit, Offset: offset, Search: search, SortBy: sortBy, Order: order}
86
+
87
+ list, total, err := c.service.ListAcademies(ctx.Request.Context(), p)
88
+ if err != nil {
89
+ ResponseJSON[any, any](ctx, nil, nil, err)
90
+ return
91
+ }
92
+
93
+ totalPages := int((total + int64(limit) - 1) / int64(limit))
94
+ if total == 0 {
95
+ totalPages = 1
96
+ }
97
+ if page > totalPages {
98
+ page = totalPages
99
+ }
100
+
101
+ meta := gin.H{
102
+ "totalItems": total,
103
+ "totalPages": totalPages,
104
+ "currentPage": page,
105
+ "limit": limit,
106
+ }
107
+ ResponseJSON(ctx, meta, list, nil)
108
+ }
109
+
110
  // CreateAcademy godoc
111
  // @Summary Create Academy
112
  // @Description Create a new academy
repositories/admin_academy_repository.go CHANGED
@@ -11,6 +11,7 @@ import (
11
 
12
  type AdminAcademyRepository interface {
13
  Atomic(ctx context.Context, fn func(r AdminAcademyRepository) error) error
 
14
 
15
  GetAcademyByID(ctx context.Context, id uuid.UUID) (entity.Academy, error)
16
  GetAcademyBySlug(ctx context.Context, slug string) (entity.Academy, error)
@@ -74,6 +75,59 @@ func (r *adminAcademyRepository) Atomic(ctx context.Context, fn func(rr AdminAca
74
  })
75
  }
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  func (r *adminAcademyRepository) GetAcademyByID(ctx context.Context, id uuid.UUID) (entity.Academy, error) {
78
  return r.academyRepo.GetAcademyByID(ctx, id)
79
  }
 
11
 
12
  type AdminAcademyRepository interface {
13
  Atomic(ctx context.Context, fn func(r AdminAcademyRepository) error) error
14
+ ListAcademiesWithPagination(ctx context.Context, p entity.Pagination) ([]entity.Academy, int64, error)
15
 
16
  GetAcademyByID(ctx context.Context, id uuid.UUID) (entity.Academy, error)
17
  GetAcademyBySlug(ctx context.Context, slug string) (entity.Academy, error)
 
75
  })
76
  }
77
 
78
+ func (r *adminAcademyRepository) ListAcademiesWithPagination(ctx context.Context, p entity.Pagination) ([]entity.Academy, int64, error) {
79
+ var list []entity.Academy
80
+
81
+ countQ := r.db.WithContext(ctx).
82
+ Model(&entity.Academy{}).
83
+ Where("deleted_at IS NULL")
84
+
85
+ if s := strings.TrimSpace(p.Search); s != "" {
86
+ like := "%" + s + "%"
87
+ countQ = countQ.Where("title ILIKE ? OR slug ILIKE ? OR code ILIKE ?", like, like, like)
88
+ }
89
+
90
+ var total int64
91
+ if err := countQ.Count(&total).Error; err != nil {
92
+ return nil, 0, err
93
+ }
94
+
95
+ q := r.db.WithContext(ctx).
96
+ Model(&entity.Academy{}).
97
+ Where("deleted_at IS NULL")
98
+
99
+ if s := strings.TrimSpace(p.Search); s != "" {
100
+ like := "%" + s + "%"
101
+ q = q.Where("title ILIKE ? OR slug ILIKE ? OR code ILIKE ?", like, like, like)
102
+ }
103
+
104
+ col := strings.ToLower(strings.TrimSpace(p.SortBy))
105
+ ord := strings.ToLower(strings.TrimSpace(p.Order))
106
+ if ord != "asc" {
107
+ ord = "desc"
108
+ }
109
+
110
+ switch col {
111
+ case "title", "slug", "code", "created_at", "is_public":
112
+ q = q.Order(col + " " + ord)
113
+ default:
114
+ q = q.Order("created_at " + ord)
115
+ }
116
+
117
+ if p.Limit > 0 {
118
+ q = q.Limit(p.Limit)
119
+ }
120
+ if p.Offset > 0 {
121
+ q = q.Offset(p.Offset)
122
+ }
123
+
124
+ if err := q.Find(&list).Error; err != nil {
125
+ return nil, 0, err
126
+ }
127
+
128
+ return list, total, nil
129
+ }
130
+
131
  func (r *adminAcademyRepository) GetAcademyByID(ctx context.Context, id uuid.UUID) (entity.Academy, error) {
132
  return r.academyRepo.GetAcademyByID(ctx, id)
133
  }
router/admin_academy_router.go CHANGED
@@ -12,6 +12,7 @@ func AdminAcademyRouter(router *gin.Engine, middleware provider.MiddlewareProvid
12
 
13
  adminAcademyGroup := router.Group("/api/v1/admin/academy", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifyAdmin)
14
  {
 
15
  adminAcademyGroup.POST("/", adminAcademyController.CreateAcademy)
16
  adminAcademyGroup.GET("/id/:academy_id/detail", adminAcademyController.GetAcademyDetail)
17
  adminAcademyGroup.PUT("/id/:academy_id", adminAcademyController.UpdateAcademy)
 
12
 
13
  adminAcademyGroup := router.Group("/api/v1/admin/academy", authenticationMiddleware.VerifyAccount, authorizationMiddleware.VerifyAdmin)
14
  {
15
+ adminAcademyGroup.GET("/", adminAcademyController.ListAcademies)
16
  adminAcademyGroup.POST("/", adminAcademyController.CreateAcademy)
17
  adminAcademyGroup.GET("/id/:academy_id/detail", adminAcademyController.GetAcademyDetail)
18
  adminAcademyGroup.PUT("/id/:academy_id", adminAcademyController.UpdateAcademy)
services/admin_academy_service.go CHANGED
@@ -15,6 +15,7 @@ import (
15
  )
16
 
17
  type AdminAcademyService interface {
 
18
  CreateAcademy(ctx context.Context, req dto.CreateAcademyRequest) (entity.Academy, error)
19
  GetAcademyDetail(ctx context.Context, id uuid.UUID) (entity.Academy, error)
20
  UpdateAcademy(ctx context.Context, id uuid.UUID, req dto.UpdateAcademyRequest) (entity.Academy, error)
@@ -49,6 +50,10 @@ func NewAdminAcademyService(repo repositories.AdminAcademyRepository) AdminAcade
49
  return &adminAcademyService{repo: repo}
50
  }
51
 
 
 
 
 
52
  func (s *adminAcademyService) CreateAcademy(ctx context.Context, req dto.CreateAcademyRequest) (entity.Academy, error) {
53
  if strings.TrimSpace(req.Title) == "" {
54
  return entity.Academy{}, http_error.TITLE_REQUIRED
 
15
  )
16
 
17
  type AdminAcademyService interface {
18
+ ListAcademies(ctx context.Context, p entity.Pagination) ([]entity.Academy, int64, error)
19
  CreateAcademy(ctx context.Context, req dto.CreateAcademyRequest) (entity.Academy, error)
20
  GetAcademyDetail(ctx context.Context, id uuid.UUID) (entity.Academy, error)
21
  UpdateAcademy(ctx context.Context, id uuid.UUID, req dto.UpdateAcademyRequest) (entity.Academy, error)
 
50
  return &adminAcademyService{repo: repo}
51
  }
52
 
53
+ func (s *adminAcademyService) ListAcademies(ctx context.Context, p entity.Pagination) ([]entity.Academy, int64, error) {
54
+ return s.repo.ListAcademiesWithPagination(ctx, p)
55
+ }
56
+
57
  func (s *adminAcademyService) CreateAcademy(ctx context.Context, req dto.CreateAcademyRequest) (entity.Academy, error) {
58
  if strings.TrimSpace(req.Title) == "" {
59
  return entity.Academy{}, http_error.TITLE_REQUIRED
swagger/docs/docs.go CHANGED
@@ -369,6 +369,72 @@ const docTemplate = `{
369
  }
370
  },
371
  "/api/v1/admin/academy": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  "post": {
373
  "security": [
374
  {
@@ -7006,6 +7072,22 @@ const docTemplate = `{
7006
  }
7007
  }
7008
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7009
  "dto.SuccessResponse-array_models_AcademyAssign": {
7010
  "type": "object",
7011
  "properties": {
 
369
  }
370
  },
371
  "/api/v1/admin/academy": {
372
+ "get": {
373
+ "security": [
374
+ {
375
+ "BearerAuth": []
376
+ }
377
+ ],
378
+ "description": "List all academies (private and public)",
379
+ "consumes": [
380
+ "application/json"
381
+ ],
382
+ "produces": [
383
+ "application/json"
384
+ ],
385
+ "tags": [
386
+ "Admin Academy"
387
+ ],
388
+ "summary": "Admin: List Academies",
389
+ "parameters": [
390
+ {
391
+ "type": "integer",
392
+ "default": 10,
393
+ "description": "Items per page",
394
+ "name": "limit",
395
+ "in": "query"
396
+ },
397
+ {
398
+ "type": "integer",
399
+ "default": 1,
400
+ "description": "Page number",
401
+ "name": "page",
402
+ "in": "query"
403
+ },
404
+ {
405
+ "type": "string",
406
+ "description": "Search by title / slug / code",
407
+ "name": "search",
408
+ "in": "query"
409
+ },
410
+ {
411
+ "type": "string",
412
+ "description": "Sort field (title, slug, code, created_at, is_public)",
413
+ "name": "sortBy",
414
+ "in": "query"
415
+ },
416
+ {
417
+ "type": "string",
418
+ "description": "Sort direction (asc / desc)",
419
+ "name": "orderBy",
420
+ "in": "query"
421
+ }
422
+ ],
423
+ "responses": {
424
+ "200": {
425
+ "description": "OK",
426
+ "schema": {
427
+ "$ref": "#/definitions/dto.SuccessResponse-array_models_Academy"
428
+ }
429
+ },
430
+ "400": {
431
+ "description": "Bad Request",
432
+ "schema": {
433
+ "$ref": "#/definitions/dto.ErrorResponse"
434
+ }
435
+ }
436
+ }
437
+ },
438
  "post": {
439
  "security": [
440
  {
 
7072
  }
7073
  }
7074
  },
7075
+ "dto.SuccessResponse-array_models_Academy": {
7076
+ "type": "object",
7077
+ "properties": {
7078
+ "data": {
7079
+ "type": "array",
7080
+ "items": {
7081
+ "$ref": "#/definitions/models.Academy"
7082
+ }
7083
+ },
7084
+ "message": {},
7085
+ "meta_data": {},
7086
+ "status": {
7087
+ "type": "string"
7088
+ }
7089
+ }
7090
+ },
7091
  "dto.SuccessResponse-array_models_AcademyAssign": {
7092
  "type": "object",
7093
  "properties": {
swagger/docs/swagger.json CHANGED
@@ -366,6 +366,72 @@
366
  }
367
  },
368
  "/api/v1/admin/academy": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  "post": {
370
  "security": [
371
  {
@@ -7003,6 +7069,22 @@
7003
  }
7004
  }
7005
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7006
  "dto.SuccessResponse-array_models_AcademyAssign": {
7007
  "type": "object",
7008
  "properties": {
 
366
  }
367
  },
368
  "/api/v1/admin/academy": {
369
+ "get": {
370
+ "security": [
371
+ {
372
+ "BearerAuth": []
373
+ }
374
+ ],
375
+ "description": "List all academies (private and public)",
376
+ "consumes": [
377
+ "application/json"
378
+ ],
379
+ "produces": [
380
+ "application/json"
381
+ ],
382
+ "tags": [
383
+ "Admin Academy"
384
+ ],
385
+ "summary": "Admin: List Academies",
386
+ "parameters": [
387
+ {
388
+ "type": "integer",
389
+ "default": 10,
390
+ "description": "Items per page",
391
+ "name": "limit",
392
+ "in": "query"
393
+ },
394
+ {
395
+ "type": "integer",
396
+ "default": 1,
397
+ "description": "Page number",
398
+ "name": "page",
399
+ "in": "query"
400
+ },
401
+ {
402
+ "type": "string",
403
+ "description": "Search by title / slug / code",
404
+ "name": "search",
405
+ "in": "query"
406
+ },
407
+ {
408
+ "type": "string",
409
+ "description": "Sort field (title, slug, code, created_at, is_public)",
410
+ "name": "sortBy",
411
+ "in": "query"
412
+ },
413
+ {
414
+ "type": "string",
415
+ "description": "Sort direction (asc / desc)",
416
+ "name": "orderBy",
417
+ "in": "query"
418
+ }
419
+ ],
420
+ "responses": {
421
+ "200": {
422
+ "description": "OK",
423
+ "schema": {
424
+ "$ref": "#/definitions/dto.SuccessResponse-array_models_Academy"
425
+ }
426
+ },
427
+ "400": {
428
+ "description": "Bad Request",
429
+ "schema": {
430
+ "$ref": "#/definitions/dto.ErrorResponse"
431
+ }
432
+ }
433
+ }
434
+ },
435
  "post": {
436
  "security": [
437
  {
 
7069
  }
7070
  }
7071
  },
7072
+ "dto.SuccessResponse-array_models_Academy": {
7073
+ "type": "object",
7074
+ "properties": {
7075
+ "data": {
7076
+ "type": "array",
7077
+ "items": {
7078
+ "$ref": "#/definitions/models.Academy"
7079
+ }
7080
+ },
7081
+ "message": {},
7082
+ "meta_data": {},
7083
+ "status": {
7084
+ "type": "string"
7085
+ }
7086
+ }
7087
+ },
7088
  "dto.SuccessResponse-array_models_AcademyAssign": {
7089
  "type": "object",
7090
  "properties": {
swagger/docs/swagger.yaml CHANGED
@@ -657,6 +657,17 @@ definitions:
657
  status:
658
  type: string
659
  type: object
 
 
 
 
 
 
 
 
 
 
 
660
  dto.SuccessResponse-array_models_AcademyAssign:
661
  properties:
662
  data:
@@ -2007,6 +2018,49 @@ paths:
2007
  tags:
2008
  - Account Detail
2009
  /api/v1/admin/academy:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2010
  post:
2011
  consumes:
2012
  - application/json
 
657
  status:
658
  type: string
659
  type: object
660
+ dto.SuccessResponse-array_models_Academy:
661
+ properties:
662
+ data:
663
+ items:
664
+ $ref: '#/definitions/models.Academy'
665
+ type: array
666
+ message: {}
667
+ meta_data: {}
668
+ status:
669
+ type: string
670
+ type: object
671
  dto.SuccessResponse-array_models_AcademyAssign:
672
  properties:
673
  data:
 
2018
  tags:
2019
  - Account Detail
2020
  /api/v1/admin/academy:
2021
+ get:
2022
+ consumes:
2023
+ - application/json
2024
+ description: List all academies (private and public)
2025
+ parameters:
2026
+ - default: 10
2027
+ description: Items per page
2028
+ in: query
2029
+ name: limit
2030
+ type: integer
2031
+ - default: 1
2032
+ description: Page number
2033
+ in: query
2034
+ name: page
2035
+ type: integer
2036
+ - description: Search by title / slug / code
2037
+ in: query
2038
+ name: search
2039
+ type: string
2040
+ - description: Sort field (title, slug, code, created_at, is_public)
2041
+ in: query
2042
+ name: sortBy
2043
+ type: string
2044
+ - description: Sort direction (asc / desc)
2045
+ in: query
2046
+ name: orderBy
2047
+ type: string
2048
+ produces:
2049
+ - application/json
2050
+ responses:
2051
+ "200":
2052
+ description: OK
2053
+ schema:
2054
+ $ref: '#/definitions/dto.SuccessResponse-array_models_Academy'
2055
+ "400":
2056
+ description: Bad Request
2057
+ schema:
2058
+ $ref: '#/definitions/dto.ErrorResponse'
2059
+ security:
2060
+ - BearerAuth: []
2061
+ summary: 'Admin: List Academies'
2062
+ tags:
2063
+ - Admin Academy
2064
  post:
2065
  consumes:
2066
  - application/json