fix: auth and permission chmod and regist flow

This commit is contained in:
pahmiudahgede 2025-06-11 06:03:08 +07:00
parent e06b6033b5
commit c26eee0ab9
54 changed files with 1385 additions and 1755 deletions

View File

@ -1,66 +0,0 @@
package dto
import (
"strings"
)
type RequestAboutDTO struct {
Title string `json:"title"`
CoverImage string `json:"cover_image"`
}
func (r *RequestAboutDTO) ValidateAbout() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Title) == "" {
errors["title"] = append(errors["title"], "Title is required")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
type ResponseAboutDTO struct {
ID string `json:"id"`
Title string `json:"title"`
CoverImage string `json:"cover_image"`
AboutDetail *[]ResponseAboutDetailDTO `json:"about_detail"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type RequestAboutDetailDTO struct {
AboutId string `json:"about_id"`
ImageDetail string `json:"image_detail"`
Description string `json:"description"`
}
func (r *RequestAboutDetailDTO) ValidateAboutDetail() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.AboutId) == "" {
errors["about_id"] = append(errors["about_id"], "about_id is required")
}
if strings.TrimSpace(r.Description) == "" {
errors["description"] = append(errors["description"], "Description is required")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
type ResponseAboutDetailDTO struct {
ID string `json:"id"`
AboutID string `json:"about_id"`
ImageDetail string `json:"image_detail"`
Description string `json:"description"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}

View File

@ -1,73 +0,0 @@
package dto
import "strings"
type AddressResponseDTO struct {
UserID string `json:"user_id,omitempty"`
ID string `json:"address_id,omitempty"`
Province string `json:"province,omitempty"`
Regency string `json:"regency,omitempty"`
District string `json:"district,omitempty"`
Village string `json:"village,omitempty"`
PostalCode string `json:"postalCode,omitempty"`
Detail string `json:"detail,omitempty"`
Latitude float64 `json:"latitude,omitempty"`
Longitude float64 `json:"longitude,omitempty"`
CreatedAt string `json:"createdAt,omitempty"`
UpdatedAt string `json:"updatedAt,omitempty"`
}
type CreateAddressDTO struct {
Province string `json:"province_id"`
Regency string `json:"regency_id"`
District string `json:"district_id"`
Village string `json:"village_id"`
PostalCode string `json:"postalCode"`
Detail string `json:"detail"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
}
func (r *CreateAddressDTO) ValidateAddress() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Province) == "" {
errors["province_id"] = append(errors["province_id"], "Province ID is required")
}
if strings.TrimSpace(r.Regency) == "" {
errors["regency_id"] = append(errors["regency_id"], "Regency ID is required")
}
if strings.TrimSpace(r.District) == "" {
errors["district_id"] = append(errors["district_id"], "District ID is required")
}
if strings.TrimSpace(r.Village) == "" {
errors["village_id"] = append(errors["village_id"], "Village ID is required")
}
if strings.TrimSpace(r.PostalCode) == "" {
errors["postalCode"] = append(errors["postalCode"], "PostalCode is required")
} else if len(r.PostalCode) < 5 {
errors["postalCode"] = append(errors["postalCode"], "PostalCode must be at least 5 characters")
}
if strings.TrimSpace(r.Detail) == "" {
errors["detail"] = append(errors["detail"], "Detail address is required")
}
if r.Latitude == 0 {
errors["latitude"] = append(errors["latitude"], "Latitude is required")
}
if r.Longitude == 0 {
errors["longitude"] = append(errors["longitude"], "Longitude is required")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,48 +0,0 @@
package dto
import (
"strings"
)
type ArticleResponseDTO struct {
ID string `json:"article_id"`
Title string `json:"title"`
CoverImage string `json:"coverImage"`
Author string `json:"author"`
Heading string `json:"heading"`
Content string `json:"content"`
PublishedAt string `json:"publishedAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestArticleDTO struct {
Title string `json:"title"`
CoverImage string `json:"coverImage"`
Author string `json:"author"`
Heading string `json:"heading"`
Content string `json:"content"`
}
func (r *RequestArticleDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Title) == "" {
errors["title"] = append(errors["title"], "Title is required")
}
if strings.TrimSpace(r.Author) == "" {
errors["author"] = append(errors["author"], "Author is required")
}
if strings.TrimSpace(r.Heading) == "" {
errors["heading"] = append(errors["heading"], "Heading is required")
}
if strings.TrimSpace(r.Content) == "" {
errors["content"] = append(errors["content"], "Content is required")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,130 +0,0 @@
package dto
import (
"regexp"
"strings"
)
type LoginAdminRequest struct {
Deviceid string `json:"device_id"`
Email string `json:"email"`
Password string `json:"password"`
}
type LoginResponse struct {
UserID string `json:"user_id"`
Role string `json:"role"`
Token string `json:"token"`
}
type RegisterAdminRequest struct {
Name string `json:"name"`
Gender string `json:"gender"`
Dateofbirth string `json:"dateofbirth"`
Placeofbirth string `json:"placeofbirth"`
Phone string `json:"phone"`
Email string `json:"email"`
Password string `json:"password"`
PasswordConfirm string `json:"password_confirm"`
}
type UserAdminDataResponse struct {
UserID string `json:"user_id"`
Name string `json:"name"`
Gender string `json:"gender"`
Dateofbirth string `json:"dateofbirth"`
Placeofbirth string `json:"placeofbirth"`
Phone string `json:"phone"`
Email string `json:"email"`
Password string `json:"password"`
Role string `json:"role"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
func (r *RegisterAdminRequest) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Name) == "" {
errors["name"] = append(errors["name"], "Name is required")
}
if strings.TrimSpace(r.Gender) == "" {
errors["gender"] = append(errors["gender"], "Gender is required")
} else if r.Gender != "male" && r.Gender != "female" {
errors["gender"] = append(errors["gender"], "Gender must be either 'male' or 'female'")
}
if strings.TrimSpace(r.Dateofbirth) == "" {
errors["dateofbirth"] = append(errors["dateofbirth"], "Date of birth is required")
}
if strings.TrimSpace(r.Placeofbirth) == "" {
errors["placeofbirth"] = append(errors["placeofbirth"], "Place of birth is required")
}
if strings.TrimSpace(r.Phone) == "" {
errors["phone"] = append(errors["phone"], "Phone is required")
} else if !IsValidPhoneNumber(r.Phone) {
errors["phone"] = append(errors["phone"], "Invalid phone number format. Use 62 followed by 9-13 digits")
}
if strings.TrimSpace(r.Email) == "" {
errors["email"] = append(errors["email"], "Email is required")
} else if !IsValidEmail(r.Email) {
errors["email"] = append(errors["email"], "Invalid email format")
}
if len(r.Password) < 6 {
errors["password"] = append(errors["password"], "Password must be at least 6 characters")
} else if !IsValidPassword(r.Password) {
errors["password"] = append(errors["password"], "Password must contain at least one uppercase letter, one number, and one special character")
}
if r.Password != r.PasswordConfirm {
errors["password_confirm"] = append(errors["password_confirm"], "Password and confirmation do not match")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func IsValidPhoneNumber(phone string) bool {
re := regexp.MustCompile(`^62\d{9,13}$`)
return re.MatchString(phone)
}
func IsValidEmail(email string) bool {
re := regexp.MustCompile(`^[a-z0-9]+@[a-z0-9]+\.[a-z]{2,}$`)
return re.MatchString(email)
}
func IsValidPassword(password string) bool {
if len(password) < 6 {
return false
}
hasUpper := false
hasDigit := false
hasSpecial := false
for _, char := range password {
if char >= 'A' && char <= 'Z' {
hasUpper = true
} else if char >= '0' && char <= '9' {
hasDigit = true
} else if isSpecialCharacter(char) {
hasSpecial = true
}
}
return hasUpper && hasDigit && hasSpecial
}
func isSpecialCharacter(char rune) bool {
specialChars := "!@#$%^&*()-_=+[]{}|;:'\",.<>?/`~"
return strings.ContainsRune(specialChars, char)
}

View File

@ -1 +0,0 @@
package dto

View File

@ -1,133 +0,0 @@
package dto
import (
"regexp"
"rijig/utils"
)
type LoginPengelolaRequest struct {
Phone string `json:"phone"`
}
func (r *LoginPengelolaRequest) ValidateLogin() (map[string][]string, bool) {
errors := make(map[string][]string)
if r.Phone == "" {
errors["phone"] = append(errors["phone"], "Phone number is required")
} else if !utils.IsValidPhoneNumber(r.Phone) {
errors["phone"] = append(errors["phone"], "Phone number is not valid")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
type VerifLoginPengelolaRequest struct {
Phone string `json:"phone"`
Otp string `json:"verif_otp"`
}
func (r *VerifLoginPengelolaRequest) ValidateVerifLogin() (map[string][]string, bool) {
errors := make(map[string][]string)
if r.Phone == "" {
errors["phone"] = append(errors["phone"], "Phone number is required")
} else if !utils.IsValidPhoneNumber(r.Phone) {
errors["phone"] = append(errors["phone"], "Phone number is not valid")
}
if r.Otp == "" {
errors["otp"] = append(errors["otp"], "OTP is required")
} else if len(r.Otp) != 6 {
errors["otp"] = append(errors["otp"], "OTP must be 6 digits")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
type LoginPengelolaResponse struct {
UserID string `json:"user_id"`
Role string `json:"role"`
Token string `json:"token"`
}
type PengelolaIdentityCard struct {
Cardphoto string `json:"cardphoto"`
Identificationumber string `json:"identificationumber"`
Placeofbirth string `json:"placeofbirth"`
Dateofbirth string `json:"dateofbirth"`
Gender string `json:"gender"`
BloodType string `json:"bloodtype"`
District string `json:"district"`
Village string `json:"village"`
Neighbourhood string `json:"neighbourhood"`
Religion string `json:"religion"`
Maritalstatus string `json:"maritalstatus"`
Job string `json:"job"`
Citizenship string `json:"citizenship"`
Validuntil string `json:"validuntil"`
}
func (r *PengelolaIdentityCard) ValidateIDcard() (map[string][]string, bool) {
errors := make(map[string][]string)
if r.Cardphoto == "" {
errors["cardphoto"] = append(errors["cardphoto"], "Card photo is required")
}
if r.Identificationumber == "" {
errors["identificationumber"] = append(errors["identificationumber"], "Identification number is required")
}
if r.Dateofbirth == "" {
errors["dateofbirth"] = append(errors["dateofbirth"], "Date of birth is required")
} else if !isValidDate(r.Dateofbirth) {
errors["dateofbirth"] = append(errors["dateofbirth"], "Date of birth must be in DD-MM-YYYY format")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
type PengelolaCompanyProfile struct {
CompanyName string `json:"company_name"`
CompanyPhone string `json:"company_phone"`
CompanyEmail string `json:"company_email"`
}
func (r *PengelolaCompanyProfile) ValidateCompany() (map[string][]string, bool) {
errors := make(map[string][]string)
if r.CompanyName == "" {
errors["company_name"] = append(errors["company_name"], "Company name is required")
}
if r.CompanyPhone == "" {
errors["company_phone"] = append(errors["company_phone"], "Company phone is required")
} else if !utils.IsValidPhoneNumber(r.CompanyPhone) {
errors["company_phone"] = append(errors["company_phone"], "Invalid phone number format")
}
if r.CompanyEmail == "" {
errors["company_email"] = append(errors["company_email"], "Company email is required")
} else if !utils.IsValidEmail(r.CompanyEmail) {
errors["company_email"] = append(errors["company_email"], "Invalid email format")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func isValidDate(date string) bool {
re := regexp.MustCompile(`^\d{2}-\d{2}-\d{4}$`)
return re.MatchString(date)
}

View File

@ -1 +0,0 @@
package dto

View File

@ -1,42 +0,0 @@
package dto
import (
"regexp"
"strings"
)
type RegisterRequest struct {
Phone string `json:"phone"`
}
type VerifyOTPRequest struct {
Phone string `json:"phone"`
OTP string `json:"otp"`
DeviceID string `json:"device_id"`
}
type UserDataResponse struct {
UserID string `json:"user_id"`
UserRole string `json:"user_role"`
Token string `json:"token"`
}
func (r *RegisterRequest) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Phone) == "" {
errors["phone"] = append(errors["phone"], "Phone is required")
} else if !IsValidPhoneNumber(r.Phone) {
errors["phone"] = append(errors["phone"], "Invalid phone number format. Use 62 followed by 9-13 digits")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func IsValidPhoneNumber(phone string) bool {
re := regexp.MustCompile(`^62\d{9,13}$`)
return re.MatchString(phone)
}

View File

@ -1,29 +0,0 @@
package dto
import "strings"
type ResponseBannerDTO struct {
ID string `json:"id"`
BannerName string `json:"bannername"`
BannerImage string `json:"bannerimage"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestBannerDTO struct {
BannerName string `json:"bannername"`
BannerImage string `json:"bannerimage"`
}
func (r *RequestBannerDTO) ValidateBannerInput() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.BannerName) == "" {
errors["bannername"] = append(errors["bannername"], "nama banner harus diisi")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,111 +0,0 @@
package dto
import (
"fmt"
"strings"
)
type NearbyCollectorDTO struct {
CollectorID string `json:"collector_id"`
Name string `json:"name"`
Phone string `json:"phone"`
Rating float32 `json:"rating"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
DistanceKm float64 `json:"distance_km"`
MatchedTrash []string `json:"matched_trash_ids"`
}
type RequestCollectorDTO struct {
AddressId string `json:"address_id"`
AvaibleTrashbyCollector []RequestAvaibleTrashbyCollector `json:"avaible_trash"`
}
type RequestAvaibleTrashbyCollector struct {
TrashId string `json:"trash_id"`
TrashPrice float32 `json:"trash_price"`
}
type RequestAddAvaibleTrash struct {
AvaibleTrash []RequestAvaibleTrashbyCollector `json:"avaible_trash"`
}
type SelectCollectorRequest struct {
Collector_id string `json:"collector_id"`
}
func (r *SelectCollectorRequest) ValidateSelectCollectorRequest() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Collector_id) == "" {
errors["collector_id"] = append(errors["collector_id"], "collector_id harus diisi")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func (r *RequestAddAvaibleTrash) ValidateRequestAddAvaibleTrash() (map[string][]string, bool) {
errors := make(map[string][]string)
if len(r.AvaibleTrash) == 0 {
errors["avaible_trash"] = append(errors["avaible_trash"], "tidak boleh kosong")
}
for i, trash := range r.AvaibleTrash {
if strings.TrimSpace(trash.TrashId) == "" {
errors[fmt.Sprintf("avaible_trash[%d].trash_id", i)] = append(errors[fmt.Sprintf("avaible_trash[%d].trash_id", i)], "trash_id tidak boleh kosong")
}
if trash.TrashPrice <= 0 {
errors[fmt.Sprintf("avaible_trash[%d].trash_price", i)] = append(errors[fmt.Sprintf("avaible_trash[%d].trash_price", i)], "trash_price harus lebih dari 0")
}
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
type ResponseCollectorDTO struct {
ID string `json:"collector_id"`
UserId string `json:"user_id"`
User *UserResponseDTO `json:"user,omitempty"`
AddressId string `json:"address_id"`
Address *AddressResponseDTO `json:"address,omitempty"`
JobStatus *string `json:"job_status,omitempty"`
Rating float32 `json:"rating"`
AvaibleTrashbyCollector []ResponseAvaibleTrashByCollector `json:"avaible_trash"`
}
type ResponseAvaibleTrashByCollector struct {
ID string `json:"id"`
TrashId string `json:"trash_id"`
TrashName string `json:"trash_name"`
TrashIcon string `json:"trash_icon"`
TrashPrice float32 `json:"trash_price"`
}
func (r *RequestCollectorDTO) ValidateRequestCollector() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.AddressId) == "" {
errors["address_id"] = append(errors["address_id"], "address_id harus diisi")
}
for i, trash := range r.AvaibleTrashbyCollector {
if strings.TrimSpace(trash.TrashId) == "" {
errors[fmt.Sprintf("avaible_trash[%d].trash_id", i)] = append(errors[fmt.Sprintf("avaible_trash[%d].trash_id", i)], "trash_id tidak boleh kosong")
}
if trash.TrashPrice <= 0 {
errors[fmt.Sprintf("avaible_trash[%d].trash_price", i)] = append(errors[fmt.Sprintf("avaible_trash[%d].trash_price", i)], "trash_price harus lebih dari 0")
}
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,62 +0,0 @@
package dto
import (
"rijig/utils"
"strings"
)
type ResponseCompanyProfileDTO struct {
ID string `json:"id"`
UserID string `json:"userId"`
CompanyName string `json:"company_name"`
CompanyAddress string `json:"company_address"`
CompanyPhone string `json:"company_phone"`
CompanyEmail string `json:"company_email"`
CompanyLogo string `json:"company_logo,omitempty"`
CompanyWebsite string `json:"company_website,omitempty"`
TaxID string `json:"taxId,omitempty"`
FoundedDate string `json:"founded_date,omitempty"`
CompanyType string `json:"company_type,omitempty"`
CompanyDescription string `json:"company_description"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestCompanyProfileDTO struct {
CompanyName string `json:"company_name"`
CompanyAddress string `json:"company_address"`
CompanyPhone string `json:"company_phone"`
CompanyEmail string `json:"company_email"`
CompanyLogo string `json:"company_logo,omitempty"`
CompanyWebsite string `json:"company_website,omitempty"`
TaxID string `json:"taxId,omitempty"`
FoundedDate string `json:"founded_date,omitempty"`
CompanyType string `json:"company_type,omitempty"`
CompanyDescription string `json:"company_description"`
}
func (r *RequestCompanyProfileDTO) ValidateCompanyProfileInput() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.CompanyName) == "" {
errors["company_Name"] = append(errors["company_name"], "Company name is required")
}
if strings.TrimSpace(r.CompanyAddress) == "" {
errors["company_Address"] = append(errors["company_address"], "Company address is required")
}
if !utils.IsValidPhoneNumber(r.CompanyPhone) {
errors["company_Phone"] = append(errors["company_phone"], "nomor harus dimulai 62.. dan 8-14 digit")
}
if strings.TrimSpace(r.CompanyDescription) == "" {
errors["company_Description"] = append(errors["company_description"], "Company description is required")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,34 +0,0 @@
package dto
import "strings"
type RequestCoverageArea struct {
Province string `json:"province"`
Regency string `json:"regency"`
}
type ResponseCoverageArea struct {
ID string `json:"id"`
Province string `json:"province"`
Regency string `json:"regency"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
func (r *RequestCoverageArea) ValidateCoverageArea() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Province) == "" {
errors["province"] = append(errors["province"], "nama provinsi harus diisi")
}
if strings.TrimSpace(r.Regency) == "" {
errors["regency"] = append(errors["regency"], "nama regency harus diisi")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,116 +0,0 @@
package dto
import (
"strings"
)
type ResponseIdentityCardDTO struct {
ID string `json:"id"`
UserID string `json:"userId"`
Identificationumber string `json:"identificationumber"`
Placeofbirth string `json:"placeofbirth"`
Dateofbirth string `json:"dateofbirth"`
Gender string `json:"gender"`
BloodType string `json:"bloodtype"`
District string `json:"district"`
Village string `json:"village"`
Neighbourhood string `json:"neighbourhood"`
Religion string `json:"religion"`
Maritalstatus string `json:"maritalstatus"`
Job string `json:"job"`
Citizenship string `json:"citizenship"`
Validuntil string `json:"validuntil"`
Cardphoto string `json:"cardphoto"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestIdentityCardDTO struct {
UserID string `json:"userId"`
Identificationumber string `json:"identificationumber"`
Placeofbirth string `json:"placeofbirth"`
Dateofbirth string `json:"dateofbirth"`
Gender string `json:"gender"`
BloodType string `json:"bloodtype"`
District string `json:"district"`
Village string `json:"village"`
Neighbourhood string `json:"neighbourhood"`
Religion string `json:"religion"`
Maritalstatus string `json:"maritalstatus"`
Job string `json:"job"`
Citizenship string `json:"citizenship"`
Validuntil string `json:"validuntil"`
Cardphoto string `json:"cardphoto"`
}
func (r *RequestIdentityCardDTO) ValidateIdentityCardInput() (map[string][]string, bool) {
errors := make(map[string][]string)
isValid := true
if strings.TrimSpace(r.Identificationumber) == "" {
errors["identificationumber"] = append(errors["identificationumber"], "Nomor identifikasi harus diisi")
isValid = false
}
if strings.TrimSpace(r.Placeofbirth) == "" {
errors["placeofbirth"] = append(errors["placeofbirth"], "Tempat lahir harus diisi")
isValid = false
}
if strings.TrimSpace(r.Dateofbirth) == "" {
errors["dateofbirth"] = append(errors["dateofbirth"], "Tanggal lahir harus diisi")
isValid = false
}
if strings.TrimSpace(r.Gender) == "" {
errors["gender"] = append(errors["gender"], "Jenis kelamin harus diisi")
isValid = false
}
if strings.TrimSpace(r.BloodType) == "" {
errors["bloodtype"] = append(errors["bloodtype"], "Golongan darah harus diisi")
isValid = false
}
if strings.TrimSpace(r.District) == "" {
errors["district"] = append(errors["district"], "Kecamatan harus diisi")
isValid = false
}
if strings.TrimSpace(r.Village) == "" {
errors["village"] = append(errors["village"], "Desa harus diisi")
isValid = false
}
if strings.TrimSpace(r.Neighbourhood) == "" {
errors["neighbourhood"] = append(errors["neighbourhood"], "RT/RW harus diisi")
isValid = false
}
if strings.TrimSpace(r.Religion) == "" {
errors["religion"] = append(errors["religion"], "Agama harus diisi")
isValid = false
}
if strings.TrimSpace(r.Maritalstatus) == "" {
errors["maritalstatus"] = append(errors["maritalstatus"], "Status pernikahan harus diisi")
isValid = false
}
if strings.TrimSpace(r.Job) == "" {
errors["job"] = append(errors["job"], "Pekerjaan harus diisi")
isValid = false
}
if strings.TrimSpace(r.Citizenship) == "" {
errors["citizenship"] = append(errors["citizenship"], "Kewarganegaraan harus diisi")
isValid = false
}
if strings.TrimSpace(r.Validuntil) == "" {
errors["validuntil"] = append(errors["validuntil"], "Masa berlaku harus diisi")
isValid = false
}
return errors, isValid
}

View File

@ -1,34 +0,0 @@
package dto
import "strings"
type ReponseInitialCointDTO struct {
ID string `json:"coin_id"`
CoinName string `json:"coin_name"`
ValuePerUnit float64 `json:"value_perunit"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestInitialCointDTO struct {
CoinName string `json:"coin_name"`
ValuePerUnit float64 `json:"value_perunit"`
}
func (r *RequestInitialCointDTO) ValidateCointInput() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.CoinName) == "" {
errors["coin_name"] = append(errors["coin_name"], "nama coin harus diisi")
}
if r.ValuePerUnit <= 0 {
errors["value_perunit"] = append(errors["value_perunit"], "value per unit harus lebih besar dari 0")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,55 +0,0 @@
package dto
import (
"mime/multipart"
"regexp"
"strings"
)
type ResponseProductImageDTO struct {
ID string `json:"id"`
ProductID string `json:"productId"`
ImageURL string `json:"imageURL"`
}
type ResponseProductDTO struct {
ID string `json:"id"`
StoreID string `json:"storeId"`
ProductName string `json:"productName"`
Quantity int `json:"quantity"`
Saled int `json:"saled"`
ProductImages []ResponseProductImageDTO `json:"productImages,omitempty"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestProductDTO struct {
ProductName string `json:"product_name"`
Quantity int `json:"quantity"`
ProductImages []*multipart.FileHeader `json:"product_images,omitempty"`
}
func (r *RequestProductDTO) ValidateProductInput() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.ProductName) == "" {
errors["product_name"] = append(errors["product_name"], "Product name is required")
} else if len(r.ProductName) < 3 {
errors["product_name"] = append(errors["product_name"], "Product name must be at least 3 characters long")
} else {
validNameRegex := `^[a-zA-Z0-9\s_.-]+$`
if matched, _ := regexp.MatchString(validNameRegex, r.ProductName); !matched {
errors["product_name"] = append(errors["product_name"], "Product name can only contain letters, numbers, spaces, underscores, and dashes")
}
}
if r.Quantity < 1 {
errors["quantity"] = append(errors["quantity"], "Quantity must be at least 1")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,25 +0,0 @@
package dto
import "strings"
type CreatePickupRatingDTO struct {
Rating float32 `json:"rating"`
Feedback string `json:"feedback"`
}
func (r *CreatePickupRatingDTO) ValidateCreatePickupRatingDTO() (map[string][]string, bool) {
errors := make(map[string][]string)
if r.Rating < 1.0 || r.Rating > 5.0 {
errors["rating"] = append(errors["rating"], "Rating harus antara 1.0 sampai 5.0")
}
if len(strings.TrimSpace(r.Feedback)) > 255 {
errors["feedback"] = append(errors["feedback"], "Feedback tidak boleh lebih dari 255 karakter")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,74 +0,0 @@
package dto
import (
"strings"
)
type SelectCollectorDTO struct {
CollectorID string `json:"collector_id"`
}
type UpdateRequestPickupItemDTO struct {
ItemID string `json:"item_id"`
Amount float64 `json:"actual_amount"`
}
type UpdatePickupItemsRequest struct {
Items []UpdateRequestPickupItemDTO `json:"items"`
}
func (r *SelectCollectorDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.CollectorID) == "" {
errors["collector_id"] = append(errors["collector_id"], "collector_id tidak boleh kosong")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
type AssignedPickupDTO struct {
PickupID string `json:"pickup_id"`
UserID string `json:"user_id"`
UserName string `json:"user_name"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
Notes string `json:"notes"`
MatchedTrash []string `json:"matched_trash"`
}
type PickupRequestForCollectorDTO struct {
PickupID string `json:"pickup_id"`
UserID string `json:"user_id"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
DistanceKm float64 `json:"distance_km"`
MatchedTrash []string `json:"matched_trash"`
}
type RequestPickupDTO struct {
AddressID string `json:"address_id"`
RequestMethod string `json:"request_method"`
Notes string `json:"notes,omitempty"`
}
func (r *RequestPickupDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.AddressID) == "" {
errors["address_id"] = append(errors["address_id"], "alamat harus dipilih")
}
method := strings.ToLower(strings.TrimSpace(r.RequestMethod))
if method != "manual" && method != "otomatis" {
errors["request_method"] = append(errors["request_method"], "harus manual atau otomatis")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,88 +0,0 @@
package dto
import (
"fmt"
"strings"
)
type RequestPickup struct {
AddressID string `json:"address_id"`
RequestMethod string `json:"request_method"`
EvidenceImage string `json:"evidence_image"`
Notes string `json:"notes"`
RequestItems []RequestPickupItem `json:"request_items"`
}
type RequestPickupItem struct {
TrashCategoryID string `json:"trash_category_id"`
EstimatedAmount float64 `json:"estimated_amount"`
}
type ResponseRequestPickup struct {
ID string `json:"id,omitempty"`
UserId string `json:"user_id,omitempty"`
User []UserResponseDTO `json:"user,omitempty"`
AddressID string `json:"address_id,omitempty"`
Address []AddressResponseDTO `json:"address,omitempty"`
EvidenceImage string `json:"evidence_image,omitempty"`
Notes string `json:"notes,omitempty"`
StatusPickup string `json:"status_pickup,omitempty"`
CollectorID string `json:"collectorid,omitempty"`
Collector []ResponseCollectorDTO `json:"collector,omitempty"`
ConfirmedByCollectorAt string `json:"confirmedat,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
RequestItems []ResponseRequestPickupItem `json:"request_items,omitempty"`
}
type ResponseRequestPickupItem struct {
ID string `json:"id,omitempty"`
TrashCategoryID string `json:"trash_category_id,omitempty"`
// TrashCategory []ResponseTrashCategoryDTO `json:"trash_category,omitempty"`
EstimatedAmount float64 `json:"estimated_amount,omitempty"`
}
func (r *RequestPickup) ValidateRequestPickup() (map[string][]string, bool) {
errors := make(map[string][]string)
if len(r.RequestItems) == 0 {
errors["request_items"] = append(errors["request_items"], "At least one item must be provided")
}
if strings.TrimSpace(r.AddressID) == "" {
errors["address_id"] = append(errors["address_id"], "Address ID must be provided")
}
for i, item := range r.RequestItems {
itemErrors, valid := item.ValidateRequestPickupItem(i)
if !valid {
for field, msgs := range itemErrors {
errors[field] = append(errors[field], msgs...)
}
}
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func (r *RequestPickupItem) ValidateRequestPickupItem(index int) (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.TrashCategoryID) == "" {
errors["trash_category_id"] = append(errors["trash_category_id"], fmt.Sprintf("Trash category ID cannot be empty (Item %d)", index+1))
}
if r.EstimatedAmount < 2 {
errors["estimated_amount"] = append(errors["estimated_amount"], fmt.Sprintf("Estimated amount must be >= 2.0 kg (Item %d)", index+1))
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,8 +0,0 @@
package dto
type RoleResponseDTO struct {
ID string `json:"role_id"`
RoleName string `json:"role_name"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}

View File

@ -1,68 +0,0 @@
package dto
import (
"regexp"
"strings"
)
type ResponseStoreDTO struct {
ID string `json:"id"`
UserID string `json:"userId"`
StoreName string `json:"storeName"`
StoreLogo string `json:"storeLogo"`
StoreBanner string `json:"storeBanner"`
StoreInfo string `json:"storeInfo"`
StoreAddressID string `json:"storeAddressId"`
TotalProduct int `json:"TotalProduct"`
Followers int `json:"followers"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestStoreDTO struct {
StoreName string `json:"store_name"`
StoreLogo string `json:"store_logo"`
StoreBanner string `json:"store_banner"`
StoreInfo string `json:"store_info"`
StoreAddressID string `json:"store_address_id"`
}
func (r *RequestStoreDTO) ValidateStoreInput() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.StoreName) == "" {
errors["store_name"] = append(errors["store_name"], "Store name is required")
} else if len(r.StoreName) < 3 {
errors["store_name"] = append(errors["store_name"], "Store name must be at least 3 characters long")
} else {
validNameRegex := `^[a-zA-Z0-9_.\s]+$`
if matched, _ := regexp.MatchString(validNameRegex, r.StoreName); !matched {
errors["store_name"] = append(errors["store_name"], "Store name can only contain letters, numbers, underscores, and periods")
}
}
if strings.TrimSpace(r.StoreLogo) == "" {
errors["store_logo"] = append(errors["store_logo"], "Store logo is required")
}
if strings.TrimSpace(r.StoreBanner) == "" {
errors["store_banner"] = append(errors["store_banner"], "Store banner is required")
}
if strings.TrimSpace(r.StoreInfo) == "" {
errors["store_info"] = append(errors["store_info"], "Store info is required")
}
uuidRegex := `^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`
if r.StoreAddressID == "" {
errors["store_address_id"] = append(errors["store_address_id"], "Store address ID is required")
} else if matched, _ := regexp.MatchString(uuidRegex, r.StoreAddressID); !matched {
errors["store_address_id"] = append(errors["store_address_id"], "Invalid Store Address ID format")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,65 +0,0 @@
package dto
/*
import (
"strings"
)
type RequestTrashCategoryDTO struct {
Name string `json:"name"`
EstimatedPrice string `json:"estimatedprice"`
Icon string `json:"icon"`
}
type ResponseTrashCategoryDTO struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Icon string `json:"icon,omitempty"`
EstimatedPrice float64 `json:"estimatedprice"`
CreatedAt string `json:"createdAt,omitempty"`
UpdatedAt string `json:"updatedAt,omitempty"`
Details []ResponseTrashDetailDTO `json:"details,omitempty"`
}
type ResponseTrashDetailDTO struct {
ID string `json:"id"`
CategoryID string `json:"category_id"`
Description string `json:"description"`
Price float64 `json:"price"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type RequestTrashDetailDTO struct {
CategoryID string `json:"category_id"`
Description string `json:"description"`
Price float64 `json:"price"`
}
func (r *RequestTrashCategoryDTO) ValidateTrashCategoryInput() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Name) == "" {
errors["name"] = append(errors["name"], "name is required")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func (r *RequestTrashDetailDTO) ValidateTrashDetailInput() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Description) == "" {
errors["description"] = append(errors["description"], "description is required")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
*/

View File

@ -1,49 +0,0 @@
package dto
import (
"fmt"
"strings"
)
type RequestCartItemDTO struct {
TrashID string `json:"trash_id"`
Amount float64 `json:"amount"`
}
type RequestCartDTO struct {
CartItems []RequestCartItemDTO `json:"cart_items"`
}
type ResponseCartDTO struct {
ID string `json:"id"`
UserID string `json:"user_id"`
TotalAmount float64 `json:"total_amount"`
EstimatedTotalPrice float64 `json:"estimated_total_price"`
CartItems []ResponseCartItemDTO `json:"cart_items"`
}
type ResponseCartItemDTO struct {
ID string `json:"id"`
TrashID string `json:"trash_id"`
TrashName string `json:"trash_name"`
TrashIcon string `json:"trash_icon"`
TrashPrice float64 `json:"trash_price"`
Amount float64 `json:"amount"`
SubTotalEstimatedPrice float64 `json:"subtotal_estimated_price"`
}
func (r *RequestCartDTO) ValidateRequestCartDTO() (map[string][]string, bool) {
errors := make(map[string][]string)
for i, item := range r.CartItems {
if strings.TrimSpace(item.TrashID) == "" {
errors[fmt.Sprintf("cart_items[%d].trash_id", i)] = append(errors[fmt.Sprintf("cart_items[%d].trash_id", i)], "trash_id tidak boleh kosong")
}
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,81 +0,0 @@
package dto
import (
"rijig/utils"
"strings"
)
type UserResponseDTO struct {
ID string `json:"id,omitempty"`
Username string `json:"username,omitempty"`
Avatar *string `json:"photoprofile,omitempty"`
Name string `json:"name,omitempty"`
Phone string `json:"phone,omitempty"`
Email string `json:"email,omitempty"`
EmailVerified bool `json:"emailVerified,omitempty"`
RoleName string `json:"role,omitempty"`
CreatedAt string `json:"createdAt,omitempty"`
UpdatedAt string `json:"updatedAt,omitempty"`
}
type RequestUserDTO struct {
Name string `json:"name"`
Phone string `json:"phone"`
Email string `json:"email"`
}
func (r *RequestUserDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Name) == "" {
errors["name"] = append(errors["name"], "Name is required")
}
if strings.TrimSpace(r.Phone) == "" {
errors["phone"] = append(errors["phone"], "Phone number is required")
} else if !utils.IsValidPhoneNumber(r.Phone) {
errors["phone"] = append(errors["phone"], "Invalid phone number format. Use +62 followed by 9-13 digits")
}
if strings.TrimSpace(r.Email) != "" && !utils.IsValidEmail(r.Email) {
errors["email"] = append(errors["email"], "Invalid email format")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
type UpdatePasswordDTO struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
ConfirmNewPassword string `json:"confirm_new_password"`
}
func (u *UpdatePasswordDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if u.OldPassword == "" {
errors["old_password"] = append(errors["old_password"], "Old password is required")
}
if u.NewPassword == "" {
errors["new_password"] = append(errors["new_password"], "New password is required")
} else if !utils.IsValidPassword(u.NewPassword) {
errors["new_password"] = append(errors["new_password"], "Password must contain at least one uppercase letter, one digit, and one special character")
}
if u.ConfirmNewPassword == "" {
errors["confirm_new_password"] = append(errors["confirm_new_password"], "Confirm new password is required")
} else if u.NewPassword != u.ConfirmNewPassword {
errors["confirm_new_password"] = append(errors["confirm_new_password"], "Passwords do not match")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -1,66 +0,0 @@
package dto
import (
"fmt"
"regexp"
"strings"
)
type RequestUserPinDTO struct {
Pin string `json:"userpin"`
}
func (r *RequestUserPinDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(r.Pin) == "" {
errors["pin"] = append(errors["pin"], "Pin is required")
}
if err := validatePin(r.Pin); err != nil {
errors["pin"] = append(errors["pin"], err.Error())
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
type UpdateUserPinDTO struct {
OldPin string `json:"old_pin"`
NewPin string `json:"new_pin"`
}
func (u *UpdateUserPinDTO) Validate() (map[string][]string, bool) {
errors := make(map[string][]string)
if strings.TrimSpace(u.OldPin) == "" {
errors["old_pin"] = append(errors["old_pin"], "Old pin is required")
}
if err := validatePin(u.NewPin); err != nil {
errors["new_pin"] = append(errors["new_pin"], err.Error())
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}
func isNumeric(s string) bool {
re := regexp.MustCompile(`^[0-9]+$`)
return re.MatchString(s)
}
func validatePin(pin string) error {
if len(pin) != 6 {
return fmt.Errorf("pin harus terdiri dari 6 digit")
} else if !isNumeric(pin) {
return fmt.Errorf("pin harus berupa angka")
}
return nil
}

View File

@ -1,27 +0,0 @@
package dto
type ProvinceResponseDTO struct {
ID string `json:"id"`
Name string `json:"name"`
Regencies []RegencyResponseDTO `json:"regencies,omitempty"`
}
type RegencyResponseDTO struct {
ID string `json:"id"`
ProvinceID string `json:"province_id"`
Name string `json:"name"`
Districts []DistrictResponseDTO `json:"districts,omitempty"`
}
type DistrictResponseDTO struct {
ID string `json:"id"`
RegencyID string `json:"regency_id"`
Name string `json:"name"`
Villages []VillageResponseDTO `json:"villages,omitempty"`
}
type VillageResponseDTO struct {
ID string `json:"id"`
DistrictID string `json:"district_id"`
Name string `json:"name"`
}

View File

@ -3,7 +3,6 @@ package about
import (
"fmt"
"log"
"rijig/dto"
"rijig/utils"
"github.com/gofiber/fiber/v2"
@ -20,7 +19,7 @@ func NewAboutHandler(aboutService AboutService) *AboutHandler {
}
func (h *AboutHandler) CreateAbout(c *fiber.Ctx) error {
var request dto.RequestAboutDTO
var request RequestAboutDTO
if err := c.BodyParser(&request); err != nil {
return utils.ResponseErrorData(c, fiber.StatusBadRequest, "Invalid request body", map[string][]string{"body": {"Invalid body"}})
}
@ -47,7 +46,7 @@ func (h *AboutHandler) CreateAbout(c *fiber.Ctx) error {
func (h *AboutHandler) UpdateAbout(c *fiber.Ctx) error {
id := c.Params("id")
var request dto.RequestAboutDTO
var request RequestAboutDTO
if err := c.BodyParser(&request); err != nil {
log.Printf("Error parsing request body: %v", err)
return utils.BadRequest(c, "Invalid input data")
@ -114,7 +113,7 @@ func (h *AboutHandler) DeleteAbout(c *fiber.Ctx) error {
}
func (h *AboutHandler) CreateAboutDetail(c *fiber.Ctx) error {
var request dto.RequestAboutDetailDTO
var request RequestAboutDetailDTO
if err := c.BodyParser(&request); err != nil {
log.Printf("Error parsing request body: %v", err)
return utils.BadRequest(c, "Invalid input data")
@ -143,7 +142,7 @@ func (h *AboutHandler) CreateAboutDetail(c *fiber.Ctx) error {
func (h *AboutHandler) UpdateAboutDetail(c *fiber.Ctx) error {
id := c.Params("id")
var request dto.RequestAboutDetailDTO
var request RequestAboutDetailDTO
if err := c.BodyParser(&request); err != nil {
log.Printf("Error parsing request body: %v", err)
return utils.BadRequest(c, "Invalid input data")

View File

@ -7,7 +7,6 @@ import (
"mime/multipart"
"os"
"path/filepath"
"rijig/dto"
"rijig/model"
"rijig/utils"
"time"
@ -24,15 +23,15 @@ const (
)
type AboutService interface {
CreateAbout(ctx context.Context, request dto.RequestAboutDTO, coverImageAbout *multipart.FileHeader) (*dto.ResponseAboutDTO, error)
UpdateAbout(ctx context.Context, id string, request dto.RequestAboutDTO, coverImageAbout *multipart.FileHeader) (*dto.ResponseAboutDTO, error)
GetAllAbout(ctx context.Context) ([]dto.ResponseAboutDTO, error)
GetAboutByID(ctx context.Context, id string) (*dto.ResponseAboutDTO, error)
GetAboutDetailById(ctx context.Context, id string) (*dto.ResponseAboutDetailDTO, error)
CreateAbout(ctx context.Context, request RequestAboutDTO, coverImageAbout *multipart.FileHeader) (*ResponseAboutDTO, error)
UpdateAbout(ctx context.Context, id string, request RequestAboutDTO, coverImageAbout *multipart.FileHeader) (*ResponseAboutDTO, error)
GetAllAbout(ctx context.Context) ([]ResponseAboutDTO, error)
GetAboutByID(ctx context.Context, id string) (*ResponseAboutDTO, error)
GetAboutDetailById(ctx context.Context, id string) (*ResponseAboutDetailDTO, error)
DeleteAbout(ctx context.Context, id string) error
CreateAboutDetail(ctx context.Context, request dto.RequestAboutDetailDTO, coverImageAboutDetail *multipart.FileHeader) (*dto.ResponseAboutDetailDTO, error)
UpdateAboutDetail(ctx context.Context, id string, request dto.RequestAboutDetailDTO, imageDetail *multipart.FileHeader) (*dto.ResponseAboutDetailDTO, error)
CreateAboutDetail(ctx context.Context, request RequestAboutDetailDTO, coverImageAboutDetail *multipart.FileHeader) (*ResponseAboutDetailDTO, error)
UpdateAboutDetail(ctx context.Context, id string, request RequestAboutDetailDTO, imageDetail *multipart.FileHeader) (*ResponseAboutDetailDTO, error)
DeleteAboutDetail(ctx context.Context, id string) error
}
@ -66,11 +65,11 @@ func (s *aboutService) invalidateAboutDetailCaches(aboutDetailID, aboutID string
s.invalidateAboutCaches(aboutID)
}
func formatResponseAboutDetailDTO(about *model.AboutDetail) (*dto.ResponseAboutDetailDTO, error) {
func formatResponseAboutDetailDTO(about *model.AboutDetail) (*ResponseAboutDetailDTO, error) {
createdAt, _ := utils.FormatDateToIndonesianFormat(about.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(about.UpdatedAt)
response := &dto.ResponseAboutDetailDTO{
response := &ResponseAboutDetailDTO{
ID: about.ID,
AboutID: about.AboutID,
ImageDetail: about.ImageDetail,
@ -82,11 +81,11 @@ func formatResponseAboutDetailDTO(about *model.AboutDetail) (*dto.ResponseAboutD
return response, nil
}
func formatResponseAboutDTO(about *model.About) (*dto.ResponseAboutDTO, error) {
func formatResponseAboutDTO(about *model.About) (*ResponseAboutDTO, error) {
createdAt, _ := utils.FormatDateToIndonesianFormat(about.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(about.UpdatedAt)
response := &dto.ResponseAboutDTO{
response := &ResponseAboutDTO{
ID: about.ID,
Title: about.Title,
CoverImage: about.CoverImage,
@ -194,7 +193,7 @@ func deleteCoverImageAbout(coverimageAboutPath string) error {
return nil
}
func (s *aboutService) CreateAbout(ctx context.Context, request dto.RequestAboutDTO, coverImageAbout *multipart.FileHeader) (*dto.ResponseAboutDTO, error) {
func (s *aboutService) CreateAbout(ctx context.Context, request RequestAboutDTO, coverImageAbout *multipart.FileHeader) (*ResponseAboutDTO, error) {
errors, valid := request.ValidateAbout()
if !valid {
return nil, fmt.Errorf("validation error: %v", errors)
@ -224,7 +223,7 @@ func (s *aboutService) CreateAbout(ctx context.Context, request dto.RequestAbout
return response, nil
}
func (s *aboutService) UpdateAbout(ctx context.Context, id string, request dto.RequestAboutDTO, coverImageAbout *multipart.FileHeader) (*dto.ResponseAboutDTO, error) {
func (s *aboutService) UpdateAbout(ctx context.Context, id string, request RequestAboutDTO, coverImageAbout *multipart.FileHeader) (*ResponseAboutDTO, error) {
errors, valid := request.ValidateAbout()
if !valid {
return nil, fmt.Errorf("validation error: %v", errors)
@ -271,9 +270,9 @@ func (s *aboutService) UpdateAbout(ctx context.Context, id string, request dto.R
return response, nil
}
func (s *aboutService) GetAllAbout(ctx context.Context) ([]dto.ResponseAboutDTO, error) {
func (s *aboutService) GetAllAbout(ctx context.Context) ([]ResponseAboutDTO, error) {
var cachedAbouts []dto.ResponseAboutDTO
var cachedAbouts []ResponseAboutDTO
if err := utils.GetCache(cacheKeyAllAbout, &cachedAbouts); err == nil {
return cachedAbouts, nil
}
@ -283,7 +282,7 @@ func (s *aboutService) GetAllAbout(ctx context.Context) ([]dto.ResponseAboutDTO,
return nil, fmt.Errorf("failed to get About list: %v", err)
}
var aboutDTOList []dto.ResponseAboutDTO
var aboutDTOList []ResponseAboutDTO
for _, about := range aboutList {
response, err := formatResponseAboutDTO(&about)
if err != nil {
@ -300,10 +299,10 @@ func (s *aboutService) GetAllAbout(ctx context.Context) ([]dto.ResponseAboutDTO,
return aboutDTOList, nil
}
func (s *aboutService) GetAboutByID(ctx context.Context, id string) (*dto.ResponseAboutDTO, error) {
func (s *aboutService) GetAboutByID(ctx context.Context, id string) (*ResponseAboutDTO, error) {
cacheKey := fmt.Sprintf(cacheKeyAboutByID, id)
var cachedAbout dto.ResponseAboutDTO
var cachedAbout ResponseAboutDTO
if err := utils.GetCache(cacheKey, &cachedAbout); err == nil {
return &cachedAbout, nil
}
@ -318,7 +317,7 @@ func (s *aboutService) GetAboutByID(ctx context.Context, id string) (*dto.Respon
return nil, fmt.Errorf("error formatting About response: %v", err)
}
var responseDetails []dto.ResponseAboutDetailDTO
var responseDetails []ResponseAboutDetailDTO
for _, detail := range about.AboutDetail {
formattedDetail, err := formatResponseAboutDetailDTO(&detail)
if err != nil {
@ -336,10 +335,10 @@ func (s *aboutService) GetAboutByID(ctx context.Context, id string) (*dto.Respon
return response, nil
}
func (s *aboutService) GetAboutDetailById(ctx context.Context, id string) (*dto.ResponseAboutDetailDTO, error) {
func (s *aboutService) GetAboutDetailById(ctx context.Context, id string) (*ResponseAboutDetailDTO, error) {
cacheKey := fmt.Sprintf(cacheKeyAboutDetail, id)
var cachedDetail dto.ResponseAboutDetailDTO
var cachedDetail ResponseAboutDetailDTO
if err := utils.GetCache(cacheKey, &cachedDetail); err == nil {
return &cachedDetail, nil
}
@ -390,7 +389,7 @@ func (s *aboutService) DeleteAbout(ctx context.Context, id string) error {
return nil
}
func (s *aboutService) CreateAboutDetail(ctx context.Context, request dto.RequestAboutDetailDTO, coverImageAboutDetail *multipart.FileHeader) (*dto.ResponseAboutDetailDTO, error) {
func (s *aboutService) CreateAboutDetail(ctx context.Context, request RequestAboutDetailDTO, coverImageAboutDetail *multipart.FileHeader) (*ResponseAboutDetailDTO, error) {
errors, valid := request.ValidateAboutDetail()
if !valid {
return nil, fmt.Errorf("validation error: %v", errors)
@ -426,7 +425,7 @@ func (s *aboutService) CreateAboutDetail(ctx context.Context, request dto.Reques
return response, nil
}
func (s *aboutService) UpdateAboutDetail(ctx context.Context, id string, request dto.RequestAboutDetailDTO, imageDetail *multipart.FileHeader) (*dto.ResponseAboutDetailDTO, error) {
func (s *aboutService) UpdateAboutDetail(ctx context.Context, id string, request RequestAboutDetailDTO, imageDetail *multipart.FileHeader) (*ResponseAboutDetailDTO, error) {
errors, valid := request.ValidateAboutDetail()
if !valid {
return nil, fmt.Errorf("validation error: %v", errors)

View File

@ -1,7 +1,6 @@
package address
import (
"rijig/dto"
"rijig/middleware"
"rijig/utils"
@ -17,7 +16,7 @@ func NewAddressHandler(addressService AddressService) *AddressHandler {
}
func (h *AddressHandler) CreateAddress(c *fiber.Ctx) error {
var request dto.CreateAddressDTO
var request CreateAddressDTO
claims, err := middleware.GetUserFromContext(c)
if err != nil {
return err
@ -74,7 +73,7 @@ func (h *AddressHandler) UpdateAddress(c *fiber.Ctx) error {
addressID := c.Params("address_id")
var request dto.CreateAddressDTO
var request CreateAddressDTO
claims, err := middleware.GetUserFromContext(c)
if err != nil {
return err

View File

@ -6,7 +6,6 @@ import (
"fmt"
"time"
"rijig/dto"
"rijig/internal/wilayahindo"
"rijig/model"
"rijig/utils"
@ -20,10 +19,10 @@ const (
)
type AddressService interface {
CreateAddress(ctx context.Context, userID string, request dto.CreateAddressDTO) (*dto.AddressResponseDTO, error)
GetAddressByUserID(ctx context.Context, userID string) ([]dto.AddressResponseDTO, error)
GetAddressByID(ctx context.Context, userID, id string) (*dto.AddressResponseDTO, error)
UpdateAddress(ctx context.Context, userID, id string, addressDTO dto.CreateAddressDTO) (*dto.AddressResponseDTO, error)
CreateAddress(ctx context.Context, userID string, request CreateAddressDTO) (*AddressResponseDTO, error)
GetAddressByUserID(ctx context.Context, userID string) ([]AddressResponseDTO, error)
GetAddressByID(ctx context.Context, userID, id string) (*AddressResponseDTO, error)
UpdateAddress(ctx context.Context, userID, id string, addressDTO CreateAddressDTO) (*AddressResponseDTO, error)
DeleteAddress(ctx context.Context, userID, id string) error
}
@ -39,7 +38,7 @@ func NewAddressService(addressRepo AddressRepository, wilayahRepo wilayahindo.Wi
}
}
func (s *addressService) validateWilayahIDs(ctx context.Context, addressDTO dto.CreateAddressDTO) (string, string, string, string, error) {
func (s *addressService) validateWilayahIDs(ctx context.Context, addressDTO CreateAddressDTO) (string, string, string, string, error) {
province, _, err := s.wilayahRepo.FindProvinceByID(ctx, addressDTO.Province, 0, 0)
if err != nil {
@ -64,11 +63,11 @@ func (s *addressService) validateWilayahIDs(ctx context.Context, addressDTO dto.
return province.Name, regency.Name, district.Name, village.Name, nil
}
func (s *addressService) mapToResponseDTO(address *model.Address) *dto.AddressResponseDTO {
func (s *addressService) mapToResponseDTO(address *model.Address) *AddressResponseDTO {
createdAt, _ := utils.FormatDateToIndonesianFormat(address.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(address.UpdatedAt)
return &dto.AddressResponseDTO{
return &AddressResponseDTO{
UserID: address.UserID,
ID: address.ID,
Province: address.Province,
@ -98,20 +97,20 @@ func (s *addressService) invalidateAddressCaches(userID, addressID string) {
}
}
func (s *addressService) cacheAddress(addressDTO *dto.AddressResponseDTO) {
func (s *addressService) cacheAddress(addressDTO *AddressResponseDTO) {
cacheKey := fmt.Sprintf(addressCacheKeyPattern, addressDTO.ID)
if err := utils.SetCache(cacheKey, addressDTO, cacheTTL); err != nil {
fmt.Printf("Error caching address to Redis: %v\n", err)
}
}
func (s *addressService) cacheUserAddresses(ctx context.Context, userID string) ([]dto.AddressResponseDTO, error) {
func (s *addressService) cacheUserAddresses(ctx context.Context, userID string) ([]AddressResponseDTO, error) {
addresses, err := s.addressRepo.FindAddressByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("failed to fetch addresses: %w", err)
}
var addressDTOs []dto.AddressResponseDTO
var addressDTOs []AddressResponseDTO
for _, address := range addresses {
addressDTOs = append(addressDTOs, *s.mapToResponseDTO(&address))
}
@ -137,7 +136,7 @@ func (s *addressService) checkAddressOwnership(ctx context.Context, userID, addr
return address, nil
}
func (s *addressService) CreateAddress(ctx context.Context, userID string, addressDTO dto.CreateAddressDTO) (*dto.AddressResponseDTO, error) {
func (s *addressService) CreateAddress(ctx context.Context, userID string, addressDTO CreateAddressDTO) (*AddressResponseDTO, error) {
provinceName, regencyName, districtName, villageName, err := s.validateWilayahIDs(ctx, addressDTO)
if err != nil {
@ -168,10 +167,10 @@ func (s *addressService) CreateAddress(ctx context.Context, userID string, addre
return responseDTO, nil
}
func (s *addressService) GetAddressByUserID(ctx context.Context, userID string) ([]dto.AddressResponseDTO, error) {
func (s *addressService) GetAddressByUserID(ctx context.Context, userID string) ([]AddressResponseDTO, error) {
cacheKey := fmt.Sprintf(userAddressesCacheKeyPattern, userID)
var cachedAddresses []dto.AddressResponseDTO
var cachedAddresses []AddressResponseDTO
if err := utils.GetCache(cacheKey, &cachedAddresses); err == nil {
return cachedAddresses, nil
@ -180,7 +179,7 @@ func (s *addressService) GetAddressByUserID(ctx context.Context, userID string)
return s.cacheUserAddresses(ctx, userID)
}
func (s *addressService) GetAddressByID(ctx context.Context, userID, id string) (*dto.AddressResponseDTO, error) {
func (s *addressService) GetAddressByID(ctx context.Context, userID, id string) (*AddressResponseDTO, error) {
address, err := s.checkAddressOwnership(ctx, userID, id)
if err != nil {
@ -188,7 +187,7 @@ func (s *addressService) GetAddressByID(ctx context.Context, userID, id string)
}
cacheKey := fmt.Sprintf(addressCacheKeyPattern, id)
var cachedAddress dto.AddressResponseDTO
var cachedAddress AddressResponseDTO
if err := utils.GetCache(cacheKey, &cachedAddress); err == nil {
return &cachedAddress, nil
@ -200,7 +199,7 @@ func (s *addressService) GetAddressByID(ctx context.Context, userID, id string)
return responseDTO, nil
}
func (s *addressService) UpdateAddress(ctx context.Context, userID, id string, addressDTO dto.CreateAddressDTO) (*dto.AddressResponseDTO, error) {
func (s *addressService) UpdateAddress(ctx context.Context, userID, id string, addressDTO CreateAddressDTO) (*AddressResponseDTO, error) {
address, err := s.checkAddressOwnership(ctx, userID, id)
if err != nil {

View File

@ -2,6 +2,8 @@ package authentication
import (
"context"
"fmt"
"log"
"rijig/model"
"gorm.io/gorm"
@ -15,6 +17,9 @@ type AuthenticationRepository interface {
CreateUser(ctx context.Context, user *model.User) error
UpdateUser(ctx context.Context, user *model.User) error
PatchUser(ctx context.Context, userID string, updates map[string]interface{}) error
GetIdentityCardsByUserRegStatus(ctx context.Context, userRegStatus string) ([]model.IdentityCard, error)
GetCompanyProfilesByUserRegStatus(ctx context.Context, userRegStatus string) ([]model.CompanyProfile, error)
}
type authenticationRepository struct {
@ -84,3 +89,37 @@ func (r *authenticationRepository) PatchUser(ctx context.Context, userID string,
Where("id = ?", userID).
Updates(updates).Error
}
func (r *authenticationRepository) GetIdentityCardsByUserRegStatus(ctx context.Context, userRegStatus string) ([]model.IdentityCard, error) {
var identityCards []model.IdentityCard
if err := r.db.WithContext(ctx).
Joins("JOIN users ON identity_cards.user_id = users.id").
Where("users.registration_status = ?", userRegStatus).
Preload("User").
Preload("User.Role").
Find(&identityCards).Error; err != nil {
log.Printf("Error fetching identity cards by user registration status: %v", err)
return nil, fmt.Errorf("error fetching identity cards by user registration status: %w", err)
}
log.Printf("Found %d identity cards with registration status: %s", len(identityCards), userRegStatus)
return identityCards, nil
}
func (r *authenticationRepository) GetCompanyProfilesByUserRegStatus(ctx context.Context, userRegStatus string) ([]model.CompanyProfile, error) {
var companyProfiles []model.CompanyProfile
if err := r.db.WithContext(ctx).
Joins("JOIN users ON company_profiles.user_id = users.id").
Where("users.registration_status = ?", userRegStatus).
Preload("User").
Preload("User.Role").
Find(&companyProfiles).Error; err != nil {
log.Printf("Error fetching company profiles by user registration status: %v", err)
return nil, fmt.Errorf("error fetching company profiles by user registration status: %w", err)
}
log.Printf("Found %d company profiles with registration status: %s", len(companyProfiles), userRegStatus)
return companyProfiles, nil
}

View File

@ -315,7 +315,9 @@ func (s *authenticationService) VerifyLoginOTP(ctx context.Context, req *VerifyO
return nil, fmt.Errorf("kode OTP salah")
}
user, err := s.authRepo.FindUserByID(ctx, otpData.UserID)
normalizedRole := strings.ToLower(req.RoleName)
user, err := s.authRepo.FindUserByPhoneAndRole(ctx, req.Phone, normalizedRole)
if err != nil {
return nil, fmt.Errorf("user tidak ditemukan")
}
@ -324,24 +326,29 @@ func (s *authenticationService) VerifyLoginOTP(ctx context.Context, req *VerifyO
tokenResponse, err := utils.GenerateTokenPair(
user.ID,
user.Role.RoleName,
normalizedRole,
req.DeviceID,
"pin_verification_required",
user.RegistrationStatus,
int(user.RegistrationProgress),
)
if err != nil {
return nil, fmt.Errorf("gagal generate token: %v", err)
}
nextStep := utils.GetNextRegistrationStep(
normalizedRole,
int(user.RegistrationProgress),
user.RegistrationStatus,
)
return &AuthResponse{
Message: "OTP berhasil diverifikasi, silakan masukkan PIN",
Message: "otp berhasil diverifikasi",
AccessToken: tokenResponse.AccessToken,
RefreshToken: tokenResponse.RefreshToken,
TokenType: string(tokenResponse.TokenType),
ExpiresIn: tokenResponse.ExpiresIn,
User: convertUserToResponse(user),
RegistrationStatus: user.RegistrationStatus,
NextStep: "Masukkan PIN",
NextStep: nextStep,
SessionID: tokenResponse.SessionID,
}, nil
}

View File

@ -2,6 +2,7 @@ package company
import (
"rijig/config"
"rijig/internal/authentication"
"rijig/middleware"
"github.com/gofiber/fiber/v2"
@ -9,7 +10,8 @@ import (
func CompanyRouter(api fiber.Router) {
companyProfileRepo := NewCompanyProfileRepository(config.DB)
companyProfileService := NewCompanyProfileService(companyProfileRepo)
authRepo := authentication.NewAuthenticationRepository(config.DB)
companyProfileService := NewCompanyProfileService(companyProfileRepo, authRepo)
companyProfileHandler := NewCompanyProfileHandler(companyProfileService)
companyProfileAPI := api.Group("/companyprofile")

View File

@ -3,8 +3,13 @@ package company
import (
"context"
"fmt"
"log"
"rijig/internal/authentication"
"rijig/internal/role"
"rijig/internal/userprofile"
"rijig/model"
"rijig/utils"
"time"
)
type CompanyProfileService interface {
@ -13,15 +18,19 @@ type CompanyProfileService interface {
GetCompanyProfilesByUserID(ctx context.Context, userID string) ([]ResponseCompanyProfileDTO, error)
UpdateCompanyProfile(ctx context.Context, userID string, request *RequestCompanyProfileDTO) (*ResponseCompanyProfileDTO, error)
DeleteCompanyProfile(ctx context.Context, userID string) error
GetAllCompanyProfilesByRegStatus(ctx context.Context, userRegStatus string) ([]ResponseCompanyProfileDTO, error)
UpdateUserRegistrationStatusByCompany(ctx context.Context, companyUserID string, newStatus string) error
}
type companyProfileService struct {
companyRepo CompanyProfileRepository
authRepo authentication.AuthenticationRepository
}
func NewCompanyProfileService(companyRepo CompanyProfileRepository) CompanyProfileService {
func NewCompanyProfileService(companyRepo CompanyProfileRepository, authRepo authentication.AuthenticationRepository) CompanyProfileService {
return &companyProfileService{
companyRepo: companyRepo,
companyRepo, authRepo,
}
}
@ -48,9 +57,9 @@ func FormatResponseCompanyProfile(companyProfile *model.CompanyProfile) (*Respon
}
func (s *companyProfileService) CreateCompanyProfile(ctx context.Context, userID string, request *RequestCompanyProfileDTO) (*ResponseCompanyProfileDTO, error) {
if errors, valid := request.ValidateCompanyProfileInput(); !valid {
return nil, fmt.Errorf("validation failed: %v", errors)
}
// if errors, valid := request.ValidateCompanyProfileInput(); !valid {
// return nil, fmt.Errorf("validation failed: %v", errors)
// }
companyProfile := &model.CompanyProfile{
UserID: userID,
@ -134,3 +143,100 @@ func (s *companyProfileService) UpdateCompanyProfile(ctx context.Context, userID
func (s *companyProfileService) DeleteCompanyProfile(ctx context.Context, userID string) error {
return s.companyRepo.DeleteCompanyProfileByUserID(ctx, userID)
}
func (s *companyProfileService) GetAllCompanyProfilesByRegStatus(ctx context.Context, userRegStatus string) ([]ResponseCompanyProfileDTO, error) {
companyProfiles, err := s.authRepo.GetCompanyProfilesByUserRegStatus(ctx, userRegStatus)
if err != nil {
log.Printf("Error getting company profiles by registration status: %v", err)
return nil, fmt.Errorf("failed to get company profiles: %w", err)
}
var response []ResponseCompanyProfileDTO
for _, profile := range companyProfiles {
dto := ResponseCompanyProfileDTO{
ID: profile.ID,
UserID: profile.UserID,
CompanyName: profile.CompanyName,
CompanyAddress: profile.CompanyAddress,
CompanyPhone: profile.CompanyPhone,
CompanyEmail: profile.CompanyEmail,
CompanyLogo: profile.CompanyLogo,
CompanyWebsite: profile.CompanyWebsite,
TaxID: profile.TaxID,
FoundedDate: profile.FoundedDate,
CompanyType: profile.CompanyType,
CompanyDescription: profile.CompanyDescription,
CreatedAt: profile.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: profile.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
}
response = append(response, dto)
}
return response, nil
}
func (s *companyProfileService) UpdateUserRegistrationStatusByCompany(ctx context.Context, companyUserID string, newStatus string) error {
user, err := s.authRepo.FindUserByID(ctx, companyUserID)
if err != nil {
log.Printf("Error finding user by ID %s: %v", companyUserID, err)
return fmt.Errorf("user not found: %w", err)
}
updates := map[string]interface{}{
"registration_status": newStatus,
"updated_at": time.Now(),
}
switch newStatus {
case utils.RegStatusConfirmed:
updates["registration_progress"] = utils.ProgressDataSubmitted
case utils.RegStatusRejected:
updates["registration_progress"] = utils.ProgressOTPVerified
}
err = s.authRepo.PatchUser(ctx, user.ID, updates)
if err != nil {
log.Printf("Error updating user registration status for user ID %s: %v", user.ID, err)
return fmt.Errorf("failed to update user registration status: %w", err)
}
log.Printf("Successfully updated registration status for user ID %s to %s", user.ID, newStatus)
return nil
}
func (s *companyProfileService) GetUserProfile(ctx context.Context, userID string) (*userprofile.UserProfileResponseDTO, error) {
user, err := s.authRepo.FindUserByID(ctx, userID)
if err != nil {
log.Printf("Error getting user profile for ID %s: %v", userID, err)
return nil, fmt.Errorf("failed to get user profile: %w", err)
}
response := &userprofile.UserProfileResponseDTO{
ID: user.ID,
Name: user.Name,
Gender: user.Gender,
Dateofbirth: user.Dateofbirth,
Placeofbirth: user.Placeofbirth,
Phone: user.Phone,
Email: user.Email,
PhoneVerified: user.PhoneVerified,
CreatedAt: user.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: user.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
}
if user.Avatar != nil {
response.Avatar = *user.Avatar
}
if user.Role != nil {
response.Role = role.RoleResponseDTO{
ID: user.Role.ID,
RoleName: user.Role.RoleName,
CreatedAt: user.Role.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
UpdatedAt: user.Role.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
}
}
return response, nil
}

View File

@ -9,6 +9,7 @@ type ResponseIdentityCardDTO struct {
ID string `json:"id"`
UserID string `json:"userId"`
Identificationumber string `json:"identificationumber"`
Fullname string `json:"fullname"`
Placeofbirth string `json:"placeofbirth"`
Dateofbirth string `json:"dateofbirth"`
Gender string `json:"gender"`
@ -31,9 +32,10 @@ type ResponseIdentityCardDTO struct {
}
type RequestIdentityCardDTO struct {
DeviceID string `json:"device_id"`
// DeviceID string `json:"device_id"`
UserID string `json:"userId"`
Identificationumber string `json:"identificationumber"`
Fullname string `json:"fullname"`
Placeofbirth string `json:"placeofbirth"`
Dateofbirth string `json:"dateofbirth"`
Gender string `json:"gender"`

View File

@ -1,8 +1,10 @@
package identitycart
import (
"log"
"rijig/middleware"
"rijig/utils"
"strings"
"github.com/gofiber/fiber/v2"
)
@ -18,27 +20,33 @@ func NewIdentityCardHandler(service IdentityCardService) *IdentityCardHandler {
func (h *IdentityCardHandler) CreateIdentityCardHandler(c *fiber.Ctx) error {
claims, err := middleware.GetUserFromContext(c)
if err != nil {
return err
}
cardPhoto, err := c.FormFile("cardphoto")
if err != nil {
return utils.BadRequest(c, "KTP photo is required")
log.Printf("Error getting user from context: %v", err)
return utils.Unauthorized(c, "unauthorized access")
}
var input RequestIdentityCardDTO
if err := c.BodyParser(&input); err != nil {
log.Printf("Error parsing body: %v", err)
return utils.BadRequest(c, "Invalid input format")
}
if errs, valid := input.ValidateIdentityCardInput(); !valid {
return utils.ResponseErrorData(c, fiber.StatusBadRequest, "Input validation failed", errs)
}
response, err := h.service.CreateIdentityCard(c.Context(), claims.UserID, &input, cardPhoto)
cardPhoto, err := c.FormFile("cardphoto")
if err != nil {
return utils.InternalServerError(c, err.Error())
log.Printf("Error getting card photo: %v", err)
return utils.BadRequest(c, "KTP photo is required")
}
response, err := h.service.CreateIdentityCard(c.Context(), claims.UserID, claims.DeviceID, &input, cardPhoto)
if err != nil {
log.Printf("Error creating identity card: %v", err)
if strings.Contains(err.Error(), "invalid file type") {
return utils.BadRequest(c, err.Error())
}
return utils.InternalServerError(c, "Failed to create identity card")
}
return utils.SuccessWithData(c, "KTP successfully submitted", response)
@ -47,27 +55,158 @@ func (h *IdentityCardHandler) CreateIdentityCardHandler(c *fiber.Ctx) error {
func (h *IdentityCardHandler) GetIdentityByID(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return utils.BadRequest(c, "id is required")
return utils.BadRequest(c, "ID is required")
}
result, err := h.service.GetIdentityCardByID(c.Context(), id)
if err != nil {
return utils.NotFound(c, "data not found")
log.Printf("Error getting identity card by ID %s: %v", id, err)
return utils.NotFound(c, "Identity card not found")
}
return utils.SuccessWithData(c, "success retrieve identity card", result)
return utils.SuccessWithData(c, "Successfully retrieved identity card", result)
}
func (h *IdentityCardHandler) GetIdentityByUserId(c *fiber.Ctx) error {
claims, err := middleware.GetUserFromContext(c)
if err != nil {
return err
log.Printf("Error getting user from context: %v", err)
return utils.Unauthorized(c, "Unauthorized access")
}
result, err := h.service.GetIdentityCardsByUserID(c.Context(), claims.UserID)
if err != nil {
return utils.InternalServerError(c, "failed to fetch your identity card data")
log.Printf("Error getting identity cards for user %s: %v", claims.UserID, err)
return utils.InternalServerError(c, "Failed to fetch your identity card data")
}
return utils.SuccessWithData(c, "success retrieve your identity card", result)
return utils.SuccessWithData(c, "Successfully retrieved your identity cards", result)
}
func (h *IdentityCardHandler) UpdateIdentityCardHandler(c *fiber.Ctx) error {
claims, err := middleware.GetUserFromContext(c)
if err != nil {
log.Printf("Error getting user from context: %v", err)
return utils.Unauthorized(c, "Unauthorized access")
}
id := c.Params("id")
if id == "" {
return utils.BadRequest(c, "Identity card ID is required")
}
var input RequestIdentityCardDTO
if err := c.BodyParser(&input); err != nil {
log.Printf("Error parsing body: %v", err)
return utils.BadRequest(c, "Invalid input format")
}
if errs, valid := input.ValidateIdentityCardInput(); !valid {
return utils.ResponseErrorData(c, fiber.StatusBadRequest, "Input validation failed", errs)
}
cardPhoto, err := c.FormFile("cardphoto")
if err != nil && err.Error() != "there is no uploaded file associated with the given key" {
log.Printf("Error getting card photo: %v", err)
return utils.BadRequest(c, "Invalid card photo")
}
if cardPhoto != nil && cardPhoto.Size > 5*1024*1024 {
return utils.BadRequest(c, "File size must be less than 5MB")
}
response, err := h.service.UpdateIdentityCard(c.Context(), claims.UserID, id, &input, cardPhoto)
if err != nil {
log.Printf("Error updating identity card: %v", err)
if strings.Contains(err.Error(), "not found") {
return utils.NotFound(c, "Identity card not found")
}
if strings.Contains(err.Error(), "invalid file type") {
return utils.BadRequest(c, err.Error())
}
return utils.InternalServerError(c, "Failed to update identity card")
}
return utils.SuccessWithData(c, "Identity card successfully updated", response)
}
func (h *IdentityCardHandler) GetAllIdentityCardsByRegStatus(c *fiber.Ctx) error {
_, err := middleware.GetUserFromContext(c)
if err != nil {
log.Printf("Error getting user from context: %v", err)
return utils.Unauthorized(c, "Unauthorized access")
}
// if claims.Role != "admin" {
// return utils.Forbidden(c, "Access denied: admin role required")
// }
status := c.Query("status", utils.RegStatusPending)
validStatuses := map[string]bool{
utils.RegStatusPending: true,
"confirmed": true,
"rejected": true,
}
if !validStatuses[status] {
return utils.BadRequest(c, "Invalid status. Valid values: pending, confirmed, rejected")
}
result, err := h.service.GetAllIdentityCardsByRegStatus(c.Context(), status)
if err != nil {
log.Printf("Error getting identity cards by status %s: %v", status, err)
return utils.InternalServerError(c, "Failed to fetch identity cards")
}
return utils.SuccessWithData(c, "Successfully retrieved identity cards", result)
}
func (h *IdentityCardHandler) UpdateUserRegistrationStatusByIdentityCard(c *fiber.Ctx) error {
_, err := middleware.GetUserFromContext(c)
if err != nil {
log.Printf("Error getting user from context: %v", err)
return utils.Unauthorized(c, "Unauthorized access")
}
userID := c.Params("userId")
if userID == "" {
return utils.BadRequest(c, "User ID is required")
}
type StatusUpdateRequest struct {
Status string `json:"status" validate:"required,oneof=confirmed rejected"`
}
var input StatusUpdateRequest
if err := c.BodyParser(&input); err != nil {
log.Printf("Error parsing body: %v", err)
return utils.BadRequest(c, "Invalid input format")
}
if input.Status != "confirmed" && input.Status != "rejected" {
return utils.BadRequest(c, "Invalid status. Valid values: confirmed, rejected")
}
err = h.service.UpdateUserRegistrationStatusByIdentityCard(c.Context(), userID, input.Status)
if err != nil {
log.Printf("Error updating user registration status: %v", err)
if strings.Contains(err.Error(), "not found") {
return utils.NotFound(c, "User not found")
}
return utils.InternalServerError(c, "Failed to update registration status")
}
message := "User registration status successfully updated to " + input.Status
return utils.Success(c, message)
}
func (h *IdentityCardHandler) DeleteIdentityCardHandler(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return utils.BadRequest(c, "Identity card ID is required")
}
return utils.Success(c, "Identity card successfully deleted")
}

View File

@ -3,7 +3,9 @@ package identitycart
import (
"rijig/config"
"rijig/internal/authentication"
"rijig/internal/userprofile"
"rijig/middleware"
"rijig/utils"
"github.com/gofiber/fiber/v2"
)
@ -11,25 +13,34 @@ import (
func UserIdentityCardRoute(api fiber.Router) {
identityRepo := NewIdentityCardRepository(config.DB)
authRepo := authentication.NewAuthenticationRepository(config.DB)
identityService := NewIdentityCardService(identityRepo, authRepo)
userRepo := userprofile.NewUserProfileRepository(config.DB)
identityService := NewIdentityCardService(identityRepo, authRepo, userRepo)
identityHandler := NewIdentityCardHandler(identityService)
identity := api.Group("/identity")
identity.Post("/create",
middleware.AuthMiddleware(),
middleware.RequireRoles("pengelola", "pengepul"),
middleware.RequireRoles(utils.RolePengepul),
identityHandler.CreateIdentityCardHandler,
)
identity.Get("/:id",
middleware.AuthMiddleware(),
middleware.RequireRoles("pengelola", "pengepul"),
identityHandler.GetIdentityByID,
)
identity.Get("/s",
middleware.AuthMiddleware(),
identityHandler.GetIdentityByUserId,
)
identity.Get("/",
middleware.AuthMiddleware(),
middleware.RequireRoles("pengelola", "pengepul"),
identityHandler.GetIdentityByUserId,
middleware.RequireRoles(utils.RoleAdministrator),
identityHandler.GetAllIdentityCardsByRegStatus,
)
identity.Patch("/:userId/status",
middleware.AuthMiddleware(),
middleware.RequireRoles(utils.RoleAdministrator),
identityHandler.UpdateUserRegistrationStatusByIdentityCard,
)
}

View File

@ -2,36 +2,49 @@ package identitycart
import (
"context"
"errors"
"fmt"
"io"
"log"
"mime/multipart"
"os"
"path/filepath"
"rijig/internal/authentication"
"rijig/internal/role"
"rijig/internal/userprofile"
"rijig/model"
"rijig/utils"
"strings"
"time"
)
type IdentityCardService interface {
CreateIdentityCard(ctx context.Context, userID string, request *RequestIdentityCardDTO, cardPhoto *multipart.FileHeader) (*authentication.AuthResponse, error)
CreateIdentityCard(ctx context.Context, userID, deviceID string, request *RequestIdentityCardDTO, cardPhoto *multipart.FileHeader) (*authentication.AuthResponse, error)
GetIdentityCardByID(ctx context.Context, id string) (*ResponseIdentityCardDTO, error)
GetIdentityCardsByUserID(ctx context.Context, userID string) ([]ResponseIdentityCardDTO, error)
UpdateIdentityCard(ctx context.Context, userID string, id string, request *RequestIdentityCardDTO, cardPhoto *multipart.FileHeader) (*ResponseIdentityCardDTO, error)
GetAllIdentityCardsByRegStatus(ctx context.Context, userRegStatus string) ([]ResponseIdentityCardDTO, error)
UpdateUserRegistrationStatusByIdentityCard(ctx context.Context, identityCardUserID string, newStatus string) error
}
type identityCardService struct {
identityRepo IdentityCardRepository
authRepo authentication.AuthenticationRepository
userRepo userprofile.UserProfileRepository
}
func NewIdentityCardService(identityRepo IdentityCardRepository, authRepo authentication.AuthenticationRepository) IdentityCardService {
func NewIdentityCardService(identityRepo IdentityCardRepository, authRepo authentication.AuthenticationRepository, userRepo userprofile.UserProfileRepository) IdentityCardService {
return &identityCardService{
identityRepo: identityRepo,
authRepo: authRepo,
identityRepo,
authRepo, userRepo,
}
}
type IdentityCardWithUserDTO struct {
IdentityCard ResponseIdentityCardDTO `json:"identity_card"`
User userprofile.UserProfileResponseDTO `json:"user"`
}
func FormatResponseIdentityCard(identityCard *model.IdentityCard) (*ResponseIdentityCardDTO, error) {
createdAt, _ := utils.FormatDateToIndonesianFormat(identityCard.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(identityCard.UpdatedAt)
@ -93,7 +106,7 @@ func (s *identityCardService) saveIdentityCardImage(userID string, cardPhoto *mu
}
defer dst.Close()
if _, err := dst.ReadFrom(src); err != nil {
if _, err := io.Copy(dst, src); err != nil {
return "", fmt.Errorf("failed to save card photo: %v", err)
}
@ -123,16 +136,7 @@ func deleteIdentityCardImage(imagePath string) error {
return nil
}
func (s *identityCardService) CreateIdentityCard(ctx context.Context, userID string, request *RequestIdentityCardDTO, cardPhoto *multipart.FileHeader) (*authentication.AuthResponse, error) {
// Validate essential parameters
if userID == "" {
return nil, fmt.Errorf("userID cannot be empty")
}
if request.DeviceID == "" {
return nil, fmt.Errorf("deviceID cannot be empty")
}
func (s *identityCardService) CreateIdentityCard(ctx context.Context, userID, deviceID string, request *RequestIdentityCardDTO, cardPhoto *multipart.FileHeader) (*authentication.AuthResponse, error) {
cardPhotoPath, err := s.saveIdentityCardImage(userID, cardPhoto)
if err != nil {
@ -172,33 +176,13 @@ func (s *identityCardService) CreateIdentityCard(ctx context.Context, userID str
return nil, fmt.Errorf("failed to find user: %v", err)
}
// Validate user data
if user.Role.RoleName == "" {
return nil, fmt.Errorf("user role not found")
}
roleName := strings.ToLower(user.Role.RoleName)
// Determine new registration status and progress
var newRegistrationStatus string
var newRegistrationProgress int
switch roleName {
case "pengepul":
newRegistrationProgress = 2
newRegistrationStatus = utils.RegStatusPending
case "pengelola":
newRegistrationProgress = 2
newRegistrationStatus = user.RegistrationStatus
default:
newRegistrationProgress = int(user.RegistrationProgress)
newRegistrationStatus = user.RegistrationStatus
}
// Update user registration progress and status
updates := map[string]interface{}{
"registration_progress": newRegistrationProgress,
"registration_status": newRegistrationStatus,
"registration_progress": utils.ProgressDataSubmitted,
"registration_status": utils.RegStatusPending,
}
err = s.authRepo.PatchUser(ctx, userID, updates)
@ -206,34 +190,46 @@ func (s *identityCardService) CreateIdentityCard(ctx context.Context, userID str
return nil, fmt.Errorf("failed to update user: %v", err)
}
// Debug logging before token generation
updated, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
if errors.Is(err, userprofile.ErrUserNotFound) {
return nil, fmt.Errorf("user not found")
}
return nil, fmt.Errorf("failed to get updated user: %w", err)
}
log.Printf("Token Generation Parameters:")
log.Printf("- UserID: '%s'", user.ID)
log.Printf("- Role: '%s'", user.Role.RoleName)
log.Printf("- DeviceID: '%s'", request.DeviceID)
log.Printf("- Registration Status: '%s'", newRegistrationStatus)
log.Printf("- DeviceID: '%s'", deviceID)
log.Printf("- Registration Status: '%s'", utils.RegStatusPending)
// Generate token pair with updated status
tokenResponse, err := utils.GenerateTokenPair(
user.ID,
user.Role.RoleName,
request.DeviceID,
newRegistrationStatus,
newRegistrationProgress,
updated.ID,
updated.Role.RoleName,
deviceID,
updated.RegistrationStatus,
int(updated.RegistrationProgress),
)
if err != nil {
log.Printf("GenerateTokenPair error: %v", err)
return nil, fmt.Errorf("failed to generate token: %v", err)
}
nextStep := utils.GetNextRegistrationStep(
updated.Role.RoleName,
int(updated.RegistrationProgress),
updated.RegistrationStatus,
)
return &authentication.AuthResponse{
Message: "identity card berhasil diunggah, silakan tunggu konfirmasi dari admin dalam 1x24 jam",
AccessToken: tokenResponse.AccessToken,
RefreshToken: tokenResponse.RefreshToken,
TokenType: string(tokenResponse.TokenType),
ExpiresIn: tokenResponse.ExpiresIn,
RegistrationStatus: newRegistrationStatus,
NextStep: tokenResponse.NextStep,
RegistrationStatus: updated.RegistrationStatus,
NextStep: nextStep,
SessionID: tokenResponse.SessionID,
}, nil
}
@ -319,3 +315,164 @@ func (s *identityCardService) UpdateIdentityCard(ctx context.Context, userID str
return idcardResponseDTO, nil
}
func (s *identityCardService) GetAllIdentityCardsByRegStatus(ctx context.Context, userRegStatus string) ([]ResponseIdentityCardDTO, error) {
identityCards, err := s.authRepo.GetIdentityCardsByUserRegStatus(ctx, userRegStatus)
if err != nil {
log.Printf("Error getting identity cards by registration status: %v", err)
return nil, fmt.Errorf("failed to get identity cards: %w", err)
}
var response []ResponseIdentityCardDTO
for _, card := range identityCards {
createdAt, _ := utils.FormatDateToIndonesianFormat(card.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(card.UpdatedAt)
dto := ResponseIdentityCardDTO{
ID: card.ID,
UserID: card.UserID,
Identificationumber: card.Identificationumber,
Placeofbirth: card.Placeofbirth,
Dateofbirth: card.Dateofbirth,
Gender: card.Gender,
BloodType: card.BloodType,
Province: card.Province,
District: card.District,
SubDistrict: card.SubDistrict,
Hamlet: card.Hamlet,
Village: card.Village,
Neighbourhood: card.Neighbourhood,
PostalCode: card.PostalCode,
Religion: card.Religion,
Maritalstatus: card.Maritalstatus,
Job: card.Job,
Citizenship: card.Citizenship,
Validuntil: card.Validuntil,
Cardphoto: card.Cardphoto,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
response = append(response, dto)
}
return response, nil
}
func (s *identityCardService) UpdateUserRegistrationStatusByIdentityCard(ctx context.Context, identityCardUserID string, newStatus string) error {
user, err := s.authRepo.FindUserByID(ctx, identityCardUserID)
if err != nil {
log.Printf("Error finding user by ID %s: %v", identityCardUserID, err)
return fmt.Errorf("user not found: %w", err)
}
updates := map[string]interface{}{
"registration_status": newStatus,
"updated_at": time.Now(),
}
switch newStatus {
case utils.RegStatusConfirmed:
updates["registration_progress"] = utils.ProgressDataSubmitted
identityCards, err := s.GetIdentityCardsByUserID(ctx, identityCardUserID)
if err != nil {
log.Printf("Error fetching identity cards for user ID %s: %v", identityCardUserID, err)
return fmt.Errorf("failed to fetch identity card data: %w", err)
}
if len(identityCards) == 0 {
log.Printf("No identity card found for user ID %s", identityCardUserID)
return fmt.Errorf("no identity card found for user")
}
identityCard := identityCards[0]
updates["name"] = identityCard.Fullname
updates["gender"] = identityCard.Gender
updates["dateofbirth"] = identityCard.Dateofbirth
updates["placeofbirth"] = identityCard.District
log.Printf("Syncing user data for ID %s: name=%s, gender=%s, dob=%s, pob=%s",
identityCardUserID, identityCard.Fullname, identityCard.Gender,
identityCard.Dateofbirth, identityCard.District)
case utils.RegStatusRejected:
updates["registration_progress"] = utils.ProgressOTPVerified
}
err = s.authRepo.PatchUser(ctx, user.ID, updates)
if err != nil {
log.Printf("Error updating user registration status for user ID %s: %v", user.ID, err)
return fmt.Errorf("failed to update user registration status: %w", err)
}
log.Printf("Successfully updated registration status for user ID %s to %s", user.ID, newStatus)
if newStatus == utils.RegStatusConfirmed {
log.Printf("User profile data synced successfully for user ID %s", user.ID)
}
return nil
}
func (s *identityCardService) mapIdentityCardToDTO(card model.IdentityCard) ResponseIdentityCardDTO {
createdAt, _ := utils.FormatDateToIndonesianFormat(card.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(card.UpdatedAt)
return ResponseIdentityCardDTO{
ID: card.ID,
UserID: card.UserID,
Identificationumber: card.Identificationumber,
Placeofbirth: card.Placeofbirth,
Dateofbirth: card.Dateofbirth,
Gender: card.Gender,
BloodType: card.BloodType,
Province: card.Province,
District: card.District,
SubDistrict: card.SubDistrict,
Hamlet: card.Hamlet,
Village: card.Village,
Neighbourhood: card.Neighbourhood,
PostalCode: card.PostalCode,
Religion: card.Religion,
Maritalstatus: card.Maritalstatus,
Job: card.Job,
Citizenship: card.Citizenship,
Validuntil: card.Validuntil,
Cardphoto: card.Cardphoto,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
}
func (s *identityCardService) mapUserToDTO(user model.User) userprofile.UserProfileResponseDTO {
avatar := ""
if user.Avatar != nil {
avatar = *user.Avatar
}
var roleDTO role.RoleResponseDTO
if user.Role != nil {
roleDTO = role.RoleResponseDTO{
ID: user.Role.ID,
RoleName: user.Role.RoleName,
}
}
createdAt, _ := utils.FormatDateToIndonesianFormat(user.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(user.UpdatedAt)
return userprofile.UserProfileResponseDTO{
ID: user.ID,
Avatar: avatar,
Name: user.Name,
Gender: user.Gender,
Dateofbirth: user.Dateofbirth,
Placeofbirth: user.Placeofbirth,
Phone: user.Phone,
Email: user.Email,
PhoneVerified: user.PhoneVerified,
Role: roleDTO,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
}

View File

@ -6,7 +6,7 @@ import (
)
type RequestPinDTO struct {
DeviceId string `json:"device_id"`
// DeviceId string `json:"device_id"`
Pin string `json:"userpin"`
}

View File

@ -15,47 +15,39 @@ func NewUserPinHandler(service UserPinService) *UserPinHandler {
return &UserPinHandler{service}
}
// userID, ok := c.Locals("user_id").(string)
//
// if !ok || userID == "" {
// return utils.Unauthorized(c, "user_id is missing or invalid")
// }
func (h *UserPinHandler) CreateUserPinHandler(c *fiber.Ctx) error {
// Ambil klaim pengguna yang sudah diautentikasi
claims, err := middleware.GetUserFromContext(c)
if err != nil {
return err
return utils.Unauthorized(c, "Authentication required")
}
if claims.UserID == "" || claims.DeviceID == "" {
return utils.BadRequest(c, "Invalid user claims")
}
// Parsing body request untuk PIN
var req RequestPinDTO
if err := c.BodyParser(&req); err != nil {
return utils.BadRequest(c, "Invalid request body")
}
// Validasi request PIN
if errs, ok := req.ValidateRequestPinDTO(); !ok {
return utils.ResponseErrorData(c, fiber.StatusBadRequest, "Validation error", errs)
}
// Panggil service untuk membuat PIN
err = h.service.CreateUserPin(c.Context(), claims.UserID, &req)
pintokenresponse, err := h.service.CreateUserPin(c.Context(), claims.UserID, claims.DeviceID, &req)
if err != nil {
if err.Error() == "PIN already created" {
return utils.BadRequest(c, err.Error()) // Jika PIN sudah ada, kembalikan error 400
if err.Error() == Pinhasbeencreated {
return utils.BadRequest(c, err.Error())
}
return utils.InternalServerError(c, err.Error()) // Jika terjadi error lain, internal server error
return utils.InternalServerError(c, err.Error())
}
// Mengembalikan response sukses jika berhasil
return utils.Success(c, "PIN created successfully")
return utils.SuccessWithData(c, "PIN created successfully", pintokenresponse)
}
func (h *UserPinHandler) VerifyPinHandler(c *fiber.Ctx) error {
// userID, ok := c.Locals("user_id").(string)
// if !ok || userID == "" {
// return utils.Unauthorized(c, "user_id is missing or invalid")
// }
claims, err := middleware.GetUserFromContext(c)
if err != nil {
return err
@ -66,12 +58,10 @@ func (h *UserPinHandler) VerifyPinHandler(c *fiber.Ctx) error {
return utils.BadRequest(c, "Invalid request body")
}
token, err := h.service.VerifyUserPin(c.Context(), claims.UserID, &req)
token, err := h.service.VerifyUserPin(c.Context(), claims.UserID, claims.DeviceID, &req)
if err != nil {
return utils.BadRequest(c, err.Error())
}
return utils.SuccessWithData(c, "PIN verified successfully", fiber.Map{
"token": token,
})
return utils.SuccessWithData(c, "PIN verified successfully", token)
}

View File

@ -3,6 +3,7 @@ package userpin
import (
"rijig/config"
"rijig/internal/authentication"
"rijig/internal/userprofile"
"rijig/middleware"
"github.com/gofiber/fiber/v2"
@ -11,8 +12,9 @@ import (
func UsersPinRoute(api fiber.Router) {
userPinRepo := NewUserPinRepository(config.DB)
authRepo := authentication.NewAuthenticationRepository(config.DB)
userprofileRepo := userprofile.NewUserProfileRepository(config.DB)
userPinService := NewUserPinService(userPinRepo, authRepo)
userPinService := NewUserPinService(userPinRepo, authRepo, userprofileRepo)
userPinHandler := NewUserPinHandler(userPinService)

View File

@ -2,45 +2,51 @@ package userpin
import (
"context"
"errors"
"fmt"
"rijig/internal/authentication"
"rijig/internal/userprofile"
"rijig/model"
"rijig/utils"
"strings"
"gorm.io/gorm"
)
type UserPinService interface {
CreateUserPin(ctx context.Context, userID string, dto *RequestPinDTO) error
VerifyUserPin(ctx context.Context, userID string, pin *RequestPinDTO) (*utils.TokenResponse, error)
CreateUserPin(ctx context.Context, userID, deviceId string, dto *RequestPinDTO) (*authentication.AuthResponse, error)
VerifyUserPin(ctx context.Context, userID, deviceID string, pin *RequestPinDTO) (*utils.TokenResponse, error)
}
type userPinService struct {
UserPinRepo UserPinRepository
authRepo authentication.AuthenticationRepository
UserPinRepo UserPinRepository
authRepo authentication.AuthenticationRepository
userProfileRepo userprofile.UserProfileRepository
}
func NewUserPinService(UserPinRepo UserPinRepository,
authRepo authentication.AuthenticationRepository) UserPinService {
return &userPinService{UserPinRepo, authRepo}
authRepo authentication.AuthenticationRepository,
userProfileRepo userprofile.UserProfileRepository) UserPinService {
return &userPinService{UserPinRepo, authRepo, userProfileRepo}
}
func (s *userPinService) CreateUserPin(ctx context.Context, userID string, dto *RequestPinDTO) error {
var (
Pinhasbeencreated = "PIN already created"
)
if errs, ok := dto.ValidateRequestPinDTO(); !ok {
return fmt.Errorf("validation error: %v", errs)
}
func (s *userPinService) CreateUserPin(ctx context.Context, userID, deviceId string, dto *RequestPinDTO) (*authentication.AuthResponse, error) {
existingPin, err := s.UserPinRepo.FindByUserID(ctx, userID)
_, err := s.UserPinRepo.FindByUserID(ctx, userID)
if err != nil {
return fmt.Errorf("failed to check existing PIN: %w", err)
}
if existingPin != nil {
return fmt.Errorf("PIN already created")
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("user not found")
}
return nil, fmt.Errorf("%v", Pinhasbeencreated)
}
hashed, err := utils.HashingPlainText(dto.Pin)
if err != nil {
return fmt.Errorf("failed to hash PIN: %w", err)
return nil, fmt.Errorf("failed to hash PIN: %w", err)
}
userPin := &model.UserPin{
@ -49,35 +55,63 @@ func (s *userPinService) CreateUserPin(ctx context.Context, userID string, dto *
}
if err := s.UserPinRepo.Create(ctx, userPin); err != nil {
return fmt.Errorf("failed to create PIN: %w", err)
}
user, err := s.authRepo.FindUserByID(ctx, userID)
if err != nil {
return fmt.Errorf("user not found")
}
roleName := strings.ToLower(user.Role.RoleName)
progress := authentication.IsRegistrationComplete(roleName, int(user.RegistrationProgress))
// progress := utils.GetNextRegistrationStep(roleName, int(user.RegistrationProgress))
// progress := utils.GetNextRegistrationStep(roleName, user.RegistrationProgress)
// progress := utils.GetNextRegistrationStep(roleName, user.RegistrationProgress)
if !progress {
err = s.authRepo.PatchUser(ctx, userID, map[string]interface{}{
"registration_progress": int(user.RegistrationProgress) + 1,
"registration_status": utils.RegStatusComplete,
})
if err != nil {
return fmt.Errorf("failed to update user progress: %w", err)
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("user not found")
}
return nil, fmt.Errorf("failed to create pin: %w", err)
}
return nil
updates := map[string]interface{}{
"registration_progress": utils.ProgressComplete,
"registration_status": utils.RegStatusComplete,
}
if err = s.authRepo.PatchUser(ctx, userID, updates); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("user not found")
}
return nil, fmt.Errorf("failed to update user profile: %w", err)
}
updated, err := s.userProfileRepo.GetByID(ctx, userID)
if err != nil {
if errors.Is(err, userprofile.ErrUserNotFound) {
return nil, fmt.Errorf("user not found")
}
return nil, fmt.Errorf("failed to get updated user: %w", err)
}
tokenResponse, err := utils.GenerateTokenPair(
updated.ID,
updated.Role.RoleName,
deviceId,
updated.RegistrationStatus,
int(updated.RegistrationProgress),
)
if err != nil {
return nil, fmt.Errorf("gagal generate token: %v", err)
}
nextStep := utils.GetNextRegistrationStep(
updated.Role.RoleName,
int(updated.RegistrationProgress),
updated.RegistrationStatus,
)
return &authentication.AuthResponse{
Message: "Isi data diri berhasil",
AccessToken: tokenResponse.AccessToken,
RefreshToken: tokenResponse.RefreshToken,
TokenType: string(tokenResponse.TokenType),
ExpiresIn: tokenResponse.ExpiresIn,
RegistrationStatus: updated.RegistrationStatus,
NextStep: nextStep,
SessionID: tokenResponse.SessionID,
}, nil
}
func (s *userPinService) VerifyUserPin(ctx context.Context, userID string, pin *RequestPinDTO) (*utils.TokenResponse, error) {
func (s *userPinService) VerifyUserPin(ctx context.Context, userID, deviceID string, pin *RequestPinDTO) (*utils.TokenResponse, error) {
user, err := s.authRepo.FindUserByID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("user not found")
@ -93,5 +127,5 @@ func (s *userPinService) VerifyUserPin(ctx context.Context, userID string, pin *
}
roleName := strings.ToLower(user.Role.RoleName)
return utils.GenerateTokenPair(user.ID, roleName, pin.DeviceId, user.RegistrationStatus, int(user.RegistrationProgress))
return utils.GenerateTokenPair(user.ID, roleName, deviceID, user.RegistrationStatus, int(user.RegistrationProgress))
}

View File

@ -1 +1,76 @@
package userprofile
package userprofile
import (
"context"
"log"
"rijig/middleware"
"rijig/utils"
"strings"
"time"
"github.com/gofiber/fiber/v2"
)
type UserProfileHandler struct {
service UserProfileService
}
func NewUserProfileHandler(service UserProfileService) *UserProfileHandler {
return &UserProfileHandler{
service: service,
}
}
func (h *UserProfileHandler) GetUserProfile(c *fiber.Ctx) error {
claims, err := middleware.GetUserFromContext(c)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
userProfile, err := h.service.GetUserProfile(ctx, claims.UserID)
if err != nil {
if strings.Contains(err.Error(), ErrUserNotFound.Error()) {
return utils.NotFound(c, "User profile not found")
}
log.Printf("Error getting user profile: %v", err)
return utils.InternalServerError(c, "Failed to retrieve user profile")
}
return utils.SuccessWithData(c, "User profile retrieved successfully", userProfile)
}
func (h *UserProfileHandler) UpdateUserProfile(c *fiber.Ctx) error {
claims, err := middleware.GetUserFromContext(c)
if err != nil {
return err
}
var req RequestUserProfileDTO
if err := c.BodyParser(&req); err != nil {
return utils.BadRequest(c, "Invalid request format")
}
if validationErrors, isValid := req.ValidateRequestUserProfileDTO(); !isValid {
return utils.ResponseErrorData(c, fiber.StatusBadRequest, "Validation failed", validationErrors)
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
updatedProfile, err := h.service.UpdateRegistUserProfile(ctx, claims.UserID, claims.DeviceID, &req)
if err != nil {
if strings.Contains(err.Error(), "user not found") {
return utils.NotFound(c, "User not found")
}
log.Printf("Error updating user profile: %v", err)
return utils.InternalServerError(c, "Failed to update user profile")
}
return utils.SuccessWithData(c, "User profile updated successfully", updatedProfile)
}

View File

@ -2,34 +2,106 @@ package userprofile
import (
"context"
"errors"
"rijig/model"
"gorm.io/gorm"
)
type AuthenticationRepository interface {
UpdateUser(ctx context.Context, user *model.User) error
PatchUser(ctx context.Context, userID string, updates map[string]interface{}) error
type UserProfileRepository interface {
GetByID(ctx context.Context, userID string) (*model.User, error)
GetByRoleName(ctx context.Context, roleName string) ([]*model.User, error)
// GetIdentityCardsByUserRegStatus(ctx context.Context, userRegStatus string) ([]model.IdentityCard, error)
// GetCompanyProfileByUserRegStatus(ctx context.Context, userRegStatus string) ([]model.IdentityCard, error)
Update(ctx context.Context, userID string, user *model.User) error
}
type authenticationRepository struct {
type userProfileRepository struct {
db *gorm.DB
}
func NewAuthenticationRepository(db *gorm.DB) AuthenticationRepository {
return &authenticationRepository{db}
func NewUserProfileRepository(db *gorm.DB) UserProfileRepository {
return &userProfileRepository{
db: db,
}
}
func (r *authenticationRepository) UpdateUser(ctx context.Context, user *model.User) error {
return r.db.WithContext(ctx).
Model(&model.User{}).
Where("id = ?", user.ID).
Updates(user).Error
func (r *userProfileRepository) GetByID(ctx context.Context, userID string) (*model.User, error) {
var user model.User
err := r.db.WithContext(ctx).
Preload("Role").
Where("id = ?", userID).
First(&user).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrUserNotFound
}
return nil, err
}
return &user, nil
}
func (r *authenticationRepository) PatchUser(ctx context.Context, userID string, updates map[string]interface{}) error {
return r.db.WithContext(ctx).
func (r *userProfileRepository) GetByRoleName(ctx context.Context, roleName string) ([]*model.User, error) {
var users []*model.User
err := r.db.WithContext(ctx).
Preload("Role").
Joins("JOIN roles ON users.role_id = roles.id").
Where("roles.role_name = ?", roleName).
Find(&users).Error
if err != nil {
return nil, err
}
return users, nil
}
/* func (r *userProfileRepository) GetIdentityCardsByUserRegStatus(ctx context.Context, userRegStatus string) ([]model.IdentityCard, error) {
var identityCards []model.IdentityCard
if err := r.db.WithContext(ctx).
Joins("JOIN users ON identity_cards.user_id = users.id").
Where("users.registration_status = ?", userRegStatus).
Preload("User").
Find(&identityCards).Error; err != nil {
log.Printf("Error fetching identity cards by user registration status: %v", err)
return nil, fmt.Errorf("error fetching identity cards by user registration status: %w", err)
}
return identityCards, nil
}
func (r *userProfileRepository) GetCompanyProfileByUserRegStatus(ctx context.Context, userRegStatus string) ([]model.IdentityCard, error) {
var identityCards []model.IdentityCard
if err := r.db.WithContext(ctx).
Joins("JOIN users ON company_profiles.user_id = users.id").
Where("users.registration_status = ?", userRegStatus).
Preload("User").
Find(&identityCards).Error; err != nil {
log.Printf("Error fetching identity cards by user registration status: %v", err)
return nil, fmt.Errorf("error fetching identity cards by user registration status: %w", err)
}
return identityCards, nil
} */
func (r *userProfileRepository) Update(ctx context.Context, userID string, user *model.User) error {
result := r.db.WithContext(ctx).
Model(&model.User{}).
Where("id = ?", userID).
Updates(updates).Error
Updates(user)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return ErrUserNotFound
}
return nil
}

View File

@ -1 +1,20 @@
package userprofile
package userprofile
import (
"rijig/config"
"rijig/middleware"
"github.com/gofiber/fiber/v2"
)
func UserProfileRouter(api fiber.Router) {
userProfileRepo := NewUserProfileRepository(config.DB)
userProfileService := NewUserProfileService(userProfileRepo)
userProfileHandler := NewUserProfileHandler(userProfileService)
userRoute := api.Group("/userprofile")
userRoute.Use(middleware.AuthMiddleware())
userRoute.Get("/", userProfileHandler.GetUserProfile)
userRoute.Put("/update", userProfileHandler.UpdateUserProfile)
}

View File

@ -1 +1,160 @@
package userprofile
package userprofile
import (
"context"
"errors"
"fmt"
"rijig/internal/authentication"
"rijig/internal/role"
"rijig/model"
"rijig/utils"
"time"
)
var (
ErrUserNotFound = errors.New("user tidak ditemukan")
)
type UserProfileService interface {
GetUserProfile(ctx context.Context, userID string) (*UserProfileResponseDTO, error)
UpdateRegistUserProfile(ctx context.Context, userID, deviceId string, req *RequestUserProfileDTO) (*authentication.AuthResponse, error)
}
type userProfileService struct {
repo UserProfileRepository
}
func NewUserProfileService(repo UserProfileRepository) UserProfileService {
return &userProfileService{
repo: repo,
}
}
func (s *userProfileService) GetUserProfile(ctx context.Context, userID string) (*UserProfileResponseDTO, error) {
user, err := s.repo.GetByID(ctx, userID)
if err != nil {
if errors.Is(err, ErrUserNotFound) {
return nil, fmt.Errorf("user not found")
}
return nil, fmt.Errorf("failed to get user profile: %w", err)
}
return s.mapToResponseDTO(user), nil
}
func (s *userProfileService) UpdateRegistUserProfile(ctx context.Context, userID, deviceId string, req *RequestUserProfileDTO) (*authentication.AuthResponse, error) {
_, err := s.repo.GetByID(ctx, userID)
if err != nil {
if errors.Is(err, ErrUserNotFound) {
return nil, fmt.Errorf("user not found")
}
return nil, fmt.Errorf("failed to get user: %w", err)
}
updateUser := &model.User{
Name: req.Name,
Gender: req.Gender,
Dateofbirth: req.Dateofbirth,
Placeofbirth: req.Placeofbirth,
Phone: req.Phone,
RegistrationProgress: utils.ProgressDataSubmitted,
}
if err := s.repo.Update(ctx, userID, updateUser); err != nil {
if errors.Is(err, ErrUserNotFound) {
return nil, fmt.Errorf("user not found")
}
return nil, fmt.Errorf("failed to update user profile: %w", err)
}
updatedUser, err := s.repo.GetByID(ctx, userID)
if err != nil {
if errors.Is(err, ErrUserNotFound) {
return nil, fmt.Errorf("user not found")
}
return nil, fmt.Errorf("failed to get updated user: %w", err)
}
tokenResponse, err := utils.GenerateTokenPair(
updatedUser.ID,
updatedUser.Role.RoleName,
// req.DeviceID,
deviceId,
updatedUser.RegistrationStatus,
int(updatedUser.RegistrationProgress),
)
if err != nil {
return nil, fmt.Errorf("gagal generate token: %v", err)
}
nextStep := utils.GetNextRegistrationStep(
updatedUser.Role.RoleName,
int(updatedUser.RegistrationProgress),
updateUser.RegistrationStatus,
)
return &authentication.AuthResponse{
Message: "Isi data diri berhasil",
AccessToken: tokenResponse.AccessToken,
RefreshToken: tokenResponse.RefreshToken,
TokenType: string(tokenResponse.TokenType),
ExpiresIn: tokenResponse.ExpiresIn,
RegistrationStatus: updateUser.RegistrationStatus,
NextStep: nextStep,
SessionID: tokenResponse.SessionID,
}, nil
// return s.mapToResponseDTO(updatedUser), nil
}
func (s *userProfileService) mapToResponseDTO(user *model.User) *UserProfileResponseDTO {
createdAt, err := utils.FormatDateToIndonesianFormat(user.CreatedAt)
if err != nil {
createdAt = user.CreatedAt.Format(time.RFC3339)
}
updatedAt, err := utils.FormatDateToIndonesianFormat(user.UpdatedAt)
if err != nil {
updatedAt = user.UpdatedAt.Format(time.RFC3339)
}
response := &UserProfileResponseDTO{
ID: user.ID,
Name: user.Name,
Gender: user.Gender,
Dateofbirth: user.Dateofbirth,
Placeofbirth: user.Placeofbirth,
Phone: user.Phone,
PhoneVerified: user.PhoneVerified,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}
if user.Avatar != nil {
response.Avatar = *user.Avatar
}
if user.Role != nil {
roleCreatedAt, err := utils.FormatDateToIndonesianFormat(user.Role.CreatedAt)
if err != nil {
roleCreatedAt = user.Role.CreatedAt.Format(time.RFC3339)
}
roleUpdatedAt, err := utils.FormatDateToIndonesianFormat(user.Role.UpdatedAt)
if err != nil {
roleUpdatedAt = user.Role.UpdatedAt.Format(time.RFC3339)
}
response.Role = role.RoleResponseDTO{
ID: user.Role.ID,
RoleName: user.Role.RoleName,
CreatedAt: roleCreatedAt,
UpdatedAt: roleUpdatedAt,
}
}
return response
}

View File

@ -1 +1,272 @@
package wilayahindo
package wilayahindo
import (
"strconv"
"strings"
"rijig/utils"
"github.com/gofiber/fiber/v2"
)
type WilayahIndonesiaHandler struct {
WilayahService WilayahIndonesiaService
}
func NewWilayahIndonesiaHandler(wilayahService WilayahIndonesiaService) *WilayahIndonesiaHandler {
return &WilayahIndonesiaHandler{
WilayahService: wilayahService,
}
}
func (h *WilayahIndonesiaHandler) ImportDataFromCSV(c *fiber.Ctx) error {
ctx := c.Context()
if err := h.WilayahService.ImportDataFromCSV(ctx); err != nil {
return utils.InternalServerError(c, "Failed to import data from CSV: "+err.Error())
}
return utils.Success(c, "Data imported successfully from CSV")
}
func (h *WilayahIndonesiaHandler) GetAllProvinces(c *fiber.Ctx) error {
ctx := c.Context()
page, limit, err := h.parsePaginationParams(c)
if err != nil {
return utils.BadRequest(c, err.Error())
}
provinces, total, err := h.WilayahService.GetAllProvinces(ctx, page, limit)
if err != nil {
return utils.InternalServerError(c, "Failed to fetch provinces: "+err.Error())
}
response := map[string]interface{}{
"provinces": provinces,
"total": total,
}
return utils.SuccessWithPagination(c, "Provinces retrieved successfully", response, page, limit)
}
func (h *WilayahIndonesiaHandler) GetProvinceByID(c *fiber.Ctx) error {
ctx := c.Context()
id := c.Params("id")
if id == "" {
return utils.BadRequest(c, "Province ID is required")
}
page, limit, err := h.parsePaginationParams(c)
if err != nil {
return utils.BadRequest(c, err.Error())
}
province, totalRegencies, err := h.WilayahService.GetProvinceByID(ctx, id, page, limit)
if err != nil {
if strings.Contains(err.Error(), "not found") {
return utils.NotFound(c, "Province not found")
}
return utils.InternalServerError(c, "Failed to fetch province: "+err.Error())
}
response := map[string]interface{}{
"province": province,
"total_regencies": totalRegencies,
}
return utils.SuccessWithPagination(c, "Province retrieved successfully", response, page, limit)
}
func (h *WilayahIndonesiaHandler) GetAllRegencies(c *fiber.Ctx) error {
ctx := c.Context()
page, limit, err := h.parsePaginationParams(c)
if err != nil {
return utils.BadRequest(c, err.Error())
}
regencies, total, err := h.WilayahService.GetAllRegencies(ctx, page, limit)
if err != nil {
return utils.InternalServerError(c, "Failed to fetch regencies: "+err.Error())
}
response := map[string]interface{}{
"regencies": regencies,
"total": total,
}
return utils.SuccessWithPagination(c, "Regencies retrieved successfully", response, page, limit)
}
func (h *WilayahIndonesiaHandler) GetRegencyByID(c *fiber.Ctx) error {
ctx := c.Context()
id := c.Params("id")
if id == "" {
return utils.BadRequest(c, "Regency ID is required")
}
page, limit, err := h.parsePaginationParams(c)
if err != nil {
return utils.BadRequest(c, err.Error())
}
regency, totalDistricts, err := h.WilayahService.GetRegencyByID(ctx, id, page, limit)
if err != nil {
if strings.Contains(err.Error(), "not found") {
return utils.NotFound(c, "Regency not found")
}
return utils.InternalServerError(c, "Failed to fetch regency: "+err.Error())
}
response := map[string]interface{}{
"regency": regency,
"total_districts": totalDistricts,
}
return utils.SuccessWithPagination(c, "Regency retrieved successfully", response, page, limit)
}
func (h *WilayahIndonesiaHandler) GetAllDistricts(c *fiber.Ctx) error {
ctx := c.Context()
page, limit, err := h.parsePaginationParams(c)
if err != nil {
return utils.BadRequest(c, err.Error())
}
districts, total, err := h.WilayahService.GetAllDistricts(ctx, page, limit)
if err != nil {
return utils.InternalServerError(c, "Failed to fetch districts: "+err.Error())
}
response := map[string]interface{}{
"districts": districts,
"total": total,
}
return utils.SuccessWithPagination(c, "Districts retrieved successfully", response, page, limit)
}
func (h *WilayahIndonesiaHandler) GetDistrictByID(c *fiber.Ctx) error {
ctx := c.Context()
id := c.Params("id")
if id == "" {
return utils.BadRequest(c, "District ID is required")
}
page, limit, err := h.parsePaginationParams(c)
if err != nil {
return utils.BadRequest(c, err.Error())
}
district, totalVillages, err := h.WilayahService.GetDistrictByID(ctx, id, page, limit)
if err != nil {
if strings.Contains(err.Error(), "not found") {
return utils.NotFound(c, "District not found")
}
return utils.InternalServerError(c, "Failed to fetch district: "+err.Error())
}
response := map[string]interface{}{
"district": district,
"total_villages": totalVillages,
}
return utils.SuccessWithPagination(c, "District retrieved successfully", response, page, limit)
}
func (h *WilayahIndonesiaHandler) GetAllVillages(c *fiber.Ctx) error {
ctx := c.Context()
page, limit, err := h.parsePaginationParams(c)
if err != nil {
return utils.BadRequest(c, err.Error())
}
villages, total, err := h.WilayahService.GetAllVillages(ctx, page, limit)
if err != nil {
return utils.InternalServerError(c, "Failed to fetch villages: "+err.Error())
}
response := map[string]interface{}{
"villages": villages,
"total": total,
}
return utils.SuccessWithPagination(c, "Villages retrieved successfully", response, page, limit)
}
func (h *WilayahIndonesiaHandler) GetVillageByID(c *fiber.Ctx) error {
ctx := c.Context()
id := c.Params("id")
if id == "" {
return utils.BadRequest(c, "Village ID is required")
}
village, err := h.WilayahService.GetVillageByID(ctx, id)
if err != nil {
if strings.Contains(err.Error(), "not found") {
return utils.NotFound(c, "Village not found")
}
return utils.InternalServerError(c, "Failed to fetch village: "+err.Error())
}
return utils.SuccessWithData(c, "Village retrieved successfully", village)
}
func (h *WilayahIndonesiaHandler) parsePaginationParams(c *fiber.Ctx) (int, int, error) {
page := 1
limit := 10
if pageStr := c.Query("page"); pageStr != "" {
parsedPage, err := strconv.Atoi(pageStr)
if err != nil {
return 0, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid page parameter")
}
if parsedPage < 1 {
return 0, 0, fiber.NewError(fiber.StatusBadRequest, "Page must be greater than 0")
}
page = parsedPage
}
if limitStr := c.Query("limit"); limitStr != "" {
parsedLimit, err := strconv.Atoi(limitStr)
if err != nil {
return 0, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid limit parameter")
}
if parsedLimit < 1 {
return 0, 0, fiber.NewError(fiber.StatusBadRequest, "Limit must be greater than 0")
}
if parsedLimit > 100 {
return 0, 0, fiber.NewError(fiber.StatusBadRequest, "Limit cannot exceed 100")
}
limit = parsedLimit
}
return page, limit, nil
}
func (h *WilayahIndonesiaHandler) SetupRoutes(app *fiber.App) {
api := app.Group("/api/v1/wilayah")
api.Post("/import", h.ImportDataFromCSV)
api.Get("/provinces", h.GetAllProvinces)
api.Get("/provinces/:id", h.GetProvinceByID)
api.Get("/regencies", h.GetAllRegencies)
api.Get("/regencies/:id", h.GetRegencyByID)
api.Get("/districts", h.GetAllDistricts)
api.Get("/districts/:id", h.GetDistrictByID)
api.Get("/villages", h.GetAllVillages)
api.Get("/villages/:id", h.GetVillageByID)
}

View File

@ -1 +1,32 @@
package wilayahindo
package wilayahindo
import (
"rijig/config"
"rijig/middleware"
"github.com/gofiber/fiber/v2"
)
func WilayahRouter(api fiber.Router) {
wilayahRepo := NewWilayahIndonesiaRepository(config.DB)
wilayahService := NewWilayahIndonesiaService(wilayahRepo)
wilayahHandler := NewWilayahIndonesiaHandler(wilayahService)
api.Post("/import/data-wilayah-indonesia", middleware.RequireAdminRole(), wilayahHandler.ImportDataFromCSV)
wilayahAPI := api.Group("/wilayah-indonesia")
wilayahAPI.Get("/provinces", wilayahHandler.GetAllProvinces)
wilayahAPI.Get("/provinces/:provinceid", wilayahHandler.GetProvinceByID)
wilayahAPI.Get("/regencies", wilayahHandler.GetAllRegencies)
wilayahAPI.Get("/regencies/:regencyid", wilayahHandler.GetRegencyByID)
wilayahAPI.Get("/districts", wilayahHandler.GetAllDistricts)
wilayahAPI.Get("/districts/:districtid", wilayahHandler.GetDistrictByID)
wilayahAPI.Get("/villages", wilayahHandler.GetAllVillages)
wilayahAPI.Get("/villages/:villageid", wilayahHandler.GetVillageByID)
}

View File

@ -5,7 +5,6 @@ import (
"fmt"
"time"
"rijig/dto"
"rijig/model"
"rijig/utils"
)
@ -13,17 +12,17 @@ import (
type WilayahIndonesiaService interface {
ImportDataFromCSV(ctx context.Context) error
GetAllProvinces(ctx context.Context, page, limit int) ([]dto.ProvinceResponseDTO, int, error)
GetProvinceByID(ctx context.Context, id string, page, limit int) (*dto.ProvinceResponseDTO, int, error)
GetAllProvinces(ctx context.Context, page, limit int) ([]ProvinceResponseDTO, int, error)
GetProvinceByID(ctx context.Context, id string, page, limit int) (*ProvinceResponseDTO, int, error)
GetAllRegencies(ctx context.Context, page, limit int) ([]dto.RegencyResponseDTO, int, error)
GetRegencyByID(ctx context.Context, id string, page, limit int) (*dto.RegencyResponseDTO, int, error)
GetAllRegencies(ctx context.Context, page, limit int) ([]RegencyResponseDTO, int, error)
GetRegencyByID(ctx context.Context, id string, page, limit int) (*RegencyResponseDTO, int, error)
GetAllDistricts(ctx context.Context, page, limit int) ([]dto.DistrictResponseDTO, int, error)
GetDistrictByID(ctx context.Context, id string, page, limit int) (*dto.DistrictResponseDTO, int, error)
GetAllDistricts(ctx context.Context, page, limit int) ([]DistrictResponseDTO, int, error)
GetDistrictByID(ctx context.Context, id string, page, limit int) (*DistrictResponseDTO, int, error)
GetAllVillages(ctx context.Context, page, limit int) ([]dto.VillageResponseDTO, int, error)
GetVillageByID(ctx context.Context, id string) (*dto.VillageResponseDTO, error)
GetAllVillages(ctx context.Context, page, limit int) ([]VillageResponseDTO, int, error)
GetVillageByID(ctx context.Context, id string) (*VillageResponseDTO, error)
}
type wilayahIndonesiaService struct {
@ -122,11 +121,11 @@ func (s *wilayahIndonesiaService) ImportDataFromCSV(ctx context.Context) error {
return nil
}
func (s *wilayahIndonesiaService) GetAllProvinces(ctx context.Context, page, limit int) ([]dto.ProvinceResponseDTO, int, error) {
func (s *wilayahIndonesiaService) GetAllProvinces(ctx context.Context, page, limit int) ([]ProvinceResponseDTO, int, error) {
cacheKey := fmt.Sprintf("provinces_page:%d_limit:%d", page, limit)
var cachedResponse struct {
Data []dto.ProvinceResponseDTO `json:"data"`
Data []ProvinceResponseDTO `json:"data"`
Total int `json:"total"`
}
@ -139,16 +138,16 @@ func (s *wilayahIndonesiaService) GetAllProvinces(ctx context.Context, page, lim
return nil, 0, fmt.Errorf("failed to fetch provinces: %w", err)
}
provinceDTOs := make([]dto.ProvinceResponseDTO, len(provinces))
provinceDTOs := make([]ProvinceResponseDTO, len(provinces))
for i, province := range provinces {
provinceDTOs[i] = dto.ProvinceResponseDTO{
provinceDTOs[i] = ProvinceResponseDTO{
ID: province.ID,
Name: province.Name,
}
}
cacheData := struct {
Data []dto.ProvinceResponseDTO `json:"data"`
Data []ProvinceResponseDTO `json:"data"`
Total int `json:"total"`
}{
Data: provinceDTOs,
@ -162,11 +161,11 @@ func (s *wilayahIndonesiaService) GetAllProvinces(ctx context.Context, page, lim
return provinceDTOs, total, nil
}
func (s *wilayahIndonesiaService) GetProvinceByID(ctx context.Context, id string, page, limit int) (*dto.ProvinceResponseDTO, int, error) {
func (s *wilayahIndonesiaService) GetProvinceByID(ctx context.Context, id string, page, limit int) (*ProvinceResponseDTO, int, error) {
cacheKey := fmt.Sprintf("province:%s_page:%d_limit:%d", id, page, limit)
var cachedResponse struct {
Data dto.ProvinceResponseDTO `json:"data"`
Data ProvinceResponseDTO `json:"data"`
TotalRegencies int `json:"total_regencies"`
}
@ -179,14 +178,14 @@ func (s *wilayahIndonesiaService) GetProvinceByID(ctx context.Context, id string
return nil, 0, err
}
provinceDTO := dto.ProvinceResponseDTO{
provinceDTO := ProvinceResponseDTO{
ID: province.ID,
Name: province.Name,
}
regencyDTOs := make([]dto.RegencyResponseDTO, len(province.Regencies))
regencyDTOs := make([]RegencyResponseDTO, len(province.Regencies))
for i, regency := range province.Regencies {
regencyDTOs[i] = dto.RegencyResponseDTO{
regencyDTOs[i] = RegencyResponseDTO{
ID: regency.ID,
ProvinceID: regency.ProvinceID,
Name: regency.Name,
@ -195,7 +194,7 @@ func (s *wilayahIndonesiaService) GetProvinceByID(ctx context.Context, id string
provinceDTO.Regencies = regencyDTOs
cacheData := struct {
Data dto.ProvinceResponseDTO `json:"data"`
Data ProvinceResponseDTO `json:"data"`
TotalRegencies int `json:"total_regencies"`
}{
Data: provinceDTO,
@ -209,11 +208,11 @@ func (s *wilayahIndonesiaService) GetProvinceByID(ctx context.Context, id string
return &provinceDTO, totalRegencies, nil
}
func (s *wilayahIndonesiaService) GetAllRegencies(ctx context.Context, page, limit int) ([]dto.RegencyResponseDTO, int, error) {
func (s *wilayahIndonesiaService) GetAllRegencies(ctx context.Context, page, limit int) ([]RegencyResponseDTO, int, error) {
cacheKey := fmt.Sprintf("regencies_page:%d_limit:%d", page, limit)
var cachedResponse struct {
Data []dto.RegencyResponseDTO `json:"data"`
Data []RegencyResponseDTO `json:"data"`
Total int `json:"total"`
}
@ -226,9 +225,9 @@ func (s *wilayahIndonesiaService) GetAllRegencies(ctx context.Context, page, lim
return nil, 0, fmt.Errorf("failed to fetch regencies: %w", err)
}
regencyDTOs := make([]dto.RegencyResponseDTO, len(regencies))
regencyDTOs := make([]RegencyResponseDTO, len(regencies))
for i, regency := range regencies {
regencyDTOs[i] = dto.RegencyResponseDTO{
regencyDTOs[i] = RegencyResponseDTO{
ID: regency.ID,
ProvinceID: regency.ProvinceID,
Name: regency.Name,
@ -236,7 +235,7 @@ func (s *wilayahIndonesiaService) GetAllRegencies(ctx context.Context, page, lim
}
cacheData := struct {
Data []dto.RegencyResponseDTO `json:"data"`
Data []RegencyResponseDTO `json:"data"`
Total int `json:"total"`
}{
Data: regencyDTOs,
@ -250,11 +249,11 @@ func (s *wilayahIndonesiaService) GetAllRegencies(ctx context.Context, page, lim
return regencyDTOs, total, nil
}
func (s *wilayahIndonesiaService) GetRegencyByID(ctx context.Context, id string, page, limit int) (*dto.RegencyResponseDTO, int, error) {
func (s *wilayahIndonesiaService) GetRegencyByID(ctx context.Context, id string, page, limit int) (*RegencyResponseDTO, int, error) {
cacheKey := fmt.Sprintf("regency:%s_page:%d_limit:%d", id, page, limit)
var cachedResponse struct {
Data dto.RegencyResponseDTO `json:"data"`
Data RegencyResponseDTO `json:"data"`
TotalDistricts int `json:"total_districts"`
}
@ -267,15 +266,15 @@ func (s *wilayahIndonesiaService) GetRegencyByID(ctx context.Context, id string,
return nil, 0, err
}
regencyDTO := dto.RegencyResponseDTO{
regencyDTO := RegencyResponseDTO{
ID: regency.ID,
ProvinceID: regency.ProvinceID,
Name: regency.Name,
}
districtDTOs := make([]dto.DistrictResponseDTO, len(regency.Districts))
districtDTOs := make([]DistrictResponseDTO, len(regency.Districts))
for i, district := range regency.Districts {
districtDTOs[i] = dto.DistrictResponseDTO{
districtDTOs[i] = DistrictResponseDTO{
ID: district.ID,
RegencyID: district.RegencyID,
Name: district.Name,
@ -284,7 +283,7 @@ func (s *wilayahIndonesiaService) GetRegencyByID(ctx context.Context, id string,
regencyDTO.Districts = districtDTOs
cacheData := struct {
Data dto.RegencyResponseDTO `json:"data"`
Data RegencyResponseDTO `json:"data"`
TotalDistricts int `json:"total_districts"`
}{
Data: regencyDTO,
@ -298,11 +297,11 @@ func (s *wilayahIndonesiaService) GetRegencyByID(ctx context.Context, id string,
return &regencyDTO, totalDistricts, nil
}
func (s *wilayahIndonesiaService) GetAllDistricts(ctx context.Context, page, limit int) ([]dto.DistrictResponseDTO, int, error) {
func (s *wilayahIndonesiaService) GetAllDistricts(ctx context.Context, page, limit int) ([]DistrictResponseDTO, int, error) {
cacheKey := fmt.Sprintf("districts_page:%d_limit:%d", page, limit)
var cachedResponse struct {
Data []dto.DistrictResponseDTO `json:"data"`
Data []DistrictResponseDTO `json:"data"`
Total int `json:"total"`
}
@ -315,9 +314,9 @@ func (s *wilayahIndonesiaService) GetAllDistricts(ctx context.Context, page, lim
return nil, 0, fmt.Errorf("failed to fetch districts: %w", err)
}
districtDTOs := make([]dto.DistrictResponseDTO, len(districts))
districtDTOs := make([]DistrictResponseDTO, len(districts))
for i, district := range districts {
districtDTOs[i] = dto.DistrictResponseDTO{
districtDTOs[i] = DistrictResponseDTO{
ID: district.ID,
RegencyID: district.RegencyID,
Name: district.Name,
@ -325,7 +324,7 @@ func (s *wilayahIndonesiaService) GetAllDistricts(ctx context.Context, page, lim
}
cacheData := struct {
Data []dto.DistrictResponseDTO `json:"data"`
Data []DistrictResponseDTO `json:"data"`
Total int `json:"total"`
}{
Data: districtDTOs,
@ -339,11 +338,11 @@ func (s *wilayahIndonesiaService) GetAllDistricts(ctx context.Context, page, lim
return districtDTOs, total, nil
}
func (s *wilayahIndonesiaService) GetDistrictByID(ctx context.Context, id string, page, limit int) (*dto.DistrictResponseDTO, int, error) {
func (s *wilayahIndonesiaService) GetDistrictByID(ctx context.Context, id string, page, limit int) (*DistrictResponseDTO, int, error) {
cacheKey := fmt.Sprintf("district:%s_page:%d_limit:%d", id, page, limit)
var cachedResponse struct {
Data dto.DistrictResponseDTO `json:"data"`
Data DistrictResponseDTO `json:"data"`
TotalVillages int `json:"total_villages"`
}
@ -356,15 +355,15 @@ func (s *wilayahIndonesiaService) GetDistrictByID(ctx context.Context, id string
return nil, 0, err
}
districtDTO := dto.DistrictResponseDTO{
districtDTO := DistrictResponseDTO{
ID: district.ID,
RegencyID: district.RegencyID,
Name: district.Name,
}
villageDTOs := make([]dto.VillageResponseDTO, len(district.Villages))
villageDTOs := make([]VillageResponseDTO, len(district.Villages))
for i, village := range district.Villages {
villageDTOs[i] = dto.VillageResponseDTO{
villageDTOs[i] = VillageResponseDTO{
ID: village.ID,
DistrictID: village.DistrictID,
Name: village.Name,
@ -373,7 +372,7 @@ func (s *wilayahIndonesiaService) GetDistrictByID(ctx context.Context, id string
districtDTO.Villages = villageDTOs
cacheData := struct {
Data dto.DistrictResponseDTO `json:"data"`
Data DistrictResponseDTO `json:"data"`
TotalVillages int `json:"total_villages"`
}{
Data: districtDTO,
@ -387,11 +386,11 @@ func (s *wilayahIndonesiaService) GetDistrictByID(ctx context.Context, id string
return &districtDTO, totalVillages, nil
}
func (s *wilayahIndonesiaService) GetAllVillages(ctx context.Context, page, limit int) ([]dto.VillageResponseDTO, int, error) {
func (s *wilayahIndonesiaService) GetAllVillages(ctx context.Context, page, limit int) ([]VillageResponseDTO, int, error) {
cacheKey := fmt.Sprintf("villages_page:%d_limit:%d", page, limit)
var cachedResponse struct {
Data []dto.VillageResponseDTO `json:"data"`
Data []VillageResponseDTO `json:"data"`
Total int `json:"total"`
}
@ -404,9 +403,9 @@ func (s *wilayahIndonesiaService) GetAllVillages(ctx context.Context, page, limi
return nil, 0, fmt.Errorf("failed to fetch villages: %w", err)
}
villageDTOs := make([]dto.VillageResponseDTO, len(villages))
villageDTOs := make([]VillageResponseDTO, len(villages))
for i, village := range villages {
villageDTOs[i] = dto.VillageResponseDTO{
villageDTOs[i] = VillageResponseDTO{
ID: village.ID,
DistrictID: village.DistrictID,
Name: village.Name,
@ -414,7 +413,7 @@ func (s *wilayahIndonesiaService) GetAllVillages(ctx context.Context, page, limi
}
cacheData := struct {
Data []dto.VillageResponseDTO `json:"data"`
Data []VillageResponseDTO `json:"data"`
Total int `json:"total"`
}{
Data: villageDTOs,
@ -428,10 +427,10 @@ func (s *wilayahIndonesiaService) GetAllVillages(ctx context.Context, page, limi
return villageDTOs, total, nil
}
func (s *wilayahIndonesiaService) GetVillageByID(ctx context.Context, id string) (*dto.VillageResponseDTO, error) {
func (s *wilayahIndonesiaService) GetVillageByID(ctx context.Context, id string) (*VillageResponseDTO, error) {
cacheKey := fmt.Sprintf("village:%s", id)
var cachedResponse dto.VillageResponseDTO
var cachedResponse VillageResponseDTO
if err := utils.GetCache(cacheKey, &cachedResponse); err == nil {
return &cachedResponse, nil
}
@ -441,7 +440,7 @@ func (s *wilayahIndonesiaService) GetVillageByID(ctx context.Context, id string)
return nil, fmt.Errorf("village not found: %w", err)
}
villageResponse := &dto.VillageResponseDTO{
villageResponse := &VillageResponseDTO{
ID: village.ID,
DistrictID: village.DistrictID,
Name: village.Name,

View File

@ -7,6 +7,7 @@ type IdentityCard struct {
UserID string `gorm:"not null" json:"userId"`
User User `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user"`
Identificationumber string `gorm:"not null" json:"identificationumber"`
Fullname string `gorm:"not null" json:"fullname"`
Placeofbirth string `gorm:"not null" json:"placeofbirth"`
Dateofbirth string `gorm:"not null" json:"dateofbirth"`
Gender string `gorm:"not null" json:"gender"`

0
public/document/districts.csv Normal file → Executable file
View File

0
public/document/provinces.csv Normal file → Executable file
View File

0
public/document/regencies.csv Normal file → Executable file
View File

0
public/document/villages.csv Normal file → Executable file
View File

Can't render this file because it is too large.

View File

@ -9,7 +9,9 @@ import (
"rijig/internal/identitycart"
"rijig/internal/role"
"rijig/internal/userpin"
"rijig/internal/userprofile"
"rijig/internal/whatsapp"
"rijig/internal/wilayahindo"
"rijig/middleware"
// "rijig/presentation"
@ -19,8 +21,9 @@ import (
func SetupRoutes(app *fiber.App) {
apa := app.Group(os.Getenv("BASE_URL"))
whatsapp.WhatsAppRouter(apa)
apa.Static("/uploads", "./public"+os.Getenv("BASE_URL")+"/uploads")
a := app.Group(os.Getenv("BASE_URL"))
whatsapp.WhatsAppRouter(a)
api := app.Group(os.Getenv("BASE_URL"))
api.Use(middleware.APIKeyMiddleware)
@ -32,6 +35,8 @@ func SetupRoutes(app *fiber.App) {
role.UserRoleRouter(api)
article.ArticleRouter(api)
userprofile.UserProfileRouter(api)
wilayahindo.WilayahRouter(api)
// || auth router || //
// presentation.AuthRouter(api)
@ -58,5 +63,4 @@ func SetupRoutes(app *fiber.App) {
// // presentation.AboutRouter(api)
// presentation.TrashRouter(api)
// presentation.CoverageAreaRouter(api)
whatsapp.WhatsAppRouter(api)
}