diff --git a/dto/identitycard_dto.go b/dto/identitycard_dto.go new file mode 100644 index 0000000..14967f6 --- /dev/null +++ b/dto/identitycard_dto.go @@ -0,0 +1,126 @@ +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.UserID) == "" { + // errors["userId"] = append(errors["userId"], "UserID harus diisi") + // isValid = false + // } + + 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 + } + + // if strings.TrimSpace(r.Cardphoto) == "" { + // errors["cardphoto"] = append(errors["cardphoto"], "Foto KTP harus diisi") + // isValid = false + // } + + return errors, isValid +} diff --git a/internal/handler/identitycard_handler.go b/internal/handler/identitycard_handler.go new file mode 100644 index 0000000..b05ba95 --- /dev/null +++ b/internal/handler/identitycard_handler.go @@ -0,0 +1,134 @@ +package handler + +import ( + "log" + "rijig/dto" + "rijig/internal/services" + "rijig/utils" + + "github.com/gofiber/fiber/v2" +) + +type IdentityCardHandler struct { + IdentityCardService services.IdentityCardService +} + +func NewIdentityCardHandler(identityCardService services.IdentityCardService) *IdentityCardHandler { + return &IdentityCardHandler{ + IdentityCardService: identityCardService, + } +} + +func (h *IdentityCardHandler) CreateIdentityCard(c *fiber.Ctx) error { + + userID, ok := c.Locals("userID").(string) + if !ok || userID == "" { + return utils.GenericResponse(c, fiber.StatusUnauthorized, "User not authenticated") + } + + var request dto.RequestIdentityCardDTO + if err := c.BodyParser(&request); err != nil { + log.Printf("Error parsing body: %v", err) + return utils.ErrorResponse(c, "Invalid request data") + } + + cardPhoto, err := c.FormFile("cardphoto") + if err != nil { + log.Printf("Error retrieving card photo from request: %v", err) + return utils.ErrorResponse(c, "Card photo is required") + } + + identityCard, err := h.IdentityCardService.CreateIdentityCard(userID, &request, cardPhoto) + if err != nil { + log.Printf("Error creating identity card: %v", err) + return utils.ErrorResponse(c, err.Error()) + } + + return utils.CreateResponse(c, identityCard, "Identity card created successfully") +} + +func (h *IdentityCardHandler) UpdateIdentityCard(c *fiber.Ctx) error { + + userID, ok := c.Locals("userID").(string) + if !ok || userID == "" { + return utils.GenericResponse(c, fiber.StatusUnauthorized, "User not authenticated") + } + + id := c.Params("identity_id") + if id == "" { + return utils.ErrorResponse(c, "Identity card ID is required") + } + + var request dto.RequestIdentityCardDTO + if err := c.BodyParser(&request); err != nil { + log.Printf("Error parsing body: %v", err) + return utils.ErrorResponse(c, "Invalid request data") + } + + cardPhoto, err := c.FormFile("cardphoto") + if err != nil && err.Error() != "File not found" { + log.Printf("Error retrieving card photo: %v", err) + return utils.ErrorResponse(c, "Card photo is required") + } + + updatedCard, err := h.IdentityCardService.UpdateIdentityCard(userID, id, &request, cardPhoto) + if err != nil { + log.Printf("Error updating identity card: %v", err) + return utils.ErrorResponse(c, err.Error()) + } + + return utils.SuccessResponse(c, updatedCard, "Identity card updated successfully") +} + +func (h *IdentityCardHandler) GetIdentityCardById(c *fiber.Ctx) error { + + id := c.Params("identity_id") + if id == "" { + return utils.ErrorResponse(c, "Identity card ID is required") + } + + identityCard, err := h.IdentityCardService.GetIdentityCardByID(id) + if err != nil { + log.Printf("Error retrieving identity card: %v", err) + return utils.ErrorResponse(c, err.Error()) + } + + return utils.SuccessResponse(c, identityCard, "Identity card retrieved successfully") +} + +func (h *IdentityCardHandler) GetIdentityCard(c *fiber.Ctx) error { + + userID, ok := c.Locals("userID").(string) + if !ok || userID == "" { + return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found") + } + + identityCard, err := h.IdentityCardService.GetIdentityCardsByUserID(userID) + if err != nil { + log.Printf("Error retrieving identity card: %v", err) + return utils.ErrorResponse(c, err.Error()) + } + + return utils.SuccessResponse(c, identityCard, "Identity card retrieved successfully") +} + +func (h *IdentityCardHandler) DeleteIdentityCard(c *fiber.Ctx) error { + + userID, ok := c.Locals("userID").(string) + if !ok || userID == "" { + return utils.GenericResponse(c, fiber.StatusUnauthorized, "User not authenticated") + } + + id := c.Params("identity_id") + if id == "" { + return utils.ErrorResponse(c, "Identity card ID is required") + } + + err := h.IdentityCardService.DeleteIdentityCard(id) + if err != nil { + log.Printf("Error deleting identity card: %v", err) + return utils.ErrorResponse(c, err.Error()) + } + + return utils.GenericResponse(c, fiber.StatusOK, "Identity card deleted successfully") +} diff --git a/internal/repositories/identitycard_repo.go b/internal/repositories/identitycard_repo.go new file mode 100644 index 0000000..763791a --- /dev/null +++ b/internal/repositories/identitycard_repo.go @@ -0,0 +1,82 @@ +package repositories + +import ( + "errors" + "fmt" + "log" + "rijig/model" + + "gorm.io/gorm" +) + +type IdentityCardRepository interface { + CreateIdentityCard(identityCard *model.IdentityCard) (*model.IdentityCard, error) + GetIdentityCardByID(id string) (*model.IdentityCard, error) + GetIdentityCardsByUserID(userID string) ([]model.IdentityCard, error) + UpdateIdentityCard(id string, updatedCard *model.IdentityCard) (*model.IdentityCard, error) + DeleteIdentityCard(id string) error +} + +type identityCardRepository struct { + db *gorm.DB +} + +func NewIdentityCardRepository(db *gorm.DB) IdentityCardRepository { + return &identityCardRepository{ + db: db, + } +} + +func (r *identityCardRepository) CreateIdentityCard(identityCard *model.IdentityCard) (*model.IdentityCard, error) { + if err := r.db.Create(identityCard).Error; err != nil { + log.Printf("Error creating identity card: %v", err) + return nil, fmt.Errorf("failed to create identity card: %w", err) + } + return identityCard, nil +} + +func (r *identityCardRepository) GetIdentityCardByID(id string) (*model.IdentityCard, error) { + var identityCard model.IdentityCard + if err := r.db.Where("id = ?", id).First(&identityCard).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("identity card not found with id %s", id) + } + log.Printf("Error fetching identity card by ID: %v", err) + return nil, fmt.Errorf("error fetching identity card by ID: %w", err) + } + return &identityCard, nil +} + +func (r *identityCardRepository) GetIdentityCardsByUserID(userID string) ([]model.IdentityCard, error) { + var identityCards []model.IdentityCard + if err := r.db.Where("user_id = ?", userID).Find(&identityCards).Error; err != nil { + log.Printf("Error fetching identity cards by userID: %v", err) + return nil, fmt.Errorf("error fetching identity cards by userID: %w", err) + } + return identityCards, nil +} + +func (r *identityCardRepository) UpdateIdentityCard(id string, updatedCard *model.IdentityCard) (*model.IdentityCard, error) { + var existingCard model.IdentityCard + if err := r.db.Where("id = ?", id).First(&existingCard).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("identity card with ID %s not found", id) + } + log.Printf("Error fetching identity card for update: %v", err) + return nil, fmt.Errorf("error fetching identity card for update: %w", err) + } + + if err := r.db.Save(&existingCard).Error; err != nil { + log.Printf("Error updating identity card: %v", err) + return nil, fmt.Errorf("failed to update identity card: %w", err) + } + return &existingCard, nil +} + +func (r *identityCardRepository) DeleteIdentityCard(id string) error { + if err := r.db.Where("id = ?", id).Delete(&model.IdentityCard{}).Error; err != nil { + log.Printf("Error deleting identity card: %v", err) + return fmt.Errorf("failed to delete identity card: %w", err) + } + return nil +} diff --git a/internal/services/identitycard_service.go b/internal/services/identitycard_service.go new file mode 100644 index 0000000..b5e653b --- /dev/null +++ b/internal/services/identitycard_service.go @@ -0,0 +1,276 @@ +package services + +import ( + "fmt" + "log" + "mime/multipart" + "os" + "path/filepath" + "rijig/dto" + "rijig/internal/repositories" + "rijig/model" + "rijig/utils" +) + +type IdentityCardService interface { + CreateIdentityCard(userID string, request *dto.RequestIdentityCardDTO, cardPhoto *multipart.FileHeader) (*dto.ResponseIdentityCardDTO, error) + GetIdentityCardByID(id string) (*dto.ResponseIdentityCardDTO, error) + GetIdentityCardsByUserID(userID string) ([]dto.ResponseIdentityCardDTO, error) + UpdateIdentityCard(userID string, id string, request *dto.RequestIdentityCardDTO, cardPhoto *multipart.FileHeader) (*dto.ResponseIdentityCardDTO, error) + DeleteIdentityCard(id string) error +} + +type identityCardService struct { + identityCardRepo repositories.IdentityCardRepository +} + +func NewIdentityCardService(identityCardRepo repositories.IdentityCardRepository) IdentityCardService { + return &identityCardService{ + identityCardRepo: identityCardRepo, + } +} + +func FormatResponseIdentityCars(identityCard *model.IdentityCard) (*dto.ResponseIdentityCardDTO, error) { + + createdAt, _ := utils.FormatDateToIndonesianFormat(identityCard.CreatedAt) + updatedAt, _ := utils.FormatDateToIndonesianFormat(identityCard.UpdatedAt) + + idcardResponseDTO := &dto.ResponseIdentityCardDTO{ + ID: identityCard.ID, + UserID: identityCard.UserID, + Identificationumber: identityCard.Identificationumber, + Placeofbirth: identityCard.Placeofbirth, + Dateofbirth: identityCard.Dateofbirth, + Gender: identityCard.Gender, + BloodType: identityCard.BloodType, + District: identityCard.District, + Village: identityCard.Village, + Neighbourhood: identityCard.Neighbourhood, + Religion: identityCard.Religion, + Maritalstatus: identityCard.Maritalstatus, + Job: identityCard.Job, + Citizenship: identityCard.Citizenship, + Validuntil: identityCard.Validuntil, + Cardphoto: identityCard.Cardphoto, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + } + + return idcardResponseDTO, nil +} + +func (s *identityCardService) saveIdentityCardImage(userID string, cardPhoto *multipart.FileHeader) (string, error) { + pathImage := "/uploads/identitycards/" + cardPhotoDir := "./public" + os.Getenv("BASE_URL") + pathImage + if _, err := os.Stat(cardPhotoDir); os.IsNotExist(err) { + + if err := os.MkdirAll(cardPhotoDir, os.ModePerm); err != nil { + return "", fmt.Errorf("failed to create directory for identity card photo: %v", err) + } + } + + allowedExtensions := map[string]bool{".jpg": true, ".jpeg": true, ".png": true} + extension := filepath.Ext(cardPhoto.Filename) + if !allowedExtensions[extension] { + return "", fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed") + } + + cardPhotoFileName := fmt.Sprintf("%s_cardphoto%s", userID, extension) + cardPhotoPath := filepath.Join(cardPhotoDir, cardPhotoFileName) + + src, err := cardPhoto.Open() + if err != nil { + return "", fmt.Errorf("failed to open uploaded file: %v", err) + } + defer src.Close() + + dst, err := os.Create(cardPhotoPath) + if err != nil { + return "", fmt.Errorf("failed to create card photo file: %v", err) + } + defer dst.Close() + + if _, err := dst.ReadFrom(src); err != nil { + return "", fmt.Errorf("failed to save card photo: %v", err) + } + + cardPhotoURL := fmt.Sprintf("%s%s", pathImage, cardPhotoFileName) + + return cardPhotoURL, nil +} + +func deleteIdentityCardImage(imagePath string) error { + if imagePath == "" { + return nil + } + + baseDir := "./public/" + os.Getenv("BASE_URL") + absolutePath := baseDir + imagePath + + if _, err := os.Stat(absolutePath); os.IsNotExist(err) { + return fmt.Errorf("image file not found: %v", err) + } + + err := os.Remove(absolutePath) + if err != nil { + return fmt.Errorf("failed to delete image: %v", err) + } + + log.Printf("Image deleted successfully: %s", absolutePath) + return nil +} + +func (s *identityCardService) CreateIdentityCard(userID string, request *dto.RequestIdentityCardDTO, cardPhoto *multipart.FileHeader) (*dto.ResponseIdentityCardDTO, error) { + + errors, valid := request.ValidateIdentityCardInput() + if !valid { + return nil, fmt.Errorf("validation failed: %v", errors) + } + + cardPhotoPath, err := s.saveIdentityCardImage(userID, cardPhoto) + if err != nil { + return nil, fmt.Errorf("failed to save card photo: %v", err) + } + + identityCard := &model.IdentityCard{ + UserID: userID, + Identificationumber: request.Identificationumber, + Placeofbirth: request.Placeofbirth, + Dateofbirth: request.Dateofbirth, + Gender: request.Gender, + BloodType: request.BloodType, + District: request.District, + Village: request.Village, + Neighbourhood: request.Neighbourhood, + Religion: request.Religion, + Maritalstatus: request.Maritalstatus, + Job: request.Job, + Citizenship: request.Citizenship, + Validuntil: request.Validuntil, + Cardphoto: cardPhotoPath, + } + + identityCard, err = s.identityCardRepo.CreateIdentityCard(identityCard) + if err != nil { + log.Printf("Error creating identity card: %v", err) + return nil, fmt.Errorf("failed to create identity card: %v", err) + } + + idcardResponseDTO, _ := FormatResponseIdentityCars(identityCard) + + return idcardResponseDTO, nil +} + +func (s *identityCardService) GetIdentityCardByID(id string) (*dto.ResponseIdentityCardDTO, error) { + + identityCard, err := s.identityCardRepo.GetIdentityCardByID(id) + if err != nil { + log.Printf("Error fetching identity card: %v", err) + return nil, fmt.Errorf("failed to fetch identity card") + } + + idcardResponseDTO, _ := FormatResponseIdentityCars(identityCard) + + return idcardResponseDTO, nil + +} + +func (s *identityCardService) GetIdentityCardsByUserID(userID string) ([]dto.ResponseIdentityCardDTO, error) { + + identityCards, err := s.identityCardRepo.GetIdentityCardsByUserID(userID) + if err != nil { + log.Printf("Error fetching identity cards by userID: %v", err) + return nil, fmt.Errorf("failed to fetch identity cards by userID") + } + + var response []dto.ResponseIdentityCardDTO + for _, card := range identityCards { + + idcardResponseDTO, err := FormatResponseIdentityCars(&card) + if err != nil { + log.Printf("Error creating response DTO for identity card ID %v: %v", card.ID, err) + + continue + } + response = append(response, *idcardResponseDTO) + } + + return response, nil +} + +func (s *identityCardService) UpdateIdentityCard(userID string, id string, request *dto.RequestIdentityCardDTO, cardPhoto *multipart.FileHeader) (*dto.ResponseIdentityCardDTO, error) { + + errors, valid := request.ValidateIdentityCardInput() + if !valid { + return nil, fmt.Errorf("validation failed: %v", errors) + } + + identityCard, err := s.identityCardRepo.GetIdentityCardByID(id) + if err != nil { + return nil, fmt.Errorf("identity card not found: %v", err) + } + + if identityCard.Cardphoto != "" { + err := deleteIdentityCardImage(identityCard.Cardphoto) + if err != nil { + return nil, fmt.Errorf("failed to delete old image: %v", err) + } + } + + var cardPhotoPath string + if cardPhoto != nil { + cardPhotoPath, err = s.saveIdentityCardImage(userID, cardPhoto) + if err != nil { + return nil, fmt.Errorf("failed to save card photo: %v", err) + } + } + + identityCard.Identificationumber = request.Identificationumber + identityCard.Placeofbirth = request.Placeofbirth + identityCard.Dateofbirth = request.Dateofbirth + identityCard.Gender = request.Gender + identityCard.BloodType = request.BloodType + identityCard.District = request.District + identityCard.Village = request.Village + identityCard.Neighbourhood = request.Neighbourhood + identityCard.Religion = request.Religion + identityCard.Maritalstatus = request.Maritalstatus + identityCard.Job = request.Job + identityCard.Citizenship = request.Citizenship + identityCard.Validuntil = request.Validuntil + if cardPhotoPath != "" { + identityCard.Cardphoto = cardPhotoPath + } + + identityCard, err = s.identityCardRepo.UpdateIdentityCard(id, identityCard) + if err != nil { + log.Printf("Error updating identity card: %v", err) + return nil, fmt.Errorf("failed to update identity card: %v", err) + } + + idcardResponseDTO, _ := FormatResponseIdentityCars(identityCard) + + return idcardResponseDTO, nil +} + +func (s *identityCardService) DeleteIdentityCard(id string) error { + + identityCard, err := s.identityCardRepo.GetIdentityCardByID(id) + if err != nil { + return fmt.Errorf("identity card not found: %v", err) + } + + if identityCard.Cardphoto != "" { + err := deleteIdentityCardImage(identityCard.Cardphoto) + if err != nil { + return fmt.Errorf("failed to delete card photo: %v", err) + } + } + + err = s.identityCardRepo.DeleteIdentityCard(id) + if err != nil { + return fmt.Errorf("failed to delete identity card: %v", err) + } + + return nil +} diff --git a/presentation/identitycard_route.go b/presentation/identitycard_route.go new file mode 100644 index 0000000..dfb187d --- /dev/null +++ b/presentation/identitycard_route.go @@ -0,0 +1,27 @@ +package presentation + +import ( + "rijig/config" + "rijig/internal/handler" + "rijig/internal/repositories" + "rijig/internal/services" + "rijig/middleware" + "rijig/utils" + + "github.com/gofiber/fiber/v2" +) + +func IdentityCardRouter(api fiber.Router) { + identityCardRepo := repositories.NewIdentityCardRepository(config.DB) + identityCardService := services.NewIdentityCardService(identityCardRepo) + identityCardHandler := handler.NewIdentityCardHandler(identityCardService) + + identityCardApi := api.Group("/identitycard") + identityCardApi.Use(middleware.AuthMiddleware) + + identityCardApi.Post("/create", middleware.RoleMiddleware(utils.RolePengelola, utils.RolePengepul), identityCardHandler.CreateIdentityCard) + identityCardApi.Get("/get/:identity_id", identityCardHandler.GetIdentityCardById) + identityCardApi.Get("/get", identityCardHandler.GetIdentityCard) + identityCardApi.Put("/update/:identity_id", middleware.RoleMiddleware(utils.RolePengelola, utils.RolePengepul), identityCardHandler.UpdateIdentityCard) + identityCardApi.Delete("/delete/:identity_id", middleware.RoleMiddleware(utils.RolePengelola, utils.RolePengepul), identityCardHandler.DeleteIdentityCard) +} diff --git a/router/setup_routes.go.go b/router/setup_routes.go.go index feae5f6..8846a88 100644 --- a/router/setup_routes.go.go +++ b/router/setup_routes.go.go @@ -11,10 +11,10 @@ import ( ) func SetupRoutes(app *fiber.App) { - app.Static(os.Getenv("BASE_URL")+"/uploads", "./public"+os.Getenv("BASE_URL")+"/uploads") api := app.Group(os.Getenv("BASE_URL")) api.Use(middleware.APIKeyMiddleware) + api.Static("/uploads", "./public"+os.Getenv("BASE_URL")+"/uploads") // || auth router || // // presentation.AuthRouter(api) @@ -23,6 +23,8 @@ func SetupRoutes(app *fiber.App) { presentationn.AuthPengepulRouter(api) presentationn.AuthMasyarakatRouter(api) // || auth router || // + presentation.IdentityCardRouter(api) + presentation.UserProfileRouter(api) presentation.UserPinRouter(api) presentation.RoleRouter(api)