lifedebugger commited on
Commit
1301478
·
1 Parent(s): 58a3ab8

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. config/database_connection_config.go +1 -0
  2. controller/marriage_readiness_profile/marriage_readiness_profile_controller.go +66 -0
  3. main.go +12 -5
  4. models/database_orm_model.go +65 -13
  5. models/request_model.go +55 -9
  6. pkg/worker/task_send_forgot_password_email.go +8 -6
  7. pkg/worker/task_send_verify_email.go +8 -5
  8. repositories/marriage_readiness_profile_repository.go +38 -0
  9. router/marriage_readiness_profile_route.go +11 -0
  10. router/router.go +1 -0
  11. router/server.go +11 -7
  12. services/marriage_readiness_profile_service.go +90 -0
  13. space/controller/cv/cv_controller.go +171 -47
  14. space/models/database_orm_model.go +95 -43
  15. space/models/request_model.go +199 -45
  16. space/pkg/validation/validation.go +10 -1
  17. space/router/cv_route.go +1 -1
  18. space/services/cv_service.go +188 -181
  19. space/space/repositories/quiz_repository.go +1 -1
  20. space/space/services/academy_quiz_answer_service.go +3 -0
  21. space/space/services/academy_quiz_question_service.go +6 -0
  22. space/space/services/academy_quiz_service.go +13 -3
  23. space/space/space/controller/quiz/result_quiz_controller.go +24 -0
  24. space/space/space/controller/quiz/submit_quiz_controller.go +4 -3
  25. space/space/space/models/database_orm_model.go +4 -3
  26. space/space/space/repositories/academy_repository.go +10 -0
  27. space/space/space/repositories/quiz_repository.go +12 -0
  28. space/space/space/router/quiz_route.go +2 -0
  29. space/space/space/services/academy_quiz_service.go +64 -2
  30. space/space/space/services/academy_service.go +13 -0
  31. space/space/space/space/.env.example +26 -9
  32. space/space/space/space/space/pkg/validation/custom_rules.go +1 -1
  33. space/space/space/space/space/space/services/cv_service.go +11 -13
  34. space/space/space/space/space/space/space/controller/cv/cv_controller.go +17 -2
  35. space/space/space/space/space/space/space/models/field_counter.go +163 -0
  36. space/space/space/space/space/space/space/models/request_model.go +12 -0
  37. space/space/space/space/space/space/space/router/cv_route.go +2 -0
  38. space/space/space/space/space/space/space/services/cv_service.go +115 -0
  39. space/space/space/space/space/space/space/space/models/database_orm_model.go +47 -9
  40. space/space/space/space/space/space/space/space/repositories/quiz_repository.go +5 -2
  41. space/space/space/space/space/space/space/space/services/academy_quiz_service.go +1 -1
  42. space/space/space/space/space/space/space/space/space/pkg/validation/custom_rules.go +42 -0
  43. space/space/space/space/space/space/space/space/space/pkg/validation/validation.go +1 -0
  44. space/space/space/space/space/space/space/space/space/space/Makefile +9 -1
  45. space/space/space/space/space/space/space/space/space/space/config/config.go +3 -0
  46. space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go +20 -0
  47. space/space/space/space/space/space/space/space/space/space/docker-compose.yml +4 -7
  48. space/space/space/space/space/space/space/space/space/space/go.mod +1 -1
  49. space/space/space/space/space/space/space/space/space/space/main.go +8 -4
  50. space/space/space/space/space/space/space/space/space/space/models/exception_model.go +3 -1
config/database_connection_config.go CHANGED
@@ -77,6 +77,7 @@ func AutoMigrateAll(db *gorm.DB) {
77
  &models.EducationCV{},
78
  &models.JobCV{},
79
  &models.AchievementCV{},
 
80
  )
81
 
82
  if err != nil {
 
77
  &models.EducationCV{},
78
  &models.JobCV{},
79
  &models.AchievementCV{},
80
+ &models.MarriageReadinessProfile{},
81
  )
82
 
83
  if err != nil {
controller/marriage_readiness_profile/marriage_readiness_profile_controller.go ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package cv_controller
2
+
3
+ import (
4
+ "net/http"
5
+
6
+ "api.qobiltu.id/middleware"
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 MarriageReadinessProfileController interface {
14
+ SaveMarriageReadinessProfile(ctx *gin.Context)
15
+ GetMarriageReadinessProfile(ctx *gin.Context)
16
+ }
17
+
18
+ type marriageReadinessProfileController struct {
19
+ marriageReadinessProfileService services.MarriageReadinessProfileService
20
+ }
21
+
22
+ func NewMarriageReadinessProfileController(marriageReadinessProfileService services.MarriageReadinessProfileService) MarriageReadinessProfileController {
23
+ return &marriageReadinessProfileController{
24
+ marriageReadinessProfileService: marriageReadinessProfileService,
25
+ }
26
+ }
27
+
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
+
39
+ accountData := middleware.GetAccountData(ctx)
40
+ req.AccountID = int64(accountData.UserID)
41
+
42
+ res, err := c.marriageReadinessProfileService.SaveMarriageReadinessProfile(ctx, &req)
43
+ if err != nil {
44
+ response.HandleError(ctx, err)
45
+ return
46
+ }
47
+
48
+ response.HandleSuccess(ctx, http.StatusOK, "Marriage readiness profile saved", res, nil)
49
+ }
50
+
51
+ func (c *marriageReadinessProfileController) GetMarriageReadinessProfile(ctx *gin.Context) {
52
+ accountData := middleware.GetAccountData(ctx)
53
+ accountID := int64(accountData.UserID)
54
+
55
+ req := models.GetMarriageReadinessProfileRequest{
56
+ AccountID: accountID,
57
+ }
58
+
59
+ res, err := c.marriageReadinessProfileService.GetMarriageReadinessProfile(ctx, &req)
60
+ if err != nil {
61
+ response.HandleError(ctx, err)
62
+ return
63
+ }
64
+
65
+ response.HandleSuccess(ctx, http.StatusOK, "Get marriage readiness profile success", res, nil)
66
+ }
main.go CHANGED
@@ -1,9 +1,14 @@
1
  package main
2
 
3
  import (
 
 
 
 
4
  "api.qobiltu.id/config"
5
- "api.qobiltu.id/controller/cv"
6
- "api.qobiltu.id/controller/health_check"
 
7
  "api.qobiltu.id/mail"
8
  "api.qobiltu.id/pkg/storage"
9
  "api.qobiltu.id/pkg/validation"
@@ -13,9 +18,6 @@ import (
13
  "api.qobiltu.id/services"
14
  "api.qobiltu.id/utils"
15
  "github.com/hibiken/asynq"
16
- "log/slog"
17
- "net"
18
- "strconv"
19
  )
20
 
21
  func main() {
@@ -60,6 +62,10 @@ func main() {
60
  cvService := services.NewCVService(cvRepository, localStorage)
61
  cvController := cv_controller.NewCVController(cvService)
62
 
 
 
 
 
63
  // start task processor
64
  err = taskProcessor.Start()
65
  utils.FatalIfErr("failed to start task processor", err)
@@ -69,6 +75,7 @@ func main() {
69
  s, err := router.NewServer(
70
  healthCheckController,
71
  cvController,
 
72
  )
73
  utils.FatalIfErr("failed to create server", err)
74
 
 
1
  package main
2
 
3
  import (
4
+ "log/slog"
5
+ "net"
6
+ "strconv"
7
+
8
  "api.qobiltu.id/config"
9
+ cv_controller "api.qobiltu.id/controller/cv"
10
+ health_check_controller "api.qobiltu.id/controller/health_check"
11
+ marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
12
  "api.qobiltu.id/mail"
13
  "api.qobiltu.id/pkg/storage"
14
  "api.qobiltu.id/pkg/validation"
 
18
  "api.qobiltu.id/services"
19
  "api.qobiltu.id/utils"
20
  "github.com/hibiken/asynq"
 
 
 
21
  )
22
 
23
  func main() {
 
62
  cvService := services.NewCVService(cvRepository, localStorage)
63
  cvController := cv_controller.NewCVController(cvService)
64
 
65
+ marriageReadinessProfileRepository := repositories.NewMarriageReadinessProfileRepository(config.DB)
66
+ marriageReadinessProfileService := services.NewMarriageReadinessProfileService(marriageReadinessProfileRepository)
67
+ marriageReadinessProfileController := marriage_readiness_profile_controller.NewMarriageReadinessProfileController(marriageReadinessProfileService)
68
+
69
  // start task processor
70
  err = taskProcessor.Start()
71
  utils.FatalIfErr("failed to start task processor", err)
 
75
  s, err := router.NewServer(
76
  healthCheckController,
77
  cvController,
78
+ marriageReadinessProfileController,
79
  )
80
  utils.FatalIfErr("failed to create server", err)
81
 
models/database_orm_model.go CHANGED
@@ -231,19 +231,19 @@ type (
231
  }
232
 
233
  PhysicalAndHealthCV struct {
234
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
235
- AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
236
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
237
- HeightInCm *int `gorm:"column:height_cm" json:"height_cm"` // Tinggi badan dalam satuan sentimeter
238
- WeightInKg *int `gorm:"column:weight_kg" json:"weight_kg"` // Berat badan dalam satuan kilogram
239
- BodyShape *string `gorm:"column:body_shape" json:"body_shape"` // Bentuk tubuh
240
- SkinColor *string `gorm:"column:skin_color" json:"skin_color"` // Warna kulit
241
- HairType *string `gorm:"column:hair_type" json:"hair_type"` // Tipe rambut
242
- MedicalHistory *string `gorm:"column:medical_history" json:"medical_history"` // Riwayat penyakit
243
- PhysicalDisorder *string `gorm:"column:physical_disorder" json:"physical_disorder"` // Cacat fisik
244
- PhysicalTraits *string `gorm:"column:physical_traits" json:"physical_traits"` // Ciri khas fisik
245
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // Waktu data dibuat
246
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // Waktu data terakhir diperbarui
247
  FieldCounter
248
  }
249
 
@@ -339,6 +339,55 @@ func (w WorshipAndReligiousUnderstandingCV) GetFilledFields() []string {
339
  return w.FieldCounter.GetFilledFields(w)
340
  }
341
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  // Gorm table name settings
343
  func (Account) TableName() string { return "account" }
344
  func (AccountDetails) TableName() string { return "account_details" }
@@ -369,3 +418,6 @@ func (WorshipAndReligiousUnderstandingCV) TableName() string {
369
  func (EducationCV) TableName() string { return "education_cv" }
370
  func (JobCV) TableName() string { return "job_cv" }
371
  func (AchievementCV) TableName() string { return "achievement_cv" }
 
 
 
 
231
  }
232
 
233
  PhysicalAndHealthCV struct {
234
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
235
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
236
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
237
+ HeightInCm *int `gorm:"column:height_cm" json:"height_cm"` // Tinggi badan dalam satuan sentimeter
238
+ WeightInKg *int `gorm:"column:weight_kg" json:"weight_kg"` // Berat badan dalam satuan kilogram
239
+ BodyShape *string `gorm:"column:body_shape" json:"body_shape"` // Bentuk tubuh
240
+ SkinColor *string `gorm:"column:skin_color" json:"skin_color"` // Warna kulit
241
+ HairType *string `gorm:"column:hair_type" json:"hair_type"` // Tipe rambut
242
+ MedicalHistory *pq.StringArray `gorm:"column:medical_history;type:varchar(255)[]" json:"medical_history"` // Riwayat penyakit
243
+ PhysicalDisorder *string `gorm:"column:physical_disorder" json:"physical_disorder"` // Cacat fisik
244
+ PhysicalTraits *string `gorm:"column:physical_traits" json:"physical_traits"` // Ciri khas fisik
245
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // Waktu data dibuat
246
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // Waktu data terakhir diperbarui
247
  FieldCounter
248
  }
249
 
 
339
  return w.FieldCounter.GetFilledFields(w)
340
  }
341
 
342
+ type (
343
+ MarriageReadinessProfile struct {
344
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
345
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
346
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
347
+
348
+ // Visi Misi Rumah Tangga
349
+ MarriageVision *string `gorm:"column:marriage_vision" json:"marriage_vision"` // Apa visi dan tujuan utama kamu dalam membangun rumah tangga?
350
+ LivingPlan *string `gorm:"column:living_plan" json:"living_plan"` // Setelah menikah, kamu berencana tinggal di mana?
351
+ SpouseRoles *string `gorm:"column:spouse_roles" json:"spouse_roles"` // Menurutmu, apa peran utama suami dan istri dalam rumah tangga?
352
+ SpouseRights *string `gorm:"column:spouse_rights" json:"spouse_rights"` // Apa saja hak suami dan istri menurutmu?
353
+ ConflictResolution *string `gorm:"column:conflict_resolution" json:"conflict_resolution"` // Jika terjadi konflik, bagaimana kamu menyikapinya?
354
+ ParentingStyle *string `gorm:"column:parenting_style" json:"parenting_style"` // Setelah punya anak, pola pengasuhan seperti apa yang kamu inginkan?
355
+
356
+ // Konsep Acara Pernikahan
357
+ ReadyToMarryDuration *string `gorm:"column:ready_to_marry_duration" json:"ready_to_marry_duration"` // Setelah taaruf dimulai, berapa lama kamu butuh untuk siap menikah?
358
+ WeddingConcept *string `gorm:"column:wedding_concept" json:"wedding_concept"` // Seperti apa konsep pernikahan yang kamu harapkan?
359
+ WeddingFunding *string `gorm:"column:wedding_funding" json:"wedding_funding"` // Apakah kamu sudah mempersiapkan dana pernikahan? Dari mana sumbernya?
360
+
361
+ // Karir Kedepannya
362
+ CareerAfterMarriage *string `gorm:"column:career_after_marriage" json:"career_after_marriage"` // Apakah kamu ingin tetap bekerja setelah menikah?
363
+ TimeManagement *string `gorm:"column:time_management" json:"time_management"` // Bagaimana kamu membagi waktu antara keluarga, ibadah, dan pekerjaan?
364
+ CareerGoals *string `gorm:"column:career_goals" json:"career_goals"` // Apa impian karier atau cita-cita kamu dalam 5 sampai 10 tahun ke depan?
365
+ SelfDevelopment *string `gorm:"column:self_development" json:"self_development"` // Apa usaha kamu untuk terus mengembangkan diri dan skill?
366
+
367
+ // Pendidikan Keluarga
368
+ DelayChildren *string `gorm:"column:delay_children" json:"delay_children"` // Apakah kamu berencana menunda memiliki anak?
369
+ ChildEducationPlan *string `gorm:"column:child_education_plan" json:"child_education_plan"` // Apakah kamu sudah punya rencana pendidikan anak?
370
+ ReligiousEmotionalBond *string `gorm:"column:religious_emotional_bond" json:"religious_emotional_bond"` // Bagaimana cara menjaga nilai agama & kedekatan emosional dengan anak?
371
+ ParentingChallenges *string `gorm:"column:parenting_challenges" json:"parenting_challenges"` // Saat menghadapi tantangan pengasuhan, bagaimana kamu menyikapinya?
372
+
373
+ // Finansial Keluarga
374
+ MonthlyFinance *string `gorm:"column:monthly_finance" json:"monthly_finance"` // Bagaimana kamu mengelola keuangan bulanan?
375
+ FamilyResponsibility *string `gorm:"column:family_responsibility" json:"family_responsibility"` // Apakah kamu masih punya tanggungan keluarga setelah menikah?
376
+ DebtStatus *string `gorm:"column:debt_status" json:"debt_status"` // Apakah kamu punya cicilan/utang setelah menikah?
377
+ FinanceSharing *string `gorm:"column:finance_sharing" json:"finance_sharing"` // Setelah menikah, bagaimana pembagian tanggung jawab keuangan?
378
+ IncomeGapView *string `gorm:"column:income_gap_view" json:"income_gap_view"` // Pandangan kamu jika istri berpenghasilan lebih besar dari suami?
379
+
380
+ // Keputusan dan Komunikasi
381
+ DecisionMaking *string `gorm:"column:decision_making" json:"decision_making"` // Dalam mengambil keputusan, kamu lebih mempertimbangkan pasangan atau sendiri?
382
+ GrowthTogether *string `gorm:"column:growth_together" json:"growth_together"` // Apakah kamu siap berjuang bersama pasangan? Apa saja yang ingin diperjuangkan?
383
+ ParentIntervention *string `gorm:"column:parent_intervention" json:"parent_intervention"` // Sejauh mana orang tua/mertua boleh ikut campur dalam rumah tangga?
384
+ HabitResponse *string `gorm:"column:habit_response" json:"habit_response"` // Bagaimana kamu menyikapi kebiasaan pasangan yang kurang kamu sukai?
385
+
386
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
387
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
388
+ }
389
+ )
390
+
391
  // Gorm table name settings
392
  func (Account) TableName() string { return "account" }
393
  func (AccountDetails) TableName() string { return "account_details" }
 
418
  func (EducationCV) TableName() string { return "education_cv" }
419
  func (JobCV) TableName() string { return "job_cv" }
420
  func (AchievementCV) TableName() string { return "achievement_cv" }
421
+ func (MarriageReadinessProfile) TableName() string {
422
+ return "marriage_readiness_profile"
423
+ }
models/request_model.go CHANGED
@@ -117,15 +117,15 @@ type (
117
  }
118
 
119
  SavePhysicalAndHealthRequest struct {
120
- AccountID int64 `json:"-"`
121
- HeightInCm *int `json:"height_cm"` // Tinggi badan dalam satuan sentimeter
122
- WeightInKg *int `json:"weight_kg"` // Berat badan dalam satuan kilogram
123
- BodyShape *string `json:"body_shape" validate:"body_shape"` // Bentuk tubuh
124
- SkinColor *string `json:"skin_color" validate:"skin_color"` // Warna kulit
125
- HairType *string `json:"hair_type" validate:"hair_type"` // Tipe rambut
126
- MedicalHistory *string `json:"medical_history"` // Riwayat penyakit
127
- PhysicalDisorder *string `json:"physical_disorder"` // Cacat fisik
128
- PhysicalTraits *string `json:"physical_traits"` // Ciri khas fisik
129
  }
130
 
131
  GetPhysicalAndHealthRequest struct {
@@ -282,3 +282,49 @@ type (
282
  TotalProgress float64 `json:"total_progress"`
283
  }
284
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  }
118
 
119
  SavePhysicalAndHealthRequest struct {
120
+ AccountID int64 `json:"-"`
121
+ HeightInCm *int `json:"height_cm"` // Tinggi badan dalam satuan sentimeter
122
+ WeightInKg *int `json:"weight_kg"` // Berat badan dalam satuan kilogram
123
+ BodyShape *string `json:"body_shape" validate:"body_shape"` // Bentuk tubuh
124
+ SkinColor *string `json:"skin_color" validate:"skin_color"` // Warna kulit
125
+ HairType *string `json:"hair_type" validate:"hair_type"` // Tipe rambut
126
+ MedicalHistory *pq.StringArray `json:"medical_history"` // Riwayat penyakit
127
+ PhysicalDisorder *string `json:"physical_disorder"` // Cacat fisik
128
+ PhysicalTraits *string `json:"physical_traits"` // Ciri khas fisik
129
  }
130
 
131
  GetPhysicalAndHealthRequest struct {
 
282
  TotalProgress float64 `json:"total_progress"`
283
  }
284
  )
285
+
286
+ type SaveMarriageReadinessProfileRequest struct {
287
+ AccountID int64 `json:"account_id"`
288
+
289
+ // Visi Misi Rumah Tangga
290
+ MarriageVision *string `gorm:"column:marriage_vision" json:"marriage_vision"` // Apa visi dan tujuan utama kamu dalam membangun rumah tangga?
291
+ LivingPlan *string `gorm:"column:living_plan" json:"living_plan"` // Setelah menikah, kamu berencana tinggal di mana?
292
+ SpouseRoles *string `gorm:"column:spouse_roles" json:"spouse_roles"` // Menurutmu, apa peran utama suami dan istri dalam rumah tangga?
293
+ SpouseRights *string `gorm:"column:spouse_rights" json:"spouse_rights"` // Apa saja hak suami dan istri menurutmu?
294
+ ConflictResolution *string `gorm:"column:conflict_resolution" json:"conflict_resolution"` // Jika terjadi konflik, bagaimana kamu menyikapinya?
295
+ ParentingStyle *string `gorm:"column:parenting_style" json:"parenting_style"` // Setelah punya anak, pola pengasuhan seperti apa yang kamu inginkan?
296
+
297
+ // Konsep Acara Pernikahan
298
+ ReadyToMarryDuration *string `gorm:"column:ready_to_marry_duration" json:"ready_to_marry_duration"` // Setelah taaruf dimulai, berapa lama kamu butuh untuk siap menikah?
299
+ WeddingConcept *string `gorm:"column:wedding_concept" json:"wedding_concept"` // Seperti apa konsep pernikahan yang kamu harapkan?
300
+ WeddingFunding *string `gorm:"column:wedding_funding" json:"wedding_funding"` // Apakah kamu sudah mempersiapkan dana pernikahan? Dari mana sumbernya?
301
+
302
+ // Karir Kedepannya
303
+ CareerAfterMarriage *string `gorm:"column:career_after_marriage" json:"career_after_marriage"` // Apakah kamu ingin tetap bekerja setelah menikah?
304
+ TimeManagement *string `gorm:"column:time_management" json:"time_management"` // Bagaimana kamu membagi waktu antara keluarga, ibadah, dan pekerjaan?
305
+ CareerGoals *string `gorm:"column:career_goals" json:"career_goals"` // Apa impian karier atau cita-cita kamu dalam 5 sampai 10 tahun ke depan?
306
+ SelfDevelopment *string `gorm:"column:self_development" json:"self_development"` // Apa usaha kamu untuk terus mengembangkan diri dan skill?
307
+
308
+ // Pendidikan Keluarga
309
+ DelayChildren *string `gorm:"column:delay_children" json:"delay_children"` // Apakah kamu berencana menunda memiliki anak?
310
+ ChildEducationPlan *string `gorm:"column:child_education_plan" json:"child_education_plan"` // Apakah kamu sudah punya rencana pendidikan anak?
311
+ ReligiousEmotionalBond *string `gorm:"column:religious_emotional_bond" json:"religious_emotional_bond"` // Bagaimana cara menjaga nilai agama & kedekatan emosional dengan anak?
312
+ ParentingChallenges *string `gorm:"column:parenting_challenges" json:"parenting_challenges"` // Saat menghadapi tantangan pengasuhan, bagaimana kamu menyikapinya?
313
+
314
+ // Finansial Keluarga
315
+ MonthlyFinance *string `gorm:"column:monthly_finance" json:"monthly_finance"` // Bagaimana kamu mengelola keuangan bulanan?
316
+ FamilyResponsibility *string `gorm:"column:family_responsibility" json:"family_responsibility"` // Apakah kamu masih punya tanggungan keluarga setelah menikah?
317
+ DebtStatus *string `gorm:"column:debt_status" json:"debt_status"` // Apakah kamu punya cicilan/utang setelah menikah?
318
+ FinanceSharing *string `gorm:"column:finance_sharing" json:"finance_sharing"` // Setelah menikah, bagaimana pembagian tanggung jawab keuangan?
319
+ IncomeGapView *string `gorm:"column:income_gap_view" json:"income_gap_view"` // Pandangan kamu jika istri berpenghasilan lebih besar dari suami?
320
+
321
+ // Keputusan dan Komunikasi
322
+ DecisionMaking *string `gorm:"column:decision_making" json:"decision_making"` // Dalam mengambil keputusan, kamu lebih mempertimbangkan pasangan atau sendiri?
323
+ GrowthTogether *string `gorm:"column:growth_together" json:"growth_together"` // Apakah kamu siap berjuang bersama pasangan? Apa saja yang ingin diperjuangkan?
324
+ ParentIntervention *string `gorm:"column:parent_intervention" json:"parent_intervention"` // Sejauh mana orang tua/mertua boleh ikut campur dalam rumah tangga?
325
+ HabitResponse *string `gorm:"column:habit_response" json:"habit_response"` // Bagaimana kamu menyikapi kebiasaan pasangan yang kurang kamu sukai?
326
+ }
327
+
328
+ type GetMarriageReadinessProfileRequest struct {
329
+ AccountID int64 `json:"-"`
330
+ }
pkg/worker/task_send_forgot_password_email.go CHANGED
@@ -1,14 +1,15 @@
1
  package worker
2
 
3
  import (
4
- "api.qobiltu.id/assets"
5
  "bytes"
6
  "context"
7
  "encoding/json"
8
  "fmt"
9
- "github.com/hibiken/asynq"
10
  "html/template"
11
- "log/slog"
 
 
 
12
  )
13
 
14
  const (
@@ -62,14 +63,15 @@ func (p *RedisTaskProcessor) ProcessTaskSendForgotPasswordEmail(ctx context.Cont
62
  }
63
  htmlContent := body.String()
64
 
65
- slog.Info("Sending forgot password email", slog.String("email", payload.EmailAddress))
 
66
 
67
  err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
68
  if err != nil {
69
  return fmt.Errorf("failed to send forgot password email: %w", err)
70
  }
71
 
72
- slog.Info("Forgot password email sent successfully", slog.String("email", payload.EmailAddress))
73
-
74
  return nil
75
  }
 
1
  package worker
2
 
3
  import (
 
4
  "bytes"
5
  "context"
6
  "encoding/json"
7
  "fmt"
 
8
  "html/template"
9
+ "time"
10
+
11
+ "api.qobiltu.id/assets"
12
+ "github.com/hibiken/asynq"
13
  )
14
 
15
  const (
 
63
  }
64
  htmlContent := body.String()
65
 
66
+ fmt.Println("Sending forgot password email", payload.EmailAddress)
67
+ start := time.Now()
68
 
69
  err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
70
  if err != nil {
71
  return fmt.Errorf("failed to send forgot password email: %w", err)
72
  }
73
 
74
+ fmt.Println("Forgot password email sent successfully", payload.EmailAddress)
75
+ fmt.Println("Time taken", time.Since(start))
76
  return nil
77
  }
pkg/worker/task_send_verify_email.go CHANGED
@@ -1,14 +1,15 @@
1
  package worker
2
 
3
  import (
4
- "api.qobiltu.id/assets"
5
  "bytes"
6
  "context"
7
  "encoding/json"
8
  "fmt"
9
- "github.com/hibiken/asynq"
10
  "html/template"
11
- "log/slog"
 
 
 
12
  )
13
 
14
  const (
@@ -62,14 +63,16 @@ func (p *RedisTaskProcessor) ProcessTaskSendVerifyEmail(ctx context.Context, tas
62
  }
63
  htmlContent := body.String()
64
 
65
- slog.Info("Sending verification email", slog.String("email", payload.EmailAddress))
 
66
 
67
  err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
68
  if err != nil {
69
  return fmt.Errorf("failed to send verify email: %w", err)
70
  }
71
 
72
- slog.Info("Verification email sent successfully", slog.String("email", payload.EmailAddress))
 
73
 
74
  return nil
75
  }
 
1
  package worker
2
 
3
  import (
 
4
  "bytes"
5
  "context"
6
  "encoding/json"
7
  "fmt"
 
8
  "html/template"
9
+ "time"
10
+
11
+ "api.qobiltu.id/assets"
12
+ "github.com/hibiken/asynq"
13
  )
14
 
15
  const (
 
63
  }
64
  htmlContent := body.String()
65
 
66
+ fmt.Println("Sending verification email", payload.EmailAddress)
67
+ start := time.Now()
68
 
69
  err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
70
  if err != nil {
71
  return fmt.Errorf("failed to send verify email: %w", err)
72
  }
73
 
74
+ fmt.Println("Verification email sent successfully", payload.EmailAddress)
75
+ fmt.Println("Time taken", time.Since(start))
76
 
77
  return nil
78
  }
repositories/marriage_readiness_profile_repository.go ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package repositories
2
+
3
+ import (
4
+ "context"
5
+
6
+ "api.qobiltu.id/models"
7
+ "gorm.io/gorm"
8
+ )
9
+
10
+ type MarriageReadinessProfileRepository interface {
11
+ SaveMarriageReadinessProfile(ctx context.Context, req *models.MarriageReadinessProfile) (*models.MarriageReadinessProfile, error)
12
+ GetMarriageReadinessProfile(ctx context.Context, accountID int64) (*models.MarriageReadinessProfile, error)
13
+ }
14
+
15
+ type marriageReadinessProfileRepository struct {
16
+ db *gorm.DB
17
+ }
18
+
19
+ func NewMarriageReadinessProfileRepository(db *gorm.DB) MarriageReadinessProfileRepository {
20
+ return &marriageReadinessProfileRepository{
21
+ db: db,
22
+ }
23
+ }
24
+
25
+ func (r *marriageReadinessProfileRepository) SaveMarriageReadinessProfile(ctx context.Context, req *models.MarriageReadinessProfile) (*models.MarriageReadinessProfile, error) {
26
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
27
+ return req, err
28
+ }
29
+ return req, nil
30
+ }
31
+
32
+ func (r *marriageReadinessProfileRepository) GetMarriageReadinessProfile(ctx context.Context, accountID int64) (*models.MarriageReadinessProfile, error) {
33
+ var marriageReadinessProfile models.MarriageReadinessProfile
34
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&marriageReadinessProfile).Error; err != nil {
35
+ return nil, err
36
+ }
37
+ return &marriageReadinessProfile, nil
38
+ }
router/marriage_readiness_profile_route.go ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package router
2
+
3
+ import "api.qobiltu.id/middleware"
4
+
5
+ func (s *Server) MarriageReadinessProfileRoute() {
6
+ routerGroup := s.router.Group("/api/v1/marriage-readiness-profile").Use(middleware.AuthUser)
7
+ {
8
+ routerGroup.POST("", s.marriageReadinessProfileController.SaveMarriageReadinessProfile)
9
+ routerGroup.GET("", s.marriageReadinessProfileController.GetMarriageReadinessProfile)
10
+ }
11
+ }
router/router.go CHANGED
@@ -18,5 +18,6 @@ func (s *Server) setupRoutes() {
18
  // another way to register routes
19
  s.HealthCheckRoute()
20
  s.CVRoute()
 
21
  s.StorageRoute()
22
  }
 
18
  // another way to register routes
19
  s.HealthCheckRoute()
20
  s.CVRoute()
21
+ s.MarriageReadinessProfileRoute()
22
  s.StorageRoute()
23
  }
router/server.go CHANGED
@@ -2,28 +2,32 @@ package router
2
 
3
  import (
4
  cv_controller "api.qobiltu.id/controller/cv"
5
- "api.qobiltu.id/controller/health_check"
 
6
  "github.com/gin-gonic/gin"
7
  )
8
 
9
  type Server struct {
10
- router *gin.Engine
11
- healthCheckController health_check_controller.HealthCheckController
12
- cvController cv_controller.CVController
 
13
  }
14
 
15
  func NewServer(
16
  healthCheckController health_check_controller.HealthCheckController,
17
  cvController cv_controller.CVController,
 
18
  ) (*Server, error) {
19
 
20
  router := gin.Default()
21
  router.Use(gin.Recovery())
22
 
23
  server := &Server{
24
- healthCheckController: healthCheckController,
25
- cvController: cvController,
26
- router: router,
 
27
  }
28
 
29
  server.setupRoutes()
 
2
 
3
  import (
4
  cv_controller "api.qobiltu.id/controller/cv"
5
+ health_check_controller "api.qobiltu.id/controller/health_check"
6
+ marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
7
  "github.com/gin-gonic/gin"
8
  )
9
 
10
  type Server struct {
11
+ router *gin.Engine
12
+ healthCheckController health_check_controller.HealthCheckController
13
+ cvController cv_controller.CVController
14
+ marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController
15
  }
16
 
17
  func NewServer(
18
  healthCheckController health_check_controller.HealthCheckController,
19
  cvController cv_controller.CVController,
20
+ marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController,
21
  ) (*Server, error) {
22
 
23
  router := gin.Default()
24
  router.Use(gin.Recovery())
25
 
26
  server := &Server{
27
+ healthCheckController: healthCheckController,
28
+ cvController: cvController,
29
+ marriageReadinessProfileController: marriageReadinessProfileController,
30
+ router: router,
31
  }
32
 
33
  server.setupRoutes()
services/marriage_readiness_profile_service.go ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package services
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+
7
+ "api.qobiltu.id/models"
8
+ "api.qobiltu.id/pkg/validation"
9
+ "api.qobiltu.id/repositories"
10
+ "api.qobiltu.id/response"
11
+ "api.qobiltu.id/utils"
12
+ "gorm.io/gorm"
13
+ )
14
+
15
+ type MarriageReadinessProfileService interface {
16
+ SaveMarriageReadinessProfile(ctx context.Context, req *models.SaveMarriageReadinessProfileRequest) (*models.MarriageReadinessProfile, error)
17
+ GetMarriageReadinessProfile(ctx context.Context, req *models.GetMarriageReadinessProfileRequest) (*models.MarriageReadinessProfile, error)
18
+ }
19
+
20
+ type marriageReadinessProfileService struct {
21
+ marriageReadinessProfileRepository repositories.MarriageReadinessProfileRepository
22
+ }
23
+
24
+ func NewMarriageReadinessProfileService(marriageReadinessProfileRepository repositories.MarriageReadinessProfileRepository) MarriageReadinessProfileService {
25
+ return &marriageReadinessProfileService{marriageReadinessProfileRepository: marriageReadinessProfileRepository}
26
+ }
27
+
28
+ func (s *marriageReadinessProfileService) SaveMarriageReadinessProfile(ctx context.Context, req *models.SaveMarriageReadinessProfileRequest) (*models.MarriageReadinessProfile, error) {
29
+ if err := validation.Validate(req); err != nil {
30
+ return nil, response.HandleValidationError(err)
31
+ }
32
+
33
+ marriageReadinessProfile, err := s.marriageReadinessProfileRepository.GetMarriageReadinessProfile(ctx, req.AccountID)
34
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
35
+ return nil, response.HandleGormError(err, "Internal Server Error")
36
+ }
37
+
38
+ if marriageReadinessProfile == nil {
39
+ marriageReadinessProfile = &models.MarriageReadinessProfile{}
40
+ }
41
+
42
+ marriageReadinessProfile.AccountID = req.AccountID
43
+
44
+ utils.AssignIfNotNil(&marriageReadinessProfile.MarriageVision, req.MarriageVision)
45
+ utils.AssignIfNotNil(&marriageReadinessProfile.LivingPlan, req.LivingPlan)
46
+ utils.AssignIfNotNil(&marriageReadinessProfile.SpouseRoles, req.SpouseRoles)
47
+ utils.AssignIfNotNil(&marriageReadinessProfile.SpouseRights, req.SpouseRights)
48
+ utils.AssignIfNotNil(&marriageReadinessProfile.ConflictResolution, req.ConflictResolution)
49
+ utils.AssignIfNotNil(&marriageReadinessProfile.ParentingStyle, req.ParentingStyle)
50
+
51
+ utils.AssignIfNotNil(&marriageReadinessProfile.ReadyToMarryDuration, req.ReadyToMarryDuration)
52
+ utils.AssignIfNotNil(&marriageReadinessProfile.WeddingConcept, req.WeddingConcept)
53
+ utils.AssignIfNotNil(&marriageReadinessProfile.WeddingFunding, req.WeddingFunding)
54
+
55
+ utils.AssignIfNotNil(&marriageReadinessProfile.CareerAfterMarriage, req.CareerAfterMarriage)
56
+ utils.AssignIfNotNil(&marriageReadinessProfile.TimeManagement, req.TimeManagement)
57
+ utils.AssignIfNotNil(&marriageReadinessProfile.CareerGoals, req.CareerGoals)
58
+ utils.AssignIfNotNil(&marriageReadinessProfile.SelfDevelopment, req.SelfDevelopment)
59
+
60
+ utils.AssignIfNotNil(&marriageReadinessProfile.DelayChildren, req.DelayChildren)
61
+ utils.AssignIfNotNil(&marriageReadinessProfile.ChildEducationPlan, req.ChildEducationPlan)
62
+ utils.AssignIfNotNil(&marriageReadinessProfile.ReligiousEmotionalBond, req.ReligiousEmotionalBond)
63
+ utils.AssignIfNotNil(&marriageReadinessProfile.ParentingChallenges, req.ParentingChallenges)
64
+
65
+ utils.AssignIfNotNil(&marriageReadinessProfile.MonthlyFinance, req.MonthlyFinance)
66
+ utils.AssignIfNotNil(&marriageReadinessProfile.FamilyResponsibility, req.FamilyResponsibility)
67
+ utils.AssignIfNotNil(&marriageReadinessProfile.DebtStatus, req.DebtStatus)
68
+ utils.AssignIfNotNil(&marriageReadinessProfile.FinanceSharing, req.FinanceSharing)
69
+ utils.AssignIfNotNil(&marriageReadinessProfile.IncomeGapView, req.IncomeGapView)
70
+
71
+ utils.AssignIfNotNil(&marriageReadinessProfile.DecisionMaking, req.DecisionMaking)
72
+ utils.AssignIfNotNil(&marriageReadinessProfile.GrowthTogether, req.GrowthTogether)
73
+ utils.AssignIfNotNil(&marriageReadinessProfile.ParentIntervention, req.ParentIntervention)
74
+ utils.AssignIfNotNil(&marriageReadinessProfile.HabitResponse, req.HabitResponse)
75
+
76
+ res, err := s.marriageReadinessProfileRepository.SaveMarriageReadinessProfile(ctx, marriageReadinessProfile)
77
+ if err != nil {
78
+ return nil, response.HandleGormError(err, "Internal Server Error")
79
+ }
80
+
81
+ return res, nil
82
+ }
83
+
84
+ func (s *marriageReadinessProfileService) GetMarriageReadinessProfile(ctx context.Context, req *models.GetMarriageReadinessProfileRequest) (*models.MarriageReadinessProfile, error) {
85
+ res, err := s.marriageReadinessProfileRepository.GetMarriageReadinessProfile(ctx, req.AccountID)
86
+ if err != nil {
87
+ return nil, response.HandleGormError(err, "Internal Server Error")
88
+ }
89
+ return res, nil
90
+ }
space/controller/cv/cv_controller.go CHANGED
@@ -49,7 +49,7 @@ type CVController interface {
49
  DeleteAchievement(ctx *gin.Context)
50
 
51
  UploadProfileImage(ctx *gin.Context)
52
- GetProgress(ctx *gin.Context)
53
  }
54
 
55
  type cvController struct {
@@ -64,9 +64,13 @@ func NewCVController(cvService services.CVService) CVController {
64
 
65
  // --- Account Details ---
66
  func (c *cvController) SaveAccountDetails(ctx *gin.Context) {
67
- var req models.AccountDetailsRequest
68
  if err := ctx.ShouldBindJSON(&req); err != nil {
69
- response.HandleError(ctx, err)
 
 
 
 
70
  return
71
  }
72
 
@@ -86,7 +90,11 @@ func (c *cvController) GetAccountDetails(ctx *gin.Context) {
86
  accountData := middleware.GetAccountData(ctx)
87
  accountID := int64(accountData.UserID)
88
 
89
- res, err := c.cvService.GetAccountDetails(ctx, accountID)
 
 
 
 
90
  if err != nil {
91
  response.HandleError(ctx, err)
92
  return
@@ -98,9 +106,13 @@ func (c *cvController) GetAccountDetails(ctx *gin.Context) {
98
  // --- Personality & Preference ---
99
 
100
  func (c *cvController) SavePersonalityAndPreference(ctx *gin.Context) {
101
- var req models.PersonalityAndPreferenceCVRequest
102
  if err := ctx.ShouldBindJSON(&req); err != nil {
103
- response.HandleError(ctx, err)
 
 
 
 
104
  return
105
  }
106
 
@@ -120,7 +132,11 @@ func (c *cvController) GetPersonalityAndPreference(ctx *gin.Context) {
120
  accountData := middleware.GetAccountData(ctx)
121
  accountID := int64(accountData.UserID)
122
 
123
- res, err := c.cvService.GetPersonalityAndPreference(ctx, accountID)
 
 
 
 
124
  if err != nil {
125
  response.HandleError(ctx, err)
126
  return
@@ -132,9 +148,13 @@ func (c *cvController) GetPersonalityAndPreference(ctx *gin.Context) {
132
  // --- Family Member ---
133
 
134
  func (c *cvController) CreateFamilyMember(ctx *gin.Context) {
135
- var req models.FamilyMemberRequest
136
  if err := ctx.ShouldBindJSON(&req); err != nil {
137
- response.HandleError(ctx, err)
 
 
 
 
138
  return
139
  }
140
 
@@ -158,13 +178,19 @@ func (c *cvController) UpdateFamilyMember(ctx *gin.Context) {
158
  return
159
  }
160
 
161
- var req models.FamilyMemberRequest
162
  if err := ctx.ShouldBindJSON(&req); err != nil {
163
- response.HandleError(ctx, err)
 
 
 
 
164
  return
165
  }
166
 
167
- res, err := c.cvService.UpdateFamilyMember(ctx, id, &req)
 
 
168
  if err != nil {
169
  response.HandleError(ctx, err)
170
  return
@@ -177,7 +203,11 @@ func (c *cvController) ListFamilyMember(ctx *gin.Context) {
177
  accountData := middleware.GetAccountData(ctx)
178
  accountID := int64(accountData.UserID)
179
 
180
- list, err := c.cvService.ListFamilyMember(ctx, accountID)
 
 
 
 
181
  if err != nil {
182
  response.HandleError(ctx, err)
183
  return
@@ -194,7 +224,11 @@ func (c *cvController) GetFamilyMember(ctx *gin.Context) {
194
  return
195
  }
196
 
197
- res, err := c.cvService.GetFamilyMember(ctx, id)
 
 
 
 
198
  if err != nil {
199
  response.HandleError(ctx, err)
200
  return
@@ -211,7 +245,11 @@ func (c *cvController) DeleteFamilyMember(ctx *gin.Context) {
211
  return
212
  }
213
 
214
- err = c.cvService.DeleteFamilyMember(ctx, id)
 
 
 
 
215
  if err != nil {
216
  response.HandleError(ctx, err)
217
  return
@@ -223,9 +261,13 @@ func (c *cvController) DeleteFamilyMember(ctx *gin.Context) {
223
  // --- Physical and Health ---
224
 
225
  func (c *cvController) SavePhysicalAndHealth(ctx *gin.Context) {
226
- var req models.PhysicalAndHealthRequest
227
  if err := ctx.ShouldBindJSON(&req); err != nil {
228
- response.HandleError(ctx, err)
 
 
 
 
229
  return
230
  }
231
 
@@ -245,7 +287,11 @@ func (c *cvController) GetPhysicalAndHealth(ctx *gin.Context) {
245
  accountData := middleware.GetAccountData(ctx)
246
  accountID := int64(accountData.UserID)
247
 
248
- res, err := c.cvService.GetPhysicalAndHealth(ctx, accountID)
 
 
 
 
249
  if err != nil {
250
  response.HandleError(ctx, err)
251
  return
@@ -257,9 +303,13 @@ func (c *cvController) GetPhysicalAndHealth(ctx *gin.Context) {
257
  // --- Worship and Religious Understanding ---
258
 
259
  func (c *cvController) SaveWorshipAndReligiousUnderstanding(ctx *gin.Context) {
260
- var req models.WorshipAndReligiousUnderstandingRequest
261
  if err := ctx.ShouldBindJSON(&req); err != nil {
262
- response.HandleError(ctx, err)
 
 
 
 
263
  return
264
  }
265
 
@@ -279,7 +329,11 @@ func (c *cvController) GetWorshipAndReligiousUnderstanding(ctx *gin.Context) {
279
  accountData := middleware.GetAccountData(ctx)
280
  accountID := int64(accountData.UserID)
281
 
282
- res, err := c.cvService.GetWorshipAndReligiousUnderstanding(ctx, accountID)
 
 
 
 
283
  if err != nil {
284
  response.HandleError(ctx, err)
285
  return
@@ -290,9 +344,13 @@ func (c *cvController) GetWorshipAndReligiousUnderstanding(ctx *gin.Context) {
290
 
291
  // --- Education ---
292
  func (c *cvController) CreateEducation(ctx *gin.Context) {
293
- var req models.EducationRequest
294
  if err := ctx.ShouldBindJSON(&req); err != nil {
295
- response.HandleError(ctx, err)
 
 
 
 
296
  return
297
  }
298
 
@@ -316,13 +374,19 @@ func (c *cvController) UpdateEducation(ctx *gin.Context) {
316
  return
317
  }
318
 
319
- var req models.EducationRequest
320
  if err := ctx.ShouldBindJSON(&req); err != nil {
321
- response.HandleError(ctx, err)
 
 
 
 
322
  return
323
  }
324
 
325
- res, err := c.cvService.UpdateEducation(ctx, id, &req)
 
 
326
  if err != nil {
327
  response.HandleError(ctx, err)
328
  return
@@ -335,7 +399,11 @@ func (c *cvController) ListEducation(ctx *gin.Context) {
335
  accountData := middleware.GetAccountData(ctx)
336
  accountID := int64(accountData.UserID)
337
 
338
- res, err := c.cvService.ListEducation(ctx, accountID)
 
 
 
 
339
  if err != nil {
340
  response.HandleError(ctx, err)
341
  return
@@ -352,7 +420,11 @@ func (c *cvController) GetEducation(ctx *gin.Context) {
352
  return
353
  }
354
 
355
- res, err := c.cvService.GetEducation(ctx, id)
 
 
 
 
356
  if err != nil {
357
  response.HandleError(ctx, err)
358
  return
@@ -369,7 +441,11 @@ func (c *cvController) DeleteEducation(ctx *gin.Context) {
369
  return
370
  }
371
 
372
- err = c.cvService.DeleteEducation(ctx, id)
 
 
 
 
373
  if err != nil {
374
  response.HandleError(ctx, err)
375
  return
@@ -380,9 +456,13 @@ func (c *cvController) DeleteEducation(ctx *gin.Context) {
380
 
381
  // --- Job ---
382
  func (c *cvController) CreateJob(ctx *gin.Context) {
383
- var req models.JobRequest
384
  if err := ctx.ShouldBindJSON(&req); err != nil {
385
- response.HandleError(ctx, err)
 
 
 
 
386
  return
387
  }
388
 
@@ -406,13 +486,19 @@ func (c *cvController) UpdateJob(ctx *gin.Context) {
406
  return
407
  }
408
 
409
- var req models.JobRequest
410
  if err := ctx.ShouldBindJSON(&req); err != nil {
411
- response.HandleError(ctx, err)
 
 
 
 
412
  return
413
  }
414
 
415
- res, err := c.cvService.UpdateJob(ctx, id, &req)
 
 
416
  if err != nil {
417
  response.HandleError(ctx, err)
418
  return
@@ -425,7 +511,11 @@ func (c *cvController) ListJob(ctx *gin.Context) {
425
  accountData := middleware.GetAccountData(ctx)
426
  accountID := int64(accountData.UserID)
427
 
428
- res, err := c.cvService.ListJob(ctx, accountID)
 
 
 
 
429
  if err != nil {
430
  response.HandleError(ctx, err)
431
  return
@@ -442,7 +532,11 @@ func (c *cvController) GetJob(ctx *gin.Context) {
442
  return
443
  }
444
 
445
- res, err := c.cvService.GetJob(ctx, id)
 
 
 
 
446
  if err != nil {
447
  response.HandleError(ctx, err)
448
  return
@@ -459,7 +553,11 @@ func (c *cvController) DeleteJob(ctx *gin.Context) {
459
  return
460
  }
461
 
462
- err = c.cvService.DeleteJob(ctx, id)
 
 
 
 
463
  if err != nil {
464
  response.HandleError(ctx, err)
465
  return
@@ -470,9 +568,13 @@ func (c *cvController) DeleteJob(ctx *gin.Context) {
470
 
471
  // --- Achievement ---
472
  func (c *cvController) CreateAchievement(ctx *gin.Context) {
473
- var req models.AchievementRequest
474
  if err := ctx.ShouldBindJSON(&req); err != nil {
475
- response.HandleError(ctx, err)
 
 
 
 
476
  return
477
  }
478
 
@@ -496,13 +598,19 @@ func (c *cvController) UpdateAchievement(ctx *gin.Context) {
496
  return
497
  }
498
 
499
- var req models.AchievementRequest
500
  if err := ctx.ShouldBindJSON(&req); err != nil {
501
- response.HandleError(ctx, err)
 
 
 
 
502
  return
503
  }
504
 
505
- res, err := c.cvService.UpdateAchievement(ctx, id, &req)
 
 
506
  if err != nil {
507
  response.HandleError(ctx, err)
508
  return
@@ -515,7 +623,11 @@ func (c *cvController) ListAchievement(ctx *gin.Context) {
515
  accountData := middleware.GetAccountData(ctx)
516
  accountID := int64(accountData.UserID)
517
 
518
- res, err := c.cvService.ListAchievement(ctx, accountID)
 
 
 
 
519
  if err != nil {
520
  response.HandleError(ctx, err)
521
  return
@@ -532,7 +644,11 @@ func (c *cvController) GetAchievement(ctx *gin.Context) {
532
  return
533
  }
534
 
535
- res, err := c.cvService.GetAchievement(ctx, id)
 
 
 
 
536
  if err != nil {
537
  response.HandleError(ctx, err)
538
  return
@@ -549,7 +665,11 @@ func (c *cvController) DeleteAchievement(ctx *gin.Context) {
549
  return
550
  }
551
 
552
- err = c.cvService.DeleteAchievement(ctx, id)
 
 
 
 
553
  if err != nil {
554
  response.HandleError(ctx, err)
555
  return
@@ -576,11 +696,15 @@ func (c *cvController) UploadProfileImage(ctx *gin.Context) {
576
  response.HandleSuccess(ctx, http.StatusOK, "Profile image uploaded successfully", res, nil)
577
  }
578
 
579
- func (c *cvController) GetProgress(ctx *gin.Context) {
580
  accountData := middleware.GetAccountData(ctx)
581
  accountID := int64(accountData.UserID)
582
 
583
- res, err := c.cvService.GetProgress(ctx, accountID)
 
 
 
 
584
  if err != nil {
585
  response.HandleError(ctx, err)
586
  return
 
49
  DeleteAchievement(ctx *gin.Context)
50
 
51
  UploadProfileImage(ctx *gin.Context)
52
+ GetProgressCV(ctx *gin.Context)
53
  }
54
 
55
  type cvController struct {
 
64
 
65
  // --- Account Details ---
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
 
 
90
  accountData := middleware.GetAccountData(ctx)
91
  accountID := int64(accountData.UserID)
92
 
93
+ req := models.GetAccountDetailsRequest{
94
+ AccountID: accountID,
95
+ }
96
+
97
+ res, err := c.cvService.GetAccountDetails(ctx, &req)
98
  if err != nil {
99
  response.HandleError(ctx, err)
100
  return
 
106
  // --- Personality & Preference ---
107
 
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
 
 
132
  accountData := middleware.GetAccountData(ctx)
133
  accountID := int64(accountData.UserID)
134
 
135
+ req := models.GetPersonalityAndPreferenceRequest{
136
+ AccountID: accountID,
137
+ }
138
+
139
+ res, err := c.cvService.GetPersonalityAndPreference(ctx, &req)
140
  if err != nil {
141
  response.HandleError(ctx, err)
142
  return
 
148
  // --- Family Member ---
149
 
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
 
 
178
  return
179
  }
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
 
191
+ req.ID = id
192
+
193
+ res, err := c.cvService.UpdateFamilyMember(ctx, &req)
194
  if err != nil {
195
  response.HandleError(ctx, err)
196
  return
 
203
  accountData := middleware.GetAccountData(ctx)
204
  accountID := int64(accountData.UserID)
205
 
206
+ req := models.ListFamilyMemberRequest{
207
+ AccountID: accountID,
208
+ }
209
+
210
+ list, err := c.cvService.ListFamilyMember(ctx, &req)
211
  if err != nil {
212
  response.HandleError(ctx, err)
213
  return
 
224
  return
225
  }
226
 
227
+ req := models.GetFamilyMemberRequest{
228
+ ID: id,
229
+ }
230
+
231
+ res, err := c.cvService.GetFamilyMember(ctx, &req)
232
  if err != nil {
233
  response.HandleError(ctx, err)
234
  return
 
245
  return
246
  }
247
 
248
+ req := models.DeleteFamilyMemberRequest{
249
+ ID: id,
250
+ }
251
+
252
+ err = c.cvService.DeleteFamilyMember(ctx, &req)
253
  if err != nil {
254
  response.HandleError(ctx, err)
255
  return
 
261
  // --- Physical and Health ---
262
 
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
 
 
287
  accountData := middleware.GetAccountData(ctx)
288
  accountID := int64(accountData.UserID)
289
 
290
+ req := models.GetPhysicalAndHealthRequest{
291
+ AccountID: accountID,
292
+ }
293
+
294
+ res, err := c.cvService.GetPhysicalAndHealth(ctx, &req)
295
  if err != nil {
296
  response.HandleError(ctx, err)
297
  return
 
303
  // --- Worship and Religious Understanding ---
304
 
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
 
 
329
  accountData := middleware.GetAccountData(ctx)
330
  accountID := int64(accountData.UserID)
331
 
332
+ req := models.GetWorshipAndReligiousUnderstandingRequest{
333
+ AccountID: accountID,
334
+ }
335
+
336
+ res, err := c.cvService.GetWorshipAndReligiousUnderstanding(ctx, &req)
337
  if err != nil {
338
  response.HandleError(ctx, err)
339
  return
 
344
 
345
  // --- Education ---
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
 
 
374
  return
375
  }
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
 
387
+ req.ID = id
388
+
389
+ res, err := c.cvService.UpdateEducation(ctx, &req)
390
  if err != nil {
391
  response.HandleError(ctx, err)
392
  return
 
399
  accountData := middleware.GetAccountData(ctx)
400
  accountID := int64(accountData.UserID)
401
 
402
+ req := models.ListEducationRequest{
403
+ AccountID: accountID,
404
+ }
405
+
406
+ res, err := c.cvService.ListEducation(ctx, &req)
407
  if err != nil {
408
  response.HandleError(ctx, err)
409
  return
 
420
  return
421
  }
422
 
423
+ req := models.GetEducationRequest{
424
+ ID: id,
425
+ }
426
+
427
+ res, err := c.cvService.GetEducation(ctx, &req)
428
  if err != nil {
429
  response.HandleError(ctx, err)
430
  return
 
441
  return
442
  }
443
 
444
+ req := models.DeleteEducationRequest{
445
+ ID: id,
446
+ }
447
+
448
+ err = c.cvService.DeleteEducation(ctx, &req)
449
  if err != nil {
450
  response.HandleError(ctx, err)
451
  return
 
456
 
457
  // --- Job ---
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
 
 
486
  return
487
  }
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
 
499
+ req.ID = id
500
+
501
+ res, err := c.cvService.UpdateJob(ctx, &req)
502
  if err != nil {
503
  response.HandleError(ctx, err)
504
  return
 
511
  accountData := middleware.GetAccountData(ctx)
512
  accountID := int64(accountData.UserID)
513
 
514
+ req := models.ListJobRequest{
515
+ AccountID: accountID,
516
+ }
517
+
518
+ res, err := c.cvService.ListJob(ctx, &req)
519
  if err != nil {
520
  response.HandleError(ctx, err)
521
  return
 
532
  return
533
  }
534
 
535
+ req := models.GetJobRequest{
536
+ ID: id,
537
+ }
538
+
539
+ res, err := c.cvService.GetJob(ctx, &req)
540
  if err != nil {
541
  response.HandleError(ctx, err)
542
  return
 
553
  return
554
  }
555
 
556
+ req := models.DeleteJobRequest{
557
+ ID: id,
558
+ }
559
+
560
+ err = c.cvService.DeleteJob(ctx, &req)
561
  if err != nil {
562
  response.HandleError(ctx, err)
563
  return
 
568
 
569
  // --- Achievement ---
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
 
 
598
  return
599
  }
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
 
611
+ req.ID = id
612
+
613
+ res, err := c.cvService.UpdateAchievement(ctx, &req)
614
  if err != nil {
615
  response.HandleError(ctx, err)
616
  return
 
623
  accountData := middleware.GetAccountData(ctx)
624
  accountID := int64(accountData.UserID)
625
 
626
+ req := models.ListAchievementRequest{
627
+ AccountID: accountID,
628
+ }
629
+
630
+ res, err := c.cvService.ListAchievement(ctx, &req)
631
  if err != nil {
632
  response.HandleError(ctx, err)
633
  return
 
644
  return
645
  }
646
 
647
+ req := models.GetAchievementRequest{
648
+ ID: id,
649
+ }
650
+
651
+ res, err := c.cvService.GetAchievement(ctx, &req)
652
  if err != nil {
653
  response.HandleError(ctx, err)
654
  return
 
665
  return
666
  }
667
 
668
+ req := models.DeleteAchievementRequest{
669
+ ID: id,
670
+ }
671
+
672
+ err = c.cvService.DeleteAchievement(ctx, &req)
673
  if err != nil {
674
  response.HandleError(ctx, err)
675
  return
 
696
  response.HandleSuccess(ctx, http.StatusOK, "Profile image uploaded successfully", res, nil)
697
  }
698
 
699
+ func (c *cvController) GetProgressCV(ctx *gin.Context) {
700
  accountData := middleware.GetAccountData(ctx)
701
  accountID := int64(accountData.UserID)
702
 
703
+ req := models.GetProgressCVRequest{
704
+ AccountID: accountID,
705
+ }
706
+
707
+ res, err := c.cvService.GetProgressCV(ctx, &req)
708
  if err != nil {
709
  response.HandleError(ctx, err)
710
  return
space/models/database_orm_model.go CHANGED
@@ -231,43 +231,43 @@ type (
231
  }
232
 
233
  PhysicalAndHealthCV struct {
234
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
235
- AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
236
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
237
- HeightInCm *int `gorm:"column:height_cm" json:"height_cm"` // Tinggi badan dalam satuan sentimeter
238
- WeightInKg *int `gorm:"column:weight_kg" json:"weight_kg"` // Berat badan dalam satuan kilogram
239
- BodyShape *string `gorm:"column:body_shape" json:"body_shape"` // Bentuk tubuh
240
- SkinColor *string `gorm:"column:skin_color" json:"skin_color"` // Warna kulit
241
- HairType *string `gorm:"column:hair_type" json:"hair_type"` // Tipe rambut
242
- MedicalHistory *string `gorm:"column:medical_history" json:"medical_history"` // Riwayat penyakit
243
- PhysicalDisorder *string `gorm:"column:physical_disorder" json:"physical_disorder"` // Cacat fisik
244
- PhysicalTraits *string `gorm:"column:physical_traits" json:"physical_traits"` // Ciri khas fisik
245
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // Waktu data dibuat
246
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // Waktu data terakhir diperbarui
247
  FieldCounter
248
  }
249
 
250
  WorshipAndReligiousUnderstandingCV struct {
251
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
252
- AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
253
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
254
- ObligatoryPrayer *string `gorm:"column:obligatory_prayer" json:"obligatory_prayer"` // sholat_wajib_5_waktu
255
- CongregationalPrayer *string `gorm:"column:congregational_prayer" json:"congregational_prayer"` // sholat_berjamaah_di_masjid
256
- TahajjudPrayer *string `gorm:"column:tahajjud_prayer" json:"tahajjud_prayer"` // sholat_tahajud
257
- DhuhaPrayer *string `gorm:"column:dhuha_prayer" json:"dhuha_prayer"` // sholat_dhuha
258
- QuranMemorization *string `gorm:"column:quran_memorization" json:"quran_memorization"` // hafalan_alquran
259
- QuranReadingAbility *string `gorm:"column:quran_reading_ability" json:"quran_reading_ability"` // kemampuan_baca_alquran
260
- DaudFasting *string `gorm:"column:daud_fasting" json:"daud_fasting"` // puasa_daud
261
- AyyamulBidhFasting *string `gorm:"column:ayyamul_bidh_fasting" json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
262
- HajjOrUmrah pq.StringArray `gorm:"column:hajj_or_umrah;type:varchar(255)[]" json:"hajj_or_umrah"` // ibadah_haji_umroh
263
- ListeningToMusic *string `gorm:"column:listening_to_music" json:"listening_to_music"` // mendengarkan_musik
264
- OpinionOnIkhtilat *string `gorm:"column:opinion_on_ikhtilat" json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
265
- OpinionOnTouchingNonMahram *string `gorm:"column:opinion_on_touching_non_mahram" json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
266
- OpinionOnVeil *string `gorm:"column:opinion_on_veil" json:"opinion_on_veil"` // pendapat_tentang_cadar
267
- WeeklyReligiousStudies *string `gorm:"column:weekly_religious_studies" json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
268
- FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
269
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
270
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
271
  FieldCounter
272
  }
273
 
@@ -285,16 +285,16 @@ type (
285
  }
286
 
287
  JobCV struct {
288
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
289
- AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
290
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
291
- InstitutionName *string `gorm:"column:institution_name" json:"institution_name"` // nama instansi
292
- CurrentJob *string `gorm:"column:current_job" json:"current_job"` // pekerjaan saat ini
293
- YearStartedWorking *int `gorm:"column:year_started_working" json:"year_started_working"` // tahun mulai bekerja
294
- MonthlyIncome *string `gorm:"column:monthly_income" json:"monthly_income"` // penghasilan per bulan
295
- IncomeSources pq.StringArray `gorm:"column:income_sources;type:varchar(255)[]" json:"income_sources"` // sumber penghasilan
296
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
297
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
298
  }
299
 
300
  AchievementCV struct {
@@ -339,6 +339,55 @@ func (w WorshipAndReligiousUnderstandingCV) GetFilledFields() []string {
339
  return w.FieldCounter.GetFilledFields(w)
340
  }
341
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  // Gorm table name settings
343
  func (Account) TableName() string { return "account" }
344
  func (AccountDetails) TableName() string { return "account_details" }
@@ -369,3 +418,6 @@ func (WorshipAndReligiousUnderstandingCV) TableName() string {
369
  func (EducationCV) TableName() string { return "education_cv" }
370
  func (JobCV) TableName() string { return "job_cv" }
371
  func (AchievementCV) TableName() string { return "achievement_cv" }
 
 
 
 
231
  }
232
 
233
  PhysicalAndHealthCV struct {
234
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
235
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
236
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
237
+ HeightInCm *int `gorm:"column:height_cm" json:"height_cm"` // Tinggi badan dalam satuan sentimeter
238
+ WeightInKg *int `gorm:"column:weight_kg" json:"weight_kg"` // Berat badan dalam satuan kilogram
239
+ BodyShape *string `gorm:"column:body_shape" json:"body_shape"` // Bentuk tubuh
240
+ SkinColor *string `gorm:"column:skin_color" json:"skin_color"` // Warna kulit
241
+ HairType *string `gorm:"column:hair_type" json:"hair_type"` // Tipe rambut
242
+ MedicalHistory *pq.StringArray `gorm:"column:medical_history;type:varchar(255)[]" json:"medical_history"` // Riwayat penyakit
243
+ PhysicalDisorder *string `gorm:"column:physical_disorder" json:"physical_disorder"` // Cacat fisik
244
+ PhysicalTraits *string `gorm:"column:physical_traits" json:"physical_traits"` // Ciri khas fisik
245
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // Waktu data dibuat
246
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // Waktu data terakhir diperbarui
247
  FieldCounter
248
  }
249
 
250
  WorshipAndReligiousUnderstandingCV struct {
251
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
252
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
253
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
254
+ ObligatoryPrayer *string `gorm:"column:obligatory_prayer" json:"obligatory_prayer"` // sholat_wajib_5_waktu
255
+ CongregationalPrayer *string `gorm:"column:congregational_prayer" json:"congregational_prayer"` // sholat_berjamaah_di_masjid
256
+ TahajjudPrayer *string `gorm:"column:tahajjud_prayer" json:"tahajjud_prayer"` // sholat_tahajud
257
+ DhuhaPrayer *string `gorm:"column:dhuha_prayer" json:"dhuha_prayer"` // sholat_dhuha
258
+ QuranMemorization *string `gorm:"column:quran_memorization" json:"quran_memorization"` // hafalan_alquran
259
+ QuranReadingAbility *string `gorm:"column:quran_reading_ability" json:"quran_reading_ability"` // kemampuan_baca_alquran
260
+ DaudFasting *string `gorm:"column:daud_fasting" json:"daud_fasting"` // puasa_daud
261
+ AyyamulBidhFasting *string `gorm:"column:ayyamul_bidh_fasting" json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
262
+ HajjOrUmrah *pq.StringArray `gorm:"column:hajj_or_umrah;type:varchar(255)[]" json:"hajj_or_umrah"` // ibadah_haji_umroh
263
+ ListeningToMusic *string `gorm:"column:listening_to_music" json:"listening_to_music"` // mendengarkan_musik
264
+ OpinionOnIkhtilat *string `gorm:"column:opinion_on_ikhtilat" json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
265
+ OpinionOnTouchingNonMahram *string `gorm:"column:opinion_on_touching_non_mahram" json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
266
+ OpinionOnVeil *string `gorm:"column:opinion_on_veil" json:"opinion_on_veil"` // pendapat_tentang_cadar
267
+ WeeklyReligiousStudies *string `gorm:"column:weekly_religious_studies" json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
268
+ FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
269
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
270
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
271
  FieldCounter
272
  }
273
 
 
285
  }
286
 
287
  JobCV struct {
288
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
289
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
290
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
291
+ InstitutionName *string `gorm:"column:institution_name" json:"institution_name"` // nama instansi
292
+ CurrentJob *string `gorm:"column:current_job" json:"current_job"` // pekerjaan saat ini
293
+ YearStartedWorking *int `gorm:"column:year_started_working" json:"year_started_working"` // tahun mulai bekerja
294
+ MonthlyIncome *string `gorm:"column:monthly_income" json:"monthly_income"` // penghasilan per bulan
295
+ IncomeSources *pq.StringArray `gorm:"column:income_sources;type:varchar(255)[]" json:"income_sources"` // sumber penghasilan
296
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
297
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
298
  }
299
 
300
  AchievementCV struct {
 
339
  return w.FieldCounter.GetFilledFields(w)
340
  }
341
 
342
+ type (
343
+ MarriageReadinessProfile struct {
344
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
345
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
346
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
347
+
348
+ // Visi Misi Rumah Tangga
349
+ MarriageVision *string `gorm:"column:marriage_vision" json:"marriage_vision"` // Apa visi dan tujuan utama kamu dalam membangun rumah tangga?
350
+ LivingPlan *string `gorm:"column:living_plan" json:"living_plan"` // Setelah menikah, kamu berencana tinggal di mana?
351
+ SpouseRoles *string `gorm:"column:spouse_roles" json:"spouse_roles"` // Menurutmu, apa peran utama suami dan istri dalam rumah tangga?
352
+ SpouseRights *string `gorm:"column:spouse_rights" json:"spouse_rights"` // Apa saja hak suami dan istri menurutmu?
353
+ ConflictResolution *string `gorm:"column:conflict_resolution" json:"conflict_resolution"` // Jika terjadi konflik, bagaimana kamu menyikapinya?
354
+ ParentingStyle *string `gorm:"column:parenting_style" json:"parenting_style"` // Setelah punya anak, pola pengasuhan seperti apa yang kamu inginkan?
355
+
356
+ // Konsep Acara Pernikahan
357
+ ReadyToMarryDuration *string `gorm:"column:ready_to_marry_duration" json:"ready_to_marry_duration"` // Setelah taaruf dimulai, berapa lama kamu butuh untuk siap menikah?
358
+ WeddingConcept *string `gorm:"column:wedding_concept" json:"wedding_concept"` // Seperti apa konsep pernikahan yang kamu harapkan?
359
+ WeddingFunding *string `gorm:"column:wedding_funding" json:"wedding_funding"` // Apakah kamu sudah mempersiapkan dana pernikahan? Dari mana sumbernya?
360
+
361
+ // Karir Kedepannya
362
+ CareerAfterMarriage *string `gorm:"column:career_after_marriage" json:"career_after_marriage"` // Apakah kamu ingin tetap bekerja setelah menikah?
363
+ TimeManagement *string `gorm:"column:time_management" json:"time_management"` // Bagaimana kamu membagi waktu antara keluarga, ibadah, dan pekerjaan?
364
+ CareerGoals *string `gorm:"column:career_goals" json:"career_goals"` // Apa impian karier atau cita-cita kamu dalam 5 sampai 10 tahun ke depan?
365
+ SelfDevelopment *string `gorm:"column:self_development" json:"self_development"` // Apa usaha kamu untuk terus mengembangkan diri dan skill?
366
+
367
+ // Pendidikan Keluarga
368
+ DelayChildren *string `gorm:"column:delay_children" json:"delay_children"` // Apakah kamu berencana menunda memiliki anak?
369
+ ChildEducationPlan *string `gorm:"column:child_education_plan" json:"child_education_plan"` // Apakah kamu sudah punya rencana pendidikan anak?
370
+ ReligiousEmotionalBond *string `gorm:"column:religious_emotional_bond" json:"religious_emotional_bond"` // Bagaimana cara menjaga nilai agama & kedekatan emosional dengan anak?
371
+ ParentingChallenges *string `gorm:"column:parenting_challenges" json:"parenting_challenges"` // Saat menghadapi tantangan pengasuhan, bagaimana kamu menyikapinya?
372
+
373
+ // Finansial Keluarga
374
+ MonthlyFinance *string `gorm:"column:monthly_finance" json:"monthly_finance"` // Bagaimana kamu mengelola keuangan bulanan?
375
+ FamilyResponsibility *string `gorm:"column:family_responsibility" json:"family_responsibility"` // Apakah kamu masih punya tanggungan keluarga setelah menikah?
376
+ DebtStatus *string `gorm:"column:debt_status" json:"debt_status"` // Apakah kamu punya cicilan/utang setelah menikah?
377
+ FinanceSharing *string `gorm:"column:finance_sharing" json:"finance_sharing"` // Setelah menikah, bagaimana pembagian tanggung jawab keuangan?
378
+ IncomeGapView *string `gorm:"column:income_gap_view" json:"income_gap_view"` // Pandangan kamu jika istri berpenghasilan lebih besar dari suami?
379
+
380
+ // Keputusan dan Komunikasi
381
+ DecisionMaking *string `gorm:"column:decision_making" json:"decision_making"` // Dalam mengambil keputusan, kamu lebih mempertimbangkan pasangan atau sendiri?
382
+ GrowthTogether *string `gorm:"column:growth_together" json:"growth_together"` // Apakah kamu siap berjuang bersama pasangan? Apa saja yang ingin diperjuangkan?
383
+ ParentIntervention *string `gorm:"column:parent_intervention" json:"parent_intervention"` // Sejauh mana orang tua/mertua boleh ikut campur dalam rumah tangga?
384
+ HabitResponse *string `gorm:"column:habit_response" json:"habit_response"` // Bagaimana kamu menyikapi kebiasaan pasangan yang kurang kamu sukai?
385
+
386
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
387
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
388
+ }
389
+ )
390
+
391
  // Gorm table name settings
392
  func (Account) TableName() string { return "account" }
393
  func (AccountDetails) TableName() string { return "account_details" }
 
418
  func (EducationCV) TableName() string { return "education_cv" }
419
  func (JobCV) TableName() string { return "job_cv" }
420
  func (AchievementCV) TableName() string { return "achievement_cv" }
421
+ func (MarriageReadinessProfile) TableName() string {
422
+ return "marriage_readiness_profile"
423
+ }
space/models/request_model.go CHANGED
@@ -58,8 +58,9 @@ type AnswerQuizRequest struct {
58
  }
59
 
60
  type (
61
- PersonalityAndPreferenceCVRequest struct {
62
- AccountID int64 `json:"-"`
 
63
  PositiveTraits *string `json:"positive_traits"` // sifat positif
64
  NegativeTraits *string `json:"negative_traits"` // sifat negatif
65
  Hobbies *string `json:"hobbies"` // hobi
@@ -76,8 +77,25 @@ type (
76
  MonthlyExpenses *string `json:"monthly_expenses" validate:"monthly_expenses"` // pengeluaran per bulan
77
  }
78
 
79
- FamilyMemberRequest struct {
80
- AccountID int64 `json:"-"`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  Role *string `json:"role" validate:"family_role"` // Peran dalam keluarga
82
  Status *string `json:"status" validate:"life_status"` // Status (Hidup, Wafat)
83
  Religion *string `json:"religion" validate:"religion"` // Agama
@@ -86,19 +104,35 @@ type (
86
  Age *int `json:"age"` // Usia
87
  }
88
 
89
- PhysicalAndHealthRequest struct {
90
- AccountID int64 `json:"-"`
91
- HeightInCm *int `json:"height_cm"` // Tinggi badan dalam satuan sentimeter
92
- WeightInKg *int `json:"weight_kg"` // Berat badan dalam satuan kilogram
93
- BodyShape *string `json:"body_shape" validate:"body_shape"` // Bentuk tubuh
94
- SkinColor *string `json:"skin_color" validate:"skin_color"` // Warna kulit
95
- HairType *string `json:"hair_type" validate:"hair_type"` // Tipe rambut
96
- MedicalHistory *string `json:"medical_history"` // Riwayat penyakit
97
- PhysicalDisorder *string `json:"physical_disorder"` // Cacat fisik
98
- PhysicalTraits *string `json:"physical_traits"` // Ciri khas fisik
99
  }
100
 
101
- AccountDetailsRequest struct {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  AccountID int64 `json:"-"`
103
  FullName *string `json:"full_name"`
104
  Gender *string `json:"gender" validate:"gender"`
@@ -111,27 +145,45 @@ type (
111
  PhoneNumber *string `json:"phone_number" validate:"phone_number"`
112
  }
113
 
114
- WorshipAndReligiousUnderstandingRequest struct {
115
- AccountID int64 `json:"-"`
116
- ObligatoryPrayer *string `json:"obligatory_prayer"` // sholat_wajib_5_waktu
117
- CongregationalPrayer *string `json:"congregational_prayer"` // sholat_berjamaah_di_masjid
118
- TahajjudPrayer *string `json:"tahajjud_prayer"` // sholat_tahajud
119
- DhuhaPrayer *string `json:"dhuha_prayer"` // sholat_dhuha
120
- QuranMemorization *string `json:"quran_memorization"` // hafalan_alquran
121
- QuranReadingAbility *string `json:"quran_reading_ability" validate:"quran_reading_ability"` // kemampuan_baca_alquran
122
- DaudFasting *string `json:"daud_fasting"` // puasa_daud
123
- AyyamulBidhFasting *string `json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
124
- HajjOrUmrah pq.StringArray `json:"hajj_or_umrah"` // ibadah_haji_umroh
125
- ListeningToMusic *string `json:"listening_to_music"` // mendengarkan_musik
126
- OpinionOnIkhtilat *string `json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
127
- OpinionOnTouchingNonMahram *string `json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
128
- OpinionOnVeil *string `json:"opinion_on_veil"` // pendapat_tentang_cadar
129
- WeeklyReligiousStudies *string `json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
130
- FollowedUstadz *string `json:"followed_ustadz"` // ustadz_yang_diikuti
131
- }
132
-
133
- EducationRequest struct {
134
- AccountID int64 `json:"account_id"`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  LastEducation *string `json:"last_education" validate:"last_education"` // pendidikan terakhir
136
  EducationInstitute *string `json:"education_institute"` // institusi pendidikan
137
  EducationMajor *string `json:"education_major"` // jurusan pendidikan
@@ -139,20 +191,72 @@ type (
139
  YearGraduate *int `json:"year_graduate"` // tahun lulus
140
  }
141
 
142
- JobRequest struct {
143
- AccountID int64 `json:"account_id"`
144
- InstitutionName *string `json:"institution_name"` // nama instansi
145
- CurrentJob *string `json:"current_job"` // pekerjaan saat ini
146
- YearStartedWorking *int `json:"year_started_working"` // tahun mulai bekerja
147
- MonthlyIncome *string `json:"monthly_income" validate:"monthly_income"` // penghasilan per bulan
148
- IncomeSources pq.StringArray `json:"income_sources"` // sumber penghasilan
149
  }
150
 
151
- AchievementRequest struct {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  AccountID int64 `json:"account_id"`
153
  AchievementOrAward *string `json:"achievement_or_award"` // prestasi atau penghargaan
154
  }
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  UploadProfileImageRequest struct {
157
  AccountID int64
158
  File *multipart.FileHeader
@@ -162,7 +266,11 @@ type (
162
  URL string `json:"url"`
163
  }
164
 
165
- ProgressResponse struct {
 
 
 
 
166
  AccountDetailsProgress float64 `json:"account_details_progress"`
167
  PersonalityAndPreferenceCVProgress float64 `json:"personality_and_preference_cv_progress"`
168
  FamilyMemberCVProgress float64 `json:"family_member_cv_progress"`
@@ -174,3 +282,49 @@ type (
174
  TotalProgress float64 `json:"total_progress"`
175
  }
176
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  }
59
 
60
  type (
61
+ SavePersonalityAndPreferenceRequest struct {
62
+ AccountID int64 `json:"-"`
63
+
64
  PositiveTraits *string `json:"positive_traits"` // sifat positif
65
  NegativeTraits *string `json:"negative_traits"` // sifat negatif
66
  Hobbies *string `json:"hobbies"` // hobi
 
77
  MonthlyExpenses *string `json:"monthly_expenses" validate:"monthly_expenses"` // pengeluaran per bulan
78
  }
79
 
80
+ GetPersonalityAndPreferenceRequest struct {
81
+ AccountID int64 `json:"-"`
82
+ }
83
+
84
+ CreateFamilyMemberRequest struct {
85
+ AccountID int64 `json:"-"`
86
+
87
+ Role *string `json:"role" validate:"family_role"` // Peran dalam keluarga
88
+ Status *string `json:"status" validate:"life_status"` // Status (Hidup, Wafat)
89
+ Religion *string `json:"religion" validate:"religion"` // Agama
90
+ Job *string `json:"job"` // Pekerjaan
91
+ LastEducation *string `json:"last_education" validate:"last_education"` // Pendidikan terakhir
92
+ Age *int `json:"age"` // Usia
93
+ }
94
+
95
+ UpdateFamilyMemberRequest struct {
96
+ ID int64 `json:"-"`
97
+ AccountID int64 `json:"-"`
98
+
99
  Role *string `json:"role" validate:"family_role"` // Peran dalam keluarga
100
  Status *string `json:"status" validate:"life_status"` // Status (Hidup, Wafat)
101
  Religion *string `json:"religion" validate:"religion"` // Agama
 
104
  Age *int `json:"age"` // Usia
105
  }
106
 
107
+ ListFamilyMemberRequest struct {
108
+ AccountID int64 `json:"-"`
 
 
 
 
 
 
 
 
109
  }
110
 
111
+ GetFamilyMemberRequest struct {
112
+ ID int64 `json:"-"`
113
+ }
114
+
115
+ DeleteFamilyMemberRequest struct {
116
+ ID int64 `json:"-"`
117
+ }
118
+
119
+ SavePhysicalAndHealthRequest struct {
120
+ AccountID int64 `json:"-"`
121
+ HeightInCm *int `json:"height_cm"` // Tinggi badan dalam satuan sentimeter
122
+ WeightInKg *int `json:"weight_kg"` // Berat badan dalam satuan kilogram
123
+ BodyShape *string `json:"body_shape" validate:"body_shape"` // Bentuk tubuh
124
+ SkinColor *string `json:"skin_color" validate:"skin_color"` // Warna kulit
125
+ HairType *string `json:"hair_type" validate:"hair_type"` // Tipe rambut
126
+ MedicalHistory *pq.StringArray `json:"medical_history"` // Riwayat penyakit
127
+ PhysicalDisorder *string `json:"physical_disorder"` // Cacat fisik
128
+ PhysicalTraits *string `json:"physical_traits"` // Ciri khas fisik
129
+ }
130
+
131
+ GetPhysicalAndHealthRequest struct {
132
+ AccountID int64 `json:"-"`
133
+ }
134
+
135
+ SaveAccountDetailsRequest struct {
136
  AccountID int64 `json:"-"`
137
  FullName *string `json:"full_name"`
138
  Gender *string `json:"gender" validate:"gender"`
 
145
  PhoneNumber *string `json:"phone_number" validate:"phone_number"`
146
  }
147
 
148
+ GetAccountDetailsRequest struct {
149
+ AccountID int64 `json:"account_id"`
150
+ }
151
+
152
+ SaveWorshipAndReligiousUnderstandingRequest struct {
153
+ AccountID int64 `json:"-"`
154
+ ObligatoryPrayer *string `json:"obligatory_prayer"` // sholat_wajib_5_waktu
155
+ CongregationalPrayer *string `json:"congregational_prayer"` // sholat_berjamaah_di_masjid
156
+ TahajjudPrayer *string `json:"tahajjud_prayer"` // sholat_tahajud
157
+ DhuhaPrayer *string `json:"dhuha_prayer"` // sholat_dhuha
158
+ QuranMemorization *string `json:"quran_memorization"` // hafalan_alquran
159
+ QuranReadingAbility *string `json:"quran_reading_ability" validate:"quran_reading_ability"` // kemampuan_baca_alquran
160
+ DaudFasting *string `json:"daud_fasting"` // puasa_daud
161
+ AyyamulBidhFasting *string `json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
162
+ HajjOrUmrah *pq.StringArray `json:"hajj_or_umrah"` // ibadah_haji_umroh
163
+ ListeningToMusic *string `json:"listening_to_music"` // mendengarkan_musik
164
+ OpinionOnIkhtilat *string `json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
165
+ OpinionOnTouchingNonMahram *string `json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
166
+ OpinionOnVeil *string `json:"opinion_on_veil"` // pendapat_tentang_cadar
167
+ WeeklyReligiousStudies *string `json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
168
+ FollowedUstadz *string `json:"followed_ustadz"` // ustadz_yang_diikuti
169
+ }
170
+
171
+ GetWorshipAndReligiousUnderstandingRequest struct {
172
+ AccountID int64 `json:"-"`
173
+ }
174
+
175
+ CreateEducationRequest struct {
176
+ AccountID int64 `json:"-"`
177
+ LastEducation *string `json:"last_education" validate:"last_education"` // pendidikan terakhir
178
+ EducationInstitute *string `json:"education_institute"` // institusi pendidikan
179
+ EducationMajor *string `json:"education_major"` // jurusan pendidikan
180
+ YearStart *int `json:"year_start"` // tahun masuk
181
+ YearGraduate *int `json:"year_graduate"` // tahun lulus
182
+ }
183
+
184
+ UpdateEducationRequest struct {
185
+ ID int64 `json:"-"`
186
+ AccountID int64 `json:"-"`
187
  LastEducation *string `json:"last_education" validate:"last_education"` // pendidikan terakhir
188
  EducationInstitute *string `json:"education_institute"` // institusi pendidikan
189
  EducationMajor *string `json:"education_major"` // jurusan pendidikan
 
191
  YearGraduate *int `json:"year_graduate"` // tahun lulus
192
  }
193
 
194
+ ListEducationRequest struct {
195
+ AccountID int64 `json:"-"`
 
 
 
 
 
196
  }
197
 
198
+ GetEducationRequest struct {
199
+ ID int64 `json:"-"`
200
+ }
201
+
202
+ DeleteEducationRequest struct {
203
+ ID int64 `json:"-"`
204
+ }
205
+
206
+ CreateJobRequest struct {
207
+ AccountID int64 `json:"account_id"`
208
+ InstitutionName *string `json:"institution_name"` // nama instansi
209
+ CurrentJob *string `json:"current_job"` // pekerjaan saat ini
210
+ YearStartedWorking *int `json:"year_started_working"` // tahun mulai bekerja
211
+ MonthlyIncome *string `json:"monthly_income" validate:"monthly_income"` // penghasilan per bulan
212
+ IncomeSources *pq.StringArray `json:"income_sources"` // sumber penghasilan
213
+ }
214
+
215
+ UpdateJobRequest struct {
216
+ ID int64 `json:"-"`
217
+ AccountID int64 `json:"-"`
218
+ InstitutionName *string `json:"institution_name"` // nama instansi
219
+ CurrentJob *string `json:"current_job"` // pekerjaan saat ini
220
+ YearStartedWorking *int `json:"year_started_working"` // tahun mulai bekerja
221
+ MonthlyIncome *string `json:"monthly_income" validate:"monthly_income"` // penghasilan per bulan
222
+ IncomeSources *pq.StringArray `json:"income_sources"` // sumber penghasilan
223
+ }
224
+
225
+ ListJobRequest struct {
226
+ AccountID int64 `json:"-"`
227
+ }
228
+
229
+ GetJobRequest struct {
230
+ ID int64 `json:"-"`
231
+ }
232
+
233
+ DeleteJobRequest struct {
234
+ ID int64 `json:"-"`
235
+ }
236
+
237
+ CreateAchievementRequest struct {
238
  AccountID int64 `json:"account_id"`
239
  AchievementOrAward *string `json:"achievement_or_award"` // prestasi atau penghargaan
240
  }
241
 
242
+ UpdateAchievementRequest struct {
243
+ ID int64 `json:"-"`
244
+ AccountID int64 `json:"-"`
245
+ AchievementOrAward *string `json:"achievement_or_award"` // prestasi atau penghargaan
246
+ }
247
+
248
+ ListAchievementRequest struct {
249
+ AccountID int64 `json:"-"`
250
+ }
251
+
252
+ GetAchievementRequest struct {
253
+ ID int64 `json:"-"`
254
+ }
255
+
256
+ DeleteAchievementRequest struct {
257
+ ID int64 `json:"-"`
258
+ }
259
+
260
  UploadProfileImageRequest struct {
261
  AccountID int64
262
  File *multipart.FileHeader
 
266
  URL string `json:"url"`
267
  }
268
 
269
+ GetProgressCVRequest struct {
270
+ AccountID int64 `json:"-"`
271
+ }
272
+
273
+ GetProgressCVResponse struct {
274
  AccountDetailsProgress float64 `json:"account_details_progress"`
275
  PersonalityAndPreferenceCVProgress float64 `json:"personality_and_preference_cv_progress"`
276
  FamilyMemberCVProgress float64 `json:"family_member_cv_progress"`
 
282
  TotalProgress float64 `json:"total_progress"`
283
  }
284
  )
285
+
286
+ type SaveMarriageReadinessProfileRequest struct {
287
+ AccountID int64 `json:"account_id"`
288
+
289
+ // Visi Misi Rumah Tangga
290
+ MarriageVision *string `gorm:"column:marriage_vision" json:"marriage_vision"` // Apa visi dan tujuan utama kamu dalam membangun rumah tangga?
291
+ LivingPlan *string `gorm:"column:living_plan" json:"living_plan"` // Setelah menikah, kamu berencana tinggal di mana?
292
+ SpouseRoles *string `gorm:"column:spouse_roles" json:"spouse_roles"` // Menurutmu, apa peran utama suami dan istri dalam rumah tangga?
293
+ SpouseRights *string `gorm:"column:spouse_rights" json:"spouse_rights"` // Apa saja hak suami dan istri menurutmu?
294
+ ConflictResolution *string `gorm:"column:conflict_resolution" json:"conflict_resolution"` // Jika terjadi konflik, bagaimana kamu menyikapinya?
295
+ ParentingStyle *string `gorm:"column:parenting_style" json:"parenting_style"` // Setelah punya anak, pola pengasuhan seperti apa yang kamu inginkan?
296
+
297
+ // Konsep Acara Pernikahan
298
+ ReadyToMarryDuration *string `gorm:"column:ready_to_marry_duration" json:"ready_to_marry_duration"` // Setelah taaruf dimulai, berapa lama kamu butuh untuk siap menikah?
299
+ WeddingConcept *string `gorm:"column:wedding_concept" json:"wedding_concept"` // Seperti apa konsep pernikahan yang kamu harapkan?
300
+ WeddingFunding *string `gorm:"column:wedding_funding" json:"wedding_funding"` // Apakah kamu sudah mempersiapkan dana pernikahan? Dari mana sumbernya?
301
+
302
+ // Karir Kedepannya
303
+ CareerAfterMarriage *string `gorm:"column:career_after_marriage" json:"career_after_marriage"` // Apakah kamu ingin tetap bekerja setelah menikah?
304
+ TimeManagement *string `gorm:"column:time_management" json:"time_management"` // Bagaimana kamu membagi waktu antara keluarga, ibadah, dan pekerjaan?
305
+ CareerGoals *string `gorm:"column:career_goals" json:"career_goals"` // Apa impian karier atau cita-cita kamu dalam 5 sampai 10 tahun ke depan?
306
+ SelfDevelopment *string `gorm:"column:self_development" json:"self_development"` // Apa usaha kamu untuk terus mengembangkan diri dan skill?
307
+
308
+ // Pendidikan Keluarga
309
+ DelayChildren *string `gorm:"column:delay_children" json:"delay_children"` // Apakah kamu berencana menunda memiliki anak?
310
+ ChildEducationPlan *string `gorm:"column:child_education_plan" json:"child_education_plan"` // Apakah kamu sudah punya rencana pendidikan anak?
311
+ ReligiousEmotionalBond *string `gorm:"column:religious_emotional_bond" json:"religious_emotional_bond"` // Bagaimana cara menjaga nilai agama & kedekatan emosional dengan anak?
312
+ ParentingChallenges *string `gorm:"column:parenting_challenges" json:"parenting_challenges"` // Saat menghadapi tantangan pengasuhan, bagaimana kamu menyikapinya?
313
+
314
+ // Finansial Keluarga
315
+ MonthlyFinance *string `gorm:"column:monthly_finance" json:"monthly_finance"` // Bagaimana kamu mengelola keuangan bulanan?
316
+ FamilyResponsibility *string `gorm:"column:family_responsibility" json:"family_responsibility"` // Apakah kamu masih punya tanggungan keluarga setelah menikah?
317
+ DebtStatus *string `gorm:"column:debt_status" json:"debt_status"` // Apakah kamu punya cicilan/utang setelah menikah?
318
+ FinanceSharing *string `gorm:"column:finance_sharing" json:"finance_sharing"` // Setelah menikah, bagaimana pembagian tanggung jawab keuangan?
319
+ IncomeGapView *string `gorm:"column:income_gap_view" json:"income_gap_view"` // Pandangan kamu jika istri berpenghasilan lebih besar dari suami?
320
+
321
+ // Keputusan dan Komunikasi
322
+ DecisionMaking *string `gorm:"column:decision_making" json:"decision_making"` // Dalam mengambil keputusan, kamu lebih mempertimbangkan pasangan atau sendiri?
323
+ GrowthTogether *string `gorm:"column:growth_together" json:"growth_together"` // Apakah kamu siap berjuang bersama pasangan? Apa saja yang ingin diperjuangkan?
324
+ ParentIntervention *string `gorm:"column:parent_intervention" json:"parent_intervention"` // Sejauh mana orang tua/mertua boleh ikut campur dalam rumah tangga?
325
+ HabitResponse *string `gorm:"column:habit_response" json:"habit_response"` // Bagaimana kamu menyikapi kebiasaan pasangan yang kurang kamu sukai?
326
+ }
327
+
328
+ type GetMarriageReadinessProfileRequest struct {
329
+ AccountID int64 `json:"-"`
330
+ }
space/pkg/validation/validation.go CHANGED
@@ -3,6 +3,7 @@ package validation
3
  import (
4
  "errors"
5
  "fmt"
 
6
  "strings"
7
 
8
  "github.com/go-playground/locales/en"
@@ -129,10 +130,12 @@ func Validate(s any) []ErrorMessage {
129
  if validatorInstance == nil {
130
  return []ErrorMessage{{Field: "", Message: "Validator belum diinisialisasi. Panggil validation.New() terlebih dahulu."}}
131
  }
 
132
  err := validatorInstance.validate.Struct(s)
133
  if err != nil {
134
  return TranslateError(err)
135
  }
 
136
  return nil
137
  }
138
 
@@ -147,9 +150,15 @@ func TranslateError(err error) []ErrorMessage {
147
  return nil
148
  }
149
 
150
- errorMessages := make([]ErrorMessage, 0, len(validationErrors))
 
151
  for _, e := range validationErrors {
152
  fieldLabel := e.Field()
 
 
 
 
 
153
  msg, err := validatorInstance.translator.T(e.Tag(), fieldLabel)
154
  if err != nil {
155
  msg = fieldLabel + " is Invalid"
 
3
  import (
4
  "errors"
5
  "fmt"
6
+ "reflect"
7
  "strings"
8
 
9
  "github.com/go-playground/locales/en"
 
130
  if validatorInstance == nil {
131
  return []ErrorMessage{{Field: "", Message: "Validator belum diinisialisasi. Panggil validation.New() terlebih dahulu."}}
132
  }
133
+
134
  err := validatorInstance.validate.Struct(s)
135
  if err != nil {
136
  return TranslateError(err)
137
  }
138
+
139
  return nil
140
  }
141
 
 
150
  return nil
151
  }
152
 
153
+ var errorMessages []ErrorMessage
154
+
155
  for _, e := range validationErrors {
156
  fieldLabel := e.Field()
157
+
158
+ if e.Kind() == reflect.Ptr {
159
+ continue
160
+ }
161
+
162
  msg, err := validatorInstance.translator.T(e.Tag(), fieldLabel)
163
  if err != nil {
164
  msg = fieldLabel + " is Invalid"
space/router/cv_route.go CHANGED
@@ -42,6 +42,6 @@ func (s *Server) CVRoute() {
42
  routerGroup.PUT("/achievements/:id", s.cvController.UpdateAchievement)
43
  routerGroup.DELETE("/achievements/:id", s.cvController.DeleteAchievement)
44
 
45
- routerGroup.GET("/progress", s.cvController.GetProgress)
46
  }
47
  }
 
42
  routerGroup.PUT("/achievements/:id", s.cvController.UpdateAchievement)
43
  routerGroup.DELETE("/achievements/:id", s.cvController.DeleteAchievement)
44
 
45
+ routerGroup.GET("/progress", s.cvController.GetProgressCV)
46
  }
47
  }
space/services/cv_service.go CHANGED
@@ -11,48 +11,49 @@ import (
11
  "api.qobiltu.id/pkg/validation"
12
  "api.qobiltu.id/repositories"
13
  "api.qobiltu.id/response"
 
14
  "gorm.io/gorm"
15
  )
16
 
17
  type CVService interface {
18
- SaveAccountDetails(ctx context.Context, req *models.AccountDetailsRequest) (*models.AccountDetails, error)
19
- GetAccountDetails(ctx context.Context, id int64) (*models.AccountDetails, error)
20
-
21
- SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCVRequest) (*models.PersonalityAndPreferenceCV, error)
22
- GetPersonalityAndPreference(ctx context.Context, id int64) (*models.PersonalityAndPreferenceCV, error)
23
-
24
- CreateFamilyMember(ctx context.Context, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
25
- UpdateFamilyMember(ctx context.Context, id int64, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
26
- ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error)
27
- GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error)
28
- DeleteFamilyMember(ctx context.Context, id int64) error
29
-
30
- SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error)
31
- GetPhysicalAndHealth(ctx context.Context, id int64) (*models.PhysicalAndHealthCV, error)
32
-
33
- SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error)
34
- GetWorshipAndReligiousUnderstanding(ctx context.Context, id int64) (*models.WorshipAndReligiousUnderstandingCV, error)
35
-
36
- CreateEducation(ctx context.Context, req *models.EducationRequest) (*models.EducationCV, error)
37
- UpdateEducation(ctx context.Context, id int64, req *models.EducationRequest) (*models.EducationCV, error)
38
- ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error)
39
- GetEducation(ctx context.Context, id int64) (*models.EducationCV, error)
40
- DeleteEducation(ctx context.Context, id int64) error
41
-
42
- CreateJob(ctx context.Context, req *models.JobRequest) (*models.JobCV, error)
43
- UpdateJob(ctx context.Context, id int64, req *models.JobRequest) (*models.JobCV, error)
44
- ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error)
45
- GetJob(ctx context.Context, id int64) (*models.JobCV, error)
46
- DeleteJob(ctx context.Context, id int64) error
47
-
48
- CreateAchievement(ctx context.Context, req *models.AchievementRequest) (*models.AchievementCV, error)
49
- UpdateAchievement(ctx context.Context, id int64, req *models.AchievementRequest) (*models.AchievementCV, error)
50
- ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error)
51
- GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error)
52
- DeleteAchievement(ctx context.Context, id int64) error
53
 
54
  UploadProfileImage(ctx context.Context, req *models.UploadProfileImageRequest) (*models.UploadProfileImageResponse, error)
55
- GetProgress(ctx context.Context, accountID int64) (*models.ProgressResponse, error)
56
  }
57
 
58
  type cvService struct {
@@ -67,7 +68,9 @@ func NewCVService(cvRepository repositories.CVRepository, storage storage.Storag
67
  }
68
  }
69
 
70
- func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.AccountDetailsRequest) (*models.AccountDetails, error) {
 
 
71
  if err := validation.Validate(req); err != nil {
72
  return nil, response.HandleValidationError(err)
73
  }
@@ -84,14 +87,15 @@ func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.AccountD
84
  }
85
 
86
  accountDetails.AccountID = uint(req.AccountID)
87
- accountDetails.FullName = req.FullName
88
- accountDetails.Gender = req.Gender
89
- accountDetails.DateOfBirth = req.DateOfBirth
90
- accountDetails.PlaceOfBirth = req.PlaceOfBirth
91
- accountDetails.Domicile = req.Domicile
92
- accountDetails.MaritalStatus = req.MaritalStatus
93
- accountDetails.LastEducation = req.LastEducation
94
- accountDetails.LastJob = req.LastJob
 
95
 
96
  if req.PhoneNumber != nil {
97
  sanitizedPhone := validation.SanitizePhoneNumber(*req.PhoneNumber)
@@ -107,15 +111,15 @@ func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.AccountD
107
  return res, nil
108
  }
109
 
110
- func (s *cvService) GetAccountDetails(ctx context.Context, id int64) (*models.AccountDetails, error) {
111
- res, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, id)
112
  if err != nil {
113
- return nil, response.HandleGormError(err, "Data diri tidak ditemukan")
114
  }
115
  return res, nil
116
  }
117
 
118
- func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCVRequest) (*models.PersonalityAndPreferenceCV, error) {
119
  if err := validation.Validate(req); err != nil {
120
  return nil, response.HandleValidationError(err)
121
  }
@@ -132,20 +136,21 @@ func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *model
132
  }
133
 
134
  personalityAndPreference.AccountID = req.AccountID
135
- personalityAndPreference.PositiveTraits = req.PositiveTraits
136
- personalityAndPreference.NegativeTraits = req.NegativeTraits
137
- personalityAndPreference.Hobbies = req.Hobbies
138
- personalityAndPreference.LifeGoals = req.LifeGoals
139
- personalityAndPreference.DailyActivities = req.DailyActivities
140
- personalityAndPreference.LeisureActivities = req.LeisureActivities
141
- personalityAndPreference.Likes = req.Likes
142
- personalityAndPreference.Dislikes = req.Dislikes
143
- personalityAndPreference.StressHandling = req.StressHandling
144
- personalityAndPreference.AngerTriggers = req.AngerTriggers
145
- personalityAndPreference.FavoriteFoodAndDrinks = req.FavoriteFoodAndDrinks
146
- personalityAndPreference.CanCook = req.CanCook
147
- personalityAndPreference.TypesOfDishesCooked = req.TypesOfDishesCooked
148
- personalityAndPreference.MonthlyExpenses = req.MonthlyExpenses
 
149
 
150
  res, err := s.cvRepository.SavePersonalityAndPreference(ctx, personalityAndPreference)
151
  if err != nil {
@@ -155,8 +160,8 @@ func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *model
155
  return res, nil
156
  }
157
 
158
- func (s *cvService) GetPersonalityAndPreference(ctx context.Context, id int64) (*models.PersonalityAndPreferenceCV, error) {
159
- res, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, id)
160
  if err != nil {
161
  return nil, response.HandleGormError(err, "Internal Server Error")
162
  }
@@ -164,7 +169,7 @@ func (s *cvService) GetPersonalityAndPreference(ctx context.Context, id int64) (
164
  return res, nil
165
  }
166
 
167
- func (s *cvService) CreateFamilyMember(ctx context.Context, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error) {
168
  if err := validation.Validate(req); err != nil {
169
  return nil, response.HandleValidationError(err)
170
  }
@@ -183,62 +188,62 @@ func (s *cvService) CreateFamilyMember(ctx context.Context, req *models.FamilyMe
183
  // Simpan ke repository
184
  res, err := s.cvRepository.SaveFamilyMember(ctx, familyMember)
185
  if err != nil {
186
- return nil, response.HandleGormError(err, "Gagal menyimpan anggota keluarga")
187
  }
188
 
189
  return res, nil
190
  }
191
 
192
- func (s *cvService) ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error) {
193
- list, err := s.cvRepository.ListFamilyMember(ctx, accountID)
194
  if err != nil {
195
- return nil, response.HandleGormError(err, "Gagal mengambil daftar anggota keluarga")
196
  }
197
  return list, nil
198
  }
199
 
200
- func (s *cvService) GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error) {
201
- res, err := s.cvRepository.GetFamilyMember(ctx, id)
202
  if err != nil {
203
- return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
204
  }
205
  return res, nil
206
  }
207
 
208
- func (s *cvService) DeleteFamilyMember(ctx context.Context, id int64) error {
209
- err := s.cvRepository.DeleteFamilyMember(ctx, id)
210
  if err != nil {
211
- return response.HandleGormError(err, "Gagal menghapus anggota keluarga")
212
  }
213
  return nil
214
  }
215
 
216
- func (s *cvService) UpdateFamilyMember(ctx context.Context, id int64, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error) {
217
  if err := validation.Validate(req); err != nil {
218
  return nil, response.HandleValidationError(err)
219
  }
220
 
221
- existing, err := s.cvRepository.GetFamilyMember(ctx, id)
222
  if err != nil {
223
- return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
224
  }
225
 
226
- existing.Role = req.Role
227
- existing.Status = req.Status
228
- existing.Religion = req.Religion
229
- existing.Job = req.Job
230
- existing.LastEducation = req.LastEducation
231
- existing.Age = req.Age
232
 
233
  updated, err := s.cvRepository.SaveFamilyMember(ctx, existing)
234
  if err != nil {
235
- return nil, response.HandleGormError(err, "Gagal memperbarui anggota keluarga")
236
  }
237
 
238
  return updated, nil
239
  }
240
 
241
- func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error) {
242
  if err := validation.Validate(req); err != nil {
243
  return nil, response.HandleValidationError(err)
244
  }
@@ -246,7 +251,7 @@ func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.Physi
246
  // Cek apakah data sudah ada berdasarkan account_id
247
  existing, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, req.AccountID)
248
  if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
249
- return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data fisik dan kesehatan")
250
  }
251
 
252
  // Jika belum ada, buat objek baru
@@ -256,33 +261,34 @@ func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.Physi
256
 
257
  // Mapping field dari request
258
  existing.AccountID = req.AccountID
259
- existing.HeightInCm = req.HeightInCm
260
- existing.WeightInKg = req.WeightInKg
261
- existing.BodyShape = req.BodyShape
262
- existing.SkinColor = req.SkinColor
263
- existing.HairType = req.HairType
264
- existing.MedicalHistory = req.MedicalHistory
265
- existing.PhysicalDisorder = req.PhysicalDisorder
266
- existing.PhysicalTraits = req.PhysicalTraits
 
267
 
268
  // Simpan data
269
  res, err := s.cvRepository.SavePhysicalAndHealth(ctx, existing)
270
  if err != nil {
271
- return nil, response.HandleGormError(err, "Gagal menyimpan data fisik dan kesehatan")
272
  }
273
 
274
  return res, nil
275
  }
276
 
277
- func (s *cvService) GetPhysicalAndHealth(ctx context.Context, id int64) (*models.PhysicalAndHealthCV, error) {
278
- res, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, id)
279
  if err != nil {
280
- return nil, response.HandleGormError(err, "Data fisik dan kesehatan tidak ditemukan")
281
  }
282
  return res, nil
283
  }
284
 
285
- func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error) {
286
  if err := validation.Validate(req); err != nil {
287
  return nil, response.HandleValidationError(err)
288
  }
@@ -290,7 +296,7 @@ func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, re
290
  // Cek apakah data sudah ada berdasarkan account_id
291
  worshipAndReligiousUnderstanding, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, req.AccountID)
292
  if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
293
- return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data agama dan pemahaman agama")
294
  }
295
 
296
  // Jika belum ada, buat objek baru
@@ -300,21 +306,22 @@ func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, re
300
 
301
  // Mapping field dari request
302
  worshipAndReligiousUnderstanding.AccountID = req.AccountID
303
- worshipAndReligiousUnderstanding.ObligatoryPrayer = req.ObligatoryPrayer
304
- worshipAndReligiousUnderstanding.CongregationalPrayer = req.CongregationalPrayer
305
- worshipAndReligiousUnderstanding.TahajjudPrayer = req.TahajjudPrayer
306
- worshipAndReligiousUnderstanding.DhuhaPrayer = req.DhuhaPrayer
307
- worshipAndReligiousUnderstanding.QuranMemorization = req.QuranMemorization
308
- worshipAndReligiousUnderstanding.QuranReadingAbility = req.QuranReadingAbility
309
- worshipAndReligiousUnderstanding.DaudFasting = req.DaudFasting
310
- worshipAndReligiousUnderstanding.AyyamulBidhFasting = req.AyyamulBidhFasting
311
- worshipAndReligiousUnderstanding.HajjOrUmrah = req.HajjOrUmrah
312
- worshipAndReligiousUnderstanding.ListeningToMusic = req.ListeningToMusic
313
- worshipAndReligiousUnderstanding.OpinionOnIkhtilat = req.OpinionOnIkhtilat
314
- worshipAndReligiousUnderstanding.OpinionOnTouchingNonMahram = req.OpinionOnTouchingNonMahram
315
- worshipAndReligiousUnderstanding.OpinionOnVeil = req.OpinionOnVeil
316
- worshipAndReligiousUnderstanding.WeeklyReligiousStudies = req.WeeklyReligiousStudies
317
- worshipAndReligiousUnderstanding.FollowedUstadz = req.FollowedUstadz
 
318
 
319
  // Simpan data
320
  res, err := s.cvRepository.SaveWorshipAndReligiousUnderstanding(ctx, worshipAndReligiousUnderstanding)
@@ -325,15 +332,15 @@ func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, re
325
  return res, nil
326
  }
327
 
328
- func (s *cvService) GetWorshipAndReligiousUnderstanding(ctx context.Context, id int64) (*models.WorshipAndReligiousUnderstandingCV, error) {
329
- res, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, id)
330
  if err != nil {
331
- return nil, response.HandleGormError(err, "Data agama dan pemahaman agama tidak ditemukan")
332
  }
333
  return res, nil
334
  }
335
 
336
- func (s *cvService) CreateEducation(ctx context.Context, req *models.EducationRequest) (*models.EducationCV, error) {
337
  if err := validation.Validate(req); err != nil {
338
  return nil, response.HandleValidationError(err)
339
  }
@@ -349,52 +356,52 @@ func (s *cvService) CreateEducation(ctx context.Context, req *models.EducationRe
349
 
350
  res, err := s.cvRepository.SaveEducation(ctx, edu)
351
  if err != nil {
352
- return nil, response.HandleGormError(err, "Gagal menambahkan data pendidikan")
353
  }
354
 
355
  return res, nil
356
  }
357
 
358
- func (s *cvService) UpdateEducation(ctx context.Context, id int64, req *models.EducationRequest) (*models.EducationCV, error) {
359
  if err := validation.Validate(req); err != nil {
360
  return nil, response.HandleValidationError(err)
361
  }
362
 
363
- edu, err := s.cvRepository.GetEducation(ctx, id)
364
  if err != nil {
365
- return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
366
  }
367
 
368
- edu.LastEducation = req.LastEducation
369
- edu.EducationInstitute = req.EducationInstitute
370
- edu.EducationMajor = req.EducationMajor
371
- edu.YearStart = req.YearStart
372
- edu.YearGraduate = req.YearGraduate
373
 
374
  res, err := s.cvRepository.SaveEducation(ctx, edu)
375
  if err != nil {
376
- return nil, response.HandleGormError(err, "Gagal memperbarui data pendidikan")
377
  }
378
  return res, nil
379
  }
380
 
381
- func (s *cvService) ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error) {
382
- return s.cvRepository.ListEducation(ctx, accountID)
383
  }
384
 
385
- func (s *cvService) GetEducation(ctx context.Context, id int64) (*models.EducationCV, error) {
386
- edu, err := s.cvRepository.GetEducation(ctx, id)
387
  if err != nil {
388
- return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
389
  }
390
  return edu, nil
391
  }
392
 
393
- func (s *cvService) DeleteEducation(ctx context.Context, id int64) error {
394
- return s.cvRepository.DeleteEducation(ctx, id)
395
  }
396
 
397
- func (s *cvService) CreateJob(ctx context.Context, req *models.JobRequest) (*models.JobCV, error) {
398
  if err := validation.Validate(req); err != nil {
399
  return nil, response.HandleValidationError(err)
400
  }
@@ -409,93 +416,93 @@ func (s *cvService) CreateJob(ctx context.Context, req *models.JobRequest) (*mod
409
  }
410
  res, err := s.cvRepository.SaveJob(ctx, job)
411
  if err != nil {
412
- return nil, response.HandleGormError(err, "Gagal menambahkan data pekerjaan")
413
  }
414
  return res, nil
415
  }
416
 
417
- func (s *cvService) UpdateJob(ctx context.Context, id int64, req *models.JobRequest) (*models.JobCV, error) {
418
  if err := validation.Validate(req); err != nil {
419
  return nil, response.HandleValidationError(err)
420
  }
421
 
422
- job, err := s.cvRepository.GetJob(ctx, id)
423
  if err != nil {
424
- return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
425
  }
426
 
427
- job.InstitutionName = req.InstitutionName
428
- job.CurrentJob = req.CurrentJob
429
- job.YearStartedWorking = req.YearStartedWorking
430
- job.MonthlyIncome = req.MonthlyIncome
431
- job.IncomeSources = req.IncomeSources
432
 
433
  res, err := s.cvRepository.SaveJob(ctx, job)
434
  if err != nil {
435
- return nil, response.HandleGormError(err, "Gagal memperbarui data pekerjaan")
436
  }
437
  return res, nil
438
  }
439
 
440
- func (s *cvService) ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error) {
441
- return s.cvRepository.ListJob(ctx, accountID)
442
  }
443
 
444
- func (s *cvService) GetJob(ctx context.Context, id int64) (*models.JobCV, error) {
445
- job, err := s.cvRepository.GetJob(ctx, id)
446
  if err != nil {
447
- return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
448
  }
449
  return job, nil
450
  }
451
 
452
- func (s *cvService) DeleteJob(ctx context.Context, id int64) error {
453
- return s.cvRepository.DeleteJob(ctx, id)
454
  }
455
 
456
- func (s *cvService) CreateAchievement(ctx context.Context, req *models.AchievementRequest) (*models.AchievementCV, error) {
457
  ach := &models.AchievementCV{
458
  AccountID: req.AccountID,
459
  AchievementOrAward: req.AchievementOrAward,
460
  }
461
  res, err := s.cvRepository.SaveAchievement(ctx, ach)
462
  if err != nil {
463
- return nil, response.HandleGormError(err, "Gagal menambahkan data prestasi")
464
  }
465
 
466
  return res, nil
467
  }
468
 
469
- func (s *cvService) UpdateAchievement(ctx context.Context, id int64, req *models.AchievementRequest) (*models.AchievementCV, error) {
470
- ach, err := s.cvRepository.GetAchievement(ctx, id)
471
  if err != nil {
472
- return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
473
  }
474
 
475
  ach.AchievementOrAward = req.AchievementOrAward
476
 
477
  res, err := s.cvRepository.SaveAchievement(ctx, ach)
478
  if err != nil {
479
- return nil, response.HandleGormError(err, "Gagal memperbarui data prestasi")
480
  }
481
 
482
  return res, nil
483
  }
484
 
485
- func (s *cvService) ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error) {
486
- return s.cvRepository.ListAchievement(ctx, accountID)
487
  }
488
 
489
- func (s *cvService) GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error) {
490
- ach, err := s.cvRepository.GetAchievement(ctx, id)
491
  if err != nil {
492
- return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
493
  }
494
  return ach, nil
495
  }
496
 
497
- func (s *cvService) DeleteAchievement(ctx context.Context, id int64) error {
498
- return s.cvRepository.DeleteAchievement(ctx, id)
499
  }
500
 
501
  func (s *cvService) UploadProfileImage(ctx context.Context, req *models.UploadProfileImageRequest) (*models.UploadProfileImageResponse, error) {
@@ -569,8 +576,8 @@ func (s *cvService) UploadProfileImage(ctx context.Context, req *models.UploadPr
569
  }, nil
570
  }
571
 
572
- func (s *cvService) GetProgress(ctx context.Context, accountID int64) (*models.ProgressResponse, error) {
573
- accountDetails, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, accountID)
574
  if err != nil {
575
  if !errors.Is(err, gorm.ErrRecordNotFound) {
576
  return nil, response.HandleGormError(err, "Internal Server Error")
@@ -578,7 +585,7 @@ func (s *cvService) GetProgress(ctx context.Context, accountID int64) (*models.P
578
  accountDetails = &models.AccountDetails{}
579
  }
580
 
581
- personalityAndPreferenceCV, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, accountID)
582
  if err != nil {
583
  if !errors.Is(err, gorm.ErrRecordNotFound) {
584
  return nil, response.HandleGormError(err, "Internal Server Error")
@@ -586,7 +593,7 @@ func (s *cvService) GetProgress(ctx context.Context, accountID int64) (*models.P
586
  personalityAndPreferenceCV = &models.PersonalityAndPreferenceCV{}
587
  }
588
 
589
- physicalAndHealthCV, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, accountID)
590
  if err != nil {
591
  if !errors.Is(err, gorm.ErrRecordNotFound) {
592
  return nil, response.HandleGormError(err, "Internal Server Error")
@@ -594,7 +601,7 @@ func (s *cvService) GetProgress(ctx context.Context, accountID int64) (*models.P
594
  physicalAndHealthCV = &models.PhysicalAndHealthCV{}
595
  }
596
 
597
- worshipAndReligiousUnderstandingCV, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, accountID)
598
  if err != nil {
599
  if !errors.Is(err, gorm.ErrRecordNotFound) {
600
  return nil, response.HandleGormError(err, "Internal Server Error")
@@ -602,22 +609,22 @@ func (s *cvService) GetProgress(ctx context.Context, accountID int64) (*models.P
602
  worshipAndReligiousUnderstandingCV = &models.WorshipAndReligiousUnderstandingCV{}
603
  }
604
 
605
- educationCV, err := s.cvRepository.ListEducation(ctx, accountID)
606
  if err != nil {
607
  return nil, response.HandleGormError(err, "Internal Server Error")
608
  }
609
 
610
- familyMemberCV, err := s.cvRepository.ListFamilyMember(ctx, accountID)
611
  if err != nil {
612
  return nil, response.HandleGormError(err, "Internal Server Error")
613
  }
614
 
615
- jobCV, err := s.cvRepository.ListJob(ctx, accountID)
616
  if err != nil {
617
  return nil, response.HandleGormError(err, "Internal Server Error")
618
  }
619
 
620
- achievementCV, err := s.cvRepository.ListAchievement(ctx, accountID)
621
  if err != nil {
622
  return nil, response.HandleGormError(err, "Internal Server Error")
623
  }
@@ -645,9 +652,9 @@ func calculateProgress(
645
  familyMemberCV int,
646
  jobCV int,
647
  achievementCV int,
648
- ) *models.ProgressResponse {
649
 
650
- // fullIfPresent returns 100 if the data is greater than 0, otherwise returns 0
651
  fullIfPresent := func(data int) float64 {
652
  if data > 0 {
653
  return 100
@@ -667,7 +674,7 @@ func calculateProgress(
667
 
668
  overallProgress := (accountDetailsPercentage + personalityAndPreferenceCVPercentage + physicalAndHealthCVPercentage + worshipAndReligiousUnderstandingCVPercentage + educationCVPercentage + familyMemberCVPercentage + jobCVPercentage + achievementCVPercentage) / 8
669
 
670
- return &models.ProgressResponse{
671
  AccountDetailsProgress: math.Round(accountDetailsPercentage*100) / 100,
672
  PersonalityAndPreferenceCVProgress: math.Round(personalityAndPreferenceCVPercentage*100) / 100,
673
  FamilyMemberCVProgress: math.Round(familyMemberCVPercentage*100) / 100,
 
11
  "api.qobiltu.id/pkg/validation"
12
  "api.qobiltu.id/repositories"
13
  "api.qobiltu.id/response"
14
+ "api.qobiltu.id/utils"
15
  "gorm.io/gorm"
16
  )
17
 
18
  type CVService interface {
19
+ SaveAccountDetails(ctx context.Context, req *models.SaveAccountDetailsRequest) (*models.AccountDetails, error)
20
+ GetAccountDetails(ctx context.Context, req *models.GetAccountDetailsRequest) (*models.AccountDetails, error)
21
+
22
+ SavePersonalityAndPreference(ctx context.Context, req *models.SavePersonalityAndPreferenceRequest) (*models.PersonalityAndPreferenceCV, error)
23
+ GetPersonalityAndPreference(ctx context.Context, req *models.GetPersonalityAndPreferenceRequest) (*models.PersonalityAndPreferenceCV, error)
24
+
25
+ CreateFamilyMember(ctx context.Context, req *models.CreateFamilyMemberRequest) (*models.FamilyMemberCV, error)
26
+ UpdateFamilyMember(ctx context.Context, req *models.UpdateFamilyMemberRequest) (*models.FamilyMemberCV, error)
27
+ ListFamilyMember(ctx context.Context, req *models.ListFamilyMemberRequest) ([]models.FamilyMemberCV, error)
28
+ GetFamilyMember(ctx context.Context, req *models.GetFamilyMemberRequest) (*models.FamilyMemberCV, error)
29
+ DeleteFamilyMember(ctx context.Context, req *models.DeleteFamilyMemberRequest) error
30
+
31
+ SavePhysicalAndHealth(ctx context.Context, req *models.SavePhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error)
32
+ GetPhysicalAndHealth(ctx context.Context, req *models.GetPhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error)
33
+
34
+ SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.SaveWorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error)
35
+ GetWorshipAndReligiousUnderstanding(ctx context.Context, req *models.GetWorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error)
36
+
37
+ CreateEducation(ctx context.Context, req *models.CreateEducationRequest) (*models.EducationCV, error)
38
+ UpdateEducation(ctx context.Context, req *models.UpdateEducationRequest) (*models.EducationCV, error)
39
+ ListEducation(ctx context.Context, req *models.ListEducationRequest) ([]models.EducationCV, error)
40
+ GetEducation(ctx context.Context, req *models.GetEducationRequest) (*models.EducationCV, error)
41
+ DeleteEducation(ctx context.Context, req *models.DeleteEducationRequest) error
42
+
43
+ CreateJob(ctx context.Context, req *models.CreateJobRequest) (*models.JobCV, error)
44
+ UpdateJob(ctx context.Context, req *models.UpdateJobRequest) (*models.JobCV, error)
45
+ ListJob(ctx context.Context, req *models.ListJobRequest) ([]models.JobCV, error)
46
+ GetJob(ctx context.Context, req *models.GetJobRequest) (*models.JobCV, error)
47
+ DeleteJob(ctx context.Context, req *models.DeleteJobRequest) error
48
+
49
+ CreateAchievement(ctx context.Context, req *models.CreateAchievementRequest) (*models.AchievementCV, error)
50
+ UpdateAchievement(ctx context.Context, req *models.UpdateAchievementRequest) (*models.AchievementCV, error)
51
+ ListAchievement(ctx context.Context, req *models.ListAchievementRequest) ([]models.AchievementCV, error)
52
+ GetAchievement(ctx context.Context, req *models.GetAchievementRequest) (*models.AchievementCV, error)
53
+ DeleteAchievement(ctx context.Context, req *models.DeleteAchievementRequest) error
54
 
55
  UploadProfileImage(ctx context.Context, req *models.UploadProfileImageRequest) (*models.UploadProfileImageResponse, error)
56
+ GetProgressCV(ctx context.Context, req *models.GetProgressCVRequest) (*models.GetProgressCVResponse, error)
57
  }
58
 
59
  type cvService struct {
 
68
  }
69
  }
70
 
71
+ func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAccountDetailsRequest) (*models.AccountDetails, error) {
72
+ // notes
73
+ // jika ingin mengubah value wajib kirimkan field beserta value nya
74
  if err := validation.Validate(req); err != nil {
75
  return nil, response.HandleValidationError(err)
76
  }
 
87
  }
88
 
89
  accountDetails.AccountID = uint(req.AccountID)
90
+
91
+ utils.AssignIfNotNil(&accountDetails.FullName, req.FullName)
92
+ utils.AssignIfNotNil(&accountDetails.Gender, req.Gender)
93
+ utils.AssignIfNotNil(&accountDetails.DateOfBirth, req.DateOfBirth)
94
+ utils.AssignIfNotNil(&accountDetails.PlaceOfBirth, req.PlaceOfBirth)
95
+ utils.AssignIfNotNil(&accountDetails.Domicile, req.Domicile)
96
+ utils.AssignIfNotNil(&accountDetails.MaritalStatus, req.MaritalStatus)
97
+ utils.AssignIfNotNil(&accountDetails.LastEducation, req.LastEducation)
98
+ utils.AssignIfNotNil(&accountDetails.LastJob, req.LastJob)
99
 
100
  if req.PhoneNumber != nil {
101
  sanitizedPhone := validation.SanitizePhoneNumber(*req.PhoneNumber)
 
111
  return res, nil
112
  }
113
 
114
+ func (s *cvService) GetAccountDetails(ctx context.Context, req *models.GetAccountDetailsRequest) (*models.AccountDetails, error) {
115
+ res, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, req.AccountID)
116
  if err != nil {
117
+ return nil, response.HandleGormError(err, "Internal Server Error")
118
  }
119
  return res, nil
120
  }
121
 
122
+ func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *models.SavePersonalityAndPreferenceRequest) (*models.PersonalityAndPreferenceCV, error) {
123
  if err := validation.Validate(req); err != nil {
124
  return nil, response.HandleValidationError(err)
125
  }
 
136
  }
137
 
138
  personalityAndPreference.AccountID = req.AccountID
139
+
140
+ utils.AssignIfNotNil(&personalityAndPreference.PositiveTraits, req.PositiveTraits)
141
+ utils.AssignIfNotNil(&personalityAndPreference.NegativeTraits, req.NegativeTraits)
142
+ utils.AssignIfNotNil(&personalityAndPreference.Hobbies, req.Hobbies)
143
+ utils.AssignIfNotNil(&personalityAndPreference.LifeGoals, req.LifeGoals)
144
+ utils.AssignIfNotNil(&personalityAndPreference.DailyActivities, req.DailyActivities)
145
+ utils.AssignIfNotNil(&personalityAndPreference.LeisureActivities, req.LeisureActivities)
146
+ utils.AssignIfNotNil(&personalityAndPreference.Likes, req.Likes)
147
+ utils.AssignIfNotNil(&personalityAndPreference.Dislikes, req.Dislikes)
148
+ utils.AssignIfNotNil(&personalityAndPreference.StressHandling, req.StressHandling)
149
+ utils.AssignIfNotNil(&personalityAndPreference.AngerTriggers, req.AngerTriggers)
150
+ utils.AssignIfNotNil(&personalityAndPreference.FavoriteFoodAndDrinks, req.FavoriteFoodAndDrinks)
151
+ utils.AssignIfNotNil(&personalityAndPreference.CanCook, req.CanCook)
152
+ utils.AssignIfNotNil(&personalityAndPreference.TypesOfDishesCooked, req.TypesOfDishesCooked)
153
+ utils.AssignIfNotNil(&personalityAndPreference.MonthlyExpenses, req.MonthlyExpenses)
154
 
155
  res, err := s.cvRepository.SavePersonalityAndPreference(ctx, personalityAndPreference)
156
  if err != nil {
 
160
  return res, nil
161
  }
162
 
163
+ func (s *cvService) GetPersonalityAndPreference(ctx context.Context, req *models.GetPersonalityAndPreferenceRequest) (*models.PersonalityAndPreferenceCV, error) {
164
+ res, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, req.AccountID)
165
  if err != nil {
166
  return nil, response.HandleGormError(err, "Internal Server Error")
167
  }
 
169
  return res, nil
170
  }
171
 
172
+ func (s *cvService) CreateFamilyMember(ctx context.Context, req *models.CreateFamilyMemberRequest) (*models.FamilyMemberCV, error) {
173
  if err := validation.Validate(req); err != nil {
174
  return nil, response.HandleValidationError(err)
175
  }
 
188
  // Simpan ke repository
189
  res, err := s.cvRepository.SaveFamilyMember(ctx, familyMember)
190
  if err != nil {
191
+ return nil, response.HandleGormError(err, "Internal Server Error")
192
  }
193
 
194
  return res, nil
195
  }
196
 
197
+ func (s *cvService) ListFamilyMember(ctx context.Context, req *models.ListFamilyMemberRequest) ([]models.FamilyMemberCV, error) {
198
+ list, err := s.cvRepository.ListFamilyMember(ctx, req.AccountID)
199
  if err != nil {
200
+ return nil, response.HandleGormError(err, "Internal Server Error")
201
  }
202
  return list, nil
203
  }
204
 
205
+ func (s *cvService) GetFamilyMember(ctx context.Context, req *models.GetFamilyMemberRequest) (*models.FamilyMemberCV, error) {
206
+ res, err := s.cvRepository.GetFamilyMember(ctx, req.ID)
207
  if err != nil {
208
+ return nil, response.HandleGormError(err, "Internal Server Error")
209
  }
210
  return res, nil
211
  }
212
 
213
+ func (s *cvService) DeleteFamilyMember(ctx context.Context, req *models.DeleteFamilyMemberRequest) error {
214
+ err := s.cvRepository.DeleteFamilyMember(ctx, req.ID)
215
  if err != nil {
216
+ return response.HandleGormError(err, "Internal Server Error")
217
  }
218
  return nil
219
  }
220
 
221
+ func (s *cvService) UpdateFamilyMember(ctx context.Context, req *models.UpdateFamilyMemberRequest) (*models.FamilyMemberCV, error) {
222
  if err := validation.Validate(req); err != nil {
223
  return nil, response.HandleValidationError(err)
224
  }
225
 
226
+ existing, err := s.cvRepository.GetFamilyMember(ctx, req.ID)
227
  if err != nil {
228
+ return nil, response.HandleGormError(err, "Internal Server Error")
229
  }
230
 
231
+ utils.AssignIfNotNil(&existing.Role, req.Role)
232
+ utils.AssignIfNotNil(&existing.Status, req.Status)
233
+ utils.AssignIfNotNil(&existing.Religion, req.Religion)
234
+ utils.AssignIfNotNil(&existing.Job, req.Job)
235
+ utils.AssignIfNotNil(&existing.LastEducation, req.LastEducation)
236
+ utils.AssignIfNotNil(&existing.Age, req.Age)
237
 
238
  updated, err := s.cvRepository.SaveFamilyMember(ctx, existing)
239
  if err != nil {
240
+ return nil, response.HandleGormError(err, "Internal Server Error")
241
  }
242
 
243
  return updated, nil
244
  }
245
 
246
+ func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.SavePhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error) {
247
  if err := validation.Validate(req); err != nil {
248
  return nil, response.HandleValidationError(err)
249
  }
 
251
  // Cek apakah data sudah ada berdasarkan account_id
252
  existing, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, req.AccountID)
253
  if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
254
+ return nil, response.HandleGormError(err, "Internal Server Error")
255
  }
256
 
257
  // Jika belum ada, buat objek baru
 
261
 
262
  // Mapping field dari request
263
  existing.AccountID = req.AccountID
264
+
265
+ utils.AssignIfNotNil(&existing.HeightInCm, req.HeightInCm)
266
+ utils.AssignIfNotNil(&existing.WeightInKg, req.WeightInKg)
267
+ utils.AssignIfNotNil(&existing.BodyShape, req.BodyShape)
268
+ utils.AssignIfNotNil(&existing.SkinColor, req.SkinColor)
269
+ utils.AssignIfNotNil(&existing.HairType, req.HairType)
270
+ utils.AssignIfNotNil(&existing.MedicalHistory, req.MedicalHistory)
271
+ utils.AssignIfNotNil(&existing.PhysicalDisorder, req.PhysicalDisorder)
272
+ utils.AssignIfNotNil(&existing.PhysicalTraits, req.PhysicalTraits)
273
 
274
  // Simpan data
275
  res, err := s.cvRepository.SavePhysicalAndHealth(ctx, existing)
276
  if err != nil {
277
+ return nil, response.HandleGormError(err, "Internal Server Error")
278
  }
279
 
280
  return res, nil
281
  }
282
 
283
+ func (s *cvService) GetPhysicalAndHealth(ctx context.Context, req *models.GetPhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error) {
284
+ res, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, req.AccountID)
285
  if err != nil {
286
+ return nil, response.HandleGormError(err, "Internal Server Error")
287
  }
288
  return res, nil
289
  }
290
 
291
+ func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.SaveWorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error) {
292
  if err := validation.Validate(req); err != nil {
293
  return nil, response.HandleValidationError(err)
294
  }
 
296
  // Cek apakah data sudah ada berdasarkan account_id
297
  worshipAndReligiousUnderstanding, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, req.AccountID)
298
  if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
299
+ return nil, response.HandleGormError(err, "Internal Server Error")
300
  }
301
 
302
  // Jika belum ada, buat objek baru
 
306
 
307
  // Mapping field dari request
308
  worshipAndReligiousUnderstanding.AccountID = req.AccountID
309
+
310
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.ObligatoryPrayer, req.ObligatoryPrayer)
311
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.CongregationalPrayer, req.CongregationalPrayer)
312
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.TahajjudPrayer, req.TahajjudPrayer)
313
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.DhuhaPrayer, req.DhuhaPrayer)
314
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.QuranMemorization, req.QuranMemorization)
315
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.QuranReadingAbility, req.QuranReadingAbility)
316
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.DaudFasting, req.DaudFasting)
317
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.AyyamulBidhFasting, req.AyyamulBidhFasting)
318
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.HajjOrUmrah, req.HajjOrUmrah)
319
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.ListeningToMusic, req.ListeningToMusic)
320
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.OpinionOnIkhtilat, req.OpinionOnIkhtilat)
321
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.OpinionOnTouchingNonMahram, req.OpinionOnTouchingNonMahram)
322
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.OpinionOnVeil, req.OpinionOnVeil)
323
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.WeeklyReligiousStudies, req.WeeklyReligiousStudies)
324
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.FollowedUstadz, req.FollowedUstadz)
325
 
326
  // Simpan data
327
  res, err := s.cvRepository.SaveWorshipAndReligiousUnderstanding(ctx, worshipAndReligiousUnderstanding)
 
332
  return res, nil
333
  }
334
 
335
+ func (s *cvService) GetWorshipAndReligiousUnderstanding(ctx context.Context, req *models.GetWorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error) {
336
+ res, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, req.AccountID)
337
  if err != nil {
338
+ return nil, response.HandleGormError(err, "Internal Server Error")
339
  }
340
  return res, nil
341
  }
342
 
343
+ func (s *cvService) CreateEducation(ctx context.Context, req *models.CreateEducationRequest) (*models.EducationCV, error) {
344
  if err := validation.Validate(req); err != nil {
345
  return nil, response.HandleValidationError(err)
346
  }
 
356
 
357
  res, err := s.cvRepository.SaveEducation(ctx, edu)
358
  if err != nil {
359
+ return nil, response.HandleGormError(err, "Internal Server Error")
360
  }
361
 
362
  return res, nil
363
  }
364
 
365
+ func (s *cvService) UpdateEducation(ctx context.Context, req *models.UpdateEducationRequest) (*models.EducationCV, error) {
366
  if err := validation.Validate(req); err != nil {
367
  return nil, response.HandleValidationError(err)
368
  }
369
 
370
+ edu, err := s.cvRepository.GetEducation(ctx, req.ID)
371
  if err != nil {
372
+ return nil, response.HandleGormError(err, "Internal Server Error")
373
  }
374
 
375
+ utils.AssignIfNotNil(&edu.LastEducation, req.LastEducation)
376
+ utils.AssignIfNotNil(&edu.EducationInstitute, req.EducationInstitute)
377
+ utils.AssignIfNotNil(&edu.EducationMajor, req.EducationMajor)
378
+ utils.AssignIfNotNil(&edu.YearStart, req.YearStart)
379
+ utils.AssignIfNotNil(&edu.YearGraduate, req.YearGraduate)
380
 
381
  res, err := s.cvRepository.SaveEducation(ctx, edu)
382
  if err != nil {
383
+ return nil, response.HandleGormError(err, "Internal Server Error")
384
  }
385
  return res, nil
386
  }
387
 
388
+ func (s *cvService) ListEducation(ctx context.Context, req *models.ListEducationRequest) ([]models.EducationCV, error) {
389
+ return s.cvRepository.ListEducation(ctx, req.AccountID)
390
  }
391
 
392
+ func (s *cvService) GetEducation(ctx context.Context, req *models.GetEducationRequest) (*models.EducationCV, error) {
393
+ edu, err := s.cvRepository.GetEducation(ctx, req.ID)
394
  if err != nil {
395
+ return nil, response.HandleGormError(err, "Internal Server Error")
396
  }
397
  return edu, nil
398
  }
399
 
400
+ func (s *cvService) DeleteEducation(ctx context.Context, req *models.DeleteEducationRequest) error {
401
+ return s.cvRepository.DeleteEducation(ctx, req.ID)
402
  }
403
 
404
+ func (s *cvService) CreateJob(ctx context.Context, req *models.CreateJobRequest) (*models.JobCV, error) {
405
  if err := validation.Validate(req); err != nil {
406
  return nil, response.HandleValidationError(err)
407
  }
 
416
  }
417
  res, err := s.cvRepository.SaveJob(ctx, job)
418
  if err != nil {
419
+ return nil, response.HandleGormError(err, "Internal Server Error")
420
  }
421
  return res, nil
422
  }
423
 
424
+ func (s *cvService) UpdateJob(ctx context.Context, req *models.UpdateJobRequest) (*models.JobCV, error) {
425
  if err := validation.Validate(req); err != nil {
426
  return nil, response.HandleValidationError(err)
427
  }
428
 
429
+ job, err := s.cvRepository.GetJob(ctx, req.ID)
430
  if err != nil {
431
+ return nil, response.HandleGormError(err, "Internal Server Error")
432
  }
433
 
434
+ utils.AssignIfNotNil(&job.InstitutionName, req.InstitutionName)
435
+ utils.AssignIfNotNil(&job.CurrentJob, req.CurrentJob)
436
+ utils.AssignIfNotNil(&job.YearStartedWorking, req.YearStartedWorking)
437
+ utils.AssignIfNotNil(&job.MonthlyIncome, req.MonthlyIncome)
438
+ utils.AssignIfNotNil(&job.IncomeSources, req.IncomeSources)
439
 
440
  res, err := s.cvRepository.SaveJob(ctx, job)
441
  if err != nil {
442
+ return nil, response.HandleGormError(err, "Internal Server Error")
443
  }
444
  return res, nil
445
  }
446
 
447
+ func (s *cvService) ListJob(ctx context.Context, req *models.ListJobRequest) ([]models.JobCV, error) {
448
+ return s.cvRepository.ListJob(ctx, req.AccountID)
449
  }
450
 
451
+ func (s *cvService) GetJob(ctx context.Context, req *models.GetJobRequest) (*models.JobCV, error) {
452
+ job, err := s.cvRepository.GetJob(ctx, req.ID)
453
  if err != nil {
454
+ return nil, response.HandleGormError(err, "Internal Server Error")
455
  }
456
  return job, nil
457
  }
458
 
459
+ func (s *cvService) DeleteJob(ctx context.Context, req *models.DeleteJobRequest) error {
460
+ return s.cvRepository.DeleteJob(ctx, req.ID)
461
  }
462
 
463
+ func (s *cvService) CreateAchievement(ctx context.Context, req *models.CreateAchievementRequest) (*models.AchievementCV, error) {
464
  ach := &models.AchievementCV{
465
  AccountID: req.AccountID,
466
  AchievementOrAward: req.AchievementOrAward,
467
  }
468
  res, err := s.cvRepository.SaveAchievement(ctx, ach)
469
  if err != nil {
470
+ return nil, response.HandleGormError(err, "Internal Server Error")
471
  }
472
 
473
  return res, nil
474
  }
475
 
476
+ func (s *cvService) UpdateAchievement(ctx context.Context, req *models.UpdateAchievementRequest) (*models.AchievementCV, error) {
477
+ ach, err := s.cvRepository.GetAchievement(ctx, req.ID)
478
  if err != nil {
479
+ return nil, response.HandleGormError(err, "Internal Server Error")
480
  }
481
 
482
  ach.AchievementOrAward = req.AchievementOrAward
483
 
484
  res, err := s.cvRepository.SaveAchievement(ctx, ach)
485
  if err != nil {
486
+ return nil, response.HandleGormError(err, "Internal Server Error")
487
  }
488
 
489
  return res, nil
490
  }
491
 
492
+ func (s *cvService) ListAchievement(ctx context.Context, req *models.ListAchievementRequest) ([]models.AchievementCV, error) {
493
+ return s.cvRepository.ListAchievement(ctx, req.AccountID)
494
  }
495
 
496
+ func (s *cvService) GetAchievement(ctx context.Context, req *models.GetAchievementRequest) (*models.AchievementCV, error) {
497
+ ach, err := s.cvRepository.GetAchievement(ctx, req.ID)
498
  if err != nil {
499
+ return nil, response.HandleGormError(err, "Internal Server Error")
500
  }
501
  return ach, nil
502
  }
503
 
504
+ func (s *cvService) DeleteAchievement(ctx context.Context, req *models.DeleteAchievementRequest) error {
505
+ return s.cvRepository.DeleteAchievement(ctx, req.ID)
506
  }
507
 
508
  func (s *cvService) UploadProfileImage(ctx context.Context, req *models.UploadProfileImageRequest) (*models.UploadProfileImageResponse, error) {
 
576
  }, nil
577
  }
578
 
579
+ func (s *cvService) GetProgressCV(ctx context.Context, req *models.GetProgressCVRequest) (*models.GetProgressCVResponse, error) {
580
+ accountDetails, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, req.AccountID)
581
  if err != nil {
582
  if !errors.Is(err, gorm.ErrRecordNotFound) {
583
  return nil, response.HandleGormError(err, "Internal Server Error")
 
585
  accountDetails = &models.AccountDetails{}
586
  }
587
 
588
+ personalityAndPreferenceCV, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, req.AccountID)
589
  if err != nil {
590
  if !errors.Is(err, gorm.ErrRecordNotFound) {
591
  return nil, response.HandleGormError(err, "Internal Server Error")
 
593
  personalityAndPreferenceCV = &models.PersonalityAndPreferenceCV{}
594
  }
595
 
596
+ physicalAndHealthCV, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, req.AccountID)
597
  if err != nil {
598
  if !errors.Is(err, gorm.ErrRecordNotFound) {
599
  return nil, response.HandleGormError(err, "Internal Server Error")
 
601
  physicalAndHealthCV = &models.PhysicalAndHealthCV{}
602
  }
603
 
604
+ worshipAndReligiousUnderstandingCV, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, req.AccountID)
605
  if err != nil {
606
  if !errors.Is(err, gorm.ErrRecordNotFound) {
607
  return nil, response.HandleGormError(err, "Internal Server Error")
 
609
  worshipAndReligiousUnderstandingCV = &models.WorshipAndReligiousUnderstandingCV{}
610
  }
611
 
612
+ educationCV, err := s.cvRepository.ListEducation(ctx, req.AccountID)
613
  if err != nil {
614
  return nil, response.HandleGormError(err, "Internal Server Error")
615
  }
616
 
617
+ familyMemberCV, err := s.cvRepository.ListFamilyMember(ctx, req.AccountID)
618
  if err != nil {
619
  return nil, response.HandleGormError(err, "Internal Server Error")
620
  }
621
 
622
+ jobCV, err := s.cvRepository.ListJob(ctx, req.AccountID)
623
  if err != nil {
624
  return nil, response.HandleGormError(err, "Internal Server Error")
625
  }
626
 
627
+ achievementCV, err := s.cvRepository.ListAchievement(ctx, req.AccountID)
628
  if err != nil {
629
  return nil, response.HandleGormError(err, "Internal Server Error")
630
  }
 
652
  familyMemberCV int,
653
  jobCV int,
654
  achievementCV int,
655
+ ) *models.GetProgressCVResponse {
656
 
657
+ // fullIfPresent mengembalikan 100 jika data lebih dari 0, jika tidak maka mengembalikan 0
658
  fullIfPresent := func(data int) float64 {
659
  if data > 0 {
660
  return 100
 
674
 
675
  overallProgress := (accountDetailsPercentage + personalityAndPreferenceCVPercentage + physicalAndHealthCVPercentage + worshipAndReligiousUnderstandingCVPercentage + educationCVPercentage + familyMemberCVPercentage + jobCVPercentage + achievementCVPercentage) / 8
676
 
677
+ return &models.GetProgressCVResponse{
678
  AccountDetailsProgress: math.Round(accountDetailsPercentage*100) / 100,
679
  PersonalityAndPreferenceCVProgress: math.Round(personalityAndPreferenceCVPercentage*100) / 100,
680
  FamilyMemberCVProgress: math.Round(familyMemberCVPercentage*100) / 100,
space/space/repositories/quiz_repository.go CHANGED
@@ -129,6 +129,6 @@ func CountUserAttemptScore(attemptId uint) Repository[models.QuizAttempt, models
129
  )
130
  repo.Transaction.Model(&repo.Constructor).Raw("SELECT quiz_attempt_id,COUNT(*) AS total_questions,SUM(CASE WHEN is_correct = true THEN 1 ELSE 0 END) AS correct_answers,CAST(SUM(CASE WHEN is_correct = true THEN 1 ELSE 0 END) AS FLOAT) / COUNT(*) AS average_score FROM user_answers WHERE quiz_attempt_id = ? GROUP BY quiz_attempt_id", attemptId).Scan(&repo.Result)
131
  repo.RowsError = repo.Transaction.Error
132
- repo.NoRecord = true
133
  return *repo
134
  }
 
129
  )
130
  repo.Transaction.Model(&repo.Constructor).Raw("SELECT quiz_attempt_id,COUNT(*) AS total_questions,SUM(CASE WHEN is_correct = true THEN 1 ELSE 0 END) AS correct_answers,CAST(SUM(CASE WHEN is_correct = true THEN 1 ELSE 0 END) AS FLOAT) / COUNT(*) AS average_score FROM user_answers WHERE quiz_attempt_id = ? GROUP BY quiz_attempt_id", attemptId).Scan(&repo.Result)
131
  repo.RowsError = repo.Transaction.Error
132
+ repo.NoRecord = false
133
  return *repo
134
  }
space/space/services/academy_quiz_answer_service.go CHANGED
@@ -53,4 +53,7 @@ func (s *AnswerQuizService) Update(userID uint, questionNo int, answer int) {
53
  }
54
  return
55
  })
 
 
 
56
  }
 
53
  }
54
  return
55
  })
56
+ s.Exception = QuizAttemptService.Exception
57
+ s.Error = errors.Join(s.Error, QuizAttemptService.Error)
58
+ return
59
  }
space/space/services/academy_quiz_question_service.go CHANGED
@@ -1,6 +1,8 @@
1
  package services
2
 
3
  import (
 
 
4
  "api.qobiltu.id/models"
5
  "api.qobiltu.id/repositories"
6
  )
@@ -39,4 +41,8 @@ func (s *QuestionQuizService) Retrieve(userID uint, questionNo int) {
39
  }
40
  return
41
  })
 
 
 
 
42
  }
 
1
  package services
2
 
3
  import (
4
+ "errors"
5
+
6
  "api.qobiltu.id/models"
7
  "api.qobiltu.id/repositories"
8
  )
 
41
  }
42
  return
43
  })
44
+
45
+ s.Exception = QuizAttemptService.Exception
46
+ s.Error = errors.Join(s.Error, QuizAttemptService.Error)
47
+ return
48
  }
space/space/services/academy_quiz_service.go CHANGED
@@ -2,6 +2,7 @@ package services
2
 
3
  import (
4
  "errors"
 
5
  "time"
6
 
7
  "api.qobiltu.id/models"
@@ -52,7 +53,7 @@ func CheckUserAttemptLimit(s *AttemptQuizService, allAttemptsRepo repositories.R
52
 
53
  func CheckUserLatestAttempt(s *AttemptQuizService, latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz], userID uint, next func()) {
54
  currentTime := time.Now()
55
- if currentTime.After(latestAttemptRepo.Result.DueAt) {
56
  s.Exception.IsTimeOut = true
57
  s.Exception.Message = "Your latest attempt is timeout!"
58
  // Submit
@@ -127,15 +128,24 @@ func Attempt(s *AttemptQuizService, quizRepo repositories.Repository[models.Quiz
127
  func (s *AttemptQuizService) Create(userID uint) {
128
  s.Validate(userID, func(latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz]) {
129
  if latestAttemptRepo.Result.FinishedAt != nil {
130
- Attempt(s, quizRepo, userID)
 
 
 
 
 
131
  } else {
132
  s.Result = latestAttemptRepo.Result
 
133
  }
134
  })
135
  }
136
 
137
  func (s *SubmitQuizService) Create() {
 
 
138
  quizAttemptRepo := repositories.GetAttemptByIdandUserId(s.Constructor.ID, s.Constructor.AccountID)
 
139
  if quizAttemptRepo.NoRecord {
140
  s.Exception.DataNotFound = true
141
  s.Exception.Message = "There is no quiz attempt with given user!"
@@ -145,7 +155,7 @@ func (s *SubmitQuizService) Create() {
145
  countScoreRepo := repositories.CountUserAttemptScore(s.Constructor.ID)
146
  if countScoreRepo.NoRecord {
147
  s.Exception.DataNotFound = true
148
- s.Exception.Message = "There is no quiz attempt with given user!"
149
  }
150
  s.Error = errors.Join(s.Error, countScoreRepo.RowsError)
151
 
 
2
 
3
  import (
4
  "errors"
5
+ "fmt"
6
  "time"
7
 
8
  "api.qobiltu.id/models"
 
53
 
54
  func CheckUserLatestAttempt(s *AttemptQuizService, latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz], userID uint, next func()) {
55
  currentTime := time.Now()
56
+ if currentTime.After(latestAttemptRepo.Result.DueAt) && latestAttemptRepo.Result.FinishedAt == nil {
57
  s.Exception.IsTimeOut = true
58
  s.Exception.Message = "Your latest attempt is timeout!"
59
  // Submit
 
128
  func (s *AttemptQuizService) Create(userID uint) {
129
  s.Validate(userID, func(latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz]) {
130
  if latestAttemptRepo.Result.FinishedAt != nil {
131
+ if latestAttemptRepo.Result.Score < float64(quizRepo.Result.MinScore) {
132
+ Attempt(s, quizRepo, userID)
133
+ } else {
134
+ s.Result = latestAttemptRepo.Result
135
+ return
136
+ }
137
  } else {
138
  s.Result = latestAttemptRepo.Result
139
+ return
140
  }
141
  })
142
  }
143
 
144
  func (s *SubmitQuizService) Create() {
145
+ fmt.Println(s.Constructor.ID)
146
+ fmt.Println(s.Constructor.AccountID)
147
  quizAttemptRepo := repositories.GetAttemptByIdandUserId(s.Constructor.ID, s.Constructor.AccountID)
148
+ fmt.Println(quizAttemptRepo.Result)
149
  if quizAttemptRepo.NoRecord {
150
  s.Exception.DataNotFound = true
151
  s.Exception.Message = "There is no quiz attempt with given user!"
 
155
  countScoreRepo := repositories.CountUserAttemptScore(s.Constructor.ID)
156
  if countScoreRepo.NoRecord {
157
  s.Exception.DataNotFound = true
158
+ s.Exception.Message = "There is no quiz attempt result with given user!"
159
  }
160
  s.Error = errors.Join(s.Error, countScoreRepo.RowsError)
161
 
space/space/space/controller/quiz/result_quiz_controller.go ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package controller
2
+
3
+ import (
4
+ "strconv"
5
+
6
+ "api.qobiltu.id/controller"
7
+ "api.qobiltu.id/models"
8
+ "api.qobiltu.id/services"
9
+ "github.com/gin-gonic/gin"
10
+ )
11
+
12
+ func Result(c *gin.Context) {
13
+ quizResult := services.QuizResultService{}
14
+ quizResultController := controller.Controller[any, models.QuizAttempt, []models.QuizResultResponse]{
15
+ Service: &quizResult.Service,
16
+ }
17
+ quizResultController.HeaderParse(c, func() {
18
+ academyId, _ := strconv.Atoi(c.Param("attempt_id"))
19
+ quizResult.Constructor.AccountID = uint(quizResultController.AccountData.UserID)
20
+ quizResult.Constructor.ID = uint(academyId) | 0
21
+ quizResult.Retrieve()
22
+ quizResultController.Response(c)
23
+ })
24
+ }
space/space/space/controller/quiz/submit_quiz_controller.go CHANGED
@@ -15,9 +15,10 @@ func Submit(c *gin.Context) {
15
  Service: &submitQuiz.Service,
16
  }
17
  submitQuizController.HeaderParse(c, func() {
18
- quizId, _ := strconv.Atoi(c.Param("attempt_id"))
19
- submitQuizController.Service.Constructor.ID = uint(quizId)
20
- submitQuiz.Create(submitQuizController.AccountData.UserID)
 
21
  submitQuizController.Response(c)
22
  })
23
  }
 
15
  Service: &submitQuiz.Service,
16
  }
17
  submitQuizController.HeaderParse(c, func() {
18
+ attemptId, _ := strconv.Atoi(c.Param("attempt_id"))
19
+ submitQuizController.Service.Constructor.ID = uint(attemptId)
20
+ submitQuizController.Service.Constructor.AccountID = uint(submitQuizController.AccountData.UserID)
21
+ submitQuiz.Create()
22
  submitQuizController.Response(c)
23
  })
24
  }
space/space/space/models/database_orm_model.go CHANGED
@@ -152,7 +152,8 @@ type Question struct {
152
  QuizID uint `json:"quiz_id"`
153
  Content string `json:"content"`
154
  Order int `json:"order"`
155
- CorrectAnswer uint `json:"-"`
 
156
  }
157
  type Quiz struct {
158
  ID uint `gorm:"primaryKey" json:"id"`
@@ -160,7 +161,7 @@ type Quiz struct {
160
  Slug string `json:"slug" gorm:"uniqueIndex" `
161
  Title string `json:"title"`
162
  Description string `json:"description"`
163
- TotalQuestions string `json:"total_questions"`
164
  AttemptLimit int `json:"attempt_limit"`
165
  TimeLimit int `json:"time_limit"`
166
  MinScore int `json:"min_score"`
@@ -274,7 +275,7 @@ type (
274
  ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
275
  AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
276
  Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
277
- LastEducation *string `gorm:"column:last_education" json:"last_education" validate:""` // pendidikan terakhir
278
  EducationInstitute *string `gorm:"column:education_institute" json:"education_institute"` // institusi pendidikan
279
  EducationMajor *string `gorm:"column:education_major" json:"education_major"` // jurusan pendidikan
280
  YearStart *int `gorm:"column:year_start" json:"year_start"` // tahun masuk
 
152
  QuizID uint `json:"quiz_id"`
153
  Content string `json:"content"`
154
  Order int `json:"order"`
155
+ CorrectAnswer uint `json:"corrent_answer"`
156
+ Review string `json:"reviews"`
157
  }
158
  type Quiz struct {
159
  ID uint `gorm:"primaryKey" json:"id"`
 
161
  Slug string `json:"slug" gorm:"uniqueIndex" `
162
  Title string `json:"title"`
163
  Description string `json:"description"`
164
+ TotalQuestions int `json:"total_questions"`
165
  AttemptLimit int `json:"attempt_limit"`
166
  TimeLimit int `json:"time_limit"`
167
  MinScore int `json:"min_score"`
 
275
  ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
276
  AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
277
  Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
278
+ LastEducation *string `gorm:"column:last_education" json:"last_education"` // pendidikan terakhir
279
  EducationInstitute *string `gorm:"column:education_institute" json:"education_institute"` // institusi pendidikan
280
  EducationMajor *string `gorm:"column:education_major" json:"education_major"` // jurusan pendidikan
281
  YearStart *int `gorm:"column:year_start" json:"year_start"` // tahun masuk
space/space/space/repositories/academy_repository.go CHANGED
@@ -93,3 +93,13 @@ func GetAcademyByID(id uint) Repository[models.Academy, models.Academy] {
93
  )
94
  return *repo
95
  }
 
 
 
 
 
 
 
 
 
 
 
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)
space/space/space/repositories/quiz_repository.go CHANGED
@@ -63,6 +63,18 @@ func GetAttemptByIdandUserId(attemptId uint, userId uint) Repository[models.Quiz
63
  return *repo
64
  }
65
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  func CreateAttempt(quizAttempt models.QuizAttempt) Repository[models.QuizAttempt, models.QuizAttempt] {
67
  repo := Construct[models.QuizAttempt, models.QuizAttempt](
68
  quizAttempt,
 
63
  return *repo
64
  }
65
 
66
+ func GetAttemptByUserId(userId uint) Repository[models.QuizAttempt, []models.QuizAttempt] {
67
+ repo := Construct[models.QuizAttempt, []models.QuizAttempt](
68
+ models.QuizAttempt{
69
+ AccountID: userId,
70
+ },
71
+ )
72
+ repo.Transactions(
73
+ WhereGivenConstructor[models.QuizAttempt, []models.QuizAttempt],
74
+ Find[models.QuizAttempt, []models.QuizAttempt],
75
+ )
76
+ return *repo
77
+ }
78
  func CreateAttempt(quizAttempt models.QuizAttempt) Repository[models.QuizAttempt, models.QuizAttempt] {
79
  repo := Construct[models.QuizAttempt, models.QuizAttempt](
80
  quizAttempt,
space/space/space/router/quiz_route.go CHANGED
@@ -13,6 +13,8 @@ func QuizRoute(router *gin.Engine) {
13
  routerGroup.POST("/:academy_id/:quiz_id/attempt", middleware.AuthUser, QuizController.Attempt)
14
  routerGroup.GET("/:academy_id/:quiz_id/question", middleware.AuthUser, QuizController.Question)
15
  routerGroup.PUT("/:academy_id/:quiz_id/choose-answer", middleware.AuthUser, QuizController.Answer)
 
 
16
  routerGroup.POST("/submit-attempt/:attempt_id", middleware.AuthUser, QuizController.Submit)
17
  }
18
  }
 
13
  routerGroup.POST("/:academy_id/:quiz_id/attempt", middleware.AuthUser, QuizController.Attempt)
14
  routerGroup.GET("/:academy_id/:quiz_id/question", middleware.AuthUser, QuizController.Question)
15
  routerGroup.PUT("/:academy_id/:quiz_id/choose-answer", middleware.AuthUser, QuizController.Answer)
16
+ routerGroup.GET("result/:attempt_id", middleware.AuthUser, QuizController.Result)
17
+ routerGroup.GET("result/", middleware.AuthUser, QuizController.Result)
18
  routerGroup.POST("/submit-attempt/:attempt_id", middleware.AuthUser, QuizController.Submit)
19
  }
20
  }
space/space/space/services/academy_quiz_service.go CHANGED
@@ -20,6 +20,10 @@ type QuizListService struct {
20
  Service[models.Academy, []models.Quiz]
21
  }
22
 
 
 
 
 
23
  func AuthorizeQuizwithAcademy(s *AttemptQuizService, next func()) {
24
  academyRepo := repositories.GetAcademyByID(s.Constructor.AcademyID)
25
  s.Error = academyRepo.RowsError
@@ -130,8 +134,8 @@ func (s *AttemptQuizService) Create(userID uint) {
130
  })
131
  }
132
 
133
- func (s *SubmitQuizService) Create(userID uint) {
134
- quizAttemptRepo := repositories.GetAttemptByIdandUserId(s.Constructor.ID, userID)
135
  if quizAttemptRepo.NoRecord {
136
  s.Exception.DataNotFound = true
137
  s.Exception.Message = "There is no quiz attempt with given user!"
@@ -139,6 +143,10 @@ func (s *SubmitQuizService) Create(userID uint) {
139
  }
140
  s.Error = errors.Join(s.Error, quizAttemptRepo.RowsError)
141
  countScoreRepo := repositories.CountUserAttemptScore(s.Constructor.ID)
 
 
 
 
142
  s.Error = errors.Join(s.Error, countScoreRepo.RowsError)
143
 
144
  if quizAttemptRepo.Result.FinishedAt == nil {
@@ -168,3 +176,57 @@ func (s *QuizListService) Retrieve() {
168
  s.Result = quizRepo.Result
169
  return
170
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  Service[models.Academy, []models.Quiz]
21
  }
22
 
23
+ type QuizResultService struct {
24
+ Service[models.QuizAttempt, []models.QuizResultResponse]
25
+ }
26
+
27
  func AuthorizeQuizwithAcademy(s *AttemptQuizService, next func()) {
28
  academyRepo := repositories.GetAcademyByID(s.Constructor.AcademyID)
29
  s.Error = academyRepo.RowsError
 
134
  })
135
  }
136
 
137
+ func (s *SubmitQuizService) Create() {
138
+ quizAttemptRepo := repositories.GetAttemptByIdandUserId(s.Constructor.ID, s.Constructor.AccountID)
139
  if quizAttemptRepo.NoRecord {
140
  s.Exception.DataNotFound = true
141
  s.Exception.Message = "There is no quiz attempt with given user!"
 
143
  }
144
  s.Error = errors.Join(s.Error, quizAttemptRepo.RowsError)
145
  countScoreRepo := repositories.CountUserAttemptScore(s.Constructor.ID)
146
+ if countScoreRepo.NoRecord {
147
+ s.Exception.DataNotFound = true
148
+ s.Exception.Message = "There is no quiz attempt with given user!"
149
+ }
150
  s.Error = errors.Join(s.Error, countScoreRepo.RowsError)
151
 
152
  if quizAttemptRepo.Result.FinishedAt == nil {
 
176
  s.Result = quizRepo.Result
177
  return
178
  }
179
+
180
+ func (s *QuizResultService) Retrieve() {
181
+ if s.Constructor.ID != 0 {
182
+ attemptRepo := repositories.GetAttemptByIdandUserId(s.Constructor.ID, s.Constructor.AccountID)
183
+ s.Error = attemptRepo.RowsError
184
+ if attemptRepo.NoRecord {
185
+ s.Exception.DataNotFound = true
186
+ s.Exception.Message = "There is no attempt with given user id"
187
+ return
188
+ }
189
+ if attemptRepo.Result.FinishedAt == nil {
190
+ s.Exception.Forbidden = true
191
+ s.Exception.Message = "You have to submit the exam first to see the result!"
192
+ return
193
+ }
194
+ countScoreRepo := repositories.CountUserAttemptScore(s.Constructor.ID)
195
+ s.Error = errors.Join(s.Error, countScoreRepo.RowsError)
196
+ if countScoreRepo.NoRecord {
197
+ s.Exception.DataNotFound = true
198
+ s.Exception.Message = "There is no quiz attempt with given user!"
199
+ }
200
+ s.Result = []models.QuizResultResponse{
201
+ models.QuizResultResponse{
202
+ QuizAttempt: attemptRepo.Result,
203
+ Result: countScoreRepo.Result,
204
+ },
205
+ }
206
+ return
207
+ } else {
208
+ allAttemptsRepo := repositories.GetAttemptByUserId(s.Constructor.AccountID)
209
+ if allAttemptsRepo.NoRecord {
210
+ s.Exception.DataNotFound = true
211
+ s.Exception.Message = "There is no attempt with given user id!"
212
+ return
213
+ }
214
+ s.Error = allAttemptsRepo.RowsError
215
+ var arrResult []models.QuizResultResponse
216
+ for _, attempt := range allAttemptsRepo.Result {
217
+ if attempt.FinishedAt != nil {
218
+ countScoreRepo := repositories.CountUserAttemptScore(attempt.ID)
219
+ s.Error = errors.Join(s.Error, countScoreRepo.RowsError)
220
+ if countScoreRepo.NoRecord {
221
+ continue
222
+ }
223
+ arrResult = append(arrResult, models.QuizResultResponse{
224
+ QuizAttempt: attempt,
225
+ Result: countScoreRepo.Result,
226
+ })
227
+ }
228
+ }
229
+ s.Result = arrResult
230
+ return
231
+ }
232
+ }
space/space/space/services/academy_service.go CHANGED
@@ -25,6 +25,10 @@ type AcademyContentService struct {
25
  Service[models.AcademyMaterial, models.AcademyContent]
26
  }
27
 
 
 
 
 
28
  func castAcademyMaterials(academyId uint) []models.AcademyMaterialResponse {
29
  var ArrMaterials []models.AcademyMaterialResponse
30
  academyMaterialsRepo := repositories.GetAllAcademyMaterialsByAcademyID(academyId)
@@ -135,3 +139,12 @@ func (s *AcademyContentService) Retrieve() {
135
  }
136
  s.Result = academyContentRepo.Result
137
  }
 
 
 
 
 
 
 
 
 
 
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)
 
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
+ }
space/space/space/space/.env.example CHANGED
@@ -1,9 +1,26 @@
1
- DB_HOST =
2
- DB_USER =
3
- DB_PASSWORD =
4
- DB_PORT =
5
- DB_NAME =
6
- SALT =
7
- HOST_ADDRESS =
8
- HOST_PORT =
9
- LOG_PATH = logs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ APP_URL=https://api.qobiltu.id
2
+ ENV=production
3
+
4
+ DB_HOST=db
5
+ DB_USER=qobiltu
6
+ DB_PASSWORD=
7
+ DB_PORT=
8
+ DB_NAME=
9
+ SALT=
10
+ HOST_ADDRESS=
11
+ HOST_PORT=
12
+ LOG_PATH=logs
13
+
14
+ SMTP_SENDER_EMAIL=
15
+ SMTP_SENDER_PASSWORD=
16
+ SMTP_HOST=
17
+ SMTP_PORT=
18
+ EMAIL_VERIFICATION_DURATION=
19
+
20
+ REDIS_HOST=redis
21
+ REDIS_PORT=6379
22
+ REDIS_PASSWORD=
23
+ REDIS_DB=0
24
+ REDIS_MIN_IDLE_CONNS=10
25
+ REDIS_POOL_SIZE=10
26
+ REDIS_POOL_TIMEOUT=30s
space/space/space/space/space/pkg/validation/custom_rules.go CHANGED
@@ -32,7 +32,7 @@ var inMemoryOptions = map[string][]string{
32
  "body_shape": {"Ideal", "Kurus", "Berisi", "Gemuk"},
33
  "skin_color": {"Putih", "Kuning Langsat", "Sawo Matang"},
34
  "hair_type": {"Lurus", "Bergelombang", "Keriting"},
35
- "frequently": {"Selalu", "Sering", "Kadang", "Jarang"},
36
  "quran_reading_ability": {"Lancar", "Menengah", "Perlu Bimbingan"},
37
  }
38
 
 
32
  "body_shape": {"Ideal", "Kurus", "Berisi", "Gemuk"},
33
  "skin_color": {"Putih", "Kuning Langsat", "Sawo Matang"},
34
  "hair_type": {"Lurus", "Bergelombang", "Keriting"},
35
+ "frequently": {"Selalu", "Sering", "Kadang", "Jarang", "Tidak Pernah"},
36
  "quran_reading_ability": {"Lancar", "Menengah", "Perlu Bimbingan"},
37
  }
38
 
space/space/space/space/space/space/services/cv_service.go CHANGED
@@ -3,7 +3,6 @@ package services
3
  import (
4
  "context"
5
  "errors"
6
- "fmt"
7
  "math"
8
  "strconv"
9
 
@@ -648,26 +647,25 @@ func calculateProgress(
648
  achievementCV int,
649
  ) *models.ProgressResponse {
650
 
651
- exists := func(data int) float64 {
 
652
  if data > 0 {
653
  return 100
654
  }
655
  return 0
656
  }
657
 
658
- fmt.Println("accountDetails", accountDetails.GetFilledFields())
659
- fmt.Println("accountDetailsTotal", accountDetails.TotalFields())
660
-
661
  accountDetailsPercentage := float64(len(accountDetails.GetFilledFields())) / float64(accountDetails.TotalFields()) * 100
662
  personalityAndPreferenceCVPercentage := float64(len(personalityAndPreferenceCV.GetFilledFields())) / float64(personalityAndPreferenceCV.TotalFields()) * 100
663
  physicalAndHealthCVPercentage := float64(len(physicalAndHealthCV.GetFilledFields())) / float64(physicalAndHealthCV.TotalFields()) * 100
664
  worshipAndReligiousUnderstandingCVPercentage := float64(len(worshipAndReligiousUnderstandingCV.GetFilledFields())) / float64(worshipAndReligiousUnderstandingCV.TotalFields()) * 100
665
- edicationCVPercentage := exists(educationCV)
666
- familyMemberCVPercentage := exists(familyMemberCV)
667
- jobCVPercentage := exists(jobCV)
668
- achievementCVPercentage := exists(achievementCV)
669
 
670
- overallProgress := (accountDetailsPercentage + personalityAndPreferenceCVPercentage + physicalAndHealthCVPercentage + worshipAndReligiousUnderstandingCVPercentage + edicationCVPercentage + familyMemberCVPercentage + jobCVPercentage + achievementCVPercentage) / 8
 
 
 
 
 
671
 
672
  return &models.ProgressResponse{
673
  AccountDetailsProgress: math.Round(accountDetailsPercentage*100) / 100,
@@ -675,9 +673,9 @@ func calculateProgress(
675
  FamilyMemberCVProgress: math.Round(familyMemberCVPercentage*100) / 100,
676
  PhysicalAndHealthCVProgress: math.Round(physicalAndHealthCVPercentage*100) / 100,
677
  WorshipAndReligiousUnderstandingCVProgress: math.Round(worshipAndReligiousUnderstandingCVPercentage*100) / 100,
678
- EducationCVProgress: math.Round(edicationCVPercentage*100) / 100,
679
- JobCVProgress: jobCVPercentage,
680
- AchievementCVProgress: achievementCVPercentage,
681
  TotalProgress: math.Round(overallProgress*100) / 100,
682
  }
683
  }
 
3
  import (
4
  "context"
5
  "errors"
 
6
  "math"
7
  "strconv"
8
 
 
647
  achievementCV int,
648
  ) *models.ProgressResponse {
649
 
650
+ // fullIfPresent returns 100 if the data is greater than 0, otherwise returns 0
651
+ fullIfPresent := func(data int) float64 {
652
  if data > 0 {
653
  return 100
654
  }
655
  return 0
656
  }
657
 
 
 
 
658
  accountDetailsPercentage := float64(len(accountDetails.GetFilledFields())) / float64(accountDetails.TotalFields()) * 100
659
  personalityAndPreferenceCVPercentage := float64(len(personalityAndPreferenceCV.GetFilledFields())) / float64(personalityAndPreferenceCV.TotalFields()) * 100
660
  physicalAndHealthCVPercentage := float64(len(physicalAndHealthCV.GetFilledFields())) / float64(physicalAndHealthCV.TotalFields()) * 100
661
  worshipAndReligiousUnderstandingCVPercentage := float64(len(worshipAndReligiousUnderstandingCV.GetFilledFields())) / float64(worshipAndReligiousUnderstandingCV.TotalFields()) * 100
 
 
 
 
662
 
663
+ educationCVPercentage := fullIfPresent(educationCV)
664
+ familyMemberCVPercentage := fullIfPresent(familyMemberCV)
665
+ jobCVPercentage := fullIfPresent(jobCV)
666
+ achievementCVPercentage := fullIfPresent(achievementCV)
667
+
668
+ overallProgress := (accountDetailsPercentage + personalityAndPreferenceCVPercentage + physicalAndHealthCVPercentage + worshipAndReligiousUnderstandingCVPercentage + educationCVPercentage + familyMemberCVPercentage + jobCVPercentage + achievementCVPercentage) / 8
669
 
670
  return &models.ProgressResponse{
671
  AccountDetailsProgress: math.Round(accountDetailsPercentage*100) / 100,
 
673
  FamilyMemberCVProgress: math.Round(familyMemberCVPercentage*100) / 100,
674
  PhysicalAndHealthCVProgress: math.Round(physicalAndHealthCVPercentage*100) / 100,
675
  WorshipAndReligiousUnderstandingCVProgress: math.Round(worshipAndReligiousUnderstandingCVPercentage*100) / 100,
676
+ EducationCVProgress: math.Round(educationCVPercentage*100) / 100,
677
+ JobCVProgress: math.Round(jobCVPercentage*100) / 100,
678
+ AchievementCVProgress: math.Round(achievementCVPercentage*100) / 100,
679
  TotalProgress: math.Round(overallProgress*100) / 100,
680
  }
681
  }
space/space/space/space/space/space/space/controller/cv/cv_controller.go CHANGED
@@ -1,13 +1,14 @@
1
  package cv_controller
2
 
3
  import (
 
 
 
4
  "api.qobiltu.id/middleware"
5
  "api.qobiltu.id/models"
6
  "api.qobiltu.id/response"
7
  "api.qobiltu.id/services"
8
  "github.com/gin-gonic/gin"
9
- "net/http"
10
- "strconv"
11
  )
12
 
13
  type CVController interface {
@@ -48,6 +49,7 @@ type CVController interface {
48
  DeleteAchievement(ctx *gin.Context)
49
 
50
  UploadProfileImage(ctx *gin.Context)
 
51
  }
52
 
53
  type cvController struct {
@@ -573,3 +575,16 @@ func (c *cvController) UploadProfileImage(ctx *gin.Context) {
573
 
574
  response.HandleSuccess(ctx, http.StatusOK, "Profile image uploaded successfully", res, nil)
575
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  package cv_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 CVController interface {
 
49
  DeleteAchievement(ctx *gin.Context)
50
 
51
  UploadProfileImage(ctx *gin.Context)
52
+ GetProgress(ctx *gin.Context)
53
  }
54
 
55
  type cvController struct {
 
575
 
576
  response.HandleSuccess(ctx, http.StatusOK, "Profile image uploaded successfully", res, nil)
577
  }
578
+
579
+ func (c *cvController) GetProgress(ctx *gin.Context) {
580
+ accountData := middleware.GetAccountData(ctx)
581
+ accountID := int64(accountData.UserID)
582
+
583
+ res, err := c.cvService.GetProgress(ctx, accountID)
584
+ if err != nil {
585
+ response.HandleError(ctx, err)
586
+ return
587
+ }
588
+
589
+ response.HandleSuccess(ctx, http.StatusOK, "Progress retrieved successfully", res, nil)
590
+ }
space/space/space/space/space/space/space/models/field_counter.go ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package models
2
+
3
+ import (
4
+ "reflect"
5
+ "time"
6
+ )
7
+
8
+ // CounterGetter adalah interface yang menyediakan method untuk menghitung dan mendapatkan field
9
+ type CounterGetter interface {
10
+ TotalFields() int
11
+ GetFilledFields() []string
12
+ }
13
+
14
+ // FieldCounter menyediakan fungsionalitas generik penghitungan dan pengecekan field
15
+ // untuk diembed ke dalam struct lain
16
+ type FieldCounter struct{}
17
+
18
+ // shouldSkipField menentukan apakah field harus dilewati dalam penghitungan
19
+ func shouldSkipField(field reflect.StructField) bool {
20
+ // Skip field dengan nama FieldCounter (embedded type)
21
+ if field.Name == "FieldCounter" {
22
+ return true
23
+ }
24
+
25
+ // Skip field yang berasal dari GORM atau standard fields
26
+ // seperti ID, timestamps, dan foreign keys
27
+ if field.Name == "ID" || field.Name == "CreatedAt" || field.Name == "UpdatedAt" || field.Name == "DeletedAt" ||
28
+ field.Name == "AccountID" || field.Name == "Account" {
29
+ return true
30
+ }
31
+
32
+ // Periksa tag gorm untuk auto fields
33
+ gormTag := field.Tag.Get("gorm")
34
+ if len(gormTag) > 0 {
35
+ // Skip kolom yang auto-increment atau auto-timestamp
36
+ if gormTag == "primaryKey" || gormTag == "autoIncrement" ||
37
+ gormTag == "autoCreateTime" || gormTag == "autoUpdateTime" {
38
+ return true
39
+ }
40
+ }
41
+
42
+ return false
43
+ }
44
+
45
+ // TotalFields menghitung jumlah total field dalam struct
46
+ // dengan mengecualikan field internal, metadata, dan embedded FieldCounter
47
+ func (fc FieldCounter) TotalFields(s any) int {
48
+ t := reflect.TypeOf(s)
49
+ count := 0
50
+
51
+ // Jika s adalah pointer, dapatkan elemen yang ditunjuknya
52
+ if t.Kind() == reflect.Ptr {
53
+ t = t.Elem()
54
+ }
55
+
56
+ // Pastikan kita berurusan dengan struct
57
+ if t.Kind() != reflect.Struct {
58
+ return 0
59
+ }
60
+
61
+ // Hitung field yang sesuai dengan kriteria
62
+ for i := 0; i < t.NumField(); i++ {
63
+ field := t.Field(i)
64
+
65
+ // Skip field yang memenuhi kriteria yang ditentukan
66
+ if shouldSkipField(field) {
67
+ continue
68
+ }
69
+
70
+ count++
71
+ }
72
+
73
+ return count
74
+ }
75
+
76
+ // GetFilledFields mengembalikan slice berisi nama field yang telah diisi dengan nilai
77
+ // (tidak nil, string tidak kosong, int/float tidak 0, dll)
78
+ func (fc FieldCounter) GetFilledFields(s any) []string {
79
+ var filledFields []string
80
+ v := reflect.ValueOf(s)
81
+
82
+ // Jika s adalah pointer, dapatkan elemen yang ditunjuknya
83
+ if v.Kind() == reflect.Ptr {
84
+ if v.IsNil() {
85
+ return filledFields
86
+ }
87
+ v = v.Elem()
88
+ }
89
+
90
+ // Pastikan kita berurusan dengan struct
91
+ if v.Kind() != reflect.Struct {
92
+ return filledFields
93
+ }
94
+
95
+ t := v.Type()
96
+
97
+ // Loop melalui semua field
98
+ for i := 0; i < v.NumField(); i++ {
99
+ field := v.Field(i)
100
+ fieldType := t.Field(i)
101
+ fieldName := fieldType.Name
102
+
103
+ // Skip field yang memenuhi kriteria yang ditentukan
104
+ if shouldSkipField(fieldType) {
105
+ continue
106
+ }
107
+
108
+ // Cek apakah field diisi berdasarkan tipenya
109
+ if isFieldFilled(field) {
110
+ filledFields = append(filledFields, fieldName)
111
+ }
112
+ }
113
+
114
+ return filledFields
115
+ }
116
+
117
+ // isFieldFilled memeriksa apakah field memiliki nilai non-zero
118
+ func isFieldFilled(field reflect.Value) bool {
119
+ // Jika field tidak dapat di-address atau diakses, anggap kosong
120
+ if !field.IsValid() || !field.CanInterface() {
121
+ return false
122
+ }
123
+
124
+ // Handle untuk pointer
125
+ if field.Kind() == reflect.Ptr {
126
+ if field.IsNil() {
127
+ return false
128
+ }
129
+ // Untuk pointer, periksa nilai yang ditunjuk
130
+ return isFieldFilled(field.Elem())
131
+ }
132
+
133
+ // Cek berdasarkan tipe data
134
+ switch field.Kind() {
135
+ case reflect.String:
136
+ return field.String() != ""
137
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
138
+ return field.Int() != 0
139
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
140
+ return field.Uint() != 0
141
+ case reflect.Float32, reflect.Float64:
142
+ return field.Float() != 0
143
+ case reflect.Bool:
144
+ return field.Bool()
145
+ case reflect.Slice, reflect.Map, reflect.Array:
146
+ return !field.IsNil() && field.Len() > 0
147
+ case reflect.Struct:
148
+ // Perlakuan khusus untuk time.Time
149
+ if field.Type() == reflect.TypeOf(time.Time{}) {
150
+ // Anggap time.Time kosong jika zero value atau tahun sangat awal
151
+ timeVal := field.Interface().(time.Time)
152
+ return !timeVal.IsZero() && timeVal.Year() > 1
153
+ }
154
+
155
+ // Untuk struct lain, bandingkan dengan zero value
156
+ return !reflect.DeepEqual(field.Interface(), reflect.Zero(field.Type()).Interface())
157
+ case reflect.Interface:
158
+ return !field.IsNil()
159
+ default:
160
+ // Untuk tipe lain, gunakan perbandingan dengan zero value
161
+ return !field.IsZero()
162
+ }
163
+ }
space/space/space/space/space/space/space/models/request_model.go CHANGED
@@ -161,4 +161,16 @@ type (
161
  UploadProfileImageResponse struct {
162
  URL string `json:"url"`
163
  }
 
 
 
 
 
 
 
 
 
 
 
 
164
  )
 
161
  UploadProfileImageResponse struct {
162
  URL string `json:"url"`
163
  }
164
+
165
+ ProgressResponse struct {
166
+ AccountDetailsProgress float64 `json:"account_details_progress"`
167
+ PersonalityAndPreferenceCVProgress float64 `json:"personality_and_preference_cv_progress"`
168
+ FamilyMemberCVProgress float64 `json:"family_member_cv_progress"`
169
+ PhysicalAndHealthCVProgress float64 `json:"physical_and_health_cv_progress"`
170
+ WorshipAndReligiousUnderstandingCVProgress float64 `json:"worship_and_religious_understanding_cv_progress"`
171
+ EducationCVProgress float64 `json:"education_cv_progress"`
172
+ JobCVProgress float64 `json:"job_cv_progress"`
173
+ AchievementCVProgress float64 `json:"achievement_cv_progress"`
174
+ TotalProgress float64 `json:"total_progress"`
175
+ }
176
  )
space/space/space/space/space/space/space/router/cv_route.go CHANGED
@@ -41,5 +41,7 @@ func (s *Server) CVRoute() {
41
  routerGroup.GET("/achievements/:id", s.cvController.GetAchievement)
42
  routerGroup.PUT("/achievements/:id", s.cvController.UpdateAchievement)
43
  routerGroup.DELETE("/achievements/:id", s.cvController.DeleteAchievement)
 
 
44
  }
45
  }
 
41
  routerGroup.GET("/achievements/:id", s.cvController.GetAchievement)
42
  routerGroup.PUT("/achievements/:id", s.cvController.UpdateAchievement)
43
  routerGroup.DELETE("/achievements/:id", s.cvController.DeleteAchievement)
44
+
45
+ routerGroup.GET("/progress", s.cvController.GetProgress)
46
  }
47
  }
space/space/space/space/space/space/space/services/cv_service.go CHANGED
@@ -3,6 +3,8 @@ package services
3
  import (
4
  "context"
5
  "errors"
 
 
6
  "strconv"
7
 
8
  "api.qobiltu.id/models"
@@ -51,6 +53,7 @@ type CVService interface {
51
  DeleteAchievement(ctx context.Context, id int64) error
52
 
53
  UploadProfileImage(ctx context.Context, req *models.UploadProfileImageRequest) (*models.UploadProfileImageResponse, error)
 
54
  }
55
 
56
  type cvService struct {
@@ -566,3 +569,115 @@ func (s *cvService) UploadProfileImage(ctx context.Context, req *models.UploadPr
566
  URL: url,
567
  }, nil
568
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import (
4
  "context"
5
  "errors"
6
+ "fmt"
7
+ "math"
8
  "strconv"
9
 
10
  "api.qobiltu.id/models"
 
53
  DeleteAchievement(ctx context.Context, id int64) error
54
 
55
  UploadProfileImage(ctx context.Context, req *models.UploadProfileImageRequest) (*models.UploadProfileImageResponse, error)
56
+ GetProgress(ctx context.Context, accountID int64) (*models.ProgressResponse, error)
57
  }
58
 
59
  type cvService struct {
 
569
  URL: url,
570
  }, nil
571
  }
572
+
573
+ func (s *cvService) GetProgress(ctx context.Context, accountID int64) (*models.ProgressResponse, error) {
574
+ accountDetails, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, accountID)
575
+ if err != nil {
576
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
577
+ return nil, response.HandleGormError(err, "Internal Server Error")
578
+ }
579
+ accountDetails = &models.AccountDetails{}
580
+ }
581
+
582
+ personalityAndPreferenceCV, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, accountID)
583
+ if err != nil {
584
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
585
+ return nil, response.HandleGormError(err, "Internal Server Error")
586
+ }
587
+ personalityAndPreferenceCV = &models.PersonalityAndPreferenceCV{}
588
+ }
589
+
590
+ physicalAndHealthCV, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, accountID)
591
+ if err != nil {
592
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
593
+ return nil, response.HandleGormError(err, "Internal Server Error")
594
+ }
595
+ physicalAndHealthCV = &models.PhysicalAndHealthCV{}
596
+ }
597
+
598
+ worshipAndReligiousUnderstandingCV, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, accountID)
599
+ if err != nil {
600
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
601
+ return nil, response.HandleGormError(err, "Internal Server Error")
602
+ }
603
+ worshipAndReligiousUnderstandingCV = &models.WorshipAndReligiousUnderstandingCV{}
604
+ }
605
+
606
+ educationCV, err := s.cvRepository.ListEducation(ctx, accountID)
607
+ if err != nil {
608
+ return nil, response.HandleGormError(err, "Internal Server Error")
609
+ }
610
+
611
+ familyMemberCV, err := s.cvRepository.ListFamilyMember(ctx, accountID)
612
+ if err != nil {
613
+ return nil, response.HandleGormError(err, "Internal Server Error")
614
+ }
615
+
616
+ jobCV, err := s.cvRepository.ListJob(ctx, accountID)
617
+ if err != nil {
618
+ return nil, response.HandleGormError(err, "Internal Server Error")
619
+ }
620
+
621
+ achievementCV, err := s.cvRepository.ListAchievement(ctx, accountID)
622
+ if err != nil {
623
+ return nil, response.HandleGormError(err, "Internal Server Error")
624
+ }
625
+
626
+ progressDetails := calculateProgress(
627
+ accountDetails,
628
+ personalityAndPreferenceCV,
629
+ physicalAndHealthCV,
630
+ worshipAndReligiousUnderstandingCV,
631
+ len(educationCV),
632
+ len(familyMemberCV),
633
+ len(jobCV),
634
+ len(achievementCV),
635
+ )
636
+
637
+ return progressDetails, nil
638
+ }
639
+
640
+ func calculateProgress(
641
+ accountDetails *models.AccountDetails,
642
+ personalityAndPreferenceCV *models.PersonalityAndPreferenceCV,
643
+ physicalAndHealthCV *models.PhysicalAndHealthCV,
644
+ worshipAndReligiousUnderstandingCV *models.WorshipAndReligiousUnderstandingCV,
645
+ educationCV int,
646
+ familyMemberCV int,
647
+ jobCV int,
648
+ achievementCV int,
649
+ ) *models.ProgressResponse {
650
+
651
+ exists := func(data int) float64 {
652
+ if data > 0 {
653
+ return 100
654
+ }
655
+ return 0
656
+ }
657
+
658
+ fmt.Println("accountDetails", accountDetails.GetFilledFields())
659
+ fmt.Println("accountDetailsTotal", accountDetails.TotalFields())
660
+
661
+ accountDetailsPercentage := float64(len(accountDetails.GetFilledFields())) / float64(accountDetails.TotalFields()) * 100
662
+ personalityAndPreferenceCVPercentage := float64(len(personalityAndPreferenceCV.GetFilledFields())) / float64(personalityAndPreferenceCV.TotalFields()) * 100
663
+ physicalAndHealthCVPercentage := float64(len(physicalAndHealthCV.GetFilledFields())) / float64(physicalAndHealthCV.TotalFields()) * 100
664
+ worshipAndReligiousUnderstandingCVPercentage := float64(len(worshipAndReligiousUnderstandingCV.GetFilledFields())) / float64(worshipAndReligiousUnderstandingCV.TotalFields()) * 100
665
+ edicationCVPercentage := exists(educationCV)
666
+ familyMemberCVPercentage := exists(familyMemberCV)
667
+ jobCVPercentage := exists(jobCV)
668
+ achievementCVPercentage := exists(achievementCV)
669
+
670
+ overallProgress := (accountDetailsPercentage + personalityAndPreferenceCVPercentage + physicalAndHealthCVPercentage + worshipAndReligiousUnderstandingCVPercentage + edicationCVPercentage + familyMemberCVPercentage + jobCVPercentage + achievementCVPercentage) / 8
671
+
672
+ return &models.ProgressResponse{
673
+ AccountDetailsProgress: math.Round(accountDetailsPercentage*100) / 100,
674
+ PersonalityAndPreferenceCVProgress: math.Round(personalityAndPreferenceCVPercentage*100) / 100,
675
+ FamilyMemberCVProgress: math.Round(familyMemberCVPercentage*100) / 100,
676
+ PhysicalAndHealthCVProgress: math.Round(physicalAndHealthCVPercentage*100) / 100,
677
+ WorshipAndReligiousUnderstandingCVProgress: math.Round(worshipAndReligiousUnderstandingCVPercentage*100) / 100,
678
+ EducationCVProgress: math.Round(edicationCVPercentage*100) / 100,
679
+ JobCVProgress: jobCVPercentage,
680
+ AchievementCVProgress: achievementCVPercentage,
681
+ TotalProgress: math.Round(overallProgress*100) / 100,
682
+ }
683
+ }
space/space/space/space/space/space/space/space/models/database_orm_model.go CHANGED
@@ -34,6 +34,7 @@ type AccountDetails struct {
34
  PhoneNumber *string `gorm:"column:phone_number" json:"phone_number"`
35
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
36
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
 
37
  }
38
 
39
  type EmailVerification struct {
@@ -154,15 +155,16 @@ type Question struct {
154
  CorrectAnswer uint `json:"-"`
155
  }
156
  type Quiz struct {
157
- ID uint `gorm:"primaryKey" json:"id"`
158
- AcademyID uint `json:"academy_id"`
159
- Slug string `json:"slug" gorm:"uniqueIndex" `
160
- Title string `json:"title"`
161
- Description string `json:"description"`
162
- AttemptLimit int `json:"attempt_limit"`
163
- TimeLimit int `json:"time_limit"`
164
- MinScore int `json:"min_score"`
165
- CreatedAt time.Time `json:"created_at"`
 
166
  }
167
 
168
  type QuizAttempt struct {
@@ -209,6 +211,7 @@ type (
209
  MonthlyExpenses *string `gorm:"column:monthly_expenses" json:"monthly_expenses"` // pengeluaran per bulan
210
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
211
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
 
212
  }
213
 
214
  FamilyMemberCV struct {
@@ -223,6 +226,7 @@ type (
223
  Age *int `gorm:"column:age" json:"age"` // Usia
224
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
225
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
 
226
  }
227
 
228
  PhysicalAndHealthCV struct {
@@ -239,6 +243,7 @@ type (
239
  PhysicalTraits *string `gorm:"column:physical_traits" json:"physical_traits"` // Ciri khas fisik
240
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // Waktu data dibuat
241
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // Waktu data terakhir diperbarui
 
242
  }
243
 
244
  WorshipAndReligiousUnderstandingCV struct {
@@ -262,6 +267,7 @@ type (
262
  FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
263
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
264
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
 
265
  }
266
 
267
  EducationCV struct {
@@ -300,6 +306,38 @@ type (
300
  }
301
  )
302
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  // Gorm table name settings
304
  func (Account) TableName() string { return "account" }
305
  func (AccountDetails) TableName() string { return "account_details" }
 
34
  PhoneNumber *string `gorm:"column:phone_number" json:"phone_number"`
35
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
36
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
37
+ FieldCounter
38
  }
39
 
40
  type EmailVerification struct {
 
155
  CorrectAnswer uint `json:"-"`
156
  }
157
  type Quiz struct {
158
+ ID uint `gorm:"primaryKey" json:"id"`
159
+ AcademyID uint `json:"academy_id"`
160
+ Slug string `json:"slug" gorm:"uniqueIndex" `
161
+ Title string `json:"title"`
162
+ Description string `json:"description"`
163
+ TotalQuestions string `json:"total_questions"`
164
+ AttemptLimit int `json:"attempt_limit"`
165
+ TimeLimit int `json:"time_limit"`
166
+ MinScore int `json:"min_score"`
167
+ CreatedAt time.Time `json:"created_at"`
168
  }
169
 
170
  type QuizAttempt struct {
 
211
  MonthlyExpenses *string `gorm:"column:monthly_expenses" json:"monthly_expenses"` // pengeluaran per bulan
212
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
213
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
214
+ FieldCounter
215
  }
216
 
217
  FamilyMemberCV struct {
 
226
  Age *int `gorm:"column:age" json:"age"` // Usia
227
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
228
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
229
+ FieldCounter
230
  }
231
 
232
  PhysicalAndHealthCV struct {
 
243
  PhysicalTraits *string `gorm:"column:physical_traits" json:"physical_traits"` // Ciri khas fisik
244
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // Waktu data dibuat
245
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // Waktu data terakhir diperbarui
246
+ FieldCounter
247
  }
248
 
249
  WorshipAndReligiousUnderstandingCV struct {
 
267
  FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
268
  CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
269
  UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
270
+ FieldCounter
271
  }
272
 
273
  EducationCV struct {
 
306
  }
307
  )
308
 
309
+ func (a AccountDetails) TotalFields() int {
310
+ return a.FieldCounter.TotalFields(a)
311
+ }
312
+
313
+ func (a AccountDetails) GetFilledFields() []string {
314
+ return a.FieldCounter.GetFilledFields(a)
315
+ }
316
+
317
+ func (p PersonalityAndPreferenceCV) TotalFields() int {
318
+ return p.FieldCounter.TotalFields(p)
319
+ }
320
+
321
+ func (p PersonalityAndPreferenceCV) GetFilledFields() []string {
322
+ return p.FieldCounter.GetFilledFields(p)
323
+ }
324
+
325
+ func (p PhysicalAndHealthCV) TotalFields() int {
326
+ return p.FieldCounter.TotalFields(p)
327
+ }
328
+
329
+ func (p PhysicalAndHealthCV) GetFilledFields() []string {
330
+ return p.FieldCounter.GetFilledFields(p)
331
+ }
332
+
333
+ func (w WorshipAndReligiousUnderstandingCV) TotalFields() int {
334
+ return w.FieldCounter.TotalFields(w)
335
+ }
336
+
337
+ func (w WorshipAndReligiousUnderstandingCV) GetFilledFields() []string {
338
+ return w.FieldCounter.GetFilledFields(w)
339
+ }
340
+
341
  // Gorm table name settings
342
  func (Account) TableName() string { return "account" }
343
  func (AccountDetails) TableName() string { return "account_details" }
space/space/space/space/space/space/space/space/repositories/quiz_repository.go CHANGED
@@ -49,9 +49,12 @@ func GetUserLastAttempt(userId uint, quizId uint) Repository[models.QuizAttempt,
49
  return *repo
50
  }
51
 
52
- func GetAttemptById(attemptId uint) Repository[models.QuizAttempt, models.QuizAttempt] {
53
  repo := Construct[models.QuizAttempt, models.QuizAttempt](
54
- models.QuizAttempt{ID: attemptId},
 
 
 
55
  )
56
  repo.Transactions(
57
  WhereGivenConstructor[models.QuizAttempt, models.QuizAttempt],
 
49
  return *repo
50
  }
51
 
52
+ func GetAttemptByIdandUserId(attemptId uint, userId uint) Repository[models.QuizAttempt, models.QuizAttempt] {
53
  repo := Construct[models.QuizAttempt, models.QuizAttempt](
54
+ models.QuizAttempt{
55
+ ID: attemptId,
56
+ AccountID: userId,
57
+ },
58
  )
59
  repo.Transactions(
60
  WhereGivenConstructor[models.QuizAttempt, models.QuizAttempt],
space/space/space/space/space/space/space/space/services/academy_quiz_service.go CHANGED
@@ -131,7 +131,7 @@ func (s *AttemptQuizService) Create(userID uint) {
131
  }
132
 
133
  func (s *SubmitQuizService) Create(userID uint) {
134
- quizAttemptRepo := repositories.GetAttemptById(s.Constructor.ID)
135
  if quizAttemptRepo.NoRecord {
136
  s.Exception.DataNotFound = true
137
  s.Exception.Message = "There is no quiz attempt with given user!"
 
131
  }
132
 
133
  func (s *SubmitQuizService) Create(userID uint) {
134
+ quizAttemptRepo := repositories.GetAttemptByIdandUserId(s.Constructor.ID, userID)
135
  if quizAttemptRepo.NoRecord {
136
  s.Exception.DataNotFound = true
137
  s.Exception.Message = "There is no quiz attempt with given user!"
space/space/space/space/space/space/space/space/space/pkg/validation/custom_rules.go CHANGED
@@ -1,6 +1,7 @@
1
  package validation
2
 
3
  import (
 
4
  "strings"
5
  "sync"
6
 
@@ -150,6 +151,11 @@ func (v *Validator) RegisterAllCustomRules(validate *v10.Validate) error {
150
  return err
151
  }
152
 
 
 
 
 
 
153
  return nil
154
  }
155
 
@@ -160,3 +166,39 @@ func (v *Validator) PasswordRule(fl v10.FieldLevel) bool {
160
  strings.ContainsAny(password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") &&
161
  strings.ContainsAny(password, "0123456789")
162
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  package validation
2
 
3
  import (
4
+ "regexp"
5
  "strings"
6
  "sync"
7
 
 
151
  return err
152
  }
153
 
154
+ err = validate.RegisterValidation("phone_number", v.PhoneNumberRule)
155
+ if err != nil {
156
+ return err
157
+ }
158
+
159
  return nil
160
  }
161
 
 
166
  strings.ContainsAny(password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") &&
167
  strings.ContainsAny(password, "0123456789")
168
  }
169
+
170
+ func (v *Validator) PhoneNumberRule(fl v10.FieldLevel) bool {
171
+ phone := SanitizePhoneNumber(fl.Field().String())
172
+ return strings.HasPrefix(phone, "+62")
173
+ }
174
+
175
+ func SanitizePhoneNumber(input string) string {
176
+ // Hilangkan semua spasi dan strip
177
+ input = strings.ReplaceAll(input, " ", "")
178
+ input = strings.ReplaceAll(input, "-", "")
179
+ input = strings.ReplaceAll(input, "(", "")
180
+ input = strings.ReplaceAll(input, ")", "")
181
+
182
+ // Hilangkan semua karakter non-digit kecuali +
183
+ re := regexp.MustCompile(`[^0-9\+]`)
184
+ input = re.ReplaceAllString(input, "")
185
+
186
+ // Handle nomor diawali 0 (contoh: 0812...) menjadi +62812...
187
+ if strings.HasPrefix(input, "0") {
188
+ input = "+62" + input[1:]
189
+ }
190
+
191
+ // Handle jika diawali dengan 62 tanpa + (contoh: 62812...)
192
+ if strings.HasPrefix(input, "62") && !strings.HasPrefix(input, "+62") {
193
+ input = "+" + input
194
+ }
195
+
196
+ // Handle jika tidak ada awalan +62 sama sekali (contoh: 8123456789)
197
+ if !strings.HasPrefix(input, "+62") {
198
+ if strings.HasPrefix(input, "8") {
199
+ input = "+62" + input
200
+ }
201
+ }
202
+
203
+ return input
204
+ }
space/space/space/space/space/space/space/space/space/pkg/validation/validation.go CHANGED
@@ -79,6 +79,7 @@ func setupValidations(validate *v10.Validate) error {
79
  if err := rules.RegisterAllCustomRules(validate); err != nil {
80
  return err
81
  }
 
82
  return nil
83
  }
84
 
 
79
  if err := rules.RegisterAllCustomRules(validate); err != nil {
80
  return err
81
  }
82
+
83
  return nil
84
  }
85
 
space/space/space/space/space/space/space/space/space/space/Makefile CHANGED
@@ -1,7 +1,15 @@
1
  up-dev:
2
  docker compose -f docker-compose.dev.yml up -d
3
 
 
 
 
 
 
 
 
 
4
  run-dev:
5
  go run main.go
6
 
7
- .PHONY : up-dev run-dev
 
1
  up-dev:
2
  docker compose -f docker-compose.dev.yml up -d
3
 
4
+ down-dev:
5
+ docker compose -f docker-compose.dev.yml down
6
+
7
+ up:
8
+ docker compose up -d --build
9
+ down:
10
+ docker compose down
11
+
12
  run-dev:
13
  go run main.go
14
 
15
+ .PHONY : up-dev run-dev down-dev up down
space/space/space/space/space/space/space/space/space/space/config/config.go CHANGED
@@ -29,6 +29,8 @@ var REDIS_MIN_IDLE_CONNS int
29
  var REDIS_POOL_SIZE int
30
  var REDIS_POOL_TIMEOUT time.Duration
31
 
 
 
32
  func init() {
33
  godotenv.Load()
34
  ENV = os.Getenv("ENV")
@@ -49,6 +51,7 @@ func init() {
49
  REDIS_POOL_SIZE = getValue(os.Getenv("REDIS_POOL_SIZE"), 10, func(s string) (int, error) { return strconv.Atoi(s) })
50
  REDIS_MIN_IDLE_CONNS = getValue(os.Getenv("REDIS_MIN_IDLE_CONNS"), 10, func(s string) (int, error) { return strconv.Atoi(s) })
51
  REDIS_POOL_TIMEOUT = getValue(os.Getenv("REDIS_POOL_TIMEOUT"), time.Second*30, func(s string) (time.Duration, error) { return time.ParseDuration(s) })
 
52
  }
53
 
54
  func getValue[T any](value string, defaultValue T, convert func(string) (T, error)) T {
 
29
  var REDIS_POOL_SIZE int
30
  var REDIS_POOL_TIMEOUT time.Duration
31
 
32
+ var APP_URL string
33
+
34
  func init() {
35
  godotenv.Load()
36
  ENV = os.Getenv("ENV")
 
51
  REDIS_POOL_SIZE = getValue(os.Getenv("REDIS_POOL_SIZE"), 10, func(s string) (int, error) { return strconv.Atoi(s) })
52
  REDIS_MIN_IDLE_CONNS = getValue(os.Getenv("REDIS_MIN_IDLE_CONNS"), 10, func(s string) (int, error) { return strconv.Atoi(s) })
53
  REDIS_POOL_TIMEOUT = getValue(os.Getenv("REDIS_POOL_TIMEOUT"), time.Second*30, func(s string) (time.Duration, error) { return time.ParseDuration(s) })
54
+ APP_URL = getValue(os.Getenv("APP_URL"), "http://localhost:3000", func(s string) (string, error) { return s, nil })
55
  }
56
 
57
  func getValue[T any](value string, defaultValue T, convert func(string) (T, error)) T {
space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go CHANGED
@@ -46,6 +46,8 @@ type CVController interface {
46
  ListAchievement(ctx *gin.Context)
47
  GetAchievement(ctx *gin.Context)
48
  DeleteAchievement(ctx *gin.Context)
 
 
49
  }
50
 
51
  type cvController struct {
@@ -553,3 +555,21 @@ func (c *cvController) DeleteAchievement(ctx *gin.Context) {
553
 
554
  response.HandleSuccess(ctx, http.StatusOK, "Achievement deleted successfully", nil, nil)
555
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  ListAchievement(ctx *gin.Context)
47
  GetAchievement(ctx *gin.Context)
48
  DeleteAchievement(ctx *gin.Context)
49
+
50
+ UploadProfileImage(ctx *gin.Context)
51
  }
52
 
53
  type cvController struct {
 
555
 
556
  response.HandleSuccess(ctx, http.StatusOK, "Achievement deleted successfully", nil, nil)
557
  }
558
+
559
+ func (c *cvController) UploadProfileImage(ctx *gin.Context) {
560
+ accountData := middleware.GetAccountData(ctx)
561
+
562
+ avatarFile, _ := ctx.FormFile("avatar")
563
+ req := models.UploadProfileImageRequest{
564
+ AccountID: int64(accountData.UserID),
565
+ File: avatarFile,
566
+ }
567
+
568
+ res, err := c.cvService.UploadProfileImage(ctx, &req)
569
+ if err != nil {
570
+ response.HandleError(ctx, err)
571
+ return
572
+ }
573
+
574
+ response.HandleSuccess(ctx, http.StatusOK, "Profile image uploaded successfully", res, nil)
575
+ }
space/space/space/space/space/space/space/space/space/space/docker-compose.yml CHANGED
@@ -6,14 +6,15 @@ services:
6
  build: .
7
  depends_on:
8
  - db
 
9
  env_file: .env
10
  ports:
11
  - "8080:8080"
 
 
 
12
  networks:
13
  - qobiltu-network
14
- # volumes:
15
- # - ./logs:/app/logs
16
- # - /home/qobiltu/api-qobiltu:/app
17
  restart: unless-stopped
18
 
19
  db:
@@ -45,10 +46,6 @@ services:
45
  - qobiltu-network
46
  restart: always
47
 
48
- volumes:
49
- db-data:
50
- redis-data:
51
-
52
  networks:
53
  qobiltu-network:
54
  driver: bridge
 
6
  build: .
7
  depends_on:
8
  - db
9
+ - redis
10
  env_file: .env
11
  ports:
12
  - "8080:8080"
13
+ volumes:
14
+ - ./logs:/app/logs
15
+ - ./uploads:/app/uploads
16
  networks:
17
  - qobiltu-network
 
 
 
18
  restart: unless-stopped
19
 
20
  db:
 
46
  - qobiltu-network
47
  restart: always
48
 
 
 
 
 
49
  networks:
50
  qobiltu-network:
51
  driver: bridge
space/space/space/space/space/space/space/space/space/space/go.mod CHANGED
@@ -8,6 +8,7 @@ require (
8
  github.com/go-playground/universal-translator v0.18.1
9
  github.com/go-playground/validator/v10 v10.25.0
10
  github.com/golang-jwt/jwt/v5 v5.2.1
 
11
  github.com/gosimple/slug v1.15.0
12
  github.com/hibiken/asynq v0.25.1
13
  github.com/joho/godotenv v1.5.1
@@ -37,7 +38,6 @@ require (
37
  github.com/go-logr/stdr v1.2.2 // indirect
38
  github.com/goccy/go-json v0.10.5 // indirect
39
  github.com/google/s2a-go v0.1.9 // indirect
40
- github.com/google/uuid v1.6.0 // indirect
41
  github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
42
  github.com/googleapis/gax-go/v2 v2.14.1 // indirect
43
  github.com/gosimple/unidecode v1.0.1 // indirect
 
8
  github.com/go-playground/universal-translator v0.18.1
9
  github.com/go-playground/validator/v10 v10.25.0
10
  github.com/golang-jwt/jwt/v5 v5.2.1
11
+ github.com/google/uuid v1.6.0
12
  github.com/gosimple/slug v1.15.0
13
  github.com/hibiken/asynq v0.25.1
14
  github.com/joho/godotenv v1.5.1
 
38
  github.com/go-logr/stdr v1.2.2 // indirect
39
  github.com/goccy/go-json v0.10.5 // indirect
40
  github.com/google/s2a-go v0.1.9 // indirect
 
41
  github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
42
  github.com/googleapis/gax-go/v2 v2.14.1 // indirect
43
  github.com/gosimple/unidecode v1.0.1 // indirect
space/space/space/space/space/space/space/space/space/space/main.go CHANGED
@@ -2,15 +2,16 @@ package main
2
 
3
  import (
4
  "api.qobiltu.id/config"
5
- cv_controller "api.qobiltu.id/controller/cv"
6
  "api.qobiltu.id/controller/health_check"
7
  "api.qobiltu.id/mail"
 
 
 
8
  "api.qobiltu.id/repositories"
9
  "api.qobiltu.id/router"
10
  "api.qobiltu.id/services"
11
  "api.qobiltu.id/utils"
12
- "api.qobiltu.id/validation"
13
- "api.qobiltu.id/worker"
14
  "github.com/hibiken/asynq"
15
  "log/slog"
16
  "net"
@@ -23,6 +24,9 @@ func main() {
23
  err := validation.New(validation.LocaleID)
24
  utils.FatalIfErr("failed to setup validator", err)
25
 
 
 
 
26
  // setup email sender
27
  emailConfig := mail.Config{
28
  Host: config.SMTP_HOST,
@@ -53,7 +57,7 @@ func main() {
53
  healthCheckController := health_check_controller.NewHealthCheckController(healthCheckService)
54
 
55
  cvRepository := repositories.NewCVRepository(config.DB)
56
- cvService := services.NewCVService(cvRepository)
57
  cvController := cv_controller.NewCVController(cvService)
58
 
59
  // start task processor
 
2
 
3
  import (
4
  "api.qobiltu.id/config"
5
+ "api.qobiltu.id/controller/cv"
6
  "api.qobiltu.id/controller/health_check"
7
  "api.qobiltu.id/mail"
8
+ "api.qobiltu.id/pkg/storage"
9
+ "api.qobiltu.id/pkg/validation"
10
+ "api.qobiltu.id/pkg/worker"
11
  "api.qobiltu.id/repositories"
12
  "api.qobiltu.id/router"
13
  "api.qobiltu.id/services"
14
  "api.qobiltu.id/utils"
 
 
15
  "github.com/hibiken/asynq"
16
  "log/slog"
17
  "net"
 
24
  err := validation.New(validation.LocaleID)
25
  utils.FatalIfErr("failed to setup validator", err)
26
 
27
+ // setup storage
28
+ localStorage := storage.NewLocalStorage("uploads", config.APP_URL+"/storage/")
29
+
30
  // setup email sender
31
  emailConfig := mail.Config{
32
  Host: config.SMTP_HOST,
 
57
  healthCheckController := health_check_controller.NewHealthCheckController(healthCheckService)
58
 
59
  cvRepository := repositories.NewCVRepository(config.DB)
60
+ cvService := services.NewCVService(cvRepository, localStorage)
61
  cvController := cv_controller.NewCVController(cvService)
62
 
63
  // start task processor
space/space/space/space/space/space/space/space/space/space/models/exception_model.go CHANGED
@@ -1,6 +1,8 @@
1
  package models
2
 
3
- import "api.qobiltu.id/validation"
 
 
4
 
5
  type Exception struct {
6
  Unauthorized bool `json:"unauthorized,omitempty"`
 
1
  package models
2
 
3
+ import (
4
+ "api.qobiltu.id/pkg/validation"
5
+ )
6
 
7
  type Exception struct {
8
  Unauthorized bool `json:"unauthorized,omitempty"`