SiLaju / services /report_service.go
RyZ
refactor: cleaning magic variable and error
e02b818
package services
import (
"fmt"
"mime/multipart"
"path/filepath"
"strings"
"time"
"dinacom-11.0-backend/models/dto"
entity "dinacom-11.0-backend/models/entity"
http_error "dinacom-11.0-backend/models/error"
"dinacom-11.0-backend/repositories"
"dinacom-11.0-backend/utils"
"github.com/google/uuid"
)
const maxFileSize = 32 << 20
var allowedExtensions = map[string]bool{
".jpg": true,
".jpeg": true,
".png": true,
}
type ReportService interface {
CreateReport(userID uuid.UUID, file multipart.File, header *multipart.FileHeader, req dto.ReportRequest) (*dto.ReportResponse, error)
GetReports() ([]dto.ReportLocationResponse, error)
AssignWorker(req dto.AssignWorkerRequest) (string, error)
GetAssignedReports() ([]dto.AssignedWorkerResponse, error)
FinishReport(workerID uuid.UUID, file multipart.File, header *multipart.FileHeader, reportID string) error
GetUserReports(userID uuid.UUID, page, limit int) (*dto.PaginatedReportsResponse, error)
GetWorkerAssignedReports(workerID uuid.UUID, page, limit int) (*dto.PaginatedReportsResponse, error)
GetWorkerHistory(workerID uuid.UUID, verifyAdmin bool, page, limit int) (*dto.PaginatedReportsResponse, error)
VerifyReport(reportID string) error
}
type reportService struct {
reportRepo repositories.ReportRepository
userRepo repositories.UserRepository
cloudinaryClient *utils.CloudinaryClient
}
func NewReportService(reportRepo repositories.ReportRepository, userRepo repositories.UserRepository) ReportService {
client, _ := utils.NewCloudinaryClient()
return &reportService{
reportRepo: reportRepo,
userRepo: userRepo,
cloudinaryClient: client,
}
}
func (s *reportService) CreateReport(userID uuid.UUID, file multipart.File, header *multipart.FileHeader, req dto.ReportRequest) (*dto.ReportResponse, error) {
if header.Size > maxFileSize {
return nil, http_error.FILE_TOO_LARGE
}
ext := strings.ToLower(filepath.Ext(header.Filename))
if !allowedExtensions[ext] {
return nil, http_error.INVALID_FILE_FORMAT
}
reportID := fmt.Sprintf("%s_%s_%.6f_%.6f", uuid.New().String(), time.Now().Format("20060102150405"), req.Longitude, req.Latitude)
imageURL, err := s.cloudinaryClient.UploadImage(file, reportID, req.Longitude, req.Latitude, req.Description)
if err != nil {
return nil, http_error.CLOUDINARY_UPLOAD_FAILED
}
report := &entity.Report{
ID: reportID,
UserID: userID,
Longitude: req.Longitude,
Latitude: req.Latitude,
RoadName: req.RoadName,
BeforeImageURL: imageURL,
Description: req.Description,
Status: entity.STATUS_PENDING,
}
if err := s.reportRepo.CreateReport(report); err != nil {
return nil, http_error.REPORT_CREATION_FAILED
}
return &dto.ReportResponse{
ID: report.ID,
UserID: report.UserID,
Longitude: report.Longitude,
Latitude: report.Latitude,
RoadName: report.RoadName,
BeforeImageURL: report.BeforeImageURL,
AfterImageURL: report.AfterImageURL,
Description: report.Description,
DestructClass: report.DestructClass,
LocationScore: report.LocationScore,
TotalScore: report.TotalScore,
Status: report.Status,
}, nil
}
func (s *reportService) GetReports() ([]dto.ReportLocationResponse, error) {
reports, err := s.reportRepo.GetCompletedNonGoodReports()
if err != nil {
return nil, err
}
var response []dto.ReportLocationResponse
for _, report := range reports {
response = append(response, dto.ReportLocationResponse{
ID: report.ID,
Longitude: report.Longitude,
Latitude: report.Latitude,
DestructClass: report.DestructClass,
TotalScore: report.TotalScore,
})
}
return response, nil
}
func (s *reportService) AssignWorker(req dto.AssignWorkerRequest) (string, error) {
report, err := s.reportRepo.GetReportByID(req.ReportID)
if err != nil {
return "", http_error.REPORT_NOT_FOUND
}
if report.WorkerID != nil {
return "", http_error.REPORT_ALREADY_ASSIGNED
}
worker, err := s.userRepo.FindUserByID(req.WorkerID)
if err != nil || worker == nil {
return "", http_error.WORKER_NOT_FOUND
}
if worker.Role != entity.ROLE_WORKER {
return "", http_error.ONLY_WORKER_CAN_ASSIGN
}
if err := s.reportRepo.AssignWorker(req.ReportID, req.WorkerID, req.AdminNotes, req.Deadline); err != nil {
return "", err
}
return fmt.Sprintf("%s is successfully assigned", worker.Fullname), nil
}
func (s *reportService) GetAssignedReports() ([]dto.AssignedWorkerResponse, error) {
reports, err := s.reportRepo.GetAssignedReports()
if err != nil {
return nil, err
}
var response []dto.AssignedWorkerResponse
for _, report := range reports {
workerName := ""
if report.WorkerID != nil {
worker, _ := s.userRepo.FindUserByID(*report.WorkerID)
if worker != nil {
workerName = worker.Fullname
}
}
response = append(response, dto.AssignedWorkerResponse{
WorkerName: workerName,
RoadName: report.RoadName,
Longitude: report.Longitude,
Latitude: report.Latitude,
Status: report.Status,
})
}
return response, nil
}
func (s *reportService) FinishReport(workerID uuid.UUID, file multipart.File, header *multipart.FileHeader, reportID string) error {
if header.Size > maxFileSize {
return http_error.FILE_TOO_LARGE
}
ext := strings.ToLower(filepath.Ext(header.Filename))
if !allowedExtensions[ext] {
return http_error.INVALID_FILE_FORMAT
}
report, err := s.reportRepo.GetReportByID(reportID)
if err != nil {
return http_error.REPORT_NOT_FOUND
}
if report.WorkerID == nil || *report.WorkerID != workerID {
return http_error.NOT_ASSIGNED_TO_REPORT
}
afterImageID := fmt.Sprintf("%s_after_%s", reportID, time.Now().Format("20060102150405"))
afterImageURL, err := s.cloudinaryClient.UploadImage(file, afterImageID, report.Longitude, report.Latitude, "After image")
if err != nil {
return http_error.CLOUDINARY_UPLOAD_FAILED
}
return s.reportRepo.UpdateAfterImage(reportID, afterImageURL, entity.STATUS_FINISH_BY_WORKER)
}
func (s *reportService) GetUserReports(userID uuid.UUID, page, limit int) (*dto.PaginatedReportsResponse, error) {
offset := (page - 1) * limit
reports, total, err := s.reportRepo.GetReportsByUserID(userID, limit, offset)
if err != nil {
return nil, err
}
return s.buildPaginatedResponse(reports, total, page, limit), nil
}
func (s *reportService) GetWorkerAssignedReports(workerID uuid.UUID, page, limit int) (*dto.PaginatedReportsResponse, error) {
offset := (page - 1) * limit
reports, total, err := s.reportRepo.GetAssignedReportsByWorkerID(workerID, limit, offset)
if err != nil {
return nil, err
}
return s.buildPaginatedResponse(reports, total, page, limit), nil
}
func (s *reportService) GetWorkerHistory(workerID uuid.UUID, verifyAdmin bool, page, limit int) (*dto.PaginatedReportsResponse, error) {
offset := (page - 1) * limit
status := entity.STATUS_FINISH_BY_WORKER
if verifyAdmin {
status = entity.STATUS_FINISHED
}
reports, total, err := s.reportRepo.GetWorkerHistory(workerID, status, limit, offset)
if err != nil {
return nil, err
}
return s.buildPaginatedResponse(reports, total, page, limit), nil
}
func (s *reportService) VerifyReport(reportID string) error {
report, err := s.reportRepo.GetReportByID(reportID)
if err != nil {
return http_error.REPORT_NOT_FOUND
}
if report.Status != entity.STATUS_FINISH_BY_WORKER {
return http_error.ONLY_FINISH_BY_WORKER_VERIFY
}
return s.reportRepo.UpdateStatus(reportID, entity.STATUS_FINISHED)
}
func (s *reportService) buildPaginatedResponse(reports []entity.Report, total int64, page, limit int) *dto.PaginatedReportsResponse {
var reportDTOs []dto.UserReportResponse
for _, report := range reports {
reportDTOs = append(reportDTOs, dto.UserReportResponse{
ID: report.ID,
Longitude: report.Longitude,
Latitude: report.Latitude,
RoadName: report.RoadName,
BeforeImageURL: report.BeforeImageURL,
AfterImageURL: report.AfterImageURL,
Description: report.Description,
DestructClass: report.DestructClass,
LocationScore: report.LocationScore,
TotalScore: report.TotalScore,
Status: report.Status,
AdminNotes: report.AdminNotes,
Deadline: report.Deadline,
CreatedAt: report.CreatedAt,
})
}
totalPages := int(total) / limit
if int(total)%limit != 0 {
totalPages++
}
return &dto.PaginatedReportsResponse{
Reports: reportDTOs,
TotalCount: total,
Page: page,
Limit: limit,
TotalPages: totalPages,
}
}