package service import ( "context" "github.com/gofiber/fiber/v2" "github.com/minio/minio-go/v7" "github.com/rs/zerolog" "go-humas-be/app/database/entity" "go-humas-be/app/module/ppid_data_files/mapper" "go-humas-be/app/module/ppid_data_files/repository" "go-humas-be/app/module/ppid_data_files/request" "go-humas-be/app/module/ppid_data_files/response" config "go-humas-be/config/config" "go-humas-be/utils/paginator" "io" "log" "math/rand" "mime" "path/filepath" "strconv" "strings" "time" ) // PpidDataFilesService type ppidDataFilesService struct { Repo repository.PpidDataFilesRepository Log zerolog.Logger MinioStorage *config.MinioStorage Cfg *config.Config } // PpidDataFilesService define interface of IPpidDataFilesService type PpidDataFilesService interface { All(req request.PpidDataFilesQueryRequest) (ppidDataFiles []*response.PpidDataFilesResponse, paging paginator.Pagination, err error) Show(id uint) (ppidDataFiles *response.PpidDataFilesResponse, err error) Save(c *fiber.Ctx) error Update(id uint, req request.PpidDataFilesUpdateRequest) (err error) UpdatePosition(req []request.PpidDataFilesUpdatePositionRequest) (err error) Delete(id uint) error Viewer(c *fiber.Ctx, filename string) error } // NewPpidDataFilesService init PpidDataFilesService func NewPpidDataFilesService(repo repository.PpidDataFilesRepository, log zerolog.Logger, minioStorage *config.MinioStorage, cfg *config.Config) PpidDataFilesService { return &ppidDataFilesService{ Repo: repo, Log: log, MinioStorage: minioStorage, Cfg: cfg, } } // All implement interface of PpidDataFilesService func (_i *ppidDataFilesService) All(req request.PpidDataFilesQueryRequest) (ppidDataFiless []*response.PpidDataFilesResponse, paging paginator.Pagination, err error) { results, paging, err := _i.Repo.GetAll(req) if err != nil { return } for _, result := range results { ppidDataFiless = append(ppidDataFiless, mapper.PpidDataFilesResponseMapper(result)) } return } func (_i *ppidDataFilesService) Show(id uint) (ppidDataFiles *response.PpidDataFilesResponse, err error) { result, err := _i.Repo.FindOne(id) if err != nil { return nil, err } return mapper.PpidDataFilesResponseMapper(result), nil } func (_i *ppidDataFilesService) Save(c *fiber.Ctx) (err error) { id, err := strconv.ParseUint(c.Params("ppidDataId"), 10, 0) if err != nil { return err } host := _i.Cfg.App.Domain port := _i.Cfg.App.ExternalPort form, err := c.MultipartForm() _i.Log.Info().Str("timestamp", time.Now(). Format(time.RFC3339)).Str("Service", "Save"). Interface("form", form).Msg("") titlesString := form.Value["titles"] titles := strings.Split(titlesString[0], ",") typesString := form.Value["types"] types := strings.Split(typesString[0], ",") positionsString := form.Value["positions"] positions := strings.Split(positionsString[0], ",") var urls []string urlsString := form.Value["urls"] if urlsString != nil { urls = strings.Split(urlsString[0], ",") } files := form.File["files"] _i.Log.Info().Str("timestamp", time.Now(). Format(time.RFC3339)).Str("Service", "Save"). Interface("titlesString", titlesString).Msg("") _i.Log.Info().Str("timestamp", time.Now(). Format(time.RFC3339)).Str("Service", "Save"). Interface("typesString", typesString).Msg("") _i.Log.Info().Str("timestamp", time.Now(). Format(time.RFC3339)).Str("Service", "Save"). Interface("positionsString", positionsString).Msg("") _i.Log.Info().Str("timestamp", time.Now(). Format(time.RFC3339)).Str("Service", "Save"). Interface("urlsString", urlsString).Msg("") _i.Log.Info().Str("timestamp", time.Now(). Format(time.RFC3339)).Str("Service", "Save"). Interface("urls", urls).Msg("") fileCounter := 0 urlCounter := 0 _i.Log.Info().Str("timestamp", time.Now(). Format(time.RFC3339)).Str("Service", "Save"). Interface("form", files).Msg("") for index, fileType := range types { _i.Log.Info().Str("timestamp", time.Now(). Format(time.RFC3339)).Str("Service", "Save"). Interface("types", fileType).Msg("") position := &positions[index] positionInt64, err := strconv.ParseInt(*position, 10, 0) if err != nil { return err } positionInt := int(positionInt64) statusId := 1 title := titles[index] req := request.PpidDataFilesCreateRequest{ Title: &title, PpidDataId: int(id), Type: &fileType, Position: &positionInt, StatusId: &statusId, } _i.Log.Info().Str("timestamp", time.Now(). Format(time.RFC3339)).Str("Service", "Save"). Interface("request", req).Msg("") if fileType == "url" { url := urls[urlCounter] //filename := path.Base(url) //filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) //extension := filepath.Ext(filename)[1:] //title := strings.ReplaceAll(filenameWithoutExt, "-", " ") //req.Title = &title //req.FileType = &extension req.FileName = &title req.FileUrl = &url urlCounter += 1 } else { bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName // Create minio connection. minioClient, err := _i.MinioStorage.ConnectMinio() if err != nil { // Return status 500 and minio connection error. return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": true, "msg": err.Error(), }) } // Iterasi semua file yang diunggah file := files[fileCounter] _i.Log.Info().Str("timestamp", time.Now(). Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop1"). Interface("data", file).Msg("") src, err := file.Open() if err != nil { return err } defer src.Close() filename := filepath.Base(file.Filename) filename = strings.ReplaceAll(filename, " ", "") filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) extension := filepath.Ext(file.Filename)[1:] rand.New(rand.NewSource(time.Now().UnixNano())) randUniqueId := rand.Intn(1000000) newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) newFilename := newFilenameWithoutExt + "." + extension objectName := "ppid/upload/" + newFilename size := strconv.FormatInt(file.Size, 10) viewerPath := "/ppid-data-files/viewer/" fileUrl := host + port + viewerPath + newFilename //req.Title = &newFilenameWithoutExt req.FileType = &extension req.FileName = &newFilename req.FilePath = &objectName req.FileUrl = &fileUrl req.Size = &size // Upload file to MinIO _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, file.Size, minio.PutObjectOptions{}) if err != nil { return err } fileCounter += 1 } err = _i.Repo.Create(req.ToEntity()) if err != nil { return err } } _i.Log.Info().Str("timestamp", time.Now(). Format(time.RFC3339)).Str("Service:Resource", "User:All"). Interface("data", "Successfully uploaded").Msg("") return } func (_i *ppidDataFilesService) Update(id uint, req request.PpidDataFilesUpdateRequest) (err error) { _i.Log.Info().Interface("data", req).Msg("") return _i.Repo.Update(id, req.ToEntity()) } func (_i *ppidDataFilesService) UpdatePosition(req []request.PpidDataFilesUpdatePositionRequest) (err error) { _i.Log.Info().Interface("data", req).Msg("") var entityReq []*entity.PpidDataFiles for _, reqItem := range req { entityReq = append(entityReq, reqItem.ToEntity()) } return _i.Repo.UpdateAll(entityReq) } func (_i *ppidDataFilesService) Delete(id uint) error { result, err := _i.Repo.FindOne(id) if err != nil { return err } isActive := false result.IsActive = &isActive return _i.Repo.Update(id, result) } func (_i *ppidDataFilesService) Viewer(c *fiber.Ctx, filename string) (err error) { result, err := _i.Repo.FindByFilename(filename) if err != nil { return err } if result.FilePath == nil { return nil } ctx := context.Background() bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName objectName := result.FilePath _i.Log.Info().Str("timestamp", time.Now(). Format(time.RFC3339)).Str("Service:Resource", "Article:Uploads"). Interface("data", objectName).Msg("") // Create minio connection. minioClient, err := _i.MinioStorage.ConnectMinio() if err != nil { // Return status 500 and minio connection error. return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": true, "msg": err.Error(), }) } fileContent, err := minioClient.GetObject(ctx, bucketName, *objectName, minio.GetObjectOptions{}) if err != nil { log.Fatalln(err) } defer fileContent.Close() // Tentukan Content-Type berdasarkan ekstensi file contentType := mime.TypeByExtension("." + getFileExtension(*objectName)) if contentType == "" { contentType = "application/octet-stream" // fallback jika tidak ada tipe MIME yang cocok } c.Set("Content-Type", contentType) if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { return err } return } func getFileExtension(filename string) string { // split file name parts := strings.Split(filename, ".") // jika tidak ada ekstensi, kembalikan string kosong if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { return "" } // ambil ekstensi terakhir return parts[len(parts)-1] }