lifedebugger commited on
Commit
4f90512
·
1 Parent(s): aa83ff3

Deploy files from GitHub repository

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. models/request_model.go +17 -17
  2. repositories/academy_repository.go +11 -7
  3. space/controller/academy/academy_controller.go +11 -5
  4. space/models/database_orm_model.go +3 -1
  5. space/models/request_model.go +56 -4
  6. space/repositories/academy_repository.go +21 -13
  7. space/repositories/quiz_repository.go +19 -0
  8. space/router/quiz_route.go +1 -1
  9. space/services/academy_service.go +17 -17
  10. space/services/quiz_service.go +37 -0
  11. space/space/models/field_counter.go +0 -2
  12. space/space/services/partner_criteria_service.go +2 -2
  13. space/space/space/config/database_connection_config.go +0 -2
  14. space/space/space/controller/academy/academy_controller.go +369 -31
  15. space/space/space/controller/cv/cv_controller.go +12 -60
  16. space/space/space/controller/email/email_controller.go +1 -5
  17. space/space/space/controller/marriage_readiness_profile/marriage_readiness_profile_controller.go +2 -6
  18. space/space/space/controller/options/options_controller.go +1 -5
  19. space/space/space/controller/partner_criteria/partner_criteria_controller.go +1 -5
  20. space/space/space/controller/quiz/list_quiz_controller.go +1 -1
  21. space/space/space/main.go +6 -0
  22. space/space/space/models/database_orm_model.go +32 -50
  23. space/space/space/models/filter_and_paging.go +107 -0
  24. space/space/space/models/request_model.go +107 -2
  25. space/space/space/models/response_model.go +0 -13
  26. space/space/space/repositories/academy_repository.go +406 -103
  27. space/space/space/repositories/question_repository.go +11 -0
  28. space/space/space/response/api_response_v2.go +37 -11
  29. space/space/space/router/academy_route.go +25 -8
  30. space/space/space/router/router.go +1 -1
  31. space/space/space/router/server.go +4 -0
  32. space/space/space/services/academy_quiz_service.go +2 -2
  33. space/space/space/services/academy_service.go +316 -150
  34. space/space/space/space/space/assets/efs.go +8 -0
  35. space/space/space/space/space/assets/seeds/city.json +4114 -0
  36. space/space/space/space/space/assets/seeds/province.json +192 -0
  37. space/space/space/space/space/controller/options/options_controller.go +72 -0
  38. space/space/space/space/space/controller/region/region_controller.go +92 -0
  39. space/space/space/space/space/main.go +13 -0
  40. space/space/space/space/space/models/database_orm_model.go +8 -5
  41. space/space/space/space/space/models/request_model.go +33 -4
  42. space/space/space/space/space/models/response_model.go +2 -0
  43. space/space/space/space/space/repositories/option_repository.go +82 -36
  44. space/space/space/space/space/repositories/region_repository.go +41 -31
  45. space/space/space/space/space/router/options_route.go +11 -15
  46. space/space/space/space/space/router/router.go +1 -1
  47. space/space/space/space/space/router/server.go +9 -2
  48. space/space/space/space/space/services/options_service.go +149 -0
  49. space/space/space/space/space/services/region_service.go +41 -63
  50. space/space/space/space/space/space/services/academy_quiz_navigation_service.go +1 -1
models/request_model.go CHANGED
@@ -56,26 +56,26 @@ type (
56
  }
57
 
58
  UserAcademyMaterialResponse struct {
59
- ID int64 `gorm:"column:id" json:"id"`
60
- AcademyID int64 `gorm:"column:academy_id" json:"academy_id"`
61
- Title string `gorm:"column:title" json:"title"`
62
- Slug string `gorm:"column:slug" json:"slug"`
63
- Order uint `gorm:"column:order" json:"order"`
64
- CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
65
- UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
66
- IsRead bool `gorm:"is_read" json:"is_read"`
67
  }
68
 
69
  UserAcademyMaterialDetailResponse struct {
70
- ID int64 `gorm:"column:id" json:"id"`
71
- AcademyID int64 `gorm:"column:academy_id" json:"academy_id"`
72
- Title string `gorm:"column:title" json:"title"`
73
- Slug string `gorm:"column:slug" json:"slug"`
74
- Content string `gorm:"column:content" json:"content"`
75
- Order uint `gorm:"column:order" json:"order"`
76
- CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
77
- UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
78
- IsRead bool `gorm:"is_read" json:"is_read"`
79
  }
80
 
81
  CreateAcademyMaterialRequest struct {
 
56
  }
57
 
58
  UserAcademyMaterialResponse struct {
59
+ ID int64 `gorm:"column:id" json:"id"`
60
+ AcademyID int64 `gorm:"column:academy_id" json:"academy_id"`
61
+ Title string `gorm:"column:title" json:"title"`
62
+ Slug string `gorm:"column:slug" json:"slug"`
63
+ Order uint `gorm:"column:order" json:"order"`
64
+ CreatedAt *time.Time `gorm:"column:read_created_at" json:"created_at"`
65
+ UpdatedAt *time.Time `gorm:"column:read_updated_at" json:"updated_at"`
66
+ IsRead bool `gorm:"is_read" json:"is_read"`
67
  }
68
 
69
  UserAcademyMaterialDetailResponse struct {
70
+ ID int64 `gorm:"column:id" json:"id"`
71
+ AcademyID int64 `gorm:"column:academy_id" json:"academy_id"`
72
+ Title string `gorm:"column:title" json:"title"`
73
+ Slug string `gorm:"column:slug" json:"slug"`
74
+ Content string `gorm:"column:content" json:"content"`
75
+ Order uint `gorm:"column:order" json:"order"`
76
+ CreatedAt *time.Time `gorm:"column:read_created_at" json:"created_at"`
77
+ UpdatedAt *time.Time `gorm:"column:read_updated_at" json:"updated_at"`
78
+ IsRead bool `gorm:"is_read" json:"is_read"`
79
  }
80
 
81
  CreateAcademyMaterialRequest struct {
repositories/academy_repository.go CHANGED
@@ -294,14 +294,16 @@ func (r *academyRepository) UserListAcademyMaterial(ctx context.Context, req *mo
294
  q := r.db.WithContext(ctx).
295
  Model(&models.AcademyMaterial{}).
296
  Select(`academy_materials.*,
297
- CASE
298
- WHEN amp.created_at IS NOT NULL THEN true
299
- ELSE false
300
- END AS is_read`).
 
 
301
  Joins("JOIN academy ON academy.id = academy_materials.academy_id").
302
  Joins(`LEFT JOIN academy_materials_progress amp ON
303
- amp.academy_material_id = academy_materials.id AND
304
- amp.account_id = ?`, req.AccountID).
305
  Where("academy.slug = ?", req.Slug)
306
 
307
  if req.Filter.HasKeyword() {
@@ -371,7 +373,9 @@ func (r *academyRepository) UserGetAcademyMaterialBySlug(ctx context.Context, sl
371
  CASE
372
  WHEN amp.created_at IS NOT NULL THEN true
373
  ELSE false
374
- END AS is_read
 
 
375
  `).
376
  Joins("JOIN academy ON academy.id = academy_materials.academy_id").
377
  Joins(`LEFT JOIN academy_materials_progress amp ON
 
294
  q := r.db.WithContext(ctx).
295
  Model(&models.AcademyMaterial{}).
296
  Select(`academy_materials.*,
297
+ amp.created_at AS read_created_at,
298
+ amp.updated_at AS read_updated_at,
299
+ CASE
300
+ WHEN amp.created_at IS NOT NULL THEN true
301
+ ELSE false
302
+ END AS is_read`).
303
  Joins("JOIN academy ON academy.id = academy_materials.academy_id").
304
  Joins(`LEFT JOIN academy_materials_progress amp ON
305
+ amp.academy_material_id = academy_materials.id AND
306
+ amp.account_id = ?`, req.AccountID).
307
  Where("academy.slug = ?", req.Slug)
308
 
309
  if req.Filter.HasKeyword() {
 
373
  CASE
374
  WHEN amp.created_at IS NOT NULL THEN true
375
  ELSE false
376
+ END AS is_read,
377
+ amp.created_at AS read_created_at,
378
+ amp.updated_at AS read_updated_at
379
  `).
380
  Joins("JOIN academy ON academy.id = academy_materials.academy_id").
381
  Joins(`LEFT JOIN academy_materials_progress amp ON
space/controller/academy/academy_controller.go CHANGED
@@ -349,15 +349,21 @@ func (c *academyController) UserGetAcademyMaterialBySlug(ctx *gin.Context) {
349
  }
350
 
351
  func (c *academyController) UserToggleAcademyMaterialProgress(ctx *gin.Context) {
352
- slug := ctx.Param("materialSlug")
 
 
 
 
 
 
 
353
 
354
  accountData := middleware.GetAccountData(ctx)
355
  accountID := int64(accountData.UserID)
356
 
357
- req := models.ToggleAcademyMaterialProgressRequest{
358
- AccountID: accountID,
359
- Slug: slug,
360
- }
361
 
362
  err := c.academyService.UserToggleAcademyMaterialProgress(ctx, &req)
363
  if err != nil {
 
349
  }
350
 
351
  func (c *academyController) UserToggleAcademyMaterialProgress(ctx *gin.Context) {
352
+ var req models.ToggleAcademyMaterialProgressRequest
353
+ if err := ctx.ShouldBindJSON(&req); err != nil {
354
+ response.HandleError(ctx, err)
355
+ return
356
+ }
357
+
358
+ academySlug := ctx.Param("slug")
359
+ materialSlug := ctx.Param("materialSlug")
360
 
361
  accountData := middleware.GetAccountData(ctx)
362
  accountID := int64(accountData.UserID)
363
 
364
+ req.AccountID = accountID
365
+ req.AcademySlug = academySlug
366
+ req.MaterialSlug = materialSlug
 
367
 
368
  err := c.academyService.UserToggleAcademyMaterialProgress(ctx, &req)
369
  if err != nil {
space/models/database_orm_model.go CHANGED
@@ -80,6 +80,7 @@ type (
80
  Slug string `gorm:"column:slug;uniqueIndex" json:"slug"`
81
  Description string `gorm:"column:description" json:"description"`
82
  Order uint `gorm:"column:order" json:"order"`
 
83
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
84
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
85
  }
@@ -101,7 +102,8 @@ type (
101
  Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
102
  AcademyMaterialID int64 `gorm:"primaryKey;column:academy_material_id" json:"academy_material_id"`
103
  AcademyMaterial *AcademyMaterial `gorm:"foreignKey:AcademyMaterialID;constraint:OnDelete:CASCADE" json:"academy_material,omitempty"`
104
- ReadAt *time.Time `gorm:"column:read_at" json:"read_at"`
 
105
  }
106
  )
107
 
 
80
  Slug string `gorm:"column:slug;uniqueIndex" json:"slug"`
81
  Description string `gorm:"column:description" json:"description"`
82
  Order uint `gorm:"column:order" json:"order"`
83
+ Image *string `gorm:"column:image" json:"image"`
84
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
85
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
86
  }
 
102
  Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
103
  AcademyMaterialID int64 `gorm:"primaryKey;column:academy_material_id" json:"academy_material_id"`
104
  AcademyMaterial *AcademyMaterial `gorm:"foreignKey:AcademyMaterialID;constraint:OnDelete:CASCADE" json:"academy_material,omitempty"`
105
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
106
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
107
  }
108
  )
109
 
space/models/request_model.go CHANGED
@@ -56,8 +56,26 @@ type (
56
  }
57
 
58
  UserAcademyMaterialResponse struct {
59
- AcademyMaterial
60
- IsRead bool `gorm:"is_read" json:"is_read"`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
62
 
63
  CreateAcademyMaterialRequest struct {
@@ -108,8 +126,10 @@ type (
108
  }
109
 
110
  ToggleAcademyMaterialProgressRequest struct {
111
- AccountID int64 `json:"account_id"`
112
- Slug string `json:"slug"`
 
 
113
  }
114
  )
115
 
@@ -131,6 +151,38 @@ func NewListAcademyContentRequest() ListAcademyContentRequest {
131
  }
132
  }
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  type (
135
  MultipleOptionsRequest struct {
136
  OptionName string `json:"option_name" validate:"required"`
 
56
  }
57
 
58
  UserAcademyMaterialResponse struct {
59
+ ID int64 `gorm:"column:id" json:"id"`
60
+ AcademyID int64 `gorm:"column:academy_id" json:"academy_id"`
61
+ Title string `gorm:"column:title" json:"title"`
62
+ Slug string `gorm:"column:slug" json:"slug"`
63
+ Order uint `gorm:"column:order" json:"order"`
64
+ CreatedAt *time.Time `gorm:"column:read_created_at" json:"created_at"`
65
+ UpdatedAt *time.Time `gorm:"column:read_updated_at" json:"updated_at"`
66
+ IsRead bool `gorm:"is_read" json:"is_read"`
67
+ }
68
+
69
+ UserAcademyMaterialDetailResponse struct {
70
+ ID int64 `gorm:"column:id" json:"id"`
71
+ AcademyID int64 `gorm:"column:academy_id" json:"academy_id"`
72
+ Title string `gorm:"column:title" json:"title"`
73
+ Slug string `gorm:"column:slug" json:"slug"`
74
+ Content string `gorm:"column:content" json:"content"`
75
+ Order uint `gorm:"column:order" json:"order"`
76
+ CreatedAt *time.Time `gorm:"column:read_created_at" json:"created_at"`
77
+ UpdatedAt *time.Time `gorm:"column:read_updated_at" json:"updated_at"`
78
+ IsRead bool `gorm:"is_read" json:"is_read"`
79
  }
80
 
81
  CreateAcademyMaterialRequest struct {
 
126
  }
127
 
128
  ToggleAcademyMaterialProgressRequest struct {
129
+ AccountID int64 `json:"-"`
130
+ MaterialSlug string `json:"-"`
131
+ AcademySlug string `json:"-"`
132
+ IsRead bool `json:"is_read"`
133
  }
134
  )
135
 
 
151
  }
152
  }
153
 
154
+ type (
155
+ ListQuizRequest struct {
156
+ AccountID int64 `json:"account_id"`
157
+ AcademyID int64 `json:"academy_id" validate:"required"`
158
+ }
159
+
160
+ ListQuizResponse struct {
161
+ Quiz []Quiz `json:"quiz"`
162
+ }
163
+
164
+ AttemptQuizRequest struct {
165
+ AccountID int64 `json:"account_id"`
166
+ QuizID int64 `json:"quiz_id" validate:"required"`
167
+ }
168
+
169
+ AttemptQuizResponse struct {
170
+ QuizAttempt QuizAttempt `json:"quiz_attempt"`
171
+ }
172
+
173
+ GetQuestionQuizRequest struct {
174
+ AccountID int64 `json:"account_id"`
175
+ QuizID int64 `json:"quiz_id" validate:"required"`
176
+ }
177
+
178
+ GetQuestionQuizResponse struct {
179
+ Question Question `json:"question"`
180
+ Answer []Answer `json:"answer_options"`
181
+ UserAnswer int `json:"current_user_answer"`
182
+ IsDoubt bool `json:"is_doubt"`
183
+ }
184
+ )
185
+
186
  type (
187
  MultipleOptionsRequest struct {
188
  OptionName string `json:"option_name" validate:"required"`
space/repositories/academy_repository.go CHANGED
@@ -28,7 +28,7 @@ type AcademyRepository interface {
28
  UserListAcademy(ctx context.Context, req *models.ListAcademyRequest) ([]models.UserAcademyResponse, *models.Paging, error)
29
  UserListAcademyMaterial(ctx context.Context, req *models.ListAcademyMaterialRequest) ([]models.UserAcademyMaterialResponse, *models.Paging, error)
30
  UserGetAcademyBySlug(ctx context.Context, slug string) (*models.AcademyResponse, error)
31
- UserGetAcademyMaterialBySlug(ctx context.Context, slug string, accountID int64) (*models.UserAcademyMaterialResponse, error)
32
  UserSaveAcademyMaterialProgress(ctx context.Context, req *models.AcademyMaterialProgress) error
33
  UserGetAcademyMaterialProgressByAccountID(ctx context.Context, accountID int64, materialID int64) (*models.AcademyMaterialProgress, error)
34
  UserDeleteAcademyMaterialProgress(ctx context.Context, accountID int64, materialID int64) error
@@ -245,7 +245,7 @@ func (r *academyRepository) UserListAcademy(ctx context.Context, req *models.Lis
245
  Model(&models.Academy{}).
246
  Select(`academy.*,
247
  COUNT(DISTINCT am.id) AS total_material,
248
- COUNT(DISTINCT CASE WHEN amp.read_at IS NOT NULL THEN amp.academy_material_id END) AS total_read_material`).
249
  Joins("LEFT JOIN academy_materials am ON am.academy_id = academy.id").
250
  Joins(`LEFT JOIN "academy_materials_progress" amp ON amp.academy_material_id = am.id AND amp.account_id = ?`, req.AccountID).
251
  Group("academy.id")
@@ -294,14 +294,16 @@ func (r *academyRepository) UserListAcademyMaterial(ctx context.Context, req *mo
294
  q := r.db.WithContext(ctx).
295
  Model(&models.AcademyMaterial{}).
296
  Select(`academy_materials.*,
297
- CASE
298
- WHEN amp.read_at IS NOT NULL THEN true
299
- ELSE false
300
- END AS is_read`).
 
 
301
  Joins("JOIN academy ON academy.id = academy_materials.academy_id").
302
  Joins(`LEFT JOIN academy_materials_progress amp ON
303
- amp.academy_material_id = academy_materials.id AND
304
- amp.account_id = ?`, req.AccountID).
305
  Where("academy.slug = ?", req.Slug)
306
 
307
  if req.Filter.HasKeyword() {
@@ -359,8 +361,8 @@ func (r *academyRepository) UserGetAcademyBySlug(ctx context.Context, slug strin
359
  return &academy, nil
360
  }
361
 
362
- func (r *academyRepository) UserGetAcademyMaterialBySlug(ctx context.Context, slug string, accountID int64) (*models.UserAcademyMaterialResponse, error) {
363
- var result models.UserAcademyMaterialResponse
364
 
365
  err := r.db.WithContext(ctx).
366
  Model(&models.AcademyMaterial{}).
@@ -369,22 +371,28 @@ func (r *academyRepository) UserGetAcademyMaterialBySlug(ctx context.Context, sl
369
  academy.title as academy_title,
370
  academy.slug as academy_slug,
371
  CASE
372
- WHEN amp.read_at IS NOT NULL THEN true
373
  ELSE false
374
- END AS is_read
 
 
375
  `).
376
  Joins("JOIN academy ON academy.id = academy_materials.academy_id").
377
  Joins(`LEFT JOIN academy_materials_progress amp ON
378
  amp.academy_material_id = academy_materials.id AND
379
  amp.account_id = ?`, accountID).
380
  Where("academy_materials.slug = ?", slug).
381
- First(&result).
382
  Error
383
 
384
  if err != nil {
385
  return nil, err
386
  }
387
 
 
 
 
 
388
  return &result, nil
389
  }
390
 
 
28
  UserListAcademy(ctx context.Context, req *models.ListAcademyRequest) ([]models.UserAcademyResponse, *models.Paging, error)
29
  UserListAcademyMaterial(ctx context.Context, req *models.ListAcademyMaterialRequest) ([]models.UserAcademyMaterialResponse, *models.Paging, error)
30
  UserGetAcademyBySlug(ctx context.Context, slug string) (*models.AcademyResponse, error)
31
+ UserGetAcademyMaterialBySlug(ctx context.Context, slug string, accountID int64) (*models.UserAcademyMaterialDetailResponse, error)
32
  UserSaveAcademyMaterialProgress(ctx context.Context, req *models.AcademyMaterialProgress) error
33
  UserGetAcademyMaterialProgressByAccountID(ctx context.Context, accountID int64, materialID int64) (*models.AcademyMaterialProgress, error)
34
  UserDeleteAcademyMaterialProgress(ctx context.Context, accountID int64, materialID int64) error
 
245
  Model(&models.Academy{}).
246
  Select(`academy.*,
247
  COUNT(DISTINCT am.id) AS total_material,
248
+ COUNT(DISTINCT CASE WHEN amp.created_at IS NOT NULL THEN amp.academy_material_id END) AS total_read_material`).
249
  Joins("LEFT JOIN academy_materials am ON am.academy_id = academy.id").
250
  Joins(`LEFT JOIN "academy_materials_progress" amp ON amp.academy_material_id = am.id AND amp.account_id = ?`, req.AccountID).
251
  Group("academy.id")
 
294
  q := r.db.WithContext(ctx).
295
  Model(&models.AcademyMaterial{}).
296
  Select(`academy_materials.*,
297
+ amp.created_at AS read_created_at,
298
+ amp.updated_at AS read_updated_at,
299
+ CASE
300
+ WHEN amp.created_at IS NOT NULL THEN true
301
+ ELSE false
302
+ END AS is_read`).
303
  Joins("JOIN academy ON academy.id = academy_materials.academy_id").
304
  Joins(`LEFT JOIN academy_materials_progress amp ON
305
+ amp.academy_material_id = academy_materials.id AND
306
+ amp.account_id = ?`, req.AccountID).
307
  Where("academy.slug = ?", req.Slug)
308
 
309
  if req.Filter.HasKeyword() {
 
361
  return &academy, nil
362
  }
363
 
364
+ func (r *academyRepository) UserGetAcademyMaterialBySlug(ctx context.Context, slug string, accountID int64) (*models.UserAcademyMaterialDetailResponse, error) {
365
+ var result models.UserAcademyMaterialDetailResponse
366
 
367
  err := r.db.WithContext(ctx).
368
  Model(&models.AcademyMaterial{}).
 
371
  academy.title as academy_title,
372
  academy.slug as academy_slug,
373
  CASE
374
+ WHEN amp.created_at IS NOT NULL THEN true
375
  ELSE false
376
+ END AS is_read,
377
+ amp.created_at AS read_created_at,
378
+ amp.updated_at AS read_updated_at
379
  `).
380
  Joins("JOIN academy ON academy.id = academy_materials.academy_id").
381
  Joins(`LEFT JOIN academy_materials_progress amp ON
382
  amp.academy_material_id = academy_materials.id AND
383
  amp.account_id = ?`, accountID).
384
  Where("academy_materials.slug = ?", slug).
385
+ Scan(&result).
386
  Error
387
 
388
  if err != nil {
389
  return nil, err
390
  }
391
 
392
+ if result.ID == 0 {
393
+ return nil, gorm.ErrRecordNotFound
394
+ }
395
+
396
  return &result, nil
397
  }
398
 
space/repositories/quiz_repository.go CHANGED
@@ -1,9 +1,28 @@
1
  package repositories
2
 
3
  import (
 
 
4
  "api.qobiltu.id/models"
 
5
  )
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  func GetQuizbyAcademyId(academyId uint) Repository[models.Quiz, []models.Quiz] {
8
  repo := Construct[models.Quiz, []models.Quiz](
9
  models.Quiz{AcademyID: academyId},
 
1
  package repositories
2
 
3
  import (
4
+ "context"
5
+
6
  "api.qobiltu.id/models"
7
+ "gorm.io/gorm"
8
  )
9
 
10
+ type QuizRepository interface {
11
+ UserListQuiz(ctx context.Context, req *models.ListQuizRequest) (*models.ListQuizResponse, *models.Paging, error)
12
+ }
13
+
14
+ type quizRepository struct {
15
+ db *gorm.DB
16
+ }
17
+
18
+ func NewQuizRepository(db *gorm.DB) QuizRepository {
19
+ return &quizRepository{db: db}
20
+ }
21
+
22
+ func (r *quizRepository) UserListQuiz(ctx context.Context, req *models.ListQuizRequest) (*models.ListQuizResponse, *models.Paging, error) {
23
+ panic("not implemented")
24
+ }
25
+
26
  func GetQuizbyAcademyId(academyId uint) Repository[models.Quiz, []models.Quiz] {
27
  repo := Construct[models.Quiz, []models.Quiz](
28
  models.Quiz{AcademyID: academyId},
space/router/quiz_route.go CHANGED
@@ -15,7 +15,7 @@ func QuizRoute(router *gin.Engine) {
15
  routerGroup.GET("/:academy_id/:quiz_id/review", middleware.AuthUser, QuizController.Review)
16
  routerGroup.PUT("/:academy_id/:quiz_id/choose-answer", middleware.AuthUser, QuizController.Answer)
17
  routerGroup.GET("result/:attempt_id", middleware.AuthUser, QuizController.Result)
18
- routerGroup.GET("result/", middleware.AuthUser, QuizController.Result)
19
  routerGroup.POST("/submit-attempt/:attempt_id", middleware.AuthUser, QuizController.Submit)
20
  routerGroup.GET("/navigation/:attempt_id", middleware.AuthUser, QuizController.Navigation)
21
  }
 
15
  routerGroup.GET("/:academy_id/:quiz_id/review", middleware.AuthUser, QuizController.Review)
16
  routerGroup.PUT("/:academy_id/:quiz_id/choose-answer", middleware.AuthUser, QuizController.Answer)
17
  routerGroup.GET("result/:attempt_id", middleware.AuthUser, QuizController.Result)
18
+ routerGroup.GET("result", middleware.AuthUser, QuizController.Result)
19
  routerGroup.POST("/submit-attempt/:attempt_id", middleware.AuthUser, QuizController.Submit)
20
  routerGroup.GET("/navigation/:attempt_id", middleware.AuthUser, QuizController.Navigation)
21
  }
space/services/academy_service.go CHANGED
@@ -33,7 +33,7 @@ type AcademyService interface {
33
  UserListAcademy(ctx context.Context, req *models.ListAcademyRequest) ([]models.UserAcademyResponse, *models.Paging, error)
34
  UserListAcademyMaterial(ctx context.Context, req *models.ListAcademyMaterialRequest) ([]models.UserAcademyMaterialResponse, *models.Paging, error)
35
  UserGetAcademyBySlug(ctx context.Context, slug string) (*models.AcademyResponse, error)
36
- UserGetAcademyMaterialBySlug(ctx context.Context, slug string, accountID int64) (*models.UserAcademyMaterialResponse, error)
37
  UserToggleAcademyMaterialProgress(ctx context.Context, req *models.ToggleAcademyMaterialProgressRequest) error
38
  }
39
 
@@ -277,7 +277,7 @@ func (s *academyService) UserGetAcademyBySlug(ctx context.Context, slug string)
277
  return academy, nil
278
  }
279
 
280
- func (s *academyService) UserGetAcademyMaterialBySlug(ctx context.Context, slug string, accountID int64) (*models.UserAcademyMaterialResponse, error) {
281
  academyMaterial, err := s.academyRepository.UserGetAcademyMaterialBySlug(ctx, slug, accountID)
282
  if err != nil {
283
  return nil, response.HandleGormError(err, "Internal Server Error")
@@ -287,29 +287,29 @@ func (s *academyService) UserGetAcademyMaterialBySlug(ctx context.Context, slug
287
  }
288
 
289
  func (s *academyService) UserToggleAcademyMaterialProgress(ctx context.Context, req *models.ToggleAcademyMaterialProgressRequest) error {
290
-
291
- academyMaterial, err := s.academyRepository.UserGetAcademyMaterialBySlug(ctx, req.Slug, req.AccountID)
292
  if err != nil {
293
  return response.HandleGormError(err, "Internal Server Error")
294
  }
295
 
296
- if academyMaterial.IsRead {
297
- err = s.academyRepository.UserDeleteAcademyMaterialProgress(ctx, req.AccountID, academyMaterial.ID)
 
298
  if err != nil {
299
  return response.HandleGormError(err, "Internal Server Error")
300
  }
301
- } else {
302
- now := time.Now()
303
- academyMaterialProgress := &models.AcademyMaterialProgress{
304
- AccountID: req.AccountID,
305
- AcademyMaterialID: academyMaterial.ID,
306
- ReadAt: &now,
307
- }
308
 
309
- err = s.academyRepository.UserSaveAcademyMaterialProgress(ctx, academyMaterialProgress)
310
- if err != nil {
311
- return response.HandleGormError(err, "Internal Server Error")
312
- }
 
 
 
 
 
313
  }
314
 
315
  return nil
 
33
  UserListAcademy(ctx context.Context, req *models.ListAcademyRequest) ([]models.UserAcademyResponse, *models.Paging, error)
34
  UserListAcademyMaterial(ctx context.Context, req *models.ListAcademyMaterialRequest) ([]models.UserAcademyMaterialResponse, *models.Paging, error)
35
  UserGetAcademyBySlug(ctx context.Context, slug string) (*models.AcademyResponse, error)
36
+ UserGetAcademyMaterialBySlug(ctx context.Context, slug string, accountID int64) (*models.UserAcademyMaterialDetailResponse, error)
37
  UserToggleAcademyMaterialProgress(ctx context.Context, req *models.ToggleAcademyMaterialProgressRequest) error
38
  }
39
 
 
277
  return academy, nil
278
  }
279
 
280
+ func (s *academyService) UserGetAcademyMaterialBySlug(ctx context.Context, slug string, accountID int64) (*models.UserAcademyMaterialDetailResponse, error) {
281
  academyMaterial, err := s.academyRepository.UserGetAcademyMaterialBySlug(ctx, slug, accountID)
282
  if err != nil {
283
  return nil, response.HandleGormError(err, "Internal Server Error")
 
287
  }
288
 
289
  func (s *academyService) UserToggleAcademyMaterialProgress(ctx context.Context, req *models.ToggleAcademyMaterialProgressRequest) error {
290
+ material, err := s.academyRepository.UserGetAcademyMaterialBySlug(ctx, req.MaterialSlug, req.AccountID)
 
291
  if err != nil {
292
  return response.HandleGormError(err, "Internal Server Error")
293
  }
294
 
295
+ // langsung hapus progress jika isRead adalah false
296
+ if !req.IsRead {
297
+ err := s.academyRepository.UserDeleteAcademyMaterialProgress(ctx, req.AccountID, material.ID)
298
  if err != nil {
299
  return response.HandleGormError(err, "Internal Server Error")
300
  }
301
+ return nil
302
+ }
 
 
 
 
 
303
 
304
+ academyMaterialProgress := models.AcademyMaterialProgress{
305
+ AccountID: req.AccountID,
306
+ AcademyMaterialID: material.ID,
307
+ CreatedAt: time.Now(),
308
+ }
309
+
310
+ err = s.academyRepository.UserSaveAcademyMaterialProgress(ctx, &academyMaterialProgress)
311
+ if err != nil {
312
+ return response.HandleGormError(err, "Internal Server Error")
313
  }
314
 
315
  return nil
space/services/quiz_service.go ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package services
2
+
3
+ // type QuizService interface {
4
+ // // === ADMIN ===
5
+
6
+ // // === USER ===
7
+ // UserListQuiz(ctx context.Context, req *models.ListQuizRequest) (*models.ListQuizResponse, *models.Paging, error)
8
+ // UserAttemptQuiz(ctx context.Context, req *models.AttemptQuizRequest) (*models.AttemptQuizResponse, error)
9
+ // UserGetQuestionQuiz(ctx context.Context, req *models.GetQuestionQuizRequest) (*models.GetQuestionQuizResponse, error)
10
+ // // UserAnswerQuiz(ctx context.Context, req *models.AnswerQuizRequest) (*models.AnswerQuizResponse, error)
11
+ // // UserReviewQuiz(ctx context.Context, req *models.ReviewQuizRequest) (*models.ReviewQuizResponse, error)
12
+ // // UserSubmitQuiz(ctx context.Context, req *models.SubmitQuizRequest) (*models.SubmitQuizResponse, error)
13
+ // // UserResultQuiz(ctx context.Context, req *models.ResultQuizRequest) (*models.ResultQuizResponse, error)
14
+ // // UserListResultQuiz(ctx context.Context, req *models.ListResultQuizRequest) (*models.ListResultQuizResponse, *models.Paging, error)
15
+ // }
16
+
17
+ // type quizService struct {
18
+ // quizRepository repositories.QuizRepository
19
+ // validator *validation.Validator
20
+ // }
21
+
22
+ // func NewQuizService(quizRepository repositories.QuizRepository, validator *validation.Validator) QuizService {
23
+ // return &quizService{quizRepository: quizRepository, validator: validator}
24
+ // }
25
+
26
+ // func (s *quizService) UserListQuiz(ctx context.Context, req *models.ListQuizRequest) (*models.ListQuizResponse, *models.Paging, error) {
27
+ // quiz, paging, err := s.quizRepository.UserListQuiz(ctx, req)
28
+ // if err != nil {
29
+ // return nil, nil, response.HandleGormError(err, "Internal Server Error")
30
+ // }
31
+
32
+ // return quiz, paging, nil
33
+ // }
34
+
35
+ // func (s *quizService) UserAttemptQuiz(ctx context.Context, req *models.AttemptQuizRequest) (*models.AttemptQuizResponse, error) {
36
+ // panic("not implemented")
37
+ // }
space/space/models/field_counter.go CHANGED
@@ -1,7 +1,6 @@
1
  package models
2
 
3
  import (
4
- "fmt"
5
  "reflect"
6
  "time"
7
  )
@@ -23,7 +22,6 @@ func shouldSkipField(field reflect.StructField) bool {
23
  }
24
 
25
  counterTag := field.Tag.Get("counter")
26
- fmt.Println(field.Name, "=", counterTag)
27
  return counterTag == "skip"
28
  }
29
 
 
1
  package models
2
 
3
  import (
 
4
  "reflect"
5
  "time"
6
  )
 
22
  }
23
 
24
  counterTag := field.Tag.Get("counter")
 
25
  return counterTag == "skip"
26
  }
27
 
space/space/services/partner_criteria_service.go CHANGED
@@ -59,9 +59,9 @@ func (s *partnerCriteriaService) SavePartnerCriteria(ctx context.Context, req *m
59
  utils.AssignIfNotNil(&partnerCriteria.ExpectedMaxHeightLimit, req.ExpectedMaxHeightLimit)
60
 
61
  utils.AssignIfNotNil(&partnerCriteria.ExpectedPartnerIncome, req.ExpectedPartnerIncome)
62
- utils.AssignIfNotNil(&partnerCriteria.ExpectedIncomeSources, req.ExpectedIncomeSources)
63
  utils.AssignIfNotNil(&partnerCriteria.ExpectedLastEducation, req.ExpectedLastEducation)
64
- utils.AssignIfNotNil(&partnerCriteria.ExpectedJobType, req.ExpectedJobType)
 
65
 
66
  res, err := s.partnerCriteriaRepository.SavePartnerCriteria(ctx, partnerCriteria)
67
  if err != nil {
 
59
  utils.AssignIfNotNil(&partnerCriteria.ExpectedMaxHeightLimit, req.ExpectedMaxHeightLimit)
60
 
61
  utils.AssignIfNotNil(&partnerCriteria.ExpectedPartnerIncome, req.ExpectedPartnerIncome)
 
62
  utils.AssignIfNotNil(&partnerCriteria.ExpectedLastEducation, req.ExpectedLastEducation)
63
+ // utils.AssignIfNotNil(&partnerCriteria.ExpectedIncomeSources, req.ExpectedIncomeSources)
64
+ // utils.AssignIfNotNil(&partnerCriteria.ExpectedJobType, req.ExpectedJobType)
65
 
66
  res, err := s.partnerCriteriaRepository.SavePartnerCriteria(ctx, partnerCriteria)
67
  if err != nil {
space/space/space/config/database_connection_config.go CHANGED
@@ -57,9 +57,7 @@ func AutoMigrateAll(db *gorm.DB) {
57
  &models.ForgotPassword{},
58
  &models.Academy{},
59
  &models.AcademyMaterial{},
60
- &models.AcademyContent{},
61
  &models.AcademyMaterialProgress{},
62
- &models.AcademyContentProgress{},
63
  &models.RegionCity{},
64
  &models.RegionProvince{},
65
  &models.OptionCategory{},
 
57
  &models.ForgotPassword{},
58
  &models.Academy{},
59
  &models.AcademyMaterial{},
 
60
  &models.AcademyMaterialProgress{},
 
61
  &models.RegionCity{},
62
  &models.RegionProvince{},
63
  &models.OptionCategory{},
space/space/space/controller/academy/academy_controller.go CHANGED
@@ -1,31 +1,369 @@
1
- package academy
2
-
3
- import (
4
- "api.qobiltu.id/controller"
5
- "api.qobiltu.id/models"
6
- "api.qobiltu.id/services"
7
- "github.com/gin-gonic/gin"
8
- )
9
-
10
- func List(c *gin.Context) {
11
- academy := services.AcademyService{}
12
- academyController := controller.Controller[any, models.Academy, []models.Academy]{
13
- Service: &academy.Service,
14
- }
15
- academyController.Service.Constructor.Slug = c.Param("slug")
16
- academy.Retrieve()
17
- academyController.Response(c)
18
-
19
- }
20
-
21
- func Create(c *gin.Context) {
22
- createAcademy := services.CreateAcademyService{}
23
- academyController := controller.Controller[models.AllAcademyResponse, models.AllAcademyResponse, models.AllAcademyResponse]{
24
- Service: &createAcademy.Service,
25
- }
26
- academyController.RequestJSON(c, func() {
27
- academyController.Service.Constructor.Academies = academyController.Request.Academies
28
- createAcademy.Create()
29
- })
30
-
31
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package academy_controller
2
+
3
+ import (
4
+ "net/http"
5
+ "strconv"
6
+
7
+ "api.qobiltu.id/middleware"
8
+ "api.qobiltu.id/models"
9
+ "api.qobiltu.id/response"
10
+ "api.qobiltu.id/services"
11
+ "github.com/gin-gonic/gin"
12
+ )
13
+
14
+ type AcademyController interface {
15
+ // === ADMIN ===
16
+ AdminCreateAcademy(ctx *gin.Context)
17
+ AdminGetAcademy(ctx *gin.Context)
18
+ AdminUpdateAcademy(ctx *gin.Context)
19
+ AdminDeleteAcademy(ctx *gin.Context)
20
+ AdminListAcademy(ctx *gin.Context)
21
+ AdminReorderAcademy(ctx *gin.Context)
22
+
23
+ AdminCreateAcademyMaterial(ctx *gin.Context)
24
+ AdminGetAcademyMaterial(ctx *gin.Context)
25
+ AdminUpdateAcademyMaterial(ctx *gin.Context)
26
+ AdminDeleteAcademyMaterial(ctx *gin.Context)
27
+ AdminListAcademyMaterial(ctx *gin.Context)
28
+ AdminReorderAcademyMaterial(ctx *gin.Context)
29
+
30
+ // === USER ===
31
+ UserListAcademy(ctx *gin.Context)
32
+ UserListAcademyMaterial(ctx *gin.Context)
33
+ UserGetAcademyMaterialBySlug(ctx *gin.Context)
34
+ UserToggleAcademyMaterialProgress(ctx *gin.Context)
35
+ }
36
+
37
+ type academyController struct {
38
+ academyService services.AcademyService
39
+ }
40
+
41
+ func NewAcademyController(academyService services.AcademyService) AcademyController {
42
+ return &academyController{
43
+ academyService: academyService,
44
+ }
45
+ }
46
+
47
+ func (c *academyController) AdminListAcademy(ctx *gin.Context) {
48
+ req := models.NewListAcademyRequest()
49
+ if err := ctx.ShouldBindQuery(&req); err != nil {
50
+ response.HandleError(ctx, err)
51
+ return
52
+ }
53
+
54
+ res, paging, err := c.academyService.AdminListAcademy(ctx, &req)
55
+ if err != nil {
56
+ response.HandleError(ctx, err)
57
+ return
58
+ }
59
+
60
+ response.HandleSuccess(ctx, http.StatusOK, "Academy list retrieved successfully", res, paging)
61
+ }
62
+
63
+ func (c *academyController) AdminCreateAcademy(ctx *gin.Context) {
64
+ var req models.CreateAcademyRequest
65
+ if err := ctx.ShouldBindJSON(&req); err != nil {
66
+ response.HandleError(ctx, err)
67
+ return
68
+ }
69
+
70
+ accountData := middleware.GetAccountData(ctx)
71
+ req.AccountID = int64(accountData.UserID)
72
+
73
+ res, err := c.academyService.AdminCreateAcademy(ctx, &req)
74
+ if err != nil {
75
+ response.HandleError(ctx, err)
76
+ return
77
+ }
78
+
79
+ response.HandleSuccess(ctx, http.StatusOK, "Academy created successfully", res, nil)
80
+ }
81
+
82
+ func (c *academyController) AdminGetAcademy(ctx *gin.Context) {
83
+ id := ctx.Param("id")
84
+ idInt, err := strconv.ParseInt(id, 10, 64)
85
+ if err != nil {
86
+ response.HandleError(ctx, err)
87
+ return
88
+ }
89
+
90
+ res, err := c.academyService.AdminGetAcademyByID(ctx, idInt)
91
+ if err != nil {
92
+ response.HandleError(ctx, err)
93
+ return
94
+ }
95
+
96
+ response.HandleSuccess(ctx, http.StatusOK, "Academy retrieved successfully", res, nil)
97
+ }
98
+
99
+ func (c *academyController) AdminUpdateAcademy(ctx *gin.Context) {
100
+ id := ctx.Param("id")
101
+ idInt, err := strconv.ParseInt(id, 10, 64)
102
+ if err != nil {
103
+ response.HandleError(ctx, err)
104
+ return
105
+ }
106
+
107
+ var req models.UpdateAcademyRequest
108
+ if err := ctx.ShouldBindJSON(&req); err != nil {
109
+ response.HandleError(ctx, err)
110
+ return
111
+ }
112
+
113
+ req.ID = idInt
114
+
115
+ res, err := c.academyService.AdminUpdateAcademy(ctx, &req)
116
+ if err != nil {
117
+ response.HandleError(ctx, err)
118
+ return
119
+ }
120
+
121
+ response.HandleSuccess(ctx, http.StatusOK, "Academy updated successfully", res, nil)
122
+ }
123
+
124
+ func (c *academyController) AdminDeleteAcademy(ctx *gin.Context) {
125
+ id := ctx.Param("id")
126
+ idInt, err := strconv.ParseInt(id, 10, 64)
127
+ if err != nil {
128
+ response.HandleError(ctx, err)
129
+ return
130
+ }
131
+
132
+ err = c.academyService.AdminDeleteAcademy(ctx, idInt)
133
+ if err != nil {
134
+ response.HandleError(ctx, err)
135
+ return
136
+ }
137
+
138
+ response.HandleSuccess(ctx, http.StatusOK, "Academy deleted successfully", nil, nil)
139
+ }
140
+
141
+ func (c *academyController) AdminReorderAcademy(ctx *gin.Context) {
142
+ var req models.ReorderAcademyRequest
143
+ if err := ctx.ShouldBindJSON(&req); err != nil {
144
+ response.HandleError(ctx, err)
145
+ return
146
+ }
147
+
148
+ err := c.academyService.AdminReorderAcademy(ctx, &req)
149
+ if err != nil {
150
+ response.HandleError(ctx, err)
151
+ return
152
+ }
153
+
154
+ response.HandleSuccess(ctx, http.StatusOK, "Academy reordered successfully", nil, nil)
155
+ }
156
+
157
+ func (c *academyController) AdminCreateAcademyMaterial(ctx *gin.Context) {
158
+ var req models.CreateAcademyMaterialRequest
159
+ if err := ctx.ShouldBindJSON(&req); err != nil {
160
+ response.HandleError(ctx, err)
161
+ return
162
+ }
163
+
164
+ academyID := ctx.Param("id")
165
+ academyIDInt, err := strconv.ParseInt(academyID, 10, 64)
166
+ if err != nil {
167
+ response.HandleError(ctx, err)
168
+ return
169
+ }
170
+
171
+ req.AcademyID = academyIDInt
172
+
173
+ res, err := c.academyService.AdminCreateAcademyMaterial(ctx, &req)
174
+ if err != nil {
175
+ response.HandleError(ctx, err)
176
+ return
177
+ }
178
+
179
+ response.HandleSuccess(ctx, http.StatusOK, "Academy material created successfully", res, nil)
180
+ }
181
+
182
+ func (c *academyController) AdminGetAcademyMaterial(ctx *gin.Context) {
183
+ id := ctx.Param("materialId")
184
+ idInt, err := strconv.ParseInt(id, 10, 64)
185
+ if err != nil {
186
+ response.HandleError(ctx, err)
187
+ return
188
+ }
189
+
190
+ res, err := c.academyService.AdminGetAcademyMaterialByID(ctx, idInt)
191
+ if err != nil {
192
+ response.HandleError(ctx, err)
193
+ return
194
+ }
195
+
196
+ response.HandleSuccess(ctx, http.StatusOK, "Academy material retrieved successfully", res, nil)
197
+ }
198
+
199
+ func (c *academyController) AdminUpdateAcademyMaterial(ctx *gin.Context) {
200
+ id := ctx.Param("materialId")
201
+ idInt, err := strconv.ParseInt(id, 10, 64)
202
+ if err != nil {
203
+ response.HandleError(ctx, err)
204
+ return
205
+ }
206
+
207
+ var req models.UpdateAcademyMaterialRequest
208
+ if err := ctx.ShouldBindJSON(&req); err != nil {
209
+ response.HandleError(ctx, err)
210
+ return
211
+ }
212
+
213
+ req.ID = idInt
214
+
215
+ res, err := c.academyService.AdminUpdateAcademyMaterial(ctx, &req)
216
+ if err != nil {
217
+ response.HandleError(ctx, err)
218
+ return
219
+ }
220
+
221
+ response.HandleSuccess(ctx, http.StatusOK, "Academy material updated successfully", res, nil)
222
+ }
223
+
224
+ func (c *academyController) AdminDeleteAcademyMaterial(ctx *gin.Context) {
225
+ id := ctx.Param("materialId")
226
+ idInt, err := strconv.ParseInt(id, 10, 64)
227
+ if err != nil {
228
+ response.HandleError(ctx, err)
229
+ return
230
+ }
231
+
232
+ err = c.academyService.AdminDeleteAcademyMaterial(ctx, idInt)
233
+ if err != nil {
234
+ response.HandleError(ctx, err)
235
+ return
236
+ }
237
+
238
+ response.HandleSuccess(ctx, http.StatusOK, "Academy material deleted successfully", nil, nil)
239
+ }
240
+
241
+ func (c *academyController) AdminReorderAcademyMaterial(ctx *gin.Context) {
242
+ req := models.ReorderAcademyMaterialRequest{}
243
+ if err := ctx.ShouldBindJSON(&req); err != nil {
244
+ response.HandleError(ctx, err)
245
+ return
246
+ }
247
+
248
+ academyID := ctx.Param("id")
249
+ academyIDInt, err := strconv.ParseInt(academyID, 10, 64)
250
+ if err != nil {
251
+ response.HandleError(ctx, err)
252
+ return
253
+ }
254
+
255
+ req.AcademyID = academyIDInt
256
+
257
+ err = c.academyService.AdminReorderAcademyMaterial(ctx, &req)
258
+ if err != nil {
259
+ response.HandleError(ctx, err)
260
+ return
261
+ }
262
+
263
+ response.HandleSuccess(ctx, http.StatusOK, "Academy material reordered successfully", nil, nil)
264
+ }
265
+
266
+ func (c *academyController) AdminListAcademyMaterial(ctx *gin.Context) {
267
+ req := models.NewListAcademyMaterialRequest()
268
+ if err := ctx.ShouldBindQuery(&req); err != nil {
269
+ response.HandleError(ctx, err)
270
+ return
271
+ }
272
+
273
+ academyID := ctx.Param("id")
274
+ academyIDInt, err := strconv.ParseInt(academyID, 10, 64)
275
+ if err != nil {
276
+ response.HandleError(ctx, err)
277
+ return
278
+ }
279
+
280
+ accountData := middleware.GetAccountData(ctx)
281
+ req.AccountID = int64(accountData.UserID)
282
+ req.AcademyID = academyIDInt
283
+
284
+ res, paging, err := c.academyService.AdminListAcademyMaterial(ctx, &req)
285
+ if err != nil {
286
+ response.HandleError(ctx, err)
287
+ return
288
+ }
289
+
290
+ response.HandleSuccess(ctx, http.StatusOK, "Academy material list retrieved successfully", res, paging)
291
+ }
292
+
293
+ // === USER ===
294
+
295
+ func (c *academyController) UserListAcademy(ctx *gin.Context) {
296
+ req := models.NewListAcademyRequest()
297
+ if err := ctx.ShouldBindQuery(&req); err != nil {
298
+ response.HandleError(ctx, err)
299
+ return
300
+ }
301
+
302
+ accountData := middleware.GetAccountData(ctx)
303
+ req.AccountID = int64(accountData.UserID)
304
+
305
+ res, paging, err := c.academyService.UserListAcademy(ctx, &req)
306
+ if err != nil {
307
+ response.HandleError(ctx, err)
308
+ return
309
+ }
310
+
311
+ response.HandleSuccess(ctx, http.StatusOK, "Academy list retrieved successfully", res, paging)
312
+ }
313
+
314
+ func (c *academyController) UserListAcademyMaterial(ctx *gin.Context) {
315
+ req := models.NewListAcademyMaterialRequest()
316
+ if err := ctx.ShouldBindQuery(&req); err != nil {
317
+ response.HandleError(ctx, err)
318
+ return
319
+ }
320
+
321
+ accountData := middleware.GetAccountData(ctx)
322
+ req.AccountID = int64(accountData.UserID)
323
+
324
+ slug := ctx.Param("slug")
325
+ req.Slug = slug
326
+
327
+ res, paging, err := c.academyService.UserListAcademyMaterial(ctx, &req)
328
+ if err != nil {
329
+ response.HandleError(ctx, err)
330
+ return
331
+ }
332
+
333
+ response.HandleSuccess(ctx, http.StatusOK, "Academy material list retrieved successfully", res, paging)
334
+ }
335
+
336
+ func (c *academyController) UserGetAcademyMaterialBySlug(ctx *gin.Context) {
337
+ slug := ctx.Param("materialSlug")
338
+
339
+ accountData := middleware.GetAccountData(ctx)
340
+ accountID := int64(accountData.UserID)
341
+
342
+ res, err := c.academyService.UserGetAcademyMaterialBySlug(ctx, slug, accountID)
343
+ if err != nil {
344
+ response.HandleError(ctx, err)
345
+ return
346
+ }
347
+
348
+ response.HandleSuccess(ctx, http.StatusOK, "Academy material retrieved successfully", res, nil)
349
+ }
350
+
351
+ func (c *academyController) UserToggleAcademyMaterialProgress(ctx *gin.Context) {
352
+ slug := ctx.Param("materialSlug")
353
+
354
+ accountData := middleware.GetAccountData(ctx)
355
+ accountID := int64(accountData.UserID)
356
+
357
+ req := models.ToggleAcademyMaterialProgressRequest{
358
+ AccountID: accountID,
359
+ Slug: slug,
360
+ }
361
+
362
+ err := c.academyService.UserToggleAcademyMaterialProgress(ctx, &req)
363
+ if err != nil {
364
+ response.HandleError(ctx, err)
365
+ return
366
+ }
367
+
368
+ response.HandleSuccess(ctx, http.StatusOK, "Academy material progress toggled successfully", nil, nil)
369
+ }
space/space/space/controller/cv/cv_controller.go CHANGED
@@ -66,11 +66,7 @@ func NewCVController(cvService services.CVService) CVController {
66
  func (c *cvController) SaveAccountDetails(ctx *gin.Context) {
67
  var req models.SaveAccountDetailsRequest
68
  if err := ctx.ShouldBindJSON(&req); err != nil {
69
- response.HandleError(ctx, models.Exception{
70
- Message: "Invalid body request",
71
- BadRequest: true,
72
- Err: err,
73
- })
74
  return
75
  }
76
 
@@ -108,11 +104,7 @@ func (c *cvController) GetAccountDetails(ctx *gin.Context) {
108
  func (c *cvController) SavePersonalityAndPreference(ctx *gin.Context) {
109
  var req models.SavePersonalityAndPreferenceRequest
110
  if err := ctx.ShouldBindJSON(&req); err != nil {
111
- response.HandleError(ctx, models.Exception{
112
- Message: "Invalid body request",
113
- BadRequest: true,
114
- Err: err,
115
- })
116
  return
117
  }
118
 
@@ -150,11 +142,7 @@ func (c *cvController) GetPersonalityAndPreference(ctx *gin.Context) {
150
  func (c *cvController) CreateFamilyMember(ctx *gin.Context) {
151
  var req models.CreateFamilyMemberRequest
152
  if err := ctx.ShouldBindJSON(&req); err != nil {
153
- response.HandleError(ctx, models.Exception{
154
- Message: "Invalid body request",
155
- BadRequest: true,
156
- Err: err,
157
- })
158
  return
159
  }
160
 
@@ -180,11 +168,7 @@ func (c *cvController) UpdateFamilyMember(ctx *gin.Context) {
180
 
181
  var req models.UpdateFamilyMemberRequest
182
  if err := ctx.ShouldBindJSON(&req); err != nil {
183
- response.HandleError(ctx, models.Exception{
184
- Message: "Invalid body request",
185
- BadRequest: true,
186
- Err: err,
187
- })
188
  return
189
  }
190
 
@@ -263,11 +247,7 @@ func (c *cvController) DeleteFamilyMember(ctx *gin.Context) {
263
  func (c *cvController) SavePhysicalAndHealth(ctx *gin.Context) {
264
  var req models.SavePhysicalAndHealthRequest
265
  if err := ctx.ShouldBindJSON(&req); err != nil {
266
- response.HandleError(ctx, models.Exception{
267
- Message: "Invalid body request",
268
- BadRequest: true,
269
- Err: err,
270
- })
271
  return
272
  }
273
 
@@ -305,11 +285,7 @@ func (c *cvController) GetPhysicalAndHealth(ctx *gin.Context) {
305
  func (c *cvController) SaveWorshipAndReligiousUnderstanding(ctx *gin.Context) {
306
  var req models.SaveWorshipAndReligiousUnderstandingRequest
307
  if err := ctx.ShouldBindJSON(&req); err != nil {
308
- response.HandleError(ctx, models.Exception{
309
- Message: "Invalid body request",
310
- BadRequest: true,
311
- Err: err,
312
- })
313
  return
314
  }
315
 
@@ -346,11 +322,7 @@ func (c *cvController) GetWorshipAndReligiousUnderstanding(ctx *gin.Context) {
346
  func (c *cvController) CreateEducation(ctx *gin.Context) {
347
  var req models.CreateEducationRequest
348
  if err := ctx.ShouldBindJSON(&req); err != nil {
349
- response.HandleError(ctx, models.Exception{
350
- Message: "Invalid body request",
351
- BadRequest: true,
352
- Err: err,
353
- })
354
  return
355
  }
356
 
@@ -376,11 +348,7 @@ func (c *cvController) UpdateEducation(ctx *gin.Context) {
376
 
377
  var req models.UpdateEducationRequest
378
  if err := ctx.ShouldBindJSON(&req); err != nil {
379
- response.HandleError(ctx, models.Exception{
380
- Message: "Invalid body request",
381
- BadRequest: true,
382
- Err: err,
383
- })
384
  return
385
  }
386
 
@@ -458,11 +426,7 @@ func (c *cvController) DeleteEducation(ctx *gin.Context) {
458
  func (c *cvController) CreateJob(ctx *gin.Context) {
459
  var req models.CreateJobRequest
460
  if err := ctx.ShouldBindJSON(&req); err != nil {
461
- response.HandleError(ctx, models.Exception{
462
- Message: "Invalid body request",
463
- BadRequest: true,
464
- Err: err,
465
- })
466
  return
467
  }
468
 
@@ -488,11 +452,7 @@ func (c *cvController) UpdateJob(ctx *gin.Context) {
488
 
489
  var req models.UpdateJobRequest
490
  if err := ctx.ShouldBindJSON(&req); err != nil {
491
- response.HandleError(ctx, models.Exception{
492
- Message: "Invalid body request",
493
- BadRequest: true,
494
- Err: err,
495
- })
496
  return
497
  }
498
 
@@ -570,11 +530,7 @@ func (c *cvController) DeleteJob(ctx *gin.Context) {
570
  func (c *cvController) CreateAchievement(ctx *gin.Context) {
571
  var req models.CreateAchievementRequest
572
  if err := ctx.ShouldBindJSON(&req); err != nil {
573
- response.HandleError(ctx, models.Exception{
574
- Message: "Invalid body request",
575
- BadRequest: true,
576
- Err: err,
577
- })
578
  return
579
  }
580
 
@@ -600,11 +556,7 @@ func (c *cvController) UpdateAchievement(ctx *gin.Context) {
600
 
601
  var req models.UpdateAchievementRequest
602
  if err := ctx.ShouldBindJSON(&req); err != nil {
603
- response.HandleError(ctx, models.Exception{
604
- Message: "Invalid body request",
605
- BadRequest: true,
606
- Err: err,
607
- })
608
  return
609
  }
610
 
 
66
  func (c *cvController) SaveAccountDetails(ctx *gin.Context) {
67
  var req models.SaveAccountDetailsRequest
68
  if err := ctx.ShouldBindJSON(&req); err != nil {
69
+ response.HandleError(ctx, err)
 
 
 
 
70
  return
71
  }
72
 
 
104
  func (c *cvController) SavePersonalityAndPreference(ctx *gin.Context) {
105
  var req models.SavePersonalityAndPreferenceRequest
106
  if err := ctx.ShouldBindJSON(&req); err != nil {
107
+ response.HandleError(ctx, err)
 
 
 
 
108
  return
109
  }
110
 
 
142
  func (c *cvController) CreateFamilyMember(ctx *gin.Context) {
143
  var req models.CreateFamilyMemberRequest
144
  if err := ctx.ShouldBindJSON(&req); err != nil {
145
+ response.HandleError(ctx, err)
 
 
 
 
146
  return
147
  }
148
 
 
168
 
169
  var req models.UpdateFamilyMemberRequest
170
  if err := ctx.ShouldBindJSON(&req); err != nil {
171
+ response.HandleError(ctx, err)
 
 
 
 
172
  return
173
  }
174
 
 
247
  func (c *cvController) SavePhysicalAndHealth(ctx *gin.Context) {
248
  var req models.SavePhysicalAndHealthRequest
249
  if err := ctx.ShouldBindJSON(&req); err != nil {
250
+ response.HandleError(ctx, err)
 
 
 
 
251
  return
252
  }
253
 
 
285
  func (c *cvController) SaveWorshipAndReligiousUnderstanding(ctx *gin.Context) {
286
  var req models.SaveWorshipAndReligiousUnderstandingRequest
287
  if err := ctx.ShouldBindJSON(&req); err != nil {
288
+ response.HandleError(ctx, err)
 
 
 
 
289
  return
290
  }
291
 
 
322
  func (c *cvController) CreateEducation(ctx *gin.Context) {
323
  var req models.CreateEducationRequest
324
  if err := ctx.ShouldBindJSON(&req); err != nil {
325
+ response.HandleError(ctx, err)
 
 
 
 
326
  return
327
  }
328
 
 
348
 
349
  var req models.UpdateEducationRequest
350
  if err := ctx.ShouldBindJSON(&req); err != nil {
351
+ response.HandleError(ctx, err)
 
 
 
 
352
  return
353
  }
354
 
 
426
  func (c *cvController) CreateJob(ctx *gin.Context) {
427
  var req models.CreateJobRequest
428
  if err := ctx.ShouldBindJSON(&req); err != nil {
429
+ response.HandleError(ctx, err)
 
 
 
 
430
  return
431
  }
432
 
 
452
 
453
  var req models.UpdateJobRequest
454
  if err := ctx.ShouldBindJSON(&req); err != nil {
455
+ response.HandleError(ctx, err)
 
 
 
 
456
  return
457
  }
458
 
 
530
  func (c *cvController) CreateAchievement(ctx *gin.Context) {
531
  var req models.CreateAchievementRequest
532
  if err := ctx.ShouldBindJSON(&req); err != nil {
533
+ response.HandleError(ctx, err)
 
 
 
 
534
  return
535
  }
536
 
 
556
 
557
  var req models.UpdateAchievementRequest
558
  if err := ctx.ShouldBindJSON(&req); err != nil {
559
+ response.HandleError(ctx, err)
 
 
 
 
560
  return
561
  }
562
 
space/space/space/controller/email/email_controller.go CHANGED
@@ -43,11 +43,7 @@ func (c *emailController) CreateEmailVerification(ctx *gin.Context) {
43
  func (c *emailController) Verify(ctx *gin.Context) {
44
  var req models.ValidateEmailVerificationRequest
45
  if err := ctx.ShouldBindJSON(&req); err != nil {
46
- response.HandleError(ctx, models.Exception{
47
- Message: "Invalid body request",
48
- BadRequest: true,
49
- Err: err,
50
- })
51
  return
52
  }
53
 
 
43
  func (c *emailController) Verify(ctx *gin.Context) {
44
  var req models.ValidateEmailVerificationRequest
45
  if err := ctx.ShouldBindJSON(&req); err != nil {
46
+ response.HandleError(ctx, err)
 
 
 
 
47
  return
48
  }
49
 
space/space/space/controller/marriage_readiness_profile/marriage_readiness_profile_controller.go CHANGED
@@ -1,4 +1,4 @@
1
- package cv_controller
2
 
3
  import (
4
  "net/http"
@@ -28,11 +28,7 @@ func NewMarriageReadinessProfileController(marriageReadinessProfileService servi
28
  func (c *marriageReadinessProfileController) SaveMarriageReadinessProfile(ctx *gin.Context) {
29
  var req models.SaveMarriageReadinessProfileRequest
30
  if err := ctx.ShouldBindJSON(&req); err != nil {
31
- response.HandleError(ctx, models.Exception{
32
- Message: "Invalid body request",
33
- BadRequest: true,
34
- Err: err,
35
- })
36
  return
37
  }
38
 
 
1
+ package marriage_readiness_profile_controller
2
 
3
  import (
4
  "net/http"
 
28
  func (c *marriageReadinessProfileController) SaveMarriageReadinessProfile(ctx *gin.Context) {
29
  var req models.SaveMarriageReadinessProfileRequest
30
  if err := ctx.ShouldBindJSON(&req); err != nil {
31
+ response.HandleError(ctx, err)
 
 
 
 
32
  return
33
  }
34
 
space/space/space/controller/options/options_controller.go CHANGED
@@ -28,11 +28,7 @@ func NewOptionsController(optionService services.OptionsService) OptionsControll
28
  func (c *optionsController) CreateOptions(ctx *gin.Context) {
29
  var req []models.MultipleOptionsRequest
30
  if err := ctx.ShouldBindJSON(&req); err != nil {
31
- response.HandleError(ctx, models.Exception{
32
- Message: "Invalid body request",
33
- BadRequest: true,
34
- Err: err,
35
- })
36
  return
37
  }
38
 
 
28
  func (c *optionsController) CreateOptions(ctx *gin.Context) {
29
  var req []models.MultipleOptionsRequest
30
  if err := ctx.ShouldBindJSON(&req); err != nil {
31
+ response.HandleError(ctx, err)
 
 
 
 
32
  return
33
  }
34
 
space/space/space/controller/partner_criteria/partner_criteria_controller.go CHANGED
@@ -28,11 +28,7 @@ func NewPartnerCriteriaController(partnerCriteriaService services.PartnerCriteri
28
  func (c *partnerCriteriaController) SavePartnerCriteria(ctx *gin.Context) {
29
  var req models.SavePartnerCriteriaRequest
30
  if err := ctx.ShouldBindJSON(&req); err != nil {
31
- response.HandleError(ctx, models.Exception{
32
- Message: "Invalid body request",
33
- BadRequest: true,
34
- Err: err,
35
- })
36
  return
37
  }
38
 
 
28
  func (c *partnerCriteriaController) SavePartnerCriteria(ctx *gin.Context) {
29
  var req models.SavePartnerCriteriaRequest
30
  if err := ctx.ShouldBindJSON(&req); err != nil {
31
+ response.HandleError(ctx, err)
 
 
 
 
32
  return
33
  }
34
 
space/space/space/controller/quiz/list_quiz_controller.go CHANGED
@@ -16,7 +16,7 @@ func List(c *gin.Context) {
16
  }
17
  quizListController.HeaderParse(c, func() {
18
  academy_id, _ := strconv.Atoi(c.Param("academy_id"))
19
- quizList.Constructor.ID = uint(academy_id)
20
  quizList.Retrieve()
21
  quizListController.Response(c)
22
  })
 
16
  }
17
  quizListController.HeaderParse(c, func() {
18
  academy_id, _ := strconv.Atoi(c.Param("academy_id"))
19
+ quizList.Constructor.ID = int64(academy_id)
20
  quizList.Retrieve()
21
  quizListController.Response(c)
22
  })
space/space/space/main.go CHANGED
@@ -6,6 +6,7 @@ import (
6
  "strconv"
7
 
8
  "api.qobiltu.id/config"
 
9
  cv_controller "api.qobiltu.id/controller/cv"
10
  email_controller "api.qobiltu.id/controller/email"
11
  marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
@@ -70,6 +71,10 @@ func main() {
70
  emailService := services.NewEmailService(emailRepository, taskDistributor)
71
  emailController := email_controller.NewEmailController(emailService)
72
 
 
 
 
 
73
  cvRepository := repositories.NewCVRepository(config.DB)
74
  cvService := services.NewCVService(cvRepository, localStorage, validator)
75
  cvController := cv_controller.NewCVController(cvService)
@@ -92,6 +97,7 @@ func main() {
92
  regionController,
93
  optionController,
94
  emailController,
 
95
  cvController,
96
  marriageReadinessProfileController,
97
  partnerCriteriaController,
 
6
  "strconv"
7
 
8
  "api.qobiltu.id/config"
9
+ academy_controller "api.qobiltu.id/controller/academy"
10
  cv_controller "api.qobiltu.id/controller/cv"
11
  email_controller "api.qobiltu.id/controller/email"
12
  marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
 
71
  emailService := services.NewEmailService(emailRepository, taskDistributor)
72
  emailController := email_controller.NewEmailController(emailService)
73
 
74
+ academyRepository := repositories.NewAcademyRepository(config.DB)
75
+ academyService := services.NewAcademyService(academyRepository, validator)
76
+ academyController := academy_controller.NewAcademyController(academyService)
77
+
78
  cvRepository := repositories.NewCVRepository(config.DB)
79
  cvService := services.NewCVService(cvRepository, localStorage, validator)
80
  cvController := cv_controller.NewCVController(cvService)
 
97
  regionController,
98
  optionController,
99
  emailController,
100
+ academyController,
101
  cvController,
102
  marriageReadinessProfileController,
103
  partnerCriteriaController,
space/space/space/models/database_orm_model.go CHANGED
@@ -30,7 +30,7 @@ type AccountDetails struct {
30
  Gender *string `gorm:"column:gender" json:"gender"`
31
  LastEducation *string `gorm:"column:last_education" json:"last_education"`
32
  MaritalStatus *string `gorm:"column:marital_status" json:"marital_status"`
33
- Avatar *string `gorm:"column:avatar" json:"avatar"`
34
  PhoneNumber *string `gorm:"column:phone_number" json:"phone_number"`
35
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"`
36
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
@@ -73,38 +73,37 @@ type ForgotPassword struct {
73
  ExpiredAt time.Time `json:"expired_at"`
74
  }
75
 
76
- type Academy struct {
77
- ID uint `gorm:"primaryKey" json:"id"`
78
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
79
- Title string `json:"title"`
80
- Slug string `json:"slug" gorm:"uniqueIndex" `
81
- TotalMaterial int `json:"total_material"`
82
- CompletedMaterial int `json:"completed_material"`
83
- TotalQuiz int `json:"total_quiz"`
84
- PassedQuiz int `json:"passed_quiz"`
85
- IsCompletedRead bool `json:"is_read"`
86
- IsPassedExam bool `json:"is_passed_exam"`
87
- Description string `json:"description"`
88
- }
89
 
90
- type AcademyMaterial struct {
91
- ID uint `gorm:"primaryKey" json:"id"`
92
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
93
- AcademyID uint `json:"academy_id"`
94
- Title string `json:"title"`
95
- Slug string `json:"slug" gorm:"uniqueIndex"`
96
- IsCompleted bool `json:"is_completed"`
97
- Description string `json:"description"`
98
- }
 
 
99
 
100
- type AcademyContent struct {
101
- ID uint `gorm:"primaryKey" json:"id"`
102
- UUID uuid.UUID `json:"uuid"`
103
- Title string `json:"title"`
104
- Order uint `json:"order"`
105
- AcademyMaterialID uint `json:"academy_material_id"`
106
- Description string `json:"description"`
107
- }
108
 
109
  type OptionCategory struct {
110
  ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
@@ -119,21 +118,6 @@ type OptionValues struct {
119
  OptionValue string `json:"option_value"`
120
  }
121
 
122
- type AcademyMaterialProgress struct {
123
- ID uint `gorm:"primaryKey" json:"id"`
124
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
125
- AccountID uint `json:"account_id"`
126
- AcademyMaterialID uint `json:"academy_material_id"`
127
- Progress uint `json:"progress"`
128
- }
129
-
130
- type AcademyContentProgress struct {
131
- ID uint `gorm:"primaryKey" json:"id"`
132
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
133
- AccountID uint `json:"account_id"`
134
- AcademyID uint `json:"academy_id"`
135
- }
136
-
137
  type RegionProvince struct {
138
  ID uint `json:"id"`
139
  Name string `json:"name"`
@@ -424,9 +408,9 @@ type (
424
 
425
  // Pendidikan & Pekerjaan
426
  ExpectedPartnerIncome *string `gorm:"column:expected_partner_income" json:"expected_partner_income"` // penghasilan pasangan per bulan yang diharapkan
427
- ExpectedIncomeSources *pq.StringArray `gorm:"column:expected_income_sources;type:varchar(255)[]" json:"expected_income_sources"` // sumber penghasilan pasangan
428
  ExpectedLastEducation *pq.StringArray `gorm:"column:expected_last_education;type:varchar(255)[]" json:"expected_last_education"` // pendidikan terakhir yang diharapkan
429
- ExpectedJobType *string `gorm:"column:expected_job_type" json:"expected_job_type"` // jenis pekerjaan pasangan yang diharapkan
 
430
 
431
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
432
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
@@ -442,9 +426,7 @@ func (FCM) TableName() string { return "fcm" }
442
  func (ForgotPassword) TableName() string { return "forgot_password" }
443
  func (Academy) TableName() string { return "academy" }
444
  func (AcademyMaterial) TableName() string { return "academy_materials" }
445
- func (AcademyContent) TableName() string { return "academy_contents" }
446
  func (AcademyMaterialProgress) TableName() string { return "academy_materials_progress" }
447
- func (AcademyContentProgress) TableName() string { return "academy_contents_progress" }
448
  func (RegionProvince) TableName() string { return "region_provinces" }
449
  func (RegionCity) TableName() string { return "region_cities" }
450
  func (Answer) TableName() string { return "answers" }
 
30
  Gender *string `gorm:"column:gender" json:"gender"`
31
  LastEducation *string `gorm:"column:last_education" json:"last_education"`
32
  MaritalStatus *string `gorm:"column:marital_status" json:"marital_status"`
33
+ Avatar *string `gorm:"column:avatar" json:"avatar" counter:"skip"`
34
  PhoneNumber *string `gorm:"column:phone_number" json:"phone_number"`
35
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"`
36
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
 
73
  ExpiredAt time.Time `json:"expired_at"`
74
  }
75
 
76
+ type (
77
+ Academy struct {
78
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
79
+ Title string `gorm:"column:title" json:"title"`
80
+ Slug string `gorm:"column:slug;uniqueIndex" json:"slug"`
81
+ Description string `gorm:"column:description" json:"description"`
82
+ Order uint `gorm:"column:order" json:"order"`
83
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
84
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
85
+ }
 
 
 
86
 
87
+ AcademyMaterial struct {
88
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
89
+ AcademyID int64 `gorm:"column:academy_id;not null" json:"academy_id"`
90
+ Academy *Academy `gorm:"foreignKey:AcademyID;constraint:OnDelete:CASCADE" json:"academy,omitempty"`
91
+ Title string `gorm:"column:title" json:"title"`
92
+ Slug string `gorm:"column:slug;uniqueIndex" json:"slug"`
93
+ Content string `gorm:"column:content" json:"content"`
94
+ Order uint `gorm:"column:order" json:"order"`
95
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
96
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
97
+ }
98
 
99
+ AcademyMaterialProgress struct {
100
+ AccountID int64 `gorm:"primaryKey;column:account_id" json:"account_id" counter:"skip"`
101
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
102
+ AcademyMaterialID int64 `gorm:"primaryKey;column:academy_material_id" json:"academy_material_id"`
103
+ AcademyMaterial *AcademyMaterial `gorm:"foreignKey:AcademyMaterialID;constraint:OnDelete:CASCADE" json:"academy_material,omitempty"`
104
+ ReadAt *time.Time `gorm:"column:read_at" json:"read_at"`
105
+ }
106
+ )
107
 
108
  type OptionCategory struct {
109
  ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
 
118
  OptionValue string `json:"option_value"`
119
  }
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  type RegionProvince struct {
122
  ID uint `json:"id"`
123
  Name string `json:"name"`
 
408
 
409
  // Pendidikan & Pekerjaan
410
  ExpectedPartnerIncome *string `gorm:"column:expected_partner_income" json:"expected_partner_income"` // penghasilan pasangan per bulan yang diharapkan
 
411
  ExpectedLastEducation *pq.StringArray `gorm:"column:expected_last_education;type:varchar(255)[]" json:"expected_last_education"` // pendidikan terakhir yang diharapkan
412
+ // ExpectedIncomeSources *pq.StringArray `gorm:"column:expected_income_sources;type:varchar(255)[]" json:"expected_income_sources"` // sumber penghasilan pasangan
413
+ // ExpectedJobType *string `gorm:"column:expected_job_type" json:"expected_job_type"` // jenis pekerjaan pasangan yang diharapkan
414
 
415
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
416
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
 
426
  func (ForgotPassword) TableName() string { return "forgot_password" }
427
  func (Academy) TableName() string { return "academy" }
428
  func (AcademyMaterial) TableName() string { return "academy_materials" }
 
429
  func (AcademyMaterialProgress) TableName() string { return "academy_materials_progress" }
 
430
  func (RegionProvince) TableName() string { return "region_provinces" }
431
  func (RegionCity) TableName() string { return "region_cities" }
432
  func (Answer) TableName() string { return "answers" }
space/space/space/models/filter_and_paging.go ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package models
2
+
3
+ import (
4
+ "errors"
5
+ "fmt"
6
+ "strings"
7
+ )
8
+
9
+ const (
10
+ AscOrder string = "ASC"
11
+ DescOrder string = "DESC"
12
+ DefaultPageLimit int = 50
13
+ DefaultCurrentPage int = 1
14
+ DefaultColumnOrder string = AscOrder
15
+ DefaultMaxPageLimit int = 100
16
+ )
17
+
18
+ type Filter struct {
19
+ CurrentPage int `json:"current_page" form:"current_page" query:"current_page"` // page (berpindah-pindah halaman)
20
+ PerPage int `json:"per_page" form:"per_page" query:"per_page"` // limit (batas data yang ditampilkan)
21
+ Keyword string `json:"keyword" form:"keyword" query:"keyword"` // search keyword (keyword pencarian)
22
+ SortBy string `json:"sort_by" form:"sort_by" query:"sort_by"` // column name to sort
23
+ Order string `json:"order" form:"order" query:"order"` // asc or desc order
24
+ }
25
+
26
+ func NewFilter() Filter {
27
+ return Filter{
28
+ CurrentPage: DefaultCurrentPage,
29
+ PerPage: DefaultPageLimit,
30
+ Order: DefaultColumnOrder,
31
+ }
32
+ }
33
+
34
+ func (f *Filter) GetLimit() int {
35
+ return f.PerPage
36
+ }
37
+
38
+ func (f *Filter) GetOffset() int {
39
+ offset := (f.CurrentPage - 1) * f.PerPage // example: (1 - 1) * 10 = 0, (2 - 1) * 10 = 10
40
+ return offset
41
+ }
42
+
43
+ func (f *Filter) HasKeyword() bool {
44
+ return f.Keyword != ""
45
+ }
46
+
47
+ func (f *Filter) HasSort() bool {
48
+ return f.SortBy != ""
49
+ }
50
+
51
+ func (f *Filter) IsDesc() bool {
52
+ return strings.ToUpper(f.Order) == DescOrder
53
+ }
54
+
55
+ // ===============================
56
+
57
+ type Paging struct {
58
+ HasPreviousPage bool `json:"has_previous_page"`
59
+ HasNextPage bool `json:"has_next_page"`
60
+ CurrentPage int `json:"current_page"`
61
+ PerPage int `json:"per_page"`
62
+ TotalData int `json:"total_data"`
63
+ LastPage int `json:"last_page"`
64
+ From int `json:"from"`
65
+ To int `json:"to"`
66
+ TotalDataInCurrentPage int `json:"total_data_in_current_page"`
67
+ Label string `json:"label"`
68
+ }
69
+
70
+ var ErrPaging = errors.New("per_page harus lebih besar dari 0 dan offset tidak boleh negatif")
71
+
72
+ func NewPaging(currentPage, perPage, offset, totalData int) (*Paging, error) {
73
+ if perPage <= 0 || offset < 0 {
74
+ return nil, ErrPaging
75
+ }
76
+
77
+ lastPage := totalData / perPage
78
+ if totalData%perPage != 0 {
79
+ lastPage++
80
+ }
81
+
82
+ to := min(offset+perPage, totalData)
83
+ from := int(0)
84
+ if to > offset {
85
+ from = offset + 1
86
+ }
87
+
88
+ if currentPage > lastPage {
89
+ currentPage = lastPage
90
+ }
91
+
92
+ totalDataInCurrentPage := to - offset
93
+ label := fmt.Sprintf("Menampilkan %d sampai %d dari %d data", from, to, totalData)
94
+
95
+ return &Paging{
96
+ HasPreviousPage: currentPage > 1,
97
+ HasNextPage: currentPage < lastPage,
98
+ CurrentPage: currentPage,
99
+ PerPage: perPage,
100
+ TotalData: totalData,
101
+ LastPage: lastPage,
102
+ From: from,
103
+ To: to,
104
+ TotalDataInCurrentPage: totalDataInCurrentPage,
105
+ Label: label,
106
+ }, nil
107
+ }
space/space/space/models/request_model.go CHANGED
@@ -28,6 +28,109 @@ type CreateVerifyEmailRequest struct {
28
  Token uint `json:"token" binding:"required"`
29
  }
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  type (
32
  MultipleOptionsRequest struct {
33
  OptionName string `json:"option_name" validate:"required"`
@@ -395,9 +498,11 @@ type (
395
 
396
  // Pendidikan & Pekerjaan
397
  ExpectedPartnerIncome *string `gorm:"column:expected_partner_income" json:"expected_partner_income"` // penghasilan pasangan per bulan yang diharapkan
398
- ExpectedIncomeSources *pq.StringArray `gorm:"column:expected_income_sources;type:varchar(255)[]" json:"expected_income_sources"` // sumber penghasilan pasangan
399
  ExpectedLastEducation *pq.StringArray `gorm:"column:expected_last_education;type:varchar(255)[]" json:"expected_last_education"` // pendidikan terakhir yang diharapkan
400
- ExpectedJobType *string `gorm:"column:expected_job_type" json:"expected_job_type"` // jenis pekerjaan pasangan yang diharapkan
 
 
 
401
 
402
  }
403
 
 
28
  Token uint `json:"token" binding:"required"`
29
  }
30
 
31
+ type (
32
+ CreateAcademyRequest struct {
33
+ AccountID int64 `json:"account_id"`
34
+ Title string `json:"title" validate:"required"`
35
+ Description string `json:"description"`
36
+ Order *uint `json:"order"`
37
+ }
38
+
39
+ UpdateAcademyRequest struct {
40
+ ID int64 `json:"id"`
41
+ Title *string `json:"title"`
42
+ Description *string `json:"description"`
43
+ Order *uint `json:"order"`
44
+ }
45
+
46
+ AcademyResponse struct {
47
+ Academy
48
+ TotalMaterial int64 `gorm:"total_material" json:"total_material"`
49
+ }
50
+
51
+ UserAcademyResponse struct {
52
+ Academy
53
+ TotalMaterial int64 `gorm:"total_material" json:"total_material"`
54
+ TotalReadMaterial int64 `gorm:"total_read_material" json:"total_read_material"`
55
+ PercentageReadMaterial float64 `gorm:"percentage_read_material" json:"percentage_read_material"`
56
+ }
57
+
58
+ UserAcademyMaterialResponse struct {
59
+ AcademyMaterial
60
+ IsRead bool `gorm:"is_read" json:"is_read"`
61
+ }
62
+
63
+ CreateAcademyMaterialRequest struct {
64
+ AcademyID int64 `json:"academy_id" validate:"required"`
65
+ Title string `json:"title" validate:"required"`
66
+ Content string `json:"content"`
67
+ Order *uint `json:"order"`
68
+ }
69
+
70
+ UpdateAcademyMaterialRequest struct {
71
+ ID int64 `json:"id"`
72
+ Title *string `json:"title"`
73
+ Content *string `json:"content"`
74
+ Order *uint `json:"order"`
75
+ }
76
+
77
+ ListAcademyRequest struct {
78
+ AccountID int64
79
+ Filter
80
+ }
81
+
82
+ ListAcademyMaterialRequest struct {
83
+ AccountID int64
84
+ AcademyID int64
85
+ Slug string
86
+ Filter
87
+ }
88
+
89
+ ListAcademyContentRequest struct {
90
+ AccountID int64
91
+ AcademyID int64
92
+ AcademyMaterialID int64
93
+ Filter
94
+ }
95
+
96
+ ReorderRequest struct {
97
+ ID int64 `json:"id"`
98
+ Order int64 `json:"order"`
99
+ }
100
+
101
+ ReorderAcademyRequest struct {
102
+ Reorder []ReorderRequest `json:"reorder"`
103
+ }
104
+
105
+ ReorderAcademyMaterialRequest struct {
106
+ AcademyID int64 `json:"academy_id"`
107
+ Reorder []ReorderRequest `json:"reorder"`
108
+ }
109
+
110
+ ToggleAcademyMaterialProgressRequest struct {
111
+ AccountID int64 `json:"account_id"`
112
+ Slug string `json:"slug"`
113
+ }
114
+ )
115
+
116
+ func NewListAcademyRequest() ListAcademyRequest {
117
+ return ListAcademyRequest{
118
+ Filter: NewFilter(),
119
+ }
120
+ }
121
+
122
+ func NewListAcademyMaterialRequest() ListAcademyMaterialRequest {
123
+ return ListAcademyMaterialRequest{
124
+ Filter: NewFilter(),
125
+ }
126
+ }
127
+
128
+ func NewListAcademyContentRequest() ListAcademyContentRequest {
129
+ return ListAcademyContentRequest{
130
+ Filter: NewFilter(),
131
+ }
132
+ }
133
+
134
  type (
135
  MultipleOptionsRequest struct {
136
  OptionName string `json:"option_name" validate:"required"`
 
498
 
499
  // Pendidikan & Pekerjaan
500
  ExpectedPartnerIncome *string `gorm:"column:expected_partner_income" json:"expected_partner_income"` // penghasilan pasangan per bulan yang diharapkan
 
501
  ExpectedLastEducation *pq.StringArray `gorm:"column:expected_last_education;type:varchar(255)[]" json:"expected_last_education"` // pendidikan terakhir yang diharapkan
502
+
503
+ // NOT USED
504
+ // ExpectedIncomeSources *pq.StringArray `gorm:"column:expected_income_sources;type:varchar(255)[]" json:"expected_income_sources"` // sumber penghasilan pasangan
505
+ // ExpectedJobType *string `gorm:"column:expected_job_type" json:"expected_job_type"` // jenis pekerjaan pasangan yang diharapkan
506
 
507
  }
508
 
space/space/space/models/response_model.go CHANGED
@@ -33,19 +33,6 @@ type UserProfileResponse struct {
33
  Details AccountDetails `json:"details"`
34
  }
35
 
36
- type AcademyMaterialResponse struct {
37
- Materials AcademyMaterial
38
- Contents AcademyContent
39
- }
40
- type AcademyResponse struct {
41
- Academy Academy `json:"academy"`
42
- Materials []AcademyMaterialResponse `json:"academy_materials"`
43
- }
44
-
45
- type AllAcademyResponse struct {
46
- Academies []AcademyResponse `json:"academy_dasar"`
47
- }
48
-
49
  type AttemptExamResponse struct {
50
  Exam Quiz `json:"exam"`
51
  Questions []Question `json:"questions"`
 
33
  Details AccountDetails `json:"details"`
34
  }
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  type AttemptExamResponse struct {
37
  Exam Quiz `json:"exam"`
38
  Questions []Question `json:"questions"`
space/space/space/repositories/academy_repository.go CHANGED
@@ -1,105 +1,408 @@
1
  package repositories
2
 
3
- import "api.qobiltu.id/models"
4
-
5
- func GetAllAcademy() Repository[models.Academy, []models.Academy] {
6
- repo := Construct[models.Academy, []models.Academy](
7
- models.Academy{},
8
- )
9
- repo.Transactions(
10
- WhereGivenConstructor[models.Academy, []models.Academy],
11
- Find[models.Academy, []models.Academy],
12
- )
13
- return *repo
14
- }
15
-
16
- func GetAcademyDataBySlug(slug string) Repository[models.Academy, models.Academy] {
17
- repo := Construct[models.Academy, models.Academy](
18
- models.Academy{Slug: slug},
19
- )
20
- repo.Transactions(
21
- WhereGivenConstructor[models.Academy, models.Academy],
22
- Find[models.Academy, models.Academy],
23
- )
24
- return *repo
25
- }
26
-
27
- func GetAllAcademyMaterialsByAcademyID(acaddemyId uint) Repository[models.AcademyMaterial, []models.AcademyMaterial] {
28
- repo := Construct[models.AcademyMaterial, []models.AcademyMaterial](
29
- models.AcademyMaterial{AcademyID: acaddemyId},
30
- )
31
- repo.Transactions(
32
- WhereGivenConstructor[models.AcademyMaterial, []models.AcademyMaterial],
33
- Find[models.AcademyMaterial, []models.AcademyMaterial],
34
- )
35
- return *repo
36
- }
37
-
38
- func GetAllAcademyContentsByMaterialID(materialId uint) Repository[models.AcademyContent, models.AcademyContent] {
39
- repo := Construct[models.AcademyContent, models.AcademyContent](
40
- models.AcademyContent{AcademyMaterialID: materialId},
41
- )
42
- repo.Transactions(
43
- WhereGivenConstructor[models.AcademyContent, models.AcademyContent],
44
- Find[models.AcademyContent, models.AcademyContent],
45
- )
46
- return *repo
47
- }
48
-
49
- func CreateAcademy(academies models.Academy) Repository[models.Academy, models.Academy] {
50
- repo := Construct[models.Academy, models.Academy](
51
- academies,
52
- )
53
-
54
- Create(repo)
55
- return *repo
56
- }
57
-
58
- func CreateAcademyMaterial(academyMaterial models.AcademyMaterial) Repository[models.AcademyMaterial, models.AcademyMaterial] {
59
- repo := Construct[models.AcademyMaterial, models.AcademyMaterial](
60
- academyMaterial,
61
- )
62
-
63
- Create(repo)
64
- return *repo
65
- }
66
-
67
- func CreateAcademyContent(academyContent models.AcademyContent) Repository[models.AcademyContent, models.AcademyContent] {
68
- repo := Construct[models.AcademyContent, models.AcademyContent](
69
- academyContent,
70
- )
71
- Create(repo)
72
- return *repo
73
- }
74
-
75
- func GetAcademyMaterialBySlug(slug string) Repository[models.AcademyMaterial, models.AcademyMaterial] {
76
- repo := Construct[models.AcademyMaterial, models.AcademyMaterial](
77
- models.AcademyMaterial{Slug: slug},
78
- )
79
- repo.Transactions(
80
- WhereGivenConstructor[models.AcademyMaterial, models.AcademyMaterial],
81
- Find[models.AcademyMaterial, models.AcademyMaterial],
82
- )
83
- return *repo
84
- }
85
-
86
- func GetAcademyByID(id uint) Repository[models.Academy, models.Academy] {
87
- repo := Construct[models.Academy, models.Academy](
88
- models.Academy{ID: id},
89
- )
90
- repo.Transactions(
91
- WhereGivenConstructor[models.Academy, models.Academy],
92
- Find[models.Academy, models.Academy],
93
- )
94
- return *repo
95
- }
96
-
97
- func UpdateAcademyMaterialCompletedById(id_material uint) Repository[models.AcademyMaterial, models.AcademyMaterial] {
98
- repo := Construct[models.AcademyMaterial, models.AcademyMaterial](
99
- models.AcademyMaterial{ID: id_material, IsCompleted: true},
100
- )
101
- Update(repo)
102
- return *repo
103
- }
104
-
105
- // func UpdateAcademyProgressById(id_academy uint)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  package repositories
2
 
3
+ import (
4
+ "context"
5
+
6
+ "api.qobiltu.id/models"
7
+ "gorm.io/gorm"
8
+ "gorm.io/gorm/clause"
9
+ )
10
+
11
+ type AcademyRepository interface {
12
+ // === ADMIN ===
13
+ AdminSaveAcademy(ctx context.Context, req *models.Academy) (*models.AcademyResponse, error)
14
+ AdminGetAcademyByID(ctx context.Context, id int64) (*models.AcademyResponse, error)
15
+ AdminListAcademy(ctx context.Context, req *models.ListAcademyRequest) ([]models.AcademyResponse, *models.Paging, error)
16
+ AdminDeleteAcademy(ctx context.Context, id int64) error
17
+ AdminReorderAcademy(ctx context.Context, req *models.ReorderAcademyRequest) error
18
+ AdminGetLastOrderAcademy(ctx context.Context) (uint, error)
19
+
20
+ AdminSaveAcademyMaterial(ctx context.Context, req *models.AcademyMaterial) error
21
+ AdminGetAcademyMaterialByID(ctx context.Context, id int64) (*models.AcademyMaterial, error)
22
+ AdminListAcademyMaterial(ctx context.Context, req *models.ListAcademyMaterialRequest) ([]models.AcademyMaterial, *models.Paging, error)
23
+ AdminDeleteAcademyMaterial(ctx context.Context, id int64) error
24
+ AdminReorderAcademyMaterial(ctx context.Context, req *models.ReorderAcademyMaterialRequest) error
25
+ AdminGetLastOrderAcademyMaterial(ctx context.Context) (uint, error)
26
+
27
+ // === USER ===
28
+ UserListAcademy(ctx context.Context, req *models.ListAcademyRequest) ([]models.UserAcademyResponse, *models.Paging, error)
29
+ UserListAcademyMaterial(ctx context.Context, req *models.ListAcademyMaterialRequest) ([]models.UserAcademyMaterialResponse, *models.Paging, error)
30
+ UserGetAcademyBySlug(ctx context.Context, slug string) (*models.AcademyResponse, error)
31
+ UserGetAcademyMaterialBySlug(ctx context.Context, slug string, accountID int64) (*models.UserAcademyMaterialResponse, error)
32
+ UserSaveAcademyMaterialProgress(ctx context.Context, req *models.AcademyMaterialProgress) error
33
+ UserGetAcademyMaterialProgressByAccountID(ctx context.Context, accountID int64, materialID int64) (*models.AcademyMaterialProgress, error)
34
+ UserDeleteAcademyMaterialProgress(ctx context.Context, accountID int64, materialID int64) error
35
+ }
36
+
37
+ type academyRepository struct {
38
+ db *gorm.DB
39
+ }
40
+
41
+ func NewAcademyRepository(db *gorm.DB) AcademyRepository {
42
+ return &academyRepository{
43
+ db: db,
44
+ }
45
+ }
46
+
47
+ func (r *academyRepository) AdminSaveAcademy(ctx context.Context, req *models.Academy) (*models.AcademyResponse, error) {
48
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
49
+ return nil, err
50
+ }
51
+
52
+ var academy models.AcademyResponse
53
+ var totalMaterial int64
54
+ if err := r.db.WithContext(ctx).Model(&models.AcademyMaterial{}).Where("academy_id = ?", req.ID).Count(&totalMaterial).Error; err != nil {
55
+ return nil, err
56
+ }
57
+
58
+ academy.Academy = *req
59
+ academy.TotalMaterial = totalMaterial
60
+
61
+ return &academy, nil
62
+ }
63
+
64
+ func (r *academyRepository) AdminGetAcademyByID(ctx context.Context, id int64) (*models.AcademyResponse, error) {
65
+ var academy models.AcademyResponse
66
+
67
+ subQuery := r.db.Model(&models.AcademyMaterial{}).
68
+ Select("COUNT(*)").
69
+ Where("academy_id = academy.id")
70
+
71
+ if err := r.db.WithContext(ctx).
72
+ Model(&models.Academy{}).
73
+ Select("academy.*, (?) AS total_material", subQuery).
74
+ Where("academy.id = ?", id).
75
+ Scan(&academy).Error; err != nil {
76
+ return nil, err
77
+ }
78
+
79
+ if academy.ID == 0 {
80
+ return nil, gorm.ErrRecordNotFound
81
+ }
82
+
83
+ return &academy, nil
84
+ }
85
+
86
+ func (r *academyRepository) AdminListAcademy(ctx context.Context, req *models.ListAcademyRequest) ([]models.AcademyResponse, *models.Paging, error) {
87
+ academies := make([]models.AcademyResponse, 0)
88
+
89
+ offset := req.Filter.GetOffset()
90
+ limit := req.Filter.GetLimit()
91
+
92
+ // Subquery untuk count materials
93
+ subQuery := r.db.Model(&models.AcademyMaterial{}).
94
+ Select("COUNT(*)").
95
+ Where("academy_id = academy.id")
96
+
97
+ q := r.db.WithContext(ctx).
98
+ Model(&models.Academy{}).
99
+ Select("academy.*, (?) AS total_material", subQuery)
100
+
101
+ if req.Filter.HasKeyword() {
102
+ q = q.Where("title ILIKE ? OR description ILIKE ?", "%"+req.Filter.Keyword+"%", "%"+req.Filter.Keyword+"%")
103
+ }
104
+
105
+ if req.Filter.HasSort() {
106
+ q = q.Order(clause.OrderByColumn{
107
+ Column: clause.Column{Name: req.Filter.SortBy},
108
+ Desc: req.Filter.IsDesc(),
109
+ })
110
+ } else {
111
+ q = q.Order(`"order" ASC`)
112
+ }
113
+
114
+ // Menghitung jumlah total data tanpa offset dan limit
115
+ var totalData int64
116
+ if err := q.Count(&totalData).Error; err != nil {
117
+ return nil, nil, err
118
+ }
119
+
120
+ q = q.Offset(int(offset)).Limit(int(limit))
121
+
122
+ if err := q.Scan(&academies).Error; err != nil {
123
+ return nil, nil, err
124
+ }
125
+
126
+ pageInfo, err := models.NewPaging(req.Filter.CurrentPage, limit, offset, int(totalData))
127
+ if err != nil {
128
+ return nil, nil, err
129
+ }
130
+
131
+ return academies, pageInfo, nil
132
+ }
133
+
134
+ func (r *academyRepository) AdminDeleteAcademy(ctx context.Context, id int64) error {
135
+ return r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.Academy{}).Error
136
+ }
137
+
138
+ func (r *academyRepository) AdminReorderAcademy(ctx context.Context, req *models.ReorderAcademyRequest) error {
139
+ return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
140
+ for _, reorder := range req.Reorder {
141
+ if err := tx.Model(&models.Academy{}).Where("id = ?", reorder.ID).Update("order", reorder.Order).Error; err != nil {
142
+ return err
143
+ }
144
+ }
145
+ return nil
146
+ })
147
+ }
148
+
149
+ func (r *academyRepository) AdminGetLastOrderAcademy(ctx context.Context) (uint, error) {
150
+ var lastOrder uint
151
+ if err := r.db.WithContext(ctx).Model(&models.Academy{}).Order(`"order" DESC`).Limit(1).Select("order").Scan(&lastOrder).Error; err != nil {
152
+ return 0, err
153
+ }
154
+ return lastOrder, nil
155
+ }
156
+
157
+ func (r *academyRepository) AdminSaveAcademyMaterial(ctx context.Context, req *models.AcademyMaterial) error {
158
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
159
+ return err
160
+ }
161
+ return nil
162
+ }
163
+
164
+ func (r *academyRepository) AdminGetAcademyMaterialByID(ctx context.Context, id int64) (*models.AcademyMaterial, error) {
165
+ var academyMaterial models.AcademyMaterial
166
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&academyMaterial).Error; err != nil {
167
+ return nil, err
168
+ }
169
+ return &academyMaterial, nil
170
+ }
171
+
172
+ func (r *academyRepository) AdminListAcademyMaterial(ctx context.Context, req *models.ListAcademyMaterialRequest) ([]models.AcademyMaterial, *models.Paging, error) {
173
+ academyMaterials := make([]models.AcademyMaterial, 0)
174
+
175
+ offset := req.Filter.GetOffset()
176
+ limit := req.Filter.GetLimit()
177
+
178
+ q := r.db.WithContext(ctx).Model(&models.AcademyMaterial{}).Where("academy_id = ?", req.AcademyID)
179
+
180
+ if req.Filter.HasKeyword() {
181
+ q = q.Where("title ILIKE ? OR description ILIKE ?", "%"+req.Filter.Keyword+"%", "%"+req.Filter.Keyword+"%")
182
+ }
183
+
184
+ if req.Filter.HasSort() {
185
+ q = q.Order(clause.OrderByColumn{
186
+ Column: clause.Column{Name: req.Filter.SortBy},
187
+ Desc: req.Filter.IsDesc(),
188
+ })
189
+ } else {
190
+ q = q.Order(`"order" ASC`)
191
+ }
192
+
193
+ // Menghitung jumlah total data tanpa offset dan limit
194
+ var totalData int64
195
+ if err := q.Count(&totalData).Error; err != nil {
196
+ return nil, nil, err
197
+ }
198
+
199
+ q = q.Offset(int(offset)).Limit(int(limit))
200
+
201
+ if err := q.Scan(&academyMaterials).Error; err != nil {
202
+ return nil, nil, err
203
+ }
204
+
205
+ pageInfo, err := models.NewPaging(req.Filter.CurrentPage, limit, offset, int(totalData))
206
+ if err != nil {
207
+ return nil, nil, err
208
+ }
209
+
210
+ return academyMaterials, pageInfo, nil
211
+ }
212
+
213
+ func (r *academyRepository) AdminDeleteAcademyMaterial(ctx context.Context, id int64) error {
214
+ return r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.AcademyMaterial{}).Error
215
+ }
216
+
217
+ func (r *academyRepository) AdminGetLastOrderAcademyMaterial(ctx context.Context) (uint, error) {
218
+ var lastOrder uint
219
+ if err := r.db.WithContext(ctx).Model(&models.AcademyMaterial{}).Order(`"order" DESC`).Limit(1).Select("order").Scan(&lastOrder).Error; err != nil {
220
+ return 0, err
221
+ }
222
+ return lastOrder, nil
223
+ }
224
+
225
+ func (r *academyRepository) AdminReorderAcademyMaterial(ctx context.Context, req *models.ReorderAcademyMaterialRequest) error {
226
+ return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
227
+ for _, reorder := range req.Reorder {
228
+ if err := tx.Model(&models.AcademyMaterial{}).Where("id = ? AND academy_id = ?", reorder.ID, req.AcademyID).Update("order", reorder.Order).Error; err != nil {
229
+ return err
230
+ }
231
+ }
232
+ return nil
233
+ })
234
+ }
235
+
236
+ // === USER ===
237
+
238
+ func (r *academyRepository) UserListAcademy(ctx context.Context, req *models.ListAcademyRequest) ([]models.UserAcademyResponse, *models.Paging, error) {
239
+ academies := make([]models.UserAcademyResponse, 0)
240
+ offset := req.Filter.GetOffset()
241
+ limit := req.Filter.GetLimit()
242
+
243
+ // Base query
244
+ q := r.db.WithContext(ctx).
245
+ Model(&models.Academy{}).
246
+ Select(`academy.*,
247
+ COUNT(DISTINCT am.id) AS total_material,
248
+ COUNT(DISTINCT CASE WHEN amp.read_at IS NOT NULL THEN amp.academy_material_id END) AS total_read_material`).
249
+ Joins("LEFT JOIN academy_materials am ON am.academy_id = academy.id").
250
+ Joins(`LEFT JOIN "academy_materials_progress" amp ON amp.academy_material_id = am.id AND amp.account_id = ?`, req.AccountID).
251
+ Group("academy.id")
252
+
253
+ if req.Filter.HasKeyword() {
254
+ q = q.Where("academy.title ILIKE ? OR academy.description ILIKE ?", "%"+req.Filter.Keyword+"%", "%"+req.Filter.Keyword+"%")
255
+ }
256
+
257
+ // Default ordering
258
+ q = q.Order(`academy."order" ASC`)
259
+
260
+ // Count total records (without limit/offset)
261
+ var totalData int64
262
+ countQuery := q.Session(&gorm.Session{NewDB: true}).Table("academy")
263
+ if err := countQuery.Count(&totalData).Error; err != nil {
264
+ return nil, nil, err
265
+ }
266
+
267
+ // Apply pagination
268
+ q = q.Offset(int(offset)).Limit(int(limit))
269
+
270
+ if err := q.Scan(&academies).Error; err != nil {
271
+ return nil, nil, err
272
+ }
273
+
274
+ // Calculate percentages
275
+ for i := range academies {
276
+ if academies[i].TotalMaterial > 0 {
277
+ academies[i].PercentageReadMaterial = float64(academies[i].TotalReadMaterial) / float64(academies[i].TotalMaterial) * 100
278
+ }
279
+ }
280
+
281
+ pageInfo, err := models.NewPaging(req.Filter.CurrentPage, limit, offset, int(totalData))
282
+ if err != nil {
283
+ return nil, nil, err
284
+ }
285
+
286
+ return academies, pageInfo, nil
287
+ }
288
+
289
+ func (r *academyRepository) UserListAcademyMaterial(ctx context.Context, req *models.ListAcademyMaterialRequest) ([]models.UserAcademyMaterialResponse, *models.Paging, error) {
290
+ materials := make([]models.UserAcademyMaterialResponse, 0)
291
+ offset := req.Filter.GetOffset()
292
+ limit := req.Filter.GetLimit()
293
+
294
+ q := r.db.WithContext(ctx).
295
+ Model(&models.AcademyMaterial{}).
296
+ Select(`academy_materials.*,
297
+ CASE
298
+ WHEN amp.read_at IS NOT NULL THEN true
299
+ ELSE false
300
+ END AS is_read`).
301
+ Joins("JOIN academy ON academy.id = academy_materials.academy_id").
302
+ Joins(`LEFT JOIN academy_materials_progress amp ON
303
+ amp.academy_material_id = academy_materials.id AND
304
+ amp.account_id = ?`, req.AccountID).
305
+ Where("academy.slug = ?", req.Slug)
306
+
307
+ if req.Filter.HasKeyword() {
308
+ q = q.Where("academy_materials.title ILIKE ?", "%"+req.Filter.Keyword+"%")
309
+ }
310
+
311
+ if req.Filter.HasSort() {
312
+ q = q.Order(clause.OrderByColumn{
313
+ Column: clause.Column{Name: req.Filter.SortBy},
314
+ Desc: req.Filter.IsDesc(),
315
+ })
316
+ } else {
317
+ q = q.Order("academy_materials.order ASC")
318
+ }
319
+
320
+ // Count total records (without limit/offset)
321
+ var total int64
322
+ if err := q.Count(&total).Error; err != nil {
323
+ return nil, nil, err
324
+ }
325
+
326
+ q = q.Offset(int(offset)).Limit(int(limit))
327
+
328
+ if err := q.Scan(&materials).Error; err != nil {
329
+ return nil, nil, err
330
+ }
331
+
332
+ paging, err := models.NewPaging(req.Filter.CurrentPage, limit, offset, int(total))
333
+ if err != nil {
334
+ return nil, nil, err
335
+ }
336
+
337
+ return materials, paging, nil
338
+ }
339
+
340
+ func (r *academyRepository) UserGetAcademyBySlug(ctx context.Context, slug string) (*models.AcademyResponse, error) {
341
+ var academy models.AcademyResponse
342
+
343
+ subQuery := r.db.Model(&models.AcademyMaterial{}).
344
+ Select("COUNT(*)").
345
+ Where("academy_id = academy.id")
346
+
347
+ if err := r.db.WithContext(ctx).
348
+ Model(&models.Academy{}).
349
+ Select("academy.*, (?) AS total_material", subQuery).
350
+ Where("academy.slug = ?", slug).
351
+ Scan(&academy).Error; err != nil {
352
+ return nil, err
353
+ }
354
+
355
+ if academy.ID == 0 {
356
+ return nil, gorm.ErrRecordNotFound
357
+ }
358
+
359
+ return &academy, nil
360
+ }
361
+
362
+ func (r *academyRepository) UserGetAcademyMaterialBySlug(ctx context.Context, slug string, accountID int64) (*models.UserAcademyMaterialResponse, error) {
363
+ var result models.UserAcademyMaterialResponse
364
+
365
+ err := r.db.WithContext(ctx).
366
+ Model(&models.AcademyMaterial{}).
367
+ Select(`
368
+ academy_materials.*,
369
+ academy.title as academy_title,
370
+ academy.slug as academy_slug,
371
+ CASE
372
+ WHEN amp.read_at IS NOT NULL THEN true
373
+ ELSE false
374
+ END AS is_read
375
+ `).
376
+ Joins("JOIN academy ON academy.id = academy_materials.academy_id").
377
+ Joins(`LEFT JOIN academy_materials_progress amp ON
378
+ amp.academy_material_id = academy_materials.id AND
379
+ amp.account_id = ?`, accountID).
380
+ Where("academy_materials.slug = ?", slug).
381
+ First(&result).
382
+ Error
383
+
384
+ if err != nil {
385
+ return nil, err
386
+ }
387
+
388
+ return &result, nil
389
+ }
390
+
391
+ func (r *academyRepository) UserSaveAcademyMaterialProgress(ctx context.Context, req *models.AcademyMaterialProgress) error {
392
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
393
+ return err
394
+ }
395
+ return nil
396
+ }
397
+
398
+ func (r *academyRepository) UserGetAcademyMaterialProgressByAccountID(ctx context.Context, accountID int64, materialID int64) (*models.AcademyMaterialProgress, error) {
399
+ var academyMaterialProgress models.AcademyMaterialProgress
400
+ if err := r.db.WithContext(ctx).Where("account_id = ? AND academy_material_id = ?", accountID, materialID).First(&academyMaterialProgress).Error; err != nil {
401
+ return nil, err
402
+ }
403
+ return &academyMaterialProgress, nil
404
+ }
405
+
406
+ func (r *academyRepository) UserDeleteAcademyMaterialProgress(ctx context.Context, accountID int64, materialID int64) error {
407
+ return r.db.WithContext(ctx).Where("account_id = ? AND academy_material_id = ?", accountID, materialID).Delete(&models.AcademyMaterialProgress{}).Error
408
+ }
space/space/space/repositories/question_repository.go CHANGED
@@ -34,3 +34,14 @@ func GetAnswerByQuestionId(questionId uint) Repository[models.Answer, []models.A
34
  )
35
  return *repo
36
  }
 
 
 
 
 
 
 
 
 
 
 
 
34
  )
35
  return *repo
36
  }
37
+
38
+ func GetAcademyByID(id uint) Repository[models.Academy, models.Academy] {
39
+ repo := Construct[models.Academy, models.Academy](
40
+ models.Academy{ID: int64(id)},
41
+ )
42
+ repo.Transactions(
43
+ WhereGivenConstructor[models.Academy, models.Academy],
44
+ Find[models.Academy, models.Academy],
45
+ )
46
+ return *repo
47
+ }
space/space/space/response/api_response_v2.go CHANGED
@@ -1,11 +1,13 @@
1
  package response
2
 
3
  import (
 
 
 
 
4
  "api.qobiltu.id/models"
5
  "api.qobiltu.id/pkg/validation"
6
  "api.qobiltu.id/utils"
7
- "errors"
8
- "net/http"
9
 
10
  "github.com/gin-gonic/gin"
11
  )
@@ -40,17 +42,44 @@ func HandleError(c *gin.Context, err error) {
40
  case exception.AttemptNotFound:
41
  responseError(c, http.StatusNotFound, exception)
42
  case exception.ValidationError:
43
- responseValidationError(c, http.StatusUnprocessableEntity, exception.ValidationErrorFields) // Gunakan fungsi khusus untuk validasi
44
  default:
45
  responseError(c, http.StatusInternalServerError, exception)
46
  }
47
- } else {
48
- utils.LogError(err)
49
- responseError(c, http.StatusInternalServerError, models.Exception{
50
- InternalServerError: true,
51
- Message: "Internal Server Error",
 
 
 
 
 
 
 
 
 
52
  })
 
 
53
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  }
55
 
56
  func HandleSuccess(c *gin.Context, status int, msg string, data any, metaData any) {
@@ -62,7 +91,6 @@ func HandleSuccess(c *gin.Context, status int, msg string, data any, metaData an
62
  }
63
 
64
  c.JSON(status, res)
65
- return
66
  }
67
 
68
  func responseError(c *gin.Context, status int, exception models.Exception) {
@@ -76,7 +104,6 @@ func responseError(c *gin.Context, status int, exception models.Exception) {
76
  }
77
 
78
  c.AbortWithStatusJSON(status, res)
79
- return
80
  }
81
 
82
  func responseValidationError(c *gin.Context, status int, validationErrors []validation.ErrorMessage) {
@@ -90,5 +117,4 @@ func responseValidationError(c *gin.Context, status int, validationErrors []vali
90
  }
91
 
92
  c.AbortWithStatusJSON(status, res)
93
- return
94
  }
 
1
  package response
2
 
3
  import (
4
+ "encoding/json"
5
+ "errors"
6
+ "net/http"
7
+
8
  "api.qobiltu.id/models"
9
  "api.qobiltu.id/pkg/validation"
10
  "api.qobiltu.id/utils"
 
 
11
 
12
  "github.com/gin-gonic/gin"
13
  )
 
42
  case exception.AttemptNotFound:
43
  responseError(c, http.StatusNotFound, exception)
44
  case exception.ValidationError:
45
+ responseValidationError(c, http.StatusUnprocessableEntity, exception.ValidationErrorFields)
46
  default:
47
  responseError(c, http.StatusInternalServerError, exception)
48
  }
49
+
50
+ return
51
+ }
52
+
53
+ var (
54
+ jsonSyntaxError *json.SyntaxError
55
+ jsonUnmarshalTypeError *json.UnmarshalTypeError
56
+ )
57
+
58
+ if errors.As(err, &jsonSyntaxError) {
59
+ responseError(c, http.StatusBadRequest, models.Exception{
60
+ BadRequest: true,
61
+ Message: "Invalid JSON syntax",
62
+ Err: err,
63
  })
64
+
65
+ return
66
  }
67
+
68
+ if errors.As(err, &jsonUnmarshalTypeError) {
69
+ responseError(c, http.StatusBadRequest, models.Exception{
70
+ BadRequest: true,
71
+ Message: "Invalid body request",
72
+ Err: err,
73
+ })
74
+
75
+ return
76
+ }
77
+
78
+ utils.LogError(err)
79
+ responseError(c, http.StatusInternalServerError, models.Exception{
80
+ InternalServerError: true,
81
+ Message: "Internal Server Error",
82
+ })
83
  }
84
 
85
  func HandleSuccess(c *gin.Context, status int, msg string, data any, metaData any) {
 
91
  }
92
 
93
  c.JSON(status, res)
 
94
  }
95
 
96
  func responseError(c *gin.Context, status int, exception models.Exception) {
 
104
  }
105
 
106
  c.AbortWithStatusJSON(status, res)
 
107
  }
108
 
109
  func responseValidationError(c *gin.Context, status int, validationErrors []validation.ErrorMessage) {
 
117
  }
118
 
119
  c.AbortWithStatusJSON(status, res)
 
120
  }
space/space/space/router/academy_route.go CHANGED
@@ -1,17 +1,34 @@
1
  package router
2
 
3
  import (
4
- AcademyController "api.qobiltu.id/controller/academy"
5
  "api.qobiltu.id/middleware"
6
- "github.com/gin-gonic/gin"
7
  )
8
 
9
- func AcademyRoute(router *gin.Engine) {
10
- routerGroup := router.Group("/api/v1/academy")
11
  {
12
- routerGroup.GET("/:slug", middleware.AuthUser, AcademyController.MaterialsList)
13
- routerGroup.GET("/:slug/:slug_material", middleware.AuthUser, AcademyController.ContentList)
14
- routerGroup.GET("/list", middleware.AuthUser, AcademyController.List)
15
- routerGroup.POST("/create", middleware.AuthUser, AcademyController.Create)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  }
17
  }
 
1
  package router
2
 
3
  import (
 
4
  "api.qobiltu.id/middleware"
 
5
  )
6
 
7
+ func (s *Server) AcademyRoute() {
8
+ adminRouterGroup := s.router.Group("/api/v1/admin/academy")
9
  {
10
+ // TODO: add role checker middleware
11
+
12
+ adminRouterGroup.POST("", middleware.AuthUser, s.academyController.AdminCreateAcademy)
13
+ adminRouterGroup.GET("", middleware.AuthUser, s.academyController.AdminListAcademy)
14
+ adminRouterGroup.PUT("/reorder", middleware.AuthUser, s.academyController.AdminReorderAcademy)
15
+ adminRouterGroup.GET("/:id", middleware.AuthUser, s.academyController.AdminGetAcademy)
16
+ adminRouterGroup.PUT("/:id", middleware.AuthUser, s.academyController.AdminUpdateAcademy)
17
+ adminRouterGroup.DELETE("/:id", middleware.AuthUser, s.academyController.AdminDeleteAcademy)
18
+
19
+ adminRouterGroup.POST("/:id/materials", middleware.AuthUser, s.academyController.AdminCreateAcademyMaterial)
20
+ adminRouterGroup.GET("/:id/materials", middleware.AuthUser, s.academyController.AdminListAcademyMaterial)
21
+ adminRouterGroup.PUT("/:id/materials/reorder", middleware.AuthUser, s.academyController.AdminReorderAcademyMaterial)
22
+ adminRouterGroup.GET("/:id/materials/:materialId", middleware.AuthUser, s.academyController.AdminGetAcademyMaterial)
23
+ adminRouterGroup.PUT("/:id/materials/:materialId", middleware.AuthUser, s.academyController.AdminUpdateAcademyMaterial)
24
+ adminRouterGroup.DELETE("/:id/materials/:materialId", middleware.AuthUser, s.academyController.AdminDeleteAcademyMaterial)
25
+ }
26
+
27
+ userRouterGroup := s.router.Group("/api/v1/academy")
28
+ {
29
+ userRouterGroup.GET("", middleware.AuthUser, s.academyController.UserListAcademy)
30
+ userRouterGroup.GET("/:slug/materials", middleware.AuthUser, s.academyController.UserListAcademyMaterial)
31
+ userRouterGroup.GET("/:slug/materials/:materialSlug", middleware.AuthUser, s.academyController.UserGetAcademyMaterialBySlug)
32
+ userRouterGroup.POST("/:slug/materials/:materialSlug/toggle-read", middleware.AuthUser, s.academyController.UserToggleAcademyMaterialProgress)
33
  }
34
  }
space/space/space/router/router.go CHANGED
@@ -10,11 +10,11 @@ func (s *Server) setupRoutes() {
10
 
11
  AuthRoute(s.router)
12
  UserRoute(s.router)
13
- AcademyRoute(s.router)
14
  QuizRoute(s.router)
15
 
16
  s.OptionsRoute()
17
  s.EmailRoute()
 
18
  s.CVRoute()
19
  s.MarriageReadinessProfileRoute()
20
  s.PartnerCriteriaRoute()
 
10
 
11
  AuthRoute(s.router)
12
  UserRoute(s.router)
 
13
  QuizRoute(s.router)
14
 
15
  s.OptionsRoute()
16
  s.EmailRoute()
17
+ s.AcademyRoute()
18
  s.CVRoute()
19
  s.MarriageReadinessProfileRoute()
20
  s.PartnerCriteriaRoute()
space/space/space/router/server.go CHANGED
@@ -1,6 +1,7 @@
1
  package router
2
 
3
  import (
 
4
  cv_controller "api.qobiltu.id/controller/cv"
5
  email_controller "api.qobiltu.id/controller/email"
6
  marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
@@ -15,6 +16,7 @@ type Server struct {
15
  regionController region_controller.RegionController
16
  optionsController options_controller.OptionsController
17
  emailController email_controller.EmailController
 
18
  cvController cv_controller.CVController
19
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController
20
  partnerCriteriaController partner_criteria_controller.PartnerCriteriaController
@@ -24,6 +26,7 @@ func NewServer(
24
  regionController region_controller.RegionController,
25
  optionsController options_controller.OptionsController,
26
  emailController email_controller.EmailController,
 
27
  cvController cv_controller.CVController,
28
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController,
29
  partnerCriteriaController partner_criteria_controller.PartnerCriteriaController,
@@ -36,6 +39,7 @@ func NewServer(
36
  regionController: regionController,
37
  optionsController: optionsController,
38
  emailController: emailController,
 
39
  cvController: cvController,
40
  marriageReadinessProfileController: marriageReadinessProfileController,
41
  partnerCriteriaController: partnerCriteriaController,
 
1
  package router
2
 
3
  import (
4
+ academy_controller "api.qobiltu.id/controller/academy"
5
  cv_controller "api.qobiltu.id/controller/cv"
6
  email_controller "api.qobiltu.id/controller/email"
7
  marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
 
16
  regionController region_controller.RegionController
17
  optionsController options_controller.OptionsController
18
  emailController email_controller.EmailController
19
+ academyController academy_controller.AcademyController
20
  cvController cv_controller.CVController
21
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController
22
  partnerCriteriaController partner_criteria_controller.PartnerCriteriaController
 
26
  regionController region_controller.RegionController,
27
  optionsController options_controller.OptionsController,
28
  emailController email_controller.EmailController,
29
+ academyController academy_controller.AcademyController,
30
  cvController cv_controller.CVController,
31
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController,
32
  partnerCriteriaController partner_criteria_controller.PartnerCriteriaController,
 
39
  regionController: regionController,
40
  optionsController: optionsController,
41
  emailController: emailController,
42
+ academyController: academyController,
43
  cvController: cvController,
44
  marriageReadinessProfileController: marriageReadinessProfileController,
45
  partnerCriteriaController: partnerCriteriaController,
space/space/space/services/academy_quiz_service.go CHANGED
@@ -32,7 +32,7 @@ func AuthorizeQuizwithAcademy(s *AttemptQuizService, next func()) {
32
  s.Exception.Message = "There is no academy with given slug!"
33
  return
34
  }
35
- quizRepo := repositories.GetQuizbyAcademyId(academyRepo.Result.ID)
36
  s.Error = quizRepo.RowsError
37
  if quizRepo.NoRecord {
38
  s.Exception.DataNotFound = true
@@ -173,7 +173,7 @@ func (s *SubmitQuizService) Create() {
173
  }
174
 
175
  func (s *QuizListService) Retrieve() {
176
- quizRepo := repositories.GetQuizbyAcademyId(s.Constructor.ID)
177
  s.Error = quizRepo.RowsError
178
  if quizRepo.NoRecord {
179
  s.Exception.DataNotFound = true
 
32
  s.Exception.Message = "There is no academy with given slug!"
33
  return
34
  }
35
+ quizRepo := repositories.GetQuizbyAcademyId(uint(academyRepo.Result.ID))
36
  s.Error = quizRepo.RowsError
37
  if quizRepo.NoRecord {
38
  s.Exception.DataNotFound = true
 
173
  }
174
 
175
  func (s *QuizListService) Retrieve() {
176
+ quizRepo := repositories.GetQuizbyAcademyId(uint(s.Constructor.ID))
177
  s.Error = quizRepo.RowsError
178
  if quizRepo.NoRecord {
179
  s.Exception.DataNotFound = true
space/space/space/services/academy_service.go CHANGED
@@ -1,150 +1,316 @@
1
- package services
2
-
3
- import (
4
- "errors"
5
-
6
- "api.qobiltu.id/models"
7
- "api.qobiltu.id/repositories"
8
- "github.com/gosimple/slug"
9
- uuid "github.com/satori/go.uuid"
10
- )
11
-
12
- type AcademyService struct {
13
- Service[models.Academy, []models.Academy]
14
- }
15
-
16
- type CreateAcademyService struct {
17
- Service[models.AllAcademyResponse, models.AllAcademyResponse]
18
- }
19
-
20
- type AcademyMaterialService struct {
21
- Service[models.Academy, []models.AcademyMaterial]
22
- }
23
-
24
- type AcademyContentService struct {
25
- Service[models.AcademyMaterial, models.AcademyContent]
26
- }
27
-
28
- type AcademyMarkReadService struct {
29
- Service[models.AcademyMaterial, models.Academy]
30
- }
31
-
32
- func castAcademyMaterials(academyId uint) []models.AcademyMaterialResponse {
33
- var ArrMaterials []models.AcademyMaterialResponse
34
- academyMaterialsRepo := repositories.GetAllAcademyMaterialsByAcademyID(academyId)
35
- for _, academyMaterial := range academyMaterialsRepo.Result {
36
- ArrMaterials = append(ArrMaterials, models.AcademyMaterialResponse{
37
- Materials: academyMaterial,
38
- Contents: repositories.GetAllAcademyContentsByMaterialID(academyMaterial.ID).Result,
39
- })
40
- }
41
- return ArrMaterials
42
- }
43
- func (s *AcademyService) Retrieve() {
44
- if s.Constructor.Slug != "" {
45
- AcademyRepo := repositories.GetAcademyDataBySlug(s.Constructor.Slug)
46
- s.Error = AcademyRepo.RowsError
47
- if AcademyRepo.NoRecord {
48
- s.Exception.Message = "Academy not found"
49
- s.Exception.DataNotFound = true
50
- return
51
- }
52
-
53
- s.Result = []models.Academy{
54
- AcademyRepo.Result,
55
- }
56
- } else {
57
- AcademyRepo := repositories.GetAllAcademy()
58
- s.Error = AcademyRepo.RowsError
59
- s.Result = AcademyRepo.Result
60
- }
61
-
62
- }
63
-
64
- func (s *CreateAcademyService) Create() {
65
- var ArrAcademy []models.AcademyResponse
66
- for _, academy := range s.Constructor.Academies {
67
- academy.Academy.UUID = uuid.NewV4()
68
- if academy.Academy.Slug == "" {
69
- academy.Academy.Slug = slug.Make(academy.Academy.Title)
70
- }
71
-
72
- createdAcademy := repositories.CreateAcademy(academy.Academy)
73
- if createdAcademy.RowsError != nil {
74
- s.Error = createdAcademy.RowsError
75
- return
76
- }
77
- for _, material := range academy.Materials {
78
- material.Materials.AcademyID = createdAcademy.Result.ID
79
- material.Materials.UUID = uuid.NewV4()
80
- if material.Materials.Slug == "" {
81
- material.Materials.Slug = slug.Make(material.Materials.Title)
82
- }
83
- createdMaterial := repositories.CreateAcademyMaterial(material.Materials)
84
- if createdMaterial.RowsError != nil {
85
- s.Error = createdMaterial.RowsError
86
- return
87
- }
88
- for _, content := range []models.AcademyContent{material.Contents} {
89
- content.UUID = uuid.NewV4()
90
- content.AcademyMaterialID = createdMaterial.Result.ID
91
- createdContent := repositories.CreateAcademyContent(content)
92
- if createdContent.RowsError != nil {
93
- s.Error = createdContent.RowsError
94
- return
95
- }
96
- ArrAcademy = append(ArrAcademy, models.AcademyResponse{
97
- Academy: createdAcademy.Result,
98
- Materials: castAcademyMaterials(createdAcademy.Result.ID),
99
- })
100
- }
101
- }
102
- }
103
- s.Result = models.AllAcademyResponse{Academies: ArrAcademy}
104
-
105
- }
106
-
107
- func (s *AcademyMaterialService) Retrieve() {
108
- academyRepo := repositories.GetAcademyDataBySlug(s.Constructor.Slug)
109
- if academyRepo.NoRecord {
110
- s.Exception.DataNotFound = true
111
- s.Exception.Message = "There is no Academy found with given Slug!"
112
- return
113
- }
114
- s.Error = academyRepo.RowsError
115
- academyMaterialRepo := repositories.GetAllAcademyMaterialsByAcademyID(academyRepo.Result.ID)
116
- s.Error = errors.Join(s.Error, academyMaterialRepo.RowsError)
117
- if academyMaterialRepo.NoRecord {
118
- s.Exception.DataNotFound = true
119
- s.Exception.Message = "There is no Academy Material with given ID"
120
- return
121
- }
122
- s.Result = academyMaterialRepo.Result
123
- }
124
-
125
- func (s *AcademyContentService) Retrieve() {
126
- academyMaterialRepo := repositories.GetAcademyMaterialBySlug(s.Constructor.Slug)
127
- s.Error = academyMaterialRepo.RowsError
128
- if academyMaterialRepo.NoRecord {
129
- s.Exception.DataNotFound = true
130
- s.Exception.Message = "There is no Academy Material with given Slug"
131
- return
132
- }
133
- academyContentRepo := repositories.GetAllAcademyContentsByMaterialID(academyMaterialRepo.Result.ID)
134
- s.Error = errors.Join(s.Error, academyContentRepo.RowsError)
135
- if academyContentRepo.NoRecord {
136
- s.Exception.DataNotFound = true
137
- s.Exception.Message = "There is no Academy Contents with given Material ID"
138
- return
139
- }
140
- s.Result = academyContentRepo.Result
141
- }
142
-
143
- func (s *AcademyMarkReadService) Update() {
144
- markReadRepo := repositories.UpdateAcademyMaterialCompletedById(s.Constructor.ID)
145
- if markReadRepo.NoRecord {
146
- s.Exception.DataNotFound = true
147
- s.Error = markReadRepo.RowsError
148
- return
149
- }
150
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package services
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "time"
7
+
8
+ "api.qobiltu.id/models"
9
+ "api.qobiltu.id/pkg/validation"
10
+ "api.qobiltu.id/repositories"
11
+ "api.qobiltu.id/response"
12
+ "github.com/gosimple/slug"
13
+ "gorm.io/gorm"
14
+ )
15
+
16
+ type AcademyService interface {
17
+ // === ADMIN ===
18
+ AdminCreateAcademy(ctx context.Context, req *models.CreateAcademyRequest) (*models.AcademyResponse, error)
19
+ AdminUpdateAcademy(ctx context.Context, req *models.UpdateAcademyRequest) (*models.AcademyResponse, error)
20
+ AdminGetAcademyByID(ctx context.Context, id int64) (*models.AcademyResponse, error)
21
+ AdminListAcademy(ctx context.Context, req *models.ListAcademyRequest) ([]models.AcademyResponse, *models.Paging, error)
22
+ AdminDeleteAcademy(ctx context.Context, id int64) error
23
+ AdminReorderAcademy(ctx context.Context, req *models.ReorderAcademyRequest) error
24
+
25
+ AdminCreateAcademyMaterial(ctx context.Context, req *models.CreateAcademyMaterialRequest) (*models.AcademyMaterial, error)
26
+ AdminUpdateAcademyMaterial(ctx context.Context, req *models.UpdateAcademyMaterialRequest) (*models.AcademyMaterial, error)
27
+ AdminGetAcademyMaterialByID(ctx context.Context, id int64) (*models.AcademyMaterial, error)
28
+ AdminListAcademyMaterial(ctx context.Context, req *models.ListAcademyMaterialRequest) ([]models.AcademyMaterial, *models.Paging, error)
29
+ AdminDeleteAcademyMaterial(ctx context.Context, id int64) error
30
+ AdminReorderAcademyMaterial(ctx context.Context, req *models.ReorderAcademyMaterialRequest) error
31
+
32
+ // === USER ===
33
+ UserListAcademy(ctx context.Context, req *models.ListAcademyRequest) ([]models.UserAcademyResponse, *models.Paging, error)
34
+ UserListAcademyMaterial(ctx context.Context, req *models.ListAcademyMaterialRequest) ([]models.UserAcademyMaterialResponse, *models.Paging, error)
35
+ UserGetAcademyBySlug(ctx context.Context, slug string) (*models.AcademyResponse, error)
36
+ UserGetAcademyMaterialBySlug(ctx context.Context, slug string, accountID int64) (*models.UserAcademyMaterialResponse, error)
37
+ UserToggleAcademyMaterialProgress(ctx context.Context, req *models.ToggleAcademyMaterialProgressRequest) error
38
+ }
39
+
40
+ type academyService struct {
41
+ academyRepository repositories.AcademyRepository
42
+ validator *validation.Validator
43
+ }
44
+
45
+ func NewAcademyService(academyRepository repositories.AcademyRepository, validator *validation.Validator) AcademyService {
46
+ return &academyService{
47
+ academyRepository: academyRepository,
48
+ validator: validator,
49
+ }
50
+ }
51
+
52
+ func (s *academyService) AdminCreateAcademy(ctx context.Context, req *models.CreateAcademyRequest) (*models.AcademyResponse, error) {
53
+ if err := s.validator.Validate(req); err != nil {
54
+ return nil, response.HandleValidationError(err)
55
+ }
56
+
57
+ academy := &models.Academy{
58
+ Title: req.Title,
59
+ Slug: slug.Make(req.Title),
60
+ Description: req.Description,
61
+ }
62
+
63
+ if req.Order == nil {
64
+ lastOrder, err := s.academyRepository.AdminGetLastOrderAcademy(ctx)
65
+ if err != nil {
66
+ return nil, err
67
+ }
68
+
69
+ academy.Order = lastOrder + 1
70
+ } else {
71
+ academy.Order = *req.Order
72
+ }
73
+
74
+ res, err := s.academyRepository.AdminSaveAcademy(ctx, academy)
75
+ if err != nil {
76
+ return nil, response.HandleGormError(err, "Internal Server Error")
77
+ }
78
+
79
+ return res, nil
80
+ }
81
+
82
+ func (s *academyService) AdminUpdateAcademy(ctx context.Context, req *models.UpdateAcademyRequest) (*models.AcademyResponse, error) {
83
+ if err := s.validator.Validate(req); err != nil {
84
+ return nil, response.HandleValidationError(err)
85
+ }
86
+
87
+ academy, err := s.academyRepository.AdminGetAcademyByID(ctx, req.ID)
88
+ if err != nil {
89
+ return nil, response.HandleGormError(err, "Internal Server Error")
90
+ }
91
+
92
+ if req.Title != nil {
93
+ academy.Title = *req.Title
94
+ academy.Slug = slug.Make(*req.Title)
95
+ }
96
+
97
+ if req.Description != nil {
98
+ academy.Description = *req.Description
99
+ }
100
+
101
+ if req.Order != nil {
102
+ academy.Order = *req.Order
103
+ }
104
+
105
+ fmt.Println("lolos")
106
+ res, err := s.academyRepository.AdminSaveAcademy(ctx, &academy.Academy)
107
+ if err != nil {
108
+ return nil, err
109
+ }
110
+
111
+ return res, nil
112
+ }
113
+
114
+ func (s *academyService) AdminGetAcademyByID(ctx context.Context, id int64) (*models.AcademyResponse, error) {
115
+ academy, err := s.academyRepository.AdminGetAcademyByID(ctx, id)
116
+ if err != nil {
117
+ return nil, response.HandleGormError(err, "Internal Server Error")
118
+ }
119
+
120
+ return academy, nil
121
+ }
122
+
123
+ func (s *academyService) AdminListAcademy(ctx context.Context, req *models.ListAcademyRequest) ([]models.AcademyResponse, *models.Paging, error) {
124
+ academies, paging, err := s.academyRepository.AdminListAcademy(ctx, req)
125
+ if err != nil {
126
+ return nil, nil, response.HandleGormError(err, "Internal Server Error")
127
+ }
128
+
129
+ return academies, paging, nil
130
+ }
131
+
132
+ func (s *academyService) AdminDeleteAcademy(ctx context.Context, id int64) error {
133
+ err := s.academyRepository.AdminDeleteAcademy(ctx, id)
134
+ if err != nil {
135
+ return response.HandleGormError(err, "Internal Server Error")
136
+ }
137
+
138
+ return nil
139
+ }
140
+
141
+ func (s *academyService) AdminReorderAcademy(ctx context.Context, req *models.ReorderAcademyRequest) error {
142
+ err := s.academyRepository.AdminReorderAcademy(ctx, req)
143
+ if err != nil {
144
+ return response.HandleGormError(err, "Internal Server Error")
145
+ }
146
+
147
+ return nil
148
+ }
149
+
150
+ func (s *academyService) AdminCreateAcademyMaterial(ctx context.Context, req *models.CreateAcademyMaterialRequest) (*models.AcademyMaterial, error) {
151
+ if err := s.validator.Validate(req); err != nil {
152
+ return nil, response.HandleValidationError(err)
153
+ }
154
+
155
+ academy, err := s.academyRepository.AdminGetAcademyByID(ctx, req.AcademyID)
156
+ if err != nil {
157
+ return nil, response.HandleGormError(gorm.ErrRecordNotFound, "Internal Server Error")
158
+ }
159
+
160
+ academyMaterial := &models.AcademyMaterial{
161
+ AcademyID: academy.ID,
162
+ Title: req.Title,
163
+ Slug: slug.Make(req.Title),
164
+ Content: req.Content,
165
+ }
166
+
167
+ if req.Order == nil {
168
+ lastOrder, err := s.academyRepository.AdminGetLastOrderAcademyMaterial(ctx)
169
+ if err != nil {
170
+ return nil, err
171
+ }
172
+
173
+ academyMaterial.Order = lastOrder + 1
174
+ } else {
175
+ academyMaterial.Order = *req.Order
176
+ }
177
+
178
+ if err := s.academyRepository.AdminSaveAcademyMaterial(ctx, academyMaterial); err != nil {
179
+ return nil, response.HandleGormError(err, "Internal Server Error")
180
+ }
181
+
182
+ return academyMaterial, nil
183
+ }
184
+
185
+ func (s *academyService) AdminUpdateAcademyMaterial(ctx context.Context, req *models.UpdateAcademyMaterialRequest) (*models.AcademyMaterial, error) {
186
+ if err := s.validator.Validate(req); err != nil {
187
+ return nil, response.HandleValidationError(err)
188
+ }
189
+
190
+ academyMaterial, err := s.academyRepository.AdminGetAcademyMaterialByID(ctx, req.ID)
191
+ if err != nil {
192
+ return nil, response.HandleGormError(err, "Internal Server Error")
193
+ }
194
+
195
+ if req.Title != nil {
196
+ academyMaterial.Title = *req.Title
197
+ academyMaterial.Slug = slug.Make(*req.Title)
198
+ }
199
+
200
+ if req.Content != nil {
201
+ academyMaterial.Content = *req.Content
202
+ }
203
+
204
+ if req.Order != nil {
205
+ academyMaterial.Order = *req.Order
206
+ }
207
+
208
+ if err := s.academyRepository.AdminSaveAcademyMaterial(ctx, academyMaterial); err != nil {
209
+ return nil, err
210
+ }
211
+
212
+ return academyMaterial, nil
213
+ }
214
+
215
+ func (s *academyService) AdminGetAcademyMaterialByID(ctx context.Context, id int64) (*models.AcademyMaterial, error) {
216
+ academyMaterial, err := s.academyRepository.AdminGetAcademyMaterialByID(ctx, id)
217
+ if err != nil {
218
+ return nil, response.HandleGormError(err, "Internal Server Error")
219
+ }
220
+
221
+ return academyMaterial, nil
222
+ }
223
+
224
+ func (s *academyService) AdminListAcademyMaterial(ctx context.Context, req *models.ListAcademyMaterialRequest) ([]models.AcademyMaterial, *models.Paging, error) {
225
+ academyMaterials, paging, err := s.academyRepository.AdminListAcademyMaterial(ctx, req)
226
+ if err != nil {
227
+ return nil, nil, response.HandleGormError(err, "Internal Server Error")
228
+ }
229
+
230
+ return academyMaterials, paging, nil
231
+ }
232
+
233
+ func (s *academyService) AdminDeleteAcademyMaterial(ctx context.Context, id int64) error {
234
+ err := s.academyRepository.AdminDeleteAcademyMaterial(ctx, id)
235
+ if err != nil {
236
+ return response.HandleGormError(err, "Internal Server Error")
237
+ }
238
+
239
+ return nil
240
+ }
241
+
242
+ func (s *academyService) AdminReorderAcademyMaterial(ctx context.Context, req *models.ReorderAcademyMaterialRequest) error {
243
+ err := s.academyRepository.AdminReorderAcademyMaterial(ctx, req)
244
+ if err != nil {
245
+ return response.HandleGormError(err, "Internal Server Error")
246
+ }
247
+
248
+ return nil
249
+ }
250
+
251
+ // === USER ===
252
+
253
+ func (s *academyService) UserListAcademy(ctx context.Context, req *models.ListAcademyRequest) ([]models.UserAcademyResponse, *models.Paging, error) {
254
+ academies, paging, err := s.academyRepository.UserListAcademy(ctx, req)
255
+ if err != nil {
256
+ return nil, nil, response.HandleGormError(err, "Internal Server Error")
257
+ }
258
+
259
+ return academies, paging, nil
260
+ }
261
+
262
+ func (s *academyService) UserListAcademyMaterial(ctx context.Context, req *models.ListAcademyMaterialRequest) ([]models.UserAcademyMaterialResponse, *models.Paging, error) {
263
+ academyMaterials, paging, err := s.academyRepository.UserListAcademyMaterial(ctx, req)
264
+ if err != nil {
265
+ return nil, nil, response.HandleGormError(err, "Internal Server Error")
266
+ }
267
+
268
+ return academyMaterials, paging, nil
269
+ }
270
+
271
+ func (s *academyService) UserGetAcademyBySlug(ctx context.Context, slug string) (*models.AcademyResponse, error) {
272
+ academy, err := s.academyRepository.UserGetAcademyBySlug(ctx, slug)
273
+ if err != nil {
274
+ return nil, response.HandleGormError(err, "Internal Server Error")
275
+ }
276
+
277
+ return academy, nil
278
+ }
279
+
280
+ func (s *academyService) UserGetAcademyMaterialBySlug(ctx context.Context, slug string, accountID int64) (*models.UserAcademyMaterialResponse, error) {
281
+ academyMaterial, err := s.academyRepository.UserGetAcademyMaterialBySlug(ctx, slug, accountID)
282
+ if err != nil {
283
+ return nil, response.HandleGormError(err, "Internal Server Error")
284
+ }
285
+
286
+ return academyMaterial, nil
287
+ }
288
+
289
+ func (s *academyService) UserToggleAcademyMaterialProgress(ctx context.Context, req *models.ToggleAcademyMaterialProgressRequest) error {
290
+
291
+ academyMaterial, err := s.academyRepository.UserGetAcademyMaterialBySlug(ctx, req.Slug, req.AccountID)
292
+ if err != nil {
293
+ return response.HandleGormError(err, "Internal Server Error")
294
+ }
295
+
296
+ if academyMaterial.IsRead {
297
+ err = s.academyRepository.UserDeleteAcademyMaterialProgress(ctx, req.AccountID, academyMaterial.ID)
298
+ if err != nil {
299
+ return response.HandleGormError(err, "Internal Server Error")
300
+ }
301
+ } else {
302
+ now := time.Now()
303
+ academyMaterialProgress := &models.AcademyMaterialProgress{
304
+ AccountID: req.AccountID,
305
+ AcademyMaterialID: academyMaterial.ID,
306
+ ReadAt: &now,
307
+ }
308
+
309
+ err = s.academyRepository.UserSaveAcademyMaterialProgress(ctx, academyMaterialProgress)
310
+ if err != nil {
311
+ return response.HandleGormError(err, "Internal Server Error")
312
+ }
313
+ }
314
+
315
+ return nil
316
+ }
space/space/space/space/space/assets/efs.go CHANGED
@@ -11,3 +11,11 @@ const (
11
  EmailConfirmationTemplatePath = "emails/email-confirmation.tmpl"
12
  EmailForgotPasswordTemplatePath = "emails/email-forgot-password.tmpl"
13
  )
 
 
 
 
 
 
 
 
 
11
  EmailConfirmationTemplatePath = "emails/email-confirmation.tmpl"
12
  EmailForgotPasswordTemplatePath = "emails/email-forgot-password.tmpl"
13
  )
14
+
15
+ var (
16
+ //go:embed "seeds/province.json"
17
+ EmbeddedProvinceJSON []byte
18
+
19
+ //go:embed "seeds/city.json"
20
+ EmbeddedCityJSON []byte
21
+ )
space/space/space/space/space/assets/seeds/city.json ADDED
@@ -0,0 +1,4114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": 1,
4
+ "type": "Kabupaten",
5
+ "name": "Aceh Barat",
6
+ "code": "05",
7
+ "full_code": "1105",
8
+ "province_id": 1
9
+ },
10
+ {
11
+ "id": 2,
12
+ "type": "Kabupaten",
13
+ "name": "Aceh Barat Daya",
14
+ "code": "12",
15
+ "full_code": "1112",
16
+ "province_id": 1
17
+ },
18
+ {
19
+ "id": 3,
20
+ "type": "Kabupaten",
21
+ "name": "Sabu Raijua",
22
+ "code": "20",
23
+ "full_code": "5320",
24
+ "province_id": 23
25
+ },
26
+ {
27
+ "id": 4,
28
+ "type": "Kota",
29
+ "name": "Salatiga",
30
+ "code": "73",
31
+ "full_code": "3373",
32
+ "province_id": 10
33
+ },
34
+ {
35
+ "id": 5,
36
+ "type": "Kabupaten",
37
+ "name": "Aceh Besar",
38
+ "code": "06",
39
+ "full_code": "1106",
40
+ "province_id": 1
41
+ },
42
+ {
43
+ "id": 6,
44
+ "type": "Kabupaten",
45
+ "name": "Aceh Jaya",
46
+ "code": "14",
47
+ "full_code": "1114",
48
+ "province_id": 1
49
+ },
50
+ {
51
+ "id": 7,
52
+ "type": "Kabupaten",
53
+ "name": "Aceh Selatan",
54
+ "code": "01",
55
+ "full_code": "1101",
56
+ "province_id": 1
57
+ },
58
+ {
59
+ "id": 8,
60
+ "type": "Kabupaten",
61
+ "name": "Aceh Singkil",
62
+ "code": "10",
63
+ "full_code": "1110",
64
+ "province_id": 1
65
+ },
66
+ {
67
+ "id": 9,
68
+ "type": "Kabupaten",
69
+ "name": "Aceh Tamiang",
70
+ "code": "16",
71
+ "full_code": "1116",
72
+ "province_id": 1
73
+ },
74
+ {
75
+ "id": 10,
76
+ "type": "Kabupaten",
77
+ "name": "Aceh Tengah",
78
+ "code": "04",
79
+ "full_code": "1104",
80
+ "province_id": 1
81
+ },
82
+ {
83
+ "id": 11,
84
+ "type": "Kabupaten",
85
+ "name": "Aceh Tenggara",
86
+ "code": "02",
87
+ "full_code": "1102",
88
+ "province_id": 1
89
+ },
90
+ {
91
+ "id": 12,
92
+ "type": "Kabupaten",
93
+ "name": "Aceh Timur",
94
+ "code": "03",
95
+ "full_code": "1103",
96
+ "province_id": 1
97
+ },
98
+ {
99
+ "id": 13,
100
+ "type": "Kabupaten",
101
+ "name": "Gorontalo Utara",
102
+ "code": "05",
103
+ "full_code": "7505",
104
+ "province_id": 7
105
+ },
106
+ {
107
+ "id": 14,
108
+ "type": "Kabupaten",
109
+ "name": "Aceh Utara",
110
+ "code": "08",
111
+ "full_code": "1108",
112
+ "province_id": 1
113
+ },
114
+ {
115
+ "id": 15,
116
+ "type": "Kabupaten",
117
+ "name": "Agam",
118
+ "code": "06",
119
+ "full_code": "1306",
120
+ "province_id": 36
121
+ },
122
+ {
123
+ "id": 16,
124
+ "type": "Kabupaten",
125
+ "name": "Alor",
126
+ "code": "05",
127
+ "full_code": "5305",
128
+ "province_id": 23
129
+ },
130
+ {
131
+ "id": 17,
132
+ "type": "Kota",
133
+ "name": "Ambon",
134
+ "code": "71",
135
+ "full_code": "8171",
136
+ "province_id": 20
137
+ },
138
+ {
139
+ "id": 18,
140
+ "type": "Kabupaten",
141
+ "name": "Gowa",
142
+ "code": "06",
143
+ "full_code": "7306",
144
+ "province_id": 32
145
+ },
146
+ {
147
+ "id": 19,
148
+ "type": "Kota",
149
+ "name": "Samarinda",
150
+ "code": "72",
151
+ "full_code": "6472",
152
+ "province_id": 15
153
+ },
154
+ {
155
+ "id": 20,
156
+ "type": "Kabupaten",
157
+ "name": "Asahan",
158
+ "code": "09",
159
+ "full_code": "1209",
160
+ "province_id": 38
161
+ },
162
+ {
163
+ "id": 21,
164
+ "type": "Kabupaten",
165
+ "name": "Sambas",
166
+ "code": "01",
167
+ "full_code": "6101",
168
+ "province_id": 12
169
+ },
170
+ {
171
+ "id": 22,
172
+ "type": "Kabupaten",
173
+ "name": "Asmat",
174
+ "code": "04",
175
+ "full_code": "9304",
176
+ "province_id": 28
177
+ },
178
+ {
179
+ "id": 23,
180
+ "type": "Kabupaten",
181
+ "name": "Samosir",
182
+ "code": "17",
183
+ "full_code": "1217",
184
+ "province_id": 38
185
+ },
186
+ {
187
+ "id": 24,
188
+ "type": "Kabupaten",
189
+ "name": "Badung",
190
+ "code": "03",
191
+ "full_code": "5103",
192
+ "province_id": 2
193
+ },
194
+ {
195
+ "id": 25,
196
+ "type": "Kabupaten",
197
+ "name": "Sampang",
198
+ "code": "27",
199
+ "full_code": "3527",
200
+ "province_id": 11
201
+ },
202
+ {
203
+ "id": 26,
204
+ "type": "Kabupaten",
205
+ "name": "Balangan",
206
+ "code": "11",
207
+ "full_code": "6311",
208
+ "province_id": 13
209
+ },
210
+ {
211
+ "id": 27,
212
+ "type": "Kabupaten",
213
+ "name": "Sanggau",
214
+ "code": "03",
215
+ "full_code": "6103",
216
+ "province_id": 12
217
+ },
218
+ {
219
+ "id": 28,
220
+ "type": "Kabupaten",
221
+ "name": "Sarmi",
222
+ "code": "10",
223
+ "full_code": "9110",
224
+ "province_id": 24
225
+ },
226
+ {
227
+ "id": 29,
228
+ "type": "Kabupaten",
229
+ "name": "Gresik",
230
+ "code": "25",
231
+ "full_code": "3525",
232
+ "province_id": 11
233
+ },
234
+ {
235
+ "id": 30,
236
+ "type": "Kabupaten",
237
+ "name": "Grobogan",
238
+ "code": "15",
239
+ "full_code": "3315",
240
+ "province_id": 10
241
+ },
242
+ {
243
+ "id": 31,
244
+ "type": "Kabupaten",
245
+ "name": "Gunung Mas",
246
+ "code": "10",
247
+ "full_code": "6210",
248
+ "province_id": 14
249
+ },
250
+ {
251
+ "id": 32,
252
+ "type": "Kabupaten",
253
+ "name": "Gunungkidul",
254
+ "code": "03",
255
+ "full_code": "3403",
256
+ "province_id": 5
257
+ },
258
+ {
259
+ "id": 33,
260
+ "type": "Kabupaten",
261
+ "name": "Sarolangun",
262
+ "code": "03",
263
+ "full_code": "1503",
264
+ "province_id": 8
265
+ },
266
+ {
267
+ "id": 34,
268
+ "type": "Kota",
269
+ "name": "Gunungsitoli",
270
+ "code": "78",
271
+ "full_code": "1278",
272
+ "province_id": 38
273
+ },
274
+ {
275
+ "id": 35,
276
+ "type": "Kota",
277
+ "name": "Sawahlunto",
278
+ "code": "73",
279
+ "full_code": "1373",
280
+ "province_id": 36
281
+ },
282
+ {
283
+ "id": 36,
284
+ "type": "Kabupaten",
285
+ "name": "Halmahera Barat",
286
+ "code": "01",
287
+ "full_code": "8201",
288
+ "province_id": 21
289
+ },
290
+ {
291
+ "id": 37,
292
+ "type": "Kabupaten",
293
+ "name": "Sekadau",
294
+ "code": "09",
295
+ "full_code": "6109",
296
+ "province_id": 12
297
+ },
298
+ {
299
+ "id": 38,
300
+ "type": "Kabupaten",
301
+ "name": "Halmahera Selatan",
302
+ "code": "04",
303
+ "full_code": "8204",
304
+ "province_id": 21
305
+ },
306
+ {
307
+ "id": 39,
308
+ "type": "Kabupaten",
309
+ "name": "Seluma",
310
+ "code": "05",
311
+ "full_code": "1705",
312
+ "province_id": 4
313
+ },
314
+ {
315
+ "id": 40,
316
+ "type": "Kabupaten",
317
+ "name": "Halmahera Tengah",
318
+ "code": "02",
319
+ "full_code": "8202",
320
+ "province_id": 21
321
+ },
322
+ {
323
+ "id": 41,
324
+ "type": "Kabupaten",
325
+ "name": "Semarang",
326
+ "code": "22",
327
+ "full_code": "3322",
328
+ "province_id": 10
329
+ },
330
+ {
331
+ "id": 42,
332
+ "type": "Kota",
333
+ "name": "Semarang",
334
+ "code": "74",
335
+ "full_code": "3374",
336
+ "province_id": 10
337
+ },
338
+ {
339
+ "id": 43,
340
+ "type": "Kabupaten",
341
+ "name": "Halmahera Timur",
342
+ "code": "06",
343
+ "full_code": "8206",
344
+ "province_id": 21
345
+ },
346
+ {
347
+ "id": 44,
348
+ "type": "Kabupaten",
349
+ "name": "Seram Bagian Barat",
350
+ "code": "06",
351
+ "full_code": "8106",
352
+ "province_id": 20
353
+ },
354
+ {
355
+ "id": 45,
356
+ "type": "Kabupaten",
357
+ "name": "Halmahera Utara",
358
+ "code": "03",
359
+ "full_code": "8203",
360
+ "province_id": 21
361
+ },
362
+ {
363
+ "id": 46,
364
+ "type": "Kabupaten",
365
+ "name": "Seram Bagian Timur",
366
+ "code": "05",
367
+ "full_code": "8105",
368
+ "province_id": 20
369
+ },
370
+ {
371
+ "id": 47,
372
+ "type": "Kabupaten",
373
+ "name": "Hulu Sungai Selatan",
374
+ "code": "06",
375
+ "full_code": "6306",
376
+ "province_id": 13
377
+ },
378
+ {
379
+ "id": 48,
380
+ "type": "Kabupaten",
381
+ "name": "Hulu Sungai Tengah",
382
+ "code": "07",
383
+ "full_code": "6307",
384
+ "province_id": 13
385
+ },
386
+ {
387
+ "id": 49,
388
+ "type": "Kabupaten",
389
+ "name": "Hulu Sungai Utara",
390
+ "code": "08",
391
+ "full_code": "6308",
392
+ "province_id": 13
393
+ },
394
+ {
395
+ "id": 50,
396
+ "type": "Kota",
397
+ "name": "Serang",
398
+ "code": "73",
399
+ "full_code": "3673",
400
+ "province_id": 3
401
+ },
402
+ {
403
+ "id": 51,
404
+ "type": "Kabupaten",
405
+ "name": "Serang",
406
+ "code": "04",
407
+ "full_code": "3604",
408
+ "province_id": 3
409
+ },
410
+ {
411
+ "id": 52,
412
+ "type": "Kabupaten",
413
+ "name": "Humbang Hasundutan",
414
+ "code": "16",
415
+ "full_code": "1216",
416
+ "province_id": 38
417
+ },
418
+ {
419
+ "id": 53,
420
+ "type": "Kota",
421
+ "name": "Balikpapan",
422
+ "code": "71",
423
+ "full_code": "6471",
424
+ "province_id": 15
425
+ },
426
+ {
427
+ "id": 54,
428
+ "type": "Kabupaten",
429
+ "name": "Indragiri Hilir",
430
+ "code": "04",
431
+ "full_code": "1404",
432
+ "province_id": 30
433
+ },
434
+ {
435
+ "id": 55,
436
+ "type": "Kabupaten",
437
+ "name": "Indragiri Hulu",
438
+ "code": "02",
439
+ "full_code": "1402",
440
+ "province_id": 30
441
+ },
442
+ {
443
+ "id": 56,
444
+ "type": "Kota",
445
+ "name": "Banda Aceh",
446
+ "code": "71",
447
+ "full_code": "1171",
448
+ "province_id": 1
449
+ },
450
+ {
451
+ "id": 57,
452
+ "type": "Kota",
453
+ "name": "Bandar Lampung",
454
+ "code": "71",
455
+ "full_code": "1871",
456
+ "province_id": 19
457
+ },
458
+ {
459
+ "id": 58,
460
+ "type": "Kota",
461
+ "name": "Bandung",
462
+ "code": "73",
463
+ "full_code": "3273",
464
+ "province_id": 9
465
+ },
466
+ {
467
+ "id": 59,
468
+ "type": "Kabupaten",
469
+ "name": "Bandung",
470
+ "code": "04",
471
+ "full_code": "3204",
472
+ "province_id": 9
473
+ },
474
+ {
475
+ "id": 60,
476
+ "type": "Kabupaten",
477
+ "name": "Bandung Barat",
478
+ "code": "17",
479
+ "full_code": "3217",
480
+ "province_id": 9
481
+ },
482
+ {
483
+ "id": 61,
484
+ "type": "Kabupaten",
485
+ "name": "Banggai",
486
+ "code": "01",
487
+ "full_code": "7201",
488
+ "province_id": 33
489
+ },
490
+ {
491
+ "id": 62,
492
+ "type": "Kabupaten",
493
+ "name": "Banggai Kepulauan",
494
+ "code": "07",
495
+ "full_code": "7207",
496
+ "province_id": 33
497
+ },
498
+ {
499
+ "id": 63,
500
+ "type": "Kabupaten",
501
+ "name": "Banggai Laut",
502
+ "code": "11",
503
+ "full_code": "7211",
504
+ "province_id": 33
505
+ },
506
+ {
507
+ "id": 64,
508
+ "type": "Kabupaten",
509
+ "name": "Bangka",
510
+ "code": "01",
511
+ "full_code": "1901",
512
+ "province_id": 17
513
+ },
514
+ {
515
+ "id": 65,
516
+ "type": "Kabupaten",
517
+ "name": "Bangka Barat",
518
+ "code": "05",
519
+ "full_code": "1905",
520
+ "province_id": 17
521
+ },
522
+ {
523
+ "id": 66,
524
+ "type": "Kabupaten",
525
+ "name": "Bangka Selatan",
526
+ "code": "03",
527
+ "full_code": "1903",
528
+ "province_id": 17
529
+ },
530
+ {
531
+ "id": 67,
532
+ "type": "Kabupaten",
533
+ "name": "Bangka Tengah",
534
+ "code": "04",
535
+ "full_code": "1904",
536
+ "province_id": 17
537
+ },
538
+ {
539
+ "id": 68,
540
+ "type": "Kabupaten",
541
+ "name": "Bangkalan",
542
+ "code": "26",
543
+ "full_code": "3526",
544
+ "province_id": 11
545
+ },
546
+ {
547
+ "id": 69,
548
+ "type": "Kabupaten",
549
+ "name": "Bangli",
550
+ "code": "06",
551
+ "full_code": "5106",
552
+ "province_id": 2
553
+ },
554
+ {
555
+ "id": 70,
556
+ "type": "Kota",
557
+ "name": "Banjar",
558
+ "code": "79",
559
+ "full_code": "3279",
560
+ "province_id": 9
561
+ },
562
+ {
563
+ "id": 71,
564
+ "type": "Kabupaten",
565
+ "name": "Banjar",
566
+ "code": "03",
567
+ "full_code": "6303",
568
+ "province_id": 13
569
+ },
570
+ {
571
+ "id": 72,
572
+ "type": "Kota",
573
+ "name": "Banjarbaru",
574
+ "code": "72",
575
+ "full_code": "6372",
576
+ "province_id": 13
577
+ },
578
+ {
579
+ "id": 73,
580
+ "type": "Kota",
581
+ "name": "Banjarmasin",
582
+ "code": "71",
583
+ "full_code": "6371",
584
+ "province_id": 13
585
+ },
586
+ {
587
+ "id": 74,
588
+ "type": "Kabupaten",
589
+ "name": "Banjarnegara",
590
+ "code": "04",
591
+ "full_code": "3304",
592
+ "province_id": 10
593
+ },
594
+ {
595
+ "id": 75,
596
+ "type": "Kabupaten",
597
+ "name": "Bantaeng",
598
+ "code": "03",
599
+ "full_code": "7303",
600
+ "province_id": 32
601
+ },
602
+ {
603
+ "id": 76,
604
+ "type": "Kabupaten",
605
+ "name": "Bantul",
606
+ "code": "02",
607
+ "full_code": "3402",
608
+ "province_id": 5
609
+ },
610
+ {
611
+ "id": 77,
612
+ "type": "Kabupaten",
613
+ "name": "Banyuasin",
614
+ "code": "07",
615
+ "full_code": "1607",
616
+ "province_id": 37
617
+ },
618
+ {
619
+ "id": 78,
620
+ "type": "Kabupaten",
621
+ "name": "Banyumas",
622
+ "code": "02",
623
+ "full_code": "3302",
624
+ "province_id": 10
625
+ },
626
+ {
627
+ "id": 79,
628
+ "type": "Kabupaten",
629
+ "name": "Banyuwangi",
630
+ "code": "10",
631
+ "full_code": "3510",
632
+ "province_id": 11
633
+ },
634
+ {
635
+ "id": 80,
636
+ "type": "Kabupaten",
637
+ "name": "Barito Kuala",
638
+ "code": "04",
639
+ "full_code": "6304",
640
+ "province_id": 13
641
+ },
642
+ {
643
+ "id": 81,
644
+ "type": "Kabupaten",
645
+ "name": "Barito Selatan",
646
+ "code": "04",
647
+ "full_code": "6204",
648
+ "province_id": 14
649
+ },
650
+ {
651
+ "id": 82,
652
+ "type": "Kabupaten",
653
+ "name": "Barito Timur",
654
+ "code": "13",
655
+ "full_code": "6213",
656
+ "province_id": 14
657
+ },
658
+ {
659
+ "id": 83,
660
+ "type": "Kabupaten",
661
+ "name": "Barito Utara",
662
+ "code": "05",
663
+ "full_code": "6205",
664
+ "province_id": 14
665
+ },
666
+ {
667
+ "id": 84,
668
+ "type": "Kabupaten",
669
+ "name": "Barru",
670
+ "code": "11",
671
+ "full_code": "7311",
672
+ "province_id": 32
673
+ },
674
+ {
675
+ "id": 85,
676
+ "type": "Kota",
677
+ "name": "Batam",
678
+ "code": "71",
679
+ "full_code": "2171",
680
+ "province_id": 18
681
+ },
682
+ {
683
+ "id": 86,
684
+ "type": "Kabupaten",
685
+ "name": "Batang",
686
+ "code": "25",
687
+ "full_code": "3325",
688
+ "province_id": 10
689
+ },
690
+ {
691
+ "id": 87,
692
+ "type": "Kabupaten",
693
+ "name": "Batanghari",
694
+ "code": "04",
695
+ "full_code": "1504",
696
+ "province_id": 8
697
+ },
698
+ {
699
+ "id": 88,
700
+ "type": "Kota",
701
+ "name": "Batu",
702
+ "code": "79",
703
+ "full_code": "3579",
704
+ "province_id": 11
705
+ },
706
+ {
707
+ "id": 89,
708
+ "type": "Kabupaten",
709
+ "name": "Batu Bara",
710
+ "code": "19",
711
+ "full_code": "1219",
712
+ "province_id": 38
713
+ },
714
+ {
715
+ "id": 90,
716
+ "type": "Kota",
717
+ "name": "Bau Bau",
718
+ "code": "72",
719
+ "full_code": "7472",
720
+ "province_id": 34
721
+ },
722
+ {
723
+ "id": 91,
724
+ "type": "Kota",
725
+ "name": "Bekasi",
726
+ "code": "75",
727
+ "full_code": "3275",
728
+ "province_id": 9
729
+ },
730
+ {
731
+ "id": 92,
732
+ "type": "Kabupaten",
733
+ "name": "Bekasi",
734
+ "code": "16",
735
+ "full_code": "3216",
736
+ "province_id": 9
737
+ },
738
+ {
739
+ "id": 93,
740
+ "type": "Kabupaten",
741
+ "name": "Belitung",
742
+ "code": "02",
743
+ "full_code": "1902",
744
+ "province_id": 17
745
+ },
746
+ {
747
+ "id": 94,
748
+ "type": "Kabupaten",
749
+ "name": "Belitung Timur",
750
+ "code": "06",
751
+ "full_code": "1906",
752
+ "province_id": 17
753
+ },
754
+ {
755
+ "id": 95,
756
+ "type": "Kabupaten",
757
+ "name": "Belu",
758
+ "code": "04",
759
+ "full_code": "5304",
760
+ "province_id": 23
761
+ },
762
+ {
763
+ "id": 96,
764
+ "type": "Kabupaten",
765
+ "name": "Bener Meriah",
766
+ "code": "17",
767
+ "full_code": "1117",
768
+ "province_id": 1
769
+ },
770
+ {
771
+ "id": 97,
772
+ "type": "Kabupaten",
773
+ "name": "Bengkalis",
774
+ "code": "03",
775
+ "full_code": "1403",
776
+ "province_id": 30
777
+ },
778
+ {
779
+ "id": 98,
780
+ "type": "Kabupaten",
781
+ "name": "Bengkayang",
782
+ "code": "07",
783
+ "full_code": "6107",
784
+ "province_id": 12
785
+ },
786
+ {
787
+ "id": 99,
788
+ "type": "Kabupaten",
789
+ "name": "Serdang Bedagai",
790
+ "code": "18",
791
+ "full_code": "1218",
792
+ "province_id": 38
793
+ },
794
+ {
795
+ "id": 100,
796
+ "type": "Kota",
797
+ "name": "Bengkulu",
798
+ "code": "71",
799
+ "full_code": "1771",
800
+ "province_id": 4
801
+ },
802
+ {
803
+ "id": 101,
804
+ "type": "Kabupaten",
805
+ "name": "Bengkulu Selatan",
806
+ "code": "01",
807
+ "full_code": "1701",
808
+ "province_id": 4
809
+ },
810
+ {
811
+ "id": 102,
812
+ "type": "Kabupaten",
813
+ "name": "Seruyan",
814
+ "code": "07",
815
+ "full_code": "6207",
816
+ "province_id": 14
817
+ },
818
+ {
819
+ "id": 103,
820
+ "type": "Kabupaten",
821
+ "name": "Indramayu",
822
+ "code": "12",
823
+ "full_code": "3212",
824
+ "province_id": 9
825
+ },
826
+ {
827
+ "id": 104,
828
+ "type": "Kabupaten",
829
+ "name": "Siak",
830
+ "code": "08",
831
+ "full_code": "1408",
832
+ "province_id": 30
833
+ },
834
+ {
835
+ "id": 105,
836
+ "type": "Kabupaten",
837
+ "name": "Intan Jaya",
838
+ "code": "07",
839
+ "full_code": "9407",
840
+ "province_id": 29
841
+ },
842
+ {
843
+ "id": 106,
844
+ "type": "Kota",
845
+ "name": "Jakarta Barat",
846
+ "code": "73",
847
+ "full_code": "3173",
848
+ "province_id": 6
849
+ },
850
+ {
851
+ "id": 107,
852
+ "type": "Kota",
853
+ "name": "Sibolga",
854
+ "code": "73",
855
+ "full_code": "1273",
856
+ "province_id": 38
857
+ },
858
+ {
859
+ "id": 108,
860
+ "type": "Kabupaten",
861
+ "name": "Bengkulu Tengah",
862
+ "code": "09",
863
+ "full_code": "1709",
864
+ "province_id": 4
865
+ },
866
+ {
867
+ "id": 109,
868
+ "type": "Kota",
869
+ "name": "Jakarta Pusat",
870
+ "code": "71",
871
+ "full_code": "3171",
872
+ "province_id": 6
873
+ },
874
+ {
875
+ "id": 110,
876
+ "type": "Kabupaten",
877
+ "name": "Sidenreng Rappang",
878
+ "code": "14",
879
+ "full_code": "7314",
880
+ "province_id": 32
881
+ },
882
+ {
883
+ "id": 111,
884
+ "type": "Kabupaten",
885
+ "name": "Bengkulu Utara",
886
+ "code": "03",
887
+ "full_code": "1703",
888
+ "province_id": 4
889
+ },
890
+ {
891
+ "id": 112,
892
+ "type": "Kota",
893
+ "name": "Jakarta Selatan",
894
+ "code": "74",
895
+ "full_code": "3174",
896
+ "province_id": 6
897
+ },
898
+ {
899
+ "id": 113,
900
+ "type": "Kabupaten",
901
+ "name": "Sidoarjo",
902
+ "code": "15",
903
+ "full_code": "3515",
904
+ "province_id": 11
905
+ },
906
+ {
907
+ "id": 114,
908
+ "type": "Kota",
909
+ "name": "Jakarta Timur",
910
+ "code": "75",
911
+ "full_code": "3175",
912
+ "province_id": 6
913
+ },
914
+ {
915
+ "id": 115,
916
+ "type": "Kabupaten",
917
+ "name": "Sigi",
918
+ "code": "10",
919
+ "full_code": "7210",
920
+ "province_id": 33
921
+ },
922
+ {
923
+ "id": 116,
924
+ "type": "Kabupaten",
925
+ "name": "Berau",
926
+ "code": "03",
927
+ "full_code": "6403",
928
+ "province_id": 15
929
+ },
930
+ {
931
+ "id": 117,
932
+ "type": "Kabupaten",
933
+ "name": "Biak Numfor",
934
+ "code": "06",
935
+ "full_code": "9106",
936
+ "province_id": 24
937
+ },
938
+ {
939
+ "id": 118,
940
+ "type": "Kabupaten",
941
+ "name": "Bima",
942
+ "code": "06",
943
+ "full_code": "5206",
944
+ "province_id": 22
945
+ },
946
+ {
947
+ "id": 119,
948
+ "type": "Kota",
949
+ "name": "Jakarta Utara",
950
+ "code": "72",
951
+ "full_code": "3172",
952
+ "province_id": 6
953
+ },
954
+ {
955
+ "id": 120,
956
+ "type": "Kota",
957
+ "name": "Bima",
958
+ "code": "72",
959
+ "full_code": "5272",
960
+ "province_id": 22
961
+ },
962
+ {
963
+ "id": 121,
964
+ "type": "Kota",
965
+ "name": "Jambi",
966
+ "code": "71",
967
+ "full_code": "1571",
968
+ "province_id": 8
969
+ },
970
+ {
971
+ "id": 122,
972
+ "type": "Kabupaten",
973
+ "name": "Sijunjung",
974
+ "code": "03",
975
+ "full_code": "1303",
976
+ "province_id": 36
977
+ },
978
+ {
979
+ "id": 123,
980
+ "type": "Kota",
981
+ "name": "Binjai",
982
+ "code": "75",
983
+ "full_code": "1275",
984
+ "province_id": 38
985
+ },
986
+ {
987
+ "id": 124,
988
+ "type": "Kabupaten",
989
+ "name": "Jayapura",
990
+ "code": "03",
991
+ "full_code": "9103",
992
+ "province_id": 24
993
+ },
994
+ {
995
+ "id": 125,
996
+ "type": "Kabupaten",
997
+ "name": "Bintan",
998
+ "code": "01",
999
+ "full_code": "2101",
1000
+ "province_id": 18
1001
+ },
1002
+ {
1003
+ "id": 126,
1004
+ "type": "Kabupaten",
1005
+ "name": "Sikka",
1006
+ "code": "07",
1007
+ "full_code": "5307",
1008
+ "province_id": 23
1009
+ },
1010
+ {
1011
+ "id": 127,
1012
+ "type": "Kabupaten",
1013
+ "name": "Bireuen",
1014
+ "code": "11",
1015
+ "full_code": "1111",
1016
+ "province_id": 1
1017
+ },
1018
+ {
1019
+ "id": 128,
1020
+ "type": "Kabupaten",
1021
+ "name": "Simalungun",
1022
+ "code": "08",
1023
+ "full_code": "1208",
1024
+ "province_id": 38
1025
+ },
1026
+ {
1027
+ "id": 129,
1028
+ "type": "Kota",
1029
+ "name": "Bitung",
1030
+ "code": "72",
1031
+ "full_code": "7172",
1032
+ "province_id": 35
1033
+ },
1034
+ {
1035
+ "id": 130,
1036
+ "type": "Kota",
1037
+ "name": "Jayapura",
1038
+ "code": "71",
1039
+ "full_code": "9171",
1040
+ "province_id": 24
1041
+ },
1042
+ {
1043
+ "id": 131,
1044
+ "type": "Kabupaten",
1045
+ "name": "Simeulue",
1046
+ "code": "09",
1047
+ "full_code": "1109",
1048
+ "province_id": 1
1049
+ },
1050
+ {
1051
+ "id": 132,
1052
+ "type": "Kabupaten",
1053
+ "name": "Jayawijaya",
1054
+ "code": "01",
1055
+ "full_code": "9501",
1056
+ "province_id": 27
1057
+ },
1058
+ {
1059
+ "id": 133,
1060
+ "type": "Kabupaten",
1061
+ "name": "Jember",
1062
+ "code": "09",
1063
+ "full_code": "3509",
1064
+ "province_id": 11
1065
+ },
1066
+ {
1067
+ "id": 134,
1068
+ "type": "Kota",
1069
+ "name": "Singkawang",
1070
+ "code": "72",
1071
+ "full_code": "6172",
1072
+ "province_id": 12
1073
+ },
1074
+ {
1075
+ "id": 135,
1076
+ "type": "Kabupaten",
1077
+ "name": "Blitar",
1078
+ "code": "05",
1079
+ "full_code": "3505",
1080
+ "province_id": 11
1081
+ },
1082
+ {
1083
+ "id": 136,
1084
+ "type": "Kabupaten",
1085
+ "name": "Jembrana",
1086
+ "code": "01",
1087
+ "full_code": "5101",
1088
+ "province_id": 2
1089
+ },
1090
+ {
1091
+ "id": 137,
1092
+ "type": "Kabupaten",
1093
+ "name": "Sinjai",
1094
+ "code": "07",
1095
+ "full_code": "7307",
1096
+ "province_id": 32
1097
+ },
1098
+ {
1099
+ "id": 138,
1100
+ "type": "Kota",
1101
+ "name": "Blitar",
1102
+ "code": "72",
1103
+ "full_code": "3572",
1104
+ "province_id": 11
1105
+ },
1106
+ {
1107
+ "id": 139,
1108
+ "type": "Kabupaten",
1109
+ "name": "Jeneponto",
1110
+ "code": "04",
1111
+ "full_code": "7304",
1112
+ "province_id": 32
1113
+ },
1114
+ {
1115
+ "id": 140,
1116
+ "type": "Kabupaten",
1117
+ "name": "Blora",
1118
+ "code": "16",
1119
+ "full_code": "3316",
1120
+ "province_id": 10
1121
+ },
1122
+ {
1123
+ "id": 141,
1124
+ "type": "Kabupaten",
1125
+ "name": "Jepara",
1126
+ "code": "20",
1127
+ "full_code": "3320",
1128
+ "province_id": 10
1129
+ },
1130
+ {
1131
+ "id": 142,
1132
+ "type": "Kabupaten",
1133
+ "name": "Sintang",
1134
+ "code": "05",
1135
+ "full_code": "6105",
1136
+ "province_id": 12
1137
+ },
1138
+ {
1139
+ "id": 143,
1140
+ "type": "Kabupaten",
1141
+ "name": "Boalemo",
1142
+ "code": "02",
1143
+ "full_code": "7502",
1144
+ "province_id": 7
1145
+ },
1146
+ {
1147
+ "id": 144,
1148
+ "type": "Kabupaten",
1149
+ "name": "Jombang",
1150
+ "code": "17",
1151
+ "full_code": "3517",
1152
+ "province_id": 11
1153
+ },
1154
+ {
1155
+ "id": 145,
1156
+ "type": "Kabupaten",
1157
+ "name": "Bogor",
1158
+ "code": "01",
1159
+ "full_code": "3201",
1160
+ "province_id": 9
1161
+ },
1162
+ {
1163
+ "id": 146,
1164
+ "type": "Kabupaten",
1165
+ "name": "Situbondo",
1166
+ "code": "12",
1167
+ "full_code": "3512",
1168
+ "province_id": 11
1169
+ },
1170
+ {
1171
+ "id": 147,
1172
+ "type": "Kabupaten",
1173
+ "name": "Kaimana",
1174
+ "code": "08",
1175
+ "full_code": "9208",
1176
+ "province_id": 25
1177
+ },
1178
+ {
1179
+ "id": 148,
1180
+ "type": "Kota",
1181
+ "name": "Bogor",
1182
+ "code": "71",
1183
+ "full_code": "3271",
1184
+ "province_id": 9
1185
+ },
1186
+ {
1187
+ "id": 149,
1188
+ "type": "Kabupaten",
1189
+ "name": "Sleman",
1190
+ "code": "04",
1191
+ "full_code": "3404",
1192
+ "province_id": 5
1193
+ },
1194
+ {
1195
+ "id": 150,
1196
+ "type": "Kabupaten",
1197
+ "name": "Kampar",
1198
+ "code": "01",
1199
+ "full_code": "1401",
1200
+ "province_id": 30
1201
+ },
1202
+ {
1203
+ "id": 151,
1204
+ "type": "Kabupaten",
1205
+ "name": "Bojonegoro",
1206
+ "code": "22",
1207
+ "full_code": "3522",
1208
+ "province_id": 11
1209
+ },
1210
+ {
1211
+ "id": 152,
1212
+ "type": "Kabupaten",
1213
+ "name": "Solok",
1214
+ "code": "02",
1215
+ "full_code": "1302",
1216
+ "province_id": 36
1217
+ },
1218
+ {
1219
+ "id": 153,
1220
+ "type": "Kabupaten",
1221
+ "name": "Bolaang Mongondow",
1222
+ "code": "01",
1223
+ "full_code": "7101",
1224
+ "province_id": 35
1225
+ },
1226
+ {
1227
+ "id": 154,
1228
+ "type": "Kabupaten",
1229
+ "name": "Bolaang Mongondow Selatan",
1230
+ "code": "11",
1231
+ "full_code": "7111",
1232
+ "province_id": 35
1233
+ },
1234
+ {
1235
+ "id": 155,
1236
+ "type": "Kota",
1237
+ "name": "Solok",
1238
+ "code": "72",
1239
+ "full_code": "1372",
1240
+ "province_id": 36
1241
+ },
1242
+ {
1243
+ "id": 156,
1244
+ "type": "Kabupaten",
1245
+ "name": "Kapuas",
1246
+ "code": "03",
1247
+ "full_code": "6203",
1248
+ "province_id": 14
1249
+ },
1250
+ {
1251
+ "id": 157,
1252
+ "type": "Kabupaten",
1253
+ "name": "Solok Selatan",
1254
+ "code": "11",
1255
+ "full_code": "1311",
1256
+ "province_id": 36
1257
+ },
1258
+ {
1259
+ "id": 158,
1260
+ "type": "Kabupaten",
1261
+ "name": "Bolaang Mongondow Timur",
1262
+ "code": "10",
1263
+ "full_code": "7110",
1264
+ "province_id": 35
1265
+ },
1266
+ {
1267
+ "id": 159,
1268
+ "type": "Kabupaten",
1269
+ "name": "Kapuas Hulu",
1270
+ "code": "06",
1271
+ "full_code": "6106",
1272
+ "province_id": 12
1273
+ },
1274
+ {
1275
+ "id": 160,
1276
+ "type": "Kabupaten",
1277
+ "name": "Karanganyar",
1278
+ "code": "13",
1279
+ "full_code": "3313",
1280
+ "province_id": 10
1281
+ },
1282
+ {
1283
+ "id": 161,
1284
+ "type": "Kabupaten",
1285
+ "name": "Karangasem",
1286
+ "code": "07",
1287
+ "full_code": "5107",
1288
+ "province_id": 2
1289
+ },
1290
+ {
1291
+ "id": 162,
1292
+ "type": "Kabupaten",
1293
+ "name": "Bolaang Mongondow Utara",
1294
+ "code": "08",
1295
+ "full_code": "7108",
1296
+ "province_id": 35
1297
+ },
1298
+ {
1299
+ "id": 163,
1300
+ "type": "Kabupaten",
1301
+ "name": "Karawang",
1302
+ "code": "15",
1303
+ "full_code": "3215",
1304
+ "province_id": 9
1305
+ },
1306
+ {
1307
+ "id": 164,
1308
+ "type": "Kabupaten",
1309
+ "name": "Bombana",
1310
+ "code": "06",
1311
+ "full_code": "7406",
1312
+ "province_id": 34
1313
+ },
1314
+ {
1315
+ "id": 165,
1316
+ "type": "Kabupaten",
1317
+ "name": "Karimun",
1318
+ "code": "02",
1319
+ "full_code": "2102",
1320
+ "province_id": 18
1321
+ },
1322
+ {
1323
+ "id": 166,
1324
+ "type": "Kabupaten",
1325
+ "name": "Bondowoso",
1326
+ "code": "11",
1327
+ "full_code": "3511",
1328
+ "province_id": 11
1329
+ },
1330
+ {
1331
+ "id": 167,
1332
+ "type": "Kabupaten",
1333
+ "name": "Karo",
1334
+ "code": "06",
1335
+ "full_code": "1206",
1336
+ "province_id": 38
1337
+ },
1338
+ {
1339
+ "id": 168,
1340
+ "type": "Kabupaten",
1341
+ "name": "Soppeng",
1342
+ "code": "12",
1343
+ "full_code": "7312",
1344
+ "province_id": 32
1345
+ },
1346
+ {
1347
+ "id": 169,
1348
+ "type": "Kabupaten",
1349
+ "name": "Katingan",
1350
+ "code": "06",
1351
+ "full_code": "6206",
1352
+ "province_id": 14
1353
+ },
1354
+ {
1355
+ "id": 170,
1356
+ "type": "Kabupaten",
1357
+ "name": "Kaur",
1358
+ "code": "04",
1359
+ "full_code": "1704",
1360
+ "province_id": 4
1361
+ },
1362
+ {
1363
+ "id": 171,
1364
+ "type": "Kabupaten",
1365
+ "name": "Sorong",
1366
+ "code": "01",
1367
+ "full_code": "9201",
1368
+ "province_id": 26
1369
+ },
1370
+ {
1371
+ "id": 172,
1372
+ "type": "Kabupaten",
1373
+ "name": "Kayong Utara",
1374
+ "code": "11",
1375
+ "full_code": "6111",
1376
+ "province_id": 12
1377
+ },
1378
+ {
1379
+ "id": 173,
1380
+ "type": "Kota",
1381
+ "name": "Sorong",
1382
+ "code": "71",
1383
+ "full_code": "9271",
1384
+ "province_id": 26
1385
+ },
1386
+ {
1387
+ "id": 174,
1388
+ "type": "Kabupaten",
1389
+ "name": "Sorong Selatan",
1390
+ "code": "04",
1391
+ "full_code": "9204",
1392
+ "province_id": 26
1393
+ },
1394
+ {
1395
+ "id": 175,
1396
+ "type": "Kabupaten",
1397
+ "name": "Kebumen",
1398
+ "code": "05",
1399
+ "full_code": "3305",
1400
+ "province_id": 10
1401
+ },
1402
+ {
1403
+ "id": 176,
1404
+ "type": "Kabupaten",
1405
+ "name": "Sragen",
1406
+ "code": "14",
1407
+ "full_code": "3314",
1408
+ "province_id": 10
1409
+ },
1410
+ {
1411
+ "id": 177,
1412
+ "type": "Kabupaten",
1413
+ "name": "Kediri",
1414
+ "code": "06",
1415
+ "full_code": "3506",
1416
+ "province_id": 11
1417
+ },
1418
+ {
1419
+ "id": 178,
1420
+ "type": "Kabupaten",
1421
+ "name": "Subang",
1422
+ "code": "13",
1423
+ "full_code": "3213",
1424
+ "province_id": 9
1425
+ },
1426
+ {
1427
+ "id": 179,
1428
+ "type": "Kota",
1429
+ "name": "Subulussalam",
1430
+ "code": "75",
1431
+ "full_code": "1175",
1432
+ "province_id": 1
1433
+ },
1434
+ {
1435
+ "id": 180,
1436
+ "type": "Kota",
1437
+ "name": "Sukabumi",
1438
+ "code": "72",
1439
+ "full_code": "3272",
1440
+ "province_id": 9
1441
+ },
1442
+ {
1443
+ "id": 181,
1444
+ "type": "Kota",
1445
+ "name": "Kediri",
1446
+ "code": "71",
1447
+ "full_code": "3571",
1448
+ "province_id": 11
1449
+ },
1450
+ {
1451
+ "id": 182,
1452
+ "type": "Kabupaten",
1453
+ "name": "Bone",
1454
+ "code": "08",
1455
+ "full_code": "7308",
1456
+ "province_id": 32
1457
+ },
1458
+ {
1459
+ "id": 183,
1460
+ "type": "Kabupaten",
1461
+ "name": "Keerom",
1462
+ "code": "11",
1463
+ "full_code": "9111",
1464
+ "province_id": 24
1465
+ },
1466
+ {
1467
+ "id": 184,
1468
+ "type": "Kabupaten",
1469
+ "name": "Sukabumi",
1470
+ "code": "02",
1471
+ "full_code": "3202",
1472
+ "province_id": 9
1473
+ },
1474
+ {
1475
+ "id": 185,
1476
+ "type": "Kabupaten",
1477
+ "name": "Bone Bolango",
1478
+ "code": "03",
1479
+ "full_code": "7503",
1480
+ "province_id": 7
1481
+ },
1482
+ {
1483
+ "id": 186,
1484
+ "type": "Kabupaten",
1485
+ "name": "Kendal",
1486
+ "code": "24",
1487
+ "full_code": "3324",
1488
+ "province_id": 10
1489
+ },
1490
+ {
1491
+ "id": 187,
1492
+ "type": "Kota",
1493
+ "name": "Bontang",
1494
+ "code": "74",
1495
+ "full_code": "6474",
1496
+ "province_id": 15
1497
+ },
1498
+ {
1499
+ "id": 188,
1500
+ "type": "Kota",
1501
+ "name": "Kendari",
1502
+ "code": "71",
1503
+ "full_code": "7471",
1504
+ "province_id": 34
1505
+ },
1506
+ {
1507
+ "id": 189,
1508
+ "type": "Kabupaten",
1509
+ "name": "Boven Digoel",
1510
+ "code": "02",
1511
+ "full_code": "9302",
1512
+ "province_id": 28
1513
+ },
1514
+ {
1515
+ "id": 190,
1516
+ "type": "Kabupaten",
1517
+ "name": "Kepahiang",
1518
+ "code": "08",
1519
+ "full_code": "1708",
1520
+ "province_id": 4
1521
+ },
1522
+ {
1523
+ "id": 191,
1524
+ "type": "Kabupaten",
1525
+ "name": "Boyolali",
1526
+ "code": "09",
1527
+ "full_code": "3309",
1528
+ "province_id": 10
1529
+ },
1530
+ {
1531
+ "id": 192,
1532
+ "type": "Kabupaten",
1533
+ "name": "Sukamara",
1534
+ "code": "08",
1535
+ "full_code": "6208",
1536
+ "province_id": 14
1537
+ },
1538
+ {
1539
+ "id": 193,
1540
+ "type": "Kabupaten",
1541
+ "name": "Kepulauan Anambas",
1542
+ "code": "05",
1543
+ "full_code": "2105",
1544
+ "province_id": 18
1545
+ },
1546
+ {
1547
+ "id": 194,
1548
+ "type": "Kabupaten",
1549
+ "name": "Sukoharjo",
1550
+ "code": "11",
1551
+ "full_code": "3311",
1552
+ "province_id": 10
1553
+ },
1554
+ {
1555
+ "id": 195,
1556
+ "type": "Kabupaten",
1557
+ "name": "Sumba Barat",
1558
+ "code": "12",
1559
+ "full_code": "5312",
1560
+ "province_id": 23
1561
+ },
1562
+ {
1563
+ "id": 196,
1564
+ "type": "Kabupaten",
1565
+ "name": "Brebes",
1566
+ "code": "29",
1567
+ "full_code": "3329",
1568
+ "province_id": 10
1569
+ },
1570
+ {
1571
+ "id": 197,
1572
+ "type": "Kota",
1573
+ "name": "Bukittinggi",
1574
+ "code": "75",
1575
+ "full_code": "1375",
1576
+ "province_id": 36
1577
+ },
1578
+ {
1579
+ "id": 198,
1580
+ "type": "Kabupaten",
1581
+ "name": "Buleleng",
1582
+ "code": "08",
1583
+ "full_code": "5108",
1584
+ "province_id": 2
1585
+ },
1586
+ {
1587
+ "id": 199,
1588
+ "type": "Kabupaten",
1589
+ "name": "Bulukumba",
1590
+ "code": "02",
1591
+ "full_code": "7302",
1592
+ "province_id": 32
1593
+ },
1594
+ {
1595
+ "id": 200,
1596
+ "type": "Kabupaten",
1597
+ "name": "Sumba Barat Daya",
1598
+ "code": "18",
1599
+ "full_code": "5318",
1600
+ "province_id": 23
1601
+ },
1602
+ {
1603
+ "id": 201,
1604
+ "type": "Kabupaten",
1605
+ "name": "Sumba Tengah",
1606
+ "code": "17",
1607
+ "full_code": "5317",
1608
+ "province_id": 23
1609
+ },
1610
+ {
1611
+ "id": 202,
1612
+ "type": "Kabupaten",
1613
+ "name": "Sumba Timur",
1614
+ "code": "11",
1615
+ "full_code": "5311",
1616
+ "province_id": 23
1617
+ },
1618
+ {
1619
+ "id": 203,
1620
+ "type": "Kabupaten",
1621
+ "name": "Bulungan",
1622
+ "code": "01",
1623
+ "full_code": "6501",
1624
+ "province_id": 16
1625
+ },
1626
+ {
1627
+ "id": 204,
1628
+ "type": "Kabupaten",
1629
+ "name": "Sumbawa",
1630
+ "code": "04",
1631
+ "full_code": "5204",
1632
+ "province_id": 22
1633
+ },
1634
+ {
1635
+ "id": 205,
1636
+ "type": "Kabupaten",
1637
+ "name": "Bungo",
1638
+ "code": "08",
1639
+ "full_code": "1508",
1640
+ "province_id": 8
1641
+ },
1642
+ {
1643
+ "id": 206,
1644
+ "type": "Kabupaten",
1645
+ "name": "Sumbawa Barat",
1646
+ "code": "07",
1647
+ "full_code": "5207",
1648
+ "province_id": 22
1649
+ },
1650
+ {
1651
+ "id": 207,
1652
+ "type": "Kabupaten",
1653
+ "name": "Buol",
1654
+ "code": "05",
1655
+ "full_code": "7205",
1656
+ "province_id": 33
1657
+ },
1658
+ {
1659
+ "id": 208,
1660
+ "type": "Kabupaten",
1661
+ "name": "Sumedang",
1662
+ "code": "11",
1663
+ "full_code": "3211",
1664
+ "province_id": 9
1665
+ },
1666
+ {
1667
+ "id": 209,
1668
+ "type": "Kabupaten",
1669
+ "name": "Buru",
1670
+ "code": "04",
1671
+ "full_code": "8104",
1672
+ "province_id": 20
1673
+ },
1674
+ {
1675
+ "id": 210,
1676
+ "type": "Kabupaten",
1677
+ "name": "Buru Selatan",
1678
+ "code": "09",
1679
+ "full_code": "8109",
1680
+ "province_id": 20
1681
+ },
1682
+ {
1683
+ "id": 211,
1684
+ "type": "Kabupaten",
1685
+ "name": "Kepulauan Aru",
1686
+ "code": "07",
1687
+ "full_code": "8107",
1688
+ "province_id": 20
1689
+ },
1690
+ {
1691
+ "id": 212,
1692
+ "type": "Kabupaten",
1693
+ "name": "Sumenep",
1694
+ "code": "29",
1695
+ "full_code": "3529",
1696
+ "province_id": 11
1697
+ },
1698
+ {
1699
+ "id": 213,
1700
+ "type": "Kabupaten",
1701
+ "name": "Buton",
1702
+ "code": "04",
1703
+ "full_code": "7404",
1704
+ "province_id": 34
1705
+ },
1706
+ {
1707
+ "id": 214,
1708
+ "type": "Kota",
1709
+ "name": "Sungai Penuh",
1710
+ "code": "72",
1711
+ "full_code": "1572",
1712
+ "province_id": 8
1713
+ },
1714
+ {
1715
+ "id": 215,
1716
+ "type": "Kabupaten",
1717
+ "name": "Buton Selatan",
1718
+ "code": "15",
1719
+ "full_code": "7415",
1720
+ "province_id": 34
1721
+ },
1722
+ {
1723
+ "id": 216,
1724
+ "type": "Kabupaten",
1725
+ "name": "Supiori",
1726
+ "code": "19",
1727
+ "full_code": "9119",
1728
+ "province_id": 24
1729
+ },
1730
+ {
1731
+ "id": 217,
1732
+ "type": "Kabupaten",
1733
+ "name": "Buton Tengah",
1734
+ "code": "14",
1735
+ "full_code": "7414",
1736
+ "province_id": 34
1737
+ },
1738
+ {
1739
+ "id": 218,
1740
+ "type": "Kabupaten",
1741
+ "name": "Buton Utara",
1742
+ "code": "10",
1743
+ "full_code": "7410",
1744
+ "province_id": 34
1745
+ },
1746
+ {
1747
+ "id": 219,
1748
+ "type": "Kota",
1749
+ "name": "Surabaya",
1750
+ "code": "78",
1751
+ "full_code": "3578",
1752
+ "province_id": 11
1753
+ },
1754
+ {
1755
+ "id": 220,
1756
+ "type": "Kabupaten",
1757
+ "name": "Kepulauan Mentawai",
1758
+ "code": "09",
1759
+ "full_code": "1309",
1760
+ "province_id": 36
1761
+ },
1762
+ {
1763
+ "id": 221,
1764
+ "type": "Kabupaten",
1765
+ "name": "Ciamis",
1766
+ "code": "07",
1767
+ "full_code": "3207",
1768
+ "province_id": 9
1769
+ },
1770
+ {
1771
+ "id": 222,
1772
+ "type": "Kota",
1773
+ "name": "Surakarta",
1774
+ "code": "72",
1775
+ "full_code": "3372",
1776
+ "province_id": 10
1777
+ },
1778
+ {
1779
+ "id": 223,
1780
+ "type": "Kabupaten",
1781
+ "name": "Kepulauan Meranti",
1782
+ "code": "10",
1783
+ "full_code": "1410",
1784
+ "province_id": 30
1785
+ },
1786
+ {
1787
+ "id": 224,
1788
+ "type": "Kabupaten",
1789
+ "name": "Cianjur",
1790
+ "code": "03",
1791
+ "full_code": "3203",
1792
+ "province_id": 9
1793
+ },
1794
+ {
1795
+ "id": 225,
1796
+ "type": "Kabupaten",
1797
+ "name": "Tabalong",
1798
+ "code": "09",
1799
+ "full_code": "6309",
1800
+ "province_id": 13
1801
+ },
1802
+ {
1803
+ "id": 226,
1804
+ "type": "Kabupaten",
1805
+ "name": "Kepulauan Sangihe",
1806
+ "code": "03",
1807
+ "full_code": "7103",
1808
+ "province_id": 35
1809
+ },
1810
+ {
1811
+ "id": 227,
1812
+ "type": "Kabupaten",
1813
+ "name": "Kepulauan Selayar",
1814
+ "code": "01",
1815
+ "full_code": "7301",
1816
+ "province_id": 32
1817
+ },
1818
+ {
1819
+ "id": 228,
1820
+ "type": "Kabupaten",
1821
+ "name": "Cilacap",
1822
+ "code": "01",
1823
+ "full_code": "3301",
1824
+ "province_id": 10
1825
+ },
1826
+ {
1827
+ "id": 229,
1828
+ "type": "Kota",
1829
+ "name": "Cilegon",
1830
+ "code": "72",
1831
+ "full_code": "3672",
1832
+ "province_id": 3
1833
+ },
1834
+ {
1835
+ "id": 230,
1836
+ "type": "Kabupaten",
1837
+ "name": "Tabanan",
1838
+ "code": "02",
1839
+ "full_code": "5102",
1840
+ "province_id": 2
1841
+ },
1842
+ {
1843
+ "id": 231,
1844
+ "type": "Kota",
1845
+ "name": "Cimahi",
1846
+ "code": "77",
1847
+ "full_code": "3277",
1848
+ "province_id": 9
1849
+ },
1850
+ {
1851
+ "id": 232,
1852
+ "type": "Kabupaten",
1853
+ "name": "Kepulauan Seribu",
1854
+ "code": "01",
1855
+ "full_code": "3101",
1856
+ "province_id": 6
1857
+ },
1858
+ {
1859
+ "id": 233,
1860
+ "type": "Kabupaten",
1861
+ "name": "Takalar",
1862
+ "code": "05",
1863
+ "full_code": "7305",
1864
+ "province_id": 32
1865
+ },
1866
+ {
1867
+ "id": 234,
1868
+ "type": "Kota",
1869
+ "name": "Cirebon",
1870
+ "code": "74",
1871
+ "full_code": "3274",
1872
+ "province_id": 9
1873
+ },
1874
+ {
1875
+ "id": 235,
1876
+ "type": "Kabupaten",
1877
+ "name": "Kepulauan Siau Tagulandang Biaro (Sitaro)",
1878
+ "code": "09",
1879
+ "full_code": "7109",
1880
+ "province_id": 35
1881
+ },
1882
+ {
1883
+ "id": 236,
1884
+ "type": "Kabupaten",
1885
+ "name": "Kepulauan Sula",
1886
+ "code": "05",
1887
+ "full_code": "8205",
1888
+ "province_id": 21
1889
+ },
1890
+ {
1891
+ "id": 237,
1892
+ "type": "Kabupaten",
1893
+ "name": "Tambrauw",
1894
+ "code": "09",
1895
+ "full_code": "9209",
1896
+ "province_id": 26
1897
+ },
1898
+ {
1899
+ "id": 238,
1900
+ "type": "Kabupaten",
1901
+ "name": "Kepulauan Talaud",
1902
+ "code": "04",
1903
+ "full_code": "7104",
1904
+ "province_id": 35
1905
+ },
1906
+ {
1907
+ "id": 239,
1908
+ "type": "Kabupaten",
1909
+ "name": "Kepulauan Tanimbar (Maluku Tenggara Barat)",
1910
+ "code": "03",
1911
+ "full_code": "8103",
1912
+ "province_id": 20
1913
+ },
1914
+ {
1915
+ "id": 240,
1916
+ "type": "Kabupaten",
1917
+ "name": "Cirebon",
1918
+ "code": "09",
1919
+ "full_code": "3209",
1920
+ "province_id": 9
1921
+ },
1922
+ {
1923
+ "id": 241,
1924
+ "type": "Kabupaten",
1925
+ "name": "Dairi",
1926
+ "code": "11",
1927
+ "full_code": "1211",
1928
+ "province_id": 38
1929
+ },
1930
+ {
1931
+ "id": 242,
1932
+ "type": "Kabupaten",
1933
+ "name": "Tana Tidung",
1934
+ "code": "04",
1935
+ "full_code": "6504",
1936
+ "province_id": 16
1937
+ },
1938
+ {
1939
+ "id": 243,
1940
+ "type": "Kabupaten",
1941
+ "name": "Deiyai",
1942
+ "code": "08",
1943
+ "full_code": "9408",
1944
+ "province_id": 29
1945
+ },
1946
+ {
1947
+ "id": 244,
1948
+ "type": "Kabupaten",
1949
+ "name": "Kepulauan Yapen",
1950
+ "code": "05",
1951
+ "full_code": "9105",
1952
+ "province_id": 24
1953
+ },
1954
+ {
1955
+ "id": 245,
1956
+ "type": "Kabupaten",
1957
+ "name": "Deli Serdang",
1958
+ "code": "07",
1959
+ "full_code": "1207",
1960
+ "province_id": 38
1961
+ },
1962
+ {
1963
+ "id": 246,
1964
+ "type": "Kabupaten",
1965
+ "name": "Tana Toraja",
1966
+ "code": "18",
1967
+ "full_code": "7318",
1968
+ "province_id": 32
1969
+ },
1970
+ {
1971
+ "id": 247,
1972
+ "type": "Kabupaten",
1973
+ "name": "Demak",
1974
+ "code": "21",
1975
+ "full_code": "3321",
1976
+ "province_id": 10
1977
+ },
1978
+ {
1979
+ "id": 248,
1980
+ "type": "Kabupaten",
1981
+ "name": "Kerinci",
1982
+ "code": "01",
1983
+ "full_code": "1501",
1984
+ "province_id": 8
1985
+ },
1986
+ {
1987
+ "id": 249,
1988
+ "type": "Kabupaten",
1989
+ "name": "Tanah Bumbu",
1990
+ "code": "10",
1991
+ "full_code": "6310",
1992
+ "province_id": 13
1993
+ },
1994
+ {
1995
+ "id": 250,
1996
+ "type": "Kota",
1997
+ "name": "Denpasar",
1998
+ "code": "71",
1999
+ "full_code": "5171",
2000
+ "province_id": 2
2001
+ },
2002
+ {
2003
+ "id": 251,
2004
+ "type": "Kabupaten",
2005
+ "name": "Ketapang",
2006
+ "code": "04",
2007
+ "full_code": "6104",
2008
+ "province_id": 12
2009
+ },
2010
+ {
2011
+ "id": 252,
2012
+ "type": "Kota",
2013
+ "name": "Depok",
2014
+ "code": "76",
2015
+ "full_code": "3276",
2016
+ "province_id": 9
2017
+ },
2018
+ {
2019
+ "id": 253,
2020
+ "type": "Kabupaten",
2021
+ "name": "Tanah Datar",
2022
+ "code": "04",
2023
+ "full_code": "1304",
2024
+ "province_id": 36
2025
+ },
2026
+ {
2027
+ "id": 254,
2028
+ "type": "Kabupaten",
2029
+ "name": "Klaten",
2030
+ "code": "10",
2031
+ "full_code": "3310",
2032
+ "province_id": 10
2033
+ },
2034
+ {
2035
+ "id": 255,
2036
+ "type": "Kabupaten",
2037
+ "name": "Dharmasraya",
2038
+ "code": "10",
2039
+ "full_code": "1310",
2040
+ "province_id": 36
2041
+ },
2042
+ {
2043
+ "id": 256,
2044
+ "type": "Kabupaten",
2045
+ "name": "Tanah Laut",
2046
+ "code": "01",
2047
+ "full_code": "6301",
2048
+ "province_id": 13
2049
+ },
2050
+ {
2051
+ "id": 257,
2052
+ "type": "Kabupaten",
2053
+ "name": "Dogiyai",
2054
+ "code": "06",
2055
+ "full_code": "9406",
2056
+ "province_id": 29
2057
+ },
2058
+ {
2059
+ "id": 258,
2060
+ "type": "Kabupaten",
2061
+ "name": "Klungkung",
2062
+ "code": "05",
2063
+ "full_code": "5105",
2064
+ "province_id": 2
2065
+ },
2066
+ {
2067
+ "id": 259,
2068
+ "type": "Kabupaten",
2069
+ "name": "Kolaka",
2070
+ "code": "01",
2071
+ "full_code": "7401",
2072
+ "province_id": 34
2073
+ },
2074
+ {
2075
+ "id": 260,
2076
+ "type": "Kota",
2077
+ "name": "Tangerang",
2078
+ "code": "71",
2079
+ "full_code": "3671",
2080
+ "province_id": 3
2081
+ },
2082
+ {
2083
+ "id": 261,
2084
+ "type": "Kabupaten",
2085
+ "name": "Kolaka Timur",
2086
+ "code": "11",
2087
+ "full_code": "7411",
2088
+ "province_id": 34
2089
+ },
2090
+ {
2091
+ "id": 262,
2092
+ "type": "Kabupaten",
2093
+ "name": "Tangerang",
2094
+ "code": "03",
2095
+ "full_code": "3603",
2096
+ "province_id": 3
2097
+ },
2098
+ {
2099
+ "id": 263,
2100
+ "type": "Kota",
2101
+ "name": "Tangerang Selatan",
2102
+ "code": "74",
2103
+ "full_code": "3674",
2104
+ "province_id": 3
2105
+ },
2106
+ {
2107
+ "id": 264,
2108
+ "type": "Kabupaten",
2109
+ "name": "Tanggamus",
2110
+ "code": "06",
2111
+ "full_code": "1806",
2112
+ "province_id": 19
2113
+ },
2114
+ {
2115
+ "id": 265,
2116
+ "type": "Kabupaten",
2117
+ "name": "Kolaka Utara",
2118
+ "code": "08",
2119
+ "full_code": "7408",
2120
+ "province_id": 34
2121
+ },
2122
+ {
2123
+ "id": 266,
2124
+ "type": "Kabupaten",
2125
+ "name": "Konawe",
2126
+ "code": "02",
2127
+ "full_code": "7402",
2128
+ "province_id": 34
2129
+ },
2130
+ {
2131
+ "id": 267,
2132
+ "type": "Kabupaten",
2133
+ "name": "Konawe Kepulauan",
2134
+ "code": "12",
2135
+ "full_code": "7412",
2136
+ "province_id": 34
2137
+ },
2138
+ {
2139
+ "id": 268,
2140
+ "type": "Kota",
2141
+ "name": "Tanjung Balai",
2142
+ "code": "74",
2143
+ "full_code": "1274",
2144
+ "province_id": 38
2145
+ },
2146
+ {
2147
+ "id": 269,
2148
+ "type": "Kabupaten",
2149
+ "name": "Konawe Selatan",
2150
+ "code": "05",
2151
+ "full_code": "7405",
2152
+ "province_id": 34
2153
+ },
2154
+ {
2155
+ "id": 270,
2156
+ "type": "Kabupaten",
2157
+ "name": "Konawe Utara",
2158
+ "code": "09",
2159
+ "full_code": "7409",
2160
+ "province_id": 34
2161
+ },
2162
+ {
2163
+ "id": 271,
2164
+ "type": "Kabupaten",
2165
+ "name": "Kotabaru",
2166
+ "code": "02",
2167
+ "full_code": "6302",
2168
+ "province_id": 13
2169
+ },
2170
+ {
2171
+ "id": 272,
2172
+ "type": "Kabupaten",
2173
+ "name": "Tanjung Jabung Barat",
2174
+ "code": "06",
2175
+ "full_code": "1506",
2176
+ "province_id": 8
2177
+ },
2178
+ {
2179
+ "id": 273,
2180
+ "type": "Kota",
2181
+ "name": "Kotamobagu",
2182
+ "code": "74",
2183
+ "full_code": "7174",
2184
+ "province_id": 35
2185
+ },
2186
+ {
2187
+ "id": 274,
2188
+ "type": "Kabupaten",
2189
+ "name": "Tanjung Jabung Timur",
2190
+ "code": "07",
2191
+ "full_code": "1507",
2192
+ "province_id": 8
2193
+ },
2194
+ {
2195
+ "id": 275,
2196
+ "type": "Kabupaten",
2197
+ "name": "Kotawaringin Barat",
2198
+ "code": "01",
2199
+ "full_code": "6201",
2200
+ "province_id": 14
2201
+ },
2202
+ {
2203
+ "id": 276,
2204
+ "type": "Kabupaten",
2205
+ "name": "Kotawaringin Timur",
2206
+ "code": "02",
2207
+ "full_code": "6202",
2208
+ "province_id": 14
2209
+ },
2210
+ {
2211
+ "id": 277,
2212
+ "type": "Kota",
2213
+ "name": "Tanjung Pinang",
2214
+ "code": "72",
2215
+ "full_code": "2172",
2216
+ "province_id": 18
2217
+ },
2218
+ {
2219
+ "id": 278,
2220
+ "type": "Kabupaten",
2221
+ "name": "Kuantan Singingi",
2222
+ "code": "09",
2223
+ "full_code": "1409",
2224
+ "province_id": 30
2225
+ },
2226
+ {
2227
+ "id": 279,
2228
+ "type": "Kabupaten",
2229
+ "name": "Tapanuli Selatan",
2230
+ "code": "03",
2231
+ "full_code": "1203",
2232
+ "province_id": 38
2233
+ },
2234
+ {
2235
+ "id": 280,
2236
+ "type": "Kabupaten",
2237
+ "name": "Kubu Raya",
2238
+ "code": "12",
2239
+ "full_code": "6112",
2240
+ "province_id": 12
2241
+ },
2242
+ {
2243
+ "id": 281,
2244
+ "type": "Kabupaten",
2245
+ "name": "Kudus",
2246
+ "code": "19",
2247
+ "full_code": "3319",
2248
+ "province_id": 10
2249
+ },
2250
+ {
2251
+ "id": 282,
2252
+ "type": "Kabupaten",
2253
+ "name": "Tapanuli Tengah",
2254
+ "code": "01",
2255
+ "full_code": "1201",
2256
+ "province_id": 38
2257
+ },
2258
+ {
2259
+ "id": 283,
2260
+ "type": "Kabupaten",
2261
+ "name": "Tapanuli Utara",
2262
+ "code": "02",
2263
+ "full_code": "1202",
2264
+ "province_id": 38
2265
+ },
2266
+ {
2267
+ "id": 284,
2268
+ "type": "Kabupaten",
2269
+ "name": "Tapin",
2270
+ "code": "05",
2271
+ "full_code": "6305",
2272
+ "province_id": 13
2273
+ },
2274
+ {
2275
+ "id": 285,
2276
+ "type": "Kota",
2277
+ "name": "Tarakan",
2278
+ "code": "71",
2279
+ "full_code": "6571",
2280
+ "province_id": 16
2281
+ },
2282
+ {
2283
+ "id": 286,
2284
+ "type": "Kota",
2285
+ "name": "Tasikmalaya",
2286
+ "code": "78",
2287
+ "full_code": "3278",
2288
+ "province_id": 9
2289
+ },
2290
+ {
2291
+ "id": 287,
2292
+ "type": "Kabupaten",
2293
+ "name": "Kulon Progo",
2294
+ "code": "01",
2295
+ "full_code": "3401",
2296
+ "province_id": 5
2297
+ },
2298
+ {
2299
+ "id": 288,
2300
+ "type": "Kabupaten",
2301
+ "name": "Kuningan",
2302
+ "code": "08",
2303
+ "full_code": "3208",
2304
+ "province_id": 9
2305
+ },
2306
+ {
2307
+ "id": 289,
2308
+ "type": "Kabupaten",
2309
+ "name": "Kupang",
2310
+ "code": "01",
2311
+ "full_code": "5301",
2312
+ "province_id": 23
2313
+ },
2314
+ {
2315
+ "id": 290,
2316
+ "type": "Kota",
2317
+ "name": "Kupang",
2318
+ "code": "71",
2319
+ "full_code": "5371",
2320
+ "province_id": 23
2321
+ },
2322
+ {
2323
+ "id": 291,
2324
+ "type": "Kabupaten",
2325
+ "name": "Tasikmalaya",
2326
+ "code": "06",
2327
+ "full_code": "3206",
2328
+ "province_id": 9
2329
+ },
2330
+ {
2331
+ "id": 292,
2332
+ "type": "Kabupaten",
2333
+ "name": "Kutai Barat",
2334
+ "code": "07",
2335
+ "full_code": "6407",
2336
+ "province_id": 15
2337
+ },
2338
+ {
2339
+ "id": 293,
2340
+ "type": "Kabupaten",
2341
+ "name": "Kutai Kartanegara",
2342
+ "code": "02",
2343
+ "full_code": "6402",
2344
+ "province_id": 15
2345
+ },
2346
+ {
2347
+ "id": 294,
2348
+ "type": "Kabupaten",
2349
+ "name": "Kutai Timur",
2350
+ "code": "08",
2351
+ "full_code": "6408",
2352
+ "province_id": 15
2353
+ },
2354
+ {
2355
+ "id": 295,
2356
+ "type": "Kabupaten",
2357
+ "name": "Labuhanbatu",
2358
+ "code": "10",
2359
+ "full_code": "1210",
2360
+ "province_id": 38
2361
+ },
2362
+ {
2363
+ "id": 296,
2364
+ "type": "Kota",
2365
+ "name": "Tebing Tinggi",
2366
+ "code": "76",
2367
+ "full_code": "1276",
2368
+ "province_id": 38
2369
+ },
2370
+ {
2371
+ "id": 297,
2372
+ "type": "Kabupaten",
2373
+ "name": "Labuhanbatu Selatan",
2374
+ "code": "22",
2375
+ "full_code": "1222",
2376
+ "province_id": 38
2377
+ },
2378
+ {
2379
+ "id": 298,
2380
+ "type": "Kabupaten",
2381
+ "name": "Tebo",
2382
+ "code": "09",
2383
+ "full_code": "1509",
2384
+ "province_id": 8
2385
+ },
2386
+ {
2387
+ "id": 299,
2388
+ "type": "Kabupaten",
2389
+ "name": "Tegal",
2390
+ "code": "28",
2391
+ "full_code": "3328",
2392
+ "province_id": 10
2393
+ },
2394
+ {
2395
+ "id": 300,
2396
+ "type": "Kota",
2397
+ "name": "Tegal",
2398
+ "code": "76",
2399
+ "full_code": "3376",
2400
+ "province_id": 10
2401
+ },
2402
+ {
2403
+ "id": 301,
2404
+ "type": "Kabupaten",
2405
+ "name": "Teluk Bintuni",
2406
+ "code": "06",
2407
+ "full_code": "9206",
2408
+ "province_id": 25
2409
+ },
2410
+ {
2411
+ "id": 302,
2412
+ "type": "Kabupaten",
2413
+ "name": "Teluk Wondama",
2414
+ "code": "07",
2415
+ "full_code": "9207",
2416
+ "province_id": 25
2417
+ },
2418
+ {
2419
+ "id": 303,
2420
+ "type": "Kabupaten",
2421
+ "name": "Labuhanbatu Utara",
2422
+ "code": "23",
2423
+ "full_code": "1223",
2424
+ "province_id": 38
2425
+ },
2426
+ {
2427
+ "id": 304,
2428
+ "type": "Kabupaten",
2429
+ "name": "Temanggung",
2430
+ "code": "23",
2431
+ "full_code": "3323",
2432
+ "province_id": 10
2433
+ },
2434
+ {
2435
+ "id": 305,
2436
+ "type": "Kabupaten",
2437
+ "name": "Lahat",
2438
+ "code": "04",
2439
+ "full_code": "1604",
2440
+ "province_id": 37
2441
+ },
2442
+ {
2443
+ "id": 306,
2444
+ "type": "Kota",
2445
+ "name": "Ternate",
2446
+ "code": "71",
2447
+ "full_code": "8271",
2448
+ "province_id": 21
2449
+ },
2450
+ {
2451
+ "id": 307,
2452
+ "type": "Kabupaten",
2453
+ "name": "Lamandau",
2454
+ "code": "09",
2455
+ "full_code": "6209",
2456
+ "province_id": 14
2457
+ },
2458
+ {
2459
+ "id": 308,
2460
+ "type": "Kabupaten",
2461
+ "name": "Dompu",
2462
+ "code": "05",
2463
+ "full_code": "5205",
2464
+ "province_id": 22
2465
+ },
2466
+ {
2467
+ "id": 309,
2468
+ "type": "Kabupaten",
2469
+ "name": "Donggala",
2470
+ "code": "03",
2471
+ "full_code": "7203",
2472
+ "province_id": 33
2473
+ },
2474
+ {
2475
+ "id": 310,
2476
+ "type": "Kota",
2477
+ "name": "Dumai",
2478
+ "code": "72",
2479
+ "full_code": "1472",
2480
+ "province_id": 30
2481
+ },
2482
+ {
2483
+ "id": 311,
2484
+ "type": "Kabupaten",
2485
+ "name": "Empat Lawang",
2486
+ "code": "11",
2487
+ "full_code": "1611",
2488
+ "province_id": 37
2489
+ },
2490
+ {
2491
+ "id": 312,
2492
+ "type": "Kabupaten",
2493
+ "name": "Lamongan",
2494
+ "code": "24",
2495
+ "full_code": "3524",
2496
+ "province_id": 11
2497
+ },
2498
+ {
2499
+ "id": 313,
2500
+ "type": "Kabupaten",
2501
+ "name": "Ende",
2502
+ "code": "08",
2503
+ "full_code": "5308",
2504
+ "province_id": 23
2505
+ },
2506
+ {
2507
+ "id": 314,
2508
+ "type": "Kabupaten",
2509
+ "name": "Lampung Barat",
2510
+ "code": "04",
2511
+ "full_code": "1804",
2512
+ "province_id": 19
2513
+ },
2514
+ {
2515
+ "id": 315,
2516
+ "type": "Kota",
2517
+ "name": "Tidore Kepulauan",
2518
+ "code": "72",
2519
+ "full_code": "8272",
2520
+ "province_id": 21
2521
+ },
2522
+ {
2523
+ "id": 316,
2524
+ "type": "Kabupaten",
2525
+ "name": "Enrekang",
2526
+ "code": "16",
2527
+ "full_code": "7316",
2528
+ "province_id": 32
2529
+ },
2530
+ {
2531
+ "id": 317,
2532
+ "type": "Kabupaten",
2533
+ "name": "Lampung Selatan",
2534
+ "code": "01",
2535
+ "full_code": "1801",
2536
+ "province_id": 19
2537
+ },
2538
+ {
2539
+ "id": 318,
2540
+ "type": "Kabupaten",
2541
+ "name": "Fak Fak",
2542
+ "code": "03",
2543
+ "full_code": "9203",
2544
+ "province_id": 25
2545
+ },
2546
+ {
2547
+ "id": 319,
2548
+ "type": "Kabupaten",
2549
+ "name": "Timor Tengah Selatan",
2550
+ "code": "02",
2551
+ "full_code": "5302",
2552
+ "province_id": 23
2553
+ },
2554
+ {
2555
+ "id": 320,
2556
+ "type": "Kabupaten",
2557
+ "name": "Lampung Tengah",
2558
+ "code": "02",
2559
+ "full_code": "1802",
2560
+ "province_id": 19
2561
+ },
2562
+ {
2563
+ "id": 321,
2564
+ "type": "Kabupaten",
2565
+ "name": "Flores Timur",
2566
+ "code": "06",
2567
+ "full_code": "5306",
2568
+ "province_id": 23
2569
+ },
2570
+ {
2571
+ "id": 322,
2572
+ "type": "Kabupaten",
2573
+ "name": "Lampung Timur",
2574
+ "code": "07",
2575
+ "full_code": "1807",
2576
+ "province_id": 19
2577
+ },
2578
+ {
2579
+ "id": 323,
2580
+ "type": "Kabupaten",
2581
+ "name": "Garut",
2582
+ "code": "05",
2583
+ "full_code": "3205",
2584
+ "province_id": 9
2585
+ },
2586
+ {
2587
+ "id": 324,
2588
+ "type": "Kabupaten",
2589
+ "name": "Timor Tengah Utara",
2590
+ "code": "03",
2591
+ "full_code": "5303",
2592
+ "province_id": 23
2593
+ },
2594
+ {
2595
+ "id": 325,
2596
+ "type": "Kabupaten",
2597
+ "name": "Lampung Utara",
2598
+ "code": "03",
2599
+ "full_code": "1803",
2600
+ "province_id": 19
2601
+ },
2602
+ {
2603
+ "id": 326,
2604
+ "type": "Kabupaten",
2605
+ "name": "Toba",
2606
+ "code": "12",
2607
+ "full_code": "1212",
2608
+ "province_id": 38
2609
+ },
2610
+ {
2611
+ "id": 327,
2612
+ "type": "Kabupaten",
2613
+ "name": "Tojo Una Una",
2614
+ "code": "09",
2615
+ "full_code": "7209",
2616
+ "province_id": 33
2617
+ },
2618
+ {
2619
+ "id": 328,
2620
+ "type": "Kabupaten",
2621
+ "name": "Gayo Lues",
2622
+ "code": "13",
2623
+ "full_code": "1113",
2624
+ "province_id": 1
2625
+ },
2626
+ {
2627
+ "id": 329,
2628
+ "type": "Kabupaten",
2629
+ "name": "Landak",
2630
+ "code": "08",
2631
+ "full_code": "6108",
2632
+ "province_id": 12
2633
+ },
2634
+ {
2635
+ "id": 330,
2636
+ "type": "Kabupaten",
2637
+ "name": "Gianyar",
2638
+ "code": "04",
2639
+ "full_code": "5104",
2640
+ "province_id": 2
2641
+ },
2642
+ {
2643
+ "id": 331,
2644
+ "type": "Kabupaten",
2645
+ "name": "Langkat",
2646
+ "code": "05",
2647
+ "full_code": "1205",
2648
+ "province_id": 38
2649
+ },
2650
+ {
2651
+ "id": 332,
2652
+ "type": "Kabupaten",
2653
+ "name": "Gorontalo",
2654
+ "code": "01",
2655
+ "full_code": "7501",
2656
+ "province_id": 7
2657
+ },
2658
+ {
2659
+ "id": 333,
2660
+ "type": "Kota",
2661
+ "name": "Langsa",
2662
+ "code": "74",
2663
+ "full_code": "1174",
2664
+ "province_id": 1
2665
+ },
2666
+ {
2667
+ "id": 334,
2668
+ "type": "Kabupaten",
2669
+ "name": "Toli Toli",
2670
+ "code": "04",
2671
+ "full_code": "7204",
2672
+ "province_id": 33
2673
+ },
2674
+ {
2675
+ "id": 335,
2676
+ "type": "Kabupaten",
2677
+ "name": "Lanny Jaya",
2678
+ "code": "07",
2679
+ "full_code": "9507",
2680
+ "province_id": 27
2681
+ },
2682
+ {
2683
+ "id": 336,
2684
+ "type": "Kabupaten",
2685
+ "name": "Lebak",
2686
+ "code": "02",
2687
+ "full_code": "3602",
2688
+ "province_id": 3
2689
+ },
2690
+ {
2691
+ "id": 337,
2692
+ "type": "Kota",
2693
+ "name": "Gorontalo",
2694
+ "code": "71",
2695
+ "full_code": "7571",
2696
+ "province_id": 7
2697
+ },
2698
+ {
2699
+ "id": 338,
2700
+ "type": "Kabupaten",
2701
+ "name": "Tolikara",
2702
+ "code": "04",
2703
+ "full_code": "9504",
2704
+ "province_id": 27
2705
+ },
2706
+ {
2707
+ "id": 339,
2708
+ "type": "Kota",
2709
+ "name": "Tomohon",
2710
+ "code": "73",
2711
+ "full_code": "7173",
2712
+ "province_id": 35
2713
+ },
2714
+ {
2715
+ "id": 340,
2716
+ "type": "Kabupaten",
2717
+ "name": "Lebong",
2718
+ "code": "07",
2719
+ "full_code": "1707",
2720
+ "province_id": 4
2721
+ },
2722
+ {
2723
+ "id": 341,
2724
+ "type": "Kabupaten",
2725
+ "name": "Toraja Utara",
2726
+ "code": "26",
2727
+ "full_code": "7326",
2728
+ "province_id": 32
2729
+ },
2730
+ {
2731
+ "id": 342,
2732
+ "type": "Kabupaten",
2733
+ "name": "Lembata",
2734
+ "code": "13",
2735
+ "full_code": "5313",
2736
+ "province_id": 23
2737
+ },
2738
+ {
2739
+ "id": 343,
2740
+ "type": "Kota",
2741
+ "name": "Lhokseumawe",
2742
+ "code": "73",
2743
+ "full_code": "1173",
2744
+ "province_id": 1
2745
+ },
2746
+ {
2747
+ "id": 344,
2748
+ "type": "Kabupaten",
2749
+ "name": "Lima Puluh Kota",
2750
+ "code": "07",
2751
+ "full_code": "1307",
2752
+ "province_id": 36
2753
+ },
2754
+ {
2755
+ "id": 345,
2756
+ "type": "Kabupaten",
2757
+ "name": "Lingga",
2758
+ "code": "04",
2759
+ "full_code": "2104",
2760
+ "province_id": 18
2761
+ },
2762
+ {
2763
+ "id": 346,
2764
+ "type": "Kabupaten",
2765
+ "name": "Trenggalek",
2766
+ "code": "03",
2767
+ "full_code": "3503",
2768
+ "province_id": 11
2769
+ },
2770
+ {
2771
+ "id": 347,
2772
+ "type": "Kota",
2773
+ "name": "Tual",
2774
+ "code": "72",
2775
+ "full_code": "8172",
2776
+ "province_id": 20
2777
+ },
2778
+ {
2779
+ "id": 348,
2780
+ "type": "Kabupaten",
2781
+ "name": "Tuban",
2782
+ "code": "23",
2783
+ "full_code": "3523",
2784
+ "province_id": 11
2785
+ },
2786
+ {
2787
+ "id": 349,
2788
+ "type": "Kabupaten",
2789
+ "name": "Lombok Barat",
2790
+ "code": "01",
2791
+ "full_code": "5201",
2792
+ "province_id": 22
2793
+ },
2794
+ {
2795
+ "id": 350,
2796
+ "type": "Kabupaten",
2797
+ "name": "Tulang Bawang",
2798
+ "code": "05",
2799
+ "full_code": "1805",
2800
+ "province_id": 19
2801
+ },
2802
+ {
2803
+ "id": 351,
2804
+ "type": "Kabupaten",
2805
+ "name": "Lombok Tengah",
2806
+ "code": "02",
2807
+ "full_code": "5202",
2808
+ "province_id": 22
2809
+ },
2810
+ {
2811
+ "id": 352,
2812
+ "type": "Kabupaten",
2813
+ "name": "Lombok Timur",
2814
+ "code": "03",
2815
+ "full_code": "5203",
2816
+ "province_id": 22
2817
+ },
2818
+ {
2819
+ "id": 353,
2820
+ "type": "Kabupaten",
2821
+ "name": "Tulang Bawang Barat",
2822
+ "code": "12",
2823
+ "full_code": "1812",
2824
+ "province_id": 19
2825
+ },
2826
+ {
2827
+ "id": 354,
2828
+ "type": "Kabupaten",
2829
+ "name": "Lombok Utara",
2830
+ "code": "08",
2831
+ "full_code": "5208",
2832
+ "province_id": 22
2833
+ },
2834
+ {
2835
+ "id": 355,
2836
+ "type": "Kota",
2837
+ "name": "Lubuk Linggau",
2838
+ "code": "73",
2839
+ "full_code": "1673",
2840
+ "province_id": 37
2841
+ },
2842
+ {
2843
+ "id": 356,
2844
+ "type": "Kabupaten",
2845
+ "name": "Tulungagung",
2846
+ "code": "04",
2847
+ "full_code": "3504",
2848
+ "province_id": 11
2849
+ },
2850
+ {
2851
+ "id": 357,
2852
+ "type": "Kabupaten",
2853
+ "name": "Lumajang",
2854
+ "code": "08",
2855
+ "full_code": "3508",
2856
+ "province_id": 11
2857
+ },
2858
+ {
2859
+ "id": 358,
2860
+ "type": "Kabupaten",
2861
+ "name": "Wajo",
2862
+ "code": "13",
2863
+ "full_code": "7313",
2864
+ "province_id": 32
2865
+ },
2866
+ {
2867
+ "id": 359,
2868
+ "type": "Kabupaten",
2869
+ "name": "Luwu",
2870
+ "code": "17",
2871
+ "full_code": "7317",
2872
+ "province_id": 32
2873
+ },
2874
+ {
2875
+ "id": 360,
2876
+ "type": "Kabupaten",
2877
+ "name": "Luwu Timur",
2878
+ "code": "24",
2879
+ "full_code": "7324",
2880
+ "province_id": 32
2881
+ },
2882
+ {
2883
+ "id": 361,
2884
+ "type": "Kabupaten",
2885
+ "name": "Wakatobi",
2886
+ "code": "07",
2887
+ "full_code": "7407",
2888
+ "province_id": 34
2889
+ },
2890
+ {
2891
+ "id": 362,
2892
+ "type": "Kabupaten",
2893
+ "name": "Waropen",
2894
+ "code": "15",
2895
+ "full_code": "9115",
2896
+ "province_id": 24
2897
+ },
2898
+ {
2899
+ "id": 363,
2900
+ "type": "Kabupaten",
2901
+ "name": "Way Kanan",
2902
+ "code": "08",
2903
+ "full_code": "1808",
2904
+ "province_id": 19
2905
+ },
2906
+ {
2907
+ "id": 364,
2908
+ "type": "Kabupaten",
2909
+ "name": "Wonogiri",
2910
+ "code": "12",
2911
+ "full_code": "3312",
2912
+ "province_id": 10
2913
+ },
2914
+ {
2915
+ "id": 365,
2916
+ "type": "Kabupaten",
2917
+ "name": "Wonosobo",
2918
+ "code": "07",
2919
+ "full_code": "3307",
2920
+ "province_id": 10
2921
+ },
2922
+ {
2923
+ "id": 366,
2924
+ "type": "Kabupaten",
2925
+ "name": "Yahukimo",
2926
+ "code": "03",
2927
+ "full_code": "9503",
2928
+ "province_id": 27
2929
+ },
2930
+ {
2931
+ "id": 367,
2932
+ "type": "Kabupaten",
2933
+ "name": "Yalimo",
2934
+ "code": "06",
2935
+ "full_code": "9506",
2936
+ "province_id": 27
2937
+ },
2938
+ {
2939
+ "id": 368,
2940
+ "type": "Kota",
2941
+ "name": "Yogyakarta",
2942
+ "code": "71",
2943
+ "full_code": "3471",
2944
+ "province_id": 5
2945
+ },
2946
+ {
2947
+ "id": 369,
2948
+ "type": "Kabupaten",
2949
+ "name": "Luwu Utara",
2950
+ "code": "22",
2951
+ "full_code": "7322",
2952
+ "province_id": 32
2953
+ },
2954
+ {
2955
+ "id": 370,
2956
+ "type": "Kabupaten",
2957
+ "name": "Madiun",
2958
+ "code": "19",
2959
+ "full_code": "3519",
2960
+ "province_id": 11
2961
+ },
2962
+ {
2963
+ "id": 371,
2964
+ "type": "Kota",
2965
+ "name": "Madiun",
2966
+ "code": "77",
2967
+ "full_code": "3577",
2968
+ "province_id": 11
2969
+ },
2970
+ {
2971
+ "id": 372,
2972
+ "type": "Kabupaten",
2973
+ "name": "Magelang",
2974
+ "code": "08",
2975
+ "full_code": "3308",
2976
+ "province_id": 10
2977
+ },
2978
+ {
2979
+ "id": 373,
2980
+ "type": "Kota",
2981
+ "name": "Magelang",
2982
+ "code": "71",
2983
+ "full_code": "3371",
2984
+ "province_id": 10
2985
+ },
2986
+ {
2987
+ "id": 374,
2988
+ "type": "Kabupaten",
2989
+ "name": "Magetan",
2990
+ "code": "20",
2991
+ "full_code": "3520",
2992
+ "province_id": 11
2993
+ },
2994
+ {
2995
+ "id": 375,
2996
+ "type": "Kabupaten",
2997
+ "name": "Mahakam Ulu",
2998
+ "code": "11",
2999
+ "full_code": "6411",
3000
+ "province_id": 15
3001
+ },
3002
+ {
3003
+ "id": 376,
3004
+ "type": "Kabupaten",
3005
+ "name": "Majalengka",
3006
+ "code": "10",
3007
+ "full_code": "3210",
3008
+ "province_id": 9
3009
+ },
3010
+ {
3011
+ "id": 377,
3012
+ "type": "Kabupaten",
3013
+ "name": "Majene",
3014
+ "code": "05",
3015
+ "full_code": "7605",
3016
+ "province_id": 31
3017
+ },
3018
+ {
3019
+ "id": 378,
3020
+ "type": "Kota",
3021
+ "name": "Makassar",
3022
+ "code": "71",
3023
+ "full_code": "7371",
3024
+ "province_id": 32
3025
+ },
3026
+ {
3027
+ "id": 379,
3028
+ "type": "Kabupaten",
3029
+ "name": "Malaka",
3030
+ "code": "21",
3031
+ "full_code": "5321",
3032
+ "province_id": 23
3033
+ },
3034
+ {
3035
+ "id": 380,
3036
+ "type": "Kabupaten",
3037
+ "name": "Malang",
3038
+ "code": "07",
3039
+ "full_code": "3507",
3040
+ "province_id": 11
3041
+ },
3042
+ {
3043
+ "id": 381,
3044
+ "type": "Kota",
3045
+ "name": "Malang",
3046
+ "code": "73",
3047
+ "full_code": "3573",
3048
+ "province_id": 11
3049
+ },
3050
+ {
3051
+ "id": 382,
3052
+ "type": "Kabupaten",
3053
+ "name": "Malinau",
3054
+ "code": "02",
3055
+ "full_code": "6502",
3056
+ "province_id": 16
3057
+ },
3058
+ {
3059
+ "id": 383,
3060
+ "type": "Kabupaten",
3061
+ "name": "Maluku Barat Daya",
3062
+ "code": "08",
3063
+ "full_code": "8108",
3064
+ "province_id": 20
3065
+ },
3066
+ {
3067
+ "id": 384,
3068
+ "type": "Kabupaten",
3069
+ "name": "Maluku Tengah",
3070
+ "code": "01",
3071
+ "full_code": "8101",
3072
+ "province_id": 20
3073
+ },
3074
+ {
3075
+ "id": 385,
3076
+ "type": "Kabupaten",
3077
+ "name": "Maluku Tenggara",
3078
+ "code": "02",
3079
+ "full_code": "8102",
3080
+ "province_id": 20
3081
+ },
3082
+ {
3083
+ "id": 386,
3084
+ "type": "Kabupaten",
3085
+ "name": "Mamasa",
3086
+ "code": "03",
3087
+ "full_code": "7603",
3088
+ "province_id": 31
3089
+ },
3090
+ {
3091
+ "id": 387,
3092
+ "type": "Kabupaten",
3093
+ "name": "Mamberamo Raya",
3094
+ "code": "20",
3095
+ "full_code": "9120",
3096
+ "province_id": 24
3097
+ },
3098
+ {
3099
+ "id": 388,
3100
+ "type": "Kabupaten",
3101
+ "name": "Mamberamo Tengah",
3102
+ "code": "05",
3103
+ "full_code": "9505",
3104
+ "province_id": 27
3105
+ },
3106
+ {
3107
+ "id": 389,
3108
+ "type": "Kabupaten",
3109
+ "name": "Mamuju",
3110
+ "code": "02",
3111
+ "full_code": "7602",
3112
+ "province_id": 31
3113
+ },
3114
+ {
3115
+ "id": 390,
3116
+ "type": "Kabupaten",
3117
+ "name": "Mamuju Tengah",
3118
+ "code": "06",
3119
+ "full_code": "7606",
3120
+ "province_id": 31
3121
+ },
3122
+ {
3123
+ "id": 391,
3124
+ "type": "Kota",
3125
+ "name": "Manado",
3126
+ "code": "71",
3127
+ "full_code": "7171",
3128
+ "province_id": 35
3129
+ },
3130
+ {
3131
+ "id": 392,
3132
+ "type": "Kabupaten",
3133
+ "name": "Mandailing Natal",
3134
+ "code": "13",
3135
+ "full_code": "1213",
3136
+ "province_id": 38
3137
+ },
3138
+ {
3139
+ "id": 393,
3140
+ "type": "Kabupaten",
3141
+ "name": "Manggarai",
3142
+ "code": "10",
3143
+ "full_code": "5310",
3144
+ "province_id": 23
3145
+ },
3146
+ {
3147
+ "id": 394,
3148
+ "type": "Kabupaten",
3149
+ "name": "Manggarai Barat",
3150
+ "code": "15",
3151
+ "full_code": "5315",
3152
+ "province_id": 23
3153
+ },
3154
+ {
3155
+ "id": 395,
3156
+ "type": "Kabupaten",
3157
+ "name": "Manggarai Timur",
3158
+ "code": "19",
3159
+ "full_code": "5319",
3160
+ "province_id": 23
3161
+ },
3162
+ {
3163
+ "id": 396,
3164
+ "type": "Kabupaten",
3165
+ "name": "Manokwari",
3166
+ "code": "02",
3167
+ "full_code": "9202",
3168
+ "province_id": 25
3169
+ },
3170
+ {
3171
+ "id": 397,
3172
+ "type": "Kabupaten",
3173
+ "name": "Manokwari Selatan",
3174
+ "code": "11",
3175
+ "full_code": "9211",
3176
+ "province_id": 25
3177
+ },
3178
+ {
3179
+ "id": 398,
3180
+ "type": "Kabupaten",
3181
+ "name": "Mappi",
3182
+ "code": "03",
3183
+ "full_code": "9303",
3184
+ "province_id": 28
3185
+ },
3186
+ {
3187
+ "id": 399,
3188
+ "type": "Kabupaten",
3189
+ "name": "Maros",
3190
+ "code": "09",
3191
+ "full_code": "7309",
3192
+ "province_id": 32
3193
+ },
3194
+ {
3195
+ "id": 400,
3196
+ "type": "Kota",
3197
+ "name": "Mataram",
3198
+ "code": "71",
3199
+ "full_code": "5271",
3200
+ "province_id": 22
3201
+ },
3202
+ {
3203
+ "id": 401,
3204
+ "type": "Kabupaten",
3205
+ "name": "Maybrat",
3206
+ "code": "10",
3207
+ "full_code": "9210",
3208
+ "province_id": 26
3209
+ },
3210
+ {
3211
+ "id": 402,
3212
+ "type": "Kota",
3213
+ "name": "Medan",
3214
+ "code": "71",
3215
+ "full_code": "1271",
3216
+ "province_id": 38
3217
+ },
3218
+ {
3219
+ "id": 403,
3220
+ "type": "Kabupaten",
3221
+ "name": "Melawi",
3222
+ "code": "10",
3223
+ "full_code": "6110",
3224
+ "province_id": 12
3225
+ },
3226
+ {
3227
+ "id": 404,
3228
+ "type": "Kabupaten",
3229
+ "name": "Mempawah",
3230
+ "code": "02",
3231
+ "full_code": "6102",
3232
+ "province_id": 12
3233
+ },
3234
+ {
3235
+ "id": 405,
3236
+ "type": "Kabupaten",
3237
+ "name": "Merangin",
3238
+ "code": "02",
3239
+ "full_code": "1502",
3240
+ "province_id": 8
3241
+ },
3242
+ {
3243
+ "id": 406,
3244
+ "type": "Kabupaten",
3245
+ "name": "Merauke",
3246
+ "code": "01",
3247
+ "full_code": "9301",
3248
+ "province_id": 28
3249
+ },
3250
+ {
3251
+ "id": 407,
3252
+ "type": "Kabupaten",
3253
+ "name": "Mesuji",
3254
+ "code": "11",
3255
+ "full_code": "1811",
3256
+ "province_id": 19
3257
+ },
3258
+ {
3259
+ "id": 408,
3260
+ "type": "Kota",
3261
+ "name": "Metro",
3262
+ "code": "72",
3263
+ "full_code": "1872",
3264
+ "province_id": 19
3265
+ },
3266
+ {
3267
+ "id": 409,
3268
+ "type": "Kabupaten",
3269
+ "name": "Mimika",
3270
+ "code": "04",
3271
+ "full_code": "9404",
3272
+ "province_id": 29
3273
+ },
3274
+ {
3275
+ "id": 410,
3276
+ "type": "Kabupaten",
3277
+ "name": "Minahasa",
3278
+ "code": "02",
3279
+ "full_code": "7102",
3280
+ "province_id": 35
3281
+ },
3282
+ {
3283
+ "id": 411,
3284
+ "type": "Kabupaten",
3285
+ "name": "Minahasa Selatan",
3286
+ "code": "05",
3287
+ "full_code": "7105",
3288
+ "province_id": 35
3289
+ },
3290
+ {
3291
+ "id": 412,
3292
+ "type": "Kabupaten",
3293
+ "name": "Minahasa Tenggara",
3294
+ "code": "07",
3295
+ "full_code": "7107",
3296
+ "province_id": 35
3297
+ },
3298
+ {
3299
+ "id": 413,
3300
+ "type": "Kabupaten",
3301
+ "name": "Minahasa Utara",
3302
+ "code": "06",
3303
+ "full_code": "7106",
3304
+ "province_id": 35
3305
+ },
3306
+ {
3307
+ "id": 414,
3308
+ "type": "Kabupaten",
3309
+ "name": "Mojokerto",
3310
+ "code": "16",
3311
+ "full_code": "3516",
3312
+ "province_id": 11
3313
+ },
3314
+ {
3315
+ "id": 415,
3316
+ "type": "Kota",
3317
+ "name": "Mojokerto",
3318
+ "code": "76",
3319
+ "full_code": "3576",
3320
+ "province_id": 11
3321
+ },
3322
+ {
3323
+ "id": 416,
3324
+ "type": "Kabupaten",
3325
+ "name": "Morowali",
3326
+ "code": "06",
3327
+ "full_code": "7206",
3328
+ "province_id": 33
3329
+ },
3330
+ {
3331
+ "id": 417,
3332
+ "type": "Kabupaten",
3333
+ "name": "Morowali Utara",
3334
+ "code": "12",
3335
+ "full_code": "7212",
3336
+ "province_id": 33
3337
+ },
3338
+ {
3339
+ "id": 418,
3340
+ "type": "Kabupaten",
3341
+ "name": "Muara Enim",
3342
+ "code": "03",
3343
+ "full_code": "1603",
3344
+ "province_id": 37
3345
+ },
3346
+ {
3347
+ "id": 419,
3348
+ "type": "Kabupaten",
3349
+ "name": "Muaro Jambi",
3350
+ "code": "05",
3351
+ "full_code": "1505",
3352
+ "province_id": 8
3353
+ },
3354
+ {
3355
+ "id": 420,
3356
+ "type": "Kabupaten",
3357
+ "name": "Muko Muko",
3358
+ "code": "06",
3359
+ "full_code": "1706",
3360
+ "province_id": 4
3361
+ },
3362
+ {
3363
+ "id": 421,
3364
+ "type": "Kabupaten",
3365
+ "name": "Muna",
3366
+ "code": "03",
3367
+ "full_code": "7403",
3368
+ "province_id": 34
3369
+ },
3370
+ {
3371
+ "id": 422,
3372
+ "type": "Kabupaten",
3373
+ "name": "Muna Barat",
3374
+ "code": "13",
3375
+ "full_code": "7413",
3376
+ "province_id": 34
3377
+ },
3378
+ {
3379
+ "id": 423,
3380
+ "type": "Kabupaten",
3381
+ "name": "Murung Raya",
3382
+ "code": "12",
3383
+ "full_code": "6212",
3384
+ "province_id": 14
3385
+ },
3386
+ {
3387
+ "id": 424,
3388
+ "type": "Kabupaten",
3389
+ "name": "Musi Banyuasin",
3390
+ "code": "06",
3391
+ "full_code": "1606",
3392
+ "province_id": 37
3393
+ },
3394
+ {
3395
+ "id": 425,
3396
+ "type": "Kabupaten",
3397
+ "name": "Musi Rawas",
3398
+ "code": "05",
3399
+ "full_code": "1605",
3400
+ "province_id": 37
3401
+ },
3402
+ {
3403
+ "id": 426,
3404
+ "type": "Kabupaten",
3405
+ "name": "Musi Rawas Utara",
3406
+ "code": "13",
3407
+ "full_code": "1613",
3408
+ "province_id": 37
3409
+ },
3410
+ {
3411
+ "id": 427,
3412
+ "type": "Kabupaten",
3413
+ "name": "Nabire",
3414
+ "code": "01",
3415
+ "full_code": "9401",
3416
+ "province_id": 29
3417
+ },
3418
+ {
3419
+ "id": 428,
3420
+ "type": "Kabupaten",
3421
+ "name": "Nagan Raya",
3422
+ "code": "15",
3423
+ "full_code": "1115",
3424
+ "province_id": 1
3425
+ },
3426
+ {
3427
+ "id": 429,
3428
+ "type": "Kabupaten",
3429
+ "name": "Nagekeo",
3430
+ "code": "16",
3431
+ "full_code": "5316",
3432
+ "province_id": 23
3433
+ },
3434
+ {
3435
+ "id": 430,
3436
+ "type": "Kabupaten",
3437
+ "name": "Natuna",
3438
+ "code": "03",
3439
+ "full_code": "2103",
3440
+ "province_id": 18
3441
+ },
3442
+ {
3443
+ "id": 431,
3444
+ "type": "Kabupaten",
3445
+ "name": "Nduga",
3446
+ "code": "08",
3447
+ "full_code": "9508",
3448
+ "province_id": 27
3449
+ },
3450
+ {
3451
+ "id": 432,
3452
+ "type": "Kabupaten",
3453
+ "name": "Ngada",
3454
+ "code": "09",
3455
+ "full_code": "5309",
3456
+ "province_id": 23
3457
+ },
3458
+ {
3459
+ "id": 433,
3460
+ "type": "Kabupaten",
3461
+ "name": "Nganjuk",
3462
+ "code": "18",
3463
+ "full_code": "3518",
3464
+ "province_id": 11
3465
+ },
3466
+ {
3467
+ "id": 434,
3468
+ "type": "Kabupaten",
3469
+ "name": "Ngawi",
3470
+ "code": "21",
3471
+ "full_code": "3521",
3472
+ "province_id": 11
3473
+ },
3474
+ {
3475
+ "id": 435,
3476
+ "type": "Kabupaten",
3477
+ "name": "Nias",
3478
+ "code": "04",
3479
+ "full_code": "1204",
3480
+ "province_id": 38
3481
+ },
3482
+ {
3483
+ "id": 436,
3484
+ "type": "Kabupaten",
3485
+ "name": "Nias Barat",
3486
+ "code": "25",
3487
+ "full_code": "1225",
3488
+ "province_id": 38
3489
+ },
3490
+ {
3491
+ "id": 437,
3492
+ "type": "Kabupaten",
3493
+ "name": "Nias Selatan",
3494
+ "code": "14",
3495
+ "full_code": "1214",
3496
+ "province_id": 38
3497
+ },
3498
+ {
3499
+ "id": 438,
3500
+ "type": "Kabupaten",
3501
+ "name": "Nias Utara",
3502
+ "code": "24",
3503
+ "full_code": "1224",
3504
+ "province_id": 38
3505
+ },
3506
+ {
3507
+ "id": 439,
3508
+ "type": "Kabupaten",
3509
+ "name": "Nunukan",
3510
+ "code": "03",
3511
+ "full_code": "6503",
3512
+ "province_id": 16
3513
+ },
3514
+ {
3515
+ "id": 440,
3516
+ "type": "Kabupaten",
3517
+ "name": "Ogan Ilir",
3518
+ "code": "10",
3519
+ "full_code": "1610",
3520
+ "province_id": 37
3521
+ },
3522
+ {
3523
+ "id": 441,
3524
+ "type": "Kabupaten",
3525
+ "name": "Ogan Komering Ilir",
3526
+ "code": "02",
3527
+ "full_code": "1602",
3528
+ "province_id": 37
3529
+ },
3530
+ {
3531
+ "id": 442,
3532
+ "type": "Kabupaten",
3533
+ "name": "Ogan Komering Ulu",
3534
+ "code": "01",
3535
+ "full_code": "1601",
3536
+ "province_id": 37
3537
+ },
3538
+ {
3539
+ "id": 443,
3540
+ "type": "Kabupaten",
3541
+ "name": "Ogan Komering Ulu Selatan",
3542
+ "code": "09",
3543
+ "full_code": "1609",
3544
+ "province_id": 37
3545
+ },
3546
+ {
3547
+ "id": 444,
3548
+ "type": "Kabupaten",
3549
+ "name": "Ogan Komering Ulu Timur",
3550
+ "code": "08",
3551
+ "full_code": "1608",
3552
+ "province_id": 37
3553
+ },
3554
+ {
3555
+ "id": 445,
3556
+ "type": "Kabupaten",
3557
+ "name": "Pacitan",
3558
+ "code": "01",
3559
+ "full_code": "3501",
3560
+ "province_id": 11
3561
+ },
3562
+ {
3563
+ "id": 446,
3564
+ "type": "Kota",
3565
+ "name": "Padang",
3566
+ "code": "71",
3567
+ "full_code": "1371",
3568
+ "province_id": 36
3569
+ },
3570
+ {
3571
+ "id": 447,
3572
+ "type": "Kabupaten",
3573
+ "name": "Padang Lawas",
3574
+ "code": "21",
3575
+ "full_code": "1221",
3576
+ "province_id": 38
3577
+ },
3578
+ {
3579
+ "id": 448,
3580
+ "type": "Kabupaten",
3581
+ "name": "Padang Lawas Utara",
3582
+ "code": "20",
3583
+ "full_code": "1220",
3584
+ "province_id": 38
3585
+ },
3586
+ {
3587
+ "id": 449,
3588
+ "type": "Kota",
3589
+ "name": "Padang Panjang",
3590
+ "code": "74",
3591
+ "full_code": "1374",
3592
+ "province_id": 36
3593
+ },
3594
+ {
3595
+ "id": 450,
3596
+ "type": "Kabupaten",
3597
+ "name": "Padang Pariaman",
3598
+ "code": "05",
3599
+ "full_code": "1305",
3600
+ "province_id": 36
3601
+ },
3602
+ {
3603
+ "id": 451,
3604
+ "type": "Kota",
3605
+ "name": "Padangsidimpuan",
3606
+ "code": "77",
3607
+ "full_code": "1277",
3608
+ "province_id": 38
3609
+ },
3610
+ {
3611
+ "id": 452,
3612
+ "type": "Kota",
3613
+ "name": "Pagar Alam",
3614
+ "code": "72",
3615
+ "full_code": "1672",
3616
+ "province_id": 37
3617
+ },
3618
+ {
3619
+ "id": 453,
3620
+ "type": "Kabupaten",
3621
+ "name": "Pahuwato",
3622
+ "code": "04",
3623
+ "full_code": "7504",
3624
+ "province_id": 7
3625
+ },
3626
+ {
3627
+ "id": 454,
3628
+ "type": "Kabupaten",
3629
+ "name": "Pakpak Bharat",
3630
+ "code": "15",
3631
+ "full_code": "1215",
3632
+ "province_id": 38
3633
+ },
3634
+ {
3635
+ "id": 455,
3636
+ "type": "Kota",
3637
+ "name": "Palangkaraya",
3638
+ "code": "71",
3639
+ "full_code": "6271",
3640
+ "province_id": 14
3641
+ },
3642
+ {
3643
+ "id": 456,
3644
+ "type": "Kota",
3645
+ "name": "Palembang",
3646
+ "code": "71",
3647
+ "full_code": "1671",
3648
+ "province_id": 37
3649
+ },
3650
+ {
3651
+ "id": 457,
3652
+ "type": "Kota",
3653
+ "name": "Palopo",
3654
+ "code": "73",
3655
+ "full_code": "7373",
3656
+ "province_id": 32
3657
+ },
3658
+ {
3659
+ "id": 458,
3660
+ "type": "Kota",
3661
+ "name": "Palu",
3662
+ "code": "71",
3663
+ "full_code": "7271",
3664
+ "province_id": 33
3665
+ },
3666
+ {
3667
+ "id": 459,
3668
+ "type": "Kabupaten",
3669
+ "name": "Pamekasan",
3670
+ "code": "28",
3671
+ "full_code": "3528",
3672
+ "province_id": 11
3673
+ },
3674
+ {
3675
+ "id": 460,
3676
+ "type": "Kabupaten",
3677
+ "name": "Pandeglang",
3678
+ "code": "01",
3679
+ "full_code": "3601",
3680
+ "province_id": 3
3681
+ },
3682
+ {
3683
+ "id": 461,
3684
+ "type": "Kabupaten",
3685
+ "name": "Pangandaran",
3686
+ "code": "18",
3687
+ "full_code": "3218",
3688
+ "province_id": 9
3689
+ },
3690
+ {
3691
+ "id": 462,
3692
+ "type": "Kabupaten",
3693
+ "name": "Pangkajene Kepulauan",
3694
+ "code": "10",
3695
+ "full_code": "7310",
3696
+ "province_id": 32
3697
+ },
3698
+ {
3699
+ "id": 463,
3700
+ "type": "Kota",
3701
+ "name": "Pangkal Pinang",
3702
+ "code": "71",
3703
+ "full_code": "1971",
3704
+ "province_id": 17
3705
+ },
3706
+ {
3707
+ "id": 464,
3708
+ "type": "Kabupaten",
3709
+ "name": "Paniai",
3710
+ "code": "03",
3711
+ "full_code": "9403",
3712
+ "province_id": 29
3713
+ },
3714
+ {
3715
+ "id": 465,
3716
+ "type": "Kota",
3717
+ "name": "Pare Pare",
3718
+ "code": "72",
3719
+ "full_code": "7372",
3720
+ "province_id": 32
3721
+ },
3722
+ {
3723
+ "id": 466,
3724
+ "type": "Kota",
3725
+ "name": "Pariaman",
3726
+ "code": "77",
3727
+ "full_code": "1377",
3728
+ "province_id": 36
3729
+ },
3730
+ {
3731
+ "id": 467,
3732
+ "type": "Kabupaten",
3733
+ "name": "Parigi Moutong",
3734
+ "code": "08",
3735
+ "full_code": "7208",
3736
+ "province_id": 33
3737
+ },
3738
+ {
3739
+ "id": 468,
3740
+ "type": "Kabupaten",
3741
+ "name": "Pasaman",
3742
+ "code": "08",
3743
+ "full_code": "1308",
3744
+ "province_id": 36
3745
+ },
3746
+ {
3747
+ "id": 469,
3748
+ "type": "Kabupaten",
3749
+ "name": "Pasaman Barat",
3750
+ "code": "12",
3751
+ "full_code": "1312",
3752
+ "province_id": 36
3753
+ },
3754
+ {
3755
+ "id": 470,
3756
+ "type": "Kabupaten",
3757
+ "name": "Pasangkayu (Mamuju Utara)",
3758
+ "code": "01",
3759
+ "full_code": "7601",
3760
+ "province_id": 31
3761
+ },
3762
+ {
3763
+ "id": 471,
3764
+ "type": "Kabupaten",
3765
+ "name": "Paser",
3766
+ "code": "01",
3767
+ "full_code": "6401",
3768
+ "province_id": 15
3769
+ },
3770
+ {
3771
+ "id": 472,
3772
+ "type": "Kabupaten",
3773
+ "name": "Pasuruan",
3774
+ "code": "14",
3775
+ "full_code": "3514",
3776
+ "province_id": 11
3777
+ },
3778
+ {
3779
+ "id": 473,
3780
+ "type": "Kota",
3781
+ "name": "Pasuruan",
3782
+ "code": "75",
3783
+ "full_code": "3575",
3784
+ "province_id": 11
3785
+ },
3786
+ {
3787
+ "id": 474,
3788
+ "type": "Kabupaten",
3789
+ "name": "Pati",
3790
+ "code": "18",
3791
+ "full_code": "3318",
3792
+ "province_id": 10
3793
+ },
3794
+ {
3795
+ "id": 475,
3796
+ "type": "Kota",
3797
+ "name": "Payakumbuh",
3798
+ "code": "76",
3799
+ "full_code": "1376",
3800
+ "province_id": 36
3801
+ },
3802
+ {
3803
+ "id": 476,
3804
+ "type": "Kabupaten",
3805
+ "name": "Pegunungan Arfak",
3806
+ "code": "12",
3807
+ "full_code": "9212",
3808
+ "province_id": 25
3809
+ },
3810
+ {
3811
+ "id": 477,
3812
+ "type": "Kabupaten",
3813
+ "name": "Pegunungan Bintang",
3814
+ "code": "02",
3815
+ "full_code": "9502",
3816
+ "province_id": 27
3817
+ },
3818
+ {
3819
+ "id": 478,
3820
+ "type": "Kabupaten",
3821
+ "name": "Pekalongan",
3822
+ "code": "26",
3823
+ "full_code": "3326",
3824
+ "province_id": 10
3825
+ },
3826
+ {
3827
+ "id": 479,
3828
+ "type": "Kota",
3829
+ "name": "Pekalongan",
3830
+ "code": "75",
3831
+ "full_code": "3375",
3832
+ "province_id": 10
3833
+ },
3834
+ {
3835
+ "id": 480,
3836
+ "type": "Kota",
3837
+ "name": "Pekanbaru",
3838
+ "code": "71",
3839
+ "full_code": "1471",
3840
+ "province_id": 30
3841
+ },
3842
+ {
3843
+ "id": 481,
3844
+ "type": "Kabupaten",
3845
+ "name": "Pelalawan",
3846
+ "code": "05",
3847
+ "full_code": "1405",
3848
+ "province_id": 30
3849
+ },
3850
+ {
3851
+ "id": 482,
3852
+ "type": "Kabupaten",
3853
+ "name": "Pemalang",
3854
+ "code": "27",
3855
+ "full_code": "3327",
3856
+ "province_id": 10
3857
+ },
3858
+ {
3859
+ "id": 483,
3860
+ "type": "Kota",
3861
+ "name": "Pematangsiantar",
3862
+ "code": "72",
3863
+ "full_code": "1272",
3864
+ "province_id": 38
3865
+ },
3866
+ {
3867
+ "id": 484,
3868
+ "type": "Kabupaten",
3869
+ "name": "Penajam Paser Utara",
3870
+ "code": "09",
3871
+ "full_code": "6409",
3872
+ "province_id": 15
3873
+ },
3874
+ {
3875
+ "id": 485,
3876
+ "type": "Kabupaten",
3877
+ "name": "Penukal Abab Lematang Ilir",
3878
+ "code": "12",
3879
+ "full_code": "1612",
3880
+ "province_id": 37
3881
+ },
3882
+ {
3883
+ "id": 486,
3884
+ "type": "Kabupaten",
3885
+ "name": "Pesawaran",
3886
+ "code": "09",
3887
+ "full_code": "1809",
3888
+ "province_id": 19
3889
+ },
3890
+ {
3891
+ "id": 487,
3892
+ "type": "Kabupaten",
3893
+ "name": "Pesisir Barat",
3894
+ "code": "13",
3895
+ "full_code": "1813",
3896
+ "province_id": 19
3897
+ },
3898
+ {
3899
+ "id": 488,
3900
+ "type": "Kabupaten",
3901
+ "name": "Pesisir Selatan",
3902
+ "code": "01",
3903
+ "full_code": "1301",
3904
+ "province_id": 36
3905
+ },
3906
+ {
3907
+ "id": 489,
3908
+ "type": "Kabupaten",
3909
+ "name": "Pidie",
3910
+ "code": "07",
3911
+ "full_code": "1107",
3912
+ "province_id": 1
3913
+ },
3914
+ {
3915
+ "id": 490,
3916
+ "type": "Kabupaten",
3917
+ "name": "Pidie Jaya",
3918
+ "code": "18",
3919
+ "full_code": "1118",
3920
+ "province_id": 1
3921
+ },
3922
+ {
3923
+ "id": 491,
3924
+ "type": "Kabupaten",
3925
+ "name": "Pinrang",
3926
+ "code": "15",
3927
+ "full_code": "7315",
3928
+ "province_id": 32
3929
+ },
3930
+ {
3931
+ "id": 492,
3932
+ "type": "Kabupaten",
3933
+ "name": "Polewali Mandar",
3934
+ "code": "04",
3935
+ "full_code": "7604",
3936
+ "province_id": 31
3937
+ },
3938
+ {
3939
+ "id": 493,
3940
+ "type": "Kabupaten",
3941
+ "name": "Ponorogo",
3942
+ "code": "02",
3943
+ "full_code": "3502",
3944
+ "province_id": 11
3945
+ },
3946
+ {
3947
+ "id": 494,
3948
+ "type": "Kota",
3949
+ "name": "Pontianak",
3950
+ "code": "71",
3951
+ "full_code": "6171",
3952
+ "province_id": 12
3953
+ },
3954
+ {
3955
+ "id": 495,
3956
+ "type": "Kabupaten",
3957
+ "name": "Poso",
3958
+ "code": "02",
3959
+ "full_code": "7202",
3960
+ "province_id": 33
3961
+ },
3962
+ {
3963
+ "id": 496,
3964
+ "type": "Kota",
3965
+ "name": "Prabumulih",
3966
+ "code": "74",
3967
+ "full_code": "1674",
3968
+ "province_id": 37
3969
+ },
3970
+ {
3971
+ "id": 497,
3972
+ "type": "Kabupaten",
3973
+ "name": "Pringsewu",
3974
+ "code": "10",
3975
+ "full_code": "1810",
3976
+ "province_id": 19
3977
+ },
3978
+ {
3979
+ "id": 498,
3980
+ "type": "Kabupaten",
3981
+ "name": "Probolinggo",
3982
+ "code": "13",
3983
+ "full_code": "3513",
3984
+ "province_id": 11
3985
+ },
3986
+ {
3987
+ "id": 499,
3988
+ "type": "Kota",
3989
+ "name": "Probolinggo",
3990
+ "code": "74",
3991
+ "full_code": "3574",
3992
+ "province_id": 11
3993
+ },
3994
+ {
3995
+ "id": 500,
3996
+ "type": "Kabupaten",
3997
+ "name": "Pulang Pisau",
3998
+ "code": "11",
3999
+ "full_code": "6211",
4000
+ "province_id": 14
4001
+ },
4002
+ {
4003
+ "id": 501,
4004
+ "type": "Kabupaten",
4005
+ "name": "Pulau Morotai",
4006
+ "code": "07",
4007
+ "full_code": "8207",
4008
+ "province_id": 21
4009
+ },
4010
+ {
4011
+ "id": 502,
4012
+ "type": "Kabupaten",
4013
+ "name": "Pulau Taliabu",
4014
+ "code": "08",
4015
+ "full_code": "8208",
4016
+ "province_id": 21
4017
+ },
4018
+ {
4019
+ "id": 503,
4020
+ "type": "Kabupaten",
4021
+ "name": "Puncak",
4022
+ "code": "05",
4023
+ "full_code": "9405",
4024
+ "province_id": 29
4025
+ },
4026
+ {
4027
+ "id": 504,
4028
+ "type": "Kabupaten",
4029
+ "name": "Puncak Jaya",
4030
+ "code": "02",
4031
+ "full_code": "9402",
4032
+ "province_id": 29
4033
+ },
4034
+ {
4035
+ "id": 505,
4036
+ "type": "Kabupaten",
4037
+ "name": "Purbalingga",
4038
+ "code": "03",
4039
+ "full_code": "3303",
4040
+ "province_id": 10
4041
+ },
4042
+ {
4043
+ "id": 506,
4044
+ "type": "Kabupaten",
4045
+ "name": "Purwakarta",
4046
+ "code": "14",
4047
+ "full_code": "3214",
4048
+ "province_id": 9
4049
+ },
4050
+ {
4051
+ "id": 507,
4052
+ "type": "Kabupaten",
4053
+ "name": "Purworejo",
4054
+ "code": "06",
4055
+ "full_code": "3306",
4056
+ "province_id": 10
4057
+ },
4058
+ {
4059
+ "id": 508,
4060
+ "type": "Kabupaten",
4061
+ "name": "Raja Ampat",
4062
+ "code": "05",
4063
+ "full_code": "9205",
4064
+ "province_id": 26
4065
+ },
4066
+ {
4067
+ "id": 509,
4068
+ "type": "Kabupaten",
4069
+ "name": "Rejang Lebong",
4070
+ "code": "02",
4071
+ "full_code": "1702",
4072
+ "province_id": 4
4073
+ },
4074
+ {
4075
+ "id": 510,
4076
+ "type": "Kabupaten",
4077
+ "name": "Rembang",
4078
+ "code": "17",
4079
+ "full_code": "3317",
4080
+ "province_id": 10
4081
+ },
4082
+ {
4083
+ "id": 511,
4084
+ "type": "Kabupaten",
4085
+ "name": "Rokan Hilir",
4086
+ "code": "07",
4087
+ "full_code": "1407",
4088
+ "province_id": 30
4089
+ },
4090
+ {
4091
+ "id": 512,
4092
+ "type": "Kabupaten",
4093
+ "name": "Rokan Hulu",
4094
+ "code": "06",
4095
+ "full_code": "1406",
4096
+ "province_id": 30
4097
+ },
4098
+ {
4099
+ "id": 513,
4100
+ "type": "Kabupaten",
4101
+ "name": "Rote Ndao",
4102
+ "code": "14",
4103
+ "full_code": "5314",
4104
+ "province_id": 23
4105
+ },
4106
+ {
4107
+ "id": 514,
4108
+ "type": "Kota",
4109
+ "name": "Sabang",
4110
+ "code": "72",
4111
+ "full_code": "1172",
4112
+ "province_id": 1
4113
+ }
4114
+ ]
space/space/space/space/space/assets/seeds/province.json ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": 1,
4
+ "name": "Aceh (NAD)",
5
+ "code": "11"
6
+ },
7
+ {
8
+ "id": 2,
9
+ "name": "Bali",
10
+ "code": "51"
11
+ },
12
+ {
13
+ "id": 3,
14
+ "name": "Banten",
15
+ "code": "36"
16
+ },
17
+ {
18
+ "id": 4,
19
+ "name": "Bengkulu",
20
+ "code": "17"
21
+ },
22
+ {
23
+ "id": 5,
24
+ "name": "DI Yogyakarta",
25
+ "code": "34"
26
+ },
27
+ {
28
+ "id": 6,
29
+ "name": "DKI Jakarta",
30
+ "code": "31"
31
+ },
32
+ {
33
+ "id": 7,
34
+ "name": "Gorontalo",
35
+ "code": "75"
36
+ },
37
+ {
38
+ "id": 8,
39
+ "name": "Jambi",
40
+ "code": "15"
41
+ },
42
+ {
43
+ "id": 9,
44
+ "name": "Jawa Barat",
45
+ "code": "32"
46
+ },
47
+ {
48
+ "id": 10,
49
+ "name": "Jawa Tengah",
50
+ "code": "33"
51
+ },
52
+ {
53
+ "id": 11,
54
+ "name": "Jawa Timur",
55
+ "code": "35"
56
+ },
57
+ {
58
+ "id": 12,
59
+ "name": "Kalimantan Barat",
60
+ "code": "61"
61
+ },
62
+ {
63
+ "id": 13,
64
+ "name": "Kalimantan Selatan",
65
+ "code": "63"
66
+ },
67
+ {
68
+ "id": 14,
69
+ "name": "Kalimantan Tengah",
70
+ "code": "62"
71
+ },
72
+ {
73
+ "id": 15,
74
+ "name": "Kalimantan Timur",
75
+ "code": "64"
76
+ },
77
+ {
78
+ "id": 16,
79
+ "name": "Kalimantan Utara",
80
+ "code": "65"
81
+ },
82
+ {
83
+ "id": 17,
84
+ "name": "Kepulauan Bangka Belitung",
85
+ "code": "19"
86
+ },
87
+ {
88
+ "id": 18,
89
+ "name": "Kepulauan Riau",
90
+ "code": "21"
91
+ },
92
+ {
93
+ "id": 19,
94
+ "name": "Lampung",
95
+ "code": "18"
96
+ },
97
+ {
98
+ "id": 20,
99
+ "name": "Maluku",
100
+ "code": "81"
101
+ },
102
+ {
103
+ "id": 21,
104
+ "name": "Maluku Utara",
105
+ "code": "82"
106
+ },
107
+ {
108
+ "id": 22,
109
+ "name": "Nusa Tenggara Barat (NTB)",
110
+ "code": "52"
111
+ },
112
+ {
113
+ "id": 23,
114
+ "name": "Nusa Tenggara Timur (NTT)",
115
+ "code": "53"
116
+ },
117
+ {
118
+ "id": 24,
119
+ "name": "Papua",
120
+ "code": "91"
121
+ },
122
+ {
123
+ "id": 25,
124
+ "name": "Papua Barat",
125
+ "code": "92"
126
+ },
127
+ {
128
+ "id": 26,
129
+ "name": "Papua Barat Daya",
130
+ "code": "92"
131
+ },
132
+ {
133
+ "id": 27,
134
+ "name": "Papua Pegunungan",
135
+ "code": "95"
136
+ },
137
+ {
138
+ "id": 28,
139
+ "name": "Papua Selatan",
140
+ "code": "93"
141
+ },
142
+ {
143
+ "id": 29,
144
+ "name": "Papua Tengah",
145
+ "code": "94"
146
+ },
147
+ {
148
+ "id": 30,
149
+ "name": "Riau",
150
+ "code": "14"
151
+ },
152
+ {
153
+ "id": 31,
154
+ "name": "Sulawesi Barat",
155
+ "code": "76"
156
+ },
157
+ {
158
+ "id": 32,
159
+ "name": "Sulawesi Selatan",
160
+ "code": "73"
161
+ },
162
+ {
163
+ "id": 33,
164
+ "name": "Sulawesi Tengah",
165
+ "code": "72"
166
+ },
167
+ {
168
+ "id": 34,
169
+ "name": "Sulawesi Tenggara",
170
+ "code": "74"
171
+ },
172
+ {
173
+ "id": 35,
174
+ "name": "Sulawesi Utara",
175
+ "code": "71"
176
+ },
177
+ {
178
+ "id": 36,
179
+ "name": "Sumatera Barat",
180
+ "code": "13"
181
+ },
182
+ {
183
+ "id": 37,
184
+ "name": "Sumatera Selatan",
185
+ "code": "16"
186
+ },
187
+ {
188
+ "id": 38,
189
+ "name": "Sumatera Utara",
190
+ "code": "12"
191
+ }
192
+ ]
space/space/space/space/space/controller/options/options_controller.go ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package options_controller
2
+
3
+ import (
4
+ "net/http"
5
+
6
+ "api.qobiltu.id/models"
7
+ "api.qobiltu.id/response"
8
+ "api.qobiltu.id/services"
9
+ "github.com/gin-gonic/gin"
10
+ )
11
+
12
+ type OptionsController interface {
13
+ CreateOptions(ctx *gin.Context)
14
+ ListOptions(ctx *gin.Context)
15
+ ListOptionsBySlug(ctx *gin.Context)
16
+ }
17
+
18
+ type optionsController struct {
19
+ optionService services.OptionsService
20
+ }
21
+
22
+ func NewOptionsController(optionService services.OptionsService) OptionsController {
23
+ return &optionsController{
24
+ optionService: optionService,
25
+ }
26
+ }
27
+
28
+ func (c *optionsController) CreateOptions(ctx *gin.Context) {
29
+ var req []models.MultipleOptionsRequest
30
+ if err := ctx.ShouldBindJSON(&req); err != nil {
31
+ response.HandleError(ctx, models.Exception{
32
+ Message: "Invalid body request",
33
+ BadRequest: true,
34
+ Err: err,
35
+ })
36
+ return
37
+ }
38
+
39
+ res, err := c.optionService.CreateOptions(ctx, &models.CreateOptionsRequest{
40
+ Options: req,
41
+ })
42
+ if err != nil {
43
+ response.HandleError(ctx, err)
44
+ return
45
+ }
46
+
47
+ response.HandleSuccess(ctx, http.StatusOK, "Options created successfully", res, nil)
48
+ }
49
+
50
+ func (c *optionsController) ListOptions(ctx *gin.Context) {
51
+ res, err := c.optionService.ListOptions(ctx)
52
+ if err != nil {
53
+ response.HandleError(ctx, err)
54
+ return
55
+ }
56
+
57
+ response.HandleSuccess(ctx, http.StatusOK, "Options retrieved successfully", res.Options, nil)
58
+ }
59
+
60
+ func (c *optionsController) ListOptionsBySlug(ctx *gin.Context) {
61
+ slug := ctx.Param("slug")
62
+
63
+ res, err := c.optionService.ListOptionsBySlug(ctx, &models.ListOptionsBySlugRequest{
64
+ Slug: slug,
65
+ })
66
+ if err != nil {
67
+ response.HandleError(ctx, err)
68
+ return
69
+ }
70
+
71
+ response.HandleSuccess(ctx, http.StatusOK, "Options retrieved successfully", res, nil)
72
+ }
space/space/space/space/space/controller/region/region_controller.go ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package cv_controller
2
+
3
+ import (
4
+ "net/http"
5
+ "strconv"
6
+
7
+ "api.qobiltu.id/models"
8
+ "api.qobiltu.id/response"
9
+ "api.qobiltu.id/services"
10
+ "github.com/gin-gonic/gin"
11
+ )
12
+
13
+ type RegionController interface {
14
+ SeedProvince(ctx *gin.Context)
15
+ SeedCity(ctx *gin.Context)
16
+
17
+ ListProvinces(ctx *gin.Context)
18
+ ListCities(ctx *gin.Context)
19
+ }
20
+
21
+ type regionController struct {
22
+ regionService services.RegionService
23
+ }
24
+
25
+ func NewRegionController(regionService services.RegionService) RegionController {
26
+ return &regionController{
27
+ regionService: regionService,
28
+ }
29
+ }
30
+
31
+ func (c *regionController) SeedProvince(ctx *gin.Context) {
32
+ err := c.regionService.SeedProvince(ctx)
33
+ if err != nil {
34
+ response.HandleError(ctx, err)
35
+ return
36
+ }
37
+
38
+ response.HandleSuccess(ctx, http.StatusOK, "Province seeded", nil, nil)
39
+ }
40
+
41
+ func (c *regionController) SeedCity(ctx *gin.Context) {
42
+ err := c.regionService.SeedCity(ctx)
43
+ if err != nil {
44
+ response.HandleError(ctx, err)
45
+ return
46
+ }
47
+
48
+ response.HandleSuccess(ctx, http.StatusOK, "City seeded", nil, nil)
49
+ }
50
+
51
+ func (c *regionController) ListProvinces(ctx *gin.Context) {
52
+ provinces, err := c.regionService.ListProvinces(ctx)
53
+ if err != nil {
54
+ response.HandleError(ctx, err)
55
+ return
56
+ }
57
+
58
+ response.HandleSuccess(ctx, http.StatusOK, "Provinces retrieved", provinces, nil)
59
+ }
60
+
61
+ func (c *regionController) ListCities(ctx *gin.Context) {
62
+ provinceID := ctx.Query("province_id")
63
+
64
+ if provinceID == "" {
65
+ cities, err := c.regionService.ListCities(ctx)
66
+ if err != nil {
67
+ response.HandleError(ctx, err)
68
+ return
69
+ }
70
+
71
+ response.HandleSuccess(ctx, http.StatusOK, "Cities retrieved", cities, nil)
72
+ return
73
+ }
74
+
75
+ provinceIDInt, err := strconv.ParseInt(provinceID, 10, 64)
76
+ if err != nil {
77
+ response.HandleError(ctx, err)
78
+ return
79
+ }
80
+
81
+ req := models.ListCitiesByProvinceIdRequest{
82
+ ProvinceID: provinceIDInt,
83
+ }
84
+
85
+ cities, err := c.regionService.ListCitiesByProvinceId(ctx, &req)
86
+ if err != nil {
87
+ response.HandleError(ctx, err)
88
+ return
89
+ }
90
+
91
+ response.HandleSuccess(ctx, http.StatusOK, "Cities by province retrieved", cities, nil)
92
+ }
space/space/space/space/space/main.go CHANGED
@@ -9,7 +9,9 @@ import (
9
  cv_controller "api.qobiltu.id/controller/cv"
10
  email_controller "api.qobiltu.id/controller/email"
11
  marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
 
12
  partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
 
13
  "api.qobiltu.id/pkg/mail"
14
  "api.qobiltu.id/pkg/storage"
15
  "api.qobiltu.id/pkg/validation"
@@ -55,6 +57,15 @@ func main() {
55
  worker.AsyncTaskDistributor = taskDistributor
56
 
57
  // setup repo, service, and controller
 
 
 
 
 
 
 
 
 
58
  emailRepository := repositories.NewEmailRepository(config.DB)
59
  emailService := services.NewEmailService(emailRepository, taskDistributor)
60
  emailController := email_controller.NewEmailController(emailService)
@@ -78,6 +89,8 @@ func main() {
78
 
79
  // create server
80
  s, err := router.NewServer(
 
 
81
  emailController,
82
  cvController,
83
  marriageReadinessProfileController,
 
9
  cv_controller "api.qobiltu.id/controller/cv"
10
  email_controller "api.qobiltu.id/controller/email"
11
  marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
12
+ options_controller "api.qobiltu.id/controller/options"
13
  partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
14
+ region_controller "api.qobiltu.id/controller/region"
15
  "api.qobiltu.id/pkg/mail"
16
  "api.qobiltu.id/pkg/storage"
17
  "api.qobiltu.id/pkg/validation"
 
57
  worker.AsyncTaskDistributor = taskDistributor
58
 
59
  // setup repo, service, and controller
60
+
61
+ regionRepository := repositories.NewRegionRepository(config.DB)
62
+ regionService := services.NewRegionService(regionRepository)
63
+ regionController := region_controller.NewRegionController(regionService)
64
+
65
+ optionRepository := repositories.NewOptionRepository(config.DB)
66
+ optionService := services.NewOptionsService(optionRepository)
67
+ optionController := options_controller.NewOptionsController(optionService)
68
+
69
  emailRepository := repositories.NewEmailRepository(config.DB)
70
  emailService := services.NewEmailService(emailRepository, taskDistributor)
71
  emailController := email_controller.NewEmailController(emailService)
 
89
 
90
  // create server
91
  s, err := router.NewServer(
92
+ regionController,
93
+ optionController,
94
  emailController,
95
  cvController,
96
  marriageReadinessProfileController,
space/space/space/space/space/models/database_orm_model.go CHANGED
@@ -105,17 +105,20 @@ type AcademyContent struct {
105
  AcademyMaterialID uint `json:"academy_material_id"`
106
  Description string `json:"description"`
107
  }
 
108
  type OptionCategory struct {
109
- ID uint `gorm:"primaryKey" json:"id"`
110
- OptionName string `json:"option_name"`
111
- OptionSlug string `json:"option_slug" gorm:"uniqueIndex"`
 
112
  }
113
 
114
  type OptionValues struct {
115
- ID uint `gorm:"primaryKey" json:"id"`
116
- OptionCategoryID uint `json:"option_category_id"`
117
  OptionValue string `json:"option_value"`
118
  }
 
119
  type AcademyMaterialProgress struct {
120
  ID uint `gorm:"primaryKey" json:"id"`
121
  UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
 
105
  AcademyMaterialID uint `json:"academy_material_id"`
106
  Description string `json:"description"`
107
  }
108
+
109
  type OptionCategory struct {
110
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
111
+ OptionName string `json:"option_name"`
112
+ OptionSlug string `json:"option_slug" gorm:"uniqueIndex"`
113
+ OptionValues []OptionValues `gorm:"foreignKey:OptionCategoryID" json:"-"`
114
  }
115
 
116
  type OptionValues struct {
117
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
118
+ OptionCategoryID int64 `json:"option_category_id"`
119
  OptionValue string `json:"option_value"`
120
  }
121
+
122
  type AcademyMaterialProgress struct {
123
  ID uint `gorm:"primaryKey" json:"id"`
124
  UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
space/space/space/space/space/models/request_model.go CHANGED
@@ -28,10 +28,33 @@ type CreateVerifyEmailRequest struct {
28
  Token uint `json:"token" binding:"required"`
29
  }
30
 
31
- type OptionsRequest struct {
32
- OptionName string `json:"option_name" binding:"required"`
33
- OptionValue []string `json:"option_values" binding:"required"`
34
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  type ExternalAuthRequest struct {
37
  OauthID string `json:"oauth_id" binding:"required"`
@@ -57,6 +80,12 @@ type AnswerQuizRequest struct {
57
  Answer int `json:"answer" binding:"required"`
58
  }
59
 
 
 
 
 
 
 
60
  type (
61
  CreateEmailVerificationRequest struct {
62
  AccountID int64 `json:"-"`
 
28
  Token uint `json:"token" binding:"required"`
29
  }
30
 
31
+ type (
32
+ MultipleOptionsRequest struct {
33
+ OptionName string `json:"option_name" validate:"required"`
34
+ OptionValue []string `json:"option_values" validate:"required"`
35
+ }
36
+
37
+ CreateOptionsRequest struct {
38
+ Options []MultipleOptionsRequest `json:"options" validate:"required"`
39
+ }
40
+
41
+ CreateOptionsResponse struct {
42
+ Options []Options `json:"options"`
43
+ }
44
+
45
+ ListOptionsResponse struct {
46
+ Options []Options `json:"options"`
47
+ }
48
+
49
+ ListOptionsBySlugRequest struct {
50
+ Slug string `json:"slug"`
51
+ }
52
+
53
+ ListOptionsBySlugResponse struct {
54
+ OptionCategory *OptionCategory `json:"option_category"`
55
+ OptionValues []OptionValues `json:"option_values"`
56
+ }
57
+ )
58
 
59
  type ExternalAuthRequest struct {
60
  OauthID string `json:"oauth_id" binding:"required"`
 
80
  Answer int `json:"answer" binding:"required"`
81
  }
82
 
83
+ type (
84
+ ListCitiesByProvinceIdRequest struct {
85
+ ProvinceID int64 `json:"province_id" binding:"required"`
86
+ }
87
+ )
88
+
89
  type (
90
  CreateEmailVerificationRequest struct {
91
  AccountID int64 `json:"-"`
space/space/space/space/space/models/response_model.go CHANGED
@@ -18,10 +18,12 @@ type AuthenticatedUser struct {
18
  Account Account `json:"account"`
19
  Token string `json:"token"`
20
  }
 
21
  type Options struct {
22
  OptionCategory OptionCategory `json:"option_category"`
23
  OptionValues []OptionValues `json:"option_values"`
24
  }
 
25
  type OptionsResponse struct {
26
  Options []Options `json:"options"`
27
  }
 
18
  Account Account `json:"account"`
19
  Token string `json:"token"`
20
  }
21
+
22
  type Options struct {
23
  OptionCategory OptionCategory `json:"option_category"`
24
  OptionValues []OptionValues `json:"option_values"`
25
  }
26
+
27
  type OptionsResponse struct {
28
  Options []Options `json:"options"`
29
  }
space/space/space/space/space/repositories/option_repository.go CHANGED
@@ -1,43 +1,89 @@
1
  package repositories
2
 
3
  import (
 
 
4
  "api.qobiltu.id/models"
 
5
  )
6
 
7
- func CreateOptionCategory(categories models.OptionCategory) Repository[models.OptionCategory, models.OptionCategory] {
8
- repo := Construct[models.OptionCategory, models.OptionCategory](
9
- categories,
10
- )
11
- Create(repo)
12
- return *repo
13
- }
14
-
15
- func CreateOptionValues(values models.OptionValues) Repository[models.OptionValues, models.OptionValues] {
16
- repo := Construct[models.OptionValues, models.OptionValues](
17
- values,
18
- )
19
- Create(repo)
20
- return *repo
21
- }
22
-
23
- func GetOptionCategoryBySlug(slug string) Repository[models.OptionCategory, models.OptionCategory] {
24
- repo := Construct[models.OptionCategory, models.OptionCategory](
25
- models.OptionCategory{OptionSlug: slug},
26
- )
27
- repo.Transactions(
28
- WhereGivenConstructor[models.OptionCategory, models.OptionCategory],
29
- Find[models.OptionCategory, models.OptionCategory],
30
- )
31
- return *repo
32
- }
33
-
34
- func GetOptionValuesByCategoryId(categoryId uint) Repository[models.OptionValues, []models.OptionValues] {
35
- repo := Construct[models.OptionValues, []models.OptionValues](
36
- models.OptionValues{OptionCategoryID: categoryId},
37
- )
38
- repo.Transactions(
39
- WhereGivenConstructor[models.OptionValues, []models.OptionValues],
40
- Find[models.OptionValues, []models.OptionValues],
41
- )
42
- return *repo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  }
 
1
  package repositories
2
 
3
  import (
4
+ "context"
5
+
6
  "api.qobiltu.id/models"
7
+ "gorm.io/gorm"
8
  )
9
 
10
+ type OptionRepository interface {
11
+ CreateOptionCategory(ctx context.Context, optionCategory *models.OptionCategory) (*models.OptionCategory, error)
12
+ CreateOptionValues(ctx context.Context, optionValue *models.OptionValues) (*models.OptionValues, error)
13
+ GetOptionCategoryBySlug(ctx context.Context, slug string) (*models.OptionCategory, error)
14
+ GetOptionValuesByCategoryId(ctx context.Context, categoryId int64) ([]models.OptionValues, error)
15
+ ListOptions(ctx context.Context) ([]models.OptionCategory, error)
16
+ DeleteOptionValuesByCategoryId(ctx context.Context, categoryId int64) error
17
+ DeleteOptionValue(ctx context.Context, id int64) error
18
+ }
19
+
20
+ type optionRepository struct {
21
+ db *gorm.DB
22
+ }
23
+
24
+ func NewOptionRepository(db *gorm.DB) OptionRepository {
25
+ return &optionRepository{db: db}
26
+ }
27
+
28
+ func (r *optionRepository) CreateOptionCategory(ctx context.Context, optionCategory *models.OptionCategory) (*models.OptionCategory, error) {
29
+ err := r.db.Save(&optionCategory).Error
30
+ if err != nil {
31
+ return nil, err
32
+ }
33
+ return optionCategory, nil
34
+ }
35
+
36
+ func (r *optionRepository) CreateOptionValues(ctx context.Context, optionValue *models.OptionValues) (*models.OptionValues, error) {
37
+ err := r.db.Save(&optionValue).Error
38
+ if err != nil {
39
+ return nil, err
40
+ }
41
+ return optionValue, nil
42
+ }
43
+
44
+ func (r *optionRepository) GetOptionCategoryBySlug(ctx context.Context, slug string) (*models.OptionCategory, error) {
45
+ var category models.OptionCategory
46
+ err := r.db.Where("option_slug = ?", slug).First(&category).Error
47
+ if err != nil {
48
+ return nil, err
49
+ }
50
+ return &category, nil
51
+ }
52
+
53
+ func (r *optionRepository) GetOptionValuesByCategoryId(ctx context.Context, categoryId int64) ([]models.OptionValues, error) {
54
+ values := make([]models.OptionValues, 0)
55
+ err := r.db.Where("option_category_id = ?", categoryId).Find(&values).Error
56
+ if err != nil {
57
+ return nil, err
58
+ }
59
+ return values, nil
60
+ }
61
+
62
+ func (r *optionRepository) ListOptions(ctx context.Context) ([]models.OptionCategory, error) {
63
+ categories := make([]models.OptionCategory, 0)
64
+
65
+ err := r.db.WithContext(ctx).
66
+ Preload("OptionValues").
67
+ Find(&categories).Error
68
+ if err != nil {
69
+ return nil, err
70
+ }
71
+
72
+ return categories, nil
73
+ }
74
+
75
+ func (r *optionRepository) DeleteOptionValuesByCategoryId(ctx context.Context, categoryId int64) error {
76
+ err := r.db.Where("option_category_id = ?", categoryId).Delete(&models.OptionValues{}).Error
77
+ if err != nil {
78
+ return err
79
+ }
80
+ return nil
81
+ }
82
+
83
+ func (r *optionRepository) DeleteOptionValue(ctx context.Context, id int64) error {
84
+ err := r.db.Where("id = ?", id).Delete(&models.OptionValues{}).Error
85
+ if err != nil {
86
+ return err
87
+ }
88
+ return nil
89
  }
space/space/space/space/space/repositories/region_repository.go CHANGED
@@ -1,47 +1,57 @@
1
  package repositories
2
 
3
  import (
 
 
4
  "api.qobiltu.id/models"
 
5
  )
6
 
7
- func BulkCreateProvince(provinces []models.RegionProvince) Repository[[]models.RegionProvince, []models.RegionProvince] {
8
- repo := Construct[[]models.RegionProvince, []models.RegionProvince](
9
- provinces,
10
- )
 
 
 
 
11
 
12
- Create(repo)
13
- return *repo
14
  }
15
 
16
- func BulkCreateCity(cities []models.RegionCity) Repository[[]models.RegionCity, []models.RegionCity] {
17
- repo := Construct[[]models.RegionCity, []models.RegionCity](
18
- cities,
19
- )
20
 
21
- Create(repo)
22
- return *repo
23
  }
24
 
25
- func GetListProvinces() Repository[models.RegionProvince, []models.RegionProvince] {
26
- repo := Construct[models.RegionProvince, []models.RegionProvince](
27
- models.RegionProvince{},
28
- )
 
 
 
 
 
 
 
29
 
30
- repo.Transactions(
31
- Find[models.RegionProvince, []models.RegionProvince],
32
- )
33
- return *repo
 
 
34
  }
35
 
36
- func GetListCitiesByProvinceId(provinceId uint) Repository[models.RegionCity, []models.RegionCity] {
37
- repo := Construct[models.RegionCity, []models.RegionCity](
38
- models.RegionCity{
39
- ProvinceID: provinceId,
40
- },
41
- )
42
- repo.Transactions(
43
- WhereGivenConstructor[models.RegionCity, []models.RegionCity],
44
- Find[models.RegionCity, []models.RegionCity],
45
- )
46
- return *repo
47
  }
 
1
  package repositories
2
 
3
  import (
4
+ "context"
5
+
6
  "api.qobiltu.id/models"
7
+ "gorm.io/gorm"
8
  )
9
 
10
+ type RegionRepository interface {
11
+ CreateProvince(ctx context.Context, provinces []models.RegionProvince) error
12
+ CreateCity(ctx context.Context, cities []models.RegionCity) error
13
+
14
+ ListProvinces(ctx context.Context) ([]models.RegionProvince, error)
15
+ ListCitiesByProvinceId(ctx context.Context, provinceId int64) ([]models.RegionCity, error)
16
+ ListCities(ctx context.Context) ([]models.RegionCity, error)
17
+ }
18
 
19
+ type regionRepository struct {
20
+ db *gorm.DB
21
  }
22
 
23
+ func NewRegionRepository(db *gorm.DB) RegionRepository {
24
+ return &regionRepository{db: db}
25
+ }
 
26
 
27
+ func (r *regionRepository) CreateProvince(ctx context.Context, provinces []models.RegionProvince) error {
28
+ return r.db.Save(provinces).Error
29
  }
30
 
31
+ func (r *regionRepository) CreateCity(ctx context.Context, cities []models.RegionCity) error {
32
+ return r.db.Save(cities).Error
33
+ }
34
+
35
+ func (r *regionRepository) ListProvinces(ctx context.Context) ([]models.RegionProvince, error) {
36
+ provinces := make([]models.RegionProvince, 0)
37
+ if err := r.db.Order("id ASC").Find(&provinces).Error; err != nil {
38
+ return nil, err
39
+ }
40
+ return provinces, nil
41
+ }
42
 
43
+ func (r *regionRepository) ListCitiesByProvinceId(ctx context.Context, provinceId int64) ([]models.RegionCity, error) {
44
+ cities := make([]models.RegionCity, 0)
45
+ if err := r.db.Where("province_id = ?", provinceId).Order("id ASC").Find(&cities).Error; err != nil {
46
+ return nil, err
47
+ }
48
+ return cities, nil
49
  }
50
 
51
+ func (r *regionRepository) ListCities(ctx context.Context) ([]models.RegionCity, error) {
52
+ cities := make([]models.RegionCity, 0)
53
+ if err := r.db.Find(&cities).Error; err != nil {
54
+ return nil, err
55
+ }
56
+ return cities, nil
 
 
 
 
 
57
  }
space/space/space/space/space/router/options_route.go CHANGED
@@ -1,20 +1,16 @@
1
  package router
2
 
3
- import (
4
- OptionsController "api.qobiltu.id/controller/options"
5
- CityController "api.qobiltu.id/controller/region/city"
6
- ProvinceController "api.qobiltu.id/controller/region/province"
7
- "github.com/gin-gonic/gin"
8
- )
9
-
10
- func OptionsRoute(router *gin.Engine) {
11
- routerGroup := router.Group("/api/v1/options")
12
  {
13
- routerGroup.POST("/create", OptionsController.AddOptions)
14
- routerGroup.GET("/list/:slug", OptionsController.List)
15
- routerGroup.GET("/region/provinces", ProvinceController.List)
16
- routerGroup.GET("/region/cities", CityController.List)
17
- routerGroup.POST("/region/seed-provinces", ProvinceController.Seeds)
18
- routerGroup.POST("/region/seed-cities", CityController.Seeds)
 
 
 
19
  }
20
  }
 
1
  package router
2
 
3
+ func (s *Server) OptionsRoute() {
4
+ routerGroup := s.router.Group("/api/v1/options")
 
 
 
 
 
 
 
5
  {
6
+ routerGroup.POST("/create", s.optionsController.CreateOptions)
7
+ routerGroup.GET("/list", s.optionsController.ListOptions)
8
+ routerGroup.GET("/list/:slug", s.optionsController.ListOptionsBySlug)
9
+
10
+ routerGroup.GET("/region/provinces", s.regionController.ListProvinces)
11
+ routerGroup.GET("/region/cities", s.regionController.ListCities)
12
+
13
+ routerGroup.POST("/region/seed-provinces", s.regionController.SeedProvince)
14
+ routerGroup.POST("/region/seed-cities", s.regionController.SeedCity)
15
  }
16
  }
space/space/space/space/space/router/router.go CHANGED
@@ -10,10 +10,10 @@ func (s *Server) setupRoutes() {
10
 
11
  AuthRoute(s.router)
12
  UserRoute(s.router)
13
- OptionsRoute(s.router)
14
  AcademyRoute(s.router)
15
  QuizRoute(s.router)
16
 
 
17
  s.EmailRoute()
18
  s.CVRoute()
19
  s.MarriageReadinessProfileRoute()
 
10
 
11
  AuthRoute(s.router)
12
  UserRoute(s.router)
 
13
  AcademyRoute(s.router)
14
  QuizRoute(s.router)
15
 
16
+ s.OptionsRoute()
17
  s.EmailRoute()
18
  s.CVRoute()
19
  s.MarriageReadinessProfileRoute()
space/space/space/space/space/router/server.go CHANGED
@@ -4,12 +4,16 @@ import (
4
  cv_controller "api.qobiltu.id/controller/cv"
5
  email_controller "api.qobiltu.id/controller/email"
6
  marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
 
7
  partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
 
8
  "github.com/gin-gonic/gin"
9
  )
10
 
11
  type Server struct {
12
  router *gin.Engine
 
 
13
  emailController email_controller.EmailController
14
  cvController cv_controller.CVController
15
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController
@@ -17,21 +21,24 @@ type Server struct {
17
  }
18
 
19
  func NewServer(
 
 
20
  emailController email_controller.EmailController,
21
  cvController cv_controller.CVController,
22
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController,
23
  partnerCriteriaController partner_criteria_controller.PartnerCriteriaController,
24
  ) (*Server, error) {
25
-
26
  router := gin.Default()
27
  router.Use(gin.Recovery())
28
 
29
  server := &Server{
 
 
 
30
  emailController: emailController,
31
  cvController: cvController,
32
  marriageReadinessProfileController: marriageReadinessProfileController,
33
  partnerCriteriaController: partnerCriteriaController,
34
- router: router,
35
  }
36
 
37
  server.setupRoutes()
 
4
  cv_controller "api.qobiltu.id/controller/cv"
5
  email_controller "api.qobiltu.id/controller/email"
6
  marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
7
+ options_controller "api.qobiltu.id/controller/options"
8
  partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
9
+ region_controller "api.qobiltu.id/controller/region"
10
  "github.com/gin-gonic/gin"
11
  )
12
 
13
  type Server struct {
14
  router *gin.Engine
15
+ regionController region_controller.RegionController
16
+ optionsController options_controller.OptionsController
17
  emailController email_controller.EmailController
18
  cvController cv_controller.CVController
19
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController
 
21
  }
22
 
23
  func NewServer(
24
+ regionController region_controller.RegionController,
25
+ optionsController options_controller.OptionsController,
26
  emailController email_controller.EmailController,
27
  cvController cv_controller.CVController,
28
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController,
29
  partnerCriteriaController partner_criteria_controller.PartnerCriteriaController,
30
  ) (*Server, error) {
 
31
  router := gin.Default()
32
  router.Use(gin.Recovery())
33
 
34
  server := &Server{
35
+ router: router,
36
+ regionController: regionController,
37
+ optionsController: optionsController,
38
  emailController: emailController,
39
  cvController: cvController,
40
  marriageReadinessProfileController: marriageReadinessProfileController,
41
  partnerCriteriaController: partnerCriteriaController,
 
42
  }
43
 
44
  server.setupRoutes()
space/space/space/space/space/services/options_service.go ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package services
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+
7
+ "api.qobiltu.id/models"
8
+ "api.qobiltu.id/repositories"
9
+ "api.qobiltu.id/response"
10
+ "github.com/gosimple/slug"
11
+ "gorm.io/gorm"
12
+ )
13
+
14
+ type OptionsService interface {
15
+ CreateOptions(ctx context.Context, req *models.CreateOptionsRequest) (*models.CreateOptionsResponse, error)
16
+ ListOptions(ctx context.Context) (*models.ListOptionsResponse, error)
17
+ ListOptionsBySlug(ctx context.Context, req *models.ListOptionsBySlugRequest) (*models.ListOptionsBySlugResponse, error)
18
+ }
19
+
20
+ type optionsService struct {
21
+ optionRepository repositories.OptionRepository
22
+ }
23
+
24
+ func NewOptionsService(optionRepository repositories.OptionRepository) OptionsService {
25
+ return &optionsService{
26
+ optionRepository: optionRepository,
27
+ }
28
+ }
29
+
30
+ func (s *optionsService) CreateOptions(ctx context.Context, req *models.CreateOptionsRequest) (*models.CreateOptionsResponse, error) {
31
+ createdOptions := make([]models.Options, 0)
32
+
33
+ for _, optReq := range req.Options {
34
+ slug := slug.Make(optReq.OptionName)
35
+
36
+ // 1. Cek apakah kategori sudah ada
37
+ category, err := s.optionRepository.GetOptionCategoryBySlug(ctx, slug)
38
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
39
+ return nil, response.HandleGormError(err, "Internal server error")
40
+ }
41
+
42
+ if errors.Is(err, gorm.ErrRecordNotFound) {
43
+ // 2. Kalau belum ada, buat kategori baru
44
+ newCategory := models.OptionCategory{
45
+ OptionName: optReq.OptionName,
46
+ OptionSlug: slug,
47
+ }
48
+
49
+ category, err = s.optionRepository.CreateOptionCategory(ctx, &newCategory)
50
+ if err != nil {
51
+ return nil, response.HandleGormError(err, "Internal server error")
52
+ }
53
+ }
54
+
55
+ // 3. Ambil semua option values yang ada
56
+ existingValues, err := s.optionRepository.GetOptionValuesByCategoryId(ctx, category.ID)
57
+ if err != nil {
58
+ return nil, response.HandleGormError(err, "Internal server error")
59
+ }
60
+
61
+ // 4. Buat map untuk nilai yang sudah ada
62
+ existingValuesMap := make(map[string]bool)
63
+ for _, val := range existingValues {
64
+ existingValuesMap[val.OptionValue] = true
65
+ }
66
+
67
+ // 5. Proses setiap nilai yang diminta
68
+ for _, val := range optReq.OptionValue {
69
+ // Jika nilai belum ada, buat baru
70
+ if !existingValuesMap[val] {
71
+ _, err := s.optionRepository.CreateOptionValues(ctx, &models.OptionValues{
72
+ OptionCategoryID: category.ID,
73
+ OptionValue: val,
74
+ })
75
+ if err != nil {
76
+ return nil, response.HandleGormError(err, "Failed to create option value")
77
+ }
78
+ }
79
+ // Jika nilai sudah ada, tidak perlu dilakukan apa-apa
80
+ }
81
+
82
+ // 6. Hapus nilai yang tidak ada dalam request tapi ada di database
83
+ requestedValuesMap := make(map[string]bool)
84
+ for _, val := range optReq.OptionValue {
85
+ requestedValuesMap[val] = true
86
+ }
87
+
88
+ for _, existingVal := range existingValues {
89
+ if !requestedValuesMap[existingVal.OptionValue] {
90
+ err := s.optionRepository.DeleteOptionValue(ctx, existingVal.ID)
91
+ if err != nil {
92
+ return nil, response.HandleGormError(err, "Failed to delete unused option value")
93
+ }
94
+ }
95
+ }
96
+
97
+ // 7. Ambil ulang semua value terbaru
98
+ allValues, err := s.optionRepository.GetOptionValuesByCategoryId(ctx, category.ID)
99
+ if err != nil {
100
+ return nil, response.HandleGormError(err, "Internal server error")
101
+ }
102
+
103
+ // 8. Masukkan ke hasil response
104
+ createdOptions = append(createdOptions, models.Options{
105
+ OptionCategory: *category,
106
+ OptionValues: allValues,
107
+ })
108
+ }
109
+
110
+ return &models.CreateOptionsResponse{
111
+ Options: createdOptions,
112
+ }, nil
113
+ }
114
+
115
+ func (s *optionsService) ListOptions(ctx context.Context) (*models.ListOptionsResponse, error) {
116
+ listOptions, err := s.optionRepository.ListOptions(ctx)
117
+ if err != nil {
118
+ return nil, response.HandleGormError(err, "Internal server error")
119
+ }
120
+
121
+ options := make([]models.Options, 0)
122
+ for _, option := range listOptions {
123
+ options = append(options, models.Options{
124
+ OptionCategory: option,
125
+ OptionValues: option.OptionValues,
126
+ })
127
+ }
128
+
129
+ return &models.ListOptionsResponse{
130
+ Options: options,
131
+ }, nil
132
+ }
133
+
134
+ func (s *optionsService) ListOptionsBySlug(ctx context.Context, req *models.ListOptionsBySlugRequest) (*models.ListOptionsBySlugResponse, error) {
135
+ optionCategory, err := s.optionRepository.GetOptionCategoryBySlug(ctx, req.Slug)
136
+ if err != nil {
137
+ return nil, response.HandleGormError(err, "Internal server error")
138
+ }
139
+
140
+ optionValues, err := s.optionRepository.GetOptionValuesByCategoryId(ctx, optionCategory.ID)
141
+ if err != nil {
142
+ return nil, response.HandleGormError(err, "Internal server error")
143
+ }
144
+
145
+ return &models.ListOptionsBySlugResponse{
146
+ OptionCategory: optionCategory,
147
+ OptionValues: optionValues,
148
+ }, nil
149
+ }
space/space/space/space/space/services/region_service.go CHANGED
@@ -1,90 +1,68 @@
1
  package services
2
 
3
  import (
 
4
  "encoding/json"
5
- "log"
6
- "os"
7
- "path/filepath"
8
- "runtime"
9
 
 
10
  "api.qobiltu.id/models"
11
  "api.qobiltu.id/repositories"
 
12
  )
13
 
14
- type ProvinceService struct {
15
- Service[models.RegionProvince, []models.RegionProvince]
 
 
 
 
 
16
  }
17
 
18
- type CityService struct {
19
- Service[models.RegionCity, []models.RegionCity]
20
  }
21
 
22
- func seedCity() ([]models.RegionCity, error) {
23
- log.Println("Seed City")
24
- _, b, _, _ := runtime.Caller(0)
25
- basePath := filepath.Dir(b)
26
- file, err := os.Open(filepath.Join(basePath, "..", "utils", "seeds", "city.json"))
27
- if err != nil {
28
- return nil, err
29
- }
30
- defer file.Close()
31
- var cities []models.RegionCity
32
- if err := json.NewDecoder(file).Decode(&cities); err != nil {
33
- return nil, err
34
- }
35
- return cities, nil
36
  }
37
 
38
- func seedProvince() ([]models.RegionProvince, error) {
39
- log.Println("Seed City")
40
- _, b, _, _ := runtime.Caller(0)
41
- basePath := filepath.Dir(b)
42
- file, err := os.Open(filepath.Join(basePath, "..", "utils", "seeds", "province.json"))
43
- if err != nil {
44
- return nil, err
45
  }
46
- defer file.Close()
47
- var provinces []models.RegionProvince
48
- if err := json.NewDecoder(file).Decode(&provinces); err != nil {
49
- return nil, err
50
  }
51
- return provinces, nil
 
52
  }
53
 
54
- func (s *ProvinceService) Create() {
55
- provinces, errSeed := seedProvince()
56
- if errSeed != nil {
57
- s.Error = errSeed
58
- s.Exception.InternalServerError = true
59
- s.Exception.Message = "Failed to seed province"
60
- return
61
  }
62
- createProvince := repositories.BulkCreateProvince(provinces)
63
- s.Error = createProvince.RowsError
64
- s.Result = createProvince.Result
65
- }
66
 
67
- func (s *CityService) Create() {
68
- cities, errSeed := seedCity()
69
- if errSeed != nil {
70
- s.Error = errSeed
71
- s.Exception.InternalServerError = true
72
- s.Exception.Message = "Failed to seed province"
73
- return
74
  }
75
- createCity := repositories.BulkCreateCity(cities)
76
- s.Error = createCity.RowsError
77
- s.Result = createCity.Result
 
 
 
78
  }
79
 
80
- func (s *ProvinceService) Retrieve() {
81
- Province := repositories.GetListProvinces()
82
- s.Error = Province.RowsError
83
- s.Result = Province.Result
84
  }
85
 
86
- func (s *CityService) Retrieve() {
87
- cities := repositories.GetListCitiesByProvinceId(s.Constructor.ProvinceID)
88
- s.Error = cities.RowsError
89
- s.Result = cities.Result
90
  }
 
1
  package services
2
 
3
  import (
4
+ "context"
5
  "encoding/json"
 
 
 
 
6
 
7
+ "api.qobiltu.id/assets"
8
  "api.qobiltu.id/models"
9
  "api.qobiltu.id/repositories"
10
+ "api.qobiltu.id/response"
11
  )
12
 
13
+ type RegionService interface {
14
+ SeedProvince(ctx context.Context) error
15
+ SeedCity(ctx context.Context) error
16
+
17
+ ListProvinces(ctx context.Context) ([]models.RegionProvince, error)
18
+ ListCitiesByProvinceId(ctx context.Context, req *models.ListCitiesByProvinceIdRequest) ([]models.RegionCity, error)
19
+ ListCities(ctx context.Context) ([]models.RegionCity, error)
20
  }
21
 
22
+ type regionService struct {
23
+ regionRepository repositories.RegionRepository
24
  }
25
 
26
+ func NewRegionService(regionRepository repositories.RegionRepository) RegionService {
27
+ return &regionService{regionRepository: regionRepository}
 
 
 
 
 
 
 
 
 
 
 
 
28
  }
29
 
30
+ func (s *regionService) SeedProvince(ctx context.Context) error {
31
+ provinces := make([]models.RegionProvince, 0)
32
+
33
+ if err := json.Unmarshal(assets.EmbeddedProvinceJSON, &provinces); err != nil {
34
+ return response.HandleGormError(err, "Internal Server Error")
 
 
35
  }
36
+
37
+ if err := s.regionRepository.CreateProvince(ctx, provinces); err != nil {
38
+ return response.HandleGormError(err, "Internal Server Error")
 
39
  }
40
+
41
+ return nil
42
  }
43
 
44
+ func (s *regionService) SeedCity(ctx context.Context) error {
45
+ cities := make([]models.RegionCity, 0)
46
+
47
+ if err := json.Unmarshal(assets.EmbeddedCityJSON, &cities); err != nil {
48
+ return response.HandleGormError(err, "Internal Server Error")
 
 
49
  }
 
 
 
 
50
 
51
+ if err := s.regionRepository.CreateCity(ctx, cities); err != nil {
52
+ return response.HandleGormError(err, "Internal Server Error")
 
 
 
 
 
53
  }
54
+
55
+ return nil
56
+ }
57
+
58
+ func (s *regionService) ListProvinces(ctx context.Context) ([]models.RegionProvince, error) {
59
+ return s.regionRepository.ListProvinces(ctx)
60
  }
61
 
62
+ func (s *regionService) ListCitiesByProvinceId(ctx context.Context, req *models.ListCitiesByProvinceIdRequest) ([]models.RegionCity, error) {
63
+ return s.regionRepository.ListCitiesByProvinceId(ctx, req.ProvinceID)
 
 
64
  }
65
 
66
+ func (s *regionService) ListCities(ctx context.Context) ([]models.RegionCity, error) {
67
+ return s.regionRepository.ListCities(ctx)
 
 
68
  }
space/space/space/space/space/space/services/academy_quiz_navigation_service.go CHANGED
@@ -12,7 +12,7 @@ type NavigationQuizService struct {
12
  }
13
 
14
  func (s *NavigationQuizService) Retrieve(userID uint) {
15
- attemptRepo := repositories.GetAttemptByUserId(userID)
16
  if attemptRepo.NoRecord {
17
  s.Exception.DataNotFound = true
18
  s.Exception.Message = "there is no attempt data with given user ID!"
 
12
  }
13
 
14
  func (s *NavigationQuizService) Retrieve(userID uint) {
15
+ attemptRepo := repositories.GetAttemptByIdandUserId(s.Constructor.QuizAttemptID, userID)
16
  if attemptRepo.NoRecord {
17
  s.Exception.DataNotFound = true
18
  s.Exception.Message = "there is no attempt data with given user ID!"