diff --git a/config/database_connection_config.go b/config/database_connection_config.go
index 1812dab2e387668188f7631c6a0f076b5eeddc9c..885f8487ee6f87a23c0096509df50d877cf9840d 100644
--- a/config/database_connection_config.go
+++ b/config/database_connection_config.go
@@ -77,6 +77,7 @@ func AutoMigrateAll(db *gorm.DB) {
&models.JobCV{},
&models.AchievementCV{},
&models.MarriageReadinessProfile{},
+ &models.PartnerCriteria{},
)
if err != nil {
diff --git a/controller/partner_criteria/partner_criteria_controller.go b/controller/partner_criteria/partner_criteria_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..d0e093ad369b3e0f0c94f22afbee5be0a1435be3
--- /dev/null
+++ b/controller/partner_criteria/partner_criteria_controller.go
@@ -0,0 +1,66 @@
+package partner_criteria_controller
+
+import (
+ "net/http"
+
+ "api.qobiltu.id/middleware"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/response"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+type PartnerCriteriaController interface {
+ SavePartnerCriteria(ctx *gin.Context)
+ GetPartnerCriteria(ctx *gin.Context)
+}
+
+type partnerCriteriaController struct {
+ partnerCriteriaService services.PartnerCriteriaService
+}
+
+func NewPartnerCriteriaController(partnerCriteriaService services.PartnerCriteriaService) PartnerCriteriaController {
+ return &partnerCriteriaController{
+ partnerCriteriaService: partnerCriteriaService,
+ }
+}
+
+func (c *partnerCriteriaController) SavePartnerCriteria(ctx *gin.Context) {
+ var req models.SavePartnerCriteriaRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
+ return
+ }
+
+ accountData := middleware.GetAccountData(ctx)
+ req.AccountID = int64(accountData.UserID)
+
+ res, err := c.partnerCriteriaService.SavePartnerCriteria(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Partner criteria saved", res, nil)
+}
+
+func (c *partnerCriteriaController) GetPartnerCriteria(ctx *gin.Context) {
+ accountData := middleware.GetAccountData(ctx)
+ accountID := int64(accountData.UserID)
+
+ req := models.GetPartnerCriteriaRequest{
+ AccountID: accountID,
+ }
+
+ res, err := c.partnerCriteriaService.GetPartnerCriteria(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Get partner criteria success", res, nil)
+}
diff --git a/main.go b/main.go
index c7724f635a467ce48272416364a19562ece2e334..40a5b7f888927120475357b7d2a2efea22a2e3a9 100644
--- a/main.go
+++ b/main.go
@@ -9,6 +9,7 @@ import (
cv_controller "api.qobiltu.id/controller/cv"
health_check_controller "api.qobiltu.id/controller/health_check"
marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
+ partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
"api.qobiltu.id/mail"
"api.qobiltu.id/pkg/storage"
"api.qobiltu.id/pkg/validation"
@@ -66,6 +67,10 @@ func main() {
marriageReadinessProfileService := services.NewMarriageReadinessProfileService(marriageReadinessProfileRepository)
marriageReadinessProfileController := marriage_readiness_profile_controller.NewMarriageReadinessProfileController(marriageReadinessProfileService)
+ partnerCriteriaRepository := repositories.NewPartnerCriteriaRepository(config.DB)
+ partnerCriteriaService := services.NewPartnerCriteriaService(partnerCriteriaRepository)
+ partnerCriteriaController := partner_criteria_controller.NewPartnerCriteriaController(partnerCriteriaService)
+
// start task processor
err = taskProcessor.Start()
utils.FatalIfErr("failed to start task processor", err)
@@ -76,6 +81,7 @@ func main() {
healthCheckController,
cvController,
marriageReadinessProfileController,
+ partnerCriteriaController,
)
utils.FatalIfErr("failed to create server", err)
diff --git a/models/database_orm_model.go b/models/database_orm_model.go
index 6a6ca63819f8201a50c7e3f1fd7f49529b8bb613..a22c0a0356257f49957d89e453a68ac925f7d7a0 100644
--- a/models/database_orm_model.go
+++ b/models/database_orm_model.go
@@ -248,26 +248,27 @@ type (
}
WorshipAndReligiousUnderstandingCV struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
- AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id" counter:"skip"`
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
- ObligatoryPrayer *string `gorm:"column:obligatory_prayer" json:"obligatory_prayer"` // sholat_wajib_5_waktu
- CongregationalPrayer *string `gorm:"column:congregational_prayer" json:"congregational_prayer"` // sholat_berjamaah_di_masjid
- TahajjudPrayer *string `gorm:"column:tahajjud_prayer" json:"tahajjud_prayer"` // sholat_tahajud
- DhuhaPrayer *string `gorm:"column:dhuha_prayer" json:"dhuha_prayer"` // sholat_dhuha
- QuranMemorization *string `gorm:"column:quran_memorization" json:"quran_memorization"` // hafalan_alquran
- QuranReadingAbility *string `gorm:"column:quran_reading_ability" json:"quran_reading_ability"` // kemampuan_baca_alquran
- DaudFasting *string `gorm:"column:daud_fasting" json:"daud_fasting"` // puasa_daud
- AyyamulBidhFasting *string `gorm:"column:ayyamul_bidh_fasting" json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
- HajjOrUmrah *pq.StringArray `gorm:"column:hajj_or_umrah;type:varchar(255)[]" json:"hajj_or_umrah"` // ibadah_haji_umroh
- ListeningToMusic *string `gorm:"column:listening_to_music" json:"listening_to_music"` // mendengarkan_musik
- OpinionOnIkhtilat *string `gorm:"column:opinion_on_ikhtilat" json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
- OpinionOnTouchingNonMahram *string `gorm:"column:opinion_on_touching_non_mahram" json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
- OpinionOnVeil *string `gorm:"column:opinion_on_veil" json:"opinion_on_veil"` // pendapat_tentang_cadar
- WeeklyReligiousStudies *string `gorm:"column:weekly_religious_studies" json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
- FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"`
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id" counter:"skip"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
+ ObligatoryPrayer *string `gorm:"column:obligatory_prayer" json:"obligatory_prayer"` // sholat_wajib_5_waktu
+ CongregationalPrayer *string `gorm:"column:congregational_prayer" json:"congregational_prayer"` // sholat_berjamaah_di_masjid
+ TahajjudPrayer *string `gorm:"column:tahajjud_prayer" json:"tahajjud_prayer"` // sholat_tahajud
+ DhuhaPrayer *string `gorm:"column:dhuha_prayer" json:"dhuha_prayer"` // sholat_dhuha
+ QuranMemorization *string `gorm:"column:quran_memorization" json:"quran_memorization"` // hafalan_alquran
+ QuranReadingAbility *string `gorm:"column:quran_reading_ability" json:"quran_reading_ability"` // kemampuan_baca_alquran
+ WeeklyReligiousStudyFrequency *string `gorm:"column:weekly_religious_study_frequency" json:"weekly_religious_study_frequency"` // kajian_yang_diikuti_dalam_sepekan
+ DaudFasting *string `gorm:"column:daud_fasting" json:"daud_fasting"` // puasa_daud
+ AyyamulBidhFasting *string `gorm:"column:ayyamul_bidh_fasting" json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
+ HajjOrUmrah *pq.StringArray `gorm:"column:hajj_or_umrah;type:varchar(255)[]" json:"hajj_or_umrah"` // ibadah_haji_umroh
+ ListeningToMusic *string `gorm:"column:listening_to_music" json:"listening_to_music"` // mendengarkan_musik
+ OpinionOnIkhtilat *string `gorm:"column:opinion_on_ikhtilat" json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
+ OpinionOnTouchingNonMahram *string `gorm:"column:opinion_on_touching_non_mahram" json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
+ OpinionOnVeil *string `gorm:"column:opinion_on_veil" json:"opinion_on_veil"` // pendapat_tentang_cadar
+ WeeklyReligiousStudies *string `gorm:"column:weekly_religious_studies" json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
+ FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"`
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
FieldCounter
}
@@ -388,6 +389,39 @@ type (
}
)
+type (
+ PartnerCriteria struct {
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
+
+ // Kriteria Umum
+ ExpectedAgeLimit *int `gorm:"column:expected_age_limit" json:"expected_age_limit"` // batas usia pasangan yang diharapkan
+ ExpectedDomicile *string `gorm:"column:expected_domicile" json:"expected_domicile"` // domisili pasangan yang diharapkan
+ AcceptedMaritalStatus *pq.StringArray `gorm:"column:accepted_marital_status;type:varchar(255)[]" json:"accepted_marital_status"` // status pernikahan yang diterima
+ PartnerFamilyReligion *string `gorm:"column:partner_family_religion" json:"partner_family_religion"` // agama yang dianut keluarga pasangan
+ PartnerWeeklyReligiousStudyFrequency *string `gorm:"column:partner_weekly_religious_study_frequency" json:"partner_weekly_religious_study_frequency"` // rata-rata jumlah kajian yang diikuti pasangan per pekan
+ PartnerMonthlySpendingEstimate *string `gorm:"column:partner_monthly_spending_estimate" json:"partner_monthly_spending_estimate"` // estimasi pengeluaran pasangan per bulan
+ PartnerCanCook *string `gorm:"column:partner_can_cook" json:"partner_can_cook"` // apakah pasangan diharapkan bisa memasak
+
+ // Kriteria Fisik
+ ExpectedBodyShapes *pq.StringArray `gorm:"column:expected_body_shapes;type:varchar(255)[]" json:"expected_body_shapes"` // bentuk tubuh yang diharapkan
+ ExpectedSkinColors *pq.StringArray `gorm:"column:expected_skin_colors;type:varchar(255)[]" json:"expected_skin_colors"` // warna kulit yang diharapkan
+ ExpectedHairTypes *pq.StringArray `gorm:"column:expected_hair_types;type:varchar(255)[]" json:"expected_hair_types"` // tipe rambut yang diharapkan
+ ExpectedHairThickness *pq.StringArray `gorm:"column:expected_hair_thickness;type:varchar(255)[]" json:"expected_hair_thickness"` // jenis rambut yang diharapkan
+ ExpectedHeight *int `gorm:"column:expected_height" json:"expected_height"` // tinggi badan yang diharapkan
+
+ // Pendidikan & Pekerjaan
+ ExpectedPartnerIncome *string `gorm:"column:expected_partner_income" json:"expected_partner_income"` // penghasilan pasangan per bulan yang diharapkan
+ ExpectedIncomeSources *pq.StringArray `gorm:"column:expected_income_sources;type:varchar(255)[]" json:"expected_income_sources"` // sumber penghasilan pasangan
+ ExpectedLastEducation *pq.StringArray `gorm:"column:expected_last_education;type:varchar(255)[]" json:"expected_last_education"` // pendidikan terakhir yang diharapkan
+ ExpectedJobType *string `gorm:"column:expected_job_type" json:"expected_job_type"` // jenis pekerjaan pasangan yang diharapkan
+
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ }
+)
+
// Gorm table name settings
func (Account) TableName() string { return "account" }
func (AccountDetails) TableName() string { return "account_details" }
diff --git a/models/request_model.go b/models/request_model.go
index 00ddf280beb02479bef5b2f26f032396d258ab1c..da96eb95aa5c95aac7d97e1e58e3295ea6cd7ed1 100644
--- a/models/request_model.go
+++ b/models/request_model.go
@@ -150,22 +150,23 @@ type (
}
SaveWorshipAndReligiousUnderstandingRequest struct {
- AccountID int64 `json:"-"`
- ObligatoryPrayer *string `json:"obligatory_prayer"` // sholat_wajib_5_waktu
- CongregationalPrayer *string `json:"congregational_prayer"` // sholat_berjamaah_di_masjid
- TahajjudPrayer *string `json:"tahajjud_prayer"` // sholat_tahajud
- DhuhaPrayer *string `json:"dhuha_prayer"` // sholat_dhuha
- QuranMemorization *string `json:"quran_memorization"` // hafalan_alquran
- QuranReadingAbility *string `json:"quran_reading_ability" validate:"quran_reading_ability"` // kemampuan_baca_alquran
- DaudFasting *string `json:"daud_fasting"` // puasa_daud
- AyyamulBidhFasting *string `json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
- HajjOrUmrah *pq.StringArray `json:"hajj_or_umrah"` // ibadah_haji_umroh
- ListeningToMusic *string `json:"listening_to_music"` // mendengarkan_musik
- OpinionOnIkhtilat *string `json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
- OpinionOnTouchingNonMahram *string `json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
- OpinionOnVeil *string `json:"opinion_on_veil"` // pendapat_tentang_cadar
- WeeklyReligiousStudies *string `json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
- FollowedUstadz *string `json:"followed_ustadz"` // ustadz_yang_diikuti
+ AccountID int64 `json:"-"`
+ ObligatoryPrayer *string `json:"obligatory_prayer"` // sholat_wajib_5_waktu
+ CongregationalPrayer *string `json:"congregational_prayer"` // sholat_berjamaah_di_masjid
+ TahajjudPrayer *string `json:"tahajjud_prayer"` // sholat_tahajud
+ DhuhaPrayer *string `json:"dhuha_prayer"` // sholat_dhuha
+ QuranMemorization *string `json:"quran_memorization"` // hafalan_alquran
+ QuranReadingAbility *string `json:"quran_reading_ability" validate:"quran_reading_ability"` // kemampuan_baca_alquran
+ WeeklyReligiousStudyFrequency *string `json:"weekly_religious_study_frequency" validate:"weekly_religious_study_frequency"` // kajian_yang_diikuti_dalam_sepekan
+ DaudFasting *string `json:"daud_fasting"` // puasa_daud
+ AyyamulBidhFasting *string `json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
+ HajjOrUmrah *pq.StringArray `json:"hajj_or_umrah"` // ibadah_haji_umroh
+ ListeningToMusic *string `json:"listening_to_music"` // mendengarkan_musik
+ OpinionOnIkhtilat *string `json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
+ OpinionOnTouchingNonMahram *string `json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
+ OpinionOnVeil *string `json:"opinion_on_veil"` // pendapat_tentang_cadar
+ WeeklyReligiousStudies *string `json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
+ FollowedUstadz *string `json:"followed_ustadz"` // ustadz_yang_diikuti
}
GetWorshipAndReligiousUnderstandingRequest struct {
@@ -283,48 +284,82 @@ type (
}
)
-type SaveMarriageReadinessProfileRequest struct {
- AccountID int64 `json:"account_id"`
-
- // Visi Misi Rumah Tangga
- MarriageVision *string `gorm:"column:marriage_vision" json:"marriage_vision"` // Apa visi dan tujuan utama kamu dalam membangun rumah tangga?
- LivingPlan *string `gorm:"column:living_plan" json:"living_plan"` // Setelah menikah, kamu berencana tinggal di mana?
- SpouseRoles *string `gorm:"column:spouse_roles" json:"spouse_roles"` // Menurutmu, apa peran utama suami dan istri dalam rumah tangga?
- SpouseRights *string `gorm:"column:spouse_rights" json:"spouse_rights"` // Apa saja hak suami dan istri menurutmu?
- ConflictResolution *string `gorm:"column:conflict_resolution" json:"conflict_resolution"` // Jika terjadi konflik, bagaimana kamu menyikapinya?
- ParentingStyle *string `gorm:"column:parenting_style" json:"parenting_style"` // Setelah punya anak, pola pengasuhan seperti apa yang kamu inginkan?
-
- // Konsep Acara Pernikahan
- ReadyToMarryDuration *string `gorm:"column:ready_to_marry_duration" json:"ready_to_marry_duration"` // Setelah taaruf dimulai, berapa lama kamu butuh untuk siap menikah?
- WeddingConcept *string `gorm:"column:wedding_concept" json:"wedding_concept"` // Seperti apa konsep pernikahan yang kamu harapkan?
- WeddingFunding *string `gorm:"column:wedding_funding" json:"wedding_funding"` // Apakah kamu sudah mempersiapkan dana pernikahan? Dari mana sumbernya?
-
- // Karir Kedepannya
- CareerAfterMarriage *string `gorm:"column:career_after_marriage" json:"career_after_marriage"` // Apakah kamu ingin tetap bekerja setelah menikah?
- TimeManagement *string `gorm:"column:time_management" json:"time_management"` // Bagaimana kamu membagi waktu antara keluarga, ibadah, dan pekerjaan?
- CareerGoals *string `gorm:"column:career_goals" json:"career_goals"` // Apa impian karier atau cita-cita kamu dalam 5 sampai 10 tahun ke depan?
- SelfDevelopment *string `gorm:"column:self_development" json:"self_development"` // Apa usaha kamu untuk terus mengembangkan diri dan skill?
-
- // Pendidikan Keluarga
- DelayChildren *string `gorm:"column:delay_children" json:"delay_children"` // Apakah kamu berencana menunda memiliki anak?
- ChildEducationPlan *string `gorm:"column:child_education_plan" json:"child_education_plan"` // Apakah kamu sudah punya rencana pendidikan anak?
- ReligiousEmotionalBond *string `gorm:"column:religious_emotional_bond" json:"religious_emotional_bond"` // Bagaimana cara menjaga nilai agama & kedekatan emosional dengan anak?
- ParentingChallenges *string `gorm:"column:parenting_challenges" json:"parenting_challenges"` // Saat menghadapi tantangan pengasuhan, bagaimana kamu menyikapinya?
-
- // Finansial Keluarga
- MonthlyFinance *string `gorm:"column:monthly_finance" json:"monthly_finance"` // Bagaimana kamu mengelola keuangan bulanan?
- FamilyResponsibility *string `gorm:"column:family_responsibility" json:"family_responsibility"` // Apakah kamu masih punya tanggungan keluarga setelah menikah?
- DebtStatus *string `gorm:"column:debt_status" json:"debt_status"` // Apakah kamu punya cicilan/utang setelah menikah?
- FinanceSharing *string `gorm:"column:finance_sharing" json:"finance_sharing"` // Setelah menikah, bagaimana pembagian tanggung jawab keuangan?
- IncomeGapView *string `gorm:"column:income_gap_view" json:"income_gap_view"` // Pandangan kamu jika istri berpenghasilan lebih besar dari suami?
-
- // Keputusan dan Komunikasi
- DecisionMaking *string `gorm:"column:decision_making" json:"decision_making"` // Dalam mengambil keputusan, kamu lebih mempertimbangkan pasangan atau sendiri?
- GrowthTogether *string `gorm:"column:growth_together" json:"growth_together"` // Apakah kamu siap berjuang bersama pasangan? Apa saja yang ingin diperjuangkan?
- ParentIntervention *string `gorm:"column:parent_intervention" json:"parent_intervention"` // Sejauh mana orang tua/mertua boleh ikut campur dalam rumah tangga?
- HabitResponse *string `gorm:"column:habit_response" json:"habit_response"` // Bagaimana kamu menyikapi kebiasaan pasangan yang kurang kamu sukai?
-}
+type (
+ SaveMarriageReadinessProfileRequest struct {
+ AccountID int64 `json:"account_id"`
-type GetMarriageReadinessProfileRequest struct {
- AccountID int64 `json:"-"`
-}
+ // Visi Misi Rumah Tangga
+ MarriageVision *string `gorm:"column:marriage_vision" json:"marriage_vision"` // Apa visi dan tujuan utama kamu dalam membangun rumah tangga?
+ LivingPlan *string `gorm:"column:living_plan" json:"living_plan"` // Setelah menikah, kamu berencana tinggal di mana?
+ SpouseRoles *string `gorm:"column:spouse_roles" json:"spouse_roles"` // Menurutmu, apa peran utama suami dan istri dalam rumah tangga?
+ SpouseRights *string `gorm:"column:spouse_rights" json:"spouse_rights"` // Apa saja hak suami dan istri menurutmu?
+ ConflictResolution *string `gorm:"column:conflict_resolution" json:"conflict_resolution"` // Jika terjadi konflik, bagaimana kamu menyikapinya?
+ ParentingStyle *string `gorm:"column:parenting_style" json:"parenting_style"` // Setelah punya anak, pola pengasuhan seperti apa yang kamu inginkan?
+
+ // Konsep Acara Pernikahan
+ ReadyToMarryDuration *string `gorm:"column:ready_to_marry_duration" json:"ready_to_marry_duration"` // Setelah taaruf dimulai, berapa lama kamu butuh untuk siap menikah?
+ WeddingConcept *string `gorm:"column:wedding_concept" json:"wedding_concept"` // Seperti apa konsep pernikahan yang kamu harapkan?
+ WeddingFunding *string `gorm:"column:wedding_funding" json:"wedding_funding"` // Apakah kamu sudah mempersiapkan dana pernikahan? Dari mana sumbernya?
+
+ // Karir Kedepannya
+ CareerAfterMarriage *string `gorm:"column:career_after_marriage" json:"career_after_marriage"` // Apakah kamu ingin tetap bekerja setelah menikah?
+ TimeManagement *string `gorm:"column:time_management" json:"time_management"` // Bagaimana kamu membagi waktu antara keluarga, ibadah, dan pekerjaan?
+ CareerGoals *string `gorm:"column:career_goals" json:"career_goals"` // Apa impian karier atau cita-cita kamu dalam 5 sampai 10 tahun ke depan?
+ SelfDevelopment *string `gorm:"column:self_development" json:"self_development"` // Apa usaha kamu untuk terus mengembangkan diri dan skill?
+
+ // Pendidikan Keluarga
+ DelayChildren *string `gorm:"column:delay_children" json:"delay_children"` // Apakah kamu berencana menunda memiliki anak?
+ ChildEducationPlan *string `gorm:"column:child_education_plan" json:"child_education_plan"` // Apakah kamu sudah punya rencana pendidikan anak?
+ ReligiousEmotionalBond *string `gorm:"column:religious_emotional_bond" json:"religious_emotional_bond"` // Bagaimana cara menjaga nilai agama & kedekatan emosional dengan anak?
+ ParentingChallenges *string `gorm:"column:parenting_challenges" json:"parenting_challenges"` // Saat menghadapi tantangan pengasuhan, bagaimana kamu menyikapinya?
+
+ // Finansial Keluarga
+ MonthlyFinance *string `gorm:"column:monthly_finance" json:"monthly_finance"` // Bagaimana kamu mengelola keuangan bulanan?
+ FamilyResponsibility *string `gorm:"column:family_responsibility" json:"family_responsibility"` // Apakah kamu masih punya tanggungan keluarga setelah menikah?
+ DebtStatus *string `gorm:"column:debt_status" json:"debt_status"` // Apakah kamu punya cicilan/utang setelah menikah?
+ FinanceSharing *string `gorm:"column:finance_sharing" json:"finance_sharing"` // Setelah menikah, bagaimana pembagian tanggung jawab keuangan?
+ IncomeGapView *string `gorm:"column:income_gap_view" json:"income_gap_view"` // Pandangan kamu jika istri berpenghasilan lebih besar dari suami?
+
+ // Keputusan dan Komunikasi
+ DecisionMaking *string `gorm:"column:decision_making" json:"decision_making"` // Dalam mengambil keputusan, kamu lebih mempertimbangkan pasangan atau sendiri?
+ GrowthTogether *string `gorm:"column:growth_together" json:"growth_together"` // Apakah kamu siap berjuang bersama pasangan? Apa saja yang ingin diperjuangkan?
+ ParentIntervention *string `gorm:"column:parent_intervention" json:"parent_intervention"` // Sejauh mana orang tua/mertua boleh ikut campur dalam rumah tangga?
+ HabitResponse *string `gorm:"column:habit_response" json:"habit_response"` // Bagaimana kamu menyikapi kebiasaan pasangan yang kurang kamu sukai?
+ }
+
+ GetMarriageReadinessProfileRequest struct {
+ AccountID int64 `json:"-"`
+ }
+)
+
+type (
+ SavePartnerCriteriaRequest struct {
+ AccountID int64 `json:"account_id"`
+
+ ExpectedAgeLimit *int `gorm:"column:expected_age_limit" json:"expected_age_limit"` // batas usia pasangan yang diharapkan
+ ExpectedDomicile *string `gorm:"column:expected_domicile" json:"expected_domicile"` // domisili pasangan yang diharapkan
+ AcceptedMaritalStatus *pq.StringArray `gorm:"column:accepted_marital_status;type:varchar(255)[]" json:"accepted_marital_status"` // status pernikahan yang diterima
+ PartnerFamilyReligion *string `gorm:"column:partner_family_religion" json:"partner_family_religion"` // agama yang dianut keluarga pasangan
+ PartnerWeeklyReligiousStudyFrequency *string `gorm:"column:partner_weekly_religious_study_frequency" json:"partner_weekly_religious_study_frequency"` // rata-rata jumlah kajian yang diikuti pasangan per pekan
+ PartnerMonthlySpendingEstimate *string `gorm:"column:partner_monthly_spending_estimate" json:"partner_monthly_spending_estimate"` // estimasi pengeluaran pasangan per bulan
+ PartnerCanCook *string `gorm:"column:partner_can_cook" json:"partner_can_cook"` // apakah pasangan diharapkan bisa memasak
+
+ // Kriteria Fisik
+ ExpectedBodyShapes *pq.StringArray `gorm:"column:expected_body_shapes;type:varchar(255)[]" json:"expected_body_shapes"` // bentuk tubuh yang diharapkan
+ ExpectedSkinColors *pq.StringArray `gorm:"column:expected_skin_colors;type:varchar(255)[]" json:"expected_skin_colors"` // warna kulit yang diharapkan
+ ExpectedHairTypes *pq.StringArray `gorm:"column:expected_hair_types;type:varchar(255)[]" json:"expected_hair_types"` // tipe rambut yang diharapkan
+ ExpectedHairThickness *pq.StringArray `gorm:"column:expected_hair_thickness;type:varchar(255)[]" json:"expected_hair_thickness"` // jenis rambut yang diharapkan
+ ExpectedHeight *int `gorm:"column:expected_height" json:"expected_height"` // tinggi badan yang diharapkan
+
+ // Pendidikan & Pekerjaan
+ ExpectedPartnerIncome *string `gorm:"column:expected_partner_income" json:"expected_partner_income"` // penghasilan pasangan per bulan yang diharapkan
+ ExpectedIncomeSources *pq.StringArray `gorm:"column:expected_income_sources;type:varchar(255)[]" json:"expected_income_sources"` // sumber penghasilan pasangan
+ ExpectedLastEducation *pq.StringArray `gorm:"column:expected_last_education;type:varchar(255)[]" json:"expected_last_education"` // pendidikan terakhir yang diharapkan
+ ExpectedJobType *string `gorm:"column:expected_job_type" json:"expected_job_type"` // jenis pekerjaan pasangan yang diharapkan
+
+ }
+
+ GetPartnerCriteriaRequest struct {
+ AccountID int64 `json:"-"`
+ }
+)
diff --git a/pkg/validation/custom_rules.go b/pkg/validation/custom_rules.go
index a8beb66ef2af72829ac3acff5cfed8f1ba0b80be..031b8c4c8d5484078b9ddfcfc0eadde5d06fbddc 100644
--- a/pkg/validation/custom_rules.go
+++ b/pkg/validation/custom_rules.go
@@ -21,19 +21,20 @@ type ValidOptionSource interface {
type InMemoryOptionSource struct{}
var inMemoryOptions = map[string][]string{
- "last_education": {"SD", "SMP", "SMA", "D1", "D2", "D3", "D4", "D5", "S1", "S2", "S3"},
- "marital_status": {"Belum Menikah", "Duda", "Janda"},
- "gender": {"Laki-laki", "Perempuan"},
- "monthly_expenses": {"< 2 Juta", "2-5 Juta", "5-20 Juta", "> 10 Juta"},
- "monthly_income": {"< 3 Juta", "3-5 Juta", "5-10 Juta", "> 10 Juta"},
- "religion": {"Islam", "Non-Islam"},
- "family_role": {"Ayah", "Ibu", "Kakak", "Adik", "Anak"},
- "life_status": {"Hidup", "Wafat"},
- "body_shape": {"Ideal", "Kurus", "Berisi", "Gemuk"},
- "skin_color": {"Putih", "Kuning Langsat", "Sawo Matang"},
- "hair_type": {"Lurus", "Bergelombang", "Keriting"},
- "frequently": {"Selalu", "Sering", "Kadang", "Jarang", "Tidak Pernah"},
- "quran_reading_ability": {"Lancar", "Menengah", "Perlu Bimbingan"},
+ "last_education": {"SD", "SMP", "SMA", "D1", "D2", "D3", "D4", "D5", "S1", "S2", "S3"},
+ "marital_status": {"Belum Menikah", "Pernah Menikah (Cerai Hidup)", "Pernah Menikah (Cerai Mati)"},
+ "gender": {"Laki-laki", "Perempuan"},
+ "monthly_expenses": {"< 2 Juta", "2-5 Juta", "5-20 Juta", "> 10 Juta"},
+ "monthly_income": {"< 3 Juta", "3-5 Juta", "5-10 Juta", "> 10 Juta"},
+ "religion": {"Islam", "Non-Islam"},
+ "family_role": {"Ayah", "Ibu", "Kakak", "Adik", "Anak"},
+ "life_status": {"Hidup", "Wafat"},
+ "body_shape": {"Ideal", "Kurus", "Normal", "Berisi", "Gemuk"},
+ "skin_color": {"Putih", "Kuning Langsat", "Sawo Matang"},
+ "hair_type": {"Lurus", "Bergelombang", "Keriting"},
+ "frequently": {"Selalu", "Sering", "Kadang", "Jarang", "Tidak Pernah"},
+ "quran_reading_ability": {"Lancar", "Menengah", "Perlu Bimbingan"},
+ "weekly_religious_study_frequency": {"0", "1", "2-3", ">3"},
}
func (s *InMemoryOptionSource) GetValidOptions(key string) ([]string, error) {
diff --git a/repositories/partner_criteria_repository.go b/repositories/partner_criteria_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..c2f7e22979b54b6c9ed308efbf736b862196f7a3
--- /dev/null
+++ b/repositories/partner_criteria_repository.go
@@ -0,0 +1,38 @@
+package repositories
+
+import (
+ "context"
+
+ "api.qobiltu.id/models"
+ "gorm.io/gorm"
+)
+
+type PartnerCriteriaRepository interface {
+ SavePartnerCriteria(ctx context.Context, req *models.PartnerCriteria) (*models.PartnerCriteria, error)
+ GetPartnerCriteria(ctx context.Context, accountID int64) (*models.PartnerCriteria, error)
+}
+
+type partnerCriteriaRepository struct {
+ db *gorm.DB
+}
+
+func NewPartnerCriteriaRepository(db *gorm.DB) PartnerCriteriaRepository {
+ return &partnerCriteriaRepository{
+ db: db,
+ }
+}
+
+func (r *partnerCriteriaRepository) SavePartnerCriteria(ctx context.Context, req *models.PartnerCriteria) (*models.PartnerCriteria, error) {
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
+}
+
+func (r *partnerCriteriaRepository) GetPartnerCriteria(ctx context.Context, accountID int64) (*models.PartnerCriteria, error) {
+ var partnerCriteria models.PartnerCriteria
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&partnerCriteria).Error; err != nil {
+ return nil, err
+ }
+ return &partnerCriteria, nil
+}
diff --git a/router/partner_criteria_route.go b/router/partner_criteria_route.go
new file mode 100644
index 0000000000000000000000000000000000000000..04cbb93cd8ab7dd647021749a276dd67b3e41bc2
--- /dev/null
+++ b/router/partner_criteria_route.go
@@ -0,0 +1,11 @@
+package router
+
+import "api.qobiltu.id/middleware"
+
+func (s *Server) PartnerCriteriaRoute() {
+ routerGroup := s.router.Group("/api/v1/partner-criteria").Use(middleware.AuthUser)
+ {
+ routerGroup.POST("", s.partnerCriteriaController.SavePartnerCriteria)
+ routerGroup.GET("", s.partnerCriteriaController.GetPartnerCriteria)
+ }
+}
diff --git a/router/router.go b/router/router.go
index 47abd2033afd236b8c2ac1d9e766b287906aa2db..82a20254b025833d629a2e549f69ed5562e352e0 100644
--- a/router/router.go
+++ b/router/router.go
@@ -19,5 +19,6 @@ func (s *Server) setupRoutes() {
s.HealthCheckRoute()
s.CVRoute()
s.MarriageReadinessProfileRoute()
+ s.PartnerCriteriaRoute()
s.StorageRoute()
}
diff --git a/router/server.go b/router/server.go
index 601a8ee215eedafff6238c59bef3dee099584096..8dd19894af42aceb6b4f5503a83f4f7a964dcdcc 100644
--- a/router/server.go
+++ b/router/server.go
@@ -4,6 +4,7 @@ import (
cv_controller "api.qobiltu.id/controller/cv"
health_check_controller "api.qobiltu.id/controller/health_check"
marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
+ partner_criteria_controller "api.qobiltu.id/controller/partner_criteria"
"github.com/gin-gonic/gin"
)
@@ -12,12 +13,14 @@ type Server struct {
healthCheckController health_check_controller.HealthCheckController
cvController cv_controller.CVController
marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController
+ partnerCriteriaController partner_criteria_controller.PartnerCriteriaController
}
func NewServer(
healthCheckController health_check_controller.HealthCheckController,
cvController cv_controller.CVController,
marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController,
+ partnerCriteriaController partner_criteria_controller.PartnerCriteriaController,
) (*Server, error) {
router := gin.Default()
@@ -27,6 +30,7 @@ func NewServer(
healthCheckController: healthCheckController,
cvController: cvController,
marriageReadinessProfileController: marriageReadinessProfileController,
+ partnerCriteriaController: partnerCriteriaController,
router: router,
}
diff --git a/services/cv_service.go b/services/cv_service.go
index d89724595ea9a4008a0b51a9876ab196cfbbcec9..9b70d992f399991395d708a61bbf237540932c1a 100644
--- a/services/cv_service.go
+++ b/services/cv_service.go
@@ -339,6 +339,7 @@ func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, re
utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.DhuhaPrayer, req.DhuhaPrayer)
utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.QuranMemorization, req.QuranMemorization)
utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.QuranReadingAbility, req.QuranReadingAbility)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.WeeklyReligiousStudyFrequency, req.WeeklyReligiousStudyFrequency)
utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.DaudFasting, req.DaudFasting)
utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.AyyamulBidhFasting, req.AyyamulBidhFasting)
utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.HajjOrUmrah, req.HajjOrUmrah)
diff --git a/services/partner_criteria_service.go b/services/partner_criteria_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..8154e2c8be277dbdf6006e793e321a704456eded
--- /dev/null
+++ b/services/partner_criteria_service.go
@@ -0,0 +1,77 @@
+package services
+
+import (
+ "context"
+ "errors"
+
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/pkg/validation"
+ "api.qobiltu.id/repositories"
+ "api.qobiltu.id/response"
+ "api.qobiltu.id/utils"
+ "gorm.io/gorm"
+)
+
+type PartnerCriteriaService interface {
+ SavePartnerCriteria(ctx context.Context, req *models.SavePartnerCriteriaRequest) (*models.PartnerCriteria, error)
+ GetPartnerCriteria(ctx context.Context, req *models.GetPartnerCriteriaRequest) (*models.PartnerCriteria, error)
+}
+
+type partnerCriteriaService struct {
+ partnerCriteriaRepository repositories.PartnerCriteriaRepository
+}
+
+func NewPartnerCriteriaService(partnerCriteriaRepository repositories.PartnerCriteriaRepository) PartnerCriteriaService {
+ return &partnerCriteriaService{partnerCriteriaRepository: partnerCriteriaRepository}
+}
+
+func (s *partnerCriteriaService) SavePartnerCriteria(ctx context.Context, req *models.SavePartnerCriteriaRequest) (*models.PartnerCriteria, error) {
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ partnerCriteria, err := s.partnerCriteriaRepository.GetPartnerCriteria(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ if partnerCriteria == nil {
+ partnerCriteria = &models.PartnerCriteria{}
+ }
+
+ partnerCriteria.AccountID = req.AccountID
+
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedAgeLimit, req.ExpectedAgeLimit)
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedDomicile, req.ExpectedDomicile)
+ utils.AssignIfNotNil(&partnerCriteria.AcceptedMaritalStatus, req.AcceptedMaritalStatus)
+ utils.AssignIfNotNil(&partnerCriteria.PartnerFamilyReligion, req.PartnerFamilyReligion)
+ utils.AssignIfNotNil(&partnerCriteria.PartnerWeeklyReligiousStudyFrequency, req.PartnerWeeklyReligiousStudyFrequency)
+ utils.AssignIfNotNil(&partnerCriteria.PartnerMonthlySpendingEstimate, req.PartnerMonthlySpendingEstimate)
+ utils.AssignIfNotNil(&partnerCriteria.PartnerCanCook, req.PartnerCanCook)
+
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedBodyShapes, req.ExpectedBodyShapes)
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedSkinColors, req.ExpectedSkinColors)
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedHairTypes, req.ExpectedHairTypes)
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedHairThickness, req.ExpectedHairThickness)
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedHeight, req.ExpectedHeight)
+
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedPartnerIncome, req.ExpectedPartnerIncome)
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedIncomeSources, req.ExpectedIncomeSources)
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedLastEducation, req.ExpectedLastEducation)
+ utils.AssignIfNotNil(&partnerCriteria.ExpectedJobType, req.ExpectedJobType)
+
+ res, err := s.partnerCriteriaRepository.SavePartnerCriteria(ctx, partnerCriteria)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return res, nil
+}
+
+func (s *partnerCriteriaService) GetPartnerCriteria(ctx context.Context, req *models.GetPartnerCriteriaRequest) (*models.PartnerCriteria, error) {
+ res, err := s.partnerCriteriaRepository.GetPartnerCriteria(ctx, req.AccountID)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+ return res, nil
+}
diff --git a/space/config/database_connection_config.go b/space/config/database_connection_config.go
index ec3835a070b704d9d3b3dff371e3cb621a335fac..885f8487ee6f87a23c0096509df50d877cf9840d 100644
--- a/space/config/database_connection_config.go
+++ b/space/config/database_connection_config.go
@@ -3,7 +3,6 @@ package config
import (
"fmt"
"log"
- "log/slog"
"os"
"gorm.io/driver/postgres"
@@ -78,11 +77,23 @@ func AutoMigrateAll(db *gorm.DB) {
&models.JobCV{},
&models.AchievementCV{},
&models.MarriageReadinessProfile{},
+ &models.PartnerCriteria{},
)
if err != nil {
log.Fatal(err)
}
- slog.Info("Auto-migration completed successfully")
+ sequences := []string{
+ `CREATE SEQUENCE IF NOT EXISTS seq_ikh_counter START 1 INCREMENT 1 MINVALUE 1;`,
+ `CREATE SEQUENCE IF NOT EXISTS seq_akh_counter START 1 INCREMENT 1 MINVALUE 1;`,
+ }
+
+ for _, seq := range sequences {
+ if err := db.Exec(seq).Error; err != nil {
+ fmt.Printf("Gagal membuat sequence: %v", err)
+ }
+ }
+
+ fmt.Println("Auto-migration sequence check completed successfully")
}
diff --git a/space/models/sequence.go b/space/models/sequence.go
new file mode 100644
index 0000000000000000000000000000000000000000..26eb7b0ab18d9c06e1b763f79949efa3c4bc99df
--- /dev/null
+++ b/space/models/sequence.go
@@ -0,0 +1,25 @@
+package models
+
+import (
+ "fmt"
+ "strings"
+)
+
+const (
+ SeqIkhCounter = "seq_ikh_counter"
+ SeqAkhCounter = "seq_akh_counter"
+)
+
+func GetSequenceName(gender string) string {
+ if strings.ToLower(gender) == "laki-laki" {
+ return SeqIkhCounter
+ }
+ return SeqAkhCounter
+}
+
+func BuildInitialName(gender string, number int64) string {
+ if strings.ToLower(gender) == "laki-laki" {
+ return fmt.Sprintf("IKH_%d", number)
+ }
+ return fmt.Sprintf("AKH_%d", number)
+}
diff --git a/space/repositories/account_repository.go b/space/repositories/account_repository.go
index b623ed382a9d0631977fb3e5be33584e3002788e..b95bbc13a903938c852f3b3590abaf0bac3a8610 100644
--- a/space/repositories/account_repository.go
+++ b/space/repositories/account_repository.go
@@ -85,3 +85,21 @@ func UpdateAccountDetails(accountDetails models.AccountDetails) Repository[model
repo.Result = accountDetails
return *repo
}
+
+func GetNextInitialNameNumber(gender string) Repository[string, int] {
+ repo := Construct[string, int](gender)
+ sequenceName := models.GetSequenceName(gender)
+ repo.Transaction.Raw("SELECT nextval('" + sequenceName + "')").Scan(&repo.Result)
+ return *repo
+}
+
+func GetAccountDetailsByAccountID(accountID uint) Repository[models.AccountDetails, models.AccountDetails] {
+ repo := Construct[models.AccountDetails, models.AccountDetails](
+ models.AccountDetails{AccountID: accountID},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.AccountDetails, models.AccountDetails],
+ Find[models.AccountDetails, models.AccountDetails],
+ )
+ return *repo
+}
diff --git a/space/repositories/cv_repository.go b/space/repositories/cv_repository.go
index 833e4d1a44d3e962231c0ef636a934361635a883..7f0aee8377cd01df5d88cb4f9f3c24a03e1ee767 100644
--- a/space/repositories/cv_repository.go
+++ b/space/repositories/cv_repository.go
@@ -1,243 +1,259 @@
package repositories
import (
- "api.qobiltu.id/models"
- "context"
- "gorm.io/gorm"
+ "context"
+ "fmt"
+
+ "api.qobiltu.id/models"
+ "gorm.io/gorm"
)
type CVRepository interface {
- SaveAccountDetails(ctx context.Context, req *models.AccountDetails) (*models.AccountDetails, error)
- GetAccountDetailsByAccountID(ctx context.Context, accountID int64) (*models.AccountDetails, error)
-
- SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCV) (*models.PersonalityAndPreferenceCV, error)
- GetPersonalityAndPreferenceByAccountID(ctx context.Context, accountID int64) (*models.PersonalityAndPreferenceCV, error)
-
- SaveFamilyMember(ctx context.Context, req *models.FamilyMemberCV) (*models.FamilyMemberCV, error)
- ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error)
- GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error)
- DeleteFamilyMember(ctx context.Context, id int64) error
-
- SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthCV) (*models.PhysicalAndHealthCV, error)
- GetPhysicalAndHealthByAccountID(ctx context.Context, accountID int64) (*models.PhysicalAndHealthCV, error)
-
- SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingCV) (*models.WorshipAndReligiousUnderstandingCV, error)
- GetWorshipAndReligiousUnderstandingByAccountID(ctx context.Context, accountID int64) (*models.WorshipAndReligiousUnderstandingCV, error)
-
- SaveEducation(ctx context.Context, req *models.EducationCV) (*models.EducationCV, error)
- ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error)
- GetEducation(ctx context.Context, id int64) (*models.EducationCV, error)
- DeleteEducation(ctx context.Context, id int64) error
-
- SaveJob(ctx context.Context, req *models.JobCV) (*models.JobCV, error)
- ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error)
- GetJob(ctx context.Context, id int64) (*models.JobCV, error)
- DeleteJob(ctx context.Context, id int64) error
-
- SaveAchievement(ctx context.Context, req *models.AchievementCV) (*models.AchievementCV, error)
- ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error)
- GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error)
- DeleteAchievement(ctx context.Context, id int64) error
+ SaveAccountDetails(ctx context.Context, req *models.AccountDetails) (*models.AccountDetails, error)
+ GetAccountDetailsByAccountID(ctx context.Context, accountID int64) (*models.AccountDetails, error)
+ GetNextInitialNameNumber(ctx context.Context, gender string) (int64, error)
+
+ SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCV) (*models.PersonalityAndPreferenceCV, error)
+ GetPersonalityAndPreferenceByAccountID(ctx context.Context, accountID int64) (*models.PersonalityAndPreferenceCV, error)
+
+ SaveFamilyMember(ctx context.Context, req *models.FamilyMemberCV) (*models.FamilyMemberCV, error)
+ ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error)
+ GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error)
+ DeleteFamilyMember(ctx context.Context, id int64) error
+
+ SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthCV) (*models.PhysicalAndHealthCV, error)
+ GetPhysicalAndHealthByAccountID(ctx context.Context, accountID int64) (*models.PhysicalAndHealthCV, error)
+
+ SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingCV) (*models.WorshipAndReligiousUnderstandingCV, error)
+ GetWorshipAndReligiousUnderstandingByAccountID(ctx context.Context, accountID int64) (*models.WorshipAndReligiousUnderstandingCV, error)
+
+ SaveEducation(ctx context.Context, req *models.EducationCV) (*models.EducationCV, error)
+ ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error)
+ GetEducation(ctx context.Context, id int64) (*models.EducationCV, error)
+ DeleteEducation(ctx context.Context, id int64) error
+
+ SaveJob(ctx context.Context, req *models.JobCV) (*models.JobCV, error)
+ ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error)
+ GetJob(ctx context.Context, id int64) (*models.JobCV, error)
+ DeleteJob(ctx context.Context, id int64) error
+
+ SaveAchievement(ctx context.Context, req *models.AchievementCV) (*models.AchievementCV, error)
+ ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error)
+ GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error)
+ DeleteAchievement(ctx context.Context, id int64) error
}
type cvRepository struct {
- db *gorm.DB
+ db *gorm.DB
}
func NewCVRepository(db *gorm.DB) CVRepository {
- return &cvRepository{
- db: db,
- }
+ return &cvRepository{
+ db: db,
+ }
}
func (r *cvRepository) SaveAccountDetails(ctx context.Context, req *models.AccountDetails) (*models.AccountDetails, error) {
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
- return req, err
- }
- return req, nil
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
}
func (r *cvRepository) GetAccountDetailsByAccountID(ctx context.Context, accountID int64) (*models.AccountDetails, error) {
- var accountDetails models.AccountDetails
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&accountDetails).Error; err != nil {
- return nil, err
- }
- return &accountDetails, nil
+ var accountDetails models.AccountDetails
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&accountDetails).Error; err != nil {
+ return nil, err
+ }
+ return &accountDetails, nil
+}
+
+func (r *cvRepository) GetNextInitialNameNumber(ctx context.Context, gender string) (int64, error) {
+ sequenceName := models.GetSequenceName(gender)
+
+ var number int64
+ query := fmt.Sprintf("SELECT nextval('%s')", sequenceName)
+ err := r.db.WithContext(ctx).Raw(query).Scan(&number).Error
+ if err != nil {
+ return 0, err
+ }
+
+ return number, nil
}
func (r *cvRepository) SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCV) (*models.PersonalityAndPreferenceCV, error) {
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
- return req, err
- }
- return req, nil
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
}
func (r *cvRepository) GetPersonalityAndPreferenceByAccountID(ctx context.Context, accountID int64) (*models.PersonalityAndPreferenceCV, error) {
- var personalityAndPreference models.PersonalityAndPreferenceCV
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&personalityAndPreference).Error; err != nil {
- return nil, err
- }
- return &personalityAndPreference, nil
+ var personalityAndPreference models.PersonalityAndPreferenceCV
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&personalityAndPreference).Error; err != nil {
+ return nil, err
+ }
+ return &personalityAndPreference, nil
}
func (r *cvRepository) SaveFamilyMember(ctx context.Context, req *models.FamilyMemberCV) (*models.FamilyMemberCV, error) {
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
- return req, err
- }
- return req, nil
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
}
func (r *cvRepository) GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error) {
- var familyMember models.FamilyMemberCV
- if err := r.db.WithContext(ctx).Where("id = ?", id).First(&familyMember).Error; err != nil {
- return nil, err
- }
- return &familyMember, nil
+ var familyMember models.FamilyMemberCV
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&familyMember).Error; err != nil {
+ return nil, err
+ }
+ return &familyMember, nil
}
func (r *cvRepository) DeleteFamilyMember(ctx context.Context, id int64) error {
- if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.FamilyMemberCV{}).Error; err != nil {
- return err
- }
- return nil
+ if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.FamilyMemberCV{}).Error; err != nil {
+ return err
+ }
+ return nil
}
func (r *cvRepository) ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error) {
- familyMembers := make([]models.FamilyMemberCV, 0)
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&familyMembers).Error; err != nil {
- return nil, err
- }
- return familyMembers, nil
+ familyMembers := make([]models.FamilyMemberCV, 0)
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&familyMembers).Error; err != nil {
+ return nil, err
+ }
+ return familyMembers, nil
}
func (r *cvRepository) SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthCV) (*models.PhysicalAndHealthCV, error) {
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
- return req, err
- }
- return req, nil
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
}
func (r *cvRepository) GetPhysicalAndHealthByAccountID(ctx context.Context, accountID int64) (*models.PhysicalAndHealthCV, error) {
- var physicalAndHealth models.PhysicalAndHealthCV
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&physicalAndHealth).Error; err != nil {
- return nil, err
- }
- return &physicalAndHealth, nil
+ var physicalAndHealth models.PhysicalAndHealthCV
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&physicalAndHealth).Error; err != nil {
+ return nil, err
+ }
+ return &physicalAndHealth, nil
}
func (r *cvRepository) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingCV) (*models.WorshipAndReligiousUnderstandingCV, error) {
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
- return req, err
- }
- return req, nil
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
}
func (r *cvRepository) GetWorshipAndReligiousUnderstandingByAccountID(ctx context.Context, accountID int64) (*models.WorshipAndReligiousUnderstandingCV, error) {
- var worshipAndReligiousUnderstanding models.WorshipAndReligiousUnderstandingCV
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&worshipAndReligiousUnderstanding).Error; err != nil {
- return nil, err
- }
- return &worshipAndReligiousUnderstanding, nil
+ var worshipAndReligiousUnderstanding models.WorshipAndReligiousUnderstandingCV
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&worshipAndReligiousUnderstanding).Error; err != nil {
+ return nil, err
+ }
+ return &worshipAndReligiousUnderstanding, nil
}
// SaveEducation menyimpan atau memperbarui data pendidikan ke database
func (r *cvRepository) SaveEducation(ctx context.Context, req *models.EducationCV) (*models.EducationCV, error) {
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
- return req, err
- }
- return req, nil
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
}
// ListEducation mengambil daftar data pendidikan berdasarkan account_id
func (r *cvRepository) ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error) {
- var educations []models.EducationCV
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&educations).Error; err != nil {
- return nil, err
- }
- return educations, nil
+ var educations []models.EducationCV
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&educations).Error; err != nil {
+ return nil, err
+ }
+ return educations, nil
}
// GetEducation mengambil satu data pendidikan berdasarkan id
func (r *cvRepository) GetEducation(ctx context.Context, id int64) (*models.EducationCV, error) {
- var education models.EducationCV
- if err := r.db.WithContext(ctx).Where("id = ?", id).First(&education).Error; err != nil {
- return nil, err
- }
- return &education, nil
+ var education models.EducationCV
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&education).Error; err != nil {
+ return nil, err
+ }
+ return &education, nil
}
// DeleteEducation menghapus data pendidikan berdasarkan id
func (r *cvRepository) DeleteEducation(ctx context.Context, id int64) error {
- if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.EducationCV{}).Error; err != nil {
- return err
- }
- return nil
+ if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.EducationCV{}).Error; err != nil {
+ return err
+ }
+ return nil
}
// SaveJob menyimpan atau memperbarui data pekerjaan ke database
func (r *cvRepository) SaveJob(ctx context.Context, req *models.JobCV) (*models.JobCV, error) {
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
- return req, err
- }
- return req, nil
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
}
// ListJob mengambil daftar data pekerjaan berdasarkan account_id
func (r *cvRepository) ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error) {
- var jobs []models.JobCV
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&jobs).Error; err != nil {
- return nil, err
- }
- return jobs, nil
+ var jobs []models.JobCV
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&jobs).Error; err != nil {
+ return nil, err
+ }
+ return jobs, nil
}
// GetJob mengambil satu data pekerjaan berdasarkan id
func (r *cvRepository) GetJob(ctx context.Context, id int64) (*models.JobCV, error) {
- var job models.JobCV
- if err := r.db.WithContext(ctx).Where("id = ?", id).First(&job).Error; err != nil {
- return nil, err
- }
- return &job, nil
+ var job models.JobCV
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&job).Error; err != nil {
+ return nil, err
+ }
+ return &job, nil
}
// DeleteJob menghapus data pekerjaan berdasarkan id
func (r *cvRepository) DeleteJob(ctx context.Context, id int64) error {
- if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.JobCV{}).Error; err != nil {
- return err
- }
- return nil
+ if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.JobCV{}).Error; err != nil {
+ return err
+ }
+ return nil
}
// SaveAchievement menyimpan atau memperbarui data prestasi ke database
func (r *cvRepository) SaveAchievement(ctx context.Context, req *models.AchievementCV) (*models.AchievementCV, error) {
- if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
- return req, err
- }
- return req, nil
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
}
// ListAchievement mengambil daftar data prestasi berdasarkan account_id
func (r *cvRepository) ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error) {
- var achievements []models.AchievementCV
- if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&achievements).Error; err != nil {
- return nil, err
- }
- return achievements, nil
+ var achievements []models.AchievementCV
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&achievements).Error; err != nil {
+ return nil, err
+ }
+ return achievements, nil
}
// GetAchievement mengambil satu data prestasi berdasarkan id
func (r *cvRepository) GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error) {
- var achievement models.AchievementCV
- if err := r.db.WithContext(ctx).Where("id = ?", id).First(&achievement).Error; err != nil {
- return nil, err
- }
- return &achievement, nil
+ var achievement models.AchievementCV
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&achievement).Error; err != nil {
+ return nil, err
+ }
+ return &achievement, nil
}
// DeleteAchievement menghapus data prestasi berdasarkan id
func (r *cvRepository) DeleteAchievement(ctx context.Context, id int64) error {
- if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.AchievementCV{}).Error; err != nil {
- return err
- }
- return nil
+ if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.AchievementCV{}).Error; err != nil {
+ return err
+ }
+ return nil
}
diff --git a/space/services/cv_service.go b/space/services/cv_service.go
index db8b6abb7357a69409b53828e30aca315c12227a..d89724595ea9a4008a0b51a9876ab196cfbbcec9 100644
--- a/space/services/cv_service.go
+++ b/space/services/cv_service.go
@@ -5,6 +5,7 @@ import (
"errors"
"math"
"strconv"
+ "strings"
"api.qobiltu.id/models"
"api.qobiltu.id/pkg/storage"
@@ -69,8 +70,6 @@ func NewCVService(cvRepository repositories.CVRepository, storage storage.Storag
}
func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAccountDetailsRequest) (*models.AccountDetails, error) {
- // notes
- // jika ingin mengubah value wajib kirimkan field beserta value nya
if err := validation.Validate(req); err != nil {
return nil, response.HandleValidationError(err)
}
@@ -81,15 +80,42 @@ func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAcco
return nil, response.HandleGormError(err, "Internal Server Error")
}
- // Apply perubahan
- if accountDetails == nil {
+ // Cek apakah accountDetails baru atau existing
+ isNew := accountDetails == nil
+ if isNew {
accountDetails = &models.AccountDetails{}
+ accountDetails.AccountID = uint(req.AccountID)
+ }
+
+ // Simpan gender lama jika ada
+ oldGender := ""
+ if accountDetails.Gender != nil {
+ oldGender = strings.ToLower(*accountDetails.Gender)
}
- accountDetails.AccountID = uint(req.AccountID)
+ // Update gender jika dikirim dari request
+ var genderChanged bool
+ if req.Gender != nil {
+ newGender := strings.ToLower(*req.Gender)
+ if oldGender != newGender {
+ genderChanged = true
+ accountDetails.Gender = req.Gender
+ }
+ }
+ // Jika gender baru atau gender berubah, generate InitialName baru via sequence
+ if isNew || genderChanged {
+ if accountDetails.Gender != nil {
+ number, err := s.cvRepository.GetNextInitialNameNumber(ctx, *accountDetails.Gender)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal generate initial name")
+ }
+ accountDetails.InitialName = models.BuildInitialName(*accountDetails.Gender, number)
+ }
+ }
+
+ // Apply perubahan field lainnya
utils.AssignIfNotNil(&accountDetails.FullName, req.FullName)
- utils.AssignIfNotNil(&accountDetails.Gender, req.Gender)
utils.AssignIfNotNil(&accountDetails.DateOfBirth, req.DateOfBirth)
utils.AssignIfNotNil(&accountDetails.PlaceOfBirth, req.PlaceOfBirth)
utils.AssignIfNotNil(&accountDetails.Domicile, req.Domicile)
@@ -102,7 +128,7 @@ func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAcco
accountDetails.PhoneNumber = &sanitizedPhone
}
- // Simpan data
+ // Simpan ke database
res, err := s.cvRepository.SaveAccountDetails(ctx, accountDetails)
if err != nil {
return nil, response.HandleGormError(err, "Internal Server Error")
diff --git a/space/services/user_profile_service.go b/space/services/user_profile_service.go
index 591763f10fbf4c5b7c60ba9f64d0fd6427f99c9f..6f5dd7a19ce18e38d63ba5b379df50629c3680be 100644
--- a/space/services/user_profile_service.go
+++ b/space/services/user_profile_service.go
@@ -2,7 +2,6 @@ package services
import (
"regexp"
- "strconv"
"strings"
"api.qobiltu.id/models"
@@ -73,22 +72,47 @@ func (s *UserProfileService) Retrieve() {
}
func (s *UserProfileService) Update() {
+ // Sanitize phone number
if s.Constructor.PhoneNumber != nil {
phoneNumber := *s.Constructor.PhoneNumber
*s.Constructor.PhoneNumber = SanitizePhoneNumber(phoneNumber)
}
- usersCount := repositories.GetAllAccount().RowsCount
+
+ // Ambil data account details dari DB
+ existingDetails := repositories.GetAccountDetailsByAccountID(uint(s.Constructor.AccountID))
+ if existingDetails.RowsError != nil {
+ s.Error = existingDetails.RowsError
+ return
+ }
+ if existingDetails.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "Account details not found"
+ return
+ }
+
+ // Update initial name jika gender berubah
var initialName string
+ shouldUpdateInitialName := false
+
if s.Constructor.Gender != nil {
- if strings.ToLower(*s.Constructor.Gender) == "laki-laki" {
- initialName = "IKH_"
- } else {
- initialName = "AKH_"
+ newGender := strings.ToLower(*s.Constructor.Gender)
+ oldGender := ""
+ if existingDetails.Result.Gender != nil {
+ oldGender = strings.ToLower(*existingDetails.Result.Gender)
+ }
+ if newGender != oldGender {
+ initialNameNumber := repositories.GetNextInitialNameNumber(*s.Constructor.Gender)
+ initialName = models.BuildInitialName(*s.Constructor.Gender, int64(initialNameNumber.Result))
+ s.Constructor.InitialName = initialName
+ shouldUpdateInitialName = true
}
}
- initialName += strconv.Itoa(usersCount)
- s.Constructor.InitialName = initialName
+ // Update ke database
+ if !shouldUpdateInitialName {
+ s.Constructor.InitialName = existingDetails.Result.InitialName
+ }
+
userProfile := repositories.UpdateAccountDetails(s.Constructor)
s.Error = userProfile.RowsError
if userProfile.NoRecord {
@@ -96,6 +120,8 @@ func (s *UserProfileService) Update() {
s.Exception.Message = "There is no account with given credentials!"
return
}
+
+ // Update flag isDetailCompleted di account
account := repositories.GetAccountById(uint(s.Constructor.AccountID))
account.Result.IsDetailCompleted = (userProfile.Result.InitialName != "" &&
userProfile.Result.FullName != nil &&
@@ -107,9 +133,11 @@ func (s *UserProfileService) Update() {
userProfile.Result.LastEducation != nil &&
userProfile.Result.MaritalStatus != nil)
repositories.UpdateAccount(account.Result)
+
s.Result = models.UserProfileResponse{
Account: account.Result,
Details: userProfile.Result,
}
+
s.Result.Account.Password = "SECRET"
}
diff --git a/space/space/models/database_orm_model.go b/space/space/models/database_orm_model.go
index 416647a06a315d8c3d0299bffaf95f9c9e69d9e3..6a6ca63819f8201a50c7e3f1fd7f49529b8bb613 100644
--- a/space/space/models/database_orm_model.go
+++ b/space/space/models/database_orm_model.go
@@ -19,8 +19,8 @@ type Account struct {
}
type AccountDetails struct {
- ID uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
- AccountID uint `gorm:"column:account_id;not null;unique" json:"account_id"`
+ ID uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
+ AccountID uint `gorm:"column:account_id;not null;unique" json:"account_id" counter:"skip"`
InitialName string `gorm:"column:initial_name;not null" json:"initial_name"`
FullName *string `gorm:"column:full_name" json:"full_name"`
DateOfBirth *time.Time `gorm:"column:date_of_birth" json:"date_of_birth"`
@@ -32,8 +32,8 @@ type AccountDetails struct {
MaritalStatus *string `gorm:"column:marital_status" json:"marital_status"`
Avatar *string `gorm:"column:avatar" json:"avatar"`
PhoneNumber *string `gorm:"column:phone_number" json:"phone_number"`
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"`
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
FieldCounter
}
@@ -193,9 +193,9 @@ type QuizResult struct {
type (
PersonalityAndPreferenceCV struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
- AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id" counter:"skip"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
PositiveTraits *string `gorm:"column:positive_traits" json:"positive_traits"` // sifat positif
NegativeTraits *string `gorm:"column:negative_traits" json:"negative_traits"` // sifat negatif
Hobbies *string `gorm:"column:hobbies" json:"hobbies"` // hobi
@@ -210,30 +210,30 @@ type (
CanCook *bool `gorm:"column:can_cook" json:"can_cook"` // bisa memasak
TypesOfDishesCooked *string `gorm:"column:types_of_dishes_cooked" json:"types_of_dishes_cooked"` // jenis masakan yang bisa dimasak
MonthlyExpenses *string `gorm:"column:monthly_expenses" json:"monthly_expenses"` // pengeluaran per bulan
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"`
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
FieldCounter
}
FamilyMemberCV struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
- AccountID int64 `gorm:"column:account_id;not null" json:"account_id"`
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id" counter:"skip"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
Role *string `gorm:"column:role" json:"role"` // Peran dalam keluarga
Status *string `gorm:"column:status" json:"status"` // Status (Hidup, Wafat)
Religion *string `gorm:"column:religion" json:"religion"` // Agama
Job *string `gorm:"column:job" json:"job"` // Pekerjaan
LastEducation *string `gorm:"column:last_education" json:"last_education"` // Pendidikan terakhir
Age *int `gorm:"column:age" json:"age"` // Usia
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"`
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
FieldCounter
}
PhysicalAndHealthCV struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
- AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id" counter:"skip"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
HeightInCm *int `gorm:"column:height_cm" json:"height_cm"` // Tinggi badan dalam satuan sentimeter
WeightInKg *int `gorm:"column:weight_kg" json:"weight_kg"` // Berat badan dalam satuan kilogram
BodyShape *string `gorm:"column:body_shape" json:"body_shape"` // Bentuk tubuh
@@ -242,15 +242,15 @@ type (
MedicalHistory *pq.StringArray `gorm:"column:medical_history;type:varchar(255)[]" json:"medical_history"` // Riwayat penyakit
PhysicalDisorder *string `gorm:"column:physical_disorder" json:"physical_disorder"` // Cacat fisik
PhysicalTraits *string `gorm:"column:physical_traits" json:"physical_traits"` // Ciri khas fisik
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // Waktu data dibuat
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // Waktu data terakhir diperbarui
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"` // Waktu data dibuat
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"` // Waktu data terakhir diperbarui
FieldCounter
}
WorshipAndReligiousUnderstandingCV struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
- AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"`
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id" counter:"skip"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"`
ObligatoryPrayer *string `gorm:"column:obligatory_prayer" json:"obligatory_prayer"` // sholat_wajib_5_waktu
CongregationalPrayer *string `gorm:"column:congregational_prayer" json:"congregational_prayer"` // sholat_berjamaah_di_masjid
TahajjudPrayer *string `gorm:"column:tahajjud_prayer" json:"tahajjud_prayer"` // sholat_tahajud
@@ -266,44 +266,44 @@ type (
OpinionOnVeil *string `gorm:"column:opinion_on_veil" json:"opinion_on_veil"` // pendapat_tentang_cadar
WeeklyReligiousStudies *string `gorm:"column:weekly_religious_studies" json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"`
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"`
FieldCounter
}
EducationCV struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
- AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
- LastEducation *string `gorm:"column:last_education" json:"last_education"` // pendidikan terakhir
- EducationInstitute *string `gorm:"column:education_institute" json:"education_institute"` // institusi pendidikan
- EducationMajor *string `gorm:"column:education_major" json:"education_major"` // jurusan pendidikan
- YearStart *int `gorm:"column:year_start" json:"year_start"` // tahun masuk
- YearGraduate *int `gorm:"column:year_graduate" json:"year_graduate"` // tahun lulus
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"` // id
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id" counter:"skip"` // id akun
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"` // relasi ke akun
+ LastEducation *string `gorm:"column:last_education" json:"last_education"` // pendidikan terakhir
+ EducationInstitute *string `gorm:"column:education_institute" json:"education_institute"` // institusi pendidikan
+ EducationMajor *string `gorm:"column:education_major" json:"education_major"` // jurusan pendidikan
+ YearStart *int `gorm:"column:year_start" json:"year_start"` // tahun masuk
+ YearGraduate *int `gorm:"column:year_graduate" json:"year_graduate"` // tahun lulus
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"` // tanggal dibuat
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"` // tanggal diperbarui
}
JobCV struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
- AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
- InstitutionName *string `gorm:"column:institution_name" json:"institution_name"` // nama instansi
- CurrentJob *string `gorm:"column:current_job" json:"current_job"` // pekerjaan saat ini
- YearStartedWorking *int `gorm:"column:year_started_working" json:"year_started_working"` // tahun mulai bekerja
- MonthlyIncome *string `gorm:"column:monthly_income" json:"monthly_income"` // penghasilan per bulan
- IncomeSources *pq.StringArray `gorm:"column:income_sources;type:varchar(255)[]" json:"income_sources"` // sumber penghasilan
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"` // id
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id" counter:"skip"` // id akun
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"` // relasi ke akun
+ InstitutionName *string `gorm:"column:institution_name" json:"institution_name"` // nama instansi
+ CurrentJob *string `gorm:"column:current_job" json:"current_job"` // pekerjaan saat ini
+ YearStartedWorking *int `gorm:"column:year_started_working" json:"year_started_working"` // tahun mulai bekerja
+ MonthlyIncome *string `gorm:"column:monthly_income" json:"monthly_income"` // penghasilan per bulan
+ IncomeSources *pq.StringArray `gorm:"column:income_sources;type:varchar(255)[]" json:"income_sources"` // sumber penghasilan
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"` // tanggal dibuat
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"` // tanggal diperbarui
}
AchievementCV struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
- AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
- AchievementOrAward *string `gorm:"column:achievement_or_award" json:"achievement_or_award"` // prestasi atau penghargaan
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id" counter:"skip"` // id
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id" counter:"skip"` // id akun
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty" counter:"skip"` // relasi ke akun
+ AchievementOrAward *string `gorm:"column:achievement_or_award" json:"achievement_or_award"` // prestasi atau penghargaan
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at" counter:"skip"` // tanggal dibuat
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at" counter:"skip"` // tanggal diperbarui
}
)
diff --git a/space/space/models/field_counter.go b/space/space/models/field_counter.go
index 01978a61547b1f0ef0071e86fdce34f8da80f710..f31f2dc004b3a46e6dadd5cb20a42d317b6ee585 100644
--- a/space/space/models/field_counter.go
+++ b/space/space/models/field_counter.go
@@ -1,6 +1,7 @@
package models
import (
+ "fmt"
"reflect"
"time"
)
@@ -17,29 +18,13 @@ type FieldCounter struct{}
// shouldSkipField menentukan apakah field harus dilewati dalam penghitungan
func shouldSkipField(field reflect.StructField) bool {
- // Skip field dengan nama FieldCounter (embedded type)
if field.Name == "FieldCounter" {
return true
}
- // Skip field yang berasal dari GORM atau standard fields
- // seperti ID, timestamps, dan foreign keys
- if field.Name == "ID" || field.Name == "CreatedAt" || field.Name == "UpdatedAt" || field.Name == "DeletedAt" ||
- field.Name == "AccountID" || field.Name == "Account" {
- return true
- }
-
- // Periksa tag gorm untuk auto fields
- gormTag := field.Tag.Get("gorm")
- if len(gormTag) > 0 {
- // Skip kolom yang auto-increment atau auto-timestamp
- if gormTag == "primaryKey" || gormTag == "autoIncrement" ||
- gormTag == "autoCreateTime" || gormTag == "autoUpdateTime" {
- return true
- }
- }
-
- return false
+ counterTag := field.Tag.Get("counter")
+ fmt.Println(field.Name, "=", counterTag)
+ return counterTag == "skip"
}
// TotalFields menghitung jumlah total field dalam struct
diff --git a/space/space/space/config/database_connection_config.go b/space/space/space/config/database_connection_config.go
index 3a1837085cd191fc54d996d58ae48ef41df229d0..ec3835a070b704d9d3b3dff371e3cb621a335fac 100644
--- a/space/space/space/config/database_connection_config.go
+++ b/space/space/space/config/database_connection_config.go
@@ -77,6 +77,7 @@ func AutoMigrateAll(db *gorm.DB) {
&models.EducationCV{},
&models.JobCV{},
&models.AchievementCV{},
+ &models.MarriageReadinessProfile{},
)
if err != nil {
diff --git a/space/space/space/controller/marriage_readiness_profile/marriage_readiness_profile_controller.go b/space/space/space/controller/marriage_readiness_profile/marriage_readiness_profile_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..639cb8631322ae4f628b1a7b851028642904918c
--- /dev/null
+++ b/space/space/space/controller/marriage_readiness_profile/marriage_readiness_profile_controller.go
@@ -0,0 +1,66 @@
+package cv_controller
+
+import (
+ "net/http"
+
+ "api.qobiltu.id/middleware"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/response"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+type MarriageReadinessProfileController interface {
+ SaveMarriageReadinessProfile(ctx *gin.Context)
+ GetMarriageReadinessProfile(ctx *gin.Context)
+}
+
+type marriageReadinessProfileController struct {
+ marriageReadinessProfileService services.MarriageReadinessProfileService
+}
+
+func NewMarriageReadinessProfileController(marriageReadinessProfileService services.MarriageReadinessProfileService) MarriageReadinessProfileController {
+ return &marriageReadinessProfileController{
+ marriageReadinessProfileService: marriageReadinessProfileService,
+ }
+}
+
+func (c *marriageReadinessProfileController) SaveMarriageReadinessProfile(ctx *gin.Context) {
+ var req models.SaveMarriageReadinessProfileRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
+ return
+ }
+
+ accountData := middleware.GetAccountData(ctx)
+ req.AccountID = int64(accountData.UserID)
+
+ res, err := c.marriageReadinessProfileService.SaveMarriageReadinessProfile(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Marriage readiness profile saved", res, nil)
+}
+
+func (c *marriageReadinessProfileController) GetMarriageReadinessProfile(ctx *gin.Context) {
+ accountData := middleware.GetAccountData(ctx)
+ accountID := int64(accountData.UserID)
+
+ req := models.GetMarriageReadinessProfileRequest{
+ AccountID: accountID,
+ }
+
+ res, err := c.marriageReadinessProfileService.GetMarriageReadinessProfile(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Get marriage readiness profile success", res, nil)
+}
diff --git a/space/space/space/main.go b/space/space/space/main.go
index 6c931622855c83028e6a2fc50fda1fdd54525139..c7724f635a467ce48272416364a19562ece2e334 100644
--- a/space/space/space/main.go
+++ b/space/space/space/main.go
@@ -1,9 +1,14 @@
package main
import (
+ "log/slog"
+ "net"
+ "strconv"
+
"api.qobiltu.id/config"
- "api.qobiltu.id/controller/cv"
- "api.qobiltu.id/controller/health_check"
+ cv_controller "api.qobiltu.id/controller/cv"
+ health_check_controller "api.qobiltu.id/controller/health_check"
+ marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
"api.qobiltu.id/mail"
"api.qobiltu.id/pkg/storage"
"api.qobiltu.id/pkg/validation"
@@ -13,9 +18,6 @@ import (
"api.qobiltu.id/services"
"api.qobiltu.id/utils"
"github.com/hibiken/asynq"
- "log/slog"
- "net"
- "strconv"
)
func main() {
@@ -60,6 +62,10 @@ func main() {
cvService := services.NewCVService(cvRepository, localStorage)
cvController := cv_controller.NewCVController(cvService)
+ marriageReadinessProfileRepository := repositories.NewMarriageReadinessProfileRepository(config.DB)
+ marriageReadinessProfileService := services.NewMarriageReadinessProfileService(marriageReadinessProfileRepository)
+ marriageReadinessProfileController := marriage_readiness_profile_controller.NewMarriageReadinessProfileController(marriageReadinessProfileService)
+
// start task processor
err = taskProcessor.Start()
utils.FatalIfErr("failed to start task processor", err)
@@ -69,6 +75,7 @@ func main() {
s, err := router.NewServer(
healthCheckController,
cvController,
+ marriageReadinessProfileController,
)
utils.FatalIfErr("failed to create server", err)
diff --git a/space/space/space/pkg/worker/task_send_forgot_password_email.go b/space/space/space/pkg/worker/task_send_forgot_password_email.go
index 8675c2b7b005df053a3660f8b42ec6cd2bc5722c..d4fa03aca1920635dd42f39ecca3dc99e1012593 100644
--- a/space/space/space/pkg/worker/task_send_forgot_password_email.go
+++ b/space/space/space/pkg/worker/task_send_forgot_password_email.go
@@ -1,14 +1,15 @@
package worker
import (
- "api.qobiltu.id/assets"
"bytes"
"context"
"encoding/json"
"fmt"
- "github.com/hibiken/asynq"
"html/template"
- "log/slog"
+ "time"
+
+ "api.qobiltu.id/assets"
+ "github.com/hibiken/asynq"
)
const (
@@ -62,14 +63,15 @@ func (p *RedisTaskProcessor) ProcessTaskSendForgotPasswordEmail(ctx context.Cont
}
htmlContent := body.String()
- slog.Info("Sending forgot password email", slog.String("email", payload.EmailAddress))
+ fmt.Println("Sending forgot password email", payload.EmailAddress)
+ start := time.Now()
err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
if err != nil {
return fmt.Errorf("failed to send forgot password email: %w", err)
}
- slog.Info("Forgot password email sent successfully", slog.String("email", payload.EmailAddress))
-
+ fmt.Println("Forgot password email sent successfully", payload.EmailAddress)
+ fmt.Println("Time taken", time.Since(start))
return nil
}
diff --git a/space/space/space/pkg/worker/task_send_verify_email.go b/space/space/space/pkg/worker/task_send_verify_email.go
index 9f6fd14a6a279def7ca9036bea59175f731f91ef..eda356fa8631e181e88b30863b8b55db310baf16 100644
--- a/space/space/space/pkg/worker/task_send_verify_email.go
+++ b/space/space/space/pkg/worker/task_send_verify_email.go
@@ -1,14 +1,15 @@
package worker
import (
- "api.qobiltu.id/assets"
"bytes"
"context"
"encoding/json"
"fmt"
- "github.com/hibiken/asynq"
"html/template"
- "log/slog"
+ "time"
+
+ "api.qobiltu.id/assets"
+ "github.com/hibiken/asynq"
)
const (
@@ -62,14 +63,16 @@ func (p *RedisTaskProcessor) ProcessTaskSendVerifyEmail(ctx context.Context, tas
}
htmlContent := body.String()
- slog.Info("Sending verification email", slog.String("email", payload.EmailAddress))
+ fmt.Println("Sending verification email", payload.EmailAddress)
+ start := time.Now()
err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
if err != nil {
return fmt.Errorf("failed to send verify email: %w", err)
}
- slog.Info("Verification email sent successfully", slog.String("email", payload.EmailAddress))
+ fmt.Println("Verification email sent successfully", payload.EmailAddress)
+ fmt.Println("Time taken", time.Since(start))
return nil
}
diff --git a/space/space/space/repositories/marriage_readiness_profile_repository.go b/space/space/space/repositories/marriage_readiness_profile_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..955c912bf03b121054441c6ff169069456e9cc99
--- /dev/null
+++ b/space/space/space/repositories/marriage_readiness_profile_repository.go
@@ -0,0 +1,38 @@
+package repositories
+
+import (
+ "context"
+
+ "api.qobiltu.id/models"
+ "gorm.io/gorm"
+)
+
+type MarriageReadinessProfileRepository interface {
+ SaveMarriageReadinessProfile(ctx context.Context, req *models.MarriageReadinessProfile) (*models.MarriageReadinessProfile, error)
+ GetMarriageReadinessProfile(ctx context.Context, accountID int64) (*models.MarriageReadinessProfile, error)
+}
+
+type marriageReadinessProfileRepository struct {
+ db *gorm.DB
+}
+
+func NewMarriageReadinessProfileRepository(db *gorm.DB) MarriageReadinessProfileRepository {
+ return &marriageReadinessProfileRepository{
+ db: db,
+ }
+}
+
+func (r *marriageReadinessProfileRepository) SaveMarriageReadinessProfile(ctx context.Context, req *models.MarriageReadinessProfile) (*models.MarriageReadinessProfile, error) {
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
+}
+
+func (r *marriageReadinessProfileRepository) GetMarriageReadinessProfile(ctx context.Context, accountID int64) (*models.MarriageReadinessProfile, error) {
+ var marriageReadinessProfile models.MarriageReadinessProfile
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&marriageReadinessProfile).Error; err != nil {
+ return nil, err
+ }
+ return &marriageReadinessProfile, nil
+}
diff --git a/space/space/space/router/marriage_readiness_profile_route.go b/space/space/space/router/marriage_readiness_profile_route.go
new file mode 100644
index 0000000000000000000000000000000000000000..09d521976c6872405521752091e9cf2f4837924f
--- /dev/null
+++ b/space/space/space/router/marriage_readiness_profile_route.go
@@ -0,0 +1,11 @@
+package router
+
+import "api.qobiltu.id/middleware"
+
+func (s *Server) MarriageReadinessProfileRoute() {
+ routerGroup := s.router.Group("/api/v1/marriage-readiness-profile").Use(middleware.AuthUser)
+ {
+ routerGroup.POST("", s.marriageReadinessProfileController.SaveMarriageReadinessProfile)
+ routerGroup.GET("", s.marriageReadinessProfileController.GetMarriageReadinessProfile)
+ }
+}
diff --git a/space/space/space/router/router.go b/space/space/space/router/router.go
index 8b647f502ab36d3165e4dfedf4b3e62039827a25..47abd2033afd236b8c2ac1d9e766b287906aa2db 100644
--- a/space/space/space/router/router.go
+++ b/space/space/space/router/router.go
@@ -18,5 +18,6 @@ func (s *Server) setupRoutes() {
// another way to register routes
s.HealthCheckRoute()
s.CVRoute()
+ s.MarriageReadinessProfileRoute()
s.StorageRoute()
}
diff --git a/space/space/space/router/server.go b/space/space/space/router/server.go
index ae7ea916bd383632c60b6dcd5995d71840bd5717..601a8ee215eedafff6238c59bef3dee099584096 100644
--- a/space/space/space/router/server.go
+++ b/space/space/space/router/server.go
@@ -2,28 +2,32 @@ package router
import (
cv_controller "api.qobiltu.id/controller/cv"
- "api.qobiltu.id/controller/health_check"
+ health_check_controller "api.qobiltu.id/controller/health_check"
+ marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
"github.com/gin-gonic/gin"
)
type Server struct {
- router *gin.Engine
- healthCheckController health_check_controller.HealthCheckController
- cvController cv_controller.CVController
+ router *gin.Engine
+ healthCheckController health_check_controller.HealthCheckController
+ cvController cv_controller.CVController
+ marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController
}
func NewServer(
healthCheckController health_check_controller.HealthCheckController,
cvController cv_controller.CVController,
+ marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController,
) (*Server, error) {
router := gin.Default()
router.Use(gin.Recovery())
server := &Server{
- healthCheckController: healthCheckController,
- cvController: cvController,
- router: router,
+ healthCheckController: healthCheckController,
+ cvController: cvController,
+ marriageReadinessProfileController: marriageReadinessProfileController,
+ router: router,
}
server.setupRoutes()
diff --git a/space/space/space/services/marriage_readiness_profile_service.go b/space/space/space/services/marriage_readiness_profile_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..417e16ba0129f0723712e127463555657e22812a
--- /dev/null
+++ b/space/space/space/services/marriage_readiness_profile_service.go
@@ -0,0 +1,90 @@
+package services
+
+import (
+ "context"
+ "errors"
+
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/pkg/validation"
+ "api.qobiltu.id/repositories"
+ "api.qobiltu.id/response"
+ "api.qobiltu.id/utils"
+ "gorm.io/gorm"
+)
+
+type MarriageReadinessProfileService interface {
+ SaveMarriageReadinessProfile(ctx context.Context, req *models.SaveMarriageReadinessProfileRequest) (*models.MarriageReadinessProfile, error)
+ GetMarriageReadinessProfile(ctx context.Context, req *models.GetMarriageReadinessProfileRequest) (*models.MarriageReadinessProfile, error)
+}
+
+type marriageReadinessProfileService struct {
+ marriageReadinessProfileRepository repositories.MarriageReadinessProfileRepository
+}
+
+func NewMarriageReadinessProfileService(marriageReadinessProfileRepository repositories.MarriageReadinessProfileRepository) MarriageReadinessProfileService {
+ return &marriageReadinessProfileService{marriageReadinessProfileRepository: marriageReadinessProfileRepository}
+}
+
+func (s *marriageReadinessProfileService) SaveMarriageReadinessProfile(ctx context.Context, req *models.SaveMarriageReadinessProfileRequest) (*models.MarriageReadinessProfile, error) {
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ marriageReadinessProfile, err := s.marriageReadinessProfileRepository.GetMarriageReadinessProfile(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ if marriageReadinessProfile == nil {
+ marriageReadinessProfile = &models.MarriageReadinessProfile{}
+ }
+
+ marriageReadinessProfile.AccountID = req.AccountID
+
+ utils.AssignIfNotNil(&marriageReadinessProfile.MarriageVision, req.MarriageVision)
+ utils.AssignIfNotNil(&marriageReadinessProfile.LivingPlan, req.LivingPlan)
+ utils.AssignIfNotNil(&marriageReadinessProfile.SpouseRoles, req.SpouseRoles)
+ utils.AssignIfNotNil(&marriageReadinessProfile.SpouseRights, req.SpouseRights)
+ utils.AssignIfNotNil(&marriageReadinessProfile.ConflictResolution, req.ConflictResolution)
+ utils.AssignIfNotNil(&marriageReadinessProfile.ParentingStyle, req.ParentingStyle)
+
+ utils.AssignIfNotNil(&marriageReadinessProfile.ReadyToMarryDuration, req.ReadyToMarryDuration)
+ utils.AssignIfNotNil(&marriageReadinessProfile.WeddingConcept, req.WeddingConcept)
+ utils.AssignIfNotNil(&marriageReadinessProfile.WeddingFunding, req.WeddingFunding)
+
+ utils.AssignIfNotNil(&marriageReadinessProfile.CareerAfterMarriage, req.CareerAfterMarriage)
+ utils.AssignIfNotNil(&marriageReadinessProfile.TimeManagement, req.TimeManagement)
+ utils.AssignIfNotNil(&marriageReadinessProfile.CareerGoals, req.CareerGoals)
+ utils.AssignIfNotNil(&marriageReadinessProfile.SelfDevelopment, req.SelfDevelopment)
+
+ utils.AssignIfNotNil(&marriageReadinessProfile.DelayChildren, req.DelayChildren)
+ utils.AssignIfNotNil(&marriageReadinessProfile.ChildEducationPlan, req.ChildEducationPlan)
+ utils.AssignIfNotNil(&marriageReadinessProfile.ReligiousEmotionalBond, req.ReligiousEmotionalBond)
+ utils.AssignIfNotNil(&marriageReadinessProfile.ParentingChallenges, req.ParentingChallenges)
+
+ utils.AssignIfNotNil(&marriageReadinessProfile.MonthlyFinance, req.MonthlyFinance)
+ utils.AssignIfNotNil(&marriageReadinessProfile.FamilyResponsibility, req.FamilyResponsibility)
+ utils.AssignIfNotNil(&marriageReadinessProfile.DebtStatus, req.DebtStatus)
+ utils.AssignIfNotNil(&marriageReadinessProfile.FinanceSharing, req.FinanceSharing)
+ utils.AssignIfNotNil(&marriageReadinessProfile.IncomeGapView, req.IncomeGapView)
+
+ utils.AssignIfNotNil(&marriageReadinessProfile.DecisionMaking, req.DecisionMaking)
+ utils.AssignIfNotNil(&marriageReadinessProfile.GrowthTogether, req.GrowthTogether)
+ utils.AssignIfNotNil(&marriageReadinessProfile.ParentIntervention, req.ParentIntervention)
+ utils.AssignIfNotNil(&marriageReadinessProfile.HabitResponse, req.HabitResponse)
+
+ res, err := s.marriageReadinessProfileRepository.SaveMarriageReadinessProfile(ctx, marriageReadinessProfile)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return res, nil
+}
+
+func (s *marriageReadinessProfileService) GetMarriageReadinessProfile(ctx context.Context, req *models.GetMarriageReadinessProfileRequest) (*models.MarriageReadinessProfile, error) {
+ res, err := s.marriageReadinessProfileRepository.GetMarriageReadinessProfile(ctx, req.AccountID)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+ return res, nil
+}
diff --git a/space/space/space/space/controller/cv/cv_controller.go b/space/space/space/space/controller/cv/cv_controller.go
index 8ce30c1b260a6937e865023f58534978daed3d25..f04619dc5c2fef1f5893a35e55060521cab706b9 100644
--- a/space/space/space/space/controller/cv/cv_controller.go
+++ b/space/space/space/space/controller/cv/cv_controller.go
@@ -49,7 +49,7 @@ type CVController interface {
DeleteAchievement(ctx *gin.Context)
UploadProfileImage(ctx *gin.Context)
- GetProgress(ctx *gin.Context)
+ GetProgressCV(ctx *gin.Context)
}
type cvController struct {
@@ -64,9 +64,13 @@ func NewCVController(cvService services.CVService) CVController {
// --- Account Details ---
func (c *cvController) SaveAccountDetails(ctx *gin.Context) {
- var req models.AccountDetailsRequest
+ var req models.SaveAccountDetailsRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
- response.HandleError(ctx, err)
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
return
}
@@ -86,7 +90,11 @@ func (c *cvController) GetAccountDetails(ctx *gin.Context) {
accountData := middleware.GetAccountData(ctx)
accountID := int64(accountData.UserID)
- res, err := c.cvService.GetAccountDetails(ctx, accountID)
+ req := models.GetAccountDetailsRequest{
+ AccountID: accountID,
+ }
+
+ res, err := c.cvService.GetAccountDetails(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -98,9 +106,13 @@ func (c *cvController) GetAccountDetails(ctx *gin.Context) {
// --- Personality & Preference ---
func (c *cvController) SavePersonalityAndPreference(ctx *gin.Context) {
- var req models.PersonalityAndPreferenceCVRequest
+ var req models.SavePersonalityAndPreferenceRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
- response.HandleError(ctx, err)
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
return
}
@@ -120,7 +132,11 @@ func (c *cvController) GetPersonalityAndPreference(ctx *gin.Context) {
accountData := middleware.GetAccountData(ctx)
accountID := int64(accountData.UserID)
- res, err := c.cvService.GetPersonalityAndPreference(ctx, accountID)
+ req := models.GetPersonalityAndPreferenceRequest{
+ AccountID: accountID,
+ }
+
+ res, err := c.cvService.GetPersonalityAndPreference(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -132,9 +148,13 @@ func (c *cvController) GetPersonalityAndPreference(ctx *gin.Context) {
// --- Family Member ---
func (c *cvController) CreateFamilyMember(ctx *gin.Context) {
- var req models.FamilyMemberRequest
+ var req models.CreateFamilyMemberRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
- response.HandleError(ctx, err)
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
return
}
@@ -158,13 +178,19 @@ func (c *cvController) UpdateFamilyMember(ctx *gin.Context) {
return
}
- var req models.FamilyMemberRequest
+ var req models.UpdateFamilyMemberRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
- response.HandleError(ctx, err)
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
return
}
- res, err := c.cvService.UpdateFamilyMember(ctx, id, &req)
+ req.ID = id
+
+ res, err := c.cvService.UpdateFamilyMember(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -177,7 +203,11 @@ func (c *cvController) ListFamilyMember(ctx *gin.Context) {
accountData := middleware.GetAccountData(ctx)
accountID := int64(accountData.UserID)
- list, err := c.cvService.ListFamilyMember(ctx, accountID)
+ req := models.ListFamilyMemberRequest{
+ AccountID: accountID,
+ }
+
+ list, err := c.cvService.ListFamilyMember(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -194,7 +224,11 @@ func (c *cvController) GetFamilyMember(ctx *gin.Context) {
return
}
- res, err := c.cvService.GetFamilyMember(ctx, id)
+ req := models.GetFamilyMemberRequest{
+ ID: id,
+ }
+
+ res, err := c.cvService.GetFamilyMember(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -211,7 +245,11 @@ func (c *cvController) DeleteFamilyMember(ctx *gin.Context) {
return
}
- err = c.cvService.DeleteFamilyMember(ctx, id)
+ req := models.DeleteFamilyMemberRequest{
+ ID: id,
+ }
+
+ err = c.cvService.DeleteFamilyMember(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -223,9 +261,13 @@ func (c *cvController) DeleteFamilyMember(ctx *gin.Context) {
// --- Physical and Health ---
func (c *cvController) SavePhysicalAndHealth(ctx *gin.Context) {
- var req models.PhysicalAndHealthRequest
+ var req models.SavePhysicalAndHealthRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
- response.HandleError(ctx, err)
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
return
}
@@ -245,7 +287,11 @@ func (c *cvController) GetPhysicalAndHealth(ctx *gin.Context) {
accountData := middleware.GetAccountData(ctx)
accountID := int64(accountData.UserID)
- res, err := c.cvService.GetPhysicalAndHealth(ctx, accountID)
+ req := models.GetPhysicalAndHealthRequest{
+ AccountID: accountID,
+ }
+
+ res, err := c.cvService.GetPhysicalAndHealth(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -257,9 +303,13 @@ func (c *cvController) GetPhysicalAndHealth(ctx *gin.Context) {
// --- Worship and Religious Understanding ---
func (c *cvController) SaveWorshipAndReligiousUnderstanding(ctx *gin.Context) {
- var req models.WorshipAndReligiousUnderstandingRequest
+ var req models.SaveWorshipAndReligiousUnderstandingRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
- response.HandleError(ctx, err)
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
return
}
@@ -279,7 +329,11 @@ func (c *cvController) GetWorshipAndReligiousUnderstanding(ctx *gin.Context) {
accountData := middleware.GetAccountData(ctx)
accountID := int64(accountData.UserID)
- res, err := c.cvService.GetWorshipAndReligiousUnderstanding(ctx, accountID)
+ req := models.GetWorshipAndReligiousUnderstandingRequest{
+ AccountID: accountID,
+ }
+
+ res, err := c.cvService.GetWorshipAndReligiousUnderstanding(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -290,9 +344,13 @@ func (c *cvController) GetWorshipAndReligiousUnderstanding(ctx *gin.Context) {
// --- Education ---
func (c *cvController) CreateEducation(ctx *gin.Context) {
- var req models.EducationRequest
+ var req models.CreateEducationRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
- response.HandleError(ctx, err)
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
return
}
@@ -316,13 +374,19 @@ func (c *cvController) UpdateEducation(ctx *gin.Context) {
return
}
- var req models.EducationRequest
+ var req models.UpdateEducationRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
- response.HandleError(ctx, err)
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
return
}
- res, err := c.cvService.UpdateEducation(ctx, id, &req)
+ req.ID = id
+
+ res, err := c.cvService.UpdateEducation(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -335,7 +399,11 @@ func (c *cvController) ListEducation(ctx *gin.Context) {
accountData := middleware.GetAccountData(ctx)
accountID := int64(accountData.UserID)
- res, err := c.cvService.ListEducation(ctx, accountID)
+ req := models.ListEducationRequest{
+ AccountID: accountID,
+ }
+
+ res, err := c.cvService.ListEducation(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -352,7 +420,11 @@ func (c *cvController) GetEducation(ctx *gin.Context) {
return
}
- res, err := c.cvService.GetEducation(ctx, id)
+ req := models.GetEducationRequest{
+ ID: id,
+ }
+
+ res, err := c.cvService.GetEducation(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -369,7 +441,11 @@ func (c *cvController) DeleteEducation(ctx *gin.Context) {
return
}
- err = c.cvService.DeleteEducation(ctx, id)
+ req := models.DeleteEducationRequest{
+ ID: id,
+ }
+
+ err = c.cvService.DeleteEducation(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -380,9 +456,13 @@ func (c *cvController) DeleteEducation(ctx *gin.Context) {
// --- Job ---
func (c *cvController) CreateJob(ctx *gin.Context) {
- var req models.JobRequest
+ var req models.CreateJobRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
- response.HandleError(ctx, err)
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
return
}
@@ -406,13 +486,19 @@ func (c *cvController) UpdateJob(ctx *gin.Context) {
return
}
- var req models.JobRequest
+ var req models.UpdateJobRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
- response.HandleError(ctx, err)
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
return
}
- res, err := c.cvService.UpdateJob(ctx, id, &req)
+ req.ID = id
+
+ res, err := c.cvService.UpdateJob(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -425,7 +511,11 @@ func (c *cvController) ListJob(ctx *gin.Context) {
accountData := middleware.GetAccountData(ctx)
accountID := int64(accountData.UserID)
- res, err := c.cvService.ListJob(ctx, accountID)
+ req := models.ListJobRequest{
+ AccountID: accountID,
+ }
+
+ res, err := c.cvService.ListJob(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -442,7 +532,11 @@ func (c *cvController) GetJob(ctx *gin.Context) {
return
}
- res, err := c.cvService.GetJob(ctx, id)
+ req := models.GetJobRequest{
+ ID: id,
+ }
+
+ res, err := c.cvService.GetJob(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -459,7 +553,11 @@ func (c *cvController) DeleteJob(ctx *gin.Context) {
return
}
- err = c.cvService.DeleteJob(ctx, id)
+ req := models.DeleteJobRequest{
+ ID: id,
+ }
+
+ err = c.cvService.DeleteJob(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -470,9 +568,13 @@ func (c *cvController) DeleteJob(ctx *gin.Context) {
// --- Achievement ---
func (c *cvController) CreateAchievement(ctx *gin.Context) {
- var req models.AchievementRequest
+ var req models.CreateAchievementRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
- response.HandleError(ctx, err)
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
return
}
@@ -496,13 +598,19 @@ func (c *cvController) UpdateAchievement(ctx *gin.Context) {
return
}
- var req models.AchievementRequest
+ var req models.UpdateAchievementRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
- response.HandleError(ctx, err)
+ response.HandleError(ctx, models.Exception{
+ Message: "Invalid body request",
+ BadRequest: true,
+ Err: err,
+ })
return
}
- res, err := c.cvService.UpdateAchievement(ctx, id, &req)
+ req.ID = id
+
+ res, err := c.cvService.UpdateAchievement(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -515,7 +623,11 @@ func (c *cvController) ListAchievement(ctx *gin.Context) {
accountData := middleware.GetAccountData(ctx)
accountID := int64(accountData.UserID)
- res, err := c.cvService.ListAchievement(ctx, accountID)
+ req := models.ListAchievementRequest{
+ AccountID: accountID,
+ }
+
+ res, err := c.cvService.ListAchievement(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -532,7 +644,11 @@ func (c *cvController) GetAchievement(ctx *gin.Context) {
return
}
- res, err := c.cvService.GetAchievement(ctx, id)
+ req := models.GetAchievementRequest{
+ ID: id,
+ }
+
+ res, err := c.cvService.GetAchievement(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -549,7 +665,11 @@ func (c *cvController) DeleteAchievement(ctx *gin.Context) {
return
}
- err = c.cvService.DeleteAchievement(ctx, id)
+ req := models.DeleteAchievementRequest{
+ ID: id,
+ }
+
+ err = c.cvService.DeleteAchievement(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
@@ -576,11 +696,15 @@ func (c *cvController) UploadProfileImage(ctx *gin.Context) {
response.HandleSuccess(ctx, http.StatusOK, "Profile image uploaded successfully", res, nil)
}
-func (c *cvController) GetProgress(ctx *gin.Context) {
+func (c *cvController) GetProgressCV(ctx *gin.Context) {
accountData := middleware.GetAccountData(ctx)
accountID := int64(accountData.UserID)
- res, err := c.cvService.GetProgress(ctx, accountID)
+ req := models.GetProgressCVRequest{
+ AccountID: accountID,
+ }
+
+ res, err := c.cvService.GetProgressCV(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
diff --git a/space/space/space/space/models/database_orm_model.go b/space/space/space/space/models/database_orm_model.go
index 269a4f55ced39eb0e71b026ddb2c355a563d925d..416647a06a315d8c3d0299bffaf95f9c9e69d9e3 100644
--- a/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/models/database_orm_model.go
@@ -231,43 +231,43 @@ type (
}
PhysicalAndHealthCV struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
- AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
- HeightInCm *int `gorm:"column:height_cm" json:"height_cm"` // Tinggi badan dalam satuan sentimeter
- WeightInKg *int `gorm:"column:weight_kg" json:"weight_kg"` // Berat badan dalam satuan kilogram
- BodyShape *string `gorm:"column:body_shape" json:"body_shape"` // Bentuk tubuh
- SkinColor *string `gorm:"column:skin_color" json:"skin_color"` // Warna kulit
- HairType *string `gorm:"column:hair_type" json:"hair_type"` // Tipe rambut
- MedicalHistory *string `gorm:"column:medical_history" json:"medical_history"` // Riwayat penyakit
- PhysicalDisorder *string `gorm:"column:physical_disorder" json:"physical_disorder"` // Cacat fisik
- PhysicalTraits *string `gorm:"column:physical_traits" json:"physical_traits"` // Ciri khas fisik
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // Waktu data dibuat
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // Waktu data terakhir diperbarui
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
+ HeightInCm *int `gorm:"column:height_cm" json:"height_cm"` // Tinggi badan dalam satuan sentimeter
+ WeightInKg *int `gorm:"column:weight_kg" json:"weight_kg"` // Berat badan dalam satuan kilogram
+ BodyShape *string `gorm:"column:body_shape" json:"body_shape"` // Bentuk tubuh
+ SkinColor *string `gorm:"column:skin_color" json:"skin_color"` // Warna kulit
+ HairType *string `gorm:"column:hair_type" json:"hair_type"` // Tipe rambut
+ MedicalHistory *pq.StringArray `gorm:"column:medical_history;type:varchar(255)[]" json:"medical_history"` // Riwayat penyakit
+ PhysicalDisorder *string `gorm:"column:physical_disorder" json:"physical_disorder"` // Cacat fisik
+ PhysicalTraits *string `gorm:"column:physical_traits" json:"physical_traits"` // Ciri khas fisik
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // Waktu data dibuat
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // Waktu data terakhir diperbarui
FieldCounter
}
WorshipAndReligiousUnderstandingCV struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
- AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
- ObligatoryPrayer *string `gorm:"column:obligatory_prayer" json:"obligatory_prayer"` // sholat_wajib_5_waktu
- CongregationalPrayer *string `gorm:"column:congregational_prayer" json:"congregational_prayer"` // sholat_berjamaah_di_masjid
- TahajjudPrayer *string `gorm:"column:tahajjud_prayer" json:"tahajjud_prayer"` // sholat_tahajud
- DhuhaPrayer *string `gorm:"column:dhuha_prayer" json:"dhuha_prayer"` // sholat_dhuha
- QuranMemorization *string `gorm:"column:quran_memorization" json:"quran_memorization"` // hafalan_alquran
- QuranReadingAbility *string `gorm:"column:quran_reading_ability" json:"quran_reading_ability"` // kemampuan_baca_alquran
- DaudFasting *string `gorm:"column:daud_fasting" json:"daud_fasting"` // puasa_daud
- AyyamulBidhFasting *string `gorm:"column:ayyamul_bidh_fasting" json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
- HajjOrUmrah pq.StringArray `gorm:"column:hajj_or_umrah;type:varchar(255)[]" json:"hajj_or_umrah"` // ibadah_haji_umroh
- ListeningToMusic *string `gorm:"column:listening_to_music" json:"listening_to_music"` // mendengarkan_musik
- OpinionOnIkhtilat *string `gorm:"column:opinion_on_ikhtilat" json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
- OpinionOnTouchingNonMahram *string `gorm:"column:opinion_on_touching_non_mahram" json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
- OpinionOnVeil *string `gorm:"column:opinion_on_veil" json:"opinion_on_veil"` // pendapat_tentang_cadar
- WeeklyReligiousStudies *string `gorm:"column:weekly_religious_studies" json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
- FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
+ ObligatoryPrayer *string `gorm:"column:obligatory_prayer" json:"obligatory_prayer"` // sholat_wajib_5_waktu
+ CongregationalPrayer *string `gorm:"column:congregational_prayer" json:"congregational_prayer"` // sholat_berjamaah_di_masjid
+ TahajjudPrayer *string `gorm:"column:tahajjud_prayer" json:"tahajjud_prayer"` // sholat_tahajud
+ DhuhaPrayer *string `gorm:"column:dhuha_prayer" json:"dhuha_prayer"` // sholat_dhuha
+ QuranMemorization *string `gorm:"column:quran_memorization" json:"quran_memorization"` // hafalan_alquran
+ QuranReadingAbility *string `gorm:"column:quran_reading_ability" json:"quran_reading_ability"` // kemampuan_baca_alquran
+ DaudFasting *string `gorm:"column:daud_fasting" json:"daud_fasting"` // puasa_daud
+ AyyamulBidhFasting *string `gorm:"column:ayyamul_bidh_fasting" json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
+ HajjOrUmrah *pq.StringArray `gorm:"column:hajj_or_umrah;type:varchar(255)[]" json:"hajj_or_umrah"` // ibadah_haji_umroh
+ ListeningToMusic *string `gorm:"column:listening_to_music" json:"listening_to_music"` // mendengarkan_musik
+ OpinionOnIkhtilat *string `gorm:"column:opinion_on_ikhtilat" json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
+ OpinionOnTouchingNonMahram *string `gorm:"column:opinion_on_touching_non_mahram" json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
+ OpinionOnVeil *string `gorm:"column:opinion_on_veil" json:"opinion_on_veil"` // pendapat_tentang_cadar
+ WeeklyReligiousStudies *string `gorm:"column:weekly_religious_studies" json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
+ FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
FieldCounter
}
@@ -285,16 +285,16 @@ type (
}
JobCV struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
- AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
- InstitutionName *string `gorm:"column:institution_name" json:"institution_name"` // nama instansi
- CurrentJob *string `gorm:"column:current_job" json:"current_job"` // pekerjaan saat ini
- YearStartedWorking *int `gorm:"column:year_started_working" json:"year_started_working"` // tahun mulai bekerja
- MonthlyIncome *string `gorm:"column:monthly_income" json:"monthly_income"` // penghasilan per bulan
- IncomeSources pq.StringArray `gorm:"column:income_sources;type:varchar(255)[]" json:"income_sources"` // sumber penghasilan
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
+ InstitutionName *string `gorm:"column:institution_name" json:"institution_name"` // nama instansi
+ CurrentJob *string `gorm:"column:current_job" json:"current_job"` // pekerjaan saat ini
+ YearStartedWorking *int `gorm:"column:year_started_working" json:"year_started_working"` // tahun mulai bekerja
+ MonthlyIncome *string `gorm:"column:monthly_income" json:"monthly_income"` // penghasilan per bulan
+ IncomeSources *pq.StringArray `gorm:"column:income_sources;type:varchar(255)[]" json:"income_sources"` // sumber penghasilan
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
}
AchievementCV struct {
@@ -339,6 +339,55 @@ func (w WorshipAndReligiousUnderstandingCV) GetFilledFields() []string {
return w.FieldCounter.GetFilledFields(w)
}
+type (
+ MarriageReadinessProfile struct {
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
+
+ // Visi Misi Rumah Tangga
+ MarriageVision *string `gorm:"column:marriage_vision" json:"marriage_vision"` // Apa visi dan tujuan utama kamu dalam membangun rumah tangga?
+ LivingPlan *string `gorm:"column:living_plan" json:"living_plan"` // Setelah menikah, kamu berencana tinggal di mana?
+ SpouseRoles *string `gorm:"column:spouse_roles" json:"spouse_roles"` // Menurutmu, apa peran utama suami dan istri dalam rumah tangga?
+ SpouseRights *string `gorm:"column:spouse_rights" json:"spouse_rights"` // Apa saja hak suami dan istri menurutmu?
+ ConflictResolution *string `gorm:"column:conflict_resolution" json:"conflict_resolution"` // Jika terjadi konflik, bagaimana kamu menyikapinya?
+ ParentingStyle *string `gorm:"column:parenting_style" json:"parenting_style"` // Setelah punya anak, pola pengasuhan seperti apa yang kamu inginkan?
+
+ // Konsep Acara Pernikahan
+ ReadyToMarryDuration *string `gorm:"column:ready_to_marry_duration" json:"ready_to_marry_duration"` // Setelah taaruf dimulai, berapa lama kamu butuh untuk siap menikah?
+ WeddingConcept *string `gorm:"column:wedding_concept" json:"wedding_concept"` // Seperti apa konsep pernikahan yang kamu harapkan?
+ WeddingFunding *string `gorm:"column:wedding_funding" json:"wedding_funding"` // Apakah kamu sudah mempersiapkan dana pernikahan? Dari mana sumbernya?
+
+ // Karir Kedepannya
+ CareerAfterMarriage *string `gorm:"column:career_after_marriage" json:"career_after_marriage"` // Apakah kamu ingin tetap bekerja setelah menikah?
+ TimeManagement *string `gorm:"column:time_management" json:"time_management"` // Bagaimana kamu membagi waktu antara keluarga, ibadah, dan pekerjaan?
+ CareerGoals *string `gorm:"column:career_goals" json:"career_goals"` // Apa impian karier atau cita-cita kamu dalam 5 sampai 10 tahun ke depan?
+ SelfDevelopment *string `gorm:"column:self_development" json:"self_development"` // Apa usaha kamu untuk terus mengembangkan diri dan skill?
+
+ // Pendidikan Keluarga
+ DelayChildren *string `gorm:"column:delay_children" json:"delay_children"` // Apakah kamu berencana menunda memiliki anak?
+ ChildEducationPlan *string `gorm:"column:child_education_plan" json:"child_education_plan"` // Apakah kamu sudah punya rencana pendidikan anak?
+ ReligiousEmotionalBond *string `gorm:"column:religious_emotional_bond" json:"religious_emotional_bond"` // Bagaimana cara menjaga nilai agama & kedekatan emosional dengan anak?
+ ParentingChallenges *string `gorm:"column:parenting_challenges" json:"parenting_challenges"` // Saat menghadapi tantangan pengasuhan, bagaimana kamu menyikapinya?
+
+ // Finansial Keluarga
+ MonthlyFinance *string `gorm:"column:monthly_finance" json:"monthly_finance"` // Bagaimana kamu mengelola keuangan bulanan?
+ FamilyResponsibility *string `gorm:"column:family_responsibility" json:"family_responsibility"` // Apakah kamu masih punya tanggungan keluarga setelah menikah?
+ DebtStatus *string `gorm:"column:debt_status" json:"debt_status"` // Apakah kamu punya cicilan/utang setelah menikah?
+ FinanceSharing *string `gorm:"column:finance_sharing" json:"finance_sharing"` // Setelah menikah, bagaimana pembagian tanggung jawab keuangan?
+ IncomeGapView *string `gorm:"column:income_gap_view" json:"income_gap_view"` // Pandangan kamu jika istri berpenghasilan lebih besar dari suami?
+
+ // Keputusan dan Komunikasi
+ DecisionMaking *string `gorm:"column:decision_making" json:"decision_making"` // Dalam mengambil keputusan, kamu lebih mempertimbangkan pasangan atau sendiri?
+ GrowthTogether *string `gorm:"column:growth_together" json:"growth_together"` // Apakah kamu siap berjuang bersama pasangan? Apa saja yang ingin diperjuangkan?
+ ParentIntervention *string `gorm:"column:parent_intervention" json:"parent_intervention"` // Sejauh mana orang tua/mertua boleh ikut campur dalam rumah tangga?
+ HabitResponse *string `gorm:"column:habit_response" json:"habit_response"` // Bagaimana kamu menyikapi kebiasaan pasangan yang kurang kamu sukai?
+
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ }
+)
+
// Gorm table name settings
func (Account) TableName() string { return "account" }
func (AccountDetails) TableName() string { return "account_details" }
@@ -369,3 +418,6 @@ func (WorshipAndReligiousUnderstandingCV) TableName() string {
func (EducationCV) TableName() string { return "education_cv" }
func (JobCV) TableName() string { return "job_cv" }
func (AchievementCV) TableName() string { return "achievement_cv" }
+func (MarriageReadinessProfile) TableName() string {
+ return "marriage_readiness_profile"
+}
diff --git a/space/space/space/space/models/request_model.go b/space/space/space/space/models/request_model.go
index a48b864c20c1365ce50c12f9e04aa67a95ffa095..00ddf280beb02479bef5b2f26f032396d258ab1c 100644
--- a/space/space/space/space/models/request_model.go
+++ b/space/space/space/space/models/request_model.go
@@ -58,8 +58,9 @@ type AnswerQuizRequest struct {
}
type (
- PersonalityAndPreferenceCVRequest struct {
- AccountID int64 `json:"-"`
+ SavePersonalityAndPreferenceRequest struct {
+ AccountID int64 `json:"-"`
+
PositiveTraits *string `json:"positive_traits"` // sifat positif
NegativeTraits *string `json:"negative_traits"` // sifat negatif
Hobbies *string `json:"hobbies"` // hobi
@@ -76,8 +77,25 @@ type (
MonthlyExpenses *string `json:"monthly_expenses" validate:"monthly_expenses"` // pengeluaran per bulan
}
- FamilyMemberRequest struct {
- AccountID int64 `json:"-"`
+ GetPersonalityAndPreferenceRequest struct {
+ AccountID int64 `json:"-"`
+ }
+
+ CreateFamilyMemberRequest struct {
+ AccountID int64 `json:"-"`
+
+ Role *string `json:"role" validate:"family_role"` // Peran dalam keluarga
+ Status *string `json:"status" validate:"life_status"` // Status (Hidup, Wafat)
+ Religion *string `json:"religion" validate:"religion"` // Agama
+ Job *string `json:"job"` // Pekerjaan
+ LastEducation *string `json:"last_education" validate:"last_education"` // Pendidikan terakhir
+ Age *int `json:"age"` // Usia
+ }
+
+ UpdateFamilyMemberRequest struct {
+ ID int64 `json:"-"`
+ AccountID int64 `json:"-"`
+
Role *string `json:"role" validate:"family_role"` // Peran dalam keluarga
Status *string `json:"status" validate:"life_status"` // Status (Hidup, Wafat)
Religion *string `json:"religion" validate:"religion"` // Agama
@@ -86,19 +104,35 @@ type (
Age *int `json:"age"` // Usia
}
- PhysicalAndHealthRequest struct {
- AccountID int64 `json:"-"`
- HeightInCm *int `json:"height_cm"` // Tinggi badan dalam satuan sentimeter
- WeightInKg *int `json:"weight_kg"` // Berat badan dalam satuan kilogram
- BodyShape *string `json:"body_shape" validate:"body_shape"` // Bentuk tubuh
- SkinColor *string `json:"skin_color" validate:"skin_color"` // Warna kulit
- HairType *string `json:"hair_type" validate:"hair_type"` // Tipe rambut
- MedicalHistory *string `json:"medical_history"` // Riwayat penyakit
- PhysicalDisorder *string `json:"physical_disorder"` // Cacat fisik
- PhysicalTraits *string `json:"physical_traits"` // Ciri khas fisik
+ ListFamilyMemberRequest struct {
+ AccountID int64 `json:"-"`
}
- AccountDetailsRequest struct {
+ GetFamilyMemberRequest struct {
+ ID int64 `json:"-"`
+ }
+
+ DeleteFamilyMemberRequest struct {
+ ID int64 `json:"-"`
+ }
+
+ SavePhysicalAndHealthRequest struct {
+ AccountID int64 `json:"-"`
+ HeightInCm *int `json:"height_cm"` // Tinggi badan dalam satuan sentimeter
+ WeightInKg *int `json:"weight_kg"` // Berat badan dalam satuan kilogram
+ BodyShape *string `json:"body_shape" validate:"body_shape"` // Bentuk tubuh
+ SkinColor *string `json:"skin_color" validate:"skin_color"` // Warna kulit
+ HairType *string `json:"hair_type" validate:"hair_type"` // Tipe rambut
+ MedicalHistory *pq.StringArray `json:"medical_history"` // Riwayat penyakit
+ PhysicalDisorder *string `json:"physical_disorder"` // Cacat fisik
+ PhysicalTraits *string `json:"physical_traits"` // Ciri khas fisik
+ }
+
+ GetPhysicalAndHealthRequest struct {
+ AccountID int64 `json:"-"`
+ }
+
+ SaveAccountDetailsRequest struct {
AccountID int64 `json:"-"`
FullName *string `json:"full_name"`
Gender *string `json:"gender" validate:"gender"`
@@ -111,27 +145,45 @@ type (
PhoneNumber *string `json:"phone_number" validate:"phone_number"`
}
- WorshipAndReligiousUnderstandingRequest struct {
- AccountID int64 `json:"-"`
- ObligatoryPrayer *string `json:"obligatory_prayer"` // sholat_wajib_5_waktu
- CongregationalPrayer *string `json:"congregational_prayer"` // sholat_berjamaah_di_masjid
- TahajjudPrayer *string `json:"tahajjud_prayer"` // sholat_tahajud
- DhuhaPrayer *string `json:"dhuha_prayer"` // sholat_dhuha
- QuranMemorization *string `json:"quran_memorization"` // hafalan_alquran
- QuranReadingAbility *string `json:"quran_reading_ability" validate:"quran_reading_ability"` // kemampuan_baca_alquran
- DaudFasting *string `json:"daud_fasting"` // puasa_daud
- AyyamulBidhFasting *string `json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
- HajjOrUmrah pq.StringArray `json:"hajj_or_umrah"` // ibadah_haji_umroh
- ListeningToMusic *string `json:"listening_to_music"` // mendengarkan_musik
- OpinionOnIkhtilat *string `json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
- OpinionOnTouchingNonMahram *string `json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
- OpinionOnVeil *string `json:"opinion_on_veil"` // pendapat_tentang_cadar
- WeeklyReligiousStudies *string `json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
- FollowedUstadz *string `json:"followed_ustadz"` // ustadz_yang_diikuti
- }
-
- EducationRequest struct {
- AccountID int64 `json:"account_id"`
+ GetAccountDetailsRequest struct {
+ AccountID int64 `json:"account_id"`
+ }
+
+ SaveWorshipAndReligiousUnderstandingRequest struct {
+ AccountID int64 `json:"-"`
+ ObligatoryPrayer *string `json:"obligatory_prayer"` // sholat_wajib_5_waktu
+ CongregationalPrayer *string `json:"congregational_prayer"` // sholat_berjamaah_di_masjid
+ TahajjudPrayer *string `json:"tahajjud_prayer"` // sholat_tahajud
+ DhuhaPrayer *string `json:"dhuha_prayer"` // sholat_dhuha
+ QuranMemorization *string `json:"quran_memorization"` // hafalan_alquran
+ QuranReadingAbility *string `json:"quran_reading_ability" validate:"quran_reading_ability"` // kemampuan_baca_alquran
+ DaudFasting *string `json:"daud_fasting"` // puasa_daud
+ AyyamulBidhFasting *string `json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
+ HajjOrUmrah *pq.StringArray `json:"hajj_or_umrah"` // ibadah_haji_umroh
+ ListeningToMusic *string `json:"listening_to_music"` // mendengarkan_musik
+ OpinionOnIkhtilat *string `json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
+ OpinionOnTouchingNonMahram *string `json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
+ OpinionOnVeil *string `json:"opinion_on_veil"` // pendapat_tentang_cadar
+ WeeklyReligiousStudies *string `json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
+ FollowedUstadz *string `json:"followed_ustadz"` // ustadz_yang_diikuti
+ }
+
+ GetWorshipAndReligiousUnderstandingRequest struct {
+ AccountID int64 `json:"-"`
+ }
+
+ CreateEducationRequest struct {
+ AccountID int64 `json:"-"`
+ LastEducation *string `json:"last_education" validate:"last_education"` // pendidikan terakhir
+ EducationInstitute *string `json:"education_institute"` // institusi pendidikan
+ EducationMajor *string `json:"education_major"` // jurusan pendidikan
+ YearStart *int `json:"year_start"` // tahun masuk
+ YearGraduate *int `json:"year_graduate"` // tahun lulus
+ }
+
+ UpdateEducationRequest struct {
+ ID int64 `json:"-"`
+ AccountID int64 `json:"-"`
LastEducation *string `json:"last_education" validate:"last_education"` // pendidikan terakhir
EducationInstitute *string `json:"education_institute"` // institusi pendidikan
EducationMajor *string `json:"education_major"` // jurusan pendidikan
@@ -139,20 +191,72 @@ type (
YearGraduate *int `json:"year_graduate"` // tahun lulus
}
- JobRequest struct {
- AccountID int64 `json:"account_id"`
- InstitutionName *string `json:"institution_name"` // nama instansi
- CurrentJob *string `json:"current_job"` // pekerjaan saat ini
- YearStartedWorking *int `json:"year_started_working"` // tahun mulai bekerja
- MonthlyIncome *string `json:"monthly_income" validate:"monthly_income"` // penghasilan per bulan
- IncomeSources pq.StringArray `json:"income_sources"` // sumber penghasilan
+ ListEducationRequest struct {
+ AccountID int64 `json:"-"`
}
- AchievementRequest struct {
+ GetEducationRequest struct {
+ ID int64 `json:"-"`
+ }
+
+ DeleteEducationRequest struct {
+ ID int64 `json:"-"`
+ }
+
+ CreateJobRequest struct {
+ AccountID int64 `json:"account_id"`
+ InstitutionName *string `json:"institution_name"` // nama instansi
+ CurrentJob *string `json:"current_job"` // pekerjaan saat ini
+ YearStartedWorking *int `json:"year_started_working"` // tahun mulai bekerja
+ MonthlyIncome *string `json:"monthly_income" validate:"monthly_income"` // penghasilan per bulan
+ IncomeSources *pq.StringArray `json:"income_sources"` // sumber penghasilan
+ }
+
+ UpdateJobRequest struct {
+ ID int64 `json:"-"`
+ AccountID int64 `json:"-"`
+ InstitutionName *string `json:"institution_name"` // nama instansi
+ CurrentJob *string `json:"current_job"` // pekerjaan saat ini
+ YearStartedWorking *int `json:"year_started_working"` // tahun mulai bekerja
+ MonthlyIncome *string `json:"monthly_income" validate:"monthly_income"` // penghasilan per bulan
+ IncomeSources *pq.StringArray `json:"income_sources"` // sumber penghasilan
+ }
+
+ ListJobRequest struct {
+ AccountID int64 `json:"-"`
+ }
+
+ GetJobRequest struct {
+ ID int64 `json:"-"`
+ }
+
+ DeleteJobRequest struct {
+ ID int64 `json:"-"`
+ }
+
+ CreateAchievementRequest struct {
AccountID int64 `json:"account_id"`
AchievementOrAward *string `json:"achievement_or_award"` // prestasi atau penghargaan
}
+ UpdateAchievementRequest struct {
+ ID int64 `json:"-"`
+ AccountID int64 `json:"-"`
+ AchievementOrAward *string `json:"achievement_or_award"` // prestasi atau penghargaan
+ }
+
+ ListAchievementRequest struct {
+ AccountID int64 `json:"-"`
+ }
+
+ GetAchievementRequest struct {
+ ID int64 `json:"-"`
+ }
+
+ DeleteAchievementRequest struct {
+ ID int64 `json:"-"`
+ }
+
UploadProfileImageRequest struct {
AccountID int64
File *multipart.FileHeader
@@ -162,7 +266,11 @@ type (
URL string `json:"url"`
}
- ProgressResponse struct {
+ GetProgressCVRequest struct {
+ AccountID int64 `json:"-"`
+ }
+
+ GetProgressCVResponse struct {
AccountDetailsProgress float64 `json:"account_details_progress"`
PersonalityAndPreferenceCVProgress float64 `json:"personality_and_preference_cv_progress"`
FamilyMemberCVProgress float64 `json:"family_member_cv_progress"`
@@ -174,3 +282,49 @@ type (
TotalProgress float64 `json:"total_progress"`
}
)
+
+type SaveMarriageReadinessProfileRequest struct {
+ AccountID int64 `json:"account_id"`
+
+ // Visi Misi Rumah Tangga
+ MarriageVision *string `gorm:"column:marriage_vision" json:"marriage_vision"` // Apa visi dan tujuan utama kamu dalam membangun rumah tangga?
+ LivingPlan *string `gorm:"column:living_plan" json:"living_plan"` // Setelah menikah, kamu berencana tinggal di mana?
+ SpouseRoles *string `gorm:"column:spouse_roles" json:"spouse_roles"` // Menurutmu, apa peran utama suami dan istri dalam rumah tangga?
+ SpouseRights *string `gorm:"column:spouse_rights" json:"spouse_rights"` // Apa saja hak suami dan istri menurutmu?
+ ConflictResolution *string `gorm:"column:conflict_resolution" json:"conflict_resolution"` // Jika terjadi konflik, bagaimana kamu menyikapinya?
+ ParentingStyle *string `gorm:"column:parenting_style" json:"parenting_style"` // Setelah punya anak, pola pengasuhan seperti apa yang kamu inginkan?
+
+ // Konsep Acara Pernikahan
+ ReadyToMarryDuration *string `gorm:"column:ready_to_marry_duration" json:"ready_to_marry_duration"` // Setelah taaruf dimulai, berapa lama kamu butuh untuk siap menikah?
+ WeddingConcept *string `gorm:"column:wedding_concept" json:"wedding_concept"` // Seperti apa konsep pernikahan yang kamu harapkan?
+ WeddingFunding *string `gorm:"column:wedding_funding" json:"wedding_funding"` // Apakah kamu sudah mempersiapkan dana pernikahan? Dari mana sumbernya?
+
+ // Karir Kedepannya
+ CareerAfterMarriage *string `gorm:"column:career_after_marriage" json:"career_after_marriage"` // Apakah kamu ingin tetap bekerja setelah menikah?
+ TimeManagement *string `gorm:"column:time_management" json:"time_management"` // Bagaimana kamu membagi waktu antara keluarga, ibadah, dan pekerjaan?
+ CareerGoals *string `gorm:"column:career_goals" json:"career_goals"` // Apa impian karier atau cita-cita kamu dalam 5 sampai 10 tahun ke depan?
+ SelfDevelopment *string `gorm:"column:self_development" json:"self_development"` // Apa usaha kamu untuk terus mengembangkan diri dan skill?
+
+ // Pendidikan Keluarga
+ DelayChildren *string `gorm:"column:delay_children" json:"delay_children"` // Apakah kamu berencana menunda memiliki anak?
+ ChildEducationPlan *string `gorm:"column:child_education_plan" json:"child_education_plan"` // Apakah kamu sudah punya rencana pendidikan anak?
+ ReligiousEmotionalBond *string `gorm:"column:religious_emotional_bond" json:"religious_emotional_bond"` // Bagaimana cara menjaga nilai agama & kedekatan emosional dengan anak?
+ ParentingChallenges *string `gorm:"column:parenting_challenges" json:"parenting_challenges"` // Saat menghadapi tantangan pengasuhan, bagaimana kamu menyikapinya?
+
+ // Finansial Keluarga
+ MonthlyFinance *string `gorm:"column:monthly_finance" json:"monthly_finance"` // Bagaimana kamu mengelola keuangan bulanan?
+ FamilyResponsibility *string `gorm:"column:family_responsibility" json:"family_responsibility"` // Apakah kamu masih punya tanggungan keluarga setelah menikah?
+ DebtStatus *string `gorm:"column:debt_status" json:"debt_status"` // Apakah kamu punya cicilan/utang setelah menikah?
+ FinanceSharing *string `gorm:"column:finance_sharing" json:"finance_sharing"` // Setelah menikah, bagaimana pembagian tanggung jawab keuangan?
+ IncomeGapView *string `gorm:"column:income_gap_view" json:"income_gap_view"` // Pandangan kamu jika istri berpenghasilan lebih besar dari suami?
+
+ // Keputusan dan Komunikasi
+ DecisionMaking *string `gorm:"column:decision_making" json:"decision_making"` // Dalam mengambil keputusan, kamu lebih mempertimbangkan pasangan atau sendiri?
+ GrowthTogether *string `gorm:"column:growth_together" json:"growth_together"` // Apakah kamu siap berjuang bersama pasangan? Apa saja yang ingin diperjuangkan?
+ ParentIntervention *string `gorm:"column:parent_intervention" json:"parent_intervention"` // Sejauh mana orang tua/mertua boleh ikut campur dalam rumah tangga?
+ HabitResponse *string `gorm:"column:habit_response" json:"habit_response"` // Bagaimana kamu menyikapi kebiasaan pasangan yang kurang kamu sukai?
+}
+
+type GetMarriageReadinessProfileRequest struct {
+ AccountID int64 `json:"-"`
+}
diff --git a/space/space/space/space/pkg/validation/validation.go b/space/space/space/space/pkg/validation/validation.go
index fc44b56a157f42bf9e7032cf3abafd4fee97aea7..006ace4f8748879b5853975e22ba0228fdb5bc12 100644
--- a/space/space/space/space/pkg/validation/validation.go
+++ b/space/space/space/space/pkg/validation/validation.go
@@ -3,6 +3,7 @@ package validation
import (
"errors"
"fmt"
+ "reflect"
"strings"
"github.com/go-playground/locales/en"
@@ -129,10 +130,12 @@ func Validate(s any) []ErrorMessage {
if validatorInstance == nil {
return []ErrorMessage{{Field: "", Message: "Validator belum diinisialisasi. Panggil validation.New() terlebih dahulu."}}
}
+
err := validatorInstance.validate.Struct(s)
if err != nil {
return TranslateError(err)
}
+
return nil
}
@@ -147,9 +150,15 @@ func TranslateError(err error) []ErrorMessage {
return nil
}
- errorMessages := make([]ErrorMessage, 0, len(validationErrors))
+ var errorMessages []ErrorMessage
+
for _, e := range validationErrors {
fieldLabel := e.Field()
+
+ if e.Kind() == reflect.Ptr {
+ continue
+ }
+
msg, err := validatorInstance.translator.T(e.Tag(), fieldLabel)
if err != nil {
msg = fieldLabel + " is Invalid"
diff --git a/space/space/space/space/pkg/worker/task_send_forgot_password_email.go b/space/space/space/space/pkg/worker/task_send_forgot_password_email.go
index 8675c2b7b005df053a3660f8b42ec6cd2bc5722c..d4fa03aca1920635dd42f39ecca3dc99e1012593 100644
--- a/space/space/space/space/pkg/worker/task_send_forgot_password_email.go
+++ b/space/space/space/space/pkg/worker/task_send_forgot_password_email.go
@@ -1,14 +1,15 @@
package worker
import (
- "api.qobiltu.id/assets"
"bytes"
"context"
"encoding/json"
"fmt"
- "github.com/hibiken/asynq"
"html/template"
- "log/slog"
+ "time"
+
+ "api.qobiltu.id/assets"
+ "github.com/hibiken/asynq"
)
const (
@@ -62,14 +63,15 @@ func (p *RedisTaskProcessor) ProcessTaskSendForgotPasswordEmail(ctx context.Cont
}
htmlContent := body.String()
- slog.Info("Sending forgot password email", slog.String("email", payload.EmailAddress))
+ fmt.Println("Sending forgot password email", payload.EmailAddress)
+ start := time.Now()
err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
if err != nil {
return fmt.Errorf("failed to send forgot password email: %w", err)
}
- slog.Info("Forgot password email sent successfully", slog.String("email", payload.EmailAddress))
-
+ fmt.Println("Forgot password email sent successfully", payload.EmailAddress)
+ fmt.Println("Time taken", time.Since(start))
return nil
}
diff --git a/space/space/space/space/pkg/worker/task_send_verify_email.go b/space/space/space/space/pkg/worker/task_send_verify_email.go
index 9f6fd14a6a279def7ca9036bea59175f731f91ef..eda356fa8631e181e88b30863b8b55db310baf16 100644
--- a/space/space/space/space/pkg/worker/task_send_verify_email.go
+++ b/space/space/space/space/pkg/worker/task_send_verify_email.go
@@ -1,14 +1,15 @@
package worker
import (
- "api.qobiltu.id/assets"
"bytes"
"context"
"encoding/json"
"fmt"
- "github.com/hibiken/asynq"
"html/template"
- "log/slog"
+ "time"
+
+ "api.qobiltu.id/assets"
+ "github.com/hibiken/asynq"
)
const (
@@ -62,14 +63,16 @@ func (p *RedisTaskProcessor) ProcessTaskSendVerifyEmail(ctx context.Context, tas
}
htmlContent := body.String()
- slog.Info("Sending verification email", slog.String("email", payload.EmailAddress))
+ fmt.Println("Sending verification email", payload.EmailAddress)
+ start := time.Now()
err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
if err != nil {
return fmt.Errorf("failed to send verify email: %w", err)
}
- slog.Info("Verification email sent successfully", slog.String("email", payload.EmailAddress))
+ fmt.Println("Verification email sent successfully", payload.EmailAddress)
+ fmt.Println("Time taken", time.Since(start))
return nil
}
diff --git a/space/space/space/space/router/cv_route.go b/space/space/space/space/router/cv_route.go
index 6a2ab9e4ef32a778b0a7597b4a36ba0ce0739f94..b9ab7519c1ce16547e79046ce8984104f8cd3f17 100644
--- a/space/space/space/space/router/cv_route.go
+++ b/space/space/space/space/router/cv_route.go
@@ -42,6 +42,6 @@ func (s *Server) CVRoute() {
routerGroup.PUT("/achievements/:id", s.cvController.UpdateAchievement)
routerGroup.DELETE("/achievements/:id", s.cvController.DeleteAchievement)
- routerGroup.GET("/progress", s.cvController.GetProgress)
+ routerGroup.GET("/progress", s.cvController.GetProgressCV)
}
}
diff --git a/space/space/space/space/router/router.go b/space/space/space/space/router/router.go
index 8b647f502ab36d3165e4dfedf4b3e62039827a25..47abd2033afd236b8c2ac1d9e766b287906aa2db 100644
--- a/space/space/space/space/router/router.go
+++ b/space/space/space/space/router/router.go
@@ -18,5 +18,6 @@ func (s *Server) setupRoutes() {
// another way to register routes
s.HealthCheckRoute()
s.CVRoute()
+ s.MarriageReadinessProfileRoute()
s.StorageRoute()
}
diff --git a/space/space/space/space/router/server.go b/space/space/space/space/router/server.go
index ae7ea916bd383632c60b6dcd5995d71840bd5717..601a8ee215eedafff6238c59bef3dee099584096 100644
--- a/space/space/space/space/router/server.go
+++ b/space/space/space/space/router/server.go
@@ -2,28 +2,32 @@ package router
import (
cv_controller "api.qobiltu.id/controller/cv"
- "api.qobiltu.id/controller/health_check"
+ health_check_controller "api.qobiltu.id/controller/health_check"
+ marriage_readiness_profile_controller "api.qobiltu.id/controller/marriage_readiness_profile"
"github.com/gin-gonic/gin"
)
type Server struct {
- router *gin.Engine
- healthCheckController health_check_controller.HealthCheckController
- cvController cv_controller.CVController
+ router *gin.Engine
+ healthCheckController health_check_controller.HealthCheckController
+ cvController cv_controller.CVController
+ marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController
}
func NewServer(
healthCheckController health_check_controller.HealthCheckController,
cvController cv_controller.CVController,
+ marriageReadinessProfileController marriage_readiness_profile_controller.MarriageReadinessProfileController,
) (*Server, error) {
router := gin.Default()
router.Use(gin.Recovery())
server := &Server{
- healthCheckController: healthCheckController,
- cvController: cvController,
- router: router,
+ healthCheckController: healthCheckController,
+ cvController: cvController,
+ marriageReadinessProfileController: marriageReadinessProfileController,
+ router: router,
}
server.setupRoutes()
diff --git a/space/space/space/space/services/cv_service.go b/space/space/space/space/services/cv_service.go
index 0208b10ddcf6e62dc3ad6c87f512097434efef0c..db8b6abb7357a69409b53828e30aca315c12227a 100644
--- a/space/space/space/space/services/cv_service.go
+++ b/space/space/space/space/services/cv_service.go
@@ -11,48 +11,49 @@ import (
"api.qobiltu.id/pkg/validation"
"api.qobiltu.id/repositories"
"api.qobiltu.id/response"
+ "api.qobiltu.id/utils"
"gorm.io/gorm"
)
type CVService interface {
- SaveAccountDetails(ctx context.Context, req *models.AccountDetailsRequest) (*models.AccountDetails, error)
- GetAccountDetails(ctx context.Context, id int64) (*models.AccountDetails, error)
-
- SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCVRequest) (*models.PersonalityAndPreferenceCV, error)
- GetPersonalityAndPreference(ctx context.Context, id int64) (*models.PersonalityAndPreferenceCV, error)
-
- CreateFamilyMember(ctx context.Context, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
- UpdateFamilyMember(ctx context.Context, id int64, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
- ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error)
- GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error)
- DeleteFamilyMember(ctx context.Context, id int64) error
-
- SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error)
- GetPhysicalAndHealth(ctx context.Context, id int64) (*models.PhysicalAndHealthCV, error)
-
- SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error)
- GetWorshipAndReligiousUnderstanding(ctx context.Context, id int64) (*models.WorshipAndReligiousUnderstandingCV, error)
-
- CreateEducation(ctx context.Context, req *models.EducationRequest) (*models.EducationCV, error)
- UpdateEducation(ctx context.Context, id int64, req *models.EducationRequest) (*models.EducationCV, error)
- ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error)
- GetEducation(ctx context.Context, id int64) (*models.EducationCV, error)
- DeleteEducation(ctx context.Context, id int64) error
-
- CreateJob(ctx context.Context, req *models.JobRequest) (*models.JobCV, error)
- UpdateJob(ctx context.Context, id int64, req *models.JobRequest) (*models.JobCV, error)
- ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error)
- GetJob(ctx context.Context, id int64) (*models.JobCV, error)
- DeleteJob(ctx context.Context, id int64) error
-
- CreateAchievement(ctx context.Context, req *models.AchievementRequest) (*models.AchievementCV, error)
- UpdateAchievement(ctx context.Context, id int64, req *models.AchievementRequest) (*models.AchievementCV, error)
- ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error)
- GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error)
- DeleteAchievement(ctx context.Context, id int64) error
+ SaveAccountDetails(ctx context.Context, req *models.SaveAccountDetailsRequest) (*models.AccountDetails, error)
+ GetAccountDetails(ctx context.Context, req *models.GetAccountDetailsRequest) (*models.AccountDetails, error)
+
+ SavePersonalityAndPreference(ctx context.Context, req *models.SavePersonalityAndPreferenceRequest) (*models.PersonalityAndPreferenceCV, error)
+ GetPersonalityAndPreference(ctx context.Context, req *models.GetPersonalityAndPreferenceRequest) (*models.PersonalityAndPreferenceCV, error)
+
+ CreateFamilyMember(ctx context.Context, req *models.CreateFamilyMemberRequest) (*models.FamilyMemberCV, error)
+ UpdateFamilyMember(ctx context.Context, req *models.UpdateFamilyMemberRequest) (*models.FamilyMemberCV, error)
+ ListFamilyMember(ctx context.Context, req *models.ListFamilyMemberRequest) ([]models.FamilyMemberCV, error)
+ GetFamilyMember(ctx context.Context, req *models.GetFamilyMemberRequest) (*models.FamilyMemberCV, error)
+ DeleteFamilyMember(ctx context.Context, req *models.DeleteFamilyMemberRequest) error
+
+ SavePhysicalAndHealth(ctx context.Context, req *models.SavePhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error)
+ GetPhysicalAndHealth(ctx context.Context, req *models.GetPhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error)
+
+ SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.SaveWorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error)
+ GetWorshipAndReligiousUnderstanding(ctx context.Context, req *models.GetWorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error)
+
+ CreateEducation(ctx context.Context, req *models.CreateEducationRequest) (*models.EducationCV, error)
+ UpdateEducation(ctx context.Context, req *models.UpdateEducationRequest) (*models.EducationCV, error)
+ ListEducation(ctx context.Context, req *models.ListEducationRequest) ([]models.EducationCV, error)
+ GetEducation(ctx context.Context, req *models.GetEducationRequest) (*models.EducationCV, error)
+ DeleteEducation(ctx context.Context, req *models.DeleteEducationRequest) error
+
+ CreateJob(ctx context.Context, req *models.CreateJobRequest) (*models.JobCV, error)
+ UpdateJob(ctx context.Context, req *models.UpdateJobRequest) (*models.JobCV, error)
+ ListJob(ctx context.Context, req *models.ListJobRequest) ([]models.JobCV, error)
+ GetJob(ctx context.Context, req *models.GetJobRequest) (*models.JobCV, error)
+ DeleteJob(ctx context.Context, req *models.DeleteJobRequest) error
+
+ CreateAchievement(ctx context.Context, req *models.CreateAchievementRequest) (*models.AchievementCV, error)
+ UpdateAchievement(ctx context.Context, req *models.UpdateAchievementRequest) (*models.AchievementCV, error)
+ ListAchievement(ctx context.Context, req *models.ListAchievementRequest) ([]models.AchievementCV, error)
+ GetAchievement(ctx context.Context, req *models.GetAchievementRequest) (*models.AchievementCV, error)
+ DeleteAchievement(ctx context.Context, req *models.DeleteAchievementRequest) error
UploadProfileImage(ctx context.Context, req *models.UploadProfileImageRequest) (*models.UploadProfileImageResponse, error)
- GetProgress(ctx context.Context, accountID int64) (*models.ProgressResponse, error)
+ GetProgressCV(ctx context.Context, req *models.GetProgressCVRequest) (*models.GetProgressCVResponse, error)
}
type cvService struct {
@@ -67,7 +68,9 @@ func NewCVService(cvRepository repositories.CVRepository, storage storage.Storag
}
}
-func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.AccountDetailsRequest) (*models.AccountDetails, error) {
+func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.SaveAccountDetailsRequest) (*models.AccountDetails, error) {
+ // notes
+ // jika ingin mengubah value wajib kirimkan field beserta value nya
if err := validation.Validate(req); err != nil {
return nil, response.HandleValidationError(err)
}
@@ -84,14 +87,15 @@ func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.AccountD
}
accountDetails.AccountID = uint(req.AccountID)
- accountDetails.FullName = req.FullName
- accountDetails.Gender = req.Gender
- accountDetails.DateOfBirth = req.DateOfBirth
- accountDetails.PlaceOfBirth = req.PlaceOfBirth
- accountDetails.Domicile = req.Domicile
- accountDetails.MaritalStatus = req.MaritalStatus
- accountDetails.LastEducation = req.LastEducation
- accountDetails.LastJob = req.LastJob
+
+ utils.AssignIfNotNil(&accountDetails.FullName, req.FullName)
+ utils.AssignIfNotNil(&accountDetails.Gender, req.Gender)
+ utils.AssignIfNotNil(&accountDetails.DateOfBirth, req.DateOfBirth)
+ utils.AssignIfNotNil(&accountDetails.PlaceOfBirth, req.PlaceOfBirth)
+ utils.AssignIfNotNil(&accountDetails.Domicile, req.Domicile)
+ utils.AssignIfNotNil(&accountDetails.MaritalStatus, req.MaritalStatus)
+ utils.AssignIfNotNil(&accountDetails.LastEducation, req.LastEducation)
+ utils.AssignIfNotNil(&accountDetails.LastJob, req.LastJob)
if req.PhoneNumber != nil {
sanitizedPhone := validation.SanitizePhoneNumber(*req.PhoneNumber)
@@ -107,15 +111,15 @@ func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.AccountD
return res, nil
}
-func (s *cvService) GetAccountDetails(ctx context.Context, id int64) (*models.AccountDetails, error) {
- res, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, id)
+func (s *cvService) GetAccountDetails(ctx context.Context, req *models.GetAccountDetailsRequest) (*models.AccountDetails, error) {
+ res, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, req.AccountID)
if err != nil {
- return nil, response.HandleGormError(err, "Data diri tidak ditemukan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return res, nil
}
-func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCVRequest) (*models.PersonalityAndPreferenceCV, error) {
+func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *models.SavePersonalityAndPreferenceRequest) (*models.PersonalityAndPreferenceCV, error) {
if err := validation.Validate(req); err != nil {
return nil, response.HandleValidationError(err)
}
@@ -132,20 +136,21 @@ func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *model
}
personalityAndPreference.AccountID = req.AccountID
- personalityAndPreference.PositiveTraits = req.PositiveTraits
- personalityAndPreference.NegativeTraits = req.NegativeTraits
- personalityAndPreference.Hobbies = req.Hobbies
- personalityAndPreference.LifeGoals = req.LifeGoals
- personalityAndPreference.DailyActivities = req.DailyActivities
- personalityAndPreference.LeisureActivities = req.LeisureActivities
- personalityAndPreference.Likes = req.Likes
- personalityAndPreference.Dislikes = req.Dislikes
- personalityAndPreference.StressHandling = req.StressHandling
- personalityAndPreference.AngerTriggers = req.AngerTriggers
- personalityAndPreference.FavoriteFoodAndDrinks = req.FavoriteFoodAndDrinks
- personalityAndPreference.CanCook = req.CanCook
- personalityAndPreference.TypesOfDishesCooked = req.TypesOfDishesCooked
- personalityAndPreference.MonthlyExpenses = req.MonthlyExpenses
+
+ utils.AssignIfNotNil(&personalityAndPreference.PositiveTraits, req.PositiveTraits)
+ utils.AssignIfNotNil(&personalityAndPreference.NegativeTraits, req.NegativeTraits)
+ utils.AssignIfNotNil(&personalityAndPreference.Hobbies, req.Hobbies)
+ utils.AssignIfNotNil(&personalityAndPreference.LifeGoals, req.LifeGoals)
+ utils.AssignIfNotNil(&personalityAndPreference.DailyActivities, req.DailyActivities)
+ utils.AssignIfNotNil(&personalityAndPreference.LeisureActivities, req.LeisureActivities)
+ utils.AssignIfNotNil(&personalityAndPreference.Likes, req.Likes)
+ utils.AssignIfNotNil(&personalityAndPreference.Dislikes, req.Dislikes)
+ utils.AssignIfNotNil(&personalityAndPreference.StressHandling, req.StressHandling)
+ utils.AssignIfNotNil(&personalityAndPreference.AngerTriggers, req.AngerTriggers)
+ utils.AssignIfNotNil(&personalityAndPreference.FavoriteFoodAndDrinks, req.FavoriteFoodAndDrinks)
+ utils.AssignIfNotNil(&personalityAndPreference.CanCook, req.CanCook)
+ utils.AssignIfNotNil(&personalityAndPreference.TypesOfDishesCooked, req.TypesOfDishesCooked)
+ utils.AssignIfNotNil(&personalityAndPreference.MonthlyExpenses, req.MonthlyExpenses)
res, err := s.cvRepository.SavePersonalityAndPreference(ctx, personalityAndPreference)
if err != nil {
@@ -155,8 +160,8 @@ func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *model
return res, nil
}
-func (s *cvService) GetPersonalityAndPreference(ctx context.Context, id int64) (*models.PersonalityAndPreferenceCV, error) {
- res, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, id)
+func (s *cvService) GetPersonalityAndPreference(ctx context.Context, req *models.GetPersonalityAndPreferenceRequest) (*models.PersonalityAndPreferenceCV, error) {
+ res, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, req.AccountID)
if err != nil {
return nil, response.HandleGormError(err, "Internal Server Error")
}
@@ -164,7 +169,7 @@ func (s *cvService) GetPersonalityAndPreference(ctx context.Context, id int64) (
return res, nil
}
-func (s *cvService) CreateFamilyMember(ctx context.Context, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error) {
+func (s *cvService) CreateFamilyMember(ctx context.Context, req *models.CreateFamilyMemberRequest) (*models.FamilyMemberCV, error) {
if err := validation.Validate(req); err != nil {
return nil, response.HandleValidationError(err)
}
@@ -183,62 +188,62 @@ func (s *cvService) CreateFamilyMember(ctx context.Context, req *models.FamilyMe
// Simpan ke repository
res, err := s.cvRepository.SaveFamilyMember(ctx, familyMember)
if err != nil {
- return nil, response.HandleGormError(err, "Gagal menyimpan anggota keluarga")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return res, nil
}
-func (s *cvService) ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error) {
- list, err := s.cvRepository.ListFamilyMember(ctx, accountID)
+func (s *cvService) ListFamilyMember(ctx context.Context, req *models.ListFamilyMemberRequest) ([]models.FamilyMemberCV, error) {
+ list, err := s.cvRepository.ListFamilyMember(ctx, req.AccountID)
if err != nil {
- return nil, response.HandleGormError(err, "Gagal mengambil daftar anggota keluarga")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return list, nil
}
-func (s *cvService) GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error) {
- res, err := s.cvRepository.GetFamilyMember(ctx, id)
+func (s *cvService) GetFamilyMember(ctx context.Context, req *models.GetFamilyMemberRequest) (*models.FamilyMemberCV, error) {
+ res, err := s.cvRepository.GetFamilyMember(ctx, req.ID)
if err != nil {
- return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return res, nil
}
-func (s *cvService) DeleteFamilyMember(ctx context.Context, id int64) error {
- err := s.cvRepository.DeleteFamilyMember(ctx, id)
+func (s *cvService) DeleteFamilyMember(ctx context.Context, req *models.DeleteFamilyMemberRequest) error {
+ err := s.cvRepository.DeleteFamilyMember(ctx, req.ID)
if err != nil {
- return response.HandleGormError(err, "Gagal menghapus anggota keluarga")
+ return response.HandleGormError(err, "Internal Server Error")
}
return nil
}
-func (s *cvService) UpdateFamilyMember(ctx context.Context, id int64, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error) {
+func (s *cvService) UpdateFamilyMember(ctx context.Context, req *models.UpdateFamilyMemberRequest) (*models.FamilyMemberCV, error) {
if err := validation.Validate(req); err != nil {
return nil, response.HandleValidationError(err)
}
- existing, err := s.cvRepository.GetFamilyMember(ctx, id)
+ existing, err := s.cvRepository.GetFamilyMember(ctx, req.ID)
if err != nil {
- return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
- existing.Role = req.Role
- existing.Status = req.Status
- existing.Religion = req.Religion
- existing.Job = req.Job
- existing.LastEducation = req.LastEducation
- existing.Age = req.Age
+ utils.AssignIfNotNil(&existing.Role, req.Role)
+ utils.AssignIfNotNil(&existing.Status, req.Status)
+ utils.AssignIfNotNil(&existing.Religion, req.Religion)
+ utils.AssignIfNotNil(&existing.Job, req.Job)
+ utils.AssignIfNotNil(&existing.LastEducation, req.LastEducation)
+ utils.AssignIfNotNil(&existing.Age, req.Age)
updated, err := s.cvRepository.SaveFamilyMember(ctx, existing)
if err != nil {
- return nil, response.HandleGormError(err, "Gagal memperbarui anggota keluarga")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return updated, nil
}
-func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error) {
+func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.SavePhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error) {
if err := validation.Validate(req); err != nil {
return nil, response.HandleValidationError(err)
}
@@ -246,7 +251,7 @@ func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.Physi
// Cek apakah data sudah ada berdasarkan account_id
existing, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, req.AccountID)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data fisik dan kesehatan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
// Jika belum ada, buat objek baru
@@ -256,33 +261,34 @@ func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.Physi
// Mapping field dari request
existing.AccountID = req.AccountID
- existing.HeightInCm = req.HeightInCm
- existing.WeightInKg = req.WeightInKg
- existing.BodyShape = req.BodyShape
- existing.SkinColor = req.SkinColor
- existing.HairType = req.HairType
- existing.MedicalHistory = req.MedicalHistory
- existing.PhysicalDisorder = req.PhysicalDisorder
- existing.PhysicalTraits = req.PhysicalTraits
+
+ utils.AssignIfNotNil(&existing.HeightInCm, req.HeightInCm)
+ utils.AssignIfNotNil(&existing.WeightInKg, req.WeightInKg)
+ utils.AssignIfNotNil(&existing.BodyShape, req.BodyShape)
+ utils.AssignIfNotNil(&existing.SkinColor, req.SkinColor)
+ utils.AssignIfNotNil(&existing.HairType, req.HairType)
+ utils.AssignIfNotNil(&existing.MedicalHistory, req.MedicalHistory)
+ utils.AssignIfNotNil(&existing.PhysicalDisorder, req.PhysicalDisorder)
+ utils.AssignIfNotNil(&existing.PhysicalTraits, req.PhysicalTraits)
// Simpan data
res, err := s.cvRepository.SavePhysicalAndHealth(ctx, existing)
if err != nil {
- return nil, response.HandleGormError(err, "Gagal menyimpan data fisik dan kesehatan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return res, nil
}
-func (s *cvService) GetPhysicalAndHealth(ctx context.Context, id int64) (*models.PhysicalAndHealthCV, error) {
- res, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, id)
+func (s *cvService) GetPhysicalAndHealth(ctx context.Context, req *models.GetPhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error) {
+ res, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, req.AccountID)
if err != nil {
- return nil, response.HandleGormError(err, "Data fisik dan kesehatan tidak ditemukan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return res, nil
}
-func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error) {
+func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.SaveWorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error) {
if err := validation.Validate(req); err != nil {
return nil, response.HandleValidationError(err)
}
@@ -290,7 +296,7 @@ func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, re
// Cek apakah data sudah ada berdasarkan account_id
worshipAndReligiousUnderstanding, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, req.AccountID)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data agama dan pemahaman agama")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
// Jika belum ada, buat objek baru
@@ -300,21 +306,22 @@ func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, re
// Mapping field dari request
worshipAndReligiousUnderstanding.AccountID = req.AccountID
- worshipAndReligiousUnderstanding.ObligatoryPrayer = req.ObligatoryPrayer
- worshipAndReligiousUnderstanding.CongregationalPrayer = req.CongregationalPrayer
- worshipAndReligiousUnderstanding.TahajjudPrayer = req.TahajjudPrayer
- worshipAndReligiousUnderstanding.DhuhaPrayer = req.DhuhaPrayer
- worshipAndReligiousUnderstanding.QuranMemorization = req.QuranMemorization
- worshipAndReligiousUnderstanding.QuranReadingAbility = req.QuranReadingAbility
- worshipAndReligiousUnderstanding.DaudFasting = req.DaudFasting
- worshipAndReligiousUnderstanding.AyyamulBidhFasting = req.AyyamulBidhFasting
- worshipAndReligiousUnderstanding.HajjOrUmrah = req.HajjOrUmrah
- worshipAndReligiousUnderstanding.ListeningToMusic = req.ListeningToMusic
- worshipAndReligiousUnderstanding.OpinionOnIkhtilat = req.OpinionOnIkhtilat
- worshipAndReligiousUnderstanding.OpinionOnTouchingNonMahram = req.OpinionOnTouchingNonMahram
- worshipAndReligiousUnderstanding.OpinionOnVeil = req.OpinionOnVeil
- worshipAndReligiousUnderstanding.WeeklyReligiousStudies = req.WeeklyReligiousStudies
- worshipAndReligiousUnderstanding.FollowedUstadz = req.FollowedUstadz
+
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.ObligatoryPrayer, req.ObligatoryPrayer)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.CongregationalPrayer, req.CongregationalPrayer)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.TahajjudPrayer, req.TahajjudPrayer)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.DhuhaPrayer, req.DhuhaPrayer)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.QuranMemorization, req.QuranMemorization)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.QuranReadingAbility, req.QuranReadingAbility)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.DaudFasting, req.DaudFasting)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.AyyamulBidhFasting, req.AyyamulBidhFasting)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.HajjOrUmrah, req.HajjOrUmrah)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.ListeningToMusic, req.ListeningToMusic)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.OpinionOnIkhtilat, req.OpinionOnIkhtilat)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.OpinionOnTouchingNonMahram, req.OpinionOnTouchingNonMahram)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.OpinionOnVeil, req.OpinionOnVeil)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.WeeklyReligiousStudies, req.WeeklyReligiousStudies)
+ utils.AssignIfNotNil(&worshipAndReligiousUnderstanding.FollowedUstadz, req.FollowedUstadz)
// Simpan data
res, err := s.cvRepository.SaveWorshipAndReligiousUnderstanding(ctx, worshipAndReligiousUnderstanding)
@@ -325,15 +332,15 @@ func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, re
return res, nil
}
-func (s *cvService) GetWorshipAndReligiousUnderstanding(ctx context.Context, id int64) (*models.WorshipAndReligiousUnderstandingCV, error) {
- res, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, id)
+func (s *cvService) GetWorshipAndReligiousUnderstanding(ctx context.Context, req *models.GetWorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error) {
+ res, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, req.AccountID)
if err != nil {
- return nil, response.HandleGormError(err, "Data agama dan pemahaman agama tidak ditemukan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return res, nil
}
-func (s *cvService) CreateEducation(ctx context.Context, req *models.EducationRequest) (*models.EducationCV, error) {
+func (s *cvService) CreateEducation(ctx context.Context, req *models.CreateEducationRequest) (*models.EducationCV, error) {
if err := validation.Validate(req); err != nil {
return nil, response.HandleValidationError(err)
}
@@ -349,52 +356,52 @@ func (s *cvService) CreateEducation(ctx context.Context, req *models.EducationRe
res, err := s.cvRepository.SaveEducation(ctx, edu)
if err != nil {
- return nil, response.HandleGormError(err, "Gagal menambahkan data pendidikan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return res, nil
}
-func (s *cvService) UpdateEducation(ctx context.Context, id int64, req *models.EducationRequest) (*models.EducationCV, error) {
+func (s *cvService) UpdateEducation(ctx context.Context, req *models.UpdateEducationRequest) (*models.EducationCV, error) {
if err := validation.Validate(req); err != nil {
return nil, response.HandleValidationError(err)
}
- edu, err := s.cvRepository.GetEducation(ctx, id)
+ edu, err := s.cvRepository.GetEducation(ctx, req.ID)
if err != nil {
- return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
- edu.LastEducation = req.LastEducation
- edu.EducationInstitute = req.EducationInstitute
- edu.EducationMajor = req.EducationMajor
- edu.YearStart = req.YearStart
- edu.YearGraduate = req.YearGraduate
+ utils.AssignIfNotNil(&edu.LastEducation, req.LastEducation)
+ utils.AssignIfNotNil(&edu.EducationInstitute, req.EducationInstitute)
+ utils.AssignIfNotNil(&edu.EducationMajor, req.EducationMajor)
+ utils.AssignIfNotNil(&edu.YearStart, req.YearStart)
+ utils.AssignIfNotNil(&edu.YearGraduate, req.YearGraduate)
res, err := s.cvRepository.SaveEducation(ctx, edu)
if err != nil {
- return nil, response.HandleGormError(err, "Gagal memperbarui data pendidikan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return res, nil
}
-func (s *cvService) ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error) {
- return s.cvRepository.ListEducation(ctx, accountID)
+func (s *cvService) ListEducation(ctx context.Context, req *models.ListEducationRequest) ([]models.EducationCV, error) {
+ return s.cvRepository.ListEducation(ctx, req.AccountID)
}
-func (s *cvService) GetEducation(ctx context.Context, id int64) (*models.EducationCV, error) {
- edu, err := s.cvRepository.GetEducation(ctx, id)
+func (s *cvService) GetEducation(ctx context.Context, req *models.GetEducationRequest) (*models.EducationCV, error) {
+ edu, err := s.cvRepository.GetEducation(ctx, req.ID)
if err != nil {
- return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return edu, nil
}
-func (s *cvService) DeleteEducation(ctx context.Context, id int64) error {
- return s.cvRepository.DeleteEducation(ctx, id)
+func (s *cvService) DeleteEducation(ctx context.Context, req *models.DeleteEducationRequest) error {
+ return s.cvRepository.DeleteEducation(ctx, req.ID)
}
-func (s *cvService) CreateJob(ctx context.Context, req *models.JobRequest) (*models.JobCV, error) {
+func (s *cvService) CreateJob(ctx context.Context, req *models.CreateJobRequest) (*models.JobCV, error) {
if err := validation.Validate(req); err != nil {
return nil, response.HandleValidationError(err)
}
@@ -409,93 +416,93 @@ func (s *cvService) CreateJob(ctx context.Context, req *models.JobRequest) (*mod
}
res, err := s.cvRepository.SaveJob(ctx, job)
if err != nil {
- return nil, response.HandleGormError(err, "Gagal menambahkan data pekerjaan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return res, nil
}
-func (s *cvService) UpdateJob(ctx context.Context, id int64, req *models.JobRequest) (*models.JobCV, error) {
+func (s *cvService) UpdateJob(ctx context.Context, req *models.UpdateJobRequest) (*models.JobCV, error) {
if err := validation.Validate(req); err != nil {
return nil, response.HandleValidationError(err)
}
- job, err := s.cvRepository.GetJob(ctx, id)
+ job, err := s.cvRepository.GetJob(ctx, req.ID)
if err != nil {
- return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
- job.InstitutionName = req.InstitutionName
- job.CurrentJob = req.CurrentJob
- job.YearStartedWorking = req.YearStartedWorking
- job.MonthlyIncome = req.MonthlyIncome
- job.IncomeSources = req.IncomeSources
+ utils.AssignIfNotNil(&job.InstitutionName, req.InstitutionName)
+ utils.AssignIfNotNil(&job.CurrentJob, req.CurrentJob)
+ utils.AssignIfNotNil(&job.YearStartedWorking, req.YearStartedWorking)
+ utils.AssignIfNotNil(&job.MonthlyIncome, req.MonthlyIncome)
+ utils.AssignIfNotNil(&job.IncomeSources, req.IncomeSources)
res, err := s.cvRepository.SaveJob(ctx, job)
if err != nil {
- return nil, response.HandleGormError(err, "Gagal memperbarui data pekerjaan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return res, nil
}
-func (s *cvService) ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error) {
- return s.cvRepository.ListJob(ctx, accountID)
+func (s *cvService) ListJob(ctx context.Context, req *models.ListJobRequest) ([]models.JobCV, error) {
+ return s.cvRepository.ListJob(ctx, req.AccountID)
}
-func (s *cvService) GetJob(ctx context.Context, id int64) (*models.JobCV, error) {
- job, err := s.cvRepository.GetJob(ctx, id)
+func (s *cvService) GetJob(ctx context.Context, req *models.GetJobRequest) (*models.JobCV, error) {
+ job, err := s.cvRepository.GetJob(ctx, req.ID)
if err != nil {
- return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return job, nil
}
-func (s *cvService) DeleteJob(ctx context.Context, id int64) error {
- return s.cvRepository.DeleteJob(ctx, id)
+func (s *cvService) DeleteJob(ctx context.Context, req *models.DeleteJobRequest) error {
+ return s.cvRepository.DeleteJob(ctx, req.ID)
}
-func (s *cvService) CreateAchievement(ctx context.Context, req *models.AchievementRequest) (*models.AchievementCV, error) {
+func (s *cvService) CreateAchievement(ctx context.Context, req *models.CreateAchievementRequest) (*models.AchievementCV, error) {
ach := &models.AchievementCV{
AccountID: req.AccountID,
AchievementOrAward: req.AchievementOrAward,
}
res, err := s.cvRepository.SaveAchievement(ctx, ach)
if err != nil {
- return nil, response.HandleGormError(err, "Gagal menambahkan data prestasi")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return res, nil
}
-func (s *cvService) UpdateAchievement(ctx context.Context, id int64, req *models.AchievementRequest) (*models.AchievementCV, error) {
- ach, err := s.cvRepository.GetAchievement(ctx, id)
+func (s *cvService) UpdateAchievement(ctx context.Context, req *models.UpdateAchievementRequest) (*models.AchievementCV, error) {
+ ach, err := s.cvRepository.GetAchievement(ctx, req.ID)
if err != nil {
- return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
ach.AchievementOrAward = req.AchievementOrAward
res, err := s.cvRepository.SaveAchievement(ctx, ach)
if err != nil {
- return nil, response.HandleGormError(err, "Gagal memperbarui data prestasi")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return res, nil
}
-func (s *cvService) ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error) {
- return s.cvRepository.ListAchievement(ctx, accountID)
+func (s *cvService) ListAchievement(ctx context.Context, req *models.ListAchievementRequest) ([]models.AchievementCV, error) {
+ return s.cvRepository.ListAchievement(ctx, req.AccountID)
}
-func (s *cvService) GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error) {
- ach, err := s.cvRepository.GetAchievement(ctx, id)
+func (s *cvService) GetAchievement(ctx context.Context, req *models.GetAchievementRequest) (*models.AchievementCV, error) {
+ ach, err := s.cvRepository.GetAchievement(ctx, req.ID)
if err != nil {
- return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
+ return nil, response.HandleGormError(err, "Internal Server Error")
}
return ach, nil
}
-func (s *cvService) DeleteAchievement(ctx context.Context, id int64) error {
- return s.cvRepository.DeleteAchievement(ctx, id)
+func (s *cvService) DeleteAchievement(ctx context.Context, req *models.DeleteAchievementRequest) error {
+ return s.cvRepository.DeleteAchievement(ctx, req.ID)
}
func (s *cvService) UploadProfileImage(ctx context.Context, req *models.UploadProfileImageRequest) (*models.UploadProfileImageResponse, error) {
@@ -569,8 +576,8 @@ func (s *cvService) UploadProfileImage(ctx context.Context, req *models.UploadPr
}, nil
}
-func (s *cvService) GetProgress(ctx context.Context, accountID int64) (*models.ProgressResponse, error) {
- accountDetails, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, accountID)
+func (s *cvService) GetProgressCV(ctx context.Context, req *models.GetProgressCVRequest) (*models.GetProgressCVResponse, error) {
+ accountDetails, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, req.AccountID)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, response.HandleGormError(err, "Internal Server Error")
@@ -578,7 +585,7 @@ func (s *cvService) GetProgress(ctx context.Context, accountID int64) (*models.P
accountDetails = &models.AccountDetails{}
}
- personalityAndPreferenceCV, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, accountID)
+ personalityAndPreferenceCV, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, req.AccountID)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, response.HandleGormError(err, "Internal Server Error")
@@ -586,7 +593,7 @@ func (s *cvService) GetProgress(ctx context.Context, accountID int64) (*models.P
personalityAndPreferenceCV = &models.PersonalityAndPreferenceCV{}
}
- physicalAndHealthCV, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, accountID)
+ physicalAndHealthCV, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, req.AccountID)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, response.HandleGormError(err, "Internal Server Error")
@@ -594,7 +601,7 @@ func (s *cvService) GetProgress(ctx context.Context, accountID int64) (*models.P
physicalAndHealthCV = &models.PhysicalAndHealthCV{}
}
- worshipAndReligiousUnderstandingCV, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, accountID)
+ worshipAndReligiousUnderstandingCV, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, req.AccountID)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, response.HandleGormError(err, "Internal Server Error")
@@ -602,22 +609,22 @@ func (s *cvService) GetProgress(ctx context.Context, accountID int64) (*models.P
worshipAndReligiousUnderstandingCV = &models.WorshipAndReligiousUnderstandingCV{}
}
- educationCV, err := s.cvRepository.ListEducation(ctx, accountID)
+ educationCV, err := s.cvRepository.ListEducation(ctx, req.AccountID)
if err != nil {
return nil, response.HandleGormError(err, "Internal Server Error")
}
- familyMemberCV, err := s.cvRepository.ListFamilyMember(ctx, accountID)
+ familyMemberCV, err := s.cvRepository.ListFamilyMember(ctx, req.AccountID)
if err != nil {
return nil, response.HandleGormError(err, "Internal Server Error")
}
- jobCV, err := s.cvRepository.ListJob(ctx, accountID)
+ jobCV, err := s.cvRepository.ListJob(ctx, req.AccountID)
if err != nil {
return nil, response.HandleGormError(err, "Internal Server Error")
}
- achievementCV, err := s.cvRepository.ListAchievement(ctx, accountID)
+ achievementCV, err := s.cvRepository.ListAchievement(ctx, req.AccountID)
if err != nil {
return nil, response.HandleGormError(err, "Internal Server Error")
}
@@ -645,9 +652,9 @@ func calculateProgress(
familyMemberCV int,
jobCV int,
achievementCV int,
-) *models.ProgressResponse {
+) *models.GetProgressCVResponse {
- // fullIfPresent returns 100 if the data is greater than 0, otherwise returns 0
+ // fullIfPresent mengembalikan 100 jika data lebih dari 0, jika tidak maka mengembalikan 0
fullIfPresent := func(data int) float64 {
if data > 0 {
return 100
@@ -667,7 +674,7 @@ func calculateProgress(
overallProgress := (accountDetailsPercentage + personalityAndPreferenceCVPercentage + physicalAndHealthCVPercentage + worshipAndReligiousUnderstandingCVPercentage + educationCVPercentage + familyMemberCVPercentage + jobCVPercentage + achievementCVPercentage) / 8
- return &models.ProgressResponse{
+ return &models.GetProgressCVResponse{
AccountDetailsProgress: math.Round(accountDetailsPercentage*100) / 100,
PersonalityAndPreferenceCVProgress: math.Round(personalityAndPreferenceCVPercentage*100) / 100,
FamilyMemberCVProgress: math.Round(familyMemberCVPercentage*100) / 100,
diff --git a/space/space/space/space/space/repositories/quiz_repository.go b/space/space/space/space/space/repositories/quiz_repository.go
index 85dac99b92d4c2306e86433397c94b3f8cf2554b..177a4f4869872882380700d8eb4fae4e85a9311a 100644
--- a/space/space/space/space/space/repositories/quiz_repository.go
+++ b/space/space/space/space/space/repositories/quiz_repository.go
@@ -129,6 +129,6 @@ func CountUserAttemptScore(attemptId uint) Repository[models.QuizAttempt, models
)
repo.Transaction.Model(&repo.Constructor).Raw("SELECT quiz_attempt_id,COUNT(*) AS total_questions,SUM(CASE WHEN is_correct = true THEN 1 ELSE 0 END) AS correct_answers,CAST(SUM(CASE WHEN is_correct = true THEN 1 ELSE 0 END) AS FLOAT) / COUNT(*) AS average_score FROM user_answers WHERE quiz_attempt_id = ? GROUP BY quiz_attempt_id", attemptId).Scan(&repo.Result)
repo.RowsError = repo.Transaction.Error
- repo.NoRecord = true
+ repo.NoRecord = false
return *repo
}
diff --git a/space/space/space/space/space/services/academy_quiz_answer_service.go b/space/space/space/space/space/services/academy_quiz_answer_service.go
index 2f383c92fac4a3c38cfd3c5af4f55cdd4cd4d3b2..36eeba112164624092bcc1506beb40236a7701f6 100644
--- a/space/space/space/space/space/services/academy_quiz_answer_service.go
+++ b/space/space/space/space/space/services/academy_quiz_answer_service.go
@@ -53,4 +53,7 @@ func (s *AnswerQuizService) Update(userID uint, questionNo int, answer int) {
}
return
})
+ s.Exception = QuizAttemptService.Exception
+ s.Error = errors.Join(s.Error, QuizAttemptService.Error)
+ return
}
diff --git a/space/space/space/space/space/services/academy_quiz_question_service.go b/space/space/space/space/space/services/academy_quiz_question_service.go
index 8dc23afae98d277e9e6f8e79495b1abe04369a3e..7fd95902fbb1457d4e45096ed7bb317a8eae1834 100644
--- a/space/space/space/space/space/services/academy_quiz_question_service.go
+++ b/space/space/space/space/space/services/academy_quiz_question_service.go
@@ -1,6 +1,8 @@
package services
import (
+ "errors"
+
"api.qobiltu.id/models"
"api.qobiltu.id/repositories"
)
@@ -39,4 +41,8 @@ func (s *QuestionQuizService) Retrieve(userID uint, questionNo int) {
}
return
})
+
+ s.Exception = QuizAttemptService.Exception
+ s.Error = errors.Join(s.Error, QuizAttemptService.Error)
+ return
}
diff --git a/space/space/space/space/space/services/academy_quiz_service.go b/space/space/space/space/space/services/academy_quiz_service.go
index 98448a782fa129baef497f9095cdded308adc5f9..084b6835765df24f8accf9b8e3f589eb9589fa24 100644
--- a/space/space/space/space/space/services/academy_quiz_service.go
+++ b/space/space/space/space/space/services/academy_quiz_service.go
@@ -2,6 +2,7 @@ package services
import (
"errors"
+ "fmt"
"time"
"api.qobiltu.id/models"
@@ -52,7 +53,7 @@ func CheckUserAttemptLimit(s *AttemptQuizService, allAttemptsRepo repositories.R
func CheckUserLatestAttempt(s *AttemptQuizService, latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz], userID uint, next func()) {
currentTime := time.Now()
- if currentTime.After(latestAttemptRepo.Result.DueAt) {
+ if currentTime.After(latestAttemptRepo.Result.DueAt) && latestAttemptRepo.Result.FinishedAt == nil {
s.Exception.IsTimeOut = true
s.Exception.Message = "Your latest attempt is timeout!"
// Submit
@@ -127,15 +128,24 @@ func Attempt(s *AttemptQuizService, quizRepo repositories.Repository[models.Quiz
func (s *AttemptQuizService) Create(userID uint) {
s.Validate(userID, func(latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz]) {
if latestAttemptRepo.Result.FinishedAt != nil {
- Attempt(s, quizRepo, userID)
+ if latestAttemptRepo.Result.Score < float64(quizRepo.Result.MinScore) {
+ Attempt(s, quizRepo, userID)
+ } else {
+ s.Result = latestAttemptRepo.Result
+ return
+ }
} else {
s.Result = latestAttemptRepo.Result
+ return
}
})
}
func (s *SubmitQuizService) Create() {
+ fmt.Println(s.Constructor.ID)
+ fmt.Println(s.Constructor.AccountID)
quizAttemptRepo := repositories.GetAttemptByIdandUserId(s.Constructor.ID, s.Constructor.AccountID)
+ fmt.Println(quizAttemptRepo.Result)
if quizAttemptRepo.NoRecord {
s.Exception.DataNotFound = true
s.Exception.Message = "There is no quiz attempt with given user!"
@@ -145,7 +155,7 @@ func (s *SubmitQuizService) Create() {
countScoreRepo := repositories.CountUserAttemptScore(s.Constructor.ID)
if countScoreRepo.NoRecord {
s.Exception.DataNotFound = true
- s.Exception.Message = "There is no quiz attempt with given user!"
+ s.Exception.Message = "There is no quiz attempt result with given user!"
}
s.Error = errors.Join(s.Error, countScoreRepo.RowsError)
diff --git a/space/space/space/space/space/space/controller/quiz/result_quiz_controller.go b/space/space/space/space/space/space/controller/quiz/result_quiz_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..b5bfd023930c65b8a4c58a1749862fac602c7f9f
--- /dev/null
+++ b/space/space/space/space/space/space/controller/quiz/result_quiz_controller.go
@@ -0,0 +1,24 @@
+package controller
+
+import (
+ "strconv"
+
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Result(c *gin.Context) {
+ quizResult := services.QuizResultService{}
+ quizResultController := controller.Controller[any, models.QuizAttempt, []models.QuizResultResponse]{
+ Service: &quizResult.Service,
+ }
+ quizResultController.HeaderParse(c, func() {
+ academyId, _ := strconv.Atoi(c.Param("attempt_id"))
+ quizResult.Constructor.AccountID = uint(quizResultController.AccountData.UserID)
+ quizResult.Constructor.ID = uint(academyId) | 0
+ quizResult.Retrieve()
+ quizResultController.Response(c)
+ })
+}
diff --git a/space/space/space/space/space/space/controller/quiz/submit_quiz_controller.go b/space/space/space/space/space/space/controller/quiz/submit_quiz_controller.go
index f6d8348c392ce62c712bf400e711d9c6773bc1e4..76778da8c5031a5ae1cd407ef22b428caa725408 100644
--- a/space/space/space/space/space/space/controller/quiz/submit_quiz_controller.go
+++ b/space/space/space/space/space/space/controller/quiz/submit_quiz_controller.go
@@ -15,9 +15,10 @@ func Submit(c *gin.Context) {
Service: &submitQuiz.Service,
}
submitQuizController.HeaderParse(c, func() {
- quizId, _ := strconv.Atoi(c.Param("attempt_id"))
- submitQuizController.Service.Constructor.ID = uint(quizId)
- submitQuiz.Create(submitQuizController.AccountData.UserID)
+ attemptId, _ := strconv.Atoi(c.Param("attempt_id"))
+ submitQuizController.Service.Constructor.ID = uint(attemptId)
+ submitQuizController.Service.Constructor.AccountID = uint(submitQuizController.AccountData.UserID)
+ submitQuiz.Create()
submitQuizController.Response(c)
})
}
diff --git a/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/models/database_orm_model.go
index 90623608d4eeb80c039e39ced164b9f0d7b34b78..269a4f55ced39eb0e71b026ddb2c355a563d925d 100644
--- a/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/models/database_orm_model.go
@@ -152,7 +152,8 @@ type Question struct {
QuizID uint `json:"quiz_id"`
Content string `json:"content"`
Order int `json:"order"`
- CorrectAnswer uint `json:"-"`
+ CorrectAnswer uint `json:"corrent_answer"`
+ Review string `json:"reviews"`
}
type Quiz struct {
ID uint `gorm:"primaryKey" json:"id"`
@@ -160,7 +161,7 @@ type Quiz struct {
Slug string `json:"slug" gorm:"uniqueIndex" `
Title string `json:"title"`
Description string `json:"description"`
- TotalQuestions string `json:"total_questions"`
+ TotalQuestions int `json:"total_questions"`
AttemptLimit int `json:"attempt_limit"`
TimeLimit int `json:"time_limit"`
MinScore int `json:"min_score"`
@@ -274,7 +275,7 @@ type (
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
- LastEducation *string `gorm:"column:last_education" json:"last_education" validate:""` // pendidikan terakhir
+ LastEducation *string `gorm:"column:last_education" json:"last_education"` // pendidikan terakhir
EducationInstitute *string `gorm:"column:education_institute" json:"education_institute"` // institusi pendidikan
EducationMajor *string `gorm:"column:education_major" json:"education_major"` // jurusan pendidikan
YearStart *int `gorm:"column:year_start" json:"year_start"` // tahun masuk
diff --git a/space/space/space/space/space/space/repositories/academy_repository.go b/space/space/space/space/space/space/repositories/academy_repository.go
index bda561509638851811a64cdff30102bd5ee488c1..08ef8d43f20bd7dff0cf0ffca94a28b6bca5b987 100644
--- a/space/space/space/space/space/space/repositories/academy_repository.go
+++ b/space/space/space/space/space/space/repositories/academy_repository.go
@@ -93,3 +93,13 @@ func GetAcademyByID(id uint) Repository[models.Academy, models.Academy] {
)
return *repo
}
+
+func UpdateAcademyMaterialCompletedById(id_material uint) Repository[models.AcademyMaterial, models.AcademyMaterial] {
+ repo := Construct[models.AcademyMaterial, models.AcademyMaterial](
+ models.AcademyMaterial{ID: id_material, IsCompleted: true},
+ )
+ Update(repo)
+ return *repo
+}
+
+// func UpdateAcademyProgressById(id_academy uint)
\ No newline at end of file
diff --git a/space/space/space/space/space/space/repositories/quiz_repository.go b/space/space/space/space/space/space/repositories/quiz_repository.go
index 373de505c150035f75c3095a011ca8c7ce261905..85dac99b92d4c2306e86433397c94b3f8cf2554b 100644
--- a/space/space/space/space/space/space/repositories/quiz_repository.go
+++ b/space/space/space/space/space/space/repositories/quiz_repository.go
@@ -63,6 +63,18 @@ func GetAttemptByIdandUserId(attemptId uint, userId uint) Repository[models.Quiz
return *repo
}
+func GetAttemptByUserId(userId uint) Repository[models.QuizAttempt, []models.QuizAttempt] {
+ repo := Construct[models.QuizAttempt, []models.QuizAttempt](
+ models.QuizAttempt{
+ AccountID: userId,
+ },
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.QuizAttempt, []models.QuizAttempt],
+ Find[models.QuizAttempt, []models.QuizAttempt],
+ )
+ return *repo
+}
func CreateAttempt(quizAttempt models.QuizAttempt) Repository[models.QuizAttempt, models.QuizAttempt] {
repo := Construct[models.QuizAttempt, models.QuizAttempt](
quizAttempt,
diff --git a/space/space/space/space/space/space/router/quiz_route.go b/space/space/space/space/space/space/router/quiz_route.go
index 2d7b62e6b8f2400497024257c2ec4032769e6239..5ef8986199c33be9ab94abc28e46f880a7628ad2 100644
--- a/space/space/space/space/space/space/router/quiz_route.go
+++ b/space/space/space/space/space/space/router/quiz_route.go
@@ -13,6 +13,8 @@ func QuizRoute(router *gin.Engine) {
routerGroup.POST("/:academy_id/:quiz_id/attempt", middleware.AuthUser, QuizController.Attempt)
routerGroup.GET("/:academy_id/:quiz_id/question", middleware.AuthUser, QuizController.Question)
routerGroup.PUT("/:academy_id/:quiz_id/choose-answer", middleware.AuthUser, QuizController.Answer)
+ routerGroup.GET("result/:attempt_id", middleware.AuthUser, QuizController.Result)
+ routerGroup.GET("result/", middleware.AuthUser, QuizController.Result)
routerGroup.POST("/submit-attempt/:attempt_id", middleware.AuthUser, QuizController.Submit)
}
}
diff --git a/space/space/space/space/space/space/services/academy_quiz_service.go b/space/space/space/space/space/space/services/academy_quiz_service.go
index 15917ff81e3ecb8ece7d682a8896dfe0e8c35965..98448a782fa129baef497f9095cdded308adc5f9 100644
--- a/space/space/space/space/space/space/services/academy_quiz_service.go
+++ b/space/space/space/space/space/space/services/academy_quiz_service.go
@@ -20,6 +20,10 @@ type QuizListService struct {
Service[models.Academy, []models.Quiz]
}
+type QuizResultService struct {
+ Service[models.QuizAttempt, []models.QuizResultResponse]
+}
+
func AuthorizeQuizwithAcademy(s *AttemptQuizService, next func()) {
academyRepo := repositories.GetAcademyByID(s.Constructor.AcademyID)
s.Error = academyRepo.RowsError
@@ -130,8 +134,8 @@ func (s *AttemptQuizService) Create(userID uint) {
})
}
-func (s *SubmitQuizService) Create(userID uint) {
- quizAttemptRepo := repositories.GetAttemptByIdandUserId(s.Constructor.ID, userID)
+func (s *SubmitQuizService) Create() {
+ quizAttemptRepo := repositories.GetAttemptByIdandUserId(s.Constructor.ID, s.Constructor.AccountID)
if quizAttemptRepo.NoRecord {
s.Exception.DataNotFound = true
s.Exception.Message = "There is no quiz attempt with given user!"
@@ -139,6 +143,10 @@ func (s *SubmitQuizService) Create(userID uint) {
}
s.Error = errors.Join(s.Error, quizAttemptRepo.RowsError)
countScoreRepo := repositories.CountUserAttemptScore(s.Constructor.ID)
+ if countScoreRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no quiz attempt with given user!"
+ }
s.Error = errors.Join(s.Error, countScoreRepo.RowsError)
if quizAttemptRepo.Result.FinishedAt == nil {
@@ -168,3 +176,57 @@ func (s *QuizListService) Retrieve() {
s.Result = quizRepo.Result
return
}
+
+func (s *QuizResultService) Retrieve() {
+ if s.Constructor.ID != 0 {
+ attemptRepo := repositories.GetAttemptByIdandUserId(s.Constructor.ID, s.Constructor.AccountID)
+ s.Error = attemptRepo.RowsError
+ if attemptRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no attempt with given user id"
+ return
+ }
+ if attemptRepo.Result.FinishedAt == nil {
+ s.Exception.Forbidden = true
+ s.Exception.Message = "You have to submit the exam first to see the result!"
+ return
+ }
+ countScoreRepo := repositories.CountUserAttemptScore(s.Constructor.ID)
+ s.Error = errors.Join(s.Error, countScoreRepo.RowsError)
+ if countScoreRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no quiz attempt with given user!"
+ }
+ s.Result = []models.QuizResultResponse{
+ models.QuizResultResponse{
+ QuizAttempt: attemptRepo.Result,
+ Result: countScoreRepo.Result,
+ },
+ }
+ return
+ } else {
+ allAttemptsRepo := repositories.GetAttemptByUserId(s.Constructor.AccountID)
+ if allAttemptsRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no attempt with given user id!"
+ return
+ }
+ s.Error = allAttemptsRepo.RowsError
+ var arrResult []models.QuizResultResponse
+ for _, attempt := range allAttemptsRepo.Result {
+ if attempt.FinishedAt != nil {
+ countScoreRepo := repositories.CountUserAttemptScore(attempt.ID)
+ s.Error = errors.Join(s.Error, countScoreRepo.RowsError)
+ if countScoreRepo.NoRecord {
+ continue
+ }
+ arrResult = append(arrResult, models.QuizResultResponse{
+ QuizAttempt: attempt,
+ Result: countScoreRepo.Result,
+ })
+ }
+ }
+ s.Result = arrResult
+ return
+ }
+}
diff --git a/space/space/space/space/space/space/services/academy_service.go b/space/space/space/space/space/space/services/academy_service.go
index c3f9fcce8634493c3583c4f11f8666a6eee4a612..35eeba344bb415253f4280e42e324b697ff62e2d 100644
--- a/space/space/space/space/space/space/services/academy_service.go
+++ b/space/space/space/space/space/space/services/academy_service.go
@@ -25,6 +25,10 @@ type AcademyContentService struct {
Service[models.AcademyMaterial, models.AcademyContent]
}
+type AcademyMarkReadService struct {
+ Service[models.AcademyMaterial, models.Academy]
+}
+
func castAcademyMaterials(academyId uint) []models.AcademyMaterialResponse {
var ArrMaterials []models.AcademyMaterialResponse
academyMaterialsRepo := repositories.GetAllAcademyMaterialsByAcademyID(academyId)
@@ -135,3 +139,12 @@ func (s *AcademyContentService) Retrieve() {
}
s.Result = academyContentRepo.Result
}
+
+func (s *AcademyMarkReadService) Update() {
+ markReadRepo := repositories.UpdateAcademyMaterialCompletedById(s.Constructor.ID)
+ if markReadRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Error = markReadRepo.RowsError
+ return
+ }
+}
diff --git a/space/space/space/space/space/space/space/.env.example b/space/space/space/space/space/space/space/.env.example
index 7a13c9d5665f68dc7087f208910958eb620cc5c6..f10aa86f475f117e3341ade81463d8f8143da719 100644
--- a/space/space/space/space/space/space/space/.env.example
+++ b/space/space/space/space/space/space/space/.env.example
@@ -1,9 +1,26 @@
-DB_HOST =
-DB_USER =
-DB_PASSWORD =
-DB_PORT =
-DB_NAME =
-SALT =
-HOST_ADDRESS =
-HOST_PORT =
-LOG_PATH = logs
\ No newline at end of file
+APP_URL=https://api.qobiltu.id
+ENV=production
+
+DB_HOST=db
+DB_USER=qobiltu
+DB_PASSWORD=
+DB_PORT=
+DB_NAME=
+SALT=
+HOST_ADDRESS=
+HOST_PORT=
+LOG_PATH=logs
+
+SMTP_SENDER_EMAIL=
+SMTP_SENDER_PASSWORD=
+SMTP_HOST=
+SMTP_PORT=
+EMAIL_VERIFICATION_DURATION=
+
+REDIS_HOST=redis
+REDIS_PORT=6379
+REDIS_PASSWORD=
+REDIS_DB=0
+REDIS_MIN_IDLE_CONNS=10
+REDIS_POOL_SIZE=10
+REDIS_POOL_TIMEOUT=30s
\ No newline at end of file
diff --git a/space/space/space/space/space/space/space/space/pkg/validation/custom_rules.go b/space/space/space/space/space/space/space/space/pkg/validation/custom_rules.go
index feb4b6d372cdf74158f1a33dcd5a696a0afcbf78..a8beb66ef2af72829ac3acff5cfed8f1ba0b80be 100644
--- a/space/space/space/space/space/space/space/space/pkg/validation/custom_rules.go
+++ b/space/space/space/space/space/space/space/space/pkg/validation/custom_rules.go
@@ -32,7 +32,7 @@ var inMemoryOptions = map[string][]string{
"body_shape": {"Ideal", "Kurus", "Berisi", "Gemuk"},
"skin_color": {"Putih", "Kuning Langsat", "Sawo Matang"},
"hair_type": {"Lurus", "Bergelombang", "Keriting"},
- "frequently": {"Selalu", "Sering", "Kadang", "Jarang"},
+ "frequently": {"Selalu", "Sering", "Kadang", "Jarang", "Tidak Pernah"},
"quran_reading_ability": {"Lancar", "Menengah", "Perlu Bimbingan"},
}
diff --git a/space/space/space/space/space/space/space/space/space/services/cv_service.go b/space/space/space/space/space/space/space/space/space/services/cv_service.go
index e1b38630e73698a1ea381f8cc246717b22388f58..0208b10ddcf6e62dc3ad6c87f512097434efef0c 100644
--- a/space/space/space/space/space/space/space/space/space/services/cv_service.go
+++ b/space/space/space/space/space/space/space/space/space/services/cv_service.go
@@ -3,7 +3,6 @@ package services
import (
"context"
"errors"
- "fmt"
"math"
"strconv"
@@ -648,26 +647,25 @@ func calculateProgress(
achievementCV int,
) *models.ProgressResponse {
- exists := func(data int) float64 {
+ // fullIfPresent returns 100 if the data is greater than 0, otherwise returns 0
+ fullIfPresent := func(data int) float64 {
if data > 0 {
return 100
}
return 0
}
- fmt.Println("accountDetails", accountDetails.GetFilledFields())
- fmt.Println("accountDetailsTotal", accountDetails.TotalFields())
-
accountDetailsPercentage := float64(len(accountDetails.GetFilledFields())) / float64(accountDetails.TotalFields()) * 100
personalityAndPreferenceCVPercentage := float64(len(personalityAndPreferenceCV.GetFilledFields())) / float64(personalityAndPreferenceCV.TotalFields()) * 100
physicalAndHealthCVPercentage := float64(len(physicalAndHealthCV.GetFilledFields())) / float64(physicalAndHealthCV.TotalFields()) * 100
worshipAndReligiousUnderstandingCVPercentage := float64(len(worshipAndReligiousUnderstandingCV.GetFilledFields())) / float64(worshipAndReligiousUnderstandingCV.TotalFields()) * 100
- edicationCVPercentage := exists(educationCV)
- familyMemberCVPercentage := exists(familyMemberCV)
- jobCVPercentage := exists(jobCV)
- achievementCVPercentage := exists(achievementCV)
- overallProgress := (accountDetailsPercentage + personalityAndPreferenceCVPercentage + physicalAndHealthCVPercentage + worshipAndReligiousUnderstandingCVPercentage + edicationCVPercentage + familyMemberCVPercentage + jobCVPercentage + achievementCVPercentage) / 8
+ educationCVPercentage := fullIfPresent(educationCV)
+ familyMemberCVPercentage := fullIfPresent(familyMemberCV)
+ jobCVPercentage := fullIfPresent(jobCV)
+ achievementCVPercentage := fullIfPresent(achievementCV)
+
+ overallProgress := (accountDetailsPercentage + personalityAndPreferenceCVPercentage + physicalAndHealthCVPercentage + worshipAndReligiousUnderstandingCVPercentage + educationCVPercentage + familyMemberCVPercentage + jobCVPercentage + achievementCVPercentage) / 8
return &models.ProgressResponse{
AccountDetailsProgress: math.Round(accountDetailsPercentage*100) / 100,
@@ -675,9 +673,9 @@ func calculateProgress(
FamilyMemberCVProgress: math.Round(familyMemberCVPercentage*100) / 100,
PhysicalAndHealthCVProgress: math.Round(physicalAndHealthCVPercentage*100) / 100,
WorshipAndReligiousUnderstandingCVProgress: math.Round(worshipAndReligiousUnderstandingCVPercentage*100) / 100,
- EducationCVProgress: math.Round(edicationCVPercentage*100) / 100,
- JobCVProgress: jobCVPercentage,
- AchievementCVProgress: achievementCVPercentage,
+ EducationCVProgress: math.Round(educationCVPercentage*100) / 100,
+ JobCVProgress: math.Round(jobCVPercentage*100) / 100,
+ AchievementCVProgress: math.Round(achievementCVPercentage*100) / 100,
TotalProgress: math.Round(overallProgress*100) / 100,
}
}
diff --git a/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go b/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go
index 824fdaab60bdf60863dbf12a430f41d5caf12227..8ce30c1b260a6937e865023f58534978daed3d25 100644
--- a/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go
@@ -1,13 +1,14 @@
package cv_controller
import (
+ "net/http"
+ "strconv"
+
"api.qobiltu.id/middleware"
"api.qobiltu.id/models"
"api.qobiltu.id/response"
"api.qobiltu.id/services"
"github.com/gin-gonic/gin"
- "net/http"
- "strconv"
)
type CVController interface {
@@ -48,6 +49,7 @@ type CVController interface {
DeleteAchievement(ctx *gin.Context)
UploadProfileImage(ctx *gin.Context)
+ GetProgress(ctx *gin.Context)
}
type cvController struct {
@@ -573,3 +575,16 @@ func (c *cvController) UploadProfileImage(ctx *gin.Context) {
response.HandleSuccess(ctx, http.StatusOK, "Profile image uploaded successfully", res, nil)
}
+
+func (c *cvController) GetProgress(ctx *gin.Context) {
+ accountData := middleware.GetAccountData(ctx)
+ accountID := int64(accountData.UserID)
+
+ res, err := c.cvService.GetProgress(ctx, accountID)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Progress retrieved successfully", res, nil)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/models/field_counter.go b/space/space/space/space/space/space/space/space/space/space/models/field_counter.go
new file mode 100644
index 0000000000000000000000000000000000000000..01978a61547b1f0ef0071e86fdce34f8da80f710
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/models/field_counter.go
@@ -0,0 +1,163 @@
+package models
+
+import (
+ "reflect"
+ "time"
+)
+
+// CounterGetter adalah interface yang menyediakan method untuk menghitung dan mendapatkan field
+type CounterGetter interface {
+ TotalFields() int
+ GetFilledFields() []string
+}
+
+// FieldCounter menyediakan fungsionalitas generik penghitungan dan pengecekan field
+// untuk diembed ke dalam struct lain
+type FieldCounter struct{}
+
+// shouldSkipField menentukan apakah field harus dilewati dalam penghitungan
+func shouldSkipField(field reflect.StructField) bool {
+ // Skip field dengan nama FieldCounter (embedded type)
+ if field.Name == "FieldCounter" {
+ return true
+ }
+
+ // Skip field yang berasal dari GORM atau standard fields
+ // seperti ID, timestamps, dan foreign keys
+ if field.Name == "ID" || field.Name == "CreatedAt" || field.Name == "UpdatedAt" || field.Name == "DeletedAt" ||
+ field.Name == "AccountID" || field.Name == "Account" {
+ return true
+ }
+
+ // Periksa tag gorm untuk auto fields
+ gormTag := field.Tag.Get("gorm")
+ if len(gormTag) > 0 {
+ // Skip kolom yang auto-increment atau auto-timestamp
+ if gormTag == "primaryKey" || gormTag == "autoIncrement" ||
+ gormTag == "autoCreateTime" || gormTag == "autoUpdateTime" {
+ return true
+ }
+ }
+
+ return false
+}
+
+// TotalFields menghitung jumlah total field dalam struct
+// dengan mengecualikan field internal, metadata, dan embedded FieldCounter
+func (fc FieldCounter) TotalFields(s any) int {
+ t := reflect.TypeOf(s)
+ count := 0
+
+ // Jika s adalah pointer, dapatkan elemen yang ditunjuknya
+ if t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+
+ // Pastikan kita berurusan dengan struct
+ if t.Kind() != reflect.Struct {
+ return 0
+ }
+
+ // Hitung field yang sesuai dengan kriteria
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+
+ // Skip field yang memenuhi kriteria yang ditentukan
+ if shouldSkipField(field) {
+ continue
+ }
+
+ count++
+ }
+
+ return count
+}
+
+// GetFilledFields mengembalikan slice berisi nama field yang telah diisi dengan nilai
+// (tidak nil, string tidak kosong, int/float tidak 0, dll)
+func (fc FieldCounter) GetFilledFields(s any) []string {
+ var filledFields []string
+ v := reflect.ValueOf(s)
+
+ // Jika s adalah pointer, dapatkan elemen yang ditunjuknya
+ if v.Kind() == reflect.Ptr {
+ if v.IsNil() {
+ return filledFields
+ }
+ v = v.Elem()
+ }
+
+ // Pastikan kita berurusan dengan struct
+ if v.Kind() != reflect.Struct {
+ return filledFields
+ }
+
+ t := v.Type()
+
+ // Loop melalui semua field
+ for i := 0; i < v.NumField(); i++ {
+ field := v.Field(i)
+ fieldType := t.Field(i)
+ fieldName := fieldType.Name
+
+ // Skip field yang memenuhi kriteria yang ditentukan
+ if shouldSkipField(fieldType) {
+ continue
+ }
+
+ // Cek apakah field diisi berdasarkan tipenya
+ if isFieldFilled(field) {
+ filledFields = append(filledFields, fieldName)
+ }
+ }
+
+ return filledFields
+}
+
+// isFieldFilled memeriksa apakah field memiliki nilai non-zero
+func isFieldFilled(field reflect.Value) bool {
+ // Jika field tidak dapat di-address atau diakses, anggap kosong
+ if !field.IsValid() || !field.CanInterface() {
+ return false
+ }
+
+ // Handle untuk pointer
+ if field.Kind() == reflect.Ptr {
+ if field.IsNil() {
+ return false
+ }
+ // Untuk pointer, periksa nilai yang ditunjuk
+ return isFieldFilled(field.Elem())
+ }
+
+ // Cek berdasarkan tipe data
+ switch field.Kind() {
+ case reflect.String:
+ return field.String() != ""
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return field.Int() != 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ return field.Uint() != 0
+ case reflect.Float32, reflect.Float64:
+ return field.Float() != 0
+ case reflect.Bool:
+ return field.Bool()
+ case reflect.Slice, reflect.Map, reflect.Array:
+ return !field.IsNil() && field.Len() > 0
+ case reflect.Struct:
+ // Perlakuan khusus untuk time.Time
+ if field.Type() == reflect.TypeOf(time.Time{}) {
+ // Anggap time.Time kosong jika zero value atau tahun sangat awal
+ timeVal := field.Interface().(time.Time)
+ return !timeVal.IsZero() && timeVal.Year() > 1
+ }
+
+ // Untuk struct lain, bandingkan dengan zero value
+ return !reflect.DeepEqual(field.Interface(), reflect.Zero(field.Type()).Interface())
+ case reflect.Interface:
+ return !field.IsNil()
+ default:
+ // Untuk tipe lain, gunakan perbandingan dengan zero value
+ return !field.IsZero()
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/models/request_model.go b/space/space/space/space/space/space/space/space/space/space/models/request_model.go
index e1396f820cfbd9498156d7655a51ad42b0d045e3..a48b864c20c1365ce50c12f9e04aa67a95ffa095 100644
--- a/space/space/space/space/space/space/space/space/space/space/models/request_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/models/request_model.go
@@ -161,4 +161,16 @@ type (
UploadProfileImageResponse struct {
URL string `json:"url"`
}
+
+ ProgressResponse struct {
+ AccountDetailsProgress float64 `json:"account_details_progress"`
+ PersonalityAndPreferenceCVProgress float64 `json:"personality_and_preference_cv_progress"`
+ FamilyMemberCVProgress float64 `json:"family_member_cv_progress"`
+ PhysicalAndHealthCVProgress float64 `json:"physical_and_health_cv_progress"`
+ WorshipAndReligiousUnderstandingCVProgress float64 `json:"worship_and_religious_understanding_cv_progress"`
+ EducationCVProgress float64 `json:"education_cv_progress"`
+ JobCVProgress float64 `json:"job_cv_progress"`
+ AchievementCVProgress float64 `json:"achievement_cv_progress"`
+ TotalProgress float64 `json:"total_progress"`
+ }
)
diff --git a/space/space/space/space/space/space/space/space/space/space/router/cv_route.go b/space/space/space/space/space/space/space/space/space/space/router/cv_route.go
index 06b7a23c9d80ef9e906c46fc0de5f3ee7e9b6177..6a2ab9e4ef32a778b0a7597b4a36ba0ce0739f94 100644
--- a/space/space/space/space/space/space/space/space/space/space/router/cv_route.go
+++ b/space/space/space/space/space/space/space/space/space/space/router/cv_route.go
@@ -41,5 +41,7 @@ func (s *Server) CVRoute() {
routerGroup.GET("/achievements/:id", s.cvController.GetAchievement)
routerGroup.PUT("/achievements/:id", s.cvController.UpdateAchievement)
routerGroup.DELETE("/achievements/:id", s.cvController.DeleteAchievement)
+
+ routerGroup.GET("/progress", s.cvController.GetProgress)
}
}
diff --git a/space/space/space/space/space/space/space/space/space/space/services/cv_service.go b/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
index e47290e892830c1ed9d2d09f2126484a8250fda3..e1b38630e73698a1ea381f8cc246717b22388f58 100644
--- a/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
@@ -3,6 +3,8 @@ package services
import (
"context"
"errors"
+ "fmt"
+ "math"
"strconv"
"api.qobiltu.id/models"
@@ -51,6 +53,7 @@ type CVService interface {
DeleteAchievement(ctx context.Context, id int64) error
UploadProfileImage(ctx context.Context, req *models.UploadProfileImageRequest) (*models.UploadProfileImageResponse, error)
+ GetProgress(ctx context.Context, accountID int64) (*models.ProgressResponse, error)
}
type cvService struct {
@@ -566,3 +569,115 @@ func (s *cvService) UploadProfileImage(ctx context.Context, req *models.UploadPr
URL: url,
}, nil
}
+
+func (s *cvService) GetProgress(ctx context.Context, accountID int64) (*models.ProgressResponse, error) {
+ accountDetails, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, accountID)
+ if err != nil {
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+ accountDetails = &models.AccountDetails{}
+ }
+
+ personalityAndPreferenceCV, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, accountID)
+ if err != nil {
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+ personalityAndPreferenceCV = &models.PersonalityAndPreferenceCV{}
+ }
+
+ physicalAndHealthCV, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, accountID)
+ if err != nil {
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+ physicalAndHealthCV = &models.PhysicalAndHealthCV{}
+ }
+
+ worshipAndReligiousUnderstandingCV, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, accountID)
+ if err != nil {
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+ worshipAndReligiousUnderstandingCV = &models.WorshipAndReligiousUnderstandingCV{}
+ }
+
+ educationCV, err := s.cvRepository.ListEducation(ctx, accountID)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ familyMemberCV, err := s.cvRepository.ListFamilyMember(ctx, accountID)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ jobCV, err := s.cvRepository.ListJob(ctx, accountID)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ achievementCV, err := s.cvRepository.ListAchievement(ctx, accountID)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ progressDetails := calculateProgress(
+ accountDetails,
+ personalityAndPreferenceCV,
+ physicalAndHealthCV,
+ worshipAndReligiousUnderstandingCV,
+ len(educationCV),
+ len(familyMemberCV),
+ len(jobCV),
+ len(achievementCV),
+ )
+
+ return progressDetails, nil
+}
+
+func calculateProgress(
+ accountDetails *models.AccountDetails,
+ personalityAndPreferenceCV *models.PersonalityAndPreferenceCV,
+ physicalAndHealthCV *models.PhysicalAndHealthCV,
+ worshipAndReligiousUnderstandingCV *models.WorshipAndReligiousUnderstandingCV,
+ educationCV int,
+ familyMemberCV int,
+ jobCV int,
+ achievementCV int,
+) *models.ProgressResponse {
+
+ exists := func(data int) float64 {
+ if data > 0 {
+ return 100
+ }
+ return 0
+ }
+
+ fmt.Println("accountDetails", accountDetails.GetFilledFields())
+ fmt.Println("accountDetailsTotal", accountDetails.TotalFields())
+
+ accountDetailsPercentage := float64(len(accountDetails.GetFilledFields())) / float64(accountDetails.TotalFields()) * 100
+ personalityAndPreferenceCVPercentage := float64(len(personalityAndPreferenceCV.GetFilledFields())) / float64(personalityAndPreferenceCV.TotalFields()) * 100
+ physicalAndHealthCVPercentage := float64(len(physicalAndHealthCV.GetFilledFields())) / float64(physicalAndHealthCV.TotalFields()) * 100
+ worshipAndReligiousUnderstandingCVPercentage := float64(len(worshipAndReligiousUnderstandingCV.GetFilledFields())) / float64(worshipAndReligiousUnderstandingCV.TotalFields()) * 100
+ edicationCVPercentage := exists(educationCV)
+ familyMemberCVPercentage := exists(familyMemberCV)
+ jobCVPercentage := exists(jobCV)
+ achievementCVPercentage := exists(achievementCV)
+
+ overallProgress := (accountDetailsPercentage + personalityAndPreferenceCVPercentage + physicalAndHealthCVPercentage + worshipAndReligiousUnderstandingCVPercentage + edicationCVPercentage + familyMemberCVPercentage + jobCVPercentage + achievementCVPercentage) / 8
+
+ return &models.ProgressResponse{
+ AccountDetailsProgress: math.Round(accountDetailsPercentage*100) / 100,
+ PersonalityAndPreferenceCVProgress: math.Round(personalityAndPreferenceCVPercentage*100) / 100,
+ FamilyMemberCVProgress: math.Round(familyMemberCVPercentage*100) / 100,
+ PhysicalAndHealthCVProgress: math.Round(physicalAndHealthCVPercentage*100) / 100,
+ WorshipAndReligiousUnderstandingCVProgress: math.Round(worshipAndReligiousUnderstandingCVPercentage*100) / 100,
+ EducationCVProgress: math.Round(edicationCVPercentage*100) / 100,
+ JobCVProgress: jobCVPercentage,
+ AchievementCVProgress: achievementCVPercentage,
+ TotalProgress: math.Round(overallProgress*100) / 100,
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
index 303ee1c0d57a4494e0e00e7566bc99da5fb3ba14..90623608d4eeb80c039e39ced164b9f0d7b34b78 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -34,6 +34,7 @@ type AccountDetails struct {
PhoneNumber *string `gorm:"column:phone_number" json:"phone_number"`
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ FieldCounter
}
type EmailVerification struct {
@@ -154,15 +155,16 @@ type Question struct {
CorrectAnswer uint `json:"-"`
}
type Quiz struct {
- ID uint `gorm:"primaryKey" json:"id"`
- AcademyID uint `json:"academy_id"`
- Slug string `json:"slug" gorm:"uniqueIndex" `
- Title string `json:"title"`
- Description string `json:"description"`
- AttemptLimit int `json:"attempt_limit"`
- TimeLimit int `json:"time_limit"`
- MinScore int `json:"min_score"`
- CreatedAt time.Time `json:"created_at"`
+ ID uint `gorm:"primaryKey" json:"id"`
+ AcademyID uint `json:"academy_id"`
+ Slug string `json:"slug" gorm:"uniqueIndex" `
+ Title string `json:"title"`
+ Description string `json:"description"`
+ TotalQuestions string `json:"total_questions"`
+ AttemptLimit int `json:"attempt_limit"`
+ TimeLimit int `json:"time_limit"`
+ MinScore int `json:"min_score"`
+ CreatedAt time.Time `json:"created_at"`
}
type QuizAttempt struct {
@@ -209,6 +211,7 @@ type (
MonthlyExpenses *string `gorm:"column:monthly_expenses" json:"monthly_expenses"` // pengeluaran per bulan
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ FieldCounter
}
FamilyMemberCV struct {
@@ -223,6 +226,7 @@ type (
Age *int `gorm:"column:age" json:"age"` // Usia
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ FieldCounter
}
PhysicalAndHealthCV struct {
@@ -239,6 +243,7 @@ type (
PhysicalTraits *string `gorm:"column:physical_traits" json:"physical_traits"` // Ciri khas fisik
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // Waktu data dibuat
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // Waktu data terakhir diperbarui
+ FieldCounter
}
WorshipAndReligiousUnderstandingCV struct {
@@ -262,6 +267,7 @@ type (
FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ FieldCounter
}
EducationCV struct {
@@ -300,6 +306,38 @@ type (
}
)
+func (a AccountDetails) TotalFields() int {
+ return a.FieldCounter.TotalFields(a)
+}
+
+func (a AccountDetails) GetFilledFields() []string {
+ return a.FieldCounter.GetFilledFields(a)
+}
+
+func (p PersonalityAndPreferenceCV) TotalFields() int {
+ return p.FieldCounter.TotalFields(p)
+}
+
+func (p PersonalityAndPreferenceCV) GetFilledFields() []string {
+ return p.FieldCounter.GetFilledFields(p)
+}
+
+func (p PhysicalAndHealthCV) TotalFields() int {
+ return p.FieldCounter.TotalFields(p)
+}
+
+func (p PhysicalAndHealthCV) GetFilledFields() []string {
+ return p.FieldCounter.GetFilledFields(p)
+}
+
+func (w WorshipAndReligiousUnderstandingCV) TotalFields() int {
+ return w.FieldCounter.TotalFields(w)
+}
+
+func (w WorshipAndReligiousUnderstandingCV) GetFilledFields() []string {
+ return w.FieldCounter.GetFilledFields(w)
+}
+
// Gorm table name settings
func (Account) TableName() string { return "account" }
func (AccountDetails) TableName() string { return "account_details" }
diff --git a/space/space/space/space/space/space/space/space/space/space/space/repositories/quiz_repository.go b/space/space/space/space/space/space/space/space/space/space/space/repositories/quiz_repository.go
index f139508068134e86f341f05f9e233342f82e88a9..373de505c150035f75c3095a011ca8c7ce261905 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/repositories/quiz_repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/repositories/quiz_repository.go
@@ -49,9 +49,12 @@ func GetUserLastAttempt(userId uint, quizId uint) Repository[models.QuizAttempt,
return *repo
}
-func GetAttemptById(attemptId uint) Repository[models.QuizAttempt, models.QuizAttempt] {
+func GetAttemptByIdandUserId(attemptId uint, userId uint) Repository[models.QuizAttempt, models.QuizAttempt] {
repo := Construct[models.QuizAttempt, models.QuizAttempt](
- models.QuizAttempt{ID: attemptId},
+ models.QuizAttempt{
+ ID: attemptId,
+ AccountID: userId,
+ },
)
repo.Transactions(
WhereGivenConstructor[models.QuizAttempt, models.QuizAttempt],
diff --git a/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go b/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go
index 0e56c168ec59eea7398ec38854104ade16a10b9f..15917ff81e3ecb8ece7d682a8896dfe0e8c35965 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go
@@ -131,7 +131,7 @@ func (s *AttemptQuizService) Create(userID uint) {
}
func (s *SubmitQuizService) Create(userID uint) {
- quizAttemptRepo := repositories.GetAttemptById(s.Constructor.ID)
+ quizAttemptRepo := repositories.GetAttemptByIdandUserId(s.Constructor.ID, userID)
if quizAttemptRepo.NoRecord {
s.Exception.DataNotFound = true
s.Exception.Message = "There is no quiz attempt with given user!"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/custom_rules.go b/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/custom_rules.go
index d559c28835d184e33224c72e90001800888a893b..feb4b6d372cdf74158f1a33dcd5a696a0afcbf78 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/custom_rules.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/custom_rules.go
@@ -1,6 +1,7 @@
package validation
import (
+ "regexp"
"strings"
"sync"
@@ -150,6 +151,11 @@ func (v *Validator) RegisterAllCustomRules(validate *v10.Validate) error {
return err
}
+ err = validate.RegisterValidation("phone_number", v.PhoneNumberRule)
+ if err != nil {
+ return err
+ }
+
return nil
}
@@ -160,3 +166,39 @@ func (v *Validator) PasswordRule(fl v10.FieldLevel) bool {
strings.ContainsAny(password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") &&
strings.ContainsAny(password, "0123456789")
}
+
+func (v *Validator) PhoneNumberRule(fl v10.FieldLevel) bool {
+ phone := SanitizePhoneNumber(fl.Field().String())
+ return strings.HasPrefix(phone, "+62")
+}
+
+func SanitizePhoneNumber(input string) string {
+ // Hilangkan semua spasi dan strip
+ input = strings.ReplaceAll(input, " ", "")
+ input = strings.ReplaceAll(input, "-", "")
+ input = strings.ReplaceAll(input, "(", "")
+ input = strings.ReplaceAll(input, ")", "")
+
+ // Hilangkan semua karakter non-digit kecuali +
+ re := regexp.MustCompile(`[^0-9\+]`)
+ input = re.ReplaceAllString(input, "")
+
+ // Handle nomor diawali 0 (contoh: 0812...) menjadi +62812...
+ if strings.HasPrefix(input, "0") {
+ input = "+62" + input[1:]
+ }
+
+ // Handle jika diawali dengan 62 tanpa + (contoh: 62812...)
+ if strings.HasPrefix(input, "62") && !strings.HasPrefix(input, "+62") {
+ input = "+" + input
+ }
+
+ // Handle jika tidak ada awalan +62 sama sekali (contoh: 8123456789)
+ if !strings.HasPrefix(input, "+62") {
+ if strings.HasPrefix(input, "8") {
+ input = "+62" + input
+ }
+ }
+
+ return input
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/validation.go b/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/validation.go
index 8e579ea390fd5e3a12e255367accb92310f88411..fc44b56a157f42bf9e7032cf3abafd4fee97aea7 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/validation.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/validation.go
@@ -79,6 +79,7 @@ func setupValidations(validate *v10.Validate) error {
if err := rules.RegisterAllCustomRules(validate); err != nil {
return err
}
+
return nil
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/Makefile b/space/space/space/space/space/space/space/space/space/space/space/space/space/Makefile
index eaea5509230b5b4c1aecc6a0f015eef921d7a5d7..01ba0fde6a2ac601c3736c98cd63a60b10c38704 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/Makefile
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/Makefile
@@ -1,7 +1,15 @@
up-dev:
docker compose -f docker-compose.dev.yml up -d
+down-dev:
+ docker compose -f docker-compose.dev.yml down
+
+up:
+ docker compose up -d --build
+down:
+ docker compose down
+
run-dev:
go run main.go
-.PHONY : up-dev run-dev
+.PHONY : up-dev run-dev down-dev up down
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
index a7076106e74ca435298b12ec61e0ca8fc0ca4b11..c092793f5a71f36d5717eddbf4be7b58ba3bf55f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
@@ -29,6 +29,8 @@ var REDIS_MIN_IDLE_CONNS int
var REDIS_POOL_SIZE int
var REDIS_POOL_TIMEOUT time.Duration
+var APP_URL string
+
func init() {
godotenv.Load()
ENV = os.Getenv("ENV")
@@ -49,6 +51,7 @@ func init() {
REDIS_POOL_SIZE = getValue(os.Getenv("REDIS_POOL_SIZE"), 10, func(s string) (int, error) { return strconv.Atoi(s) })
REDIS_MIN_IDLE_CONNS = getValue(os.Getenv("REDIS_MIN_IDLE_CONNS"), 10, func(s string) (int, error) { return strconv.Atoi(s) })
REDIS_POOL_TIMEOUT = getValue(os.Getenv("REDIS_POOL_TIMEOUT"), time.Second*30, func(s string) (time.Duration, error) { return time.ParseDuration(s) })
+ APP_URL = getValue(os.Getenv("APP_URL"), "http://localhost:3000", func(s string) (string, error) { return s, nil })
}
func getValue[T any](value string, defaultValue T, convert func(string) (T, error)) T {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go
index 3efeb5c4763ed90e6ea6becfff1833c42d9a10e8..824fdaab60bdf60863dbf12a430f41d5caf12227 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go
@@ -46,6 +46,8 @@ type CVController interface {
ListAchievement(ctx *gin.Context)
GetAchievement(ctx *gin.Context)
DeleteAchievement(ctx *gin.Context)
+
+ UploadProfileImage(ctx *gin.Context)
}
type cvController struct {
@@ -553,3 +555,21 @@ func (c *cvController) DeleteAchievement(ctx *gin.Context) {
response.HandleSuccess(ctx, http.StatusOK, "Achievement deleted successfully", nil, nil)
}
+
+func (c *cvController) UploadProfileImage(ctx *gin.Context) {
+ accountData := middleware.GetAccountData(ctx)
+
+ avatarFile, _ := ctx.FormFile("avatar")
+ req := models.UploadProfileImageRequest{
+ AccountID: int64(accountData.UserID),
+ File: avatarFile,
+ }
+
+ res, err := c.cvService.UploadProfileImage(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Profile image uploaded successfully", res, nil)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml b/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml
index c9493fabb6148385e056c035008355f5cc7f0b77..41ea841b3f0345c7235cf3dc08f0df140879ad0a 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml
@@ -6,14 +6,15 @@ services:
build: .
depends_on:
- db
+ - redis
env_file: .env
ports:
- "8080:8080"
+ volumes:
+ - ./logs:/app/logs
+ - ./uploads:/app/uploads
networks:
- qobiltu-network
- # volumes:
- # - ./logs:/app/logs
- # - /home/qobiltu/api-qobiltu:/app
restart: unless-stopped
db:
@@ -45,10 +46,6 @@ services:
- qobiltu-network
restart: always
-volumes:
- db-data:
- redis-data:
-
networks:
qobiltu-network:
driver: bridge
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod b/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
index 3d26451156d4c811028f96036e214f6aa11d6f9f..1c65a870c8fb2135ba6d7dbfc723102793098816 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
@@ -8,6 +8,7 @@ require (
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.25.0
github.com/golang-jwt/jwt/v5 v5.2.1
+ github.com/google/uuid v1.6.0
github.com/gosimple/slug v1.15.0
github.com/hibiken/asynq v0.25.1
github.com/joho/godotenv v1.5.1
@@ -37,7 +38,6 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/s2a-go v0.1.9 // indirect
- github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
index 9dccb05f32ab004da9268a08cb093984986c8ccb..6c931622855c83028e6a2fc50fda1fdd54525139 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
@@ -2,15 +2,16 @@ package main
import (
"api.qobiltu.id/config"
- cv_controller "api.qobiltu.id/controller/cv"
+ "api.qobiltu.id/controller/cv"
"api.qobiltu.id/controller/health_check"
"api.qobiltu.id/mail"
+ "api.qobiltu.id/pkg/storage"
+ "api.qobiltu.id/pkg/validation"
+ "api.qobiltu.id/pkg/worker"
"api.qobiltu.id/repositories"
"api.qobiltu.id/router"
"api.qobiltu.id/services"
"api.qobiltu.id/utils"
- "api.qobiltu.id/validation"
- "api.qobiltu.id/worker"
"github.com/hibiken/asynq"
"log/slog"
"net"
@@ -23,6 +24,9 @@ func main() {
err := validation.New(validation.LocaleID)
utils.FatalIfErr("failed to setup validator", err)
+ // setup storage
+ localStorage := storage.NewLocalStorage("uploads", config.APP_URL+"/storage/")
+
// setup email sender
emailConfig := mail.Config{
Host: config.SMTP_HOST,
@@ -53,7 +57,7 @@ func main() {
healthCheckController := health_check_controller.NewHealthCheckController(healthCheckService)
cvRepository := repositories.NewCVRepository(config.DB)
- cvService := services.NewCVService(cvRepository)
+ cvService := services.NewCVService(cvRepository, localStorage)
cvController := cv_controller.NewCVController(cvService)
// start task processor
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
index d819b9a4881ea1bb166856b66693b748e26ef241..48496246a4234441191ad2032c68a5fb451ad86f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
@@ -1,6 +1,8 @@
package models
-import "api.qobiltu.id/validation"
+import (
+ "api.qobiltu.id/pkg/validation"
+)
type Exception struct {
Unauthorized bool `json:"unauthorized,omitempty"`
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
index ee2d0aebd7759d95df5c3392947e2c38139dfb61..e1396f820cfbd9498156d7655a51ad42b0d045e3 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
@@ -1,8 +1,10 @@
package models
import (
- "github.com/lib/pq"
+ "mime/multipart"
"time"
+
+ "github.com/lib/pq"
)
type LoginRequest struct {
@@ -106,6 +108,7 @@ type (
MaritalStatus *string `json:"marital_status" validate:"marital_status"`
LastEducation *string `json:"last_education" validate:"last_education"`
LastJob *string `json:"last_job"`
+ PhoneNumber *string `json:"phone_number" validate:"phone_number"`
}
WorshipAndReligiousUnderstandingRequest struct {
@@ -149,4 +152,13 @@ type (
AccountID int64 `json:"account_id"`
AchievementOrAward *string `json:"achievement_or_award"` // prestasi atau penghargaan
}
+
+ UploadProfileImageRequest struct {
+ AccountID int64
+ File *multipart.FileHeader
+ }
+
+ UploadProfileImageResponse struct {
+ URL string `json:"url"`
+ }
)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/storage/local.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/storage/local.go
new file mode 100644
index 0000000000000000000000000000000000000000..86c95e803932412fad74f5f3bf4b30b0d604e8d9
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/storage/local.go
@@ -0,0 +1,67 @@
+package storage
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+type LocalStorage struct {
+ *BaseStorage
+ basePath string
+ baseURL string
+}
+
+func NewLocalStorage(basePath, baseURL string) *LocalStorage {
+ return &LocalStorage{
+ BaseStorage: NewBaseStorage(),
+ basePath: basePath,
+ baseURL: baseURL,
+ }
+}
+
+func (s *LocalStorage) Upload(ctx context.Context, file io.ReadSeeker, path string) error {
+ fullPath := filepath.Join(s.basePath, path)
+
+ dir := filepath.Dir(fullPath)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return err
+ }
+
+ dst, err := os.Create(fullPath)
+ if err != nil {
+ return err
+ }
+ defer dst.Close()
+
+ if _, err := io.Copy(dst, file); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (s *LocalStorage) GetURL(path string) string {
+ return filepath.Join(s.baseURL, path)
+}
+
+func (s *LocalStorage) Delete(ctx context.Context, path string) error {
+ // Contoh input: "http://localhost:8080/storage/users/5/profile/filename.png"
+
+ // Ambil bagian setelah "/storage/"
+ const prefix = "/storage/"
+ idx := strings.Index(path, prefix)
+ if idx == -1 {
+ return os.ErrNotExist
+ }
+
+ relativePath := path[idx+len(prefix):]
+
+ // Gabungkan dengan basePath
+ fullPath := filepath.Join(s.basePath, relativePath)
+ fmt.Println(fullPath)
+
+ return os.Remove(fullPath)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/storage/storage.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/storage/storage.go
new file mode 100644
index 0000000000000000000000000000000000000000..40615038ab546d818c380633503a10562a9920f5
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/storage/storage.go
@@ -0,0 +1,71 @@
+package storage
+
+import (
+ "context"
+ "errors"
+ "github.com/google/uuid"
+ "io"
+ "path/filepath"
+)
+
+type FileType int
+
+const (
+ ProfileImage FileType = iota
+)
+
+var (
+ ErrInvalidFileType = errors.New("invalid file type")
+ ErrFileTooLarge = errors.New("file too large")
+ ErrNoFileUploaded = errors.New("no file uploaded")
+)
+
+type Storage interface {
+ Upload(ctx context.Context, file io.ReadSeeker, path string) error
+ GetURL(path string) string
+ Delete(ctx context.Context, path string) error
+
+ ValidateExtension(fileType FileType, filename string) bool
+ GenerateFilename(originalName string) string
+ GetPath(fileType FileType, identifier string, filename string) string
+}
+
+var (
+ _ Storage = (*LocalStorage)(nil)
+)
+
+type BaseStorage struct {
+ allowedExtensions map[FileType][]string
+}
+
+func NewBaseStorage() *BaseStorage {
+ return &BaseStorage{
+ allowedExtensions: map[FileType][]string{
+ ProfileImage: {".jpg", ".jpeg", ".png", ".webp"},
+ },
+ }
+}
+
+func (bs *BaseStorage) ValidateExtension(fileType FileType, filename string) bool {
+ ext := filepath.Ext(filename)
+ for _, allowed := range bs.allowedExtensions[fileType] {
+ if ext == allowed {
+ return true
+ }
+ }
+ return false
+}
+
+func (bs *BaseStorage) GenerateFilename(originalName string) string {
+ ext := filepath.Ext(originalName)
+ return uuid.New().String() + ext
+}
+
+func (bs *BaseStorage) GetPath(fileType FileType, identifier string, filename string) string {
+ switch fileType {
+ case ProfileImage:
+ return filepath.Join("users", identifier, "profile", filename)
+ default:
+ return filepath.Join("misc", filename)
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/response/api_response_v2.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/response/api_response_v2.go
index 3cfda1d94cee9b2e64e0f10acc461ef47c7e1283..931f86bab6f5a69a68dc1ed10b5cde3fbfaec48c 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/response/api_response_v2.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/response/api_response_v2.go
@@ -2,8 +2,8 @@ package response
import (
"api.qobiltu.id/models"
+ "api.qobiltu.id/pkg/validation"
"api.qobiltu.id/utils"
- "api.qobiltu.id/validation"
"errors"
"net/http"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/response/validation.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/response/validation.go
index e147ba60284d833987433c020a8ed3745bd5ab44..b20af0d1de80eacf6185c2e1989d8b010c374c33 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/response/validation.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/response/validation.go
@@ -2,7 +2,7 @@ package response
import (
"api.qobiltu.id/models"
- "api.qobiltu.id/validation"
+ "api.qobiltu.id/pkg/validation"
)
func HandleValidationError(validationErrors []validation.ErrorMessage) error {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/router/cv_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/router/cv_route.go
index 4a8164d910db1b69dea62b75373703c0563cecce..06b7a23c9d80ef9e906c46fc0de5f3ee7e9b6177 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/router/cv_route.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/router/cv_route.go
@@ -7,6 +7,7 @@ func (s *Server) CVRoute() {
{
routerGroup.POST("/account-details", s.cvController.SaveAccountDetails)
routerGroup.GET("/account-details", s.cvController.GetAccountDetails)
+ routerGroup.POST("/account-details/avatar", s.cvController.UploadProfileImage)
routerGroup.POST("/personality-and-preferences", s.cvController.SavePersonalityAndPreference)
routerGroup.GET("/personality-and-preferences", s.cvController.GetPersonalityAndPreference)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
index 97ce1f39c35e67d4fbae4f0a0c1b6eb5a0bbe795..8b647f502ab36d3165e4dfedf4b3e62039827a25 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
@@ -18,4 +18,5 @@ func (s *Server) setupRoutes() {
// another way to register routes
s.HealthCheckRoute()
s.CVRoute()
+ s.StorageRoute()
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/router/storage_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/router/storage_route.go
new file mode 100644
index 0000000000000000000000000000000000000000..e6f16e985be3ba05e0a7d2fa8abec287b3eac778
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/router/storage_route.go
@@ -0,0 +1,24 @@
+package router
+
+import (
+ "api.qobiltu.id/middleware"
+ "github.com/gin-gonic/gin"
+ "net/http"
+ "strings"
+)
+
+func (s *Server) StorageRoute() {
+ s.router.GET("/storage/*filepath", func(ctx *gin.Context) {
+ filepath := ctx.Param("filepath")
+
+ // Prevent directory listing
+ if filepath == "" || strings.HasSuffix(filepath, "/") {
+ ctx.AbortWithStatus(http.StatusForbidden)
+ return
+ }
+
+ fs := http.Dir("uploads")
+
+ ctx.FileFromFS(filepath, fs)
+ }).Use(middleware.AuthUser)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
index 8cf914d6a0fae5872c22d846b3d88257e32bd9ca..e47290e892830c1ed9d2d09f2126484a8250fda3 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
@@ -1,485 +1,568 @@
package services
import (
- "api.qobiltu.id/models"
- "api.qobiltu.id/repositories"
- "api.qobiltu.id/response"
- "api.qobiltu.id/validation"
- "context"
- "errors"
- "gorm.io/gorm"
+ "context"
+ "errors"
+ "strconv"
+
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/pkg/storage"
+ "api.qobiltu.id/pkg/validation"
+ "api.qobiltu.id/repositories"
+ "api.qobiltu.id/response"
+ "gorm.io/gorm"
)
type CVService interface {
- SaveAccountDetails(ctx context.Context, req *models.AccountDetailsRequest) (*models.AccountDetails, error)
- GetAccountDetails(ctx context.Context, id int64) (*models.AccountDetails, error)
-
- SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCVRequest) (*models.PersonalityAndPreferenceCV, error)
- GetPersonalityAndPreference(ctx context.Context, id int64) (*models.PersonalityAndPreferenceCV, error)
-
- CreateFamilyMember(ctx context.Context, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
- UpdateFamilyMember(ctx context.Context, id int64, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
- ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error)
- GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error)
- DeleteFamilyMember(ctx context.Context, id int64) error
-
- SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error)
- GetPhysicalAndHealth(ctx context.Context, id int64) (*models.PhysicalAndHealthCV, error)
-
- SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error)
- GetWorshipAndReligiousUnderstanding(ctx context.Context, id int64) (*models.WorshipAndReligiousUnderstandingCV, error)
-
- CreateEducation(ctx context.Context, req *models.EducationRequest) (*models.EducationCV, error)
- UpdateEducation(ctx context.Context, id int64, req *models.EducationRequest) (*models.EducationCV, error)
- ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error)
- GetEducation(ctx context.Context, id int64) (*models.EducationCV, error)
- DeleteEducation(ctx context.Context, id int64) error
-
- CreateJob(ctx context.Context, req *models.JobRequest) (*models.JobCV, error)
- UpdateJob(ctx context.Context, id int64, req *models.JobRequest) (*models.JobCV, error)
- ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error)
- GetJob(ctx context.Context, id int64) (*models.JobCV, error)
- DeleteJob(ctx context.Context, id int64) error
-
- CreateAchievement(ctx context.Context, req *models.AchievementRequest) (*models.AchievementCV, error)
- UpdateAchievement(ctx context.Context, id int64, req *models.AchievementRequest) (*models.AchievementCV, error)
- ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error)
- GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error)
- DeleteAchievement(ctx context.Context, id int64) error
+ SaveAccountDetails(ctx context.Context, req *models.AccountDetailsRequest) (*models.AccountDetails, error)
+ GetAccountDetails(ctx context.Context, id int64) (*models.AccountDetails, error)
+
+ SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCVRequest) (*models.PersonalityAndPreferenceCV, error)
+ GetPersonalityAndPreference(ctx context.Context, id int64) (*models.PersonalityAndPreferenceCV, error)
+
+ CreateFamilyMember(ctx context.Context, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
+ UpdateFamilyMember(ctx context.Context, id int64, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
+ ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error)
+ GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error)
+ DeleteFamilyMember(ctx context.Context, id int64) error
+
+ SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error)
+ GetPhysicalAndHealth(ctx context.Context, id int64) (*models.PhysicalAndHealthCV, error)
+
+ SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error)
+ GetWorshipAndReligiousUnderstanding(ctx context.Context, id int64) (*models.WorshipAndReligiousUnderstandingCV, error)
+
+ CreateEducation(ctx context.Context, req *models.EducationRequest) (*models.EducationCV, error)
+ UpdateEducation(ctx context.Context, id int64, req *models.EducationRequest) (*models.EducationCV, error)
+ ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error)
+ GetEducation(ctx context.Context, id int64) (*models.EducationCV, error)
+ DeleteEducation(ctx context.Context, id int64) error
+
+ CreateJob(ctx context.Context, req *models.JobRequest) (*models.JobCV, error)
+ UpdateJob(ctx context.Context, id int64, req *models.JobRequest) (*models.JobCV, error)
+ ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error)
+ GetJob(ctx context.Context, id int64) (*models.JobCV, error)
+ DeleteJob(ctx context.Context, id int64) error
+
+ CreateAchievement(ctx context.Context, req *models.AchievementRequest) (*models.AchievementCV, error)
+ UpdateAchievement(ctx context.Context, id int64, req *models.AchievementRequest) (*models.AchievementCV, error)
+ ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error)
+ GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error)
+ DeleteAchievement(ctx context.Context, id int64) error
+
+ UploadProfileImage(ctx context.Context, req *models.UploadProfileImageRequest) (*models.UploadProfileImageResponse, error)
}
type cvService struct {
- cvRepository repositories.CVRepository
+ cvRepository repositories.CVRepository
+ storage storage.Storage
}
-func NewCVService(cvRepository repositories.CVRepository) CVService {
- return &cvService{
- cvRepository: cvRepository,
- }
+func NewCVService(cvRepository repositories.CVRepository, storage storage.Storage) CVService {
+ return &cvService{
+ cvRepository: cvRepository,
+ storage: storage,
+ }
}
func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.AccountDetailsRequest) (*models.AccountDetails, error) {
- if err := validation.Validate(req); err != nil {
- return nil, response.HandleValidationError(err)
- }
-
- // Ambil data lama jika ada
- accountDetails, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, req.AccountID)
- if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, response.HandleGormError(err, "Internal Server Error")
- }
-
- // Apply perubahan
- if accountDetails == nil {
- accountDetails = &models.AccountDetails{}
- }
-
- accountDetails.AccountID = uint(req.AccountID)
- accountDetails.FullName = req.FullName
- accountDetails.Gender = req.Gender
- accountDetails.DateOfBirth = req.DateOfBirth
- accountDetails.PlaceOfBirth = req.PlaceOfBirth
- accountDetails.Domicile = req.Domicile
- accountDetails.MaritalStatus = req.MaritalStatus
- accountDetails.LastEducation = req.LastEducation
- accountDetails.LastJob = req.LastJob
-
- // Simpan data
- res, err := s.cvRepository.SaveAccountDetails(ctx, accountDetails)
- if err != nil {
- return nil, response.HandleGormError(err, "Internal Server Error")
- }
-
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ // Ambil data lama jika ada
+ accountDetails, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ // Apply perubahan
+ if accountDetails == nil {
+ accountDetails = &models.AccountDetails{}
+ }
+
+ accountDetails.AccountID = uint(req.AccountID)
+ accountDetails.FullName = req.FullName
+ accountDetails.Gender = req.Gender
+ accountDetails.DateOfBirth = req.DateOfBirth
+ accountDetails.PlaceOfBirth = req.PlaceOfBirth
+ accountDetails.Domicile = req.Domicile
+ accountDetails.MaritalStatus = req.MaritalStatus
+ accountDetails.LastEducation = req.LastEducation
+ accountDetails.LastJob = req.LastJob
+
+ if req.PhoneNumber != nil {
+ sanitizedPhone := validation.SanitizePhoneNumber(*req.PhoneNumber)
+ accountDetails.PhoneNumber = &sanitizedPhone
+ }
+
+ // Simpan data
+ res, err := s.cvRepository.SaveAccountDetails(ctx, accountDetails)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return res, nil
}
func (s *cvService) GetAccountDetails(ctx context.Context, id int64) (*models.AccountDetails, error) {
- res, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data diri tidak ditemukan")
- }
- return res, nil
+ res, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data diri tidak ditemukan")
+ }
+ return res, nil
}
func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCVRequest) (*models.PersonalityAndPreferenceCV, error) {
- if err := validation.Validate(req); err != nil {
- return nil, response.HandleValidationError(err)
- }
-
- // Ambil data lama jika ada
- personalityAndPreference, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, req.AccountID)
- if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, response.HandleGormError(err, "Internal Server Error")
- }
-
- // Apply perubahan
- if personalityAndPreference == nil {
- personalityAndPreference = &models.PersonalityAndPreferenceCV{}
- }
-
- personalityAndPreference.AccountID = req.AccountID
- personalityAndPreference.PositiveTraits = req.PositiveTraits
- personalityAndPreference.NegativeTraits = req.NegativeTraits
- personalityAndPreference.Hobbies = req.Hobbies
- personalityAndPreference.LifeGoals = req.LifeGoals
- personalityAndPreference.DailyActivities = req.DailyActivities
- personalityAndPreference.LeisureActivities = req.LeisureActivities
- personalityAndPreference.Likes = req.Likes
- personalityAndPreference.Dislikes = req.Dislikes
- personalityAndPreference.StressHandling = req.StressHandling
- personalityAndPreference.AngerTriggers = req.AngerTriggers
- personalityAndPreference.FavoriteFoodAndDrinks = req.FavoriteFoodAndDrinks
- personalityAndPreference.CanCook = req.CanCook
- personalityAndPreference.TypesOfDishesCooked = req.TypesOfDishesCooked
- personalityAndPreference.MonthlyExpenses = req.MonthlyExpenses
-
- res, err := s.cvRepository.SavePersonalityAndPreference(ctx, personalityAndPreference)
- if err != nil {
- return nil, response.HandleGormError(err, "Internal Server Error")
- }
-
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ // Ambil data lama jika ada
+ personalityAndPreference, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ // Apply perubahan
+ if personalityAndPreference == nil {
+ personalityAndPreference = &models.PersonalityAndPreferenceCV{}
+ }
+
+ personalityAndPreference.AccountID = req.AccountID
+ personalityAndPreference.PositiveTraits = req.PositiveTraits
+ personalityAndPreference.NegativeTraits = req.NegativeTraits
+ personalityAndPreference.Hobbies = req.Hobbies
+ personalityAndPreference.LifeGoals = req.LifeGoals
+ personalityAndPreference.DailyActivities = req.DailyActivities
+ personalityAndPreference.LeisureActivities = req.LeisureActivities
+ personalityAndPreference.Likes = req.Likes
+ personalityAndPreference.Dislikes = req.Dislikes
+ personalityAndPreference.StressHandling = req.StressHandling
+ personalityAndPreference.AngerTriggers = req.AngerTriggers
+ personalityAndPreference.FavoriteFoodAndDrinks = req.FavoriteFoodAndDrinks
+ personalityAndPreference.CanCook = req.CanCook
+ personalityAndPreference.TypesOfDishesCooked = req.TypesOfDishesCooked
+ personalityAndPreference.MonthlyExpenses = req.MonthlyExpenses
+
+ res, err := s.cvRepository.SavePersonalityAndPreference(ctx, personalityAndPreference)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return res, nil
}
func (s *cvService) GetPersonalityAndPreference(ctx context.Context, id int64) (*models.PersonalityAndPreferenceCV, error) {
- res, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Internal Server Error")
- }
+ res, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
- return res, nil
+ return res, nil
}
func (s *cvService) CreateFamilyMember(ctx context.Context, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error) {
- if err := validation.Validate(req); err != nil {
- return nil, response.HandleValidationError(err)
- }
-
- // Mapping request ke model
- familyMember := &models.FamilyMemberCV{
- AccountID: req.AccountID,
- Role: req.Role,
- Status: req.Status,
- Religion: req.Religion,
- Job: req.Job,
- LastEducation: req.LastEducation,
- Age: req.Age,
- }
-
- // Simpan ke repository
- res, err := s.cvRepository.SaveFamilyMember(ctx, familyMember)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal menyimpan anggota keluarga")
- }
-
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ // Mapping request ke model
+ familyMember := &models.FamilyMemberCV{
+ AccountID: req.AccountID,
+ Role: req.Role,
+ Status: req.Status,
+ Religion: req.Religion,
+ Job: req.Job,
+ LastEducation: req.LastEducation,
+ Age: req.Age,
+ }
+
+ // Simpan ke repository
+ res, err := s.cvRepository.SaveFamilyMember(ctx, familyMember)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menyimpan anggota keluarga")
+ }
+
+ return res, nil
}
func (s *cvService) ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error) {
- list, err := s.cvRepository.ListFamilyMember(ctx, accountID)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal mengambil daftar anggota keluarga")
- }
- return list, nil
+ list, err := s.cvRepository.ListFamilyMember(ctx, accountID)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal mengambil daftar anggota keluarga")
+ }
+ return list, nil
}
func (s *cvService) GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error) {
- res, err := s.cvRepository.GetFamilyMember(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
- }
- return res, nil
+ res, err := s.cvRepository.GetFamilyMember(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
+ }
+ return res, nil
}
func (s *cvService) DeleteFamilyMember(ctx context.Context, id int64) error {
- err := s.cvRepository.DeleteFamilyMember(ctx, id)
- if err != nil {
- return response.HandleGormError(err, "Gagal menghapus anggota keluarga")
- }
- return nil
+ err := s.cvRepository.DeleteFamilyMember(ctx, id)
+ if err != nil {
+ return response.HandleGormError(err, "Gagal menghapus anggota keluarga")
+ }
+ return nil
}
func (s *cvService) UpdateFamilyMember(ctx context.Context, id int64, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error) {
- if err := validation.Validate(req); err != nil {
- return nil, response.HandleValidationError(err)
- }
-
- existing, err := s.cvRepository.GetFamilyMember(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
- }
-
- existing.Role = req.Role
- existing.Status = req.Status
- existing.Religion = req.Religion
- existing.Job = req.Job
- existing.LastEducation = req.LastEducation
- existing.Age = req.Age
-
- updated, err := s.cvRepository.SaveFamilyMember(ctx, existing)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal memperbarui anggota keluarga")
- }
-
- return updated, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ existing, err := s.cvRepository.GetFamilyMember(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
+ }
+
+ existing.Role = req.Role
+ existing.Status = req.Status
+ existing.Religion = req.Religion
+ existing.Job = req.Job
+ existing.LastEducation = req.LastEducation
+ existing.Age = req.Age
+
+ updated, err := s.cvRepository.SaveFamilyMember(ctx, existing)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal memperbarui anggota keluarga")
+ }
+
+ return updated, nil
}
func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error) {
- if err := validation.Validate(req); err != nil {
- return nil, response.HandleValidationError(err)
- }
-
- // Cek apakah data sudah ada berdasarkan account_id
- existing, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, req.AccountID)
- if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data fisik dan kesehatan")
- }
-
- // Jika belum ada, buat objek baru
- if existing == nil {
- existing = &models.PhysicalAndHealthCV{}
- }
-
- // Mapping field dari request
- existing.AccountID = req.AccountID
- existing.HeightInCm = req.HeightInCm
- existing.WeightInKg = req.WeightInKg
- existing.BodyShape = req.BodyShape
- existing.SkinColor = req.SkinColor
- existing.HairType = req.HairType
- existing.MedicalHistory = req.MedicalHistory
- existing.PhysicalDisorder = req.PhysicalDisorder
- existing.PhysicalTraits = req.PhysicalTraits
-
- // Simpan data
- res, err := s.cvRepository.SavePhysicalAndHealth(ctx, existing)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal menyimpan data fisik dan kesehatan")
- }
-
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ // Cek apakah data sudah ada berdasarkan account_id
+ existing, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data fisik dan kesehatan")
+ }
+
+ // Jika belum ada, buat objek baru
+ if existing == nil {
+ existing = &models.PhysicalAndHealthCV{}
+ }
+
+ // Mapping field dari request
+ existing.AccountID = req.AccountID
+ existing.HeightInCm = req.HeightInCm
+ existing.WeightInKg = req.WeightInKg
+ existing.BodyShape = req.BodyShape
+ existing.SkinColor = req.SkinColor
+ existing.HairType = req.HairType
+ existing.MedicalHistory = req.MedicalHistory
+ existing.PhysicalDisorder = req.PhysicalDisorder
+ existing.PhysicalTraits = req.PhysicalTraits
+
+ // Simpan data
+ res, err := s.cvRepository.SavePhysicalAndHealth(ctx, existing)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menyimpan data fisik dan kesehatan")
+ }
+
+ return res, nil
}
func (s *cvService) GetPhysicalAndHealth(ctx context.Context, id int64) (*models.PhysicalAndHealthCV, error) {
- res, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data fisik dan kesehatan tidak ditemukan")
- }
- return res, nil
+ res, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data fisik dan kesehatan tidak ditemukan")
+ }
+ return res, nil
}
func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error) {
- if err := validation.Validate(req); err != nil {
- return nil, response.HandleValidationError(err)
- }
-
- // Cek apakah data sudah ada berdasarkan account_id
- worshipAndReligiousUnderstanding, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, req.AccountID)
- if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data agama dan pemahaman agama")
- }
-
- // Jika belum ada, buat objek baru
- if worshipAndReligiousUnderstanding == nil {
- worshipAndReligiousUnderstanding = &models.WorshipAndReligiousUnderstandingCV{}
- }
-
- // Mapping field dari request
- worshipAndReligiousUnderstanding.AccountID = req.AccountID
- worshipAndReligiousUnderstanding.ObligatoryPrayer = req.ObligatoryPrayer
- worshipAndReligiousUnderstanding.CongregationalPrayer = req.CongregationalPrayer
- worshipAndReligiousUnderstanding.TahajjudPrayer = req.TahajjudPrayer
- worshipAndReligiousUnderstanding.DhuhaPrayer = req.DhuhaPrayer
- worshipAndReligiousUnderstanding.QuranMemorization = req.QuranMemorization
- worshipAndReligiousUnderstanding.QuranReadingAbility = req.QuranReadingAbility
- worshipAndReligiousUnderstanding.DaudFasting = req.DaudFasting
- worshipAndReligiousUnderstanding.AyyamulBidhFasting = req.AyyamulBidhFasting
- worshipAndReligiousUnderstanding.HajjOrUmrah = req.HajjOrUmrah
- worshipAndReligiousUnderstanding.ListeningToMusic = req.ListeningToMusic
- worshipAndReligiousUnderstanding.OpinionOnIkhtilat = req.OpinionOnIkhtilat
- worshipAndReligiousUnderstanding.OpinionOnTouchingNonMahram = req.OpinionOnTouchingNonMahram
- worshipAndReligiousUnderstanding.OpinionOnVeil = req.OpinionOnVeil
- worshipAndReligiousUnderstanding.WeeklyReligiousStudies = req.WeeklyReligiousStudies
- worshipAndReligiousUnderstanding.FollowedUstadz = req.FollowedUstadz
-
- // Simpan data
- res, err := s.cvRepository.SaveWorshipAndReligiousUnderstanding(ctx, worshipAndReligiousUnderstanding)
- if err != nil {
- return nil, response.HandleGormError(err, "Internal Server Error")
- }
-
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ // Cek apakah data sudah ada berdasarkan account_id
+ worshipAndReligiousUnderstanding, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data agama dan pemahaman agama")
+ }
+
+ // Jika belum ada, buat objek baru
+ if worshipAndReligiousUnderstanding == nil {
+ worshipAndReligiousUnderstanding = &models.WorshipAndReligiousUnderstandingCV{}
+ }
+
+ // Mapping field dari request
+ worshipAndReligiousUnderstanding.AccountID = req.AccountID
+ worshipAndReligiousUnderstanding.ObligatoryPrayer = req.ObligatoryPrayer
+ worshipAndReligiousUnderstanding.CongregationalPrayer = req.CongregationalPrayer
+ worshipAndReligiousUnderstanding.TahajjudPrayer = req.TahajjudPrayer
+ worshipAndReligiousUnderstanding.DhuhaPrayer = req.DhuhaPrayer
+ worshipAndReligiousUnderstanding.QuranMemorization = req.QuranMemorization
+ worshipAndReligiousUnderstanding.QuranReadingAbility = req.QuranReadingAbility
+ worshipAndReligiousUnderstanding.DaudFasting = req.DaudFasting
+ worshipAndReligiousUnderstanding.AyyamulBidhFasting = req.AyyamulBidhFasting
+ worshipAndReligiousUnderstanding.HajjOrUmrah = req.HajjOrUmrah
+ worshipAndReligiousUnderstanding.ListeningToMusic = req.ListeningToMusic
+ worshipAndReligiousUnderstanding.OpinionOnIkhtilat = req.OpinionOnIkhtilat
+ worshipAndReligiousUnderstanding.OpinionOnTouchingNonMahram = req.OpinionOnTouchingNonMahram
+ worshipAndReligiousUnderstanding.OpinionOnVeil = req.OpinionOnVeil
+ worshipAndReligiousUnderstanding.WeeklyReligiousStudies = req.WeeklyReligiousStudies
+ worshipAndReligiousUnderstanding.FollowedUstadz = req.FollowedUstadz
+
+ // Simpan data
+ res, err := s.cvRepository.SaveWorshipAndReligiousUnderstanding(ctx, worshipAndReligiousUnderstanding)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return res, nil
}
func (s *cvService) GetWorshipAndReligiousUnderstanding(ctx context.Context, id int64) (*models.WorshipAndReligiousUnderstandingCV, error) {
- res, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data agama dan pemahaman agama tidak ditemukan")
- }
- return res, nil
+ res, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data agama dan pemahaman agama tidak ditemukan")
+ }
+ return res, nil
}
func (s *cvService) CreateEducation(ctx context.Context, req *models.EducationRequest) (*models.EducationCV, error) {
- if err := validation.Validate(req); err != nil {
- return nil, response.HandleValidationError(err)
- }
-
- edu := &models.EducationCV{
- AccountID: req.AccountID,
- LastEducation: req.LastEducation,
- EducationInstitute: req.EducationInstitute,
- EducationMajor: req.EducationMajor,
- YearStart: req.YearStart,
- YearGraduate: req.YearGraduate,
- }
-
- res, err := s.cvRepository.SaveEducation(ctx, edu)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal menambahkan data pendidikan")
- }
-
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ edu := &models.EducationCV{
+ AccountID: req.AccountID,
+ LastEducation: req.LastEducation,
+ EducationInstitute: req.EducationInstitute,
+ EducationMajor: req.EducationMajor,
+ YearStart: req.YearStart,
+ YearGraduate: req.YearGraduate,
+ }
+
+ res, err := s.cvRepository.SaveEducation(ctx, edu)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menambahkan data pendidikan")
+ }
+
+ return res, nil
}
func (s *cvService) UpdateEducation(ctx context.Context, id int64, req *models.EducationRequest) (*models.EducationCV, error) {
- if err := validation.Validate(req); err != nil {
- return nil, response.HandleValidationError(err)
- }
-
- edu, err := s.cvRepository.GetEducation(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
- }
-
- edu.LastEducation = req.LastEducation
- edu.EducationInstitute = req.EducationInstitute
- edu.EducationMajor = req.EducationMajor
- edu.YearStart = req.YearStart
- edu.YearGraduate = req.YearGraduate
-
- res, err := s.cvRepository.SaveEducation(ctx, edu)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal memperbarui data pendidikan")
- }
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ edu, err := s.cvRepository.GetEducation(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
+ }
+
+ edu.LastEducation = req.LastEducation
+ edu.EducationInstitute = req.EducationInstitute
+ edu.EducationMajor = req.EducationMajor
+ edu.YearStart = req.YearStart
+ edu.YearGraduate = req.YearGraduate
+
+ res, err := s.cvRepository.SaveEducation(ctx, edu)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal memperbarui data pendidikan")
+ }
+ return res, nil
}
func (s *cvService) ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error) {
- return s.cvRepository.ListEducation(ctx, accountID)
+ return s.cvRepository.ListEducation(ctx, accountID)
}
func (s *cvService) GetEducation(ctx context.Context, id int64) (*models.EducationCV, error) {
- edu, err := s.cvRepository.GetEducation(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
- }
- return edu, nil
+ edu, err := s.cvRepository.GetEducation(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
+ }
+ return edu, nil
}
func (s *cvService) DeleteEducation(ctx context.Context, id int64) error {
- return s.cvRepository.DeleteEducation(ctx, id)
+ return s.cvRepository.DeleteEducation(ctx, id)
}
func (s *cvService) CreateJob(ctx context.Context, req *models.JobRequest) (*models.JobCV, error) {
- if err := validation.Validate(req); err != nil {
- return nil, response.HandleValidationError(err)
- }
-
- job := &models.JobCV{
- AccountID: req.AccountID,
- InstitutionName: req.InstitutionName,
- CurrentJob: req.CurrentJob,
- YearStartedWorking: req.YearStartedWorking,
- MonthlyIncome: req.MonthlyIncome,
- IncomeSources: req.IncomeSources,
- }
- res, err := s.cvRepository.SaveJob(ctx, job)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal menambahkan data pekerjaan")
- }
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ job := &models.JobCV{
+ AccountID: req.AccountID,
+ InstitutionName: req.InstitutionName,
+ CurrentJob: req.CurrentJob,
+ YearStartedWorking: req.YearStartedWorking,
+ MonthlyIncome: req.MonthlyIncome,
+ IncomeSources: req.IncomeSources,
+ }
+ res, err := s.cvRepository.SaveJob(ctx, job)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menambahkan data pekerjaan")
+ }
+ return res, nil
}
func (s *cvService) UpdateJob(ctx context.Context, id int64, req *models.JobRequest) (*models.JobCV, error) {
- if err := validation.Validate(req); err != nil {
- return nil, response.HandleValidationError(err)
- }
-
- job, err := s.cvRepository.GetJob(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
- }
-
- job.InstitutionName = req.InstitutionName
- job.CurrentJob = req.CurrentJob
- job.YearStartedWorking = req.YearStartedWorking
- job.MonthlyIncome = req.MonthlyIncome
- job.IncomeSources = req.IncomeSources
-
- res, err := s.cvRepository.SaveJob(ctx, job)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal memperbarui data pekerjaan")
- }
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ job, err := s.cvRepository.GetJob(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
+ }
+
+ job.InstitutionName = req.InstitutionName
+ job.CurrentJob = req.CurrentJob
+ job.YearStartedWorking = req.YearStartedWorking
+ job.MonthlyIncome = req.MonthlyIncome
+ job.IncomeSources = req.IncomeSources
+
+ res, err := s.cvRepository.SaveJob(ctx, job)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal memperbarui data pekerjaan")
+ }
+ return res, nil
}
func (s *cvService) ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error) {
- return s.cvRepository.ListJob(ctx, accountID)
+ return s.cvRepository.ListJob(ctx, accountID)
}
func (s *cvService) GetJob(ctx context.Context, id int64) (*models.JobCV, error) {
- job, err := s.cvRepository.GetJob(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
- }
- return job, nil
+ job, err := s.cvRepository.GetJob(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
+ }
+ return job, nil
}
func (s *cvService) DeleteJob(ctx context.Context, id int64) error {
- return s.cvRepository.DeleteJob(ctx, id)
+ return s.cvRepository.DeleteJob(ctx, id)
}
func (s *cvService) CreateAchievement(ctx context.Context, req *models.AchievementRequest) (*models.AchievementCV, error) {
- ach := &models.AchievementCV{
- AccountID: req.AccountID,
- AchievementOrAward: req.AchievementOrAward,
- }
- res, err := s.cvRepository.SaveAchievement(ctx, ach)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal menambahkan data prestasi")
- }
-
- return res, nil
+ ach := &models.AchievementCV{
+ AccountID: req.AccountID,
+ AchievementOrAward: req.AchievementOrAward,
+ }
+ res, err := s.cvRepository.SaveAchievement(ctx, ach)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menambahkan data prestasi")
+ }
+
+ return res, nil
}
func (s *cvService) UpdateAchievement(ctx context.Context, id int64, req *models.AchievementRequest) (*models.AchievementCV, error) {
- ach, err := s.cvRepository.GetAchievement(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
- }
+ ach, err := s.cvRepository.GetAchievement(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
+ }
- ach.AchievementOrAward = req.AchievementOrAward
+ ach.AchievementOrAward = req.AchievementOrAward
- res, err := s.cvRepository.SaveAchievement(ctx, ach)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal memperbarui data prestasi")
- }
+ res, err := s.cvRepository.SaveAchievement(ctx, ach)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal memperbarui data prestasi")
+ }
- return res, nil
+ return res, nil
}
func (s *cvService) ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error) {
- return s.cvRepository.ListAchievement(ctx, accountID)
+ return s.cvRepository.ListAchievement(ctx, accountID)
}
func (s *cvService) GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error) {
- ach, err := s.cvRepository.GetAchievement(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
- }
- return ach, nil
+ ach, err := s.cvRepository.GetAchievement(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
+ }
+ return ach, nil
}
func (s *cvService) DeleteAchievement(ctx context.Context, id int64) error {
- return s.cvRepository.DeleteAchievement(ctx, id)
+ return s.cvRepository.DeleteAchievement(ctx, id)
+}
+
+func (s *cvService) UploadProfileImage(ctx context.Context, req *models.UploadProfileImageRequest) (*models.UploadProfileImageResponse, error) {
+ if req.File == nil {
+ return nil, models.Exception{
+ BadRequest: true,
+ Message: "No file uploaded",
+ Err: storage.ErrNoFileUploaded,
+ }
+ }
+
+ var maxFileSize int64 = 5 << 20 // 5MB
+
+ if req.File.Size > maxFileSize {
+ return nil, models.Exception{
+ BadRequest: true,
+ Message: "File too large, max size is 5MB",
+ Err: storage.ErrFileTooLarge,
+ }
+ }
+
+ if !s.storage.ValidateExtension(storage.ProfileImage, req.File.Filename) {
+ return nil, models.Exception{
+ BadRequest: true,
+ Message: "Invalid file extension",
+ Err: storage.ErrInvalidFileType,
+ }
+ }
+
+ accountDetails, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, req.AccountID)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ filename := s.storage.GenerateFilename(req.File.Filename)
+ path := s.storage.GetPath(storage.ProfileImage, strconv.Itoa(int(req.AccountID)), filename)
+
+ file, err := req.File.Open()
+ if err != nil {
+ return nil, models.Exception{
+ InternalServerError: true,
+ Message: "Internal Server Error",
+ Err: err,
+ }
+ }
+ defer file.Close()
+
+ if err := s.storage.Upload(ctx, file, path); err != nil {
+ return nil, models.Exception{
+ InternalServerError: true,
+ Message: "Internal Server Error",
+ Err: err,
+ }
+ }
+
+ // remove old avatar
+ if accountDetails.Avatar != nil {
+ s.storage.Delete(ctx, *accountDetails.Avatar)
+ }
+
+ url := s.storage.GetURL(path)
+ accountDetails.Avatar = &url
+
+ _, err = s.cvRepository.SaveAccountDetails(ctx, accountDetails)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return &models.UploadProfileImageResponse{
+ URL: url,
+ }, nil
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
index cbce83a534b6a792d37ab60b578dca8151e24843..15b18f58b96e9b6da84b7a1b43a0b85a14abcca8 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
@@ -1,101 +1,101 @@
package services
import (
- "api.qobiltu.id/utils"
- "api.qobiltu.id/worker"
- "context"
- "github.com/hibiken/asynq"
- "strconv"
- "time"
+ "api.qobiltu.id/pkg/worker"
+ "api.qobiltu.id/utils"
+ "context"
+ "github.com/hibiken/asynq"
+ "strconv"
+ "time"
- "api.qobiltu.id/config"
- "api.qobiltu.id/models"
- "api.qobiltu.id/repositories"
- uuid "github.com/satori/go.uuid"
+ "api.qobiltu.id/config"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+ uuid "github.com/satori/go.uuid"
)
type EmailVerificationService struct {
- Service[models.EmailVerification, models.EmailVerification]
+ Service[models.EmailVerification, models.EmailVerification]
}
func (s *EmailVerificationService) Create() {
- accountRepo := repositories.GetAccountById(s.Constructor.AccountID)
- if accountRepo.NoRecord {
- s.Error = accountRepo.RowsError
- s.Exception.DataNotFound = true
- s.Exception.Message = "There is no account data with given credentials!"
- return
- }
+ accountRepo := repositories.GetAccountById(s.Constructor.AccountID)
+ if accountRepo.NoRecord {
+ s.Error = accountRepo.RowsError
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account data with given credentials!"
+ return
+ }
- token, err := utils.GenerateToken()
- if err != nil {
- s.Error = err
- s.Exception.InternalServerError = true
- s.Exception.Message = "failed to generate token for email verification"
- return
- }
+ token, err := utils.GenerateToken()
+ if err != nil {
+ s.Error = err
+ s.Exception.InternalServerError = true
+ s.Exception.Message = "failed to generate token for email verification"
+ return
+ }
- remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
- dueTime := CalculateDueTime(remainingTime)
- s.Constructor.UUID = uuid.NewV4()
- repo := repositories.CreateEmailVerification(s.Constructor.UUID, s.Constructor.AccountID, dueTime, uint(token))
- s.Error = repo.RowsError
- s.Result = repo.Result
- if s.Error != nil {
- return
- }
+ remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
+ dueTime := CalculateDueTime(remainingTime)
+ s.Constructor.UUID = uuid.NewV4()
+ repo := repositories.CreateEmailVerification(s.Constructor.UUID, s.Constructor.AccountID, dueTime, uint(token))
+ s.Error = repo.RowsError
+ s.Result = repo.Result
+ if s.Error != nil {
+ return
+ }
- err = worker.AsyncTaskDistributor.DistributeTaskSendVerifyEmail(
- context.Background(),
- &worker.PayloadSendVerifyEmail{
- EmailAddress: accountRepo.Result.Email,
- VerificationCode: strconv.Itoa(int(token)),
- ExpirationInMinutes: int(remainingTime.Minutes()),
- Subject: worker.TaskSendVerifyEmailSubject,
- },
- []asynq.Option{
- asynq.MaxRetry(worker.TaskSendVerifyEmailMaxRetry),
- asynq.Queue(worker.Critical),
- }...)
- if err != nil {
- s.Error = err
- s.Exception.InternalServerError = true
- s.Exception.Message = "failed to send email verification"
- return
- }
+ err = worker.AsyncTaskDistributor.DistributeTaskSendVerifyEmail(
+ context.Background(),
+ &worker.PayloadSendVerifyEmail{
+ EmailAddress: accountRepo.Result.Email,
+ VerificationCode: strconv.Itoa(int(token)),
+ ExpirationInMinutes: int(remainingTime.Minutes()),
+ Subject: worker.TaskSendVerifyEmailSubject,
+ },
+ []asynq.Option{
+ asynq.MaxRetry(worker.TaskSendVerifyEmailMaxRetry),
+ asynq.Queue(worker.Critical),
+ }...)
+ if err != nil {
+ s.Error = err
+ s.Exception.InternalServerError = true
+ s.Exception.Message = "failed to send email verification"
+ return
+ }
}
func (s *EmailVerificationService) Validate() {
- repo := repositories.GetEmailVerification(s.Constructor.AccountID, s.Constructor.Token)
- s.Error = repo.RowsError
+ repo := repositories.GetEmailVerification(s.Constructor.AccountID, s.Constructor.Token)
+ s.Error = repo.RowsError
- if repo.NoRecord {
- s.Exception.DataNotFound = true
- s.Exception.Message = "Invalid token!"
- return
- }
+ if repo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "Invalid token!"
+ return
+ }
- if repo.Result.ExpiredAt.Before(time.Now()) {
- s.Exception.Unauthorized = true
- s.Exception.Message = "Token has expired!"
- repositories.UpdateExpiredEmailVerification(s.Constructor.UUID)
- s.Delete()
- return
- }
- account := repositories.GetAccountById(repo.Result.AccountID)
- account.Result.IsEmailVerified = true
+ if repo.Result.ExpiredAt.Before(time.Now()) {
+ s.Exception.Unauthorized = true
+ s.Exception.Message = "Token has expired!"
+ repositories.UpdateExpiredEmailVerification(s.Constructor.UUID)
+ s.Delete()
+ return
+ }
+ account := repositories.GetAccountById(repo.Result.AccountID)
+ account.Result.IsEmailVerified = true
- repositories.UpdateAccount(account.Result)
- s.Result = repo.Result
+ repositories.UpdateAccount(account.Result)
+ s.Result = repo.Result
}
func (s *EmailVerificationService) Delete() {
- repo := repositories.DeleteEmailVerification(s.Constructor.Token)
- s.Error = repo.RowsError
- if repo.NoRecord {
- s.Exception.DataNotFound = true
- s.Exception.Message = "Invalid token!"
- return
- }
- s.Result = repo.Result
+ repo := repositories.DeleteEmailVerification(s.Constructor.Token)
+ s.Error = repo.RowsError
+ if repo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "Invalid token!"
+ return
+ }
+ s.Result = repo.Result
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
index 52e73f86566cd775be6af04b2364d9ec744bd777..5cd530f29111ae309a78d2e55b5468a801e0a6b9 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
@@ -1,8 +1,8 @@
package services
import (
+ "api.qobiltu.id/pkg/worker"
"api.qobiltu.id/utils"
- "api.qobiltu.id/worker"
"context"
"github.com/hibiken/asynq"
"strconv"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
index ee8a7c397f4d8ff8f198ea32f16976e47afecbc8..10a99f10d4e2b3824fa5da4e8a0ad860a4d4c9f9 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
@@ -7,3 +7,4 @@ README.md
logs/
.idea
my-notes
+uploads
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/custom_rules.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/custom_rules.go
new file mode 100644
index 0000000000000000000000000000000000000000..d559c28835d184e33224c72e90001800888a893b
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/custom_rules.go
@@ -0,0 +1,162 @@
+package validation
+
+import (
+ "strings"
+ "sync"
+
+ v10 "github.com/go-playground/validator/v10"
+ "gorm.io/gorm"
+)
+
+type ValidOptionSource interface {
+ GetValidOptions(key string) ([]string, error)
+ GetValidKeys() []string
+}
+
+// --------------------
+// InMemoryOptionSource
+// --------------------
+
+type InMemoryOptionSource struct{}
+
+var inMemoryOptions = map[string][]string{
+ "last_education": {"SD", "SMP", "SMA", "D1", "D2", "D3", "D4", "D5", "S1", "S2", "S3"},
+ "marital_status": {"Belum Menikah", "Duda", "Janda"},
+ "gender": {"Laki-laki", "Perempuan"},
+ "monthly_expenses": {"< 2 Juta", "2-5 Juta", "5-20 Juta", "> 10 Juta"},
+ "monthly_income": {"< 3 Juta", "3-5 Juta", "5-10 Juta", "> 10 Juta"},
+ "religion": {"Islam", "Non-Islam"},
+ "family_role": {"Ayah", "Ibu", "Kakak", "Adik", "Anak"},
+ "life_status": {"Hidup", "Wafat"},
+ "body_shape": {"Ideal", "Kurus", "Berisi", "Gemuk"},
+ "skin_color": {"Putih", "Kuning Langsat", "Sawo Matang"},
+ "hair_type": {"Lurus", "Bergelombang", "Keriting"},
+ "frequently": {"Selalu", "Sering", "Kadang", "Jarang"},
+ "quran_reading_ability": {"Lancar", "Menengah", "Perlu Bimbingan"},
+}
+
+func (s *InMemoryOptionSource) GetValidOptions(key string) ([]string, error) {
+ return inMemoryOptions[key], nil
+}
+
+func (s *InMemoryOptionSource) GetValidKeys() []string {
+ keys := make([]string, 0, len(inMemoryOptions))
+ for k := range inMemoryOptions {
+ keys = append(keys, k)
+ }
+ return keys
+}
+
+// --------------------
+// DBOptionSource
+// --------------------
+
+type DBOptionSource struct {
+ options map[string][]string
+ mu sync.RWMutex
+}
+
+type (
+ OptionCategory struct {
+ ID int64 `gorm:"primaryKey" json:"id"`
+ OptionName string `json:"option_name"`
+ OptionSlug string `json:"option_slug" gorm:"uniqueIndex"`
+ }
+
+ OptionValues struct {
+ ID int64 `gorm:"primaryKey" json:"id"`
+ OptionCategoryID int64 `json:"option_category_id"`
+ OptionValue string `json:"option_value"`
+ }
+)
+
+func NewDBOptionSource(db *gorm.DB) (*DBOptionSource, error) {
+ var categories []OptionCategory
+ if err := db.Find(&categories).Error; err != nil {
+ return nil, err
+ }
+
+ options := make(map[string][]string)
+ for _, cat := range categories {
+ var values []OptionValues
+ if err := db.Where("option_category_id = ?", cat.ID).Find(&values).Error; err != nil {
+ return nil, err
+ }
+ for _, val := range values {
+ options[cat.OptionSlug] = append(options[cat.OptionSlug], val.OptionValue)
+ }
+ }
+
+ return &DBOptionSource{options: options}, nil
+}
+
+func (s *DBOptionSource) GetValidOptions(key string) ([]string, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ return s.options[key], nil
+}
+
+func (s *DBOptionSource) GetValidKeys() []string {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ keys := make([]string, 0, len(s.options))
+ for k := range s.options {
+ keys = append(keys, k)
+ }
+ return keys
+}
+
+// --------------------
+// Validator
+// --------------------
+
+type Validator struct {
+ source ValidOptionSource
+}
+
+func NewValidatorRules(source ValidOptionSource) *Validator {
+ return &Validator{source: source}
+}
+
+func (v *Validator) GenericOptionRule(key string) func(fl v10.FieldLevel) bool {
+ return func(fl v10.FieldLevel) bool {
+ value := fl.Field().String()
+ if value == "" {
+ return true
+ }
+ validOptions, err := v.source.GetValidOptions(key)
+ if err != nil {
+ return false
+ }
+ for _, opt := range validOptions {
+ if opt == value {
+ return true
+ }
+ }
+ return false
+ }
+}
+
+func (v *Validator) RegisterAllCustomRules(validate *v10.Validate) error {
+ for _, key := range v.source.GetValidKeys() {
+ err := validate.RegisterValidation(key, v.GenericOptionRule(key))
+ if err != nil {
+ return err
+ }
+ }
+
+ err := validate.RegisterValidation("password", v.PasswordRule)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (v *Validator) PasswordRule(fl v10.FieldLevel) bool {
+ password := fl.Field().String()
+ return len(password) >= 8 &&
+ strings.ContainsAny(password, "abcdefghijklmnopqrstuvwxyz") &&
+ strings.ContainsAny(password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") &&
+ strings.ContainsAny(password, "0123456789")
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/validation.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/validation.go
new file mode 100644
index 0000000000000000000000000000000000000000..8e579ea390fd5e3a12e255367accb92310f88411
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/validation/validation.go
@@ -0,0 +1,164 @@
+package validation
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/go-playground/locales/en"
+ "github.com/go-playground/locales/id"
+ ut "github.com/go-playground/universal-translator"
+ v10 "github.com/go-playground/validator/v10"
+ entranslations "github.com/go-playground/validator/v10/translations/en"
+ idtranslations "github.com/go-playground/validator/v10/translations/id"
+)
+
+// Constants for supported locales
+const (
+ LocaleID = "id"
+ LocaleEN = "en"
+)
+
+// ErrorMessage represents a validation error message
+type ErrorMessage struct {
+ Field string `json:"field"`
+ Message string `json:"-"`
+}
+
+type validator struct {
+ validate *v10.Validate
+ translator ut.Translator
+}
+
+// validatorInstance adalah instance global dari validator.
+var validatorInstance *validator
+
+// New creates a new validation instance with the specified locale
+// dan menginisialisasi instance global validatorInstance.
+func New(locale string) error {
+ v := &validator{}
+ parsedLocale := parseLocale(locale)
+
+ uni := ut.New(en.New(), id.New(), en.New())
+ translator, found := uni.GetTranslator(parsedLocale)
+ if !found {
+ return fmt.Errorf("translator not found for locale: %s", parsedLocale)
+ }
+
+ validate := v10.New()
+
+ if err := setupValidations(validate); err != nil {
+ return fmt.Errorf("failed to setup validations: %w", err)
+ }
+
+ if err := setupTranslations(validate, translator, parsedLocale); err != nil {
+ return fmt.Errorf("failed to setup translations for locale %s: %w", parsedLocale, err)
+ }
+
+ v.validate = validate
+ v.translator = translator
+
+ validatorInstance = v // Inisialisasi instance global
+ return nil
+}
+
+func parseLocale(locale string) string {
+ switch strings.ToLower(locale) {
+ case "id":
+ return LocaleID
+ case "en":
+ return LocaleEN
+ default:
+ return LocaleID // Default to Indonesian
+ }
+}
+
+// setupValidations configures custom validation rules.
+func setupValidations(validate *v10.Validate) error {
+ rules := NewValidatorRules(&InMemoryOptionSource{})
+ if err := rules.RegisterAllCustomRules(validate); err != nil {
+ return err
+ }
+ return nil
+}
+
+// setupTranslations configures translations for validation messages.
+func setupTranslations(validate *v10.Validate, translator ut.Translator, locale string) error {
+ // Register default translations based on locale
+ if err := registerDefaultTranslations(validate, translator, locale); err != nil {
+ return fmt.Errorf("failed to register default translations for locale %s: %w", locale, err)
+ }
+
+ // Register custom password validation translation
+ err := validate.RegisterTranslation("password", translator,
+ func(ut ut.Translator) error {
+ return ut.Add("password", "harus mengandung minimal 8 karakter, huruf besar, huruf kecil, dan angka.", true)
+ },
+ func(ut ut.Translator, fe v10.FieldError) string {
+ translated, err := ut.T(fe.Tag(), fe.Field())
+ if err != nil {
+ return fe.Field() + " is invalid"
+ }
+ return translated
+ },
+ )
+ if err != nil {
+ return fmt.Errorf("failed to register password translation: %w", err)
+ }
+
+ return nil
+}
+
+// registerDefaultTranslations sets up default translations for the specified locale.
+func registerDefaultTranslations(validate *v10.Validate, translator ut.Translator, locale string) error {
+ switch locale {
+ case LocaleID:
+ return idtranslations.RegisterDefaultTranslations(validate, translator)
+ case LocaleEN:
+ return entranslations.RegisterDefaultTranslations(validate, translator)
+ default:
+ // Fallback to English if the locale is not supported
+ return entranslations.RegisterDefaultTranslations(validate, translator)
+ }
+}
+
+// Validate validates a struct using the global validator instance
+// and returns a slice of ErrorMessage.
+func Validate(s any) []ErrorMessage {
+ if validatorInstance == nil {
+ return []ErrorMessage{{Field: "", Message: "Validator belum diinisialisasi. Panggil validation.New() terlebih dahulu."}}
+ }
+ err := validatorInstance.validate.Struct(s)
+ if err != nil {
+ return TranslateError(err)
+ }
+ return nil
+}
+
+// TranslateError takes a validation error and translates it using the global translator.
+func TranslateError(err error) []ErrorMessage {
+ if validatorInstance == nil {
+ return nil
+ }
+
+ var validationErrors v10.ValidationErrors
+ if !errors.As(err, &validationErrors) {
+ return nil
+ }
+
+ errorMessages := make([]ErrorMessage, 0, len(validationErrors))
+ for _, e := range validationErrors {
+ fieldLabel := e.Field()
+ msg, err := validatorInstance.translator.T(e.Tag(), fieldLabel)
+ if err != nil {
+ msg = fieldLabel + " is Invalid"
+ }
+
+ errorMessages = append(errorMessages, ErrorMessage{
+ Field: e.Tag(),
+ Message: msg,
+ })
+ }
+
+ return errorMessages
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/distributor.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/distributor.go
new file mode 100644
index 0000000000000000000000000000000000000000..32d48fae2110e1b001f5ae5ac39697c4263ad2a3
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/distributor.go
@@ -0,0 +1,23 @@
+package worker
+
+import (
+ "context"
+ "github.com/hibiken/asynq"
+)
+
+type TaskDistributor interface {
+ DistributeTaskSendVerifyEmail(
+ ctx context.Context,
+ payload *PayloadSendVerifyEmail,
+ opts ...asynq.Option,
+ ) error
+
+ DistributeTaskSendForgotPasswordEmail(
+ ctx context.Context,
+ payload *PayloadSendForgotPasswordEmail,
+ opts ...asynq.Option,
+ ) error
+}
+
+// AsyncTaskDistributor is a global variable to hold the task distributor
+var AsyncTaskDistributor TaskDistributor
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/logger.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/logger.go
new file mode 100644
index 0000000000000000000000000000000000000000..464c4525aff31d30c3913cfca6c136899eb7badf
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/logger.go
@@ -0,0 +1,37 @@
+package worker
+
+import (
+ "context"
+ "fmt"
+ "log/slog"
+)
+
+type Logger struct{}
+
+func NewLogger() *Logger {
+ return &Logger{}
+}
+
+func (logger *Logger) Printf(ctx context.Context, format string, v ...interface{}) {
+ slog.Info(fmt.Sprintf(format, v...))
+}
+
+func (logger *Logger) Debug(args ...interface{}) {
+ slog.Debug(fmt.Sprint(args...))
+}
+
+func (logger *Logger) Info(args ...interface{}) {
+ slog.Info(fmt.Sprint(args...))
+}
+
+func (logger *Logger) Warn(args ...interface{}) {
+ slog.Warn(fmt.Sprint(args...))
+}
+
+func (logger *Logger) Error(args ...interface{}) {
+ slog.Error(fmt.Sprint(args...))
+}
+
+func (logger *Logger) Fatal(args ...interface{}) {
+ slog.Error(fmt.Sprint(args...))
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/processor.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/processor.go
new file mode 100644
index 0000000000000000000000000000000000000000..dc3880bbfb667336e3e33c3ac539df1940894cf7
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/processor.go
@@ -0,0 +1,66 @@
+package worker
+
+import (
+ "api.qobiltu.id/mail"
+ "context"
+ "github.com/hibiken/asynq"
+ "github.com/redis/go-redis/v9"
+ "log/slog"
+)
+
+const (
+ Low = "low"
+ Default = "default"
+ Critical = "critical"
+)
+
+type TaskProcessor interface {
+ Start() error
+ Shutdown()
+ ProcessTaskSendVerifyEmail(ctx context.Context, task *asynq.Task) error
+ ProcessTaskSendForgotPasswordEmail(ctx context.Context, task *asynq.Task) error
+}
+
+type RedisTaskProcessor struct {
+ server *asynq.Server
+ emailSender mail.Sender
+}
+
+func NewRedisTaskProcessor(redisOpt asynq.RedisClientOpt, emailSender mail.Sender) TaskProcessor {
+ logger := NewLogger()
+ redis.SetLogger(logger)
+
+ server := asynq.NewServer(
+ redisOpt,
+ asynq.Config{
+ // priority value. Keys are the names of the queues and values are associated priority value.
+ Queues: map[string]int{
+ Critical: 6,
+ Default: 3,
+ Low: 1,
+ },
+ ErrorHandler: asynq.ErrorHandlerFunc(func(ctx context.Context, task *asynq.Task, err error) {
+ slog.Error("process task failed", "error", err, "type", task.Type(), "payload", string(task.Payload()))
+ }),
+ // maximum number of concurrent processing of tasks.
+ Concurrency: 50,
+ Logger: logger,
+ },
+ )
+
+ return &RedisTaskProcessor{
+ server: server,
+ emailSender: emailSender,
+ }
+}
+
+func (p *RedisTaskProcessor) Start() error {
+ mux := asynq.NewServeMux()
+ mux.HandleFunc(TaskSendVerifyEmail, p.ProcessTaskSendVerifyEmail)
+ mux.HandleFunc(TaskSendForgotPasswordEmail, p.ProcessTaskSendForgotPasswordEmail)
+ return p.server.Start(mux)
+}
+
+func (p *RedisTaskProcessor) Shutdown() {
+ p.server.Shutdown()
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/redis_distributor.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/redis_distributor.go
new file mode 100644
index 0000000000000000000000000000000000000000..96f30783c7d97bfa4824f656e2e7f805161888ac
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/redis_distributor.go
@@ -0,0 +1,16 @@
+package worker
+
+import (
+ "github.com/hibiken/asynq"
+)
+
+type RedisTaskDistributor struct {
+ client *asynq.Client
+}
+
+func NewRedisTaskDistributor(redisOpt asynq.RedisClientOpt) TaskDistributor {
+ client := asynq.NewClient(redisOpt)
+ return &RedisTaskDistributor{
+ client: client,
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/task_send_forgot_password_email.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/task_send_forgot_password_email.go
new file mode 100644
index 0000000000000000000000000000000000000000..8675c2b7b005df053a3660f8b42ec6cd2bc5722c
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/task_send_forgot_password_email.go
@@ -0,0 +1,75 @@
+package worker
+
+import (
+ "api.qobiltu.id/assets"
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "github.com/hibiken/asynq"
+ "html/template"
+ "log/slog"
+)
+
+const (
+ TaskSendForgotPasswordEmailMaxRetry = 5
+ TaskSendForgotPasswordEmail = "task:send_forgot_password_email"
+ TaskSendForgotPasswordEmailSubject = "Permintaan Reset Password"
+)
+
+type PayloadSendForgotPasswordEmail struct {
+ EmailAddress string `json:"email_address"`
+ ResetToken string `json:"reset_token"`
+ ExpirationInMinutes int `json:"expiration_in_minutes"`
+ Subject string `json:"subject"`
+ AppName string `json:"app_name"`
+}
+
+func (d *RedisTaskDistributor) DistributeTaskSendForgotPasswordEmail(
+ ctx context.Context,
+ payload *PayloadSendForgotPasswordEmail,
+ opts ...asynq.Option,
+) error {
+ jsonPayload, err := json.Marshal(payload)
+ if err != nil {
+ return fmt.Errorf("failed to marshal task payload: %w", err)
+ }
+
+ task := asynq.NewTask(TaskSendForgotPasswordEmail, jsonPayload, opts...)
+
+ _, err = d.client.EnqueueContext(ctx, task)
+ if err != nil {
+ return fmt.Errorf("failed to enqueue task: %w", err)
+ }
+
+ return nil
+}
+
+func (p *RedisTaskProcessor) ProcessTaskSendForgotPasswordEmail(ctx context.Context, task *asynq.Task) error {
+ var payload PayloadSendForgotPasswordEmail
+ if err := json.Unmarshal(task.Payload(), &payload); err != nil {
+ return fmt.Errorf("failed to unmarshal payload: %w", asynq.SkipRetry)
+ }
+
+ var tmpl *template.Template
+ tmpl, err := template.ParseFS(assets.EmbeddedFiles, assets.EmailForgotPasswordTemplatePath)
+ if err != nil {
+ return fmt.Errorf("failed to parse forgot password email template: %w", err)
+ }
+ var body bytes.Buffer
+ if err := tmpl.ExecuteTemplate(&body, "htmlBody", payload); err != nil {
+ return fmt.Errorf("failed to execute forgot password email template: %w", err)
+ }
+ htmlContent := body.String()
+
+ slog.Info("Sending forgot password email", slog.String("email", payload.EmailAddress))
+
+ err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
+ if err != nil {
+ return fmt.Errorf("failed to send forgot password email: %w", err)
+ }
+
+ slog.Info("Forgot password email sent successfully", slog.String("email", payload.EmailAddress))
+
+ return nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/task_send_verify_email.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/task_send_verify_email.go
new file mode 100644
index 0000000000000000000000000000000000000000..9f6fd14a6a279def7ca9036bea59175f731f91ef
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/pkg/worker/task_send_verify_email.go
@@ -0,0 +1,75 @@
+package worker
+
+import (
+ "api.qobiltu.id/assets"
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "github.com/hibiken/asynq"
+ "html/template"
+ "log/slog"
+)
+
+const (
+ TaskSendVerifyEmailMaxRetry = 3
+ TaskSendVerifyEmail = "task:send_verify_email"
+ TaskSendVerifyEmailSubject = "Verifikasi Email"
+)
+
+type PayloadSendVerifyEmail struct {
+ EmailAddress string `json:"email_address"`
+ VerificationCode string `json:"verification_code"`
+ ExpirationInMinutes int `json:"expiration_in_minutes"`
+ Subject string `json:"subject"`
+}
+
+func (d *RedisTaskDistributor) DistributeTaskSendVerifyEmail(
+ ctx context.Context,
+ payload *PayloadSendVerifyEmail,
+ opts ...asynq.Option,
+) error {
+ jsonPayload, err := json.Marshal(payload)
+ if err != nil {
+ return fmt.Errorf("failed to marshal task payload: %w", err)
+ }
+
+ task := asynq.NewTask(TaskSendVerifyEmail, jsonPayload, opts...)
+
+ _, err = d.client.EnqueueContext(ctx, task)
+ if err != nil {
+ return fmt.Errorf("failed to enqueue task: %w", err)
+ }
+
+ return nil
+}
+
+func (p *RedisTaskProcessor) ProcessTaskSendVerifyEmail(ctx context.Context, task *asynq.Task) error {
+ var payload PayloadSendVerifyEmail
+ if err := json.Unmarshal(task.Payload(), &payload); err != nil {
+ return fmt.Errorf("failed to unmarshal payload: %w", asynq.SkipRetry)
+ }
+
+ var tmpl *template.Template
+ tmpl, err := template.ParseFS(assets.EmbeddedFiles, assets.EmailConfirmationTemplatePath)
+ if err != nil {
+ return fmt.Errorf("failed to parse email template: %w", err)
+ }
+
+ var body bytes.Buffer
+ if err := tmpl.ExecuteTemplate(&body, "htmlBody", payload); err != nil {
+ return fmt.Errorf("failed to execute email template: %w", err)
+ }
+ htmlContent := body.String()
+
+ slog.Info("Sending verification email", slog.String("email", payload.EmailAddress))
+
+ err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
+ if err != nil {
+ return fmt.Errorf("failed to send verify email: %w", err)
+ }
+
+ slog.Info("Verification email sent successfully", slog.String("email", payload.EmailAddress))
+
+ return nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
index 7a8ae3cdc19f818f8dea72dde9ce4bcb6b57d692..9dccb05f32ab004da9268a08cb093984986c8ccb 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
@@ -9,6 +9,7 @@ import (
"api.qobiltu.id/router"
"api.qobiltu.id/services"
"api.qobiltu.id/utils"
+ "api.qobiltu.id/validation"
"api.qobiltu.id/worker"
"github.com/hibiken/asynq"
"log/slog"
@@ -18,6 +19,10 @@ import (
func main() {
+ // setup validation
+ err := validation.New(validation.LocaleID)
+ utils.FatalIfErr("failed to setup validator", err)
+
// setup email sender
emailConfig := mail.Config{
Host: config.SMTP_HOST,
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
index 239311daab53241d70b48aae836b8ea483f22196..d819b9a4881ea1bb166856b66693b748e26ef241 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
@@ -1,23 +1,26 @@
package models
+import "api.qobiltu.id/validation"
+
type Exception struct {
- Unauthorized bool `json:"unauthorized,omitempty"`
- BadRequest bool `json:"bad_request,omitempty"`
- DataNotFound bool `json:"data_not_found,omitempty"`
- InternalServerError bool `json:"internal_server_error,omitempty"`
- DataDuplicate bool `json:"data_duplicate,omitempty"`
- QueryError bool `json:"query_error,omitempty"`
- InvalidPasswordLength bool `json:"invalid_password_length,omitempty"`
- IsPassTheLimit bool `json:"is_pass_the_limit,omitempty"`
- IsTimeOut bool `json:"is_time_out,omitempty"`
- AttemptNotFound bool `json:"attempt_not_found,omitempty"`
- Forbidden bool `json:"forbidden,omitempty"`
- ValidationError bool `json:"validation_error,omitempty"`
+ Unauthorized bool `json:"unauthorized,omitempty"`
+ BadRequest bool `json:"bad_request,omitempty"`
+ DataNotFound bool `json:"data_not_found,omitempty"`
+ InternalServerError bool `json:"internal_server_error,omitempty"`
+ DataDuplicate bool `json:"data_duplicate,omitempty"`
+ QueryError bool `json:"query_error,omitempty"`
+ InvalidPasswordLength bool `json:"invalid_password_length,omitempty"`
+ IsPassTheLimit bool `json:"is_pass_the_limit,omitempty"`
+ IsTimeOut bool `json:"is_time_out,omitempty"`
+ AttemptNotFound bool `json:"attempt_not_found,omitempty"`
+ Forbidden bool `json:"forbidden,omitempty"`
+ ValidationError bool `json:"validation_error,omitempty"`
- Message string `json:"message,omitempty"`
- Err error `json:"-"`
+ Message string `json:"message,omitempty"`
+ Err error `json:"-"`
+ ValidationErrorFields []validation.ErrorMessage `json:"validation_error_fields,omitempty"`
}
func (a Exception) Error() string {
- return a.Err.Error()
+ return a.Err.Error()
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/api_response_v2.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/api_response_v2.go
index 9369a49284f499bbc175cd53d28f0629b12c1b0e..3cfda1d94cee9b2e64e0f10acc461ef47c7e1283 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/api_response_v2.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/api_response_v2.go
@@ -3,6 +3,7 @@ package response
import (
"api.qobiltu.id/models"
"api.qobiltu.id/utils"
+ "api.qobiltu.id/validation"
"errors"
"net/http"
@@ -39,7 +40,7 @@ func HandleError(c *gin.Context, err error) {
case exception.AttemptNotFound:
responseError(c, http.StatusNotFound, exception)
case exception.ValidationError:
- responseError(c, http.StatusUnprocessableEntity, exception)
+ responseValidationError(c, http.StatusUnprocessableEntity, exception.ValidationErrorFields) // Gunakan fungsi khusus untuk validasi
default:
responseError(c, http.StatusInternalServerError, exception)
}
@@ -77,3 +78,17 @@ func responseError(c *gin.Context, status int, exception models.Exception) {
c.AbortWithStatusJSON(status, res)
return
}
+
+func responseValidationError(c *gin.Context, status int, validationErrors []validation.ErrorMessage) {
+ res := models.ErrorResponse{
+ Status: "error",
+ Message: "Validasi data gagal.",
+ Errors: models.Exception{
+ ValidationError: true,
+ ValidationErrorFields: validationErrors,
+ },
+ }
+
+ c.AbortWithStatusJSON(status, res)
+ return
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/validation.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/validation.go
new file mode 100644
index 0000000000000000000000000000000000000000..e147ba60284d833987433c020a8ed3745bd5ab44
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/validation.go
@@ -0,0 +1,14 @@
+package response
+
+import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/validation"
+)
+
+func HandleValidationError(validationErrors []validation.ErrorMessage) error {
+ return models.Exception{
+ ValidationError: true,
+ Message: "Validation failed",
+ ValidationErrorFields: validationErrors,
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
index 7684ad8bd92d867976abbe66dd1f0e9de0acf0f5..8cf914d6a0fae5872c22d846b3d88257e32bd9ca 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
@@ -1,444 +1,485 @@
package services
import (
- "api.qobiltu.id/models"
- "api.qobiltu.id/repositories"
- "api.qobiltu.id/response"
- "context"
- "errors"
- "gorm.io/gorm"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+ "api.qobiltu.id/response"
+ "api.qobiltu.id/validation"
+ "context"
+ "errors"
+ "gorm.io/gorm"
)
type CVService interface {
- SaveAccountDetails(ctx context.Context, req *models.AccountDetailsRequest) (*models.AccountDetails, error)
- GetAccountDetails(ctx context.Context, id int64) (*models.AccountDetails, error)
-
- SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCVRequest) (*models.PersonalityAndPreferenceCV, error)
- GetPersonalityAndPreference(ctx context.Context, id int64) (*models.PersonalityAndPreferenceCV, error)
-
- CreateFamilyMember(ctx context.Context, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
- UpdateFamilyMember(ctx context.Context, id int64, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
- ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error)
- GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error)
- DeleteFamilyMember(ctx context.Context, id int64) error
-
- SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error)
- GetPhysicalAndHealth(ctx context.Context, id int64) (*models.PhysicalAndHealthCV, error)
-
- SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error)
- GetWorshipAndReligiousUnderstanding(ctx context.Context, id int64) (*models.WorshipAndReligiousUnderstandingCV, error)
-
- CreateEducation(ctx context.Context, req *models.EducationRequest) (*models.EducationCV, error)
- UpdateEducation(ctx context.Context, id int64, req *models.EducationRequest) (*models.EducationCV, error)
- ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error)
- GetEducation(ctx context.Context, id int64) (*models.EducationCV, error)
- DeleteEducation(ctx context.Context, id int64) error
-
- CreateJob(ctx context.Context, req *models.JobRequest) (*models.JobCV, error)
- UpdateJob(ctx context.Context, id int64, req *models.JobRequest) (*models.JobCV, error)
- ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error)
- GetJob(ctx context.Context, id int64) (*models.JobCV, error)
- DeleteJob(ctx context.Context, id int64) error
-
- CreateAchievement(ctx context.Context, req *models.AchievementRequest) (*models.AchievementCV, error)
- UpdateAchievement(ctx context.Context, id int64, req *models.AchievementRequest) (*models.AchievementCV, error)
- ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error)
- GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error)
- DeleteAchievement(ctx context.Context, id int64) error
+ SaveAccountDetails(ctx context.Context, req *models.AccountDetailsRequest) (*models.AccountDetails, error)
+ GetAccountDetails(ctx context.Context, id int64) (*models.AccountDetails, error)
+
+ SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCVRequest) (*models.PersonalityAndPreferenceCV, error)
+ GetPersonalityAndPreference(ctx context.Context, id int64) (*models.PersonalityAndPreferenceCV, error)
+
+ CreateFamilyMember(ctx context.Context, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
+ UpdateFamilyMember(ctx context.Context, id int64, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
+ ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error)
+ GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error)
+ DeleteFamilyMember(ctx context.Context, id int64) error
+
+ SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error)
+ GetPhysicalAndHealth(ctx context.Context, id int64) (*models.PhysicalAndHealthCV, error)
+
+ SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error)
+ GetWorshipAndReligiousUnderstanding(ctx context.Context, id int64) (*models.WorshipAndReligiousUnderstandingCV, error)
+
+ CreateEducation(ctx context.Context, req *models.EducationRequest) (*models.EducationCV, error)
+ UpdateEducation(ctx context.Context, id int64, req *models.EducationRequest) (*models.EducationCV, error)
+ ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error)
+ GetEducation(ctx context.Context, id int64) (*models.EducationCV, error)
+ DeleteEducation(ctx context.Context, id int64) error
+
+ CreateJob(ctx context.Context, req *models.JobRequest) (*models.JobCV, error)
+ UpdateJob(ctx context.Context, id int64, req *models.JobRequest) (*models.JobCV, error)
+ ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error)
+ GetJob(ctx context.Context, id int64) (*models.JobCV, error)
+ DeleteJob(ctx context.Context, id int64) error
+
+ CreateAchievement(ctx context.Context, req *models.AchievementRequest) (*models.AchievementCV, error)
+ UpdateAchievement(ctx context.Context, id int64, req *models.AchievementRequest) (*models.AchievementCV, error)
+ ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error)
+ GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error)
+ DeleteAchievement(ctx context.Context, id int64) error
}
type cvService struct {
- cvRepository repositories.CVRepository
+ cvRepository repositories.CVRepository
}
func NewCVService(cvRepository repositories.CVRepository) CVService {
- return &cvService{
- cvRepository: cvRepository,
- }
+ return &cvService{
+ cvRepository: cvRepository,
+ }
}
func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.AccountDetailsRequest) (*models.AccountDetails, error) {
- // Ambil data lama jika ada
- accountDetails, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, req.AccountID)
- if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, response.HandleGormError(err, "Internal Server Error")
- }
-
- // Apply perubahan
- if accountDetails == nil {
- accountDetails = &models.AccountDetails{}
- }
-
- accountDetails.AccountID = uint(req.AccountID)
- accountDetails.FullName = req.FullName
- accountDetails.Gender = req.Gender
- accountDetails.DateOfBirth = req.DateOfBirth
- accountDetails.PlaceOfBirth = req.PlaceOfBirth
- accountDetails.Domicile = req.Domicile
- accountDetails.MaritalStatus = req.MaritalStatus
- accountDetails.LastEducation = req.LastEducation
- accountDetails.LastJob = req.LastJob
-
- // Simpan data
- res, err := s.cvRepository.SaveAccountDetails(ctx, accountDetails)
- if err != nil {
- return nil, response.HandleGormError(err, "Internal Server Error")
- }
-
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ // Ambil data lama jika ada
+ accountDetails, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ // Apply perubahan
+ if accountDetails == nil {
+ accountDetails = &models.AccountDetails{}
+ }
+
+ accountDetails.AccountID = uint(req.AccountID)
+ accountDetails.FullName = req.FullName
+ accountDetails.Gender = req.Gender
+ accountDetails.DateOfBirth = req.DateOfBirth
+ accountDetails.PlaceOfBirth = req.PlaceOfBirth
+ accountDetails.Domicile = req.Domicile
+ accountDetails.MaritalStatus = req.MaritalStatus
+ accountDetails.LastEducation = req.LastEducation
+ accountDetails.LastJob = req.LastJob
+
+ // Simpan data
+ res, err := s.cvRepository.SaveAccountDetails(ctx, accountDetails)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return res, nil
}
func (s *cvService) GetAccountDetails(ctx context.Context, id int64) (*models.AccountDetails, error) {
- res, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data diri tidak ditemukan")
- }
- return res, nil
+ res, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data diri tidak ditemukan")
+ }
+ return res, nil
}
func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCVRequest) (*models.PersonalityAndPreferenceCV, error) {
- // Ambil data lama jika ada
- personalityAndPreference, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, req.AccountID)
- if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, response.HandleGormError(err, "Internal Server Error")
- }
-
- // Apply perubahan
- if personalityAndPreference == nil {
- personalityAndPreference = &models.PersonalityAndPreferenceCV{}
- }
-
- personalityAndPreference.AccountID = req.AccountID
- personalityAndPreference.PositiveTraits = req.PositiveTraits
- personalityAndPreference.NegativeTraits = req.NegativeTraits
- personalityAndPreference.Hobbies = req.Hobbies
- personalityAndPreference.LifeGoals = req.LifeGoals
- personalityAndPreference.DailyActivities = req.DailyActivities
- personalityAndPreference.LeisureActivities = req.LeisureActivities
- personalityAndPreference.Likes = req.Likes
- personalityAndPreference.Dislikes = req.Dislikes
- personalityAndPreference.StressHandling = req.StressHandling
- personalityAndPreference.AngerTriggers = req.AngerTriggers
- personalityAndPreference.FavoriteFoodAndDrinks = req.FavoriteFoodAndDrinks
- personalityAndPreference.CanCook = req.CanCook
- personalityAndPreference.TypesOfDishesCooked = req.TypesOfDishesCooked
- personalityAndPreference.MonthlyExpenses = req.MonthlyExpenses
-
- res, err := s.cvRepository.SavePersonalityAndPreference(ctx, personalityAndPreference)
- if err != nil {
- return nil, response.HandleGormError(err, "Internal Server Error")
- }
-
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ // Ambil data lama jika ada
+ personalityAndPreference, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ // Apply perubahan
+ if personalityAndPreference == nil {
+ personalityAndPreference = &models.PersonalityAndPreferenceCV{}
+ }
+
+ personalityAndPreference.AccountID = req.AccountID
+ personalityAndPreference.PositiveTraits = req.PositiveTraits
+ personalityAndPreference.NegativeTraits = req.NegativeTraits
+ personalityAndPreference.Hobbies = req.Hobbies
+ personalityAndPreference.LifeGoals = req.LifeGoals
+ personalityAndPreference.DailyActivities = req.DailyActivities
+ personalityAndPreference.LeisureActivities = req.LeisureActivities
+ personalityAndPreference.Likes = req.Likes
+ personalityAndPreference.Dislikes = req.Dislikes
+ personalityAndPreference.StressHandling = req.StressHandling
+ personalityAndPreference.AngerTriggers = req.AngerTriggers
+ personalityAndPreference.FavoriteFoodAndDrinks = req.FavoriteFoodAndDrinks
+ personalityAndPreference.CanCook = req.CanCook
+ personalityAndPreference.TypesOfDishesCooked = req.TypesOfDishesCooked
+ personalityAndPreference.MonthlyExpenses = req.MonthlyExpenses
+
+ res, err := s.cvRepository.SavePersonalityAndPreference(ctx, personalityAndPreference)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return res, nil
}
func (s *cvService) GetPersonalityAndPreference(ctx context.Context, id int64) (*models.PersonalityAndPreferenceCV, error) {
- res, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Internal Server Error")
- }
+ res, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
- return res, nil
+ return res, nil
}
func (s *cvService) CreateFamilyMember(ctx context.Context, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error) {
- // Mapping request ke model
- familyMember := &models.FamilyMemberCV{
- AccountID: req.AccountID,
- Role: req.Role,
- Status: req.Status,
- Religion: req.Religion,
- Job: req.Job,
- LastEducation: req.LastEducation,
- Age: req.Age,
- }
-
- // Simpan ke repository
- res, err := s.cvRepository.SaveFamilyMember(ctx, familyMember)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal menyimpan anggota keluarga")
- }
-
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ // Mapping request ke model
+ familyMember := &models.FamilyMemberCV{
+ AccountID: req.AccountID,
+ Role: req.Role,
+ Status: req.Status,
+ Religion: req.Religion,
+ Job: req.Job,
+ LastEducation: req.LastEducation,
+ Age: req.Age,
+ }
+
+ // Simpan ke repository
+ res, err := s.cvRepository.SaveFamilyMember(ctx, familyMember)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menyimpan anggota keluarga")
+ }
+
+ return res, nil
}
func (s *cvService) ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error) {
- list, err := s.cvRepository.ListFamilyMember(ctx, accountID)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal mengambil daftar anggota keluarga")
- }
- return list, nil
+ list, err := s.cvRepository.ListFamilyMember(ctx, accountID)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal mengambil daftar anggota keluarga")
+ }
+ return list, nil
}
func (s *cvService) GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error) {
- res, err := s.cvRepository.GetFamilyMember(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
- }
- return res, nil
+ res, err := s.cvRepository.GetFamilyMember(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
+ }
+ return res, nil
}
func (s *cvService) DeleteFamilyMember(ctx context.Context, id int64) error {
- err := s.cvRepository.DeleteFamilyMember(ctx, id)
- if err != nil {
- return response.HandleGormError(err, "Gagal menghapus anggota keluarga")
- }
- return nil
+ err := s.cvRepository.DeleteFamilyMember(ctx, id)
+ if err != nil {
+ return response.HandleGormError(err, "Gagal menghapus anggota keluarga")
+ }
+ return nil
}
func (s *cvService) UpdateFamilyMember(ctx context.Context, id int64, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error) {
- existing, err := s.cvRepository.GetFamilyMember(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
- }
-
- existing.Role = req.Role
- existing.Status = req.Status
- existing.Religion = req.Religion
- existing.Job = req.Job
- existing.LastEducation = req.LastEducation
- existing.Age = req.Age
-
- updated, err := s.cvRepository.SaveFamilyMember(ctx, existing)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal memperbarui anggota keluarga")
- }
-
- return updated, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ existing, err := s.cvRepository.GetFamilyMember(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
+ }
+
+ existing.Role = req.Role
+ existing.Status = req.Status
+ existing.Religion = req.Religion
+ existing.Job = req.Job
+ existing.LastEducation = req.LastEducation
+ existing.Age = req.Age
+
+ updated, err := s.cvRepository.SaveFamilyMember(ctx, existing)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal memperbarui anggota keluarga")
+ }
+
+ return updated, nil
}
func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error) {
- // Cek apakah data sudah ada berdasarkan account_id
- existing, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, req.AccountID)
- if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data fisik dan kesehatan")
- }
-
- // Jika belum ada, buat objek baru
- if existing == nil {
- existing = &models.PhysicalAndHealthCV{}
- }
-
- // Mapping field dari request
- existing.AccountID = req.AccountID
- existing.HeightInCm = req.HeightInCm
- existing.WeightInKg = req.WeightInKg
- existing.BodyShape = req.BodyShape
- existing.SkinColor = req.SkinColor
- existing.HairType = req.HairType
- existing.MedicalHistory = req.MedicalHistory
- existing.PhysicalDisorder = req.PhysicalDisorder
- existing.PhysicalTraits = req.PhysicalTraits
-
- // Simpan data
- res, err := s.cvRepository.SavePhysicalAndHealth(ctx, existing)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal menyimpan data fisik dan kesehatan")
- }
-
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ // Cek apakah data sudah ada berdasarkan account_id
+ existing, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data fisik dan kesehatan")
+ }
+
+ // Jika belum ada, buat objek baru
+ if existing == nil {
+ existing = &models.PhysicalAndHealthCV{}
+ }
+
+ // Mapping field dari request
+ existing.AccountID = req.AccountID
+ existing.HeightInCm = req.HeightInCm
+ existing.WeightInKg = req.WeightInKg
+ existing.BodyShape = req.BodyShape
+ existing.SkinColor = req.SkinColor
+ existing.HairType = req.HairType
+ existing.MedicalHistory = req.MedicalHistory
+ existing.PhysicalDisorder = req.PhysicalDisorder
+ existing.PhysicalTraits = req.PhysicalTraits
+
+ // Simpan data
+ res, err := s.cvRepository.SavePhysicalAndHealth(ctx, existing)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menyimpan data fisik dan kesehatan")
+ }
+
+ return res, nil
}
func (s *cvService) GetPhysicalAndHealth(ctx context.Context, id int64) (*models.PhysicalAndHealthCV, error) {
- res, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data fisik dan kesehatan tidak ditemukan")
- }
- return res, nil
+ res, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data fisik dan kesehatan tidak ditemukan")
+ }
+ return res, nil
}
func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error) {
- // Cek apakah data sudah ada berdasarkan account_id
- worshipAndReligiousUnderstanding, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, req.AccountID)
- if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
- return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data agama dan pemahaman agama")
- }
-
- // Jika belum ada, buat objek baru
- if worshipAndReligiousUnderstanding == nil {
- worshipAndReligiousUnderstanding = &models.WorshipAndReligiousUnderstandingCV{}
- }
-
- // Mapping field dari request
- worshipAndReligiousUnderstanding.AccountID = req.AccountID
- worshipAndReligiousUnderstanding.ObligatoryPrayer = req.ObligatoryPrayer
- worshipAndReligiousUnderstanding.CongregationalPrayer = req.CongregationalPrayer
- worshipAndReligiousUnderstanding.TahajjudPrayer = req.TahajjudPrayer
- worshipAndReligiousUnderstanding.DhuhaPrayer = req.DhuhaPrayer
- worshipAndReligiousUnderstanding.QuranMemorization = req.QuranMemorization
- worshipAndReligiousUnderstanding.QuranReadingAbility = req.QuranReadingAbility
- worshipAndReligiousUnderstanding.DaudFasting = req.DaudFasting
- worshipAndReligiousUnderstanding.AyyamulBidhFasting = req.AyyamulBidhFasting
- worshipAndReligiousUnderstanding.HajjOrUmrah = req.HajjOrUmrah
- worshipAndReligiousUnderstanding.ListeningToMusic = req.ListeningToMusic
- worshipAndReligiousUnderstanding.OpinionOnIkhtilat = req.OpinionOnIkhtilat
- worshipAndReligiousUnderstanding.OpinionOnTouchingNonMahram = req.OpinionOnTouchingNonMahram
- worshipAndReligiousUnderstanding.OpinionOnVeil = req.OpinionOnVeil
- worshipAndReligiousUnderstanding.WeeklyReligiousStudies = req.WeeklyReligiousStudies
- worshipAndReligiousUnderstanding.FollowedUstadz = req.FollowedUstadz
-
- // Simpan data
- res, err := s.cvRepository.SaveWorshipAndReligiousUnderstanding(ctx, worshipAndReligiousUnderstanding)
- if err != nil {
- return nil, response.HandleGormError(err, "Internal Server Error")
- }
-
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ // Cek apakah data sudah ada berdasarkan account_id
+ worshipAndReligiousUnderstanding, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data agama dan pemahaman agama")
+ }
+
+ // Jika belum ada, buat objek baru
+ if worshipAndReligiousUnderstanding == nil {
+ worshipAndReligiousUnderstanding = &models.WorshipAndReligiousUnderstandingCV{}
+ }
+
+ // Mapping field dari request
+ worshipAndReligiousUnderstanding.AccountID = req.AccountID
+ worshipAndReligiousUnderstanding.ObligatoryPrayer = req.ObligatoryPrayer
+ worshipAndReligiousUnderstanding.CongregationalPrayer = req.CongregationalPrayer
+ worshipAndReligiousUnderstanding.TahajjudPrayer = req.TahajjudPrayer
+ worshipAndReligiousUnderstanding.DhuhaPrayer = req.DhuhaPrayer
+ worshipAndReligiousUnderstanding.QuranMemorization = req.QuranMemorization
+ worshipAndReligiousUnderstanding.QuranReadingAbility = req.QuranReadingAbility
+ worshipAndReligiousUnderstanding.DaudFasting = req.DaudFasting
+ worshipAndReligiousUnderstanding.AyyamulBidhFasting = req.AyyamulBidhFasting
+ worshipAndReligiousUnderstanding.HajjOrUmrah = req.HajjOrUmrah
+ worshipAndReligiousUnderstanding.ListeningToMusic = req.ListeningToMusic
+ worshipAndReligiousUnderstanding.OpinionOnIkhtilat = req.OpinionOnIkhtilat
+ worshipAndReligiousUnderstanding.OpinionOnTouchingNonMahram = req.OpinionOnTouchingNonMahram
+ worshipAndReligiousUnderstanding.OpinionOnVeil = req.OpinionOnVeil
+ worshipAndReligiousUnderstanding.WeeklyReligiousStudies = req.WeeklyReligiousStudies
+ worshipAndReligiousUnderstanding.FollowedUstadz = req.FollowedUstadz
+
+ // Simpan data
+ res, err := s.cvRepository.SaveWorshipAndReligiousUnderstanding(ctx, worshipAndReligiousUnderstanding)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return res, nil
}
func (s *cvService) GetWorshipAndReligiousUnderstanding(ctx context.Context, id int64) (*models.WorshipAndReligiousUnderstandingCV, error) {
- res, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data agama dan pemahaman agama tidak ditemukan")
- }
- return res, nil
+ res, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data agama dan pemahaman agama tidak ditemukan")
+ }
+ return res, nil
}
func (s *cvService) CreateEducation(ctx context.Context, req *models.EducationRequest) (*models.EducationCV, error) {
- edu := &models.EducationCV{
- AccountID: req.AccountID,
- LastEducation: req.LastEducation,
- EducationInstitute: req.EducationInstitute,
- EducationMajor: req.EducationMajor,
- YearStart: req.YearStart,
- YearGraduate: req.YearGraduate,
- }
-
- res, err := s.cvRepository.SaveEducation(ctx, edu)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal menambahkan data pendidikan")
- }
-
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ edu := &models.EducationCV{
+ AccountID: req.AccountID,
+ LastEducation: req.LastEducation,
+ EducationInstitute: req.EducationInstitute,
+ EducationMajor: req.EducationMajor,
+ YearStart: req.YearStart,
+ YearGraduate: req.YearGraduate,
+ }
+
+ res, err := s.cvRepository.SaveEducation(ctx, edu)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menambahkan data pendidikan")
+ }
+
+ return res, nil
}
func (s *cvService) UpdateEducation(ctx context.Context, id int64, req *models.EducationRequest) (*models.EducationCV, error) {
- edu, err := s.cvRepository.GetEducation(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
- }
-
- edu.LastEducation = req.LastEducation
- edu.EducationInstitute = req.EducationInstitute
- edu.EducationMajor = req.EducationMajor
- edu.YearStart = req.YearStart
- edu.YearGraduate = req.YearGraduate
-
- res, err := s.cvRepository.SaveEducation(ctx, edu)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal memperbarui data pendidikan")
- }
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ edu, err := s.cvRepository.GetEducation(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
+ }
+
+ edu.LastEducation = req.LastEducation
+ edu.EducationInstitute = req.EducationInstitute
+ edu.EducationMajor = req.EducationMajor
+ edu.YearStart = req.YearStart
+ edu.YearGraduate = req.YearGraduate
+
+ res, err := s.cvRepository.SaveEducation(ctx, edu)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal memperbarui data pendidikan")
+ }
+ return res, nil
}
func (s *cvService) ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error) {
- return s.cvRepository.ListEducation(ctx, accountID)
+ return s.cvRepository.ListEducation(ctx, accountID)
}
func (s *cvService) GetEducation(ctx context.Context, id int64) (*models.EducationCV, error) {
- edu, err := s.cvRepository.GetEducation(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
- }
- return edu, nil
+ edu, err := s.cvRepository.GetEducation(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
+ }
+ return edu, nil
}
func (s *cvService) DeleteEducation(ctx context.Context, id int64) error {
- return s.cvRepository.DeleteEducation(ctx, id)
+ return s.cvRepository.DeleteEducation(ctx, id)
}
func (s *cvService) CreateJob(ctx context.Context, req *models.JobRequest) (*models.JobCV, error) {
- job := &models.JobCV{
- AccountID: req.AccountID,
- InstitutionName: req.InstitutionName,
- CurrentJob: req.CurrentJob,
- YearStartedWorking: req.YearStartedWorking,
- MonthlyIncome: req.MonthlyIncome,
- IncomeSources: req.IncomeSources,
- }
- res, err := s.cvRepository.SaveJob(ctx, job)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal menambahkan data pekerjaan")
- }
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ job := &models.JobCV{
+ AccountID: req.AccountID,
+ InstitutionName: req.InstitutionName,
+ CurrentJob: req.CurrentJob,
+ YearStartedWorking: req.YearStartedWorking,
+ MonthlyIncome: req.MonthlyIncome,
+ IncomeSources: req.IncomeSources,
+ }
+ res, err := s.cvRepository.SaveJob(ctx, job)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menambahkan data pekerjaan")
+ }
+ return res, nil
}
func (s *cvService) UpdateJob(ctx context.Context, id int64, req *models.JobRequest) (*models.JobCV, error) {
- job, err := s.cvRepository.GetJob(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
- }
-
- job.InstitutionName = req.InstitutionName
- job.CurrentJob = req.CurrentJob
- job.YearStartedWorking = req.YearStartedWorking
- job.MonthlyIncome = req.MonthlyIncome
- job.IncomeSources = req.IncomeSources
-
- res, err := s.cvRepository.SaveJob(ctx, job)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal memperbarui data pekerjaan")
- }
- return res, nil
+ if err := validation.Validate(req); err != nil {
+ return nil, response.HandleValidationError(err)
+ }
+
+ job, err := s.cvRepository.GetJob(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
+ }
+
+ job.InstitutionName = req.InstitutionName
+ job.CurrentJob = req.CurrentJob
+ job.YearStartedWorking = req.YearStartedWorking
+ job.MonthlyIncome = req.MonthlyIncome
+ job.IncomeSources = req.IncomeSources
+
+ res, err := s.cvRepository.SaveJob(ctx, job)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal memperbarui data pekerjaan")
+ }
+ return res, nil
}
func (s *cvService) ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error) {
- return s.cvRepository.ListJob(ctx, accountID)
+ return s.cvRepository.ListJob(ctx, accountID)
}
func (s *cvService) GetJob(ctx context.Context, id int64) (*models.JobCV, error) {
- job, err := s.cvRepository.GetJob(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
- }
- return job, nil
+ job, err := s.cvRepository.GetJob(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
+ }
+ return job, nil
}
func (s *cvService) DeleteJob(ctx context.Context, id int64) error {
- return s.cvRepository.DeleteJob(ctx, id)
+ return s.cvRepository.DeleteJob(ctx, id)
}
func (s *cvService) CreateAchievement(ctx context.Context, req *models.AchievementRequest) (*models.AchievementCV, error) {
- ach := &models.AchievementCV{
- AccountID: req.AccountID,
- AchievementOrAward: req.AchievementOrAward,
- }
- res, err := s.cvRepository.SaveAchievement(ctx, ach)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal menambahkan data prestasi")
- }
-
- return res, nil
+ ach := &models.AchievementCV{
+ AccountID: req.AccountID,
+ AchievementOrAward: req.AchievementOrAward,
+ }
+ res, err := s.cvRepository.SaveAchievement(ctx, ach)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menambahkan data prestasi")
+ }
+
+ return res, nil
}
func (s *cvService) UpdateAchievement(ctx context.Context, id int64, req *models.AchievementRequest) (*models.AchievementCV, error) {
- ach, err := s.cvRepository.GetAchievement(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
- }
+ ach, err := s.cvRepository.GetAchievement(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
+ }
- ach.AchievementOrAward = req.AchievementOrAward
+ ach.AchievementOrAward = req.AchievementOrAward
- res, err := s.cvRepository.SaveAchievement(ctx, ach)
- if err != nil {
- return nil, response.HandleGormError(err, "Gagal memperbarui data prestasi")
- }
+ res, err := s.cvRepository.SaveAchievement(ctx, ach)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal memperbarui data prestasi")
+ }
- return res, nil
+ return res, nil
}
func (s *cvService) ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error) {
- return s.cvRepository.ListAchievement(ctx, accountID)
+ return s.cvRepository.ListAchievement(ctx, accountID)
}
func (s *cvService) GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error) {
- ach, err := s.cvRepository.GetAchievement(ctx, id)
- if err != nil {
- return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
- }
- return ach, nil
+ ach, err := s.cvRepository.GetAchievement(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
+ }
+ return ach, nil
}
func (s *cvService) DeleteAchievement(ctx context.Context, id int64) error {
- return s.cvRepository.DeleteAchievement(ctx, id)
+ return s.cvRepository.DeleteAchievement(ctx, id)
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
index 23723ce748c6199b01cb89b503e2328a53552138..b6e0e7a9afec018d49dbaab8f0cee0b1d32294c5 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
@@ -97,6 +97,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
index 0b64fec3b839d0f5b64a38ced137eea99c4cef82..303ee1c0d57a4494e0e00e7566bc99da5fb3ba14 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -3,6 +3,7 @@ package models
import (
"time"
+ "github.com/lib/pq"
uuid "github.com/satori/go.uuid"
)
@@ -241,33 +242,33 @@ type (
}
WorshipAndReligiousUnderstandingCV struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
- AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
- ObligatoryPrayer *string `gorm:"column:obligatory_prayer" json:"obligatory_prayer"` // sholat_wajib_5_waktu
- CongregationalPrayer *string `gorm:"column:congregational_prayer" json:"congregational_prayer"` // sholat_berjamaah_di_masjid
- TahajjudPrayer *string `gorm:"column:tahajjud_prayer" json:"tahajjud_prayer"` // sholat_tahajud
- DhuhaPrayer *string `gorm:"column:dhuha_prayer" json:"dhuha_prayer"` // sholat_dhuha
- QuranMemorization *string `gorm:"column:quran_memorization" json:"quran_memorization"` // hafalan_alquran
- QuranReadingAbility *string `gorm:"column:quran_reading_ability" json:"quran_reading_ability"` // kemampuan_baca_alquran
- DaudFasting *string `gorm:"column:daud_fasting" json:"daud_fasting"` // puasa_daud
- AyyamulBidhFasting *string `gorm:"column:ayyamul_bidh_fasting" json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
- HajjOrUmrah *string `gorm:"column:hajj_or_umrah" json:"hajj_or_umrah"` // ibadah_haji_umroh
- ListeningToMusic *string `gorm:"column:listening_to_music" json:"listening_to_music"` // mendengarkan_musik
- OpinionOnIkhtilat *string `gorm:"column:opinion_on_ikhtilat" json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
- OpinionOnTouchingNonMahram *string `gorm:"column:opinion_on_touching_non_mahram" json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
- OpinionOnVeil *string `gorm:"column:opinion_on_veil" json:"opinion_on_veil"` // pendapat_tentang_cadar
- WeeklyReligiousStudies *string `gorm:"column:weekly_religious_studies" json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
- FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
+ ObligatoryPrayer *string `gorm:"column:obligatory_prayer" json:"obligatory_prayer"` // sholat_wajib_5_waktu
+ CongregationalPrayer *string `gorm:"column:congregational_prayer" json:"congregational_prayer"` // sholat_berjamaah_di_masjid
+ TahajjudPrayer *string `gorm:"column:tahajjud_prayer" json:"tahajjud_prayer"` // sholat_tahajud
+ DhuhaPrayer *string `gorm:"column:dhuha_prayer" json:"dhuha_prayer"` // sholat_dhuha
+ QuranMemorization *string `gorm:"column:quran_memorization" json:"quran_memorization"` // hafalan_alquran
+ QuranReadingAbility *string `gorm:"column:quran_reading_ability" json:"quran_reading_ability"` // kemampuan_baca_alquran
+ DaudFasting *string `gorm:"column:daud_fasting" json:"daud_fasting"` // puasa_daud
+ AyyamulBidhFasting *string `gorm:"column:ayyamul_bidh_fasting" json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
+ HajjOrUmrah pq.StringArray `gorm:"column:hajj_or_umrah;type:varchar(255)[]" json:"hajj_or_umrah"` // ibadah_haji_umroh
+ ListeningToMusic *string `gorm:"column:listening_to_music" json:"listening_to_music"` // mendengarkan_musik
+ OpinionOnIkhtilat *string `gorm:"column:opinion_on_ikhtilat" json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
+ OpinionOnTouchingNonMahram *string `gorm:"column:opinion_on_touching_non_mahram" json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
+ OpinionOnVeil *string `gorm:"column:opinion_on_veil" json:"opinion_on_veil"` // pendapat_tentang_cadar
+ WeeklyReligiousStudies *string `gorm:"column:weekly_religious_studies" json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
+ FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
}
EducationCV struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
- LastEducation *string `gorm:"column:last_education" json:"last_education"` // pendidikan terakhir
+ LastEducation *string `gorm:"column:last_education" json:"last_education" validate:""` // pendidikan terakhir
EducationInstitute *string `gorm:"column:education_institute" json:"education_institute"` // institusi pendidikan
EducationMajor *string `gorm:"column:education_major" json:"education_major"` // jurusan pendidikan
YearStart *int `gorm:"column:year_start" json:"year_start"` // tahun masuk
@@ -277,16 +278,16 @@ type (
}
JobCV struct {
- ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
- AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
- Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
- InstitutionName *string `gorm:"column:institution_name" json:"institution_name"` // nama instansi
- CurrentJob *string `gorm:"column:current_job" json:"current_job"` // pekerjaan saat ini
- YearStartedWorking *int `gorm:"column:year_started_working" json:"year_started_working"` // tahun mulai bekerja
- MonthlyIncome *string `gorm:"column:monthly_income" json:"monthly_income"` // penghasilan per bulan
- IncomeSources *string `gorm:"column:income_sources" json:"income_sources"` // sumber penghasilan
- CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
- UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
+ InstitutionName *string `gorm:"column:institution_name" json:"institution_name"` // nama instansi
+ CurrentJob *string `gorm:"column:current_job" json:"current_job"` // pekerjaan saat ini
+ YearStartedWorking *int `gorm:"column:year_started_working" json:"year_started_working"` // tahun mulai bekerja
+ MonthlyIncome *string `gorm:"column:monthly_income" json:"monthly_income"` // penghasilan per bulan
+ IncomeSources pq.StringArray `gorm:"column:income_sources;type:varchar(255)[]" json:"income_sources"` // sumber penghasilan
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
}
AchievementCV struct {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
index 803ef615e812f4739afaad61afd8f2712c9ea72a..ee2d0aebd7759d95df5c3392947e2c38139dfb61 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
@@ -1,6 +1,9 @@
package models
-import "time"
+import (
+ "github.com/lib/pq"
+ "time"
+)
type LoginRequest struct {
Email string `json:"email" binding:"required"`
@@ -55,91 +58,91 @@ type AnswerQuizRequest struct {
type (
PersonalityAndPreferenceCVRequest struct {
AccountID int64 `json:"-"`
- PositiveTraits *string `json:"positive_traits"` // sifat positif
- NegativeTraits *string `json:"negative_traits"` // sifat negatif
- Hobbies *string `json:"hobbies"` // hobi
- LifeGoals *string `json:"life_goals"` // target hidup
- DailyActivities *string `json:"daily_activities"` // kegiatan sehari-hari
- LeisureActivities *string `json:"leisure_activities"` // kegiatan waktu luang
- Likes *string `json:"likes"` // hal yang disukai
- Dislikes *string `json:"dislikes"` // hal yang tidak disukai
- StressHandling *string `json:"stress_handling"` // cara mengatasi stres
- AngerTriggers *string `json:"anger_triggers"` // pemicu amarah
- FavoriteFoodAndDrinks *string `json:"favorite_food_and_drinks"` // makanan dan minuman favorit
- CanCook *bool `json:"can_cook"` // bisa memasak
- TypesOfDishesCooked *string `json:"types_of_dishes_cooked"` // jenis masakan yang bisa dimasak
- MonthlyExpenses *string `json:"monthly_expenses"` // pengeluaran per bulan
+ PositiveTraits *string `json:"positive_traits"` // sifat positif
+ NegativeTraits *string `json:"negative_traits"` // sifat negatif
+ Hobbies *string `json:"hobbies"` // hobi
+ LifeGoals *string `json:"life_goals"` // target hidup
+ DailyActivities *string `json:"daily_activities"` // kegiatan sehari-hari
+ LeisureActivities *string `json:"leisure_activities"` // kegiatan waktu luang
+ Likes *string `json:"likes"` // hal yang disukai
+ Dislikes *string `json:"dislikes"` // hal yang tidak disukai
+ StressHandling *string `json:"stress_handling"` // cara mengatasi stres
+ AngerTriggers *string `json:"anger_triggers"` // pemicu amarah
+ FavoriteFoodAndDrinks *string `json:"favorite_food_and_drinks"` // makanan dan minuman favorit
+ CanCook *bool `json:"can_cook"` // bisa memasak
+ TypesOfDishesCooked *string `json:"types_of_dishes_cooked"` // jenis masakan yang bisa dimasak
+ MonthlyExpenses *string `json:"monthly_expenses" validate:"monthly_expenses"` // pengeluaran per bulan
}
FamilyMemberRequest struct {
AccountID int64 `json:"-"`
- Role *string `json:"role"` // Peran dalam keluarga
- Status *string `json:"status"` // Status (Hidup, Wafat)
- Religion *string `json:"religion"` // Agama
- Job *string `json:"job"` // Pekerjaan
- LastEducation *string `json:"last_education"` // Pendidikan terakhir
- Age *int `json:"age"` // Usia
+ Role *string `json:"role" validate:"family_role"` // Peran dalam keluarga
+ Status *string `json:"status" validate:"life_status"` // Status (Hidup, Wafat)
+ Religion *string `json:"religion" validate:"religion"` // Agama
+ Job *string `json:"job"` // Pekerjaan
+ LastEducation *string `json:"last_education" validate:"last_education"` // Pendidikan terakhir
+ Age *int `json:"age"` // Usia
}
PhysicalAndHealthRequest struct {
AccountID int64 `json:"-"`
- HeightInCm *int `json:"height_cm"` // Tinggi badan dalam satuan sentimeter
- WeightInKg *int `json:"weight_kg"` // Berat badan dalam satuan kilogram
- BodyShape *string `json:"body_shape"` // Bentuk tubuh
- SkinColor *string `json:"skin_color"` // Warna kulit
- HairType *string `json:"hair_type"` // Tipe rambut
- MedicalHistory *string `json:"medical_history"` // Riwayat penyakit
- PhysicalDisorder *string `json:"physical_disorder"` // Cacat fisik
- PhysicalTraits *string `json:"physical_traits"` // Ciri khas fisik
+ HeightInCm *int `json:"height_cm"` // Tinggi badan dalam satuan sentimeter
+ WeightInKg *int `json:"weight_kg"` // Berat badan dalam satuan kilogram
+ BodyShape *string `json:"body_shape" validate:"body_shape"` // Bentuk tubuh
+ SkinColor *string `json:"skin_color" validate:"skin_color"` // Warna kulit
+ HairType *string `json:"hair_type" validate:"hair_type"` // Tipe rambut
+ MedicalHistory *string `json:"medical_history"` // Riwayat penyakit
+ PhysicalDisorder *string `json:"physical_disorder"` // Cacat fisik
+ PhysicalTraits *string `json:"physical_traits"` // Ciri khas fisik
}
AccountDetailsRequest struct {
AccountID int64 `json:"-"`
FullName *string `json:"full_name"`
- Gender *string `json:"gender"`
+ Gender *string `json:"gender" validate:"gender"`
DateOfBirth *time.Time `json:"date_of_birth"`
PlaceOfBirth *string `json:"place_of_birth"`
Domicile *string `json:"domicile"`
- MaritalStatus *string `json:"marital_status"`
- LastEducation *string `json:"last_education"`
+ MaritalStatus *string `json:"marital_status" validate:"marital_status"`
+ LastEducation *string `json:"last_education" validate:"last_education"`
LastJob *string `json:"last_job"`
}
WorshipAndReligiousUnderstandingRequest struct {
- AccountID int64 `json:"-"`
- ObligatoryPrayer *string `json:"obligatory_prayer"` // sholat_wajib_5_waktu
- CongregationalPrayer *string `json:"congregational_prayer"` // sholat_berjamaah_di_masjid
- TahajjudPrayer *string `json:"tahajjud_prayer"` // sholat_tahajud
- DhuhaPrayer *string `json:"dhuha_prayer"` // sholat_dhuha
- QuranMemorization *string `json:"quran_memorization"` // hafalan_alquran
- QuranReadingAbility *string `json:"quran_reading_ability"` // kemampuan_baca_alquran
- DaudFasting *string `json:"daud_fasting"` // puasa_daud
- AyyamulBidhFasting *string `json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
- HajjOrUmrah *string `json:"hajj_or_umrah"` // ibadah_haji_umroh
- ListeningToMusic *string `json:"listening_to_music"` // mendengarkan_musik
- OpinionOnIkhtilat *string `json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
- OpinionOnTouchingNonMahram *string `json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
- OpinionOnVeil *string `json:"opinion_on_veil"` // pendapat_tentang_cadar
- WeeklyReligiousStudies *string `json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
- FollowedUstadz *string `json:"followed_ustadz"` // ustadz_yang_diikuti
+ AccountID int64 `json:"-"`
+ ObligatoryPrayer *string `json:"obligatory_prayer"` // sholat_wajib_5_waktu
+ CongregationalPrayer *string `json:"congregational_prayer"` // sholat_berjamaah_di_masjid
+ TahajjudPrayer *string `json:"tahajjud_prayer"` // sholat_tahajud
+ DhuhaPrayer *string `json:"dhuha_prayer"` // sholat_dhuha
+ QuranMemorization *string `json:"quran_memorization"` // hafalan_alquran
+ QuranReadingAbility *string `json:"quran_reading_ability" validate:"quran_reading_ability"` // kemampuan_baca_alquran
+ DaudFasting *string `json:"daud_fasting"` // puasa_daud
+ AyyamulBidhFasting *string `json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
+ HajjOrUmrah pq.StringArray `json:"hajj_or_umrah"` // ibadah_haji_umroh
+ ListeningToMusic *string `json:"listening_to_music"` // mendengarkan_musik
+ OpinionOnIkhtilat *string `json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
+ OpinionOnTouchingNonMahram *string `json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
+ OpinionOnVeil *string `json:"opinion_on_veil"` // pendapat_tentang_cadar
+ WeeklyReligiousStudies *string `json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
+ FollowedUstadz *string `json:"followed_ustadz"` // ustadz_yang_diikuti
}
EducationRequest struct {
AccountID int64 `json:"account_id"`
- LastEducation *string `json:"last_education"` // pendidikan terakhir
- EducationInstitute *string `json:"education_institute"` // institusi pendidikan
- EducationMajor *string `json:"education_major"` // jurusan pendidikan
- YearStart *int `json:"year_start"` // tahun masuk
- YearGraduate *int `json:"year_graduate"` // tahun lulus
+ LastEducation *string `json:"last_education" validate:"last_education"` // pendidikan terakhir
+ EducationInstitute *string `json:"education_institute"` // institusi pendidikan
+ EducationMajor *string `json:"education_major"` // jurusan pendidikan
+ YearStart *int `json:"year_start"` // tahun masuk
+ YearGraduate *int `json:"year_graduate"` // tahun lulus
}
JobRequest struct {
- AccountID int64 `json:"account_id"`
- InstitutionName *string `json:"institution_name"` // nama instansi
- CurrentJob *string `json:"current_job"` // pekerjaan saat ini
- YearStartedWorking *int `json:"year_started_working"` // tahun mulai bekerja
- MonthlyIncome *string `json:"monthly_income"` // penghasilan per bulan
- IncomeSources *string `json:"income_sources"` // sumber penghasilan
+ AccountID int64 `json:"account_id"`
+ InstitutionName *string `json:"institution_name"` // nama instansi
+ CurrentJob *string `json:"current_job"` // pekerjaan saat ini
+ YearStartedWorking *int `json:"year_started_working"` // tahun mulai bekerja
+ MonthlyIncome *string `json:"monthly_income" validate:"monthly_income"` // penghasilan per bulan
+ IncomeSources pq.StringArray `json:"income_sources"` // sumber penghasilan
}
AchievementRequest struct {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/list_quiz_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/list_quiz_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..f8dcc3c971915d95482e3eae6e75631b589b52b9
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/list_quiz_controller.go
@@ -0,0 +1,23 @@
+package controller
+
+import (
+ "strconv"
+
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func List(c *gin.Context) {
+ quizList := services.QuizListService{}
+ quizListController := controller.Controller[any, models.Academy, []models.Quiz]{
+ Service: &quizList.Service,
+ }
+ quizListController.HeaderParse(c, func() {
+ academy_id, _ := strconv.Atoi(c.Param("academy_id"))
+ quizList.Constructor.ID = uint(academy_id)
+ quizList.Retrieve()
+ quizListController.Response(c)
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
index fd18921eaf4b8e2155384c78e6c16ab795d5a9a9..3d26451156d4c811028f96036e214f6aa11d6f9f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
@@ -4,11 +4,15 @@ go 1.24.0
require (
github.com/gin-gonic/gin v1.10.0
+ github.com/go-playground/locales v0.14.1
+ github.com/go-playground/universal-translator v0.18.1
+ github.com/go-playground/validator/v10 v10.25.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/gosimple/slug v1.15.0
github.com/hibiken/asynq v0.25.1
github.com/joho/godotenv v1.5.1
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
+ github.com/lib/pq v1.10.9
github.com/redis/go-redis/v9 v9.7.0
github.com/satori/go.uuid v1.2.0
golang.org/x/crypto v0.36.0
@@ -31,9 +35,6 @@ require (
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
- github.com/go-playground/locales v0.14.1 // indirect
- github.com/go-playground/universal-translator v0.18.1 // indirect
- github.com/go-playground/validator/v10 v10.25.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/quiz_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/quiz_route.go
index e38d28dfc2b2fe029f120313a66e17bf414fe35c..2d7b62e6b8f2400497024257c2ec4032769e6239 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/quiz_route.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/quiz_route.go
@@ -9,6 +9,7 @@ import (
func QuizRoute(router *gin.Engine) {
routerGroup := router.Group("/api/v1/quiz")
{
+ routerGroup.GET("/:academy_id/list", middleware.AuthUser, QuizController.List)
routerGroup.POST("/:academy_id/:quiz_id/attempt", middleware.AuthUser, QuizController.Attempt)
routerGroup.GET("/:academy_id/:quiz_id/question", middleware.AuthUser, QuizController.Question)
routerGroup.PUT("/:academy_id/:quiz_id/choose-answer", middleware.AuthUser, QuizController.Answer)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go
index 249b018886c4aac504af16e36f34498de64d6e7f..0e56c168ec59eea7398ec38854104ade16a10b9f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go
@@ -2,12 +2,12 @@ package services
import (
"errors"
- "fmt"
"time"
"api.qobiltu.id/models"
"api.qobiltu.id/repositories"
)
+
type AttemptQuizService struct {
Service[models.Quiz, models.QuizAttempt]
}
@@ -16,6 +16,10 @@ type SubmitQuizService struct {
Service[models.QuizAttempt, models.QuizResultResponse]
}
+type QuizListService struct {
+ Service[models.Academy, []models.Quiz]
+}
+
func AuthorizeQuizwithAcademy(s *AttemptQuizService, next func()) {
academyRepo := repositories.GetAcademyByID(s.Constructor.AcademyID)
s.Error = academyRepo.RowsError
@@ -77,8 +81,8 @@ func (s *AttemptQuizService) Validate(userID uint, next func(latestAttemptRepo r
}
s.Error = errors.Join(allAttemptsRepo.RowsError, quizRepo.RowsError)
CheckUserAttemptLimit(s, allAttemptsRepo, quizRepo, userID, func() {
- fmt.Println("accountID", userID)
- fmt.Println("quizID", s.Constructor.ID)
+ // fmt.Println("accountID", userID)
+ // fmt.Println("quizID", s.Constructor.ID)
latestAttemptRepo := repositories.GetUserLastAttempt(userID, s.Constructor.ID)
if latestAttemptRepo.NoRecord {
s.Exception.DataNotFound = true
@@ -125,6 +129,7 @@ func (s *AttemptQuizService) Create(userID uint) {
}
})
}
+
func (s *SubmitQuizService) Create(userID uint) {
quizAttemptRepo := repositories.GetAttemptById(s.Constructor.ID)
if quizAttemptRepo.NoRecord {
@@ -151,3 +156,15 @@ func (s *SubmitQuizService) Create(userID uint) {
return
}
+
+func (s *QuizListService) Retrieve() {
+ quizRepo := repositories.GetQuizbyAcademyId(s.Constructor.ID)
+ s.Error = quizRepo.RowsError
+ if quizRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no quiz with given academy ID!"
+ return
+ }
+ s.Result = quizRepo.Result
+ return
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
index 0d10ff95c9537f77159b809f6f6b3a3b1822615f..3a1837085cd191fc54d996d58ae48ef41df229d0 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
@@ -74,6 +74,9 @@ func AutoMigrateAll(db *gorm.DB) {
&models.FamilyMemberCV{},
&models.PhysicalAndHealthCV{},
&models.WorshipAndReligiousUnderstandingCV{},
+ &models.EducationCV{},
+ &models.JobCV{},
+ &models.AchievementCV{},
)
if err != nil {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go
index ab2e665cc2b2795d9db10b42e5f85395f7e04f84..3efeb5c4763ed90e6ea6becfff1833c42d9a10e8 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go
@@ -28,6 +28,24 @@ type CVController interface {
SaveWorshipAndReligiousUnderstanding(ctx *gin.Context)
GetWorshipAndReligiousUnderstanding(ctx *gin.Context)
+
+ CreateEducation(ctx *gin.Context)
+ UpdateEducation(ctx *gin.Context)
+ ListEducation(ctx *gin.Context)
+ GetEducation(ctx *gin.Context)
+ DeleteEducation(ctx *gin.Context)
+
+ CreateJob(ctx *gin.Context)
+ UpdateJob(ctx *gin.Context)
+ ListJob(ctx *gin.Context)
+ GetJob(ctx *gin.Context)
+ DeleteJob(ctx *gin.Context)
+
+ CreateAchievement(ctx *gin.Context)
+ UpdateAchievement(ctx *gin.Context)
+ ListAchievement(ctx *gin.Context)
+ GetAchievement(ctx *gin.Context)
+ DeleteAchievement(ctx *gin.Context)
}
type cvController struct {
@@ -265,3 +283,273 @@ func (c *cvController) GetWorshipAndReligiousUnderstanding(ctx *gin.Context) {
response.HandleSuccess(ctx, http.StatusOK, "Get worship and religious understanding success", res, nil)
}
+
+// --- Education ---
+func (c *cvController) CreateEducation(ctx *gin.Context) {
+ var req models.EducationRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ accountData := middleware.GetAccountData(ctx)
+ req.AccountID = int64(accountData.UserID)
+
+ res, err := c.cvService.CreateEducation(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Education created successfully", res, nil)
+}
+
+func (c *cvController) UpdateEducation(ctx *gin.Context) {
+ idStr := ctx.Param("id")
+ id, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ var req models.EducationRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ res, err := c.cvService.UpdateEducation(ctx, id, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Education updated successfully", res, nil)
+}
+
+func (c *cvController) ListEducation(ctx *gin.Context) {
+ accountData := middleware.GetAccountData(ctx)
+ accountID := int64(accountData.UserID)
+
+ res, err := c.cvService.ListEducation(ctx, accountID)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "List of education retrieved successfully", res, nil)
+}
+
+func (c *cvController) GetEducation(ctx *gin.Context) {
+ idStr := ctx.Param("id")
+ id, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ res, err := c.cvService.GetEducation(ctx, id)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Get education success", res, nil)
+}
+
+func (c *cvController) DeleteEducation(ctx *gin.Context) {
+ idStr := ctx.Param("id")
+ id, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ err = c.cvService.DeleteEducation(ctx, id)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Education deleted successfully", nil, nil)
+}
+
+// --- Job ---
+func (c *cvController) CreateJob(ctx *gin.Context) {
+ var req models.JobRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ accountData := middleware.GetAccountData(ctx)
+ req.AccountID = int64(accountData.UserID)
+
+ res, err := c.cvService.CreateJob(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Job created successfully", res, nil)
+}
+
+func (c *cvController) UpdateJob(ctx *gin.Context) {
+ idStr := ctx.Param("id")
+ id, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ var req models.JobRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ res, err := c.cvService.UpdateJob(ctx, id, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Job updated successfully", res, nil)
+}
+
+func (c *cvController) ListJob(ctx *gin.Context) {
+ accountData := middleware.GetAccountData(ctx)
+ accountID := int64(accountData.UserID)
+
+ res, err := c.cvService.ListJob(ctx, accountID)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "List of jobs retrieved successfully", res, nil)
+}
+
+func (c *cvController) GetJob(ctx *gin.Context) {
+ idStr := ctx.Param("id")
+ id, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ res, err := c.cvService.GetJob(ctx, id)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Get job success", res, nil)
+}
+
+func (c *cvController) DeleteJob(ctx *gin.Context) {
+ idStr := ctx.Param("id")
+ id, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ err = c.cvService.DeleteJob(ctx, id)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Job deleted successfully", nil, nil)
+}
+
+// --- Achievement ---
+func (c *cvController) CreateAchievement(ctx *gin.Context) {
+ var req models.AchievementRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ accountData := middleware.GetAccountData(ctx)
+ req.AccountID = int64(accountData.UserID)
+
+ res, err := c.cvService.CreateAchievement(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Achievement created successfully", res, nil)
+}
+
+func (c *cvController) UpdateAchievement(ctx *gin.Context) {
+ idStr := ctx.Param("id")
+ id, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ var req models.AchievementRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ res, err := c.cvService.UpdateAchievement(ctx, id, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Achievement updated successfully", res, nil)
+}
+
+func (c *cvController) ListAchievement(ctx *gin.Context) {
+ accountData := middleware.GetAccountData(ctx)
+ accountID := int64(accountData.UserID)
+
+ res, err := c.cvService.ListAchievement(ctx, accountID)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "List of achievements retrieved successfully", res, nil)
+}
+
+func (c *cvController) GetAchievement(ctx *gin.Context) {
+ idStr := ctx.Param("id")
+ id, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ res, err := c.cvService.GetAchievement(ctx, id)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Get achievement success", res, nil)
+}
+
+func (c *cvController) DeleteAchievement(ctx *gin.Context) {
+ idStr := ctx.Param("id")
+ id, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ err = c.cvService.DeleteAchievement(ctx, id)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Achievement deleted successfully", nil, nil)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
index 8b8dcf092d99d8a06895c665e173c4e8fe39e014..0b64fec3b839d0f5b64a38ced137eea99c4cef82 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -155,6 +155,7 @@ type Question struct {
type Quiz struct {
ID uint `gorm:"primaryKey" json:"id"`
AcademyID uint `json:"academy_id"`
+ Slug string `json:"slug" gorm:"uniqueIndex" `
Title string `json:"title"`
Description string `json:"description"`
AttemptLimit int `json:"attempt_limit"`
@@ -261,6 +262,41 @@ type (
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
}
+
+ EducationCV struct {
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
+ LastEducation *string `gorm:"column:last_education" json:"last_education"` // pendidikan terakhir
+ EducationInstitute *string `gorm:"column:education_institute" json:"education_institute"` // institusi pendidikan
+ EducationMajor *string `gorm:"column:education_major" json:"education_major"` // jurusan pendidikan
+ YearStart *int `gorm:"column:year_start" json:"year_start"` // tahun masuk
+ YearGraduate *int `gorm:"column:year_graduate" json:"year_graduate"` // tahun lulus
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
+ }
+
+ JobCV struct {
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
+ InstitutionName *string `gorm:"column:institution_name" json:"institution_name"` // nama instansi
+ CurrentJob *string `gorm:"column:current_job" json:"current_job"` // pekerjaan saat ini
+ YearStartedWorking *int `gorm:"column:year_started_working" json:"year_started_working"` // tahun mulai bekerja
+ MonthlyIncome *string `gorm:"column:monthly_income" json:"monthly_income"` // penghasilan per bulan
+ IncomeSources *string `gorm:"column:income_sources" json:"income_sources"` // sumber penghasilan
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
+ }
+
+ AchievementCV struct {
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` // id
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id"` // id akun
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"` // relasi ke akun
+ AchievementOrAward *string `gorm:"column:achievement_or_award" json:"achievement_or_award"` // prestasi atau penghargaan
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // tanggal dibuat
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // tanggal diperbarui
+ }
)
// Gorm table name settings
@@ -290,3 +326,6 @@ func (PhysicalAndHealthCV) TableName() string { return "physical_and_heal
func (WorshipAndReligiousUnderstandingCV) TableName() string {
return "worship_and_religious_understanding_cv"
}
+func (EducationCV) TableName() string { return "education_cv" }
+func (JobCV) TableName() string { return "job_cv" }
+func (AchievementCV) TableName() string { return "achievement_cv" }
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
index 218277b337490138d5af499ecb00ef23027cac51..803ef615e812f4739afaad61afd8f2712c9ea72a 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
@@ -123,4 +123,27 @@ type (
WeeklyReligiousStudies *string `json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
FollowedUstadz *string `json:"followed_ustadz"` // ustadz_yang_diikuti
}
+
+ EducationRequest struct {
+ AccountID int64 `json:"account_id"`
+ LastEducation *string `json:"last_education"` // pendidikan terakhir
+ EducationInstitute *string `json:"education_institute"` // institusi pendidikan
+ EducationMajor *string `json:"education_major"` // jurusan pendidikan
+ YearStart *int `json:"year_start"` // tahun masuk
+ YearGraduate *int `json:"year_graduate"` // tahun lulus
+ }
+
+ JobRequest struct {
+ AccountID int64 `json:"account_id"`
+ InstitutionName *string `json:"institution_name"` // nama instansi
+ CurrentJob *string `json:"current_job"` // pekerjaan saat ini
+ YearStartedWorking *int `json:"year_started_working"` // tahun mulai bekerja
+ MonthlyIncome *string `json:"monthly_income"` // penghasilan per bulan
+ IncomeSources *string `json:"income_sources"` // sumber penghasilan
+ }
+
+ AchievementRequest struct {
+ AccountID int64 `json:"account_id"`
+ AchievementOrAward *string `json:"achievement_or_award"` // prestasi atau penghargaan
+ }
)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/cv_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/cv_repository.go
index 067132cd39bdad688a06f65a3364626b7effb28f..833e4d1a44d3e962231c0ef636a934361635a883 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/cv_repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/cv_repository.go
@@ -23,6 +23,21 @@ type CVRepository interface {
SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingCV) (*models.WorshipAndReligiousUnderstandingCV, error)
GetWorshipAndReligiousUnderstandingByAccountID(ctx context.Context, accountID int64) (*models.WorshipAndReligiousUnderstandingCV, error)
+
+ SaveEducation(ctx context.Context, req *models.EducationCV) (*models.EducationCV, error)
+ ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error)
+ GetEducation(ctx context.Context, id int64) (*models.EducationCV, error)
+ DeleteEducation(ctx context.Context, id int64) error
+
+ SaveJob(ctx context.Context, req *models.JobCV) (*models.JobCV, error)
+ ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error)
+ GetJob(ctx context.Context, id int64) (*models.JobCV, error)
+ DeleteJob(ctx context.Context, id int64) error
+
+ SaveAchievement(ctx context.Context, req *models.AchievementCV) (*models.AchievementCV, error)
+ ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error)
+ GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error)
+ DeleteAchievement(ctx context.Context, id int64) error
}
type cvRepository struct {
@@ -124,3 +139,105 @@ func (r *cvRepository) GetWorshipAndReligiousUnderstandingByAccountID(ctx contex
}
return &worshipAndReligiousUnderstanding, nil
}
+
+// SaveEducation menyimpan atau memperbarui data pendidikan ke database
+func (r *cvRepository) SaveEducation(ctx context.Context, req *models.EducationCV) (*models.EducationCV, error) {
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
+}
+
+// ListEducation mengambil daftar data pendidikan berdasarkan account_id
+func (r *cvRepository) ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error) {
+ var educations []models.EducationCV
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&educations).Error; err != nil {
+ return nil, err
+ }
+ return educations, nil
+}
+
+// GetEducation mengambil satu data pendidikan berdasarkan id
+func (r *cvRepository) GetEducation(ctx context.Context, id int64) (*models.EducationCV, error) {
+ var education models.EducationCV
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&education).Error; err != nil {
+ return nil, err
+ }
+ return &education, nil
+}
+
+// DeleteEducation menghapus data pendidikan berdasarkan id
+func (r *cvRepository) DeleteEducation(ctx context.Context, id int64) error {
+ if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.EducationCV{}).Error; err != nil {
+ return err
+ }
+ return nil
+}
+
+// SaveJob menyimpan atau memperbarui data pekerjaan ke database
+func (r *cvRepository) SaveJob(ctx context.Context, req *models.JobCV) (*models.JobCV, error) {
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
+}
+
+// ListJob mengambil daftar data pekerjaan berdasarkan account_id
+func (r *cvRepository) ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error) {
+ var jobs []models.JobCV
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&jobs).Error; err != nil {
+ return nil, err
+ }
+ return jobs, nil
+}
+
+// GetJob mengambil satu data pekerjaan berdasarkan id
+func (r *cvRepository) GetJob(ctx context.Context, id int64) (*models.JobCV, error) {
+ var job models.JobCV
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&job).Error; err != nil {
+ return nil, err
+ }
+ return &job, nil
+}
+
+// DeleteJob menghapus data pekerjaan berdasarkan id
+func (r *cvRepository) DeleteJob(ctx context.Context, id int64) error {
+ if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.JobCV{}).Error; err != nil {
+ return err
+ }
+ return nil
+}
+
+// SaveAchievement menyimpan atau memperbarui data prestasi ke database
+func (r *cvRepository) SaveAchievement(ctx context.Context, req *models.AchievementCV) (*models.AchievementCV, error) {
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
+}
+
+// ListAchievement mengambil daftar data prestasi berdasarkan account_id
+func (r *cvRepository) ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error) {
+ var achievements []models.AchievementCV
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&achievements).Error; err != nil {
+ return nil, err
+ }
+ return achievements, nil
+}
+
+// GetAchievement mengambil satu data prestasi berdasarkan id
+func (r *cvRepository) GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error) {
+ var achievement models.AchievementCV
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&achievement).Error; err != nil {
+ return nil, err
+ }
+ return &achievement, nil
+}
+
+// DeleteAchievement menghapus data prestasi berdasarkan id
+func (r *cvRepository) DeleteAchievement(ctx context.Context, id int64) error {
+ if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.AchievementCV{}).Error; err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/cv_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/cv_route.go
index eed8dc1644450ff4156a1d30f6636e2d60c53d5b..4a8164d910db1b69dea62b75373703c0563cecce 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/cv_route.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/cv_route.go
@@ -22,5 +22,23 @@ func (s *Server) CVRoute() {
routerGroup.POST("/worship-and-religious-understandings", s.cvController.SaveWorshipAndReligiousUnderstanding)
routerGroup.GET("/worship-and-religious-understandings", s.cvController.GetWorshipAndReligiousUnderstanding)
+
+ routerGroup.POST("/educations", s.cvController.CreateEducation)
+ routerGroup.GET("/educations", s.cvController.ListEducation)
+ routerGroup.GET("/educations/:id", s.cvController.GetEducation)
+ routerGroup.PUT("/educations/:id", s.cvController.UpdateEducation)
+ routerGroup.DELETE("/educations/:id", s.cvController.DeleteEducation)
+
+ routerGroup.POST("/jobs", s.cvController.CreateJob)
+ routerGroup.GET("/jobs", s.cvController.ListJob)
+ routerGroup.GET("/jobs/:id", s.cvController.GetJob)
+ routerGroup.PUT("/jobs/:id", s.cvController.UpdateJob)
+ routerGroup.DELETE("/jobs/:id", s.cvController.DeleteJob)
+
+ routerGroup.POST("/achievements", s.cvController.CreateAchievement)
+ routerGroup.GET("/achievements", s.cvController.ListAchievement)
+ routerGroup.GET("/achievements/:id", s.cvController.GetAchievement)
+ routerGroup.PUT("/achievements/:id", s.cvController.UpdateAchievement)
+ routerGroup.DELETE("/achievements/:id", s.cvController.DeleteAchievement)
}
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
index 2a1a89efab3cd99b9559c3b051493212a42b5672..7684ad8bd92d867976abbe66dd1f0e9de0acf0f5 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
@@ -27,6 +27,24 @@ type CVService interface {
SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error)
GetWorshipAndReligiousUnderstanding(ctx context.Context, id int64) (*models.WorshipAndReligiousUnderstandingCV, error)
+
+ CreateEducation(ctx context.Context, req *models.EducationRequest) (*models.EducationCV, error)
+ UpdateEducation(ctx context.Context, id int64, req *models.EducationRequest) (*models.EducationCV, error)
+ ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error)
+ GetEducation(ctx context.Context, id int64) (*models.EducationCV, error)
+ DeleteEducation(ctx context.Context, id int64) error
+
+ CreateJob(ctx context.Context, req *models.JobRequest) (*models.JobCV, error)
+ UpdateJob(ctx context.Context, id int64, req *models.JobRequest) (*models.JobCV, error)
+ ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error)
+ GetJob(ctx context.Context, id int64) (*models.JobCV, error)
+ DeleteJob(ctx context.Context, id int64) error
+
+ CreateAchievement(ctx context.Context, req *models.AchievementRequest) (*models.AchievementCV, error)
+ UpdateAchievement(ctx context.Context, id int64, req *models.AchievementRequest) (*models.AchievementCV, error)
+ ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error)
+ GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error)
+ DeleteAchievement(ctx context.Context, id int64) error
}
type cvService struct {
@@ -275,3 +293,152 @@ func (s *cvService) GetWorshipAndReligiousUnderstanding(ctx context.Context, id
}
return res, nil
}
+
+func (s *cvService) CreateEducation(ctx context.Context, req *models.EducationRequest) (*models.EducationCV, error) {
+ edu := &models.EducationCV{
+ AccountID: req.AccountID,
+ LastEducation: req.LastEducation,
+ EducationInstitute: req.EducationInstitute,
+ EducationMajor: req.EducationMajor,
+ YearStart: req.YearStart,
+ YearGraduate: req.YearGraduate,
+ }
+
+ res, err := s.cvRepository.SaveEducation(ctx, edu)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menambahkan data pendidikan")
+ }
+
+ return res, nil
+}
+
+func (s *cvService) UpdateEducation(ctx context.Context, id int64, req *models.EducationRequest) (*models.EducationCV, error) {
+ edu, err := s.cvRepository.GetEducation(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
+ }
+
+ edu.LastEducation = req.LastEducation
+ edu.EducationInstitute = req.EducationInstitute
+ edu.EducationMajor = req.EducationMajor
+ edu.YearStart = req.YearStart
+ edu.YearGraduate = req.YearGraduate
+
+ res, err := s.cvRepository.SaveEducation(ctx, edu)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal memperbarui data pendidikan")
+ }
+ return res, nil
+}
+
+func (s *cvService) ListEducation(ctx context.Context, accountID int64) ([]models.EducationCV, error) {
+ return s.cvRepository.ListEducation(ctx, accountID)
+}
+
+func (s *cvService) GetEducation(ctx context.Context, id int64) (*models.EducationCV, error) {
+ edu, err := s.cvRepository.GetEducation(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data pendidikan tidak ditemukan")
+ }
+ return edu, nil
+}
+
+func (s *cvService) DeleteEducation(ctx context.Context, id int64) error {
+ return s.cvRepository.DeleteEducation(ctx, id)
+}
+
+func (s *cvService) CreateJob(ctx context.Context, req *models.JobRequest) (*models.JobCV, error) {
+ job := &models.JobCV{
+ AccountID: req.AccountID,
+ InstitutionName: req.InstitutionName,
+ CurrentJob: req.CurrentJob,
+ YearStartedWorking: req.YearStartedWorking,
+ MonthlyIncome: req.MonthlyIncome,
+ IncomeSources: req.IncomeSources,
+ }
+ res, err := s.cvRepository.SaveJob(ctx, job)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menambahkan data pekerjaan")
+ }
+ return res, nil
+}
+
+func (s *cvService) UpdateJob(ctx context.Context, id int64, req *models.JobRequest) (*models.JobCV, error) {
+ job, err := s.cvRepository.GetJob(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
+ }
+
+ job.InstitutionName = req.InstitutionName
+ job.CurrentJob = req.CurrentJob
+ job.YearStartedWorking = req.YearStartedWorking
+ job.MonthlyIncome = req.MonthlyIncome
+ job.IncomeSources = req.IncomeSources
+
+ res, err := s.cvRepository.SaveJob(ctx, job)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal memperbarui data pekerjaan")
+ }
+ return res, nil
+}
+
+func (s *cvService) ListJob(ctx context.Context, accountID int64) ([]models.JobCV, error) {
+ return s.cvRepository.ListJob(ctx, accountID)
+}
+
+func (s *cvService) GetJob(ctx context.Context, id int64) (*models.JobCV, error) {
+ job, err := s.cvRepository.GetJob(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data pekerjaan tidak ditemukan")
+ }
+ return job, nil
+}
+
+func (s *cvService) DeleteJob(ctx context.Context, id int64) error {
+ return s.cvRepository.DeleteJob(ctx, id)
+}
+
+func (s *cvService) CreateAchievement(ctx context.Context, req *models.AchievementRequest) (*models.AchievementCV, error) {
+ ach := &models.AchievementCV{
+ AccountID: req.AccountID,
+ AchievementOrAward: req.AchievementOrAward,
+ }
+ res, err := s.cvRepository.SaveAchievement(ctx, ach)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menambahkan data prestasi")
+ }
+
+ return res, nil
+}
+
+func (s *cvService) UpdateAchievement(ctx context.Context, id int64, req *models.AchievementRequest) (*models.AchievementCV, error) {
+ ach, err := s.cvRepository.GetAchievement(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
+ }
+
+ ach.AchievementOrAward = req.AchievementOrAward
+
+ res, err := s.cvRepository.SaveAchievement(ctx, ach)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal memperbarui data prestasi")
+ }
+
+ return res, nil
+}
+
+func (s *cvService) ListAchievement(ctx context.Context, accountID int64) ([]models.AchievementCV, error) {
+ return s.cvRepository.ListAchievement(ctx, accountID)
+}
+
+func (s *cvService) GetAchievement(ctx context.Context, id int64) (*models.AchievementCV, error) {
+ ach, err := s.cvRepository.GetAchievement(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data prestasi tidak ditemukan")
+ }
+ return ach, nil
+}
+
+func (s *cvService) DeleteAchievement(ctx context.Context, id int64) error {
+ return s.cvRepository.DeleteAchievement(ctx, id)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..ab2e665cc2b2795d9db10b42e5f85395f7e04f84
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/cv/cv_controller.go
@@ -0,0 +1,267 @@
+package cv_controller
+
+import (
+ "api.qobiltu.id/middleware"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/response"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+ "net/http"
+ "strconv"
+)
+
+type CVController interface {
+ SaveAccountDetails(ctx *gin.Context)
+ GetAccountDetails(ctx *gin.Context)
+
+ SavePersonalityAndPreference(ctx *gin.Context)
+ GetPersonalityAndPreference(ctx *gin.Context)
+
+ CreateFamilyMember(ctx *gin.Context)
+ UpdateFamilyMember(ctx *gin.Context)
+ ListFamilyMember(ctx *gin.Context)
+ GetFamilyMember(ctx *gin.Context)
+ DeleteFamilyMember(ctx *gin.Context)
+
+ SavePhysicalAndHealth(ctx *gin.Context)
+ GetPhysicalAndHealth(ctx *gin.Context)
+
+ SaveWorshipAndReligiousUnderstanding(ctx *gin.Context)
+ GetWorshipAndReligiousUnderstanding(ctx *gin.Context)
+}
+
+type cvController struct {
+ cvService services.CVService
+}
+
+func NewCVController(cvService services.CVService) CVController {
+ return &cvController{
+ cvService: cvService,
+ }
+}
+
+// --- Account Details ---
+func (c *cvController) SaveAccountDetails(ctx *gin.Context) {
+ var req models.AccountDetailsRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ accountData := middleware.GetAccountData(ctx)
+ req.AccountID = int64(accountData.UserID)
+
+ res, err := c.cvService.SaveAccountDetails(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Account details saved", res, nil)
+}
+
+func (c *cvController) GetAccountDetails(ctx *gin.Context) {
+ accountData := middleware.GetAccountData(ctx)
+ accountID := int64(accountData.UserID)
+
+ res, err := c.cvService.GetAccountDetails(ctx, accountID)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Get account details success", res, nil)
+}
+
+// --- Personality & Preference ---
+
+func (c *cvController) SavePersonalityAndPreference(ctx *gin.Context) {
+ var req models.PersonalityAndPreferenceCVRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ accountData := middleware.GetAccountData(ctx)
+ req.AccountID = int64(accountData.UserID)
+
+ res, err := c.cvService.SavePersonalityAndPreference(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Personality and Preference saved", res, nil)
+}
+
+func (c *cvController) GetPersonalityAndPreference(ctx *gin.Context) {
+ accountData := middleware.GetAccountData(ctx)
+ accountID := int64(accountData.UserID)
+
+ res, err := c.cvService.GetPersonalityAndPreference(ctx, accountID)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Get Personality and Preference success", res, nil)
+}
+
+// --- Family Member ---
+
+func (c *cvController) CreateFamilyMember(ctx *gin.Context) {
+ var req models.FamilyMemberRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ accountData := middleware.GetAccountData(ctx)
+ req.AccountID = int64(accountData.UserID)
+
+ res, err := c.cvService.CreateFamilyMember(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Family member saved", res, nil)
+}
+
+func (c *cvController) UpdateFamilyMember(ctx *gin.Context) {
+ idStr := ctx.Param("id")
+ id, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ var req models.FamilyMemberRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ res, err := c.cvService.UpdateFamilyMember(ctx, id, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Family member updated", res, nil)
+}
+
+func (c *cvController) ListFamilyMember(ctx *gin.Context) {
+ accountData := middleware.GetAccountData(ctx)
+ accountID := int64(accountData.UserID)
+
+ list, err := c.cvService.ListFamilyMember(ctx, accountID)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "List family members", list, nil)
+}
+
+func (c *cvController) GetFamilyMember(ctx *gin.Context) {
+ idStr := ctx.Param("id")
+ id, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ res, err := c.cvService.GetFamilyMember(ctx, id)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Get family member success", res, nil)
+}
+
+func (c *cvController) DeleteFamilyMember(ctx *gin.Context) {
+ idStr := ctx.Param("id")
+ id, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ err = c.cvService.DeleteFamilyMember(ctx, id)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Family member deleted", nil, nil)
+}
+
+// --- Physical and Health ---
+
+func (c *cvController) SavePhysicalAndHealth(ctx *gin.Context) {
+ var req models.PhysicalAndHealthRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ accountData := middleware.GetAccountData(ctx)
+ req.AccountID = int64(accountData.UserID)
+
+ res, err := c.cvService.SavePhysicalAndHealth(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Physical and health saved", res, nil)
+}
+
+func (c *cvController) GetPhysicalAndHealth(ctx *gin.Context) {
+ accountData := middleware.GetAccountData(ctx)
+ accountID := int64(accountData.UserID)
+
+ res, err := c.cvService.GetPhysicalAndHealth(ctx, accountID)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Get physical and health success", res, nil)
+}
+
+// --- Worship and Religious Understanding ---
+
+func (c *cvController) SaveWorshipAndReligiousUnderstanding(ctx *gin.Context) {
+ var req models.WorshipAndReligiousUnderstandingRequest
+ if err := ctx.ShouldBindJSON(&req); err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ accountData := middleware.GetAccountData(ctx)
+ req.AccountID = int64(accountData.UserID)
+
+ res, err := c.cvService.SaveWorshipAndReligiousUnderstanding(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Worship and religious understanding saved", res, nil)
+}
+
+func (c *cvController) GetWorshipAndReligiousUnderstanding(ctx *gin.Context) {
+ accountData := middleware.GetAccountData(ctx)
+ accountID := int64(accountData.UserID)
+
+ res, err := c.cvService.GetWorshipAndReligiousUnderstanding(ctx, accountID)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, http.StatusOK, "Get worship and religious understanding success", res, nil)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go
index 08f850c1f5991e30eabd8d66d364110d83dcfd75..ca2bc4602900073bf0407e8a415aeee73e703fc0 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go
@@ -1,21 +1,21 @@
-package user
-
-import (
- "api.qobiltu.id/controller"
- "api.qobiltu.id/models"
- "api.qobiltu.id/services"
- "github.com/gin-gonic/gin"
-)
-
-func Profile(c *gin.Context) {
- userProfile := services.UserProfileService{}
- userProfileController := controller.Controller[any, models.AccountDetails, models.UserProfileResponse]{
- Service: &userProfile.Service,
- }
- userProfileController.HeaderParse(c, func() {
- userProfileController.Service.Constructor.AccountID = uint(userProfileController.AccountData.UserID)
- userProfile.Retrieve()
- userProfileController.Response(c)
- },
- )
-}
+package user
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Profile(c *gin.Context) {
+ userProfile := services.UserProfileService{}
+ userProfileController := controller.Controller[any, models.AccountDetails, models.UserProfileResponse]{
+ Service: &userProfile.Service,
+ }
+ userProfileController.HeaderParse(c, func() {
+ userProfileController.Service.Constructor.AccountID = uint(userProfileController.AccountData.UserID)
+ userProfile.Retrieve()
+ userProfileController.Response(c)
+ },
+ )
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
index 058d97e7fea905feb469147c880d9141c25e0de3..6263f02ff8d6b1c33d9ec5bf86716b4f13ce6944 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
@@ -1,25 +1,25 @@
-package user
-
-import (
- "api.qobiltu.id/controller"
- "api.qobiltu.id/models"
- "api.qobiltu.id/services"
- "github.com/gin-gonic/gin"
-)
-
-func UpdateProfile(c *gin.Context) {
- userProfile := services.UserProfileService{}
- userUpdateProfileController := controller.Controller[models.AccountDetails, models.AccountDetails, models.UserProfileResponse]{
- Service: &userProfile.Service,
- }
-
- userUpdateProfileController.RequestJSON(c, func() {
- userUpdateProfileController.Service.Constructor = userUpdateProfileController.Request
- userUpdateProfileController.HeaderParse(c, func() {
- userUpdateProfileController.Service.Constructor.AccountID = uint(userUpdateProfileController.AccountData.UserID)
-
- })
- userProfile.Update()
- },
- )
-}
+package user
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func UpdateProfile(c *gin.Context) {
+ userProfile := services.UserProfileService{}
+ userUpdateProfileController := controller.Controller[models.AccountDetails, models.AccountDetails, models.UserProfileResponse]{
+ Service: &userProfile.Service,
+ }
+
+ userUpdateProfileController.RequestJSON(c, func() {
+ userUpdateProfileController.Service.Constructor = userUpdateProfileController.Request
+ userUpdateProfileController.HeaderParse(c, func() {
+ userUpdateProfileController.Service.Constructor.AccountID = userUpdateProfileController.AccountData.UserID
+
+ })
+ userProfile.Update()
+ },
+ )
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
index 0cceb9094856d6284d6321676b170a1222201063..b623ed382a9d0631977fb3e5be33584e3002788e 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
@@ -1,87 +1,87 @@
-package repositories
-
-import (
- "api.qobiltu.id/models"
-)
-
-func GetAccountbyEmail(email string) Repository[models.Account, models.Account] {
- repo := Construct[models.Account, models.Account](
- models.Account{Email: email},
- )
- repo.Transactions(
- WhereGivenConstructor[models.Account, models.Account],
- Find[models.Account, models.Account],
- )
- return *repo
-}
-
-func GetAllAccount() Repository[models.Account, []models.Account] {
- repo := Construct[models.Account, []models.Account](
- models.Account{},
- )
- repo.Transactions(
- Find[models.Account, []models.Account],
- )
- return *repo
-}
-func GetAccountById(accountId uint) Repository[models.Account, models.Account] {
- repo := Construct[models.Account, models.Account](
- models.Account{Id: accountId},
- )
- repo.Transactions(
- WhereGivenConstructor[models.Account, models.Account],
- Find[models.Account, models.Account],
- )
- return *repo
-}
-
-func UpdateAccount(account models.Account) Repository[models.Account, models.Account] {
- repo := Construct[models.Account, models.Account](
- account,
- )
- repo.Transaction.Save(&repo.Constructor)
- repo.Result = repo.Constructor
- return *repo
-}
-
-func GetDetailAccountById(accountId uint) Repository[models.AccountDetails, models.AccountDetails] {
- repo := Construct[models.AccountDetails, models.AccountDetails](
- models.AccountDetails{AccountID: accountId},
- )
-
- // fmt.Println("Account ID:", repo.Constructor.AccountID)
- repo.Transactions(
- WhereGivenConstructor[models.AccountDetails, models.AccountDetails],
- Find[models.AccountDetails, models.AccountDetails],
- )
- return *repo
-}
-
-func CreateAccount(account models.Account) Repository[models.Account, models.Account] {
- repo := Construct[models.Account, models.Account](
- account,
- )
- Create(repo)
- return *repo
-}
-
-func CreateAccountDetails(accountDetails models.AccountDetails) Repository[models.AccountDetails, models.AccountDetails] {
- repo := Construct[models.AccountDetails, models.AccountDetails](
- accountDetails,
- )
- Create(repo)
- return *repo
-}
-
-func UpdateAccountDetails(accountDetails models.AccountDetails) Repository[models.AccountDetails, models.AccountDetails] {
- repo := Construct[models.AccountDetails, models.AccountDetails](
- models.AccountDetails{AccountID: accountDetails.AccountID},
- )
- repo.Transaction.Where("account_id = ?", accountDetails.AccountID).First(&repo.Constructor)
- accountDetails.ID = repo.Constructor.ID
- // fmt.Println(repo.Constructor)
- // fmt.Println(accountDetails)
- repo.Transaction.Updates(accountDetails)
- repo.Result = accountDetails
- return *repo
-}
+package repositories
+
+import (
+ "api.qobiltu.id/models"
+)
+
+func GetAccountbyEmail(email string) Repository[models.Account, models.Account] {
+ repo := Construct[models.Account, models.Account](
+ models.Account{Email: email},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.Account, models.Account],
+ Find[models.Account, models.Account],
+ )
+ return *repo
+}
+
+func GetAllAccount() Repository[models.Account, []models.Account] {
+ repo := Construct[models.Account, []models.Account](
+ models.Account{},
+ )
+ repo.Transactions(
+ Find[models.Account, []models.Account],
+ )
+ return *repo
+}
+func GetAccountById(accountId uint) Repository[models.Account, models.Account] {
+ repo := Construct[models.Account, models.Account](
+ models.Account{Id: accountId},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.Account, models.Account],
+ Find[models.Account, models.Account],
+ )
+ return *repo
+}
+
+func UpdateAccount(account models.Account) Repository[models.Account, models.Account] {
+ repo := Construct[models.Account, models.Account](
+ account,
+ )
+ repo.Transaction.Save(&repo.Constructor)
+ repo.Result = repo.Constructor
+ return *repo
+}
+
+func GetDetailAccountById(accountId uint) Repository[models.AccountDetails, models.AccountDetails] {
+ repo := Construct[models.AccountDetails, models.AccountDetails](
+ models.AccountDetails{AccountID: accountId},
+ )
+
+ // fmt.Println("Account ID:", repo.Constructor.AccountID)
+ repo.Transactions(
+ WhereGivenConstructor[models.AccountDetails, models.AccountDetails],
+ Find[models.AccountDetails, models.AccountDetails],
+ )
+ return *repo
+}
+
+func CreateAccount(account models.Account) Repository[models.Account, models.Account] {
+ repo := Construct[models.Account, models.Account](
+ account,
+ )
+ Create(repo)
+ return *repo
+}
+
+func CreateAccountDetails(accountDetails models.AccountDetails) Repository[models.AccountDetails, models.AccountDetails] {
+ repo := Construct[models.AccountDetails, models.AccountDetails](
+ accountDetails,
+ )
+ Create(repo)
+ return *repo
+}
+
+func UpdateAccountDetails(accountDetails models.AccountDetails) Repository[models.AccountDetails, models.AccountDetails] {
+ repo := Construct[models.AccountDetails, models.AccountDetails](
+ models.AccountDetails{AccountID: accountDetails.AccountID},
+ )
+ repo.Transaction.Where("account_id = ?", accountDetails.AccountID).First(&repo.Constructor)
+ accountDetails.ID = repo.Constructor.ID
+ // fmt.Println(repo.Constructor)
+ // fmt.Println(accountDetails)
+ repo.Transaction.Updates(accountDetails)
+ repo.Result = accountDetails
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/cv_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/cv_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..067132cd39bdad688a06f65a3364626b7effb28f
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/cv_repository.go
@@ -0,0 +1,126 @@
+package repositories
+
+import (
+ "api.qobiltu.id/models"
+ "context"
+ "gorm.io/gorm"
+)
+
+type CVRepository interface {
+ SaveAccountDetails(ctx context.Context, req *models.AccountDetails) (*models.AccountDetails, error)
+ GetAccountDetailsByAccountID(ctx context.Context, accountID int64) (*models.AccountDetails, error)
+
+ SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCV) (*models.PersonalityAndPreferenceCV, error)
+ GetPersonalityAndPreferenceByAccountID(ctx context.Context, accountID int64) (*models.PersonalityAndPreferenceCV, error)
+
+ SaveFamilyMember(ctx context.Context, req *models.FamilyMemberCV) (*models.FamilyMemberCV, error)
+ ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error)
+ GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error)
+ DeleteFamilyMember(ctx context.Context, id int64) error
+
+ SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthCV) (*models.PhysicalAndHealthCV, error)
+ GetPhysicalAndHealthByAccountID(ctx context.Context, accountID int64) (*models.PhysicalAndHealthCV, error)
+
+ SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingCV) (*models.WorshipAndReligiousUnderstandingCV, error)
+ GetWorshipAndReligiousUnderstandingByAccountID(ctx context.Context, accountID int64) (*models.WorshipAndReligiousUnderstandingCV, error)
+}
+
+type cvRepository struct {
+ db *gorm.DB
+}
+
+func NewCVRepository(db *gorm.DB) CVRepository {
+ return &cvRepository{
+ db: db,
+ }
+}
+
+func (r *cvRepository) SaveAccountDetails(ctx context.Context, req *models.AccountDetails) (*models.AccountDetails, error) {
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
+}
+
+func (r *cvRepository) GetAccountDetailsByAccountID(ctx context.Context, accountID int64) (*models.AccountDetails, error) {
+ var accountDetails models.AccountDetails
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&accountDetails).Error; err != nil {
+ return nil, err
+ }
+ return &accountDetails, nil
+}
+
+func (r *cvRepository) SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCV) (*models.PersonalityAndPreferenceCV, error) {
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
+}
+
+func (r *cvRepository) GetPersonalityAndPreferenceByAccountID(ctx context.Context, accountID int64) (*models.PersonalityAndPreferenceCV, error) {
+ var personalityAndPreference models.PersonalityAndPreferenceCV
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&personalityAndPreference).Error; err != nil {
+ return nil, err
+ }
+ return &personalityAndPreference, nil
+}
+
+func (r *cvRepository) SaveFamilyMember(ctx context.Context, req *models.FamilyMemberCV) (*models.FamilyMemberCV, error) {
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
+}
+
+func (r *cvRepository) GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error) {
+ var familyMember models.FamilyMemberCV
+ if err := r.db.WithContext(ctx).Where("id = ?", id).First(&familyMember).Error; err != nil {
+ return nil, err
+ }
+ return &familyMember, nil
+}
+
+func (r *cvRepository) DeleteFamilyMember(ctx context.Context, id int64) error {
+ if err := r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.FamilyMemberCV{}).Error; err != nil {
+ return err
+ }
+ return nil
+}
+
+func (r *cvRepository) ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error) {
+ familyMembers := make([]models.FamilyMemberCV, 0)
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Find(&familyMembers).Error; err != nil {
+ return nil, err
+ }
+ return familyMembers, nil
+}
+
+func (r *cvRepository) SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthCV) (*models.PhysicalAndHealthCV, error) {
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
+}
+
+func (r *cvRepository) GetPhysicalAndHealthByAccountID(ctx context.Context, accountID int64) (*models.PhysicalAndHealthCV, error) {
+ var physicalAndHealth models.PhysicalAndHealthCV
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&physicalAndHealth).Error; err != nil {
+ return nil, err
+ }
+ return &physicalAndHealth, nil
+}
+
+func (r *cvRepository) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingCV) (*models.WorshipAndReligiousUnderstandingCV, error) {
+ if err := r.db.WithContext(ctx).Save(req).Error; err != nil {
+ return req, err
+ }
+ return req, nil
+}
+
+func (r *cvRepository) GetWorshipAndReligiousUnderstandingByAccountID(ctx context.Context, accountID int64) (*models.WorshipAndReligiousUnderstandingCV, error) {
+ var worshipAndReligiousUnderstanding models.WorshipAndReligiousUnderstandingCV
+ if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).First(&worshipAndReligiousUnderstanding).Error; err != nil {
+ return nil, err
+ }
+ return &worshipAndReligiousUnderstanding, nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/cv_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/cv_route.go
new file mode 100644
index 0000000000000000000000000000000000000000..eed8dc1644450ff4156a1d30f6636e2d60c53d5b
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/cv_route.go
@@ -0,0 +1,26 @@
+package router
+
+import "api.qobiltu.id/middleware"
+
+func (s *Server) CVRoute() {
+ routerGroup := s.router.Group("/api/v1/cv").Use(middleware.AuthUser)
+ {
+ routerGroup.POST("/account-details", s.cvController.SaveAccountDetails)
+ routerGroup.GET("/account-details", s.cvController.GetAccountDetails)
+
+ routerGroup.POST("/personality-and-preferences", s.cvController.SavePersonalityAndPreference)
+ routerGroup.GET("/personality-and-preferences", s.cvController.GetPersonalityAndPreference)
+
+ routerGroup.POST("/family-members", s.cvController.CreateFamilyMember)
+ routerGroup.GET("/family-members", s.cvController.ListFamilyMember)
+ routerGroup.GET("/family-members/:id", s.cvController.GetFamilyMember)
+ routerGroup.PUT("/family-members/:id", s.cvController.UpdateFamilyMember)
+ routerGroup.DELETE("/family-members/:id", s.cvController.DeleteFamilyMember)
+
+ routerGroup.POST("/physical-and-healths", s.cvController.SavePhysicalAndHealth)
+ routerGroup.GET("/physical-and-healths", s.cvController.GetPhysicalAndHealth)
+
+ routerGroup.POST("/worship-and-religious-understandings", s.cvController.SaveWorshipAndReligiousUnderstanding)
+ routerGroup.GET("/worship-and-religious-understandings", s.cvController.GetWorshipAndReligiousUnderstanding)
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
index 0cec98800c1d1189118a1af77f0b116395372de1..97ce1f39c35e67d4fbae4f0a0c1b6eb5a0bbe795 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
@@ -1,20 +1,21 @@
package router
import (
- "api.qobiltu.id/controller"
+ "api.qobiltu.id/controller"
)
func (s *Server) setupRoutes() {
- s.router.GET("/", controller.HomeController)
+ s.router.GET("/", controller.HomeController)
- AuthRoute(s.router)
- UserRoute(s.router)
- EmailRoute(s.router)
- OptionsRoute(s.router)
- AcademyRoute(s.router)
- QuizRoute(s.router)
+ AuthRoute(s.router)
+ UserRoute(s.router)
+ EmailRoute(s.router)
+ OptionsRoute(s.router)
+ AcademyRoute(s.router)
+ QuizRoute(s.router)
- // another way to register routes
- s.HealthCheckRoute()
+ // another way to register routes
+ s.HealthCheckRoute()
+ s.CVRoute()
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/server.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/server.go
index 3573257d63132d14ed04a38abc3ec03fbaa99bd2..ae7ea916bd383632c60b6dcd5995d71840bd5717 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/server.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/server.go
@@ -1,6 +1,7 @@
package router
import (
+ cv_controller "api.qobiltu.id/controller/cv"
"api.qobiltu.id/controller/health_check"
"github.com/gin-gonic/gin"
)
@@ -8,10 +9,12 @@ import (
type Server struct {
router *gin.Engine
healthCheckController health_check_controller.HealthCheckController
+ cvController cv_controller.CVController
}
func NewServer(
healthCheckController health_check_controller.HealthCheckController,
+ cvController cv_controller.CVController,
) (*Server, error) {
router := gin.Default()
@@ -19,6 +22,7 @@ func NewServer(
server := &Server{
healthCheckController: healthCheckController,
+ cvController: cvController,
router: router,
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..2a1a89efab3cd99b9559c3b051493212a42b5672
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/cv_service.go
@@ -0,0 +1,277 @@
+package services
+
+import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+ "api.qobiltu.id/response"
+ "context"
+ "errors"
+ "gorm.io/gorm"
+)
+
+type CVService interface {
+ SaveAccountDetails(ctx context.Context, req *models.AccountDetailsRequest) (*models.AccountDetails, error)
+ GetAccountDetails(ctx context.Context, id int64) (*models.AccountDetails, error)
+
+ SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCVRequest) (*models.PersonalityAndPreferenceCV, error)
+ GetPersonalityAndPreference(ctx context.Context, id int64) (*models.PersonalityAndPreferenceCV, error)
+
+ CreateFamilyMember(ctx context.Context, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
+ UpdateFamilyMember(ctx context.Context, id int64, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error)
+ ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error)
+ GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error)
+ DeleteFamilyMember(ctx context.Context, id int64) error
+
+ SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error)
+ GetPhysicalAndHealth(ctx context.Context, id int64) (*models.PhysicalAndHealthCV, error)
+
+ SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error)
+ GetWorshipAndReligiousUnderstanding(ctx context.Context, id int64) (*models.WorshipAndReligiousUnderstandingCV, error)
+}
+
+type cvService struct {
+ cvRepository repositories.CVRepository
+}
+
+func NewCVService(cvRepository repositories.CVRepository) CVService {
+ return &cvService{
+ cvRepository: cvRepository,
+ }
+}
+
+func (s *cvService) SaveAccountDetails(ctx context.Context, req *models.AccountDetailsRequest) (*models.AccountDetails, error) {
+ // Ambil data lama jika ada
+ accountDetails, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ // Apply perubahan
+ if accountDetails == nil {
+ accountDetails = &models.AccountDetails{}
+ }
+
+ accountDetails.AccountID = uint(req.AccountID)
+ accountDetails.FullName = req.FullName
+ accountDetails.Gender = req.Gender
+ accountDetails.DateOfBirth = req.DateOfBirth
+ accountDetails.PlaceOfBirth = req.PlaceOfBirth
+ accountDetails.Domicile = req.Domicile
+ accountDetails.MaritalStatus = req.MaritalStatus
+ accountDetails.LastEducation = req.LastEducation
+ accountDetails.LastJob = req.LastJob
+
+ // Simpan data
+ res, err := s.cvRepository.SaveAccountDetails(ctx, accountDetails)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return res, nil
+}
+
+func (s *cvService) GetAccountDetails(ctx context.Context, id int64) (*models.AccountDetails, error) {
+ res, err := s.cvRepository.GetAccountDetailsByAccountID(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data diri tidak ditemukan")
+ }
+ return res, nil
+}
+
+func (s *cvService) SavePersonalityAndPreference(ctx context.Context, req *models.PersonalityAndPreferenceCVRequest) (*models.PersonalityAndPreferenceCV, error) {
+ // Ambil data lama jika ada
+ personalityAndPreference, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ // Apply perubahan
+ if personalityAndPreference == nil {
+ personalityAndPreference = &models.PersonalityAndPreferenceCV{}
+ }
+
+ personalityAndPreference.AccountID = req.AccountID
+ personalityAndPreference.PositiveTraits = req.PositiveTraits
+ personalityAndPreference.NegativeTraits = req.NegativeTraits
+ personalityAndPreference.Hobbies = req.Hobbies
+ personalityAndPreference.LifeGoals = req.LifeGoals
+ personalityAndPreference.DailyActivities = req.DailyActivities
+ personalityAndPreference.LeisureActivities = req.LeisureActivities
+ personalityAndPreference.Likes = req.Likes
+ personalityAndPreference.Dislikes = req.Dislikes
+ personalityAndPreference.StressHandling = req.StressHandling
+ personalityAndPreference.AngerTriggers = req.AngerTriggers
+ personalityAndPreference.FavoriteFoodAndDrinks = req.FavoriteFoodAndDrinks
+ personalityAndPreference.CanCook = req.CanCook
+ personalityAndPreference.TypesOfDishesCooked = req.TypesOfDishesCooked
+ personalityAndPreference.MonthlyExpenses = req.MonthlyExpenses
+
+ res, err := s.cvRepository.SavePersonalityAndPreference(ctx, personalityAndPreference)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return res, nil
+}
+
+func (s *cvService) GetPersonalityAndPreference(ctx context.Context, id int64) (*models.PersonalityAndPreferenceCV, error) {
+ res, err := s.cvRepository.GetPersonalityAndPreferenceByAccountID(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return res, nil
+}
+
+func (s *cvService) CreateFamilyMember(ctx context.Context, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error) {
+ // Mapping request ke model
+ familyMember := &models.FamilyMemberCV{
+ AccountID: req.AccountID,
+ Role: req.Role,
+ Status: req.Status,
+ Religion: req.Religion,
+ Job: req.Job,
+ LastEducation: req.LastEducation,
+ Age: req.Age,
+ }
+
+ // Simpan ke repository
+ res, err := s.cvRepository.SaveFamilyMember(ctx, familyMember)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menyimpan anggota keluarga")
+ }
+
+ return res, nil
+}
+
+func (s *cvService) ListFamilyMember(ctx context.Context, accountID int64) ([]models.FamilyMemberCV, error) {
+ list, err := s.cvRepository.ListFamilyMember(ctx, accountID)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal mengambil daftar anggota keluarga")
+ }
+ return list, nil
+}
+
+func (s *cvService) GetFamilyMember(ctx context.Context, id int64) (*models.FamilyMemberCV, error) {
+ res, err := s.cvRepository.GetFamilyMember(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
+ }
+ return res, nil
+}
+
+func (s *cvService) DeleteFamilyMember(ctx context.Context, id int64) error {
+ err := s.cvRepository.DeleteFamilyMember(ctx, id)
+ if err != nil {
+ return response.HandleGormError(err, "Gagal menghapus anggota keluarga")
+ }
+ return nil
+}
+
+func (s *cvService) UpdateFamilyMember(ctx context.Context, id int64, req *models.FamilyMemberRequest) (*models.FamilyMemberCV, error) {
+ existing, err := s.cvRepository.GetFamilyMember(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data anggota keluarga tidak ditemukan")
+ }
+
+ existing.Role = req.Role
+ existing.Status = req.Status
+ existing.Religion = req.Religion
+ existing.Job = req.Job
+ existing.LastEducation = req.LastEducation
+ existing.Age = req.Age
+
+ updated, err := s.cvRepository.SaveFamilyMember(ctx, existing)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal memperbarui anggota keluarga")
+ }
+
+ return updated, nil
+}
+
+func (s *cvService) SavePhysicalAndHealth(ctx context.Context, req *models.PhysicalAndHealthRequest) (*models.PhysicalAndHealthCV, error) {
+ // Cek apakah data sudah ada berdasarkan account_id
+ existing, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data fisik dan kesehatan")
+ }
+
+ // Jika belum ada, buat objek baru
+ if existing == nil {
+ existing = &models.PhysicalAndHealthCV{}
+ }
+
+ // Mapping field dari request
+ existing.AccountID = req.AccountID
+ existing.HeightInCm = req.HeightInCm
+ existing.WeightInKg = req.WeightInKg
+ existing.BodyShape = req.BodyShape
+ existing.SkinColor = req.SkinColor
+ existing.HairType = req.HairType
+ existing.MedicalHistory = req.MedicalHistory
+ existing.PhysicalDisorder = req.PhysicalDisorder
+ existing.PhysicalTraits = req.PhysicalTraits
+
+ // Simpan data
+ res, err := s.cvRepository.SavePhysicalAndHealth(ctx, existing)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Gagal menyimpan data fisik dan kesehatan")
+ }
+
+ return res, nil
+}
+
+func (s *cvService) GetPhysicalAndHealth(ctx context.Context, id int64) (*models.PhysicalAndHealthCV, error) {
+ res, err := s.cvRepository.GetPhysicalAndHealthByAccountID(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data fisik dan kesehatan tidak ditemukan")
+ }
+ return res, nil
+}
+
+func (s *cvService) SaveWorshipAndReligiousUnderstanding(ctx context.Context, req *models.WorshipAndReligiousUnderstandingRequest) (*models.WorshipAndReligiousUnderstandingCV, error) {
+ // Cek apakah data sudah ada berdasarkan account_id
+ worshipAndReligiousUnderstanding, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, req.AccountID)
+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, response.HandleGormError(err, "Terjadi kesalahan saat mengambil data agama dan pemahaman agama")
+ }
+
+ // Jika belum ada, buat objek baru
+ if worshipAndReligiousUnderstanding == nil {
+ worshipAndReligiousUnderstanding = &models.WorshipAndReligiousUnderstandingCV{}
+ }
+
+ // Mapping field dari request
+ worshipAndReligiousUnderstanding.AccountID = req.AccountID
+ worshipAndReligiousUnderstanding.ObligatoryPrayer = req.ObligatoryPrayer
+ worshipAndReligiousUnderstanding.CongregationalPrayer = req.CongregationalPrayer
+ worshipAndReligiousUnderstanding.TahajjudPrayer = req.TahajjudPrayer
+ worshipAndReligiousUnderstanding.DhuhaPrayer = req.DhuhaPrayer
+ worshipAndReligiousUnderstanding.QuranMemorization = req.QuranMemorization
+ worshipAndReligiousUnderstanding.QuranReadingAbility = req.QuranReadingAbility
+ worshipAndReligiousUnderstanding.DaudFasting = req.DaudFasting
+ worshipAndReligiousUnderstanding.AyyamulBidhFasting = req.AyyamulBidhFasting
+ worshipAndReligiousUnderstanding.HajjOrUmrah = req.HajjOrUmrah
+ worshipAndReligiousUnderstanding.ListeningToMusic = req.ListeningToMusic
+ worshipAndReligiousUnderstanding.OpinionOnIkhtilat = req.OpinionOnIkhtilat
+ worshipAndReligiousUnderstanding.OpinionOnTouchingNonMahram = req.OpinionOnTouchingNonMahram
+ worshipAndReligiousUnderstanding.OpinionOnVeil = req.OpinionOnVeil
+ worshipAndReligiousUnderstanding.WeeklyReligiousStudies = req.WeeklyReligiousStudies
+ worshipAndReligiousUnderstanding.FollowedUstadz = req.FollowedUstadz
+
+ // Simpan data
+ res, err := s.cvRepository.SaveWorshipAndReligiousUnderstanding(ctx, worshipAndReligiousUnderstanding)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return res, nil
+}
+
+func (s *cvService) GetWorshipAndReligiousUnderstanding(ctx context.Context, id int64) (*models.WorshipAndReligiousUnderstandingCV, error) {
+ res, err := s.cvRepository.GetWorshipAndReligiousUnderstandingByAccountID(ctx, id)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Data agama dan pemahaman agama tidak ditemukan")
+ }
+ return res, nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
index 1b6ac24b03b07f24fa5a65be09fe16e34c72bf82..76f05181e7c198da789be5f1ce64d594c988075f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
@@ -1,81 +1,81 @@
-package services
-
-import (
- "context"
- "errors"
-
- "api.qobiltu.id/models"
- "api.qobiltu.id/repositories"
- uuid "github.com/satori/go.uuid"
- "google.golang.org/api/idtoken"
-)
-
-type GoogleAuthService struct {
- Service[models.ExternalAuth, models.AuthenticatedUser]
-}
-
-func (s *GoogleAuthService) Authenticate(isAgree bool) {
- GoogleAuth := repositories.GetExternalAccountByOauthId(s.Constructor.OauthID)
- payload, errGoogleAuth := idtoken.Validate(context.Background(), s.Constructor.OauthID, "")
- s.Error = errGoogleAuth
- if errGoogleAuth != nil {
- s.Exception.Unauthorized = true
- s.Exception.Message = "Oauth Provider Failed Login (Google Authentication)"
- return
- }
- email := payload.Claims["email"]
- checkRegisteredEmail := repositories.GetAccountbyEmail(email.(string))
- if !checkRegisteredEmail.NoRecord {
- token, _ := GenerateToken(&checkRegisteredEmail.Result)
- checkRegisteredEmail.Result.Password = "SECRET"
- s.Result = models.AuthenticatedUser{
- Account: checkRegisteredEmail.Result,
- Token: token,
- }
- return
- }
- if GoogleAuth.NoRecord {
- if !isAgree {
- s.Exception.BadRequest = true
- s.Exception.Message = "Please agree to the terms and conditions to create an account"
- return
- }
- s.Constructor.UUID = uuid.NewV4()
- s.Constructor.OauthProvider = "Google"
-
- createAccount := repositories.CreateAccount(models.Account{
- UUID: uuid.NewV4(),
- Email: email.(string),
- IsEmailVerified: true,
- })
-
- s.Constructor.AccountID = createAccount.Result.Id
- createGoogleAuth := repositories.CreateExternalAuth(s.Constructor)
-
- GoogleAuth.Result.AccountID = createGoogleAuth.Result.AccountID
- userProfile := UserProfileService{}
- userProfile.Constructor.AccountID = GoogleAuth.Result.AccountID
- userProfile.Create()
- if userProfile.Error != nil {
- s.Error = userProfile.Error
- return
- }
- s.Error = createGoogleAuth.RowsError
- s.Error = errors.Join(s.Error, createAccount.RowsError)
- }
-
- accountData := repositories.GetAccountById(GoogleAuth.Result.AccountID)
- token, err_tok := GenerateToken(&accountData.Result)
-
- if err_tok != nil {
- s.Error = errors.Join(s.Error, err_tok)
- }
-
- accountData.Result.Password = "SECRET"
- s.Result = models.AuthenticatedUser{
- Account: accountData.Result,
- Token: token,
- }
- s.Error = accountData.RowsError
-
-}
+package services
+
+import (
+ "context"
+ "errors"
+
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+ uuid "github.com/satori/go.uuid"
+ "google.golang.org/api/idtoken"
+)
+
+type GoogleAuthService struct {
+ Service[models.ExternalAuth, models.AuthenticatedUser]
+}
+
+func (s *GoogleAuthService) Authenticate(isAgree bool) {
+ GoogleAuth := repositories.GetExternalAccountByOauthId(s.Constructor.OauthID)
+ payload, errGoogleAuth := idtoken.Validate(context.Background(), s.Constructor.OauthID, "")
+ s.Error = errGoogleAuth
+ if errGoogleAuth != nil {
+ s.Exception.Unauthorized = true
+ s.Exception.Message = "Oauth Provider Failed Login (Google Authentication)"
+ return
+ }
+ email := payload.Claims["email"]
+ checkRegisteredEmail := repositories.GetAccountbyEmail(email.(string))
+ if !checkRegisteredEmail.NoRecord {
+ token, _ := GenerateToken(&checkRegisteredEmail.Result)
+ checkRegisteredEmail.Result.Password = "SECRET"
+ s.Result = models.AuthenticatedUser{
+ Account: checkRegisteredEmail.Result,
+ Token: token,
+ }
+ return
+ }
+ if GoogleAuth.NoRecord {
+ if !isAgree {
+ s.Exception.BadRequest = true
+ s.Exception.Message = "Please agree to the terms and conditions to create an account"
+ return
+ }
+ s.Constructor.UUID = uuid.NewV4()
+ s.Constructor.OauthProvider = "Google"
+
+ createAccount := repositories.CreateAccount(models.Account{
+ UUID: uuid.NewV4(),
+ Email: email.(string),
+ IsEmailVerified: true,
+ })
+
+ s.Constructor.AccountID = createAccount.Result.Id
+ createGoogleAuth := repositories.CreateExternalAuth(s.Constructor)
+
+ GoogleAuth.Result.AccountID = createGoogleAuth.Result.AccountID
+ userProfile := UserProfileService{}
+ userProfile.Constructor.AccountID = GoogleAuth.Result.AccountID
+ userProfile.Create()
+ if userProfile.Error != nil {
+ s.Error = userProfile.Error
+ return
+ }
+ s.Error = createGoogleAuth.RowsError
+ s.Error = errors.Join(s.Error, createAccount.RowsError)
+ }
+
+ accountData := repositories.GetAccountById(GoogleAuth.Result.AccountID)
+ token, err_tok := GenerateToken(&accountData.Result)
+
+ if err_tok != nil {
+ s.Error = errors.Join(s.Error, err_tok)
+ }
+
+ accountData.Result.Password = "SECRET"
+ s.Result = models.AuthenticatedUser{
+ Account: accountData.Result,
+ Token: token,
+ }
+ s.Error = accountData.RowsError
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
index 7aa7effca54a8f9b826b9bb66742352d81ef60da..591763f10fbf4c5b7c60ba9f64d0fd6427f99c9f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
@@ -53,12 +53,12 @@ func (s *UserProfileService) Create() {
return
}
s.Result = models.UserProfileResponse{
- Account: repositories.GetAccountById(s.Constructor.AccountID).Result,
+ Account: repositories.GetAccountById(uint(s.Constructor.AccountID)).Result,
Details: userProfile.Result,
}
}
func (s *UserProfileService) Retrieve() {
- userProfile := repositories.GetDetailAccountById(s.Constructor.AccountID)
+ userProfile := repositories.GetDetailAccountById(uint(s.Constructor.AccountID))
s.Error = userProfile.RowsError
if userProfile.NoRecord {
s.Exception.DataNotFound = true
@@ -66,7 +66,7 @@ func (s *UserProfileService) Retrieve() {
return
}
s.Result = models.UserProfileResponse{
- Account: repositories.GetAccountById(s.Constructor.AccountID).Result,
+ Account: repositories.GetAccountById(uint(s.Constructor.AccountID)).Result,
Details: userProfile.Result,
}
s.Result.Account.Password = "SECRET"
@@ -96,7 +96,7 @@ func (s *UserProfileService) Update() {
s.Exception.Message = "There is no account with given credentials!"
return
}
- account := repositories.GetAccountById(s.Constructor.AccountID)
+ account := repositories.GetAccountById(uint(s.Constructor.AccountID))
account.Result.IsDetailCompleted = (userProfile.Result.InitialName != "" &&
userProfile.Result.FullName != nil &&
userProfile.Result.DateOfBirth != nil &&
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
index cc8bfad44cf1db82ffae8cf9f78f542f93099cb1..0d10ff95c9537f77159b809f6f6b3a3b1822615f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
@@ -70,6 +70,10 @@ func AutoMigrateAll(db *gorm.DB) {
&models.Question{},
&models.Answer{},
&models.UserAnswer{},
+ &models.PersonalityAndPreferenceCV{},
+ &models.FamilyMemberCV{},
+ &models.PhysicalAndHealthCV{},
+ &models.WorshipAndReligiousUnderstandingCV{},
)
if err != nil {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
index 7c4f248188e1a83231327004df674e6da8c3001b..1cfd267c3f562aea20931d07d0d6176fb1705c45 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
@@ -33,5 +33,12 @@ func AuthUser(c *gin.Context) {
c.Abort()
return
}
+}
+func GetAccountData(c *gin.Context) models.AccountData {
+ cParam, _ := c.Get("accountData")
+ if cParam != nil {
+ return cParam.(models.AccountData)
+ }
+ return models.AccountData{}
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
index 7dfdaa3ae70424316b29e4140a989ba64f49637f..8b8dcf092d99d8a06895c665e173c4e8fe39e014 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -18,19 +18,21 @@ type Account struct {
}
type AccountDetails struct {
- ID uint64 `gorm:"primaryKey" json:"id"`
- AccountID uint `json:"account_id"`
- InitialName string `json:"initial_name"`
- FullName *string `json:"full_name"`
- DateOfBirth *time.Time `json:"date_of_birth"`
- PlaceOfBirth *string `json:"place_of_birth"`
- Domicile *string `json:"domicile"`
- LastJob *string `json:"last_job"`
- Gender *string `json:"gender"`
- LastEducation *string `json:"last_education"`
- MaritalStatus *string `json:"marital_status"`
- Avatar *string `json:"avatar"`
- PhoneNumber *string `json:"phone_number"`
+ ID uint64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
+ AccountID uint `gorm:"column:account_id;not null;unique" json:"account_id"`
+ InitialName string `gorm:"column:initial_name;not null" json:"initial_name"`
+ FullName *string `gorm:"column:full_name" json:"full_name"`
+ DateOfBirth *time.Time `gorm:"column:date_of_birth" json:"date_of_birth"`
+ PlaceOfBirth *string `gorm:"column:place_of_birth" json:"place_of_birth"`
+ Domicile *string `gorm:"column:domicile" json:"domicile"`
+ LastJob *string `gorm:"column:last_job" json:"last_job"`
+ Gender *string `gorm:"column:gender" json:"gender"`
+ LastEducation *string `gorm:"column:last_education" json:"last_education"`
+ MaritalStatus *string `gorm:"column:marital_status" json:"marital_status"`
+ Avatar *string `gorm:"column:avatar" json:"avatar"`
+ PhoneNumber *string `gorm:"column:phone_number" json:"phone_number"`
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
}
type EmailVerification struct {
@@ -184,24 +186,107 @@ type QuizResult struct {
AverageScore float64 `gorm:"column:average_score" json:"average_score"`
}
+type (
+ PersonalityAndPreferenceCV struct {
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
+ PositiveTraits *string `gorm:"column:positive_traits" json:"positive_traits"` // sifat positif
+ NegativeTraits *string `gorm:"column:negative_traits" json:"negative_traits"` // sifat negatif
+ Hobbies *string `gorm:"column:hobbies" json:"hobbies"` // hobi
+ LifeGoals *string `gorm:"column:life_goals" json:"life_goals"` // target hidup
+ DailyActivities *string `gorm:"column:daily_activities" json:"daily_activities"` // kegiatan sehari-hari
+ LeisureActivities *string `gorm:"column:leisure_activities" json:"leisure_activities"` // kegiatan waktu luang
+ Likes *string `gorm:"column:likes" json:"likes"` // hal yang disukai
+ Dislikes *string `gorm:"column:dislikes" json:"dislikes"` // hal yang tidak disukai
+ StressHandling *string `gorm:"column:stress_handling" json:"stress_handling"` // cara mengatasi stres
+ AngerTriggers *string `gorm:"column:anger_triggers" json:"anger_triggers"` // pemicu amarah
+ FavoriteFoodAndDrinks *string `gorm:"column:favorite_food_and_drinks" json:"favorite_food_and_drinks"` // makanan dan minuman favorit
+ CanCook *bool `gorm:"column:can_cook" json:"can_cook"` // bisa memasak
+ TypesOfDishesCooked *string `gorm:"column:types_of_dishes_cooked" json:"types_of_dishes_cooked"` // jenis masakan yang bisa dimasak
+ MonthlyExpenses *string `gorm:"column:monthly_expenses" json:"monthly_expenses"` // pengeluaran per bulan
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ }
+
+ FamilyMemberCV struct {
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
+ AccountID int64 `gorm:"column:account_id;not null" json:"account_id"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
+ Role *string `gorm:"column:role" json:"role"` // Peran dalam keluarga
+ Status *string `gorm:"column:status" json:"status"` // Status (Hidup, Wafat)
+ Religion *string `gorm:"column:religion" json:"religion"` // Agama
+ Job *string `gorm:"column:job" json:"job"` // Pekerjaan
+ LastEducation *string `gorm:"column:last_education" json:"last_education"` // Pendidikan terakhir
+ Age *int `gorm:"column:age" json:"age"` // Usia
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ }
+
+ PhysicalAndHealthCV struct {
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
+ HeightInCm *int `gorm:"column:height_cm" json:"height_cm"` // Tinggi badan dalam satuan sentimeter
+ WeightInKg *int `gorm:"column:weight_kg" json:"weight_kg"` // Berat badan dalam satuan kilogram
+ BodyShape *string `gorm:"column:body_shape" json:"body_shape"` // Bentuk tubuh
+ SkinColor *string `gorm:"column:skin_color" json:"skin_color"` // Warna kulit
+ HairType *string `gorm:"column:hair_type" json:"hair_type"` // Tipe rambut
+ MedicalHistory *string `gorm:"column:medical_history" json:"medical_history"` // Riwayat penyakit
+ PhysicalDisorder *string `gorm:"column:physical_disorder" json:"physical_disorder"` // Cacat fisik
+ PhysicalTraits *string `gorm:"column:physical_traits" json:"physical_traits"` // Ciri khas fisik
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"` // Waktu data dibuat
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"` // Waktu data terakhir diperbarui
+ }
+
+ WorshipAndReligiousUnderstandingCV struct {
+ ID int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
+ AccountID int64 `gorm:"column:account_id;not null;unique" json:"account_id"`
+ Account *Account `gorm:"foreignKey:AccountID;constraint:OnDelete:CASCADE" json:"account,omitempty"`
+ ObligatoryPrayer *string `gorm:"column:obligatory_prayer" json:"obligatory_prayer"` // sholat_wajib_5_waktu
+ CongregationalPrayer *string `gorm:"column:congregational_prayer" json:"congregational_prayer"` // sholat_berjamaah_di_masjid
+ TahajjudPrayer *string `gorm:"column:tahajjud_prayer" json:"tahajjud_prayer"` // sholat_tahajud
+ DhuhaPrayer *string `gorm:"column:dhuha_prayer" json:"dhuha_prayer"` // sholat_dhuha
+ QuranMemorization *string `gorm:"column:quran_memorization" json:"quran_memorization"` // hafalan_alquran
+ QuranReadingAbility *string `gorm:"column:quran_reading_ability" json:"quran_reading_ability"` // kemampuan_baca_alquran
+ DaudFasting *string `gorm:"column:daud_fasting" json:"daud_fasting"` // puasa_daud
+ AyyamulBidhFasting *string `gorm:"column:ayyamul_bidh_fasting" json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
+ HajjOrUmrah *string `gorm:"column:hajj_or_umrah" json:"hajj_or_umrah"` // ibadah_haji_umroh
+ ListeningToMusic *string `gorm:"column:listening_to_music" json:"listening_to_music"` // mendengarkan_musik
+ OpinionOnIkhtilat *string `gorm:"column:opinion_on_ikhtilat" json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
+ OpinionOnTouchingNonMahram *string `gorm:"column:opinion_on_touching_non_mahram" json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
+ OpinionOnVeil *string `gorm:"column:opinion_on_veil" json:"opinion_on_veil"` // pendapat_tentang_cadar
+ WeeklyReligiousStudies *string `gorm:"column:weekly_religious_studies" json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
+ FollowedUstadz *string `gorm:"column:followed_ustadz" json:"followed_ustadz"` // ustadz_yang_diikuti
+ CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`
+ UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updated_at"`
+ }
+)
+
// Gorm table name settings
-func (Account) TableName() string { return "account" }
-func (AccountDetails) TableName() string { return "account_details" }
-func (EmailVerification) TableName() string { return "email_verifications" }
-func (ExternalAuth) TableName() string { return "extern_auth" }
-func (FCM) TableName() string { return "fcm" }
-func (ForgotPassword) TableName() string { return "forgot_password" }
-func (Academy) TableName() string { return "academy" }
-func (AcademyMaterial) TableName() string { return "academy_materials" }
-func (AcademyContent) TableName() string { return "academy_contents" }
-func (AcademyMaterialProgress) TableName() string { return "academy_materials_progress" }
-func (AcademyContentProgress) TableName() string { return "academy_contents_progress" }
-func (RegionProvince) TableName() string { return "region_provinces" }
-func (RegionCity) TableName() string { return "region_cities" }
-func (Answer) TableName() string { return "answers" }
-func (Question) TableName() string { return "questions" }
-func (Quiz) TableName() string { return "quizzes" }
-func (QuizAttempt) TableName() string { return "quiz_attempts" }
-func (UserAnswer) TableName() string { return "user_answers" }
-func (OptionCategory) TableName() string { return "option_categories" }
-func (OptionValues) TableName() string { return "option_values" }
+func (Account) TableName() string { return "account" }
+func (AccountDetails) TableName() string { return "account_details" }
+func (EmailVerification) TableName() string { return "email_verifications" }
+func (ExternalAuth) TableName() string { return "extern_auth" }
+func (FCM) TableName() string { return "fcm" }
+func (ForgotPassword) TableName() string { return "forgot_password" }
+func (Academy) TableName() string { return "academy" }
+func (AcademyMaterial) TableName() string { return "academy_materials" }
+func (AcademyContent) TableName() string { return "academy_contents" }
+func (AcademyMaterialProgress) TableName() string { return "academy_materials_progress" }
+func (AcademyContentProgress) TableName() string { return "academy_contents_progress" }
+func (RegionProvince) TableName() string { return "region_provinces" }
+func (RegionCity) TableName() string { return "region_cities" }
+func (Answer) TableName() string { return "answers" }
+func (Question) TableName() string { return "questions" }
+func (Quiz) TableName() string { return "quizzes" }
+func (QuizAttempt) TableName() string { return "quiz_attempts" }
+func (UserAnswer) TableName() string { return "user_answers" }
+func (OptionCategory) TableName() string { return "option_categories" }
+func (OptionValues) TableName() string { return "option_values" }
+func (PersonalityAndPreferenceCV) TableName() string { return "personality_and_preference_cv" }
+func (FamilyMemberCV) TableName() string { return "family_member_cv" }
+func (PhysicalAndHealthCV) TableName() string { return "physical_and_health_cv" }
+func (WorshipAndReligiousUnderstandingCV) TableName() string {
+ return "worship_and_religious_understanding_cv"
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
index e2c14412e72ec8e9b85233157f0b93ad2f7fca98..218277b337490138d5af499ecb00ef23027cac51 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
@@ -1,51 +1,126 @@
-package models
-
-type LoginRequest struct {
- Email string `json:"email" binding:"required"`
- Password string `json:"password" binding:"required"`
-}
-
-type RegisterRequest struct {
- Name string `json:"name"`
- Email string `json:"email" binding:"required,email"`
- Phone int `json:"phone"`
- Password string `json:"password" binding:"required"`
-}
-
-type ChangePasswordRequest struct {
- OldPassword string `json:"old_password" binding:"required" `
- NewPassword string `json:"new_password" binding:"required" `
-}
-
-type CreateVerifyEmailRequest struct {
- Token uint `json:"token" binding:"required"`
-}
-
-type OptionsRequest struct {
- OptionName string `json:"option_name" binding:"required"`
- OptionValue []string `json:"option_values" binding:"required"`
-}
-
-type ExternalAuthRequest struct {
- OauthID string `json:"oauth_id" binding:"required"`
- OauthProvider string `json:"oauth_provider" binding:"required"`
- IsAgreeTerms bool `json:"is_agree_terms"`
- IsSexualDisease bool `json:"is_sexual_disease"`
-}
-
-type ForgotPasswordRequest struct {
- Email string `json:"email" binding:"required,email"`
-}
-type ValidateForgotPasswordRequest struct {
- Token uint `json:"token" binding:"required"`
- NewPassword string `json:"new_password"`
-}
-
-type QuestionQuizRequest struct {
- QuestionNo int `json:"question_no" binding:"required"`
-}
-
-type AnswerQuizRequest struct {
- QuestionNo int `json:"question_no" binding:"required"`
- Answer int `json:"answer" binding:"required"`
-}
+package models
+
+import "time"
+
+type LoginRequest struct {
+ Email string `json:"email" binding:"required"`
+ Password string `json:"password" binding:"required"`
+}
+
+type RegisterRequest struct {
+ Name string `json:"name"`
+ Email string `json:"email" binding:"required,email"`
+ Phone int `json:"phone"`
+ Password string `json:"password" binding:"required"`
+}
+
+type ChangePasswordRequest struct {
+ OldPassword string `json:"old_password" binding:"required" `
+ NewPassword string `json:"new_password" binding:"required" `
+}
+
+type CreateVerifyEmailRequest struct {
+ Token uint `json:"token" binding:"required"`
+}
+
+type OptionsRequest struct {
+ OptionName string `json:"option_name" binding:"required"`
+ OptionValue []string `json:"option_values" binding:"required"`
+}
+
+type ExternalAuthRequest struct {
+ OauthID string `json:"oauth_id" binding:"required"`
+ OauthProvider string `json:"oauth_provider" binding:"required"`
+ IsAgreeTerms bool `json:"is_agree_terms"`
+ IsSexualDisease bool `json:"is_sexual_disease"`
+}
+
+type ForgotPasswordRequest struct {
+ Email string `json:"email" binding:"required,email"`
+}
+type ValidateForgotPasswordRequest struct {
+ Token uint `json:"token" binding:"required"`
+ NewPassword string `json:"new_password"`
+}
+
+type QuestionQuizRequest struct {
+ QuestionNo int `json:"question_no" binding:"required"`
+}
+
+type AnswerQuizRequest struct {
+ QuestionNo int `json:"question_no" binding:"required"`
+ Answer int `json:"answer" binding:"required"`
+}
+
+type (
+ PersonalityAndPreferenceCVRequest struct {
+ AccountID int64 `json:"-"`
+ PositiveTraits *string `json:"positive_traits"` // sifat positif
+ NegativeTraits *string `json:"negative_traits"` // sifat negatif
+ Hobbies *string `json:"hobbies"` // hobi
+ LifeGoals *string `json:"life_goals"` // target hidup
+ DailyActivities *string `json:"daily_activities"` // kegiatan sehari-hari
+ LeisureActivities *string `json:"leisure_activities"` // kegiatan waktu luang
+ Likes *string `json:"likes"` // hal yang disukai
+ Dislikes *string `json:"dislikes"` // hal yang tidak disukai
+ StressHandling *string `json:"stress_handling"` // cara mengatasi stres
+ AngerTriggers *string `json:"anger_triggers"` // pemicu amarah
+ FavoriteFoodAndDrinks *string `json:"favorite_food_and_drinks"` // makanan dan minuman favorit
+ CanCook *bool `json:"can_cook"` // bisa memasak
+ TypesOfDishesCooked *string `json:"types_of_dishes_cooked"` // jenis masakan yang bisa dimasak
+ MonthlyExpenses *string `json:"monthly_expenses"` // pengeluaran per bulan
+ }
+
+ FamilyMemberRequest struct {
+ AccountID int64 `json:"-"`
+ Role *string `json:"role"` // Peran dalam keluarga
+ Status *string `json:"status"` // Status (Hidup, Wafat)
+ Religion *string `json:"religion"` // Agama
+ Job *string `json:"job"` // Pekerjaan
+ LastEducation *string `json:"last_education"` // Pendidikan terakhir
+ Age *int `json:"age"` // Usia
+ }
+
+ PhysicalAndHealthRequest struct {
+ AccountID int64 `json:"-"`
+ HeightInCm *int `json:"height_cm"` // Tinggi badan dalam satuan sentimeter
+ WeightInKg *int `json:"weight_kg"` // Berat badan dalam satuan kilogram
+ BodyShape *string `json:"body_shape"` // Bentuk tubuh
+ SkinColor *string `json:"skin_color"` // Warna kulit
+ HairType *string `json:"hair_type"` // Tipe rambut
+ MedicalHistory *string `json:"medical_history"` // Riwayat penyakit
+ PhysicalDisorder *string `json:"physical_disorder"` // Cacat fisik
+ PhysicalTraits *string `json:"physical_traits"` // Ciri khas fisik
+ }
+
+ AccountDetailsRequest struct {
+ AccountID int64 `json:"-"`
+ FullName *string `json:"full_name"`
+ Gender *string `json:"gender"`
+ DateOfBirth *time.Time `json:"date_of_birth"`
+ PlaceOfBirth *string `json:"place_of_birth"`
+ Domicile *string `json:"domicile"`
+ MaritalStatus *string `json:"marital_status"`
+ LastEducation *string `json:"last_education"`
+ LastJob *string `json:"last_job"`
+ }
+
+ WorshipAndReligiousUnderstandingRequest struct {
+ AccountID int64 `json:"-"`
+ ObligatoryPrayer *string `json:"obligatory_prayer"` // sholat_wajib_5_waktu
+ CongregationalPrayer *string `json:"congregational_prayer"` // sholat_berjamaah_di_masjid
+ TahajjudPrayer *string `json:"tahajjud_prayer"` // sholat_tahajud
+ DhuhaPrayer *string `json:"dhuha_prayer"` // sholat_dhuha
+ QuranMemorization *string `json:"quran_memorization"` // hafalan_alquran
+ QuranReadingAbility *string `json:"quran_reading_ability"` // kemampuan_baca_alquran
+ DaudFasting *string `json:"daud_fasting"` // puasa_daud
+ AyyamulBidhFasting *string `json:"ayyamul_bidh_fasting"` // puasa_ayyamul_bidh
+ HajjOrUmrah *string `json:"hajj_or_umrah"` // ibadah_haji_umroh
+ ListeningToMusic *string `json:"listening_to_music"` // mendengarkan_musik
+ OpinionOnIkhtilat *string `json:"opinion_on_ikhtilat"` // pendapat_ikhtilat
+ OpinionOnTouchingNonMahram *string `json:"opinion_on_touching_non_mahram"` // pendapat_menyentuh_non_mahram
+ OpinionOnVeil *string `json:"opinion_on_veil"` // pendapat_tentang_cadar
+ WeeklyReligiousStudies *string `json:"weekly_religious_studies"` // kajian_yang_diikuti_dalam_sepekan
+ FollowedUstadz *string `json:"followed_ustadz"` // ustadz_yang_diikuti
+ }
+)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
index 06be35eb52d9f0c869dd8dbefe2347059a30c7ea..ee8a7c397f4d8ff8f198ea32f16976e47afecbc8 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
@@ -6,3 +6,4 @@ README.md
.error
logs/
.idea
+my-notes
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/health_check/health_check_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/health_check/health_check_controller.go
index 6c1d9a449600dd2ac3f6fd550531817150805456..f2f5e8e4ce943e20b7b39a1ff27dd2a837260c2c 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/health_check/health_check_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/health_check/health_check_controller.go
@@ -3,29 +3,33 @@ package health_check_controller
import (
"api.qobiltu.id/models"
"api.qobiltu.id/response"
- "api.qobiltu.id/services/health_check_service"
+ "api.qobiltu.id/services"
"github.com/gin-gonic/gin"
"net/http"
)
-type HealthCheckController struct {
- healthCheckService *health_check_service.HealthCheckService
+type HealthCheckController interface {
+ Check(ctx *gin.Context)
}
-func NewHealthCheckController(healthCheckService *health_check_service.HealthCheckService) *HealthCheckController {
- return &HealthCheckController{
+type healthCheckController struct {
+ healthCheckService services.HealthCheckService
+}
+
+func NewHealthCheckController(healthCheckService services.HealthCheckService) HealthCheckController {
+ return &healthCheckController{
healthCheckService: healthCheckService,
}
}
-func (h *HealthCheckController) Check(ctx *gin.Context) {
+func (c *healthCheckController) Check(ctx *gin.Context) {
req := models.HealthCheckRequest{}
- res, err := h.healthCheckService.Check(ctx, &req)
+ res, err := c.healthCheckService.Check(ctx, &req)
if err != nil {
response.HandleError(ctx, err)
return
}
- response.HandleSuccess(ctx, "Service is running", http.StatusOK, res, nil)
+ response.HandleSuccess(ctx, http.StatusOK, "Service OK", res, nil)
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
index 3823940b2cd6d5604cdcacb0bde3a110510594f0..7a8ae3cdc19f818f8dea72dde9ce4bcb6b57d692 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
@@ -2,11 +2,12 @@ package main
import (
"api.qobiltu.id/config"
+ cv_controller "api.qobiltu.id/controller/cv"
"api.qobiltu.id/controller/health_check"
"api.qobiltu.id/mail"
"api.qobiltu.id/repositories"
"api.qobiltu.id/router"
- "api.qobiltu.id/services/health_check_service"
+ "api.qobiltu.id/services"
"api.qobiltu.id/utils"
"api.qobiltu.id/worker"
"github.com/hibiken/asynq"
@@ -43,16 +44,23 @@ func main() {
// setup repo, service, and controller
healthCheckRepository := repositories.NewHealthCheckRepository(config.DB)
- healthCheckService := health_check_service.NewHealthCheckService(healthCheckRepository)
+ healthCheckService := services.NewHealthCheckService(healthCheckRepository)
healthCheckController := health_check_controller.NewHealthCheckController(healthCheckService)
+ cvRepository := repositories.NewCVRepository(config.DB)
+ cvService := services.NewCVService(cvRepository)
+ cvController := cv_controller.NewCVController(cvService)
+
// start task processor
err = taskProcessor.Start()
utils.FatalIfErr("failed to start task processor", err)
slog.Info("Task processor started")
// create server
- s, err := router.NewServer(healthCheckController)
+ s, err := router.NewServer(
+ healthCheckController,
+ cvController,
+ )
utils.FatalIfErr("failed to create server", err)
// run server
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
index 9cee0115650b4c655e0e1229f9007f737902cb6c..7dfdaa3ae70424316b29e4140a989ba64f49637f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -1,207 +1,207 @@
-package models
-
-import (
- "time"
-
- uuid "github.com/satori/go.uuid"
-)
-
-type Account struct {
- Id uint `gorm:"primaryKey" json:"id"`
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
- Email string `gorm:"uniqueIndex" json:"email"`
- Password string `json:"password"`
- IsEmailVerified bool `json:"is_email_verified"`
- IsDetailCompleted bool `json:"is_detail_completed"`
- CreatedAt time.Time `json:"created_at"`
- DeletedAt *time.Time `json:"deleted_at" gorm:"default:null"`
-}
-
-type AccountDetails struct {
- ID uint `gorm:"primaryKey" json:"id"`
- AccountID uint `json:"account_id"`
- InitialName string `json:"initial_name"`
- FullName *string `json:"full_name"`
- DateOfBirth *time.Time `json:"date_of_birth"`
- PlaceOfBirth *string `json:"place_of_birth"`
- Domicile *string `json:"domicile"`
- LastJob *string `json:"last_job"`
- Gender *bool `json:"gender"`
- LastEducation *string `json:"last_education"`
- MaritalStatus *string `json:"marital_status"`
- Avatar *string `json:"avatar"`
- PhoneNumber *string `json:"phone_number"`
-}
-
-type EmailVerification struct {
- ID uint `gorm:"primaryKey" json:"id"`
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
- Token uint `json:"token"`
- AccountID uint `json:"account_id"`
- IsExpired bool `json:"is_expired"`
- CreatedAt time.Time `json:"created_at"`
- ExpiredAt time.Time `json:"expired_at"`
-}
-
-type ExternalAuth struct {
- ID uint `gorm:"primaryKey" json:"id"`
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
- OauthID string `json:"oauth_id"`
- AccountID uint `json:"account_id"`
- OauthProvider string `json:"oauth_provider"`
-}
-
-type FCM struct {
- ID uint `gorm:"primaryKey" json:"id"`
- AccountID uint `json:"account_id"`
- FCMToken string `json:"fcm_token"`
-}
-
-type ForgotPassword struct {
- ID uint `gorm:"primaryKey" json:"id"`
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
- Token uint `json:"token"`
- AccountID uint `json:"account_id"`
- IsExpired bool `json:"is_expired"`
- CreatedAt time.Time `json:"created_at"`
- ExpiredAt time.Time `json:"expired_at"`
-}
-
-type Academy struct {
- ID uint `gorm:"primaryKey" json:"id"`
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
- Title string `json:"title"`
- Slug string `json:"slug" gorm:"uniqueIndex" `
- TotalMaterial int `json:"total_material"`
- CompletedMaterial int `json:"completed_material"`
- IsCompletedRead bool `json:"is_read"`
- IsPassedExam bool `json:"is_exam"`
- Description string `json:"description"`
-}
-
-type AcademyMaterial struct {
- ID uint `gorm:"primaryKey" json:"id"`
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
- AcademyID uint `json:"academy_id"`
- Title string `json:"title"`
- Slug string `json:"slug" gorm:"uniqueIndex"`
- IsCompleted bool `json:"is_completed"`
- Description string `json:"description"`
-}
-
-type AcademyContent struct {
- ID uint `gorm:"primaryKey" json:"id"`
- UUID uuid.UUID `json:"uuid"`
- Title string `json:"title"`
- Order uint `json:"order"`
- AcademyMaterialID uint `json:"academy_material_id"`
- Description string `json:"description"`
-}
-type OptionCategory struct {
- ID uint `gorm:"primaryKey" json:"id"`
- OptionName string `json:"option_name"`
- OptionSlug string `json:"option_slug" gorm:"uniqueIndex"`
-}
-
-type OptionValues struct {
- ID uint `gorm:"primaryKey" json:"id"`
- OptionCategoryID uint `json:"option_category_id"`
- OptionValue string `json:"option_value"`
-}
-type AcademyMaterialProgress struct {
- ID uint `gorm:"primaryKey" json:"id"`
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
- AccountID uint `json:"account_id"`
- AcademyMaterialID uint `json:"academy_material_id"`
- Progress uint `json:"progress"`
-}
-
-type AcademyContentProgress struct {
- ID uint `gorm:"primaryKey" json:"id"`
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
- AccountID uint `json:"account_id"`
- AcademyID uint `json:"academy_id"`
-}
-
-type RegionProvince struct {
- ID uint `json:"id"`
- Name string `json:"name"`
- Code string `json:"code"`
-}
-
-type RegionCity struct {
- ID uint `json:"id"`
- Type string `json:"type"`
- Name string `json:"name"`
- Code string `json:"code"`
- FullCode string `json:"full_code"`
- ProvinceID uint `json:"province_id"`
-}
-type Answer struct {
- ID uint `gorm:"primaryKey" json:"id"`
- QuestionID uint `json:"question_id"`
- Content string `json:"content"`
- IsCorrect bool `json:"-"`
-}
-type Question struct {
- ID uint `gorm:"primaryKey" json:"id"`
- QuizID uint `json:"quiz_id"`
- Content string `json:"content"`
- Order int `json:"order"`
- CorrectAnswer uint `json:"-"`
-}
-type Quiz struct {
- ID uint `gorm:"primaryKey" json:"id"`
- AcademyID uint `json:"academy_id"`
- Title string `json:"title"`
- Description string `json:"description"`
- AttemptLimit int `json:"attempt_limit"`
- TimeLimit int `json:"time_limit"`
- MinScore int `json:"min_score"`
- CreatedAt time.Time `json:"created_at"`
-}
-
-type QuizAttempt struct {
- ID uint `gorm:"primaryKey" json:"id"`
- AccountID uint `json:"user_id"`
- QuizID uint `json:"quiz_id"`
- StartedAt time.Time `json:"started_at"`
- DueAt time.Time `json:"due_at"`
- FinishedAt *time.Time `json:"finished_at"`
- Score float64 `json:"score"`
-}
-type UserAnswer struct {
- ID uint `gorm:"primaryKey" json:"id"`
- QuizAttemptID uint `json:"quiz_attempt_id"`
- QuestionID uint `json:"question_id"`
- SelectedAnswer uint `json:"selected_answer"`
- IsCorrect bool `json:"is_correct"`
-}
-type QuizResult struct {
- QuizAttemptID uint `gorm:"column:quiz_attempt_id" json:"quiz_attempt_id"`
- TotalQuestions int `gorm:"column:total_questions" json:"total_questions"`
- CorrectAnswers int `gorm:"column:correct_answers" json:"correct_answers"`
- AverageScore float64 `gorm:"column:average_score" json:"average_score"`
-}
-
-// Gorm table name settings
-func (Account) TableName() string { return "account" }
-func (AccountDetails) TableName() string { return "account_details" }
-func (EmailVerification) TableName() string { return "email_verifications" }
-func (ExternalAuth) TableName() string { return "extern_auth" }
-func (FCM) TableName() string { return "fcm" }
-func (ForgotPassword) TableName() string { return "forgot_password" }
-func (Academy) TableName() string { return "academy" }
-func (AcademyMaterial) TableName() string { return "academy_materials" }
-func (AcademyContent) TableName() string { return "academy_contents" }
-func (AcademyMaterialProgress) TableName() string { return "academy_materials_progress" }
-func (AcademyContentProgress) TableName() string { return "academy_contents_progress" }
-func (RegionProvince) TableName() string { return "region_provinces" }
-func (RegionCity) TableName() string { return "region_cities" }
-func (Answer) TableName() string { return "answers" }
-func (Question) TableName() string { return "questions" }
-func (Quiz) TableName() string { return "quizzes" }
-func (QuizAttempt) TableName() string { return "quiz_attempts" }
-func (UserAnswer) TableName() string { return "user_answers" }
-func (OptionCategory) TableName() string { return "option_categories" }
-func (OptionValues) TableName() string { return "option_values" }
+package models
+
+import (
+ "time"
+
+ uuid "github.com/satori/go.uuid"
+)
+
+type Account struct {
+ Id uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
+ Email string `gorm:"uniqueIndex" json:"email"`
+ Password string `json:"password"`
+ IsEmailVerified bool `json:"is_email_verified"`
+ IsDetailCompleted bool `json:"is_detail_completed"`
+ CreatedAt time.Time `json:"created_at"`
+ DeletedAt *time.Time `json:"deleted_at" gorm:"default:null"`
+}
+
+type AccountDetails struct {
+ ID uint64 `gorm:"primaryKey" json:"id"`
+ AccountID uint `json:"account_id"`
+ InitialName string `json:"initial_name"`
+ FullName *string `json:"full_name"`
+ DateOfBirth *time.Time `json:"date_of_birth"`
+ PlaceOfBirth *string `json:"place_of_birth"`
+ Domicile *string `json:"domicile"`
+ LastJob *string `json:"last_job"`
+ Gender *string `json:"gender"`
+ LastEducation *string `json:"last_education"`
+ MaritalStatus *string `json:"marital_status"`
+ Avatar *string `json:"avatar"`
+ PhoneNumber *string `json:"phone_number"`
+}
+
+type EmailVerification struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
+ Token uint `json:"token"`
+ AccountID uint `json:"account_id"`
+ IsExpired bool `json:"is_expired"`
+ CreatedAt time.Time `json:"created_at"`
+ ExpiredAt time.Time `json:"expired_at"`
+}
+
+type ExternalAuth struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
+ OauthID string `json:"oauth_id"`
+ AccountID uint `json:"account_id"`
+ OauthProvider string `json:"oauth_provider"`
+}
+
+type FCM struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ AccountID uint `json:"account_id"`
+ FCMToken string `json:"fcm_token"`
+}
+
+type ForgotPassword struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
+ Token uint `json:"token"`
+ AccountID uint `json:"account_id"`
+ IsExpired bool `json:"is_expired"`
+ CreatedAt time.Time `json:"created_at"`
+ ExpiredAt time.Time `json:"expired_at"`
+}
+
+type Academy struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
+ Title string `json:"title"`
+ Slug string `json:"slug" gorm:"uniqueIndex" `
+ TotalMaterial int `json:"total_material"`
+ CompletedMaterial int `json:"completed_material"`
+ IsCompletedRead bool `json:"is_read"`
+ IsPassedExam bool `json:"is_exam"`
+ Description string `json:"description"`
+}
+
+type AcademyMaterial struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
+ AcademyID uint `json:"academy_id"`
+ Title string `json:"title"`
+ Slug string `json:"slug" gorm:"uniqueIndex"`
+ IsCompleted bool `json:"is_completed"`
+ Description string `json:"description"`
+}
+
+type AcademyContent struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `json:"uuid"`
+ Title string `json:"title"`
+ Order uint `json:"order"`
+ AcademyMaterialID uint `json:"academy_material_id"`
+ Description string `json:"description"`
+}
+type OptionCategory struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ OptionName string `json:"option_name"`
+ OptionSlug string `json:"option_slug" gorm:"uniqueIndex"`
+}
+
+type OptionValues struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ OptionCategoryID uint `json:"option_category_id"`
+ OptionValue string `json:"option_value"`
+}
+type AcademyMaterialProgress struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
+ AccountID uint `json:"account_id"`
+ AcademyMaterialID uint `json:"academy_material_id"`
+ Progress uint `json:"progress"`
+}
+
+type AcademyContentProgress struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
+ AccountID uint `json:"account_id"`
+ AcademyID uint `json:"academy_id"`
+}
+
+type RegionProvince struct {
+ ID uint `json:"id"`
+ Name string `json:"name"`
+ Code string `json:"code"`
+}
+
+type RegionCity struct {
+ ID uint `json:"id"`
+ Type string `json:"type"`
+ Name string `json:"name"`
+ Code string `json:"code"`
+ FullCode string `json:"full_code"`
+ ProvinceID uint `json:"province_id"`
+}
+type Answer struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ QuestionID uint `json:"question_id"`
+ Content string `json:"content"`
+ IsCorrect bool `json:"-"`
+}
+type Question struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ QuizID uint `json:"quiz_id"`
+ Content string `json:"content"`
+ Order int `json:"order"`
+ CorrectAnswer uint `json:"-"`
+}
+type Quiz struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ AcademyID uint `json:"academy_id"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ AttemptLimit int `json:"attempt_limit"`
+ TimeLimit int `json:"time_limit"`
+ MinScore int `json:"min_score"`
+ CreatedAt time.Time `json:"created_at"`
+}
+
+type QuizAttempt struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ AccountID uint `json:"user_id"`
+ QuizID uint `json:"quiz_id"`
+ StartedAt time.Time `json:"started_at"`
+ DueAt time.Time `json:"due_at"`
+ FinishedAt *time.Time `json:"finished_at"`
+ Score float64 `json:"score"`
+}
+type UserAnswer struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ QuizAttemptID uint `json:"quiz_attempt_id"`
+ QuestionID uint `json:"question_id"`
+ SelectedAnswer uint `json:"selected_answer"`
+ IsCorrect bool `json:"is_correct"`
+}
+type QuizResult struct {
+ QuizAttemptID uint `gorm:"column:quiz_attempt_id" json:"quiz_attempt_id"`
+ TotalQuestions int `gorm:"column:total_questions" json:"total_questions"`
+ CorrectAnswers int `gorm:"column:correct_answers" json:"correct_answers"`
+ AverageScore float64 `gorm:"column:average_score" json:"average_score"`
+}
+
+// Gorm table name settings
+func (Account) TableName() string { return "account" }
+func (AccountDetails) TableName() string { return "account_details" }
+func (EmailVerification) TableName() string { return "email_verifications" }
+func (ExternalAuth) TableName() string { return "extern_auth" }
+func (FCM) TableName() string { return "fcm" }
+func (ForgotPassword) TableName() string { return "forgot_password" }
+func (Academy) TableName() string { return "academy" }
+func (AcademyMaterial) TableName() string { return "academy_materials" }
+func (AcademyContent) TableName() string { return "academy_contents" }
+func (AcademyMaterialProgress) TableName() string { return "academy_materials_progress" }
+func (AcademyContentProgress) TableName() string { return "academy_contents_progress" }
+func (RegionProvince) TableName() string { return "region_provinces" }
+func (RegionCity) TableName() string { return "region_cities" }
+func (Answer) TableName() string { return "answers" }
+func (Question) TableName() string { return "questions" }
+func (Quiz) TableName() string { return "quizzes" }
+func (QuizAttempt) TableName() string { return "quiz_attempts" }
+func (UserAnswer) TableName() string { return "user_answers" }
+func (OptionCategory) TableName() string { return "option_categories" }
+func (OptionValues) TableName() string { return "option_values" }
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
index 009c51416cb7e0700453abe79edf441f9195911d..239311daab53241d70b48aae836b8ea483f22196 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
@@ -1,17 +1,23 @@
package models
type Exception struct {
- Unauthorized bool `json:"unauthorized,omitempty"`
- BadRequest bool `json:"bad_request,omitempty"`
- DataNotFound bool `json:"data_not_found,omitempty"`
- InternalServerError bool `json:"internal_server_error,omitempty"`
- DataDuplicate bool `json:"data_duplicate,omitempty"`
- QueryError bool `json:"query_error,omitempty"`
- InvalidPasswordLength bool `json:"invalid_password_length,omitempty"`
- IsPassTheLimit bool `json:"is_pass_the_limit,omitempty"`
- IsTimeOut bool `json:"is_time_out,omitempty"`
- AttemptNotFound bool `json:"attempt_not_found,omitempty"`
- Forbidden bool `json:"forbidden,omitempty"`
- ValidationError bool `json:"validation_error,omitempty"`
- Message string `json:"message,omitempty"`
+ Unauthorized bool `json:"unauthorized,omitempty"`
+ BadRequest bool `json:"bad_request,omitempty"`
+ DataNotFound bool `json:"data_not_found,omitempty"`
+ InternalServerError bool `json:"internal_server_error,omitempty"`
+ DataDuplicate bool `json:"data_duplicate,omitempty"`
+ QueryError bool `json:"query_error,omitempty"`
+ InvalidPasswordLength bool `json:"invalid_password_length,omitempty"`
+ IsPassTheLimit bool `json:"is_pass_the_limit,omitempty"`
+ IsTimeOut bool `json:"is_time_out,omitempty"`
+ AttemptNotFound bool `json:"attempt_not_found,omitempty"`
+ Forbidden bool `json:"forbidden,omitempty"`
+ ValidationError bool `json:"validation_error,omitempty"`
+
+ Message string `json:"message,omitempty"`
+ Err error `json:"-"`
+}
+
+func (a Exception) Error() string {
+ return a.Err.Error()
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/health_check_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/health_check_model.go
index 067a6bca6ef9bc1e5e77bcd29331eab2eba1a4d9..c25ffd52635d10cb6a42d171998f80bfb9344f90 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/health_check_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/health_check_model.go
@@ -1,8 +1,6 @@
package models
-type HealthCheckRequest struct {
- ExampleCallback func() (string, string)
-}
+type HealthCheckRequest struct{}
type HealthCheckResponse struct {
DatabaseStatus string `json:"database_status"`
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/health_check_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/health_check_repository.go
index d99efa323067a9879c9e71cf7a47565d97dd8695..dcc00860da14b0f88a8574992b5a37e5bfcb7248 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/health_check_repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/health_check_repository.go
@@ -7,29 +7,25 @@ import (
"gorm.io/gorm"
)
-type HealthCheckRepository struct {
+type HealthCheckRepository interface {
+ Check(ctx context.Context, req *models.HealthCheckRequest) (*models.HealthCheckResponse, error)
+}
+
+type healthCheckRepository struct {
db *gorm.DB
}
-func NewHealthCheckRepository(db *gorm.DB) *HealthCheckRepository {
- return &HealthCheckRepository{
+func NewHealthCheckRepository(db *gorm.DB) HealthCheckRepository {
+ return &healthCheckRepository{
db: db,
}
}
-func (r *HealthCheckRepository) Check(ctx context.Context, req *models.HealthCheckRequest) (*models.HealthCheckResponse, error) {
- res := &models.HealthCheckResponse{}
-
+func (r *healthCheckRepository) Check(ctx context.Context, req *models.HealthCheckRequest) (*models.HealthCheckResponse, error) {
err := config.RunTx(ctx, r.db, func(tx *gorm.DB) error {
if err := tx.Exec("SELECT 1").Error; err != nil {
return err
}
-
- // call logic from service if needed
- // instead of writing the logic in the repository
- // and make sure the param consist of the ctx and request
- res.DatabaseStatus, res.RedisStatus = req.ExampleCallback()
-
return nil
})
@@ -37,5 +33,8 @@ func (r *HealthCheckRepository) Check(ctx context.Context, req *models.HealthChe
return nil, err
}
- return res, nil
+ return &models.HealthCheckResponse{
+ DatabaseStatus: "OK",
+ RedisStatus: "OK",
+ }, nil
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/api_response_v2.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/api_response_v2.go
new file mode 100644
index 0000000000000000000000000000000000000000..9369a49284f499bbc175cd53d28f0629b12c1b0e
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/api_response_v2.go
@@ -0,0 +1,79 @@
+package response
+
+import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/utils"
+ "errors"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+func HandleError(c *gin.Context, err error) {
+ var exception models.Exception
+
+ if errors.As(err, &exception) {
+ utils.LogError(exception.Err)
+
+ switch {
+ case exception.DataDuplicate:
+ responseError(c, http.StatusConflict, exception)
+ case exception.Unauthorized:
+ responseError(c, http.StatusUnauthorized, exception)
+ case exception.DataNotFound:
+ responseError(c, http.StatusNotFound, exception)
+ case exception.Forbidden:
+ responseError(c, http.StatusForbidden, exception)
+ case exception.BadRequest:
+ responseError(c, http.StatusBadRequest, exception)
+ case exception.InternalServerError:
+ responseError(c, http.StatusInternalServerError, exception)
+ case exception.QueryError:
+ responseError(c, http.StatusInternalServerError, exception)
+ case exception.InvalidPasswordLength:
+ responseError(c, http.StatusBadRequest, exception)
+ case exception.IsPassTheLimit:
+ responseError(c, http.StatusTooManyRequests, exception)
+ case exception.IsTimeOut:
+ responseError(c, http.StatusRequestTimeout, exception)
+ case exception.AttemptNotFound:
+ responseError(c, http.StatusNotFound, exception)
+ case exception.ValidationError:
+ responseError(c, http.StatusUnprocessableEntity, exception)
+ default:
+ responseError(c, http.StatusInternalServerError, exception)
+ }
+ } else {
+ utils.LogError(err)
+ responseError(c, http.StatusInternalServerError, models.Exception{
+ InternalServerError: true,
+ Message: "Internal Server Error",
+ })
+ }
+}
+
+func HandleSuccess(c *gin.Context, status int, msg string, data any, metaData any) {
+ res := models.SuccessResponse{
+ Status: "success",
+ Message: msg,
+ Data: data,
+ MetaData: metaData,
+ }
+
+ c.JSON(status, res)
+ return
+}
+
+func responseError(c *gin.Context, status int, exception models.Exception) {
+ message := exception.Message
+ exception.Message = ""
+
+ res := models.ErrorResponse{
+ Status: "error",
+ Message: message,
+ Errors: exception,
+ }
+
+ c.AbortWithStatusJSON(status, res)
+ return
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/gorm.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/gorm.go
new file mode 100644
index 0000000000000000000000000000000000000000..262bf4e1965972ddc4f769d9e5b365ccf2f7e6c8
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/gorm.go
@@ -0,0 +1,62 @@
+package response
+
+import (
+ "api.qobiltu.id/models"
+ "errors"
+ "strings"
+
+ "gorm.io/gorm"
+)
+
+func HandleGormError(err error, fallbackMessage string) error {
+ if err == nil {
+ return nil
+ }
+
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return models.Exception{
+ Message: "Data not found",
+ DataNotFound: true,
+ Err: err,
+ }
+ }
+
+ lowerErr := strings.ToLower(err.Error())
+ if strings.Contains(lowerErr, "duplicated key") || strings.Contains(lowerErr, "unique constraint") || strings.Contains(lowerErr, "duplicate entry") {
+ return models.Exception{
+ Message: "Data already exists",
+ DataDuplicate: true,
+ Err: err,
+ }
+ }
+
+ if strings.Contains(lowerErr, "password") && strings.Contains(lowerErr, "length") {
+ return models.Exception{
+ Message: "Invalid password length",
+ InvalidPasswordLength: true,
+ Err: err,
+ }
+ }
+
+ if strings.Contains(lowerErr, "permission denied") || strings.Contains(lowerErr, "forbidden") {
+ return models.Exception{
+ Message: "Access forbidden",
+ Forbidden: true,
+ Err: err,
+ }
+ }
+
+ if errors.As(err, &gorm.ErrInvalidData) {
+ return models.Exception{
+ Message: "Invalid data format",
+ BadRequest: true,
+ Err: err,
+ }
+ }
+
+ return models.Exception{
+ Message: fallbackMessage,
+ InternalServerError: true,
+ Err: err,
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/server.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/server.go
index a8040d69a87a669ca6eda81729fe66828afb2bab..3573257d63132d14ed04a38abc3ec03fbaa99bd2 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/server.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/server.go
@@ -7,11 +7,11 @@ import (
type Server struct {
router *gin.Engine
- healthCheckController *health_check_controller.HealthCheckController
+ healthCheckController health_check_controller.HealthCheckController
}
func NewServer(
- healthCheckController *health_check_controller.HealthCheckController,
+ healthCheckController health_check_controller.HealthCheckController,
) (*Server, error) {
router := gin.Default()
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/health_check_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/health_check_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..e9ec330bb3a64af5d68b0588264ab1e415de527f
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/health_check_service.go
@@ -0,0 +1,31 @@
+package services
+
+import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+ "api.qobiltu.id/response"
+ "context"
+)
+
+type HealthCheckService interface {
+ Check(ctx context.Context, req *models.HealthCheckRequest) (*models.HealthCheckResponse, error)
+}
+
+type healthCheckService struct {
+ healthCheckRepository repositories.HealthCheckRepository
+}
+
+func NewHealthCheckService(healthCheckRepository repositories.HealthCheckRepository) HealthCheckService {
+ return &healthCheckService{
+ healthCheckRepository: healthCheckRepository,
+ }
+}
+
+func (s *healthCheckService) Check(ctx context.Context, req *models.HealthCheckRequest) (*models.HealthCheckResponse, error) {
+ res, err := s.healthCheckRepository.Check(ctx, req)
+ if err != nil {
+ return nil, response.HandleGormError(err, "Internal Server Error")
+ }
+
+ return res, nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
index c8803332f0467a840fdcb2099ac1785bde70a06d..7aa7effca54a8f9b826b9bb66742352d81ef60da 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
@@ -1,115 +1,115 @@
-package services
-
-import (
- "regexp"
- "strconv"
- "strings"
-
- "api.qobiltu.id/models"
- "api.qobiltu.id/repositories"
-)
-
-type UserProfileService struct {
- Service[models.AccountDetails, models.UserProfileResponse]
-}
-
-// SanitizePhoneNumber membersihkan dan menormalkan nomor telepon ke format +62
-func SanitizePhoneNumber(input string) string {
- // Hilangkan semua spasi dan strip
- input = strings.ReplaceAll(input, " ", "")
- input = strings.ReplaceAll(input, "-", "")
- input = strings.ReplaceAll(input, "(", "")
- input = strings.ReplaceAll(input, ")", "")
-
- // Hilangkan semua karakter non-digit kecuali +
- re := regexp.MustCompile(`[^0-9\+]`)
- input = re.ReplaceAllString(input, "")
-
- // Handle nomor diawali 0 (contoh: 0812...) menjadi +62812...
- if strings.HasPrefix(input, "0") {
- input = "+62" + input[1:]
- }
-
- // Handle jika diawali dengan 62 tanpa + (contoh: 62812...)
- if strings.HasPrefix(input, "62") && !strings.HasPrefix(input, "+62") {
- input = "+" + input
- }
-
- // Handle jika tidak ada awalan +62 sama sekali (contoh: 8123456789)
- if !strings.HasPrefix(input, "+62") {
- if strings.HasPrefix(input, "8") {
- input = "+62" + input
- }
- }
-
- return input
-}
-func (s *UserProfileService) Create() {
- userProfile := repositories.CreateAccountDetails(s.Constructor)
- s.Error = userProfile.RowsError
- if userProfile.NoRecord {
- s.Exception.DataNotFound = true
- s.Exception.Message = "There is no account with given credentials!"
- return
- }
- s.Result = models.UserProfileResponse{
- Account: repositories.GetAccountById(s.Constructor.AccountID).Result,
- Details: userProfile.Result,
- }
-}
-func (s *UserProfileService) Retrieve() {
- userProfile := repositories.GetDetailAccountById(s.Constructor.AccountID)
- s.Error = userProfile.RowsError
- if userProfile.NoRecord {
- s.Exception.DataNotFound = true
- s.Exception.Message = "There is no account with given credentials!"
- return
- }
- s.Result = models.UserProfileResponse{
- Account: repositories.GetAccountById(s.Constructor.AccountID).Result,
- Details: userProfile.Result,
- }
- s.Result.Account.Password = "SECRET"
-}
-
-func (s *UserProfileService) Update() {
- if s.Constructor.PhoneNumber != nil {
- phoneNumber := *s.Constructor.PhoneNumber
- *s.Constructor.PhoneNumber = SanitizePhoneNumber(phoneNumber)
- }
- usersCount := repositories.GetAllAccount().RowsCount
- var initialName string
- if s.Constructor.Gender != nil {
- if *s.Constructor.Gender {
- initialName = "IKH_"
- } else {
- initialName = "AKH_"
- }
- }
-
- initialName += strconv.Itoa(usersCount)
- s.Constructor.InitialName = initialName
- userProfile := repositories.UpdateAccountDetails(s.Constructor)
- s.Error = userProfile.RowsError
- if userProfile.NoRecord {
- s.Exception.DataNotFound = true
- s.Exception.Message = "There is no account with given credentials!"
- return
- }
- account := repositories.GetAccountById(s.Constructor.AccountID)
- account.Result.IsDetailCompleted = (userProfile.Result.InitialName != "" &&
- userProfile.Result.FullName != nil &&
- userProfile.Result.DateOfBirth != nil &&
- userProfile.Result.PlaceOfBirth != nil &&
- userProfile.Result.Domicile != nil &&
- userProfile.Result.LastJob != nil &&
- userProfile.Result.Gender != nil &&
- userProfile.Result.LastEducation != nil &&
- userProfile.Result.MaritalStatus != nil)
- repositories.UpdateAccount(account.Result)
- s.Result = models.UserProfileResponse{
- Account: account.Result,
- Details: userProfile.Result,
- }
- s.Result.Account.Password = "SECRET"
-}
+package services
+
+import (
+ "regexp"
+ "strconv"
+ "strings"
+
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+)
+
+type UserProfileService struct {
+ Service[models.AccountDetails, models.UserProfileResponse]
+}
+
+// SanitizePhoneNumber membersihkan dan menormalkan nomor telepon ke format +62
+func SanitizePhoneNumber(input string) string {
+ // Hilangkan semua spasi dan strip
+ input = strings.ReplaceAll(input, " ", "")
+ input = strings.ReplaceAll(input, "-", "")
+ input = strings.ReplaceAll(input, "(", "")
+ input = strings.ReplaceAll(input, ")", "")
+
+ // Hilangkan semua karakter non-digit kecuali +
+ re := regexp.MustCompile(`[^0-9\+]`)
+ input = re.ReplaceAllString(input, "")
+
+ // Handle nomor diawali 0 (contoh: 0812...) menjadi +62812...
+ if strings.HasPrefix(input, "0") {
+ input = "+62" + input[1:]
+ }
+
+ // Handle jika diawali dengan 62 tanpa + (contoh: 62812...)
+ if strings.HasPrefix(input, "62") && !strings.HasPrefix(input, "+62") {
+ input = "+" + input
+ }
+
+ // Handle jika tidak ada awalan +62 sama sekali (contoh: 8123456789)
+ if !strings.HasPrefix(input, "+62") {
+ if strings.HasPrefix(input, "8") {
+ input = "+62" + input
+ }
+ }
+
+ return input
+}
+func (s *UserProfileService) Create() {
+ userProfile := repositories.CreateAccountDetails(s.Constructor)
+ s.Error = userProfile.RowsError
+ if userProfile.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account with given credentials!"
+ return
+ }
+ s.Result = models.UserProfileResponse{
+ Account: repositories.GetAccountById(s.Constructor.AccountID).Result,
+ Details: userProfile.Result,
+ }
+}
+func (s *UserProfileService) Retrieve() {
+ userProfile := repositories.GetDetailAccountById(s.Constructor.AccountID)
+ s.Error = userProfile.RowsError
+ if userProfile.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account with given credentials!"
+ return
+ }
+ s.Result = models.UserProfileResponse{
+ Account: repositories.GetAccountById(s.Constructor.AccountID).Result,
+ Details: userProfile.Result,
+ }
+ s.Result.Account.Password = "SECRET"
+}
+
+func (s *UserProfileService) Update() {
+ if s.Constructor.PhoneNumber != nil {
+ phoneNumber := *s.Constructor.PhoneNumber
+ *s.Constructor.PhoneNumber = SanitizePhoneNumber(phoneNumber)
+ }
+ usersCount := repositories.GetAllAccount().RowsCount
+ var initialName string
+ if s.Constructor.Gender != nil {
+ if strings.ToLower(*s.Constructor.Gender) == "laki-laki" {
+ initialName = "IKH_"
+ } else {
+ initialName = "AKH_"
+ }
+ }
+
+ initialName += strconv.Itoa(usersCount)
+ s.Constructor.InitialName = initialName
+ userProfile := repositories.UpdateAccountDetails(s.Constructor)
+ s.Error = userProfile.RowsError
+ if userProfile.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account with given credentials!"
+ return
+ }
+ account := repositories.GetAccountById(s.Constructor.AccountID)
+ account.Result.IsDetailCompleted = (userProfile.Result.InitialName != "" &&
+ userProfile.Result.FullName != nil &&
+ userProfile.Result.DateOfBirth != nil &&
+ userProfile.Result.PlaceOfBirth != nil &&
+ userProfile.Result.Domicile != nil &&
+ userProfile.Result.LastJob != nil &&
+ userProfile.Result.Gender != nil &&
+ userProfile.Result.LastEducation != nil &&
+ userProfile.Result.MaritalStatus != nil)
+ repositories.UpdateAccount(account.Result)
+ s.Result = models.UserProfileResponse{
+ Account: account.Result,
+ Details: userProfile.Result,
+ }
+ s.Result.Account.Password = "SECRET"
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
index 8483ea7af8b5c6267fb167d8dd7b84354ceed129..2a34a041638754e37acca24b7b74ff05d3645286 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
@@ -25,6 +25,12 @@ FROM alpine:latest
# Set working directory
WORKDIR /app
+# Install tzdata
+RUN apk update && apk add --no-cache tzdata
+
+# Set the timezone environment variable
+ENV TZ="Asia/Jakarta"
+
# Copy hasil build dari builder ke image runtime
COPY --from=builder /app/main .
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
index 65c8cabfc8ef2a61bf1694229cb9bf3126a94f4b..a7a0c359d4621f46d72637aeff9eae0f9037ea23 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
@@ -25,4 +25,5 @@ jobs:
git pull origin main
docker-compose down -v
docker-compose up --build -d
+ docker image prune -f
EOF
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/apperror/apperror.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/apperror/apperror.go
new file mode 100644
index 0000000000000000000000000000000000000000..eaa369d8aa61ff943d5c50cb7a282e176ec99835
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/apperror/apperror.go
@@ -0,0 +1,168 @@
+package apperror
+
+import (
+ "api.qobiltu.id/validation"
+ "fmt"
+)
+
+// AppError is a custom error type for the application.
+type AppError struct {
+ Code string // Optional: Error code for programmatic handling
+ Message string // Human-readable error message
+ Err error // Underlying error, if any
+ Details map[string]any // Optional: Additional details about the error
+}
+
+func (e AppError) Error() string {
+ if e.Code != "" {
+ return fmt.Sprintf("[%s] %s", e.Code, e.Message)
+ }
+ return e.Message
+}
+
+// NewAppError creates a new AppError.
+func NewAppError(code, message string, err error, details map[string]any) error {
+ return &AppError{
+ Code: code,
+ Message: message,
+ Err: err,
+ Details: details,
+ }
+}
+
+// ValidationError represents errors related to data validation.
+type ValidationError struct {
+ Errors []validation.ErrorMessage // Structure to hold field and message of validation errors
+}
+
+func (e ValidationError) Error() string {
+ return fmt.Sprintf("validation failed: %+v", e.Errors)
+}
+
+// NewValidationError creates a new ValidationError.
+func NewValidationError(message string, errors []validation.ErrorMessage) error {
+ return &AppError{
+ Code: "VALIDATION_ERROR",
+ Message: message,
+ Err: ValidationError{Errors: errors},
+ }
+}
+
+// InternalError represents unexpected errors within the application.
+type InternalError struct {
+ Message string
+ Err error
+}
+
+func (e InternalError) Error() string {
+ return fmt.Sprintf("internal server error: %v", e.Err)
+}
+
+// NewInternalError creates a new InternalError wrapped in AppError.
+func NewInternalError(message string, err error) error {
+ return &AppError{
+ Code: "INTERNAL_ERROR",
+ Message: message,
+ Err: InternalError{Message: message, Err: err},
+ }
+}
+
+// ConflictError represents errors due to a conflict with the current state.
+type ConflictError struct {
+ Message string
+ Err error
+}
+
+func (e ConflictError) Error() string {
+ return fmt.Sprintf("conflict: %s", e.Err)
+}
+
+// NewConflictError creates a new ConflictError wrapped in AppError.
+func NewConflictError(message string, err error) error {
+ return &AppError{
+ Code: "CONFLICT_ERROR",
+ Message: message,
+ Err: ConflictError{Message: message, Err: err},
+ }
+}
+
+// NotFoundError represents errors when a resource is not found.
+type NotFoundError struct {
+ Message string
+ Resource string
+ ID any
+}
+
+func (e NotFoundError) Error() string {
+ return fmt.Sprintf("%s with ID '%v' not found", e.Resource, e.ID)
+}
+
+// NewNotFoundError creates a new NotFoundError wrapped in AppError.
+func NewNotFoundError(resource string, id any) error {
+ message := fmt.Sprintf("%s not found", resource)
+ return &AppError{
+ Code: "NOT_FOUND_ERROR",
+ Message: fmt.Sprintf("%s not found", resource),
+ Err: NotFoundError{Message: message, Resource: resource, ID: id},
+ Details: map[string]any{
+ "resource": resource,
+ "id": id,
+ },
+ }
+}
+
+// UnauthorizedError represents errors when access is denied due to lack of credentials.
+type UnauthorizedError struct {
+ Message string
+ Err error
+}
+
+func (e UnauthorizedError) Error() string {
+ return fmt.Sprintf("unauthorized: %s", e.Err)
+}
+
+// NewUnauthorizedError creates a new UnauthorizedError wrapped in AppError.
+func NewUnauthorizedError(message string, err error) error {
+ return &AppError{
+ Code: "UNAUTHORIZED_ERROR",
+ Message: message,
+ Err: UnauthorizedError{Message: message, Err: err},
+ }
+}
+
+// ForbiddenError represents errors when access is denied even with valid credentials.
+type ForbiddenError struct {
+ Message string
+ Err error
+}
+
+func (e ForbiddenError) Error() string {
+ return fmt.Sprintf("forbidden: %s", e.Err)
+}
+
+// NewForbiddenError creates a new ForbiddenError wrapped in AppError.
+func NewForbiddenError(message string, err error) error {
+ return &AppError{
+ Code: "FORBIDDEN_ERROR",
+ Message: message,
+ Err: ForbiddenError{Message: message, Err: err},
+ }
+}
+
+// BadRequestError represents errors due to an invalid request.
+type BadRequestError struct {
+ Message string
+ Err error
+}
+
+func (e BadRequestError) Error() string {
+ return fmt.Sprintf("bad request: %s", e.Err)
+}
+
+func NewBadRequestError(message string, err error) error {
+ return &AppError{
+ Code: "BAD_REQUEST_ERROR",
+ Message: message,
+ Err: BadRequestError{Message: message, Err: err},
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
index a3a3ccb2a25825af877a90853f061de639c1cde4..cc8bfad44cf1db82ffae8cf9f78f542f93099cb1 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
@@ -1,79 +1,80 @@
-package config
-
-import (
- "fmt"
- "log"
- "os"
-
- "gorm.io/driver/postgres"
- "gorm.io/gorm"
- "gorm.io/gorm/logger"
-
- "api.qobiltu.id/models"
- "github.com/joho/godotenv"
-)
-
-var DB *gorm.DB
-var err error
-var Salt string
-
-func init() {
- godotenv.Load()
- if err != nil {
- fmt.Println("Gagal membaca file .env")
- return
- }
- os.Setenv("TZ", "Asia/Jakarta")
- dbHost := os.Getenv("DB_HOST")
- dbPort := os.Getenv("DB_PORT")
- dbUser := os.Getenv("DB_USER")
- dbPassword := os.Getenv("DB_PASSWORD")
- dbName := os.Getenv("DB_NAME")
- Salt := os.Getenv("SALT")
- dsn := "host=" + dbHost + " user=" + dbUser + " password=" + dbPassword + " dbname=" + dbName + " port=" + dbPort + " sslmode=disable TimeZone=Asia/Jakarta"
- DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{TranslateError: true})
- if err != nil {
- panic(err)
- }
- if Salt == "" {
- Salt = "D3f4u|t"
- }
-
- // Call AutoMigrateAll to perform auto-migration
- AutoMigrateAll(DB)
-}
-
-func AutoMigrateAll(db *gorm.DB) {
- // Enable logger to see SQL logs
- db.Logger.LogMode(logger.Info)
-
- // Auto-migrate all models
- err := db.AutoMigrate(
- &models.Account{},
- &models.AccountDetails{},
- &models.EmailVerification{},
- &models.ExternalAuth{},
- &models.FCM{},
- &models.ForgotPassword{},
- &models.Academy{},
- &models.AcademyMaterial{},
- &models.AcademyContent{},
- &models.AcademyMaterialProgress{},
- &models.AcademyContentProgress{},
- &models.RegionCity{},
- &models.RegionProvince{},
- &models.OptionCategory{},
- &models.OptionValues{},
- &models.Quiz{},
- &models.QuizAttempt{},
- &models.Question{},
- &models.Answer{},
- &models.UserAnswer{},
- )
-
- if err != nil {
- log.Fatal(err)
- }
-
- fmt.Println("Migration completed successfully.")
-}
+package config
+
+import (
+ "fmt"
+ "log"
+ "log/slog"
+ "os"
+
+ "gorm.io/driver/postgres"
+ "gorm.io/gorm"
+ "gorm.io/gorm/logger"
+
+ "api.qobiltu.id/models"
+ "github.com/joho/godotenv"
+)
+
+var DB *gorm.DB
+var err error
+var Salt string
+
+func init() {
+ godotenv.Load()
+ if err != nil {
+ fmt.Println("Gagal membaca file .env")
+ return
+ }
+ os.Setenv("TZ", "Asia/Jakarta")
+ dbHost := os.Getenv("DB_HOST")
+ dbPort := os.Getenv("DB_PORT")
+ dbUser := os.Getenv("DB_USER")
+ dbPassword := os.Getenv("DB_PASSWORD")
+ dbName := os.Getenv("DB_NAME")
+ Salt := os.Getenv("SALT")
+ dsn := "host=" + dbHost + " user=" + dbUser + " password=" + dbPassword + " dbname=" + dbName + " port=" + dbPort + " sslmode=disable TimeZone=Asia/Jakarta"
+ DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{TranslateError: true})
+ if err != nil {
+ panic(err)
+ }
+ if Salt == "" {
+ Salt = "D3f4u|t"
+ }
+
+ // Call AutoMigrateAll to perform auto-migration
+ AutoMigrateAll(DB)
+}
+
+func AutoMigrateAll(db *gorm.DB) {
+ // Enable logger to see SQL logs
+ db.Logger.LogMode(logger.Info)
+
+ // Auto-migrate all models
+ err := db.AutoMigrate(
+ &models.Account{},
+ &models.AccountDetails{},
+ &models.EmailVerification{},
+ &models.ExternalAuth{},
+ &models.FCM{},
+ &models.ForgotPassword{},
+ &models.Academy{},
+ &models.AcademyMaterial{},
+ &models.AcademyContent{},
+ &models.AcademyMaterialProgress{},
+ &models.AcademyContentProgress{},
+ &models.RegionCity{},
+ &models.RegionProvince{},
+ &models.OptionCategory{},
+ &models.OptionValues{},
+ &models.Quiz{},
+ &models.QuizAttempt{},
+ &models.Question{},
+ &models.Answer{},
+ &models.UserAnswer{},
+ )
+
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ slog.Info("Auto-migration completed successfully")
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/tx.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/tx.go
new file mode 100644
index 0000000000000000000000000000000000000000..b2c17675a0ef59cb6bf11243dac899b7bab746d0
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/tx.go
@@ -0,0 +1,34 @@
+package config
+
+import (
+ "context"
+ "fmt"
+ "gorm.io/gorm"
+)
+
+func RunTx(ctx context.Context, db *gorm.DB, fn func(tx *gorm.DB) error) error {
+ tx := db.WithContext(ctx).Begin()
+ if tx.Error != nil {
+ return fmt.Errorf("failed to begin transaction: %w", tx.Error)
+ }
+
+ defer func() {
+ if p := recover(); p != nil {
+ _ = tx.Rollback()
+ panic(p) // Re-throw panic setelah rollback
+ }
+ }()
+
+ if err := fn(tx); err != nil {
+ if rbErr := tx.Rollback().Error; rbErr != nil {
+ return fmt.Errorf("transaction error: %v, rollback error: %w", err, rbErr)
+ }
+ return err
+ }
+
+ if err := tx.Commit().Error; err != nil {
+ return fmt.Errorf("failed to commit transaction: %w", err)
+ }
+
+ return nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/health_check/health_check_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/health_check/health_check_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..6c1d9a449600dd2ac3f6fd550531817150805456
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/health_check/health_check_controller.go
@@ -0,0 +1,31 @@
+package health_check_controller
+
+import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/response"
+ "api.qobiltu.id/services/health_check_service"
+ "github.com/gin-gonic/gin"
+ "net/http"
+)
+
+type HealthCheckController struct {
+ healthCheckService *health_check_service.HealthCheckService
+}
+
+func NewHealthCheckController(healthCheckService *health_check_service.HealthCheckService) *HealthCheckController {
+ return &HealthCheckController{
+ healthCheckService: healthCheckService,
+ }
+}
+
+func (h *HealthCheckController) Check(ctx *gin.Context) {
+ req := models.HealthCheckRequest{}
+
+ res, err := h.healthCheckService.Check(ctx, &req)
+ if err != nil {
+ response.HandleError(ctx, err)
+ return
+ }
+
+ response.HandleSuccess(ctx, "Service is running", http.StatusOK, res, nil)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
index 3c3c55f7fad8a1356daf2ec03852b3f2ebd9786d..009c51416cb7e0700453abe79edf441f9195911d 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
@@ -1,15 +1,17 @@
-package models
-
-type Exception struct {
- Unauthorized bool `json:"unauthorized,omitempty"`
- BadRequest bool `json:"bad_request,omitempty"`
- DataNotFound bool `json:"data_not_found,omitempty"`
- InternalServerError bool `json:"internal_server_error,omitempty"`
- DataDuplicate bool `json:"data_duplicate,omitempty"`
- QueryError bool `json:"query_error,omitempty"`
- InvalidPasswordLength bool `json:"invalid_password_length,omitempty"`
- IsPassTheLimit bool `json:"is_pass_the_limit,omitempty"`
- IsTimeOut bool `json:"is_time_out,omitempty"`
- AttemptNotFound bool `json:"attempt_not_found,omitempty"`
- Message string `json:"message,omitempty"`
-}
+package models
+
+type Exception struct {
+ Unauthorized bool `json:"unauthorized,omitempty"`
+ BadRequest bool `json:"bad_request,omitempty"`
+ DataNotFound bool `json:"data_not_found,omitempty"`
+ InternalServerError bool `json:"internal_server_error,omitempty"`
+ DataDuplicate bool `json:"data_duplicate,omitempty"`
+ QueryError bool `json:"query_error,omitempty"`
+ InvalidPasswordLength bool `json:"invalid_password_length,omitempty"`
+ IsPassTheLimit bool `json:"is_pass_the_limit,omitempty"`
+ IsTimeOut bool `json:"is_time_out,omitempty"`
+ AttemptNotFound bool `json:"attempt_not_found,omitempty"`
+ Forbidden bool `json:"forbidden,omitempty"`
+ ValidationError bool `json:"validation_error,omitempty"`
+ Message string `json:"message,omitempty"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/health_check_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/health_check_model.go
new file mode 100644
index 0000000000000000000000000000000000000000..067a6bca6ef9bc1e5e77bcd29331eab2eba1a4d9
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/health_check_model.go
@@ -0,0 +1,10 @@
+package models
+
+type HealthCheckRequest struct {
+ ExampleCallback func() (string, string)
+}
+
+type HealthCheckResponse struct {
+ DatabaseStatus string `json:"database_status"`
+ RedisStatus string `json:"redis_status"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/health_check_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/health_check_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..d99efa323067a9879c9e71cf7a47565d97dd8695
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/health_check_repository.go
@@ -0,0 +1,41 @@
+package repositories
+
+import (
+ "api.qobiltu.id/config"
+ "api.qobiltu.id/models"
+ "context"
+ "gorm.io/gorm"
+)
+
+type HealthCheckRepository struct {
+ db *gorm.DB
+}
+
+func NewHealthCheckRepository(db *gorm.DB) *HealthCheckRepository {
+ return &HealthCheckRepository{
+ db: db,
+ }
+}
+
+func (r *HealthCheckRepository) Check(ctx context.Context, req *models.HealthCheckRequest) (*models.HealthCheckResponse, error) {
+ res := &models.HealthCheckResponse{}
+
+ err := config.RunTx(ctx, r.db, func(tx *gorm.DB) error {
+ if err := tx.Exec("SELECT 1").Error; err != nil {
+ return err
+ }
+
+ // call logic from service if needed
+ // instead of writing the logic in the repository
+ // and make sure the param consist of the ctx and request
+ res.DatabaseStatus, res.RedisStatus = req.ExampleCallback()
+
+ return nil
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ return res, nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/paging.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/paging.go
new file mode 100644
index 0000000000000000000000000000000000000000..86a1553626b924c3217e811cad6f5b05a5e47828
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/paging.go
@@ -0,0 +1,58 @@
+package response
+
+import (
+ "errors"
+ "fmt"
+)
+
+type PagingInfo struct {
+ HasPreviousPage bool `json:"has_previous_page"`
+ HasNextPage bool `json:"has_next_page"`
+ CurrentPage int `json:"current_page"`
+ PerPage int `json:"per_page"`
+ TotalData int `json:"total_data"`
+ LastPage int `json:"last_page"`
+ From int `json:"from"`
+ To int `json:"to"`
+ TotalDataInCurrentPage int `json:"total_data_in_current_page"`
+ Label string `json:"label"`
+}
+
+var ErrPageInfo = errors.New("per_page harus lebih besar dari 0 dan offset tidak boleh negatif")
+
+func NewPagingInfo(currentPage, perPage, offset, totalData int) (*PagingInfo, error) {
+ if perPage <= 0 || offset < 0 {
+ return nil, ErrPageInfo
+ }
+
+ lastPage := totalData / perPage
+ if totalData%perPage != 0 {
+ lastPage++
+ }
+
+ to := min(offset+perPage, totalData)
+ from := int(0)
+ if to > offset {
+ from = offset + 1
+ }
+
+ if currentPage > lastPage {
+ currentPage = lastPage
+ }
+
+ totalDataInCurrentPage := to - offset
+ label := fmt.Sprintf("Menampilkan %d sampai %d dari %d data", from, to, totalData)
+
+ return &PagingInfo{
+ HasPreviousPage: currentPage > 1,
+ HasNextPage: currentPage < lastPage,
+ CurrentPage: currentPage,
+ PerPage: perPage,
+ TotalData: totalData,
+ LastPage: lastPage,
+ From: from,
+ To: to,
+ TotalDataInCurrentPage: totalDataInCurrentPage,
+ Label: label,
+ }, nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/response.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/response.go
new file mode 100644
index 0000000000000000000000000000000000000000..203b58e3f2fb661f76d5b65fcc515e3ce3aaf5ff
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/response/response.go
@@ -0,0 +1,152 @@
+package response
+
+import (
+ errors "api.qobiltu.id/apperror"
+ "api.qobiltu.id/models"
+ goErrors "errors"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "github.com/go-playground/validator/v10"
+ "net/http"
+)
+
+type ErrorResponse struct {
+ Details string `json:"details"`
+ ValidationErrors []ValidationError `json:"validation_errors,omitempty"`
+ models.Exception
+}
+
+type API struct {
+ Status string `json:"status"`
+ Message string `json:"message,omitempty"`
+ Data any `json:"data,omitempty"`
+ MetaData any `json:"meta_data"`
+ Error *ErrorResponse `json:"errors,omitempty"`
+}
+
+type List struct {
+ List any `json:"list"`
+}
+
+type ValidationError struct {
+ Field string `json:"field"`
+ Message string `json:"message"`
+}
+
+func HandleError(c *gin.Context, err error) {
+
+ apiResponse := API{
+ Status: "error",
+ Message: "An error occurred, " + err.Error(),
+ Error: &ErrorResponse{
+ Exception: models.Exception{Message: ""},
+ },
+ MetaData: struct{}{},
+ }
+
+ var appErr *errors.AppError
+
+ if goErrors.As(err, &appErr) {
+ apiResponse.Error.Details = fmt.Sprintf("%v", appErr.Err)
+
+ switch specificErr := appErr.Err.(type) {
+ case errors.ValidationError:
+ validationError := make([]ValidationError, len(specificErr.Errors))
+ apiResponse.Message = "Validation Error"
+ apiResponse.Error.ValidationErrors = validationError
+ for i, ve := range specificErr.Errors {
+ apiResponse.Error.ValidationErrors[i] = ValidationError{
+ Field: ve.Field,
+ Message: ve.Message,
+ }
+ }
+ c.JSON(http.StatusBadRequest, apiResponse)
+ return
+ case errors.ConflictError:
+ apiResponse.Message = specificErr.Message
+ apiResponse.Error.Details = specificErr.Error()
+ apiResponse.Error.Exception.DataDuplicate = true
+ c.JSON(http.StatusConflict, apiResponse)
+ return
+ case errors.InternalError:
+ apiResponse.Message = specificErr.Message
+ apiResponse.Error.Details = specificErr.Error()
+ apiResponse.Error.Exception.InternalServerError = true
+ c.JSON(http.StatusInternalServerError, apiResponse)
+ return
+ case errors.NotFoundError:
+ apiResponse.Message = specificErr.Message
+ apiResponse.Error.Details = specificErr.Error()
+ apiResponse.Error.Exception.DataNotFound = true
+ c.JSON(http.StatusNotFound, apiResponse)
+ return
+ case errors.UnauthorizedError:
+ apiResponse.Message = specificErr.Message
+ apiResponse.Error.Details = specificErr.Error()
+ apiResponse.Error.Exception.Unauthorized = true
+ c.JSON(http.StatusUnauthorized, apiResponse)
+ return
+ case errors.ForbiddenError:
+ apiResponse.Message = specificErr.Message
+ apiResponse.Error.Details = specificErr.Error()
+ apiResponse.Error.Exception.Forbidden = true
+ c.JSON(http.StatusForbidden, apiResponse)
+ return
+ case errors.BadRequestError:
+ apiResponse.Message = specificErr.Message
+ apiResponse.Error.Details = specificErr.Error()
+ apiResponse.Error.Exception.BadRequest = true
+ c.JSON(http.StatusBadRequest, apiResponse)
+ return
+ default:
+ apiResponse.Error.Details = "An unexpected error occurred."
+ apiResponse.Error.Exception.InternalServerError = true
+ c.JSON(http.StatusInternalServerError, apiResponse)
+ return
+ }
+ } else if validationErrors, ok := err.(validator.ValidationErrors); ok {
+ apiResponse.Error.Details = "Validation failed for the request."
+ apiResponse.Error.Exception.ValidationError = true
+ apiResponse.Error.ValidationErrors = make([]ValidationError, len(validationErrors))
+ for i, fe := range validationErrors {
+ apiResponse.Error.ValidationErrors[i] = ValidationError{
+ Field: fe.Field(),
+ Message: fe.Translate(nil), // adjust if you use translator
+ }
+ }
+ c.JSON(http.StatusBadRequest, apiResponse)
+ return
+ }
+
+ c.JSON(http.StatusInternalServerError, apiResponse)
+ apiResponse.Error.Details = err.Error()
+}
+
+func HandleSuccess(c *gin.Context, message string, statusCode int, data any, metaData any) {
+ apiResponse := API{
+ Status: "success",
+ Message: message,
+ Data: data,
+ MetaData: metaData,
+ }
+
+ if metaData == nil {
+ apiResponse.MetaData = struct{}{}
+ }
+
+ c.JSON(statusCode, apiResponse)
+}
+
+func HandleSuccessWithPaging(c *gin.Context, message string, content any, pagingInfo *PagingInfo) {
+
+ apiResponse := API{
+ Message: message,
+ Status: "success",
+ Data: List{
+ List: content,
+ },
+ MetaData: pagingInfo,
+ }
+
+ c.JSON(http.StatusOK, apiResponse)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/health_check_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/health_check_route.go
new file mode 100644
index 0000000000000000000000000000000000000000..e8f5ca5e4118d0efcb28d831d2a5c084d36d1a42
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/health_check_route.go
@@ -0,0 +1,5 @@
+package router
+
+func (s *Server) HealthCheckRoute() {
+ s.router.GET("/api/v1/health-check", s.healthCheckController.Check)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/server.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..a8040d69a87a669ca6eda81729fe66828afb2bab
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/server.go
@@ -0,0 +1,32 @@
+package router
+
+import (
+ "api.qobiltu.id/controller/health_check"
+ "github.com/gin-gonic/gin"
+)
+
+type Server struct {
+ router *gin.Engine
+ healthCheckController *health_check_controller.HealthCheckController
+}
+
+func NewServer(
+ healthCheckController *health_check_controller.HealthCheckController,
+) (*Server, error) {
+
+ router := gin.Default()
+ router.Use(gin.Recovery())
+
+ server := &Server{
+ healthCheckController: healthCheckController,
+ router: router,
+ }
+
+ server.setupRoutes()
+
+ return server, nil
+}
+
+func (s *Server) Start(address string) error {
+ return s.router.Run(address)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/health_check_service/health_check_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/health_check_service/health_check_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..d13e9592fb284c118936af0213cd2a484778baf1
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/health_check_service/health_check_service.go
@@ -0,0 +1,15 @@
+package health_check_service
+
+import (
+ "api.qobiltu.id/repositories"
+)
+
+type HealthCheckService struct {
+ healthCheckRepository *repositories.HealthCheckRepository
+}
+
+func NewHealthCheckService(healthCheckRepository *repositories.HealthCheckRepository) *HealthCheckService {
+ return &HealthCheckService{
+ healthCheckRepository: healthCheckRepository,
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/health_check_service/health_check_service_check.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/health_check_service/health_check_service_check.go
new file mode 100644
index 0000000000000000000000000000000000000000..c01e0baea8ea32c5d284498f1fa82ebff4d679c7
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/health_check_service/health_check_service_check.go
@@ -0,0 +1,21 @@
+package health_check_service
+
+import (
+ "api.qobiltu.id/apperror"
+ "api.qobiltu.id/models"
+ "context"
+)
+
+func (s *HealthCheckService) Check(ctx context.Context, req *models.HealthCheckRequest) (*models.HealthCheckResponse, error) {
+
+ req.ExampleCallback = func() (string, string) {
+ return "OK", "OK"
+ }
+
+ res, err := s.healthCheckRepository.Check(ctx, req)
+ if err != nil {
+ return nil, apperror.NewInternalError("Failed to check health", err)
+ }
+
+ return res, nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
index 0a375ebb173c72b4a9eea6189d87c76acf362583..06be35eb52d9f0c869dd8dbefe2347059a30c7ea 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
@@ -4,4 +4,5 @@ quzuu-be.exe
README.md
.qodo
.error
-logs/
\ No newline at end of file
+logs/
+.idea
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Makefile b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..eaea5509230b5b4c1aecc6a0f015eef921d7a5d7
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Makefile
@@ -0,0 +1,7 @@
+up-dev:
+ docker compose -f docker-compose.dev.yml up -d
+
+run-dev:
+ go run main.go
+
+.PHONY : up-dev run-dev
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/assets/efs.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/assets/efs.go
new file mode 100644
index 0000000000000000000000000000000000000000..0704210fe8e16367f8137a99ca4f8bdd2f9a144a
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/assets/efs.go
@@ -0,0 +1,13 @@
+package assets
+
+import (
+ "embed"
+)
+
+//go:embed "emails"
+var EmbeddedFiles embed.FS
+
+const (
+ EmailConfirmationTemplatePath = "emails/email-confirmation.tmpl"
+ EmailForgotPasswordTemplatePath = "emails/email-forgot-password.tmpl"
+)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/assets/emails/email-confirmation.tmpl b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/assets/emails/email-confirmation.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..0371f154467c1f24d42058d9df99026e99ff53b5
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/assets/emails/email-confirmation.tmpl
@@ -0,0 +1,55 @@
+{{define "htmlBody"}}
+
+
+
+
+
+
+
+
+
Berikut adalah kode verifikasi Anda:
+
{{.VerificationCode}}
+
Silakan masukkan kode ini untuk memverifikasi alamat email Anda. Kode ini akan kedaluwarsa dalam {{.ExpirationInMinutes}} menit.
+
Jika Anda tidak meminta ini, abaikan email ini.
+
Salam hormat,
Tim Support Qobiltu Indonesia
+
+
+
+
+{{end}}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/assets/emails/email-forgot-password.tmpl b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/assets/emails/email-forgot-password.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..9dc2c77ace5d5b065afc246b17b86308f74adc43
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/assets/emails/email-forgot-password.tmpl
@@ -0,0 +1,56 @@
+{{define "htmlBody"}}
+
+
+
+
+
+
+
+
+
Anda menerima email ini karena ada permintaan untuk mengatur ulang kata sandi akun Anda.
+
Berikut adalah kode verifikasi Anda:
+
{{.ResetToken}}
+
Tautan ini akan kedaluwarsa dalam {{.ExpirationInMinutes}} menit.
+
Jika Anda tidak meminta pengaturan ulang kata sandi ini, abaikan email ini. Tidak ada perubahan yang akan dilakukan pada akun Anda.
+
Salam hormat,
Tim Support Qobiltu Indonesia
+
+
+
+
+{{end}}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
index ad174f7229b7f8ad7cd8719f586001d707825475..a7076106e74ca435298b12ec61e0ca8fc0ca4b11 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
@@ -1,36 +1,63 @@
-package config
-
-import (
- "os"
- "strconv"
-
- "github.com/joho/godotenv"
-)
-
-var ENV string
-var TCP_ADDRESS string
-var LOG_PATH string
-
-var HOST_ADDRESS string
-var HOST_PORT string
-var EMAIL_VERIFICATION_DURATION int
-
-var SMTP_SENDER_EMAIL string
-var SMTP_SENDER_PASSWORD string
-var SMTP_HOST string
-var SMTP_PORT string
-
-func init() {
- godotenv.Load()
- ENV = os.Getenv("ENV")
- HOST_ADDRESS = os.Getenv("HOST_ADDRESS")
- HOST_PORT = os.Getenv("HOST_PORT")
- TCP_ADDRESS = HOST_ADDRESS + ":" + HOST_PORT
- LOG_PATH = os.Getenv("LOG_PATH")
- EMAIL_VERIFICATION_DURATION, _ = strconv.Atoi(os.Getenv("EMAIL_VERIFICATION_DURATION"))
- SMTP_SENDER_EMAIL = os.Getenv("SMTP_SENDER_EMAIL")
- SMTP_SENDER_PASSWORD = os.Getenv("SMTP_SENDER_PASSWORD")
- SMTP_HOST = os.Getenv("SMTP_HOST")
- SMTP_PORT = os.Getenv("SMTP_PORT")
- // Menampilkan nilai variabel lingkungan
-}
+package config
+
+import (
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/joho/godotenv"
+)
+
+var ENV string
+var TCP_ADDRESS string
+var LOG_PATH string
+
+var HOST_ADDRESS string
+var HOST_PORT string
+var EMAIL_VERIFICATION_DURATION int
+
+var SMTP_SENDER_EMAIL string
+var SMTP_SENDER_PASSWORD string
+var SMTP_HOST string
+var SMTP_PORT string
+
+var REDIS_HOST string
+var REDIS_PORT int
+var REDIS_PASSWORD string
+var REDIS_DB int
+var REDIS_MIN_IDLE_CONNS int
+var REDIS_POOL_SIZE int
+var REDIS_POOL_TIMEOUT time.Duration
+
+func init() {
+ godotenv.Load()
+ ENV = os.Getenv("ENV")
+ HOST_ADDRESS = os.Getenv("HOST_ADDRESS")
+ HOST_PORT = os.Getenv("HOST_PORT")
+ TCP_ADDRESS = HOST_ADDRESS + ":" + HOST_PORT
+ LOG_PATH = os.Getenv("LOG_PATH")
+ EMAIL_VERIFICATION_DURATION, _ = strconv.Atoi(os.Getenv("EMAIL_VERIFICATION_DURATION"))
+ SMTP_SENDER_EMAIL = os.Getenv("SMTP_SENDER_EMAIL")
+ SMTP_SENDER_PASSWORD = os.Getenv("SMTP_SENDER_PASSWORD")
+ SMTP_HOST = os.Getenv("SMTP_HOST")
+ SMTP_PORT = os.Getenv("SMTP_PORT")
+
+ REDIS_HOST = getValue(os.Getenv("REDIS_HOST"), "redis", func(s string) (string, error) { return s, nil })
+ REDIS_PORT = getValue(os.Getenv("REDIS_PORT"), 6379, func(s string) (int, error) { return strconv.Atoi(s) })
+ REDIS_PASSWORD = getValue(os.Getenv("REDIS_PASSWORD"), "qobiltu", func(s string) (string, error) { return s, nil })
+ REDIS_DB = getValue(os.Getenv("REDIS_DB"), 0, func(s string) (int, error) { return strconv.Atoi(s) })
+ REDIS_POOL_SIZE = getValue(os.Getenv("REDIS_POOL_SIZE"), 10, func(s string) (int, error) { return strconv.Atoi(s) })
+ REDIS_MIN_IDLE_CONNS = getValue(os.Getenv("REDIS_MIN_IDLE_CONNS"), 10, func(s string) (int, error) { return strconv.Atoi(s) })
+ REDIS_POOL_TIMEOUT = getValue(os.Getenv("REDIS_POOL_TIMEOUT"), time.Second*30, func(s string) (time.Duration, error) { return time.ParseDuration(s) })
+}
+
+func getValue[T any](value string, defaultValue T, convert func(string) (T, error)) T {
+ if value == "" {
+ return defaultValue
+ }
+ convertedValue, err := convert(value)
+ if err != nil {
+ return defaultValue
+ }
+ return convertedValue
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/api_response.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/api_response.go
new file mode 100644
index 0000000000000000000000000000000000000000..a78e205b3345a3f8aa0ba090519d04eedf3a7828
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/api_response.go
@@ -0,0 +1,53 @@
+package controller
+
+import (
+ "net/http"
+ "reflect"
+
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+
+ "github.com/gin-gonic/gin"
+)
+
+func ResponseOK(c *gin.Context, data any) {
+ res := models.SuccessResponse{
+ Status: "success",
+ Message: "Data retrieved successfully!",
+ Data: data,
+ MetaData: c.Request.Body,
+ }
+ c.JSON(http.StatusOK, res)
+ return
+}
+
+func ResponseFAIL(c *gin.Context, status int, exception models.Exception) {
+ message := exception.Message
+ exception.Message = ""
+ res := models.ErrorResponse{
+ Status: "error",
+ Message: message,
+ Errors: exception,
+ MetaData: c.Request.Body,
+ }
+ c.AbortWithStatusJSON(status, res)
+ return
+}
+
+func SendResponse(c *gin.Context, data services.Service[any, any]) {
+ if reflect.ValueOf(data.Exception).IsNil() {
+ ResponseOK(c, data)
+ } else {
+ if data.Exception.Unauthorized {
+ ResponseFAIL(c, 401, data.Exception)
+ } else if data.Exception.BadRequest {
+ ResponseFAIL(c, 400, data.Exception)
+ } else if data.Exception.DataNotFound {
+ ResponseFAIL(c, 404, data.Exception)
+ } else if data.Exception.InternalServerError {
+ ResponseFAIL(c, 500, data.Exception)
+ } else {
+ ResponseFAIL(c, 403, data.Exception)
+ }
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
index 456c424696e845d0f994bd91a43f42623d427045..52f66dd50c18090636a6f4be5fb019ae37a41a88 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
@@ -1,65 +1,65 @@
-package controller
-
-import (
- "api.qobiltu.id/models"
- "api.qobiltu.id/services"
- "api.qobiltu.id/utils"
- "github.com/gin-gonic/gin"
-)
-
-type (
- Controllers interface {
- RequestJSON(c *gin.Context)
- Response(c *gin.Context)
- }
- Controller[T1 any, T2 any, T3 any] struct {
- AccountData models.AccountData
- Request T1
- Service *services.Service[T2, T3]
- }
-)
-
-func (controller *Controller[T1, T2, T3]) HeaderParse(c *gin.Context, act func()) {
- cParam, _ := c.Get("accountData")
- if cParam != nil {
- controller.AccountData = cParam.(models.AccountData)
- }
- act()
-}
-func (controller *Controller[T1, T2, T3]) RequestJSON(c *gin.Context, act func()) {
- cParam, _ := c.Get("accountData")
- if cParam != nil {
- controller.AccountData = cParam.(models.AccountData)
- }
- errBinding := c.ShouldBindJSON(&controller.Request)
- if errBinding != nil {
- utils.ResponseFAIL(c, 400, models.Exception{
- BadRequest: true,
- Message: "Invalid Request!, recheck your request, there's must be some problem about required parameter or type parameter",
- })
- return
- } else {
- act()
- controller.Response(c)
- }
-}
-func (controller *Controller[T1, T2, T3]) Response(c *gin.Context) {
- switch {
- case controller.Service.Error != nil:
- utils.ResponseFAIL(c, 500, models.Exception{
- InternalServerError: true,
- Message: "Internal Server Error",
- })
- utils.LogError(controller.Service.Error)
- case controller.Service.Exception.DataDuplicate:
- utils.ResponseFAIL(c, 400, controller.Service.Exception)
- case controller.Service.Exception.Unauthorized:
- utils.ResponseFAIL(c, 401, controller.Service.Exception)
- case controller.Service.Exception.DataNotFound:
- utils.ResponseFAIL(c, 404, controller.Service.Exception)
- case controller.Service.Exception.Message != "":
- utils.ResponseFAIL(c, 400, controller.Service.Exception)
- default:
- utils.ResponseOK(c, controller.Service.Result)
- }
-}
+package controller
+
+import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "api.qobiltu.id/utils"
+ "github.com/gin-gonic/gin"
+)
+
+type (
+ Controllers interface {
+ RequestJSON(c *gin.Context)
+ Response(c *gin.Context)
+ }
+ Controller[T1 any, T2 any, T3 any] struct {
+ AccountData models.AccountData
+ Request T1
+ Service *services.Service[T2, T3]
+ }
+)
+
+func (controller *Controller[T1, T2, T3]) HeaderParse(c *gin.Context, act func()) {
+ cParam, _ := c.Get("accountData")
+ if cParam != nil {
+ controller.AccountData = cParam.(models.AccountData)
+ }
+ act()
+}
+func (controller *Controller[T1, T2, T3]) RequestJSON(c *gin.Context, act func()) {
+ cParam, _ := c.Get("accountData")
+ if cParam != nil {
+ controller.AccountData = cParam.(models.AccountData)
+ }
+ errBinding := c.ShouldBindJSON(&controller.Request)
+ if errBinding != nil {
+ ResponseFAIL(c, 400, models.Exception{
+ BadRequest: true,
+ Message: "Invalid Request!, recheck your request, there's must be some problem about required parameter or type parameter",
+ })
+ return
+ } else {
+ act()
+ controller.Response(c)
+ }
+}
+func (controller *Controller[T1, T2, T3]) Response(c *gin.Context) {
+ switch {
+ case controller.Service.Error != nil:
+ utils.LogError(controller.Service.Error)
+ ResponseFAIL(c, 500, models.Exception{
+ InternalServerError: true,
+ Message: "Internal Server Error",
+ })
+ case controller.Service.Exception.DataDuplicate:
+ ResponseFAIL(c, 400, controller.Service.Exception)
+ case controller.Service.Exception.Unauthorized:
+ ResponseFAIL(c, 401, controller.Service.Exception)
+ case controller.Service.Exception.DataNotFound:
+ ResponseFAIL(c, 404, controller.Service.Exception)
+ case controller.Service.Exception.Message != "":
+ ResponseFAIL(c, 400, controller.Service.Exception)
+ default:
+ ResponseOK(c, controller.Service.Result)
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification_controller.go
index 75d6a1a52383fac94e73c477dd29bda62624f31b..93fcf2b65d8d45b42438681996cf93786c64dbae 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification_controller.go
@@ -1,21 +1,21 @@
-package controller
-
-import (
- "api.qobiltu.id/controller"
- "api.qobiltu.id/models"
- "api.qobiltu.id/services"
- "github.com/gin-gonic/gin"
-)
-
-func CreateVerification(c *gin.Context) {
- emailVerification := services.EmailVerificationService{}
- emailVerificationController := controller.Controller[models.CreateVerifyEmailRequest, models.EmailVerification, models.EmailVerification]{
- Service: &emailVerification.Service,
- }
- emailVerificationController.HeaderParse(c, func() {
- emailVerificationController.Service.Constructor.AccountID = uint(emailVerificationController.AccountData.UserID)
- emailVerification.Create()
- emailVerificationController.Response(c)
- })
-
-}
+package controller
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func CreateVerification(c *gin.Context) {
+ emailVerification := services.EmailVerificationService{}
+ emailVerificationController := controller.Controller[models.CreateVerifyEmailRequest, models.EmailVerification, models.EmailVerification]{
+ Service: &emailVerification.Service,
+ }
+ emailVerificationController.HeaderParse(c, func() {
+ emailVerificationController.Service.Constructor.AccountID = emailVerificationController.AccountData.UserID
+ emailVerification.Create()
+ emailVerificationController.Response(c)
+ })
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.dev.yml b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.dev.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8d38a82a2f948d309475958a8b4d352d7f283230
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.dev.yml
@@ -0,0 +1,18 @@
+version: '3.8'
+
+services:
+ db:
+ image: postgres:15
+ container_name: postgres-db
+ environment:
+ POSTGRES_USER: ${DB_USER:-qobiltu}
+ POSTGRES_PASSWORD: ${DB_PASSWORD:-qobiltu}
+ POSTGRES_DB: ${DB_NAME:-qobiltu}
+ ports:
+ - "5432:5432"
+ volumes:
+ - db-data:/var/lib/postgresql/data
+ restart: always
+
+volumes:
+ db-data:
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml
index 5d48addfd86709968791ce0c398bd0a4cb75e2ec..c9493fabb6148385e056c035008355f5cc7f0b77 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml
@@ -9,6 +9,8 @@ services:
env_file: .env
ports:
- "8080:8080"
+ networks:
+ - qobiltu-network
# volumes:
# - ./logs:/app/logs
# - /home/qobiltu/api-qobiltu:/app
@@ -25,7 +27,28 @@ services:
- "5432:5432"
volumes:
- /home/qobiltu/postgres-data:/var/lib/postgresql/data
+ networks:
+ - qobiltu-network
restart: always
+ redis:
+ image: redis/redis-stack:7.2.0-v11
+ container_name: redis-db
+ environment:
+ REDIS_ARGS: "--requirepass ${REDIS_PASSWORD:-qobiltu}"
+ ports:
+ - "8001:8001"
+ - "6379:6379"
+ volumes:
+ - /home/qobiltu/redis-data:/data
+ networks:
+ - qobiltu-network
+ restart: always
+
volumes:
db-data:
+ redis-data:
+
+networks:
+ qobiltu-network:
+ driver: bridge
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
index aa7905ab737698db63d0a25f6ff4ca83c195e4e1..fd18921eaf4b8e2155384c78e6c16ab795d5a9a9 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
@@ -6,7 +6,10 @@ require (
github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/gosimple/slug v1.15.0
+ github.com/hibiken/asynq v0.25.1
github.com/joho/godotenv v1.5.1
+ github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
+ github.com/redis/go-redis/v9 v9.7.0
github.com/satori/go.uuid v1.2.0
golang.org/x/crypto v0.36.0
google.golang.org/api v0.228.0
@@ -20,7 +23,9 @@ require (
cloud.google.com/go/compute/metadata v0.6.0 // indirect
github.com/bytedance/sonic v1.13.1 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
@@ -31,6 +36,7 @@ require (
github.com/go-playground/validator/v10 v10.25.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/s2a-go v0.1.9 // indirect
+ github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
@@ -47,6 +53,8 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
+ github.com/robfig/cron/v3 v3.0.1 // indirect
+ github.com/spf13/cast v1.7.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
@@ -60,6 +68,7 @@ require (
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
+ golang.org/x/time v0.11.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
google.golang.org/grpc v1.71.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
index 4fac69c64291b273a4a6b4d6887c80b0f43f7bc3..23723ce748c6199b01cb89b503e2328a53552138 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
@@ -4,19 +4,29 @@ cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIi
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
+github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
+github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
+github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
+github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
@@ -57,6 +67,8 @@ github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
+github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw=
+github.com/hibiken/asynq v0.25.1/go.mod h1:pazWNOLBu0FEynQRBvHA26qdIKRSmfdIfUm4HdsLmXg=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -71,6 +83,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
+github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -94,10 +108,16 @@ github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNH
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
+github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
+github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
+github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
+github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -130,6 +150,8 @@ go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/mail/sender.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/mail/sender.go
new file mode 100644
index 0000000000000000000000000000000000000000..2ad3e4b5914b7f838c2bc72dbf79391501208e03
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/mail/sender.go
@@ -0,0 +1,8 @@
+package mail
+
+type Sender interface {
+ Send(recipient, subject string, htmlContent string, data any) error
+}
+
+// EmailSender is a global variable to hold the email sender
+var EmailSender Sender
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/mail/smtp.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/mail/smtp.go
new file mode 100644
index 0000000000000000000000000000000000000000..54e347e85541a89d656f20bffedee6df0f9d064a
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/mail/smtp.go
@@ -0,0 +1,71 @@
+package mail
+
+import (
+ "errors"
+ "fmt"
+ "github.com/jordan-wright/email"
+ "net/mail"
+ "net/smtp"
+)
+
+var (
+ ErrEmailEmpty = errors.New("mail is empty")
+ ErrEmailInvalid = errors.New("invalid email")
+)
+
+// Config menyimpan pengaturan untuk koneksi SMTP.
+type Config struct {
+ Host string
+ Port string
+ Username string
+ Password string
+ From string
+}
+
+// SMTP adalah implementasi Sender untuk mengirim mail melalui SMTP
+type SMTP struct {
+ name string
+ fromEmailAddress string
+ smtpServerAddress string
+ smtpAuthAddress smtp.Auth
+}
+
+// New membuat instance baru SMTP dengan konfigurasi.
+func New(cfg *Config) (Sender, error) {
+ if err := validateEmail(cfg.From); err != nil {
+ return nil, fmt.Errorf("invalid from address: %w", err)
+ }
+
+ return &SMTP{
+ name: cfg.From,
+ fromEmailAddress: cfg.From,
+ smtpServerAddress: fmt.Sprintf("%s:%s", cfg.Host, cfg.Port),
+ smtpAuthAddress: smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host),
+ }, nil
+}
+
+// Send mengirim mail ke penerima dengan subjek, konten HTML, dan data lainnya.
+func (s *SMTP) Send(recipient, subject string, htmlContent string, data any) error {
+ e := email.NewEmail()
+ e.From = s.fromEmailAddress
+ e.To = []string{recipient}
+ e.Subject = subject
+ e.HTML = []byte(htmlContent)
+ return s.sendEmail(e)
+}
+
+func (s *SMTP) sendEmail(e *email.Email) error {
+ return e.Send(s.smtpServerAddress, s.smtpAuthAddress)
+}
+
+func validateEmail(email string) error {
+ if email == "" {
+ return ErrEmailEmpty
+ }
+
+ if _, err := mail.ParseAddress(email); err != nil {
+ return ErrEmailInvalid
+ }
+
+ return nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
index 676f1b4f77f3957678f711cc402e21791cf11471..3823940b2cd6d5604cdcacb0bde3a110510594f0 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
@@ -1,14 +1,62 @@
-package main
-
-import (
- "fmt"
-
- "api.qobiltu.id/config"
- "api.qobiltu.id/router"
-)
-
-func main() {
- fmt.Println("Server started on ", config.TCP_ADDRESS, ", port :", config.HOST_PORT)
- router.StartService()
-
-}
+package main
+
+import (
+ "api.qobiltu.id/config"
+ "api.qobiltu.id/controller/health_check"
+ "api.qobiltu.id/mail"
+ "api.qobiltu.id/repositories"
+ "api.qobiltu.id/router"
+ "api.qobiltu.id/services/health_check_service"
+ "api.qobiltu.id/utils"
+ "api.qobiltu.id/worker"
+ "github.com/hibiken/asynq"
+ "log/slog"
+ "net"
+ "strconv"
+)
+
+func main() {
+
+ // setup email sender
+ emailConfig := mail.Config{
+ Host: config.SMTP_HOST,
+ Port: config.SMTP_PORT,
+ From: config.SMTP_SENDER_EMAIL,
+ Username: config.SMTP_SENDER_EMAIL,
+ Password: config.SMTP_SENDER_PASSWORD,
+ }
+
+ emailSender, err := mail.New(&emailConfig)
+ utils.FatalIfErr("failed to setup email sender", err)
+ mail.EmailSender = emailSender
+
+ // setup redis task distributor and processor
+ asynqRedisOpt := asynq.RedisClientOpt{
+ Addr: net.JoinHostPort(config.REDIS_HOST, strconv.Itoa(config.REDIS_PORT)),
+ Password: config.REDIS_PASSWORD,
+ DB: config.REDIS_DB,
+ }
+
+ taskDistributor := worker.NewRedisTaskDistributor(asynqRedisOpt)
+ taskProcessor := worker.NewRedisTaskProcessor(asynqRedisOpt, emailSender)
+ worker.AsyncTaskDistributor = taskDistributor
+
+ // setup repo, service, and controller
+ healthCheckRepository := repositories.NewHealthCheckRepository(config.DB)
+ healthCheckService := health_check_service.NewHealthCheckService(healthCheckRepository)
+ healthCheckController := health_check_controller.NewHealthCheckController(healthCheckService)
+
+ // start task processor
+ err = taskProcessor.Start()
+ utils.FatalIfErr("failed to start task processor", err)
+ slog.Info("Task processor started")
+
+ // create server
+ s, err := router.NewServer(healthCheckController)
+ utils.FatalIfErr("failed to create server", err)
+
+ // run server
+ slog.Info("Starting server", "address", config.TCP_ADDRESS, "port", config.HOST_PORT)
+ err = s.Start(config.TCP_ADDRESS)
+ utils.FatalIfErr("failed to start server", err)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
index 7a06a07a9c505e034cba8020776da4eb84afddaa..7c4f248188e1a83231327004df674e6da8c3001b 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
@@ -1,37 +1,37 @@
-// auth/auth.go
-
-package middleware
-
-import (
- "api.qobiltu.id/models"
- "api.qobiltu.id/services"
- "api.qobiltu.id/utils"
- "github.com/gin-gonic/gin"
-)
-
-func AuthUser(c *gin.Context) {
- var currAccData models.AccountData
- if c.Request.Header["Authorization"] != nil {
- token := c.Request.Header["Authorization"]
-
- currAccData.UserID, currAccData.VerifyStatus, currAccData.ErrVerif = services.VerifyToken(token[0])
-
- if currAccData.VerifyStatus == "invalid-token" || currAccData.VerifyStatus == "expired" {
- currAccData.UserID = 0
- utils.ResponseFAIL(c, 401, models.Exception{Unauthorized: true, Message: "Your session is expired, Please re-Login!"})
- c.Abort()
- return
- } else {
- c.Set("accountData", currAccData)
- c.Next()
- }
- } else {
- currAccData.UserID = 0
- currAccData.VerifyStatus = "no-token"
- currAccData.ErrVerif = nil
- utils.ResponseFAIL(c, 401, models.Exception{Unauthorized: true, Message: "You have to login first!"})
- c.Abort()
- return
- }
-
-}
+// auth/auth.go
+
+package middleware
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func AuthUser(c *gin.Context) {
+ var currAccData models.AccountData
+ if c.Request.Header["Authorization"] != nil {
+ token := c.Request.Header["Authorization"]
+
+ currAccData.UserID, currAccData.VerifyStatus, currAccData.ErrVerif = services.VerifyToken(token[0])
+
+ if currAccData.VerifyStatus == "invalid-token" || currAccData.VerifyStatus == "expired" {
+ currAccData.UserID = 0
+ controller.ResponseFAIL(c, 401, models.Exception{Unauthorized: true, Message: "Your session is expired, Please re-Login!"})
+ c.Abort()
+ return
+ } else {
+ c.Set("accountData", currAccData)
+ c.Next()
+ }
+ } else {
+ currAccData.UserID = 0
+ currAccData.VerifyStatus = "no-token"
+ currAccData.ErrVerif = nil
+ controller.ResponseFAIL(c, 401, models.Exception{Unauthorized: true, Message: "You have to login first!"})
+ c.Abort()
+ return
+ }
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
index 4d4950d1b0d4930f015736d6c8503f5ee025c674..0cec98800c1d1189118a1af77f0b116395372de1 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
@@ -1,24 +1,20 @@
-package router
-
-import (
- "log"
-
- "api.qobiltu.id/config"
- "api.qobiltu.id/controller"
- "github.com/gin-gonic/gin"
-)
-
-func StartService() {
- router := gin.Default()
- router.GET("/", controller.HomeController)
- AuthRoute(router)
- UserRoute(router)
- EmailRoute(router)
- OptionsRoute(router)
- AcademyRoute(router)
- QuizRoute(router)
- err := router.Run(config.TCP_ADDRESS)
- if err != nil {
- log.Fatalf("Failed to run server: %v", err)
- }
-}
+package router
+
+import (
+ "api.qobiltu.id/controller"
+)
+
+func (s *Server) setupRoutes() {
+
+ s.router.GET("/", controller.HomeController)
+
+ AuthRoute(s.router)
+ UserRoute(s.router)
+ EmailRoute(s.router)
+ OptionsRoute(s.router)
+ AcademyRoute(s.router)
+ QuizRoute(s.router)
+
+ // another way to register routes
+ s.HealthCheckRoute()
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
index f965383fcd56b905fc16ae83703dc7d659b82399..cbce83a534b6a792d37ab60b578dca8151e24843 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
@@ -1,181 +1,101 @@
-package services
-
-import (
- "crypto/tls"
- "fmt"
- "log"
- "math/rand/v2"
- "net/smtp"
- "time"
-
- "api.qobiltu.id/config"
- "api.qobiltu.id/models"
- "api.qobiltu.id/repositories"
- uuid "github.com/satori/go.uuid"
-)
-
-type EmailVerificationService struct {
- Service[models.EmailVerification, models.EmailVerification]
-}
-
-func (s *EmailVerificationService) Create() {
- accountRepo := repositories.GetAccountById(s.Constructor.AccountID)
- if accountRepo.NoRecord {
- s.Error = accountRepo.RowsError
- s.Exception.DataNotFound = true
- s.Exception.Message = "There is no account data with given credentials!"
- return
- }
-
- remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
- dueTime := CalculateDueTime(remainingTime)
-
- token := uint(rand.IntN(999999-100000) + 100000)
- s.Constructor.UUID = uuid.NewV4()
-
- repo := repositories.CreateEmailVerification(s.Constructor.UUID, s.Constructor.AccountID, dueTime, token)
-
- s.Error = repo.RowsError
- s.Result = repo.Result
-
- // ⬇ Kirim token ke email user menggunakan SMTP
- go func(toEmail string, token uint) {
- env := config.ENV
- from := config.SMTP_SENDER_EMAIL
- password := config.SMTP_SENDER_PASSWORD
- smtpHost := config.SMTP_HOST
- smtpPort := config.SMTP_PORT
- to := []string{toEmail}
-
- subject := "Verification token"
- body := fmt.Sprintf("Your verification token is: %06d\nPlease use it before it expires.", token)
-
- msg := []byte(fmt.Sprintf("From: %s\r\n", from) +
- fmt.Sprintf("To: %s\r\n", toEmail) +
- fmt.Sprintf("Subject: %s\r\n", subject) +
- "\r\n" + body)
-
- // 1. Connect to the server
- conn, err := tls.Dial("tcp", fmt.Sprintf("[%s]:%s", smtpHost, smtpPort), &tls.Config{
- InsecureSkipVerify: env != "production", // ⚠️ set false di production
- ServerName: smtpHost,
- })
-
- // conn, err := net.Dial("tcp", fmt.Sprintf("[%s]:%s", smtpHost, smtpPort))
- if err != nil {
- s.Error = err
- log.Printf("Error sending verification email: %v", err)
- return
- }
-
- c, err := smtp.NewClient(conn, smtpHost)
- if err != nil {
- s.Error = err
- log.Printf("Error create new client mail: %v", err)
- return
- }
-
- // 2. Auth
- auth := smtp.PlainAuth("", from, password, smtpHost)
- if err = c.Auth(auth); err != nil {
- s.Error = err
- log.Printf("Error auth mail: %v", err)
- return
- }
-
- // 3. Set From and To
- if err = c.Mail(from); err != nil {
- s.Error = err
- log.Printf("Error set mail from to: %v", err)
- return
- }
-
- for _, addr := range to {
- if err = c.Rcpt(addr); err != nil {
- s.Error = err
- log.Printf("Error receipt addr: %v", err)
- return
- }
- }
-
- // 4. Send message
- wc, err := c.Data()
- if err != nil {
- s.Error = err
- log.Printf("Error data Send Message: %v", err)
- return
- }
-
- _, err = wc.Write(msg)
- if err != nil {
- s.Error = err
- log.Printf("Error write Send Message: %v", err)
- return
- }
-
- err = wc.Close()
- if err != nil {
- s.Error = err
- log.Printf("Error close Send Message: %v", err)
- return
- }
-
- c.Quit()
- fmt.Println("Email sent successfully!")
-
- // auth := smtp.PlainAuth("", from, password, smtpHost)
-
- // subject := "Email Verification Token"
- // body := fmt.Sprintf("Your verification token is: %06d\nPlease use it before it expires.", token)
-
- // msg := []byte("To: " + toEmail + "\r\n" +
- // "Subject: " + subject + "\r\n" +
- // "\r\n" +
- // body + "\r\n")
-
- // err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, []string{toEmail}, msg)
- // if err != nil {
- // s.Error = err
- // log.Printf("Error sending verification email: %v", err)
- // return
- // } else {
- // log.Printf("Successfully sending verification email: %v", err)
- // }
- }(accountRepo.Result.Email, token)
- // s.Result.Token = 0
-}
-
-func (s *EmailVerificationService) Validate() {
- repo := repositories.GetEmailVerification(s.Constructor.AccountID, s.Constructor.Token)
- s.Error = repo.RowsError
-
- if repo.NoRecord {
- s.Exception.DataNotFound = true
- s.Exception.Message = "Invalid token!"
- return
- }
-
- if repo.Result.ExpiredAt.Before(time.Now()) {
- s.Exception.Unauthorized = true
- s.Exception.Message = "Token has expired!"
- repositories.UpdateExpiredEmailVerification(s.Constructor.UUID)
- s.Delete()
- return
- }
- account := repositories.GetAccountById(repo.Result.AccountID)
- account.Result.IsEmailVerified = true
-
- repositories.UpdateAccount(account.Result)
- s.Result = repo.Result
-}
-
-func (s *EmailVerificationService) Delete() {
- repo := repositories.DeleteEmailVerification(s.Constructor.Token)
- s.Error = repo.RowsError
- if repo.NoRecord {
- s.Exception.DataNotFound = true
- s.Exception.Message = "Invalid token!"
- return
- }
- s.Result = repo.Result
-}
+package services
+
+import (
+ "api.qobiltu.id/utils"
+ "api.qobiltu.id/worker"
+ "context"
+ "github.com/hibiken/asynq"
+ "strconv"
+ "time"
+
+ "api.qobiltu.id/config"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+ uuid "github.com/satori/go.uuid"
+)
+
+type EmailVerificationService struct {
+ Service[models.EmailVerification, models.EmailVerification]
+}
+
+func (s *EmailVerificationService) Create() {
+ accountRepo := repositories.GetAccountById(s.Constructor.AccountID)
+ if accountRepo.NoRecord {
+ s.Error = accountRepo.RowsError
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account data with given credentials!"
+ return
+ }
+
+ token, err := utils.GenerateToken()
+ if err != nil {
+ s.Error = err
+ s.Exception.InternalServerError = true
+ s.Exception.Message = "failed to generate token for email verification"
+ return
+ }
+
+ remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
+ dueTime := CalculateDueTime(remainingTime)
+ s.Constructor.UUID = uuid.NewV4()
+ repo := repositories.CreateEmailVerification(s.Constructor.UUID, s.Constructor.AccountID, dueTime, uint(token))
+ s.Error = repo.RowsError
+ s.Result = repo.Result
+ if s.Error != nil {
+ return
+ }
+
+ err = worker.AsyncTaskDistributor.DistributeTaskSendVerifyEmail(
+ context.Background(),
+ &worker.PayloadSendVerifyEmail{
+ EmailAddress: accountRepo.Result.Email,
+ VerificationCode: strconv.Itoa(int(token)),
+ ExpirationInMinutes: int(remainingTime.Minutes()),
+ Subject: worker.TaskSendVerifyEmailSubject,
+ },
+ []asynq.Option{
+ asynq.MaxRetry(worker.TaskSendVerifyEmailMaxRetry),
+ asynq.Queue(worker.Critical),
+ }...)
+ if err != nil {
+ s.Error = err
+ s.Exception.InternalServerError = true
+ s.Exception.Message = "failed to send email verification"
+ return
+ }
+}
+
+func (s *EmailVerificationService) Validate() {
+ repo := repositories.GetEmailVerification(s.Constructor.AccountID, s.Constructor.Token)
+ s.Error = repo.RowsError
+
+ if repo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "Invalid token!"
+ return
+ }
+
+ if repo.Result.ExpiredAt.Before(time.Now()) {
+ s.Exception.Unauthorized = true
+ s.Exception.Message = "Token has expired!"
+ repositories.UpdateExpiredEmailVerification(s.Constructor.UUID)
+ s.Delete()
+ return
+ }
+ account := repositories.GetAccountById(repo.Result.AccountID)
+ account.Result.IsEmailVerified = true
+
+ repositories.UpdateAccount(account.Result)
+ s.Result = repo.Result
+}
+
+func (s *EmailVerificationService) Delete() {
+ repo := repositories.DeleteEmailVerification(s.Constructor.Token)
+ s.Error = repo.RowsError
+ if repo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "Invalid token!"
+ return
+ }
+ s.Result = repo.Result
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
index f118affbde7ff31eac3537e660a33d3dbed43f3c..52e73f86566cd775be6af04b2364d9ec744bd777 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
@@ -1,113 +1,114 @@
-package services
-
-import (
- "fmt"
- "log"
- "math/rand/v2"
- "net/smtp"
- "time"
-
- "api.qobiltu.id/config"
- "api.qobiltu.id/models"
- "api.qobiltu.id/repositories"
- uuid "github.com/satori/go.uuid"
-)
-
-type ForgotPasswordService struct {
- Service[models.ForgotPassword, models.ForgotPassword]
-}
-
-func (s *ForgotPasswordService) Create(email string) {
- if email == "" {
- s.Exception.BadRequest = true
- s.Exception.Message = "Email is required!"
- return
- }
- accountRepo := repositories.GetAccountbyEmail(email)
- if accountRepo.NoRecord {
- s.Error = accountRepo.RowsError
- s.Exception.DataNotFound = true
- s.Exception.Message = "There is no account data with given credentials!"
- return
- }
-
- remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
- dueTime := CalculateDueTime(remainingTime)
-
- token := uint(rand.IntN(999999-100000) + 100000)
- s.Constructor.UUID = uuid.NewV4()
- s.Constructor.ExpiredAt = dueTime
- s.Constructor.AccountID = accountRepo.Result.Id
- s.Constructor.Token = token
- repo := repositories.CreateForgotPassword(s.Constructor)
-
- s.Error = repo.RowsError
- s.Result = repo.Result
- // ⬇ Kirim token ke email user menggunakan SMTP
- go func(toEmail string, token uint) {
- from := config.SMTP_SENDER_EMAIL
- password := config.SMTP_SENDER_PASSWORD
- smtpHost := config.SMTP_HOST
- smtpPort := config.SMTP_PORT
-
- auth := smtp.PlainAuth("", from, password, smtpHost)
-
- subject := "Forgot Password Token"
- body := fmt.Sprintf("Your Forgot Password token is: %06d\nPlease use it before it expires.", token)
-
- msg := []byte("To: " + toEmail + "\r\n" +
- "Subject: " + subject + "\r\n" +
- "\r\n" +
- body + "\r\n")
-
- err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, []string{toEmail}, msg)
- if err != nil {
- s.Error = err
- log.Printf("Error sending verification email: %v", err)
- return
- }
- }(accountRepo.Result.Email, token)
- // s.Result.Token = 0
-}
-
-func (s *ForgotPasswordService) Validate(newPassword *string) {
-
- fgPasswordRepo := repositories.GetForgotPasswordByToken(s.Constructor.Token)
- s.Error = fgPasswordRepo.RowsError
- if fgPasswordRepo.NoRecord {
- s.Exception.DataNotFound = true
- s.Exception.Message = "There is no forgot password data with given credentials!"
- return
- }
- if fgPasswordRepo.Result.ExpiredAt.Before(time.Now()) {
- s.Exception.Unauthorized = true
- s.Exception.Message = "Token has expired!"
- return
- }
-
- accountRepo := repositories.GetAccountById(fgPasswordRepo.Result.AccountID)
- if accountRepo.NoRecord {
- s.Error = accountRepo.RowsError
- s.Exception.DataNotFound = true
- s.Exception.Message = "There is no account data with given credentials!"
- return
- }
- s.Result = fgPasswordRepo.Result
- if newPassword == nil {
- return
- }
- // fmt.Println("Previous Account", accountRepo.Result)
- // fmt.Println("New password", *newPassword)
- hashed_password, _ := HashPassword(*newPassword)
- accountRepo.Result.Password = hashed_password
- changePassword := repositories.UpdateAccount(accountRepo.Result)
- // fmt.Println("New Account", changePassword.Result)
- if changePassword.RowsError != nil {
- s.Error = changePassword.RowsError
- s.Exception.QueryError = true
- s.Exception.Message = "Failed to update password!"
- return
- }
- // fgPasswordRepo.Result.Token = 0
-
-}
+package services
+
+import (
+ "api.qobiltu.id/utils"
+ "api.qobiltu.id/worker"
+ "context"
+ "github.com/hibiken/asynq"
+ "strconv"
+ "time"
+
+ "api.qobiltu.id/config"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+ uuid "github.com/satori/go.uuid"
+)
+
+type ForgotPasswordService struct {
+ Service[models.ForgotPassword, models.ForgotPassword]
+}
+
+func (s *ForgotPasswordService) Create(email string) {
+ if email == "" {
+ s.Exception.BadRequest = true
+ s.Exception.Message = "Email is required!"
+ return
+ }
+ accountRepo := repositories.GetAccountbyEmail(email)
+ if accountRepo.NoRecord {
+ s.Error = accountRepo.RowsError
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account data with given credentials!"
+ return
+ }
+
+ token, err := utils.GenerateToken()
+ if err != nil {
+ s.Error = err
+ s.Exception.InternalServerError = true
+ s.Exception.Message = "failed to generate token for email verification"
+ return
+ }
+
+ remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
+ dueTime := CalculateDueTime(remainingTime)
+
+ s.Constructor.UUID = uuid.NewV4()
+ s.Constructor.ExpiredAt = dueTime
+ s.Constructor.AccountID = accountRepo.Result.Id
+ s.Constructor.Token = uint(token)
+ repo := repositories.CreateForgotPassword(s.Constructor)
+ s.Error = repo.RowsError
+ s.Result = repo.Result
+
+ err = worker.AsyncTaskDistributor.DistributeTaskSendForgotPasswordEmail(
+ context.Background(),
+ &worker.PayloadSendForgotPasswordEmail{
+ EmailAddress: accountRepo.Result.Email,
+ ResetToken: strconv.Itoa(int(token)),
+ ExpirationInMinutes: int(remainingTime.Minutes()),
+ Subject: worker.TaskSendForgotPasswordEmailSubject,
+ },
+ []asynq.Option{
+ asynq.MaxRetry(worker.TaskSendForgotPasswordEmailMaxRetry),
+ asynq.Queue(worker.Critical),
+ }...)
+ if err != nil {
+ s.Error = err
+ s.Exception.InternalServerError = true
+ s.Exception.Message = "failed to send email verification for forgot password request"
+ return
+ }
+}
+
+func (s *ForgotPasswordService) Validate(newPassword *string) {
+
+ fgPasswordRepo := repositories.GetForgotPasswordByToken(s.Constructor.Token)
+ s.Error = fgPasswordRepo.RowsError
+ if fgPasswordRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no forgot password data with given credentials!"
+ return
+ }
+ if fgPasswordRepo.Result.ExpiredAt.Before(time.Now()) {
+ s.Exception.Unauthorized = true
+ s.Exception.Message = "Token has expired!"
+ return
+ }
+
+ accountRepo := repositories.GetAccountById(fgPasswordRepo.Result.AccountID)
+ if accountRepo.NoRecord {
+ s.Error = accountRepo.RowsError
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account data with given credentials!"
+ return
+ }
+ s.Result = fgPasswordRepo.Result
+ if newPassword == nil {
+ return
+ }
+ // fmt.Println("Previous Account", accountRepo.Result)
+ // fmt.Println("New password", *newPassword)
+ hashed_password, _ := HashPassword(*newPassword)
+ accountRepo.Result.Password = hashed_password
+ changePassword := repositories.UpdateAccount(accountRepo.Result)
+ // fmt.Println("New Account", changePassword.Result)
+ if changePassword.RowsError != nil {
+ s.Error = changePassword.RowsError
+ s.Exception.QueryError = true
+ s.Exception.Message = "Failed to update password!"
+ return
+ }
+ // fgPasswordRepo.Result.Token = 0
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
index a2ca914abd5d53d032934d3de1a56b8fddd97a39..4d82a5eec18f6c3db7a759c06b394ae6cc8993c2 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
@@ -1,86 +1,86 @@
-package services
-
-import (
- "errors"
- "unicode"
-
- "api.qobiltu.id/models"
- "api.qobiltu.id/repositories"
- uuid "github.com/satori/go.uuid"
- "gorm.io/gorm"
-)
-
-type RegisterService struct {
- Service[models.Account, models.Account]
-}
-
-func ValidatePassword(password string) models.Exception {
- var (
- hasMinLen = false
- hasUpper = false
- hasLower = false
- hasNumber = false
- hasSpecial = false
- )
-
- if len(password) >= 8 {
- hasMinLen = true
- }
-
- for _, char := range password {
- switch {
- case unicode.IsUpper(char):
- hasUpper = true
- break
- case unicode.IsLower(char):
- hasLower = true
- break
- case unicode.IsDigit(char):
- hasNumber = true
- break
- case unicode.IsPunct(char) || unicode.IsSymbol(char):
- hasSpecial = true
- break
- }
- }
- approve := hasMinLen && hasUpper && hasLower && hasNumber && hasSpecial
- if !approve {
- return models.Exception{
- BadRequest: true,
- Message: "Password must contain at least 8 characters, including uppercase, lowercase, number, and special character!",
- }
- }
- return models.Exception{}
-}
-
-func (s *RegisterService) Create() {
- validatePassword := ValidatePassword(s.Constructor.Password)
- if validatePassword.BadRequest {
- s.Exception = validatePassword
- return
- }
- hashed_password, err_hash := HashPassword(s.Constructor.Password)
- s.Error = err_hash
- s.Constructor.Password = hashed_password
- s.Constructor.UUID = uuid.NewV4()
- accountCreated := repositories.CreateAccount(s.Constructor)
- if errors.Is(accountCreated.RowsError, gorm.ErrDuplicatedKey) {
- s.Exception.DataDuplicate = true
- s.Exception.Message = "Account with email " + s.Constructor.Email + " already exists!"
- return
- } else if errors.Is(accountCreated.RowsError, gorm.ErrModelAccessibleFieldsRequired) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidData) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidValue) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidField) {
- s.Exception.BadRequest = true
- s.Exception.Message = "Bad request!"
- return
- }
- userProfile := UserProfileService{}
- userProfile.Constructor.AccountID = accountCreated.Result.Id
- userProfile.Create()
- if userProfile.Error != nil {
- s.Error = userProfile.Error
- return
- }
- s.Error = accountCreated.RowsError
- s.Result = accountCreated.Result
- s.Result.Password = "SECRET"
-}
+package services
+
+import (
+ "errors"
+ "unicode"
+
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+ uuid "github.com/satori/go.uuid"
+ "gorm.io/gorm"
+)
+
+type RegisterService struct {
+ Service[models.Account, models.Account]
+}
+
+func ValidatePassword(password string) models.Exception {
+ var (
+ hasMinLen = false
+ hasUpper = false
+ hasLower = false
+ hasNumber = false
+ )
+
+ if len(password) >= 8 {
+ hasMinLen = true
+ }
+
+ for _, char := range password {
+ switch {
+ case unicode.IsUpper(char):
+ hasUpper = true
+ break
+ case unicode.IsLower(char):
+ hasLower = true
+ break
+ case unicode.IsDigit(char):
+ hasNumber = true
+ break
+ }
+ }
+ approve := hasMinLen && hasUpper && hasLower && hasNumber
+ if !approve {
+ return models.Exception{
+ BadRequest: true,
+ Message: "Password must contain at least 8 characters, including uppercase, lowercase, and number!",
+ }
+ }
+ return models.Exception{}
+}
+
+func (s *RegisterService) Create() {
+ validatePassword := ValidatePassword(s.Constructor.Password)
+ if validatePassword.BadRequest {
+ s.Exception = validatePassword
+ return
+ }
+
+ hashedPassword, err := HashPassword(s.Constructor.Password)
+ s.Error = err
+ s.Constructor.Password = hashedPassword
+ s.Constructor.UUID = uuid.NewV4()
+ accountCreated := repositories.CreateAccount(s.Constructor)
+
+ if errors.Is(accountCreated.RowsError, gorm.ErrDuplicatedKey) {
+ s.Exception.DataDuplicate = true
+ s.Exception.Message = "Account with email " + s.Constructor.Email + " already exists!"
+ return
+ } else if errors.Is(accountCreated.RowsError, gorm.ErrModelAccessibleFieldsRequired) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidData) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidValue) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidField) {
+ s.Exception.BadRequest = true
+ s.Exception.Message = "Bad request!"
+ return
+ }
+
+ userProfile := UserProfileService{}
+ userProfile.Constructor.AccountID = accountCreated.Result.Id
+ userProfile.Create()
+ if userProfile.Error != nil {
+ s.Error = userProfile.Error
+ return
+ }
+
+ s.Error = accountCreated.RowsError
+ s.Result = accountCreated.Result
+ s.Result.Password = "SECRET"
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
index c85da3300278d8a10cf9f23b487dad7e7b1a4979..9cee0115650b4c655e0e1229f9007f737902cb6c 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -178,10 +178,10 @@ type UserAnswer struct {
IsCorrect bool `json:"is_correct"`
}
type QuizResult struct {
- QuizAttemptID uint `gorm:"column:quiz_attempt_id"`
- TotalQuestions int `gorm:"column:total_questions"`
- CorrectAnswers int `gorm:"column:correct_answers"`
- AverageScore float64 `gorm:"column:average_score"`
+ QuizAttemptID uint `gorm:"column:quiz_attempt_id" json:"quiz_attempt_id"`
+ TotalQuestions int `gorm:"column:total_questions" json:"total_questions"`
+ CorrectAnswers int `gorm:"column:correct_answers" json:"correct_answers"`
+ AverageScore float64 `gorm:"column:average_score" json:"average_score"`
}
// Gorm table name settings
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go
index e0ea7463541b644ffc0e32943e096949572490bb..249b018886c4aac504af16e36f34498de64d6e7f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go
@@ -8,7 +8,6 @@ import (
"api.qobiltu.id/models"
"api.qobiltu.id/repositories"
)
-
type AttemptQuizService struct {
Service[models.Quiz, models.QuizAttempt]
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
index a2e3f0bdcf7d2b35ba2c72c46dc689c4a1089458..c85da3300278d8a10cf9f23b487dad7e7b1a4979 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -138,13 +138,13 @@ type RegionCity struct {
ProvinceID uint `json:"province_id"`
}
type Answer struct {
- ID uint `gorm:"primaryKey"`
+ ID uint `gorm:"primaryKey" json:"id"`
QuestionID uint `json:"question_id"`
Content string `json:"content"`
IsCorrect bool `json:"-"`
}
type Question struct {
- ID uint `gorm:"primaryKey"`
+ ID uint `gorm:"primaryKey" json:"id"`
QuizID uint `json:"quiz_id"`
Content string `json:"content"`
Order int `json:"order"`
@@ -162,7 +162,7 @@ type Quiz struct {
}
type QuizAttempt struct {
- ID uint `gorm:"primaryKey"`
+ ID uint `gorm:"primaryKey" json:"id"`
AccountID uint `json:"user_id"`
QuizID uint `json:"quiz_id"`
StartedAt time.Time `json:"started_at"`
@@ -171,7 +171,7 @@ type QuizAttempt struct {
Score float64 `json:"score"`
}
type UserAnswer struct {
- ID uint `gorm:"primaryKey"`
+ ID uint `gorm:"primaryKey" json:"id"`
QuizAttemptID uint `json:"quiz_attempt_id"`
QuestionID uint `json:"question_id"`
SelectedAnswer uint `json:"selected_answer"`
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/question_quiz_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/question_quiz_controller.go
index 54e3c633c4cd1dd81ea9655de4572ac2d9354bc7..9c99fa445e2e6a4b3d3004b2fdc3031cd1f02f35 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/question_quiz_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/question_quiz_controller.go
@@ -11,15 +11,17 @@ import (
func Question(c *gin.Context) {
questionQuiz := services.QuestionQuizService{}
- questionQuizController := controller.Controller[models.QuestionQuizRequest, models.Quiz, models.QuestionResponse]{
+ questionQuizController := controller.Controller[any, models.Quiz, models.QuestionResponse]{
Service: &questionQuiz.Service,
}
- questionQuizController.RequestJSON(c, func() {
+ questionQuizController.HeaderParse(c, func() {
quizId, _ := strconv.Atoi(c.Param("quiz_id"))
academyId, _ := strconv.Atoi(c.Param("academy_id"))
+ questionNo, _ := strconv.Atoi(c.Query("question_no"))
questionQuizController.Service.Constructor.ID = uint(quizId)
questionQuizController.Service.Constructor.AcademyID = uint(academyId)
- questionQuiz.Retrieve(questionQuizController.AccountData.UserID, questionQuizController.Request.QuestionNo)
+ questionQuiz.Retrieve(questionQuizController.AccountData.UserID, questionNo)
+ questionQuizController.Response(c)
})
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/answer_quiz_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/answer_quiz_controller.go
index 73cfc102c6a72702391ada0ab781172298712382..3e1e26c0dafd7212306e7a78d9f6098f02e864ea 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/answer_quiz_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/answer_quiz_controller.go
@@ -17,6 +17,7 @@ func Answer(c *gin.Context) {
quizAnswerController.RequestJSON(c, func() {
quizId, _ := strconv.Atoi(c.Param("quiz_id"))
academyId, _ := strconv.Atoi(c.Param("academy_id"))
+
quizAnswerController.Service.Constructor.ID = uint(quizId)
quizAnswerController.Service.Constructor.AcademyID = uint(academyId)
quizAnswer.Update(quizAnswerController.AccountData.UserID, quizAnswerController.Request.QuestionNo, quizAnswerController.Request.Answer)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/submit_quiz_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/submit_quiz_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..f6d8348c392ce62c712bf400e711d9c6773bc1e4
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/submit_quiz_controller.go
@@ -0,0 +1,23 @@
+package controller
+
+import (
+ "strconv"
+
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Submit(c *gin.Context) {
+ submitQuiz := services.SubmitQuizService{}
+ submitQuizController := controller.Controller[any, models.QuizAttempt, models.QuizResultResponse]{
+ Service: &submitQuiz.Service,
+ }
+ submitQuizController.HeaderParse(c, func() {
+ quizId, _ := strconv.Atoi(c.Param("attempt_id"))
+ submitQuizController.Service.Constructor.ID = uint(quizId)
+ submitQuiz.Create(submitQuizController.AccountData.UserID)
+ submitQuizController.Response(c)
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
index 0588c0a120d8455f8cdb8c998042a5815af9e48e..a3a3ccb2a25825af877a90853f061de639c1cde4 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
@@ -64,6 +64,11 @@ func AutoMigrateAll(db *gorm.DB) {
&models.RegionProvince{},
&models.OptionCategory{},
&models.OptionValues{},
+ &models.Quiz{},
+ &models.QuizAttempt{},
+ &models.Question{},
+ &models.Answer{},
+ &models.UserAnswer{},
)
if err != nil {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/answer_quiz_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/answer_quiz_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..73cfc102c6a72702391ada0ab781172298712382
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/answer_quiz_controller.go
@@ -0,0 +1,24 @@
+package controller
+
+import (
+ "strconv"
+
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Answer(c *gin.Context) {
+ quizAnswer := services.AnswerQuizService{}
+ quizAnswerController := controller.Controller[models.AnswerQuizRequest, models.Quiz, models.QuestionResponse]{
+ Service: &quizAnswer.Service,
+ }
+ quizAnswerController.RequestJSON(c, func() {
+ quizId, _ := strconv.Atoi(c.Param("quiz_id"))
+ academyId, _ := strconv.Atoi(c.Param("academy_id"))
+ quizAnswerController.Service.Constructor.ID = uint(quizId)
+ quizAnswerController.Service.Constructor.AcademyID = uint(academyId)
+ quizAnswer.Update(quizAnswerController.AccountData.UserID, quizAnswerController.Request.QuestionNo, quizAnswerController.Request.Answer)
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/attempt_quiz_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/attempt_quiz_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..210bb901ce1cb42ce44f93b3570636fffced950c
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/attempt_quiz_controller.go
@@ -0,0 +1,25 @@
+package controller
+
+import (
+ "strconv"
+
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Attempt(c *gin.Context) {
+ attemptQuiz := services.AttemptQuizService{}
+ attemptQuizController := controller.Controller[any, models.Quiz, models.QuizAttempt]{
+ Service: &attemptQuiz.Service,
+ }
+ attemptQuizController.HeaderParse(c, func() {
+ quizId, _ := strconv.Atoi(c.Param("quiz_id"))
+ academyId, _ := strconv.Atoi(c.Param("academy_id"))
+ attemptQuizController.Service.Constructor.ID = uint(quizId)
+ attemptQuizController.Service.Constructor.AcademyID = uint(academyId)
+ attemptQuiz.Create(attemptQuizController.AccountData.UserID)
+ attemptQuizController.Response(c)
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/question_quiz_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/question_quiz_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..54e3c633c4cd1dd81ea9655de4572ac2d9354bc7
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/quiz/question_quiz_controller.go
@@ -0,0 +1,25 @@
+package controller
+
+import (
+ "strconv"
+
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Question(c *gin.Context) {
+ questionQuiz := services.QuestionQuizService{}
+ questionQuizController := controller.Controller[models.QuestionQuizRequest, models.Quiz, models.QuestionResponse]{
+ Service: &questionQuiz.Service,
+ }
+ questionQuizController.RequestJSON(c, func() {
+ quizId, _ := strconv.Atoi(c.Param("quiz_id"))
+ academyId, _ := strconv.Atoi(c.Param("academy_id"))
+ questionQuizController.Service.Constructor.ID = uint(quizId)
+ questionQuizController.Service.Constructor.AcademyID = uint(academyId)
+ questionQuiz.Retrieve(questionQuizController.AccountData.UserID, questionQuizController.Request.QuestionNo)
+
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
index 2608f6ac39ae8bd1880562f0f08aa64ab52f8de6..a2e3f0bdcf7d2b35ba2c72c46dc689c4a1089458 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -137,6 +137,52 @@ type RegionCity struct {
FullCode string `json:"full_code"`
ProvinceID uint `json:"province_id"`
}
+type Answer struct {
+ ID uint `gorm:"primaryKey"`
+ QuestionID uint `json:"question_id"`
+ Content string `json:"content"`
+ IsCorrect bool `json:"-"`
+}
+type Question struct {
+ ID uint `gorm:"primaryKey"`
+ QuizID uint `json:"quiz_id"`
+ Content string `json:"content"`
+ Order int `json:"order"`
+ CorrectAnswer uint `json:"-"`
+}
+type Quiz struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ AcademyID uint `json:"academy_id"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ AttemptLimit int `json:"attempt_limit"`
+ TimeLimit int `json:"time_limit"`
+ MinScore int `json:"min_score"`
+ CreatedAt time.Time `json:"created_at"`
+}
+
+type QuizAttempt struct {
+ ID uint `gorm:"primaryKey"`
+ AccountID uint `json:"user_id"`
+ QuizID uint `json:"quiz_id"`
+ StartedAt time.Time `json:"started_at"`
+ DueAt time.Time `json:"due_at"`
+ FinishedAt *time.Time `json:"finished_at"`
+ Score float64 `json:"score"`
+}
+type UserAnswer struct {
+ ID uint `gorm:"primaryKey"`
+ QuizAttemptID uint `json:"quiz_attempt_id"`
+ QuestionID uint `json:"question_id"`
+ SelectedAnswer uint `json:"selected_answer"`
+ IsCorrect bool `json:"is_correct"`
+}
+type QuizResult struct {
+ QuizAttemptID uint `gorm:"column:quiz_attempt_id"`
+ TotalQuestions int `gorm:"column:total_questions"`
+ CorrectAnswers int `gorm:"column:correct_answers"`
+ AverageScore float64 `gorm:"column:average_score"`
+}
// Gorm table name settings
func (Account) TableName() string { return "account" }
@@ -152,3 +198,10 @@ func (AcademyMaterialProgress) TableName() string { return "academy_materials_pr
func (AcademyContentProgress) TableName() string { return "academy_contents_progress" }
func (RegionProvince) TableName() string { return "region_provinces" }
func (RegionCity) TableName() string { return "region_cities" }
+func (Answer) TableName() string { return "answers" }
+func (Question) TableName() string { return "questions" }
+func (Quiz) TableName() string { return "quizzes" }
+func (QuizAttempt) TableName() string { return "quiz_attempts" }
+func (UserAnswer) TableName() string { return "user_answers" }
+func (OptionCategory) TableName() string { return "option_categories" }
+func (OptionValues) TableName() string { return "option_values" }
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
index f2c026a993f950652158318977783e8563e595fb..3c3c55f7fad8a1356daf2ec03852b3f2ebd9786d 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
@@ -8,5 +8,8 @@ type Exception struct {
DataDuplicate bool `json:"data_duplicate,omitempty"`
QueryError bool `json:"query_error,omitempty"`
InvalidPasswordLength bool `json:"invalid_password_length,omitempty"`
+ IsPassTheLimit bool `json:"is_pass_the_limit,omitempty"`
+ IsTimeOut bool `json:"is_time_out,omitempty"`
+ AttemptNotFound bool `json:"attempt_not_found,omitempty"`
Message string `json:"message,omitempty"`
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
index 80e5e088cf171bff6841265d01caf14510b7ac53..e2c14412e72ec8e9b85233157f0b93ad2f7fca98 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
@@ -40,3 +40,12 @@ type ValidateForgotPasswordRequest struct {
Token uint `json:"token" binding:"required"`
NewPassword string `json:"new_password"`
}
+
+type QuestionQuizRequest struct {
+ QuestionNo int `json:"question_no" binding:"required"`
+}
+
+type AnswerQuizRequest struct {
+ QuestionNo int `json:"question_no" binding:"required"`
+ Answer int `json:"answer" binding:"required"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/academy_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/academy_repository.go
index 232722d4e2a9195950c9f579044008581bd2b5fb..bda561509638851811a64cdff30102bd5ee488c1 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/academy_repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/academy_repository.go
@@ -82,3 +82,14 @@ func GetAcademyMaterialBySlug(slug string) Repository[models.AcademyMaterial, mo
)
return *repo
}
+
+func GetAcademyByID(id uint) Repository[models.Academy, models.Academy] {
+ repo := Construct[models.Academy, models.Academy](
+ models.Academy{ID: id},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.Academy, models.Academy],
+ Find[models.Academy, models.Academy],
+ )
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/question_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/question_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..44fd9c1ff0022ca6cbe15f98facba1a0b7964ee7
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/question_repository.go
@@ -0,0 +1,36 @@
+package repositories
+
+import "api.qobiltu.id/models"
+
+func GetQuestionByQuizId(quizId uint) Repository[models.Question, []models.Question] {
+ repo := Construct[models.Question, []models.Question](
+ models.Question{QuizID: quizId},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.Question, []models.Question],
+ Find[models.Question, []models.Question],
+ )
+ return *repo
+}
+
+func GetQuestionByOrder(quizId uint, order int) Repository[models.Question, models.Question] {
+ repo := Construct[models.Question, models.Question](
+ models.Question{QuizID: quizId, Order: order},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.Question, models.Question],
+ Find[models.Question, models.Question],
+ )
+ return *repo
+}
+
+func GetAnswerByQuestionId(questionId uint) Repository[models.Answer, []models.Answer] {
+ repo := Construct[models.Answer, []models.Answer](
+ models.Answer{QuestionID: questionId},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.Answer, []models.Answer],
+ Find[models.Answer, []models.Answer],
+ )
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/quiz_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/quiz_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..f139508068134e86f341f05f9e233342f82e88a9
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/quiz_repository.go
@@ -0,0 +1,119 @@
+package repositories
+
+import (
+ "api.qobiltu.id/models"
+)
+
+func GetQuizbyAcademyId(academyId uint) Repository[models.Quiz, []models.Quiz] {
+ repo := Construct[models.Quiz, []models.Quiz](
+ models.Quiz{AcademyID: academyId},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.Quiz, []models.Quiz],
+ Find[models.Quiz, []models.Quiz],
+ )
+ return *repo
+}
+
+func GetAllUserAttempt(userId uint, quizId uint) Repository[models.QuizAttempt, []models.QuizAttempt] {
+ repo := Construct[models.QuizAttempt, []models.QuizAttempt](
+ models.QuizAttempt{AccountID: userId, QuizID: quizId},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.QuizAttempt, []models.QuizAttempt],
+ Find[models.QuizAttempt, []models.QuizAttempt],
+ )
+ return *repo
+}
+
+func GetQuizbyId(quizId uint) Repository[models.Quiz, models.Quiz] {
+ repo := Construct[models.Quiz, models.Quiz](
+ models.Quiz{ID: quizId},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.Quiz, models.Quiz],
+ Find[models.Quiz, models.Quiz],
+ )
+
+ return *repo
+}
+
+func GetUserLastAttempt(userId uint, quizId uint) Repository[models.QuizAttempt, models.QuizAttempt] {
+ repo := Construct[models.QuizAttempt, models.QuizAttempt](
+ models.QuizAttempt{AccountID: userId, QuizID: quizId},
+ )
+ repo.Transaction.Where(&repo.Constructor).Last(&repo.Result)
+ repo.RowsError = repo.Transaction.Error
+ repo.NoRecord = false
+ // fmt.Println(repo.Transaction.RowsAffected) Kenapa 0 !!!!
+ return *repo
+}
+
+func GetAttemptById(attemptId uint) Repository[models.QuizAttempt, models.QuizAttempt] {
+ repo := Construct[models.QuizAttempt, models.QuizAttempt](
+ models.QuizAttempt{ID: attemptId},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.QuizAttempt, models.QuizAttempt],
+ Find[models.QuizAttempt, models.QuizAttempt],
+ )
+ return *repo
+}
+
+func CreateAttempt(quizAttempt models.QuizAttempt) Repository[models.QuizAttempt, models.QuizAttempt] {
+ repo := Construct[models.QuizAttempt, models.QuizAttempt](
+ quizAttempt,
+ )
+
+ Create(repo)
+ return *repo
+}
+
+func GetUserAnswerByAttemptQuestionId(attemptId uint, questionId uint) Repository[models.UserAnswer, models.UserAnswer] {
+ repo := Construct[models.UserAnswer, models.UserAnswer](
+ models.UserAnswer{
+ QuizAttemptID: attemptId,
+ QuestionID: questionId,
+ },
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.UserAnswer, models.UserAnswer],
+ Find[models.UserAnswer, models.UserAnswer],
+ )
+ return *repo
+}
+
+func CreateUserAnswer(userAnswer models.UserAnswer) Repository[models.UserAnswer, models.UserAnswer] {
+ repo := Construct[models.UserAnswer, models.UserAnswer](
+ userAnswer,
+ )
+
+ Create(repo)
+ return *repo
+}
+
+func UpdateUserAnswer(userAnswer models.UserAnswer) Repository[models.UserAnswer, models.UserAnswer] {
+ repo := Construct[models.UserAnswer, models.UserAnswer](
+ userAnswer,
+ )
+ Update(repo)
+ return *repo
+}
+
+func UpdateQuizAttempt(quizAttempt models.QuizAttempt) Repository[models.QuizAttempt, models.QuizAttempt] {
+ repo := Construct[models.QuizAttempt, models.QuizAttempt](
+ quizAttempt,
+ )
+ Update(repo)
+ return *repo
+}
+
+func CountUserAttemptScore(attemptId uint) Repository[models.QuizAttempt, models.QuizResult] {
+ repo := Construct[models.QuizAttempt, models.QuizResult](
+ models.QuizAttempt{ID: attemptId},
+ )
+ repo.Transaction.Model(&repo.Constructor).Raw("SELECT quiz_attempt_id,COUNT(*) AS total_questions,SUM(CASE WHEN is_correct = true THEN 1 ELSE 0 END) AS correct_answers,CAST(SUM(CASE WHEN is_correct = true THEN 1 ELSE 0 END) AS FLOAT) / COUNT(*) AS average_score FROM user_answers WHERE quiz_attempt_id = ? GROUP BY quiz_attempt_id", attemptId).Scan(&repo.Result)
+ repo.RowsError = repo.Transaction.Error
+ repo.NoRecord = true
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/quiz_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/quiz_route.go
new file mode 100644
index 0000000000000000000000000000000000000000..e38d28dfc2b2fe029f120313a66e17bf414fe35c
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/quiz_route.go
@@ -0,0 +1,17 @@
+package router
+
+import (
+ QuizController "api.qobiltu.id/controller/quiz"
+ "api.qobiltu.id/middleware"
+ "github.com/gin-gonic/gin"
+)
+
+func QuizRoute(router *gin.Engine) {
+ routerGroup := router.Group("/api/v1/quiz")
+ {
+ routerGroup.POST("/:academy_id/:quiz_id/attempt", middleware.AuthUser, QuizController.Attempt)
+ routerGroup.GET("/:academy_id/:quiz_id/question", middleware.AuthUser, QuizController.Question)
+ routerGroup.PUT("/:academy_id/:quiz_id/choose-answer", middleware.AuthUser, QuizController.Answer)
+ routerGroup.POST("/submit-attempt/:attempt_id", middleware.AuthUser, QuizController.Submit)
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
index 31b36acd86c58c477d59b9f8b7cbaf0bee703550..4d4950d1b0d4930f015736d6c8503f5ee025c674 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
@@ -11,12 +11,12 @@ import (
func StartService() {
router := gin.Default()
router.GET("/", controller.HomeController)
-
AuthRoute(router)
UserRoute(router)
EmailRoute(router)
OptionsRoute(router)
AcademyRoute(router)
+ QuizRoute(router)
err := router.Run(config.TCP_ADDRESS)
if err != nil {
log.Fatalf("Failed to run server: %v", err)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_answer_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_answer_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..2f383c92fac4a3c38cfd3c5af4f55cdd4cd4d3b2
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_answer_service.go
@@ -0,0 +1,56 @@
+package services
+
+import (
+ "errors"
+
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+)
+
+type AnswerQuizService struct {
+ Service[models.Quiz, models.QuestionResponse]
+}
+
+func (s *AnswerQuizService) Update(userID uint, questionNo int, answer int) {
+ QuizAttemptService := AttemptQuizService{}
+ QuizAttemptService.Constructor = s.Constructor
+ QuizAttemptService.Validate(userID, func(latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz]) {
+
+ questionRepo := repositories.GetQuestionByOrder(latestAttemptRepo.Result.QuizID, questionNo)
+ if questionRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no quiz with given academy!"
+ return
+ }
+
+ s.Error = questionRepo.RowsError
+ answerRepo := repositories.GetUserAnswerByAttemptQuestionId(latestAttemptRepo.Result.ID, questionRepo.Result.ID)
+ if answerRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no Answer with given AttemptId!"
+ return
+ }
+ s.Error = errors.Join(s.Error, answerRepo.RowsError)
+
+ answerRepo.Result.SelectedAnswer = uint(answer)
+ answerRepo.Result.IsCorrect = (questionRepo.Result.CorrectAnswer == uint(answer))
+
+ updatedAnswer := repositories.UpdateUserAnswer(answerRepo.Result)
+
+ s.Error = errors.Join(s.Error, updatedAnswer.RowsError)
+
+ questionRepo.Result.CorrectAnswer = uint(0)
+ answerOptionRepo := repositories.GetAnswerByQuestionId(questionRepo.Result.ID)
+ if answerOptionRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no Answer Option with given QuestionId!"
+ return
+ }
+ s.Result = models.QuestionResponse{
+ Question: questionRepo.Result,
+ Answer: answerOptionRepo.Result,
+ UserAnswer: int(answerRepo.Result.SelectedAnswer),
+ }
+ return
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_question_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_question_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..8dc23afae98d277e9e6f8e79495b1abe04369a3e
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_question_service.go
@@ -0,0 +1,42 @@
+package services
+
+import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+)
+
+type QuestionQuizService struct {
+ Service[models.Quiz, models.QuestionResponse]
+}
+
+func (s *QuestionQuizService) Retrieve(userID uint, questionNo int) {
+ QuizAttemptService := AttemptQuizService{}
+ QuizAttemptService.Constructor = s.Constructor
+ QuizAttemptService.Validate(userID, func(latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz]) {
+ questionRepo := repositories.GetQuestionByOrder(s.Constructor.ID, questionNo)
+ s.Error = questionRepo.RowsError
+ if questionRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no quiz with given academy!"
+ return
+ }
+ answerRepo := repositories.GetUserAnswerByAttemptQuestionId(latestAttemptRepo.Result.ID, questionRepo.Result.ID)
+ if answerRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no Answer with given AttemptId!"
+ return
+ }
+ answerOptionRepo := repositories.GetAnswerByQuestionId(questionRepo.Result.ID)
+ if answerOptionRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no Answer Option with given QuestionId!"
+ return
+ }
+ s.Result = models.QuestionResponse{
+ Question: questionRepo.Result,
+ Answer: answerOptionRepo.Result,
+ UserAnswer: int(answerRepo.Result.SelectedAnswer),
+ }
+ return
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..e0ea7463541b644ffc0e32943e096949572490bb
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_quiz_service.go
@@ -0,0 +1,154 @@
+package services
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+)
+
+type AttemptQuizService struct {
+ Service[models.Quiz, models.QuizAttempt]
+}
+
+type SubmitQuizService struct {
+ Service[models.QuizAttempt, models.QuizResultResponse]
+}
+
+func AuthorizeQuizwithAcademy(s *AttemptQuizService, next func()) {
+ academyRepo := repositories.GetAcademyByID(s.Constructor.AcademyID)
+ s.Error = academyRepo.RowsError
+ if academyRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no academy with given slug!"
+ return
+ }
+ quizRepo := repositories.GetQuizbyAcademyId(academyRepo.Result.ID)
+ s.Error = quizRepo.RowsError
+ if quizRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no quiz with given academy!"
+ return
+ }
+ next()
+}
+func CheckUserAttemptLimit(s *AttemptQuizService, allAttemptsRepo repositories.Repository[models.QuizAttempt, []models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz], userID uint, next func()) {
+ if (allAttemptsRepo.RowsCount >= quizRepo.Result.AttemptLimit) && (allAttemptsRepo.Result[allAttemptsRepo.RowsCount-1].FinishedAt != nil) {
+ s.Exception.IsPassTheLimit = true
+ s.Exception.Message = "You have reached the attempt limit!"
+ return
+ }
+ next()
+}
+
+func CheckUserLatestAttempt(s *AttemptQuizService, latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz], userID uint, next func()) {
+ currentTime := time.Now()
+ if currentTime.After(latestAttemptRepo.Result.DueAt) {
+ s.Exception.IsTimeOut = true
+ s.Exception.Message = "Your latest attempt is timeout!"
+ // Submit
+ return
+ }
+ next()
+}
+
+func CheckUserAttemptable(s *AttemptQuizService, latestAttemptRepo repositories.Repository[models.QuizAttempt, []models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz], userID uint, next func()) {
+ s.Error = errors.Join(s.Error, latestAttemptRepo.RowsError, quizRepo.RowsError)
+ if latestAttemptRepo.Result[latestAttemptRepo.RowsCount-1].FinishedAt != nil {
+ s.Exception.IsPassTheLimit = true
+ s.Exception.Message = "You have reached the attempt limit!"
+ return
+ }
+ next()
+}
+func (s *AttemptQuizService) Validate(userID uint, next func(latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz])) {
+ AuthorizeQuizwithAcademy(s, func() {
+ quizRepo := repositories.GetQuizbyId(s.Constructor.ID)
+ if quizRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no quiz data with given Id!"
+ return
+ }
+ allAttemptsRepo := repositories.GetAllUserAttempt(userID, s.Constructor.ID)
+ if allAttemptsRepo.NoRecord {
+ Attempt(s, quizRepo, userID)
+ allAttemptsRepo = repositories.GetAllUserAttempt(userID, s.Constructor.ID)
+ }
+ s.Error = errors.Join(allAttemptsRepo.RowsError, quizRepo.RowsError)
+ CheckUserAttemptLimit(s, allAttemptsRepo, quizRepo, userID, func() {
+ fmt.Println("accountID", userID)
+ fmt.Println("quizID", s.Constructor.ID)
+ latestAttemptRepo := repositories.GetUserLastAttempt(userID, s.Constructor.ID)
+ if latestAttemptRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no quiz attempt with given user!"
+ return
+ }
+ s.Error = errors.Join(s.Error, latestAttemptRepo.RowsError)
+ CheckUserLatestAttempt(s, latestAttemptRepo, quizRepo, userID, func() {
+ next(latestAttemptRepo, quizRepo)
+ })
+ })
+ })
+}
+
+func Attempt(s *AttemptQuizService, quizRepo repositories.Repository[models.Quiz, models.Quiz], userID uint) {
+ startTime := time.Now()
+ dueTime := startTime.Add(time.Duration(quizRepo.Result.TimeLimit) * time.Minute)
+ createdAttemptRepo := repositories.CreateAttempt(models.QuizAttempt{
+ AccountID: userID,
+ QuizID: s.Constructor.ID,
+ StartedAt: startTime,
+ DueAt: dueTime,
+ })
+ s.Error = createdAttemptRepo.RowsError
+ questionsRepo := repositories.GetQuestionByQuizId(s.Constructor.ID)
+ for _, question := range questionsRepo.Result {
+ createdUserAnswer := repositories.CreateUserAnswer(models.UserAnswer{
+ QuizAttemptID: createdAttemptRepo.Result.ID,
+ QuestionID: question.ID,
+ })
+ if createdUserAnswer.RowsError != nil {
+ s.Error = createdUserAnswer.RowsError
+ return
+ }
+ }
+ s.Result = createdAttemptRepo.Result
+}
+func (s *AttemptQuizService) Create(userID uint) {
+ s.Validate(userID, func(latestAttemptRepo repositories.Repository[models.QuizAttempt, models.QuizAttempt], quizRepo repositories.Repository[models.Quiz, models.Quiz]) {
+ if latestAttemptRepo.Result.FinishedAt != nil {
+ Attempt(s, quizRepo, userID)
+ } else {
+ s.Result = latestAttemptRepo.Result
+ }
+ })
+}
+func (s *SubmitQuizService) Create(userID uint) {
+ quizAttemptRepo := repositories.GetAttemptById(s.Constructor.ID)
+ if quizAttemptRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no quiz attempt with given user!"
+ return
+ }
+ s.Error = errors.Join(s.Error, quizAttemptRepo.RowsError)
+ countScoreRepo := repositories.CountUserAttemptScore(s.Constructor.ID)
+ s.Error = errors.Join(s.Error, countScoreRepo.RowsError)
+
+ if quizAttemptRepo.Result.FinishedAt == nil {
+ finishTime := time.Now()
+ quizAttemptRepo.Result.FinishedAt = &finishTime
+ quizAttemptRepo.Result.Score = countScoreRepo.Result.AverageScore * 100
+ updateAttemptRepo := repositories.UpdateQuizAttempt(quizAttemptRepo.Result)
+ s.Error = errors.Join(s.Error, updateAttemptRepo.RowsError)
+ }
+
+ s.Result = models.QuizResultResponse{
+ QuizAttempt: quizAttemptRepo.Result,
+ Result: countScoreRepo.Result,
+ }
+
+ return
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
index eee34890fbf3519de21c401fbdcbf6c60bebb852..ff797479a4428577384c354a8ae7427d87ab6db0 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
@@ -33,7 +33,7 @@ type UserProfileResponse struct {
type AcademyMaterialResponse struct {
Materials AcademyMaterial
- Contents []AcademyContent
+ Contents AcademyContent
}
type AcademyResponse struct {
Academy Academy `json:"academy"`
@@ -43,3 +43,19 @@ type AcademyResponse struct {
type AllAcademyResponse struct {
Academies []AcademyResponse `json:"academy_dasar"`
}
+
+type AttemptExamResponse struct {
+ Exam Quiz `json:"exam"`
+ Questions []Question `json:"questions"`
+}
+
+type QuestionResponse struct {
+ Question Question `json:"question"`
+ Answer []Answer `json:"answer_options"`
+ UserAnswer int `json:"current_user_answer"`
+}
+
+type QuizResultResponse struct {
+ QuizAttempt QuizAttempt `json:"quiz_attempt"`
+ Result QuizResult `json:"result"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
index 4802c35b8bd0a7f5b29214a68fc8f6eb1ea8dfde..a2ca914abd5d53d032934d3de1a56b8fddd97a39 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
@@ -2,6 +2,7 @@ package services
import (
"errors"
+ "unicode"
"api.qobiltu.id/models"
"api.qobiltu.id/repositories"
@@ -13,10 +14,49 @@ type RegisterService struct {
Service[models.Account, models.Account]
}
+func ValidatePassword(password string) models.Exception {
+ var (
+ hasMinLen = false
+ hasUpper = false
+ hasLower = false
+ hasNumber = false
+ hasSpecial = false
+ )
+
+ if len(password) >= 8 {
+ hasMinLen = true
+ }
+
+ for _, char := range password {
+ switch {
+ case unicode.IsUpper(char):
+ hasUpper = true
+ break
+ case unicode.IsLower(char):
+ hasLower = true
+ break
+ case unicode.IsDigit(char):
+ hasNumber = true
+ break
+ case unicode.IsPunct(char) || unicode.IsSymbol(char):
+ hasSpecial = true
+ break
+ }
+ }
+ approve := hasMinLen && hasUpper && hasLower && hasNumber && hasSpecial
+ if !approve {
+ return models.Exception{
+ BadRequest: true,
+ Message: "Password must contain at least 8 characters, including uppercase, lowercase, number, and special character!",
+ }
+ }
+ return models.Exception{}
+}
+
func (s *RegisterService) Create() {
- if len(s.Constructor.Password) < 8 {
- s.Exception.InvalidPasswordLength = true
- s.Exception.Message = "Password must have at least 8 characters!"
+ validatePassword := ValidatePassword(s.Constructor.Password)
+ if validatePassword.BadRequest {
+ s.Exception = validatePassword
return
}
hashed_password, err_hash := HashPassword(s.Constructor.Password)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/academy/academy_contents_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/academy/academy_contents_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..61b46aca7f052c41bf333d6e8a0b170d2d18904e
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/academy/academy_contents_controller.go
@@ -0,0 +1,19 @@
+package academy
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func ContentList(c *gin.Context) {
+ academyContent := services.AcademyContentService{}
+ academyController := controller.Controller[any, models.AcademyMaterial, models.AcademyContent]{
+ Service: &academyContent.Service,
+ }
+ academyController.Service.Constructor.Slug = c.Param("slug_material")
+ academyContent.Retrieve()
+ academyController.Response(c)
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/academy/academy_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/academy/academy_controller.go
index f4210c585c26c026eb45a44c42b059cb875a51c4..5970d61e103cb3d31a7f07b1bc1bb38c244a3e5e 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/academy/academy_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/academy/academy_controller.go
@@ -9,7 +9,7 @@ import (
func List(c *gin.Context) {
academy := services.AcademyService{}
- academyController := controller.Controller[any, models.Academy, models.AllAcademyResponse]{
+ academyController := controller.Controller[any, models.Academy, []models.Academy]{
Service: &academy.Service,
}
academyController.Service.Constructor.Slug = c.Param("slug")
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/academy/academy_materials_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/academy/academy_materials_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..999642d591b2b30609eefb730fe6d63da2f5c9c4
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/academy/academy_materials_controller.go
@@ -0,0 +1,19 @@
+package academy
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func MaterialsList(c *gin.Context) {
+ academyMaterial := services.AcademyMaterialService{}
+ academyController := controller.Controller[any, models.Academy, []models.AcademyMaterial]{
+ Service: &academyMaterial.Service,
+ }
+ academyController.Service.Constructor.Slug = c.Param("slug")
+ academyMaterial.Retrieve()
+ academyController.Response(c)
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
index 96eabef126ed5779aa33b9f1c557192b3668689a..2608f6ac39ae8bd1880562f0f08aa64ab52f8de6 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -68,11 +68,15 @@ type ForgotPassword struct {
}
type Academy struct {
- ID uint `gorm:"primaryKey" json:"id"`
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
- Title string `json:"title"`
- Slug string `json:"slug"`
- Description string `json:"description"`
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
+ Title string `json:"title"`
+ Slug string `json:"slug" gorm:"uniqueIndex" `
+ TotalMaterial int `json:"total_material"`
+ CompletedMaterial int `json:"completed_material"`
+ IsCompletedRead bool `json:"is_read"`
+ IsPassedExam bool `json:"is_exam"`
+ Description string `json:"description"`
}
type AcademyMaterial struct {
@@ -80,7 +84,8 @@ type AcademyMaterial struct {
UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
AcademyID uint `json:"academy_id"`
Title string `json:"title"`
- Slug string `json:"slug"`
+ Slug string `json:"slug" gorm:"uniqueIndex"`
+ IsCompleted bool `json:"is_completed"`
Description string `json:"description"`
}
@@ -95,7 +100,7 @@ type AcademyContent struct {
type OptionCategory struct {
ID uint `gorm:"primaryKey" json:"id"`
OptionName string `json:"option_name"`
- OptionSlug string `json:"option_slug"`
+ OptionSlug string `json:"option_slug" gorm:"uniqueIndex"`
}
type OptionValues struct {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/academy_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/academy_repository.go
index 2a9235d4343ea63f784fd65184f498dca8c88338..232722d4e2a9195950c9f579044008581bd2b5fb 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/academy_repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/academy_repository.go
@@ -35,13 +35,13 @@ func GetAllAcademyMaterialsByAcademyID(acaddemyId uint) Repository[models.Academ
return *repo
}
-func GetAllAcademyContentsByMaterialID(materialId uint) Repository[models.AcademyContent, []models.AcademyContent] {
- repo := Construct[models.AcademyContent, []models.AcademyContent](
+func GetAllAcademyContentsByMaterialID(materialId uint) Repository[models.AcademyContent, models.AcademyContent] {
+ repo := Construct[models.AcademyContent, models.AcademyContent](
models.AcademyContent{AcademyMaterialID: materialId},
)
repo.Transactions(
- WhereGivenConstructor[models.AcademyContent, []models.AcademyContent],
- Find[models.AcademyContent, []models.AcademyContent],
+ WhereGivenConstructor[models.AcademyContent, models.AcademyContent],
+ Find[models.AcademyContent, models.AcademyContent],
)
return *repo
}
@@ -71,3 +71,14 @@ func CreateAcademyContent(academyContent models.AcademyContent) Repository[model
Create(repo)
return *repo
}
+
+func GetAcademyMaterialBySlug(slug string) Repository[models.AcademyMaterial, models.AcademyMaterial] {
+ repo := Construct[models.AcademyMaterial, models.AcademyMaterial](
+ models.AcademyMaterial{Slug: slug},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.AcademyMaterial, models.AcademyMaterial],
+ Find[models.AcademyMaterial, models.AcademyMaterial],
+ )
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/academy_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/academy_route.go
index d8be676cadb26d2e57fd285239a9c6c094b75480..8ea9fbd08794db83599e1b263159d607d5dd489f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/academy_route.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/academy_route.go
@@ -9,7 +9,8 @@ import (
func AcademyRoute(router *gin.Engine) {
routerGroup := router.Group("/api/v1/academy")
{
- routerGroup.GET("/list/:slug", middleware.AuthUser, AcademyController.List)
+ routerGroup.GET("/:slug", middleware.AuthUser, AcademyController.MaterialsList)
+ routerGroup.GET("/:slug/:slug_material", middleware.AuthUser, AcademyController.ContentList)
routerGroup.GET("/list", middleware.AuthUser, AcademyController.List)
routerGroup.POST("/create", middleware.AuthUser, AcademyController.Create)
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_service.go
index dc76d9e7081bf732839969a398df2c16b756b906..c3f9fcce8634493c3583c4f11f8666a6eee4a612 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_service.go
@@ -1,6 +1,8 @@
package services
import (
+ "errors"
+
"api.qobiltu.id/models"
"api.qobiltu.id/repositories"
"github.com/gosimple/slug"
@@ -8,16 +10,25 @@ import (
)
type AcademyService struct {
- Service[models.Academy, models.AllAcademyResponse]
+ Service[models.Academy, []models.Academy]
}
type CreateAcademyService struct {
Service[models.AllAcademyResponse, models.AllAcademyResponse]
}
+type AcademyMaterialService struct {
+ Service[models.Academy, []models.AcademyMaterial]
+}
+
+type AcademyContentService struct {
+ Service[models.AcademyMaterial, models.AcademyContent]
+}
+
func castAcademyMaterials(academyId uint) []models.AcademyMaterialResponse {
var ArrMaterials []models.AcademyMaterialResponse
- for _, academyMaterial := range repositories.GetAllAcademyMaterialsByAcademyID(academyId).Result {
+ academyMaterialsRepo := repositories.GetAllAcademyMaterialsByAcademyID(academyId)
+ for _, academyMaterial := range academyMaterialsRepo.Result {
ArrMaterials = append(ArrMaterials, models.AcademyMaterialResponse{
Materials: academyMaterial,
Contents: repositories.GetAllAcademyContentsByMaterialID(academyMaterial.ID).Result,
@@ -35,27 +46,13 @@ func (s *AcademyService) Retrieve() {
return
}
- s.Result = models.AllAcademyResponse{
- Academies: []models.AcademyResponse{
- models.AcademyResponse{
- Academy: AcademyRepo.Result,
- Materials: castAcademyMaterials(s.Constructor.ID),
- },
- },
+ s.Result = []models.Academy{
+ AcademyRepo.Result,
}
} else {
AcademyRepo := repositories.GetAllAcademy()
s.Error = AcademyRepo.RowsError
- var ArrAcademy []models.AcademyResponse
- for _, academy := range AcademyRepo.Result {
- ArrAcademy = append(ArrAcademy, models.AcademyResponse{
- Academy: academy,
- Materials: castAcademyMaterials(academy.ID),
- })
- }
- s.Result = models.AllAcademyResponse{
- Academies: ArrAcademy,
- }
+ s.Result = AcademyRepo.Result
}
}
@@ -84,7 +81,7 @@ func (s *CreateAcademyService) Create() {
s.Error = createdMaterial.RowsError
return
}
- for _, content := range material.Contents {
+ for _, content := range []models.AcademyContent{material.Contents} {
content.UUID = uuid.NewV4()
content.AcademyMaterialID = createdMaterial.Result.ID
createdContent := repositories.CreateAcademyContent(content)
@@ -102,3 +99,39 @@ func (s *CreateAcademyService) Create() {
s.Result = models.AllAcademyResponse{Academies: ArrAcademy}
}
+
+func (s *AcademyMaterialService) Retrieve() {
+ academyRepo := repositories.GetAcademyDataBySlug(s.Constructor.Slug)
+ if academyRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no Academy found with given Slug!"
+ return
+ }
+ s.Error = academyRepo.RowsError
+ academyMaterialRepo := repositories.GetAllAcademyMaterialsByAcademyID(academyRepo.Result.ID)
+ s.Error = errors.Join(s.Error, academyMaterialRepo.RowsError)
+ if academyMaterialRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no Academy Material with given ID"
+ return
+ }
+ s.Result = academyMaterialRepo.Result
+}
+
+func (s *AcademyContentService) Retrieve() {
+ academyMaterialRepo := repositories.GetAcademyMaterialBySlug(s.Constructor.Slug)
+ s.Error = academyMaterialRepo.RowsError
+ if academyMaterialRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no Academy Material with given Slug"
+ return
+ }
+ academyContentRepo := repositories.GetAllAcademyContentsByMaterialID(academyMaterialRepo.Result.ID)
+ s.Error = errors.Join(s.Error, academyContentRepo.RowsError)
+ if academyContentRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no Academy Contents with given Material ID"
+ return
+ }
+ s.Result = academyContentRepo.Result
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/jwt_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/jwt_service.go
index 3fdce4e4735d65dabdd71357a6d915f07219ddfb..0b076d9bd741906e04a796b99e9484e83be459c9 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/jwt_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/jwt_service.go
@@ -20,7 +20,7 @@ func GenerateToken(user *models.Account) (string, error) {
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), // Token berlaku 24 jam
IssuedAt: jwt.NewNumericDate(time.Now()),
- Issuer: "qobiltu.id",
+ Issuer: "apqobiltu.id",
},
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/service.go
index 3e6cda14fae0b3b8b7c50747311d2d8c67ee4d66..349e6f3a77ea563afbe7d2945b75e73302052337 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/service.go
@@ -16,6 +16,9 @@ type (
Authenticate()
Authorize()
}
+ IService interface {
+ Implements()
+ }
Service[TConstructor any, TResult any] struct {
Constructor TConstructor
Result TResult
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
index dc39e1086a86cd827f62ceea36d47941e402cc29..f965383fcd56b905fc16ae83703dc7d659b82399 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
@@ -75,18 +75,7 @@ func (s *EmailVerificationService) Create() {
return
}
- // 2. Start TLS
- tlsconfig := &tls.Config{
- ServerName: smtpHost,
- }
-
- if err = c.StartTLS(tlsconfig); err != nil {
- s.Error = err
- log.Printf("Error start TLS: %v", err)
- return
- }
-
- // 3. Auth
+ // 2. Auth
auth := smtp.PlainAuth("", from, password, smtpHost)
if err = c.Auth(auth); err != nil {
s.Error = err
@@ -94,7 +83,7 @@ func (s *EmailVerificationService) Create() {
return
}
- // 4. Set From and To
+ // 3. Set From and To
if err = c.Mail(from); err != nil {
s.Error = err
log.Printf("Error set mail from to: %v", err)
@@ -109,7 +98,7 @@ func (s *EmailVerificationService) Create() {
}
}
- // 5. Send message
+ // 4. Send message
wc, err := c.Data()
if err != nil {
s.Error = err
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
index 55487887878907689a66b9e5897895013b1b708f..ad174f7229b7f8ad7cd8719f586001d707825475 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
@@ -7,6 +7,7 @@ import (
"github.com/joho/godotenv"
)
+var ENV string
var TCP_ADDRESS string
var LOG_PATH string
@@ -21,6 +22,7 @@ var SMTP_PORT string
func init() {
godotenv.Load()
+ ENV = os.Getenv("ENV")
HOST_ADDRESS = os.Getenv("HOST_ADDRESS")
HOST_PORT = os.Getenv("HOST_PORT")
TCP_ADDRESS = HOST_ADDRESS + ":" + HOST_PORT
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
index c03d7a1b305ff97555f4699cfbd6c763d4cc230d..dc39e1086a86cd827f62ceea36d47941e402cc29 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
@@ -1,6 +1,7 @@
package services
import (
+ "crypto/tls"
"fmt"
"log"
"math/rand/v2"
@@ -39,27 +40,118 @@ func (s *EmailVerificationService) Create() {
// ⬇ Kirim token ke email user menggunakan SMTP
go func(toEmail string, token uint) {
+ env := config.ENV
from := config.SMTP_SENDER_EMAIL
password := config.SMTP_SENDER_PASSWORD
smtpHost := config.SMTP_HOST
smtpPort := config.SMTP_PORT
+ to := []string{toEmail}
- auth := smtp.PlainAuth("", from, password, smtpHost)
-
- subject := "Email Verification Token"
+ subject := "Verification token"
body := fmt.Sprintf("Your verification token is: %06d\nPlease use it before it expires.", token)
- msg := []byte("To: " + toEmail + "\r\n" +
- "Subject: " + subject + "\r\n" +
- "\r\n" +
- body + "\r\n")
+ msg := []byte(fmt.Sprintf("From: %s\r\n", from) +
+ fmt.Sprintf("To: %s\r\n", toEmail) +
+ fmt.Sprintf("Subject: %s\r\n", subject) +
+ "\r\n" + body)
+
+ // 1. Connect to the server
+ conn, err := tls.Dial("tcp", fmt.Sprintf("[%s]:%s", smtpHost, smtpPort), &tls.Config{
+ InsecureSkipVerify: env != "production", // ⚠️ set false di production
+ ServerName: smtpHost,
+ })
- err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, []string{toEmail}, msg)
+ // conn, err := net.Dial("tcp", fmt.Sprintf("[%s]:%s", smtpHost, smtpPort))
if err != nil {
s.Error = err
log.Printf("Error sending verification email: %v", err)
return
}
+
+ c, err := smtp.NewClient(conn, smtpHost)
+ if err != nil {
+ s.Error = err
+ log.Printf("Error create new client mail: %v", err)
+ return
+ }
+
+ // 2. Start TLS
+ tlsconfig := &tls.Config{
+ ServerName: smtpHost,
+ }
+
+ if err = c.StartTLS(tlsconfig); err != nil {
+ s.Error = err
+ log.Printf("Error start TLS: %v", err)
+ return
+ }
+
+ // 3. Auth
+ auth := smtp.PlainAuth("", from, password, smtpHost)
+ if err = c.Auth(auth); err != nil {
+ s.Error = err
+ log.Printf("Error auth mail: %v", err)
+ return
+ }
+
+ // 4. Set From and To
+ if err = c.Mail(from); err != nil {
+ s.Error = err
+ log.Printf("Error set mail from to: %v", err)
+ return
+ }
+
+ for _, addr := range to {
+ if err = c.Rcpt(addr); err != nil {
+ s.Error = err
+ log.Printf("Error receipt addr: %v", err)
+ return
+ }
+ }
+
+ // 5. Send message
+ wc, err := c.Data()
+ if err != nil {
+ s.Error = err
+ log.Printf("Error data Send Message: %v", err)
+ return
+ }
+
+ _, err = wc.Write(msg)
+ if err != nil {
+ s.Error = err
+ log.Printf("Error write Send Message: %v", err)
+ return
+ }
+
+ err = wc.Close()
+ if err != nil {
+ s.Error = err
+ log.Printf("Error close Send Message: %v", err)
+ return
+ }
+
+ c.Quit()
+ fmt.Println("Email sent successfully!")
+
+ // auth := smtp.PlainAuth("", from, password, smtpHost)
+
+ // subject := "Email Verification Token"
+ // body := fmt.Sprintf("Your verification token is: %06d\nPlease use it before it expires.", token)
+
+ // msg := []byte("To: " + toEmail + "\r\n" +
+ // "Subject: " + subject + "\r\n" +
+ // "\r\n" +
+ // body + "\r\n")
+
+ // err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, []string{toEmail}, msg)
+ // if err != nil {
+ // s.Error = err
+ // log.Printf("Error sending verification email: %v", err)
+ // return
+ // } else {
+ // log.Printf("Successfully sending verification email: %v", err)
+ // }
}(accountRepo.Result.Email, token)
// s.Result.Token = 0
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
index 4fe63453d49c45b5d8d4272de2f54f1184850b35..1b6ac24b03b07f24fa5a65be09fe16e34c72bf82 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
@@ -24,6 +24,16 @@ func (s *GoogleAuthService) Authenticate(isAgree bool) {
return
}
email := payload.Claims["email"]
+ checkRegisteredEmail := repositories.GetAccountbyEmail(email.(string))
+ if !checkRegisteredEmail.NoRecord {
+ token, _ := GenerateToken(&checkRegisteredEmail.Result)
+ checkRegisteredEmail.Result.Password = "SECRET"
+ s.Result = models.AuthenticatedUser{
+ Account: checkRegisteredEmail.Result,
+ Token: token,
+ }
+ return
+ }
if GoogleAuth.NoRecord {
if !isAgree {
s.Exception.BadRequest = true
@@ -32,12 +42,6 @@ func (s *GoogleAuthService) Authenticate(isAgree bool) {
}
s.Constructor.UUID = uuid.NewV4()
s.Constructor.OauthProvider = "Google"
- checkRegisteredEmail := repositories.GetAccountbyEmail(email.(string))
- if !checkRegisteredEmail.NoRecord {
- s.Exception.DataDuplicate = true
- s.Exception.Message = "Account with email" + email.(string) + "already registered!"
- return
- }
createAccount := repositories.CreateAccount(models.Account{
UUID: uuid.NewV4(),
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
index d156bc532db21abb6f992626e82cc931648bf62c..c03d7a1b305ff97555f4699cfbd6c763d4cc230d 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
@@ -29,10 +29,7 @@ func (s *EmailVerificationService) Create() {
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
dueTime := CalculateDueTime(remainingTime)
- token := uint(rand.IntN(1000000))
- for token < 1000000 {
- token = uint(rand.IntN(1000000))
- }
+ token := uint(rand.IntN(999999-100000) + 100000)
s.Constructor.UUID = uuid.NewV4()
repo := repositories.CreateEmailVerification(s.Constructor.UUID, s.Constructor.AccountID, dueTime, token)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
index 473bcf17fa8d55afdc313a25659e29ddae6f7e16..f118affbde7ff31eac3537e660a33d3dbed43f3c 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
@@ -34,10 +34,7 @@ func (s *ForgotPasswordService) Create(email string) {
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
dueTime := CalculateDueTime(remainingTime)
- token := uint(rand.IntN(1000000))
- for token < 1000000 {
- token = uint(rand.IntN(1000000))
- }
+ token := uint(rand.IntN(999999-100000) + 100000)
s.Constructor.UUID = uuid.NewV4()
s.Constructor.ExpiredAt = dueTime
s.Constructor.AccountID = accountRepo.Result.Id
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
index 42a6754ca09b41548528a2bcd7ef1d15362dc39b..4fe63453d49c45b5d8d4272de2f54f1184850b35 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
@@ -32,17 +32,34 @@ func (s *GoogleAuthService) Authenticate(isAgree bool) {
}
s.Constructor.UUID = uuid.NewV4()
s.Constructor.OauthProvider = "Google"
+ checkRegisteredEmail := repositories.GetAccountbyEmail(email.(string))
+ if !checkRegisteredEmail.NoRecord {
+ s.Exception.DataDuplicate = true
+ s.Exception.Message = "Account with email" + email.(string) + "already registered!"
+ return
+ }
+
createAccount := repositories.CreateAccount(models.Account{
UUID: uuid.NewV4(),
Email: email.(string),
IsEmailVerified: true,
})
+
s.Constructor.AccountID = createAccount.Result.Id
createGoogleAuth := repositories.CreateExternalAuth(s.Constructor)
- GoogleAuth.Result.AccountID = createGoogleAuth.Result.ID
+
+ GoogleAuth.Result.AccountID = createGoogleAuth.Result.AccountID
+ userProfile := UserProfileService{}
+ userProfile.Constructor.AccountID = GoogleAuth.Result.AccountID
+ userProfile.Create()
+ if userProfile.Error != nil {
+ s.Error = userProfile.Error
+ return
+ }
s.Error = createGoogleAuth.RowsError
s.Error = errors.Join(s.Error, createAccount.RowsError)
}
+
accountData := repositories.GetAccountById(GoogleAuth.Result.AccountID)
token, err_tok := GenerateToken(&accountData.Result)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/academy/academy_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/academy/academy_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..f4210c585c26c026eb45a44c42b059cb875a51c4
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/academy/academy_controller.go
@@ -0,0 +1,31 @@
+package academy
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func List(c *gin.Context) {
+ academy := services.AcademyService{}
+ academyController := controller.Controller[any, models.Academy, models.AllAcademyResponse]{
+ Service: &academy.Service,
+ }
+ academyController.Service.Constructor.Slug = c.Param("slug")
+ academy.Retrieve()
+ academyController.Response(c)
+
+}
+
+func Create(c *gin.Context) {
+ createAcademy := services.CreateAcademyService{}
+ academyController := controller.Controller[models.AllAcademyResponse, models.AllAcademyResponse, models.AllAcademyResponse]{
+ Service: &createAcademy.Service,
+ }
+ academyController.RequestJSON(c, func() {
+ academyController.Service.Constructor.Academies = academyController.Request.Academies
+ createAcademy.Create()
+ })
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
index c7d9163ce8ab396cd38c1317229e931e7da31765..96eabef126ed5779aa33b9f1c557192b3668689a 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -30,7 +30,7 @@ type AccountDetails struct {
LastEducation *string `json:"last_education"`
MaritalStatus *string `json:"marital_status"`
Avatar *string `json:"avatar"`
- PhoneNumber *uint `json:"phone_number"`
+ PhoneNumber *string `json:"phone_number"`
}
type EmailVerification struct {
@@ -85,12 +85,12 @@ type AcademyMaterial struct {
}
type AcademyContent struct {
- ID uint `gorm:"primaryKey" json:"id"`
- UUID uint `json:"uuid"`
- Title string `json:"title"`
- Order uint `json:"order"`
- AcademyMaterialID uint `json:"academy_material_id"`
- Description string `json:"description"`
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `json:"uuid"`
+ Title string `json:"title"`
+ Order uint `json:"order"`
+ AcademyMaterialID uint `json:"academy_material_id"`
+ Description string `json:"description"`
}
type OptionCategory struct {
ID uint `gorm:"primaryKey" json:"id"`
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/academy_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/academy_route.go
new file mode 100644
index 0000000000000000000000000000000000000000..d8be676cadb26d2e57fd285239a9c6c094b75480
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/academy_route.go
@@ -0,0 +1,16 @@
+package router
+
+import (
+ AcademyController "api.qobiltu.id/controller/academy"
+ "api.qobiltu.id/middleware"
+ "github.com/gin-gonic/gin"
+)
+
+func AcademyRoute(router *gin.Engine) {
+ routerGroup := router.Group("/api/v1/academy")
+ {
+ routerGroup.GET("/list/:slug", middleware.AuthUser, AcademyController.List)
+ routerGroup.GET("/list", middleware.AuthUser, AcademyController.List)
+ routerGroup.POST("/create", middleware.AuthUser, AcademyController.Create)
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
index 6987e5456230d0a920b4e44a77163fade344beb7..31b36acd86c58c477d59b9f8b7cbaf0bee703550 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
@@ -16,7 +16,7 @@ func StartService() {
UserRoute(router)
EmailRoute(router)
OptionsRoute(router)
-
+ AcademyRoute(router)
err := router.Run(config.TCP_ADDRESS)
if err != nil {
log.Fatalf("Failed to run server: %v", err)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
index ea6a81d13ba4bcbea9c844dd2156911555133bd2..d156bc532db21abb6f992626e82cc931648bf62c 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
@@ -29,7 +29,10 @@ func (s *EmailVerificationService) Create() {
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
dueTime := CalculateDueTime(remainingTime)
- token := uint(rand.IntN(100000))
+ token := uint(rand.IntN(1000000))
+ for token < 1000000 {
+ token = uint(rand.IntN(1000000))
+ }
s.Constructor.UUID = uuid.NewV4()
repo := repositories.CreateEmailVerification(s.Constructor.UUID, s.Constructor.AccountID, dueTime, token)
@@ -83,7 +86,7 @@ func (s *EmailVerificationService) Validate() {
}
account := repositories.GetAccountById(repo.Result.AccountID)
account.Result.IsEmailVerified = true
-
+
repositories.UpdateAccount(account.Result)
s.Result = repo.Result
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
index 72a976d610df12deaa49c571c86c47bf2d04e0ae..eee34890fbf3519de21c401fbdcbf6c60bebb852 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
@@ -31,12 +31,15 @@ type UserProfileResponse struct {
Details AccountDetails `json:"details"`
}
-type AkademiDasar struct {
- Academies Academy `json:"academy"`
- Materials []AcademyMaterial `json:"academy_material"`
- Contents []AcademyContent `json:"academy_content"`
+type AcademyMaterialResponse struct {
+ Materials AcademyMaterial
+ Contents []AcademyContent
+}
+type AcademyResponse struct {
+ Academy Academy `json:"academy"`
+ Materials []AcademyMaterialResponse `json:"academy_materials"`
}
-type AkademiDasarResponse struct {
- AkademiDasar []AkademiDasar `json:"academy_dasar"`
+type AllAcademyResponse struct {
+ Academies []AcademyResponse `json:"academy_dasar"`
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/academy_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/academy_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..2a9235d4343ea63f784fd65184f498dca8c88338
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/academy_repository.go
@@ -0,0 +1,73 @@
+package repositories
+
+import "api.qobiltu.id/models"
+
+func GetAllAcademy() Repository[models.Academy, []models.Academy] {
+ repo := Construct[models.Academy, []models.Academy](
+ models.Academy{},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.Academy, []models.Academy],
+ Find[models.Academy, []models.Academy],
+ )
+ return *repo
+}
+
+func GetAcademyDataBySlug(slug string) Repository[models.Academy, models.Academy] {
+ repo := Construct[models.Academy, models.Academy](
+ models.Academy{Slug: slug},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.Academy, models.Academy],
+ Find[models.Academy, models.Academy],
+ )
+ return *repo
+}
+
+func GetAllAcademyMaterialsByAcademyID(acaddemyId uint) Repository[models.AcademyMaterial, []models.AcademyMaterial] {
+ repo := Construct[models.AcademyMaterial, []models.AcademyMaterial](
+ models.AcademyMaterial{AcademyID: acaddemyId},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.AcademyMaterial, []models.AcademyMaterial],
+ Find[models.AcademyMaterial, []models.AcademyMaterial],
+ )
+ return *repo
+}
+
+func GetAllAcademyContentsByMaterialID(materialId uint) Repository[models.AcademyContent, []models.AcademyContent] {
+ repo := Construct[models.AcademyContent, []models.AcademyContent](
+ models.AcademyContent{AcademyMaterialID: materialId},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.AcademyContent, []models.AcademyContent],
+ Find[models.AcademyContent, []models.AcademyContent],
+ )
+ return *repo
+}
+
+func CreateAcademy(academies models.Academy) Repository[models.Academy, models.Academy] {
+ repo := Construct[models.Academy, models.Academy](
+ academies,
+ )
+
+ Create(repo)
+ return *repo
+}
+
+func CreateAcademyMaterial(academyMaterial models.AcademyMaterial) Repository[models.AcademyMaterial, models.AcademyMaterial] {
+ repo := Construct[models.AcademyMaterial, models.AcademyMaterial](
+ academyMaterial,
+ )
+
+ Create(repo)
+ return *repo
+}
+
+func CreateAcademyContent(academyContent models.AcademyContent) Repository[models.AcademyContent, models.AcademyContent] {
+ repo := Construct[models.AcademyContent, models.AcademyContent](
+ academyContent,
+ )
+ Create(repo)
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..dc76d9e7081bf732839969a398df2c16b756b906
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/academy_service.go
@@ -0,0 +1,104 @@
+package services
+
+import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+ "github.com/gosimple/slug"
+ uuid "github.com/satori/go.uuid"
+)
+
+type AcademyService struct {
+ Service[models.Academy, models.AllAcademyResponse]
+}
+
+type CreateAcademyService struct {
+ Service[models.AllAcademyResponse, models.AllAcademyResponse]
+}
+
+func castAcademyMaterials(academyId uint) []models.AcademyMaterialResponse {
+ var ArrMaterials []models.AcademyMaterialResponse
+ for _, academyMaterial := range repositories.GetAllAcademyMaterialsByAcademyID(academyId).Result {
+ ArrMaterials = append(ArrMaterials, models.AcademyMaterialResponse{
+ Materials: academyMaterial,
+ Contents: repositories.GetAllAcademyContentsByMaterialID(academyMaterial.ID).Result,
+ })
+ }
+ return ArrMaterials
+}
+func (s *AcademyService) Retrieve() {
+ if s.Constructor.Slug != "" {
+ AcademyRepo := repositories.GetAcademyDataBySlug(s.Constructor.Slug)
+ s.Error = AcademyRepo.RowsError
+ if AcademyRepo.NoRecord {
+ s.Exception.Message = "Academy not found"
+ s.Exception.DataNotFound = true
+ return
+ }
+
+ s.Result = models.AllAcademyResponse{
+ Academies: []models.AcademyResponse{
+ models.AcademyResponse{
+ Academy: AcademyRepo.Result,
+ Materials: castAcademyMaterials(s.Constructor.ID),
+ },
+ },
+ }
+ } else {
+ AcademyRepo := repositories.GetAllAcademy()
+ s.Error = AcademyRepo.RowsError
+ var ArrAcademy []models.AcademyResponse
+ for _, academy := range AcademyRepo.Result {
+ ArrAcademy = append(ArrAcademy, models.AcademyResponse{
+ Academy: academy,
+ Materials: castAcademyMaterials(academy.ID),
+ })
+ }
+ s.Result = models.AllAcademyResponse{
+ Academies: ArrAcademy,
+ }
+ }
+
+}
+
+func (s *CreateAcademyService) Create() {
+ var ArrAcademy []models.AcademyResponse
+ for _, academy := range s.Constructor.Academies {
+ academy.Academy.UUID = uuid.NewV4()
+ if academy.Academy.Slug == "" {
+ academy.Academy.Slug = slug.Make(academy.Academy.Title)
+ }
+
+ createdAcademy := repositories.CreateAcademy(academy.Academy)
+ if createdAcademy.RowsError != nil {
+ s.Error = createdAcademy.RowsError
+ return
+ }
+ for _, material := range academy.Materials {
+ material.Materials.AcademyID = createdAcademy.Result.ID
+ material.Materials.UUID = uuid.NewV4()
+ if material.Materials.Slug == "" {
+ material.Materials.Slug = slug.Make(material.Materials.Title)
+ }
+ createdMaterial := repositories.CreateAcademyMaterial(material.Materials)
+ if createdMaterial.RowsError != nil {
+ s.Error = createdMaterial.RowsError
+ return
+ }
+ for _, content := range material.Contents {
+ content.UUID = uuid.NewV4()
+ content.AcademyMaterialID = createdMaterial.Result.ID
+ createdContent := repositories.CreateAcademyContent(content)
+ if createdContent.RowsError != nil {
+ s.Error = createdContent.RowsError
+ return
+ }
+ ArrAcademy = append(ArrAcademy, models.AcademyResponse{
+ Academy: createdAcademy.Result,
+ Materials: castAcademyMaterials(createdAcademy.Result.ID),
+ })
+ }
+ }
+ }
+ s.Result = models.AllAcademyResponse{Academies: ArrAcademy}
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
index 22d723bd821f351074a1840afd7474338872a15d..42a6754ca09b41548528a2bcd7ef1d15362dc39b 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
@@ -33,6 +33,7 @@ func (s *GoogleAuthService) Authenticate(isAgree bool) {
s.Constructor.UUID = uuid.NewV4()
s.Constructor.OauthProvider = "Google"
createAccount := repositories.CreateAccount(models.Account{
+ UUID: uuid.NewV4(),
Email: email.(string),
IsEmailVerified: true,
})
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
index d4d232aeaed0f9cf844b708148c99b2c1e165a6a..473bcf17fa8d55afdc313a25659e29ddae6f7e16 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
@@ -34,7 +34,10 @@ func (s *ForgotPasswordService) Create(email string) {
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
dueTime := CalculateDueTime(remainingTime)
- token := uint(rand.IntN(100000))
+ token := uint(rand.IntN(1000000))
+ for token < 1000000 {
+ token = uint(rand.IntN(1000000))
+ }
s.Constructor.UUID = uuid.NewV4()
s.Constructor.ExpiredAt = dueTime
s.Constructor.AccountID = accountRepo.Result.Id
@@ -71,13 +74,6 @@ func (s *ForgotPasswordService) Create(email string) {
}
func (s *ForgotPasswordService) Validate(newPassword *string) {
- accountRepo := repositories.GetAccountById(s.Constructor.AccountID)
- if accountRepo.NoRecord {
- s.Error = accountRepo.RowsError
- s.Exception.DataNotFound = true
- s.Exception.Message = "There is no account data with given credentials!"
- return
- }
fgPasswordRepo := repositories.GetForgotPasswordByToken(s.Constructor.Token)
s.Error = fgPasswordRepo.RowsError
@@ -91,13 +87,24 @@ func (s *ForgotPasswordService) Validate(newPassword *string) {
s.Exception.Message = "Token has expired!"
return
}
+
+ accountRepo := repositories.GetAccountById(fgPasswordRepo.Result.AccountID)
+ if accountRepo.NoRecord {
+ s.Error = accountRepo.RowsError
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account data with given credentials!"
+ return
+ }
s.Result = fgPasswordRepo.Result
if newPassword == nil {
return
}
+ // fmt.Println("Previous Account", accountRepo.Result)
+ // fmt.Println("New password", *newPassword)
hashed_password, _ := HashPassword(*newPassword)
accountRepo.Result.Password = hashed_password
changePassword := repositories.UpdateAccount(accountRepo.Result)
+ // fmt.Println("New Account", changePassword.Result)
if changePassword.RowsError != nil {
s.Error = changePassword.RowsError
s.Exception.QueryError = true
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
index b2be1362b4b37f0263c167a1280104d999ea356d..aa7905ab737698db63d0a25f6ff4ca83c195e4e1 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
@@ -5,9 +5,11 @@ go 1.24.0
require (
github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.1
+ github.com/gosimple/slug v1.15.0
github.com/joho/godotenv v1.5.1
github.com/satori/go.uuid v1.2.0
golang.org/x/crypto v0.36.0
+ google.golang.org/api v0.228.0
gorm.io/driver/postgres v1.5.11
gorm.io/gorm v1.25.12
)
@@ -31,7 +33,6 @@ require (
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
- github.com/gosimple/slug v1.15.0 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
@@ -41,13 +42,11 @@ require (
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
- github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
- github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
@@ -56,13 +55,11 @@ require (
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
golang.org/x/arch v0.15.0 // indirect
- golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/oauth2 v0.28.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
- google.golang.org/api v0.228.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
google.golang.org/grpc v1.71.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
index 5497ec831debdb4d4cfb521ba2a52edced87edb2..4fac69c64291b273a4a6b4d6887c80b0f43f7bc3 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
@@ -12,7 +12,6 @@ github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFos
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -41,11 +40,15 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
@@ -74,8 +77,8 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
@@ -91,8 +94,7 @@ github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNH
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
-github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
@@ -114,20 +116,24 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
+go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
+go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
+go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
+go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
-golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
-golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
@@ -139,16 +145,14 @@ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
+golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs=
google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
-google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
-google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
index 3b90d6d4505bb2677ff4b44a9088e0a62cd656c8..72a976d610df12deaa49c571c86c47bf2d04e0ae 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
@@ -30,3 +30,13 @@ type UserProfileResponse struct {
Account Account `json:"account"`
Details AccountDetails `json:"details"`
}
+
+type AkademiDasar struct {
+ Academies Academy `json:"academy"`
+ Materials []AcademyMaterial `json:"academy_material"`
+ Contents []AcademyContent `json:"academy_content"`
+}
+
+type AkademiDasarResponse struct {
+ AkademiDasar []AkademiDasar `json:"academy_dasar"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_external_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_external_controller.go
index 06eecf8ab46e2c57ef61014182c930c738b9709d..f6ee46a8d353258cb4058b6ba1c9921eb0af79f3 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_external_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_external_controller.go
@@ -14,7 +14,7 @@ func ExternalAuth(c *gin.Context) {
GoogleLogin := services.GoogleAuthService{}
ExternalAuthController.Service = &GoogleLogin.Service
ExternalAuthController.Service.Constructor.OauthID = ExternalAuthController.Request.OauthID
- GoogleLogin.Authenticate()
+ GoogleLogin.Authenticate(ExternalAuthController.Request.IsAgreeTerms && !ExternalAuthController.Request.IsSexualDisease)
}
})
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_forgot_password_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_forgot_password_controller.go
index b963f60bb1154f2b2517d4793cb423a3aea3ece5..a1cbf45dac96d2e4d64dba1971b5ea678e2f52aa 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_forgot_password_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_forgot_password_controller.go
@@ -1 +1,29 @@
package auth
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func CreateForgotPassword(c *gin.Context) {
+ ForgotPassword := services.ForgotPasswordService{}
+ ForgotPasswordController := controller.Controller[models.ForgotPasswordRequest, models.ForgotPassword, models.ForgotPassword]{
+ Service: &ForgotPassword.Service,
+ }
+ ForgotPasswordController.RequestJSON(c, func() {
+ ForgotPassword.Create(ForgotPasswordController.Request.Email)
+ })
+
+}
+func ValidateForgotPassword(c *gin.Context) {
+ ForgotPassword := services.ForgotPasswordService{}
+ ForgotPasswordController := controller.Controller[models.ValidateForgotPasswordRequest, models.ForgotPassword, models.ForgotPassword]{
+ Service: &ForgotPassword.Service,
+ }
+ ForgotPasswordController.RequestJSON(c, func() {
+ ForgotPasswordController.Service.Constructor.Token = ForgotPasswordController.Request.Token
+ ForgotPassword.Validate(&ForgotPasswordController.Request.NewPassword)
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_validate_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_validate_controller.go
index f166c5d8df0164e6470a821bbee95e67a61f05d4..d22c912477bd131fb16529d0ffea44fed088e4f8 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_validate_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_validate_controller.go
@@ -16,7 +16,6 @@ func Verify(c *gin.Context) {
emailVerificationController.Service.Constructor.AccountID = uint(emailVerificationController.AccountData.UserID)
emailVerificationController.RequestJSON(c, func() {
emailVerificationController.Service.Constructor.Token = emailVerificationController.Request.Token
- emailVerificationController.Service.Constructor.UUID = emailVerificationController.Request.UUID
emailVerification.Validate()
})
})
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
index 249f6bfeb94001273154ad7cdc2aa8f68fa6a4ac..80e5e088cf171bff6841265d01caf14510b7ac53 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
@@ -1,7 +1,5 @@
package models
-import uuid "github.com/satori/go.uuid"
-
type LoginRequest struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required"`
@@ -20,8 +18,7 @@ type ChangePasswordRequest struct {
}
type CreateVerifyEmailRequest struct {
- Token uint `json:"token" binding:"required"`
- UUID uuid.UUID `json:"uuid" binding:"required"`
+ Token uint `json:"token" binding:"required"`
}
type OptionsRequest struct {
@@ -35,3 +32,11 @@ type ExternalAuthRequest struct {
IsAgreeTerms bool `json:"is_agree_terms"`
IsSexualDisease bool `json:"is_sexual_disease"`
}
+
+type ForgotPasswordRequest struct {
+ Email string `json:"email" binding:"required,email"`
+}
+type ValidateForgotPasswordRequest struct {
+ Token uint `json:"token" binding:"required"`
+ NewPassword string `json:"new_password"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
index 978e1e4c584390f8e8941e3b1c464b967cf8af3a..0cceb9094856d6284d6321676b170a1222201063 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
@@ -39,10 +39,8 @@ func UpdateAccount(account models.Account) Repository[models.Account, models.Acc
repo := Construct[models.Account, models.Account](
account,
)
- repo.Transactions(
- WhereGivenConstructor[models.Account, models.Account],
- Update[models.Account],
- )
+ repo.Transaction.Save(&repo.Constructor)
+ repo.Result = repo.Constructor
return *repo
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go
index 808b568cf02fd0137a46cb808a2c9102c636eccc..a30c769644383c52d4e234a9b1ba2d414dc443a4 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go
@@ -13,5 +13,7 @@ func AuthRoute(router *gin.Engine) {
routerGroup.POST("/login", AuthController.Login)
routerGroup.POST("/register", AuthController.Register)
routerGroup.PUT("/change-password", middleware.AuthUser, AuthController.ChangePassword)
+ routerGroup.POST("/forgot-password", AuthController.CreateForgotPassword)
+ routerGroup.PUT("/forgot-password", AuthController.ValidateForgotPassword)
}
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go
index 296771a68cedc99f869f398a50e56a4f0db5dbd8..8c507be6297b02accb45e0688f019d5dd97851ce 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go
@@ -58,7 +58,8 @@ func (s *AuthenticationService) Update(oldPassword string, newPassword string) {
s.Exception.Message = "incorrect old password!"
return
}
- accountData.Result.Password = newPassword
+ hashed_password, _ := HashPassword(newPassword)
+ accountData.Result.Password = hashed_password
changePassword := repositories.UpdateAccount(accountData.Result)
changePassword.Result.Password = "SECRET"
s.Result = models.AuthenticatedUser{
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
index 4876c4bc0bd36cebebee1f930a980a0e61d9f05b..ea6a81d13ba4bcbea9c844dd2156911555133bd2 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
@@ -61,6 +61,7 @@ func (s *EmailVerificationService) Create() {
return
}
}(accountRepo.Result.Email, token)
+ // s.Result.Token = 0
}
func (s *EmailVerificationService) Validate() {
@@ -80,8 +81,11 @@ func (s *EmailVerificationService) Validate() {
s.Delete()
return
}
+ account := repositories.GetAccountById(repo.Result.AccountID)
+ account.Result.IsEmailVerified = true
+
+ repositories.UpdateAccount(account.Result)
s.Result = repo.Result
- s.Delete()
}
func (s *EmailVerificationService) Delete() {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
index 9d575a2294842ae83d732e718e4b4c7540c3da7f..22d723bd821f351074a1840afd7474338872a15d 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
@@ -14,7 +14,7 @@ type GoogleAuthService struct {
Service[models.ExternalAuth, models.AuthenticatedUser]
}
-func (s *GoogleAuthService) Authenticate() {
+func (s *GoogleAuthService) Authenticate(isAgree bool) {
GoogleAuth := repositories.GetExternalAccountByOauthId(s.Constructor.OauthID)
payload, errGoogleAuth := idtoken.Validate(context.Background(), s.Constructor.OauthID, "")
s.Error = errGoogleAuth
@@ -25,6 +25,11 @@ func (s *GoogleAuthService) Authenticate() {
}
email := payload.Claims["email"]
if GoogleAuth.NoRecord {
+ if !isAgree {
+ s.Exception.BadRequest = true
+ s.Exception.Message = "Please agree to the terms and conditions to create an account"
+ return
+ }
s.Constructor.UUID = uuid.NewV4()
s.Constructor.OauthProvider = "Google"
createAccount := repositories.CreateAccount(models.Account{
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..d4d232aeaed0f9cf844b708148c99b2c1e165a6a
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/forgot_password_service.go
@@ -0,0 +1,109 @@
+package services
+
+import (
+ "fmt"
+ "log"
+ "math/rand/v2"
+ "net/smtp"
+ "time"
+
+ "api.qobiltu.id/config"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+ uuid "github.com/satori/go.uuid"
+)
+
+type ForgotPasswordService struct {
+ Service[models.ForgotPassword, models.ForgotPassword]
+}
+
+func (s *ForgotPasswordService) Create(email string) {
+ if email == "" {
+ s.Exception.BadRequest = true
+ s.Exception.Message = "Email is required!"
+ return
+ }
+ accountRepo := repositories.GetAccountbyEmail(email)
+ if accountRepo.NoRecord {
+ s.Error = accountRepo.RowsError
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account data with given credentials!"
+ return
+ }
+
+ remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
+ dueTime := CalculateDueTime(remainingTime)
+
+ token := uint(rand.IntN(100000))
+ s.Constructor.UUID = uuid.NewV4()
+ s.Constructor.ExpiredAt = dueTime
+ s.Constructor.AccountID = accountRepo.Result.Id
+ s.Constructor.Token = token
+ repo := repositories.CreateForgotPassword(s.Constructor)
+
+ s.Error = repo.RowsError
+ s.Result = repo.Result
+ // ⬇ Kirim token ke email user menggunakan SMTP
+ go func(toEmail string, token uint) {
+ from := config.SMTP_SENDER_EMAIL
+ password := config.SMTP_SENDER_PASSWORD
+ smtpHost := config.SMTP_HOST
+ smtpPort := config.SMTP_PORT
+
+ auth := smtp.PlainAuth("", from, password, smtpHost)
+
+ subject := "Forgot Password Token"
+ body := fmt.Sprintf("Your Forgot Password token is: %06d\nPlease use it before it expires.", token)
+
+ msg := []byte("To: " + toEmail + "\r\n" +
+ "Subject: " + subject + "\r\n" +
+ "\r\n" +
+ body + "\r\n")
+
+ err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, []string{toEmail}, msg)
+ if err != nil {
+ s.Error = err
+ log.Printf("Error sending verification email: %v", err)
+ return
+ }
+ }(accountRepo.Result.Email, token)
+ // s.Result.Token = 0
+}
+
+func (s *ForgotPasswordService) Validate(newPassword *string) {
+ accountRepo := repositories.GetAccountById(s.Constructor.AccountID)
+ if accountRepo.NoRecord {
+ s.Error = accountRepo.RowsError
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account data with given credentials!"
+ return
+ }
+
+ fgPasswordRepo := repositories.GetForgotPasswordByToken(s.Constructor.Token)
+ s.Error = fgPasswordRepo.RowsError
+ if fgPasswordRepo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no forgot password data with given credentials!"
+ return
+ }
+ if fgPasswordRepo.Result.ExpiredAt.Before(time.Now()) {
+ s.Exception.Unauthorized = true
+ s.Exception.Message = "Token has expired!"
+ return
+ }
+ s.Result = fgPasswordRepo.Result
+ if newPassword == nil {
+ return
+ }
+ hashed_password, _ := HashPassword(*newPassword)
+ accountRepo.Result.Password = hashed_password
+ changePassword := repositories.UpdateAccount(accountRepo.Result)
+ if changePassword.RowsError != nil {
+ s.Error = changePassword.RowsError
+ s.Exception.QueryError = true
+ s.Exception.Message = "Failed to update password!"
+ return
+ }
+ // fgPasswordRepo.Result.Token = 0
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
index 4308555dd3674be1068959b571bc80f0809ac623..c8803332f0467a840fdcb2099ac1785bde70a06d 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
@@ -69,6 +69,7 @@ func (s *UserProfileService) Retrieve() {
Account: repositories.GetAccountById(s.Constructor.AccountID).Result,
Details: userProfile.Result,
}
+ s.Result.Account.Password = "SECRET"
}
func (s *UserProfileService) Update() {
@@ -78,11 +79,14 @@ func (s *UserProfileService) Update() {
}
usersCount := repositories.GetAllAccount().RowsCount
var initialName string
- if *s.Constructor.Gender {
- initialName = "IKH_"
- } else {
- initialName = "AKH_"
+ if s.Constructor.Gender != nil {
+ if *s.Constructor.Gender {
+ initialName = "IKH_"
+ } else {
+ initialName = "AKH_"
+ }
}
+
initialName += strconv.Itoa(usersCount)
s.Constructor.InitialName = initialName
userProfile := repositories.UpdateAccountDetails(s.Constructor)
@@ -92,8 +96,19 @@ func (s *UserProfileService) Update() {
s.Exception.Message = "There is no account with given credentials!"
return
}
+ account := repositories.GetAccountById(s.Constructor.AccountID)
+ account.Result.IsDetailCompleted = (userProfile.Result.InitialName != "" &&
+ userProfile.Result.FullName != nil &&
+ userProfile.Result.DateOfBirth != nil &&
+ userProfile.Result.PlaceOfBirth != nil &&
+ userProfile.Result.Domicile != nil &&
+ userProfile.Result.LastJob != nil &&
+ userProfile.Result.Gender != nil &&
+ userProfile.Result.LastEducation != nil &&
+ userProfile.Result.MaritalStatus != nil)
+ repositories.UpdateAccount(account.Result)
s.Result = models.UserProfileResponse{
- Account: repositories.GetAccountById(s.Constructor.AccountID).Result,
+ Account: account.Result,
Details: userProfile.Result,
}
s.Result.Account.Password = "SECRET"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
index 5255a607afb53c6f64e4c590f5b7723b4806973f..4876c4bc0bd36cebebee1f930a980a0e61d9f05b 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
@@ -1,8 +1,8 @@
package services
import (
- "crypto/tls"
"fmt"
+ "log"
"math/rand/v2"
"net/smtp"
"time"
@@ -44,71 +44,22 @@ func (s *EmailVerificationService) Create() {
smtpHost := config.SMTP_HOST
smtpPort := config.SMTP_PORT
- tlsconfig := &tls.Config{
- InsecureSkipVerify: true,
- ServerName: smtpHost,
- }
-
- conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", smtpHost, smtpPort), tlsconfig)
- if err != nil {
- panic(err)
- }
-
- client, err := smtp.NewClient(conn, smtpHost)
- if err != nil {
- panic(err)
- }
-
- // auth := smtp.PlainAuth("", from, password, smtpHost)
auth := smtp.PlainAuth("", from, password, smtpHost)
- if err = client.Auth(auth); err != nil {
- panic(err)
- }
- if err = client.Mail(from); err != nil {
- panic(err)
- }
+ subject := "Email Verification Token"
+ body := fmt.Sprintf("Your verification token is: %06d\nPlease use it before it expires.", token)
- to := []string{toEmail}
- for _, addr := range to {
- if err = client.Rcpt(addr); err != nil {
- panic(err)
- }
- }
+ msg := []byte("To: " + toEmail + "\r\n" +
+ "Subject: " + subject + "\r\n" +
+ "\r\n" +
+ body + "\r\n")
- wc, err := client.Data()
+ err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, []string{toEmail}, msg)
if err != nil {
- panic(err)
+ s.Error = err
+ log.Printf("Error sending verification email: %v", err)
+ return
}
-
- msg := []byte("Subject: Test Email\r\n\r\nThis is the email body.")
- _, err = wc.Write(msg)
- if err != nil {
- panic(err)
- }
-
- err = wc.Close()
- if err != nil {
- panic(err)
- }
-
- client.Quit()
- fmt.Println("Email sent successfully!")
-
- // subject := "Email Verification Token"
- // body := fmt.Sprintf("Your verification token is: %06d\nPlease use it before it expires.", token)
-
- // msg := []byte("To: " + toEmail + "\r\n" +
- // "Subject: " + subject + "\r\n" +
- // "\r\n" +
- // body + "\r\n")
-
- // err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, []string{toEmail}, msg)
- // if err != nil {
- // s.Error = err
- // log.Printf("Error sending verification email: %v", err)
- // return
- // }
}(accountRepo.Result.Email, token)
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
index 4876c4bc0bd36cebebee1f930a980a0e61d9f05b..5255a607afb53c6f64e4c590f5b7723b4806973f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
@@ -1,8 +1,8 @@
package services
import (
+ "crypto/tls"
"fmt"
- "log"
"math/rand/v2"
"net/smtp"
"time"
@@ -44,22 +44,71 @@ func (s *EmailVerificationService) Create() {
smtpHost := config.SMTP_HOST
smtpPort := config.SMTP_PORT
+ tlsconfig := &tls.Config{
+ InsecureSkipVerify: true,
+ ServerName: smtpHost,
+ }
+
+ conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", smtpHost, smtpPort), tlsconfig)
+ if err != nil {
+ panic(err)
+ }
+
+ client, err := smtp.NewClient(conn, smtpHost)
+ if err != nil {
+ panic(err)
+ }
+
+ // auth := smtp.PlainAuth("", from, password, smtpHost)
auth := smtp.PlainAuth("", from, password, smtpHost)
+ if err = client.Auth(auth); err != nil {
+ panic(err)
+ }
- subject := "Email Verification Token"
- body := fmt.Sprintf("Your verification token is: %06d\nPlease use it before it expires.", token)
+ if err = client.Mail(from); err != nil {
+ panic(err)
+ }
- msg := []byte("To: " + toEmail + "\r\n" +
- "Subject: " + subject + "\r\n" +
- "\r\n" +
- body + "\r\n")
+ to := []string{toEmail}
+ for _, addr := range to {
+ if err = client.Rcpt(addr); err != nil {
+ panic(err)
+ }
+ }
- err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, []string{toEmail}, msg)
+ wc, err := client.Data()
if err != nil {
- s.Error = err
- log.Printf("Error sending verification email: %v", err)
- return
+ panic(err)
}
+
+ msg := []byte("Subject: Test Email\r\n\r\nThis is the email body.")
+ _, err = wc.Write(msg)
+ if err != nil {
+ panic(err)
+ }
+
+ err = wc.Close()
+ if err != nil {
+ panic(err)
+ }
+
+ client.Quit()
+ fmt.Println("Email sent successfully!")
+
+ // subject := "Email Verification Token"
+ // body := fmt.Sprintf("Your verification token is: %06d\nPlease use it before it expires.", token)
+
+ // msg := []byte("To: " + toEmail + "\r\n" +
+ // "Subject: " + subject + "\r\n" +
+ // "\r\n" +
+ // body + "\r\n")
+
+ // err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, []string{toEmail}, msg)
+ // if err != nil {
+ // s.Error = err
+ // log.Printf("Error sending verification email: %v", err)
+ // return
+ // }
}(accountRepo.Result.Email, token)
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
index d59213670e8b21d29270349fa9c598ccb2207bed..249f6bfeb94001273154ad7cdc2aa8f68fa6a4ac 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
@@ -30,6 +30,8 @@ type OptionsRequest struct {
}
type ExternalAuthRequest struct {
- OauthID string `json:"oauth_id" binding:"required"`
- OauthProvider string `json:"oauth_provider" binding:"required"`
+ OauthID string `json:"oauth_id" binding:"required"`
+ OauthProvider string `json:"oauth_provider" binding:"required"`
+ IsAgreeTerms bool `json:"is_agree_terms"`
+ IsSexualDisease bool `json:"is_sexual_disease"`
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go
index 8b3474fa876993fe8a09fde9a241c8a54df5feee..296771a68cedc99f869f398a50e56a4f0db5dbd8 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go
@@ -18,6 +18,7 @@ func (s *AuthenticationService) Authenticate() {
s.Exception.Message = "there is no account with given credentials!"
return
}
+
if VerifyPassword(accountData.Result.Password, s.Constructor.Password) != nil {
s.Exception.Unauthorized = true
s.Exception.Message = "incorrect password!"
@@ -28,6 +29,7 @@ func (s *AuthenticationService) Authenticate() {
if err_tok != nil {
s.Error = errors.Join(s.Error, err_tok)
+ return
}
accountData.Result.Password = "SECRET"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
index c0eaba5711783161d526de88c0090a594bacfda2..4876c4bc0bd36cebebee1f930a980a0e61d9f05b 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
@@ -56,7 +56,9 @@ func (s *EmailVerificationService) Create() {
err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, []string{toEmail}, msg)
if err != nil {
+ s.Error = err
log.Printf("Error sending verification email: %v", err)
+ return
}
}(accountRepo.Result.Email, token)
}
@@ -75,9 +77,11 @@ func (s *EmailVerificationService) Validate() {
s.Exception.Unauthorized = true
s.Exception.Message = "Token has expired!"
repositories.UpdateExpiredEmailVerification(s.Constructor.UUID)
+ s.Delete()
return
}
s.Result = repo.Result
+ s.Delete()
}
func (s *EmailVerificationService) Delete() {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
index 66e673f5e3830b2e1ffd5515ee1d9861675572a9..4308555dd3674be1068959b571bc80f0809ac623 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
@@ -2,6 +2,7 @@ package services
import (
"regexp"
+ "strconv"
"strings"
"api.qobiltu.id/models"
@@ -71,8 +72,10 @@ func (s *UserProfileService) Retrieve() {
}
func (s *UserProfileService) Update() {
- phoneNumber := *s.Constructor.PhoneNumber
- *s.Constructor.PhoneNumber = SanitizePhoneNumber(phoneNumber)
+ if s.Constructor.PhoneNumber != nil {
+ phoneNumber := *s.Constructor.PhoneNumber
+ *s.Constructor.PhoneNumber = SanitizePhoneNumber(phoneNumber)
+ }
usersCount := repositories.GetAllAccount().RowsCount
var initialName string
if *s.Constructor.Gender {
@@ -80,7 +83,8 @@ func (s *UserProfileService) Update() {
} else {
initialName = "AKH_"
}
- initialName += string(usersCount)
+ initialName += strconv.Itoa(usersCount)
+ s.Constructor.InitialName = initialName
userProfile := repositories.UpdateAccountDetails(s.Constructor)
s.Error = userProfile.RowsError
if userProfile.NoRecord {
@@ -92,4 +96,5 @@ func (s *UserProfileService) Update() {
Account: repositories.GetAccountById(s.Constructor.AccountID).Result,
Details: userProfile.Result,
}
+ s.Result.Account.Password = "SECRET"
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
index 8aad7510cf6ff2f1146de5a8da0d0f61debe7cb6..978e1e4c584390f8e8941e3b1c464b967cf8af3a 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
@@ -15,6 +15,15 @@ func GetAccountbyEmail(email string) Repository[models.Account, models.Account]
return *repo
}
+func GetAllAccount() Repository[models.Account, []models.Account] {
+ repo := Construct[models.Account, []models.Account](
+ models.Account{},
+ )
+ repo.Transactions(
+ Find[models.Account, []models.Account],
+ )
+ return *repo
+}
func GetAccountById(accountId uint) Repository[models.Account, models.Account] {
repo := Construct[models.Account, models.Account](
models.Account{Id: accountId},
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
index 3145d18c671432bfa78ef892b911424476cf79c4..9d575a2294842ae83d732e718e4b4c7540c3da7f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
@@ -33,7 +33,7 @@ func (s *GoogleAuthService) Authenticate() {
})
s.Constructor.AccountID = createAccount.Result.Id
createGoogleAuth := repositories.CreateExternalAuth(s.Constructor)
-
+ GoogleAuth.Result.AccountID = createGoogleAuth.Result.ID
s.Error = createGoogleAuth.RowsError
s.Error = errors.Join(s.Error, createAccount.RowsError)
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml
index a821c467525af186355b5837f8673e77a84f7563..5d48addfd86709968791ce0c398bd0a4cb75e2ec 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml
@@ -24,7 +24,7 @@ services:
ports:
- "5432:5432"
volumes:
- - db-data:/var/lib/postgresql/data
+ - /home/qobiltu/postgres-data:/var/lib/postgresql/data
restart: always
volumes:
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
index 180bb9b399fa74ab1db9b164f72e1b699481467c..66e673f5e3830b2e1ffd5515ee1d9861675572a9 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
@@ -44,8 +44,6 @@ func SanitizePhoneNumber(input string) string {
return input
}
func (s *UserProfileService) Create() {
- phoneNumber := *s.Constructor.PhoneNumber
- *s.Constructor.PhoneNumber = SanitizePhoneNumber(phoneNumber)
userProfile := repositories.CreateAccountDetails(s.Constructor)
s.Error = userProfile.RowsError
if userProfile.NoRecord {
@@ -75,6 +73,14 @@ func (s *UserProfileService) Retrieve() {
func (s *UserProfileService) Update() {
phoneNumber := *s.Constructor.PhoneNumber
*s.Constructor.PhoneNumber = SanitizePhoneNumber(phoneNumber)
+ usersCount := repositories.GetAllAccount().RowsCount
+ var initialName string
+ if *s.Constructor.Gender {
+ initialName = "IKH_"
+ } else {
+ initialName = "AKH_"
+ }
+ initialName += string(usersCount)
userProfile := repositories.UpdateAccountDetails(s.Constructor)
s.Error = userProfile.RowsError
if userProfile.NoRecord {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
index d7d9de6f6ae86a458866ad773e9886db45197e9b..8483ea7af8b5c6267fb167d8dd7b84354ceed129 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
@@ -28,6 +28,9 @@ WORKDIR /app
# Copy hasil build dari builder ke image runtime
COPY --from=builder /app/main .
+# Copy folder utils (termasuk file seeder) dari builder ke runtime
+COPY --from=builder /app/utils ./utils
+
# Copy file .env untuk konfigurasi environment
COPY .env .env
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go
index f30ee7177fa8bd15be000bcfe941ceb2732a74c1..08f850c1f5991e30eabd8d66d364110d83dcfd75 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go
@@ -9,7 +9,7 @@ import (
func Profile(c *gin.Context) {
userProfile := services.UserProfileService{}
- userProfileController := controller.Controller[any, models.AccountDetails, models.AccountDetails]{
+ userProfileController := controller.Controller[any, models.AccountDetails, models.UserProfileResponse]{
Service: &userProfile.Service,
}
userProfileController.HeaderParse(c, func() {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
index ec2b5ab25518d1aa9221afe7b07527ba298b14bc..058d97e7fea905feb469147c880d9141c25e0de3 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
@@ -1,8 +1,6 @@
package user
import (
- "fmt"
-
"api.qobiltu.id/controller"
"api.qobiltu.id/models"
"api.qobiltu.id/services"
@@ -11,7 +9,7 @@ import (
func UpdateProfile(c *gin.Context) {
userProfile := services.UserProfileService{}
- userUpdateProfileController := controller.Controller[models.AccountDetails, models.AccountDetails, models.AccountDetails]{
+ userUpdateProfileController := controller.Controller[models.AccountDetails, models.AccountDetails, models.UserProfileResponse]{
Service: &userProfile.Service,
}
@@ -19,7 +17,7 @@ func UpdateProfile(c *gin.Context) {
userUpdateProfileController.Service.Constructor = userUpdateProfileController.Request
userUpdateProfileController.HeaderParse(c, func() {
userUpdateProfileController.Service.Constructor.AccountID = uint(userUpdateProfileController.AccountData.UserID)
- fmt.Println("Account ID:", userUpdateProfileController.Service.Constructor.AccountID)
+
})
userProfile.Update()
},
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/forgot_password_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/forgot_password_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..90c293823afee7c5aaf1583cf16bbd44620894fb
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/forgot_password_repository.go
@@ -0,0 +1,22 @@
+package repositories
+
+import "api.qobiltu.id/models"
+
+func CreateForgotPassword(forgotPassword models.ForgotPassword) Repository[models.ForgotPassword, models.ForgotPassword] {
+ repo := Construct[models.ForgotPassword, models.ForgotPassword](
+ forgotPassword,
+ )
+ Create(repo)
+ return *repo
+}
+
+func GetForgotPasswordByToken(token uint) Repository[models.ForgotPassword, models.ForgotPassword] {
+ repo := Construct[models.ForgotPassword, models.ForgotPassword](
+ models.ForgotPassword{Token: token},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.ForgotPassword, models.ForgotPassword],
+ Find[models.ForgotPassword, models.ForgotPassword],
+ )
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
index 4807849046127c92c0dfdc98d156e58cc9403492..180bb9b399fa74ab1db9b164f72e1b699481467c 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
@@ -1,17 +1,51 @@
package services
import (
- "fmt"
+ "regexp"
+ "strings"
"api.qobiltu.id/models"
"api.qobiltu.id/repositories"
)
type UserProfileService struct {
- Service[models.AccountDetails, models.AccountDetails]
+ Service[models.AccountDetails, models.UserProfileResponse]
}
+// SanitizePhoneNumber membersihkan dan menormalkan nomor telepon ke format +62
+func SanitizePhoneNumber(input string) string {
+ // Hilangkan semua spasi dan strip
+ input = strings.ReplaceAll(input, " ", "")
+ input = strings.ReplaceAll(input, "-", "")
+ input = strings.ReplaceAll(input, "(", "")
+ input = strings.ReplaceAll(input, ")", "")
+
+ // Hilangkan semua karakter non-digit kecuali +
+ re := regexp.MustCompile(`[^0-9\+]`)
+ input = re.ReplaceAllString(input, "")
+
+ // Handle nomor diawali 0 (contoh: 0812...) menjadi +62812...
+ if strings.HasPrefix(input, "0") {
+ input = "+62" + input[1:]
+ }
+
+ // Handle jika diawali dengan 62 tanpa + (contoh: 62812...)
+ if strings.HasPrefix(input, "62") && !strings.HasPrefix(input, "+62") {
+ input = "+" + input
+ }
+
+ // Handle jika tidak ada awalan +62 sama sekali (contoh: 8123456789)
+ if !strings.HasPrefix(input, "+62") {
+ if strings.HasPrefix(input, "8") {
+ input = "+62" + input
+ }
+ }
+
+ return input
+}
func (s *UserProfileService) Create() {
+ phoneNumber := *s.Constructor.PhoneNumber
+ *s.Constructor.PhoneNumber = SanitizePhoneNumber(phoneNumber)
userProfile := repositories.CreateAccountDetails(s.Constructor)
s.Error = userProfile.RowsError
if userProfile.NoRecord {
@@ -19,7 +53,10 @@ func (s *UserProfileService) Create() {
s.Exception.Message = "There is no account with given credentials!"
return
}
- s.Result = userProfile.Result
+ s.Result = models.UserProfileResponse{
+ Account: repositories.GetAccountById(s.Constructor.AccountID).Result,
+ Details: userProfile.Result,
+ }
}
func (s *UserProfileService) Retrieve() {
userProfile := repositories.GetDetailAccountById(s.Constructor.AccountID)
@@ -29,11 +66,15 @@ func (s *UserProfileService) Retrieve() {
s.Exception.Message = "There is no account with given credentials!"
return
}
- s.Result = userProfile.Result
+ s.Result = models.UserProfileResponse{
+ Account: repositories.GetAccountById(s.Constructor.AccountID).Result,
+ Details: userProfile.Result,
+ }
}
func (s *UserProfileService) Update() {
- fmt.Println("Account ID:", s.Constructor.AccountID)
+ phoneNumber := *s.Constructor.PhoneNumber
+ *s.Constructor.PhoneNumber = SanitizePhoneNumber(phoneNumber)
userProfile := repositories.UpdateAccountDetails(s.Constructor)
s.Error = userProfile.RowsError
if userProfile.NoRecord {
@@ -41,5 +82,8 @@ func (s *UserProfileService) Update() {
s.Exception.Message = "There is no account with given credentials!"
return
}
- s.Result = userProfile.Result
+ s.Result = models.UserProfileResponse{
+ Account: repositories.GetAccountById(s.Constructor.AccountID).Result,
+ Details: userProfile.Result,
+ }
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
index df8cf1c15e2917803db7f00fbc386495fe8f5479..0a375ebb173c72b4a9eea6189d87c76acf362583 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
@@ -1,5 +1,7 @@
.env
vendor/
quzuu-be.exe
-README.md
-.qodo
+README.md
+.qodo
+.error
+logs/
\ No newline at end of file
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
index 823888a64fd867db2cee438c8cb64f792ec16920..55487887878907689a66b9e5897895013b1b708f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
@@ -9,10 +9,16 @@ import (
var TCP_ADDRESS string
var LOG_PATH string
+
var HOST_ADDRESS string
var HOST_PORT string
var EMAIL_VERIFICATION_DURATION int
+var SMTP_SENDER_EMAIL string
+var SMTP_SENDER_PASSWORD string
+var SMTP_HOST string
+var SMTP_PORT string
+
func init() {
godotenv.Load()
HOST_ADDRESS = os.Getenv("HOST_ADDRESS")
@@ -20,5 +26,9 @@ func init() {
TCP_ADDRESS = HOST_ADDRESS + ":" + HOST_PORT
LOG_PATH = os.Getenv("LOG_PATH")
EMAIL_VERIFICATION_DURATION, _ = strconv.Atoi(os.Getenv("EMAIL_VERIFICATION_DURATION"))
+ SMTP_SENDER_EMAIL = os.Getenv("SMTP_SENDER_EMAIL")
+ SMTP_SENDER_PASSWORD = os.Getenv("SMTP_SENDER_PASSWORD")
+ SMTP_HOST = os.Getenv("SMTP_HOST")
+ SMTP_PORT = os.Getenv("SMTP_PORT")
// Menampilkan nilai variabel lingkungan
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
index 3eea63fb7b3d4ffd51970404cadf1af8267c046d..0588c0a120d8455f8cdb8c998042a5815af9e48e 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
@@ -60,6 +60,10 @@ func AutoMigrateAll(db *gorm.DB) {
&models.AcademyContent{},
&models.AcademyMaterialProgress{},
&models.AcademyContentProgress{},
+ &models.RegionCity{},
+ &models.RegionProvince{},
+ &models.OptionCategory{},
+ &models.OptionValues{},
)
if err != nil {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_external_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_external_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..06eecf8ab46e2c57ef61014182c930c738b9709d
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_external_controller.go
@@ -0,0 +1,20 @@
+package auth
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func ExternalAuth(c *gin.Context) {
+ ExternalAuthController := controller.Controller[models.ExternalAuthRequest, models.ExternalAuth, models.AuthenticatedUser]{}
+ ExternalAuthController.RequestJSON(c, func() {
+ if ExternalAuthController.Request.OauthProvider == "google" {
+ GoogleLogin := services.GoogleAuthService{}
+ ExternalAuthController.Service = &GoogleLogin.Service
+ ExternalAuthController.Service.Constructor.OauthID = ExternalAuthController.Request.OauthID
+ GoogleLogin.Authenticate()
+ }
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_forgot_password_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_forgot_password_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..b963f60bb1154f2b2517d4793cb423a3aea3ece5
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_forgot_password_controller.go
@@ -0,0 +1 @@
+package auth
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..75d6a1a52383fac94e73c477dd29bda62624f31b
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification_controller.go
@@ -0,0 +1,21 @@
+package controller
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func CreateVerification(c *gin.Context) {
+ emailVerification := services.EmailVerificationService{}
+ emailVerificationController := controller.Controller[models.CreateVerifyEmailRequest, models.EmailVerification, models.EmailVerification]{
+ Service: &emailVerification.Service,
+ }
+ emailVerificationController.HeaderParse(c, func() {
+ emailVerificationController.Service.Constructor.AccountID = uint(emailVerificationController.AccountData.UserID)
+ emailVerification.Create()
+ emailVerificationController.Response(c)
+ })
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_validate_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_validate_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..f166c5d8df0164e6470a821bbee95e67a61f05d4
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_validate_controller.go
@@ -0,0 +1,24 @@
+package controller
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Verify(c *gin.Context) {
+ emailVerification := services.EmailVerificationService{}
+ emailVerificationController := controller.Controller[models.CreateVerifyEmailRequest, models.EmailVerification, models.EmailVerification]{
+ Service: &emailVerification.Service,
+ }
+ emailVerificationController.HeaderParse(c, func() {
+ emailVerificationController.Service.Constructor.AccountID = uint(emailVerificationController.AccountData.UserID)
+ emailVerificationController.RequestJSON(c, func() {
+ emailVerificationController.Service.Constructor.Token = emailVerificationController.Request.Token
+ emailVerificationController.Service.Constructor.UUID = emailVerificationController.Request.UUID
+ emailVerification.Validate()
+ })
+ })
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/options/option_category_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/options/option_category_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..c2824888af9eec3dca62f9f7690cfe3579fda1e5
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/options/option_category_controller.go
@@ -0,0 +1,19 @@
+package options
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func AddOptions(c *gin.Context) {
+ options := services.OptionService{}
+ addOptionController := controller.Controller[[]models.OptionsRequest, []models.OptionsRequest, models.OptionsResponse]{
+ Service: &options.Service,
+ }
+ addOptionController.RequestJSON(c, func() {
+ options.Constructor = addOptionController.Request
+ options.Create()
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/options/option_value_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/options/option_value_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..116f380726839b5629c00a46e0c31c484ba0d8f1
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/options/option_value_controller.go
@@ -0,0 +1,19 @@
+package options
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func List(c *gin.Context) {
+ options := services.OptionValueService{}
+ optionValueController := controller.Controller[any, models.OptionCategory, models.Options]{
+ Service: &options.Service,
+ }
+ slug := c.Param("slug")
+ options.Constructor.OptionSlug = slug
+ options.Retrieve()
+ optionValueController.Response(c)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/region/city/city_list_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/region/city/city_list_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..48e0158ef229664f16bff23f593355ec39d964c9
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/region/city/city_list_controller.go
@@ -0,0 +1,21 @@
+package city
+
+import (
+ "strconv"
+
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func List(c *gin.Context) {
+ city := services.CityService{}
+ CityController := controller.Controller[any, models.RegionCity, []models.RegionCity]{
+ Service: &city.Service,
+ }
+ ProvinceID, _ := strconv.Atoi(c.Query("province_id"))
+ city.Constructor.ProvinceID = uint(ProvinceID)
+ city.Retrieve()
+ CityController.Response(c)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/region/city/city_seeds_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/region/city/city_seeds_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..08f0b8ae4ed1faa1ebf0c3442f480bb5879aa3a5
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/region/city/city_seeds_controller.go
@@ -0,0 +1,17 @@
+package city
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Seeds(c *gin.Context) {
+ city := services.CityService{}
+ CityController := controller.Controller[any, models.RegionCity, []models.RegionCity]{
+ Service: &city.Service,
+ }
+ city.Create()
+ CityController.Response(c)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/region/province/province_list_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/region/province/province_list_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..5976fb0144da4ab9de16d8ccf80cff47ad81c78b
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/region/province/province_list_controller.go
@@ -0,0 +1,17 @@
+package province
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func List(c *gin.Context) {
+ province := services.ProvinceService{}
+ ProvinceController := controller.Controller[any, models.RegionProvince, []models.RegionProvince]{
+ Service: &province.Service,
+ }
+ province.Retrieve()
+ ProvinceController.Response(c)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/region/province/province_seeds_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/region/province/province_seeds_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..b1e6b630f63571e59d2ef9b1a1c12c0267b6004c
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/region/province/province_seeds_controller.go
@@ -0,0 +1,17 @@
+package province
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Seeds(c *gin.Context) {
+ province := services.ProvinceService{}
+ ProvinceController := controller.Controller[any, models.RegionProvince, []models.RegionProvince]{
+ Service: &province.Service,
+ }
+ province.Create()
+ ProvinceController.Response(c)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
index 405345ca21ac9dd77eec5b3d4e48775287157552..b2be1362b4b37f0263c167a1280104d999ea356d 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
@@ -13,15 +13,26 @@ require (
)
require (
+ cloud.google.com/go/auth v0.15.0 // indirect
+ cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
+ cloud.google.com/go/compute/metadata v0.6.0 // indirect
github.com/bytedance/sonic v1.13.1 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.25.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
+ github.com/google/s2a-go v0.1.9 // indirect
+ github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
+ github.com/googleapis/gax-go/v2 v2.14.1 // indirect
+ github.com/gosimple/slug v1.15.0 // indirect
+ github.com/gosimple/unidecode v1.0.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.2 // indirect
@@ -36,14 +47,24 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
- github.com/rogpeppe/go-internal v1.11.0 // indirect
+ github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
+ go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
+ go.opentelemetry.io/otel v1.34.0 // indirect
+ go.opentelemetry.io/otel/metric v1.34.0 // indirect
+ go.opentelemetry.io/otel/trace v1.34.0 // indirect
golang.org/x/arch v0.15.0 // indirect
+ golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/net v0.37.0 // indirect
+ golang.org/x/oauth2 v0.28.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
- google.golang.org/protobuf v1.36.5 // indirect
+ google.golang.org/api v0.228.0 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
+ google.golang.org/grpc v1.71.0 // indirect
+ google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
index fc9ac2ce0112e916606dc13ba95b03d9c01f9373..5497ec831debdb4d4cfb521ba2a52edced87edb2 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
@@ -1,3 +1,9 @@
+cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
+cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
+cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
+cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
+cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
+cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
@@ -10,12 +16,19 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -31,6 +44,16 @@ github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
+github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
+github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
+github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
+github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
+github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
+github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
+github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
+github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
+github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -70,6 +93,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -88,12 +112,26 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
+go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
+go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
+go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
+go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
+go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
+go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
+golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
+golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -103,8 +141,16 @@ golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs=
+google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
+google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
+google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
index e413a98e080bcf0700af246cd083a9337263dba1..7a06a07a9c505e034cba8020776da4eb84afddaa 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
@@ -13,11 +13,8 @@ func AuthUser(c *gin.Context) {
var currAccData models.AccountData
if c.Request.Header["Authorization"] != nil {
token := c.Request.Header["Authorization"]
- // fmt.Println("Authorization :", token)
currAccData.UserID, currAccData.VerifyStatus, currAccData.ErrVerif = services.VerifyToken(token[0])
- // fmt.Println("Verify Status :", currAccData.VerifyStatus)
- // fmt.Println("Verify UserID :", currAccData.UserID)
if currAccData.VerifyStatus == "invalid-token" || currAccData.VerifyStatus == "expired" {
currAccData.UserID = 0
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
index dfb4bfd35cd08440c3d106dd517a1821d90460c8..d59213670e8b21d29270349fa9c598ccb2207bed 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
@@ -1,5 +1,7 @@
package models
+import uuid "github.com/satori/go.uuid"
+
type LoginRequest struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required"`
@@ -12,11 +14,22 @@ type RegisterRequest struct {
Password string `json:"password" binding:"required"`
}
-type CreateEmailVerificationRequest struct {
- AccountID int `json:"account_id" binding:"required"`
-}
-
type ChangePasswordRequest struct {
OldPassword string `json:"old_password" binding:"required" `
NewPassword string `json:"new_password" binding:"required" `
}
+
+type CreateVerifyEmailRequest struct {
+ Token uint `json:"token" binding:"required"`
+ UUID uuid.UUID `json:"uuid" binding:"required"`
+}
+
+type OptionsRequest struct {
+ OptionName string `json:"option_name" binding:"required"`
+ OptionValue []string `json:"option_values" binding:"required"`
+}
+
+type ExternalAuthRequest struct {
+ OauthID string `json:"oauth_id" binding:"required"`
+ OauthProvider string `json:"oauth_provider" binding:"required"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
index 7e049aac3f7c5a196f9a54ce924b5424f5986553..3b90d6d4505bb2677ff4b44a9088e0a62cd656c8 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
@@ -18,3 +18,15 @@ type AuthenticatedUser struct {
Account Account `json:"account"`
Token string `json:"token"`
}
+type Options struct {
+ OptionCategory OptionCategory `json:"option_category"`
+ OptionValues []OptionValues `json:"option_values"`
+}
+type OptionsResponse struct {
+ Options []Options `json:"options"`
+}
+
+type UserProfileResponse struct {
+ Account Account `json:"account"`
+ Details AccountDetails `json:"details"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/email_verification_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/email_verification_repository.go
index 0072ea0952f85bc719bf4bd0d1cf90e6a7270870..f41d8f97fa93a536f7ae1c523fd2c0163a0ed00a 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/email_verification_repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/email_verification_repository.go
@@ -4,15 +4,17 @@ import (
"time"
"api.qobiltu.id/models"
+ uuid "github.com/satori/go.uuid"
)
-func CreateEmailVerification(accountId uint, dueTime time.Time, token uint) Repository[models.EmailVerification, models.EmailVerification] {
+func CreateEmailVerification(uuid uuid.UUID, accountId uint, dueTime time.Time, token uint) Repository[models.EmailVerification, models.EmailVerification] {
repo := Construct[models.EmailVerification, models.EmailVerification](
models.EmailVerification{
AccountID: accountId,
IsExpired: false,
ExpiredAt: dueTime,
Token: token,
+ UUID: uuid,
},
)
Create(repo)
@@ -34,6 +36,18 @@ func GetEmailVerification(accountId uint, token uint) Repository[models.EmailVer
return *repo
}
+func UpdateExpiredEmailVerification(uuid uuid.UUID) Repository[models.EmailVerification, models.EmailVerification] {
+ repo := Construct[models.EmailVerification, models.EmailVerification](
+ models.EmailVerification{UUID: uuid},
+ )
+
+ repo.Transaction.Where("UUID = ?", uuid).First(&repo.Constructor)
+ repo.Constructor.IsExpired = true
+ repo.Transaction.Updates(repo.Constructor)
+ repo.Result = repo.Constructor
+ return *repo
+}
+
func DeleteEmailVerification(token uint) Repository[models.EmailVerification, models.EmailVerification] {
repo := Construct[models.EmailVerification, models.EmailVerification](
models.EmailVerification{
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/external_auth_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/external_auth_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..77b6d6c3da3bc7a252a12702b12115331fc747b2
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/external_auth_repository.go
@@ -0,0 +1,37 @@
+package repositories
+
+import "api.qobiltu.id/models"
+
+func CreateExternalAuth(oauth models.ExternalAuth) Repository[models.ExternalAuth, models.ExternalAuth] {
+ repo := Construct[models.ExternalAuth, models.ExternalAuth](
+ oauth,
+ )
+ Create(repo)
+ return *repo
+}
+
+func GetExternalAuthByAccountId(accountId uint) Repository[models.ExternalAuth, []models.ExternalAuth] {
+ repo := Construct[models.ExternalAuth, []models.ExternalAuth](
+ models.ExternalAuth{
+ AccountID: accountId,
+ },
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.ExternalAuth, []models.ExternalAuth],
+ Find[models.ExternalAuth, []models.ExternalAuth],
+ )
+ return *repo
+}
+
+func GetExternalAccountByOauthId(oauthId string) Repository[models.ExternalAuth, models.ExternalAuth] {
+ repo := Construct[models.ExternalAuth, models.ExternalAuth](
+ models.ExternalAuth{
+ OauthID: oauthId,
+ },
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.ExternalAuth, models.ExternalAuth],
+ Find[models.ExternalAuth, models.ExternalAuth],
+ )
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/option_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/option_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..760678f7ac3f9916c3ed0e8f9ccf10f9fb4e8e51
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/option_repository.go
@@ -0,0 +1,43 @@
+package repositories
+
+import (
+ "api.qobiltu.id/models"
+)
+
+func CreateOptionCategory(categories models.OptionCategory) Repository[models.OptionCategory, models.OptionCategory] {
+ repo := Construct[models.OptionCategory, models.OptionCategory](
+ categories,
+ )
+ Create(repo)
+ return *repo
+}
+
+func CreateOptionValues(values models.OptionValues) Repository[models.OptionValues, models.OptionValues] {
+ repo := Construct[models.OptionValues, models.OptionValues](
+ values,
+ )
+ Create(repo)
+ return *repo
+}
+
+func GetOptionCategoryBySlug(slug string) Repository[models.OptionCategory, models.OptionCategory] {
+ repo := Construct[models.OptionCategory, models.OptionCategory](
+ models.OptionCategory{OptionSlug: slug},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.OptionCategory, models.OptionCategory],
+ Find[models.OptionCategory, models.OptionCategory],
+ )
+ return *repo
+}
+
+func GetOptionValuesByCategoryId(categoryId uint) Repository[models.OptionValues, []models.OptionValues] {
+ repo := Construct[models.OptionValues, []models.OptionValues](
+ models.OptionValues{OptionCategoryID: categoryId},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.OptionValues, []models.OptionValues],
+ Find[models.OptionValues, []models.OptionValues],
+ )
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/region_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/region_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..64ae641efeeb9a8361a8e5a4d1e43eeb2b50e98c
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/region_repository.go
@@ -0,0 +1,47 @@
+package repositories
+
+import (
+ "api.qobiltu.id/models"
+)
+
+func BulkCreateProvince(provinces []models.RegionProvince) Repository[[]models.RegionProvince, []models.RegionProvince] {
+ repo := Construct[[]models.RegionProvince, []models.RegionProvince](
+ provinces,
+ )
+
+ Create(repo)
+ return *repo
+}
+
+func BulkCreateCity(cities []models.RegionCity) Repository[[]models.RegionCity, []models.RegionCity] {
+ repo := Construct[[]models.RegionCity, []models.RegionCity](
+ cities,
+ )
+
+ Create(repo)
+ return *repo
+}
+
+func GetListProvinces() Repository[models.RegionProvince, []models.RegionProvince] {
+ repo := Construct[models.RegionProvince, []models.RegionProvince](
+ models.RegionProvince{},
+ )
+
+ repo.Transactions(
+ Find[models.RegionProvince, []models.RegionProvince],
+ )
+ return *repo
+}
+
+func GetListCitiesByProvinceId(provinceId uint) Repository[models.RegionCity, []models.RegionCity] {
+ repo := Construct[models.RegionCity, []models.RegionCity](
+ models.RegionCity{
+ ProvinceID: provinceId,
+ },
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.RegionCity, []models.RegionCity],
+ Find[models.RegionCity, []models.RegionCity],
+ )
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go
index 6ca2498ea0746d60de9cddf3b605fe0ecb9107bc..808b568cf02fd0137a46cb808a2c9102c636eccc 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go
@@ -9,6 +9,7 @@ import (
func AuthRoute(router *gin.Engine) {
routerGroup := router.Group("/api/v1/auth")
{
+ routerGroup.POST("/external-login", AuthController.ExternalAuth)
routerGroup.POST("/login", AuthController.Login)
routerGroup.POST("/register", AuthController.Register)
routerGroup.PUT("/change-password", middleware.AuthUser, AuthController.ChangePassword)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/email_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/email_route.go
index 8f2f59e88a8cf958d200ee36174549e839a0a33c..f76fc498313541896253315be8d1e57bd3b03a05 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/email_route.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/email_route.go
@@ -2,14 +2,14 @@ package router
import (
EmailController "api.qobiltu.id/controller/email"
+ "api.qobiltu.id/middleware"
"github.com/gin-gonic/gin"
)
func EmailRoute(router *gin.Engine) {
routerGroup := router.Group("/api/v1/email")
{
- routerGroup.POST("/verify", EmailController.CreateVerification)
- routerGroup.POST("/create-verification", EmailController.CreateVerification)
- routerGroup.DELETE("/delete-verification", EmailController.DeleteVerification)
+ routerGroup.POST("/verify", middleware.AuthUser, EmailController.Verify)
+ routerGroup.POST("/create-verification", middleware.AuthUser, EmailController.CreateVerification)
}
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/options_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/options_route.go
new file mode 100644
index 0000000000000000000000000000000000000000..1f935c0a433d00bdefa0f5bb3c75d00795012def
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/options_route.go
@@ -0,0 +1,20 @@
+package router
+
+import (
+ OptionsController "api.qobiltu.id/controller/options"
+ CityController "api.qobiltu.id/controller/region/city"
+ ProvinceController "api.qobiltu.id/controller/region/province"
+ "github.com/gin-gonic/gin"
+)
+
+func OptionsRoute(router *gin.Engine) {
+ routerGroup := router.Group("/api/v1/options")
+ {
+ routerGroup.POST("/create", OptionsController.AddOptions)
+ routerGroup.GET("/list/:slug", OptionsController.List)
+ routerGroup.GET("/region/provinces", ProvinceController.List)
+ routerGroup.GET("/region/cities", CityController.List)
+ routerGroup.POST("/region/seed-provinces", ProvinceController.Seeds)
+ routerGroup.POST("/region/seed-cities", CityController.Seeds)
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
index fa10e4899db30cae716cc34f9d165cffcb5b7c1d..6987e5456230d0a920b4e44a77163fade344beb7 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
@@ -1,6 +1,8 @@
package router
import (
+ "log"
+
"api.qobiltu.id/config"
"api.qobiltu.id/controller"
"github.com/gin-gonic/gin"
@@ -9,8 +11,14 @@ import (
func StartService() {
router := gin.Default()
router.GET("/", controller.HomeController)
+
AuthRoute(router)
UserRoute(router)
EmailRoute(router)
- router.Run(config.TCP_ADDRESS)
+ OptionsRoute(router)
+
+ err := router.Run(config.TCP_ADDRESS)
+ if err != nil {
+ log.Fatalf("Failed to run server: %v", err)
+ }
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go
index 22a82cb0fe7042139cfa992ca4ac35bfcc9b4490..8b3474fa876993fe8a09fde9a241c8a54df5feee 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go
@@ -2,7 +2,6 @@ package services
import (
"errors"
- "fmt"
"api.qobiltu.id/models"
"api.qobiltu.id/repositories"
@@ -45,15 +44,13 @@ func (s *AuthenticationService) Update(oldPassword string, newPassword string) {
s.Exception.Message = "Password must have at least 8 characters!"
return
}
- accountData := repositories.GetAccountbyId(s.Constructor.Id)
+ accountData := repositories.GetAccountById(s.Constructor.Id)
if accountData.NoRecord {
s.Exception.DataNotFound = true
s.Exception.Message = "there is no account with given credentials!"
return
}
- fmt.Println("Result Password", accountData.Result.Password)
- fmt.Println("old Password given", oldPassword)
if VerifyPassword(accountData.Result.Password, oldPassword) != nil {
s.Exception.Unauthorized = true
s.Exception.Message = "incorrect old password!"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..3145d18c671432bfa78ef892b911424476cf79c4
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/external_authentication_service.go
@@ -0,0 +1,54 @@
+package services
+
+import (
+ "context"
+ "errors"
+
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+ uuid "github.com/satori/go.uuid"
+ "google.golang.org/api/idtoken"
+)
+
+type GoogleAuthService struct {
+ Service[models.ExternalAuth, models.AuthenticatedUser]
+}
+
+func (s *GoogleAuthService) Authenticate() {
+ GoogleAuth := repositories.GetExternalAccountByOauthId(s.Constructor.OauthID)
+ payload, errGoogleAuth := idtoken.Validate(context.Background(), s.Constructor.OauthID, "")
+ s.Error = errGoogleAuth
+ if errGoogleAuth != nil {
+ s.Exception.Unauthorized = true
+ s.Exception.Message = "Oauth Provider Failed Login (Google Authentication)"
+ return
+ }
+ email := payload.Claims["email"]
+ if GoogleAuth.NoRecord {
+ s.Constructor.UUID = uuid.NewV4()
+ s.Constructor.OauthProvider = "Google"
+ createAccount := repositories.CreateAccount(models.Account{
+ Email: email.(string),
+ IsEmailVerified: true,
+ })
+ s.Constructor.AccountID = createAccount.Result.Id
+ createGoogleAuth := repositories.CreateExternalAuth(s.Constructor)
+
+ s.Error = createGoogleAuth.RowsError
+ s.Error = errors.Join(s.Error, createAccount.RowsError)
+ }
+ accountData := repositories.GetAccountById(GoogleAuth.Result.AccountID)
+ token, err_tok := GenerateToken(&accountData.Result)
+
+ if err_tok != nil {
+ s.Error = errors.Join(s.Error, err_tok)
+ }
+
+ accountData.Result.Password = "SECRET"
+ s.Result = models.AuthenticatedUser{
+ Account: accountData.Result,
+ Token: token,
+ }
+ s.Error = accountData.RowsError
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/option_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/option_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..a95ee656c17e94747d5a3929d7eb3f2701a59255
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/option_service.go
@@ -0,0 +1,70 @@
+package services
+
+import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+ "github.com/gosimple/slug"
+)
+
+type OptionService struct {
+ Service[[]models.OptionsRequest, models.OptionsResponse]
+}
+
+type OptionValueService struct {
+ Service[models.OptionCategory, models.Options]
+}
+
+func (s *OptionService) Create() {
+ optionsResult := []models.Options{}
+ for _, option := range s.Constructor {
+ OptionCategoryRepo := repositories.CreateOptionCategory(
+ models.OptionCategory{
+ OptionName: option.OptionName,
+ OptionSlug: slug.Make(option.OptionName),
+ },
+ )
+ if OptionCategoryRepo.RowsError != nil {
+ OptionCategoryRepo.Transaction.Rollback()
+ s.Error = OptionCategoryRepo.RowsError
+ return
+ }
+ optionsValueResult := []models.OptionValues{}
+ for _, value := range option.OptionValue {
+ OptionValuesRepo := repositories.CreateOptionValues(
+ models.OptionValues{
+ OptionCategoryID: OptionCategoryRepo.Result.ID,
+ OptionValue: value,
+ },
+ )
+
+ if OptionValuesRepo.RowsError != nil {
+ OptionValuesRepo.Transaction.Rollback()
+ s.Error = OptionValuesRepo.RowsError
+ return
+ }
+ optionsValueResult = append(optionsValueResult, OptionValuesRepo.Result)
+ }
+ optionsResult = append(optionsResult, models.Options{OptionCategory: OptionCategoryRepo.Result, OptionValues: optionsValueResult})
+ }
+ s.Result = models.OptionsResponse{
+ Options: optionsResult,
+ }
+}
+
+func (s *OptionValueService) Retrieve() {
+ optionCategory := repositories.GetOptionCategoryBySlug(s.Constructor.OptionSlug)
+ if optionCategory.RowsError != nil {
+ s.Error = optionCategory.RowsError
+ return
+ }
+ optionValues := repositories.GetOptionValuesByCategoryId(optionCategory.Result.ID)
+ if optionValues.RowsError != nil {
+ s.Error = optionValues.RowsError
+ return
+ }
+ s.Result = models.Options{
+ OptionCategory: optionCategory.Result,
+ OptionValues: optionValues.Result,
+ }
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/region_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/region_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..493269736cc9a952b2c354b85e40eb04c93cb0eb
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/region_service.go
@@ -0,0 +1,90 @@
+package services
+
+import (
+ "encoding/json"
+ "log"
+ "os"
+ "path/filepath"
+ "runtime"
+
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+)
+
+type ProvinceService struct {
+ Service[models.RegionProvince, []models.RegionProvince]
+}
+
+type CityService struct {
+ Service[models.RegionCity, []models.RegionCity]
+}
+
+func seedCity() ([]models.RegionCity, error) {
+ log.Println("Seed City")
+ _, b, _, _ := runtime.Caller(0)
+ basePath := filepath.Dir(b)
+ file, err := os.Open(filepath.Join(basePath, "..", "utils", "seeds", "city.json"))
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ var cities []models.RegionCity
+ if err := json.NewDecoder(file).Decode(&cities); err != nil {
+ return nil, err
+ }
+ return cities, nil
+}
+
+func seedProvince() ([]models.RegionProvince, error) {
+ log.Println("Seed City")
+ _, b, _, _ := runtime.Caller(0)
+ basePath := filepath.Dir(b)
+ file, err := os.Open(filepath.Join(basePath, "..", "utils", "seeds", "province.json"))
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ var provinces []models.RegionProvince
+ if err := json.NewDecoder(file).Decode(&provinces); err != nil {
+ return nil, err
+ }
+ return provinces, nil
+}
+
+func (s *ProvinceService) Create() {
+ provinces, errSeed := seedProvince()
+ if errSeed != nil {
+ s.Error = errSeed
+ s.Exception.InternalServerError = true
+ s.Exception.Message = "Failed to seed province"
+ return
+ }
+ createProvince := repositories.BulkCreateProvince(provinces)
+ s.Error = createProvince.RowsError
+ s.Result = createProvince.Result
+}
+
+func (s *CityService) Create() {
+ cities, errSeed := seedCity()
+ if errSeed != nil {
+ s.Error = errSeed
+ s.Exception.InternalServerError = true
+ s.Exception.Message = "Failed to seed province"
+ return
+ }
+ createCity := repositories.BulkCreateCity(cities)
+ s.Error = createCity.RowsError
+ s.Result = createCity.Result
+}
+
+func (s *ProvinceService) Retrieve() {
+ Province := repositories.GetListProvinces()
+ s.Error = Province.RowsError
+ s.Result = Province.Result
+}
+
+func (s *CityService) Retrieve() {
+ cities := repositories.GetListCitiesByProvinceId(s.Constructor.ProvinceID)
+ s.Error = cities.RowsError
+ s.Result = cities.Result
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification.go
index 44ee9b005702e30354ef25f46d80e9551dbfcf6b..bf5f8d44118c25981fa22cb46c17eefb2d12c684 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification.go
@@ -1,8 +1,6 @@
package controller
import (
- "strconv"
-
"api.qobiltu.id/controller"
"api.qobiltu.id/models"
"api.qobiltu.id/services"
@@ -14,9 +12,10 @@ func CreateVerification(c *gin.Context) {
emailVerificationController := controller.Controller[any, models.EmailVerification, models.EmailVerification]{
Service: &emailVerification.Service,
}
- query, _ := c.GetQuery("account_id")
- accountId, _ := strconv.Atoi(query)
- emailVerificationController.Service.Constructor.AccountID = uint(accountId)
- emailVerification.Create()
- emailVerificationController.Response(c)
+ emailVerificationController.HeaderParse(c, func() {
+ emailVerificationController.Service.Constructor.AccountID = uint(emailVerificationController.AccountData.UserID)
+ emailVerification.Create()
+ emailVerificationController.Response(c)
+ })
+
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
index 4821ed13bbf913726e6512251db3bf43728a8a19..ec2b5ab25518d1aa9221afe7b07527ba298b14bc 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
@@ -1,6 +1,8 @@
package user
import (
+ "fmt"
+
"api.qobiltu.id/controller"
"api.qobiltu.id/models"
"api.qobiltu.id/services"
@@ -17,6 +19,7 @@ func UpdateProfile(c *gin.Context) {
userUpdateProfileController.Service.Constructor = userUpdateProfileController.Request
userUpdateProfileController.HeaderParse(c, func() {
userUpdateProfileController.Service.Constructor.AccountID = uint(userUpdateProfileController.AccountData.UserID)
+ fmt.Println("Account ID:", userUpdateProfileController.Service.Constructor.AccountID)
})
userProfile.Update()
},
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
index ac953006acff71b8ab536fe0519255e65a02deae..c7d9163ce8ab396cd38c1317229e931e7da31765 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -28,13 +28,14 @@ type AccountDetails struct {
LastJob *string `json:"last_job"`
Gender *bool `json:"gender"`
LastEducation *string `json:"last_education"`
- MaritalStatus *bool `json:"marital_status"`
+ MaritalStatus *string `json:"marital_status"`
Avatar *string `json:"avatar"`
PhoneNumber *uint `json:"phone_number"`
}
type EmailVerification struct {
ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
Token uint `json:"token"`
AccountID uint `json:"account_id"`
IsExpired bool `json:"is_expired"`
@@ -43,10 +44,11 @@ type EmailVerification struct {
}
type ExternalAuth struct {
- ID uint `gorm:"primaryKey" json:"id"`
- OauthID string `json:"oauth_id"`
- AccountID uint `json:"account_id"`
- OauthProvider string `json:"oauth_provider"`
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
+ OauthID string `json:"oauth_id"`
+ AccountID uint `json:"account_id"`
+ OauthProvider string `json:"oauth_provider"`
}
type FCM struct {
@@ -57,6 +59,7 @@ type FCM struct {
type ForgotPassword struct {
ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
Token uint `json:"token"`
AccountID uint `json:"account_id"`
IsExpired bool `json:"is_expired"`
@@ -89,7 +92,17 @@ type AcademyContent struct {
AcademyMaterialID uint `json:"academy_material_id"`
Description string `json:"description"`
}
+type OptionCategory struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ OptionName string `json:"option_name"`
+ OptionSlug string `json:"option_slug"`
+}
+type OptionValues struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ OptionCategoryID uint `json:"option_category_id"`
+ OptionValue string `json:"option_value"`
+}
type AcademyMaterialProgress struct {
ID uint `gorm:"primaryKey" json:"id"`
UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
@@ -105,6 +118,21 @@ type AcademyContentProgress struct {
AcademyID uint `json:"academy_id"`
}
+type RegionProvince struct {
+ ID uint `json:"id"`
+ Name string `json:"name"`
+ Code string `json:"code"`
+}
+
+type RegionCity struct {
+ ID uint `json:"id"`
+ Type string `json:"type"`
+ Name string `json:"name"`
+ Code string `json:"code"`
+ FullCode string `json:"full_code"`
+ ProvinceID uint `json:"province_id"`
+}
+
// Gorm table name settings
func (Account) TableName() string { return "account" }
func (AccountDetails) TableName() string { return "account_details" }
@@ -117,3 +145,5 @@ func (AcademyMaterial) TableName() string { return "academy_materials" }
func (AcademyContent) TableName() string { return "academy_contents" }
func (AcademyMaterialProgress) TableName() string { return "academy_materials_progress" }
func (AcademyContentProgress) TableName() string { return "academy_contents_progress" }
+func (RegionProvince) TableName() string { return "region_provinces" }
+func (RegionCity) TableName() string { return "region_cities" }
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
index 686ccc92e7a56587581e345bbb3dca284fb1d7da..8aad7510cf6ff2f1146de5a8da0d0f61debe7cb6 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
@@ -1,8 +1,6 @@
package repositories
import (
- "fmt"
-
"api.qobiltu.id/models"
)
@@ -32,7 +30,10 @@ func UpdateAccount(account models.Account) Repository[models.Account, models.Acc
repo := Construct[models.Account, models.Account](
account,
)
- Update(repo)
+ repo.Transactions(
+ WhereGivenConstructor[models.Account, models.Account],
+ Update[models.Account],
+ )
return *repo
}
@@ -61,18 +62,19 @@ func CreateAccountDetails(accountDetails models.AccountDetails) Repository[model
repo := Construct[models.AccountDetails, models.AccountDetails](
accountDetails,
)
- fmt.Println(accountDetails)
- fmt.Println("Account ID : ", accountDetails.AccountID)
Create(repo)
return *repo
}
func UpdateAccountDetails(accountDetails models.AccountDetails) Repository[models.AccountDetails, models.AccountDetails] {
repo := Construct[models.AccountDetails, models.AccountDetails](
- accountDetails,
+ models.AccountDetails{AccountID: accountDetails.AccountID},
)
- fmt.Println(accountDetails)
- fmt.Println("Account ID : ", accountDetails.AccountID)
- Update(repo)
+ repo.Transaction.Where("account_id = ?", accountDetails.AccountID).First(&repo.Constructor)
+ accountDetails.ID = repo.Constructor.ID
+ // fmt.Println(repo.Constructor)
+ // fmt.Println(accountDetails)
+ repo.Transaction.Updates(accountDetails)
+ repo.Result = accountDetails
return *repo
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repository.go
index 0c6b1ee5c41b63f4414e7013717d7fbd58a7dbf4..89367e50d27d076540f8b7766d9b3a8cbbdf0913 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repository.go
@@ -92,7 +92,7 @@ func Create[T1 any](repo *Repository[T1, T1]) *gorm.DB {
}
func Update[T1 any](repo *Repository[T1, T1]) *gorm.DB {
- tx := repo.Transaction.Save(&repo.Constructor)
+ tx := repo.Transaction.Updates(&repo.Constructor)
repo.RowsCount = int(tx.RowsAffected)
repo.NoRecord = repo.RowsCount == 0
repo.RowsError = tx.Error
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
index d94c65bc93550413c285e2817b68392664fb3b2a..c0eaba5711783161d526de88c0090a594bacfda2 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
@@ -1,12 +1,16 @@
package services
import (
- "math/rand"
+ "fmt"
+ "log"
+ "math/rand/v2"
+ "net/smtp"
"time"
"api.qobiltu.id/config"
"api.qobiltu.id/models"
"api.qobiltu.id/repositories"
+ uuid "github.com/satori/go.uuid"
)
type EmailVerificationService struct {
@@ -14,7 +18,7 @@ type EmailVerificationService struct {
}
func (s *EmailVerificationService) Create() {
- accountRepo := repositories.GetAccountbyId(s.Constructor.AccountID)
+ accountRepo := repositories.GetAccountById(s.Constructor.AccountID)
if accountRepo.NoRecord {
s.Error = accountRepo.RowsError
s.Exception.DataNotFound = true
@@ -25,23 +29,54 @@ func (s *EmailVerificationService) Create() {
remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
dueTime := CalculateDueTime(remainingTime)
- randomizer := rand.New(rand.NewSource(10))
- token := uint(randomizer.Int())
+ token := uint(rand.IntN(100000))
+ s.Constructor.UUID = uuid.NewV4()
- repo := repositories.CreateEmailVerification(s.Constructor.AccountID, dueTime, token)
+ repo := repositories.CreateEmailVerification(s.Constructor.UUID, s.Constructor.AccountID, dueTime, token)
s.Error = repo.RowsError
s.Result = repo.Result
+
+ // ⬇ Kirim token ke email user menggunakan SMTP
+ go func(toEmail string, token uint) {
+ from := config.SMTP_SENDER_EMAIL
+ password := config.SMTP_SENDER_PASSWORD
+ smtpHost := config.SMTP_HOST
+ smtpPort := config.SMTP_PORT
+
+ auth := smtp.PlainAuth("", from, password, smtpHost)
+
+ subject := "Email Verification Token"
+ body := fmt.Sprintf("Your verification token is: %06d\nPlease use it before it expires.", token)
+
+ msg := []byte("To: " + toEmail + "\r\n" +
+ "Subject: " + subject + "\r\n" +
+ "\r\n" +
+ body + "\r\n")
+
+ err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, []string{toEmail}, msg)
+ if err != nil {
+ log.Printf("Error sending verification email: %v", err)
+ }
+ }(accountRepo.Result.Email, token)
}
func (s *EmailVerificationService) Validate() {
repo := repositories.GetEmailVerification(s.Constructor.AccountID, s.Constructor.Token)
s.Error = repo.RowsError
+
if repo.NoRecord {
s.Exception.DataNotFound = true
s.Exception.Message = "Invalid token!"
return
}
+
+ if repo.Result.ExpiredAt.Before(time.Now()) {
+ s.Exception.Unauthorized = true
+ s.Exception.Message = "Token has expired!"
+ repositories.UpdateExpiredEmailVerification(s.Constructor.UUID)
+ return
+ }
s.Result = repo.Result
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
index 419458a7d1e4845f1e4049bc3c3edabbbef748fb..4807849046127c92c0dfdc98d156e58cc9403492 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
@@ -1,6 +1,8 @@
package services
import (
+ "fmt"
+
"api.qobiltu.id/models"
"api.qobiltu.id/repositories"
)
@@ -31,6 +33,7 @@ func (s *UserProfileService) Retrieve() {
}
func (s *UserProfileService) Update() {
+ fmt.Println("Account ID:", s.Constructor.AccountID)
userProfile := repositories.UpdateAccountDetails(s.Constructor)
s.Error = userProfile.RowsError
if userProfile.NoRecord {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/auth_profile_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/auth_profile_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..f30ee7177fa8bd15be000bcfe941ceb2732a74c1
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/auth_profile_controller.go
@@ -0,0 +1,21 @@
+package user
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Profile(c *gin.Context) {
+ userProfile := services.UserProfileService{}
+ userProfileController := controller.Controller[any, models.AccountDetails, models.AccountDetails]{
+ Service: &userProfile.Service,
+ }
+ userProfileController.HeaderParse(c, func() {
+ userProfileController.Service.Constructor.AccountID = uint(userProfileController.AccountData.UserID)
+ userProfile.Retrieve()
+ userProfileController.Response(c)
+ },
+ )
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/auth_update_profile_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/auth_update_profile_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..4821ed13bbf913726e6512251db3bf43728a8a19
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/auth_update_profile_controller.go
@@ -0,0 +1,24 @@
+package user
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func UpdateProfile(c *gin.Context) {
+ userProfile := services.UserProfileService{}
+ userUpdateProfileController := controller.Controller[models.AccountDetails, models.AccountDetails, models.AccountDetails]{
+ Service: &userProfile.Service,
+ }
+
+ userUpdateProfileController.RequestJSON(c, func() {
+ userUpdateProfileController.Service.Constructor = userUpdateProfileController.Request
+ userUpdateProfileController.HeaderParse(c, func() {
+ userUpdateProfileController.Service.Constructor.AccountID = uint(userUpdateProfileController.AccountData.UserID)
+ })
+ userProfile.Update()
+ },
+ )
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go
index 4bd0d347f3705976e9973248b50103893c3f603e..6ca2498ea0746d60de9cddf3b605fe0ecb9107bc 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go
@@ -11,8 +11,6 @@ func AuthRoute(router *gin.Engine) {
{
routerGroup.POST("/login", AuthController.Login)
routerGroup.POST("/register", AuthController.Register)
- routerGroup.GET("/me", middleware.AuthUser, AuthController.Profile)
- routerGroup.PUT("/me", middleware.AuthUser, AuthController.UpdateProfile)
routerGroup.PUT("/change-password", middleware.AuthUser, AuthController.ChangePassword)
}
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
index a7dde2e239a2d254991cd00c712b9eeebe6102d1..fa10e4899db30cae716cc34f9d165cffcb5b7c1d 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
@@ -9,6 +9,7 @@ import (
func StartService() {
router := gin.Default()
router.GET("/", controller.HomeController)
+ AuthRoute(router)
UserRoute(router)
EmailRoute(router)
router.Run(config.TCP_ADDRESS)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
index 6d1cb93a61de13e3ae0f178cc701c77ed791ae7a..ac953006acff71b8ab536fe0519255e65a02deae 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -14,23 +14,23 @@ type Account struct {
IsEmailVerified bool `json:"is_email_verified"`
IsDetailCompleted bool `json:"is_detail_completed"`
CreatedAt time.Time `json:"created_at"`
- DeletedAt *time.Time `json:"deleted_at,omitempty" gorm:"default:null"`
+ DeletedAt *time.Time `json:"deleted_at" gorm:"default:null"`
}
type AccountDetails struct {
ID uint `gorm:"primaryKey" json:"id"`
AccountID uint `json:"account_id"`
InitialName string `json:"initial_name"`
- FullName *string `json:"full_name,omitempty"`
- DateOfBirth *time.Time `json:"date_of_birth,omitempty"`
- PlaceOfBirth *string `json:"place_of_birth,omitempty"`
- Domicile *string `json:"domicile,omitempty"`
- LastJob *string `json:"last_job,omitempty"`
- Gender *bool `json:"gender,omitempty"`
- LastEducation *string `json:"last_education,omitempty"`
- MaritalStatus *bool `json:"marital_status,omitempty"`
- Avatar *string `json:"avatar,omitempty"`
- PhoneNumber *uint `json:"phone_number,omitempty"`
+ FullName *string `json:"full_name"`
+ DateOfBirth *time.Time `json:"date_of_birth"`
+ PlaceOfBirth *string `json:"place_of_birth"`
+ Domicile *string `json:"domicile"`
+ LastJob *string `json:"last_job"`
+ Gender *bool `json:"gender"`
+ LastEducation *string `json:"last_education"`
+ MaritalStatus *bool `json:"marital_status"`
+ Avatar *string `json:"avatar"`
+ PhoneNumber *uint `json:"phone_number"`
}
type EmailVerification struct {
@@ -57,7 +57,7 @@ type FCM struct {
type ForgotPassword struct {
ID uint `gorm:"primaryKey" json:"id"`
- UUID uint `json:"uuid"`
+ Token uint `json:"token"`
AccountID uint `json:"account_id"`
IsExpired bool `json:"is_expired"`
CreatedAt time.Time `json:"created_at"`
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/authentication_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/authentication_route.go
new file mode 100644
index 0000000000000000000000000000000000000000..1559be8c370509101ce8508c226e252824d215ec
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/authentication_route.go
@@ -0,0 +1,16 @@
+package router
+
+import (
+ UserController "api.qobiltu.id/controller/user"
+ "api.qobiltu.id/middleware"
+ "github.com/gin-gonic/gin"
+)
+
+func AuthRoute(router *gin.Engine) {
+ routerGroup := router.Group("/api/v1/auth")
+ {
+ routerGroup.POST("/login", UserController.Login)
+ routerGroup.POST("/register", UserController.Register)
+ routerGroup.PUT("/change-password", middleware.AuthUser, UserController.ChangePassword)
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/user_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/user_route.go
index 1e5f9e405b0d936a86aed41c552cd8030fdc12bb..de080e4a230b6be743f69ce59a474cc47bc8c560 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/user_route.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/user_route.go
@@ -9,10 +9,7 @@ import (
func UserRoute(router *gin.Engine) {
routerGroup := router.Group("/api/v1/user")
{
- routerGroup.POST("/login", UserController.Login)
- routerGroup.POST("/register", UserController.Register)
routerGroup.GET("/me", middleware.AuthUser, UserController.Profile)
routerGroup.PUT("/me", middleware.AuthUser, UserController.UpdateProfile)
- routerGroup.PUT("/change-password", middleware.AuthUser, UserController.ChangePassword)
}
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_change_password_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_change_password_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..80aa4ac00b85babbbbbcab4de6c7d1fa3514ea6a
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_change_password_controller.go
@@ -0,0 +1,21 @@
+package user
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func ChangePassword(c *gin.Context) {
+ authentication := services.AuthenticationService{}
+ changePasswordController := controller.Controller[models.ChangePasswordRequest, models.Account, models.AuthenticatedUser]{
+ Service: &authentication.Service,
+ }
+ changePasswordController.HeaderParse(c, func() {
+ changePasswordController.Service.Constructor.Id = uint(changePasswordController.AccountData.UserID)
+ })
+ changePasswordController.RequestJSON(c, func() {
+ authentication.Update(changePasswordController.Request.OldPassword, changePasswordController.Request.NewPassword)
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_login_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_login_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..679fc5a018d75e815dea00cca2012d5d56034eed
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_login_controller.go
@@ -0,0 +1,20 @@
+package user
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Login(c *gin.Context) {
+ authentication := services.AuthenticationService{}
+ loginController := controller.Controller[models.LoginRequest, models.Account, models.AuthenticatedUser]{
+ Service: &authentication.Service,
+ }
+ loginController.RequestJSON(c, func() {
+ loginController.Service.Constructor.Email = loginController.Request.Email
+ loginController.Service.Constructor.Password = loginController.Request.Password
+ authentication.Authenticate()
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_profile_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_profile_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..f30ee7177fa8bd15be000bcfe941ceb2732a74c1
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_profile_controller.go
@@ -0,0 +1,21 @@
+package user
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Profile(c *gin.Context) {
+ userProfile := services.UserProfileService{}
+ userProfileController := controller.Controller[any, models.AccountDetails, models.AccountDetails]{
+ Service: &userProfile.Service,
+ }
+ userProfileController.HeaderParse(c, func() {
+ userProfileController.Service.Constructor.AccountID = uint(userProfileController.AccountData.UserID)
+ userProfile.Retrieve()
+ userProfileController.Response(c)
+ },
+ )
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_register_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_register_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..17051bee42f7b1d3fe144cedb6e8f11dbb5ac5b2
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_register_controller.go
@@ -0,0 +1,20 @@
+package user
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Register(c *gin.Context) {
+ register := services.RegisterService{}
+ registerController := controller.Controller[models.RegisterRequest, models.Account, models.Account]{
+ Service: ®ister.Service,
+ }
+ registerController.RequestJSON(c, func() {
+ registerController.Service.Constructor.Password = registerController.Request.Password
+ registerController.Service.Constructor.Email = registerController.Request.Email
+ register.Create()
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_update_profile_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_update_profile_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..4821ed13bbf913726e6512251db3bf43728a8a19
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/auth/auth_update_profile_controller.go
@@ -0,0 +1,24 @@
+package user
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func UpdateProfile(c *gin.Context) {
+ userProfile := services.UserProfileService{}
+ userUpdateProfileController := controller.Controller[models.AccountDetails, models.AccountDetails, models.AccountDetails]{
+ Service: &userProfile.Service,
+ }
+
+ userUpdateProfileController.RequestJSON(c, func() {
+ userUpdateProfileController.Service.Constructor = userUpdateProfileController.Request
+ userUpdateProfileController.HeaderParse(c, func() {
+ userUpdateProfileController.Service.Constructor.AccountID = uint(userUpdateProfileController.AccountData.UserID)
+ })
+ userProfile.Update()
+ },
+ )
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
index 0c2b7a92ff9c9eddbfa0d1506ccb9b4d0614163e..405345ca21ac9dd77eec5b3d4e48775287157552 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
@@ -3,7 +3,6 @@ module api.qobiltu.id
go 1.24.0
require (
- github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/joho/godotenv v1.5.1
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
index b7d196affb731541ab18f7ba85fbb21dd47867ac..fc9ac2ce0112e916606dc13ba95b03d9c01f9373 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
@@ -10,8 +10,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
index 41ed5f2fb2eefa1f43c991f2f502d39505c76e3b..e413a98e080bcf0700af246cd083a9337263dba1 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
@@ -3,52 +3,22 @@
package middleware
import (
- "time"
-
- "api.qobiltu.id/config"
"api.qobiltu.id/models"
+ "api.qobiltu.id/services"
"api.qobiltu.id/utils"
"github.com/gin-gonic/gin"
- "github.com/golang-jwt/jwt/v5"
)
-var salt = config.Salt
-var secretKey = []byte(salt)
-
-// VerifyPassword verifies if the provided password matches the hashed password
-
-type CustomClaims struct {
- jwt.RegisteredClaims
- UserID int `json:"id"`
-}
-
-func VerifyToken(bearer_token string) (int, string, error) {
- // fmt.Println(bearer_token)
- token, err := jwt.ParseWithClaims(bearer_token, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
- return secretKey, nil
- })
- if err != nil {
- return 0, "invalid-token", err
- }
-
- // Extract the claims
- claims, ok := token.Claims.(*CustomClaims)
- if !ok || !token.Valid {
- return 0, "invalid-token", err
- }
- if claims.ExpiresAt != nil && claims.ExpiresAt.Time.Before(time.Now()) {
- return 0, "expired", err
- }
-
- return claims.UserID, "valid", err
-}
-
func AuthUser(c *gin.Context) {
var currAccData models.AccountData
- if c.Request.Header["Auth-Bearer-Token"] != nil {
- token := c.Request.Header["Auth-Bearer-Token"]
- currAccData.UserID, currAccData.VerifyStatus, currAccData.ErrVerif = VerifyToken(token[0])
- // fmt.Println("Verify Status :", currAccData.verifyStatus)
+ if c.Request.Header["Authorization"] != nil {
+ token := c.Request.Header["Authorization"]
+ // fmt.Println("Authorization :", token)
+
+ currAccData.UserID, currAccData.VerifyStatus, currAccData.ErrVerif = services.VerifyToken(token[0])
+ // fmt.Println("Verify Status :", currAccData.VerifyStatus)
+ // fmt.Println("Verify UserID :", currAccData.UserID)
+
if currAccData.VerifyStatus == "invalid-token" || currAccData.VerifyStatus == "expired" {
currAccData.UserID = 0
utils.ResponseFAIL(c, 401, models.Exception{Unauthorized: true, Message: "Your session is expired, Please re-Login!"})
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/authentication_payload_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/authentication_payload_model.go
index 0203b9b340d066705491edf66830674602f89655..41f31eb02e14a1ad1e3b6cf372a69dd91deb12b8 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/authentication_payload_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/authentication_payload_model.go
@@ -1,7 +1,7 @@
package models
type AccountData struct {
- UserID int
+ UserID uint
VerifyStatus string
ErrVerif error
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/custom_claim.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/custom_claim.go
new file mode 100644
index 0000000000000000000000000000000000000000..858c6b95b7c9bc880b5d163dd4469b129e9355cf
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/custom_claim.go
@@ -0,0 +1,8 @@
+package models
+
+import "github.com/golang-jwt/jwt/v5"
+
+type CustomClaims struct {
+ jwt.RegisteredClaims
+ UserID uint `json:"id"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
index c45df0dbc642de90cc3537722b359a2f09a06b02..7e049aac3f7c5a196f9a54ce924b5424f5986553 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
@@ -13,6 +13,7 @@ type ErrorResponse struct {
Errors Exception `json:"errors"`
MetaData any `json:"meta_data"`
}
+
type AuthenticatedUser struct {
Account Account `json:"account"`
Token string `json:"token"`
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/email_verification_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/email_verification_repository.go
index c737e6d4b782e17630568f3616f0bf7ef36dc31f..0072ea0952f85bc719bf4bd0d1cf90e6a7270870 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/email_verification_repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/email_verification_repository.go
@@ -19,10 +19,10 @@ func CreateEmailVerification(accountId uint, dueTime time.Time, token uint) Repo
return *repo
}
-func GetEmailVerification(account_id uint, token uint) Repository[models.EmailVerification, models.EmailVerification] {
+func GetEmailVerification(accountId uint, token uint) Repository[models.EmailVerification, models.EmailVerification] {
repo := Construct[models.EmailVerification, models.EmailVerification](
models.EmailVerification{
- AccountID: account_id,
+ AccountID: accountId,
IsExpired: false,
Token: token,
},
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go
new file mode 100644
index 0000000000000000000000000000000000000000..4bd0d347f3705976e9973248b50103893c3f603e
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/auth_route.go
@@ -0,0 +1,18 @@
+package router
+
+import (
+ AuthController "api.qobiltu.id/controller/auth"
+ "api.qobiltu.id/middleware"
+ "github.com/gin-gonic/gin"
+)
+
+func AuthRoute(router *gin.Engine) {
+ routerGroup := router.Group("/api/v1/auth")
+ {
+ routerGroup.POST("/login", AuthController.Login)
+ routerGroup.POST("/register", AuthController.Register)
+ routerGroup.GET("/me", middleware.AuthUser, AuthController.Profile)
+ routerGroup.PUT("/me", middleware.AuthUser, AuthController.UpdateProfile)
+ routerGroup.PUT("/change-password", middleware.AuthUser, AuthController.ChangePassword)
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/jwt_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/jwt_service.go
index 92024df8afa7fe83da747bddc969cc1066cf431c..3fdce4e4735d65dabdd71357a6d915f07219ddfb 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/jwt_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/jwt_service.go
@@ -2,11 +2,12 @@ package services
import (
"errors"
+ "strings"
"time"
"api.qobiltu.id/config"
"api.qobiltu.id/models"
- "github.com/dgrijalva/jwt-go"
+ "github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
)
@@ -14,22 +15,56 @@ var salt = config.Salt
var secretKey = []byte(salt)
func GenerateToken(user *models.Account) (string, error) {
+ claims := models.CustomClaims{
+ UserID: user.Id,
+ RegisteredClaims: jwt.RegisteredClaims{
+ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), // Token berlaku 24 jam
+ IssuedAt: jwt.NewNumericDate(time.Now()),
+ Issuer: "qobiltu.id",
+ },
+ }
+
+ // Buat token dengan metode signing
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ return token.SignedString(secretKey)
+}
+
+func ExtractBearerToken(authHeader string) (string, error) {
+ parts := strings.Split(authHeader, " ")
+ if len(parts) != 2 || parts[0] != "Bearer" {
+ return "", errors.New("invalid authorization header format")
+ }
+ return parts[1], nil
+}
- // Create a new token
- token := jwt.New(jwt.SigningMethodHS256)
+func VerifyToken(bearerToken string) (uint, string, error) {
+ // fmt.Println("bearerToken :", bearerToken)
- // Set claims
- claims := token.Claims.(jwt.MapClaims)
- claims["id"] = user.Id
- claims["exp"] = time.Now().Add(time.Hour * 24).Unix() // Token expires in 24 hours
+ tokenData, err := ExtractBearerToken(bearerToken)
+ if err != nil {
+ return 0, "invalid-token", err
+ } else {
+ // fmt.Println("Extracted Token:", tokenData)
+ }
+
+ token, err := jwt.ParseWithClaims(tokenData, &models.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
+ return secretKey, nil
+ })
- // Sign the token with the secret key
- tokenString, err := token.SignedString(secretKey)
if err != nil {
- return "", err
+ return 0, "invalid-token", err
+ }
+
+ // Extract the claims
+ claims, ok := token.Claims.(*models.CustomClaims)
+ if !ok || !token.Valid {
+ return 0, "invalid-token", err
+ }
+ if claims.ExpiresAt != nil && claims.ExpiresAt.Time.Before(time.Now()) {
+ return 0, "expired", err
}
- return tokenString, nil
+ return claims.UserID, "valid", err
}
func VerifyPassword(hashedPassword, password string) error {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_change_password_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_change_password_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..80aa4ac00b85babbbbbcab4de6c7d1fa3514ea6a
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_change_password_controller.go
@@ -0,0 +1,21 @@
+package user
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func ChangePassword(c *gin.Context) {
+ authentication := services.AuthenticationService{}
+ changePasswordController := controller.Controller[models.ChangePasswordRequest, models.Account, models.AuthenticatedUser]{
+ Service: &authentication.Service,
+ }
+ changePasswordController.HeaderParse(c, func() {
+ changePasswordController.Service.Constructor.Id = uint(changePasswordController.AccountData.UserID)
+ })
+ changePasswordController.RequestJSON(c, func() {
+ authentication.Update(changePasswordController.Request.OldPassword, changePasswordController.Request.NewPassword)
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_login_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_login_controller.go
index b7b9673901e26d2dca8bff51c4fdf834eef30646..679fc5a018d75e815dea00cca2012d5d56034eed 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_login_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_login_controller.go
@@ -9,7 +9,7 @@ import (
func Login(c *gin.Context) {
authentication := services.AuthenticationService{}
- loginController := controller.Controller[models.LoginRequest, services.LoginConstructor, models.AuthenticatedUser]{
+ loginController := controller.Controller[models.LoginRequest, models.Account, models.AuthenticatedUser]{
Service: &authentication.Service,
}
loginController.RequestJSON(c, func() {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go
index 177520bc297345eaaf66099fe9a71efb8e53b5c5..f30ee7177fa8bd15be000bcfe941ceb2732a74c1 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go
@@ -1,8 +1,6 @@
package user
import (
- "fmt"
-
"api.qobiltu.id/controller"
"api.qobiltu.id/models"
"api.qobiltu.id/services"
@@ -11,12 +9,11 @@ import (
func Profile(c *gin.Context) {
userProfile := services.UserProfileService{}
- userProfileController := controller.Controller[any, services.UserProfileConstructor, models.Account]{
+ userProfileController := controller.Controller[any, models.AccountDetails, models.AccountDetails]{
Service: &userProfile.Service,
}
- fmt.Println(userProfileController.AccountData)
userProfileController.HeaderParse(c, func() {
- userProfileController.Service.Constructor.AccountId = userProfileController.AccountData.UserID
+ userProfileController.Service.Constructor.AccountID = uint(userProfileController.AccountData.UserID)
userProfile.Retrieve()
userProfileController.Response(c)
},
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
index 64072242e2eea309c68624c2b86ad02b3596095b..4821ed13bbf913726e6512251db3bf43728a8a19 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
@@ -1,8 +1,6 @@
package user
import (
- "fmt"
-
"api.qobiltu.id/controller"
"api.qobiltu.id/models"
"api.qobiltu.id/services"
@@ -11,14 +9,16 @@ import (
func UpdateProfile(c *gin.Context) {
userProfile := services.UserProfileService{}
- userUpdateProfileController := controller.Controller[any, services.UserProfileConstructor, models.Account]{
+ userUpdateProfileController := controller.Controller[models.AccountDetails, models.AccountDetails, models.AccountDetails]{
Service: &userProfile.Service,
}
- fmt.Println(userUpdateProfileController.AccountData)
- userUpdateProfileController.HeaderParse(c, func() {
- userUpdateProfileController.Service.Constructor.AccountId = userUpdateProfileController.AccountData.UserID
- userProfile.Retrieve()
- userUpdateProfileController.Response(c)
+
+ userUpdateProfileController.RequestJSON(c, func() {
+ userUpdateProfileController.Service.Constructor = userUpdateProfileController.Request
+ userUpdateProfileController.HeaderParse(c, func() {
+ userUpdateProfileController.Service.Constructor.AccountID = uint(userUpdateProfileController.AccountData.UserID)
+ })
+ userProfile.Update()
},
)
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/logserror_log.txt b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/logserror_log.txt
index 30ecdec40a51b2cc4d2181b7108e953e7c77166f..58309e5256ae56f8b8feef85a64e685cfdc86000 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/logserror_log.txt
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/logserror_log.txt
@@ -1 +1,3 @@
2025/03/18 21:08:07 Error Log : ERROR: relasi « email_verifications » tidak ada (SQLSTATE 42P01)
+2025/03/22 14:00:34 Error Log : duplicated key not allowed
+2025/03/22 16:57:13 Error Log : ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification (SQLSTATE 42P10)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
index bb4170dfd5e176800d18a19e52ba8feb55531b1d..dfb4bfd35cd08440c3d106dd517a1821d90460c8 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
@@ -14,5 +14,9 @@ type RegisterRequest struct {
type CreateEmailVerificationRequest struct {
AccountID int `json:"account_id" binding:"required"`
+}
+type ChangePasswordRequest struct {
+ OldPassword string `json:"old_password" binding:"required" `
+ NewPassword string `json:"new_password" binding:"required" `
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
index ef028bd358dd2e0f419ad4cbbad7eb3769fb1477..686ccc92e7a56587581e345bbb3dca284fb1d7da 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
@@ -1,6 +1,8 @@
package repositories
import (
+ "fmt"
+
"api.qobiltu.id/models"
)
@@ -15,9 +17,9 @@ func GetAccountbyEmail(email string) Repository[models.Account, models.Account]
return *repo
}
-func GetAccountbyId(account_id uint) Repository[models.Account, models.Account] {
+func GetAccountById(accountId uint) Repository[models.Account, models.Account] {
repo := Construct[models.Account, models.Account](
- models.Account{Id: account_id},
+ models.Account{Id: accountId},
)
repo.Transactions(
WhereGivenConstructor[models.Account, models.Account],
@@ -25,6 +27,28 @@ func GetAccountbyId(account_id uint) Repository[models.Account, models.Account]
)
return *repo
}
+
+func UpdateAccount(account models.Account) Repository[models.Account, models.Account] {
+ repo := Construct[models.Account, models.Account](
+ account,
+ )
+ Update(repo)
+ return *repo
+}
+
+func GetDetailAccountById(accountId uint) Repository[models.AccountDetails, models.AccountDetails] {
+ repo := Construct[models.AccountDetails, models.AccountDetails](
+ models.AccountDetails{AccountID: accountId},
+ )
+
+ // fmt.Println("Account ID:", repo.Constructor.AccountID)
+ repo.Transactions(
+ WhereGivenConstructor[models.AccountDetails, models.AccountDetails],
+ Find[models.AccountDetails, models.AccountDetails],
+ )
+ return *repo
+}
+
func CreateAccount(account models.Account) Repository[models.Account, models.Account] {
repo := Construct[models.Account, models.Account](
account,
@@ -33,6 +57,22 @@ func CreateAccount(account models.Account) Repository[models.Account, models.Acc
return *repo
}
-// func UpdateAccount(account models.Account) Repository[models.Account, models.Account] {
-// repo := Construct
-// }
+func CreateAccountDetails(accountDetails models.AccountDetails) Repository[models.AccountDetails, models.AccountDetails] {
+ repo := Construct[models.AccountDetails, models.AccountDetails](
+ accountDetails,
+ )
+ fmt.Println(accountDetails)
+ fmt.Println("Account ID : ", accountDetails.AccountID)
+ Create(repo)
+ return *repo
+}
+
+func UpdateAccountDetails(accountDetails models.AccountDetails) Repository[models.AccountDetails, models.AccountDetails] {
+ repo := Construct[models.AccountDetails, models.AccountDetails](
+ accountDetails,
+ )
+ fmt.Println(accountDetails)
+ fmt.Println("Account ID : ", accountDetails.AccountID)
+ Update(repo)
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repository.go
index 3617e40936bf9e50e39a50285105622f080d210f..0c6b1ee5c41b63f4414e7013717d7fbd58a7dbf4 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repository.go
@@ -48,6 +48,7 @@ func Construct[TConstructor any, TResult any](constructor ...TConstructor) *Repo
Transaction: config.DB.Begin(),
}
}
+
func (repo *Repository[T1, T2]) Transactions(transactions ...func(*Repository[T1, T2]) *gorm.DB) {
for _, tx := range transactions {
repo.Transaction = tx(repo)
@@ -56,6 +57,7 @@ func (repo *Repository[T1, T2]) Transactions(transactions ...func(*Repository[T1
}
}
}
+
func WhereGivenConstructor[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
tx := repo.Transaction.Where(&repo.Constructor)
repo.RowsCount = int(tx.RowsAffected)
@@ -63,6 +65,7 @@ func WhereGivenConstructor[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
repo.RowsError = tx.Error
return tx
}
+
func Find[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
tx := repo.Transaction.Find(&repo.Result)
repo.RowsCount = int(tx.RowsAffected)
@@ -93,6 +96,7 @@ func Update[T1 any](repo *Repository[T1, T1]) *gorm.DB {
repo.RowsCount = int(tx.RowsAffected)
repo.NoRecord = repo.RowsCount == 0
repo.RowsError = tx.Error
+ repo.Result = repo.Constructor
return tx
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/user_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/user_route.go
index c1abce045debeff19dabe5af38b9903abd5083a0..1e5f9e405b0d936a86aed41c552cd8030fdc12bb 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/user_route.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/user_route.go
@@ -12,5 +12,7 @@ func UserRoute(router *gin.Engine) {
routerGroup.POST("/login", UserController.Login)
routerGroup.POST("/register", UserController.Register)
routerGroup.GET("/me", middleware.AuthUser, UserController.Profile)
+ routerGroup.PUT("/me", middleware.AuthUser, UserController.UpdateProfile)
+ routerGroup.PUT("/change-password", middleware.AuthUser, UserController.ChangePassword)
}
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..22a82cb0fe7042139cfa992ca4ac35bfcc9b4490
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/authentication_service.go
@@ -0,0 +1,71 @@
+package services
+
+import (
+ "errors"
+ "fmt"
+
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+)
+
+type AuthenticationService struct {
+ Service[models.Account, models.AuthenticatedUser]
+}
+
+func (s *AuthenticationService) Authenticate() {
+ accountData := repositories.GetAccountbyEmail(s.Constructor.Email)
+ if accountData.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "there is no account with given credentials!"
+ return
+ }
+ if VerifyPassword(accountData.Result.Password, s.Constructor.Password) != nil {
+ s.Exception.Unauthorized = true
+ s.Exception.Message = "incorrect password!"
+ return
+ }
+
+ token, err_tok := GenerateToken(&accountData.Result)
+
+ if err_tok != nil {
+ s.Error = errors.Join(s.Error, err_tok)
+ }
+
+ accountData.Result.Password = "SECRET"
+ s.Result = models.AuthenticatedUser{
+ Account: accountData.Result,
+ Token: token,
+ }
+ s.Error = accountData.RowsError
+}
+
+func (s *AuthenticationService) Update(oldPassword string, newPassword string) {
+ if len(newPassword) < 8 {
+ s.Exception.InvalidPasswordLength = true
+ s.Exception.Message = "Password must have at least 8 characters!"
+ return
+ }
+ accountData := repositories.GetAccountbyId(s.Constructor.Id)
+
+ if accountData.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "there is no account with given credentials!"
+ return
+ }
+ fmt.Println("Result Password", accountData.Result.Password)
+ fmt.Println("old Password given", oldPassword)
+ if VerifyPassword(accountData.Result.Password, oldPassword) != nil {
+ s.Exception.Unauthorized = true
+ s.Exception.Message = "incorrect old password!"
+ return
+ }
+ accountData.Result.Password = newPassword
+ changePassword := repositories.UpdateAccount(accountData.Result)
+ changePassword.Result.Password = "SECRET"
+ s.Result = models.AuthenticatedUser{
+ Account: changePassword.Result,
+ }
+ s.Error = changePassword.RowsError
+}
+
+// LoginHandler handles user login
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
index f9e0d2216bd033155ffcbd73dd1b9de44d20bfa8..4802c35b8bd0a7f5b29214a68fc8f6eb1ea8dfde 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
@@ -33,6 +33,13 @@ func (s *RegisterService) Create() {
s.Exception.Message = "Bad request!"
return
}
+ userProfile := UserProfileService{}
+ userProfile.Constructor.AccountID = accountCreated.Result.Id
+ userProfile.Create()
+ if userProfile.Error != nil {
+ s.Error = userProfile.Error
+ return
+ }
s.Error = accountCreated.RowsError
s.Result = accountCreated.Result
s.Result.Password = "SECRET"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
index afea5ccbf0885293e2f782692e229b6948e2228e..419458a7d1e4845f1e4049bc3c3edabbbef748fb 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
@@ -5,33 +5,38 @@ import (
"api.qobiltu.id/repositories"
)
-type UserProfileConstructor struct {
- AccountId int
-}
type UserProfileService struct {
- Service[UserProfileConstructor, models.Account]
+ Service[models.AccountDetails, models.AccountDetails]
}
+func (s *UserProfileService) Create() {
+ userProfile := repositories.CreateAccountDetails(s.Constructor)
+ s.Error = userProfile.RowsError
+ if userProfile.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account with given credentials!"
+ return
+ }
+ s.Result = userProfile.Result
+}
func (s *UserProfileService) Retrieve() {
- userProfile := repositories.GetAccountbyId(uint(s.Constructor.AccountId))
+ userProfile := repositories.GetDetailAccountById(s.Constructor.AccountID)
s.Error = userProfile.RowsError
if userProfile.NoRecord {
s.Exception.DataNotFound = true
s.Exception.Message = "There is no account with given credentials!"
return
}
- s.Result.Password = "SECRET"
s.Result = userProfile.Result
}
func (s *UserProfileService) Update() {
- userProfile := repositories.GetAccountbyId(uint(s.Constructor.AccountId))
+ userProfile := repositories.UpdateAccountDetails(s.Constructor)
s.Error = userProfile.RowsError
if userProfile.NoRecord {
s.Exception.DataNotFound = true
s.Exception.Message = "There is no account with given credentials!"
return
}
- s.Result.Password = "SECRET"
s.Result = userProfile.Result
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
index 39a67571ac3a2e6a6e2a972b9550becd551bdded..65c8cabfc8ef2a61bf1694229cb9bf3126a94f4b 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
@@ -1,4 +1,4 @@
-name: Deploy to Development via Huggingface
+name: Deploy Golang App
on:
push:
@@ -6,47 +6,23 @@ on:
- main
jobs:
- deploy-to-huggingface:
+ deploy:
runs-on: ubuntu-latest
steps:
- # Checkout repository
- name: Checkout Repository
uses: actions/checkout@v3
- # Setup Git
- - name: Setup Git for Huggingface
- run: |
- git config --global user.email "abdan.hafidz@gmail.com"
- git config --global user.name "abdanhafidz"
-
- # Clone Huggingface Space Repository
- - name: Clone Huggingface Space
- env:
- HF_TOKEN: ${{ secrets.HF_TOKEN }}
- run: |
- git clone https://huggingface.co/spaces/lifedebugger/api-qobiltu-dev space
-
- # Update Git Remote URL and Pull Latest Changes
- - name: Update Remote and Pull Changes
- env:
- HF_TOKEN: ${{ secrets.HF_TOKEN }}
- run: |
- cd space
- git remote set-url origin https://lifedebugger:$HF_TOKEN@huggingface.co/spaces/lifedebugger/api-qobiltu-dev
- git pull origin main || echo "No changes to pull"
-
- # Copy Files to Huggingface Space
- - name: Copy Files to Space
- run: |
- rsync -av --exclude='.git' ./ space/
+ - name: Set up SSH key
+ uses: webfactory/ssh-agent@v0.5.4
+ with:
+ ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- # Commit and Push to Huggingface Space
- - name: Commit and Push to Huggingface
- env:
- HF_TOKEN: ${{ secrets.HF_TOKEN }}
+ - name: Deploy to VPS
run: |
- cd space
- git add .
- git commit -m "Deploy files from GitHub repository" || echo "No changes to commit"
- git push origin main || echo "No changes to push"
+ ssh -o StrictHostKeyChecking=no qobiltu@103.23.199.136 << 'EOF'
+ cd /home/qobiltu/api-qobiltu
+ git pull origin main
+ docker-compose down -v
+ docker-compose up --build -d
+ EOF
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main_huggingface.yml b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main_huggingface.yml
new file mode 100644
index 0000000000000000000000000000000000000000..39a67571ac3a2e6a6e2a972b9550becd551bdded
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main_huggingface.yml
@@ -0,0 +1,52 @@
+name: Deploy to Development via Huggingface
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ deploy-to-huggingface:
+ runs-on: ubuntu-latest
+
+ steps:
+ # Checkout repository
+ - name: Checkout Repository
+ uses: actions/checkout@v3
+
+ # Setup Git
+ - name: Setup Git for Huggingface
+ run: |
+ git config --global user.email "abdan.hafidz@gmail.com"
+ git config --global user.name "abdanhafidz"
+
+ # Clone Huggingface Space Repository
+ - name: Clone Huggingface Space
+ env:
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
+ run: |
+ git clone https://huggingface.co/spaces/lifedebugger/api-qobiltu-dev space
+
+ # Update Git Remote URL and Pull Latest Changes
+ - name: Update Remote and Pull Changes
+ env:
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
+ run: |
+ cd space
+ git remote set-url origin https://lifedebugger:$HF_TOKEN@huggingface.co/spaces/lifedebugger/api-qobiltu-dev
+ git pull origin main || echo "No changes to pull"
+
+ # Copy Files to Huggingface Space
+ - name: Copy Files to Space
+ run: |
+ rsync -av --exclude='.git' ./ space/
+
+ # Commit and Push to Huggingface Space
+ - name: Commit and Push to Huggingface
+ env:
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
+ run: |
+ cd space
+ git add .
+ git commit -m "Deploy files from GitHub repository" || echo "No changes to commit"
+ git push origin main || echo "No changes to push"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
index 6183c94745bf456bad5ea308482bd6c3602ad543..d7d9de6f6ae86a458866ad773e9886db45197e9b 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
@@ -1,30 +1,37 @@
-# Gunakan image dasar Golang versi 1.21.6
-FROM golang:1.21.6
-
-# Set working directory
-WORKDIR /app
-
-# Copy go.mod dan go.sum
-COPY go.mod go.sum ./
-
-# Download dependencies
-RUN go mod download
-
-# Copy seluruh kode
-COPY . .
-
-# Buat file .env dengan variabel environment yang dibutuhkan
-RUN echo "DB_HOST=aws-0-ap-southeast-1.pooler.supabase.com" >> .env && \
- echo "DB_USER=postgres.rdscploxoikqsevhduii" >> .env && \
- echo "DB_PASSWORD=Qobiltu12233334444" >> .env && \
- echo "DB_PORT=5432" >> .env && \
- echo "DB_NAME=postgres" >> .env && \
- echo "HOST_ADDRESS = 0.0.0.0" >> .env && \
- echo "HOST_PORT = 7860" >> .env && \
- echo "SALT=NZNZtY7dNPz8l0dWINJZLKafWaJrql1s" >> .env && \
- echo "LOG_PATH = logs" >> .env
-# Build aplikasi
-RUN go build -o main .
-
-# Jalankan aplikasi
-CMD ["./main"]
+# Gunakan image dasar Golang versi 1.24.1
+FROM golang:1.24.1 AS builder
+
+# Set working directory
+WORKDIR /app
+
+# Copy go.mod dan go.sum
+COPY go.mod go.sum ./
+
+# Download dependencies
+RUN go mod download
+
+# Copy seluruh kode
+COPY . .
+
+# Build aplikasi
+# RUN go build -o /app/main .
+
+# Build aplikasi untuk Linux
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /app/main .
+
+# Stage 2: Gunakan image runtime yang lebih kecil
+FROM alpine:latest
+
+# Set working directory
+WORKDIR /app
+
+# Copy hasil build dari builder ke image runtime
+COPY --from=builder /app/main .
+
+# Copy file .env untuk konfigurasi environment
+COPY .env .env
+
+RUN chmod +x /app/main
+
+# Jalankan aplikasi
+CMD ["./main"]
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a821c467525af186355b5837f8673e77a84f7563
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yml
@@ -0,0 +1,31 @@
+version: '3.8'
+
+services:
+ app:
+ container_name: api-qobiltu
+ build: .
+ depends_on:
+ - db
+ env_file: .env
+ ports:
+ - "8080:8080"
+ # volumes:
+ # - ./logs:/app/logs
+ # - /home/qobiltu/api-qobiltu:/app
+ restart: unless-stopped
+
+ db:
+ image: postgres:15
+ container_name: postgres-db
+ environment:
+ POSTGRES_USER: ${DB_USER}
+ POSTGRES_PASSWORD: ${DB_PASSWORD}
+ POSTGRES_DB: ${DB_NAME}
+ ports:
+ - "5432:5432"
+ volumes:
+ - db-data:/var/lib/postgresql/data
+ restart: always
+
+volumes:
+ db-data:
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
index bd10d926e1c503ff128e21bf273fe2732e526abf..df8cf1c15e2917803db7f00fbc386495fe8f5479 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
@@ -1,4 +1,5 @@
.env
vendor/
quzuu-be.exe
-README.md
\ No newline at end of file
+README.md
+.qodo
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
index 96d88fd641bf8a6b538d1324abff58c8d5b912a0..823888a64fd867db2cee438c8cb64f792ec16920 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
@@ -2,6 +2,7 @@ package config
import (
"os"
+ "strconv"
"github.com/joho/godotenv"
)
@@ -10,6 +11,7 @@ var TCP_ADDRESS string
var LOG_PATH string
var HOST_ADDRESS string
var HOST_PORT string
+var EMAIL_VERIFICATION_DURATION int
func init() {
godotenv.Load()
@@ -17,5 +19,6 @@ func init() {
HOST_PORT = os.Getenv("HOST_PORT")
TCP_ADDRESS = HOST_ADDRESS + ":" + HOST_PORT
LOG_PATH = os.Getenv("LOG_PATH")
+ EMAIL_VERIFICATION_DURATION, _ = strconv.Atoi(os.Getenv("EMAIL_VERIFICATION_DURATION"))
// Menampilkan nilai variabel lingkungan
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
index 458388d26dd8408234352e2a835552d6a278f267..3eea63fb7b3d4ffd51970404cadf1af8267c046d 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
@@ -51,7 +51,17 @@ func AutoMigrateAll(db *gorm.DB) {
err := db.AutoMigrate(
&models.Account{},
&models.AccountDetails{},
+ &models.EmailVerification{},
+ &models.ExternalAuth{},
+ &models.FCM{},
+ &models.ForgotPassword{},
+ &models.Academy{},
+ &models.AcademyMaterial{},
+ &models.AcademyContent{},
+ &models.AcademyMaterialProgress{},
+ &models.AcademyContentProgress{},
)
+
if err != nil {
log.Fatal(err)
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
index 32a528d703f974515cc1baa4960e87ac56481629..456c424696e845d0f994bd91a43f42623d427045 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
@@ -19,6 +19,13 @@ type (
}
)
+func (controller *Controller[T1, T2, T3]) HeaderParse(c *gin.Context, act func()) {
+ cParam, _ := c.Get("accountData")
+ if cParam != nil {
+ controller.AccountData = cParam.(models.AccountData)
+ }
+ act()
+}
func (controller *Controller[T1, T2, T3]) RequestJSON(c *gin.Context, act func()) {
cParam, _ := c.Get("accountData")
if cParam != nil {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification.go
new file mode 100644
index 0000000000000000000000000000000000000000..44ee9b005702e30354ef25f46d80e9551dbfcf6b
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_create_verification.go
@@ -0,0 +1,22 @@
+package controller
+
+import (
+ "strconv"
+
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func CreateVerification(c *gin.Context) {
+ emailVerification := services.EmailVerificationService{}
+ emailVerificationController := controller.Controller[any, models.EmailVerification, models.EmailVerification]{
+ Service: &emailVerification.Service,
+ }
+ query, _ := c.GetQuery("account_id")
+ accountId, _ := strconv.Atoi(query)
+ emailVerificationController.Service.Constructor.AccountID = uint(accountId)
+ emailVerification.Create()
+ emailVerificationController.Response(c)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_delete_verification.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_delete_verification.go
new file mode 100644
index 0000000000000000000000000000000000000000..2cd98fbc53a1234cdcbec565d8d42c4247936dd7
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_delete_verification.go
@@ -0,0 +1,22 @@
+package controller
+
+import (
+ "strconv"
+
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func DeleteVerification(c *gin.Context) {
+ emailVerification := services.EmailVerificationService{}
+ emailVerificationController := controller.Controller[any, models.EmailVerification, models.EmailVerification]{
+ Service: &emailVerification.Service,
+ }
+ query, _ := c.GetQuery("account_id")
+ accountId, _ := strconv.Atoi(query)
+ emailVerificationController.Service.Constructor.AccountID = uint(accountId)
+ emailVerification.Delete()
+ emailVerificationController.Response(c)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_verify.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_verify.go
new file mode 100644
index 0000000000000000000000000000000000000000..695bbccdbfbd8dedf63048f8734c4fe879544d33
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/email/email_verify.go
@@ -0,0 +1,22 @@
+package controller
+
+import (
+ "strconv"
+
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Verify(c *gin.Context) {
+ emailVerification := services.EmailVerificationService{}
+ emailVerificationController := controller.Controller[any, models.EmailVerification, models.EmailVerification]{
+ Service: &emailVerification.Service,
+ }
+ query, _ := c.GetQuery("account_id")
+ accountId, _ := strconv.Atoi(query)
+ emailVerificationController.Service.Constructor.AccountID = uint(accountId)
+ emailVerification.Validate()
+ emailVerificationController.Response(c)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_login_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_login_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..b7b9673901e26d2dca8bff51c4fdf834eef30646
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_login_controller.go
@@ -0,0 +1,20 @@
+package user
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Login(c *gin.Context) {
+ authentication := services.AuthenticationService{}
+ loginController := controller.Controller[models.LoginRequest, services.LoginConstructor, models.AuthenticatedUser]{
+ Service: &authentication.Service,
+ }
+ loginController.RequestJSON(c, func() {
+ loginController.Service.Constructor.Email = loginController.Request.Email
+ loginController.Service.Constructor.Password = loginController.Request.Password
+ authentication.Authenticate()
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..177520bc297345eaaf66099fe9a71efb8e53b5c5
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_profile_controller.go
@@ -0,0 +1,24 @@
+package user
+
+import (
+ "fmt"
+
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Profile(c *gin.Context) {
+ userProfile := services.UserProfileService{}
+ userProfileController := controller.Controller[any, services.UserProfileConstructor, models.Account]{
+ Service: &userProfile.Service,
+ }
+ fmt.Println(userProfileController.AccountData)
+ userProfileController.HeaderParse(c, func() {
+ userProfileController.Service.Constructor.AccountId = userProfileController.AccountData.UserID
+ userProfile.Retrieve()
+ userProfileController.Response(c)
+ },
+ )
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_register_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_register_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..17051bee42f7b1d3fe144cedb6e8f11dbb5ac5b2
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_register_controller.go
@@ -0,0 +1,20 @@
+package user
+
+import (
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func Register(c *gin.Context) {
+ register := services.RegisterService{}
+ registerController := controller.Controller[models.RegisterRequest, models.Account, models.Account]{
+ Service: ®ister.Service,
+ }
+ registerController.RequestJSON(c, func() {
+ registerController.Service.Constructor.Password = registerController.Request.Password
+ registerController.Service.Constructor.Email = registerController.Request.Email
+ register.Create()
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..64072242e2eea309c68624c2b86ad02b3596095b
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/user/user_update_profile_controller.go
@@ -0,0 +1,24 @@
+package user
+
+import (
+ "fmt"
+
+ "api.qobiltu.id/controller"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func UpdateProfile(c *gin.Context) {
+ userProfile := services.UserProfileService{}
+ userUpdateProfileController := controller.Controller[any, services.UserProfileConstructor, models.Account]{
+ Service: &userProfile.Service,
+ }
+ fmt.Println(userUpdateProfileController.AccountData)
+ userUpdateProfileController.HeaderParse(c, func() {
+ userUpdateProfileController.Service.Constructor.AccountId = userUpdateProfileController.AccountData.UserID
+ userProfile.Retrieve()
+ userUpdateProfileController.Response(c)
+ },
+ )
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yaml b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..daf0d5d58dca553ce0c035978ec48d8d4fe52456
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/docker-compose.yaml
@@ -0,0 +1,10 @@
+services:
+ postgres:
+ container_name: postgres
+ image: postgres
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=qobiltu
+ ports:
+ - 5432:5432
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
index 9b6fd06b10d941bc76ba173ba5f2983e9c1408d8..0c2b7a92ff9c9eddbfa0d1506ccb9b4d0614163e 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
@@ -3,6 +3,7 @@ module api.qobiltu.id
go 1.24.0
require (
+ github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/joho/godotenv v1.5.1
@@ -46,4 +47,4 @@ require (
golang.org/x/text v0.23.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
-)
\ No newline at end of file
+)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
index 6f39995754d2f66f294c1866a62d1810e8d524c5..b7d196affb731541ab18f7ba85fbb21dd47867ac 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
@@ -10,6 +10,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
@@ -115,4 +117,4 @@ gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
-nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
\ No newline at end of file
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/logserror_log.txt b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/logserror_log.txt
new file mode 100644
index 0000000000000000000000000000000000000000..30ecdec40a51b2cc4d2181b7108e953e7c77166f
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/logserror_log.txt
@@ -0,0 +1 @@
+2025/03/18 21:08:07 Error Log : ERROR: relasi « email_verifications » tidak ada (SQLSTATE 42P01)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
index 44178a96738817742fd4f6f7fe1ca63bbecc2d27..41ed5f2fb2eefa1f43c991f2f502d39505c76e3b 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
@@ -3,52 +3,19 @@
package middleware
import (
- "errors"
"time"
"api.qobiltu.id/config"
"api.qobiltu.id/models"
+ "api.qobiltu.id/utils"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
- "golang.org/x/crypto/bcrypt"
)
-// Define a secret key for signing the JWT token
var salt = config.Salt
var secretKey = []byte(salt)
-// GenerateToken generates a JWT token for the given user
-func GenerateToken(user *models.Account) (string, error) {
-
- // Create a new token
- token := jwt.New(jwt.SigningMethodHS256)
-
- // Set claims
- claims := token.Claims.(jwt.MapClaims)
- claims["id"] = user.Id
- claims["exp"] = time.Now().Add(time.Hour * 24).Unix() // Token expires in 24 hours
-
- // Sign the token with the secret key
- tokenString, err := token.SignedString(secretKey)
- if err != nil {
- return "", err
- }
-
- return tokenString, nil
-}
-
// VerifyPassword verifies if the provided password matches the hashed password
-func VerifyPassword(hashedPassword, password string) error {
- err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
- if err != nil {
- return errors.New("invalid password")
- }
- return nil
-}
-func HashPassword(password string) (string, error) {
- bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
- return string(bytes), err
-}
type CustomClaims struct {
jwt.RegisteredClaims
@@ -84,21 +51,20 @@ func AuthUser(c *gin.Context) {
// fmt.Println("Verify Status :", currAccData.verifyStatus)
if currAccData.VerifyStatus == "invalid-token" || currAccData.VerifyStatus == "expired" {
currAccData.UserID = 0
- message := "Your session is expired, Please re-Login!"
- SendJSON401(c, &currAccData.VerifyStatus, &message)
+ utils.ResponseFAIL(c, 401, models.Exception{Unauthorized: true, Message: "Your session is expired, Please re-Login!"})
c.Abort()
return
+ } else {
+ c.Set("accountData", currAccData)
+ c.Next()
}
} else {
currAccData.UserID = 0
currAccData.VerifyStatus = "no-token"
currAccData.ErrVerif = nil
- message := "You have to Login First!"
- SendJSON401(c, &currAccData.VerifyStatus, &message)
+ utils.ResponseFAIL(c, 401, models.Exception{Unauthorized: true, Message: "You have to login first!"})
c.Abort()
return
}
- c.Set("accountData", currAccData)
- c.Next()
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/response_middleware.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/response_middleware.go
index 55f3385077839115eadb8dace294229f4b4c3df2..d52391cd3607e6ec2697e6a5813b348d49ff5ee2 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/response_middleware.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/response_middleware.go
@@ -9,31 +9,37 @@ import (
// SendJSON200 sends a JSON response with HTTP status code 200
func SendJSON200(c *gin.Context, data interface{}) {
c.JSON(http.StatusOK, gin.H{"status": "success", "data": data})
+ return
}
// SendJSON400 sends a JSON response with HTTP status code 400
func SendJSON400(c *gin.Context, error_status *string, message *string) {
c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error-status": error_status, "message": message})
+ return
}
// SendJSON401 sends a JSON response with HTTP status code 401
func SendJSON401(c *gin.Context, error_status *string, message *string) {
c.JSON(http.StatusUnauthorized, gin.H{"status": "error", "error-status": error_status, "message": message})
+ return
}
// SendJSON403 sends a JSON response with HTTP status code 403
func SendJSON403(c *gin.Context, message *string) {
c.JSON(http.StatusForbidden, gin.H{"status": "error", "message": message})
+ return
}
// SendJSON404 sends a JSON response with HTTP status code 404
func SendJSON404(c *gin.Context, message *string) {
c.JSON(http.StatusNotFound, gin.H{"status": "error", "message": message})
+ return
}
// SendJSON500 sends a JSON response with HTTP status code 500
func SendJSON500(c *gin.Context, error_status *string, message *string) {
c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "error-status": error_status, "message": message})
+ return
}
// JSONResponseMiddleware is a middleware that provides functions for sending JSON responses
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
index 82e8b95b39b86be5fb0a4ce538d697e9df8fd0d5..6d1cb93a61de13e3ae0f178cc701c77ed791ae7a 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -7,25 +7,113 @@ import (
)
type Account struct {
- Id uint `gorm:"primaryKey" json:"id"`
- UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
- Email string `gorm:"uniqueIndex" json:"email"`
- Password string `json:"password"`
- IsEmailVerified bool `json:"is_email_verified"`
- CreatedAt time.Time `json:"created_at"`
- DeletedAt time.Time `json:"deleted_at"`
+ Id uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
+ Email string `gorm:"uniqueIndex" json:"email"`
+ Password string `json:"password"`
+ IsEmailVerified bool `json:"is_email_verified"`
+ IsDetailCompleted bool `json:"is_detail_completed"`
+ CreatedAt time.Time `json:"created_at"`
+ DeletedAt *time.Time `json:"deleted_at,omitempty" gorm:"default:null"`
}
type AccountDetails struct {
- IDDetail uint `gorm:"primaryKey" json:"id_detail"`
- Account_id uint `json:"id_account"`
- Province string `json:"province"`
- City string `json:"city"`
- Institution string `json:"institution"`
- UpdatedAt time.Time `json:"updated_at"`
- DeletedAt time.Time `json:"deleted_at"`
+ ID uint `gorm:"primaryKey" json:"id"`
+ AccountID uint `json:"account_id"`
+ InitialName string `json:"initial_name"`
+ FullName *string `json:"full_name,omitempty"`
+ DateOfBirth *time.Time `json:"date_of_birth,omitempty"`
+ PlaceOfBirth *string `json:"place_of_birth,omitempty"`
+ Domicile *string `json:"domicile,omitempty"`
+ LastJob *string `json:"last_job,omitempty"`
+ Gender *bool `json:"gender,omitempty"`
+ LastEducation *string `json:"last_education,omitempty"`
+ MaritalStatus *bool `json:"marital_status,omitempty"`
+ Avatar *string `json:"avatar,omitempty"`
+ PhoneNumber *uint `json:"phone_number,omitempty"`
+}
+
+type EmailVerification struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ Token uint `json:"token"`
+ AccountID uint `json:"account_id"`
+ IsExpired bool `json:"is_expired"`
+ CreatedAt time.Time `json:"created_at"`
+ ExpiredAt time.Time `json:"expired_at"`
+}
+
+type ExternalAuth struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ OauthID string `json:"oauth_id"`
+ AccountID uint `json:"account_id"`
+ OauthProvider string `json:"oauth_provider"`
+}
+
+type FCM struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ AccountID uint `json:"account_id"`
+ FCMToken string `json:"fcm_token"`
+}
+
+type ForgotPassword struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uint `json:"uuid"`
+ AccountID uint `json:"account_id"`
+ IsExpired bool `json:"is_expired"`
+ CreatedAt time.Time `json:"created_at"`
+ ExpiredAt time.Time `json:"expired_at"`
+}
+
+type Academy struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
+ Title string `json:"title"`
+ Slug string `json:"slug"`
+ Description string `json:"description"`
+}
+
+type AcademyMaterial struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
+ AcademyID uint `json:"academy_id"`
+ Title string `json:"title"`
+ Slug string `json:"slug"`
+ Description string `json:"description"`
+}
+
+type AcademyContent struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uint `json:"uuid"`
+ Title string `json:"title"`
+ Order uint `json:"order"`
+ AcademyMaterialID uint `json:"academy_material_id"`
+ Description string `json:"description"`
+}
+
+type AcademyMaterialProgress struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
+ AccountID uint `json:"account_id"`
+ AcademyMaterialID uint `json:"academy_material_id"`
+ Progress uint `json:"progress"`
+}
+
+type AcademyContentProgress struct {
+ ID uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid"`
+ AccountID uint `json:"account_id"`
+ AcademyID uint `json:"academy_id"`
}
// Gorm table name settings
-func (Account) TableName() string { return "account" }
-func (AccountDetails) TableName() string { return "account_details" }
+func (Account) TableName() string { return "account" }
+func (AccountDetails) TableName() string { return "account_details" }
+func (EmailVerification) TableName() string { return "email_verifications" }
+func (ExternalAuth) TableName() string { return "extern_auth" }
+func (FCM) TableName() string { return "fcm" }
+func (ForgotPassword) TableName() string { return "forgot_password" }
+func (Academy) TableName() string { return "academy" }
+func (AcademyMaterial) TableName() string { return "academy_materials" }
+func (AcademyContent) TableName() string { return "academy_contents" }
+func (AcademyMaterialProgress) TableName() string { return "academy_materials_progress" }
+func (AcademyContentProgress) TableName() string { return "academy_contents_progress" }
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
index cfae888ec34edbdd3f5b6588050e2b382c63792d..bb4170dfd5e176800d18a19e52ba8feb55531b1d 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
@@ -11,3 +11,8 @@ type RegisterRequest struct {
Phone int `json:"phone"`
Password string `json:"password" binding:"required"`
}
+
+type CreateEmailVerificationRequest struct {
+ AccountID int `json:"account_id" binding:"required"`
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
index 41bd903aff7bf12c9d81cbed1ad00521ae629640..ef028bd358dd2e0f419ad4cbbad7eb3769fb1477 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
@@ -15,6 +15,16 @@ func GetAccountbyEmail(email string) Repository[models.Account, models.Account]
return *repo
}
+func GetAccountbyId(account_id uint) Repository[models.Account, models.Account] {
+ repo := Construct[models.Account, models.Account](
+ models.Account{Id: account_id},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.Account, models.Account],
+ Find[models.Account, models.Account],
+ )
+ return *repo
+}
func CreateAccount(account models.Account) Repository[models.Account, models.Account] {
repo := Construct[models.Account, models.Account](
account,
@@ -22,3 +32,7 @@ func CreateAccount(account models.Account) Repository[models.Account, models.Acc
Create(repo)
return *repo
}
+
+// func UpdateAccount(account models.Account) Repository[models.Account, models.Account] {
+// repo := Construct
+// }
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/email_verification_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/email_verification_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..c737e6d4b782e17630568f3616f0bf7ef36dc31f
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/email_verification_repository.go
@@ -0,0 +1,49 @@
+package repositories
+
+import (
+ "time"
+
+ "api.qobiltu.id/models"
+)
+
+func CreateEmailVerification(accountId uint, dueTime time.Time, token uint) Repository[models.EmailVerification, models.EmailVerification] {
+ repo := Construct[models.EmailVerification, models.EmailVerification](
+ models.EmailVerification{
+ AccountID: accountId,
+ IsExpired: false,
+ ExpiredAt: dueTime,
+ Token: token,
+ },
+ )
+ Create(repo)
+ return *repo
+}
+
+func GetEmailVerification(account_id uint, token uint) Repository[models.EmailVerification, models.EmailVerification] {
+ repo := Construct[models.EmailVerification, models.EmailVerification](
+ models.EmailVerification{
+ AccountID: account_id,
+ IsExpired: false,
+ Token: token,
+ },
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.EmailVerification, models.EmailVerification],
+ Find[models.EmailVerification, models.EmailVerification],
+ )
+ return *repo
+}
+
+func DeleteEmailVerification(token uint) Repository[models.EmailVerification, models.EmailVerification] {
+ repo := Construct[models.EmailVerification, models.EmailVerification](
+ models.EmailVerification{
+ Token: token,
+ },
+ )
+
+ repo.Transactions(
+ WhereGivenConstructor[models.EmailVerification, models.EmailVerification],
+ Delete[models.EmailVerification],
+ )
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/email_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/email_route.go
new file mode 100644
index 0000000000000000000000000000000000000000..8f2f59e88a8cf958d200ee36174549e839a0a33c
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/email_route.go
@@ -0,0 +1,15 @@
+package router
+
+import (
+ EmailController "api.qobiltu.id/controller/email"
+ "github.com/gin-gonic/gin"
+)
+
+func EmailRoute(router *gin.Engine) {
+ routerGroup := router.Group("/api/v1/email")
+ {
+ routerGroup.POST("/verify", EmailController.CreateVerification)
+ routerGroup.POST("/create-verification", EmailController.CreateVerification)
+ routerGroup.DELETE("/delete-verification", EmailController.DeleteVerification)
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
index 114a87bb4eb5bd9713a0cd097c9b46f05eb761df..a7dde2e239a2d254991cd00c712b9eeebe6102d1 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
@@ -8,11 +8,8 @@ import (
func StartService() {
router := gin.Default()
- routerGroup := router.Group("/api/v1")
- {
- routerGroup.GET("/", controller.HomeController)
- routerGroup.POST("/login", controller.LoginController)
- routerGroup.POST("/register", controller.RegisterController)
- }
+ router.GET("/", controller.HomeController)
+ UserRoute(router)
+ EmailRoute(router)
router.Run(config.TCP_ADDRESS)
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/user_route.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/user_route.go
new file mode 100644
index 0000000000000000000000000000000000000000..c1abce045debeff19dabe5af38b9903abd5083a0
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/user_route.go
@@ -0,0 +1,16 @@
+package router
+
+import (
+ UserController "api.qobiltu.id/controller/user"
+ "api.qobiltu.id/middleware"
+ "github.com/gin-gonic/gin"
+)
+
+func UserRoute(router *gin.Engine) {
+ routerGroup := router.Group("/api/v1/user")
+ {
+ routerGroup.POST("/login", UserController.Login)
+ routerGroup.POST("/register", UserController.Register)
+ routerGroup.GET("/me", middleware.AuthUser, UserController.Profile)
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..d94c65bc93550413c285e2817b68392664fb3b2a
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/email_verification_service.go
@@ -0,0 +1,57 @@
+package services
+
+import (
+ "math/rand"
+ "time"
+
+ "api.qobiltu.id/config"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+)
+
+type EmailVerificationService struct {
+ Service[models.EmailVerification, models.EmailVerification]
+}
+
+func (s *EmailVerificationService) Create() {
+ accountRepo := repositories.GetAccountbyId(s.Constructor.AccountID)
+ if accountRepo.NoRecord {
+ s.Error = accountRepo.RowsError
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account data with given credentials!"
+ return
+ }
+
+ remainingTime := time.Duration(config.EMAIL_VERIFICATION_DURATION) * time.Hour
+ dueTime := CalculateDueTime(remainingTime)
+
+ randomizer := rand.New(rand.NewSource(10))
+ token := uint(randomizer.Int())
+
+ repo := repositories.CreateEmailVerification(s.Constructor.AccountID, dueTime, token)
+
+ s.Error = repo.RowsError
+ s.Result = repo.Result
+}
+
+func (s *EmailVerificationService) Validate() {
+ repo := repositories.GetEmailVerification(s.Constructor.AccountID, s.Constructor.Token)
+ s.Error = repo.RowsError
+ if repo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "Invalid token!"
+ return
+ }
+ s.Result = repo.Result
+}
+
+func (s *EmailVerificationService) Delete() {
+ repo := repositories.DeleteEmailVerification(s.Constructor.Token)
+ s.Error = repo.RowsError
+ if repo.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "Invalid token!"
+ return
+ }
+ s.Result = repo.Result
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/jwt_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/jwt_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..92024df8afa7fe83da747bddc969cc1066cf431c
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/jwt_service.go
@@ -0,0 +1,45 @@
+package services
+
+import (
+ "errors"
+ "time"
+
+ "api.qobiltu.id/config"
+ "api.qobiltu.id/models"
+ "github.com/dgrijalva/jwt-go"
+ "golang.org/x/crypto/bcrypt"
+)
+
+var salt = config.Salt
+var secretKey = []byte(salt)
+
+func GenerateToken(user *models.Account) (string, error) {
+
+ // Create a new token
+ token := jwt.New(jwt.SigningMethodHS256)
+
+ // Set claims
+ claims := token.Claims.(jwt.MapClaims)
+ claims["id"] = user.Id
+ claims["exp"] = time.Now().Add(time.Hour * 24).Unix() // Token expires in 24 hours
+
+ // Sign the token with the secret key
+ tokenString, err := token.SignedString(secretKey)
+ if err != nil {
+ return "", err
+ }
+
+ return tokenString, nil
+}
+
+func VerifyPassword(hashedPassword, password string) error {
+ err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
+ if err != nil {
+ return errors.New("invalid password")
+ }
+ return nil
+}
+func HashPassword(password string) (string, error) {
+ bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
+ return string(bytes), err
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/login_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/login_service.go
index 06ea6bb57e6ccdb5fa55784476af50fcda77eff1..93256b042c4daf76a9f3501111792b940a5ff200 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/login_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/login_service.go
@@ -3,7 +3,6 @@ package services
import (
"errors"
- "api.qobiltu.id/middleware"
"api.qobiltu.id/models"
"api.qobiltu.id/repositories"
)
@@ -24,13 +23,13 @@ func (s *AuthenticationService) Authenticate() {
s.Exception.Message = "there is no account with given credentials!"
return
}
- if middleware.VerifyPassword(accountData.Result.Password, s.Constructor.Password) != nil {
+ if VerifyPassword(accountData.Result.Password, s.Constructor.Password) != nil {
s.Exception.Unauthorized = true
s.Exception.Message = "incorrect password!"
return
}
- token, err_tok := middleware.GenerateToken(&accountData.Result)
+ token, err_tok := GenerateToken(&accountData.Result)
if err_tok != nil {
s.Error = errors.Join(s.Error, err_tok)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
index 6732f99fabbd315504042de844f59841294e2025..f9e0d2216bd033155ffcbd73dd1b9de44d20bfa8 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
@@ -3,7 +3,6 @@ package services
import (
"errors"
- "api.qobiltu.id/middleware"
"api.qobiltu.id/models"
"api.qobiltu.id/repositories"
uuid "github.com/satori/go.uuid"
@@ -20,7 +19,7 @@ func (s *RegisterService) Create() {
s.Exception.Message = "Password must have at least 8 characters!"
return
}
- hashed_password, err_hash := middleware.HashPassword(s.Constructor.Password)
+ hashed_password, err_hash := HashPassword(s.Constructor.Password)
s.Error = err_hash
s.Constructor.Password = hashed_password
s.Constructor.UUID = uuid.NewV4()
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/service.go
index 7d56d45d633a3e7fd485bc9348527494512a0a50..3e6cda14fae0b3b8b7c50747311d2d8c67ee4d66 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/service.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/service.go
@@ -1,6 +1,10 @@
package services
-import "api.qobiltu.id/models"
+import (
+ "time"
+
+ "api.qobiltu.id/models"
+)
type (
Services interface {
@@ -29,3 +33,7 @@ func Construct[TConstructor any, TResult any](constructor ...TConstructor) *Serv
Constructor: constructor[0],
}
}
+
+func CalculateDueTime(duration time.Duration) time.Time {
+ return time.Now().Add(duration)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..afea5ccbf0885293e2f782692e229b6948e2228e
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/user_profile_service.go
@@ -0,0 +1,37 @@
+package services
+
+import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+)
+
+type UserProfileConstructor struct {
+ AccountId int
+}
+type UserProfileService struct {
+ Service[UserProfileConstructor, models.Account]
+}
+
+func (s *UserProfileService) Retrieve() {
+ userProfile := repositories.GetAccountbyId(uint(s.Constructor.AccountId))
+ s.Error = userProfile.RowsError
+ if userProfile.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account with given credentials!"
+ return
+ }
+ s.Result.Password = "SECRET"
+ s.Result = userProfile.Result
+}
+
+func (s *UserProfileService) Update() {
+ userProfile := repositories.GetAccountbyId(uint(s.Constructor.AccountId))
+ s.Error = userProfile.RowsError
+ if userProfile.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "There is no account with given credentials!"
+ return
+ }
+ s.Result.Password = "SECRET"
+ s.Result = userProfile.Result
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/LICENSE b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..79da4b69760d1658f4dbc42604187316b368d857
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2025 Abdan Hafidz
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
index 37c1b056c4f04eaa76005b0f10c04f8764cd2d13..39a67571ac3a2e6a6e2a972b9550becd551bdded 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
@@ -49,4 +49,4 @@ jobs:
cd space
git add .
git commit -m "Deploy files from GitHub repository" || echo "No changes to commit"
- git push --force origin main || echo "No changes to push"
+ git push origin main || echo "No changes to push"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
index 39a67571ac3a2e6a6e2a972b9550becd551bdded..37c1b056c4f04eaa76005b0f10c04f8764cd2d13 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
@@ -49,4 +49,4 @@ jobs:
cd space
git add .
git commit -m "Deploy files from GitHub repository" || echo "No changes to commit"
- git push origin main || echo "No changes to push"
+ git push --force origin main || echo "No changes to push"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
index deeb9479f4cb982ccd42fa0d7210b9777509e4ae..bd10d926e1c503ff128e21bf273fe2732e526abf 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
@@ -1,3 +1,4 @@
.env
vendor/
-quzuu-be.exe
\ No newline at end of file
+quzuu-be.exe
+README.md
\ No newline at end of file
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..96d88fd641bf8a6b538d1324abff58c8d5b912a0
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/config.go
@@ -0,0 +1,21 @@
+package config
+
+import (
+ "os"
+
+ "github.com/joho/godotenv"
+)
+
+var TCP_ADDRESS string
+var LOG_PATH string
+var HOST_ADDRESS string
+var HOST_PORT string
+
+func init() {
+ godotenv.Load()
+ HOST_ADDRESS = os.Getenv("HOST_ADDRESS")
+ HOST_PORT = os.Getenv("HOST_PORT")
+ TCP_ADDRESS = HOST_ADDRESS + ":" + HOST_PORT
+ LOG_PATH = os.Getenv("LOG_PATH")
+ // Menampilkan nilai variabel lingkungan
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
new file mode 100644
index 0000000000000000000000000000000000000000..458388d26dd8408234352e2a835552d6a278f267
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/database_connection_config.go
@@ -0,0 +1,60 @@
+package config
+
+import (
+ "fmt"
+ "log"
+ "os"
+
+ "gorm.io/driver/postgres"
+ "gorm.io/gorm"
+ "gorm.io/gorm/logger"
+
+ "api.qobiltu.id/models"
+ "github.com/joho/godotenv"
+)
+
+var DB *gorm.DB
+var err error
+var Salt string
+
+func init() {
+ godotenv.Load()
+ if err != nil {
+ fmt.Println("Gagal membaca file .env")
+ return
+ }
+ os.Setenv("TZ", "Asia/Jakarta")
+ dbHost := os.Getenv("DB_HOST")
+ dbPort := os.Getenv("DB_PORT")
+ dbUser := os.Getenv("DB_USER")
+ dbPassword := os.Getenv("DB_PASSWORD")
+ dbName := os.Getenv("DB_NAME")
+ Salt := os.Getenv("SALT")
+ dsn := "host=" + dbHost + " user=" + dbUser + " password=" + dbPassword + " dbname=" + dbName + " port=" + dbPort + " sslmode=disable TimeZone=Asia/Jakarta"
+ DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{TranslateError: true})
+ if err != nil {
+ panic(err)
+ }
+ if Salt == "" {
+ Salt = "D3f4u|t"
+ }
+
+ // Call AutoMigrateAll to perform auto-migration
+ AutoMigrateAll(DB)
+}
+
+func AutoMigrateAll(db *gorm.DB) {
+ // Enable logger to see SQL logs
+ db.Logger.LogMode(logger.Info)
+
+ // Auto-migrate all models
+ err := db.AutoMigrate(
+ &models.Account{},
+ &models.AccountDetails{},
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println("Migration completed successfully.")
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/home_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/home_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..06cfc3d7fac0385d6aef847a186de2eae7dbb4bb
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/home_controller.go
@@ -0,0 +1,9 @@
+package controller
+
+import "github.com/gin-gonic/gin"
+
+func HomeController(c *gin.Context) {
+ c.JSON(200, gin.H{
+ "message": "Api Qobiltu 2025!",
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/login_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/login_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..b411fa438a6d877515eca1d40dcb21060edd68c4
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/login_controller.go
@@ -0,0 +1,19 @@
+package controller
+
+import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func LoginController(c *gin.Context) {
+ authentication := services.AuthenticationService{}
+ loginController := Controller[models.LoginRequest, services.LoginConstructor, models.AuthenticatedUser]{
+ Service: &authentication.Service,
+ }
+ loginController.RequestJSON(c, func() {
+ loginController.Service.Constructor.Email = loginController.Request.Email
+ loginController.Service.Constructor.Password = loginController.Request.Password
+ authentication.Authenticate()
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/register_controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/register_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..3dc0f3c21343fe3279a492db3e994cbf8eef3561
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/register_controller.go
@@ -0,0 +1,19 @@
+package controller
+
+import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func RegisterController(c *gin.Context) {
+ register := services.RegisterService{}
+ registerController := Controller[models.RegisterRequest, models.Account, models.Account]{
+ Service: ®ister.Service,
+ }
+ registerController.RequestJSON(c, func() {
+ registerController.Service.Constructor.Password = registerController.Request.Password
+ registerController.Service.Constructor.Email = registerController.Request.Email
+ register.Create()
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..44178a96738817742fd4f6f7fe1ca63bbecc2d27
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/authentication_middleware.go
@@ -0,0 +1,104 @@
+// auth/auth.go
+
+package middleware
+
+import (
+ "errors"
+ "time"
+
+ "api.qobiltu.id/config"
+ "api.qobiltu.id/models"
+ "github.com/gin-gonic/gin"
+ "github.com/golang-jwt/jwt/v5"
+ "golang.org/x/crypto/bcrypt"
+)
+
+// Define a secret key for signing the JWT token
+var salt = config.Salt
+var secretKey = []byte(salt)
+
+// GenerateToken generates a JWT token for the given user
+func GenerateToken(user *models.Account) (string, error) {
+
+ // Create a new token
+ token := jwt.New(jwt.SigningMethodHS256)
+
+ // Set claims
+ claims := token.Claims.(jwt.MapClaims)
+ claims["id"] = user.Id
+ claims["exp"] = time.Now().Add(time.Hour * 24).Unix() // Token expires in 24 hours
+
+ // Sign the token with the secret key
+ tokenString, err := token.SignedString(secretKey)
+ if err != nil {
+ return "", err
+ }
+
+ return tokenString, nil
+}
+
+// VerifyPassword verifies if the provided password matches the hashed password
+func VerifyPassword(hashedPassword, password string) error {
+ err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
+ if err != nil {
+ return errors.New("invalid password")
+ }
+ return nil
+}
+func HashPassword(password string) (string, error) {
+ bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
+ return string(bytes), err
+}
+
+type CustomClaims struct {
+ jwt.RegisteredClaims
+ UserID int `json:"id"`
+}
+
+func VerifyToken(bearer_token string) (int, string, error) {
+ // fmt.Println(bearer_token)
+ token, err := jwt.ParseWithClaims(bearer_token, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
+ return secretKey, nil
+ })
+ if err != nil {
+ return 0, "invalid-token", err
+ }
+
+ // Extract the claims
+ claims, ok := token.Claims.(*CustomClaims)
+ if !ok || !token.Valid {
+ return 0, "invalid-token", err
+ }
+ if claims.ExpiresAt != nil && claims.ExpiresAt.Time.Before(time.Now()) {
+ return 0, "expired", err
+ }
+
+ return claims.UserID, "valid", err
+}
+
+func AuthUser(c *gin.Context) {
+ var currAccData models.AccountData
+ if c.Request.Header["Auth-Bearer-Token"] != nil {
+ token := c.Request.Header["Auth-Bearer-Token"]
+ currAccData.UserID, currAccData.VerifyStatus, currAccData.ErrVerif = VerifyToken(token[0])
+ // fmt.Println("Verify Status :", currAccData.verifyStatus)
+ if currAccData.VerifyStatus == "invalid-token" || currAccData.VerifyStatus == "expired" {
+ currAccData.UserID = 0
+ message := "Your session is expired, Please re-Login!"
+ SendJSON401(c, &currAccData.VerifyStatus, &message)
+ c.Abort()
+ return
+ }
+ } else {
+ currAccData.UserID = 0
+ currAccData.VerifyStatus = "no-token"
+ currAccData.ErrVerif = nil
+ message := "You have to Login First!"
+ SendJSON401(c, &currAccData.VerifyStatus, &message)
+ c.Abort()
+ return
+ }
+
+ c.Set("accountData", currAccData)
+ c.Next()
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/response_middleware.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/response_middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..55f3385077839115eadb8dace294229f4b4c3df2
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/response_middleware.go
@@ -0,0 +1,39 @@
+package middleware
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+// SendJSON200 sends a JSON response with HTTP status code 200
+func SendJSON200(c *gin.Context, data interface{}) {
+ c.JSON(http.StatusOK, gin.H{"status": "success", "data": data})
+}
+
+// SendJSON400 sends a JSON response with HTTP status code 400
+func SendJSON400(c *gin.Context, error_status *string, message *string) {
+ c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error-status": error_status, "message": message})
+}
+
+// SendJSON401 sends a JSON response with HTTP status code 401
+func SendJSON401(c *gin.Context, error_status *string, message *string) {
+ c.JSON(http.StatusUnauthorized, gin.H{"status": "error", "error-status": error_status, "message": message})
+}
+
+// SendJSON403 sends a JSON response with HTTP status code 403
+func SendJSON403(c *gin.Context, message *string) {
+ c.JSON(http.StatusForbidden, gin.H{"status": "error", "message": message})
+}
+
+// SendJSON404 sends a JSON response with HTTP status code 404
+func SendJSON404(c *gin.Context, message *string) {
+ c.JSON(http.StatusNotFound, gin.H{"status": "error", "message": message})
+}
+
+// SendJSON500 sends a JSON response with HTTP status code 500
+func SendJSON500(c *gin.Context, error_status *string, message *string) {
+ c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "error-status": error_status, "message": message})
+}
+
+// JSONResponseMiddleware is a middleware that provides functions for sending JSON responses
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/authentication_payload_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/authentication_payload_model.go
new file mode 100644
index 0000000000000000000000000000000000000000..0203b9b340d066705491edf66830674602f89655
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/authentication_payload_model.go
@@ -0,0 +1,7 @@
+package models
+
+type AccountData struct {
+ UserID int
+ VerifyStatus string
+ ErrVerif error
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
new file mode 100644
index 0000000000000000000000000000000000000000..82e8b95b39b86be5fb0a4ce538d697e9df8fd0d5
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/database_orm_model.go
@@ -0,0 +1,31 @@
+package models
+
+import (
+ "time"
+
+ uuid "github.com/satori/go.uuid"
+)
+
+type Account struct {
+ Id uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
+ Email string `gorm:"uniqueIndex" json:"email"`
+ Password string `json:"password"`
+ IsEmailVerified bool `json:"is_email_verified"`
+ CreatedAt time.Time `json:"created_at"`
+ DeletedAt time.Time `json:"deleted_at"`
+}
+
+type AccountDetails struct {
+ IDDetail uint `gorm:"primaryKey" json:"id_detail"`
+ Account_id uint `json:"id_account"`
+ Province string `json:"province"`
+ City string `json:"city"`
+ Institution string `json:"institution"`
+ UpdatedAt time.Time `json:"updated_at"`
+ DeletedAt time.Time `json:"deleted_at"`
+}
+
+// Gorm table name settings
+func (Account) TableName() string { return "account" }
+func (AccountDetails) TableName() string { return "account_details" }
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
new file mode 100644
index 0000000000000000000000000000000000000000..f2c026a993f950652158318977783e8563e595fb
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/exception_model.go
@@ -0,0 +1,12 @@
+package models
+
+type Exception struct {
+ Unauthorized bool `json:"unauthorized,omitempty"`
+ BadRequest bool `json:"bad_request,omitempty"`
+ DataNotFound bool `json:"data_not_found,omitempty"`
+ InternalServerError bool `json:"internal_server_error,omitempty"`
+ DataDuplicate bool `json:"data_duplicate,omitempty"`
+ QueryError bool `json:"query_error,omitempty"`
+ InvalidPasswordLength bool `json:"invalid_password_length,omitempty"`
+ Message string `json:"message,omitempty"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/model.go
new file mode 100644
index 0000000000000000000000000000000000000000..9ce401186cb92d50737155815c1f511164b86407
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/model.go
@@ -0,0 +1 @@
+package models
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
new file mode 100644
index 0000000000000000000000000000000000000000..cfae888ec34edbdd3f5b6588050e2b382c63792d
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/request_model.go
@@ -0,0 +1,13 @@
+package models
+
+type LoginRequest struct {
+ Email string `json:"email" binding:"required"`
+ Password string `json:"password" binding:"required"`
+}
+
+type RegisterRequest struct {
+ Name string `json:"name"`
+ Email string `json:"email" binding:"required,email"`
+ Phone int `json:"phone"`
+ Password string `json:"password" binding:"required"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
new file mode 100644
index 0000000000000000000000000000000000000000..c45df0dbc642de90cc3537722b359a2f09a06b02
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/response_model.go
@@ -0,0 +1,19 @@
+package models
+
+type SuccessResponse struct {
+ Status string `json:"status"`
+ Message string `json:"message"`
+ Data any `json:"data"`
+ MetaData any `json:"meta_data"`
+}
+
+type ErrorResponse struct {
+ Status string `json:"status"`
+ Message string `json:"message"`
+ Errors Exception `json:"errors"`
+ MetaData any `json:"meta_data"`
+}
+type AuthenticatedUser struct {
+ Account Account `json:"account"`
+ Token string `json:"token"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..41bd903aff7bf12c9d81cbed1ad00521ae629640
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/account_repository.go
@@ -0,0 +1,24 @@
+package repositories
+
+import (
+ "api.qobiltu.id/models"
+)
+
+func GetAccountbyEmail(email string) Repository[models.Account, models.Account] {
+ repo := Construct[models.Account, models.Account](
+ models.Account{Email: email},
+ )
+ repo.Transactions(
+ WhereGivenConstructor[models.Account, models.Account],
+ Find[models.Account, models.Account],
+ )
+ return *repo
+}
+
+func CreateAccount(account models.Account) Repository[models.Account, models.Account] {
+ repo := Construct[models.Account, models.Account](
+ account,
+ )
+ Create(repo)
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..3617e40936bf9e50e39a50285105622f080d210f
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repository.go
@@ -0,0 +1,113 @@
+package repositories
+
+import (
+ "api.qobiltu.id/config"
+ "gorm.io/gorm"
+)
+
+type Repositories interface {
+ FindAllPaginate()
+ Where()
+ Find()
+ Create()
+ Update()
+ CustomQuery()
+ Delete()
+}
+type PaginationConstructor struct {
+ Limit int
+ Offset int
+ Filter string
+}
+
+type CustomQueryConstructor struct {
+ SQL string
+ Values interface{}
+}
+
+type Repository[TConstructor any, TResult any] struct {
+ Constructor TConstructor
+ Pagination PaginationConstructor
+ CustomQuery CustomQueryConstructor
+ Result TResult
+ Transaction *gorm.DB
+ RowsCount int
+ NoRecord bool
+ RowsError error
+}
+
+func Construct[TConstructor any, TResult any](constructor ...TConstructor) *Repository[TConstructor, TResult] {
+ if len(constructor) == 1 {
+ return &Repository[TConstructor, TResult]{
+ Constructor: constructor[0],
+ Transaction: config.DB,
+ }
+ }
+ return &Repository[TConstructor, TResult]{
+ Constructor: constructor[0],
+ Transaction: config.DB.Begin(),
+ }
+}
+func (repo *Repository[T1, T2]) Transactions(transactions ...func(*Repository[T1, T2]) *gorm.DB) {
+ for _, tx := range transactions {
+ repo.Transaction = tx(repo)
+ if repo.RowsError != nil {
+ return
+ }
+ }
+}
+func WhereGivenConstructor[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
+ tx := repo.Transaction.Where(&repo.Constructor)
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
+ repo.RowsError = tx.Error
+ return tx
+}
+func Find[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
+ tx := repo.Transaction.Find(&repo.Result)
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
+ repo.RowsError = tx.Error
+ return tx
+}
+
+func FinddAllPaginate[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
+ tx := repo.Transaction.Limit(repo.Pagination.Limit).Offset(repo.Pagination.Offset).Find(&repo.Result)
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
+ repo.RowsError = tx.Error
+ return tx
+}
+
+func Create[T1 any](repo *Repository[T1, T1]) *gorm.DB {
+ tx := repo.Transaction.Create(&repo.Constructor)
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
+ repo.RowsError = tx.Error
+ repo.Result = repo.Constructor
+ return tx
+}
+
+func Update[T1 any](repo *Repository[T1, T1]) *gorm.DB {
+ tx := repo.Transaction.Save(&repo.Constructor)
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
+ repo.RowsError = tx.Error
+ return tx
+}
+
+func Delete[T1 any](repo *Repository[T1, T1]) *gorm.DB {
+ tx := repo.Transaction.Delete(&repo.Constructor)
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
+ repo.RowsError = tx.Error
+ return tx
+}
+
+func CustomQuery[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
+ tx := repo.Transaction.Raw(repo.CustomQuery.SQL, repo.CustomQuery.Values).Scan(&repo.Result)
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
+ repo.RowsError = tx.Error
+ return tx
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/login_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/login_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..06ea6bb57e6ccdb5fa55784476af50fcda77eff1
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/login_service.go
@@ -0,0 +1,47 @@
+package services
+
+import (
+ "errors"
+
+ "api.qobiltu.id/middleware"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+)
+
+type LoginConstructor struct {
+ Email string
+ Password string
+}
+
+type AuthenticationService struct {
+ Service[LoginConstructor, models.AuthenticatedUser]
+}
+
+func (s *AuthenticationService) Authenticate() {
+ accountData := repositories.GetAccountbyEmail(s.Constructor.Email)
+ if accountData.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "there is no account with given credentials!"
+ return
+ }
+ if middleware.VerifyPassword(accountData.Result.Password, s.Constructor.Password) != nil {
+ s.Exception.Unauthorized = true
+ s.Exception.Message = "incorrect password!"
+ return
+ }
+
+ token, err_tok := middleware.GenerateToken(&accountData.Result)
+
+ if err_tok != nil {
+ s.Error = errors.Join(s.Error, err_tok)
+ }
+
+ accountData.Result.Password = "SECRET"
+ s.Result = models.AuthenticatedUser{
+ Account: accountData.Result,
+ Token: token,
+ }
+ s.Error = accountData.RowsError
+}
+
+// LoginHandler handles user login
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..6732f99fabbd315504042de844f59841294e2025
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/register_service.go
@@ -0,0 +1,40 @@
+package services
+
+import (
+ "errors"
+
+ "api.qobiltu.id/middleware"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
+ uuid "github.com/satori/go.uuid"
+ "gorm.io/gorm"
+)
+
+type RegisterService struct {
+ Service[models.Account, models.Account]
+}
+
+func (s *RegisterService) Create() {
+ if len(s.Constructor.Password) < 8 {
+ s.Exception.InvalidPasswordLength = true
+ s.Exception.Message = "Password must have at least 8 characters!"
+ return
+ }
+ hashed_password, err_hash := middleware.HashPassword(s.Constructor.Password)
+ s.Error = err_hash
+ s.Constructor.Password = hashed_password
+ s.Constructor.UUID = uuid.NewV4()
+ accountCreated := repositories.CreateAccount(s.Constructor)
+ if errors.Is(accountCreated.RowsError, gorm.ErrDuplicatedKey) {
+ s.Exception.DataDuplicate = true
+ s.Exception.Message = "Account with email " + s.Constructor.Email + " already exists!"
+ return
+ } else if errors.Is(accountCreated.RowsError, gorm.ErrModelAccessibleFieldsRequired) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidData) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidValue) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidField) {
+ s.Exception.BadRequest = true
+ s.Exception.Message = "Bad request!"
+ return
+ }
+ s.Error = accountCreated.RowsError
+ s.Result = accountCreated.Result
+ s.Result.Password = "SECRET"
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/service.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..7d56d45d633a3e7fd485bc9348527494512a0a50
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/service.go
@@ -0,0 +1,31 @@
+package services
+
+import "api.qobiltu.id/models"
+
+type (
+ Services interface {
+ Retrieve()
+ Update()
+ Create()
+ Delete()
+ Validate()
+ Authenticate()
+ Authorize()
+ }
+ Service[TConstructor any, TResult any] struct {
+ Constructor TConstructor
+ Result TResult
+ Exception models.Exception
+ Error error
+ }
+)
+
+func Construct[TConstructor any, TResult any](constructor ...TConstructor) *Service[TConstructor, TResult] {
+ if len(constructor) == 1 {
+ return &Service[TConstructor, TResult]{}
+ }
+
+ return &Service[TConstructor, TResult]{
+ Constructor: constructor[0],
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/DatabaseConfig.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/DatabaseConfig.go
index 7dfa116ec7152e6338c764ce5853ad21782bd26f..458388d26dd8408234352e2a835552d6a278f267 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/DatabaseConfig.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/DatabaseConfig.go
@@ -9,26 +9,10 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/logger"
+ "api.qobiltu.id/models"
"github.com/joho/godotenv"
- "go-dp.abdanhafidz.com/models"
)
-func AutoMigrateAll(db *gorm.DB) {
- // Enable logger to see SQL logs
- db.Logger.LogMode(logger.Info)
-
- // Auto-migrate all models
- err := db.AutoMigrate(
- &models.Account{},
- &models.AccountDetails{},
- )
- if err != nil {
- log.Fatal(err)
- }
-
- fmt.Println("Migration completed successfully.")
-}
-
var DB *gorm.DB
var err error
var Salt string
@@ -58,3 +42,19 @@ func init() {
// Call AutoMigrateAll to perform auto-migration
AutoMigrateAll(DB)
}
+
+func AutoMigrateAll(db *gorm.DB) {
+ // Enable logger to see SQL logs
+ db.Logger.LogMode(logger.Info)
+
+ // Auto-migrate all models
+ err := db.AutoMigrate(
+ &models.Account{},
+ &models.AccountDetails{},
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println("Migration completed successfully.")
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/LoginController.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/LoginController.go
index 6d04567f7a776399c5faafc4f11dff4c0735ae9a..b411fa438a6d877515eca1d40dcb21060edd68c4 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/LoginController.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/LoginController.go
@@ -1,9 +1,9 @@
package controller
import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
"github.com/gin-gonic/gin"
- "go-dp.abdanhafidz.com/models"
- "go-dp.abdanhafidz.com/services"
)
func LoginController(c *gin.Context) {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/RegisterController.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/RegisterController.go
index e3375ba58212ddd73d2d4e29fbf67d247e89398b..3dc0f3c21343fe3279a492db3e994cbf8eef3561 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/RegisterController.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/RegisterController.go
@@ -1,9 +1,9 @@
package controller
import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
"github.com/gin-gonic/gin"
- "go-dp.abdanhafidz.com/models"
- "go-dp.abdanhafidz.com/services"
)
func RegisterController(c *gin.Context) {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
index fa14d0b2c6ed4e25a72839a3fad18b2d59e40af3..32a528d703f974515cc1baa4960e87ac56481629 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
@@ -1,10 +1,10 @@
package controller
import (
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "api.qobiltu.id/utils"
"github.com/gin-gonic/gin"
- "go-dp.abdanhafidz.com/models"
- "go-dp.abdanhafidz.com/services"
- "go-dp.abdanhafidz.com/utils"
)
type (
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
index f6d85920f3d50bf7a3fc013a4ee8875237b0e108..9b6fd06b10d941bc76ba173ba5f2983e9c1408d8 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
@@ -1,49 +1,49 @@
-module go-dp.abdanhafidz.com
+module api.qobiltu.id
-go 1.21.0
+go 1.24.0
require (
- github.com/dgrijalva/jwt-go v3.2.0+incompatible
- github.com/gin-gonic/gin v1.9.1
+ github.com/gin-gonic/gin v1.10.0
+ github.com/golang-jwt/jwt/v5 v5.2.1
github.com/joho/godotenv v1.5.1
- golang.org/x/crypto v0.32.0
- gorm.io/driver/postgres v1.5.4
- gorm.io/gorm v1.25.5
+ github.com/satori/go.uuid v1.2.0
+ golang.org/x/crypto v0.36.0
+ gorm.io/driver/postgres v1.5.11
+ gorm.io/gorm v1.25.12
)
require (
- github.com/bytedance/sonic v1.10.2 // indirect
- github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
- github.com/chenzhuoyu/iasm v0.9.0 // indirect
+ github.com/bytedance/sonic v1.13.1 // indirect
+ github.com/bytedance/sonic/loader v0.2.4 // indirect
+ github.com/cloudwego/base64x v0.1.5 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
- github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/gin-contrib/sse v1.0.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.25.0 // indirect
- github.com/goccy/go-json v0.10.2 // indirect
+ github.com/goccy/go-json v0.10.5 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
- github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
- github.com/jackc/pgx/v5 v5.5.0 // indirect
- github.com/jackc/puddle/v2 v2.2.1 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
+ github.com/jackc/pgx/v5 v5.7.2 // indirect
+ github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
- github.com/klauspost/cpuid/v2 v2.2.6 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
- github.com/pelletier/go-toml/v2 v2.1.0 // indirect
+ github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
- github.com/satori/go.uuid v1.2.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
- github.com/ugorji/go/codec v1.2.11 // indirect
- golang.org/x/arch v0.6.0 // indirect
- golang.org/x/net v0.34.0 // indirect
- golang.org/x/sync v0.10.0 // indirect
- golang.org/x/sys v0.29.0 // indirect
- golang.org/x/text v0.21.0 // indirect
- google.golang.org/protobuf v1.31.0 // indirect
+ github.com/ugorji/go/codec v1.2.12 // indirect
+ golang.org/x/arch v0.15.0 // indirect
+ golang.org/x/net v0.37.0 // indirect
+ golang.org/x/sync v0.12.0 // indirect
+ golang.org/x/sys v0.31.0 // indirect
+ golang.org/x/text v0.23.0 // indirect
+ google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
-)
+)
\ No newline at end of file
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
index a82e88494d1f86d0560792cace9c2a61284c1d8a..6f39995754d2f66f294c1866a62d1810e8d524c5 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
@@ -1,51 +1,44 @@
-github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
-github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
-github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
-github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
-github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
-github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
-github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
-github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
-github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
-github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
+github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
+github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
+github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
+github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
+github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
-github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
-github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
-github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
-github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
+github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
+github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
+github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
+github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
-github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
-github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
-github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
+github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
+github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
+github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
-github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
-github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
-github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw=
-github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
-github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
-github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
+github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
+github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
+github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
@@ -55,15 +48,13 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
-github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
+github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
-github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -73,8 +64,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
-github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
+github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
@@ -84,57 +75,44 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
-github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
-github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
-golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
-golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc=
-golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
-golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
-golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
-golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
-golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
-golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
-golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
-golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
-golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
-golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
+github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
+golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
+golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
+golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
-golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
-golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
-google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
+google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
-gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
-gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
-gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
-nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
-rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
+gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
+gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
+gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
\ No newline at end of file
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
index d89532891b559d7d8361e79a1090e77a31cafb95..676f1b4f77f3957678f711cc402e21791cf11471 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
@@ -3,8 +3,8 @@ package main
import (
"fmt"
- "go-dp.abdanhafidz.com/config"
- "go-dp.abdanhafidz.com/router"
+ "api.qobiltu.id/config"
+ "api.qobiltu.id/router"
)
func main() {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/AuthMiddleware.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/AuthMiddleware.go
index c153979ac01f108193541d23f821c288b4752f01..44178a96738817742fd4f6f7fe1ca63bbecc2d27 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/AuthMiddleware.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/AuthMiddleware.go
@@ -6,10 +6,10 @@ import (
"errors"
"time"
- "github.com/dgrijalva/jwt-go"
+ "api.qobiltu.id/config"
+ "api.qobiltu.id/models"
"github.com/gin-gonic/gin"
- "go-dp.abdanhafidz.com/config"
- "go-dp.abdanhafidz.com/models"
+ "github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
)
@@ -51,8 +51,8 @@ func HashPassword(password string) (string, error) {
}
type CustomClaims struct {
- jwt.StandardClaims
- IDUser int `json:"id"`
+ jwt.RegisteredClaims
+ UserID int `json:"id"`
}
func VerifyToken(bearer_token string) (int, string, error) {
@@ -63,33 +63,34 @@ func VerifyToken(bearer_token string) (int, string, error) {
if err != nil {
return 0, "invalid-token", err
}
+
+ // Extract the claims
claims, ok := token.Claims.(*CustomClaims)
if !ok || !token.Valid {
return 0, "invalid-token", err
- } else if claims.StandardClaims.ExpiresAt != 0 && claims.ExpiresAt < time.Now().Unix() {
+ }
+ if claims.ExpiresAt != nil && claims.ExpiresAt.Time.Before(time.Now()) {
return 0, "expired", err
- } else if !ok && token.Valid {
- return 0, "invalid-token", err
}
- return claims.IDUser, "valid", err
+ return claims.UserID, "valid", err
}
func AuthUser(c *gin.Context) {
var currAccData models.AccountData
if c.Request.Header["Auth-Bearer-Token"] != nil {
token := c.Request.Header["Auth-Bearer-Token"]
- currAccData.IdUser, currAccData.VerifyStatus, currAccData.ErrVerif = VerifyToken(token[0])
+ currAccData.UserID, currAccData.VerifyStatus, currAccData.ErrVerif = VerifyToken(token[0])
// fmt.Println("Verify Status :", currAccData.verifyStatus)
if currAccData.VerifyStatus == "invalid-token" || currAccData.VerifyStatus == "expired" {
- currAccData.IdUser = 0
+ currAccData.UserID = 0
message := "Your session is expired, Please re-Login!"
SendJSON401(c, &currAccData.VerifyStatus, &message)
c.Abort()
return
}
} else {
- currAccData.IdUser = 0
+ currAccData.UserID = 0
currAccData.VerifyStatus = "no-token"
currAccData.ErrVerif = nil
message := "You have to Login First!"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
index f191583a0de0f8ec9291985a06d40c00161522d0..41bd903aff7bf12c9d81cbed1ad00521ae629640 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
@@ -1,7 +1,7 @@
package repositories
import (
- "go-dp.abdanhafidz.com/models"
+ "api.qobiltu.id/models"
)
func GetAccountbyEmail(email string) Repository[models.Account, models.Account] {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/DatabaseScoope.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/DatabaseScoope.go
index e11987b79a890dc6e84923fe7c241c6e8fa99c35..560945a97953d6c1bfef5db40b56cdab15024204 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/DatabaseScoope.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/DatabaseScoope.go
@@ -1,5 +1,5 @@
package repositories
-import "go-dp.abdanhafidz.com/config"
+import "api.qobiltu.id/config"
var db = config.DB
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
index 9d34eda9880d655f8b4200d4792fff50fc144a54..3617e40936bf9e50e39a50285105622f080d210f 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
@@ -1,7 +1,7 @@
package repositories
import (
- "go-dp.abdanhafidz.com/config"
+ "api.qobiltu.id/config"
"gorm.io/gorm"
)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
index 27ce310f782a252cab580d08873505f02ec38a03..114a87bb4eb5bd9713a0cd097c9b46f05eb761df 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
@@ -1,9 +1,9 @@
package router
import (
+ "api.qobiltu.id/config"
+ "api.qobiltu.id/controller"
"github.com/gin-gonic/gin"
- "go-dp.abdanhafidz.com/config"
- "go-dp.abdanhafidz.com/controller"
)
func StartService() {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
index 21778c5b70ddeded49df2a7069057abe28fb7c5d..06ea6bb57e6ccdb5fa55784476af50fcda77eff1 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
@@ -3,9 +3,9 @@ package services
import (
"errors"
- "go-dp.abdanhafidz.com/middleware"
- "go-dp.abdanhafidz.com/models"
- "go-dp.abdanhafidz.com/repositories"
+ "api.qobiltu.id/middleware"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
)
type LoginConstructor struct {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
index 106c30887c103cba810c602b4431fae9f0520d5d..6732f99fabbd315504042de844f59841294e2025 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
@@ -3,10 +3,10 @@ package services
import (
"errors"
+ "api.qobiltu.id/middleware"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/repositories"
uuid "github.com/satori/go.uuid"
- "go-dp.abdanhafidz.com/middleware"
- "go-dp.abdanhafidz.com/models"
- "go-dp.abdanhafidz.com/repositories"
"gorm.io/gorm"
)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/services.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/services.go
index e684786fdd3dc071dd085b87f27a0e7bb8becfff..7d56d45d633a3e7fd485bc9348527494512a0a50 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/services.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/services.go
@@ -1,6 +1,6 @@
package services
-import "go-dp.abdanhafidz.com/models"
+import "api.qobiltu.id/models"
type (
Services interface {
@@ -21,7 +21,7 @@ type (
)
func Construct[TConstructor any, TResult any](constructor ...TConstructor) *Service[TConstructor, TResult] {
- if len(constructor) == 0 {
+ if len(constructor) == 1 {
return &Service[TConstructor, TResult]{}
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ExceptionModel.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ExceptionModel.go
index b4e76203056e9c2ee6ea5b447b6d3b90842f1d88..f2c026a993f950652158318977783e8563e595fb 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ExceptionModel.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ExceptionModel.go
@@ -1,11 +1,12 @@
package models
type Exception struct {
- Unauthorized bool `json:"unauthorized,omitempty"`
- BadRequest bool `json:"bad_request,omitempty"`
- DataNotFound bool `json:"data_not_found,omitempty"`
- InternalServerError bool `json:"internal_server_error,omitempty"`
- DataDuplicate bool `json:"data_duplicate,omitempty"`
- QueryError bool `json:"query_error,omitempty"`
- Message string `json:"message,omitempty"`
+ Unauthorized bool `json:"unauthorized,omitempty"`
+ BadRequest bool `json:"bad_request,omitempty"`
+ DataNotFound bool `json:"data_not_found,omitempty"`
+ InternalServerError bool `json:"internal_server_error,omitempty"`
+ DataDuplicate bool `json:"data_duplicate,omitempty"`
+ QueryError bool `json:"query_error,omitempty"`
+ InvalidPasswordLength bool `json:"invalid_password_length,omitempty"`
+ Message string `json:"message,omitempty"`
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
index b868ad4c0353135ae0659d8fa9db3ead4f334eb8..f191583a0de0f8ec9291985a06d40c00161522d0 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
@@ -4,9 +4,9 @@ import (
"go-dp.abdanhafidz.com/models"
)
-func GetAccountbyEmailPassword(email string, password string) Repository[models.Account, models.Account] {
+func GetAccountbyEmail(email string) Repository[models.Account, models.Account] {
repo := Construct[models.Account, models.Account](
- models.Account{Email: email, Password: password},
+ models.Account{Email: email},
)
repo.Transactions(
WhereGivenConstructor[models.Account, models.Account],
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
index 7a5c2816396cbb729fc1e0df162e9d0eea6ea42b..9d34eda9880d655f8b4200d4792fff50fc144a54 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
@@ -1,8 +1,6 @@
package repositories
import (
- "strconv"
-
"go-dp.abdanhafidz.com/config"
"gorm.io/gorm"
)
@@ -51,15 +49,10 @@ func Construct[TConstructor any, TResult any](constructor ...TConstructor) *Repo
}
}
func (repo *Repository[T1, T2]) Transactions(transactions ...func(*Repository[T1, T2]) *gorm.DB) {
- i := 1
for _, tx := range transactions {
repo.Transaction = tx(repo)
if repo.RowsError != nil {
- repo.Transaction.Rollback()
return
- } else {
- repo.Transaction.SavePoint("Save Point : " + strconv.Itoa(i))
- repo.Transaction.Commit()
}
}
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
index f1bdc1d654ff34ce16ece776c45919bd3841b38d..21778c5b70ddeded49df2a7069057abe28fb7c5d 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
@@ -18,7 +18,7 @@ type AuthenticationService struct {
}
func (s *AuthenticationService) Authenticate() {
- accountData := repositories.GetAccountbyEmailPassword(s.Constructor.Email, s.Constructor.Password)
+ accountData := repositories.GetAccountbyEmail(s.Constructor.Email)
if accountData.NoRecord {
s.Exception.DataNotFound = true
s.Exception.Message = "there is no account with given credentials!"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
index 8809621fac0e901ca8c058868b6318d0ad9470ca..106c30887c103cba810c602b4431fae9f0520d5d 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
@@ -3,6 +3,7 @@ package services
import (
"errors"
+ uuid "github.com/satori/go.uuid"
"go-dp.abdanhafidz.com/middleware"
"go-dp.abdanhafidz.com/models"
"go-dp.abdanhafidz.com/repositories"
@@ -14,9 +15,15 @@ type RegisterService struct {
}
func (s *RegisterService) Create() {
+ if len(s.Constructor.Password) < 8 {
+ s.Exception.InvalidPasswordLength = true
+ s.Exception.Message = "Password must have at least 8 characters!"
+ return
+ }
hashed_password, err_hash := middleware.HashPassword(s.Constructor.Password)
s.Error = err_hash
s.Constructor.Password = hashed_password
+ s.Constructor.UUID = uuid.NewV4()
accountCreated := repositories.CreateAccount(s.Constructor)
if errors.Is(accountCreated.RowsError, gorm.ErrDuplicatedKey) {
s.Exception.DataDuplicate = true
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
index efe450181fc1f3927c0b6658778233ce43ac1d76..f6d85920f3d50bf7a3fc013a4ee8875237b0e108 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
@@ -36,6 +36,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
+ github.com/satori/go.uuid v1.2.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.6.0 // indirect
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
index 79ffaec719b95b12a37e3a787b1576ef5a53db03..a82e88494d1f86d0560792cace9c2a61284c1d8a 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
@@ -79,6 +79,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/RegisterController.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/RegisterController.go
index 25244306baf7b5ff373c0e1c925c584740710eee..e3375ba58212ddd73d2d4e29fbf67d247e89398b 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/RegisterController.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/RegisterController.go
@@ -11,9 +11,9 @@ func RegisterController(c *gin.Context) {
registerController := Controller[models.RegisterRequest, models.Account, models.Account]{
Service: ®ister.Service,
}
- registerController.RequestJSON(c)
- registerController.Service.Constructor.Password = registerController.Request.Password
- registerController.Service.Constructor.Email = registerController.Request.Email
- register.Create()
- registerController.Response(c)
+ registerController.RequestJSON(c, func() {
+ registerController.Service.Constructor.Password = registerController.Request.Password
+ registerController.Service.Constructor.Email = registerController.Request.Email
+ register.Create()
+ })
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/DatabaseModel.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/DatabaseModel.go
index 355323a487ca5c8fc593a1bdea525f30081b884d..b2f38ee65f5094a7311928d1567e5263d87f9c7a 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/DatabaseModel.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/DatabaseModel.go
@@ -2,14 +2,18 @@ package models
import (
"time"
+
+ uuid "github.com/satori/go.uuid"
)
type Account struct {
- Id uint `gorm:"primaryKey" json:"id"`
- Email string `gorm:"primaryKey" json:"email"`
- Password string `json:"password"`
- CreatedAt time.Time `json:"created_at"`
- DeletedAt time.Time `json:"deleted_at"`
+ Id uint `gorm:"primaryKey" json:"id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
+ Email string `gorm:"uniqueIndex" json:"email"`
+ Password string `json:"password"`
+ IsEmailVerified bool `json:"is_email_verified"`
+ CreatedAt time.Time `json:"created_at"`
+ DeletedAt time.Time `json:"deleted_at"`
}
type AccountDetails struct {
@@ -22,6 +26,14 @@ type AccountDetails struct {
DeletedAt time.Time `json:"deleted_at"`
}
+type EmailVerification struct {
+ Id int `gorm:"primaryKey" json:"id"`
+ AccountId int `json:"account_id"`
+ UUID uuid.UUID `gorm:"type:uuid" json:"uuid" `
+ CreatedAt time.Time `json:"created_at"`
+ ExpiredAt time.Time `json:"expired_at"`
+}
+
// Gorm table name settings
func (Account) TableName() string { return "account" }
func (AccountDetails) TableName() string { return "account_details" }
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
index 9243b47af1fc1184b20eae52a1300010764f5bab..b868ad4c0353135ae0659d8fa9db3ead4f334eb8 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
@@ -8,7 +8,10 @@ func GetAccountbyEmailPassword(email string, password string) Repository[models.
repo := Construct[models.Account, models.Account](
models.Account{Email: email, Password: password},
)
- Find(repo)
+ repo.Transactions(
+ WhereGivenConstructor[models.Account, models.Account],
+ Find[models.Account, models.Account],
+ )
return *repo
}
@@ -16,7 +19,6 @@ func CreateAccount(account models.Account) Repository[models.Account, models.Acc
repo := Construct[models.Account, models.Account](
account,
)
-
Create(repo)
return *repo
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
index 767e3d93bb01248012ed0a89768db740be5157c9..7a5c2816396cbb729fc1e0df162e9d0eea6ea42b 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
@@ -1,6 +1,8 @@
package repositories
import (
+ "strconv"
+
"go-dp.abdanhafidz.com/config"
"gorm.io/gorm"
)
@@ -56,52 +58,63 @@ func (repo *Repository[T1, T2]) Transactions(transactions ...func(*Repository[T1
repo.Transaction.Rollback()
return
} else {
- repo.Transaction.SavePoint("Save Point : " + string(i))
+ repo.Transaction.SavePoint("Save Point : " + strconv.Itoa(i))
repo.Transaction.Commit()
}
}
}
func WhereGivenConstructor[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
tx := repo.Transaction.Where(&repo.Constructor)
- repo.RowsError = repo.Transaction.Error
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
repo.RowsError = tx.Error
return tx
}
func Find[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
tx := repo.Transaction.Find(&repo.Result)
- repo.RowsError = repo.Transaction.Error
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
repo.RowsError = tx.Error
return tx
}
func FinddAllPaginate[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
tx := repo.Transaction.Limit(repo.Pagination.Limit).Offset(repo.Pagination.Offset).Find(&repo.Result)
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
repo.RowsError = tx.Error
return tx
}
func Create[T1 any](repo *Repository[T1, T1]) *gorm.DB {
tx := repo.Transaction.Create(&repo.Constructor)
- repo.Result = repo.Constructor
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
repo.RowsError = tx.Error
+ repo.Result = repo.Constructor
return tx
}
func Update[T1 any](repo *Repository[T1, T1]) *gorm.DB {
tx := repo.Transaction.Save(&repo.Constructor)
- repo.Result = repo.Constructor
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
repo.RowsError = tx.Error
return tx
}
func Delete[T1 any](repo *Repository[T1, T1]) *gorm.DB {
tx := repo.Transaction.Delete(&repo.Constructor)
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
repo.RowsError = tx.Error
return tx
}
func CustomQuery[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
tx := repo.Transaction.Raw(repo.CustomQuery.SQL, repo.CustomQuery.Values).Scan(&repo.Result)
+ repo.RowsCount = int(tx.RowsAffected)
+ repo.NoRecord = repo.RowsCount == 0
repo.RowsError = tx.Error
return tx
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
index 2d734758bcd0ea31246376da2e71187be98d81ca..f1bdc1d654ff34ce16ece776c45919bd3841b38d 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
@@ -21,10 +21,9 @@ func (s *AuthenticationService) Authenticate() {
accountData := repositories.GetAccountbyEmailPassword(s.Constructor.Email, s.Constructor.Password)
if accountData.NoRecord {
s.Exception.DataNotFound = true
- s.Exception.Message = "there is no account with given email!"
+ s.Exception.Message = "there is no account with given credentials!"
return
}
-
if middleware.VerifyPassword(accountData.Result.Password, s.Constructor.Password) != nil {
s.Exception.Unauthorized = true
s.Exception.Message = "incorrect password!"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
index 0aca7d7de67105b79deaf19685ec9414df3f5d45..8809621fac0e901ca8c058868b6318d0ad9470ca 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
@@ -2,7 +2,6 @@ package services
import (
"errors"
- "fmt"
"go-dp.abdanhafidz.com/middleware"
"go-dp.abdanhafidz.com/models"
@@ -15,12 +14,10 @@ type RegisterService struct {
}
func (s *RegisterService) Create() {
- hashed_password, _ := middleware.HashPassword(s.Constructor.Password)
+ hashed_password, err_hash := middleware.HashPassword(s.Constructor.Password)
+ s.Error = err_hash
s.Constructor.Password = hashed_password
accountCreated := repositories.CreateAccount(s.Constructor)
-
- // fmt.Println("Error :", accountCreated.RowsError.Error())
- fmt.Println(errors.Is(accountCreated.RowsError, gorm.ErrDuplicatedKey))
if errors.Is(accountCreated.RowsError, gorm.ErrDuplicatedKey) {
s.Exception.DataDuplicate = true
s.Exception.Message = "Account with email " + s.Constructor.Email + " already exists!"
@@ -30,7 +27,7 @@ func (s *RegisterService) Create() {
s.Exception.Message = "Bad request!"
return
}
-
+ s.Error = accountCreated.RowsError
s.Result = accountCreated.Result
s.Result.Password = "SECRET"
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/LoginController.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/LoginController.go
index 78d94dcb74fe945b45f4491f85d9b3784a6947a1..6d04567f7a776399c5faafc4f11dff4c0735ae9a 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/LoginController.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/LoginController.go
@@ -8,12 +8,12 @@ import (
func LoginController(c *gin.Context) {
authentication := services.AuthenticationService{}
- loginController := Controller[models.LoginRequest, services.LoginConstructor, models.Account]{
+ loginController := Controller[models.LoginRequest, services.LoginConstructor, models.AuthenticatedUser]{
Service: &authentication.Service,
}
- loginController.RequestJSON(c)
- loginController.Service.Constructor.Email = loginController.Request.Email
- loginController.Service.Constructor.Password = loginController.Request.Password
- authentication.Authenticate()
- loginController.Response(c)
+ loginController.RequestJSON(c, func() {
+ loginController.Service.Constructor.Email = loginController.Request.Email
+ loginController.Service.Constructor.Password = loginController.Request.Password
+ authentication.Authenticate()
+ })
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
index aba28ae8d9d64b8bb62cda08de9658163b038602..fa14d0b2c6ed4e25a72839a3fad18b2d59e40af3 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
@@ -19,7 +19,7 @@ type (
}
)
-func (controller *Controller[T1, T2, T3]) RequestJSON(c *gin.Context) {
+func (controller *Controller[T1, T2, T3]) RequestJSON(c *gin.Context, act func()) {
cParam, _ := c.Get("accountData")
if cParam != nil {
controller.AccountData = cParam.(models.AccountData)
@@ -28,9 +28,12 @@ func (controller *Controller[T1, T2, T3]) RequestJSON(c *gin.Context) {
if errBinding != nil {
utils.ResponseFAIL(c, 400, models.Exception{
BadRequest: true,
- Message: "Invalid Request Type",
+ Message: "Invalid Request!, recheck your request, there's must be some problem about required parameter or type parameter",
})
return
+ } else {
+ act()
+ controller.Response(c)
}
}
func (controller *Controller[T1, T2, T3]) Response(c *gin.Context) {
@@ -41,6 +44,8 @@ func (controller *Controller[T1, T2, T3]) Response(c *gin.Context) {
Message: "Internal Server Error",
})
utils.LogError(controller.Service.Error)
+ case controller.Service.Exception.DataDuplicate:
+ utils.ResponseFAIL(c, 400, controller.Service.Exception)
case controller.Service.Exception.Unauthorized:
utils.ResponseFAIL(c, 401, controller.Service.Exception)
case controller.Service.Exception.DataNotFound:
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/AuthMiddleware.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/AuthMiddleware.go
index 6dc04aa18ba75ef21252e87baf23c0158cc3daea..c153979ac01f108193541d23f821c288b4752f01 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/AuthMiddleware.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/AuthMiddleware.go
@@ -25,7 +25,7 @@ func GenerateToken(user *models.Account) (string, error) {
// Set claims
claims := token.Claims.(jwt.MapClaims)
- claims["id"] = user.IDAccount
+ claims["id"] = user.Id
claims["exp"] = time.Now().Add(time.Hour * 24).Unix() // Token expires in 24 hours
// Sign the token with the secret key
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/DatabaseModel.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/DatabaseModel.go
index f048c17a9838438555d6fd795e6c7617c2c1b729..355323a487ca5c8fc593a1bdea525f30081b884d 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/DatabaseModel.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/DatabaseModel.go
@@ -5,14 +5,11 @@ import (
)
type Account struct {
- IDAccount uint `gorm:"primaryKey" json:"id_account"`
- Name string `json:"name"`
- Username string `json:"username"`
- Email string `json:"email"`
- Password string `json:"password"`
- PhoneNumber int `json:"phone_number"`
- CreatedAt time.Time `json:"created_at"`
- DeletedAt time.Time `json:"deleted_at"`
+ Id uint `gorm:"primaryKey" json:"id"`
+ Email string `gorm:"primaryKey" json:"email"`
+ Password string `json:"password"`
+ CreatedAt time.Time `json:"created_at"`
+ DeletedAt time.Time `json:"deleted_at"`
}
type AccountDetails struct {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ResponseModel.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ResponseModel.go
index 9ce401186cb92d50737155815c1f511164b86407..cd7b65b6a4ffd53ca1cc3c430abf0b256d0a4936 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ResponseModel.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ResponseModel.go
@@ -1 +1,6 @@
package models
+
+type AuthenticatedUser struct {
+ Account Account `json:"account"`
+ Token string `json:"token"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
index 79e43f0707a436c344e5cddd53187d428274bde9..9243b47af1fc1184b20eae52a1300010764f5bab 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
@@ -1,24 +1,22 @@
package repositories
import (
- "fmt"
-
"go-dp.abdanhafidz.com/models"
)
-func GetAccountbyEmailPassword(username string, password string) Repository[models.Account, models.Account] {
+func GetAccountbyEmailPassword(email string, password string) Repository[models.Account, models.Account] {
repo := Construct[models.Account, models.Account](
- models.Account{Username: username, Password: password},
+ models.Account{Email: email, Password: password},
)
Find(repo)
return *repo
}
func CreateAccount(account models.Account) Repository[models.Account, models.Account] {
- fmt.Println(account)
repo := Construct[models.Account, models.Account](
account,
)
+
Create(repo)
return *repo
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
index c82f550472331b9b8d0f5d4cdcf5aab441e5cdfa..767e3d93bb01248012ed0a89768db740be5157c9 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
@@ -1,8 +1,6 @@
package repositories
import (
- "fmt"
-
"go-dp.abdanhafidz.com/config"
"gorm.io/gorm"
)
@@ -39,7 +37,6 @@ type Repository[TConstructor any, TResult any] struct {
}
func Construct[TConstructor any, TResult any](constructor ...TConstructor) *Repository[TConstructor, TResult] {
- fmt.Println("Len = ", len(constructor))
if len(constructor) == 1 {
return &Repository[TConstructor, TResult]{
Constructor: constructor[0],
@@ -51,11 +48,10 @@ func Construct[TConstructor any, TResult any](constructor ...TConstructor) *Repo
Transaction: config.DB.Begin(),
}
}
-func (repo *Repository[T1, T2]) Transactions(transactions ...func(*Repository[T1, T2])) {
+func (repo *Repository[T1, T2]) Transactions(transactions ...func(*Repository[T1, T2]) *gorm.DB) {
i := 1
for _, tx := range transactions {
- tx(repo)
- repo.RowsError = repo.Transaction.Error
+ repo.Transaction = tx(repo)
if repo.RowsError != nil {
repo.Transaction.Rollback()
return
@@ -65,32 +61,47 @@ func (repo *Repository[T1, T2]) Transactions(transactions ...func(*Repository[T1
}
}
}
-func WhereGivenConstructor[T1 any, T2 any](repo *Repository[T1, T2]) {
- repo.Transaction.Where(&repo.Constructor)
+func WhereGivenConstructor[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
+ tx := repo.Transaction.Where(&repo.Constructor)
+ repo.RowsError = repo.Transaction.Error
+ repo.RowsError = tx.Error
+ return tx
}
-func Find[T1 any, T2 any](repo *Repository[T1, T2]) {
- repo.Transaction.Find(&repo.Result)
+func Find[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
+ tx := repo.Transaction.Find(&repo.Result)
+ repo.RowsError = repo.Transaction.Error
+ repo.RowsError = tx.Error
+ return tx
}
-func FinddAllPaginate[T1 any, T2 any](repo *Repository[T1, T2]) {
- repo.Transaction.Limit(repo.Pagination.Limit).Offset(repo.Pagination.Offset).Find(&repo.Result)
+func FinddAllPaginate[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
+ tx := repo.Transaction.Limit(repo.Pagination.Limit).Offset(repo.Pagination.Offset).Find(&repo.Result)
+ repo.RowsError = tx.Error
+ return tx
}
-func Create[T1 any](repo *Repository[T1, T1]) {
- fmt.Println(repo.Constructor)
- repo.Transaction.Create(&repo.Constructor)
+func Create[T1 any](repo *Repository[T1, T1]) *gorm.DB {
+ tx := repo.Transaction.Create(&repo.Constructor)
repo.Result = repo.Constructor
+ repo.RowsError = tx.Error
+ return tx
}
-func Update[T1 any](repo *Repository[T1, T1]) {
- repo.Transaction.Save(&repo.Constructor)
+func Update[T1 any](repo *Repository[T1, T1]) *gorm.DB {
+ tx := repo.Transaction.Save(&repo.Constructor)
repo.Result = repo.Constructor
+ repo.RowsError = tx.Error
+ return tx
}
-func Delete[T1 any](repo *Repository[T1, T1]) {
- repo.Transaction.Delete(&repo.Constructor)
+func Delete[T1 any](repo *Repository[T1, T1]) *gorm.DB {
+ tx := repo.Transaction.Delete(&repo.Constructor)
+ repo.RowsError = tx.Error
+ return tx
}
-func CustomQuery[T1 any, T2 any](repo *Repository[T1, T2]) {
- repo.Transaction.Raw(repo.CustomQuery.SQL, repo.CustomQuery.Values).Scan(&repo.Result)
+func CustomQuery[T1 any, T2 any](repo *Repository[T1, T2]) *gorm.DB {
+ tx := repo.Transaction.Raw(repo.CustomQuery.SQL, repo.CustomQuery.Values).Scan(&repo.Result)
+ repo.RowsError = tx.Error
+ return tx
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
index e798298cb58036f0b00cc04a919500d6b9c83c09..2d734758bcd0ea31246376da2e71187be98d81ca 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
@@ -1,6 +1,8 @@
package services
import (
+ "errors"
+
"go-dp.abdanhafidz.com/middleware"
"go-dp.abdanhafidz.com/models"
"go-dp.abdanhafidz.com/repositories"
@@ -12,7 +14,7 @@ type LoginConstructor struct {
}
type AuthenticationService struct {
- Service[LoginConstructor, models.Account]
+ Service[LoginConstructor, models.AuthenticatedUser]
}
func (s *AuthenticationService) Authenticate() {
@@ -29,7 +31,17 @@ func (s *AuthenticationService) Authenticate() {
return
}
- s.Result = accountData.Result
+ token, err_tok := middleware.GenerateToken(&accountData.Result)
+
+ if err_tok != nil {
+ s.Error = errors.Join(s.Error, err_tok)
+ }
+
+ accountData.Result.Password = "SECRET"
+ s.Result = models.AuthenticatedUser{
+ Account: accountData.Result,
+ Token: token,
+ }
s.Error = accountData.RowsError
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
index 615a3a595a056802ecd6e130c8f0f04be2643d05..0aca7d7de67105b79deaf19685ec9414df3f5d45 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
@@ -2,6 +2,7 @@ package services
import (
"errors"
+ "fmt"
"go-dp.abdanhafidz.com/middleware"
"go-dp.abdanhafidz.com/models"
@@ -17,15 +18,19 @@ func (s *RegisterService) Create() {
hashed_password, _ := middleware.HashPassword(s.Constructor.Password)
s.Constructor.Password = hashed_password
accountCreated := repositories.CreateAccount(s.Constructor)
+
+ // fmt.Println("Error :", accountCreated.RowsError.Error())
+ fmt.Println(errors.Is(accountCreated.RowsError, gorm.ErrDuplicatedKey))
if errors.Is(accountCreated.RowsError, gorm.ErrDuplicatedKey) {
s.Exception.DataDuplicate = true
- s.Exception.Message = "There is account registered with given data!"
+ s.Exception.Message = "Account with email " + s.Constructor.Email + " already exists!"
return
} else if errors.Is(accountCreated.RowsError, gorm.ErrModelAccessibleFieldsRequired) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidData) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidValue) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidField) {
s.Exception.BadRequest = true
s.Exception.Message = "Bad request!"
return
}
+
s.Result = accountCreated.Result
s.Result.Password = "SECRET"
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
index 7d4a695141f39c703c8e6188f6a2b6eba686d624..6183c94745bf456bad5ea308482bd6c3602ad543 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
@@ -21,8 +21,8 @@ RUN echo "DB_HOST=aws-0-ap-southeast-1.pooler.supabase.com" >> .env && \
echo "DB_NAME=postgres" >> .env && \
echo "HOST_ADDRESS = 0.0.0.0" >> .env && \
echo "HOST_PORT = 7860" >> .env && \
- echo "SALT=NZNZtY7dNPz8l0dWINJZLKafWaJrql1s" >> .env
- echo "LOG_PATH = logs"
+ echo "SALT=NZNZtY7dNPz8l0dWINJZLKafWaJrql1s" >> .env && \
+ echo "LOG_PATH = logs" >> .env
# Build aplikasi
RUN go build -o main .
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.env.example b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.env.example
new file mode 100644
index 0000000000000000000000000000000000000000..7a13c9d5665f68dc7087f208910958eb620cc5c6
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.env.example
@@ -0,0 +1,9 @@
+DB_HOST =
+DB_USER =
+DB_PASSWORD =
+DB_PORT =
+DB_NAME =
+SALT =
+HOST_ADDRESS =
+HOST_PORT =
+LOG_PATH = logs
\ No newline at end of file
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..39a67571ac3a2e6a6e2a972b9550becd551bdded
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/main.yml
@@ -0,0 +1,52 @@
+name: Deploy to Development via Huggingface
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ deploy-to-huggingface:
+ runs-on: ubuntu-latest
+
+ steps:
+ # Checkout repository
+ - name: Checkout Repository
+ uses: actions/checkout@v3
+
+ # Setup Git
+ - name: Setup Git for Huggingface
+ run: |
+ git config --global user.email "abdan.hafidz@gmail.com"
+ git config --global user.name "abdanhafidz"
+
+ # Clone Huggingface Space Repository
+ - name: Clone Huggingface Space
+ env:
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
+ run: |
+ git clone https://huggingface.co/spaces/lifedebugger/api-qobiltu-dev space
+
+ # Update Git Remote URL and Pull Latest Changes
+ - name: Update Remote and Pull Changes
+ env:
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
+ run: |
+ cd space
+ git remote set-url origin https://lifedebugger:$HF_TOKEN@huggingface.co/spaces/lifedebugger/api-qobiltu-dev
+ git pull origin main || echo "No changes to pull"
+
+ # Copy Files to Huggingface Space
+ - name: Copy Files to Space
+ run: |
+ rsync -av --exclude='.git' ./ space/
+
+ # Commit and Push to Huggingface Space
+ - name: Commit and Push to Huggingface
+ env:
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
+ run: |
+ cd space
+ git add .
+ git commit -m "Deploy files from GitHub repository" || echo "No changes to commit"
+ git push origin main || echo "No changes to push"
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..deeb9479f4cb982ccd42fa0d7210b9777509e4ae
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitignore
@@ -0,0 +1,3 @@
+.env
+vendor/
+quzuu-be.exe
\ No newline at end of file
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..7d4a695141f39c703c8e6188f6a2b6eba686d624
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
@@ -0,0 +1,30 @@
+# Gunakan image dasar Golang versi 1.21.6
+FROM golang:1.21.6
+
+# Set working directory
+WORKDIR /app
+
+# Copy go.mod dan go.sum
+COPY go.mod go.sum ./
+
+# Download dependencies
+RUN go mod download
+
+# Copy seluruh kode
+COPY . .
+
+# Buat file .env dengan variabel environment yang dibutuhkan
+RUN echo "DB_HOST=aws-0-ap-southeast-1.pooler.supabase.com" >> .env && \
+ echo "DB_USER=postgres.rdscploxoikqsevhduii" >> .env && \
+ echo "DB_PASSWORD=Qobiltu12233334444" >> .env && \
+ echo "DB_PORT=5432" >> .env && \
+ echo "DB_NAME=postgres" >> .env && \
+ echo "HOST_ADDRESS = 0.0.0.0" >> .env && \
+ echo "HOST_PORT = 7860" >> .env && \
+ echo "SALT=NZNZtY7dNPz8l0dWINJZLKafWaJrql1s" >> .env
+ echo "LOG_PATH = logs"
+# Build aplikasi
+RUN go build -o main .
+
+# Jalankan aplikasi
+CMD ["./main"]
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/DatabaseConfig.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/DatabaseConfig.go
new file mode 100644
index 0000000000000000000000000000000000000000..7dfa116ec7152e6338c764ce5853ad21782bd26f
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/DatabaseConfig.go
@@ -0,0 +1,60 @@
+package config
+
+import (
+ "fmt"
+ "log"
+ "os"
+
+ "gorm.io/driver/postgres"
+ "gorm.io/gorm"
+ "gorm.io/gorm/logger"
+
+ "github.com/joho/godotenv"
+ "go-dp.abdanhafidz.com/models"
+)
+
+func AutoMigrateAll(db *gorm.DB) {
+ // Enable logger to see SQL logs
+ db.Logger.LogMode(logger.Info)
+
+ // Auto-migrate all models
+ err := db.AutoMigrate(
+ &models.Account{},
+ &models.AccountDetails{},
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println("Migration completed successfully.")
+}
+
+var DB *gorm.DB
+var err error
+var Salt string
+
+func init() {
+ godotenv.Load()
+ if err != nil {
+ fmt.Println("Gagal membaca file .env")
+ return
+ }
+ os.Setenv("TZ", "Asia/Jakarta")
+ dbHost := os.Getenv("DB_HOST")
+ dbPort := os.Getenv("DB_PORT")
+ dbUser := os.Getenv("DB_USER")
+ dbPassword := os.Getenv("DB_PASSWORD")
+ dbName := os.Getenv("DB_NAME")
+ Salt := os.Getenv("SALT")
+ dsn := "host=" + dbHost + " user=" + dbUser + " password=" + dbPassword + " dbname=" + dbName + " port=" + dbPort + " sslmode=disable TimeZone=Asia/Jakarta"
+ DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{TranslateError: true})
+ if err != nil {
+ panic(err)
+ }
+ if Salt == "" {
+ Salt = "D3f4u|t"
+ }
+
+ // Call AutoMigrateAll to perform auto-migration
+ AutoMigrateAll(DB)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/EnvConfig.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/EnvConfig.go
new file mode 100644
index 0000000000000000000000000000000000000000..ae4a686c65b851a6e8d960b47a77e4c67470c615
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/config/EnvConfig.go
@@ -0,0 +1,16 @@
+package config
+
+import "os"
+
+var TCP_ADDRESS string
+var LOG_PATH string
+var HOST_ADDRESS string
+var HOST_PORT string
+
+func init() {
+ HOST_ADDRESS = os.Getenv("HOST_ADDRESS")
+ HOST_PORT = os.Getenv("HOST_PORT")
+ TCP_ADDRESS = HOST_ADDRESS + ":" + HOST_PORT
+ LOG_PATH = os.Getenv("LOG_PATH")
+ // Menampilkan nilai variabel lingkungan
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/HomeController.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/HomeController.go
new file mode 100644
index 0000000000000000000000000000000000000000..06cfc3d7fac0385d6aef847a186de2eae7dbb4bb
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/HomeController.go
@@ -0,0 +1,9 @@
+package controller
+
+import "github.com/gin-gonic/gin"
+
+func HomeController(c *gin.Context) {
+ c.JSON(200, gin.H{
+ "message": "Api Qobiltu 2025!",
+ })
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/LoginController.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/LoginController.go
new file mode 100644
index 0000000000000000000000000000000000000000..78d94dcb74fe945b45f4491f85d9b3784a6947a1
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/LoginController.go
@@ -0,0 +1,19 @@
+package controller
+
+import (
+ "github.com/gin-gonic/gin"
+ "go-dp.abdanhafidz.com/models"
+ "go-dp.abdanhafidz.com/services"
+)
+
+func LoginController(c *gin.Context) {
+ authentication := services.AuthenticationService{}
+ loginController := Controller[models.LoginRequest, services.LoginConstructor, models.Account]{
+ Service: &authentication.Service,
+ }
+ loginController.RequestJSON(c)
+ loginController.Service.Constructor.Email = loginController.Request.Email
+ loginController.Service.Constructor.Password = loginController.Request.Password
+ authentication.Authenticate()
+ loginController.Response(c)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/RegisterController.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/RegisterController.go
new file mode 100644
index 0000000000000000000000000000000000000000..25244306baf7b5ff373c0e1c925c584740710eee
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/RegisterController.go
@@ -0,0 +1,19 @@
+package controller
+
+import (
+ "github.com/gin-gonic/gin"
+ "go-dp.abdanhafidz.com/models"
+ "go-dp.abdanhafidz.com/services"
+)
+
+func RegisterController(c *gin.Context) {
+ register := services.RegisterService{}
+ registerController := Controller[models.RegisterRequest, models.Account, models.Account]{
+ Service: ®ister.Service,
+ }
+ registerController.RequestJSON(c)
+ registerController.Service.Constructor.Password = registerController.Request.Password
+ registerController.Service.Constructor.Email = registerController.Request.Email
+ register.Create()
+ registerController.Response(c)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..aba28ae8d9d64b8bb62cda08de9658163b038602
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/controller/controller.go
@@ -0,0 +1,53 @@
+package controller
+
+import (
+ "github.com/gin-gonic/gin"
+ "go-dp.abdanhafidz.com/models"
+ "go-dp.abdanhafidz.com/services"
+ "go-dp.abdanhafidz.com/utils"
+)
+
+type (
+ Controllers interface {
+ RequestJSON(c *gin.Context)
+ Response(c *gin.Context)
+ }
+ Controller[T1 any, T2 any, T3 any] struct {
+ AccountData models.AccountData
+ Request T1
+ Service *services.Service[T2, T3]
+ }
+)
+
+func (controller *Controller[T1, T2, T3]) RequestJSON(c *gin.Context) {
+ cParam, _ := c.Get("accountData")
+ if cParam != nil {
+ controller.AccountData = cParam.(models.AccountData)
+ }
+ errBinding := c.ShouldBindJSON(&controller.Request)
+ if errBinding != nil {
+ utils.ResponseFAIL(c, 400, models.Exception{
+ BadRequest: true,
+ Message: "Invalid Request Type",
+ })
+ return
+ }
+}
+func (controller *Controller[T1, T2, T3]) Response(c *gin.Context) {
+ switch {
+ case controller.Service.Error != nil:
+ utils.ResponseFAIL(c, 500, models.Exception{
+ InternalServerError: true,
+ Message: "Internal Server Error",
+ })
+ utils.LogError(controller.Service.Error)
+ case controller.Service.Exception.Unauthorized:
+ utils.ResponseFAIL(c, 401, controller.Service.Exception)
+ case controller.Service.Exception.DataNotFound:
+ utils.ResponseFAIL(c, 404, controller.Service.Exception)
+ case controller.Service.Exception.Message != "":
+ utils.ResponseFAIL(c, 400, controller.Service.Exception)
+ default:
+ utils.ResponseOK(c, controller.Service.Result)
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..efe450181fc1f3927c0b6658778233ce43ac1d76
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.mod
@@ -0,0 +1,48 @@
+module go-dp.abdanhafidz.com
+
+go 1.21.0
+
+require (
+ github.com/dgrijalva/jwt-go v3.2.0+incompatible
+ github.com/gin-gonic/gin v1.9.1
+ github.com/joho/godotenv v1.5.1
+ golang.org/x/crypto v0.32.0
+ gorm.io/driver/postgres v1.5.4
+ gorm.io/gorm v1.25.5
+)
+
+require (
+ github.com/bytedance/sonic v1.10.2 // indirect
+ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
+ github.com/chenzhuoyu/iasm v0.9.0 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.8 // indirect
+ github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.25.0 // indirect
+ github.com/goccy/go-json v0.10.2 // indirect
+ github.com/jackc/pgpassfile v1.0.0 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
+ github.com/jackc/pgx/v5 v5.5.0 // indirect
+ github.com/jackc/puddle/v2 v2.2.1 // indirect
+ github.com/jinzhu/inflection v1.0.0 // indirect
+ github.com/jinzhu/now v1.1.5 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.6 // indirect
+ github.com/kr/text v0.2.0 // indirect
+ github.com/leodido/go-urn v1.4.0 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pelletier/go-toml/v2 v2.1.0 // indirect
+ github.com/rogpeppe/go-internal v1.11.0 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.2.11 // indirect
+ golang.org/x/arch v0.6.0 // indirect
+ golang.org/x/net v0.34.0 // indirect
+ golang.org/x/sync v0.10.0 // indirect
+ golang.org/x/sys v0.29.0 // indirect
+ golang.org/x/text v0.21.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..79ffaec719b95b12a37e3a787b1576ef5a53db03
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/go.sum
@@ -0,0 +1,138 @@
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
+github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
+github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
+github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
+github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
+github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
+github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
+github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
+github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
+github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
+github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
+github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw=
+github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
+github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
+github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
+github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
+github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
+github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
+github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc=
+golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
+golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
+golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
+golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
+golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
+golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
+golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
+golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
+gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
+gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
+gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..d89532891b559d7d8361e79a1090e77a31cafb95
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/main.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "fmt"
+
+ "go-dp.abdanhafidz.com/config"
+ "go-dp.abdanhafidz.com/router"
+)
+
+func main() {
+ fmt.Println("Server started on ", config.TCP_ADDRESS, ", port :", config.HOST_PORT)
+ router.StartService()
+
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/AuthMiddleware.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/AuthMiddleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..6dc04aa18ba75ef21252e87baf23c0158cc3daea
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/AuthMiddleware.go
@@ -0,0 +1,103 @@
+// auth/auth.go
+
+package middleware
+
+import (
+ "errors"
+ "time"
+
+ "github.com/dgrijalva/jwt-go"
+ "github.com/gin-gonic/gin"
+ "go-dp.abdanhafidz.com/config"
+ "go-dp.abdanhafidz.com/models"
+ "golang.org/x/crypto/bcrypt"
+)
+
+// Define a secret key for signing the JWT token
+var salt = config.Salt
+var secretKey = []byte(salt)
+
+// GenerateToken generates a JWT token for the given user
+func GenerateToken(user *models.Account) (string, error) {
+
+ // Create a new token
+ token := jwt.New(jwt.SigningMethodHS256)
+
+ // Set claims
+ claims := token.Claims.(jwt.MapClaims)
+ claims["id"] = user.IDAccount
+ claims["exp"] = time.Now().Add(time.Hour * 24).Unix() // Token expires in 24 hours
+
+ // Sign the token with the secret key
+ tokenString, err := token.SignedString(secretKey)
+ if err != nil {
+ return "", err
+ }
+
+ return tokenString, nil
+}
+
+// VerifyPassword verifies if the provided password matches the hashed password
+func VerifyPassword(hashedPassword, password string) error {
+ err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
+ if err != nil {
+ return errors.New("invalid password")
+ }
+ return nil
+}
+func HashPassword(password string) (string, error) {
+ bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
+ return string(bytes), err
+}
+
+type CustomClaims struct {
+ jwt.StandardClaims
+ IDUser int `json:"id"`
+}
+
+func VerifyToken(bearer_token string) (int, string, error) {
+ // fmt.Println(bearer_token)
+ token, err := jwt.ParseWithClaims(bearer_token, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
+ return secretKey, nil
+ })
+ if err != nil {
+ return 0, "invalid-token", err
+ }
+ claims, ok := token.Claims.(*CustomClaims)
+ if !ok || !token.Valid {
+ return 0, "invalid-token", err
+ } else if claims.StandardClaims.ExpiresAt != 0 && claims.ExpiresAt < time.Now().Unix() {
+ return 0, "expired", err
+ } else if !ok && token.Valid {
+ return 0, "invalid-token", err
+ }
+
+ return claims.IDUser, "valid", err
+}
+
+func AuthUser(c *gin.Context) {
+ var currAccData models.AccountData
+ if c.Request.Header["Auth-Bearer-Token"] != nil {
+ token := c.Request.Header["Auth-Bearer-Token"]
+ currAccData.IdUser, currAccData.VerifyStatus, currAccData.ErrVerif = VerifyToken(token[0])
+ // fmt.Println("Verify Status :", currAccData.verifyStatus)
+ if currAccData.VerifyStatus == "invalid-token" || currAccData.VerifyStatus == "expired" {
+ currAccData.IdUser = 0
+ message := "Your session is expired, Please re-Login!"
+ SendJSON401(c, &currAccData.VerifyStatus, &message)
+ c.Abort()
+ return
+ }
+ } else {
+ currAccData.IdUser = 0
+ currAccData.VerifyStatus = "no-token"
+ currAccData.ErrVerif = nil
+ message := "You have to Login First!"
+ SendJSON401(c, &currAccData.VerifyStatus, &message)
+ c.Abort()
+ return
+ }
+
+ c.Set("accountData", currAccData)
+ c.Next()
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/middleware.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..5b140903a637aa1d80f02382cb8dbb821c5c01bf
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/middleware.go
@@ -0,0 +1,31 @@
+package middleware
+
+import (
+ "math"
+ "time"
+
+ "gorm.io/gorm"
+)
+
+func RecordCheck(rows *gorm.DB) (string, error) {
+ count := rows.RowsAffected
+ err := rows.Error
+ // fmt.Println(rows)
+ // fmt.Println(count)
+ if count == 0 {
+ return "no-record", err
+ } else if err != nil {
+ return "query-error", err
+ } else {
+ return "ok", err
+ }
+}
+
+func DiffTime(t1 time.Time, t2 time.Time) (int, int, int) {
+ hs := t1.Sub(t2).Hours()
+ hs, mf := math.Modf(hs)
+ ms := mf * 60
+ ms, sf := math.Modf(ms)
+ ss := sf * 60
+ return int(hs), int(ms), int(ss)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/responseMiddleware.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/responseMiddleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..55f3385077839115eadb8dace294229f4b4c3df2
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/middleware/responseMiddleware.go
@@ -0,0 +1,39 @@
+package middleware
+
+import (
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+// SendJSON200 sends a JSON response with HTTP status code 200
+func SendJSON200(c *gin.Context, data interface{}) {
+ c.JSON(http.StatusOK, gin.H{"status": "success", "data": data})
+}
+
+// SendJSON400 sends a JSON response with HTTP status code 400
+func SendJSON400(c *gin.Context, error_status *string, message *string) {
+ c.JSON(http.StatusBadRequest, gin.H{"status": "error", "error-status": error_status, "message": message})
+}
+
+// SendJSON401 sends a JSON response with HTTP status code 401
+func SendJSON401(c *gin.Context, error_status *string, message *string) {
+ c.JSON(http.StatusUnauthorized, gin.H{"status": "error", "error-status": error_status, "message": message})
+}
+
+// SendJSON403 sends a JSON response with HTTP status code 403
+func SendJSON403(c *gin.Context, message *string) {
+ c.JSON(http.StatusForbidden, gin.H{"status": "error", "message": message})
+}
+
+// SendJSON404 sends a JSON response with HTTP status code 404
+func SendJSON404(c *gin.Context, message *string) {
+ c.JSON(http.StatusNotFound, gin.H{"status": "error", "message": message})
+}
+
+// SendJSON500 sends a JSON response with HTTP status code 500
+func SendJSON500(c *gin.Context, error_status *string, message *string) {
+ c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "error-status": error_status, "message": message})
+}
+
+// JSONResponseMiddleware is a middleware that provides functions for sending JSON responses
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/AuthModel.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/AuthModel.go
new file mode 100644
index 0000000000000000000000000000000000000000..5f7c52925aee7c529ef737ae0feb88ee3cb3dbc7
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/AuthModel.go
@@ -0,0 +1,7 @@
+package models
+
+type AccountData struct {
+ IdUser int
+ VerifyStatus string
+ ErrVerif error
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/DatabaseModel.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/DatabaseModel.go
new file mode 100644
index 0000000000000000000000000000000000000000..f048c17a9838438555d6fd795e6c7617c2c1b729
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/DatabaseModel.go
@@ -0,0 +1,30 @@
+package models
+
+import (
+ "time"
+)
+
+type Account struct {
+ IDAccount uint `gorm:"primaryKey" json:"id_account"`
+ Name string `json:"name"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ Password string `json:"password"`
+ PhoneNumber int `json:"phone_number"`
+ CreatedAt time.Time `json:"created_at"`
+ DeletedAt time.Time `json:"deleted_at"`
+}
+
+type AccountDetails struct {
+ IDDetail uint `gorm:"primaryKey" json:"id_detail"`
+ IDAccount uint `json:"id_account"`
+ Province string `json:"province"`
+ City string `json:"city"`
+ Institution string `json:"institution"`
+ UpdatedAt time.Time `json:"updated_at"`
+ DeletedAt time.Time `json:"deleted_at"`
+}
+
+// Gorm table name settings
+func (Account) TableName() string { return "account" }
+func (AccountDetails) TableName() string { return "account_details" }
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ExceptionModel.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ExceptionModel.go
new file mode 100644
index 0000000000000000000000000000000000000000..b4e76203056e9c2ee6ea5b447b6d3b90842f1d88
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ExceptionModel.go
@@ -0,0 +1,11 @@
+package models
+
+type Exception struct {
+ Unauthorized bool `json:"unauthorized,omitempty"`
+ BadRequest bool `json:"bad_request,omitempty"`
+ DataNotFound bool `json:"data_not_found,omitempty"`
+ InternalServerError bool `json:"internal_server_error,omitempty"`
+ DataDuplicate bool `json:"data_duplicate,omitempty"`
+ QueryError bool `json:"query_error,omitempty"`
+ Message string `json:"message,omitempty"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/RequestModel.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/RequestModel.go
new file mode 100644
index 0000000000000000000000000000000000000000..cfae888ec34edbdd3f5b6588050e2b382c63792d
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/RequestModel.go
@@ -0,0 +1,13 @@
+package models
+
+type LoginRequest struct {
+ Email string `json:"email" binding:"required"`
+ Password string `json:"password" binding:"required"`
+}
+
+type RegisterRequest struct {
+ Name string `json:"name"`
+ Email string `json:"email" binding:"required,email"`
+ Phone int `json:"phone"`
+ Password string `json:"password" binding:"required"`
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ResponseModel.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ResponseModel.go
new file mode 100644
index 0000000000000000000000000000000000000000..9ce401186cb92d50737155815c1f511164b86407
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/models/ResponseModel.go
@@ -0,0 +1 @@
+package models
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
new file mode 100644
index 0000000000000000000000000000000000000000..79e43f0707a436c344e5cddd53187d428274bde9
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/AccountRepository.go
@@ -0,0 +1,24 @@
+package repositories
+
+import (
+ "fmt"
+
+ "go-dp.abdanhafidz.com/models"
+)
+
+func GetAccountbyEmailPassword(username string, password string) Repository[models.Account, models.Account] {
+ repo := Construct[models.Account, models.Account](
+ models.Account{Username: username, Password: password},
+ )
+ Find(repo)
+ return *repo
+}
+
+func CreateAccount(account models.Account) Repository[models.Account, models.Account] {
+ fmt.Println(account)
+ repo := Construct[models.Account, models.Account](
+ account,
+ )
+ Create(repo)
+ return *repo
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/DatabaseScoope.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/DatabaseScoope.go
new file mode 100644
index 0000000000000000000000000000000000000000..e11987b79a890dc6e84923fe7c241c6e8fa99c35
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/DatabaseScoope.go
@@ -0,0 +1,5 @@
+package repositories
+
+import "go-dp.abdanhafidz.com/config"
+
+var db = config.DB
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
new file mode 100644
index 0000000000000000000000000000000000000000..c82f550472331b9b8d0f5d4cdcf5aab441e5cdfa
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/repositories/repositories.go
@@ -0,0 +1,96 @@
+package repositories
+
+import (
+ "fmt"
+
+ "go-dp.abdanhafidz.com/config"
+ "gorm.io/gorm"
+)
+
+type Repositories interface {
+ FindAllPaginate()
+ Where()
+ Find()
+ Create()
+ Update()
+ CustomQuery()
+ Delete()
+}
+type PaginationConstructor struct {
+ Limit int
+ Offset int
+ Filter string
+}
+
+type CustomQueryConstructor struct {
+ SQL string
+ Values interface{}
+}
+
+type Repository[TConstructor any, TResult any] struct {
+ Constructor TConstructor
+ Pagination PaginationConstructor
+ CustomQuery CustomQueryConstructor
+ Result TResult
+ Transaction *gorm.DB
+ RowsCount int
+ NoRecord bool
+ RowsError error
+}
+
+func Construct[TConstructor any, TResult any](constructor ...TConstructor) *Repository[TConstructor, TResult] {
+ fmt.Println("Len = ", len(constructor))
+ if len(constructor) == 1 {
+ return &Repository[TConstructor, TResult]{
+ Constructor: constructor[0],
+ Transaction: config.DB,
+ }
+ }
+ return &Repository[TConstructor, TResult]{
+ Constructor: constructor[0],
+ Transaction: config.DB.Begin(),
+ }
+}
+func (repo *Repository[T1, T2]) Transactions(transactions ...func(*Repository[T1, T2])) {
+ i := 1
+ for _, tx := range transactions {
+ tx(repo)
+ repo.RowsError = repo.Transaction.Error
+ if repo.RowsError != nil {
+ repo.Transaction.Rollback()
+ return
+ } else {
+ repo.Transaction.SavePoint("Save Point : " + string(i))
+ repo.Transaction.Commit()
+ }
+ }
+}
+func WhereGivenConstructor[T1 any, T2 any](repo *Repository[T1, T2]) {
+ repo.Transaction.Where(&repo.Constructor)
+}
+func Find[T1 any, T2 any](repo *Repository[T1, T2]) {
+ repo.Transaction.Find(&repo.Result)
+}
+
+func FinddAllPaginate[T1 any, T2 any](repo *Repository[T1, T2]) {
+ repo.Transaction.Limit(repo.Pagination.Limit).Offset(repo.Pagination.Offset).Find(&repo.Result)
+}
+
+func Create[T1 any](repo *Repository[T1, T1]) {
+ fmt.Println(repo.Constructor)
+ repo.Transaction.Create(&repo.Constructor)
+ repo.Result = repo.Constructor
+}
+
+func Update[T1 any](repo *Repository[T1, T1]) {
+ repo.Transaction.Save(&repo.Constructor)
+ repo.Result = repo.Constructor
+}
+
+func Delete[T1 any](repo *Repository[T1, T1]) {
+ repo.Transaction.Delete(&repo.Constructor)
+}
+
+func CustomQuery[T1 any, T2 any](repo *Repository[T1, T2]) {
+ repo.Transaction.Raw(repo.CustomQuery.SQL, repo.CustomQuery.Values).Scan(&repo.Result)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
new file mode 100644
index 0000000000000000000000000000000000000000..27ce310f782a252cab580d08873505f02ec38a03
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/router/router.go
@@ -0,0 +1,18 @@
+package router
+
+import (
+ "github.com/gin-gonic/gin"
+ "go-dp.abdanhafidz.com/config"
+ "go-dp.abdanhafidz.com/controller"
+)
+
+func StartService() {
+ router := gin.Default()
+ routerGroup := router.Group("/api/v1")
+ {
+ routerGroup.GET("/", controller.HomeController)
+ routerGroup.POST("/login", controller.LoginController)
+ routerGroup.POST("/register", controller.RegisterController)
+ }
+ router.Run(config.TCP_ADDRESS)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
new file mode 100644
index 0000000000000000000000000000000000000000..e798298cb58036f0b00cc04a919500d6b9c83c09
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/LoginService.go
@@ -0,0 +1,36 @@
+package services
+
+import (
+ "go-dp.abdanhafidz.com/middleware"
+ "go-dp.abdanhafidz.com/models"
+ "go-dp.abdanhafidz.com/repositories"
+)
+
+type LoginConstructor struct {
+ Email string
+ Password string
+}
+
+type AuthenticationService struct {
+ Service[LoginConstructor, models.Account]
+}
+
+func (s *AuthenticationService) Authenticate() {
+ accountData := repositories.GetAccountbyEmailPassword(s.Constructor.Email, s.Constructor.Password)
+ if accountData.NoRecord {
+ s.Exception.DataNotFound = true
+ s.Exception.Message = "there is no account with given email!"
+ return
+ }
+
+ if middleware.VerifyPassword(accountData.Result.Password, s.Constructor.Password) != nil {
+ s.Exception.Unauthorized = true
+ s.Exception.Message = "incorrect password!"
+ return
+ }
+
+ s.Result = accountData.Result
+ s.Error = accountData.RowsError
+}
+
+// LoginHandler handles user login
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
new file mode 100644
index 0000000000000000000000000000000000000000..615a3a595a056802ecd6e130c8f0f04be2643d05
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/RegisterService.go
@@ -0,0 +1,31 @@
+package services
+
+import (
+ "errors"
+
+ "go-dp.abdanhafidz.com/middleware"
+ "go-dp.abdanhafidz.com/models"
+ "go-dp.abdanhafidz.com/repositories"
+ "gorm.io/gorm"
+)
+
+type RegisterService struct {
+ Service[models.Account, models.Account]
+}
+
+func (s *RegisterService) Create() {
+ hashed_password, _ := middleware.HashPassword(s.Constructor.Password)
+ s.Constructor.Password = hashed_password
+ accountCreated := repositories.CreateAccount(s.Constructor)
+ if errors.Is(accountCreated.RowsError, gorm.ErrDuplicatedKey) {
+ s.Exception.DataDuplicate = true
+ s.Exception.Message = "There is account registered with given data!"
+ return
+ } else if errors.Is(accountCreated.RowsError, gorm.ErrModelAccessibleFieldsRequired) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidData) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidValue) || errors.Is(accountCreated.RowsError, gorm.ErrInvalidField) {
+ s.Exception.BadRequest = true
+ s.Exception.Message = "Bad request!"
+ return
+ }
+ s.Result = accountCreated.Result
+ s.Result.Password = "SECRET"
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/services.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/services.go
new file mode 100644
index 0000000000000000000000000000000000000000..e684786fdd3dc071dd085b87f27a0e7bb8becfff
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/services/services.go
@@ -0,0 +1,31 @@
+package services
+
+import "go-dp.abdanhafidz.com/models"
+
+type (
+ Services interface {
+ Retrieve()
+ Update()
+ Create()
+ Delete()
+ Validate()
+ Authenticate()
+ Authorize()
+ }
+ Service[TConstructor any, TResult any] struct {
+ Constructor TConstructor
+ Result TResult
+ Exception models.Exception
+ Error error
+ }
+)
+
+func Construct[TConstructor any, TResult any](constructor ...TConstructor) *Service[TConstructor, TResult] {
+ if len(constructor) == 0 {
+ return &Service[TConstructor, TResult]{}
+ }
+
+ return &Service[TConstructor, TResult]{
+ Constructor: constructor[0],
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitattributes b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..a6344aac8c09253b3b630fb776ae94478aa0275b
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitattributes
@@ -0,0 +1,35 @@
+*.7z filter=lfs diff=lfs merge=lfs -text
+*.arrow filter=lfs diff=lfs merge=lfs -text
+*.bin filter=lfs diff=lfs merge=lfs -text
+*.bz2 filter=lfs diff=lfs merge=lfs -text
+*.ckpt filter=lfs diff=lfs merge=lfs -text
+*.ftz filter=lfs diff=lfs merge=lfs -text
+*.gz filter=lfs diff=lfs merge=lfs -text
+*.h5 filter=lfs diff=lfs merge=lfs -text
+*.joblib filter=lfs diff=lfs merge=lfs -text
+*.lfs.* filter=lfs diff=lfs merge=lfs -text
+*.mlmodel filter=lfs diff=lfs merge=lfs -text
+*.model filter=lfs diff=lfs merge=lfs -text
+*.msgpack filter=lfs diff=lfs merge=lfs -text
+*.npy filter=lfs diff=lfs merge=lfs -text
+*.npz filter=lfs diff=lfs merge=lfs -text
+*.onnx filter=lfs diff=lfs merge=lfs -text
+*.ot filter=lfs diff=lfs merge=lfs -text
+*.parquet filter=lfs diff=lfs merge=lfs -text
+*.pb filter=lfs diff=lfs merge=lfs -text
+*.pickle filter=lfs diff=lfs merge=lfs -text
+*.pkl filter=lfs diff=lfs merge=lfs -text
+*.pt filter=lfs diff=lfs merge=lfs -text
+*.pth filter=lfs diff=lfs merge=lfs -text
+*.rar filter=lfs diff=lfs merge=lfs -text
+*.safetensors filter=lfs diff=lfs merge=lfs -text
+saved_model/**/* filter=lfs diff=lfs merge=lfs -text
+*.tar.* filter=lfs diff=lfs merge=lfs -text
+*.tar filter=lfs diff=lfs merge=lfs -text
+*.tflite filter=lfs diff=lfs merge=lfs -text
+*.tgz filter=lfs diff=lfs merge=lfs -text
+*.wasm filter=lfs diff=lfs merge=lfs -text
+*.xz filter=lfs diff=lfs merge=lfs -text
+*.zip filter=lfs diff=lfs merge=lfs -text
+*.zst filter=lfs diff=lfs merge=lfs -text
+*tfevents* filter=lfs diff=lfs merge=lfs -text
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
new file mode 100644
index 0000000000000000000000000000000000000000..7a3e7df219e76ae94bc17ef748c6dfe7f8afeb93
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
@@ -0,0 +1,39 @@
+package utils
+
+import (
+ "net/http"
+ "reflect"
+
+ "github.com/gin-gonic/gin"
+ "go-dp.abdanhafidz.com/models"
+ "go-dp.abdanhafidz.com/services"
+)
+
+func ResponseOK(c *gin.Context, data any) {
+ c.JSON(http.StatusOK, gin.H{"message": "Request Success!", "data": data, "status": "success"})
+}
+
+func ResponseFAIL(c *gin.Context, status int, exception models.Exception) {
+ message := exception.Message
+ exception.Message = ""
+ c.AbortWithStatusJSON(status, gin.H{"status": "error", "message": message, "error": exception})
+ return
+}
+
+func SendResponse(c *gin.Context, data services.Service[any, any]) {
+ if reflect.ValueOf(data.Exception).IsNil() {
+ ResponseOK(c, data)
+ } else {
+ if data.Exception.Unauthorized {
+ ResponseFAIL(c, 401, data.Exception)
+ } else if data.Exception.BadRequest {
+ ResponseFAIL(c, 400, data.Exception)
+ } else if data.Exception.DataNotFound {
+ ResponseFAIL(c, 404, data.Exception)
+ } else if data.Exception.InternalServerError {
+ ResponseFAIL(c, 500, data.Exception)
+ } else {
+ ResponseFAIL(c, 403, data.Exception)
+ }
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Exception.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Exception.go
new file mode 100644
index 0000000000000000000000000000000000000000..0b273b83d7e7cb140e2b9df6801f161f0ff08be4
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Exception.go
@@ -0,0 +1 @@
+package utils
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Helper.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Helper.go
new file mode 100644
index 0000000000000000000000000000000000000000..3e36fc57268d71ef58e1511b038f8c56e7f8928c
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Helper.go
@@ -0,0 +1,11 @@
+package utils
+
+import (
+ "github.com/gin-gonic/gin"
+ "go-dp.abdanhafidz.com/models"
+)
+
+func GetAccount(c *gin.Context) models.AccountData {
+ cParam, _ := c.Get("accountData")
+ return cParam.(models.AccountData)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
new file mode 100644
index 0000000000000000000000000000000000000000..98d4a9447588e08458738263844c45eb0d6ae98b
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
@@ -0,0 +1,19 @@
+package utils
+
+import (
+ "log"
+ "os"
+
+ "go-dp.abdanhafidz.com/config"
+)
+
+func LogError(errorLogged error) {
+ file, err := os.OpenFile(config.LOG_PATH+"error_log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ log.SetOutput(file)
+
+ log.Println(errorLogged)
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/utils.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/utils.go
new file mode 100644
index 0000000000000000000000000000000000000000..480645d434c01f7152d6747171e5e3ea53969533
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/utils.go
@@ -0,0 +1,9 @@
+package utils
+
+func ternaryMessage(condition bool, valueIfTrue string, valueIfFalse string) string {
+ if condition {
+ return valueIfTrue
+ } else {
+ return valueIfFalse
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
index 7a3e7df219e76ae94bc17ef748c6dfe7f8afeb93..da8a2be04c3d8ef4a28939d5c40cdb3574efb650 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
@@ -11,6 +11,7 @@ import (
func ResponseOK(c *gin.Context, data any) {
c.JSON(http.StatusOK, gin.H{"message": "Request Success!", "data": data, "status": "success"})
+ return
}
func ResponseFAIL(c *gin.Context, status int, exception models.Exception) {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
index da8a2be04c3d8ef4a28939d5c40cdb3574efb650..68eb789f1daf3ed57cfe6ea600c948f2ee0be9cc 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
@@ -18,6 +18,7 @@ func ResponseFAIL(c *gin.Context, status int, exception models.Exception) {
message := exception.Message
exception.Message = ""
c.AbortWithStatusJSON(status, gin.H{"status": "error", "message": message, "error": exception})
+ c.
return
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
index 68eb789f1daf3ed57cfe6ea600c948f2ee0be9cc..da8a2be04c3d8ef4a28939d5c40cdb3574efb650 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
@@ -18,7 +18,6 @@ func ResponseFAIL(c *gin.Context, status int, exception models.Exception) {
message := exception.Message
exception.Message = ""
c.AbortWithStatusJSON(status, gin.H{"status": "error", "message": message, "error": exception})
- c.
return
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
index 98d4a9447588e08458738263844c45eb0d6ae98b..6473e869e33fbe10150a80de942cfcb9f46a4749 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
@@ -15,5 +15,5 @@ func LogError(errorLogged error) {
log.SetOutput(file)
- log.Println(errorLogged)
+ log.Println("Error Log :", errorLogged)
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
index 6473e869e33fbe10150a80de942cfcb9f46a4749..2bb3f755429b22e6c3d62cdf95ac630010eacae6 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
@@ -1,6 +1,7 @@
package utils
import (
+ "fmt"
"log"
"os"
@@ -8,6 +9,7 @@ import (
)
func LogError(errorLogged error) {
+ fmt.Println("There is an error!")
file, err := os.OpenFile(config.LOG_PATH+"error_log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
index da8a2be04c3d8ef4a28939d5c40cdb3574efb650..455109a156badaed17549222ed98a6b0d268b98a 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/APIResponse.go
@@ -4,9 +4,9 @@ import (
"net/http"
"reflect"
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
"github.com/gin-gonic/gin"
- "go-dp.abdanhafidz.com/models"
- "go-dp.abdanhafidz.com/services"
)
func ResponseOK(c *gin.Context, data any) {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Helper.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Helper.go
index 3e36fc57268d71ef58e1511b038f8c56e7f8928c..4742c7fa5c357435cb9bdfb86e48828670a9792b 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Helper.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Helper.go
@@ -1,8 +1,8 @@
package utils
import (
+ "api.qobiltu.id/models"
"github.com/gin-gonic/gin"
- "go-dp.abdanhafidz.com/models"
)
func GetAccount(c *gin.Context) models.AccountData {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
index 2bb3f755429b22e6c3d62cdf95ac630010eacae6..d0f8ec5600ab6a524e594198900de51e76b460a8 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
@@ -5,7 +5,7 @@ import (
"log"
"os"
- "go-dp.abdanhafidz.com/config"
+ "api.qobiltu.id/config"
)
func LogError(errorLogged error) {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/api_response.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/api_response.go
new file mode 100644
index 0000000000000000000000000000000000000000..aae210ee39978d67c5412c665a041f45685f0c13
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/api_response.go
@@ -0,0 +1,52 @@
+package utils
+
+import (
+ "net/http"
+ "reflect"
+
+ "api.qobiltu.id/models"
+ "api.qobiltu.id/services"
+ "github.com/gin-gonic/gin"
+)
+
+func ResponseOK(c *gin.Context, data any) {
+ res := models.SuccessResponse{
+ Status: "success",
+ Message: "Data retrieved successfully!",
+ Data: data,
+ MetaData: c.Request.Body,
+ }
+ c.JSON(http.StatusOK, res)
+ return
+}
+
+func ResponseFAIL(c *gin.Context, status int, exception models.Exception) {
+ message := exception.Message
+ exception.Message = ""
+ res := models.ErrorResponse{
+ Status: "error",
+ Message: message,
+ Errors: exception,
+ MetaData: c.Request.Body,
+ }
+ c.AbortWithStatusJSON(status, res)
+ return
+}
+
+func SendResponse(c *gin.Context, data services.Service[any, any]) {
+ if reflect.ValueOf(data.Exception).IsNil() {
+ ResponseOK(c, data)
+ } else {
+ if data.Exception.Unauthorized {
+ ResponseFAIL(c, 401, data.Exception)
+ } else if data.Exception.BadRequest {
+ ResponseFAIL(c, 400, data.Exception)
+ } else if data.Exception.DataNotFound {
+ ResponseFAIL(c, 404, data.Exception)
+ } else if data.Exception.InternalServerError {
+ ResponseFAIL(c, 500, data.Exception)
+ } else {
+ ResponseFAIL(c, 403, data.Exception)
+ }
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/util.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/util.go
new file mode 100644
index 0000000000000000000000000000000000000000..480645d434c01f7152d6747171e5e3ea53969533
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/util.go
@@ -0,0 +1,9 @@
+package utils
+
+func ternaryMessage(condition bool, valueIfTrue string, valueIfFalse string) string {
+ if condition {
+ return valueIfTrue
+ } else {
+ return valueIfFalse
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
index d0f8ec5600ab6a524e594198900de51e76b460a8..a3d0718f321d11e5b83abd02c43e7cd849ce5cad 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
@@ -10,7 +10,7 @@ import (
func LogError(errorLogged error) {
fmt.Println("There is an error!")
- file, err := os.OpenFile(config.LOG_PATH+"error_log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
+ file, err := os.OpenFile(config.LOG_PATH+"/error_log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/seeds/city.json b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/seeds/city.json
new file mode 100644
index 0000000000000000000000000000000000000000..56d547fed553f1a98f9e13e0f161a9c7a83edf33
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/seeds/city.json
@@ -0,0 +1,4114 @@
+[
+ {
+ "id": 1,
+ "type": "Kabupaten",
+ "name": "Aceh Barat",
+ "code": "05",
+ "full_code": "1105",
+ "provinsi_id": 1
+ },
+ {
+ "id": 2,
+ "type": "Kabupaten",
+ "name": "Aceh Barat Daya",
+ "code": "12",
+ "full_code": "1112",
+ "provinsi_id": 1
+ },
+ {
+ "id": 3,
+ "type": "Kabupaten",
+ "name": "Sabu Raijua",
+ "code": "20",
+ "full_code": "5320",
+ "provinsi_id": 23
+ },
+ {
+ "id": 4,
+ "type": "Kota",
+ "name": "Salatiga",
+ "code": "73",
+ "full_code": "3373",
+ "provinsi_id": 10
+ },
+ {
+ "id": 5,
+ "type": "Kabupaten",
+ "name": "Aceh Besar",
+ "code": "06",
+ "full_code": "1106",
+ "provinsi_id": 1
+ },
+ {
+ "id": 6,
+ "type": "Kabupaten",
+ "name": "Aceh Jaya",
+ "code": "14",
+ "full_code": "1114",
+ "provinsi_id": 1
+ },
+ {
+ "id": 7,
+ "type": "Kabupaten",
+ "name": "Aceh Selatan",
+ "code": "01",
+ "full_code": "1101",
+ "provinsi_id": 1
+ },
+ {
+ "id": 8,
+ "type": "Kabupaten",
+ "name": "Aceh Singkil",
+ "code": "10",
+ "full_code": "1110",
+ "provinsi_id": 1
+ },
+ {
+ "id": 9,
+ "type": "Kabupaten",
+ "name": "Aceh Tamiang",
+ "code": "16",
+ "full_code": "1116",
+ "provinsi_id": 1
+ },
+ {
+ "id": 10,
+ "type": "Kabupaten",
+ "name": "Aceh Tengah",
+ "code": "04",
+ "full_code": "1104",
+ "provinsi_id": 1
+ },
+ {
+ "id": 11,
+ "type": "Kabupaten",
+ "name": "Aceh Tenggara",
+ "code": "02",
+ "full_code": "1102",
+ "provinsi_id": 1
+ },
+ {
+ "id": 12,
+ "type": "Kabupaten",
+ "name": "Aceh Timur",
+ "code": "03",
+ "full_code": "1103",
+ "provinsi_id": 1
+ },
+ {
+ "id": 13,
+ "type": "Kabupaten",
+ "name": "Gorontalo Utara",
+ "code": "05",
+ "full_code": "7505",
+ "provinsi_id": 7
+ },
+ {
+ "id": 14,
+ "type": "Kabupaten",
+ "name": "Aceh Utara",
+ "code": "08",
+ "full_code": "1108",
+ "provinsi_id": 1
+ },
+ {
+ "id": 15,
+ "type": "Kabupaten",
+ "name": "Agam",
+ "code": "06",
+ "full_code": "1306",
+ "provinsi_id": 36
+ },
+ {
+ "id": 16,
+ "type": "Kabupaten",
+ "name": "Alor",
+ "code": "05",
+ "full_code": "5305",
+ "provinsi_id": 23
+ },
+ {
+ "id": 17,
+ "type": "Kota",
+ "name": "Ambon",
+ "code": "71",
+ "full_code": "8171",
+ "provinsi_id": 20
+ },
+ {
+ "id": 18,
+ "type": "Kabupaten",
+ "name": "Gowa",
+ "code": "06",
+ "full_code": "7306",
+ "provinsi_id": 32
+ },
+ {
+ "id": 19,
+ "type": "Kota",
+ "name": "Samarinda",
+ "code": "72",
+ "full_code": "6472",
+ "provinsi_id": 15
+ },
+ {
+ "id": 20,
+ "type": "Kabupaten",
+ "name": "Asahan",
+ "code": "09",
+ "full_code": "1209",
+ "provinsi_id": 38
+ },
+ {
+ "id": 21,
+ "type": "Kabupaten",
+ "name": "Sambas",
+ "code": "01",
+ "full_code": "6101",
+ "provinsi_id": 12
+ },
+ {
+ "id": 22,
+ "type": "Kabupaten",
+ "name": "Asmat",
+ "code": "04",
+ "full_code": "9304",
+ "provinsi_id": 28
+ },
+ {
+ "id": 23,
+ "type": "Kabupaten",
+ "name": "Samosir",
+ "code": "17",
+ "full_code": "1217",
+ "provinsi_id": 38
+ },
+ {
+ "id": 24,
+ "type": "Kabupaten",
+ "name": "Badung",
+ "code": "03",
+ "full_code": "5103",
+ "provinsi_id": 2
+ },
+ {
+ "id": 25,
+ "type": "Kabupaten",
+ "name": "Sampang",
+ "code": "27",
+ "full_code": "3527",
+ "provinsi_id": 11
+ },
+ {
+ "id": 26,
+ "type": "Kabupaten",
+ "name": "Balangan",
+ "code": "11",
+ "full_code": "6311",
+ "provinsi_id": 13
+ },
+ {
+ "id": 27,
+ "type": "Kabupaten",
+ "name": "Sanggau",
+ "code": "03",
+ "full_code": "6103",
+ "provinsi_id": 12
+ },
+ {
+ "id": 28,
+ "type": "Kabupaten",
+ "name": "Sarmi",
+ "code": "10",
+ "full_code": "9110",
+ "provinsi_id": 24
+ },
+ {
+ "id": 29,
+ "type": "Kabupaten",
+ "name": "Gresik",
+ "code": "25",
+ "full_code": "3525",
+ "provinsi_id": 11
+ },
+ {
+ "id": 30,
+ "type": "Kabupaten",
+ "name": "Grobogan",
+ "code": "15",
+ "full_code": "3315",
+ "provinsi_id": 10
+ },
+ {
+ "id": 31,
+ "type": "Kabupaten",
+ "name": "Gunung Mas",
+ "code": "10",
+ "full_code": "6210",
+ "provinsi_id": 14
+ },
+ {
+ "id": 32,
+ "type": "Kabupaten",
+ "name": "Gunungkidul",
+ "code": "03",
+ "full_code": "3403",
+ "provinsi_id": 5
+ },
+ {
+ "id": 33,
+ "type": "Kabupaten",
+ "name": "Sarolangun",
+ "code": "03",
+ "full_code": "1503",
+ "provinsi_id": 8
+ },
+ {
+ "id": 34,
+ "type": "Kota",
+ "name": "Gunungsitoli",
+ "code": "78",
+ "full_code": "1278",
+ "provinsi_id": 38
+ },
+ {
+ "id": 35,
+ "type": "Kota",
+ "name": "Sawahlunto",
+ "code": "73",
+ "full_code": "1373",
+ "provinsi_id": 36
+ },
+ {
+ "id": 36,
+ "type": "Kabupaten",
+ "name": "Halmahera Barat",
+ "code": "01",
+ "full_code": "8201",
+ "provinsi_id": 21
+ },
+ {
+ "id": 37,
+ "type": "Kabupaten",
+ "name": "Sekadau",
+ "code": "09",
+ "full_code": "6109",
+ "provinsi_id": 12
+ },
+ {
+ "id": 38,
+ "type": "Kabupaten",
+ "name": "Halmahera Selatan",
+ "code": "04",
+ "full_code": "8204",
+ "provinsi_id": 21
+ },
+ {
+ "id": 39,
+ "type": "Kabupaten",
+ "name": "Seluma",
+ "code": "05",
+ "full_code": "1705",
+ "provinsi_id": 4
+ },
+ {
+ "id": 40,
+ "type": "Kabupaten",
+ "name": "Halmahera Tengah",
+ "code": "02",
+ "full_code": "8202",
+ "provinsi_id": 21
+ },
+ {
+ "id": 41,
+ "type": "Kabupaten",
+ "name": "Semarang",
+ "code": "22",
+ "full_code": "3322",
+ "provinsi_id": 10
+ },
+ {
+ "id": 42,
+ "type": "Kota",
+ "name": "Semarang",
+ "code": "74",
+ "full_code": "3374",
+ "provinsi_id": 10
+ },
+ {
+ "id": 43,
+ "type": "Kabupaten",
+ "name": "Halmahera Timur",
+ "code": "06",
+ "full_code": "8206",
+ "provinsi_id": 21
+ },
+ {
+ "id": 44,
+ "type": "Kabupaten",
+ "name": "Seram Bagian Barat",
+ "code": "06",
+ "full_code": "8106",
+ "provinsi_id": 20
+ },
+ {
+ "id": 45,
+ "type": "Kabupaten",
+ "name": "Halmahera Utara",
+ "code": "03",
+ "full_code": "8203",
+ "provinsi_id": 21
+ },
+ {
+ "id": 46,
+ "type": "Kabupaten",
+ "name": "Seram Bagian Timur",
+ "code": "05",
+ "full_code": "8105",
+ "provinsi_id": 20
+ },
+ {
+ "id": 47,
+ "type": "Kabupaten",
+ "name": "Hulu Sungai Selatan",
+ "code": "06",
+ "full_code": "6306",
+ "provinsi_id": 13
+ },
+ {
+ "id": 48,
+ "type": "Kabupaten",
+ "name": "Hulu Sungai Tengah",
+ "code": "07",
+ "full_code": "6307",
+ "provinsi_id": 13
+ },
+ {
+ "id": 49,
+ "type": "Kabupaten",
+ "name": "Hulu Sungai Utara",
+ "code": "08",
+ "full_code": "6308",
+ "provinsi_id": 13
+ },
+ {
+ "id": 50,
+ "type": "Kota",
+ "name": "Serang",
+ "code": "73",
+ "full_code": "3673",
+ "provinsi_id": 3
+ },
+ {
+ "id": 51,
+ "type": "Kabupaten",
+ "name": "Serang",
+ "code": "04",
+ "full_code": "3604",
+ "provinsi_id": 3
+ },
+ {
+ "id": 52,
+ "type": "Kabupaten",
+ "name": "Humbang Hasundutan",
+ "code": "16",
+ "full_code": "1216",
+ "provinsi_id": 38
+ },
+ {
+ "id": 53,
+ "type": "Kota",
+ "name": "Balikpapan",
+ "code": "71",
+ "full_code": "6471",
+ "provinsi_id": 15
+ },
+ {
+ "id": 54,
+ "type": "Kabupaten",
+ "name": "Indragiri Hilir",
+ "code": "04",
+ "full_code": "1404",
+ "provinsi_id": 30
+ },
+ {
+ "id": 55,
+ "type": "Kabupaten",
+ "name": "Indragiri Hulu",
+ "code": "02",
+ "full_code": "1402",
+ "provinsi_id": 30
+ },
+ {
+ "id": 56,
+ "type": "Kota",
+ "name": "Banda Aceh",
+ "code": "71",
+ "full_code": "1171",
+ "provinsi_id": 1
+ },
+ {
+ "id": 57,
+ "type": "Kota",
+ "name": "Bandar Lampung",
+ "code": "71",
+ "full_code": "1871",
+ "provinsi_id": 19
+ },
+ {
+ "id": 58,
+ "type": "Kota",
+ "name": "Bandung",
+ "code": "73",
+ "full_code": "3273",
+ "provinsi_id": 9
+ },
+ {
+ "id": 59,
+ "type": "Kabupaten",
+ "name": "Bandung",
+ "code": "04",
+ "full_code": "3204",
+ "provinsi_id": 9
+ },
+ {
+ "id": 60,
+ "type": "Kabupaten",
+ "name": "Bandung Barat",
+ "code": "17",
+ "full_code": "3217",
+ "provinsi_id": 9
+ },
+ {
+ "id": 61,
+ "type": "Kabupaten",
+ "name": "Banggai",
+ "code": "01",
+ "full_code": "7201",
+ "provinsi_id": 33
+ },
+ {
+ "id": 62,
+ "type": "Kabupaten",
+ "name": "Banggai Kepulauan",
+ "code": "07",
+ "full_code": "7207",
+ "provinsi_id": 33
+ },
+ {
+ "id": 63,
+ "type": "Kabupaten",
+ "name": "Banggai Laut",
+ "code": "11",
+ "full_code": "7211",
+ "provinsi_id": 33
+ },
+ {
+ "id": 64,
+ "type": "Kabupaten",
+ "name": "Bangka",
+ "code": "01",
+ "full_code": "1901",
+ "provinsi_id": 17
+ },
+ {
+ "id": 65,
+ "type": "Kabupaten",
+ "name": "Bangka Barat",
+ "code": "05",
+ "full_code": "1905",
+ "provinsi_id": 17
+ },
+ {
+ "id": 66,
+ "type": "Kabupaten",
+ "name": "Bangka Selatan",
+ "code": "03",
+ "full_code": "1903",
+ "provinsi_id": 17
+ },
+ {
+ "id": 67,
+ "type": "Kabupaten",
+ "name": "Bangka Tengah",
+ "code": "04",
+ "full_code": "1904",
+ "provinsi_id": 17
+ },
+ {
+ "id": 68,
+ "type": "Kabupaten",
+ "name": "Bangkalan",
+ "code": "26",
+ "full_code": "3526",
+ "provinsi_id": 11
+ },
+ {
+ "id": 69,
+ "type": "Kabupaten",
+ "name": "Bangli",
+ "code": "06",
+ "full_code": "5106",
+ "provinsi_id": 2
+ },
+ {
+ "id": 70,
+ "type": "Kota",
+ "name": "Banjar",
+ "code": "79",
+ "full_code": "3279",
+ "provinsi_id": 9
+ },
+ {
+ "id": 71,
+ "type": "Kabupaten",
+ "name": "Banjar",
+ "code": "03",
+ "full_code": "6303",
+ "provinsi_id": 13
+ },
+ {
+ "id": 72,
+ "type": "Kota",
+ "name": "Banjarbaru",
+ "code": "72",
+ "full_code": "6372",
+ "provinsi_id": 13
+ },
+ {
+ "id": 73,
+ "type": "Kota",
+ "name": "Banjarmasin",
+ "code": "71",
+ "full_code": "6371",
+ "provinsi_id": 13
+ },
+ {
+ "id": 74,
+ "type": "Kabupaten",
+ "name": "Banjarnegara",
+ "code": "04",
+ "full_code": "3304",
+ "provinsi_id": 10
+ },
+ {
+ "id": 75,
+ "type": "Kabupaten",
+ "name": "Bantaeng",
+ "code": "03",
+ "full_code": "7303",
+ "provinsi_id": 32
+ },
+ {
+ "id": 76,
+ "type": "Kabupaten",
+ "name": "Bantul",
+ "code": "02",
+ "full_code": "3402",
+ "provinsi_id": 5
+ },
+ {
+ "id": 77,
+ "type": "Kabupaten",
+ "name": "Banyuasin",
+ "code": "07",
+ "full_code": "1607",
+ "provinsi_id": 37
+ },
+ {
+ "id": 78,
+ "type": "Kabupaten",
+ "name": "Banyumas",
+ "code": "02",
+ "full_code": "3302",
+ "provinsi_id": 10
+ },
+ {
+ "id": 79,
+ "type": "Kabupaten",
+ "name": "Banyuwangi",
+ "code": "10",
+ "full_code": "3510",
+ "provinsi_id": 11
+ },
+ {
+ "id": 80,
+ "type": "Kabupaten",
+ "name": "Barito Kuala",
+ "code": "04",
+ "full_code": "6304",
+ "provinsi_id": 13
+ },
+ {
+ "id": 81,
+ "type": "Kabupaten",
+ "name": "Barito Selatan",
+ "code": "04",
+ "full_code": "6204",
+ "provinsi_id": 14
+ },
+ {
+ "id": 82,
+ "type": "Kabupaten",
+ "name": "Barito Timur",
+ "code": "13",
+ "full_code": "6213",
+ "provinsi_id": 14
+ },
+ {
+ "id": 83,
+ "type": "Kabupaten",
+ "name": "Barito Utara",
+ "code": "05",
+ "full_code": "6205",
+ "provinsi_id": 14
+ },
+ {
+ "id": 84,
+ "type": "Kabupaten",
+ "name": "Barru",
+ "code": "11",
+ "full_code": "7311",
+ "provinsi_id": 32
+ },
+ {
+ "id": 85,
+ "type": "Kota",
+ "name": "Batam",
+ "code": "71",
+ "full_code": "2171",
+ "provinsi_id": 18
+ },
+ {
+ "id": 86,
+ "type": "Kabupaten",
+ "name": "Batang",
+ "code": "25",
+ "full_code": "3325",
+ "provinsi_id": 10
+ },
+ {
+ "id": 87,
+ "type": "Kabupaten",
+ "name": "Batanghari",
+ "code": "04",
+ "full_code": "1504",
+ "provinsi_id": 8
+ },
+ {
+ "id": 88,
+ "type": "Kota",
+ "name": "Batu",
+ "code": "79",
+ "full_code": "3579",
+ "provinsi_id": 11
+ },
+ {
+ "id": 89,
+ "type": "Kabupaten",
+ "name": "Batu Bara",
+ "code": "19",
+ "full_code": "1219",
+ "provinsi_id": 38
+ },
+ {
+ "id": 90,
+ "type": "Kota",
+ "name": "Bau Bau",
+ "code": "72",
+ "full_code": "7472",
+ "provinsi_id": 34
+ },
+ {
+ "id": 91,
+ "type": "Kota",
+ "name": "Bekasi",
+ "code": "75",
+ "full_code": "3275",
+ "provinsi_id": 9
+ },
+ {
+ "id": 92,
+ "type": "Kabupaten",
+ "name": "Bekasi",
+ "code": "16",
+ "full_code": "3216",
+ "provinsi_id": 9
+ },
+ {
+ "id": 93,
+ "type": "Kabupaten",
+ "name": "Belitung",
+ "code": "02",
+ "full_code": "1902",
+ "provinsi_id": 17
+ },
+ {
+ "id": 94,
+ "type": "Kabupaten",
+ "name": "Belitung Timur",
+ "code": "06",
+ "full_code": "1906",
+ "provinsi_id": 17
+ },
+ {
+ "id": 95,
+ "type": "Kabupaten",
+ "name": "Belu",
+ "code": "04",
+ "full_code": "5304",
+ "provinsi_id": 23
+ },
+ {
+ "id": 96,
+ "type": "Kabupaten",
+ "name": "Bener Meriah",
+ "code": "17",
+ "full_code": "1117",
+ "provinsi_id": 1
+ },
+ {
+ "id": 97,
+ "type": "Kabupaten",
+ "name": "Bengkalis",
+ "code": "03",
+ "full_code": "1403",
+ "provinsi_id": 30
+ },
+ {
+ "id": 98,
+ "type": "Kabupaten",
+ "name": "Bengkayang",
+ "code": "07",
+ "full_code": "6107",
+ "provinsi_id": 12
+ },
+ {
+ "id": 99,
+ "type": "Kabupaten",
+ "name": "Serdang Bedagai",
+ "code": "18",
+ "full_code": "1218",
+ "provinsi_id": 38
+ },
+ {
+ "id": 100,
+ "type": "Kota",
+ "name": "Bengkulu",
+ "code": "71",
+ "full_code": "1771",
+ "provinsi_id": 4
+ },
+ {
+ "id": 101,
+ "type": "Kabupaten",
+ "name": "Bengkulu Selatan",
+ "code": "01",
+ "full_code": "1701",
+ "provinsi_id": 4
+ },
+ {
+ "id": 102,
+ "type": "Kabupaten",
+ "name": "Seruyan",
+ "code": "07",
+ "full_code": "6207",
+ "provinsi_id": 14
+ },
+ {
+ "id": 103,
+ "type": "Kabupaten",
+ "name": "Indramayu",
+ "code": "12",
+ "full_code": "3212",
+ "provinsi_id": 9
+ },
+ {
+ "id": 104,
+ "type": "Kabupaten",
+ "name": "Siak",
+ "code": "08",
+ "full_code": "1408",
+ "provinsi_id": 30
+ },
+ {
+ "id": 105,
+ "type": "Kabupaten",
+ "name": "Intan Jaya",
+ "code": "07",
+ "full_code": "9407",
+ "provinsi_id": 29
+ },
+ {
+ "id": 106,
+ "type": "Kota",
+ "name": "Jakarta Barat",
+ "code": "73",
+ "full_code": "3173",
+ "provinsi_id": 6
+ },
+ {
+ "id": 107,
+ "type": "Kota",
+ "name": "Sibolga",
+ "code": "73",
+ "full_code": "1273",
+ "provinsi_id": 38
+ },
+ {
+ "id": 108,
+ "type": "Kabupaten",
+ "name": "Bengkulu Tengah",
+ "code": "09",
+ "full_code": "1709",
+ "provinsi_id": 4
+ },
+ {
+ "id": 109,
+ "type": "Kota",
+ "name": "Jakarta Pusat",
+ "code": "71",
+ "full_code": "3171",
+ "provinsi_id": 6
+ },
+ {
+ "id": 110,
+ "type": "Kabupaten",
+ "name": "Sidenreng Rappang",
+ "code": "14",
+ "full_code": "7314",
+ "provinsi_id": 32
+ },
+ {
+ "id": 111,
+ "type": "Kabupaten",
+ "name": "Bengkulu Utara",
+ "code": "03",
+ "full_code": "1703",
+ "provinsi_id": 4
+ },
+ {
+ "id": 112,
+ "type": "Kota",
+ "name": "Jakarta Selatan",
+ "code": "74",
+ "full_code": "3174",
+ "provinsi_id": 6
+ },
+ {
+ "id": 113,
+ "type": "Kabupaten",
+ "name": "Sidoarjo",
+ "code": "15",
+ "full_code": "3515",
+ "provinsi_id": 11
+ },
+ {
+ "id": 114,
+ "type": "Kota",
+ "name": "Jakarta Timur",
+ "code": "75",
+ "full_code": "3175",
+ "provinsi_id": 6
+ },
+ {
+ "id": 115,
+ "type": "Kabupaten",
+ "name": "Sigi",
+ "code": "10",
+ "full_code": "7210",
+ "provinsi_id": 33
+ },
+ {
+ "id": 116,
+ "type": "Kabupaten",
+ "name": "Berau",
+ "code": "03",
+ "full_code": "6403",
+ "provinsi_id": 15
+ },
+ {
+ "id": 117,
+ "type": "Kabupaten",
+ "name": "Biak Numfor",
+ "code": "06",
+ "full_code": "9106",
+ "provinsi_id": 24
+ },
+ {
+ "id": 118,
+ "type": "Kabupaten",
+ "name": "Bima",
+ "code": "06",
+ "full_code": "5206",
+ "provinsi_id": 22
+ },
+ {
+ "id": 119,
+ "type": "Kota",
+ "name": "Jakarta Utara",
+ "code": "72",
+ "full_code": "3172",
+ "provinsi_id": 6
+ },
+ {
+ "id": 120,
+ "type": "Kota",
+ "name": "Bima",
+ "code": "72",
+ "full_code": "5272",
+ "provinsi_id": 22
+ },
+ {
+ "id": 121,
+ "type": "Kota",
+ "name": "Jambi",
+ "code": "71",
+ "full_code": "1571",
+ "provinsi_id": 8
+ },
+ {
+ "id": 122,
+ "type": "Kabupaten",
+ "name": "Sijunjung",
+ "code": "03",
+ "full_code": "1303",
+ "provinsi_id": 36
+ },
+ {
+ "id": 123,
+ "type": "Kota",
+ "name": "Binjai",
+ "code": "75",
+ "full_code": "1275",
+ "provinsi_id": 38
+ },
+ {
+ "id": 124,
+ "type": "Kabupaten",
+ "name": "Jayapura",
+ "code": "03",
+ "full_code": "9103",
+ "provinsi_id": 24
+ },
+ {
+ "id": 125,
+ "type": "Kabupaten",
+ "name": "Bintan",
+ "code": "01",
+ "full_code": "2101",
+ "provinsi_id": 18
+ },
+ {
+ "id": 126,
+ "type": "Kabupaten",
+ "name": "Sikka",
+ "code": "07",
+ "full_code": "5307",
+ "provinsi_id": 23
+ },
+ {
+ "id": 127,
+ "type": "Kabupaten",
+ "name": "Bireuen",
+ "code": "11",
+ "full_code": "1111",
+ "provinsi_id": 1
+ },
+ {
+ "id": 128,
+ "type": "Kabupaten",
+ "name": "Simalungun",
+ "code": "08",
+ "full_code": "1208",
+ "provinsi_id": 38
+ },
+ {
+ "id": 129,
+ "type": "Kota",
+ "name": "Bitung",
+ "code": "72",
+ "full_code": "7172",
+ "provinsi_id": 35
+ },
+ {
+ "id": 130,
+ "type": "Kota",
+ "name": "Jayapura",
+ "code": "71",
+ "full_code": "9171",
+ "provinsi_id": 24
+ },
+ {
+ "id": 131,
+ "type": "Kabupaten",
+ "name": "Simeulue",
+ "code": "09",
+ "full_code": "1109",
+ "provinsi_id": 1
+ },
+ {
+ "id": 132,
+ "type": "Kabupaten",
+ "name": "Jayawijaya",
+ "code": "01",
+ "full_code": "9501",
+ "provinsi_id": 27
+ },
+ {
+ "id": 133,
+ "type": "Kabupaten",
+ "name": "Jember",
+ "code": "09",
+ "full_code": "3509",
+ "provinsi_id": 11
+ },
+ {
+ "id": 134,
+ "type": "Kota",
+ "name": "Singkawang",
+ "code": "72",
+ "full_code": "6172",
+ "provinsi_id": 12
+ },
+ {
+ "id": 135,
+ "type": "Kabupaten",
+ "name": "Blitar",
+ "code": "05",
+ "full_code": "3505",
+ "provinsi_id": 11
+ },
+ {
+ "id": 136,
+ "type": "Kabupaten",
+ "name": "Jembrana",
+ "code": "01",
+ "full_code": "5101",
+ "provinsi_id": 2
+ },
+ {
+ "id": 137,
+ "type": "Kabupaten",
+ "name": "Sinjai",
+ "code": "07",
+ "full_code": "7307",
+ "provinsi_id": 32
+ },
+ {
+ "id": 138,
+ "type": "Kota",
+ "name": "Blitar",
+ "code": "72",
+ "full_code": "3572",
+ "provinsi_id": 11
+ },
+ {
+ "id": 139,
+ "type": "Kabupaten",
+ "name": "Jeneponto",
+ "code": "04",
+ "full_code": "7304",
+ "provinsi_id": 32
+ },
+ {
+ "id": 140,
+ "type": "Kabupaten",
+ "name": "Blora",
+ "code": "16",
+ "full_code": "3316",
+ "provinsi_id": 10
+ },
+ {
+ "id": 141,
+ "type": "Kabupaten",
+ "name": "Jepara",
+ "code": "20",
+ "full_code": "3320",
+ "provinsi_id": 10
+ },
+ {
+ "id": 142,
+ "type": "Kabupaten",
+ "name": "Sintang",
+ "code": "05",
+ "full_code": "6105",
+ "provinsi_id": 12
+ },
+ {
+ "id": 143,
+ "type": "Kabupaten",
+ "name": "Boalemo",
+ "code": "02",
+ "full_code": "7502",
+ "provinsi_id": 7
+ },
+ {
+ "id": 144,
+ "type": "Kabupaten",
+ "name": "Jombang",
+ "code": "17",
+ "full_code": "3517",
+ "provinsi_id": 11
+ },
+ {
+ "id": 145,
+ "type": "Kabupaten",
+ "name": "Bogor",
+ "code": "01",
+ "full_code": "3201",
+ "provinsi_id": 9
+ },
+ {
+ "id": 146,
+ "type": "Kabupaten",
+ "name": "Situbondo",
+ "code": "12",
+ "full_code": "3512",
+ "provinsi_id": 11
+ },
+ {
+ "id": 147,
+ "type": "Kabupaten",
+ "name": "Kaimana",
+ "code": "08",
+ "full_code": "9208",
+ "provinsi_id": 25
+ },
+ {
+ "id": 148,
+ "type": "Kota",
+ "name": "Bogor",
+ "code": "71",
+ "full_code": "3271",
+ "provinsi_id": 9
+ },
+ {
+ "id": 149,
+ "type": "Kabupaten",
+ "name": "Sleman",
+ "code": "04",
+ "full_code": "3404",
+ "provinsi_id": 5
+ },
+ {
+ "id": 150,
+ "type": "Kabupaten",
+ "name": "Kampar",
+ "code": "01",
+ "full_code": "1401",
+ "provinsi_id": 30
+ },
+ {
+ "id": 151,
+ "type": "Kabupaten",
+ "name": "Bojonegoro",
+ "code": "22",
+ "full_code": "3522",
+ "provinsi_id": 11
+ },
+ {
+ "id": 152,
+ "type": "Kabupaten",
+ "name": "Solok",
+ "code": "02",
+ "full_code": "1302",
+ "provinsi_id": 36
+ },
+ {
+ "id": 153,
+ "type": "Kabupaten",
+ "name": "Bolaang Mongondow",
+ "code": "01",
+ "full_code": "7101",
+ "provinsi_id": 35
+ },
+ {
+ "id": 154,
+ "type": "Kabupaten",
+ "name": "Bolaang Mongondow Selatan",
+ "code": "11",
+ "full_code": "7111",
+ "provinsi_id": 35
+ },
+ {
+ "id": 155,
+ "type": "Kota",
+ "name": "Solok",
+ "code": "72",
+ "full_code": "1372",
+ "provinsi_id": 36
+ },
+ {
+ "id": 156,
+ "type": "Kabupaten",
+ "name": "Kapuas",
+ "code": "03",
+ "full_code": "6203",
+ "provinsi_id": 14
+ },
+ {
+ "id": 157,
+ "type": "Kabupaten",
+ "name": "Solok Selatan",
+ "code": "11",
+ "full_code": "1311",
+ "provinsi_id": 36
+ },
+ {
+ "id": 158,
+ "type": "Kabupaten",
+ "name": "Bolaang Mongondow Timur",
+ "code": "10",
+ "full_code": "7110",
+ "provinsi_id": 35
+ },
+ {
+ "id": 159,
+ "type": "Kabupaten",
+ "name": "Kapuas Hulu",
+ "code": "06",
+ "full_code": "6106",
+ "provinsi_id": 12
+ },
+ {
+ "id": 160,
+ "type": "Kabupaten",
+ "name": "Karanganyar",
+ "code": "13",
+ "full_code": "3313",
+ "provinsi_id": 10
+ },
+ {
+ "id": 161,
+ "type": "Kabupaten",
+ "name": "Karangasem",
+ "code": "07",
+ "full_code": "5107",
+ "provinsi_id": 2
+ },
+ {
+ "id": 162,
+ "type": "Kabupaten",
+ "name": "Bolaang Mongondow Utara",
+ "code": "08",
+ "full_code": "7108",
+ "provinsi_id": 35
+ },
+ {
+ "id": 163,
+ "type": "Kabupaten",
+ "name": "Karawang",
+ "code": "15",
+ "full_code": "3215",
+ "provinsi_id": 9
+ },
+ {
+ "id": 164,
+ "type": "Kabupaten",
+ "name": "Bombana",
+ "code": "06",
+ "full_code": "7406",
+ "provinsi_id": 34
+ },
+ {
+ "id": 165,
+ "type": "Kabupaten",
+ "name": "Karimun",
+ "code": "02",
+ "full_code": "2102",
+ "provinsi_id": 18
+ },
+ {
+ "id": 166,
+ "type": "Kabupaten",
+ "name": "Bondowoso",
+ "code": "11",
+ "full_code": "3511",
+ "provinsi_id": 11
+ },
+ {
+ "id": 167,
+ "type": "Kabupaten",
+ "name": "Karo",
+ "code": "06",
+ "full_code": "1206",
+ "provinsi_id": 38
+ },
+ {
+ "id": 168,
+ "type": "Kabupaten",
+ "name": "Soppeng",
+ "code": "12",
+ "full_code": "7312",
+ "provinsi_id": 32
+ },
+ {
+ "id": 169,
+ "type": "Kabupaten",
+ "name": "Katingan",
+ "code": "06",
+ "full_code": "6206",
+ "provinsi_id": 14
+ },
+ {
+ "id": 170,
+ "type": "Kabupaten",
+ "name": "Kaur",
+ "code": "04",
+ "full_code": "1704",
+ "provinsi_id": 4
+ },
+ {
+ "id": 171,
+ "type": "Kabupaten",
+ "name": "Sorong",
+ "code": "01",
+ "full_code": "9201",
+ "provinsi_id": 26
+ },
+ {
+ "id": 172,
+ "type": "Kabupaten",
+ "name": "Kayong Utara",
+ "code": "11",
+ "full_code": "6111",
+ "provinsi_id": 12
+ },
+ {
+ "id": 173,
+ "type": "Kota",
+ "name": "Sorong",
+ "code": "71",
+ "full_code": "9271",
+ "provinsi_id": 26
+ },
+ {
+ "id": 174,
+ "type": "Kabupaten",
+ "name": "Sorong Selatan",
+ "code": "04",
+ "full_code": "9204",
+ "provinsi_id": 26
+ },
+ {
+ "id": 175,
+ "type": "Kabupaten",
+ "name": "Kebumen",
+ "code": "05",
+ "full_code": "3305",
+ "provinsi_id": 10
+ },
+ {
+ "id": 176,
+ "type": "Kabupaten",
+ "name": "Sragen",
+ "code": "14",
+ "full_code": "3314",
+ "provinsi_id": 10
+ },
+ {
+ "id": 177,
+ "type": "Kabupaten",
+ "name": "Kediri",
+ "code": "06",
+ "full_code": "3506",
+ "provinsi_id": 11
+ },
+ {
+ "id": 178,
+ "type": "Kabupaten",
+ "name": "Subang",
+ "code": "13",
+ "full_code": "3213",
+ "provinsi_id": 9
+ },
+ {
+ "id": 179,
+ "type": "Kota",
+ "name": "Subulussalam",
+ "code": "75",
+ "full_code": "1175",
+ "provinsi_id": 1
+ },
+ {
+ "id": 180,
+ "type": "Kota",
+ "name": "Sukabumi",
+ "code": "72",
+ "full_code": "3272",
+ "provinsi_id": 9
+ },
+ {
+ "id": 181,
+ "type": "Kota",
+ "name": "Kediri",
+ "code": "71",
+ "full_code": "3571",
+ "provinsi_id": 11
+ },
+ {
+ "id": 182,
+ "type": "Kabupaten",
+ "name": "Bone",
+ "code": "08",
+ "full_code": "7308",
+ "provinsi_id": 32
+ },
+ {
+ "id": 183,
+ "type": "Kabupaten",
+ "name": "Keerom",
+ "code": "11",
+ "full_code": "9111",
+ "provinsi_id": 24
+ },
+ {
+ "id": 184,
+ "type": "Kabupaten",
+ "name": "Sukabumi",
+ "code": "02",
+ "full_code": "3202",
+ "provinsi_id": 9
+ },
+ {
+ "id": 185,
+ "type": "Kabupaten",
+ "name": "Bone Bolango",
+ "code": "03",
+ "full_code": "7503",
+ "provinsi_id": 7
+ },
+ {
+ "id": 186,
+ "type": "Kabupaten",
+ "name": "Kendal",
+ "code": "24",
+ "full_code": "3324",
+ "provinsi_id": 10
+ },
+ {
+ "id": 187,
+ "type": "Kota",
+ "name": "Bontang",
+ "code": "74",
+ "full_code": "6474",
+ "provinsi_id": 15
+ },
+ {
+ "id": 188,
+ "type": "Kota",
+ "name": "Kendari",
+ "code": "71",
+ "full_code": "7471",
+ "provinsi_id": 34
+ },
+ {
+ "id": 189,
+ "type": "Kabupaten",
+ "name": "Boven Digoel",
+ "code": "02",
+ "full_code": "9302",
+ "provinsi_id": 28
+ },
+ {
+ "id": 190,
+ "type": "Kabupaten",
+ "name": "Kepahiang",
+ "code": "08",
+ "full_code": "1708",
+ "provinsi_id": 4
+ },
+ {
+ "id": 191,
+ "type": "Kabupaten",
+ "name": "Boyolali",
+ "code": "09",
+ "full_code": "3309",
+ "provinsi_id": 10
+ },
+ {
+ "id": 192,
+ "type": "Kabupaten",
+ "name": "Sukamara",
+ "code": "08",
+ "full_code": "6208",
+ "provinsi_id": 14
+ },
+ {
+ "id": 193,
+ "type": "Kabupaten",
+ "name": "Kepulauan Anambas",
+ "code": "05",
+ "full_code": "2105",
+ "provinsi_id": 18
+ },
+ {
+ "id": 194,
+ "type": "Kabupaten",
+ "name": "Sukoharjo",
+ "code": "11",
+ "full_code": "3311",
+ "provinsi_id": 10
+ },
+ {
+ "id": 195,
+ "type": "Kabupaten",
+ "name": "Sumba Barat",
+ "code": "12",
+ "full_code": "5312",
+ "provinsi_id": 23
+ },
+ {
+ "id": 196,
+ "type": "Kabupaten",
+ "name": "Brebes",
+ "code": "29",
+ "full_code": "3329",
+ "provinsi_id": 10
+ },
+ {
+ "id": 197,
+ "type": "Kota",
+ "name": "Bukittinggi",
+ "code": "75",
+ "full_code": "1375",
+ "provinsi_id": 36
+ },
+ {
+ "id": 198,
+ "type": "Kabupaten",
+ "name": "Buleleng",
+ "code": "08",
+ "full_code": "5108",
+ "provinsi_id": 2
+ },
+ {
+ "id": 199,
+ "type": "Kabupaten",
+ "name": "Bulukumba",
+ "code": "02",
+ "full_code": "7302",
+ "provinsi_id": 32
+ },
+ {
+ "id": 200,
+ "type": "Kabupaten",
+ "name": "Sumba Barat Daya",
+ "code": "18",
+ "full_code": "5318",
+ "provinsi_id": 23
+ },
+ {
+ "id": 201,
+ "type": "Kabupaten",
+ "name": "Sumba Tengah",
+ "code": "17",
+ "full_code": "5317",
+ "provinsi_id": 23
+ },
+ {
+ "id": 202,
+ "type": "Kabupaten",
+ "name": "Sumba Timur",
+ "code": "11",
+ "full_code": "5311",
+ "provinsi_id": 23
+ },
+ {
+ "id": 203,
+ "type": "Kabupaten",
+ "name": "Bulungan",
+ "code": "01",
+ "full_code": "6501",
+ "provinsi_id": 16
+ },
+ {
+ "id": 204,
+ "type": "Kabupaten",
+ "name": "Sumbawa",
+ "code": "04",
+ "full_code": "5204",
+ "provinsi_id": 22
+ },
+ {
+ "id": 205,
+ "type": "Kabupaten",
+ "name": "Bungo",
+ "code": "08",
+ "full_code": "1508",
+ "provinsi_id": 8
+ },
+ {
+ "id": 206,
+ "type": "Kabupaten",
+ "name": "Sumbawa Barat",
+ "code": "07",
+ "full_code": "5207",
+ "provinsi_id": 22
+ },
+ {
+ "id": 207,
+ "type": "Kabupaten",
+ "name": "Buol",
+ "code": "05",
+ "full_code": "7205",
+ "provinsi_id": 33
+ },
+ {
+ "id": 208,
+ "type": "Kabupaten",
+ "name": "Sumedang",
+ "code": "11",
+ "full_code": "3211",
+ "provinsi_id": 9
+ },
+ {
+ "id": 209,
+ "type": "Kabupaten",
+ "name": "Buru",
+ "code": "04",
+ "full_code": "8104",
+ "provinsi_id": 20
+ },
+ {
+ "id": 210,
+ "type": "Kabupaten",
+ "name": "Buru Selatan",
+ "code": "09",
+ "full_code": "8109",
+ "provinsi_id": 20
+ },
+ {
+ "id": 211,
+ "type": "Kabupaten",
+ "name": "Kepulauan Aru",
+ "code": "07",
+ "full_code": "8107",
+ "provinsi_id": 20
+ },
+ {
+ "id": 212,
+ "type": "Kabupaten",
+ "name": "Sumenep",
+ "code": "29",
+ "full_code": "3529",
+ "provinsi_id": 11
+ },
+ {
+ "id": 213,
+ "type": "Kabupaten",
+ "name": "Buton",
+ "code": "04",
+ "full_code": "7404",
+ "provinsi_id": 34
+ },
+ {
+ "id": 214,
+ "type": "Kota",
+ "name": "Sungai Penuh",
+ "code": "72",
+ "full_code": "1572",
+ "provinsi_id": 8
+ },
+ {
+ "id": 215,
+ "type": "Kabupaten",
+ "name": "Buton Selatan",
+ "code": "15",
+ "full_code": "7415",
+ "provinsi_id": 34
+ },
+ {
+ "id": 216,
+ "type": "Kabupaten",
+ "name": "Supiori",
+ "code": "19",
+ "full_code": "9119",
+ "provinsi_id": 24
+ },
+ {
+ "id": 217,
+ "type": "Kabupaten",
+ "name": "Buton Tengah",
+ "code": "14",
+ "full_code": "7414",
+ "provinsi_id": 34
+ },
+ {
+ "id": 218,
+ "type": "Kabupaten",
+ "name": "Buton Utara",
+ "code": "10",
+ "full_code": "7410",
+ "provinsi_id": 34
+ },
+ {
+ "id": 219,
+ "type": "Kota",
+ "name": "Surabaya",
+ "code": "78",
+ "full_code": "3578",
+ "provinsi_id": 11
+ },
+ {
+ "id": 220,
+ "type": "Kabupaten",
+ "name": "Kepulauan Mentawai",
+ "code": "09",
+ "full_code": "1309",
+ "provinsi_id": 36
+ },
+ {
+ "id": 221,
+ "type": "Kabupaten",
+ "name": "Ciamis",
+ "code": "07",
+ "full_code": "3207",
+ "provinsi_id": 9
+ },
+ {
+ "id": 222,
+ "type": "Kota",
+ "name": "Surakarta",
+ "code": "72",
+ "full_code": "3372",
+ "provinsi_id": 10
+ },
+ {
+ "id": 223,
+ "type": "Kabupaten",
+ "name": "Kepulauan Meranti",
+ "code": "10",
+ "full_code": "1410",
+ "provinsi_id": 30
+ },
+ {
+ "id": 224,
+ "type": "Kabupaten",
+ "name": "Cianjur",
+ "code": "03",
+ "full_code": "3203",
+ "provinsi_id": 9
+ },
+ {
+ "id": 225,
+ "type": "Kabupaten",
+ "name": "Tabalong",
+ "code": "09",
+ "full_code": "6309",
+ "provinsi_id": 13
+ },
+ {
+ "id": 226,
+ "type": "Kabupaten",
+ "name": "Kepulauan Sangihe",
+ "code": "03",
+ "full_code": "7103",
+ "provinsi_id": 35
+ },
+ {
+ "id": 227,
+ "type": "Kabupaten",
+ "name": "Kepulauan Selayar",
+ "code": "01",
+ "full_code": "7301",
+ "provinsi_id": 32
+ },
+ {
+ "id": 228,
+ "type": "Kabupaten",
+ "name": "Cilacap",
+ "code": "01",
+ "full_code": "3301",
+ "provinsi_id": 10
+ },
+ {
+ "id": 229,
+ "type": "Kota",
+ "name": "Cilegon",
+ "code": "72",
+ "full_code": "3672",
+ "provinsi_id": 3
+ },
+ {
+ "id": 230,
+ "type": "Kabupaten",
+ "name": "Tabanan",
+ "code": "02",
+ "full_code": "5102",
+ "provinsi_id": 2
+ },
+ {
+ "id": 231,
+ "type": "Kota",
+ "name": "Cimahi",
+ "code": "77",
+ "full_code": "3277",
+ "provinsi_id": 9
+ },
+ {
+ "id": 232,
+ "type": "Kabupaten",
+ "name": "Kepulauan Seribu",
+ "code": "01",
+ "full_code": "3101",
+ "provinsi_id": 6
+ },
+ {
+ "id": 233,
+ "type": "Kabupaten",
+ "name": "Takalar",
+ "code": "05",
+ "full_code": "7305",
+ "provinsi_id": 32
+ },
+ {
+ "id": 234,
+ "type": "Kota",
+ "name": "Cirebon",
+ "code": "74",
+ "full_code": "3274",
+ "provinsi_id": 9
+ },
+ {
+ "id": 235,
+ "type": "Kabupaten",
+ "name": "Kepulauan Siau Tagulandang Biaro (Sitaro)",
+ "code": "09",
+ "full_code": "7109",
+ "provinsi_id": 35
+ },
+ {
+ "id": 236,
+ "type": "Kabupaten",
+ "name": "Kepulauan Sula",
+ "code": "05",
+ "full_code": "8205",
+ "provinsi_id": 21
+ },
+ {
+ "id": 237,
+ "type": "Kabupaten",
+ "name": "Tambrauw",
+ "code": "09",
+ "full_code": "9209",
+ "provinsi_id": 26
+ },
+ {
+ "id": 238,
+ "type": "Kabupaten",
+ "name": "Kepulauan Talaud",
+ "code": "04",
+ "full_code": "7104",
+ "provinsi_id": 35
+ },
+ {
+ "id": 239,
+ "type": "Kabupaten",
+ "name": "Kepulauan Tanimbar (Maluku Tenggara Barat)",
+ "code": "03",
+ "full_code": "8103",
+ "provinsi_id": 20
+ },
+ {
+ "id": 240,
+ "type": "Kabupaten",
+ "name": "Cirebon",
+ "code": "09",
+ "full_code": "3209",
+ "provinsi_id": 9
+ },
+ {
+ "id": 241,
+ "type": "Kabupaten",
+ "name": "Dairi",
+ "code": "11",
+ "full_code": "1211",
+ "provinsi_id": 38
+ },
+ {
+ "id": 242,
+ "type": "Kabupaten",
+ "name": "Tana Tidung",
+ "code": "04",
+ "full_code": "6504",
+ "provinsi_id": 16
+ },
+ {
+ "id": 243,
+ "type": "Kabupaten",
+ "name": "Deiyai",
+ "code": "08",
+ "full_code": "9408",
+ "provinsi_id": 29
+ },
+ {
+ "id": 244,
+ "type": "Kabupaten",
+ "name": "Kepulauan Yapen",
+ "code": "05",
+ "full_code": "9105",
+ "provinsi_id": 24
+ },
+ {
+ "id": 245,
+ "type": "Kabupaten",
+ "name": "Deli Serdang",
+ "code": "07",
+ "full_code": "1207",
+ "provinsi_id": 38
+ },
+ {
+ "id": 246,
+ "type": "Kabupaten",
+ "name": "Tana Toraja",
+ "code": "18",
+ "full_code": "7318",
+ "provinsi_id": 32
+ },
+ {
+ "id": 247,
+ "type": "Kabupaten",
+ "name": "Demak",
+ "code": "21",
+ "full_code": "3321",
+ "provinsi_id": 10
+ },
+ {
+ "id": 248,
+ "type": "Kabupaten",
+ "name": "Kerinci",
+ "code": "01",
+ "full_code": "1501",
+ "provinsi_id": 8
+ },
+ {
+ "id": 249,
+ "type": "Kabupaten",
+ "name": "Tanah Bumbu",
+ "code": "10",
+ "full_code": "6310",
+ "provinsi_id": 13
+ },
+ {
+ "id": 250,
+ "type": "Kota",
+ "name": "Denpasar",
+ "code": "71",
+ "full_code": "5171",
+ "provinsi_id": 2
+ },
+ {
+ "id": 251,
+ "type": "Kabupaten",
+ "name": "Ketapang",
+ "code": "04",
+ "full_code": "6104",
+ "provinsi_id": 12
+ },
+ {
+ "id": 252,
+ "type": "Kota",
+ "name": "Depok",
+ "code": "76",
+ "full_code": "3276",
+ "provinsi_id": 9
+ },
+ {
+ "id": 253,
+ "type": "Kabupaten",
+ "name": "Tanah Datar",
+ "code": "04",
+ "full_code": "1304",
+ "provinsi_id": 36
+ },
+ {
+ "id": 254,
+ "type": "Kabupaten",
+ "name": "Klaten",
+ "code": "10",
+ "full_code": "3310",
+ "provinsi_id": 10
+ },
+ {
+ "id": 255,
+ "type": "Kabupaten",
+ "name": "Dharmasraya",
+ "code": "10",
+ "full_code": "1310",
+ "provinsi_id": 36
+ },
+ {
+ "id": 256,
+ "type": "Kabupaten",
+ "name": "Tanah Laut",
+ "code": "01",
+ "full_code": "6301",
+ "provinsi_id": 13
+ },
+ {
+ "id": 257,
+ "type": "Kabupaten",
+ "name": "Dogiyai",
+ "code": "06",
+ "full_code": "9406",
+ "provinsi_id": 29
+ },
+ {
+ "id": 258,
+ "type": "Kabupaten",
+ "name": "Klungkung",
+ "code": "05",
+ "full_code": "5105",
+ "provinsi_id": 2
+ },
+ {
+ "id": 259,
+ "type": "Kabupaten",
+ "name": "Kolaka",
+ "code": "01",
+ "full_code": "7401",
+ "provinsi_id": 34
+ },
+ {
+ "id": 260,
+ "type": "Kota",
+ "name": "Tangerang",
+ "code": "71",
+ "full_code": "3671",
+ "provinsi_id": 3
+ },
+ {
+ "id": 261,
+ "type": "Kabupaten",
+ "name": "Kolaka Timur",
+ "code": "11",
+ "full_code": "7411",
+ "provinsi_id": 34
+ },
+ {
+ "id": 262,
+ "type": "Kabupaten",
+ "name": "Tangerang",
+ "code": "03",
+ "full_code": "3603",
+ "provinsi_id": 3
+ },
+ {
+ "id": 263,
+ "type": "Kota",
+ "name": "Tangerang Selatan",
+ "code": "74",
+ "full_code": "3674",
+ "provinsi_id": 3
+ },
+ {
+ "id": 264,
+ "type": "Kabupaten",
+ "name": "Tanggamus",
+ "code": "06",
+ "full_code": "1806",
+ "provinsi_id": 19
+ },
+ {
+ "id": 265,
+ "type": "Kabupaten",
+ "name": "Kolaka Utara",
+ "code": "08",
+ "full_code": "7408",
+ "provinsi_id": 34
+ },
+ {
+ "id": 266,
+ "type": "Kabupaten",
+ "name": "Konawe",
+ "code": "02",
+ "full_code": "7402",
+ "provinsi_id": 34
+ },
+ {
+ "id": 267,
+ "type": "Kabupaten",
+ "name": "Konawe Kepulauan",
+ "code": "12",
+ "full_code": "7412",
+ "provinsi_id": 34
+ },
+ {
+ "id": 268,
+ "type": "Kota",
+ "name": "Tanjung Balai",
+ "code": "74",
+ "full_code": "1274",
+ "provinsi_id": 38
+ },
+ {
+ "id": 269,
+ "type": "Kabupaten",
+ "name": "Konawe Selatan",
+ "code": "05",
+ "full_code": "7405",
+ "provinsi_id": 34
+ },
+ {
+ "id": 270,
+ "type": "Kabupaten",
+ "name": "Konawe Utara",
+ "code": "09",
+ "full_code": "7409",
+ "provinsi_id": 34
+ },
+ {
+ "id": 271,
+ "type": "Kabupaten",
+ "name": "Kotabaru",
+ "code": "02",
+ "full_code": "6302",
+ "provinsi_id": 13
+ },
+ {
+ "id": 272,
+ "type": "Kabupaten",
+ "name": "Tanjung Jabung Barat",
+ "code": "06",
+ "full_code": "1506",
+ "provinsi_id": 8
+ },
+ {
+ "id": 273,
+ "type": "Kota",
+ "name": "Kotamobagu",
+ "code": "74",
+ "full_code": "7174",
+ "provinsi_id": 35
+ },
+ {
+ "id": 274,
+ "type": "Kabupaten",
+ "name": "Tanjung Jabung Timur",
+ "code": "07",
+ "full_code": "1507",
+ "provinsi_id": 8
+ },
+ {
+ "id": 275,
+ "type": "Kabupaten",
+ "name": "Kotawaringin Barat",
+ "code": "01",
+ "full_code": "6201",
+ "provinsi_id": 14
+ },
+ {
+ "id": 276,
+ "type": "Kabupaten",
+ "name": "Kotawaringin Timur",
+ "code": "02",
+ "full_code": "6202",
+ "provinsi_id": 14
+ },
+ {
+ "id": 277,
+ "type": "Kota",
+ "name": "Tanjung Pinang",
+ "code": "72",
+ "full_code": "2172",
+ "provinsi_id": 18
+ },
+ {
+ "id": 278,
+ "type": "Kabupaten",
+ "name": "Kuantan Singingi",
+ "code": "09",
+ "full_code": "1409",
+ "provinsi_id": 30
+ },
+ {
+ "id": 279,
+ "type": "Kabupaten",
+ "name": "Tapanuli Selatan",
+ "code": "03",
+ "full_code": "1203",
+ "provinsi_id": 38
+ },
+ {
+ "id": 280,
+ "type": "Kabupaten",
+ "name": "Kubu Raya",
+ "code": "12",
+ "full_code": "6112",
+ "provinsi_id": 12
+ },
+ {
+ "id": 281,
+ "type": "Kabupaten",
+ "name": "Kudus",
+ "code": "19",
+ "full_code": "3319",
+ "provinsi_id": 10
+ },
+ {
+ "id": 282,
+ "type": "Kabupaten",
+ "name": "Tapanuli Tengah",
+ "code": "01",
+ "full_code": "1201",
+ "provinsi_id": 38
+ },
+ {
+ "id": 283,
+ "type": "Kabupaten",
+ "name": "Tapanuli Utara",
+ "code": "02",
+ "full_code": "1202",
+ "provinsi_id": 38
+ },
+ {
+ "id": 284,
+ "type": "Kabupaten",
+ "name": "Tapin",
+ "code": "05",
+ "full_code": "6305",
+ "provinsi_id": 13
+ },
+ {
+ "id": 285,
+ "type": "Kota",
+ "name": "Tarakan",
+ "code": "71",
+ "full_code": "6571",
+ "provinsi_id": 16
+ },
+ {
+ "id": 286,
+ "type": "Kota",
+ "name": "Tasikmalaya",
+ "code": "78",
+ "full_code": "3278",
+ "provinsi_id": 9
+ },
+ {
+ "id": 287,
+ "type": "Kabupaten",
+ "name": "Kulon Progo",
+ "code": "01",
+ "full_code": "3401",
+ "provinsi_id": 5
+ },
+ {
+ "id": 288,
+ "type": "Kabupaten",
+ "name": "Kuningan",
+ "code": "08",
+ "full_code": "3208",
+ "provinsi_id": 9
+ },
+ {
+ "id": 289,
+ "type": "Kabupaten",
+ "name": "Kupang",
+ "code": "01",
+ "full_code": "5301",
+ "provinsi_id": 23
+ },
+ {
+ "id": 290,
+ "type": "Kota",
+ "name": "Kupang",
+ "code": "71",
+ "full_code": "5371",
+ "provinsi_id": 23
+ },
+ {
+ "id": 291,
+ "type": "Kabupaten",
+ "name": "Tasikmalaya",
+ "code": "06",
+ "full_code": "3206",
+ "provinsi_id": 9
+ },
+ {
+ "id": 292,
+ "type": "Kabupaten",
+ "name": "Kutai Barat",
+ "code": "07",
+ "full_code": "6407",
+ "provinsi_id": 15
+ },
+ {
+ "id": 293,
+ "type": "Kabupaten",
+ "name": "Kutai Kartanegara",
+ "code": "02",
+ "full_code": "6402",
+ "provinsi_id": 15
+ },
+ {
+ "id": 294,
+ "type": "Kabupaten",
+ "name": "Kutai Timur",
+ "code": "08",
+ "full_code": "6408",
+ "provinsi_id": 15
+ },
+ {
+ "id": 295,
+ "type": "Kabupaten",
+ "name": "Labuhanbatu",
+ "code": "10",
+ "full_code": "1210",
+ "provinsi_id": 38
+ },
+ {
+ "id": 296,
+ "type": "Kota",
+ "name": "Tebing Tinggi",
+ "code": "76",
+ "full_code": "1276",
+ "provinsi_id": 38
+ },
+ {
+ "id": 297,
+ "type": "Kabupaten",
+ "name": "Labuhanbatu Selatan",
+ "code": "22",
+ "full_code": "1222",
+ "provinsi_id": 38
+ },
+ {
+ "id": 298,
+ "type": "Kabupaten",
+ "name": "Tebo",
+ "code": "09",
+ "full_code": "1509",
+ "provinsi_id": 8
+ },
+ {
+ "id": 299,
+ "type": "Kabupaten",
+ "name": "Tegal",
+ "code": "28",
+ "full_code": "3328",
+ "provinsi_id": 10
+ },
+ {
+ "id": 300,
+ "type": "Kota",
+ "name": "Tegal",
+ "code": "76",
+ "full_code": "3376",
+ "provinsi_id": 10
+ },
+ {
+ "id": 301,
+ "type": "Kabupaten",
+ "name": "Teluk Bintuni",
+ "code": "06",
+ "full_code": "9206",
+ "provinsi_id": 25
+ },
+ {
+ "id": 302,
+ "type": "Kabupaten",
+ "name": "Teluk Wondama",
+ "code": "07",
+ "full_code": "9207",
+ "provinsi_id": 25
+ },
+ {
+ "id": 303,
+ "type": "Kabupaten",
+ "name": "Labuhanbatu Utara",
+ "code": "23",
+ "full_code": "1223",
+ "provinsi_id": 38
+ },
+ {
+ "id": 304,
+ "type": "Kabupaten",
+ "name": "Temanggung",
+ "code": "23",
+ "full_code": "3323",
+ "provinsi_id": 10
+ },
+ {
+ "id": 305,
+ "type": "Kabupaten",
+ "name": "Lahat",
+ "code": "04",
+ "full_code": "1604",
+ "provinsi_id": 37
+ },
+ {
+ "id": 306,
+ "type": "Kota",
+ "name": "Ternate",
+ "code": "71",
+ "full_code": "8271",
+ "provinsi_id": 21
+ },
+ {
+ "id": 307,
+ "type": "Kabupaten",
+ "name": "Lamandau",
+ "code": "09",
+ "full_code": "6209",
+ "provinsi_id": 14
+ },
+ {
+ "id": 308,
+ "type": "Kabupaten",
+ "name": "Dompu",
+ "code": "05",
+ "full_code": "5205",
+ "provinsi_id": 22
+ },
+ {
+ "id": 309,
+ "type": "Kabupaten",
+ "name": "Donggala",
+ "code": "03",
+ "full_code": "7203",
+ "provinsi_id": 33
+ },
+ {
+ "id": 310,
+ "type": "Kota",
+ "name": "Dumai",
+ "code": "72",
+ "full_code": "1472",
+ "provinsi_id": 30
+ },
+ {
+ "id": 311,
+ "type": "Kabupaten",
+ "name": "Empat Lawang",
+ "code": "11",
+ "full_code": "1611",
+ "provinsi_id": 37
+ },
+ {
+ "id": 312,
+ "type": "Kabupaten",
+ "name": "Lamongan",
+ "code": "24",
+ "full_code": "3524",
+ "provinsi_id": 11
+ },
+ {
+ "id": 313,
+ "type": "Kabupaten",
+ "name": "Ende",
+ "code": "08",
+ "full_code": "5308",
+ "provinsi_id": 23
+ },
+ {
+ "id": 314,
+ "type": "Kabupaten",
+ "name": "Lampung Barat",
+ "code": "04",
+ "full_code": "1804",
+ "provinsi_id": 19
+ },
+ {
+ "id": 315,
+ "type": "Kota",
+ "name": "Tidore Kepulauan",
+ "code": "72",
+ "full_code": "8272",
+ "provinsi_id": 21
+ },
+ {
+ "id": 316,
+ "type": "Kabupaten",
+ "name": "Enrekang",
+ "code": "16",
+ "full_code": "7316",
+ "provinsi_id": 32
+ },
+ {
+ "id": 317,
+ "type": "Kabupaten",
+ "name": "Lampung Selatan",
+ "code": "01",
+ "full_code": "1801",
+ "provinsi_id": 19
+ },
+ {
+ "id": 318,
+ "type": "Kabupaten",
+ "name": "Fak Fak",
+ "code": "03",
+ "full_code": "9203",
+ "provinsi_id": 25
+ },
+ {
+ "id": 319,
+ "type": "Kabupaten",
+ "name": "Timor Tengah Selatan",
+ "code": "02",
+ "full_code": "5302",
+ "provinsi_id": 23
+ },
+ {
+ "id": 320,
+ "type": "Kabupaten",
+ "name": "Lampung Tengah",
+ "code": "02",
+ "full_code": "1802",
+ "provinsi_id": 19
+ },
+ {
+ "id": 321,
+ "type": "Kabupaten",
+ "name": "Flores Timur",
+ "code": "06",
+ "full_code": "5306",
+ "provinsi_id": 23
+ },
+ {
+ "id": 322,
+ "type": "Kabupaten",
+ "name": "Lampung Timur",
+ "code": "07",
+ "full_code": "1807",
+ "provinsi_id": 19
+ },
+ {
+ "id": 323,
+ "type": "Kabupaten",
+ "name": "Garut",
+ "code": "05",
+ "full_code": "3205",
+ "provinsi_id": 9
+ },
+ {
+ "id": 324,
+ "type": "Kabupaten",
+ "name": "Timor Tengah Utara",
+ "code": "03",
+ "full_code": "5303",
+ "provinsi_id": 23
+ },
+ {
+ "id": 325,
+ "type": "Kabupaten",
+ "name": "Lampung Utara",
+ "code": "03",
+ "full_code": "1803",
+ "provinsi_id": 19
+ },
+ {
+ "id": 326,
+ "type": "Kabupaten",
+ "name": "Toba",
+ "code": "12",
+ "full_code": "1212",
+ "provinsi_id": 38
+ },
+ {
+ "id": 327,
+ "type": "Kabupaten",
+ "name": "Tojo Una Una",
+ "code": "09",
+ "full_code": "7209",
+ "provinsi_id": 33
+ },
+ {
+ "id": 328,
+ "type": "Kabupaten",
+ "name": "Gayo Lues",
+ "code": "13",
+ "full_code": "1113",
+ "provinsi_id": 1
+ },
+ {
+ "id": 329,
+ "type": "Kabupaten",
+ "name": "Landak",
+ "code": "08",
+ "full_code": "6108",
+ "provinsi_id": 12
+ },
+ {
+ "id": 330,
+ "type": "Kabupaten",
+ "name": "Gianyar",
+ "code": "04",
+ "full_code": "5104",
+ "provinsi_id": 2
+ },
+ {
+ "id": 331,
+ "type": "Kabupaten",
+ "name": "Langkat",
+ "code": "05",
+ "full_code": "1205",
+ "provinsi_id": 38
+ },
+ {
+ "id": 332,
+ "type": "Kabupaten",
+ "name": "Gorontalo",
+ "code": "01",
+ "full_code": "7501",
+ "provinsi_id": 7
+ },
+ {
+ "id": 333,
+ "type": "Kota",
+ "name": "Langsa",
+ "code": "74",
+ "full_code": "1174",
+ "provinsi_id": 1
+ },
+ {
+ "id": 334,
+ "type": "Kabupaten",
+ "name": "Toli Toli",
+ "code": "04",
+ "full_code": "7204",
+ "provinsi_id": 33
+ },
+ {
+ "id": 335,
+ "type": "Kabupaten",
+ "name": "Lanny Jaya",
+ "code": "07",
+ "full_code": "9507",
+ "provinsi_id": 27
+ },
+ {
+ "id": 336,
+ "type": "Kabupaten",
+ "name": "Lebak",
+ "code": "02",
+ "full_code": "3602",
+ "provinsi_id": 3
+ },
+ {
+ "id": 337,
+ "type": "Kota",
+ "name": "Gorontalo",
+ "code": "71",
+ "full_code": "7571",
+ "provinsi_id": 7
+ },
+ {
+ "id": 338,
+ "type": "Kabupaten",
+ "name": "Tolikara",
+ "code": "04",
+ "full_code": "9504",
+ "provinsi_id": 27
+ },
+ {
+ "id": 339,
+ "type": "Kota",
+ "name": "Tomohon",
+ "code": "73",
+ "full_code": "7173",
+ "provinsi_id": 35
+ },
+ {
+ "id": 340,
+ "type": "Kabupaten",
+ "name": "Lebong",
+ "code": "07",
+ "full_code": "1707",
+ "provinsi_id": 4
+ },
+ {
+ "id": 341,
+ "type": "Kabupaten",
+ "name": "Toraja Utara",
+ "code": "26",
+ "full_code": "7326",
+ "provinsi_id": 32
+ },
+ {
+ "id": 342,
+ "type": "Kabupaten",
+ "name": "Lembata",
+ "code": "13",
+ "full_code": "5313",
+ "provinsi_id": 23
+ },
+ {
+ "id": 343,
+ "type": "Kota",
+ "name": "Lhokseumawe",
+ "code": "73",
+ "full_code": "1173",
+ "provinsi_id": 1
+ },
+ {
+ "id": 344,
+ "type": "Kabupaten",
+ "name": "Lima Puluh Kota",
+ "code": "07",
+ "full_code": "1307",
+ "provinsi_id": 36
+ },
+ {
+ "id": 345,
+ "type": "Kabupaten",
+ "name": "Lingga",
+ "code": "04",
+ "full_code": "2104",
+ "provinsi_id": 18
+ },
+ {
+ "id": 346,
+ "type": "Kabupaten",
+ "name": "Trenggalek",
+ "code": "03",
+ "full_code": "3503",
+ "provinsi_id": 11
+ },
+ {
+ "id": 347,
+ "type": "Kota",
+ "name": "Tual",
+ "code": "72",
+ "full_code": "8172",
+ "provinsi_id": 20
+ },
+ {
+ "id": 348,
+ "type": "Kabupaten",
+ "name": "Tuban",
+ "code": "23",
+ "full_code": "3523",
+ "provinsi_id": 11
+ },
+ {
+ "id": 349,
+ "type": "Kabupaten",
+ "name": "Lombok Barat",
+ "code": "01",
+ "full_code": "5201",
+ "provinsi_id": 22
+ },
+ {
+ "id": 350,
+ "type": "Kabupaten",
+ "name": "Tulang Bawang",
+ "code": "05",
+ "full_code": "1805",
+ "provinsi_id": 19
+ },
+ {
+ "id": 351,
+ "type": "Kabupaten",
+ "name": "Lombok Tengah",
+ "code": "02",
+ "full_code": "5202",
+ "provinsi_id": 22
+ },
+ {
+ "id": 352,
+ "type": "Kabupaten",
+ "name": "Lombok Timur",
+ "code": "03",
+ "full_code": "5203",
+ "provinsi_id": 22
+ },
+ {
+ "id": 353,
+ "type": "Kabupaten",
+ "name": "Tulang Bawang Barat",
+ "code": "12",
+ "full_code": "1812",
+ "provinsi_id": 19
+ },
+ {
+ "id": 354,
+ "type": "Kabupaten",
+ "name": "Lombok Utara",
+ "code": "08",
+ "full_code": "5208",
+ "provinsi_id": 22
+ },
+ {
+ "id": 355,
+ "type": "Kota",
+ "name": "Lubuk Linggau",
+ "code": "73",
+ "full_code": "1673",
+ "provinsi_id": 37
+ },
+ {
+ "id": 356,
+ "type": "Kabupaten",
+ "name": "Tulungagung",
+ "code": "04",
+ "full_code": "3504",
+ "provinsi_id": 11
+ },
+ {
+ "id": 357,
+ "type": "Kabupaten",
+ "name": "Lumajang",
+ "code": "08",
+ "full_code": "3508",
+ "provinsi_id": 11
+ },
+ {
+ "id": 358,
+ "type": "Kabupaten",
+ "name": "Wajo",
+ "code": "13",
+ "full_code": "7313",
+ "provinsi_id": 32
+ },
+ {
+ "id": 359,
+ "type": "Kabupaten",
+ "name": "Luwu",
+ "code": "17",
+ "full_code": "7317",
+ "provinsi_id": 32
+ },
+ {
+ "id": 360,
+ "type": "Kabupaten",
+ "name": "Luwu Timur",
+ "code": "24",
+ "full_code": "7324",
+ "provinsi_id": 32
+ },
+ {
+ "id": 361,
+ "type": "Kabupaten",
+ "name": "Wakatobi",
+ "code": "07",
+ "full_code": "7407",
+ "provinsi_id": 34
+ },
+ {
+ "id": 362,
+ "type": "Kabupaten",
+ "name": "Waropen",
+ "code": "15",
+ "full_code": "9115",
+ "provinsi_id": 24
+ },
+ {
+ "id": 363,
+ "type": "Kabupaten",
+ "name": "Way Kanan",
+ "code": "08",
+ "full_code": "1808",
+ "provinsi_id": 19
+ },
+ {
+ "id": 364,
+ "type": "Kabupaten",
+ "name": "Wonogiri",
+ "code": "12",
+ "full_code": "3312",
+ "provinsi_id": 10
+ },
+ {
+ "id": 365,
+ "type": "Kabupaten",
+ "name": "Wonosobo",
+ "code": "07",
+ "full_code": "3307",
+ "provinsi_id": 10
+ },
+ {
+ "id": 366,
+ "type": "Kabupaten",
+ "name": "Yahukimo",
+ "code": "03",
+ "full_code": "9503",
+ "provinsi_id": 27
+ },
+ {
+ "id": 367,
+ "type": "Kabupaten",
+ "name": "Yalimo",
+ "code": "06",
+ "full_code": "9506",
+ "provinsi_id": 27
+ },
+ {
+ "id": 368,
+ "type": "Kota",
+ "name": "Yogyakarta",
+ "code": "71",
+ "full_code": "3471",
+ "provinsi_id": 5
+ },
+ {
+ "id": 369,
+ "type": "Kabupaten",
+ "name": "Luwu Utara",
+ "code": "22",
+ "full_code": "7322",
+ "provinsi_id": 32
+ },
+ {
+ "id": 370,
+ "type": "Kabupaten",
+ "name": "Madiun",
+ "code": "19",
+ "full_code": "3519",
+ "provinsi_id": 11
+ },
+ {
+ "id": 371,
+ "type": "Kota",
+ "name": "Madiun",
+ "code": "77",
+ "full_code": "3577",
+ "provinsi_id": 11
+ },
+ {
+ "id": 372,
+ "type": "Kabupaten",
+ "name": "Magelang",
+ "code": "08",
+ "full_code": "3308",
+ "provinsi_id": 10
+ },
+ {
+ "id": 373,
+ "type": "Kota",
+ "name": "Magelang",
+ "code": "71",
+ "full_code": "3371",
+ "provinsi_id": 10
+ },
+ {
+ "id": 374,
+ "type": "Kabupaten",
+ "name": "Magetan",
+ "code": "20",
+ "full_code": "3520",
+ "provinsi_id": 11
+ },
+ {
+ "id": 375,
+ "type": "Kabupaten",
+ "name": "Mahakam Ulu",
+ "code": "11",
+ "full_code": "6411",
+ "provinsi_id": 15
+ },
+ {
+ "id": 376,
+ "type": "Kabupaten",
+ "name": "Majalengka",
+ "code": "10",
+ "full_code": "3210",
+ "provinsi_id": 9
+ },
+ {
+ "id": 377,
+ "type": "Kabupaten",
+ "name": "Majene",
+ "code": "05",
+ "full_code": "7605",
+ "provinsi_id": 31
+ },
+ {
+ "id": 378,
+ "type": "Kota",
+ "name": "Makassar",
+ "code": "71",
+ "full_code": "7371",
+ "provinsi_id": 32
+ },
+ {
+ "id": 379,
+ "type": "Kabupaten",
+ "name": "Malaka",
+ "code": "21",
+ "full_code": "5321",
+ "provinsi_id": 23
+ },
+ {
+ "id": 380,
+ "type": "Kabupaten",
+ "name": "Malang",
+ "code": "07",
+ "full_code": "3507",
+ "provinsi_id": 11
+ },
+ {
+ "id": 381,
+ "type": "Kota",
+ "name": "Malang",
+ "code": "73",
+ "full_code": "3573",
+ "provinsi_id": 11
+ },
+ {
+ "id": 382,
+ "type": "Kabupaten",
+ "name": "Malinau",
+ "code": "02",
+ "full_code": "6502",
+ "provinsi_id": 16
+ },
+ {
+ "id": 383,
+ "type": "Kabupaten",
+ "name": "Maluku Barat Daya",
+ "code": "08",
+ "full_code": "8108",
+ "provinsi_id": 20
+ },
+ {
+ "id": 384,
+ "type": "Kabupaten",
+ "name": "Maluku Tengah",
+ "code": "01",
+ "full_code": "8101",
+ "provinsi_id": 20
+ },
+ {
+ "id": 385,
+ "type": "Kabupaten",
+ "name": "Maluku Tenggara",
+ "code": "02",
+ "full_code": "8102",
+ "provinsi_id": 20
+ },
+ {
+ "id": 386,
+ "type": "Kabupaten",
+ "name": "Mamasa",
+ "code": "03",
+ "full_code": "7603",
+ "provinsi_id": 31
+ },
+ {
+ "id": 387,
+ "type": "Kabupaten",
+ "name": "Mamberamo Raya",
+ "code": "20",
+ "full_code": "9120",
+ "provinsi_id": 24
+ },
+ {
+ "id": 388,
+ "type": "Kabupaten",
+ "name": "Mamberamo Tengah",
+ "code": "05",
+ "full_code": "9505",
+ "provinsi_id": 27
+ },
+ {
+ "id": 389,
+ "type": "Kabupaten",
+ "name": "Mamuju",
+ "code": "02",
+ "full_code": "7602",
+ "provinsi_id": 31
+ },
+ {
+ "id": 390,
+ "type": "Kabupaten",
+ "name": "Mamuju Tengah",
+ "code": "06",
+ "full_code": "7606",
+ "provinsi_id": 31
+ },
+ {
+ "id": 391,
+ "type": "Kota",
+ "name": "Manado",
+ "code": "71",
+ "full_code": "7171",
+ "provinsi_id": 35
+ },
+ {
+ "id": 392,
+ "type": "Kabupaten",
+ "name": "Mandailing Natal",
+ "code": "13",
+ "full_code": "1213",
+ "provinsi_id": 38
+ },
+ {
+ "id": 393,
+ "type": "Kabupaten",
+ "name": "Manggarai",
+ "code": "10",
+ "full_code": "5310",
+ "provinsi_id": 23
+ },
+ {
+ "id": 394,
+ "type": "Kabupaten",
+ "name": "Manggarai Barat",
+ "code": "15",
+ "full_code": "5315",
+ "provinsi_id": 23
+ },
+ {
+ "id": 395,
+ "type": "Kabupaten",
+ "name": "Manggarai Timur",
+ "code": "19",
+ "full_code": "5319",
+ "provinsi_id": 23
+ },
+ {
+ "id": 396,
+ "type": "Kabupaten",
+ "name": "Manokwari",
+ "code": "02",
+ "full_code": "9202",
+ "provinsi_id": 25
+ },
+ {
+ "id": 397,
+ "type": "Kabupaten",
+ "name": "Manokwari Selatan",
+ "code": "11",
+ "full_code": "9211",
+ "provinsi_id": 25
+ },
+ {
+ "id": 398,
+ "type": "Kabupaten",
+ "name": "Mappi",
+ "code": "03",
+ "full_code": "9303",
+ "provinsi_id": 28
+ },
+ {
+ "id": 399,
+ "type": "Kabupaten",
+ "name": "Maros",
+ "code": "09",
+ "full_code": "7309",
+ "provinsi_id": 32
+ },
+ {
+ "id": 400,
+ "type": "Kota",
+ "name": "Mataram",
+ "code": "71",
+ "full_code": "5271",
+ "provinsi_id": 22
+ },
+ {
+ "id": 401,
+ "type": "Kabupaten",
+ "name": "Maybrat",
+ "code": "10",
+ "full_code": "9210",
+ "provinsi_id": 26
+ },
+ {
+ "id": 402,
+ "type": "Kota",
+ "name": "Medan",
+ "code": "71",
+ "full_code": "1271",
+ "provinsi_id": 38
+ },
+ {
+ "id": 403,
+ "type": "Kabupaten",
+ "name": "Melawi",
+ "code": "10",
+ "full_code": "6110",
+ "provinsi_id": 12
+ },
+ {
+ "id": 404,
+ "type": "Kabupaten",
+ "name": "Mempawah",
+ "code": "02",
+ "full_code": "6102",
+ "provinsi_id": 12
+ },
+ {
+ "id": 405,
+ "type": "Kabupaten",
+ "name": "Merangin",
+ "code": "02",
+ "full_code": "1502",
+ "provinsi_id": 8
+ },
+ {
+ "id": 406,
+ "type": "Kabupaten",
+ "name": "Merauke",
+ "code": "01",
+ "full_code": "9301",
+ "provinsi_id": 28
+ },
+ {
+ "id": 407,
+ "type": "Kabupaten",
+ "name": "Mesuji",
+ "code": "11",
+ "full_code": "1811",
+ "provinsi_id": 19
+ },
+ {
+ "id": 408,
+ "type": "Kota",
+ "name": "Metro",
+ "code": "72",
+ "full_code": "1872",
+ "provinsi_id": 19
+ },
+ {
+ "id": 409,
+ "type": "Kabupaten",
+ "name": "Mimika",
+ "code": "04",
+ "full_code": "9404",
+ "provinsi_id": 29
+ },
+ {
+ "id": 410,
+ "type": "Kabupaten",
+ "name": "Minahasa",
+ "code": "02",
+ "full_code": "7102",
+ "provinsi_id": 35
+ },
+ {
+ "id": 411,
+ "type": "Kabupaten",
+ "name": "Minahasa Selatan",
+ "code": "05",
+ "full_code": "7105",
+ "provinsi_id": 35
+ },
+ {
+ "id": 412,
+ "type": "Kabupaten",
+ "name": "Minahasa Tenggara",
+ "code": "07",
+ "full_code": "7107",
+ "provinsi_id": 35
+ },
+ {
+ "id": 413,
+ "type": "Kabupaten",
+ "name": "Minahasa Utara",
+ "code": "06",
+ "full_code": "7106",
+ "provinsi_id": 35
+ },
+ {
+ "id": 414,
+ "type": "Kabupaten",
+ "name": "Mojokerto",
+ "code": "16",
+ "full_code": "3516",
+ "provinsi_id": 11
+ },
+ {
+ "id": 415,
+ "type": "Kota",
+ "name": "Mojokerto",
+ "code": "76",
+ "full_code": "3576",
+ "provinsi_id": 11
+ },
+ {
+ "id": 416,
+ "type": "Kabupaten",
+ "name": "Morowali",
+ "code": "06",
+ "full_code": "7206",
+ "provinsi_id": 33
+ },
+ {
+ "id": 417,
+ "type": "Kabupaten",
+ "name": "Morowali Utara",
+ "code": "12",
+ "full_code": "7212",
+ "provinsi_id": 33
+ },
+ {
+ "id": 418,
+ "type": "Kabupaten",
+ "name": "Muara Enim",
+ "code": "03",
+ "full_code": "1603",
+ "provinsi_id": 37
+ },
+ {
+ "id": 419,
+ "type": "Kabupaten",
+ "name": "Muaro Jambi",
+ "code": "05",
+ "full_code": "1505",
+ "provinsi_id": 8
+ },
+ {
+ "id": 420,
+ "type": "Kabupaten",
+ "name": "Muko Muko",
+ "code": "06",
+ "full_code": "1706",
+ "provinsi_id": 4
+ },
+ {
+ "id": 421,
+ "type": "Kabupaten",
+ "name": "Muna",
+ "code": "03",
+ "full_code": "7403",
+ "provinsi_id": 34
+ },
+ {
+ "id": 422,
+ "type": "Kabupaten",
+ "name": "Muna Barat",
+ "code": "13",
+ "full_code": "7413",
+ "provinsi_id": 34
+ },
+ {
+ "id": 423,
+ "type": "Kabupaten",
+ "name": "Murung Raya",
+ "code": "12",
+ "full_code": "6212",
+ "provinsi_id": 14
+ },
+ {
+ "id": 424,
+ "type": "Kabupaten",
+ "name": "Musi Banyuasin",
+ "code": "06",
+ "full_code": "1606",
+ "provinsi_id": 37
+ },
+ {
+ "id": 425,
+ "type": "Kabupaten",
+ "name": "Musi Rawas",
+ "code": "05",
+ "full_code": "1605",
+ "provinsi_id": 37
+ },
+ {
+ "id": 426,
+ "type": "Kabupaten",
+ "name": "Musi Rawas Utara",
+ "code": "13",
+ "full_code": "1613",
+ "provinsi_id": 37
+ },
+ {
+ "id": 427,
+ "type": "Kabupaten",
+ "name": "Nabire",
+ "code": "01",
+ "full_code": "9401",
+ "provinsi_id": 29
+ },
+ {
+ "id": 428,
+ "type": "Kabupaten",
+ "name": "Nagan Raya",
+ "code": "15",
+ "full_code": "1115",
+ "provinsi_id": 1
+ },
+ {
+ "id": 429,
+ "type": "Kabupaten",
+ "name": "Nagekeo",
+ "code": "16",
+ "full_code": "5316",
+ "provinsi_id": 23
+ },
+ {
+ "id": 430,
+ "type": "Kabupaten",
+ "name": "Natuna",
+ "code": "03",
+ "full_code": "2103",
+ "provinsi_id": 18
+ },
+ {
+ "id": 431,
+ "type": "Kabupaten",
+ "name": "Nduga",
+ "code": "08",
+ "full_code": "9508",
+ "provinsi_id": 27
+ },
+ {
+ "id": 432,
+ "type": "Kabupaten",
+ "name": "Ngada",
+ "code": "09",
+ "full_code": "5309",
+ "provinsi_id": 23
+ },
+ {
+ "id": 433,
+ "type": "Kabupaten",
+ "name": "Nganjuk",
+ "code": "18",
+ "full_code": "3518",
+ "provinsi_id": 11
+ },
+ {
+ "id": 434,
+ "type": "Kabupaten",
+ "name": "Ngawi",
+ "code": "21",
+ "full_code": "3521",
+ "provinsi_id": 11
+ },
+ {
+ "id": 435,
+ "type": "Kabupaten",
+ "name": "Nias",
+ "code": "04",
+ "full_code": "1204",
+ "provinsi_id": 38
+ },
+ {
+ "id": 436,
+ "type": "Kabupaten",
+ "name": "Nias Barat",
+ "code": "25",
+ "full_code": "1225",
+ "provinsi_id": 38
+ },
+ {
+ "id": 437,
+ "type": "Kabupaten",
+ "name": "Nias Selatan",
+ "code": "14",
+ "full_code": "1214",
+ "provinsi_id": 38
+ },
+ {
+ "id": 438,
+ "type": "Kabupaten",
+ "name": "Nias Utara",
+ "code": "24",
+ "full_code": "1224",
+ "provinsi_id": 38
+ },
+ {
+ "id": 439,
+ "type": "Kabupaten",
+ "name": "Nunukan",
+ "code": "03",
+ "full_code": "6503",
+ "provinsi_id": 16
+ },
+ {
+ "id": 440,
+ "type": "Kabupaten",
+ "name": "Ogan Ilir",
+ "code": "10",
+ "full_code": "1610",
+ "provinsi_id": 37
+ },
+ {
+ "id": 441,
+ "type": "Kabupaten",
+ "name": "Ogan Komering Ilir",
+ "code": "02",
+ "full_code": "1602",
+ "provinsi_id": 37
+ },
+ {
+ "id": 442,
+ "type": "Kabupaten",
+ "name": "Ogan Komering Ulu",
+ "code": "01",
+ "full_code": "1601",
+ "provinsi_id": 37
+ },
+ {
+ "id": 443,
+ "type": "Kabupaten",
+ "name": "Ogan Komering Ulu Selatan",
+ "code": "09",
+ "full_code": "1609",
+ "provinsi_id": 37
+ },
+ {
+ "id": 444,
+ "type": "Kabupaten",
+ "name": "Ogan Komering Ulu Timur",
+ "code": "08",
+ "full_code": "1608",
+ "provinsi_id": 37
+ },
+ {
+ "id": 445,
+ "type": "Kabupaten",
+ "name": "Pacitan",
+ "code": "01",
+ "full_code": "3501",
+ "provinsi_id": 11
+ },
+ {
+ "id": 446,
+ "type": "Kota",
+ "name": "Padang",
+ "code": "71",
+ "full_code": "1371",
+ "provinsi_id": 36
+ },
+ {
+ "id": 447,
+ "type": "Kabupaten",
+ "name": "Padang Lawas",
+ "code": "21",
+ "full_code": "1221",
+ "provinsi_id": 38
+ },
+ {
+ "id": 448,
+ "type": "Kabupaten",
+ "name": "Padang Lawas Utara",
+ "code": "20",
+ "full_code": "1220",
+ "provinsi_id": 38
+ },
+ {
+ "id": 449,
+ "type": "Kota",
+ "name": "Padang Panjang",
+ "code": "74",
+ "full_code": "1374",
+ "provinsi_id": 36
+ },
+ {
+ "id": 450,
+ "type": "Kabupaten",
+ "name": "Padang Pariaman",
+ "code": "05",
+ "full_code": "1305",
+ "provinsi_id": 36
+ },
+ {
+ "id": 451,
+ "type": "Kota",
+ "name": "Padangsidimpuan",
+ "code": "77",
+ "full_code": "1277",
+ "provinsi_id": 38
+ },
+ {
+ "id": 452,
+ "type": "Kota",
+ "name": "Pagar Alam",
+ "code": "72",
+ "full_code": "1672",
+ "provinsi_id": 37
+ },
+ {
+ "id": 453,
+ "type": "Kabupaten",
+ "name": "Pahuwato",
+ "code": "04",
+ "full_code": "7504",
+ "provinsi_id": 7
+ },
+ {
+ "id": 454,
+ "type": "Kabupaten",
+ "name": "Pakpak Bharat",
+ "code": "15",
+ "full_code": "1215",
+ "provinsi_id": 38
+ },
+ {
+ "id": 455,
+ "type": "Kota",
+ "name": "Palangkaraya",
+ "code": "71",
+ "full_code": "6271",
+ "provinsi_id": 14
+ },
+ {
+ "id": 456,
+ "type": "Kota",
+ "name": "Palembang",
+ "code": "71",
+ "full_code": "1671",
+ "provinsi_id": 37
+ },
+ {
+ "id": 457,
+ "type": "Kota",
+ "name": "Palopo",
+ "code": "73",
+ "full_code": "7373",
+ "provinsi_id": 32
+ },
+ {
+ "id": 458,
+ "type": "Kota",
+ "name": "Palu",
+ "code": "71",
+ "full_code": "7271",
+ "provinsi_id": 33
+ },
+ {
+ "id": 459,
+ "type": "Kabupaten",
+ "name": "Pamekasan",
+ "code": "28",
+ "full_code": "3528",
+ "provinsi_id": 11
+ },
+ {
+ "id": 460,
+ "type": "Kabupaten",
+ "name": "Pandeglang",
+ "code": "01",
+ "full_code": "3601",
+ "provinsi_id": 3
+ },
+ {
+ "id": 461,
+ "type": "Kabupaten",
+ "name": "Pangandaran",
+ "code": "18",
+ "full_code": "3218",
+ "provinsi_id": 9
+ },
+ {
+ "id": 462,
+ "type": "Kabupaten",
+ "name": "Pangkajene Kepulauan",
+ "code": "10",
+ "full_code": "7310",
+ "provinsi_id": 32
+ },
+ {
+ "id": 463,
+ "type": "Kota",
+ "name": "Pangkal Pinang",
+ "code": "71",
+ "full_code": "1971",
+ "provinsi_id": 17
+ },
+ {
+ "id": 464,
+ "type": "Kabupaten",
+ "name": "Paniai",
+ "code": "03",
+ "full_code": "9403",
+ "provinsi_id": 29
+ },
+ {
+ "id": 465,
+ "type": "Kota",
+ "name": "Pare Pare",
+ "code": "72",
+ "full_code": "7372",
+ "provinsi_id": 32
+ },
+ {
+ "id": 466,
+ "type": "Kota",
+ "name": "Pariaman",
+ "code": "77",
+ "full_code": "1377",
+ "provinsi_id": 36
+ },
+ {
+ "id": 467,
+ "type": "Kabupaten",
+ "name": "Parigi Moutong",
+ "code": "08",
+ "full_code": "7208",
+ "provinsi_id": 33
+ },
+ {
+ "id": 468,
+ "type": "Kabupaten",
+ "name": "Pasaman",
+ "code": "08",
+ "full_code": "1308",
+ "provinsi_id": 36
+ },
+ {
+ "id": 469,
+ "type": "Kabupaten",
+ "name": "Pasaman Barat",
+ "code": "12",
+ "full_code": "1312",
+ "provinsi_id": 36
+ },
+ {
+ "id": 470,
+ "type": "Kabupaten",
+ "name": "Pasangkayu (Mamuju Utara)",
+ "code": "01",
+ "full_code": "7601",
+ "provinsi_id": 31
+ },
+ {
+ "id": 471,
+ "type": "Kabupaten",
+ "name": "Paser",
+ "code": "01",
+ "full_code": "6401",
+ "provinsi_id": 15
+ },
+ {
+ "id": 472,
+ "type": "Kabupaten",
+ "name": "Pasuruan",
+ "code": "14",
+ "full_code": "3514",
+ "provinsi_id": 11
+ },
+ {
+ "id": 473,
+ "type": "Kota",
+ "name": "Pasuruan",
+ "code": "75",
+ "full_code": "3575",
+ "provinsi_id": 11
+ },
+ {
+ "id": 474,
+ "type": "Kabupaten",
+ "name": "Pati",
+ "code": "18",
+ "full_code": "3318",
+ "provinsi_id": 10
+ },
+ {
+ "id": 475,
+ "type": "Kota",
+ "name": "Payakumbuh",
+ "code": "76",
+ "full_code": "1376",
+ "provinsi_id": 36
+ },
+ {
+ "id": 476,
+ "type": "Kabupaten",
+ "name": "Pegunungan Arfak",
+ "code": "12",
+ "full_code": "9212",
+ "provinsi_id": 25
+ },
+ {
+ "id": 477,
+ "type": "Kabupaten",
+ "name": "Pegunungan Bintang",
+ "code": "02",
+ "full_code": "9502",
+ "provinsi_id": 27
+ },
+ {
+ "id": 478,
+ "type": "Kabupaten",
+ "name": "Pekalongan",
+ "code": "26",
+ "full_code": "3326",
+ "provinsi_id": 10
+ },
+ {
+ "id": 479,
+ "type": "Kota",
+ "name": "Pekalongan",
+ "code": "75",
+ "full_code": "3375",
+ "provinsi_id": 10
+ },
+ {
+ "id": 480,
+ "type": "Kota",
+ "name": "Pekanbaru",
+ "code": "71",
+ "full_code": "1471",
+ "provinsi_id": 30
+ },
+ {
+ "id": 481,
+ "type": "Kabupaten",
+ "name": "Pelalawan",
+ "code": "05",
+ "full_code": "1405",
+ "provinsi_id": 30
+ },
+ {
+ "id": 482,
+ "type": "Kabupaten",
+ "name": "Pemalang",
+ "code": "27",
+ "full_code": "3327",
+ "provinsi_id": 10
+ },
+ {
+ "id": 483,
+ "type": "Kota",
+ "name": "Pematangsiantar",
+ "code": "72",
+ "full_code": "1272",
+ "provinsi_id": 38
+ },
+ {
+ "id": 484,
+ "type": "Kabupaten",
+ "name": "Penajam Paser Utara",
+ "code": "09",
+ "full_code": "6409",
+ "provinsi_id": 15
+ },
+ {
+ "id": 485,
+ "type": "Kabupaten",
+ "name": "Penukal Abab Lematang Ilir",
+ "code": "12",
+ "full_code": "1612",
+ "provinsi_id": 37
+ },
+ {
+ "id": 486,
+ "type": "Kabupaten",
+ "name": "Pesawaran",
+ "code": "09",
+ "full_code": "1809",
+ "provinsi_id": 19
+ },
+ {
+ "id": 487,
+ "type": "Kabupaten",
+ "name": "Pesisir Barat",
+ "code": "13",
+ "full_code": "1813",
+ "provinsi_id": 19
+ },
+ {
+ "id": 488,
+ "type": "Kabupaten",
+ "name": "Pesisir Selatan",
+ "code": "01",
+ "full_code": "1301",
+ "provinsi_id": 36
+ },
+ {
+ "id": 489,
+ "type": "Kabupaten",
+ "name": "Pidie",
+ "code": "07",
+ "full_code": "1107",
+ "provinsi_id": 1
+ },
+ {
+ "id": 490,
+ "type": "Kabupaten",
+ "name": "Pidie Jaya",
+ "code": "18",
+ "full_code": "1118",
+ "provinsi_id": 1
+ },
+ {
+ "id": 491,
+ "type": "Kabupaten",
+ "name": "Pinrang",
+ "code": "15",
+ "full_code": "7315",
+ "provinsi_id": 32
+ },
+ {
+ "id": 492,
+ "type": "Kabupaten",
+ "name": "Polewali Mandar",
+ "code": "04",
+ "full_code": "7604",
+ "provinsi_id": 31
+ },
+ {
+ "id": 493,
+ "type": "Kabupaten",
+ "name": "Ponorogo",
+ "code": "02",
+ "full_code": "3502",
+ "provinsi_id": 11
+ },
+ {
+ "id": 494,
+ "type": "Kota",
+ "name": "Pontianak",
+ "code": "71",
+ "full_code": "6171",
+ "provinsi_id": 12
+ },
+ {
+ "id": 495,
+ "type": "Kabupaten",
+ "name": "Poso",
+ "code": "02",
+ "full_code": "7202",
+ "provinsi_id": 33
+ },
+ {
+ "id": 496,
+ "type": "Kota",
+ "name": "Prabumulih",
+ "code": "74",
+ "full_code": "1674",
+ "provinsi_id": 37
+ },
+ {
+ "id": 497,
+ "type": "Kabupaten",
+ "name": "Pringsewu",
+ "code": "10",
+ "full_code": "1810",
+ "provinsi_id": 19
+ },
+ {
+ "id": 498,
+ "type": "Kabupaten",
+ "name": "Probolinggo",
+ "code": "13",
+ "full_code": "3513",
+ "provinsi_id": 11
+ },
+ {
+ "id": 499,
+ "type": "Kota",
+ "name": "Probolinggo",
+ "code": "74",
+ "full_code": "3574",
+ "provinsi_id": 11
+ },
+ {
+ "id": 500,
+ "type": "Kabupaten",
+ "name": "Pulang Pisau",
+ "code": "11",
+ "full_code": "6211",
+ "provinsi_id": 14
+ },
+ {
+ "id": 501,
+ "type": "Kabupaten",
+ "name": "Pulau Morotai",
+ "code": "07",
+ "full_code": "8207",
+ "provinsi_id": 21
+ },
+ {
+ "id": 502,
+ "type": "Kabupaten",
+ "name": "Pulau Taliabu",
+ "code": "08",
+ "full_code": "8208",
+ "provinsi_id": 21
+ },
+ {
+ "id": 503,
+ "type": "Kabupaten",
+ "name": "Puncak",
+ "code": "05",
+ "full_code": "9405",
+ "provinsi_id": 29
+ },
+ {
+ "id": 504,
+ "type": "Kabupaten",
+ "name": "Puncak Jaya",
+ "code": "02",
+ "full_code": "9402",
+ "provinsi_id": 29
+ },
+ {
+ "id": 505,
+ "type": "Kabupaten",
+ "name": "Purbalingga",
+ "code": "03",
+ "full_code": "3303",
+ "provinsi_id": 10
+ },
+ {
+ "id": 506,
+ "type": "Kabupaten",
+ "name": "Purwakarta",
+ "code": "14",
+ "full_code": "3214",
+ "provinsi_id": 9
+ },
+ {
+ "id": 507,
+ "type": "Kabupaten",
+ "name": "Purworejo",
+ "code": "06",
+ "full_code": "3306",
+ "provinsi_id": 10
+ },
+ {
+ "id": 508,
+ "type": "Kabupaten",
+ "name": "Raja Ampat",
+ "code": "05",
+ "full_code": "9205",
+ "provinsi_id": 26
+ },
+ {
+ "id": 509,
+ "type": "Kabupaten",
+ "name": "Rejang Lebong",
+ "code": "02",
+ "full_code": "1702",
+ "provinsi_id": 4
+ },
+ {
+ "id": 510,
+ "type": "Kabupaten",
+ "name": "Rembang",
+ "code": "17",
+ "full_code": "3317",
+ "provinsi_id": 10
+ },
+ {
+ "id": 511,
+ "type": "Kabupaten",
+ "name": "Rokan Hilir",
+ "code": "07",
+ "full_code": "1407",
+ "provinsi_id": 30
+ },
+ {
+ "id": 512,
+ "type": "Kabupaten",
+ "name": "Rokan Hulu",
+ "code": "06",
+ "full_code": "1406",
+ "provinsi_id": 30
+ },
+ {
+ "id": 513,
+ "type": "Kabupaten",
+ "name": "Rote Ndao",
+ "code": "14",
+ "full_code": "5314",
+ "provinsi_id": 23
+ },
+ {
+ "id": 514,
+ "type": "Kota",
+ "name": "Sabang",
+ "code": "72",
+ "full_code": "1172",
+ "provinsi_id": 1
+ }
+]
\ No newline at end of file
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/seeds/province.json b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/seeds/province.json
new file mode 100644
index 0000000000000000000000000000000000000000..c155c28ac8278bdde49211c31c0f56ebd2350255
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/seeds/province.json
@@ -0,0 +1,192 @@
+[
+ {
+ "id": 1,
+ "name": "Aceh (NAD)",
+ "code": "11"
+ },
+ {
+ "id": 2,
+ "name": "Bali",
+ "code": "51"
+ },
+ {
+ "id": 3,
+ "name": "Banten",
+ "code": "36"
+ },
+ {
+ "id": 4,
+ "name": "Bengkulu",
+ "code": "17"
+ },
+ {
+ "id": 5,
+ "name": "DI Yogyakarta",
+ "code": "34"
+ },
+ {
+ "id": 6,
+ "name": "DKI Jakarta",
+ "code": "31"
+ },
+ {
+ "id": 7,
+ "name": "Gorontalo",
+ "code": "75"
+ },
+ {
+ "id": 8,
+ "name": "Jambi",
+ "code": "15"
+ },
+ {
+ "id": 9,
+ "name": "Jawa Barat",
+ "code": "32"
+ },
+ {
+ "id": 10,
+ "name": "Jawa Tengah",
+ "code": "33"
+ },
+ {
+ "id": 11,
+ "name": "Jawa Timur",
+ "code": "35"
+ },
+ {
+ "id": 12,
+ "name": "Kalimantan Barat",
+ "code": "61"
+ },
+ {
+ "id": 13,
+ "name": "Kalimantan Selatan",
+ "code": "63"
+ },
+ {
+ "id": 14,
+ "name": "Kalimantan Tengah",
+ "code": "62"
+ },
+ {
+ "id": 15,
+ "name": "Kalimantan Timur",
+ "code": "64"
+ },
+ {
+ "id": 16,
+ "name": "Kalimantan Utara",
+ "code": "65"
+ },
+ {
+ "id": 17,
+ "name": "Kepulauan Bangka Belitung",
+ "code": "19"
+ },
+ {
+ "id": 18,
+ "name": "Kepulauan Riau",
+ "code": "21"
+ },
+ {
+ "id": 19,
+ "name": "Lampung",
+ "code": "18"
+ },
+ {
+ "id": 20,
+ "name": "Maluku",
+ "code": "81"
+ },
+ {
+ "id": 21,
+ "name": "Maluku Utara",
+ "code": "82"
+ },
+ {
+ "id": 22,
+ "name": "Nusa Tenggara Barat (NTB)",
+ "code": "52"
+ },
+ {
+ "id": 23,
+ "name": "Nusa Tenggara Timur (NTT)",
+ "code": "53"
+ },
+ {
+ "id": 24,
+ "name": "Papua",
+ "code": "91"
+ },
+ {
+ "id": 25,
+ "name": "Papua Barat",
+ "code": "92"
+ },
+ {
+ "id": 26,
+ "name": "Papua Barat Daya",
+ "code": "92"
+ },
+ {
+ "id": 27,
+ "name": "Papua Pegunungan",
+ "code": "95"
+ },
+ {
+ "id": 28,
+ "name": "Papua Selatan",
+ "code": "93"
+ },
+ {
+ "id": 29,
+ "name": "Papua Tengah",
+ "code": "94"
+ },
+ {
+ "id": 30,
+ "name": "Riau",
+ "code": "14"
+ },
+ {
+ "id": 31,
+ "name": "Sulawesi Barat",
+ "code": "76"
+ },
+ {
+ "id": 32,
+ "name": "Sulawesi Selatan",
+ "code": "73"
+ },
+ {
+ "id": 33,
+ "name": "Sulawesi Tengah",
+ "code": "72"
+ },
+ {
+ "id": 34,
+ "name": "Sulawesi Tenggara",
+ "code": "74"
+ },
+ {
+ "id": 35,
+ "name": "Sulawesi Utara",
+ "code": "71"
+ },
+ {
+ "id": 36,
+ "name": "Sumatera Barat",
+ "code": "13"
+ },
+ {
+ "id": 37,
+ "name": "Sumatera Selatan",
+ "code": "16"
+ },
+ {
+ "id": 38,
+ "name": "Sumatera Utara",
+ "code": "12"
+ }
+]
\ No newline at end of file
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
index a3d0718f321d11e5b83abd02c43e7cd849ce5cad..ce585609f26349805e924bf4769ec2e2cc2dc4d9 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
@@ -10,9 +10,19 @@ import (
func LogError(errorLogged error) {
fmt.Println("There is an error!")
+
+ // Buat folder 'logs' kalau belum ada
+ if _, err := os.Stat(config.LOG_PATH); os.IsNotExist(err) {
+ err := os.Mkdir(config.LOG_PATH, 0755)
+ if err != nil {
+ log.Fatalf("Gagal buat folder logs: %v", err)
+ }
+ }
+
file, err := os.OpenFile(config.LOG_PATH+"/error_log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
+
if err != nil {
- log.Fatal(err)
+ log.Fatalf("Gagal buka file log: %v", err)
}
log.SetOutput(file)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/api_response.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/api_response.go
index aae210ee39978d67c5412c665a041f45685f0c13..e80d01cbaaec9732ba25efb2106de3a3055dcfd6 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/api_response.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/api_response.go
@@ -6,6 +6,7 @@ import (
"api.qobiltu.id/models"
"api.qobiltu.id/services"
+
"github.com/gin-gonic/gin"
)
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
index ce585609f26349805e924bf4769ec2e2cc2dc4d9..0aae4a5b1bab4c9ac15e661a9b9d364db927de09 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
@@ -10,14 +10,7 @@ import (
func LogError(errorLogged error) {
fmt.Println("There is an error!")
-
- // Buat folder 'logs' kalau belum ada
- if _, err := os.Stat(config.LOG_PATH); os.IsNotExist(err) {
- err := os.Mkdir(config.LOG_PATH, 0755)
- if err != nil {
- log.Fatalf("Gagal buat folder logs: %v", err)
- }
- }
+ log.Println("Error Log :", errorLogged)
file, err := os.OpenFile(config.LOG_PATH+"/error_log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
@@ -26,6 +19,4 @@ func LogError(errorLogged error) {
}
log.SetOutput(file)
-
- log.Println("Error Log :", errorLogged)
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
index 0aae4a5b1bab4c9ac15e661a9b9d364db927de09..0c869361b9fadf16ebb08a7c47a7ffa845ffbc6b 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/Logger.go
@@ -1,7 +1,6 @@
package utils
import (
- "fmt"
"log"
"os"
@@ -9,9 +8,16 @@ import (
)
func LogError(errorLogged error) {
- fmt.Println("There is an error!")
log.Println("Error Log :", errorLogged)
+ _, err := os.Stat(config.LOG_PATH + "/error_log.txt")
+ if os.IsNotExist(err) {
+ _, err = os.Create(config.LOG_PATH + "/error_log.txt")
+ if err != nil {
+ log.Fatalf("Gagal buka file log: %v", err)
+ }
+ }
+
file, err := os.OpenFile(config.LOG_PATH+"/error_log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/token.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/token.go
new file mode 100644
index 0000000000000000000000000000000000000000..bcbc091436555a7ea47a0ca96e7936862e031f48
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/token.go
@@ -0,0 +1,33 @@
+package utils
+
+import (
+ "crypto/rand"
+ "fmt"
+ "math/big"
+)
+
+// GenerateToken menghasilkan Token 6 digit yang aman secara kriptografi.
+func GenerateToken() (int64, error) {
+ // Menentukan batas bawah Token, yaitu 100000 (6 digit terkecil)
+ const minToken = 100000
+
+ // Menentukan jumlah kemungkinan angka 6 digit, yaitu dari 100000 sampai 999999,
+ // sehingga totalnya 900000 kombinasi
+ const maxRange = 900000
+
+ // Mengonversi nilai maxRange ke tipe *big.Int untuk digunakan pada fungsi rand.Int
+ rangeLimit := big.NewInt(int64(maxRange))
+
+ // Menghasilkan angka acak secara aman dalam rentang [0, 899999]
+ n, err := rand.Int(rand.Reader, rangeLimit)
+ if err != nil {
+ // Jika gagal menghasilkan angka acak, kembalikan error
+ return 0, fmt.Errorf("gagal menghasilkan Token: %w", err)
+ }
+
+ // Menambahkan minToken agar angka berada di rentang [100000, 999999]
+ tokenValue := minToken + n.Int64()
+
+ // Memformat hasil sebagai string 6 digit, menambahkan nol di depan jika perlu
+ return tokenValue, nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/util.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/util.go
index 480645d434c01f7152d6747171e5e3ea53969533..8fc197d9780b41a6939ec955c3ea3c770fbde221 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/util.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/utils/util.go
@@ -1,9 +1,17 @@
-package utils
-
-func ternaryMessage(condition bool, valueIfTrue string, valueIfFalse string) string {
- if condition {
- return valueIfTrue
- } else {
- return valueIfFalse
- }
-}
+package utils
+
+import "log"
+
+func ternaryMessage(condition bool, valueIfTrue string, valueIfFalse string) string {
+ if condition {
+ return valueIfTrue
+ } else {
+ return valueIfFalse
+ }
+}
+
+func FatalIfErr(msg string, err error) {
+ if err != nil {
+ log.Fatalf("%s: %v", msg, err)
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/distributor.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/distributor.go
new file mode 100644
index 0000000000000000000000000000000000000000..32d48fae2110e1b001f5ae5ac39697c4263ad2a3
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/distributor.go
@@ -0,0 +1,23 @@
+package worker
+
+import (
+ "context"
+ "github.com/hibiken/asynq"
+)
+
+type TaskDistributor interface {
+ DistributeTaskSendVerifyEmail(
+ ctx context.Context,
+ payload *PayloadSendVerifyEmail,
+ opts ...asynq.Option,
+ ) error
+
+ DistributeTaskSendForgotPasswordEmail(
+ ctx context.Context,
+ payload *PayloadSendForgotPasswordEmail,
+ opts ...asynq.Option,
+ ) error
+}
+
+// AsyncTaskDistributor is a global variable to hold the task distributor
+var AsyncTaskDistributor TaskDistributor
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/logger.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/logger.go
new file mode 100644
index 0000000000000000000000000000000000000000..464c4525aff31d30c3913cfca6c136899eb7badf
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/logger.go
@@ -0,0 +1,37 @@
+package worker
+
+import (
+ "context"
+ "fmt"
+ "log/slog"
+)
+
+type Logger struct{}
+
+func NewLogger() *Logger {
+ return &Logger{}
+}
+
+func (logger *Logger) Printf(ctx context.Context, format string, v ...interface{}) {
+ slog.Info(fmt.Sprintf(format, v...))
+}
+
+func (logger *Logger) Debug(args ...interface{}) {
+ slog.Debug(fmt.Sprint(args...))
+}
+
+func (logger *Logger) Info(args ...interface{}) {
+ slog.Info(fmt.Sprint(args...))
+}
+
+func (logger *Logger) Warn(args ...interface{}) {
+ slog.Warn(fmt.Sprint(args...))
+}
+
+func (logger *Logger) Error(args ...interface{}) {
+ slog.Error(fmt.Sprint(args...))
+}
+
+func (logger *Logger) Fatal(args ...interface{}) {
+ slog.Error(fmt.Sprint(args...))
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/processor.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/processor.go
new file mode 100644
index 0000000000000000000000000000000000000000..dc3880bbfb667336e3e33c3ac539df1940894cf7
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/processor.go
@@ -0,0 +1,66 @@
+package worker
+
+import (
+ "api.qobiltu.id/mail"
+ "context"
+ "github.com/hibiken/asynq"
+ "github.com/redis/go-redis/v9"
+ "log/slog"
+)
+
+const (
+ Low = "low"
+ Default = "default"
+ Critical = "critical"
+)
+
+type TaskProcessor interface {
+ Start() error
+ Shutdown()
+ ProcessTaskSendVerifyEmail(ctx context.Context, task *asynq.Task) error
+ ProcessTaskSendForgotPasswordEmail(ctx context.Context, task *asynq.Task) error
+}
+
+type RedisTaskProcessor struct {
+ server *asynq.Server
+ emailSender mail.Sender
+}
+
+func NewRedisTaskProcessor(redisOpt asynq.RedisClientOpt, emailSender mail.Sender) TaskProcessor {
+ logger := NewLogger()
+ redis.SetLogger(logger)
+
+ server := asynq.NewServer(
+ redisOpt,
+ asynq.Config{
+ // priority value. Keys are the names of the queues and values are associated priority value.
+ Queues: map[string]int{
+ Critical: 6,
+ Default: 3,
+ Low: 1,
+ },
+ ErrorHandler: asynq.ErrorHandlerFunc(func(ctx context.Context, task *asynq.Task, err error) {
+ slog.Error("process task failed", "error", err, "type", task.Type(), "payload", string(task.Payload()))
+ }),
+ // maximum number of concurrent processing of tasks.
+ Concurrency: 50,
+ Logger: logger,
+ },
+ )
+
+ return &RedisTaskProcessor{
+ server: server,
+ emailSender: emailSender,
+ }
+}
+
+func (p *RedisTaskProcessor) Start() error {
+ mux := asynq.NewServeMux()
+ mux.HandleFunc(TaskSendVerifyEmail, p.ProcessTaskSendVerifyEmail)
+ mux.HandleFunc(TaskSendForgotPasswordEmail, p.ProcessTaskSendForgotPasswordEmail)
+ return p.server.Start(mux)
+}
+
+func (p *RedisTaskProcessor) Shutdown() {
+ p.server.Shutdown()
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/redis_distributor.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/redis_distributor.go
new file mode 100644
index 0000000000000000000000000000000000000000..96f30783c7d97bfa4824f656e2e7f805161888ac
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/redis_distributor.go
@@ -0,0 +1,16 @@
+package worker
+
+import (
+ "github.com/hibiken/asynq"
+)
+
+type RedisTaskDistributor struct {
+ client *asynq.Client
+}
+
+func NewRedisTaskDistributor(redisOpt asynq.RedisClientOpt) TaskDistributor {
+ client := asynq.NewClient(redisOpt)
+ return &RedisTaskDistributor{
+ client: client,
+ }
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_forgot_password_email.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_forgot_password_email.go
new file mode 100644
index 0000000000000000000000000000000000000000..5ab35f3958b115b48f0b35f0a7f999a84c0df926
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_forgot_password_email.go
@@ -0,0 +1,70 @@
+package worker
+
+import (
+ "api.qobiltu.id/assets"
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "github.com/hibiken/asynq"
+ "html/template"
+)
+
+const (
+ TaskSendForgotPasswordEmailMaxRetry = 5
+ TaskSendForgotPasswordEmail = "task:send_forgot_password_email"
+ TaskSendForgotPasswordEmailSubject = "Permintaan Reset Password"
+)
+
+type PayloadSendForgotPasswordEmail struct {
+ EmailAddress string `json:"email_address"`
+ ResetToken string `json:"reset_token"`
+ ExpirationInMinutes int `json:"expiration_in_minutes"`
+ Subject string `json:"subject"`
+ AppName string `json:"app_name"`
+}
+
+func (d *RedisTaskDistributor) DistributeTaskSendForgotPasswordEmail(
+ ctx context.Context,
+ payload *PayloadSendForgotPasswordEmail,
+ opts ...asynq.Option,
+) error {
+ jsonPayload, err := json.Marshal(payload)
+ if err != nil {
+ return fmt.Errorf("failed to marshal task payload: %w", err)
+ }
+
+ task := asynq.NewTask(TaskSendForgotPasswordEmail, jsonPayload, opts...)
+
+ _, err = d.client.EnqueueContext(ctx, task)
+ if err != nil {
+ return fmt.Errorf("failed to enqueue task: %w", err)
+ }
+
+ return nil
+}
+
+func (p *RedisTaskProcessor) ProcessTaskSendForgotPasswordEmail(ctx context.Context, task *asynq.Task) error {
+ var payload PayloadSendForgotPasswordEmail
+ if err := json.Unmarshal(task.Payload(), &payload); err != nil {
+ return fmt.Errorf("failed to unmarshal payload: %w", asynq.SkipRetry)
+ }
+
+ var tmpl *template.Template
+ tmpl, err := template.ParseFS(assets.EmbeddedFiles, assets.EmailForgotPasswordTemplatePath)
+ if err != nil {
+ return fmt.Errorf("failed to parse forgot password email template: %w", err)
+ }
+ var body bytes.Buffer
+ if err := tmpl.ExecuteTemplate(&body, "htmlBody", payload); err != nil {
+ return fmt.Errorf("failed to execute forgot password email template: %w", err)
+ }
+ htmlContent := body.String()
+
+ err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
+ if err != nil {
+ return fmt.Errorf("failed to send forgot password email: %w", err)
+ }
+
+ return nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_verify_email.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_verify_email.go
new file mode 100644
index 0000000000000000000000000000000000000000..97717467822664da888697bdf804e7fe7882a037
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_verify_email.go
@@ -0,0 +1,70 @@
+package worker
+
+import (
+ "api.qobiltu.id/assets"
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "github.com/hibiken/asynq"
+ "html/template"
+)
+
+const (
+ TaskSendVerifyEmailMaxRetry = 3
+ TaskSendVerifyEmail = "task:send_verify_email"
+ TaskSendVerifyEmailSubject = "Verifikasi Email"
+)
+
+type PayloadSendVerifyEmail struct {
+ EmailAddress string `json:"email_address"`
+ VerificationCode string `json:"verification_code"`
+ ExpirationInMinutes int `json:"expiration_in_minutes"`
+ Subject string `json:"subject"`
+}
+
+func (d *RedisTaskDistributor) DistributeTaskSendVerifyEmail(
+ ctx context.Context,
+ payload *PayloadSendVerifyEmail,
+ opts ...asynq.Option,
+) error {
+ jsonPayload, err := json.Marshal(payload)
+ if err != nil {
+ return fmt.Errorf("failed to marshal task payload: %w", err)
+ }
+
+ task := asynq.NewTask(TaskSendVerifyEmail, jsonPayload, opts...)
+
+ _, err = d.client.EnqueueContext(ctx, task)
+ if err != nil {
+ return fmt.Errorf("failed to enqueue task: %w", err)
+ }
+
+ return nil
+}
+
+func (p *RedisTaskProcessor) ProcessTaskSendVerifyEmail(ctx context.Context, task *asynq.Task) error {
+ var payload PayloadSendVerifyEmail
+ if err := json.Unmarshal(task.Payload(), &payload); err != nil {
+ return fmt.Errorf("failed to unmarshal payload: %w", asynq.SkipRetry)
+ }
+
+ var tmpl *template.Template
+ tmpl, err := template.ParseFS(assets.EmbeddedFiles, assets.EmailConfirmationTemplatePath)
+ if err != nil {
+ return fmt.Errorf("failed to parse email template: %w", err)
+ }
+
+ var body bytes.Buffer
+ if err := tmpl.ExecuteTemplate(&body, "htmlBody", payload); err != nil {
+ return fmt.Errorf("failed to execute email template: %w", err)
+ }
+ htmlContent := body.String()
+
+ err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
+ if err != nil {
+ return fmt.Errorf("failed to send verify email: %w", err)
+ }
+
+ return nil
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/validation/password.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/validation/password.go
new file mode 100644
index 0000000000000000000000000000000000000000..97cce218dfb57bde0edcf813b3bce35dbab340ed
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/validation/password.go
@@ -0,0 +1,41 @@
+package validation
+
+import (
+ v10 "github.com/go-playground/validator/v10"
+ "regexp"
+)
+
+func PasswordRule(fl v10.FieldLevel) bool {
+ password := fl.Field().String()
+
+ // Minimum 8 karakter
+ if len(password) < 8 {
+ return false
+ }
+
+ // Harus mengandung minimal satu huruf kecil
+ lowercase, _ := regexp.MatchString(`[a-z]`, password)
+ if !lowercase {
+ return false
+ }
+
+ // Harus mengandung minimal satu huruf besar
+ uppercase, _ := regexp.MatchString(`[A-Z]`, password)
+ if !uppercase {
+ return false
+ }
+
+ // Harus mengandung minimal satu angka
+ number, _ := regexp.MatchString(`[0-9]`, password)
+ if !number {
+ return false
+ }
+
+ // Harus mengandung minimal satu karakter spesial
+ specialChar, _ := regexp.MatchString(`[!@#\$%\^&\*\(\)_\+\-=\[\]{};':"\\|,.<>\/?~]`, password)
+ if !specialChar {
+ return false
+ }
+
+ return true
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/validation/validation.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/validation/validation.go
new file mode 100644
index 0000000000000000000000000000000000000000..62f1d980b5613c46a15b8e4a9c2f04b8b7c4a00e
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/validation/validation.go
@@ -0,0 +1,156 @@
+package validation
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "strings"
+
+ "github.com/go-playground/locales/en"
+ "github.com/go-playground/locales/id"
+ ut "github.com/go-playground/universal-translator"
+ v10 "github.com/go-playground/validator/v10"
+ entranslations "github.com/go-playground/validator/v10/translations/en"
+ idtranslations "github.com/go-playground/validator/v10/translations/id"
+)
+
+// Constants for supported locales
+const (
+ LocaleID = "id"
+ LocaleEN = "en"
+)
+
+// ErrorMessage represents a validation error message
+type ErrorMessage struct {
+ Field string `json:"field"`
+ Message string `json:"message"`
+}
+
+// Validator defines the validation behavior
+type Validator interface {
+ Validate(s any) []ErrorMessage
+}
+
+// validatorImpl implements the Validator interface
+type validatorImpl struct {
+ validate *v10.Validate
+ translator ut.Translator
+}
+
+// New creates a new validation instance with the specified locale.
+// It returns a Validator interface for better decoupling.
+func New(locale string) (Validator, error) {
+ v := &validatorImpl{}
+ parsedLocale := parseLocale(locale)
+
+ uni := ut.New(en.New(), id.New(), en.New())
+ translator, found := uni.GetTranslator(parsedLocale)
+ if !found {
+ return nil, fmt.Errorf("translator not found for locale: %s", parsedLocale)
+ }
+
+ validate := v10.New()
+
+ if err := setupValidations(validate); err != nil {
+ return nil, fmt.Errorf("failed to setup validations: %w", err)
+ }
+
+ if err := setupTranslations(validate, translator, parsedLocale); err != nil {
+ return nil, fmt.Errorf("failed to setup translations for locale %s: %w", parsedLocale, err)
+ }
+
+ v.validate = validate
+ v.translator = translator
+ return v, nil
+}
+
+func parseLocale(locale string) string {
+ switch strings.ToLower(locale) {
+ case "id":
+ return LocaleID
+ case "en":
+ return LocaleEN
+ default:
+ return LocaleID // Default to Indonesian
+ }
+}
+
+// setupValidations configures custom validation rules.
+// This is now a package-level function for better testability and separation of concerns.
+func setupValidations(validate *v10.Validate) error {
+ if err := validate.RegisterValidation("password", PasswordRule); err != nil {
+ return fmt.Errorf("failed to register password validation: %w", err)
+ }
+
+ validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
+ if label := fld.Tag.Get("validate_label"); label != "" {
+ return label
+ }
+ return fld.Name
+ })
+
+ return nil
+}
+
+// setupTranslations configures translations for validation messages.
+// This is now a package-level function for better testability and separation of concerns.
+func setupTranslations(validate *v10.Validate, translator ut.Translator, locale string) error {
+ // Register default translations based on locale
+ if err := registerDefaultTranslations(validate, translator, locale); err != nil {
+ return fmt.Errorf("failed to register default translations for locale %s: %w", locale, err)
+ }
+
+ // Register custom password validation translation
+ return validate.RegisterTranslation("password", translator,
+ func(ut ut.Translator) error {
+ return ut.Add("password", "{0} harus mengandung minimal 8 karakter, huruf besar, huruf kecil, angka, karakter spesial, dan tidak menggunakan password yang sering digunakan", true)
+ },
+ func(ut ut.Translator, fe v10.FieldError) string {
+ return fe.Translate(ut)
+ },
+ )
+}
+
+// registerDefaultTranslations sets up default translations for the specified locale.
+// This is now a package-level function for better testability and separation of concerns.
+func registerDefaultTranslations(validate *v10.Validate, translator ut.Translator, locale string) error {
+ switch locale {
+ case LocaleID:
+ return idtranslations.RegisterDefaultTranslations(validate, translator)
+ case LocaleEN:
+ return entranslations.RegisterDefaultTranslations(validate, translator)
+ default:
+ // Fallback to English if the locale is not supported
+ return entranslations.RegisterDefaultTranslations(validate, translator)
+ }
+}
+
+// Validate validates a struct and returns a slice of ErrorMessage.
+// It now returns []ErrorMessage directly, making it more explicit.
+func (v *validatorImpl) Validate(s any) []ErrorMessage {
+ err := v.validate.Struct(s)
+ if err != nil {
+ return TranslateError(err, v.translator)
+ }
+ return nil
+}
+
+// TranslateError takes a validation error and a translator and returns a slice of ErrorMessage.
+// It's now a package-level function that requires the translator as an argument,
+// improving testability and making it independent of the ValidatorImpl instance.
+func TranslateError(err error, translator ut.Translator) []ErrorMessage {
+ var validationErrors v10.ValidationErrors
+ if !errors.As(err, &validationErrors) {
+ return nil
+ }
+
+ errorMessages := make([]ErrorMessage, 0, len(validationErrors))
+ for _, e := range validationErrors {
+ errorMessages = append(errorMessages, ErrorMessage{
+ Field: e.Field(),
+ Message: e.Translate(translator),
+ })
+ }
+
+ return errorMessages
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_forgot_password_email.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_forgot_password_email.go
index 5ab35f3958b115b48f0b35f0a7f999a84c0df926..8675c2b7b005df053a3660f8b42ec6cd2bc5722c 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_forgot_password_email.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_forgot_password_email.go
@@ -8,6 +8,7 @@ import (
"fmt"
"github.com/hibiken/asynq"
"html/template"
+ "log/slog"
)
const (
@@ -61,10 +62,14 @@ func (p *RedisTaskProcessor) ProcessTaskSendForgotPasswordEmail(ctx context.Cont
}
htmlContent := body.String()
+ slog.Info("Sending forgot password email", slog.String("email", payload.EmailAddress))
+
err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
if err != nil {
return fmt.Errorf("failed to send forgot password email: %w", err)
}
+ slog.Info("Forgot password email sent successfully", slog.String("email", payload.EmailAddress))
+
return nil
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_verify_email.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_verify_email.go
index 97717467822664da888697bdf804e7fe7882a037..9f6fd14a6a279def7ca9036bea59175f731f91ef 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_verify_email.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/worker/task_send_verify_email.go
@@ -8,6 +8,7 @@ import (
"fmt"
"github.com/hibiken/asynq"
"html/template"
+ "log/slog"
)
const (
@@ -61,10 +62,14 @@ func (p *RedisTaskProcessor) ProcessTaskSendVerifyEmail(ctx context.Context, tas
}
htmlContent := body.String()
+ slog.Info("Sending verification email", slog.String("email", payload.EmailAddress))
+
err = p.emailSender.Send(payload.EmailAddress, payload.Subject, htmlContent, payload)
if err != nil {
return fmt.Errorf("failed to send verify email: %w", err)
}
+ slog.Info("Verification email sent successfully", slog.String("email", payload.EmailAddress))
+
return nil
}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/validation/custom_rules.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/validation/custom_rules.go
new file mode 100644
index 0000000000000000000000000000000000000000..d559c28835d184e33224c72e90001800888a893b
--- /dev/null
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/validation/custom_rules.go
@@ -0,0 +1,162 @@
+package validation
+
+import (
+ "strings"
+ "sync"
+
+ v10 "github.com/go-playground/validator/v10"
+ "gorm.io/gorm"
+)
+
+type ValidOptionSource interface {
+ GetValidOptions(key string) ([]string, error)
+ GetValidKeys() []string
+}
+
+// --------------------
+// InMemoryOptionSource
+// --------------------
+
+type InMemoryOptionSource struct{}
+
+var inMemoryOptions = map[string][]string{
+ "last_education": {"SD", "SMP", "SMA", "D1", "D2", "D3", "D4", "D5", "S1", "S2", "S3"},
+ "marital_status": {"Belum Menikah", "Duda", "Janda"},
+ "gender": {"Laki-laki", "Perempuan"},
+ "monthly_expenses": {"< 2 Juta", "2-5 Juta", "5-20 Juta", "> 10 Juta"},
+ "monthly_income": {"< 3 Juta", "3-5 Juta", "5-10 Juta", "> 10 Juta"},
+ "religion": {"Islam", "Non-Islam"},
+ "family_role": {"Ayah", "Ibu", "Kakak", "Adik", "Anak"},
+ "life_status": {"Hidup", "Wafat"},
+ "body_shape": {"Ideal", "Kurus", "Berisi", "Gemuk"},
+ "skin_color": {"Putih", "Kuning Langsat", "Sawo Matang"},
+ "hair_type": {"Lurus", "Bergelombang", "Keriting"},
+ "frequently": {"Selalu", "Sering", "Kadang", "Jarang"},
+ "quran_reading_ability": {"Lancar", "Menengah", "Perlu Bimbingan"},
+}
+
+func (s *InMemoryOptionSource) GetValidOptions(key string) ([]string, error) {
+ return inMemoryOptions[key], nil
+}
+
+func (s *InMemoryOptionSource) GetValidKeys() []string {
+ keys := make([]string, 0, len(inMemoryOptions))
+ for k := range inMemoryOptions {
+ keys = append(keys, k)
+ }
+ return keys
+}
+
+// --------------------
+// DBOptionSource
+// --------------------
+
+type DBOptionSource struct {
+ options map[string][]string
+ mu sync.RWMutex
+}
+
+type (
+ OptionCategory struct {
+ ID int64 `gorm:"primaryKey" json:"id"`
+ OptionName string `json:"option_name"`
+ OptionSlug string `json:"option_slug" gorm:"uniqueIndex"`
+ }
+
+ OptionValues struct {
+ ID int64 `gorm:"primaryKey" json:"id"`
+ OptionCategoryID int64 `json:"option_category_id"`
+ OptionValue string `json:"option_value"`
+ }
+)
+
+func NewDBOptionSource(db *gorm.DB) (*DBOptionSource, error) {
+ var categories []OptionCategory
+ if err := db.Find(&categories).Error; err != nil {
+ return nil, err
+ }
+
+ options := make(map[string][]string)
+ for _, cat := range categories {
+ var values []OptionValues
+ if err := db.Where("option_category_id = ?", cat.ID).Find(&values).Error; err != nil {
+ return nil, err
+ }
+ for _, val := range values {
+ options[cat.OptionSlug] = append(options[cat.OptionSlug], val.OptionValue)
+ }
+ }
+
+ return &DBOptionSource{options: options}, nil
+}
+
+func (s *DBOptionSource) GetValidOptions(key string) ([]string, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ return s.options[key], nil
+}
+
+func (s *DBOptionSource) GetValidKeys() []string {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ keys := make([]string, 0, len(s.options))
+ for k := range s.options {
+ keys = append(keys, k)
+ }
+ return keys
+}
+
+// --------------------
+// Validator
+// --------------------
+
+type Validator struct {
+ source ValidOptionSource
+}
+
+func NewValidatorRules(source ValidOptionSource) *Validator {
+ return &Validator{source: source}
+}
+
+func (v *Validator) GenericOptionRule(key string) func(fl v10.FieldLevel) bool {
+ return func(fl v10.FieldLevel) bool {
+ value := fl.Field().String()
+ if value == "" {
+ return true
+ }
+ validOptions, err := v.source.GetValidOptions(key)
+ if err != nil {
+ return false
+ }
+ for _, opt := range validOptions {
+ if opt == value {
+ return true
+ }
+ }
+ return false
+ }
+}
+
+func (v *Validator) RegisterAllCustomRules(validate *v10.Validate) error {
+ for _, key := range v.source.GetValidKeys() {
+ err := validate.RegisterValidation(key, v.GenericOptionRule(key))
+ if err != nil {
+ return err
+ }
+ }
+
+ err := validate.RegisterValidation("password", v.PasswordRule)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (v *Validator) PasswordRule(fl v10.FieldLevel) bool {
+ password := fl.Field().String()
+ return len(password) >= 8 &&
+ strings.ContainsAny(password, "abcdefghijklmnopqrstuvwxyz") &&
+ strings.ContainsAny(password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") &&
+ strings.ContainsAny(password, "0123456789")
+}
diff --git a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/validation/validation.go b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/validation/validation.go
index 62f1d980b5613c46a15b8e4a9c2f04b8b7c4a00e..8e579ea390fd5e3a12e255367accb92310f88411 100644
--- a/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/validation/validation.go
+++ b/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/validation/validation.go
@@ -3,7 +3,6 @@ package validation
import (
"errors"
"fmt"
- "reflect"
"strings"
"github.com/go-playground/locales/en"
@@ -23,45 +22,44 @@ const (
// ErrorMessage represents a validation error message
type ErrorMessage struct {
Field string `json:"field"`
- Message string `json:"message"`
+ Message string `json:"-"`
}
-// Validator defines the validation behavior
-type Validator interface {
- Validate(s any) []ErrorMessage
-}
-
-// validatorImpl implements the Validator interface
-type validatorImpl struct {
+type validator struct {
validate *v10.Validate
translator ut.Translator
}
-// New creates a new validation instance with the specified locale.
-// It returns a Validator interface for better decoupling.
-func New(locale string) (Validator, error) {
- v := &validatorImpl{}
+// validatorInstance adalah instance global dari validator.
+var validatorInstance *validator
+
+// New creates a new validation instance with the specified locale
+// dan menginisialisasi instance global validatorInstance.
+func New(locale string) error {
+ v := &validator{}
parsedLocale := parseLocale(locale)
uni := ut.New(en.New(), id.New(), en.New())
translator, found := uni.GetTranslator(parsedLocale)
if !found {
- return nil, fmt.Errorf("translator not found for locale: %s", parsedLocale)
+ return fmt.Errorf("translator not found for locale: %s", parsedLocale)
}
validate := v10.New()
if err := setupValidations(validate); err != nil {
- return nil, fmt.Errorf("failed to setup validations: %w", err)
+ return fmt.Errorf("failed to setup validations: %w", err)
}
if err := setupTranslations(validate, translator, parsedLocale); err != nil {
- return nil, fmt.Errorf("failed to setup translations for locale %s: %w", parsedLocale, err)
+ return fmt.Errorf("failed to setup translations for locale %s: %w", parsedLocale, err)
}
v.validate = validate
v.translator = translator
- return v, nil
+
+ validatorInstance = v // Inisialisasi instance global
+ return nil
}
func parseLocale(locale string) string {
@@ -76,24 +74,15 @@ func parseLocale(locale string) string {
}
// setupValidations configures custom validation rules.
-// This is now a package-level function for better testability and separation of concerns.
func setupValidations(validate *v10.Validate) error {
- if err := validate.RegisterValidation("password", PasswordRule); err != nil {
- return fmt.Errorf("failed to register password validation: %w", err)
+ rules := NewValidatorRules(&InMemoryOptionSource{})
+ if err := rules.RegisterAllCustomRules(validate); err != nil {
+ return err
}
-
- validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
- if label := fld.Tag.Get("validate_label"); label != "" {
- return label
- }
- return fld.Name
- })
-
return nil
}
// setupTranslations configures translations for validation messages.
-// This is now a package-level function for better testability and separation of concerns.
func setupTranslations(validate *v10.Validate, translator ut.Translator, locale string) error {
// Register default translations based on locale
if err := registerDefaultTranslations(validate, translator, locale); err != nil {
@@ -101,18 +90,26 @@ func setupTranslations(validate *v10.Validate, translator ut.Translator, locale
}
// Register custom password validation translation
- return validate.RegisterTranslation("password", translator,
+ err := validate.RegisterTranslation("password", translator,
func(ut ut.Translator) error {
- return ut.Add("password", "{0} harus mengandung minimal 8 karakter, huruf besar, huruf kecil, angka, karakter spesial, dan tidak menggunakan password yang sering digunakan", true)
+ return ut.Add("password", "harus mengandung minimal 8 karakter, huruf besar, huruf kecil, dan angka.", true)
},
func(ut ut.Translator, fe v10.FieldError) string {
- return fe.Translate(ut)
+ translated, err := ut.T(fe.Tag(), fe.Field())
+ if err != nil {
+ return fe.Field() + " is invalid"
+ }
+ return translated
},
)
+ if err != nil {
+ return fmt.Errorf("failed to register password translation: %w", err)
+ }
+
+ return nil
}
// registerDefaultTranslations sets up default translations for the specified locale.
-// This is now a package-level function for better testability and separation of concerns.
func registerDefaultTranslations(validate *v10.Validate, translator ut.Translator, locale string) error {
switch locale {
case LocaleID:
@@ -125,20 +122,25 @@ func registerDefaultTranslations(validate *v10.Validate, translator ut.Translato
}
}
-// Validate validates a struct and returns a slice of ErrorMessage.
-// It now returns []ErrorMessage directly, making it more explicit.
-func (v *validatorImpl) Validate(s any) []ErrorMessage {
- err := v.validate.Struct(s)
+// Validate validates a struct using the global validator instance
+// and returns a slice of ErrorMessage.
+func Validate(s any) []ErrorMessage {
+ if validatorInstance == nil {
+ return []ErrorMessage{{Field: "", Message: "Validator belum diinisialisasi. Panggil validation.New() terlebih dahulu."}}
+ }
+ err := validatorInstance.validate.Struct(s)
if err != nil {
- return TranslateError(err, v.translator)
+ return TranslateError(err)
}
return nil
}
-// TranslateError takes a validation error and a translator and returns a slice of ErrorMessage.
-// It's now a package-level function that requires the translator as an argument,
-// improving testability and making it independent of the ValidatorImpl instance.
-func TranslateError(err error, translator ut.Translator) []ErrorMessage {
+// TranslateError takes a validation error and translates it using the global translator.
+func TranslateError(err error) []ErrorMessage {
+ if validatorInstance == nil {
+ return nil
+ }
+
var validationErrors v10.ValidationErrors
if !errors.As(err, &validationErrors) {
return nil
@@ -146,9 +148,15 @@ func TranslateError(err error, translator ut.Translator) []ErrorMessage {
errorMessages := make([]ErrorMessage, 0, len(validationErrors))
for _, e := range validationErrors {
+ fieldLabel := e.Field()
+ msg, err := validatorInstance.translator.T(e.Tag(), fieldLabel)
+ if err != nil {
+ msg = fieldLabel + " is Invalid"
+ }
+
errorMessages = append(errorMessages, ErrorMessage{
- Field: e.Field(),
- Message: e.Translate(translator),
+ Field: e.Tag(),
+ Message: msg,
})
}
diff --git a/space/space/space/space/utils/nil.go b/space/space/space/space/utils/nil.go
new file mode 100644
index 0000000000000000000000000000000000000000..f980af996231da2b3418e736591c56f251fa485b
--- /dev/null
+++ b/space/space/space/space/utils/nil.go
@@ -0,0 +1,8 @@
+package utils
+
+// AssignIfNotNil berguna untuk mengassign value ke target jika value tidak nil
+func AssignIfNotNil[T any](target **T, value *T) {
+ if value != nil {
+ *target = value
+ }
+}