package service import ( "context" "errors" "fmt" "math/rand" "path/filepath" "strconv" "strings" "time" "narasi-ahli-be/app/module/education_history/mapper" "narasi-ahli-be/app/module/education_history/repository" "narasi-ahli-be/app/module/education_history/request" "narasi-ahli-be/app/module/education_history/response" usersRepository "narasi-ahli-be/app/module/users/repository" "narasi-ahli-be/config/config" minioStorage "narasi-ahli-be/config/config" "narasi-ahli-be/utils/paginator" "github.com/gofiber/fiber/v2" "github.com/minio/minio-go/v7" "github.com/rs/zerolog" ) type educationHistoryService struct { Repo repository.EducationHistoryRepository UsersRepo usersRepository.UsersRepository Log zerolog.Logger Cfg *config.Config MinioStorage *minioStorage.MinioStorage } type EducationHistoryService interface { All(req request.EducationHistoryQueryRequest) (educationHistories []*response.EducationHistoryResponse, paging paginator.Pagination, err error) Show(userId uint, id uint) (educationHistory *response.EducationHistoryResponse, err error) Save(userId uint, req request.EducationHistoryCreateRequest) (educationHistory *response.EducationHistoryResponse, err error) Update(userId uint, id uint, req request.EducationHistoryUpdateRequest) (err error) Delete(userId uint, id uint) error UploadCertificate(userId uint, id uint, c *fiber.Ctx) error } func NewEducationHistoryService(repo repository.EducationHistoryRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) EducationHistoryService { return &educationHistoryService{ Repo: repo, UsersRepo: usersRepo, Log: log, Cfg: cfg, MinioStorage: minioStorage, } } func (_i *educationHistoryService) All(req request.EducationHistoryQueryRequest) (educationHistories []*response.EducationHistoryResponse, paging paginator.Pagination, err error) { results, paging, err := _i.Repo.GetAll(req.UserID, req) if err != nil { return } for _, result := range results { educationHistories = append(educationHistories, mapper.EducationHistoryResponseMapper(result)) } return } func (_i *educationHistoryService) Show(userId uint, id uint) (educationHistory *response.EducationHistoryResponse, err error) { result, err := _i.Repo.FindOneByUserAndId(userId, id) if err != nil { return nil, err } return mapper.EducationHistoryResponseMapper(result), nil } func (_i *educationHistoryService) Save(userId uint, req request.EducationHistoryCreateRequest) (educationHistory *response.EducationHistoryResponse, err error) { _i.Log.Info().Interface("data", req).Msg("Creating education history") entity := req.ToEntity(userId) result, err := _i.Repo.Create(entity) if err != nil { return nil, err } return mapper.EducationHistoryResponseMapper(result), nil } func (_i *educationHistoryService) Update(userId uint, id uint, req request.EducationHistoryUpdateRequest) (err error) { _i.Log.Info().Interface("data", req).Msg("Updating education history") // Check if record exists and belongs to user existing, err := _i.Repo.FindOneByUserAndId(userId, id) if err != nil { return err } if existing == nil { return errors.New("education history not found") } entity := req.ToEntity(userId) return _i.Repo.Update(userId, id, entity) } func (_i *educationHistoryService) Delete(userId uint, id uint) error { _i.Log.Info().Uint("userId", userId).Uint("id", id).Msg("Deleting education history") // Check if record exists and belongs to user existing, err := _i.Repo.FindOneByUserAndId(userId, id) if err != nil { return err } if existing == nil { return errors.New("education history not found") } return _i.Repo.Delete(userId, id) } func (_i *educationHistoryService) UploadCertificate(userId uint, id uint, c *fiber.Ctx) error { _i.Log.Info().Uint("userId", userId).Uint("id", id).Msg("Uploading certificate") // Check if record exists and belongs to user existing, err := _i.Repo.FindOneByUserAndId(userId, id) if err != nil { return err } if existing == nil { return errors.New("education history not found") } // Get multipart form form, err := c.MultipartForm() if err != nil { return err } // Get file from form files := form.File["certificate"] if len(files) == 0 { return errors.New("no certificate file provided") } fileHeader := files[0] bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName // Create minio connection minioClient, err := _i.MinioStorage.ConnectMinio() if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": true, "msg": err.Error(), }) } // Process file src, err := fileHeader.Open() if err != nil { return err } defer src.Close() filename := filepath.Base(fileHeader.Filename) filename = strings.ReplaceAll(filename, " ", "") filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) extension := filepath.Ext(fileHeader.Filename)[1:] now := time.Now() rand.New(rand.NewSource(now.UnixNano())) randUniqueId := rand.Intn(1000000) newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) newFilename := newFilenameWithoutExt + "." + extension objectName := fmt.Sprintf("education-history/certificates/%d/%d/%s", now.Year(), now.Month(), newFilename) // Upload file to MinIO _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{}) if err != nil { return err } // Update certificate image path with MinIO object name existing.CertificateImage = &objectName return _i.Repo.Update(userId, id, existing) }