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" utilSvc "narasi-ahli-be/utils/service" "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(authToken string, req request.EducationHistoryQueryRequest) (educationHistories []*response.EducationHistoryResponse, paging paginator.Pagination, err error) Show(authToken string, id uint) (educationHistory *response.EducationHistoryResponse, err error) Save(authToken string, req request.EducationHistoryCreateRequest) (educationHistory *response.EducationHistoryResponse, err error) Update(authToken string, id uint, req request.EducationHistoryUpdateRequest) (err error) Delete(authToken string, id uint) error UploadCertificate(authToken string, 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(authToken string, req request.EducationHistoryQueryRequest) (educationHistories []*response.EducationHistoryResponse, paging paginator.Pagination, err error) { userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if userInfo == nil { return nil, paginator.Pagination{}, errors.New("unauthorized") } results, paging, err := _i.Repo.GetAll(userInfo.ID, req) if err != nil { return } for _, result := range results { educationHistories = append(educationHistories, mapper.EducationHistoryResponseMapper(result)) } return } func (_i *educationHistoryService) Show(authToken string, id uint) (educationHistory *response.EducationHistoryResponse, err error) { userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if userInfo == nil { return nil, errors.New("unauthorized") } result, err := _i.Repo.FindOneByUserAndId(userInfo.ID, id) if err != nil { return nil, err } return mapper.EducationHistoryResponseMapper(result), nil } func (_i *educationHistoryService) Save(authToken string, req request.EducationHistoryCreateRequest) (educationHistory *response.EducationHistoryResponse, err error) { _i.Log.Info().Interface("data", req).Msg("Creating education history") userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if userInfo == nil { return nil, errors.New("unauthorized") } entity := req.ToEntity() entity.UserID = userInfo.ID result, err := _i.Repo.Create(entity) if err != nil { return nil, err } return mapper.EducationHistoryResponseMapper(result), nil } func (_i *educationHistoryService) Update(authToken string, id uint, req request.EducationHistoryUpdateRequest) (err error) { _i.Log.Info().Interface("data", req).Msg("Updating education history") userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if userInfo == nil { return errors.New("unauthorized") } // Check if record exists and belongs to user existing, err := _i.Repo.FindOneByUserAndId(userInfo.ID, id) if err != nil { return err } if existing == nil { return errors.New("education history not found") } entity := req.ToEntity() return _i.Repo.Update(userInfo.ID, id, entity) } func (_i *educationHistoryService) Delete(authToken string, id uint) error { _i.Log.Info().Str("authToken", authToken).Uint("id", id).Msg("Deleting education history") userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if userInfo == nil { return errors.New("unauthorized") } // Check if record exists and belongs to user existing, err := _i.Repo.FindOneByUserAndId(userInfo.ID, id) if err != nil { return err } if existing == nil { return errors.New("education history not found") } return _i.Repo.Delete(userInfo.ID, id) } func (_i *educationHistoryService) UploadCertificate(authToken string, id uint, c *fiber.Ctx) error { _i.Log.Info().Str("authToken", authToken).Uint("id", id).Msg("Uploading certificate") userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if userInfo == nil { return errors.New("unauthorized") } // Check if record exists and belongs to user existing, err := _i.Repo.FindOneByUserAndId(userInfo.ID, 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(userInfo.ID, id, existing) }