lifedebugger commited on
Commit
62cba99
·
1 Parent(s): f3f5b2d

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. controller/quiz/review_quiz_controller.go +26 -0
  2. models/database_orm_model.go +4 -1
  3. router/quiz_route.go +1 -0
  4. services/academy_quiz_question_service.go +2 -0
  5. services/academy_quiz_review_service.go +56 -0
  6. services/academy_quiz_service.go +2 -5
  7. space/main.go +0 -6
  8. space/models/database_orm_model.go +12 -7
  9. space/models/request_model.go +8 -6
  10. space/router/router.go +0 -1
  11. space/router/server.go +0 -4
  12. space/services/partner_criteria_service.go +4 -2
  13. space/space/pkg/validation/validation.go +16 -155
  14. space/space/response/validation.go +19 -2
  15. space/space/services/marriage_readiness_profile_service.go +4 -3
  16. space/space/services/partner_criteria_service.go +4 -3
  17. space/space/space/controller/partner_criteria/partner_criteria_controller.go +66 -0
  18. space/space/space/main.go +9 -3
  19. space/space/space/models/database_orm_model.go +54 -20
  20. space/space/space/models/request_model.go +112 -77
  21. space/space/space/pkg/validation/custom_rules.go +236 -116
  22. space/space/space/repositories/partner_criteria_repository.go +38 -0
  23. space/space/space/router/partner_criteria_route.go +11 -0
  24. space/space/space/router/router.go +1 -0
  25. space/space/space/router/server.go +4 -0
  26. space/space/space/services/cv_service.go +16 -13
  27. space/space/space/services/partner_criteria_service.go +77 -0
  28. space/space/space/space/config/database_connection_config.go +13 -2
  29. space/space/space/space/models/sequence.go +25 -0
  30. space/space/space/space/repositories/account_repository.go +18 -0
  31. space/space/space/space/repositories/cv_repository.go +162 -146
  32. space/space/space/space/services/cv_service.go +33 -7
  33. space/space/space/space/services/user_profile_service.go +36 -8
  34. space/space/space/space/space/models/database_orm_model.go +50 -50
  35. space/space/space/space/space/models/field_counter.go +4 -19
  36. space/space/space/space/space/space/config/database_connection_config.go +1 -0
  37. space/space/space/space/space/space/controller/marriage_readiness_profile/marriage_readiness_profile_controller.go +66 -0
  38. space/space/space/space/space/space/main.go +12 -5
  39. space/space/space/space/space/space/repositories/marriage_readiness_profile_repository.go +38 -0
  40. space/space/space/space/space/space/router/marriage_readiness_profile_route.go +11 -0
  41. space/space/space/space/space/space/services/marriage_readiness_profile_service.go +90 -0
  42. space/space/space/space/space/space/space/controller/cv/cv_controller.go +171 -47
  43. space/space/space/space/space/space/space/models/database_orm_model.go +95 -43
  44. space/space/space/space/space/space/space/models/request_model.go +199 -45
  45. space/space/space/space/space/space/space/pkg/validation/validation.go +10 -1
  46. space/space/space/space/space/space/space/pkg/worker/task_send_forgot_password_email.go +8 -6
  47. space/space/space/space/space/space/space/pkg/worker/task_send_verify_email.go +8 -5
  48. space/space/space/space/space/space/space/router/cv_route.go +1 -1
  49. space/space/space/space/space/space/space/router/router.go +1 -0
  50. space/space/space/space/space/space/space/router/server.go +11 -7
controller/quiz/review_quiz_controller.go ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 Review(c *gin.Context) {
13
+ reviewQuiz := services.ReviewQuizService{}
14
+ reviewQuizController := controller.Controller[any, models.Quiz, models.QuestionResponse]{
15
+ Service: &reviewQuiz.Service,
16
+ }
17
+ reviewQuizController.HeaderParse(c, func() {
18
+ quizId, _ := strconv.Atoi(c.Param("quiz_id"))
19
+ academyId, _ := strconv.Atoi(c.Param("academy_id"))
20
+ questionNo, _ := strconv.Atoi(c.Query("question_no"))
21
+ reviewQuizController.Service.Constructor.ID = uint(quizId)
22
+ reviewQuizController.Service.Constructor.AcademyID = uint(academyId)
23
+ reviewQuiz.Retrieve(reviewQuizController.AccountData.UserID, questionNo)
24
+ reviewQuizController.Response(c)
25
+ })
26
+ }
models/database_orm_model.go CHANGED
@@ -78,8 +78,10 @@ type Academy struct {
78
  Slug string `json:"slug" gorm:"uniqueIndex" `
79
  TotalMaterial int `json:"total_material"`
80
  CompletedMaterial int `json:"completed_material"`
 
 
81
  IsCompletedRead bool `json:"is_read"`
82
- IsPassedExam bool `json:"is_exam"`
83
  Description string `json:"description"`
84
  }
85
 
@@ -189,6 +191,7 @@ type QuizResult struct {
189
  TotalQuestions int `gorm:"column:total_questions" json:"total_questions"`
190
  CorrectAnswers int `gorm:"column:correct_answers" json:"correct_answers"`
191
  AverageScore float64 `gorm:"column:average_score" json:"average_score"`
 
192
  }
193
 
194
  type (
 
78
  Slug string `json:"slug" gorm:"uniqueIndex" `
79
  TotalMaterial int `json:"total_material"`
80
  CompletedMaterial int `json:"completed_material"`
81
+ TotalQuiz int `json:"total_quiz"`
82
+ PassedQuiz int `json:"passed_quiz"`
83
  IsCompletedRead bool `json:"is_read"`
84
+ IsPassedExam bool `json:"is_passed_exam"`
85
  Description string `json:"description"`
86
  }
87
 
 
191
  TotalQuestions int `gorm:"column:total_questions" json:"total_questions"`
192
  CorrectAnswers int `gorm:"column:correct_answers" json:"correct_answers"`
193
  AverageScore float64 `gorm:"column:average_score" json:"average_score"`
194
+ IsPassed bool `gorm:"column:is_passed" json:"is_passed"`
195
  }
196
 
197
  type (
router/quiz_route.go CHANGED
@@ -12,6 +12,7 @@ func QuizRoute(router *gin.Engine) {
12
  routerGroup.GET("/:academy_id/list", middleware.AuthUser, QuizController.List)
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)
 
12
  routerGroup.GET("/:academy_id/list", middleware.AuthUser, QuizController.List)
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.GET("/:academy_id/:quiz_id/review", middleware.AuthUser, QuizController.Review)
16
  routerGroup.PUT("/:academy_id/:quiz_id/choose-answer", middleware.AuthUser, QuizController.Answer)
17
  routerGroup.GET("result/:attempt_id", middleware.AuthUser, QuizController.Result)
18
  routerGroup.GET("result/", middleware.AuthUser, QuizController.Result)
services/academy_quiz_question_service.go CHANGED
@@ -34,6 +34,8 @@ func (s *QuestionQuizService) Retrieve(userID uint, questionNo int) {
34
  s.Exception.Message = "There is no Answer Option with given QuestionId!"
35
  return
36
  }
 
 
37
  s.Result = models.QuestionResponse{
38
  Question: questionRepo.Result,
39
  Answer: answerOptionRepo.Result,
 
34
  s.Exception.Message = "There is no Answer Option with given QuestionId!"
35
  return
36
  }
37
+ questionRepo.Result.Review = "SECRET"
38
+ questionRepo.Result.CorrectAnswer = 0
39
  s.Result = models.QuestionResponse{
40
  Question: questionRepo.Result,
41
  Answer: answerOptionRepo.Result,
services/academy_quiz_review_service.go ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package services
2
+
3
+ import (
4
+ "errors"
5
+
6
+ "api.qobiltu.id/models"
7
+ "api.qobiltu.id/repositories"
8
+ )
9
+
10
+ type ReviewQuizService struct {
11
+ Service[models.Quiz, models.QuestionResponse]
12
+ }
13
+
14
+ func (s *ReviewQuizService) Retrieve(userID uint, questionNo int) {
15
+ latestAttemptRepo := repositories.GetUserLastAttempt(userID, s.Constructor.ID)
16
+ s.Error = latestAttemptRepo.RowsError
17
+ if latestAttemptRepo.NoRecord {
18
+ s.Exception.DataNotFound = true
19
+ s.Exception.Message = "There is no attempt data with given user ID!"
20
+ return
21
+ }
22
+ quizRepo := repositories.GetQuizbyId(s.Constructor.ID)
23
+ s.Error = errors.Join(s.Error, quizRepo.RowsError)
24
+ if latestAttemptRepo.Result.Score >= float64(quizRepo.Result.MinScore) {
25
+ questionRepo := repositories.GetQuestionByOrder(s.Constructor.ID, questionNo)
26
+ s.Error = questionRepo.RowsError
27
+ if questionRepo.NoRecord {
28
+ s.Exception.DataNotFound = true
29
+ s.Exception.Message = "There is no quiz with given academy!"
30
+ return
31
+ }
32
+ answerRepo := repositories.GetUserAnswerByAttemptQuestionId(latestAttemptRepo.Result.ID, questionRepo.Result.ID)
33
+ if answerRepo.NoRecord {
34
+ s.Exception.DataNotFound = true
35
+ s.Exception.Message = "There is no Answer with given AttemptId!"
36
+ return
37
+ }
38
+ answerOptionRepo := repositories.GetAnswerByQuestionId(questionRepo.Result.ID)
39
+ if answerOptionRepo.NoRecord {
40
+ s.Exception.DataNotFound = true
41
+ s.Exception.Message = "There is no Answer Option with given QuestionId!"
42
+ return
43
+ }
44
+ s.Result = models.QuestionResponse{
45
+ Question: questionRepo.Result,
46
+ Answer: answerOptionRepo.Result,
47
+ UserAnswer: int(answerRepo.Result.SelectedAnswer),
48
+ }
49
+ } else {
50
+ s.Exception.Forbidden = true
51
+ s.Exception.Message = "You have to passed the exam to review the quiz! "
52
+ return
53
+ }
54
+
55
+ return
56
+ }
services/academy_quiz_service.go CHANGED
@@ -2,7 +2,6 @@ package services
2
 
3
  import (
4
  "errors"
5
- "fmt"
6
  "time"
7
 
8
  "api.qobiltu.id/models"
@@ -131,7 +130,8 @@ func (s *AttemptQuizService) Create(userID uint) {
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 {
@@ -142,10 +142,7 @@ func (s *AttemptQuizService) Create(userID uint) {
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!"
 
2
 
3
  import (
4
  "errors"
 
5
  "time"
6
 
7
  "api.qobiltu.id/models"
 
130
  if latestAttemptRepo.Result.Score < float64(quizRepo.Result.MinScore) {
131
  Attempt(s, quizRepo, userID)
132
  } else {
133
+ s.Exception.Forbidden = true
134
+ s.Exception.Message = "You're alread passed the quiz, you don't have to re-attempt!"
135
  return
136
  }
137
  } else {
 
142
  }
143
 
144
  func (s *SubmitQuizService) Create() {
 
 
145
  quizAttemptRepo := repositories.GetAttemptByIdandUserId(s.Constructor.ID, s.Constructor.AccountID)
 
146
  if quizAttemptRepo.NoRecord {
147
  s.Exception.DataNotFound = true
148
  s.Exception.Message = "There is no quiz attempt with given user!"
space/main.go CHANGED
@@ -7,7 +7,6 @@ import (
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
  partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
13
  "api.qobiltu.id/mail"
@@ -55,10 +54,6 @@ func main() {
55
  worker.AsyncTaskDistributor = taskDistributor
56
 
57
  // setup repo, service, and controller
58
- healthCheckRepository := repositories.NewHealthCheckRepository(config.DB)
59
- healthCheckService := services.NewHealthCheckService(healthCheckRepository)
60
- healthCheckController := health_check_controller.NewHealthCheckController(healthCheckService)
61
-
62
  cvRepository := repositories.NewCVRepository(config.DB)
63
  cvService := services.NewCVService(cvRepository, localStorage, validator)
64
  cvController := cv_controller.NewCVController(cvService)
@@ -78,7 +73,6 @@ func main() {
78
 
79
  // create server
80
  s, err := router.NewServer(
81
- healthCheckController,
82
  cvController,
83
  marriageReadinessProfileController,
84
  partnerCriteriaController,
 
7
 
8
  "api.qobiltu.id/config"
9
  cv_controller "api.qobiltu.id/controller/cv"
 
10
  marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
11
  partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
12
  "api.qobiltu.id/mail"
 
54
  worker.AsyncTaskDistributor = taskDistributor
55
 
56
  // setup repo, service, and controller
 
 
 
 
57
  cvRepository := repositories.NewCVRepository(config.DB)
58
  cvService := services.NewCVService(cvRepository, localStorage, validator)
59
  cvController := cv_controller.NewCVController(cvService)
 
73
 
74
  // create server
75
  s, err := router.NewServer(
 
76
  cvController,
77
  marriageReadinessProfileController,
78
  partnerCriteriaController,
space/models/database_orm_model.go CHANGED
@@ -78,8 +78,10 @@ type Academy struct {
78
  Slug string `json:"slug" gorm:"uniqueIndex" `
79
  TotalMaterial int `json:"total_material"`
80
  CompletedMaterial int `json:"completed_material"`
 
 
81
  IsCompletedRead bool `json:"is_read"`
82
- IsPassedExam bool `json:"is_exam"`
83
  Description string `json:"description"`
84
  }
85
 
@@ -189,6 +191,7 @@ type QuizResult struct {
189
  TotalQuestions int `gorm:"column:total_questions" json:"total_questions"`
190
  CorrectAnswers int `gorm:"column:correct_answers" json:"correct_answers"`
191
  AverageScore float64 `gorm:"column:average_score" json:"average_score"`
 
192
  }
193
 
194
  type (
@@ -396,7 +399,8 @@ type (
396
  Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
397
 
398
  // Kriteria Umum
399
- ExpectedAgeLimit *int `gorm:"column:expected_age_limit" json:"expected_age_limit"` // batas usia pasangan yang diharapkan
 
400
  ExpectedDomicile *string `gorm:"column:expected_domicile" json:"expected_domicile"` // domisili pasangan yang diharapkan
401
  AcceptedMaritalStatus *pq.StringArray `gorm:"column:accepted_marital_status;type:varchar(255)[]" json:"accepted_marital_status"` // status pernikahan yang diterima
402
  PartnerFamilyReligion *string `gorm:"column:partner_family_religion" json:"partner_family_religion"` // agama yang dianut keluarga pasangan
@@ -405,11 +409,12 @@ type (
405
  PartnerCanCook *string `gorm:"column:partner_can_cook" json:"partner_can_cook"` // apakah pasangan diharapkan bisa memasak
406
 
407
  // Kriteria Fisik
408
- ExpectedBodyShapes *pq.StringArray `gorm:"column:expected_body_shapes;type:varchar(255)[]" json:"expected_body_shapes"` // bentuk tubuh yang diharapkan
409
- ExpectedSkinColors *pq.StringArray `gorm:"column:expected_skin_colors;type:varchar(255)[]" json:"expected_skin_colors"` // warna kulit yang diharapkan
410
- ExpectedHairTypes *pq.StringArray `gorm:"column:expected_hair_types;type:varchar(255)[]" json:"expected_hair_types"` // tipe rambut yang diharapkan
411
- ExpectedHairThickness *pq.StringArray `gorm:"column:expected_hair_thickness;type:varchar(255)[]" json:"expected_hair_thickness"` // jenis rambut yang diharapkan
412
- ExpectedHeight *int `gorm:"column:expected_height" json:"expected_height"` // tinggi badan yang diharapkan
 
413
 
414
  // Pendidikan & Pekerjaan
415
  ExpectedPartnerIncome *string `gorm:"column:expected_partner_income" json:"expected_partner_income"` // penghasilan pasangan per bulan yang diharapkan
 
78
  Slug string `json:"slug" gorm:"uniqueIndex" `
79
  TotalMaterial int `json:"total_material"`
80
  CompletedMaterial int `json:"completed_material"`
81
+ TotalQuiz int `json:"total_quiz"`
82
+ PassedQuiz int `json:"passed_quiz"`
83
  IsCompletedRead bool `json:"is_read"`
84
+ IsPassedExam bool `json:"is_passed_exam"`
85
  Description string `json:"description"`
86
  }
87
 
 
191
  TotalQuestions int `gorm:"column:total_questions" json:"total_questions"`
192
  CorrectAnswers int `gorm:"column:correct_answers" json:"correct_answers"`
193
  AverageScore float64 `gorm:"column:average_score" json:"average_score"`
194
+ IsPassed bool `gorm:"column:is_passed" json:"is_passed"`
195
  }
196
 
197
  type (
 
399
  Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
400
 
401
  // Kriteria Umum
402
+ ExpecteMinAgeLimit *int `gorm:"column:expected_min_age_limit" json:"expected_min_age_limit"` // batas usia pasangan yang diharapkan
403
+ ExpecteMaxAgeLimit *int `gorm:"column:expected_max_age_limit" json:"expected_max_age_limit"` // batas usia pasangan yang diharapkan
404
  ExpectedDomicile *string `gorm:"column:expected_domicile" json:"expected_domicile"` // domisili pasangan yang diharapkan
405
  AcceptedMaritalStatus *pq.StringArray `gorm:"column:accepted_marital_status;type:varchar(255)[]" json:"accepted_marital_status"` // status pernikahan yang diterima
406
  PartnerFamilyReligion *string `gorm:"column:partner_family_religion" json:"partner_family_religion"` // agama yang dianut keluarga pasangan
 
409
  PartnerCanCook *string `gorm:"column:partner_can_cook" json:"partner_can_cook"` // apakah pasangan diharapkan bisa memasak
410
 
411
  // Kriteria Fisik
412
+ ExpectedBodyShapes *pq.StringArray `gorm:"column:expected_body_shapes;type:varchar(255)[]" json:"expected_body_shapes"` // bentuk tubuh yang diharapkan
413
+ ExpectedSkinColors *pq.StringArray `gorm:"column:expected_skin_colors;type:varchar(255)[]" json:"expected_skin_colors"` // warna kulit yang diharapkan
414
+ ExpectedHairTypes *pq.StringArray `gorm:"column:expected_hair_types;type:varchar(255)[]" json:"expected_hair_types"` // tipe rambut yang diharapkan
415
+ ExpectedHairThickness *pq.StringArray `gorm:"column:expected_hair_thickness;type:varchar(255)[]" json:"expected_hair_thickness"` // jenis rambut yang diharapkan
416
+ ExpectedMinHeightLimit *int `gorm:"column:expected_min_height_limit" json:"expected_min_height_limit"` // tinggi badan yang diharapkan
417
+ ExpectedMaxHeightLimit *int `gorm:"column:expected_max_height_limit" json:"expected_max_height_limit"` // tinggi badan yang diharapkan
418
 
419
  // Pendidikan & Pekerjaan
420
  ExpectedPartnerIncome *string `gorm:"column:expected_partner_income" json:"expected_partner_income"` // penghasilan pasangan per bulan yang diharapkan
space/models/request_model.go CHANGED
@@ -336,7 +336,8 @@ type (
336
  SavePartnerCriteriaRequest struct {
337
  AccountID int64 `json:"account_id"`
338
 
339
- ExpectedAgeLimit *int `gorm:"column:expected_age_limit" json:"expected_age_limit"` // batas usia pasangan yang diharapkan
 
340
  ExpectedDomicile *string `gorm:"column:expected_domicile" json:"expected_domicile"` // domisili pasangan yang diharapkan
341
  AcceptedMaritalStatus *pq.StringArray `gorm:"column:accepted_marital_status;type:varchar(255)[]" json:"accepted_marital_status"` // status pernikahan yang diterima
342
  PartnerFamilyReligion *string `gorm:"column:partner_family_religion" json:"partner_family_religion"` // agama yang dianut keluarga pasangan
@@ -345,11 +346,12 @@ type (
345
  PartnerCanCook *string `gorm:"column:partner_can_cook" json:"partner_can_cook"` // apakah pasangan diharapkan bisa memasak
346
 
347
  // Kriteria Fisik
348
- ExpectedBodyShapes *pq.StringArray `gorm:"column:expected_body_shapes;type:varchar(255)[]" json:"expected_body_shapes"` // bentuk tubuh yang diharapkan
349
- ExpectedSkinColors *pq.StringArray `gorm:"column:expected_skin_colors;type:varchar(255)[]" json:"expected_skin_colors"` // warna kulit yang diharapkan
350
- ExpectedHairTypes *pq.StringArray `gorm:"column:expected_hair_types;type:varchar(255)[]" json:"expected_hair_types"` // tipe rambut yang diharapkan
351
- ExpectedHairThickness *pq.StringArray `gorm:"column:expected_hair_thickness;type:varchar(255)[]" json:"expected_hair_thickness"` // jenis rambut yang diharapkan
352
- ExpectedHeight *int `gorm:"column:expected_height" json:"expected_height"` // tinggi badan yang diharapkan
 
353
 
354
  // Pendidikan & Pekerjaan
355
  ExpectedPartnerIncome *string `gorm:"column:expected_partner_income" json:"expected_partner_income"` // penghasilan pasangan per bulan yang diharapkan
 
336
  SavePartnerCriteriaRequest struct {
337
  AccountID int64 `json:"account_id"`
338
 
339
+ ExpecteMinAgeLimit *int `gorm:"column:expected_min_age_limit" json:"expected_min_age_limit"` // batas usia pasangan yang diharapkan
340
+ ExpecteMaxAgeLimit *int `gorm:"column:expected_max_age_limit" json:"expected_max_age_limit"` // batas usia pasangan yang diharapkan
341
  ExpectedDomicile *string `gorm:"column:expected_domicile" json:"expected_domicile"` // domisili pasangan yang diharapkan
342
  AcceptedMaritalStatus *pq.StringArray `gorm:"column:accepted_marital_status;type:varchar(255)[]" json:"accepted_marital_status"` // status pernikahan yang diterima
343
  PartnerFamilyReligion *string `gorm:"column:partner_family_religion" json:"partner_family_religion"` // agama yang dianut keluarga pasangan
 
346
  PartnerCanCook *string `gorm:"column:partner_can_cook" json:"partner_can_cook"` // apakah pasangan diharapkan bisa memasak
347
 
348
  // Kriteria Fisik
349
+ ExpectedBodyShapes *pq.StringArray `gorm:"column:expected_body_shapes;type:varchar(255)[]" json:"expected_body_shapes"` // bentuk tubuh yang diharapkan
350
+ ExpectedSkinColors *pq.StringArray `gorm:"column:expected_skin_colors;type:varchar(255)[]" json:"expected_skin_colors"` // warna kulit yang diharapkan
351
+ ExpectedHairTypes *pq.StringArray `gorm:"column:expected_hair_types;type:varchar(255)[]" json:"expected_hair_types"` // tipe rambut yang diharapkan
352
+ ExpectedHairThickness *pq.StringArray `gorm:"column:expected_hair_thickness;type:varchar(255)[]" json:"expected_hair_thickness"` // jenis rambut yang diharapkan
353
+ ExpectedMinHeightLimit *int `gorm:"column:expected_min_height_limit" json:"expected_min_height_limit"` // tinggi badan yang diharapkan
354
+ ExpectedMaxHeightLimit *int `gorm:"column:expected_max_height_limit" json:"expected_max_height_limit"` // tinggi badan yang diharapkan
355
 
356
  // Pendidikan & Pekerjaan
357
  ExpectedPartnerIncome *string `gorm:"column:expected_partner_income" json:"expected_partner_income"` // penghasilan pasangan per bulan yang diharapkan
space/router/router.go CHANGED
@@ -16,7 +16,6 @@ func (s *Server) setupRoutes() {
16
  QuizRoute(s.router)
17
 
18
  // another way to register routes
19
- s.HealthCheckRoute()
20
  s.CVRoute()
21
  s.MarriageReadinessProfileRoute()
22
  s.PartnerCriteriaRoute()
 
16
  QuizRoute(s.router)
17
 
18
  // another way to register routes
 
19
  s.CVRoute()
20
  s.MarriageReadinessProfileRoute()
21
  s.PartnerCriteriaRoute()
space/router/server.go CHANGED
@@ -2,7 +2,6 @@ package router
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
  partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
8
  "github.com/gin-gonic/gin"
@@ -10,14 +9,12 @@ import (
10
 
11
  type Server struct {
12
  router *gin.Engine
13
- healthCheckController health_check_controller.HealthCheckController
14
  cvController cv_controller.CVController
15
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController
16
  partnerCriteriaController partner_criteria_controller.PartnerCriteriaController
17
  }
18
 
19
  func NewServer(
20
- healthCheckController health_check_controller.HealthCheckController,
21
  cvController cv_controller.CVController,
22
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController,
23
  partnerCriteriaController partner_criteria_controller.PartnerCriteriaController,
@@ -27,7 +24,6 @@ func NewServer(
27
  router.Use(gin.Recovery())
28
 
29
  server := &Server{
30
- healthCheckController: healthCheckController,
31
  cvController: cvController,
32
  marriageReadinessProfileController: marriageReadinessProfileController,
33
  partnerCriteriaController: partnerCriteriaController,
 
2
 
3
  import (
4
  cv_controller "api.qobiltu.id/controller/cv"
 
5
  marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
6
  partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
7
  "github.com/gin-gonic/gin"
 
9
 
10
  type Server struct {
11
  router *gin.Engine
 
12
  cvController cv_controller.CVController
13
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController
14
  partnerCriteriaController partner_criteria_controller.PartnerCriteriaController
15
  }
16
 
17
  func NewServer(
 
18
  cvController cv_controller.CVController,
19
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController,
20
  partnerCriteriaController partner_criteria_controller.PartnerCriteriaController,
 
24
  router.Use(gin.Recovery())
25
 
26
  server := &Server{
 
27
  cvController: cvController,
28
  marriageReadinessProfileController: marriageReadinessProfileController,
29
  partnerCriteriaController: partnerCriteriaController,
space/services/partner_criteria_service.go CHANGED
@@ -42,7 +42,8 @@ func (s *partnerCriteriaService) SavePartnerCriteria(ctx context.Context, req *m
42
 
43
  partnerCriteria.AccountID = req.AccountID
44
 
45
- utils.AssignIfNotNil(&partnerCriteria.ExpectedAgeLimit, req.ExpectedAgeLimit)
 
46
  utils.AssignIfNotNil(&partnerCriteria.ExpectedDomicile, req.ExpectedDomicile)
47
  utils.AssignIfNotNil(&partnerCriteria.AcceptedMaritalStatus, req.AcceptedMaritalStatus)
48
  utils.AssignIfNotNil(&partnerCriteria.PartnerFamilyReligion, req.PartnerFamilyReligion)
@@ -54,7 +55,8 @@ func (s *partnerCriteriaService) SavePartnerCriteria(ctx context.Context, req *m
54
  utils.AssignIfNotNil(&partnerCriteria.ExpectedSkinColors, req.ExpectedSkinColors)
55
  utils.AssignIfNotNil(&partnerCriteria.ExpectedHairTypes, req.ExpectedHairTypes)
56
  utils.AssignIfNotNil(&partnerCriteria.ExpectedHairThickness, req.ExpectedHairThickness)
57
- utils.AssignIfNotNil(&partnerCriteria.ExpectedHeight, req.ExpectedHeight)
 
58
 
59
  utils.AssignIfNotNil(&partnerCriteria.ExpectedPartnerIncome, req.ExpectedPartnerIncome)
60
  utils.AssignIfNotNil(&partnerCriteria.ExpectedIncomeSources, req.ExpectedIncomeSources)
 
42
 
43
  partnerCriteria.AccountID = req.AccountID
44
 
45
+ utils.AssignIfNotNil(&partnerCriteria.ExpecteMinAgeLimit, req.ExpecteMinAgeLimit)
46
+ utils.AssignIfNotNil(&partnerCriteria.ExpecteMaxAgeLimit, req.ExpecteMaxAgeLimit)
47
  utils.AssignIfNotNil(&partnerCriteria.ExpectedDomicile, req.ExpectedDomicile)
48
  utils.AssignIfNotNil(&partnerCriteria.AcceptedMaritalStatus, req.AcceptedMaritalStatus)
49
  utils.AssignIfNotNil(&partnerCriteria.PartnerFamilyReligion, req.PartnerFamilyReligion)
 
55
  utils.AssignIfNotNil(&partnerCriteria.ExpectedSkinColors, req.ExpectedSkinColors)
56
  utils.AssignIfNotNil(&partnerCriteria.ExpectedHairTypes, req.ExpectedHairTypes)
57
  utils.AssignIfNotNil(&partnerCriteria.ExpectedHairThickness, req.ExpectedHairThickness)
58
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedMinHeightLimit, req.ExpectedMinHeightLimit)
59
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedMaxHeightLimit, req.ExpectedMaxHeightLimit)
60
 
61
  utils.AssignIfNotNil(&partnerCriteria.ExpectedPartnerIncome, req.ExpectedPartnerIncome)
62
  utils.AssignIfNotNil(&partnerCriteria.ExpectedIncomeSources, req.ExpectedIncomeSources)
space/space/pkg/validation/validation.go CHANGED
@@ -1,174 +1,35 @@
1
  package validation
2
 
3
  import (
4
- "errors"
5
- "fmt"
6
- "reflect"
7
- "strings"
8
 
9
- "github.com/go-playground/locales/en"
10
- "github.com/go-playground/locales/id"
11
- ut "github.com/go-playground/universal-translator"
12
- v10 "github.com/go-playground/validator/v10"
13
- entranslations "github.com/go-playground/validator/v10/translations/en"
14
- idtranslations "github.com/go-playground/validator/v10/translations/id"
15
  )
16
 
17
- // Constants for supported locales
18
- const (
19
- LocaleID = "id"
20
- LocaleEN = "en"
21
- )
22
-
23
- // ErrorMessage represents a validation error message
24
  type ErrorMessage struct {
25
  Field string `json:"field"`
26
  Message string `json:"-"`
27
  }
28
 
29
- type validator struct {
30
- validate *v10.Validate
31
- translator ut.Translator
32
- }
33
-
34
- // validatorInstance adalah instance global dari validator.
35
- var validatorInstance *validator
36
-
37
- // New creates a new validation instance with the specified locale
38
- // dan menginisialisasi instance global validatorInstance.
39
- func New(locale string) error {
40
- v := &validator{}
41
- parsedLocale := parseLocale(locale)
42
-
43
- uni := ut.New(en.New(), id.New(), en.New())
44
- translator, found := uni.GetTranslator(parsedLocale)
45
- if !found {
46
- return fmt.Errorf("translator not found for locale: %s", parsedLocale)
47
- }
48
-
49
- validate := v10.New()
50
-
51
- if err := setupValidations(validate); err != nil {
52
- return fmt.Errorf("failed to setup validations: %w", err)
53
- }
54
-
55
- if err := setupTranslations(validate, translator, parsedLocale); err != nil {
56
- return fmt.Errorf("failed to setup translations for locale %s: %w", parsedLocale, err)
57
- }
58
-
59
- v.validate = validate
60
- v.translator = translator
61
-
62
- validatorInstance = v // Inisialisasi instance global
63
- return nil
64
- }
65
-
66
- func parseLocale(locale string) string {
67
- switch strings.ToLower(locale) {
68
- case "id":
69
- return LocaleID
70
- case "en":
71
- return LocaleEN
72
- default:
73
- return LocaleID // Default to Indonesian
74
- }
75
- }
76
-
77
- // setupValidations configures custom validation rules.
78
- func setupValidations(validate *v10.Validate) error {
79
- rules := NewValidatorRules(&InMemoryOptionSource{})
80
- if err := rules.RegisterAllCustomRules(validate); err != nil {
81
- return err
82
- }
83
-
84
- return nil
85
- }
86
 
87
- // setupTranslations configures translations for validation messages.
88
- func setupTranslations(validate *v10.Validate, translator ut.Translator, locale string) error {
89
- // Register default translations based on locale
90
- if err := registerDefaultTranslations(validate, translator, locale); err != nil {
91
- return fmt.Errorf("failed to register default translations for locale %s: %w", locale, err)
92
- }
93
-
94
- // Register custom password validation translation
95
- err := validate.RegisterTranslation("password", translator,
96
- func(ut ut.Translator) error {
97
- return ut.Add("password", "harus mengandung minimal 8 karakter, huruf besar, huruf kecil, dan angka.", true)
98
- },
99
- func(ut ut.Translator, fe v10.FieldError) string {
100
- translated, err := ut.T(fe.Tag(), fe.Field())
101
- if err != nil {
102
- return fe.Field() + " is invalid"
103
- }
104
- return translated
105
- },
106
- )
107
  if err != nil {
108
- return fmt.Errorf("failed to register password translation: %w", err)
109
- }
110
-
111
- return nil
112
- }
113
-
114
- // registerDefaultTranslations sets up default translations for the specified locale.
115
- func registerDefaultTranslations(validate *v10.Validate, translator ut.Translator, locale string) error {
116
- switch locale {
117
- case LocaleID:
118
- return idtranslations.RegisterDefaultTranslations(validate, translator)
119
- case LocaleEN:
120
- return entranslations.RegisterDefaultTranslations(validate, translator)
121
- default:
122
- // Fallback to English if the locale is not supported
123
- return entranslations.RegisterDefaultTranslations(validate, translator)
124
  }
125
- }
126
-
127
- // Validate validates a struct using the global validator instance
128
- // and returns a slice of ErrorMessage.
129
- func Validate(s any) []ErrorMessage {
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
-
142
- // TranslateError takes a validation error and translates it using the global translator.
143
- func TranslateError(err error) []ErrorMessage {
144
- if validatorInstance == nil {
145
- return nil
146
- }
147
-
148
- var validationErrors v10.ValidationErrors
149
- if !errors.As(err, &validationErrors) {
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"
165
- }
166
 
167
- errorMessages = append(errorMessages, ErrorMessage{
168
- Field: e.Tag(),
169
- Message: msg,
170
- })
171
  }
172
 
173
- return errorMessages
174
  }
 
1
  package validation
2
 
3
  import (
4
+ "context"
5
+ "time"
 
 
6
 
7
+ "gorm.io/gorm"
 
 
 
 
 
8
  )
9
 
 
 
 
 
 
 
 
10
  type ErrorMessage struct {
11
  Field string `json:"field"`
12
  Message string `json:"-"`
13
  }
14
 
15
+ func New(db *gorm.DB) (*Validator, error) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ // Create source with 5 minute cache expiry
18
+ expiry := 5 * time.Minute
19
+ dbSource, err := NewDBOptionSource(db, expiry)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  if err != nil {
21
+ return nil, err
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ // Start background refresh every 10 minutes
25
+ ctx := context.Background()
26
+ dbSource.StartAutoRefresh(ctx, 10*time.Minute)
 
27
 
28
+ // create validator
29
+ validator := NewValidator(dbSource)
30
+ if err := validator.RegisterAllCustomRules(); err != nil {
31
+ return nil, err
32
  }
33
 
34
+ return validator, nil
35
  }
space/space/response/validation.go CHANGED
@@ -3,12 +3,29 @@ package response
3
  import (
4
  "api.qobiltu.id/models"
5
  "api.qobiltu.id/pkg/validation"
 
6
  )
7
 
8
- func HandleValidationError(validationErrors []validation.ErrorMessage) error {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  return models.Exception{
10
  ValidationError: true,
11
  Message: "Validation failed",
12
- ValidationErrorFields: validationErrors,
13
  }
14
  }
 
3
  import (
4
  "api.qobiltu.id/models"
5
  "api.qobiltu.id/pkg/validation"
6
+ "github.com/go-playground/validator/v10"
7
  )
8
 
9
+ func HandleValidationError(err error) error {
10
+ validationErrors, ok := err.(validator.ValidationErrors)
11
+ if !ok {
12
+ return models.Exception{
13
+ ValidationError: true,
14
+ Message: "Validation failed",
15
+ }
16
+ }
17
+
18
+ validationErrorMessages := make([]validation.ErrorMessage, len(validationErrors))
19
+ for i, err := range validationErrors {
20
+ validationErrorMessages[i] = validation.ErrorMessage{
21
+ Field: err.Field(),
22
+ Message: err.Error(),
23
+ }
24
+ }
25
+
26
  return models.Exception{
27
  ValidationError: true,
28
  Message: "Validation failed",
29
+ ValidationErrorFields: validationErrorMessages,
30
  }
31
  }
space/space/services/marriage_readiness_profile_service.go CHANGED
@@ -19,14 +19,15 @@ type MarriageReadinessProfileService interface {
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
 
 
19
 
20
  type marriageReadinessProfileService struct {
21
  marriageReadinessProfileRepository repositories.MarriageReadinessProfileRepository
22
+ validator *validation.Validator
23
  }
24
 
25
+ func NewMarriageReadinessProfileService(marriageReadinessProfileRepository repositories.MarriageReadinessProfileRepository, validator *validation.Validator) MarriageReadinessProfileService {
26
+ return &marriageReadinessProfileService{marriageReadinessProfileRepository: marriageReadinessProfileRepository, validator: validator}
27
  }
28
 
29
  func (s *marriageReadinessProfileService) SaveMarriageReadinessProfile(ctx context.Context, req *models.SaveMarriageReadinessProfileRequest) (*models.MarriageReadinessProfile, error) {
30
+ if err := s.validator.Validate(req); err != nil {
31
  return nil, response.HandleValidationError(err)
32
  }
33
 
space/space/services/partner_criteria_service.go CHANGED
@@ -19,14 +19,15 @@ type PartnerCriteriaService interface {
19
 
20
  type partnerCriteriaService struct {
21
  partnerCriteriaRepository repositories.PartnerCriteriaRepository
 
22
  }
23
 
24
- func NewPartnerCriteriaService(partnerCriteriaRepository repositories.PartnerCriteriaRepository) PartnerCriteriaService {
25
- return &partnerCriteriaService{partnerCriteriaRepository: partnerCriteriaRepository}
26
  }
27
 
28
  func (s *partnerCriteriaService) SavePartnerCriteria(ctx context.Context, req *models.SavePartnerCriteriaRequest) (*models.PartnerCriteria, error) {
29
- if err := validation.Validate(req); err != nil {
30
  return nil, response.HandleValidationError(err)
31
  }
32
 
 
19
 
20
  type partnerCriteriaService struct {
21
  partnerCriteriaRepository repositories.PartnerCriteriaRepository
22
+ validator *validation.Validator
23
  }
24
 
25
+ func NewPartnerCriteriaService(partnerCriteriaRepository repositories.PartnerCriteriaRepository, validator *validation.Validator) PartnerCriteriaService {
26
+ return &partnerCriteriaService{partnerCriteriaRepository: partnerCriteriaRepository, validator: validator}
27
  }
28
 
29
  func (s *partnerCriteriaService) SavePartnerCriteria(ctx context.Context, req *models.SavePartnerCriteriaRequest) (*models.PartnerCriteria, error) {
30
+ if err := s.validator.Validate(req); err != nil {
31
  return nil, response.HandleValidationError(err)
32
  }
33
 
space/space/space/controller/partner_criteria/partner_criteria_controller.go ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package partner_criteria_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 PartnerCriteriaController interface {
14
+ SavePartnerCriteria(ctx *gin.Context)
15
+ GetPartnerCriteria(ctx *gin.Context)
16
+ }
17
+
18
+ type partnerCriteriaController struct {
19
+ partnerCriteriaService services.PartnerCriteriaService
20
+ }
21
+
22
+ func NewPartnerCriteriaController(partnerCriteriaService services.PartnerCriteriaService) PartnerCriteriaController {
23
+ return &partnerCriteriaController{
24
+ partnerCriteriaService: partnerCriteriaService,
25
+ }
26
+ }
27
+
28
+ func (c *partnerCriteriaController) SavePartnerCriteria(ctx *gin.Context) {
29
+ var req models.SavePartnerCriteriaRequest
30
+ if err := ctx.ShouldBindJSON(&req); err != nil {
31
+ response.HandleError(ctx, models.Exception{
32
+ Message: "Invalid body request",
33
+ BadRequest: true,
34
+ Err: err,
35
+ })
36
+ return
37
+ }
38
+
39
+ accountData := middleware.GetAccountData(ctx)
40
+ req.AccountID = int64(accountData.UserID)
41
+
42
+ res, err := c.partnerCriteriaService.SavePartnerCriteria(ctx, &req)
43
+ if err != nil {
44
+ response.HandleError(ctx, err)
45
+ return
46
+ }
47
+
48
+ response.HandleSuccess(ctx, http.StatusOK, "Partner criteria saved", res, nil)
49
+ }
50
+
51
+ func (c *partnerCriteriaController) GetPartnerCriteria(ctx *gin.Context) {
52
+ accountData := middleware.GetAccountData(ctx)
53
+ accountID := int64(accountData.UserID)
54
+
55
+ req := models.GetPartnerCriteriaRequest{
56
+ AccountID: accountID,
57
+ }
58
+
59
+ res, err := c.partnerCriteriaService.GetPartnerCriteria(ctx, &req)
60
+ if err != nil {
61
+ response.HandleError(ctx, err)
62
+ return
63
+ }
64
+
65
+ response.HandleSuccess(ctx, http.StatusOK, "Get partner criteria success", res, nil)
66
+ }
space/space/space/main.go CHANGED
@@ -9,6 +9,7 @@ import (
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"
@@ -23,7 +24,7 @@ import (
23
  func main() {
24
 
25
  // setup validation
26
- err := validation.New(validation.LocaleID)
27
  utils.FatalIfErr("failed to setup validator", err)
28
 
29
  // setup storage
@@ -59,13 +60,17 @@ func main() {
59
  healthCheckController := health_check_controller.NewHealthCheckController(healthCheckService)
60
 
61
  cvRepository := repositories.NewCVRepository(config.DB)
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)
@@ -76,6 +81,7 @@ func main() {
76
  healthCheckController,
77
  cvController,
78
  marriageReadinessProfileController,
 
79
  )
80
  utils.FatalIfErr("failed to create server", err)
81
 
 
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
+ partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
13
  "api.qobiltu.id/mail"
14
  "api.qobiltu.id/pkg/storage"
15
  "api.qobiltu.id/pkg/validation"
 
24
  func main() {
25
 
26
  // setup validation
27
+ validator, err := validation.New(config.DB)
28
  utils.FatalIfErr("failed to setup validator", err)
29
 
30
  // setup storage
 
60
  healthCheckController := health_check_controller.NewHealthCheckController(healthCheckService)
61
 
62
  cvRepository := repositories.NewCVRepository(config.DB)
63
+ cvService := services.NewCVService(cvRepository, localStorage, validator)
64
  cvController := cv_controller.NewCVController(cvService)
65
 
66
  marriageReadinessProfileRepository := repositories.NewMarriageReadinessProfileRepository(config.DB)
67
+ marriageReadinessProfileService := services.NewMarriageReadinessProfileService(marriageReadinessProfileRepository, validator)
68
  marriageReadinessProfileController := marriage_readiness_profile_controller.NewMarriageReadinessProfileController(marriageReadinessProfileService)
69
 
70
+ partnerCriteriaRepository := repositories.NewPartnerCriteriaRepository(config.DB)
71
+ partnerCriteriaService := services.NewPartnerCriteriaService(partnerCriteriaRepository, validator)
72
+ partnerCriteriaController := partner_criteria_controller.NewPartnerCriteriaController(partnerCriteriaService)
73
+
74
  // start task processor
75
  err = taskProcessor.Start()
76
  utils.FatalIfErr("failed to start task processor", err)
 
81
  healthCheckController,
82
  cvController,
83
  marriageReadinessProfileController,
84
+ partnerCriteriaController,
85
  )
86
  utils.FatalIfErr("failed to create server", err)
87
 
space/space/space/models/database_orm_model.go CHANGED
@@ -248,26 +248,27 @@ type (
248
  }
249
 
250
  WorshipAndReligiousUnderstandingCV struct {
251
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
252
- AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id" counter:"skip"`
253
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
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" counter:"skip"`
270
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
 
271
  FieldCounter
272
  }
273
 
@@ -388,6 +389,39 @@ type (
388
  }
389
  )
390
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  // Gorm table name settings
392
  func (Account) TableName() string { return "account" }
393
  func (AccountDetails) TableName() string { return "account_details" }
 
248
  }
249
 
250
  WorshipAndReligiousUnderstandingCV struct {
251
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
252
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id" counter:"skip"`
253
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
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
+ WeeklyReligiousStudyFrequency *string `gorm:"column:weekly_religious_study_frequency" json:"weekly_religious_study_frequency"` // kajian_yang_diikuti_dalam_sepekan
261
+ DaudFasting *string `gorm:"column:daud_fasting" json:"daud_fasting"` // puasa_daud
262
+ AyyamulBidhFasting *string `gorm:"column:ayyamul_bidh_fasting" json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
263
+ HajjOrUmrah *pq.StringArray `gorm:"column:hajj_or_umrah;type:varchar(255)[]" json:"hajj_or_umrah"` // ibadah_haji_umroh
264
+ ListeningToMusic *string `gorm:"column:listening_to_music" json:"listening_to_music"` // mendengarkan_musik
265
+ OpinionOnIkhtilat *string `gorm:"column:opinion_on_ikhtilat" json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
266
+ OpinionOnTouchingNonMahram *string `gorm:"column:opinion_on_touching_non_mahram" json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
267
+ OpinionOnVeil *string `gorm:"column:opinion_on_veil" json:"opinion_on_veil"` // pendapat_tentang_cadar
268
+ WeeklyReligiousStudies *string `gorm:"column:weekly_religious_studies" json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
269
+ FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
270
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"`
271
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
272
  FieldCounter
273
  }
274
 
 
389
  }
390
  )
391
 
392
+ type (
393
+ PartnerCriteria struct {
394
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
395
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
396
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
397
+
398
+ // Kriteria Umum
399
+ ExpectedAgeLimit *int `gorm:"column:expected_age_limit" json:"expected_age_limit"` // batas usia pasangan yang diharapkan
400
+ ExpectedDomicile *string `gorm:"column:expected_domicile" json:"expected_domicile"` // domisili pasangan yang diharapkan
401
+ AcceptedMaritalStatus *pq.StringArray `gorm:"column:accepted_marital_status;type:varchar(255)[]" json:"accepted_marital_status"` // status pernikahan yang diterima
402
+ PartnerFamilyReligion *string `gorm:"column:partner_family_religion" json:"partner_family_religion"` // agama yang dianut keluarga pasangan
403
+ PartnerWeeklyReligiousStudyFrequency *string `gorm:"column:partner_weekly_religious_study_frequency" json:"partner_weekly_religious_study_frequency"` // rata-rata jumlah kajian yang diikuti pasangan per pekan
404
+ PartnerMonthlySpendingEstimate *string `gorm:"column:partner_monthly_spending_estimate" json:"partner_monthly_spending_estimate"` // estimasi pengeluaran pasangan per bulan
405
+ PartnerCanCook *string `gorm:"column:partner_can_cook" json:"partner_can_cook"` // apakah pasangan diharapkan bisa memasak
406
+
407
+ // Kriteria Fisik
408
+ ExpectedBodyShapes *pq.StringArray `gorm:"column:expected_body_shapes;type:varchar(255)[]" json:"expected_body_shapes"` // bentuk tubuh yang diharapkan
409
+ ExpectedSkinColors *pq.StringArray `gorm:"column:expected_skin_colors;type:varchar(255)[]" json:"expected_skin_colors"` // warna kulit yang diharapkan
410
+ ExpectedHairTypes *pq.StringArray `gorm:"column:expected_hair_types;type:varchar(255)[]" json:"expected_hair_types"` // tipe rambut yang diharapkan
411
+ ExpectedHairThickness *pq.StringArray `gorm:"column:expected_hair_thickness;type:varchar(255)[]" json:"expected_hair_thickness"` // jenis rambut yang diharapkan
412
+ ExpectedHeight *int `gorm:"column:expected_height" json:"expected_height"` // tinggi badan yang diharapkan
413
+
414
+ // Pendidikan & Pekerjaan
415
+ ExpectedPartnerIncome *string `gorm:"column:expected_partner_income" json:"expected_partner_income"` // penghasilan pasangan per bulan yang diharapkan
416
+ ExpectedIncomeSources *pq.StringArray `gorm:"column:expected_income_sources;type:varchar(255)[]" json:"expected_income_sources"` // sumber penghasilan pasangan
417
+ ExpectedLastEducation *pq.StringArray `gorm:"column:expected_last_education;type:varchar(255)[]" json:"expected_last_education"` // pendidikan terakhir yang diharapkan
418
+ ExpectedJobType *string `gorm:"column:expected_job_type" json:"expected_job_type"` // jenis pekerjaan pasangan yang diharapkan
419
+
420
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
421
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
422
+ }
423
+ )
424
+
425
  // Gorm table name settings
426
  func (Account) TableName() string { return "account" }
427
  func (AccountDetails) TableName() string { return "account_details" }
space/space/space/models/request_model.go CHANGED
@@ -74,7 +74,7 @@ type (
74
  FavoriteFoodAndDrinks *string `json:"favorite_food_and_drinks"` // makanan dan minuman favorit
75
  CanCook *bool `json:"can_cook"` // bisa memasak
76
  TypesOfDishesCooked *string `json:"types_of_dishes_cooked"` // jenis masakan yang bisa dimasak
77
- MonthlyExpenses *string `json:"monthly_expenses" validate:"monthly_expenses"` // pengeluaran per bulan
78
  }
79
 
80
  GetPersonalityAndPreferenceRequest struct {
@@ -84,11 +84,11 @@ type (
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
 
@@ -96,11 +96,11 @@ type (
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
102
  Job *string `json:"job"` // Pekerjaan
103
- LastEducation *string `json:"last_education" validate:"last_education"` // Pendidikan terakhir
104
  Age *int `json:"age"` // Usia
105
  }
106
 
@@ -120,9 +120,9 @@ type (
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
@@ -139,10 +139,10 @@ type (
139
  DateOfBirth *time.Time `json:"date_of_birth"`
140
  PlaceOfBirth *string `json:"place_of_birth"`
141
  Domicile *string `json:"domicile"`
142
- MaritalStatus *string `json:"marital_status" validate:"marital_status"`
143
- LastEducation *string `json:"last_education" validate:"last_education"`
144
  LastJob *string `json:"last_job"`
145
- PhoneNumber *string `json:"phone_number" validate:"phone_number"`
146
  }
147
 
148
  GetAccountDetailsRequest struct {
@@ -150,22 +150,23 @@ type (
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 {
@@ -174,7 +175,7 @@ type (
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
@@ -184,7 +185,7 @@ type (
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
190
  YearStart *int `json:"year_start"` // tahun masuk
@@ -208,7 +209,7 @@ type (
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
 
@@ -218,7 +219,7 @@ type (
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
 
@@ -283,48 +284,82 @@ type (
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
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  FavoriteFoodAndDrinks *string `json:"favorite_food_and_drinks"` // makanan dan minuman favorit
75
  CanCook *bool `json:"can_cook"` // bisa memasak
76
  TypesOfDishesCooked *string `json:"types_of_dishes_cooked"` // jenis masakan yang bisa dimasak
77
+ MonthlyExpenses *string `json:"monthly_expenses" validate:"monthly-expenses"` // pengeluaran per bulan
78
  }
79
 
80
  GetPersonalityAndPreferenceRequest struct {
 
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
 
 
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
102
  Job *string `json:"job"` // Pekerjaan
103
+ LastEducation *string `json:"last_education" validate:"last-education"` // Pendidikan terakhir
104
  Age *int `json:"age"` // Usia
105
  }
106
 
 
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
 
139
  DateOfBirth *time.Time `json:"date_of_birth"`
140
  PlaceOfBirth *string `json:"place_of_birth"`
141
  Domicile *string `json:"domicile"`
142
+ MaritalStatus *string `json:"marital_status" validate:"marital-status"`
143
+ LastEducation *string `json:"last_education" validate:"last-education"`
144
  LastJob *string `json:"last_job"`
145
+ PhoneNumber *string `json:"phone_number" validate:"phone-number"`
146
  }
147
 
148
  GetAccountDetailsRequest struct {
 
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
+ WeeklyReligiousStudyFrequency *string `json:"weekly_religious_study_frequency" validate:"weekly-religious-study-frequency"` // kajian_yang_diikuti_dalam_sepekan
161
+ DaudFasting *string `json:"daud_fasting"` // puasa_daud
162
+ AyyamulBidhFasting *string `json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
163
+ HajjOrUmrah *pq.StringArray `json:"hajj_or_umrah"` // ibadah_haji_umroh
164
+ ListeningToMusic *string `json:"listening_to_music"` // mendengarkan_musik
165
+ OpinionOnIkhtilat *string `json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
166
+ OpinionOnTouchingNonMahram *string `json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
167
+ OpinionOnVeil *string `json:"opinion_on_veil"` // pendapat_tentang_cadar
168
+ WeeklyReligiousStudies *string `json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
169
+ FollowedUstadz *string `json:"followed_ustadz"` // ustadz_yang_diikuti
170
  }
171
 
172
  GetWorshipAndReligiousUnderstandingRequest struct {
 
175
 
176
  CreateEducationRequest struct {
177
  AccountID int64 `json:"-"`
178
+ LastEducation *string `json:"last_education" validate:"last-education"` // pendidikan terakhir
179
  EducationInstitute *string `json:"education_institute"` // institusi pendidikan
180
  EducationMajor *string `json:"education_major"` // jurusan pendidikan
181
  YearStart *int `json:"year_start"` // tahun masuk
 
185
  UpdateEducationRequest struct {
186
  ID int64 `json:"-"`
187
  AccountID int64 `json:"-"`
188
+ LastEducation *string `json:"last_education" validate:"last-education"` // pendidikan terakhir
189
  EducationInstitute *string `json:"education_institute"` // institusi pendidikan
190
  EducationMajor *string `json:"education_major"` // jurusan pendidikan
191
  YearStart *int `json:"year_start"` // tahun masuk
 
209
  InstitutionName *string `json:"institution_name"` // nama instansi
210
  CurrentJob *string `json:"current_job"` // pekerjaan saat ini
211
  YearStartedWorking *int `json:"year_started_working"` // tahun mulai bekerja
212
+ MonthlyIncome *string `json:"monthly_income" validate:"monthly-income"` // penghasilan per bulan
213
  IncomeSources *pq.StringArray `json:"income_sources"` // sumber penghasilan
214
  }
215
 
 
219
  InstitutionName *string `json:"institution_name"` // nama instansi
220
  CurrentJob *string `json:"current_job"` // pekerjaan saat ini
221
  YearStartedWorking *int `json:"year_started_working"` // tahun mulai bekerja
222
+ MonthlyIncome *string `json:"monthly_income" validate:"monthly-income"` // penghasilan per bulan
223
  IncomeSources *pq.StringArray `json:"income_sources"` // sumber penghasilan
224
  }
225
 
 
284
  }
285
  )
286
 
287
+ type (
288
+ SaveMarriageReadinessProfileRequest struct {
289
+ AccountID int64 `json:"account_id"`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
+ // Visi Misi Rumah Tangga
292
+ MarriageVision *string `gorm:"column:marriage_vision" json:"marriage_vision"` // Apa visi dan tujuan utama kamu dalam membangun rumah tangga?
293
+ LivingPlan *string `gorm:"column:living_plan" json:"living_plan"` // Setelah menikah, kamu berencana tinggal di mana?
294
+ SpouseRoles *string `gorm:"column:spouse_roles" json:"spouse_roles"` // Menurutmu, apa peran utama suami dan istri dalam rumah tangga?
295
+ SpouseRights *string `gorm:"column:spouse_rights" json:"spouse_rights"` // Apa saja hak suami dan istri menurutmu?
296
+ ConflictResolution *string `gorm:"column:conflict_resolution" json:"conflict_resolution"` // Jika terjadi konflik, bagaimana kamu menyikapinya?
297
+ ParentingStyle *string `gorm:"column:parenting_style" json:"parenting_style"` // Setelah punya anak, pola pengasuhan seperti apa yang kamu inginkan?
298
+
299
+ // Konsep Acara Pernikahan
300
+ ReadyToMarryDuration *string `gorm:"column:ready_to_marry_duration" json:"ready_to_marry_duration"` // Setelah taaruf dimulai, berapa lama kamu butuh untuk siap menikah?
301
+ WeddingConcept *string `gorm:"column:wedding_concept" json:"wedding_concept"` // Seperti apa konsep pernikahan yang kamu harapkan?
302
+ WeddingFunding *string `gorm:"column:wedding_funding" json:"wedding_funding"` // Apakah kamu sudah mempersiapkan dana pernikahan? Dari mana sumbernya?
303
+
304
+ // Karir Kedepannya
305
+ CareerAfterMarriage *string `gorm:"column:career_after_marriage" json:"career_after_marriage"` // Apakah kamu ingin tetap bekerja setelah menikah?
306
+ TimeManagement *string `gorm:"column:time_management" json:"time_management"` // Bagaimana kamu membagi waktu antara keluarga, ibadah, dan pekerjaan?
307
+ CareerGoals *string `gorm:"column:career_goals" json:"career_goals"` // Apa impian karier atau cita-cita kamu dalam 5 sampai 10 tahun ke depan?
308
+ SelfDevelopment *string `gorm:"column:self_development" json:"self_development"` // Apa usaha kamu untuk terus mengembangkan diri dan skill?
309
+
310
+ // Pendidikan Keluarga
311
+ DelayChildren *string `gorm:"column:delay_children" json:"delay_children"` // Apakah kamu berencana menunda memiliki anak?
312
+ ChildEducationPlan *string `gorm:"column:child_education_plan" json:"child_education_plan"` // Apakah kamu sudah punya rencana pendidikan anak?
313
+ ReligiousEmotionalBond *string `gorm:"column:religious_emotional_bond" json:"religious_emotional_bond"` // Bagaimana cara menjaga nilai agama & kedekatan emosional dengan anak?
314
+ ParentingChallenges *string `gorm:"column:parenting_challenges" json:"parenting_challenges"` // Saat menghadapi tantangan pengasuhan, bagaimana kamu menyikapinya?
315
+
316
+ // Finansial Keluarga
317
+ MonthlyFinance *string `gorm:"column:monthly_finance" json:"monthly_finance"` // Bagaimana kamu mengelola keuangan bulanan?
318
+ FamilyResponsibility *string `gorm:"column:family_responsibility" json:"family_responsibility"` // Apakah kamu masih punya tanggungan keluarga setelah menikah?
319
+ DebtStatus *string `gorm:"column:debt_status" json:"debt_status"` // Apakah kamu punya cicilan/utang setelah menikah?
320
+ FinanceSharing *string `gorm:"column:finance_sharing" json:"finance_sharing"` // Setelah menikah, bagaimana pembagian tanggung jawab keuangan?
321
+ IncomeGapView *string `gorm:"column:income_gap_view" json:"income_gap_view"` // Pandangan kamu jika istri berpenghasilan lebih besar dari suami?
322
+
323
+ // Keputusan dan Komunikasi
324
+ DecisionMaking *string `gorm:"column:decision_making" json:"decision_making"` // Dalam mengambil keputusan, kamu lebih mempertimbangkan pasangan atau sendiri?
325
+ GrowthTogether *string `gorm:"column:growth_together" json:"growth_together"` // Apakah kamu siap berjuang bersama pasangan? Apa saja yang ingin diperjuangkan?
326
+ ParentIntervention *string `gorm:"column:parent_intervention" json:"parent_intervention"` // Sejauh mana orang tua/mertua boleh ikut campur dalam rumah tangga?
327
+ HabitResponse *string `gorm:"column:habit_response" json:"habit_response"` // Bagaimana kamu menyikapi kebiasaan pasangan yang kurang kamu sukai?
328
+ }
329
+
330
+ GetMarriageReadinessProfileRequest struct {
331
+ AccountID int64 `json:"-"`
332
+ }
333
+ )
334
+
335
+ type (
336
+ SavePartnerCriteriaRequest struct {
337
+ AccountID int64 `json:"account_id"`
338
+
339
+ ExpectedAgeLimit *int `gorm:"column:expected_age_limit" json:"expected_age_limit"` // batas usia pasangan yang diharapkan
340
+ ExpectedDomicile *string `gorm:"column:expected_domicile" json:"expected_domicile"` // domisili pasangan yang diharapkan
341
+ AcceptedMaritalStatus *pq.StringArray `gorm:"column:accepted_marital_status;type:varchar(255)[]" json:"accepted_marital_status"` // status pernikahan yang diterima
342
+ PartnerFamilyReligion *string `gorm:"column:partner_family_religion" json:"partner_family_religion"` // agama yang dianut keluarga pasangan
343
+ PartnerWeeklyReligiousStudyFrequency *string `gorm:"column:partner_weekly_religious_study_frequency" json:"partner_weekly_religious_study_frequency"` // rata-rata jumlah kajian yang diikuti pasangan per pekan
344
+ PartnerMonthlySpendingEstimate *string `gorm:"column:partner_monthly_spending_estimate" json:"partner_monthly_spending_estimate"` // estimasi pengeluaran pasangan per bulan
345
+ PartnerCanCook *string `gorm:"column:partner_can_cook" json:"partner_can_cook"` // apakah pasangan diharapkan bisa memasak
346
+
347
+ // Kriteria Fisik
348
+ ExpectedBodyShapes *pq.StringArray `gorm:"column:expected_body_shapes;type:varchar(255)[]" json:"expected_body_shapes"` // bentuk tubuh yang diharapkan
349
+ ExpectedSkinColors *pq.StringArray `gorm:"column:expected_skin_colors;type:varchar(255)[]" json:"expected_skin_colors"` // warna kulit yang diharapkan
350
+ ExpectedHairTypes *pq.StringArray `gorm:"column:expected_hair_types;type:varchar(255)[]" json:"expected_hair_types"` // tipe rambut yang diharapkan
351
+ ExpectedHairThickness *pq.StringArray `gorm:"column:expected_hair_thickness;type:varchar(255)[]" json:"expected_hair_thickness"` // jenis rambut yang diharapkan
352
+ ExpectedHeight *int `gorm:"column:expected_height" json:"expected_height"` // tinggi badan yang diharapkan
353
+
354
+ // Pendidikan & Pekerjaan
355
+ ExpectedPartnerIncome *string `gorm:"column:expected_partner_income" json:"expected_partner_income"` // penghasilan pasangan per bulan yang diharapkan
356
+ ExpectedIncomeSources *pq.StringArray `gorm:"column:expected_income_sources;type:varchar(255)[]" json:"expected_income_sources"` // sumber penghasilan pasangan
357
+ ExpectedLastEducation *pq.StringArray `gorm:"column:expected_last_education;type:varchar(255)[]" json:"expected_last_education"` // pendidikan terakhir yang diharapkan
358
+ ExpectedJobType *string `gorm:"column:expected_job_type" json:"expected_job_type"` // jenis pekerjaan pasangan yang diharapkan
359
+
360
+ }
361
+
362
+ GetPartnerCriteriaRequest struct {
363
+ AccountID int64 `json:"-"`
364
+ }
365
+ )
space/space/space/pkg/validation/custom_rules.go CHANGED
@@ -1,105 +1,154 @@
1
  package validation
2
 
3
  import (
 
 
 
4
  "regexp"
5
  "strings"
6
  "sync"
 
7
 
8
  v10 "github.com/go-playground/validator/v10"
9
  "gorm.io/gorm"
10
  )
11
 
12
- type ValidOptionSource interface {
13
  GetValidOptions(key string) ([]string, error)
14
  GetValidKeys() []string
 
 
 
15
  }
16
 
17
  // --------------------
18
- // InMemoryOptionSource
19
  // --------------------
20
 
21
- type InMemoryOptionSource struct{}
22
-
23
- var inMemoryOptions = map[string][]string{
24
- "last_education": {"SD", "SMP", "SMA", "D1", "D2", "D3", "D4", "D5", "S1", "S2", "S3"},
25
- "marital_status": {"Belum Menikah", "Duda", "Janda"},
26
- "gender": {"Laki-laki", "Perempuan"},
27
- "monthly_expenses": {"< 2 Juta", "2-5 Juta", "5-20 Juta", "> 10 Juta"},
28
- "monthly_income": {"< 3 Juta", "3-5 Juta", "5-10 Juta", "> 10 Juta"},
29
- "religion": {"Islam", "Non-Islam"},
30
- "family_role": {"Ayah", "Ibu", "Kakak", "Adik", "Anak"},
31
- "life_status": {"Hidup", "Wafat"},
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
-
39
- func (s *InMemoryOptionSource) GetValidOptions(key string) ([]string, error) {
40
- return inMemoryOptions[key], nil
41
  }
42
 
43
- func (s *InMemoryOptionSource) GetValidKeys() []string {
44
- keys := make([]string, 0, len(inMemoryOptions))
45
- for k := range inMemoryOptions {
46
- keys = append(keys, k)
 
47
  }
48
- return keys
 
 
 
49
  }
50
 
51
- // --------------------
52
- // DBOptionSource
53
- // --------------------
54
 
55
- type DBOptionSource struct {
56
- options map[string][]string
57
- mu sync.RWMutex
58
- }
59
 
60
- type (
61
- OptionCategory struct {
62
- ID int64 `gorm:"primaryKey" json:"id"`
63
- OptionName string `json:"option_name"`
64
- OptionSlug string `json:"option_slug" gorm:"uniqueIndex"`
65
  }
66
 
67
- OptionValues struct {
68
- ID int64 `gorm:"primaryKey" json:"id"`
69
- OptionCategoryID int64 `json:"option_category_id"`
70
- OptionValue string `json:"option_value"`
 
 
 
 
 
 
 
71
  }
72
- )
73
 
74
- func NewDBOptionSource(db *gorm.DB) (*DBOptionSource, error) {
75
- var categories []OptionCategory
76
- if err := db.Find(&categories).Error; err != nil {
77
- return nil, err
78
  }
79
 
80
- options := make(map[string][]string)
81
- for _, cat := range categories {
82
- var values []OptionValues
83
- if err := db.Where("option_category_id = ?", cat.ID).Find(&values).Error; err != nil {
84
- return nil, err
85
- }
86
- for _, val := range values {
87
- options[cat.OptionSlug] = append(options[cat.OptionSlug], val.OptionValue)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  }
89
- }
 
 
 
 
 
90
 
91
- return &DBOptionSource{options: options}, nil
 
 
 
 
92
  }
93
 
94
  func (s *DBOptionSource) GetValidOptions(key string) ([]string, error) {
 
 
 
 
 
 
 
 
 
 
95
  s.mu.RLock()
96
  defer s.mu.RUnlock()
97
- return s.options[key], nil
 
 
 
 
 
 
 
 
98
  }
99
 
100
  func (s *DBOptionSource) GetValidKeys() []string {
101
  s.mu.RLock()
102
  defer s.mu.RUnlock()
 
103
  keys := make([]string, 0, len(s.options))
104
  for k := range s.options {
105
  keys = append(keys, k)
@@ -108,58 +157,144 @@ func (s *DBOptionSource) GetValidKeys() []string {
108
  }
109
 
110
  // --------------------
111
- // Validator
112
  // --------------------
113
 
114
  type Validator struct {
115
- source ValidOptionSource
 
116
  }
117
 
118
- func NewValidatorRules(source ValidOptionSource) *Validator {
119
- return &Validator{source: source}
 
 
 
 
120
  }
121
 
122
- func (v *Validator) GenericOptionRule(key string) func(fl v10.FieldLevel) bool {
123
- return func(fl v10.FieldLevel) bool {
124
- value := fl.Field().String()
125
- if value == "" {
126
- return true
 
 
 
 
 
127
  }
128
- validOptions, err := v.source.GetValidOptions(key)
129
- if err != nil {
130
- return false
 
 
 
131
  }
132
- for _, opt := range validOptions {
133
- if opt == value {
134
- return true
135
- }
136
  }
137
- return false
138
  }
 
 
139
  }
 
 
 
 
140
 
141
- func (v *Validator) RegisterAllCustomRules(validate *v10.Validate) error {
142
- for _, key := range v.source.GetValidKeys() {
143
- err := validate.RegisterValidation(key, v.GenericOptionRule(key))
144
- if err != nil {
145
- return err
146
  }
 
147
  }
148
 
149
- err := validate.RegisterValidation("password", v.PasswordRule)
150
- if err != nil {
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
 
162
- func (v *Validator) PasswordRule(fl v10.FieldLevel) bool {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  password := fl.Field().String()
164
  return len(password) >= 8 &&
165
  strings.ContainsAny(password, "abcdefghijklmnopqrstuvwxyz") &&
@@ -167,38 +302,23 @@ func (v *Validator) PasswordRule(fl v10.FieldLevel) bool {
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
  }
 
1
  package validation
2
 
3
  import (
4
+ "context"
5
+ "fmt"
6
+ "reflect"
7
  "regexp"
8
  "strings"
9
  "sync"
10
+ "time"
11
 
12
  v10 "github.com/go-playground/validator/v10"
13
  "gorm.io/gorm"
14
  )
15
 
16
+ type ValidatorOptionSource interface {
17
  GetValidOptions(key string) ([]string, error)
18
  GetValidKeys() []string
19
+ Refresh() error
20
+ StartAutoRefresh(ctx context.Context, interval time.Duration)
21
+ HasKey(key string) bool
22
  }
23
 
24
  // --------------------
25
+ // DBOptionSource with Safe Handling
26
  // --------------------
27
 
28
+ type DBOptionSource struct {
29
+ db *gorm.DB
30
+ options map[string][]string
31
+ mu sync.RWMutex
32
+ lastUpdate time.Time
33
+ expiry time.Duration
34
+ stopChan chan struct{}
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  }
36
 
37
+ func NewDBOptionSource(db *gorm.DB, expiry time.Duration) (*DBOptionSource, error) {
38
+ source := &DBOptionSource{
39
+ db: db,
40
+ expiry: expiry,
41
+ stopChan: make(chan struct{}),
42
  }
43
+ if err := source.Refresh(); err != nil {
44
+ return nil, fmt.Errorf("failed to initialize DB option source: %w", err)
45
+ }
46
+ return source, nil
47
  }
48
 
49
+ func (s *DBOptionSource) Refresh() error {
50
+ s.mu.Lock()
51
+ defer s.mu.Unlock()
52
 
53
+ // Buat session baru tanpa transaction
54
+ tx := s.db.Session(&gorm.Session{SkipDefaultTransaction: true})
 
 
55
 
56
+ var results []struct {
57
+ Slug string `gorm:"column:slug"`
58
+ Value string `gorm:"column:value"`
 
 
59
  }
60
 
61
+ err := tx.Raw(`
62
+ SELECT
63
+ c.option_slug AS slug,
64
+ v.option_value AS value
65
+ FROM option_categories c
66
+ JOIN option_values v ON c.id = v.option_category_id
67
+ ORDER BY c.id, v.id
68
+ `).Scan(&results).Error
69
+
70
+ if err != nil {
71
+ return fmt.Errorf("failed to refresh options: %w", err)
72
  }
 
73
 
74
+ newOptions := make(map[string][]string)
75
+ for _, r := range results {
76
+ newOptions[r.Slug] = append(newOptions[r.Slug], r.Value)
 
77
  }
78
 
79
+ s.options = newOptions
80
+ s.lastUpdate = time.Now()
81
+
82
+ fmt.Println("options refreshed")
83
+
84
+ return nil
85
+ }
86
+
87
+ func (s *DBOptionSource) StartAutoRefresh(ctx context.Context, interval time.Duration) {
88
+ ticker := time.NewTicker(interval)
89
+ go func() {
90
+ for {
91
+ select {
92
+ case <-ticker.C:
93
+ s.mu.Lock()
94
+ needsRefresh := time.Since(s.lastUpdate) > s.expiry
95
+ s.mu.Unlock()
96
+
97
+ if needsRefresh {
98
+ if err := s.Refresh(); err != nil {
99
+ fmt.Printf("failed to auto-refresh options: %v\n", err)
100
+ }
101
+ }
102
+ case <-ctx.Done():
103
+ ticker.Stop()
104
+ return
105
+ case <-s.stopChan:
106
+ ticker.Stop()
107
+ return
108
+ }
109
  }
110
+ }()
111
+ }
112
+
113
+ func (s *DBOptionSource) StopAutoRefresh() {
114
+ close(s.stopChan)
115
+ }
116
 
117
+ func (s *DBOptionSource) HasKey(key string) bool {
118
+ s.mu.RLock()
119
+ defer s.mu.RUnlock()
120
+ _, exists := s.options[key]
121
+ return exists
122
  }
123
 
124
  func (s *DBOptionSource) GetValidOptions(key string) ([]string, error) {
125
+ s.mu.RLock()
126
+ needsRefresh := time.Since(s.lastUpdate) > s.expiry
127
+ s.mu.RUnlock()
128
+
129
+ if needsRefresh {
130
+ if err := s.Refresh(); err != nil {
131
+ return nil, fmt.Errorf("failed to refresh options: %w", err)
132
+ }
133
+ }
134
+
135
  s.mu.RLock()
136
  defer s.mu.RUnlock()
137
+
138
+ options, exists := s.options[key]
139
+ if !exists {
140
+ return nil, nil // Return nil instead of error for missing keys
141
+ }
142
+
143
+ copied := make([]string, len(options))
144
+ copy(copied, options)
145
+ return copied, nil
146
  }
147
 
148
  func (s *DBOptionSource) GetValidKeys() []string {
149
  s.mu.RLock()
150
  defer s.mu.RUnlock()
151
+
152
  keys := make([]string, 0, len(s.options))
153
  for k := range s.options {
154
  keys = append(keys, k)
 
157
  }
158
 
159
  // --------------------
160
+ // Validator with Safe Rule Handling
161
  // --------------------
162
 
163
  type Validator struct {
164
+ source ValidatorOptionSource
165
+ validate *v10.Validate
166
  }
167
 
168
+ func NewValidator(source ValidatorOptionSource) *Validator {
169
+ validate := v10.New()
170
+ return &Validator{
171
+ source: source,
172
+ validate: validate,
173
+ }
174
  }
175
 
176
+ func (v *Validator) RegisterAllCustomRules() error {
177
+ // First register static validation rules
178
+ staticRules := map[string]func(v10.FieldLevel) bool{
179
+ "password": v.validatePassword,
180
+ "phone-number": v.validatePhoneNumber,
181
+ }
182
+
183
+ for name, fn := range staticRules {
184
+ if err := v.validate.RegisterValidation(name, fn); err != nil {
185
+ return fmt.Errorf("failed to register %s validation: %w", name, err)
186
  }
187
+ }
188
+
189
+ // Then register dynamic option rules
190
+ for _, key := range v.source.GetValidKeys() {
191
+ if !v.source.HasKey(key) {
192
+ continue // Skip if key doesn't exist
193
  }
194
+
195
+ if err := v.validate.RegisterValidation(key, v.createOptionRule(key)); err != nil {
196
+ return fmt.Errorf("failed to register validation for %s: %w", key, err)
 
197
  }
 
198
  }
199
+
200
+ return nil
201
  }
202
+ func (v *Validator) Validate(input interface{}) error {
203
+ if input == nil {
204
+ return nil
205
+ }
206
 
207
+ val := reflect.ValueOf(input)
208
+ if val.Kind() == reflect.Ptr {
209
+ if val.IsNil() {
210
+ return nil
 
211
  }
212
+ val = val.Elem() // Dereference the pointer
213
  }
214
 
215
+ err := v.validate.Struct(input)
216
+ if err == nil {
217
+ return nil
218
  }
219
 
220
+ // Filter out errors for nil/empty fields
221
+ if ve, ok := err.(v10.ValidationErrors); ok {
222
+ var filteredErrors v10.ValidationErrors
223
+ for _, fe := range ve {
224
+ fieldValue := val.FieldByName(fe.StructField())
225
+ if !fieldValue.IsValid() {
226
+ continue
227
+ }
228
+
229
+ if !isEmpty(fieldValue) {
230
+ filteredErrors = append(filteredErrors, fe)
231
+ } else {
232
+ fmt.Printf("Ignoring validation error for empty field: %s\n", fe.Field())
233
+ }
234
+ }
235
+
236
+ if len(filteredErrors) > 0 {
237
+ return filteredErrors
238
+ }
239
+ return nil
240
  }
241
 
242
+ return err
243
+ }
244
+
245
+ // isEmpty checks if a value is nil or empty
246
+ func isEmpty(v reflect.Value) bool {
247
+ switch v.Kind() {
248
+ case reflect.String:
249
+ return v.Len() == 0
250
+ case reflect.Ptr, reflect.Interface:
251
+ return v.IsNil()
252
+ case reflect.Slice, reflect.Map, reflect.Array:
253
+ return v.Len() == 0
254
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
255
+ return v.Int() == 0
256
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
257
+ return v.Uint() == 0
258
+ case reflect.Float32, reflect.Float64:
259
+ return v.Float() == 0
260
+ case reflect.Bool:
261
+ return !v.Bool()
262
+ case reflect.Struct:
263
+ if t, ok := v.Interface().(time.Time); ok {
264
+ return t.IsZero()
265
+ }
266
+ // Consider non-time structs as non-empty
267
+ return false
268
+ default:
269
+ return false
270
+ }
271
  }
272
 
273
+ // createOptionRule remains the same as previous version
274
+ func (v *Validator) createOptionRule(key string) func(v10.FieldLevel) bool {
275
+ return func(fl v10.FieldLevel) bool {
276
+ field := fl.Field()
277
+ if isEmpty(field) {
278
+ return true
279
+ }
280
+
281
+ value := field.String()
282
+ validOptions, err := v.source.GetValidOptions(key)
283
+ if err != nil || validOptions == nil {
284
+ return true
285
+ }
286
+
287
+ for _, opt := range validOptions {
288
+ if opt == value {
289
+ return true
290
+ }
291
+ }
292
+
293
+ return false
294
+ }
295
+ }
296
+
297
+ func (v *Validator) validatePassword(fl v10.FieldLevel) bool {
298
  password := fl.Field().String()
299
  return len(password) >= 8 &&
300
  strings.ContainsAny(password, "abcdefghijklmnopqrstuvwxyz") &&
 
302
  strings.ContainsAny(password, "0123456789")
303
  }
304
 
305
+ func (v *Validator) validatePhoneNumber(fl v10.FieldLevel) bool {
306
+ phone := NormalizePhoneNumber(fl.Field().String())
307
  return strings.HasPrefix(phone, "+62")
308
  }
309
 
310
+ func NormalizePhoneNumber(input string) string {
 
 
 
 
 
 
 
311
  re := regexp.MustCompile(`[^0-9\+]`)
312
  input = re.ReplaceAllString(input, "")
313
 
314
+ switch {
315
+ case strings.HasPrefix(input, "0"):
316
+ return "+62" + input[1:]
317
+ case strings.HasPrefix(input, "62") && !strings.HasPrefix(input, "+62"):
318
+ return "+" + input
319
+ case strings.HasPrefix(input, "8"):
320
+ return "+62" + input
321
+ default:
322
+ return input
323
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  }
space/space/space/repositories/partner_criteria_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 PartnerCriteriaRepository interface {
11
+ SavePartnerCriteria(ctx context.Context, req *models.PartnerCriteria) (*models.PartnerCriteria, error)
12
+ GetPartnerCriteria(ctx context.Context, accountID int64) (*models.PartnerCriteria, error)
13
+ }
14
+
15
+ type partnerCriteriaRepository struct {
16
+ db *gorm.DB
17
+ }
18
+
19
+ func NewPartnerCriteriaRepository(db *gorm.DB) PartnerCriteriaRepository {
20
+ return &partnerCriteriaRepository{
21
+ db: db,
22
+ }
23
+ }
24
+
25
+ func (r *partnerCriteriaRepository) SavePartnerCriteria(ctx context.Context, req *models.PartnerCriteria) (*models.PartnerCriteria, 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 *partnerCriteriaRepository) GetPartnerCriteria(ctx context.Context, accountID int64) (*models.PartnerCriteria, error) {
33
+ var partnerCriteria models.PartnerCriteria
34
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&partnerCriteria).Error; err != nil {
35
+ return nil, err
36
+ }
37
+ return &partnerCriteria, nil
38
+ }
space/space/space/router/partner_criteria_route.go ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package router
2
+
3
+ import "api.qobiltu.id/middleware"
4
+
5
+ func (s *Server) PartnerCriteriaRoute() {
6
+ routerGroup := s.router.Group("/api/v1/partner-criteria").Use(middleware.AuthUser)
7
+ {
8
+ routerGroup.POST("", s.partnerCriteriaController.SavePartnerCriteria)
9
+ routerGroup.GET("", s.partnerCriteriaController.GetPartnerCriteria)
10
+ }
11
+ }
space/space/space/router/router.go CHANGED
@@ -19,5 +19,6 @@ func (s *Server) setupRoutes() {
19
  s.HealthCheckRoute()
20
  s.CVRoute()
21
  s.MarriageReadinessProfileRoute()
 
22
  s.StorageRoute()
23
  }
 
19
  s.HealthCheckRoute()
20
  s.CVRoute()
21
  s.MarriageReadinessProfileRoute()
22
+ s.PartnerCriteriaRoute()
23
  s.StorageRoute()
24
  }
space/space/space/router/server.go CHANGED
@@ -4,6 +4,7 @@ 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
 
@@ -12,12 +13,14 @@ type Server struct {
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()
@@ -27,6 +30,7 @@ func NewServer(
27
  healthCheckController: healthCheckController,
28
  cvController: cvController,
29
  marriageReadinessProfileController: marriageReadinessProfileController,
 
30
  router: router,
31
  }
32
 
 
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
+ partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
8
  "github.com/gin-gonic/gin"
9
  )
10
 
 
13
  healthCheckController health_check_controller.HealthCheckController
14
  cvController cv_controller.CVController
15
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController
16
+ partnerCriteriaController partner_criteria_controller.PartnerCriteriaController
17
  }
18
 
19
  func NewServer(
20
  healthCheckController health_check_controller.HealthCheckController,
21
  cvController cv_controller.CVController,
22
  marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController,
23
+ partnerCriteriaController partner_criteria_controller.PartnerCriteriaController,
24
  ) (*Server, error) {
25
 
26
  router := gin.Default()
 
30
  healthCheckController: healthCheckController,
31
  cvController: cvController,
32
  marriageReadinessProfileController: marriageReadinessProfileController,
33
+ partnerCriteriaController: partnerCriteriaController,
34
  router: router,
35
  }
36
 
space/space/space/services/cv_service.go CHANGED
@@ -60,17 +60,19 @@ type CVService interface {
60
  type cvService struct {
61
  cvRepository repositories.CVRepository
62
  storage storage.Storage
 
63
  }
64
 
65
- func NewCVService(cvRepository repositories.CVRepository, storage storage.Storage) CVService {
66
  return &cvService{
67
  cvRepository: cvRepository,
68
  storage: storage,
 
69
  }
70
  }
71
 
72
  func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAccountDetailsRequest) (*models.AccountDetails, error) {
73
- if err := validation.Validate(req); err != nil {
74
  return nil, response.HandleValidationError(err)
75
  }
76
 
@@ -124,8 +126,8 @@ func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAcco
124
  utils.AssignIfNotNil(&accountDetails.LastJob, req.LastJob)
125
 
126
  if req.PhoneNumber != nil {
127
- sanitizedPhone := validation.SanitizePhoneNumber(*req.PhoneNumber)
128
- accountDetails.PhoneNumber = &sanitizedPhone
129
  }
130
 
131
  // Simpan ke database
@@ -146,7 +148,7 @@ func (s *cvService) GetAccountDetails(ctx context.Context, req *models.GetAccoun
146
  }
147
 
148
  func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *models.SavePersonalityAndPreferenceRequest) (*models.PersonalityAndPreferenceCV, error) {
149
- if err := validation.Validate(req); err != nil {
150
  return nil, response.HandleValidationError(err)
151
  }
152
 
@@ -196,7 +198,7 @@ func (s *cvService) GetPersonalityAndPreference(ctx context.Context, req *models
196
  }
197
 
198
  func (s *cvService) CreateFamilyMember(ctx context.Context, req *models.CreateFamilyMemberRequest) (*models.FamilyMemberCV, error) {
199
- if err := validation.Validate(req); err != nil {
200
  return nil, response.HandleValidationError(err)
201
  }
202
 
@@ -245,7 +247,7 @@ func (s *cvService) DeleteFamilyMember(ctx context.Context, req *models.DeleteFa
245
  }
246
 
247
  func (s *cvService) UpdateFamilyMember(ctx context.Context, req *models.UpdateFamilyMemberRequest) (*models.FamilyMemberCV, error) {
248
- if err := validation.Validate(req); err != nil {
249
  return nil, response.HandleValidationError(err)
250
  }
251
 
@@ -270,7 +272,7 @@ func (s *cvService) UpdateFamilyMember(ctx context.Context, req *models.UpdateFa
270
  }
271
 
272
  func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.SavePhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error) {
273
- if err := validation.Validate(req); err != nil {
274
  return nil, response.HandleValidationError(err)
275
  }
276
 
@@ -315,7 +317,7 @@ func (s *cvService) GetPhysicalAndHealth(ctx context.Context, req *models.GetPhy
315
  }
316
 
317
  func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.SaveWorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error) {
318
- if err := validation.Validate(req); err != nil {
319
  return nil, response.HandleValidationError(err)
320
  }
321
 
@@ -339,6 +341,7 @@ func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, re
339
  utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.DhuhaPrayer, req.DhuhaPrayer)
340
  utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.QuranMemorization, req.QuranMemorization)
341
  utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.QuranReadingAbility, req.QuranReadingAbility)
 
342
  utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.DaudFasting, req.DaudFasting)
343
  utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.AyyamulBidhFasting, req.AyyamulBidhFasting)
344
  utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.HajjOrUmrah, req.HajjOrUmrah)
@@ -367,7 +370,7 @@ func (s *cvService) GetWorshipAndReligiousUnderstanding(ctx context.Context, req
367
  }
368
 
369
  func (s *cvService) CreateEducation(ctx context.Context, req *models.CreateEducationRequest) (*models.EducationCV, error) {
370
- if err := validation.Validate(req); err != nil {
371
  return nil, response.HandleValidationError(err)
372
  }
373
 
@@ -389,7 +392,7 @@ func (s *cvService) CreateEducation(ctx context.Context, req *models.CreateEduca
389
  }
390
 
391
  func (s *cvService) UpdateEducation(ctx context.Context, req *models.UpdateEducationRequest) (*models.EducationCV, error) {
392
- if err := validation.Validate(req); err != nil {
393
  return nil, response.HandleValidationError(err)
394
  }
395
 
@@ -428,7 +431,7 @@ func (s *cvService) DeleteEducation(ctx context.Context, req *models.DeleteEduca
428
  }
429
 
430
  func (s *cvService) CreateJob(ctx context.Context, req *models.CreateJobRequest) (*models.JobCV, error) {
431
- if err := validation.Validate(req); err != nil {
432
  return nil, response.HandleValidationError(err)
433
  }
434
 
@@ -448,7 +451,7 @@ func (s *cvService) CreateJob(ctx context.Context, req *models.CreateJobRequest)
448
  }
449
 
450
  func (s *cvService) UpdateJob(ctx context.Context, req *models.UpdateJobRequest) (*models.JobCV, error) {
451
- if err := validation.Validate(req); err != nil {
452
  return nil, response.HandleValidationError(err)
453
  }
454
 
 
60
  type cvService struct {
61
  cvRepository repositories.CVRepository
62
  storage storage.Storage
63
+ validator *validation.Validator
64
  }
65
 
66
+ func NewCVService(cvRepository repositories.CVRepository, storage storage.Storage, validator *validation.Validator) CVService {
67
  return &cvService{
68
  cvRepository: cvRepository,
69
  storage: storage,
70
+ validator: validator,
71
  }
72
  }
73
 
74
  func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAccountDetailsRequest) (*models.AccountDetails, error) {
75
+ if err := s.validator.Validate(req); err != nil {
76
  return nil, response.HandleValidationError(err)
77
  }
78
 
 
126
  utils.AssignIfNotNil(&accountDetails.LastJob, req.LastJob)
127
 
128
  if req.PhoneNumber != nil {
129
+ normalizedPhoneNumber := validation.NormalizePhoneNumber(*req.PhoneNumber)
130
+ accountDetails.PhoneNumber = &normalizedPhoneNumber
131
  }
132
 
133
  // Simpan ke database
 
148
  }
149
 
150
  func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *models.SavePersonalityAndPreferenceRequest) (*models.PersonalityAndPreferenceCV, error) {
151
+ if err := s.validator.Validate(req); err != nil {
152
  return nil, response.HandleValidationError(err)
153
  }
154
 
 
198
  }
199
 
200
  func (s *cvService) CreateFamilyMember(ctx context.Context, req *models.CreateFamilyMemberRequest) (*models.FamilyMemberCV, error) {
201
+ if err := s.validator.Validate(req); err != nil {
202
  return nil, response.HandleValidationError(err)
203
  }
204
 
 
247
  }
248
 
249
  func (s *cvService) UpdateFamilyMember(ctx context.Context, req *models.UpdateFamilyMemberRequest) (*models.FamilyMemberCV, error) {
250
+ if err := s.validator.Validate(req); err != nil {
251
  return nil, response.HandleValidationError(err)
252
  }
253
 
 
272
  }
273
 
274
  func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.SavePhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error) {
275
+ if err := s.validator.Validate(req); err != nil {
276
  return nil, response.HandleValidationError(err)
277
  }
278
 
 
317
  }
318
 
319
  func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.SaveWorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error) {
320
+ if err := s.validator.Validate(req); err != nil {
321
  return nil, response.HandleValidationError(err)
322
  }
323
 
 
341
  utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.DhuhaPrayer, req.DhuhaPrayer)
342
  utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.QuranMemorization, req.QuranMemorization)
343
  utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.QuranReadingAbility, req.QuranReadingAbility)
344
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.WeeklyReligiousStudyFrequency, req.WeeklyReligiousStudyFrequency)
345
  utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.DaudFasting, req.DaudFasting)
346
  utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.AyyamulBidhFasting, req.AyyamulBidhFasting)
347
  utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.HajjOrUmrah, req.HajjOrUmrah)
 
370
  }
371
 
372
  func (s *cvService) CreateEducation(ctx context.Context, req *models.CreateEducationRequest) (*models.EducationCV, error) {
373
+ if err := s.validator.Validate(req); err != nil {
374
  return nil, response.HandleValidationError(err)
375
  }
376
 
 
392
  }
393
 
394
  func (s *cvService) UpdateEducation(ctx context.Context, req *models.UpdateEducationRequest) (*models.EducationCV, error) {
395
+ if err := s.validator.Validate(req); err != nil {
396
  return nil, response.HandleValidationError(err)
397
  }
398
 
 
431
  }
432
 
433
  func (s *cvService) CreateJob(ctx context.Context, req *models.CreateJobRequest) (*models.JobCV, error) {
434
+ if err := s.validator.Validate(req); err != nil {
435
  return nil, response.HandleValidationError(err)
436
  }
437
 
 
451
  }
452
 
453
  func (s *cvService) UpdateJob(ctx context.Context, req *models.UpdateJobRequest) (*models.JobCV, error) {
454
+ if err := s.validator.Validate(req); err != nil {
455
  return nil, response.HandleValidationError(err)
456
  }
457
 
space/space/space/services/partner_criteria_service.go ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 PartnerCriteriaService interface {
16
+ SavePartnerCriteria(ctx context.Context, req *models.SavePartnerCriteriaRequest) (*models.PartnerCriteria, error)
17
+ GetPartnerCriteria(ctx context.Context, req *models.GetPartnerCriteriaRequest) (*models.PartnerCriteria, error)
18
+ }
19
+
20
+ type partnerCriteriaService struct {
21
+ partnerCriteriaRepository repositories.PartnerCriteriaRepository
22
+ }
23
+
24
+ func NewPartnerCriteriaService(partnerCriteriaRepository repositories.PartnerCriteriaRepository) PartnerCriteriaService {
25
+ return &partnerCriteriaService{partnerCriteriaRepository: partnerCriteriaRepository}
26
+ }
27
+
28
+ func (s *partnerCriteriaService) SavePartnerCriteria(ctx context.Context, req *models.SavePartnerCriteriaRequest) (*models.PartnerCriteria, error) {
29
+ if err := validation.Validate(req); err != nil {
30
+ return nil, response.HandleValidationError(err)
31
+ }
32
+
33
+ partnerCriteria, err := s.partnerCriteriaRepository.GetPartnerCriteria(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 partnerCriteria == nil {
39
+ partnerCriteria = &models.PartnerCriteria{}
40
+ }
41
+
42
+ partnerCriteria.AccountID = req.AccountID
43
+
44
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedAgeLimit, req.ExpectedAgeLimit)
45
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedDomicile, req.ExpectedDomicile)
46
+ utils.AssignIfNotNil(&partnerCriteria.AcceptedMaritalStatus, req.AcceptedMaritalStatus)
47
+ utils.AssignIfNotNil(&partnerCriteria.PartnerFamilyReligion, req.PartnerFamilyReligion)
48
+ utils.AssignIfNotNil(&partnerCriteria.PartnerWeeklyReligiousStudyFrequency, req.PartnerWeeklyReligiousStudyFrequency)
49
+ utils.AssignIfNotNil(&partnerCriteria.PartnerMonthlySpendingEstimate, req.PartnerMonthlySpendingEstimate)
50
+ utils.AssignIfNotNil(&partnerCriteria.PartnerCanCook, req.PartnerCanCook)
51
+
52
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedBodyShapes, req.ExpectedBodyShapes)
53
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedSkinColors, req.ExpectedSkinColors)
54
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedHairTypes, req.ExpectedHairTypes)
55
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedHairThickness, req.ExpectedHairThickness)
56
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedHeight, req.ExpectedHeight)
57
+
58
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedPartnerIncome, req.ExpectedPartnerIncome)
59
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedIncomeSources, req.ExpectedIncomeSources)
60
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedLastEducation, req.ExpectedLastEducation)
61
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedJobType, req.ExpectedJobType)
62
+
63
+ res, err := s.partnerCriteriaRepository.SavePartnerCriteria(ctx, partnerCriteria)
64
+ if err != nil {
65
+ return nil, response.HandleGormError(err, "Internal Server Error")
66
+ }
67
+
68
+ return res, nil
69
+ }
70
+
71
+ func (s *partnerCriteriaService) GetPartnerCriteria(ctx context.Context, req *models.GetPartnerCriteriaRequest) (*models.PartnerCriteria, error) {
72
+ res, err := s.partnerCriteriaRepository.GetPartnerCriteria(ctx, req.AccountID)
73
+ if err != nil {
74
+ return nil, response.HandleGormError(err, "Internal Server Error")
75
+ }
76
+ return res, nil
77
+ }
space/space/space/space/config/database_connection_config.go CHANGED
@@ -3,7 +3,6 @@ package config
3
  import (
4
  "fmt"
5
  "log"
6
- "log/slog"
7
  "os"
8
 
9
  "gorm.io/driver/postgres"
@@ -78,11 +77,23 @@ func AutoMigrateAll(db *gorm.DB) {
78
  &models.JobCV{},
79
  &models.AchievementCV{},
80
  &models.MarriageReadinessProfile{},
 
81
  )
82
 
83
  if err != nil {
84
  log.Fatal(err)
85
  }
86
 
87
- slog.Info("Auto-migration completed successfully")
 
 
 
 
 
 
 
 
 
 
 
88
  }
 
3
  import (
4
  "fmt"
5
  "log"
 
6
  "os"
7
 
8
  "gorm.io/driver/postgres"
 
77
  &models.JobCV{},
78
  &models.AchievementCV{},
79
  &models.MarriageReadinessProfile{},
80
+ &models.PartnerCriteria{},
81
  )
82
 
83
  if err != nil {
84
  log.Fatal(err)
85
  }
86
 
87
+ sequences := []string{
88
+ `CREATE SEQUENCE IF NOT EXISTS seq_ikh_counter START 1 INCREMENT 1 MINVALUE 1;`,
89
+ `CREATE SEQUENCE IF NOT EXISTS seq_akh_counter START 1 INCREMENT 1 MINVALUE 1;`,
90
+ }
91
+
92
+ for _, seq := range sequences {
93
+ if err := db.Exec(seq).Error; err != nil {
94
+ fmt.Printf("Gagal membuat sequence: %v", err)
95
+ }
96
+ }
97
+
98
+ fmt.Println("Auto-migration sequence check completed successfully")
99
  }
space/space/space/space/models/sequence.go ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package models
2
+
3
+ import (
4
+ "fmt"
5
+ "strings"
6
+ )
7
+
8
+ const (
9
+ SeqIkhCounter = "seq_ikh_counter"
10
+ SeqAkhCounter = "seq_akh_counter"
11
+ )
12
+
13
+ func GetSequenceName(gender string) string {
14
+ if strings.ToLower(gender) == "laki-laki" {
15
+ return SeqIkhCounter
16
+ }
17
+ return SeqAkhCounter
18
+ }
19
+
20
+ func BuildInitialName(gender string, number int64) string {
21
+ if strings.ToLower(gender) == "laki-laki" {
22
+ return fmt.Sprintf("IKH_%d", number)
23
+ }
24
+ return fmt.Sprintf("AKH_%d", number)
25
+ }
space/space/space/space/repositories/account_repository.go CHANGED
@@ -85,3 +85,21 @@ func UpdateAccountDetails(accountDetails models.AccountDetails) Repository[model
85
  repo.Result = accountDetails
86
  return *repo
87
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  repo.Result = accountDetails
86
  return *repo
87
  }
88
+
89
+ func GetNextInitialNameNumber(gender string) Repository[string, int] {
90
+ repo := Construct[string, int](gender)
91
+ sequenceName := models.GetSequenceName(gender)
92
+ repo.Transaction.Raw("SELECT nextval('" + sequenceName + "')").Scan(&repo.Result)
93
+ return *repo
94
+ }
95
+
96
+ func GetAccountDetailsByAccountID(accountID uint) Repository[models.AccountDetails, models.AccountDetails] {
97
+ repo := Construct[models.AccountDetails, models.AccountDetails](
98
+ models.AccountDetails{AccountID: accountID},
99
+ )
100
+ repo.Transactions(
101
+ WhereGivenConstructor[models.AccountDetails, models.AccountDetails],
102
+ Find[models.AccountDetails, models.AccountDetails],
103
+ )
104
+ return *repo
105
+ }
space/space/space/space/repositories/cv_repository.go CHANGED
@@ -1,243 +1,259 @@
1
  package repositories
2
 
3
  import (
4
- "api.qobiltu.id/models"
5
- "context"
6
- "gorm.io/gorm"
 
 
7
  )
8
 
9
  type CVRepository interface {
10
- SaveAccountDetails(ctx context.Context, req *models.AccountDetails) (*models.AccountDetails, error)
11
- GetAccountDetailsByAccountID(ctx context.Context, accountID int64) (*models.AccountDetails, error)
12
-
13
- SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCV) (*models.PersonalityAndPreferenceCV, error)
14
- GetPersonalityAndPreferenceByAccountID(ctx context.Context, accountID int64) (*models.PersonalityAndPreferenceCV, error)
15
-
16
- SaveFamilyMember(ctx context.Context, req *models.FamilyMemberCV) (*models.FamilyMemberCV, error)
17
- ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error)
18
- GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error)
19
- DeleteFamilyMember(ctx context.Context, id int64) error
20
-
21
- SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthCV) (*models.PhysicalAndHealthCV, error)
22
- GetPhysicalAndHealthByAccountID(ctx context.Context, accountID int64) (*models.PhysicalAndHealthCV, error)
23
-
24
- SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingCV) (*models.WorshipAndReligiousUnderstandingCV, error)
25
- GetWorshipAndReligiousUnderstandingByAccountID(ctx context.Context, accountID int64) (*models.WorshipAndReligiousUnderstandingCV, error)
26
-
27
- SaveEducation(ctx context.Context, req *models.EducationCV) (*models.EducationCV, error)
28
- ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error)
29
- GetEducation(ctx context.Context, id int64) (*models.EducationCV, error)
30
- DeleteEducation(ctx context.Context, id int64) error
31
-
32
- SaveJob(ctx context.Context, req *models.JobCV) (*models.JobCV, error)
33
- ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error)
34
- GetJob(ctx context.Context, id int64) (*models.JobCV, error)
35
- DeleteJob(ctx context.Context, id int64) error
36
-
37
- SaveAchievement(ctx context.Context, req *models.AchievementCV) (*models.AchievementCV, error)
38
- ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error)
39
- GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error)
40
- DeleteAchievement(ctx context.Context, id int64) error
 
41
  }
42
 
43
  type cvRepository struct {
44
- db *gorm.DB
45
  }
46
 
47
  func NewCVRepository(db *gorm.DB) CVRepository {
48
- return &cvRepository{
49
- db: db,
50
- }
51
  }
52
 
53
  func (r *cvRepository) SaveAccountDetails(ctx context.Context, req *models.AccountDetails) (*models.AccountDetails, error) {
54
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
55
- return req, err
56
- }
57
- return req, nil
58
  }
59
 
60
  func (r *cvRepository) GetAccountDetailsByAccountID(ctx context.Context, accountID int64) (*models.AccountDetails, error) {
61
- var accountDetails models.AccountDetails
62
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&accountDetails).Error; err != nil {
63
- return nil, err
64
- }
65
- return &accountDetails, nil
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  }
67
 
68
  func (r *cvRepository) SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCV) (*models.PersonalityAndPreferenceCV, error) {
69
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
70
- return req, err
71
- }
72
- return req, nil
73
  }
74
 
75
  func (r *cvRepository) GetPersonalityAndPreferenceByAccountID(ctx context.Context, accountID int64) (*models.PersonalityAndPreferenceCV, error) {
76
- var personalityAndPreference models.PersonalityAndPreferenceCV
77
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&personalityAndPreference).Error; err != nil {
78
- return nil, err
79
- }
80
- return &personalityAndPreference, nil
81
  }
82
 
83
  func (r *cvRepository) SaveFamilyMember(ctx context.Context, req *models.FamilyMemberCV) (*models.FamilyMemberCV, error) {
84
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
85
- return req, err
86
- }
87
- return req, nil
88
  }
89
 
90
  func (r *cvRepository) GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error) {
91
- var familyMember models.FamilyMemberCV
92
- if err := r.db.WithContext(ctx).Where("id = ?", id).First(&familyMember).Error; err != nil {
93
- return nil, err
94
- }
95
- return &familyMember, nil
96
  }
97
 
98
  func (r *cvRepository) DeleteFamilyMember(ctx context.Context, id int64) error {
99
- if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.FamilyMemberCV{}).Error; err != nil {
100
- return err
101
- }
102
- return nil
103
  }
104
 
105
  func (r *cvRepository) ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error) {
106
- familyMembers := make([]models.FamilyMemberCV, 0)
107
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&familyMembers).Error; err != nil {
108
- return nil, err
109
- }
110
- return familyMembers, nil
111
  }
112
 
113
  func (r *cvRepository) SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthCV) (*models.PhysicalAndHealthCV, error) {
114
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
115
- return req, err
116
- }
117
- return req, nil
118
  }
119
 
120
  func (r *cvRepository) GetPhysicalAndHealthByAccountID(ctx context.Context, accountID int64) (*models.PhysicalAndHealthCV, error) {
121
- var physicalAndHealth models.PhysicalAndHealthCV
122
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&physicalAndHealth).Error; err != nil {
123
- return nil, err
124
- }
125
- return &physicalAndHealth, nil
126
  }
127
 
128
  func (r *cvRepository) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingCV) (*models.WorshipAndReligiousUnderstandingCV, error) {
129
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
130
- return req, err
131
- }
132
- return req, nil
133
  }
134
 
135
  func (r *cvRepository) GetWorshipAndReligiousUnderstandingByAccountID(ctx context.Context, accountID int64) (*models.WorshipAndReligiousUnderstandingCV, error) {
136
- var worshipAndReligiousUnderstanding models.WorshipAndReligiousUnderstandingCV
137
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&worshipAndReligiousUnderstanding).Error; err != nil {
138
- return nil, err
139
- }
140
- return &worshipAndReligiousUnderstanding, nil
141
  }
142
 
143
  // SaveEducation menyimpan atau memperbarui data pendidikan ke database
144
  func (r *cvRepository) SaveEducation(ctx context.Context, req *models.EducationCV) (*models.EducationCV, error) {
145
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
146
- return req, err
147
- }
148
- return req, nil
149
  }
150
 
151
  // ListEducation mengambil daftar data pendidikan berdasarkan account_id
152
  func (r *cvRepository) ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error) {
153
- var educations []models.EducationCV
154
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&educations).Error; err != nil {
155
- return nil, err
156
- }
157
- return educations, nil
158
  }
159
 
160
  // GetEducation mengambil satu data pendidikan berdasarkan id
161
  func (r *cvRepository) GetEducation(ctx context.Context, id int64) (*models.EducationCV, error) {
162
- var education models.EducationCV
163
- if err := r.db.WithContext(ctx).Where("id = ?", id).First(&education).Error; err != nil {
164
- return nil, err
165
- }
166
- return &education, nil
167
  }
168
 
169
  // DeleteEducation menghapus data pendidikan berdasarkan id
170
  func (r *cvRepository) DeleteEducation(ctx context.Context, id int64) error {
171
- if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.EducationCV{}).Error; err != nil {
172
- return err
173
- }
174
- return nil
175
  }
176
 
177
  // SaveJob menyimpan atau memperbarui data pekerjaan ke database
178
  func (r *cvRepository) SaveJob(ctx context.Context, req *models.JobCV) (*models.JobCV, error) {
179
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
180
- return req, err
181
- }
182
- return req, nil
183
  }
184
 
185
  // ListJob mengambil daftar data pekerjaan berdasarkan account_id
186
  func (r *cvRepository) ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error) {
187
- var jobs []models.JobCV
188
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&jobs).Error; err != nil {
189
- return nil, err
190
- }
191
- return jobs, nil
192
  }
193
 
194
  // GetJob mengambil satu data pekerjaan berdasarkan id
195
  func (r *cvRepository) GetJob(ctx context.Context, id int64) (*models.JobCV, error) {
196
- var job models.JobCV
197
- if err := r.db.WithContext(ctx).Where("id = ?", id).First(&job).Error; err != nil {
198
- return nil, err
199
- }
200
- return &job, nil
201
  }
202
 
203
  // DeleteJob menghapus data pekerjaan berdasarkan id
204
  func (r *cvRepository) DeleteJob(ctx context.Context, id int64) error {
205
- if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.JobCV{}).Error; err != nil {
206
- return err
207
- }
208
- return nil
209
  }
210
 
211
  // SaveAchievement menyimpan atau memperbarui data prestasi ke database
212
  func (r *cvRepository) SaveAchievement(ctx context.Context, req *models.AchievementCV) (*models.AchievementCV, error) {
213
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
214
- return req, err
215
- }
216
- return req, nil
217
  }
218
 
219
  // ListAchievement mengambil daftar data prestasi berdasarkan account_id
220
  func (r *cvRepository) ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error) {
221
- var achievements []models.AchievementCV
222
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&achievements).Error; err != nil {
223
- return nil, err
224
- }
225
- return achievements, nil
226
  }
227
 
228
  // GetAchievement mengambil satu data prestasi berdasarkan id
229
  func (r *cvRepository) GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error) {
230
- var achievement models.AchievementCV
231
- if err := r.db.WithContext(ctx).Where("id = ?", id).First(&achievement).Error; err != nil {
232
- return nil, err
233
- }
234
- return &achievement, nil
235
  }
236
 
237
  // DeleteAchievement menghapus data prestasi berdasarkan id
238
  func (r *cvRepository) DeleteAchievement(ctx context.Context, id int64) error {
239
- if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.AchievementCV{}).Error; err != nil {
240
- return err
241
- }
242
- return nil
243
  }
 
1
  package repositories
2
 
3
  import (
4
+ "context"
5
+ "fmt"
6
+
7
+ "api.qobiltu.id/models"
8
+ "gorm.io/gorm"
9
  )
10
 
11
  type CVRepository interface {
12
+ SaveAccountDetails(ctx context.Context, req *models.AccountDetails) (*models.AccountDetails, error)
13
+ GetAccountDetailsByAccountID(ctx context.Context, accountID int64) (*models.AccountDetails, error)
14
+ GetNextInitialNameNumber(ctx context.Context, gender string) (int64, error)
15
+
16
+ SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCV) (*models.PersonalityAndPreferenceCV, error)
17
+ GetPersonalityAndPreferenceByAccountID(ctx context.Context, accountID int64) (*models.PersonalityAndPreferenceCV, error)
18
+
19
+ SaveFamilyMember(ctx context.Context, req *models.FamilyMemberCV) (*models.FamilyMemberCV, error)
20
+ ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error)
21
+ GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error)
22
+ DeleteFamilyMember(ctx context.Context, id int64) error
23
+
24
+ SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthCV) (*models.PhysicalAndHealthCV, error)
25
+ GetPhysicalAndHealthByAccountID(ctx context.Context, accountID int64) (*models.PhysicalAndHealthCV, error)
26
+
27
+ SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingCV) (*models.WorshipAndReligiousUnderstandingCV, error)
28
+ GetWorshipAndReligiousUnderstandingByAccountID(ctx context.Context, accountID int64) (*models.WorshipAndReligiousUnderstandingCV, error)
29
+
30
+ SaveEducation(ctx context.Context, req *models.EducationCV) (*models.EducationCV, error)
31
+ ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error)
32
+ GetEducation(ctx context.Context, id int64) (*models.EducationCV, error)
33
+ DeleteEducation(ctx context.Context, id int64) error
34
+
35
+ SaveJob(ctx context.Context, req *models.JobCV) (*models.JobCV, error)
36
+ ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error)
37
+ GetJob(ctx context.Context, id int64) (*models.JobCV, error)
38
+ DeleteJob(ctx context.Context, id int64) error
39
+
40
+ SaveAchievement(ctx context.Context, req *models.AchievementCV) (*models.AchievementCV, error)
41
+ ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error)
42
+ GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error)
43
+ DeleteAchievement(ctx context.Context, id int64) error
44
  }
45
 
46
  type cvRepository struct {
47
+ db *gorm.DB
48
  }
49
 
50
  func NewCVRepository(db *gorm.DB) CVRepository {
51
+ return &cvRepository{
52
+ db: db,
53
+ }
54
  }
55
 
56
  func (r *cvRepository) SaveAccountDetails(ctx context.Context, req *models.AccountDetails) (*models.AccountDetails, error) {
57
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
58
+ return req, err
59
+ }
60
+ return req, nil
61
  }
62
 
63
  func (r *cvRepository) GetAccountDetailsByAccountID(ctx context.Context, accountID int64) (*models.AccountDetails, error) {
64
+ var accountDetails models.AccountDetails
65
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&accountDetails).Error; err != nil {
66
+ return nil, err
67
+ }
68
+ return &accountDetails, nil
69
+ }
70
+
71
+ func (r *cvRepository) GetNextInitialNameNumber(ctx context.Context, gender string) (int64, error) {
72
+ sequenceName := models.GetSequenceName(gender)
73
+
74
+ var number int64
75
+ query := fmt.Sprintf("SELECT nextval('%s')", sequenceName)
76
+ err := r.db.WithContext(ctx).Raw(query).Scan(&number).Error
77
+ if err != nil {
78
+ return 0, err
79
+ }
80
+
81
+ return number, nil
82
  }
83
 
84
  func (r *cvRepository) SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCV) (*models.PersonalityAndPreferenceCV, error) {
85
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
86
+ return req, err
87
+ }
88
+ return req, nil
89
  }
90
 
91
  func (r *cvRepository) GetPersonalityAndPreferenceByAccountID(ctx context.Context, accountID int64) (*models.PersonalityAndPreferenceCV, error) {
92
+ var personalityAndPreference models.PersonalityAndPreferenceCV
93
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&personalityAndPreference).Error; err != nil {
94
+ return nil, err
95
+ }
96
+ return &personalityAndPreference, nil
97
  }
98
 
99
  func (r *cvRepository) SaveFamilyMember(ctx context.Context, req *models.FamilyMemberCV) (*models.FamilyMemberCV, error) {
100
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
101
+ return req, err
102
+ }
103
+ return req, nil
104
  }
105
 
106
  func (r *cvRepository) GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error) {
107
+ var familyMember models.FamilyMemberCV
108
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&familyMember).Error; err != nil {
109
+ return nil, err
110
+ }
111
+ return &familyMember, nil
112
  }
113
 
114
  func (r *cvRepository) DeleteFamilyMember(ctx context.Context, id int64) error {
115
+ if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.FamilyMemberCV{}).Error; err != nil {
116
+ return err
117
+ }
118
+ return nil
119
  }
120
 
121
  func (r *cvRepository) ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error) {
122
+ familyMembers := make([]models.FamilyMemberCV, 0)
123
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&familyMembers).Error; err != nil {
124
+ return nil, err
125
+ }
126
+ return familyMembers, nil
127
  }
128
 
129
  func (r *cvRepository) SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthCV) (*models.PhysicalAndHealthCV, error) {
130
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
131
+ return req, err
132
+ }
133
+ return req, nil
134
  }
135
 
136
  func (r *cvRepository) GetPhysicalAndHealthByAccountID(ctx context.Context, accountID int64) (*models.PhysicalAndHealthCV, error) {
137
+ var physicalAndHealth models.PhysicalAndHealthCV
138
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&physicalAndHealth).Error; err != nil {
139
+ return nil, err
140
+ }
141
+ return &physicalAndHealth, nil
142
  }
143
 
144
  func (r *cvRepository) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingCV) (*models.WorshipAndReligiousUnderstandingCV, error) {
145
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
146
+ return req, err
147
+ }
148
+ return req, nil
149
  }
150
 
151
  func (r *cvRepository) GetWorshipAndReligiousUnderstandingByAccountID(ctx context.Context, accountID int64) (*models.WorshipAndReligiousUnderstandingCV, error) {
152
+ var worshipAndReligiousUnderstanding models.WorshipAndReligiousUnderstandingCV
153
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&worshipAndReligiousUnderstanding).Error; err != nil {
154
+ return nil, err
155
+ }
156
+ return &worshipAndReligiousUnderstanding, nil
157
  }
158
 
159
  // SaveEducation menyimpan atau memperbarui data pendidikan ke database
160
  func (r *cvRepository) SaveEducation(ctx context.Context, req *models.EducationCV) (*models.EducationCV, error) {
161
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
162
+ return req, err
163
+ }
164
+ return req, nil
165
  }
166
 
167
  // ListEducation mengambil daftar data pendidikan berdasarkan account_id
168
  func (r *cvRepository) ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error) {
169
+ var educations []models.EducationCV
170
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&educations).Error; err != nil {
171
+ return nil, err
172
+ }
173
+ return educations, nil
174
  }
175
 
176
  // GetEducation mengambil satu data pendidikan berdasarkan id
177
  func (r *cvRepository) GetEducation(ctx context.Context, id int64) (*models.EducationCV, error) {
178
+ var education models.EducationCV
179
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&education).Error; err != nil {
180
+ return nil, err
181
+ }
182
+ return &education, nil
183
  }
184
 
185
  // DeleteEducation menghapus data pendidikan berdasarkan id
186
  func (r *cvRepository) DeleteEducation(ctx context.Context, id int64) error {
187
+ if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.EducationCV{}).Error; err != nil {
188
+ return err
189
+ }
190
+ return nil
191
  }
192
 
193
  // SaveJob menyimpan atau memperbarui data pekerjaan ke database
194
  func (r *cvRepository) SaveJob(ctx context.Context, req *models.JobCV) (*models.JobCV, error) {
195
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
196
+ return req, err
197
+ }
198
+ return req, nil
199
  }
200
 
201
  // ListJob mengambil daftar data pekerjaan berdasarkan account_id
202
  func (r *cvRepository) ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error) {
203
+ var jobs []models.JobCV
204
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&jobs).Error; err != nil {
205
+ return nil, err
206
+ }
207
+ return jobs, nil
208
  }
209
 
210
  // GetJob mengambil satu data pekerjaan berdasarkan id
211
  func (r *cvRepository) GetJob(ctx context.Context, id int64) (*models.JobCV, error) {
212
+ var job models.JobCV
213
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&job).Error; err != nil {
214
+ return nil, err
215
+ }
216
+ return &job, nil
217
  }
218
 
219
  // DeleteJob menghapus data pekerjaan berdasarkan id
220
  func (r *cvRepository) DeleteJob(ctx context.Context, id int64) error {
221
+ if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.JobCV{}).Error; err != nil {
222
+ return err
223
+ }
224
+ return nil
225
  }
226
 
227
  // SaveAchievement menyimpan atau memperbarui data prestasi ke database
228
  func (r *cvRepository) SaveAchievement(ctx context.Context, req *models.AchievementCV) (*models.AchievementCV, error) {
229
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
230
+ return req, err
231
+ }
232
+ return req, nil
233
  }
234
 
235
  // ListAchievement mengambil daftar data prestasi berdasarkan account_id
236
  func (r *cvRepository) ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error) {
237
+ var achievements []models.AchievementCV
238
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&achievements).Error; err != nil {
239
+ return nil, err
240
+ }
241
+ return achievements, nil
242
  }
243
 
244
  // GetAchievement mengambil satu data prestasi berdasarkan id
245
  func (r *cvRepository) GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error) {
246
+ var achievement models.AchievementCV
247
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&achievement).Error; err != nil {
248
+ return nil, err
249
+ }
250
+ return &achievement, nil
251
  }
252
 
253
  // DeleteAchievement menghapus data prestasi berdasarkan id
254
  func (r *cvRepository) DeleteAchievement(ctx context.Context, id int64) error {
255
+ if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.AchievementCV{}).Error; err != nil {
256
+ return err
257
+ }
258
+ return nil
259
  }
space/space/space/space/services/cv_service.go CHANGED
@@ -5,6 +5,7 @@ import (
5
  "errors"
6
  "math"
7
  "strconv"
 
8
 
9
  "api.qobiltu.id/models"
10
  "api.qobiltu.id/pkg/storage"
@@ -69,8 +70,6 @@ func NewCVService(cvRepository repositories.CVRepository, storage storage.Storag
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
  }
@@ -81,15 +80,42 @@ func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAcco
81
  return nil, response.HandleGormError(err, "Internal Server Error")
82
  }
83
 
84
- // Apply perubahan
85
- if accountDetails == nil {
 
86
  accountDetails = &models.AccountDetails{}
 
 
 
 
 
 
 
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)
@@ -102,7 +128,7 @@ func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAcco
102
  accountDetails.PhoneNumber = &sanitizedPhone
103
  }
104
 
105
- // Simpan data
106
  res, err := s.cvRepository.SaveAccountDetails(ctx, accountDetails)
107
  if err != nil {
108
  return nil, response.HandleGormError(err, "Internal Server Error")
 
5
  "errors"
6
  "math"
7
  "strconv"
8
+ "strings"
9
 
10
  "api.qobiltu.id/models"
11
  "api.qobiltu.id/pkg/storage"
 
70
  }
71
 
72
  func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAccountDetailsRequest) (*models.AccountDetails, error) {
 
 
73
  if err := validation.Validate(req); err != nil {
74
  return nil, response.HandleValidationError(err)
75
  }
 
80
  return nil, response.HandleGormError(err, "Internal Server Error")
81
  }
82
 
83
+ // Cek apakah accountDetails baru atau existing
84
+ isNew := accountDetails == nil
85
+ if isNew {
86
  accountDetails = &models.AccountDetails{}
87
+ accountDetails.AccountID = uint(req.AccountID)
88
+ }
89
+
90
+ // Simpan gender lama jika ada
91
+ oldGender := ""
92
+ if accountDetails.Gender != nil {
93
+ oldGender = strings.ToLower(*accountDetails.Gender)
94
  }
95
 
96
+ // Update gender jika dikirim dari request
97
+ var genderChanged bool
98
+ if req.Gender != nil {
99
+ newGender := strings.ToLower(*req.Gender)
100
+ if oldGender != newGender {
101
+ genderChanged = true
102
+ accountDetails.Gender = req.Gender
103
+ }
104
+ }
105
 
106
+ // Jika gender baru atau gender berubah, generate InitialName baru via sequence
107
+ if isNew || genderChanged {
108
+ if accountDetails.Gender != nil {
109
+ number, err := s.cvRepository.GetNextInitialNameNumber(ctx, *accountDetails.Gender)
110
+ if err != nil {
111
+ return nil, response.HandleGormError(err, "Gagal generate initial name")
112
+ }
113
+ accountDetails.InitialName = models.BuildInitialName(*accountDetails.Gender, number)
114
+ }
115
+ }
116
+
117
+ // Apply perubahan field lainnya
118
  utils.AssignIfNotNil(&accountDetails.FullName, req.FullName)
 
119
  utils.AssignIfNotNil(&accountDetails.DateOfBirth, req.DateOfBirth)
120
  utils.AssignIfNotNil(&accountDetails.PlaceOfBirth, req.PlaceOfBirth)
121
  utils.AssignIfNotNil(&accountDetails.Domicile, req.Domicile)
 
128
  accountDetails.PhoneNumber = &sanitizedPhone
129
  }
130
 
131
+ // Simpan ke database
132
  res, err := s.cvRepository.SaveAccountDetails(ctx, accountDetails)
133
  if err != nil {
134
  return nil, response.HandleGormError(err, "Internal Server Error")
space/space/space/space/services/user_profile_service.go CHANGED
@@ -2,7 +2,6 @@ package services
2
 
3
  import (
4
  "regexp"
5
- "strconv"
6
  "strings"
7
 
8
  "api.qobiltu.id/models"
@@ -73,22 +72,47 @@ func (s *UserProfileService) Retrieve() {
73
  }
74
 
75
  func (s *UserProfileService) Update() {
 
76
  if s.Constructor.PhoneNumber != nil {
77
  phoneNumber := *s.Constructor.PhoneNumber
78
  *s.Constructor.PhoneNumber = SanitizePhoneNumber(phoneNumber)
79
  }
80
- usersCount := repositories.GetAllAccount().RowsCount
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  var initialName string
 
 
82
  if s.Constructor.Gender != nil {
83
- if strings.ToLower(*s.Constructor.Gender) == "laki-laki" {
84
- initialName = "IKH_"
85
- } else {
86
- initialName = "AKH_"
 
 
 
 
 
 
87
  }
88
  }
89
 
90
- initialName += strconv.Itoa(usersCount)
91
- s.Constructor.InitialName = initialName
 
 
 
92
  userProfile := repositories.UpdateAccountDetails(s.Constructor)
93
  s.Error = userProfile.RowsError
94
  if userProfile.NoRecord {
@@ -96,6 +120,8 @@ func (s *UserProfileService) Update() {
96
  s.Exception.Message = "There is no account with given credentials!"
97
  return
98
  }
 
 
99
  account := repositories.GetAccountById(uint(s.Constructor.AccountID))
100
  account.Result.IsDetailCompleted = (userProfile.Result.InitialName != "" &&
101
  userProfile.Result.FullName != nil &&
@@ -107,9 +133,11 @@ func (s *UserProfileService) Update() {
107
  userProfile.Result.LastEducation != nil &&
108
  userProfile.Result.MaritalStatus != nil)
109
  repositories.UpdateAccount(account.Result)
 
110
  s.Result = models.UserProfileResponse{
111
  Account: account.Result,
112
  Details: userProfile.Result,
113
  }
 
114
  s.Result.Account.Password = "SECRET"
115
  }
 
2
 
3
  import (
4
  "regexp"
 
5
  "strings"
6
 
7
  "api.qobiltu.id/models"
 
72
  }
73
 
74
  func (s *UserProfileService) Update() {
75
+ // Sanitize phone number
76
  if s.Constructor.PhoneNumber != nil {
77
  phoneNumber := *s.Constructor.PhoneNumber
78
  *s.Constructor.PhoneNumber = SanitizePhoneNumber(phoneNumber)
79
  }
80
+
81
+ // Ambil data account details dari DB
82
+ existingDetails := repositories.GetAccountDetailsByAccountID(uint(s.Constructor.AccountID))
83
+ if existingDetails.RowsError != nil {
84
+ s.Error = existingDetails.RowsError
85
+ return
86
+ }
87
+ if existingDetails.NoRecord {
88
+ s.Exception.DataNotFound = true
89
+ s.Exception.Message = "Account details not found"
90
+ return
91
+ }
92
+
93
+ // Update initial name jika gender berubah
94
  var initialName string
95
+ shouldUpdateInitialName := false
96
+
97
  if s.Constructor.Gender != nil {
98
+ newGender := strings.ToLower(*s.Constructor.Gender)
99
+ oldGender := ""
100
+ if existingDetails.Result.Gender != nil {
101
+ oldGender = strings.ToLower(*existingDetails.Result.Gender)
102
+ }
103
+ if newGender != oldGender {
104
+ initialNameNumber := repositories.GetNextInitialNameNumber(*s.Constructor.Gender)
105
+ initialName = models.BuildInitialName(*s.Constructor.Gender, int64(initialNameNumber.Result))
106
+ s.Constructor.InitialName = initialName
107
+ shouldUpdateInitialName = true
108
  }
109
  }
110
 
111
+ // Update ke database
112
+ if !shouldUpdateInitialName {
113
+ s.Constructor.InitialName = existingDetails.Result.InitialName
114
+ }
115
+
116
  userProfile := repositories.UpdateAccountDetails(s.Constructor)
117
  s.Error = userProfile.RowsError
118
  if userProfile.NoRecord {
 
120
  s.Exception.Message = "There is no account with given credentials!"
121
  return
122
  }
123
+
124
+ // Update flag isDetailCompleted di account
125
  account := repositories.GetAccountById(uint(s.Constructor.AccountID))
126
  account.Result.IsDetailCompleted = (userProfile.Result.InitialName != "" &&
127
  userProfile.Result.FullName != nil &&
 
133
  userProfile.Result.LastEducation != nil &&
134
  userProfile.Result.MaritalStatus != nil)
135
  repositories.UpdateAccount(account.Result)
136
+
137
  s.Result = models.UserProfileResponse{
138
  Account: account.Result,
139
  Details: userProfile.Result,
140
  }
141
+
142
  s.Result.Account.Password = "SECRET"
143
  }
space/space/space/space/space/models/database_orm_model.go CHANGED
@@ -19,8 +19,8 @@ type Account struct {
19
  }
20
 
21
  type AccountDetails struct {
22
- ID uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
23
- AccountID uint `gorm:"column:account_id;not null;unique" json:"account_id"`
24
  InitialName string `gorm:"column:initial_name;not null" json:"initial_name"`
25
  FullName *string `gorm:"column:full_name" json:"full_name"`
26
  DateOfBirth *time.Time `gorm:"column:date_of_birth" json:"date_of_birth"`
@@ -32,8 +32,8 @@ type AccountDetails struct {
32
  MaritalStatus *string `gorm:"column:marital_status" json:"marital_status"`
33
  Avatar *string `gorm:"column:avatar" json:"avatar"`
34
  PhoneNumber *string `gorm:"column:phone_number" json:"phone_number"`
35
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
36
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
37
  FieldCounter
38
  }
39
 
@@ -193,9 +193,9 @@ type QuizResult struct {
193
 
194
  type (
195
  PersonalityAndPreferenceCV struct {
196
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
197
- AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
198
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
199
  PositiveTraits *string `gorm:"column:positive_traits" json:"positive_traits"` // sifat positif
200
  NegativeTraits *string `gorm:"column:negative_traits" json:"negative_traits"` // sifat negatif
201
  Hobbies *string `gorm:"column:hobbies" json:"hobbies"` // hobi
@@ -210,30 +210,30 @@ type (
210
  CanCook *bool `gorm:"column:can_cook" json:"can_cook"` // bisa memasak
211
  TypesOfDishesCooked *string `gorm:"column:types_of_dishes_cooked" json:"types_of_dishes_cooked"` // jenis masakan yang bisa dimasak
212
  MonthlyExpenses *string `gorm:"column:monthly_expenses" json:"monthly_expenses"` // pengeluaran per bulan
213
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
214
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
215
  FieldCounter
216
  }
217
 
218
  FamilyMemberCV struct {
219
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
220
- AccountID int64 `gorm:"column:account_id;not null" json:"account_id"`
221
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
222
  Role *string `gorm:"column:role" json:"role"` // Peran dalam keluarga
223
  Status *string `gorm:"column:status" json:"status"` // Status (Hidup, Wafat)
224
  Religion *string `gorm:"column:religion" json:"religion"` // Agama
225
  Job *string `gorm:"column:job" json:"job"` // Pekerjaan
226
  LastEducation *string `gorm:"column:last_education" json:"last_education"` // Pendidikan terakhir
227
  Age *int `gorm:"column:age" json:"age"` // Usia
228
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
229
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
230
  FieldCounter
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
@@ -242,15 +242,15 @@ type (
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
@@ -266,44 +266,44 @@ type (
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
 
274
  EducationCV struct {
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
282
- YearGraduate *int `gorm:"column:year_graduate" json:"year_graduate"` // tahun lulus
283
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
284
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
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 {
301
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
302
- AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
303
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
304
- AchievementOrAward *string `gorm:"column:achievement_or_award" json:"achievement_or_award"` // prestasi atau penghargaan
305
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
306
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
307
  }
308
  )
309
 
 
19
  }
20
 
21
  type AccountDetails struct {
22
+ ID uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
23
+ AccountID uint `gorm:"column:account_id;not null;unique" json:"account_id" counter:"skip"`
24
  InitialName string `gorm:"column:initial_name;not null" json:"initial_name"`
25
  FullName *string `gorm:"column:full_name" json:"full_name"`
26
  DateOfBirth *time.Time `gorm:"column:date_of_birth" json:"date_of_birth"`
 
32
  MaritalStatus *string `gorm:"column:marital_status" json:"marital_status"`
33
  Avatar *string `gorm:"column:avatar" json:"avatar"`
34
  PhoneNumber *string `gorm:"column:phone_number" json:"phone_number"`
35
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"`
36
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
37
  FieldCounter
38
  }
39
 
 
193
 
194
  type (
195
  PersonalityAndPreferenceCV struct {
196
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
197
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id" counter:"skip"`
198
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
199
  PositiveTraits *string `gorm:"column:positive_traits" json:"positive_traits"` // sifat positif
200
  NegativeTraits *string `gorm:"column:negative_traits" json:"negative_traits"` // sifat negatif
201
  Hobbies *string `gorm:"column:hobbies" json:"hobbies"` // hobi
 
210
  CanCook *bool `gorm:"column:can_cook" json:"can_cook"` // bisa memasak
211
  TypesOfDishesCooked *string `gorm:"column:types_of_dishes_cooked" json:"types_of_dishes_cooked"` // jenis masakan yang bisa dimasak
212
  MonthlyExpenses *string `gorm:"column:monthly_expenses" json:"monthly_expenses"` // pengeluaran per bulan
213
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"`
214
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
215
  FieldCounter
216
  }
217
 
218
  FamilyMemberCV struct {
219
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
220
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id" counter:"skip"`
221
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
222
  Role *string `gorm:"column:role" json:"role"` // Peran dalam keluarga
223
  Status *string `gorm:"column:status" json:"status"` // Status (Hidup, Wafat)
224
  Religion *string `gorm:"column:religion" json:"religion"` // Agama
225
  Job *string `gorm:"column:job" json:"job"` // Pekerjaan
226
  LastEducation *string `gorm:"column:last_education" json:"last_education"` // Pendidikan terakhir
227
  Age *int `gorm:"column:age" json:"age"` // Usia
228
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"`
229
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
230
  FieldCounter
231
  }
232
 
233
  PhysicalAndHealthCV struct {
234
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
235
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id" counter:"skip"`
236
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
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
 
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" counter:"skip"` // Waktu data dibuat
246
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"` // Waktu data terakhir diperbarui
247
  FieldCounter
248
  }
249
 
250
  WorshipAndReligiousUnderstandingCV struct {
251
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
252
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id" counter:"skip"`
253
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
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
 
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" counter:"skip"`
270
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
271
  FieldCounter
272
  }
273
 
274
  EducationCV struct {
275
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"` // id
276
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id" counter:"skip"` // id akun
277
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"` // 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
282
+ YearGraduate *int `gorm:"column:year_graduate" json:"year_graduate"` // tahun lulus
283
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"` // tanggal dibuat
284
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"` // tanggal diperbarui
285
  }
286
 
287
  JobCV struct {
288
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"` // id
289
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id" counter:"skip"` // id akun
290
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"` // 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" counter:"skip"` // tanggal dibuat
297
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"` // tanggal diperbarui
298
  }
299
 
300
  AchievementCV struct {
301
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"` // id
302
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id" counter:"skip"` // id akun
303
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"` // relasi ke akun
304
+ AchievementOrAward *string `gorm:"column:achievement_or_award" json:"achievement_or_award"` // prestasi atau penghargaan
305
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"` // tanggal dibuat
306
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"` // tanggal diperbarui
307
  }
308
  )
309
 
space/space/space/space/space/models/field_counter.go CHANGED
@@ -1,6 +1,7 @@
1
  package models
2
 
3
  import (
 
4
  "reflect"
5
  "time"
6
  )
@@ -17,29 +18,13 @@ 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
 
1
  package models
2
 
3
  import (
4
+ "fmt"
5
  "reflect"
6
  "time"
7
  )
 
18
 
19
  // shouldSkipField menentukan apakah field harus dilewati dalam penghitungan
20
  func shouldSkipField(field reflect.StructField) bool {
 
21
  if field.Name == "FieldCounter" {
22
  return true
23
  }
24
 
25
+ counterTag := field.Tag.Get("counter")
26
+ fmt.Println(field.Name, "=", counterTag)
27
+ return counterTag == "skip"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  }
29
 
30
  // TotalFields menghitung jumlah total field dalam struct
space/space/space/space/space/space/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 {
space/space/space/space/space/space/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
+ }
space/space/space/space/space/space/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
 
space/space/space/space/space/space/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
+ }
space/space/space/space/space/space/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
+ }
space/space/space/space/space/space/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/space/space/space/space/space/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/space/space/space/space/space/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/space/space/space/space/space/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/space/space/space/space/space/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/space/space/space/space/space/space/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
  }
space/space/space/space/space/space/space/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
  }
space/space/space/space/space/space/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/space/space/space/space/space/space/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
  }
space/space/space/space/space/space/space/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()