lifedebugger commited on
Commit
aa6ea88
·
1 Parent(s): c32f786

Deploy files from GitHub repository

Browse files
controllers/academy_controller.go CHANGED
@@ -17,7 +17,7 @@ type AcademyController interface {
17
  CreateAcademy(ctx *gin.Context)
18
  GetAcademy(ctx *gin.Context)
19
  GetAcademyDetail(ctx *gin.Context)
20
- ListAcademies(ctx *gin.Context)
21
  UpdateAcademy(ctx *gin.Context)
22
  DeleteAcademy(ctx *gin.Context)
23
  JoinAcademyByCode(ctx *gin.Context)
@@ -73,26 +73,66 @@ func (c *academyController) GetAcademyDetail(ctx *gin.Context) {
73
  ResponseJSON(ctx, gin.H{"id": id}, res, err)
74
  }
75
 
76
- func (c *academyController) ListAcademies(ctx *gin.Context) {
77
  accountIdStr := ctx.GetString("account_id")
78
  accountId, err := uuid.Parse(accountIdStr)
79
  if err != nil {
80
  ResponseJSON[any, any](ctx, nil, nil, http_error.UNAUTHORIZED)
81
  return
82
  }
 
83
  limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10"))
84
  page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
 
 
 
 
 
 
 
 
 
 
85
  if page < 1 {
86
  page = 1
 
87
  }
 
88
  offset := (page - 1) * limit
89
  p := repositories.Pagination{Limit: limit, Offset: offset}
90
- list, total, err := c.academyService.ListAcademies(ctx.Request.Context(), accountId, p)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  meta := gin.H{
92
  "total_records": total,
93
  "page_size": limit,
94
  "current_page": page,
 
95
  }
 
 
 
 
 
96
  ResponseJSON(ctx, meta, list, err)
97
  }
98
 
 
17
  CreateAcademy(ctx *gin.Context)
18
  GetAcademy(ctx *gin.Context)
19
  GetAcademyDetail(ctx *gin.Context)
20
+ ListAcademy(ctx *gin.Context)
21
  UpdateAcademy(ctx *gin.Context)
22
  DeleteAcademy(ctx *gin.Context)
23
  JoinAcademyByCode(ctx *gin.Context)
 
73
  ResponseJSON(ctx, gin.H{"id": id}, res, err)
74
  }
75
 
76
+ func (c *academyController) ListAcademy(ctx *gin.Context) {
77
  accountIdStr := ctx.GetString("account_id")
78
  accountId, err := uuid.Parse(accountIdStr)
79
  if err != nil {
80
  ResponseJSON[any, any](ctx, nil, nil, http_error.UNAUTHORIZED)
81
  return
82
  }
83
+
84
  limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "10"))
85
  page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
86
+ isModified := false
87
+
88
+ if limit < 1 {
89
+ limit = 10
90
+ isModified = true
91
+ } else if limit > 50 {
92
+ limit = 50
93
+ isModified = true
94
+ }
95
+
96
  if page < 1 {
97
  page = 1
98
+ isModified = true
99
  }
100
+
101
  offset := (page - 1) * limit
102
  p := repositories.Pagination{Limit: limit, Offset: offset}
103
+ list, total, err := c.academyService.ListAcademy(ctx.Request.Context(), accountId, p)
104
+
105
+ if err != nil {
106
+ ResponseJSON[any, any](ctx, nil, nil, err)
107
+ return
108
+ }
109
+
110
+ var totalPages int
111
+ if total == 0 {
112
+ totalPages = 1
113
+ } else {
114
+ totalPages = int((total + int64(limit) - 1) / int64(limit))
115
+ }
116
+
117
+ if page > totalPages {
118
+ page = totalPages
119
+ offset = (page - 1) * limit
120
+ p.Offset = offset
121
+ list, total, err = c.academyService.ListAcademy(ctx.Request.Context(), accountId, p)
122
+ isModified = true
123
+ }
124
+
125
  meta := gin.H{
126
  "total_records": total,
127
  "page_size": limit,
128
  "current_page": page,
129
+ "is_modified": isModified,
130
  }
131
+
132
+ if isModified {
133
+ ctx.Status(http.StatusAccepted)
134
+ }
135
+
136
  ResponseJSON(ctx, meta, list, err)
137
  }
138
 
models/dto/academy_dto.go CHANGED
@@ -44,6 +44,17 @@ type AcademyProgressResponse struct {
44
  CompletedAt *string `json:"completed_at"`
45
  }
46
 
 
 
 
 
 
 
 
 
 
 
 
47
  type AcademyContentResponse struct {
48
  Id uuid.UUID `json:"id"`
49
  Order uint `json:"order"`
@@ -64,6 +75,26 @@ type AcademyMaterialResponse struct {
64
  Contents []AcademyContentResponse `json:"contents"`
65
  }
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  type AcademyDetailResponse struct {
68
  Id uuid.UUID `json:"id"`
69
  Title string `json:"title"`
@@ -77,37 +108,6 @@ type AcademyDetailResponse struct {
77
  RegisterStatus int `json:"register_status" binding:"required"`
78
  }
79
 
80
- type MaterialPreview struct {
81
- Id uuid.UUID `json:"id"`
82
- Title string `json:"title"`
83
- Order uint `json:"order"`
84
- }
85
-
86
- type AcademyPublicPreviewResponse struct {
87
- Id uuid.UUID `json:"id"`
88
- Title string `json:"title"`
89
- RegisterStatus int `json:"register_status" binding:"required"`
90
- Materials []MaterialPreview `json:"materials"`
91
- }
92
-
93
- type MaterialProgressResponse struct {
94
- Id uuid.UUID `json:"id"`
95
- AccountId uuid.UUID `json:"account_id"`
96
- AcademyId uuid.UUID `json:"academy_id"`
97
- MaterialId uuid.UUID `json:"material_id"`
98
- Progress float64 `json:"progress"`
99
- TotalCompletedContents uint `json:"total_completed_contents"`
100
- Status string `json:"status"`
101
- CompletedAt *string `json:"completed_at"`
102
- }
103
-
104
- type ContentDetailResponse struct {
105
- Id uuid.UUID `json:"id"`
106
- Order uint `json:"order"`
107
- Title string `json:"title"`
108
- Status string `json:"status"`
109
- }
110
-
111
  type MaterialDetailResponse struct {
112
  Id uuid.UUID `json:"id"`
113
  AcademyId uuid.UUID `json:"academy_id"`
@@ -121,6 +121,13 @@ type MaterialDetailResponse struct {
121
  Meta map[string]string `json:"meta"`
122
  }
123
 
 
 
 
 
 
 
 
124
  type AssignRequest struct {
125
  AcademyId string `json:"academy_id"`
126
  AccountId string `json:"account_id"`
 
44
  CompletedAt *string `json:"completed_at"`
45
  }
46
 
47
+ type MaterialProgressResponse struct {
48
+ Id uuid.UUID `json:"id"`
49
+ AccountId uuid.UUID `json:"account_id"`
50
+ AcademyId uuid.UUID `json:"academy_id"`
51
+ MaterialId uuid.UUID `json:"material_id"`
52
+ Progress float64 `json:"progress"`
53
+ TotalCompletedContents uint `json:"total_completed_contents"`
54
+ Status string `json:"status"`
55
+ CompletedAt *string `json:"completed_at"`
56
+ }
57
+
58
  type AcademyContentResponse struct {
59
  Id uuid.UUID `json:"id"`
60
  Order uint `json:"order"`
 
75
  Contents []AcademyContentResponse `json:"contents"`
76
  }
77
 
78
+ // PREVIEW DTOs
79
+
80
+ type AcademyPublicPreviewResponse struct {
81
+ Id uuid.UUID `json:"id"`
82
+ Title string `json:"title"`
83
+ Description string `json:"description"`
84
+ ImageUrl string `json:"image_url"`
85
+ Code string `json:"code"`
86
+ RegisterStatus int `json:"register_status" binding:"required"`
87
+ Materials []MaterialPreview `json:"materials"`
88
+ }
89
+
90
+ type MaterialPreview struct {
91
+ Id uuid.UUID `json:"id"`
92
+ Title string `json:"title"`
93
+ Order uint `json:"order"`
94
+ }
95
+
96
+ // Detail Response
97
+
98
  type AcademyDetailResponse struct {
99
  Id uuid.UUID `json:"id"`
100
  Title string `json:"title"`
 
108
  RegisterStatus int `json:"register_status" binding:"required"`
109
  }
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  type MaterialDetailResponse struct {
112
  Id uuid.UUID `json:"id"`
113
  AcademyId uuid.UUID `json:"academy_id"`
 
121
  Meta map[string]string `json:"meta"`
122
  }
123
 
124
+ type ContentDetailResponse struct {
125
+ Id uuid.UUID `json:"id"`
126
+ Order uint `json:"order"`
127
+ Title string `json:"title"`
128
+ Status string `json:"status"`
129
+ }
130
+
131
  type AssignRequest struct {
132
  AcademyId string `json:"academy_id"`
133
  AccountId string `json:"account_id"`
models/entity/entity.go CHANGED
@@ -300,7 +300,7 @@ func (AcademyContent) TableName() string { return "academy_contents" }
300
  // Progress
301
 
302
  type AcademyProgress struct {
303
- Id uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"`
304
  AccountId uuid.UUID `gorm:"type:uuid;uniqueIndex:idx_account_academy" json:"account_id,omitempty"`
305
  AcademyId uuid.UUID `gorm:"type:uuid;uniqueIndex:idx_account_academy" json:"academy_id,omitempty"`
306
  Status string `gorm:"type:varchar(50);default:'not attempted'" json:"status,omitempty"`
@@ -312,7 +312,7 @@ type AcademyProgress struct {
312
  func (AcademyProgress) TableName() string { return "academy_progress" }
313
 
314
  type AcademyMaterialProgress struct {
315
- Id uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"`
316
  AccountId uuid.UUID `gorm:"type:uuid;uniqueIndex:idx_account_material" json:"account_id,omitempty"`
317
  AcademyId uuid.UUID `gorm:"type:uuid;index" json:"academy_id,omitempty"`
318
  MaterialId uuid.UUID `gorm:"type:uuid;uniqueIndex:idx_account_material" json:"material_id,omitempty"`
@@ -325,7 +325,7 @@ type AcademyMaterialProgress struct {
325
  func (AcademyMaterialProgress) TableName() string { return "academy_material_progress" }
326
 
327
  type AcademyContentProgress struct {
328
- Id uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"`
329
  AccountId uuid.UUID `gorm:"type:uuid;uniqueIndex:idx_account_content" json:"account_id,omitempty"`
330
  AcademyId uuid.UUID `gorm:"type:uuid;index" json:"academy_id,omitempty"`
331
  MaterialId uuid.UUID `gorm:"type:uuid;index" json:"material_id,omitempty"`
 
300
  // Progress
301
 
302
  type AcademyProgress struct {
303
+ Id uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id,omitempty"`
304
  AccountId uuid.UUID `gorm:"type:uuid;uniqueIndex:idx_account_academy" json:"account_id,omitempty"`
305
  AcademyId uuid.UUID `gorm:"type:uuid;uniqueIndex:idx_account_academy" json:"academy_id,omitempty"`
306
  Status string `gorm:"type:varchar(50);default:'not attempted'" json:"status,omitempty"`
 
312
  func (AcademyProgress) TableName() string { return "academy_progress" }
313
 
314
  type AcademyMaterialProgress struct {
315
+ Id uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id,omitempty"`
316
  AccountId uuid.UUID `gorm:"type:uuid;uniqueIndex:idx_account_material" json:"account_id,omitempty"`
317
  AcademyId uuid.UUID `gorm:"type:uuid;index" json:"academy_id,omitempty"`
318
  MaterialId uuid.UUID `gorm:"type:uuid;uniqueIndex:idx_account_material" json:"material_id,omitempty"`
 
325
  func (AcademyMaterialProgress) TableName() string { return "academy_material_progress" }
326
 
327
  type AcademyContentProgress struct {
328
+ Id uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id,omitempty"`
329
  AccountId uuid.UUID `gorm:"type:uuid;uniqueIndex:idx_account_content" json:"account_id,omitempty"`
330
  AcademyId uuid.UUID `gorm:"type:uuid;index" json:"academy_id,omitempty"`
331
  MaterialId uuid.UUID `gorm:"type:uuid;index" json:"material_id,omitempty"`
repositories/academy_repository.go CHANGED
@@ -384,7 +384,7 @@ func (r *academyRepository) GetContentProgress(ctx context.Context, accountId uu
384
  AcademyId: academyId,
385
  MaterialId: materialId,
386
  ContentId: contentId,
387
- Status: entity.StatusNotStarted,
388
  }, nil
389
  }
390
  return existing, err
 
384
  AcademyId: academyId,
385
  MaterialId: materialId,
386
  ContentId: contentId,
387
+ Status: entity.StatusInProgress,
388
  }, nil
389
  }
390
  return existing, err
router/academy_router.go CHANGED
@@ -12,25 +12,26 @@ func AcademyRouter(router *gin.Engine, middleware provider.MiddlewareProvider, c
12
  routerGroup := router.Group("/api/v1/academy")
13
 
14
  routerGroup.Use(gzip.Gzip(gzip.DefaultCompression))
15
-
16
- adminGroup := routerGroup.Group("/admin", authenticationMiddleware.VerifyAccount)
17
-
18
- adminGroup.POST("/", academyController.CreateAcademy)
19
- adminGroup.GET("/id/:id/detail", academyController.GetAcademyDetail)
20
- adminGroup.PUT("/id/:id", academyController.UpdateAcademy)
21
- adminGroup.DELETE("/id/:id", academyController.DeleteAcademy)
22
-
23
- adminGroup.POST("/materials", academyController.CreateMaterial)
24
- adminGroup.DELETE("/materials/:id", academyController.DeleteMaterial)
25
-
26
- adminGroup.POST("/contents", academyController.CreateContent)
27
- adminGroup.DELETE("/contents/:id", academyController.DeleteContent)
28
-
29
- adminGroup.POST("/assign", academyController.AssignAccountToAcademy)
30
- adminGroup.DELETE("/assign/:id", academyController.UnassignAccountFromAcademy)
31
- adminGroup.GET("/assign/:academy_id", academyController.ListAssignmentsByAcademy)
32
-
33
- routerGroup.GET("/", authenticationMiddleware.VerifyAccount, academyController.ListAcademies)
 
34
  routerGroup.POST("/join", authenticationMiddleware.VerifyAccount, academyController.JoinAcademyByCode)
35
  routerGroup.GET("/:academy_slug", authenticationMiddleware.VerifyAccount, academyController.GetAcademy)
36
  routerGroup.GET("/:academy_slug/:material_slug", authenticationMiddleware.VerifyAccount, academyController.GetMaterial)
 
12
  routerGroup := router.Group("/api/v1/academy")
13
 
14
  routerGroup.Use(gzip.Gzip(gzip.DefaultCompression))
15
+ {
16
+ adminGroup := routerGroup.Group("/admin", authenticationMiddleware.VerifyAccount)
17
+
18
+ adminGroup.POST("/", academyController.CreateAcademy)
19
+ adminGroup.GET("/id/:id/detail", academyController.GetAcademyDetail)
20
+ adminGroup.PUT("/id/:id", academyController.UpdateAcademy)
21
+ adminGroup.DELETE("/id/:id", academyController.DeleteAcademy)
22
+
23
+ adminGroup.POST("/materials", academyController.CreateMaterial)
24
+ adminGroup.DELETE("/materials/:id", academyController.DeleteMaterial)
25
+
26
+ adminGroup.POST("/contents", academyController.CreateContent)
27
+ adminGroup.DELETE("/contents/:id", academyController.DeleteContent)
28
+
29
+ adminGroup.POST("/assign", academyController.AssignAccountToAcademy)
30
+ adminGroup.DELETE("/assign/:id", academyController.UnassignAccountFromAcademy)
31
+ adminGroup.GET("/assign/:academy_id", academyController.ListAssignmentsByAcademy)
32
+ }
33
+
34
+ routerGroup.GET("/", authenticationMiddleware.VerifyAccount, academyController.ListAcademy)
35
  routerGroup.POST("/join", authenticationMiddleware.VerifyAccount, academyController.JoinAcademyByCode)
36
  routerGroup.GET("/:academy_slug", authenticationMiddleware.VerifyAccount, academyController.GetAcademy)
37
  routerGroup.GET("/:academy_slug/:material_slug", authenticationMiddleware.VerifyAccount, academyController.GetMaterial)
services/academy_service.go CHANGED
@@ -22,7 +22,7 @@ type AcademyService interface {
22
  CreateAcademy(ctx context.Context, req dto.CreateAcademyRequest) (entity.Academy, error)
23
  UpdateAcademy(ctx context.Context, id uuid.UUID, req dto.UpdateAcademyRequest) (entity.Academy, error)
24
  DeleteAcademy(ctx context.Context, id uuid.UUID) error
25
- ListAcademies(ctx context.Context, accountId uuid.UUID, p repositories.Pagination) ([]entity.Academy, int64, error)
26
 
27
  CreateMaterial(ctx context.Context, req dto.CreateMaterialRequest) (entity.AcademyMaterial, error)
28
  GetMaterial(ctx context.Context, accountId uuid.UUID, academySlug string, materialSlug string) (entity.AcademyMaterial, error)
@@ -145,7 +145,7 @@ func (s *academyService) DeleteAcademy(ctx context.Context, id uuid.UUID) error
145
  return s.academyRepo.DeleteAcademy(ctx, id)
146
  }
147
 
148
- func (s *academyService) ListAcademies(ctx context.Context, accountId uuid.UUID, p repositories.Pagination) ([]entity.Academy, int64, error) {
149
  list, total, err := s.academyRepo.ListVisibleAcademy(ctx, accountId, &p)
150
  return list, total, err
151
  }
@@ -321,6 +321,55 @@ func (s *academyService) GetContent(ctx context.Context, accountId uuid.UUID, ac
321
  if err != nil {
322
  return entity.AcademyContent{}, err
323
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  return s.academyRepo.GetContentWithProgress(ctx, accountId, material.AcademyId, material.Id, order)
325
  }
326
 
@@ -515,9 +564,21 @@ func (s *academyService) GetAcademyResponse(ctx context.Context, accountId uuid.
515
  }
516
  previews := make([]dto.MaterialPreview, len(mats))
517
  for i, m := range mats {
518
- previews[i] = dto.MaterialPreview{Id: m.Id, Title: m.Title, Order: m.Order}
 
 
 
 
 
 
 
 
 
 
 
 
 
519
  }
520
- res := dto.AcademyPublicPreviewResponse{Id: academy.Id, Title: academy.Title, RegisterStatus: 0, Materials: previews}
521
  return res, nil
522
  }
523
  return nil, http_error.UNAUTHORIZED
 
22
  CreateAcademy(ctx context.Context, req dto.CreateAcademyRequest) (entity.Academy, error)
23
  UpdateAcademy(ctx context.Context, id uuid.UUID, req dto.UpdateAcademyRequest) (entity.Academy, error)
24
  DeleteAcademy(ctx context.Context, id uuid.UUID) error
25
+ ListAcademy(ctx context.Context, accountId uuid.UUID, p repositories.Pagination) ([]entity.Academy, int64, error)
26
 
27
  CreateMaterial(ctx context.Context, req dto.CreateMaterialRequest) (entity.AcademyMaterial, error)
28
  GetMaterial(ctx context.Context, accountId uuid.UUID, academySlug string, materialSlug string) (entity.AcademyMaterial, error)
 
145
  return s.academyRepo.DeleteAcademy(ctx, id)
146
  }
147
 
148
+ func (s *academyService) ListAcademy(ctx context.Context, accountId uuid.UUID, p repositories.Pagination) ([]entity.Academy, int64, error) {
149
  list, total, err := s.academyRepo.ListVisibleAcademy(ctx, accountId, &p)
150
  return list, total, err
151
  }
 
321
  if err != nil {
322
  return entity.AcademyContent{}, err
323
  }
324
+
325
+ if err := s.academyRepo.Atomic(ctx, func(txRepo repositories.AcademyRepository) error {
326
+ existingAMP, _ := txRepo.GetMaterialProgress(ctx, accountId, material.AcademyId, material.Id)
327
+ ampID := existingAMP.Id
328
+ if ampID == uuid.Nil {
329
+ ampID = uuid.New()
330
+ }
331
+ matStatus := existingAMP.Status
332
+ if matStatus != entity.StatusFinished {
333
+ matStatus = entity.StatusInProgress
334
+ }
335
+ if _, err := txRepo.UpsertMaterialProgress(ctx, entity.AcademyMaterialProgress{
336
+ Id: ampID,
337
+ AccountId: accountId,
338
+ AcademyId: material.AcademyId,
339
+ MaterialId: material.Id,
340
+ Progress: existingAMP.Progress,
341
+ TotalCompletedContents: existingAMP.TotalCompletedContents,
342
+ Status: matStatus,
343
+ CompletedAt: existingAMP.CompletedAt,
344
+ }); err != nil {
345
+ return err
346
+ }
347
+
348
+ existingAP, _ := txRepo.GetAcademyProgress(ctx, accountId, material.AcademyId)
349
+ apID := existingAP.Id
350
+ if apID == uuid.Nil {
351
+ apID = uuid.New()
352
+ }
353
+ acadStatus := existingAP.Status
354
+ if acadStatus != entity.StatusFinished {
355
+ acadStatus = entity.StatusInProgress
356
+ }
357
+ if _, err := txRepo.UpsertAcademyProgress(ctx, entity.AcademyProgress{
358
+ Id: apID,
359
+ AccountId: accountId,
360
+ AcademyId: material.AcademyId,
361
+ Progress: existingAP.Progress,
362
+ TotalCompletedMaterials: existingAP.TotalCompletedMaterials,
363
+ Status: acadStatus,
364
+ CompletedAt: existingAP.CompletedAt,
365
+ }); err != nil {
366
+ return err
367
+ }
368
+ return nil
369
+ }); err != nil {
370
+ return entity.AcademyContent{}, err
371
+ }
372
+
373
  return s.academyRepo.GetContentWithProgress(ctx, accountId, material.AcademyId, material.Id, order)
374
  }
375
 
 
564
  }
565
  previews := make([]dto.MaterialPreview, len(mats))
566
  for i, m := range mats {
567
+ previews[i] = dto.MaterialPreview{
568
+ Id: m.Id,
569
+ Title: m.Title,
570
+ Order: m.Order,
571
+ }
572
+ }
573
+ res := dto.AcademyPublicPreviewResponse{
574
+ Id: academy.Id,
575
+ Title: academy.Title,
576
+ Description: academy.Description,
577
+ ImageUrl: academy.ImageUrl,
578
+ Code: academy.Code,
579
+ RegisterStatus: 0,
580
+ Materials: previews,
581
  }
 
582
  return res, nil
583
  }
584
  return nil, http_error.UNAUTHORIZED