diff --git a/config/database.go b/config/database.go index 263659e..9bb56e9 100644 --- a/config/database.go +++ b/config/database.go @@ -47,7 +47,12 @@ func ConnectDatabase() { &model.Address{}, &model.IdentityCard{}, &model.CompanyProfile{}, + // =>user preparation<= + // =>requestpickup preparation<= + &model.RequestPickup{}, + &model.RequestPickupItem{}, + // =>requestpickup preparation<= // =>store preparation<= &model.Store{}, diff --git a/dto/requestpickup_dto.go b/dto/requestpickup_dto.go new file mode 100644 index 0000000..73764bf --- /dev/null +++ b/dto/requestpickup_dto.go @@ -0,0 +1,80 @@ +package dto + +import ( + "fmt" + "strings" +) + +type RequestPickup struct { + RequestItems []RequestPickupItem `json:"request_items"` + EvidenceImage string `json:"evidence_image"` + AddressID string `json:"address_id"` +} + +type RequestPickupItem struct { + TrashCategoryID string `json:"trash_category_id"` + EstimatedAmount float64 `json:"estimated_amount"` +} + +type ResponseRequestPickup struct { + ID string `json:"id"` + UserId string `json:"user_id"` + AddressID string `json:"address_id"` + EvidenceImage string `json:"evidence_image"` + StatusPickup string `json:"status_pickup"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + RequestItems []ResponseRequestPickupItem `json:"request_items"` +} + +type ResponseRequestPickupItem struct { + ID string `json:"id"` + // TrashCategoryID string `json:"trash_category_id"` + TrashCategoryName string `json:"trash_category_name"` + EstimatedAmount float64 `json:"estimated_amount"` +} + +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 +} diff --git a/internal/handler/requestpickup_handler.go b/internal/handler/requestpickup_handler.go new file mode 100644 index 0000000..4ce19fe --- /dev/null +++ b/internal/handler/requestpickup_handler.go @@ -0,0 +1,101 @@ +package handler + +import ( + "fmt" + "rijig/dto" + "rijig/internal/services" + "rijig/utils" + + "github.com/gofiber/fiber/v2" +) + +type RequestPickupHandler struct { + service services.RequestPickupService +} + +func NewRequestPickupHandler(service services.RequestPickupService) *RequestPickupHandler { + return &RequestPickupHandler{service: service} +} + +func (h *RequestPickupHandler) CreateRequestPickup(c *fiber.Ctx) error { + userID, ok := c.Locals("userID").(string) + if !ok || userID == "" { + return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found") + } + + var request dto.RequestPickup + + if err := c.BodyParser(&request); err != nil { + return utils.GenericResponse(c, fiber.StatusBadRequest, "Invalid request body") + } + + errors, valid := request.ValidateRequestPickup() + if !valid { + return utils.ValidationErrorResponse(c, errors) + } + + response, err := h.service.CreateRequestPickup(request, userID) + if err != nil { + return utils.InternalServerErrorResponse(c, fmt.Sprintf("Error creating request pickup: %v", err)) + } + + return utils.SuccessResponse(c, response, "Request pickup created successfully") +} + +func (h *RequestPickupHandler) GetRequestPickupByID(c *fiber.Ctx) error { + id := c.Params("id") + + response, err := h.service.GetRequestPickupByID(id) + if err != nil { + return utils.GenericResponse(c, fiber.StatusNotFound, fmt.Sprintf("Request pickup with ID %s not found: %v", id, err)) + } + + return utils.SuccessResponse(c, response, "Request pickup retrieved successfully") +} + +func (h *RequestPickupHandler) GetAllRequestPickups(c *fiber.Ctx) error { + + response, err := h.service.GetAllRequestPickups() + if err != nil { + return utils.InternalServerErrorResponse(c, fmt.Sprintf("Error fetching all request pickups: %v", err)) + } + + return utils.SuccessResponse(c, response, "All request pickups retrieved successfully") +} + +func (h *RequestPickupHandler) UpdateRequestPickup(c *fiber.Ctx) error { + userID, ok := c.Locals("userID").(string) + if !ok || userID == "" { + return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found") + } + + id := c.Params("id") + var request dto.RequestPickup + + if err := c.BodyParser(&request); err != nil { + return utils.GenericResponse(c, fiber.StatusBadRequest, "Invalid request body") + } + + errors, valid := request.ValidateRequestPickup() + if !valid { + return utils.ValidationErrorResponse(c, errors) + } + + response, err := h.service.UpdateRequestPickup(id, request) + if err != nil { + return utils.InternalServerErrorResponse(c, fmt.Sprintf("Error updating request pickup: %v", err)) + } + + return utils.SuccessResponse(c, response, "Request pickup updated successfully") +} + +func (h *RequestPickupHandler) DeleteRequestPickup(c *fiber.Ctx) error { + id := c.Params("id") + + err := h.service.DeleteRequestPickup(id) + if err != nil { + return utils.GenericResponse(c, fiber.StatusNotFound, fmt.Sprintf("Request pickup with ID %s not found: %v", id, err)) + } + + return utils.GenericResponse(c, fiber.StatusOK, "Request pickup deleted successfully") +} diff --git a/internal/repositories/requestpickup_repo.go b/internal/repositories/requestpickup_repo.go new file mode 100644 index 0000000..912c83e --- /dev/null +++ b/internal/repositories/requestpickup_repo.go @@ -0,0 +1,114 @@ +package repositories + +import ( + "fmt" + "rijig/model" + + "gorm.io/gorm" +) + +type RequestPickupRepository interface { + CreateRequestPickup(request *model.RequestPickup) error + CreateRequestPickupItem(item *model.RequestPickupItem) error + FindRequestPickupByID(id string) (*model.RequestPickup, error) + FindAllRequestPickups() ([]model.RequestPickup, error) + UpdateRequestPickup(id string, request *model.RequestPickup) error + DeleteRequestPickup(id string) error + FindRequestPickupByAddressAndCategory(addressID string, trashCategoryID string) (*model.RequestPickup, error) + FindRequestPickupByAddressAndStatus(userId, status string) (*model.RequestPickup, error) +} + +type requestPickupRepository struct { + DB *gorm.DB +} + +func NewRequestPickupRepository(db *gorm.DB) RequestPickupRepository { + return &requestPickupRepository{DB: db} +} + +func (r *requestPickupRepository) CreateRequestPickup(request *model.RequestPickup) error { + if err := r.DB.Create(request).Error; err != nil { + return fmt.Errorf("failed to create request pickup: %v", err) + } + + for _, item := range request.RequestItems { + item.RequestPickupId = request.ID + if err := r.DB.Create(&item).Error; err != nil { + return fmt.Errorf("failed to create request pickup item: %v", err) + } + } + + return nil +} + +func (r *requestPickupRepository) CreateRequestPickupItem(item *model.RequestPickupItem) error { + if err := r.DB.Create(item).Error; err != nil { + return fmt.Errorf("failed to create request pickup item: %v", err) + } + return nil +} + +func (r *requestPickupRepository) FindRequestPickupByID(id string) (*model.RequestPickup, error) { + var request model.RequestPickup + err := r.DB.Preload("RequestItems").First(&request, "id = ?", id).Error + if err != nil { + return nil, fmt.Errorf("request pickup with ID %s not found: %v", id, err) + } + return &request, nil +} + +func (r *requestPickupRepository) FindAllRequestPickups() ([]model.RequestPickup, error) { + var requests []model.RequestPickup + err := r.DB.Preload("RequestItems").Find(&requests).Error + if err != nil { + return nil, fmt.Errorf("failed to fetch all request pickups: %v", err) + } + return requests, nil +} + +func (r *requestPickupRepository) UpdateRequestPickup(id string, request *model.RequestPickup) error { + err := r.DB.Model(&model.RequestPickup{}).Where("id = ?", id).Updates(request).Error + if err != nil { + return fmt.Errorf("failed to update request pickup: %v", err) + } + return nil +} + +func (r *requestPickupRepository) DeleteRequestPickup(id string) error { + + if err := r.DB.Where("request_pickup_id = ?", id).Delete(&model.RequestPickupItem{}).Error; err != nil { + return fmt.Errorf("failed to delete request pickup items: %v", err) + } + + err := r.DB.Delete(&model.RequestPickup{}, "id = ?", id).Error + if err != nil { + return fmt.Errorf("failed to delete request pickup: %v", err) + } + return nil +} + +func (r *requestPickupRepository) FindRequestPickupByAddressAndCategory(addressID string, trashCategoryID string) (*model.RequestPickup, error) { + var request model.RequestPickup + err := r.DB.Joins("JOIN request_pickup_items ON request_pickups.id = request_pickup_items.request_pickup_id"). + Where("request_pickups.address_id = ? AND request_pickup_items.trash_category_id = ?", addressID, trashCategoryID). + First(&request).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, fmt.Errorf("error checking request pickup for address %s and category %s: %v", addressID, trashCategoryID, err) + } + return &request, nil +} + +func (r *requestPickupRepository) FindRequestPickupByAddressAndStatus(userId, status string) (*model.RequestPickup, error) { + var request model.RequestPickup + err := r.DB.Where("user_id = ? AND status_pickup = ?", userId, status).First(&request).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, fmt.Errorf("failed to check existing request pickup: %v", err) + } + return &request, nil +} diff --git a/internal/services/requestpickup_service.go b/internal/services/requestpickup_service.go new file mode 100644 index 0000000..86d4276 --- /dev/null +++ b/internal/services/requestpickup_service.go @@ -0,0 +1,181 @@ +package services + +import ( + "fmt" + "rijig/dto" + "rijig/internal/repositories" + "rijig/model" +) + +type RequestPickupService interface { + CreateRequestPickup(request dto.RequestPickup, UserId string) (*dto.ResponseRequestPickup, error) + GetRequestPickupByID(id string) (*dto.ResponseRequestPickup, error) + GetAllRequestPickups() ([]dto.ResponseRequestPickup, error) + UpdateRequestPickup(id string, request dto.RequestPickup) (*dto.ResponseRequestPickup, error) + DeleteRequestPickup(id string) error +} + +type requestPickupService struct { + repo repositories.RequestPickupRepository + repoAddress repositories.AddressRepository + repoTrash repositories.TrashRepository +} + +func NewRequestPickupService(repo repositories.RequestPickupRepository, + repoAddress repositories.AddressRepository, + repoTrash repositories.TrashRepository) RequestPickupService { + return &requestPickupService{repo: repo, repoAddress: repoAddress, repoTrash: repoTrash} +} + +func (s *requestPickupService) CreateRequestPickup(request dto.RequestPickup, UserId string) (*dto.ResponseRequestPickup, error) { + + errors, valid := request.ValidateRequestPickup() + if !valid { + return nil, fmt.Errorf("validation errors: %v", errors) + } + + findAddress, err := s.repoAddress.FindAddressByID(request.AddressID) + if err != nil { + return nil, fmt.Errorf("address with ID %s not found", request.AddressID) + } + + existingRequest, err := s.repo.FindRequestPickupByAddressAndStatus(UserId, "waiting_pengepul") + if err != nil { + return nil, fmt.Errorf("error checking for existing request pickup: %v", err) + } + if existingRequest != nil { + return nil, fmt.Errorf("there is already a pending pickup request for address %s", request.AddressID) + } + + modelRequest := model.RequestPickup{ + UserId: UserId, + AddressId: findAddress.ID, + EvidenceImage: request.EvidenceImage, + } + + err = s.repo.CreateRequestPickup(&modelRequest) + if err != nil { + return nil, fmt.Errorf("failed to create request pickup: %v", err) + } + + response := &dto.ResponseRequestPickup{ + ID: modelRequest.ID, + UserId: UserId, + AddressID: modelRequest.AddressId, + EvidenceImage: modelRequest.EvidenceImage, + StatusPickup: modelRequest.StatusPickup, + CreatedAt: modelRequest.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: modelRequest.UpdatedAt.Format("2006-01-02 15:04:05"), + } + + for _, item := range request.RequestItems { + + findTrashCategory, err := s.repoTrash.GetCategoryByID(item.TrashCategoryID) + if err != nil { + return nil, fmt.Errorf("trash category with ID %s not found", item.TrashCategoryID) + } + + modelItem := model.RequestPickupItem{ + RequestPickupId: modelRequest.ID, + TrashCategoryId: findTrashCategory.ID, + EstimatedAmount: item.EstimatedAmount, + } + err = s.repo.CreateRequestPickupItem(&modelItem) + if err != nil { + return nil, fmt.Errorf("failed to create request pickup item: %v", err) + } + + response.RequestItems = append(response.RequestItems, dto.ResponseRequestPickupItem{ + ID: modelItem.ID, + TrashCategoryName: findTrashCategory.Name, + EstimatedAmount: modelItem.EstimatedAmount, + }) + } + + return response, nil +} + +func (s *requestPickupService) GetRequestPickupByID(id string) (*dto.ResponseRequestPickup, error) { + + request, err := s.repo.FindRequestPickupByID(id) + if err != nil { + return nil, fmt.Errorf("error fetching request pickup with ID %s: %v", id, err) + } + + response := &dto.ResponseRequestPickup{ + ID: request.ID, + UserId: request.UserId, + AddressID: request.AddressId, + EvidenceImage: request.EvidenceImage, + StatusPickup: request.StatusPickup, + CreatedAt: request.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: request.UpdatedAt.Format("2006-01-02 15:04:05"), + } + + return response, nil +} + +func (s *requestPickupService) GetAllRequestPickups() ([]dto.ResponseRequestPickup, error) { + + requests, err := s.repo.FindAllRequestPickups() + if err != nil { + return nil, fmt.Errorf("error fetching all request pickups: %v", err) + } + + var response []dto.ResponseRequestPickup + for _, request := range requests { + response = append(response, dto.ResponseRequestPickup{ + ID: request.ID, + UserId: request.UserId, + AddressID: request.AddressId, + EvidenceImage: request.EvidenceImage, + StatusPickup: request.StatusPickup, + CreatedAt: request.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: request.UpdatedAt.Format("2006-01-02 15:04:05"), + }) + } + + return response, nil +} + +func (s *requestPickupService) UpdateRequestPickup(id string, request dto.RequestPickup) (*dto.ResponseRequestPickup, error) { + + errors, valid := request.ValidateRequestPickup() + if !valid { + return nil, fmt.Errorf("validation errors: %v", errors) + } + + existingRequest, err := s.repo.FindRequestPickupByID(id) + if err != nil { + return nil, fmt.Errorf("request pickup with ID %s not found: %v", id, err) + } + + existingRequest.EvidenceImage = request.EvidenceImage + + err = s.repo.UpdateRequestPickup(id, existingRequest) + if err != nil { + return nil, fmt.Errorf("failed to update request pickup: %v", err) + } + + response := &dto.ResponseRequestPickup{ + ID: existingRequest.ID, + UserId: existingRequest.UserId, + AddressID: existingRequest.AddressId, + EvidenceImage: existingRequest.EvidenceImage, + StatusPickup: existingRequest.StatusPickup, + CreatedAt: existingRequest.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: existingRequest.UpdatedAt.Format("2006-01-02 15:04:05"), + } + + return response, nil +} + +func (s *requestPickupService) DeleteRequestPickup(id string) error { + + err := s.repo.DeleteRequestPickup(id) + if err != nil { + return fmt.Errorf("failed to delete request pickup with ID %s: %v", id, err) + } + + return nil +} diff --git a/model/requestpickup_model.go b/model/requestpickup_model.go new file mode 100644 index 0000000..584cbb2 --- /dev/null +++ b/model/requestpickup_model.go @@ -0,0 +1,24 @@ +package model + +import ( + "time" +) + +type RequestPickup struct { + ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` + UserId string `gorm:"not null" json:"user_id"` + AddressId string `gorm:"not null" json:"address_id"` + RequestItems []RequestPickupItem `gorm:"foreignKey:RequestPickupId;constraint:OnDelete:CASCADE;" json:"request_items"` + EvidenceImage string `json:"evidence_image"` + StatusPickup string `gorm:"default:'waiting_pengepul'" json:"status_pickup"` + CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"` + UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"` +} + +type RequestPickupItem struct { + ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` + RequestPickupId string `gorm:"not null" json:"request_pickup_id"` + TrashCategoryId string `gorm:"not null" json:"trash_category_id"` + TrashDetailId string `json:"trash_detail_id,omitempty"` + EstimatedAmount float64 `gorm:"not null" json:"estimated_amount"` +} diff --git a/presentation/requestpickup_route.go b/presentation/requestpickup_route.go new file mode 100644 index 0000000..6c63262 --- /dev/null +++ b/presentation/requestpickup_route.go @@ -0,0 +1,31 @@ +package presentation + +import ( + "rijig/config" + "rijig/internal/handler" + "rijig/internal/repositories" + "rijig/internal/services" + "rijig/middleware" + + "github.com/gofiber/fiber/v2" +) + +func RequestPickupRouter(api fiber.Router) { + + requestRepo := repositories.NewRequestPickupRepository(config.DB) + repoTrash := repositories.NewTrashRepository(config.DB) + repoAddress := repositories.NewAddressRepository(config.DB) + + requestPickupServices := services.NewRequestPickupService(requestRepo, repoAddress, repoTrash) + + requestPickupHandler := handler.NewRequestPickupHandler(requestPickupServices) + + requestPickupAPI := api.Group("/requestpickup") + requestPickupAPI.Use(middleware.AuthMiddleware) + + requestPickupAPI.Post("/", requestPickupHandler.CreateRequestPickup) + // requestPickupAPI.Get("/:id", requestPickupHandler.GetRequestPickupByID) + // requestPickupAPI.Get("/", requestPickupHandler.GetAllRequestPickups) + // requestPickupAPI.Put("/:id", requestPickupHandler.UpdateRequestPickup) + // requestPickupAPI.Delete("/:id", requestPickupHandler.DeleteRequestPickup) +} diff --git a/router/setup_routes.go.go b/router/setup_routes.go.go index f313766..8b3a343 100644 --- a/router/setup_routes.go.go +++ b/router/setup_routes.go.go @@ -25,6 +25,7 @@ func SetupRoutes(app *fiber.App) { // || auth router || // presentation.IdentityCardRouter(api) presentation.CompanyProfileRouter(api) + presentation.RequestPickupRouter(api) presentation.UserProfileRouter(api) presentation.UserPinRouter(api)