feat&fix: fixing debug and add confirmed feature for request pickup

This commit is contained in:
pahmiudahgede 2025-05-13 02:08:35 +07:00
parent 47ccae326a
commit 61dda3f4eb
21 changed files with 575 additions and 170 deletions

View File

@ -42,6 +42,7 @@ func ConnectDatabase() {
// ==============main feature============== // ==============main feature==============
// =>user preparation<= // =>user preparation<=
&model.User{}, &model.User{},
&model.Collector{},
&model.Role{}, &model.Role{},
&model.UserPin{}, &model.UserPin{},
&model.Address{}, &model.Address{},

View File

@ -11,8 +11,8 @@ type AddressResponseDTO struct {
Village string `json:"village"` Village string `json:"village"`
PostalCode string `json:"postalCode"` PostalCode string `json:"postalCode"`
Detail string `json:"detail"` Detail string `json:"detail"`
Latitude string `json:"latitude"` Latitude float64 `json:"latitude"`
Longitude string `json:"longitude"` Longitude float64 `json:"longitude"`
CreatedAt string `json:"createdAt"` CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"` UpdatedAt string `json:"updatedAt"`
} }
@ -24,8 +24,8 @@ type CreateAddressDTO struct {
Village string `json:"village_id"` Village string `json:"village_id"`
PostalCode string `json:"postalCode"` PostalCode string `json:"postalCode"`
Detail string `json:"detail"` Detail string `json:"detail"`
Latitude string `json:"latitude"` Latitude float64 `json:"latitude"`
Longitude string `json:"longitude"` Longitude float64 `json:"longitude"`
} }
func (r *CreateAddressDTO) ValidateAddress() (map[string][]string, bool) { func (r *CreateAddressDTO) ValidateAddress() (map[string][]string, bool) {
@ -34,28 +34,35 @@ func (r *CreateAddressDTO) ValidateAddress() (map[string][]string, bool) {
if strings.TrimSpace(r.Province) == "" { if strings.TrimSpace(r.Province) == "" {
errors["province_id"] = append(errors["province_id"], "Province ID is required") errors["province_id"] = append(errors["province_id"], "Province ID is required")
} }
if strings.TrimSpace(r.Regency) == "" { if strings.TrimSpace(r.Regency) == "" {
errors["regency_id"] = append(errors["regency_id"], "Regency ID is required") errors["regency_id"] = append(errors["regency_id"], "Regency ID is required")
} }
if strings.TrimSpace(r.District) == "" { if strings.TrimSpace(r.District) == "" {
errors["district_id"] = append(errors["district_id"], "District ID is required") errors["district_id"] = append(errors["district_id"], "District ID is required")
} }
if strings.TrimSpace(r.Village) == "" { if strings.TrimSpace(r.Village) == "" {
errors["village_id"] = append(errors["village_id"], "Village ID is required") errors["village_id"] = append(errors["village_id"], "Village ID is required")
} }
if strings.TrimSpace(r.PostalCode) == "" { if strings.TrimSpace(r.PostalCode) == "" {
errors["postalCode"] = append(errors["village_id"], "PostalCode ID is required") errors["postalCode"] = append(errors["postalCode"], "PostalCode is required")
} else if len(r.PostalCode) < 5 { } else if len(r.PostalCode) < 5 {
errors["postalCode"] = append(errors["postalCode"], "kode pos belum sesuai") errors["postalCode"] = append(errors["postalCode"], "PostalCode must be at least 5 characters")
} }
if strings.TrimSpace(r.Detail) == "" { if strings.TrimSpace(r.Detail) == "" {
errors["detail"] = append(errors["detail"], "Detail address is required") errors["detail"] = append(errors["detail"], "Detail address is required")
} }
if strings.TrimSpace(r.Latitude) == "" {
errors["latitude"] = append(errors["latitude"], "Geographic coordinates are required") if r.Latitude == 0 {
errors["latitude"] = append(errors["latitude"], "Latitude is required")
} }
if strings.TrimSpace(r.Longitude) == "" {
errors["longitude"] = append(errors["longitude"], "Geographic coordinates are required") if r.Longitude == 0 {
errors["longitude"] = append(errors["longitude"], "Longitude is required")
} }
if len(errors) > 0 { if len(errors) > 0 {

31
dto/collector_dto.go Normal file
View File

@ -0,0 +1,31 @@
package dto
import "strings"
type RequestCollectorDTO struct {
UserId string `json:"user_id"`
AddressId string `json:"address_id"`
}
type ResponseCollectorDTO struct {
ID string `json:"collector_id"`
UserId string `json:"user_id"`
AddressId string `json:"address_id"`
JobStatus string `json:"job_status"`
Rating float32 `json:"rating"`
// CreatedAt string `json:"createdAt"`
// UpdatedAt string `json:"updatedAt"`
}
func (r *RequestCollectorDTO) ValidateRequestColector() (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")
}
if len(errors) > 0 {
return errors, false
}
return nil, true
}

View File

@ -9,6 +9,7 @@ type RequestPickup struct {
RequestItems []RequestPickupItem `json:"request_items"` RequestItems []RequestPickupItem `json:"request_items"`
EvidenceImage string `json:"evidence_image"` EvidenceImage string `json:"evidence_image"`
AddressID string `json:"address_id"` AddressID string `json:"address_id"`
RequestMethod string `json:"request_method"`
} }
type RequestPickupItem struct { type RequestPickupItem struct {
@ -17,14 +18,16 @@ type RequestPickupItem struct {
} }
type ResponseRequestPickup struct { type ResponseRequestPickup struct {
ID string `json:"id"` ID string `json:"id,omitempty"`
UserId string `json:"user_id"` UserId string `json:"user_id,omitempty"`
AddressID string `json:"address_id"` AddressID string `json:"address_id,omitempty"`
EvidenceImage string `json:"evidence_image"` EvidenceImage string `json:"evidence_image,omitempty"`
StatusPickup string `json:"status_pickup"` StatusPickup string `json:"status_pickup,omitempty"`
CreatedAt string `json:"created_at"` CollectorID string `json:"collectorid,omitempty"`
UpdatedAt string `json:"updated_at"` ConfirmedByCollectorAt string `json:"confirmedat,omitempty"`
RequestItems []ResponseRequestPickupItem `json:"request_items"` CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
RequestItems []ResponseRequestPickupItem `json:"request_items,omitempty"`
} }
type ResponseRequestPickupItem struct { type ResponseRequestPickupItem struct {

1
go.mod
View File

@ -14,7 +14,6 @@ require (
) )
require ( require (
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26 // indirect
golang.org/x/term v0.30.0 // indirect golang.org/x/term v0.30.0 // indirect
rsc.io/qr v0.2.0 // indirect rsc.io/qr v0.2.0 // indirect
) )

2
go.sum
View File

@ -72,8 +72,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26 h1:UFHFmFfixpmfRBcxuu+LA9l8MdURWVdVNUHxO5n1d2w=
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26/go.mod h1:IGhd0qMDsUa9acVjsbsT7bu3ktadtGOHI79+idTew/M=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=

View File

@ -0,0 +1,36 @@
package handler
import (
"rijig/internal/services"
"rijig/utils"
"github.com/gofiber/fiber/v2"
)
type CollectorHandler struct {
service services.CollectorService
}
func NewCollectorHandler(service services.CollectorService) *CollectorHandler {
return &CollectorHandler{service}
}
func (h *CollectorHandler) ConfirmRequestPickup(c *fiber.Ctx) error {
collectorId, ok := c.Locals("userID").(string)
if !ok || collectorId == "" {
return utils.GenericResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
}
requestPickupId := c.Params("id")
if requestPickupId == "" {
return utils.ErrorResponse(c, "RequestPickup ID is required")
}
req, err := h.service.ConfirmRequestPickup(requestPickupId, collectorId)
if err != nil {
return utils.ErrorResponse(c, err.Error())
}
return utils.SuccessResponse(c, req, "Request pickup confirmed successfully")
}

View File

@ -53,49 +53,32 @@ func (h *RequestPickupHandler) GetRequestPickupByID(c *fiber.Ctx) error {
return utils.SuccessResponse(c, response, "Request pickup retrieved successfully") return utils.SuccessResponse(c, response, "Request pickup retrieved successfully")
} }
func (h *RequestPickupHandler) GetAllRequestPickups(c *fiber.Ctx) error { // func (h *RequestPickupHandler) GetAutomaticRequestByUser(c *fiber.Ctx) error {
response, err := h.service.GetAllRequestPickups() // collectorId, ok := c.Locals("userID").(string)
// if !ok || collectorId == "" {
// return utils.ErrorResponse(c, "Unauthorized: User session not found")
// }
// requestPickups, err := h.service.GetAllAutomaticRequestPickup(collectorId)
// if err != nil {
// return utils.ErrorResponse(c, err.Error())
// }
// return utils.SuccessResponse(c, requestPickups, "Request pickups fetched successfully")
// }
func (h *RequestPickupHandler) GetRequestPickups(c *fiber.Ctx) error {
// Get userID from Locals
collectorId := c.Locals("userID").(string)
// Call service layer to get the request pickups
requests, err := h.service.GetRequestPickupsForCollector(collectorId)
if err != nil { if err != nil {
return utils.InternalServerErrorResponse(c, fmt.Sprintf("Error fetching all request pickups: %v", err)) return utils.ErrorResponse(c, err.Error())
} }
return utils.SuccessResponse(c, response, "All request pickups retrieved successfully") // Return response
} return utils.SuccessResponse(c, requests, "Automatic 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")
} }

View File

@ -0,0 +1,105 @@
package repositories
import (
"errors"
"fmt"
"log"
"rijig/model"
"rijig/utils"
"gorm.io/gorm"
)
type CollectorRepository interface {
FindActiveCollectors() ([]model.Collector, error)
FindCollectorById(collector_id string) (*model.Collector, error)
CreateCollector(collector *model.Collector) error
UpdateCollector(userId string, jobStatus string) (*model.Collector, error)
FindAllAutomaticMethodRequestWithDistance(requestMethod, statuspickup string, collectorLat, collectorLon float64, maxDistance float64) ([]model.RequestPickup, error)
}
type collectorRepository struct {
DB *gorm.DB
}
func NewCollectorRepository(db *gorm.DB) CollectorRepository {
return &collectorRepository{DB: db}
}
func (r *collectorRepository) FindActiveCollectors() ([]model.Collector, error) {
var collectors []model.Collector
err := r.DB.Where("job_status = ?", "active").First(&collectors).Error
if err != nil {
return nil, fmt.Errorf("failed to fetch active collectors: %v", err)
}
return collectors, nil
}
func (r *collectorRepository) FindCollectorById(collector_id string) (*model.Collector, error) {
var collector model.Collector
err := r.DB.Where("user_id = ?", collector_id).First(&collector).Error
if err != nil {
return nil, fmt.Errorf("error fetching collector: %v", err)
}
fmt.Printf("menampilkan data collector %v", &collector)
return &collector, nil
}
func (r *collectorRepository) CreateCollector(collector *model.Collector) error {
if err := r.DB.Create(collector).Error; err != nil {
return fmt.Errorf("failed to create collector: %v", err)
}
return nil
}
func (r *collectorRepository) UpdateCollector(userId string, jobStatus string) (*model.Collector, error) {
var existingCollector model.Collector
if err := r.DB.Where("user_id = ?", userId).First(&existingCollector).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("collector dengan user_id %s tidak ditemukan", userId)
}
log.Printf("Gagal mencari collector: %v", err)
return nil, fmt.Errorf("gagal fetching collector: %w", err)
}
if jobStatus != "active" && jobStatus != "nonactive" {
return nil, fmt.Errorf("invalid job status: %v", jobStatus)
}
if err := r.DB.Model(&existingCollector).Update("jobstatus", jobStatus).Error; err != nil {
log.Printf("Gagal mengupdate data collector: %v", err)
return nil, fmt.Errorf("gagal mengupdate job status untuk collector: %w", err)
}
return &existingCollector, nil
}
// #====experimen====#
func (r *collectorRepository) FindAllAutomaticMethodRequestWithDistance(requestMethod, statuspickup string, collectorLat, collectorLon float64, maxDistance float64) ([]model.RequestPickup, error) {
var requests []model.RequestPickup
err := r.DB.Preload("RequestItems").
Where("request_method = ? AND status_pickup = ?", requestMethod, statuspickup).
Find(&requests).Error
if err != nil {
return nil, fmt.Errorf("error fetching request pickups with request_method '%s' and status '%s': %v", requestMethod, statuspickup, err)
}
var nearbyRequests []model.RequestPickup
for _, request := range requests {
address := request.Address
requestCoord := utils.Coord{Lat: address.Latitude, Lon: address.Longitude}
collectorCoord := utils.Coord{Lat: collectorLat, Lon: collectorLon}
_, km := utils.Distance(requestCoord, collectorCoord)
if km <= maxDistance {
nearbyRequests = append(nearbyRequests, request)
}
}
return nearbyRequests, nil
}

View File

@ -11,11 +11,13 @@ type RequestPickupRepository interface {
CreateRequestPickup(request *model.RequestPickup) error CreateRequestPickup(request *model.RequestPickup) error
CreateRequestPickupItem(item *model.RequestPickupItem) error CreateRequestPickupItem(item *model.RequestPickupItem) error
FindRequestPickupByID(id string) (*model.RequestPickup, error) FindRequestPickupByID(id string) (*model.RequestPickup, error)
FindAllRequestPickups() ([]model.RequestPickup, error) FindAllRequestPickups(userId string) ([]model.RequestPickup, error)
FindAllAutomaticMethodRequest(requestMethod, statuspickup string) ([]model.RequestPickup, error)
FindRequestPickupByAddressAndStatus(userId, status string) (*model.RequestPickup, error)
GetRequestPickupItems(requestPickupId string) ([]model.RequestPickupItem, error)
GetAutomaticRequestPickupsForCollector(collectorId string) ([]model.RequestPickup, error)
UpdateRequestPickup(id string, request *model.RequestPickup) error UpdateRequestPickup(id string, request *model.RequestPickup) error
DeleteRequestPickup(id string) error DeleteRequestPickup(id string) error
FindRequestPickupByAddressAndCategory(addressID string, trashCategoryID string) (*model.RequestPickup, error)
FindRequestPickupByAddressAndStatus(userId, status string) (*model.RequestPickup, error)
} }
type requestPickupRepository struct { type requestPickupRepository struct {
@ -57,20 +59,43 @@ func (r *requestPickupRepository) FindRequestPickupByID(id string) (*model.Reque
return &request, nil return &request, nil
} }
func (r *requestPickupRepository) FindAllRequestPickups() ([]model.RequestPickup, error) { func (r *requestPickupRepository) FindAllRequestPickups(userId string) ([]model.RequestPickup, error) {
var requests []model.RequestPickup var requests []model.RequestPickup
err := r.DB.Preload("RequestItems").Find(&requests).Error err := r.DB.Preload("RequestItems").Where("user_id = ?", userId).Find(&requests).Error
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch all request pickups: %v", err) return nil, fmt.Errorf("failed to fetch all request pickups: %v", err)
} }
return requests, nil return requests, nil
} }
func (r *requestPickupRepository) FindAllAutomaticMethodRequest(requestMethod, statuspickup string) ([]model.RequestPickup, error) {
var requests []model.RequestPickup
err := r.DB.Preload("RequestItems").Where("request_method = ? AND status_pickup = ?", requestMethod, statuspickup).Find(&requests).Error
if err != nil {
return nil, fmt.Errorf("error fetching request pickups with request_method %s: %v", requestMethod, err)
}
return requests, 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
}
func (r *requestPickupRepository) UpdateRequestPickup(id string, request *model.RequestPickup) error { func (r *requestPickupRepository) UpdateRequestPickup(id string, request *model.RequestPickup) error {
err := r.DB.Model(&model.RequestPickup{}).Where("id = ?", id).Updates(request).Error err := r.DB.Model(&model.RequestPickup{}).Where("id = ?", id).Updates(request).Error
if err != nil { if err != nil {
return fmt.Errorf("failed to update request pickup: %v", err) return fmt.Errorf("failed to update request pickup: %v", err)
} }
return nil return nil
} }
@ -87,28 +112,25 @@ func (r *requestPickupRepository) DeleteRequestPickup(id string) error {
return nil return nil
} }
func (r *requestPickupRepository) FindRequestPickupByAddressAndCategory(addressID string, trashCategoryID string) (*model.RequestPickup, error) { func (r *requestPickupRepository) GetAutomaticRequestPickupsForCollector(collectorId string) ([]model.RequestPickup, error) {
var request model.RequestPickup var requests []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). err := r.DB.Preload("Address").
First(&request).Error Where("request_method = ? AND status_pickup = ?", "otomatis", "waiting_collector").
Find(&requests).Error
if err != nil { if err != nil {
if err == gorm.ErrRecordNotFound { return nil, fmt.Errorf("error fetching pickup requests: %v", err)
return nil, nil
} }
return nil, fmt.Errorf("error checking request pickup for address %s and category %s: %v", addressID, trashCategoryID, err)
} return requests, nil
return &request, nil
} }
func (r *requestPickupRepository) FindRequestPickupByAddressAndStatus(userId, status string) (*model.RequestPickup, error) { func (r *requestPickupRepository) GetRequestPickupItems(requestPickupId string) ([]model.RequestPickupItem, error) {
var request model.RequestPickup var items []model.RequestPickupItem
err := r.DB.Where("user_id = ? AND status_pickup = ?", userId, status).First(&request).Error
err := r.DB.Preload("TrashCategory").Where("request_pickup_id = ?", requestPickupId).Find(&items).Error
if err != nil { if err != nil {
if err == gorm.ErrRecordNotFound { return nil, fmt.Errorf("error fetching request pickup items: %v", err)
return nil, nil
} }
return nil, fmt.Errorf("failed to check existing request pickup: %v", err) return items, nil
}
return &request, nil
} }

View File

@ -155,8 +155,8 @@ func (s *addressService) GetAddressByUserID(userID string) ([]dto.AddressRespons
Village: addressData["village"].(string), Village: addressData["village"].(string),
PostalCode: addressData["postalCode"].(string), PostalCode: addressData["postalCode"].(string),
Detail: addressData["detail"].(string), Detail: addressData["detail"].(string),
Latitude: addressData["latitude"].(string), Latitude: addressData["latitude"].(float64),
Longitude: addressData["longitude"].(string), Longitude: addressData["longitude"].(float64),
CreatedAt: addressData["createdAt"].(string), CreatedAt: addressData["createdAt"].(string),
UpdatedAt: addressData["updatedAt"].(string), UpdatedAt: addressData["updatedAt"].(string),
}) })
@ -227,8 +227,8 @@ func (s *addressService) GetAddressByID(userID, id string) (*dto.AddressResponse
Village: addressData["village"].(string), Village: addressData["village"].(string),
PostalCode: addressData["postalCode"].(string), PostalCode: addressData["postalCode"].(string),
Detail: addressData["detail"].(string), Detail: addressData["detail"].(string),
Latitude: addressData["latitude"].(string), Latitude: addressData["latitude"].(float64),
Longitude: addressData["longitude"].(string), Longitude: addressData["longitude"].(float64),
CreatedAt: addressData["createdAt"].(string), CreatedAt: addressData["createdAt"].(string),
UpdatedAt: addressData["updatedAt"].(string), UpdatedAt: addressData["updatedAt"].(string),
} }

View File

@ -67,9 +67,10 @@ func (s *authPengepulService) checkOTPRequestCooldown(phone string) error {
func (s *authPengepulService) sendOTP(phone string) error { func (s *authPengepulService) sendOTP(phone string) error {
otp := generateOTP() otp := generateOTP()
if err := config.SendWhatsAppMessage(phone, fmt.Sprintf("Your OTP is: %s", otp)); err != nil { fmt.Printf("ur otp is:%s", otp)
return err // if err := config.SendWhatsAppMessage(phone, fmt.Sprintf("Your OTP is: %s", otp)); err != nil {
} // return err
// }
if err := utils.SetStringData("otp:"+phone, otp, 10*time.Minute); err != nil { if err := utils.SetStringData("otp:"+phone, otp, 10*time.Minute); err != nil {
return err return err

View File

@ -0,0 +1,108 @@
package services
import (
"fmt"
"rijig/dto"
"rijig/internal/repositories"
"rijig/utils"
)
type CollectorService interface {
FindCollectorsNearby(userId string) ([]dto.ResponseCollectorDTO, error)
ConfirmRequestPickup(requestId, collectorId string) (*dto.ResponseRequestPickup, error)
}
type collectorService struct {
repo repositories.CollectorRepository
repoReq repositories.RequestPickupRepository
repoAddress repositories.AddressRepository
}
func NewCollectorService(repo repositories.CollectorRepository,
repoReq repositories.RequestPickupRepository,
repoAddress repositories.AddressRepository) CollectorService {
return &collectorService{repo: repo, repoReq: repoReq, repoAddress: repoAddress}
}
func (s *collectorService) FindCollectorsNearby(userId string) ([]dto.ResponseCollectorDTO, error) {
collectors, err := s.repo.FindActiveCollectors()
if err != nil {
return nil, fmt.Errorf("error fetching active collectors: %v", err)
}
request, err := s.repoReq.FindRequestPickupByAddressAndStatus(userId, "waiting_collector")
if err != nil {
return nil, fmt.Errorf("gagal mendapatkan data request pickup dengan userid: %v", err)
}
reqpickaddress, err := s.repoAddress.FindAddressByID(request.AddressId)
if err != nil {
return nil, fmt.Errorf("error fetching address for request pickup %s: %v", request.ID, err)
}
var nearbyCollectorsResponse []dto.ResponseCollectorDTO
var maxDistance = 10.0
for _, collector := range collectors {
address, err := s.repoAddress.FindAddressByID(collector.AddressId)
if err != nil {
return nil, fmt.Errorf("error fetching address for collector %s: %v", collector.ID, err)
}
collectorCoord := utils.Coord{Lat: reqpickaddress.Latitude, Lon: reqpickaddress.Longitude}
userCoord := utils.Coord{Lat: address.Latitude, Lon: address.Longitude}
_, km := utils.Distance(collectorCoord, userCoord)
if km <= maxDistance {
nearbyCollectorsResponse = append(nearbyCollectorsResponse, dto.ResponseCollectorDTO{
ID: collector.ID,
AddressId: collector.User.Name,
Rating: collector.Rating,
})
}
}
if len(nearbyCollectorsResponse) == 0 {
return nil, fmt.Errorf("no request pickups found within %v km", maxDistance)
}
return nearbyCollectorsResponse, nil
}
func (s *collectorService) ConfirmRequestPickup(requestId, collectorId string) (*dto.ResponseRequestPickup, error) {
request, err := s.repoReq.FindRequestPickupByID(requestId)
if err != nil {
return nil, fmt.Errorf("request pickup not found: %v", err)
}
if request.StatusPickup != "waiting_collector" {
return nil, fmt.Errorf("pickup request is not in 'waiting_collector' status")
}
collector, err := s.repo.FindCollectorById(collectorId)
if err != nil {
return nil, fmt.Errorf("collector tidak ditemukan: %v", err)
}
request.StatusPickup = "confirmed"
request.CollectorID = &collector.ID
err = s.repoReq.UpdateRequestPickup(requestId, request)
if err != nil {
return nil, fmt.Errorf("failed to update request pickup: %v", err)
}
confirmedAt, _ := utils.FormatDateToIndonesianFormat(request.ConfirmedByCollectorAt)
response := dto.ResponseRequestPickup{
StatusPickup: request.StatusPickup,
CollectorID: *request.CollectorID,
ConfirmedByCollectorAt: confirmedAt,
}
return &response, nil
}

View File

@ -5,18 +5,22 @@ import (
"rijig/dto" "rijig/dto"
"rijig/internal/repositories" "rijig/internal/repositories"
"rijig/model" "rijig/model"
"rijig/utils"
) )
type RequestPickupService interface { type RequestPickupService interface {
CreateRequestPickup(request dto.RequestPickup, UserId string) (*dto.ResponseRequestPickup, error) CreateRequestPickup(request dto.RequestPickup, UserId string) (*dto.ResponseRequestPickup, error)
GetRequestPickupByID(id string) (*dto.ResponseRequestPickup, error) GetRequestPickupByID(id string) (*dto.ResponseRequestPickup, error)
GetAllRequestPickups() ([]dto.ResponseRequestPickup, error) GetAllRequestPickups(userid string) ([]dto.ResponseRequestPickup, error)
UpdateRequestPickup(id string, request dto.RequestPickup) (*dto.ResponseRequestPickup, error) // GetAllAutomaticRequestPickups(collector_id string) ([]dto.ResponseRequestPickup, error)
DeleteRequestPickup(id string) error // GetAllAutomaticRequestPickup(collectorId string) ([]dto.ResponseRequestPickup, error)
GetRequestPickupsForCollector(collectorId string) ([]dto.ResponseRequestPickup, error)
} }
type requestPickupService struct { type requestPickupService struct {
repo repositories.RequestPickupRepository repo repositories.RequestPickupRepository
repoReq repositories.CollectorRepository
repoAddress repositories.AddressRepository repoAddress repositories.AddressRepository
repoTrash repositories.TrashRepository repoTrash repositories.TrashRepository
} }
@ -34,12 +38,12 @@ func (s *requestPickupService) CreateRequestPickup(request dto.RequestPickup, Us
return nil, fmt.Errorf("validation errors: %v", errors) return nil, fmt.Errorf("validation errors: %v", errors)
} }
findAddress, err := s.repoAddress.FindAddressByID(request.AddressID) _, err := s.repoAddress.FindAddressByID(request.AddressID)
if err != nil { if err != nil {
return nil, fmt.Errorf("address with ID %s not found", request.AddressID) return nil, fmt.Errorf("address with ID %s not found", request.AddressID)
} }
existingRequest, err := s.repo.FindRequestPickupByAddressAndStatus(UserId, "waiting_pengepul") existingRequest, err := s.repo.FindRequestPickupByAddressAndStatus(UserId, "waiting_collector")
if err != nil { if err != nil {
return nil, fmt.Errorf("error checking for existing request pickup: %v", err) return nil, fmt.Errorf("error checking for existing request pickup: %v", err)
} }
@ -49,8 +53,9 @@ func (s *requestPickupService) CreateRequestPickup(request dto.RequestPickup, Us
modelRequest := model.RequestPickup{ modelRequest := model.RequestPickup{
UserId: UserId, UserId: UserId,
AddressId: findAddress.ID, AddressId: request.AddressID,
EvidenceImage: request.EvidenceImage, EvidenceImage: request.EvidenceImage,
RequestMethod: request.RequestMethod,
} }
err = s.repo.CreateRequestPickup(&modelRequest) err = s.repo.CreateRequestPickup(&modelRequest)
@ -58,14 +63,17 @@ func (s *requestPickupService) CreateRequestPickup(request dto.RequestPickup, Us
return nil, fmt.Errorf("failed to create request pickup: %v", err) return nil, fmt.Errorf("failed to create request pickup: %v", err)
} }
createdAt, _ := utils.FormatDateToIndonesianFormat(modelRequest.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(modelRequest.UpdatedAt)
response := &dto.ResponseRequestPickup{ response := &dto.ResponseRequestPickup{
ID: modelRequest.ID, ID: modelRequest.ID,
UserId: UserId, UserId: UserId,
AddressID: modelRequest.AddressId, AddressID: modelRequest.AddressId,
EvidenceImage: modelRequest.EvidenceImage, EvidenceImage: modelRequest.EvidenceImage,
StatusPickup: modelRequest.StatusPickup, StatusPickup: modelRequest.StatusPickup,
CreatedAt: modelRequest.CreatedAt.Format("2006-01-02 15:04:05"), CreatedAt: createdAt,
UpdatedAt: modelRequest.UpdatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: updatedAt,
} }
for _, item := range request.RequestItems { for _, item := range request.RequestItems {
@ -102,80 +110,94 @@ func (s *requestPickupService) GetRequestPickupByID(id string) (*dto.ResponseReq
return nil, fmt.Errorf("error fetching request pickup with ID %s: %v", id, err) return nil, fmt.Errorf("error fetching request pickup with ID %s: %v", id, err)
} }
createdAt, _ := utils.FormatDateToIndonesianFormat(request.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(request.UpdatedAt)
response := &dto.ResponseRequestPickup{ response := &dto.ResponseRequestPickup{
ID: request.ID, ID: request.ID,
UserId: request.UserId, UserId: request.UserId,
AddressID: request.AddressId, AddressID: request.AddressId,
EvidenceImage: request.EvidenceImage, EvidenceImage: request.EvidenceImage,
StatusPickup: request.StatusPickup, StatusPickup: request.StatusPickup,
CreatedAt: request.CreatedAt.Format("2006-01-02 15:04:05"), CreatedAt: createdAt,
UpdatedAt: request.UpdatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: updatedAt,
} }
return response, nil return response, nil
} }
func (s *requestPickupService) GetAllRequestPickups() ([]dto.ResponseRequestPickup, error) { func (s *requestPickupService) GetAllRequestPickups(userid string) ([]dto.ResponseRequestPickup, error) {
requests, err := s.repo.FindAllRequestPickups() requests, err := s.repo.FindAllRequestPickups(userid)
if err != nil { if err != nil {
return nil, fmt.Errorf("error fetching all request pickups: %v", err) return nil, fmt.Errorf("error fetching all request pickups: %v", err)
} }
var response []dto.ResponseRequestPickup var response []dto.ResponseRequestPickup
for _, request := range requests { for _, request := range requests {
createdAt, _ := utils.FormatDateToIndonesianFormat(request.CreatedAt)
updatedAt, _ := utils.FormatDateToIndonesianFormat(request.UpdatedAt)
response = append(response, dto.ResponseRequestPickup{ response = append(response, dto.ResponseRequestPickup{
ID: request.ID, ID: request.ID,
UserId: request.UserId, UserId: request.UserId,
AddressID: request.AddressId, AddressID: request.AddressId,
EvidenceImage: request.EvidenceImage, EvidenceImage: request.EvidenceImage,
StatusPickup: request.StatusPickup, StatusPickup: request.StatusPickup,
CreatedAt: request.CreatedAt.Format("2006-01-02 15:04:05"), CreatedAt: createdAt,
UpdatedAt: request.UpdatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: updatedAt,
}) })
} }
return response, nil return response, nil
} }
func (s *requestPickupService) UpdateRequestPickup(id string, request dto.RequestPickup) (*dto.ResponseRequestPickup, error) { func (s *requestPickupService) GetRequestPickupsForCollector(collectorId string) ([]dto.ResponseRequestPickup, error) {
errors, valid := request.ValidateRequestPickup() requests, err := s.repo.GetAutomaticRequestPickupsForCollector(collectorId)
if !valid {
return nil, fmt.Errorf("validation errors: %v", errors)
}
existingRequest, err := s.repo.FindRequestPickupByID(id)
if err != nil { if err != nil {
return nil, fmt.Errorf("request pickup with ID %s not found: %v", id, err) return nil, fmt.Errorf("error retrieving automatic pickup requests: %v", err)
} }
existingRequest.EvidenceImage = request.EvidenceImage var response []dto.ResponseRequestPickup
err = s.repo.UpdateRequestPickup(id, existingRequest) for _, req := range requests {
_, distance := utils.Distance(
utils.Coord{Lat: req.Address.Latitude, Lon: req.Address.Longitude},
utils.Coord{Lat: req.Address.Latitude, Lon: req.Address.Longitude},
)
if distance <= 20 {
mappedRequest := dto.ResponseRequestPickup{
ID: req.ID,
UserId: req.UserId,
AddressID: req.AddressId,
EvidenceImage: req.EvidenceImage,
StatusPickup: req.StatusPickup,
CreatedAt: req.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: req.UpdatedAt.Format("2006-01-02 15:04:05"),
}
requestItems, err := s.repo.GetRequestPickupItems(req.ID)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to update request pickup: %v", err) return nil, fmt.Errorf("error fetching request items: %v", err)
} }
response := &dto.ResponseRequestPickup{ var mappedRequestItems []dto.ResponseRequestPickupItem
ID: existingRequest.ID, for _, item := range requestItems {
UserId: existingRequest.UserId, mappedRequestItems = append(mappedRequestItems, dto.ResponseRequestPickupItem{
AddressID: existingRequest.AddressId, ID: item.ID,
EvidenceImage: existingRequest.EvidenceImage, TrashCategoryName: item.TrashCategory.Name,
StatusPickup: existingRequest.StatusPickup, EstimatedAmount: item.EstimatedAmount,
CreatedAt: existingRequest.CreatedAt.Format("2006-01-02 15:04:05"), })
UpdatedAt: existingRequest.UpdatedAt.Format("2006-01-02 15:04:05"), }
mappedRequest.RequestItems = mappedRequestItems
response = append(response, mappedRequest)
}
} }
return response, nil 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
}

View File

@ -12,8 +12,8 @@ type Address struct {
Village string `gorm:"not null" json:"village"` Village string `gorm:"not null" json:"village"`
PostalCode string `gorm:"not null" json:"postalCode"` PostalCode string `gorm:"not null" json:"postalCode"`
Detail string `gorm:"not null" json:"detail"` Detail string `gorm:"not null" json:"detail"`
Latitude string `gorm:"not null" json:"latitude"` Latitude float64 `gorm:"not null" json:"latitude"`
Longitude string `gorm:"not null" json:"longitude"` Longitude float64 `gorm:"not null" json:"longitude"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"` CreatedAt time.Time `gorm:"default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"` UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updatedAt"`
} }

11
model/collector_model.go Normal file
View File

@ -0,0 +1,11 @@
package model
type Collector struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
UserID string `gorm:"not null" json:"userId"`
User User `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user"`
JobStatus string `gorm:"default:nonactive" json:"jobstatus"`
Rating float32 `gorm:"default:5" json:"rating"`
AddressId string `gorm:"not null" json:"address_id"`
Address Address `gorm:"foreignKey:AddressId;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"address"`
}

View File

@ -7,10 +7,15 @@ import (
type RequestPickup struct { type RequestPickup struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
UserId string `gorm:"not null" json:"user_id"` UserId string `gorm:"not null" json:"user_id"`
User User `gorm:"foreignKey:UserId;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"user"`
AddressId string `gorm:"not null" json:"address_id"` AddressId string `gorm:"not null" json:"address_id"`
Address Address `gorm:"foreignKey:AddressId;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"address"`
RequestItems []RequestPickupItem `gorm:"foreignKey:RequestPickupId;constraint:OnDelete:CASCADE;" json:"request_items"` RequestItems []RequestPickupItem `gorm:"foreignKey:RequestPickupId;constraint:OnDelete:CASCADE;" json:"request_items"`
EvidenceImage string `json:"evidence_image"` EvidenceImage string `json:"evidence_image"`
StatusPickup string `gorm:"default:'waiting_pengepul'" json:"status_pickup"` StatusPickup string `gorm:"default:'waiting_collector'" json:"status_pickup"`
CollectorID *string `gorm:"type:uuid" json:"collector_id,omitempty"`
ConfirmedByCollectorAt time.Time `gorm:"default:current_timestamp" json:"confirmed_by_collector_at,omitempty"`
RequestMethod string `gorm:"not null" json:"request_method"`
CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"` CreatedAt time.Time `gorm:"default:current_timestamp" json:"created_at"`
UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"` UpdatedAt time.Time `gorm:"default:current_timestamp" json:"updated_at"`
} }
@ -18,7 +23,8 @@ type RequestPickup struct {
type RequestPickupItem struct { type RequestPickupItem struct {
ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"` ID string `gorm:"primaryKey;type:uuid;default:uuid_generate_v4();unique;not null" json:"id"`
RequestPickupId string `gorm:"not null" json:"request_pickup_id"` RequestPickupId string `gorm:"not null" json:"request_pickup_id"`
RequestPickup RequestPickup `gorm:"foreignKey:RequestPickupId;constraint:OnDelete:CASCADE;"`
TrashCategoryId string `gorm:"not null" json:"trash_category_id"` TrashCategoryId string `gorm:"not null" json:"trash_category_id"`
TrashDetailId string `json:"trash_detail_id,omitempty"` TrashCategory TrashCategory `gorm:"foreignKey:TrashCategoryId;constraint:OnDelete:CASCADE;"`
EstimatedAmount float64 `gorm:"not null" json:"estimated_amount"` EstimatedAmount float64 `gorm:"not null" json:"estimated_amount"`
} }

View File

@ -0,0 +1,26 @@
package presentation
import (
"rijig/config"
"rijig/internal/handler"
"rijig/internal/repositories"
"rijig/internal/services"
"rijig/middleware"
"rijig/utils"
"github.com/gofiber/fiber/v2"
)
func CollectorRouter(api fiber.Router) {
repo := repositories.NewCollectorRepository(config.DB)
repoReq := repositories.NewRequestPickupRepository(config.DB)
repoAddress := repositories.NewAddressRepository(config.DB)
colectorService := services.NewCollectorService(repo, repoReq, repoAddress)
collectorHandler := handler.NewCollectorHandler(colectorService)
collector := api.Group("/collector")
collector.Use(middleware.AuthMiddleware, middleware.RoleMiddleware(utils.RolePengepul))
collector.Put("confirmrequest/:id", collectorHandler.ConfirmRequestPickup)
}

View File

@ -15,8 +15,12 @@ func RequestPickupRouter(api fiber.Router) {
requestRepo := repositories.NewRequestPickupRepository(config.DB) requestRepo := repositories.NewRequestPickupRepository(config.DB)
repoTrash := repositories.NewTrashRepository(config.DB) repoTrash := repositories.NewTrashRepository(config.DB)
repoAddress := repositories.NewAddressRepository(config.DB) repoAddress := repositories.NewAddressRepository(config.DB)
// collectorRepo := repositories.NewCollectorRepository(config.DB)
requestPickupServices := services.NewRequestPickupService(requestRepo, repoAddress, repoTrash) requestPickupServices := services.NewRequestPickupService(requestRepo, repoAddress, repoTrash)
// collectorService := services.NewCollectorService(collectorRepo, requestRepo, repoAddress)
// service services.RequestPickupService,
// collectorService services.CollectorService
requestPickupHandler := handler.NewRequestPickupHandler(requestPickupServices) requestPickupHandler := handler.NewRequestPickupHandler(requestPickupServices)
@ -24,6 +28,8 @@ func RequestPickupRouter(api fiber.Router) {
requestPickupAPI.Use(middleware.AuthMiddleware) requestPickupAPI.Use(middleware.AuthMiddleware)
requestPickupAPI.Post("/", requestPickupHandler.CreateRequestPickup) requestPickupAPI.Post("/", requestPickupHandler.CreateRequestPickup)
// requestPickupAPI.Get("/get", middleware.AuthMiddleware, requestPickupHandler.GetAutomaticRequestByUser)
requestPickupAPI.Get("/get-allrequest", requestPickupHandler.GetRequestPickups)
// requestPickupAPI.Get("/:id", requestPickupHandler.GetRequestPickupByID) // requestPickupAPI.Get("/:id", requestPickupHandler.GetRequestPickupByID)
// requestPickupAPI.Get("/", requestPickupHandler.GetAllRequestPickups) // requestPickupAPI.Get("/", requestPickupHandler.GetAllRequestPickups)
// requestPickupAPI.Put("/:id", requestPickupHandler.UpdateRequestPickup) // requestPickupAPI.Put("/:id", requestPickupHandler.UpdateRequestPickup)

View File

@ -26,6 +26,7 @@ func SetupRoutes(app *fiber.App) {
presentation.IdentityCardRouter(api) presentation.IdentityCardRouter(api)
presentation.CompanyProfileRouter(api) presentation.CompanyProfileRouter(api)
presentation.RequestPickupRouter(api) presentation.RequestPickupRouter(api)
presentation.CollectorRouter(api)
presentation.UserProfileRouter(api) presentation.UserProfileRouter(api)
presentation.UserPinRouter(api) presentation.UserPinRouter(api)

39
utils/havershine.go Normal file
View File

@ -0,0 +1,39 @@
package utils
import (
"math"
)
const (
earthRadiusMi = 3958
earthRaidusKm = 6371
)
type Coord struct {
Lat float64
Lon float64
}
func degreesToRadians(d float64) float64 {
return d * math.Pi / 180
}
func Distance(p, q Coord) (mi, km float64) {
lat1 := degreesToRadians(p.Lat)
lon1 := degreesToRadians(p.Lon)
lat2 := degreesToRadians(q.Lat)
lon2 := degreesToRadians(q.Lon)
diffLat := lat2 - lat1
diffLon := lon2 - lon1
a := math.Pow(math.Sin(diffLat/2), 2) + math.Cos(lat1)*math.Cos(lat2)*
math.Pow(math.Sin(diffLon/2), 2)
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
mi = c * earthRadiusMi
km = c * earthRaidusKm
return mi, km
}