Spaces:
Configuration error
Configuration error
Commit ·
3ccc959
1
Parent(s): abe4f0d
Deploy files from GitHub repository
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- main.go +1 -1
- pkg/mail/sender.go +8 -0
- pkg/mail/smtp.go +71 -0
- pkg/worker/processor.go +3 -2
- router/email_route.go +2 -3
- router/router.go +1 -1
- space/main.go +1 -1
- space/pkg/worker/processor.go +3 -2
- space/router/email_route.go +2 -3
- space/router/router.go +1 -1
- space/space/controller/auth/auth_login_controller.go +1 -1
- space/space/controller/auth/auth_register_controller.go +1 -1
- space/space/controller/email/email_controller.go +64 -0
- space/space/main.go +7 -1
- space/space/models/database_orm_model.go +7 -5
- space/space/models/request_model.go +11 -0
- space/space/repositories/email_repository.go +65 -0
- space/space/router/email_route.go +4 -6
- space/space/router/router.go +1 -2
- space/space/router/server.go +4 -0
- space/space/services/email_service.go +123 -0
- space/space/space/controller/quiz/review_quiz_controller.go +26 -0
- space/space/space/router/quiz_route.go +1 -0
- space/space/space/services/academy_quiz_question_service.go +2 -0
- space/space/space/services/academy_quiz_review_service.go +56 -0
- space/space/space/services/academy_quiz_service.go +2 -5
- space/space/space/space/main.go +0 -6
- space/space/space/space/models/database_orm_model.go +12 -7
- space/space/space/space/models/request_model.go +8 -6
- space/space/space/space/router/router.go +0 -1
- space/space/space/space/router/server.go +0 -4
- space/space/space/space/services/partner_criteria_service.go +4 -2
- space/space/space/space/space/pkg/validation/validation.go +16 -155
- space/space/space/space/space/response/validation.go +19 -2
- space/space/space/space/space/services/marriage_readiness_profile_service.go +4 -3
- space/space/space/space/space/space/controller/partner_criteria/partner_criteria_controller.go +66 -0
- space/space/space/space/space/space/main.go +9 -3
- space/space/space/space/space/space/models/database_orm_model.go +54 -20
- space/space/space/space/space/space/models/request_model.go +112 -77
- space/space/space/space/space/space/pkg/validation/custom_rules.go +236 -116
- space/space/space/space/space/space/repositories/partner_criteria_repository.go +38 -0
- space/space/space/space/space/space/router/partner_criteria_route.go +11 -0
- space/space/space/space/space/space/router/router.go +1 -0
- space/space/space/space/space/space/router/server.go +4 -0
- space/space/space/space/space/space/services/partner_criteria_service.go +78 -0
- space/space/space/space/space/space/space/config/database_connection_config.go +13 -2
- space/space/space/space/space/space/space/models/sequence.go +25 -0
- space/space/space/space/space/space/space/repositories/account_repository.go +18 -0
- space/space/space/space/space/space/space/repositories/cv_repository.go +162 -146
- space/space/space/space/space/space/space/services/cv_service.go +49 -20
main.go
CHANGED
|
@@ -10,7 +10,7 @@ import (
|
|
| 10 |
email_controller "api.qobiltu.id/controller/email"
|
| 11 |
marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
|
| 12 |
partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
|
| 13 |
-
"api.qobiltu.id/mail"
|
| 14 |
"api.qobiltu.id/pkg/storage"
|
| 15 |
"api.qobiltu.id/pkg/validation"
|
| 16 |
"api.qobiltu.id/pkg/worker"
|
|
|
|
| 10 |
email_controller "api.qobiltu.id/controller/email"
|
| 11 |
marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
|
| 12 |
partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
|
| 13 |
+
"api.qobiltu.id/pkg/mail"
|
| 14 |
"api.qobiltu.id/pkg/storage"
|
| 15 |
"api.qobiltu.id/pkg/validation"
|
| 16 |
"api.qobiltu.id/pkg/worker"
|
pkg/mail/sender.go
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package mail
|
| 2 |
+
|
| 3 |
+
type Sender interface {
|
| 4 |
+
Send(recipient, subject string, htmlContent string, data any) error
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
// EmailSender is a global variable to hold the email sender
|
| 8 |
+
var EmailSender Sender
|
pkg/mail/smtp.go
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package mail
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"errors"
|
| 5 |
+
"fmt"
|
| 6 |
+
"github.com/jordan-wright/email"
|
| 7 |
+
"net/mail"
|
| 8 |
+
"net/smtp"
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
var (
|
| 12 |
+
ErrEmailEmpty = errors.New("mail is empty")
|
| 13 |
+
ErrEmailInvalid = errors.New("invalid email")
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
// Config menyimpan pengaturan untuk koneksi SMTP.
|
| 17 |
+
type Config struct {
|
| 18 |
+
Host string
|
| 19 |
+
Port string
|
| 20 |
+
Username string
|
| 21 |
+
Password string
|
| 22 |
+
From string
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
// SMTP adalah implementasi Sender untuk mengirim mail melalui SMTP
|
| 26 |
+
type SMTP struct {
|
| 27 |
+
name string
|
| 28 |
+
fromEmailAddress string
|
| 29 |
+
smtpServerAddress string
|
| 30 |
+
smtpAuthAddress smtp.Auth
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
// New membuat instance baru SMTP dengan konfigurasi.
|
| 34 |
+
func New(cfg *Config) (Sender, error) {
|
| 35 |
+
if err := validateEmail(cfg.From); err != nil {
|
| 36 |
+
return nil, fmt.Errorf("invalid from address: %w", err)
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
return &SMTP{
|
| 40 |
+
name: cfg.From,
|
| 41 |
+
fromEmailAddress: cfg.From,
|
| 42 |
+
smtpServerAddress: fmt.Sprintf("%s:%s", cfg.Host, cfg.Port),
|
| 43 |
+
smtpAuthAddress: smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host),
|
| 44 |
+
}, nil
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
// Send mengirim mail ke penerima dengan subjek, konten HTML, dan data lainnya.
|
| 48 |
+
func (s *SMTP) Send(recipient, subject string, htmlContent string, data any) error {
|
| 49 |
+
e := email.NewEmail()
|
| 50 |
+
e.From = s.fromEmailAddress
|
| 51 |
+
e.To = []string{recipient}
|
| 52 |
+
e.Subject = subject
|
| 53 |
+
e.HTML = []byte(htmlContent)
|
| 54 |
+
return s.sendEmail(e)
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
func (s *SMTP) sendEmail(e *email.Email) error {
|
| 58 |
+
return e.Send(s.smtpServerAddress, s.smtpAuthAddress)
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
func validateEmail(email string) error {
|
| 62 |
+
if email == "" {
|
| 63 |
+
return ErrEmailEmpty
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
if _, err := mail.ParseAddress(email); err != nil {
|
| 67 |
+
return ErrEmailInvalid
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
return nil
|
| 71 |
+
}
|
pkg/worker/processor.go
CHANGED
|
@@ -1,11 +1,12 @@
|
|
| 1 |
package worker
|
| 2 |
|
| 3 |
import (
|
| 4 |
-
"api.qobiltu.id/mail"
|
| 5 |
"context"
|
|
|
|
|
|
|
|
|
|
| 6 |
"github.com/hibiken/asynq"
|
| 7 |
"github.com/redis/go-redis/v9"
|
| 8 |
-
"log/slog"
|
| 9 |
)
|
| 10 |
|
| 11 |
const (
|
|
|
|
| 1 |
package worker
|
| 2 |
|
| 3 |
import (
|
|
|
|
| 4 |
"context"
|
| 5 |
+
"log/slog"
|
| 6 |
+
|
| 7 |
+
"api.qobiltu.id/pkg/mail"
|
| 8 |
"github.com/hibiken/asynq"
|
| 9 |
"github.com/redis/go-redis/v9"
|
|
|
|
| 10 |
)
|
| 11 |
|
| 12 |
const (
|
router/email_route.go
CHANGED
|
@@ -2,11 +2,10 @@ package router
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"api.qobiltu.id/middleware"
|
| 5 |
-
"github.com/gin-gonic/gin"
|
| 6 |
)
|
| 7 |
|
| 8 |
-
func (s *Server) EmailRoute(
|
| 9 |
-
routerGroup := router.Group("/api/v1/email").Use(middleware.AuthUser)
|
| 10 |
{
|
| 11 |
routerGroup.POST("/create-verification", s.emailController.CreateEmailVerification)
|
| 12 |
routerGroup.POST("/verify", s.emailController.Verify)
|
|
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"api.qobiltu.id/middleware"
|
|
|
|
| 5 |
)
|
| 6 |
|
| 7 |
+
func (s *Server) EmailRoute() {
|
| 8 |
+
routerGroup := s.router.Group("/api/v1/email").Use(middleware.AuthUser)
|
| 9 |
{
|
| 10 |
routerGroup.POST("/create-verification", s.emailController.CreateEmailVerification)
|
| 11 |
routerGroup.POST("/verify", s.emailController.Verify)
|
router/router.go
CHANGED
|
@@ -14,7 +14,7 @@ func (s *Server) setupRoutes() {
|
|
| 14 |
AcademyRoute(s.router)
|
| 15 |
QuizRoute(s.router)
|
| 16 |
|
| 17 |
-
s.EmailRoute(
|
| 18 |
s.CVRoute()
|
| 19 |
s.MarriageReadinessProfileRoute()
|
| 20 |
s.PartnerCriteriaRoute()
|
|
|
|
| 14 |
AcademyRoute(s.router)
|
| 15 |
QuizRoute(s.router)
|
| 16 |
|
| 17 |
+
s.EmailRoute()
|
| 18 |
s.CVRoute()
|
| 19 |
s.MarriageReadinessProfileRoute()
|
| 20 |
s.PartnerCriteriaRoute()
|
space/main.go
CHANGED
|
@@ -10,7 +10,7 @@ import (
|
|
| 10 |
email_controller "api.qobiltu.id/controller/email"
|
| 11 |
marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
|
| 12 |
partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
|
| 13 |
-
"api.qobiltu.id/mail"
|
| 14 |
"api.qobiltu.id/pkg/storage"
|
| 15 |
"api.qobiltu.id/pkg/validation"
|
| 16 |
"api.qobiltu.id/pkg/worker"
|
|
|
|
| 10 |
email_controller "api.qobiltu.id/controller/email"
|
| 11 |
marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
|
| 12 |
partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
|
| 13 |
+
"api.qobiltu.id/pkg/mail"
|
| 14 |
"api.qobiltu.id/pkg/storage"
|
| 15 |
"api.qobiltu.id/pkg/validation"
|
| 16 |
"api.qobiltu.id/pkg/worker"
|
space/pkg/worker/processor.go
CHANGED
|
@@ -1,11 +1,12 @@
|
|
| 1 |
package worker
|
| 2 |
|
| 3 |
import (
|
| 4 |
-
"api.qobiltu.id/mail"
|
| 5 |
"context"
|
|
|
|
|
|
|
|
|
|
| 6 |
"github.com/hibiken/asynq"
|
| 7 |
"github.com/redis/go-redis/v9"
|
| 8 |
-
"log/slog"
|
| 9 |
)
|
| 10 |
|
| 11 |
const (
|
|
|
|
| 1 |
package worker
|
| 2 |
|
| 3 |
import (
|
|
|
|
| 4 |
"context"
|
| 5 |
+
"log/slog"
|
| 6 |
+
|
| 7 |
+
"api.qobiltu.id/pkg/mail"
|
| 8 |
"github.com/hibiken/asynq"
|
| 9 |
"github.com/redis/go-redis/v9"
|
|
|
|
| 10 |
)
|
| 11 |
|
| 12 |
const (
|
space/router/email_route.go
CHANGED
|
@@ -2,11 +2,10 @@ package router
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"api.qobiltu.id/middleware"
|
| 5 |
-
"github.com/gin-gonic/gin"
|
| 6 |
)
|
| 7 |
|
| 8 |
-
func (s *Server) EmailRoute(
|
| 9 |
-
routerGroup := router.Group("/api/v1/email").Use(middleware.AuthUser)
|
| 10 |
{
|
| 11 |
routerGroup.POST("/create-verification", s.emailController.CreateEmailVerification)
|
| 12 |
routerGroup.POST("/verify", s.emailController.Verify)
|
|
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"api.qobiltu.id/middleware"
|
|
|
|
| 5 |
)
|
| 6 |
|
| 7 |
+
func (s *Server) EmailRoute() {
|
| 8 |
+
routerGroup := s.router.Group("/api/v1/email").Use(middleware.AuthUser)
|
| 9 |
{
|
| 10 |
routerGroup.POST("/create-verification", s.emailController.CreateEmailVerification)
|
| 11 |
routerGroup.POST("/verify", s.emailController.Verify)
|
space/router/router.go
CHANGED
|
@@ -14,7 +14,7 @@ func (s *Server) setupRoutes() {
|
|
| 14 |
AcademyRoute(s.router)
|
| 15 |
QuizRoute(s.router)
|
| 16 |
|
| 17 |
-
s.EmailRoute(
|
| 18 |
s.CVRoute()
|
| 19 |
s.MarriageReadinessProfileRoute()
|
| 20 |
s.PartnerCriteriaRoute()
|
|
|
|
| 14 |
AcademyRoute(s.router)
|
| 15 |
QuizRoute(s.router)
|
| 16 |
|
| 17 |
+
s.EmailRoute()
|
| 18 |
s.CVRoute()
|
| 19 |
s.MarriageReadinessProfileRoute()
|
| 20 |
s.PartnerCriteriaRoute()
|
space/space/controller/auth/auth_login_controller.go
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
package
|
| 2 |
|
| 3 |
import (
|
| 4 |
"api.qobiltu.id/controller"
|
|
|
|
| 1 |
+
package auth
|
| 2 |
|
| 3 |
import (
|
| 4 |
"api.qobiltu.id/controller"
|
space/space/controller/auth/auth_register_controller.go
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
package
|
| 2 |
|
| 3 |
import (
|
| 4 |
"api.qobiltu.id/controller"
|
|
|
|
| 1 |
+
package auth
|
| 2 |
|
| 3 |
import (
|
| 4 |
"api.qobiltu.id/controller"
|
space/space/controller/email/email_controller.go
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package email_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 EmailController interface {
|
| 14 |
+
CreateEmailVerification(ctx *gin.Context)
|
| 15 |
+
Verify(ctx *gin.Context)
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
type emailController struct {
|
| 19 |
+
emailService services.EmailService
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
func NewEmailController(emailService services.EmailService) EmailController {
|
| 23 |
+
return &emailController{
|
| 24 |
+
emailService: emailService,
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
func (c *emailController) CreateEmailVerification(ctx *gin.Context) {
|
| 29 |
+
accountData := middleware.GetAccountData(ctx)
|
| 30 |
+
req := models.CreateEmailVerificationRequest{
|
| 31 |
+
AccountID: int64(accountData.UserID),
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
res, err := c.emailService.CreateEmailVerification(ctx, &req)
|
| 35 |
+
if err != nil {
|
| 36 |
+
response.HandleError(ctx, err)
|
| 37 |
+
return
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
response.HandleSuccess(ctx, http.StatusOK, "Email verification created", res, nil)
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
func (c *emailController) Verify(ctx *gin.Context) {
|
| 44 |
+
var req models.ValidateEmailVerificationRequest
|
| 45 |
+
if err := ctx.ShouldBindJSON(&req); err != nil {
|
| 46 |
+
response.HandleError(ctx, models.Exception{
|
| 47 |
+
Message: "Invalid body request",
|
| 48 |
+
BadRequest: true,
|
| 49 |
+
Err: err,
|
| 50 |
+
})
|
| 51 |
+
return
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
accountData := middleware.GetAccountData(ctx)
|
| 55 |
+
req.AccountID = int64(accountData.UserID)
|
| 56 |
+
|
| 57 |
+
res, err := c.emailService.ValidateEmailVerification(ctx, &req)
|
| 58 |
+
if err != nil {
|
| 59 |
+
response.HandleError(ctx, err)
|
| 60 |
+
return
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
response.HandleSuccess(ctx, http.StatusOK, "Email verified", res, nil)
|
| 64 |
+
}
|
space/space/main.go
CHANGED
|
@@ -7,9 +7,10 @@ import (
|
|
| 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"
|
| 13 |
"api.qobiltu.id/pkg/storage"
|
| 14 |
"api.qobiltu.id/pkg/validation"
|
| 15 |
"api.qobiltu.id/pkg/worker"
|
|
@@ -54,6 +55,10 @@ func main() {
|
|
| 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,6 +78,7 @@ func main() {
|
|
| 73 |
|
| 74 |
// create server
|
| 75 |
s, err := router.NewServer(
|
|
|
|
| 76 |
cvController,
|
| 77 |
marriageReadinessProfileController,
|
| 78 |
partnerCriteriaController,
|
|
|
|
| 7 |
|
| 8 |
"api.qobiltu.id/config"
|
| 9 |
cv_controller "api.qobiltu.id/controller/cv"
|
| 10 |
+
email_controller "api.qobiltu.id/controller/email"
|
| 11 |
marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
|
| 12 |
partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
|
| 13 |
+
"api.qobiltu.id/pkg/mail"
|
| 14 |
"api.qobiltu.id/pkg/storage"
|
| 15 |
"api.qobiltu.id/pkg/validation"
|
| 16 |
"api.qobiltu.id/pkg/worker"
|
|
|
|
| 55 |
worker.AsyncTaskDistributor = taskDistributor
|
| 56 |
|
| 57 |
// setup repo, service, and controller
|
| 58 |
+
emailRepository := repositories.NewEmailRepository(config.DB)
|
| 59 |
+
emailService := services.NewEmailService(emailRepository, taskDistributor)
|
| 60 |
+
emailController := email_controller.NewEmailController(emailService)
|
| 61 |
+
|
| 62 |
cvRepository := repositories.NewCVRepository(config.DB)
|
| 63 |
cvService := services.NewCVService(cvRepository, localStorage, validator)
|
| 64 |
cvController := cv_controller.NewCVController(cvService)
|
|
|
|
| 78 |
|
| 79 |
// create server
|
| 80 |
s, err := router.NewServer(
|
| 81 |
+
emailController,
|
| 82 |
cvController,
|
| 83 |
marriageReadinessProfileController,
|
| 84 |
partnerCriteriaController,
|
space/space/models/database_orm_model.go
CHANGED
|
@@ -38,13 +38,15 @@ type AccountDetails struct {
|
|
| 38 |
}
|
| 39 |
|
| 40 |
type EmailVerification struct {
|
| 41 |
-
ID
|
|
|
|
|
|
|
| 42 |
UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
|
| 43 |
-
Token
|
| 44 |
-
AccountID uint `json:"account_id"`
|
| 45 |
-
IsExpired bool `json:"is_expired"`
|
| 46 |
-
CreatedAt time.Time `json:"created_at"`
|
| 47 |
ExpiredAt time.Time `json:"expired_at"`
|
|
|
|
|
|
|
|
|
|
| 48 |
}
|
| 49 |
|
| 50 |
type ExternalAuth struct {
|
|
|
|
| 38 |
}
|
| 39 |
|
| 40 |
type EmailVerification struct {
|
| 41 |
+
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
| 42 |
+
AccountID int64 `gorm:"column:account_id;not null" json:"account_id"`
|
| 43 |
+
Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
|
| 44 |
UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
|
| 45 |
+
Token int64 `json:"token"`
|
|
|
|
|
|
|
|
|
|
| 46 |
ExpiredAt time.Time `json:"expired_at"`
|
| 47 |
+
IsExpired bool `json:"is_expired"`
|
| 48 |
+
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
|
| 49 |
+
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
|
| 50 |
}
|
| 51 |
|
| 52 |
type ExternalAuth struct {
|
space/space/models/request_model.go
CHANGED
|
@@ -57,6 +57,17 @@ type AnswerQuizRequest struct {
|
|
| 57 |
Answer int `json:"answer" binding:"required"`
|
| 58 |
}
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
type (
|
| 61 |
SavePersonalityAndPreferenceRequest struct {
|
| 62 |
AccountID int64 `json:"-"`
|
|
|
|
| 57 |
Answer int `json:"answer" binding:"required"`
|
| 58 |
}
|
| 59 |
|
| 60 |
+
type (
|
| 61 |
+
CreateEmailVerificationRequest struct {
|
| 62 |
+
AccountID int64 `json:"-"`
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
ValidateEmailVerificationRequest struct {
|
| 66 |
+
AccountID int64 `json:"-"`
|
| 67 |
+
Token int64 `json:"token" validate:"required"`
|
| 68 |
+
}
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
type (
|
| 72 |
SavePersonalityAndPreferenceRequest struct {
|
| 73 |
AccountID int64 `json:"-"`
|
space/space/repositories/email_repository.go
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package repositories
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"context"
|
| 5 |
+
|
| 6 |
+
"api.qobiltu.id/models"
|
| 7 |
+
"gorm.io/gorm"
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
type EmailRepository interface {
|
| 11 |
+
SaveEmailVerification(ctx context.Context, req *models.EmailVerification) (*models.EmailVerification, error)
|
| 12 |
+
GetEmailVerification(ctx context.Context, accountID int64, token int64) (*models.EmailVerification, error)
|
| 13 |
+
DeleteEmailVerification(ctx context.Context, id int64) error
|
| 14 |
+
|
| 15 |
+
// later, will relocate to account repository
|
| 16 |
+
GetAccountById(ctx context.Context, id int64) (*models.Account, error)
|
| 17 |
+
SaveAccount(ctx context.Context, req *models.Account) (*models.Account, error)
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
type emailRepository struct {
|
| 21 |
+
db *gorm.DB
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
func NewEmailRepository(db *gorm.DB) EmailRepository {
|
| 25 |
+
return &emailRepository{db: db}
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
func (r *emailRepository) SaveEmailVerification(ctx context.Context, req *models.EmailVerification) (*models.EmailVerification, error) {
|
| 29 |
+
err := r.db.WithContext(ctx).Save(req).Error
|
| 30 |
+
if err != nil {
|
| 31 |
+
return nil, err
|
| 32 |
+
}
|
| 33 |
+
return req, nil
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
func (r *emailRepository) GetEmailVerification(ctx context.Context, accountID int64, token int64) (*models.EmailVerification, error) {
|
| 37 |
+
var emailVerification models.EmailVerification
|
| 38 |
+
err := r.db.WithContext(ctx).Where("account_id = ? AND token = ?", accountID, token).First(&emailVerification).Error
|
| 39 |
+
if err != nil {
|
| 40 |
+
return nil, err
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
return &emailVerification, nil
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
func (r *emailRepository) DeleteEmailVerification(ctx context.Context, id int64) error {
|
| 47 |
+
return r.db.WithContext(ctx).Delete(&models.EmailVerification{}, id).Error
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
func (r *emailRepository) GetAccountById(ctx context.Context, id int64) (*models.Account, error) {
|
| 51 |
+
var account models.Account
|
| 52 |
+
err := r.db.WithContext(ctx).Where("id = ?", id).First(&account).Error
|
| 53 |
+
if err != nil {
|
| 54 |
+
return nil, err
|
| 55 |
+
}
|
| 56 |
+
return &account, nil
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
func (r *emailRepository) SaveAccount(ctx context.Context, req *models.Account) (*models.Account, error) {
|
| 60 |
+
err := r.db.WithContext(ctx).Save(req).Error
|
| 61 |
+
if err != nil {
|
| 62 |
+
return nil, err
|
| 63 |
+
}
|
| 64 |
+
return req, nil
|
| 65 |
+
}
|
space/space/router/email_route.go
CHANGED
|
@@ -1,15 +1,13 @@
|
|
| 1 |
package router
|
| 2 |
|
| 3 |
import (
|
| 4 |
-
EmailController "api.qobiltu.id/controller/email"
|
| 5 |
"api.qobiltu.id/middleware"
|
| 6 |
-
"github.com/gin-gonic/gin"
|
| 7 |
)
|
| 8 |
|
| 9 |
-
func
|
| 10 |
-
routerGroup := router.Group("/api/v1/email")
|
| 11 |
{
|
| 12 |
-
routerGroup.POST("/
|
| 13 |
-
routerGroup.POST("/
|
| 14 |
}
|
| 15 |
}
|
|
|
|
| 1 |
package router
|
| 2 |
|
| 3 |
import (
|
|
|
|
| 4 |
"api.qobiltu.id/middleware"
|
|
|
|
| 5 |
)
|
| 6 |
|
| 7 |
+
func (s *Server) EmailRoute() {
|
| 8 |
+
routerGroup := s.router.Group("/api/v1/email").Use(middleware.AuthUser)
|
| 9 |
{
|
| 10 |
+
routerGroup.POST("/create-verification", s.emailController.CreateEmailVerification)
|
| 11 |
+
routerGroup.POST("/verify", s.emailController.Verify)
|
| 12 |
}
|
| 13 |
}
|
space/space/router/router.go
CHANGED
|
@@ -10,12 +10,11 @@ func (s *Server) setupRoutes() {
|
|
| 10 |
|
| 11 |
AuthRoute(s.router)
|
| 12 |
UserRoute(s.router)
|
| 13 |
-
EmailRoute(s.router)
|
| 14 |
OptionsRoute(s.router)
|
| 15 |
AcademyRoute(s.router)
|
| 16 |
QuizRoute(s.router)
|
| 17 |
|
| 18 |
-
|
| 19 |
s.CVRoute()
|
| 20 |
s.MarriageReadinessProfileRoute()
|
| 21 |
s.PartnerCriteriaRoute()
|
|
|
|
| 10 |
|
| 11 |
AuthRoute(s.router)
|
| 12 |
UserRoute(s.router)
|
|
|
|
| 13 |
OptionsRoute(s.router)
|
| 14 |
AcademyRoute(s.router)
|
| 15 |
QuizRoute(s.router)
|
| 16 |
|
| 17 |
+
s.EmailRoute()
|
| 18 |
s.CVRoute()
|
| 19 |
s.MarriageReadinessProfileRoute()
|
| 20 |
s.PartnerCriteriaRoute()
|
space/space/router/server.go
CHANGED
|
@@ -2,6 +2,7 @@ package router
|
|
| 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,12 +10,14 @@ import (
|
|
| 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,6 +27,7 @@ func NewServer(
|
|
| 24 |
router.Use(gin.Recovery())
|
| 25 |
|
| 26 |
server := &Server{
|
|
|
|
| 27 |
cvController: cvController,
|
| 28 |
marriageReadinessProfileController: marriageReadinessProfileController,
|
| 29 |
partnerCriteriaController: partnerCriteriaController,
|
|
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
cv_controller "api.qobiltu.id/controller/cv"
|
| 5 |
+
email_controller "api.qobiltu.id/controller/email"
|
| 6 |
marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
|
| 7 |
partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
|
| 8 |
"github.com/gin-gonic/gin"
|
|
|
|
| 10 |
|
| 11 |
type Server struct {
|
| 12 |
router *gin.Engine
|
| 13 |
+
emailController email_controller.EmailController
|
| 14 |
cvController cv_controller.CVController
|
| 15 |
marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController
|
| 16 |
partnerCriteriaController partner_criteria_controller.PartnerCriteriaController
|
| 17 |
}
|
| 18 |
|
| 19 |
func NewServer(
|
| 20 |
+
emailController email_controller.EmailController,
|
| 21 |
cvController cv_controller.CVController,
|
| 22 |
marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController,
|
| 23 |
partnerCriteriaController partner_criteria_controller.PartnerCriteriaController,
|
|
|
|
| 27 |
router.Use(gin.Recovery())
|
| 28 |
|
| 29 |
server := &Server{
|
| 30 |
+
emailController: emailController,
|
| 31 |
cvController: cvController,
|
| 32 |
marriageReadinessProfileController: marriageReadinessProfileController,
|
| 33 |
partnerCriteriaController: partnerCriteriaController,
|
space/space/services/email_service.go
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package services
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"context"
|
| 5 |
+
"strconv"
|
| 6 |
+
"time"
|
| 7 |
+
|
| 8 |
+
"api.qobiltu.id/config"
|
| 9 |
+
"api.qobiltu.id/pkg/worker"
|
| 10 |
+
"api.qobiltu.id/response"
|
| 11 |
+
"api.qobiltu.id/utils"
|
| 12 |
+
"github.com/hibiken/asynq"
|
| 13 |
+
uuid "github.com/satori/go.uuid"
|
| 14 |
+
|
| 15 |
+
"api.qobiltu.id/models"
|
| 16 |
+
"api.qobiltu.id/repositories"
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
type EmailService interface {
|
| 20 |
+
CreateEmailVerification(ctx context.Context, req *models.CreateEmailVerificationRequest) (*models.EmailVerification, error)
|
| 21 |
+
ValidateEmailVerification(ctx context.Context, req *models.ValidateEmailVerificationRequest) (*models.EmailVerification, error)
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
type emailService struct {
|
| 25 |
+
emailRepository repositories.EmailRepository
|
| 26 |
+
taskDistributor worker.TaskDistributor
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
func NewEmailService(emailRepository repositories.EmailRepository, taskDistributor worker.TaskDistributor) EmailService {
|
| 30 |
+
return &emailService{emailRepository: emailRepository, taskDistributor: taskDistributor}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
func (s *emailService) CreateEmailVerification(ctx context.Context, req *models.CreateEmailVerificationRequest) (*models.EmailVerification, error) {
|
| 34 |
+
account, err := s.emailRepository.GetAccountById(ctx, req.AccountID)
|
| 35 |
+
if err != nil {
|
| 36 |
+
return nil, response.HandleGormError(err, "Internal Server Error")
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
token, err := utils.GenerateToken()
|
| 40 |
+
if err != nil {
|
| 41 |
+
return nil, models.Exception{
|
| 42 |
+
InternalServerError: true,
|
| 43 |
+
Message: "failed to generate token for email verification",
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Minute
|
| 48 |
+
dueTime := time.Now().Add(remainingTime)
|
| 49 |
+
|
| 50 |
+
payload := models.EmailVerification{
|
| 51 |
+
AccountID: int64(account.Id),
|
| 52 |
+
Token: token,
|
| 53 |
+
UUID: uuid.NewV4(),
|
| 54 |
+
ExpiredAt: dueTime,
|
| 55 |
+
IsExpired: false,
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
emailVerification, err := s.emailRepository.SaveEmailVerification(ctx, &payload)
|
| 59 |
+
if err != nil {
|
| 60 |
+
return nil, response.HandleGormError(err, "Internal Server Error")
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
opts := []asynq.Option{
|
| 64 |
+
asynq.MaxRetry(worker.TaskSendVerifyEmailMaxRetry),
|
| 65 |
+
asynq.Queue(worker.Critical),
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
err = s.taskDistributor.DistributeTaskSendVerifyEmail(
|
| 69 |
+
context.Background(),
|
| 70 |
+
&worker.PayloadSendVerifyEmail{
|
| 71 |
+
EmailAddress: account.Email,
|
| 72 |
+
VerificationCode: strconv.Itoa(int(token)),
|
| 73 |
+
ExpirationInMinutes: int(remainingTime.Minutes()),
|
| 74 |
+
Subject: worker.TaskSendVerifyEmailSubject,
|
| 75 |
+
}, opts...)
|
| 76 |
+
|
| 77 |
+
if err != nil {
|
| 78 |
+
return nil, models.Exception{
|
| 79 |
+
InternalServerError: true,
|
| 80 |
+
Message: "failed to distribute task send verify email",
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
return emailVerification, nil
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
func (s *emailService) ValidateEmailVerification(ctx context.Context, req *models.ValidateEmailVerificationRequest) (*models.EmailVerification, error) {
|
| 88 |
+
emailVerification, err := s.emailRepository.GetEmailVerification(ctx, req.AccountID, int64(req.Token))
|
| 89 |
+
if err != nil {
|
| 90 |
+
return nil, response.HandleGormError(err, "Internal Server Error")
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
if emailVerification.ExpiredAt.Before(time.Now()) {
|
| 94 |
+
err = s.emailRepository.DeleteEmailVerification(ctx, emailVerification.ID)
|
| 95 |
+
if err != nil {
|
| 96 |
+
return nil, response.HandleGormError(err, "Internal Server Error")
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
return nil, models.Exception{
|
| 100 |
+
Unauthorized: true,
|
| 101 |
+
Message: "Token has expired!",
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
account, err := s.emailRepository.GetAccountById(ctx, emailVerification.AccountID)
|
| 106 |
+
if err != nil {
|
| 107 |
+
return nil, response.HandleGormError(err, "Internal Server Error")
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
account.IsEmailVerified = true
|
| 111 |
+
|
| 112 |
+
_, err = s.emailRepository.SaveAccount(ctx, account)
|
| 113 |
+
if err != nil {
|
| 114 |
+
return nil, response.HandleGormError(err, "Internal Server Error")
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
err = s.emailRepository.DeleteEmailVerification(ctx, emailVerification.ID)
|
| 118 |
+
if err != nil {
|
| 119 |
+
return nil, response.HandleGormError(err, "Internal Server Error")
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
return emailVerification, nil
|
| 123 |
+
}
|
space/space/space/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 |
+
}
|
space/space/space/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)
|
space/space/space/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,
|
space/space/space/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 |
+
}
|
space/space/space/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.
|
|
|
|
| 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/space/space/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/space/space/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:"
|
| 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 |
-
|
|
|
|
| 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
|
| 409 |
-
ExpectedSkinColors
|
| 410 |
-
ExpectedHairTypes
|
| 411 |
-
ExpectedHairThickness
|
| 412 |
-
|
|
|
|
| 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/space/space/space/models/request_model.go
CHANGED
|
@@ -336,7 +336,8 @@ type (
|
|
| 336 |
SavePartnerCriteriaRequest struct {
|
| 337 |
AccountID int64 `json:"account_id"`
|
| 338 |
|
| 339 |
-
|
|
|
|
| 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
|
| 349 |
-
ExpectedSkinColors
|
| 350 |
-
ExpectedHairTypes
|
| 351 |
-
ExpectedHairThickness
|
| 352 |
-
|
|
|
|
| 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/space/space/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/space/space/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/space/space/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.
|
|
|
|
| 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.
|
|
|
|
| 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/space/space/space/pkg/validation/validation.go
CHANGED
|
@@ -1,174 +1,35 @@
|
|
| 1 |
package validation
|
| 2 |
|
| 3 |
import (
|
| 4 |
-
"
|
| 5 |
-
"
|
| 6 |
-
"reflect"
|
| 7 |
-
"strings"
|
| 8 |
|
| 9 |
-
"
|
| 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 |
-
|
| 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 |
-
//
|
| 88 |
-
|
| 89 |
-
|
| 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
|
| 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 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
}
|
| 166 |
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
}
|
| 172 |
|
| 173 |
-
return
|
| 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/space/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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
return models.Exception{
|
| 10 |
ValidationError: true,
|
| 11 |
Message: "Validation failed",
|
| 12 |
-
ValidationErrorFields:
|
| 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/space/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 :=
|
| 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/space/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/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(
|
| 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/space/space/space/models/database_orm_model.go
CHANGED
|
@@ -248,26 +248,27 @@ type (
|
|
| 248 |
}
|
| 249 |
|
| 250 |
WorshipAndReligiousUnderstandingCV struct {
|
| 251 |
-
ID
|
| 252 |
-
AccountID
|
| 253 |
-
Account
|
| 254 |
-
ObligatoryPrayer
|
| 255 |
-
CongregationalPrayer
|
| 256 |
-
TahajjudPrayer
|
| 257 |
-
DhuhaPrayer
|
| 258 |
-
QuranMemorization
|
| 259 |
-
QuranReadingAbility
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
| 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/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:"
|
| 78 |
}
|
| 79 |
|
| 80 |
GetPersonalityAndPreferenceRequest struct {
|
|
@@ -84,11 +84,11 @@ type (
|
|
| 84 |
CreateFamilyMemberRequest struct {
|
| 85 |
AccountID int64 `json:"-"`
|
| 86 |
|
| 87 |
-
Role *string `json:"role" validate:"
|
| 88 |
-
Status *string `json:"status" validate:"
|
| 89 |
Religion *string `json:"religion" validate:"religion"` // Agama
|
| 90 |
Job *string `json:"job"` // Pekerjaan
|
| 91 |
-
LastEducation *string `json:"last_education" validate:"
|
| 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:"
|
| 100 |
-
Status *string `json:"status" validate:"
|
| 101 |
Religion *string `json:"religion" validate:"religion"` // Agama
|
| 102 |
Job *string `json:"job"` // Pekerjaan
|
| 103 |
-
LastEducation *string `json:"last_education" validate:"
|
| 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:"
|
| 124 |
-
SkinColor *string `json:"skin_color" validate:"
|
| 125 |
-
HairType *string `json:"hair_type" validate:"
|
| 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:"
|
| 143 |
-
LastEducation *string `json:"last_education" validate:"
|
| 144 |
LastJob *string `json:"last_job"`
|
| 145 |
-
PhoneNumber *string `json:"phone_number" validate:"
|
| 146 |
}
|
| 147 |
|
| 148 |
GetAccountDetailsRequest struct {
|
|
@@ -150,22 +150,23 @@ type (
|
|
| 150 |
}
|
| 151 |
|
| 152 |
SaveWorshipAndReligiousUnderstandingRequest struct {
|
| 153 |
-
AccountID
|
| 154 |
-
ObligatoryPrayer
|
| 155 |
-
CongregationalPrayer
|
| 156 |
-
TahajjudPrayer
|
| 157 |
-
DhuhaPrayer
|
| 158 |
-
QuranMemorization
|
| 159 |
-
QuranReadingAbility
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
|
|
|
| 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:"
|
| 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:"
|
| 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:"
|
| 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:"
|
| 222 |
IncomeSources *pq.StringArray `json:"income_sources"` // sumber penghasilan
|
| 223 |
}
|
| 224 |
|
|
@@ -283,48 +284,82 @@ type (
|
|
| 283 |
}
|
| 284 |
)
|
| 285 |
|
| 286 |
-
type
|
| 287 |
-
|
| 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 |
-
|
| 329 |
-
|
| 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/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
|
| 13 |
GetValidOptions(key string) ([]string, error)
|
| 14 |
GetValidKeys() []string
|
|
|
|
|
|
|
|
|
|
| 15 |
}
|
| 16 |
|
| 17 |
// --------------------
|
| 18 |
-
//
|
| 19 |
// --------------------
|
| 20 |
|
| 21 |
-
type
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 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 (
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
|
|
|
| 47 |
}
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
| 49 |
}
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
mu sync.RWMutex
|
| 58 |
-
}
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
OptionName string `json:"option_name"`
|
| 64 |
-
OptionSlug string `json:"option_slug" gorm:"uniqueIndex"`
|
| 65 |
}
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
}
|
| 72 |
-
)
|
| 73 |
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
return nil, err
|
| 78 |
}
|
| 79 |
|
| 80 |
-
options
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
}
|
| 89 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
}
|
| 93 |
|
| 94 |
func (s *DBOptionSource) GetValidOptions(key string) ([]string, error) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
s.mu.RLock()
|
| 96 |
defer s.mu.RUnlock()
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
| 116 |
}
|
| 117 |
|
| 118 |
-
func
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
}
|
| 121 |
|
| 122 |
-
func (v *Validator)
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
}
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
| 131 |
}
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
}
|
| 136 |
}
|
| 137 |
-
return false
|
| 138 |
}
|
|
|
|
|
|
|
| 139 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
return err
|
| 146 |
}
|
|
|
|
| 147 |
}
|
| 148 |
|
| 149 |
-
err := validate.
|
| 150 |
-
if err
|
| 151 |
-
return
|
| 152 |
}
|
| 153 |
|
| 154 |
-
|
| 155 |
-
if
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
}
|
| 158 |
|
| 159 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
}
|
| 161 |
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|
| 171 |
-
phone :=
|
| 172 |
return strings.HasPrefix(phone, "+62")
|
| 173 |
}
|
| 174 |
|
| 175 |
-
func
|
| 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 |
-
|
| 187 |
-
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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/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/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/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/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/space/space/space/services/partner_criteria_service.go
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
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 |
+
|
| 34 |
+
partnerCriteria, err := s.partnerCriteriaRepository.GetPartnerCriteria(ctx, req.AccountID)
|
| 35 |
+
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
| 36 |
+
return nil, response.HandleGormError(err, "Internal Server Error")
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
if partnerCriteria == nil {
|
| 40 |
+
partnerCriteria = &models.PartnerCriteria{}
|
| 41 |
+
}
|
| 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)
|
| 49 |
+
utils.AssignIfNotNil(&partnerCriteria.PartnerWeeklyReligiousStudyFrequency, req.PartnerWeeklyReligiousStudyFrequency)
|
| 50 |
+
utils.AssignIfNotNil(&partnerCriteria.PartnerMonthlySpendingEstimate, req.PartnerMonthlySpendingEstimate)
|
| 51 |
+
utils.AssignIfNotNil(&partnerCriteria.PartnerCanCook, req.PartnerCanCook)
|
| 52 |
+
|
| 53 |
+
utils.AssignIfNotNil(&partnerCriteria.ExpectedBodyShapes, req.ExpectedBodyShapes)
|
| 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)
|
| 61 |
+
utils.AssignIfNotNil(&partnerCriteria.ExpectedLastEducation, req.ExpectedLastEducation)
|
| 62 |
+
utils.AssignIfNotNil(&partnerCriteria.ExpectedJobType, req.ExpectedJobType)
|
| 63 |
+
|
| 64 |
+
res, err := s.partnerCriteriaRepository.SavePartnerCriteria(ctx, partnerCriteria)
|
| 65 |
+
if err != nil {
|
| 66 |
+
return nil, response.HandleGormError(err, "Internal Server Error")
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
return res, nil
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
func (s *partnerCriteriaService) GetPartnerCriteria(ctx context.Context, req *models.GetPartnerCriteriaRequest) (*models.PartnerCriteria, error) {
|
| 73 |
+
res, err := s.partnerCriteriaRepository.GetPartnerCriteria(ctx, req.AccountID)
|
| 74 |
+
if err != nil {
|
| 75 |
+
return nil, response.HandleGormError(err, "Internal Server Error")
|
| 76 |
+
}
|
| 77 |
+
return res, nil
|
| 78 |
+
}
|
space/space/space/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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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/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/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/space/space/space/repositories/cv_repository.go
CHANGED
|
@@ -1,243 +1,259 @@
|
|
| 1 |
package repositories
|
| 2 |
|
| 3 |
import (
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
|
|
|
|
|
|
| 7 |
)
|
| 8 |
|
| 9 |
type CVRepository interface {
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
| 41 |
}
|
| 42 |
|
| 43 |
type cvRepository struct {
|
| 44 |
-
|
| 45 |
}
|
| 46 |
|
| 47 |
func NewCVRepository(db *gorm.DB) CVRepository {
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
}
|
| 52 |
|
| 53 |
func (r *cvRepository) SaveAccountDetails(ctx context.Context, req *models.AccountDetails) (*models.AccountDetails, error) {
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
}
|
| 59 |
|
| 60 |
func (r *cvRepository) GetAccountDetailsByAccountID(ctx context.Context, accountID int64) (*models.AccountDetails, error) {
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
}
|
| 67 |
|
| 68 |
func (r *cvRepository) SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCV) (*models.PersonalityAndPreferenceCV, error) {
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
}
|
| 74 |
|
| 75 |
func (r *cvRepository) GetPersonalityAndPreferenceByAccountID(ctx context.Context, accountID int64) (*models.PersonalityAndPreferenceCV, error) {
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
}
|
| 82 |
|
| 83 |
func (r *cvRepository) SaveFamilyMember(ctx context.Context, req *models.FamilyMemberCV) (*models.FamilyMemberCV, error) {
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
}
|
| 89 |
|
| 90 |
func (r *cvRepository) GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error) {
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
}
|
| 97 |
|
| 98 |
func (r *cvRepository) DeleteFamilyMember(ctx context.Context, id int64) error {
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
}
|
| 104 |
|
| 105 |
func (r *cvRepository) ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error) {
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
}
|
| 112 |
|
| 113 |
func (r *cvRepository) SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthCV) (*models.PhysicalAndHealthCV, error) {
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
}
|
| 119 |
|
| 120 |
func (r *cvRepository) GetPhysicalAndHealthByAccountID(ctx context.Context, accountID int64) (*models.PhysicalAndHealthCV, error) {
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
}
|
| 127 |
|
| 128 |
func (r *cvRepository) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingCV) (*models.WorshipAndReligiousUnderstandingCV, error) {
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
}
|
| 134 |
|
| 135 |
func (r *cvRepository) GetWorshipAndReligiousUnderstandingByAccountID(ctx context.Context, accountID int64) (*models.WorshipAndReligiousUnderstandingCV, error) {
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 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 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 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 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
}
|
| 159 |
|
| 160 |
// GetEducation mengambil satu data pendidikan berdasarkan id
|
| 161 |
func (r *cvRepository) GetEducation(ctx context.Context, id int64) (*models.EducationCV, error) {
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
}
|
| 168 |
|
| 169 |
// DeleteEducation menghapus data pendidikan berdasarkan id
|
| 170 |
func (r *cvRepository) DeleteEducation(ctx context.Context, id int64) error {
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 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 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 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 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
}
|
| 193 |
|
| 194 |
// GetJob mengambil satu data pekerjaan berdasarkan id
|
| 195 |
func (r *cvRepository) GetJob(ctx context.Context, id int64) (*models.JobCV, error) {
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
}
|
| 202 |
|
| 203 |
// DeleteJob menghapus data pekerjaan berdasarkan id
|
| 204 |
func (r *cvRepository) DeleteJob(ctx context.Context, id int64) error {
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 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 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 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 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
}
|
| 227 |
|
| 228 |
// GetAchievement mengambil satu data prestasi berdasarkan id
|
| 229 |
func (r *cvRepository) GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error) {
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
}
|
| 236 |
|
| 237 |
// DeleteAchievement menghapus data prestasi berdasarkan id
|
| 238 |
func (r *cvRepository) DeleteAchievement(ctx context.Context, id int64) error {
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 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/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"
|
|
@@ -59,19 +60,19 @@ type CVService interface {
|
|
| 59 |
type cvService struct {
|
| 60 |
cvRepository repositories.CVRepository
|
| 61 |
storage storage.Storage
|
|
|
|
| 62 |
}
|
| 63 |
|
| 64 |
-
func NewCVService(cvRepository repositories.CVRepository, storage storage.Storage) CVService {
|
| 65 |
return &cvService{
|
| 66 |
cvRepository: cvRepository,
|
| 67 |
storage: storage,
|
|
|
|
| 68 |
}
|
| 69 |
}
|
| 70 |
|
| 71 |
func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAccountDetailsRequest) (*models.AccountDetails, error) {
|
| 72 |
-
|
| 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 |
}
|
| 77 |
|
|
@@ -81,15 +82,42 @@ func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAcco
|
|
| 81 |
return nil, response.HandleGormError(err, "Internal Server Error")
|
| 82 |
}
|
| 83 |
|
| 84 |
-
//
|
| 85 |
-
|
|
|
|
| 86 |
accountDetails = &models.AccountDetails{}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
}
|
| 88 |
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|
|
@@ -98,11 +126,11 @@ func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAcco
|
|
| 98 |
utils.AssignIfNotNil(&accountDetails.LastJob, req.LastJob)
|
| 99 |
|
| 100 |
if req.PhoneNumber != nil {
|
| 101 |
-
|
| 102 |
-
accountDetails.PhoneNumber = &
|
| 103 |
}
|
| 104 |
|
| 105 |
-
// Simpan
|
| 106 |
res, err := s.cvRepository.SaveAccountDetails(ctx, accountDetails)
|
| 107 |
if err != nil {
|
| 108 |
return nil, response.HandleGormError(err, "Internal Server Error")
|
|
@@ -120,7 +148,7 @@ func (s *cvService) GetAccountDetails(ctx context.Context, req *models.GetAccoun
|
|
| 120 |
}
|
| 121 |
|
| 122 |
func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *models.SavePersonalityAndPreferenceRequest) (*models.PersonalityAndPreferenceCV, error) {
|
| 123 |
-
if err :=
|
| 124 |
return nil, response.HandleValidationError(err)
|
| 125 |
}
|
| 126 |
|
|
@@ -170,7 +198,7 @@ func (s *cvService) GetPersonalityAndPreference(ctx context.Context, req *models
|
|
| 170 |
}
|
| 171 |
|
| 172 |
func (s *cvService) CreateFamilyMember(ctx context.Context, req *models.CreateFamilyMemberRequest) (*models.FamilyMemberCV, error) {
|
| 173 |
-
if err :=
|
| 174 |
return nil, response.HandleValidationError(err)
|
| 175 |
}
|
| 176 |
|
|
@@ -219,7 +247,7 @@ func (s *cvService) DeleteFamilyMember(ctx context.Context, req *models.DeleteFa
|
|
| 219 |
}
|
| 220 |
|
| 221 |
func (s *cvService) UpdateFamilyMember(ctx context.Context, req *models.UpdateFamilyMemberRequest) (*models.FamilyMemberCV, error) {
|
| 222 |
-
if err :=
|
| 223 |
return nil, response.HandleValidationError(err)
|
| 224 |
}
|
| 225 |
|
|
@@ -244,7 +272,7 @@ func (s *cvService) UpdateFamilyMember(ctx context.Context, req *models.UpdateFa
|
|
| 244 |
}
|
| 245 |
|
| 246 |
func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.SavePhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error) {
|
| 247 |
-
if err :=
|
| 248 |
return nil, response.HandleValidationError(err)
|
| 249 |
}
|
| 250 |
|
|
@@ -289,7 +317,7 @@ func (s *cvService) GetPhysicalAndHealth(ctx context.Context, req *models.GetPhy
|
|
| 289 |
}
|
| 290 |
|
| 291 |
func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.SaveWorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error) {
|
| 292 |
-
if err :=
|
| 293 |
return nil, response.HandleValidationError(err)
|
| 294 |
}
|
| 295 |
|
|
@@ -313,6 +341,7 @@ func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, re
|
|
| 313 |
utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.DhuhaPrayer, req.DhuhaPrayer)
|
| 314 |
utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.QuranMemorization, req.QuranMemorization)
|
| 315 |
utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.QuranReadingAbility, req.QuranReadingAbility)
|
|
|
|
| 316 |
utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.DaudFasting, req.DaudFasting)
|
| 317 |
utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.AyyamulBidhFasting, req.AyyamulBidhFasting)
|
| 318 |
utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.HajjOrUmrah, req.HajjOrUmrah)
|
|
@@ -341,7 +370,7 @@ func (s *cvService) GetWorshipAndReligiousUnderstanding(ctx context.Context, req
|
|
| 341 |
}
|
| 342 |
|
| 343 |
func (s *cvService) CreateEducation(ctx context.Context, req *models.CreateEducationRequest) (*models.EducationCV, error) {
|
| 344 |
-
if err :=
|
| 345 |
return nil, response.HandleValidationError(err)
|
| 346 |
}
|
| 347 |
|
|
@@ -363,7 +392,7 @@ func (s *cvService) CreateEducation(ctx context.Context, req *models.CreateEduca
|
|
| 363 |
}
|
| 364 |
|
| 365 |
func (s *cvService) UpdateEducation(ctx context.Context, req *models.UpdateEducationRequest) (*models.EducationCV, error) {
|
| 366 |
-
if err :=
|
| 367 |
return nil, response.HandleValidationError(err)
|
| 368 |
}
|
| 369 |
|
|
@@ -402,7 +431,7 @@ func (s *cvService) DeleteEducation(ctx context.Context, req *models.DeleteEduca
|
|
| 402 |
}
|
| 403 |
|
| 404 |
func (s *cvService) CreateJob(ctx context.Context, req *models.CreateJobRequest) (*models.JobCV, error) {
|
| 405 |
-
if err :=
|
| 406 |
return nil, response.HandleValidationError(err)
|
| 407 |
}
|
| 408 |
|
|
@@ -422,7 +451,7 @@ func (s *cvService) CreateJob(ctx context.Context, req *models.CreateJobRequest)
|
|
| 422 |
}
|
| 423 |
|
| 424 |
func (s *cvService) UpdateJob(ctx context.Context, req *models.UpdateJobRequest) (*models.JobCV, error) {
|
| 425 |
-
if err :=
|
| 426 |
return nil, response.HandleValidationError(err)
|
| 427 |
}
|
| 428 |
|
|
|
|
| 5 |
"errors"
|
| 6 |
"math"
|
| 7 |
"strconv"
|
| 8 |
+
"strings"
|
| 9 |
|
| 10 |
"api.qobiltu.id/models"
|
| 11 |
"api.qobiltu.id/pkg/storage"
|
|
|
|
| 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 |
|
|
|
|
| 82 |
return nil, response.HandleGormError(err, "Internal Server Error")
|
| 83 |
}
|
| 84 |
|
| 85 |
+
// Cek apakah accountDetails baru atau existing
|
| 86 |
+
isNew := accountDetails == nil
|
| 87 |
+
if isNew {
|
| 88 |
accountDetails = &models.AccountDetails{}
|
| 89 |
+
accountDetails.AccountID = uint(req.AccountID)
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
// Simpan gender lama jika ada
|
| 93 |
+
oldGender := ""
|
| 94 |
+
if accountDetails.Gender != nil {
|
| 95 |
+
oldGender = strings.ToLower(*accountDetails.Gender)
|
| 96 |
}
|
| 97 |
|
| 98 |
+
// Update gender jika dikirim dari request
|
| 99 |
+
var genderChanged bool
|
| 100 |
+
if req.Gender != nil {
|
| 101 |
+
newGender := strings.ToLower(*req.Gender)
|
| 102 |
+
if oldGender != newGender {
|
| 103 |
+
genderChanged = true
|
| 104 |
+
accountDetails.Gender = req.Gender
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
|
| 108 |
+
// Jika gender baru atau gender berubah, generate InitialName baru via sequence
|
| 109 |
+
if isNew || genderChanged {
|
| 110 |
+
if accountDetails.Gender != nil {
|
| 111 |
+
number, err := s.cvRepository.GetNextInitialNameNumber(ctx, *accountDetails.Gender)
|
| 112 |
+
if err != nil {
|
| 113 |
+
return nil, response.HandleGormError(err, "Gagal generate initial name")
|
| 114 |
+
}
|
| 115 |
+
accountDetails.InitialName = models.BuildInitialName(*accountDetails.Gender, number)
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
// Apply perubahan field lainnya
|
| 120 |
utils.AssignIfNotNil(&accountDetails.FullName, req.FullName)
|
|
|
|
| 121 |
utils.AssignIfNotNil(&accountDetails.DateOfBirth, req.DateOfBirth)
|
| 122 |
utils.AssignIfNotNil(&accountDetails.PlaceOfBirth, req.PlaceOfBirth)
|
| 123 |
utils.AssignIfNotNil(&accountDetails.Domicile, req.Domicile)
|
|
|
|
| 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
|
| 134 |
res, err := s.cvRepository.SaveAccountDetails(ctx, accountDetails)
|
| 135 |
if err != nil {
|
| 136 |
return nil, response.HandleGormError(err, "Internal Server Error")
|
|
|
|
| 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 |
|