From d68a9bb7b7ab074e319e642503e4841bb0433db8 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Wed, 24 Sep 2025 12:13:57 +0700 Subject: [PATCH 1/3] add several columns for file url person --- .../migrations/20250924051317.sql | 2 + cmd/main-migration/migrations/atlas.sum | 5 +- internal/domain/main-entities/person/dto.go | 84 ++++++++++--------- .../domain/main-entities/person/entity.go | 46 +++++----- 4 files changed, 76 insertions(+), 61 deletions(-) create mode 100644 cmd/main-migration/migrations/20250924051317.sql diff --git a/cmd/main-migration/migrations/20250924051317.sql b/cmd/main-migration/migrations/20250924051317.sql new file mode 100644 index 00000000..f0f58947 --- /dev/null +++ b/cmd/main-migration/migrations/20250924051317.sql @@ -0,0 +1,2 @@ +-- Modify "Person" table +ALTER TABLE "public"."Person" ADD COLUMN "ResidentIdentityFileUrl" character varying(1024) NULL, ADD COLUMN "PassportFileUrl" character varying(1024) NULL, ADD COLUMN "DrivingLicenseFileUrl" character varying(1024) NULL, ADD COLUMN "FamilyIdentityFileUrl" character varying(1024) NULL; diff --git a/cmd/main-migration/migrations/atlas.sum b/cmd/main-migration/migrations/atlas.sum index e61e9880..b33a1e5c 100644 --- a/cmd/main-migration/migrations/atlas.sum +++ b/cmd/main-migration/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:0Hvo3AS2yMsS60eDIKnU5B2qvjDhNWHSDHcMxKKYJTk= +h1:n2YbCQPYchOWsVSLUNPCl29Xx3RMTi7vdulwPRHXL4E= 20250904105930.sql h1:MEM6blCgke9DzWQSTnLzasbPIrcHssNNrJqZpSkEo6k= 20250904141448.sql h1:J8cmYNk4ZrG9fhfbi2Z1IWz7YkfvhFqTzrLFo58BPY0= 20250908062237.sql h1:Pu23yEW/aKkwozHoOuROvHS/GK4ngARJGdO7FB7HZuI= @@ -15,4 +15,5 @@ h1:0Hvo3AS2yMsS60eDIKnU5B2qvjDhNWHSDHcMxKKYJTk= 20250918073552.sql h1:RJ1SvMzP6aeWnoPVD3eVAmIQOkcp6Php8z3QRri6v4g= 20250918073742.sql h1:+cEsnJTJFybe2fR69ZoOiX2R6c6iITl4m6WTZ1hjyzY= 20250918074745.sql h1:2hNVQCXF/dVYXAh+T/7oBFgERGWxzVb2FXJjwkFWGCI= -20250923025134.sql h1:q1bndJguAqjnTjXr6Jpnsrj4d8zMseR2tf02h0h6ArE= +20250923025134.sql h1:Ykz/qpHiGDXPsCsWTjydQFVSibZP2D+h2fIeb2h2JGA= +20250924051317.sql h1:vbbuylW434V23raIl6Y6Ima6aZVItLrdIpk+xehVsJc= diff --git a/internal/domain/main-entities/person/dto.go b/internal/domain/main-entities/person/dto.go index bab22944..fd38822b 100644 --- a/internal/domain/main-entities/person/dto.go +++ b/internal/domain/main-entities/person/dto.go @@ -81,48 +81,56 @@ type MetaDto struct { type ResponseDto struct { ecore.Main - Name string `json:"name"` - FrontTitle *string `json:"frontTitle"` - EndTitle *string `json:"endTitle"` - BirthDate *time.Time `json:"birthDate,omitempty"` - BirthRegency_Code *string `json:"birthRegency_code"` - Gender_Code *erp.GenderCode `json:"gender_code"` - ResidentIdentityNumber *string `json:"residentIdentityNumber"` - PassportNumber *string `json:"passportNumber"` - DrivingLicenseNumber *string `json:"drivingLicenseNumber"` - Religion_Code *erp.ReligionCode `json:"religion_code"` - Education_Code *erp.EducationCode `json:"education_code"` - Ocupation_Code *erp.OcupationCode `json:"occupation_code"` - Ocupation_Name *string `json:"occupation_name"` - Ethnic_Code *string `json:"ethnic_code"` - Ethnic *ee.Ethnic `json:"ethnic,omitempty"` - Addresses *[]epa.PersonAddress `json:"addresses,omitempty"` - Contacts *[]epc.PersonContact `json:"contacts,omitempty"` - Relatives *[]epr.PersonRelative `json:"relatives,omitempty"` - Language_Code *string `json:"language_code"` + Name string `json:"name"` + FrontTitle *string `json:"frontTitle"` + EndTitle *string `json:"endTitle"` + BirthDate *time.Time `json:"birthDate,omitempty"` + BirthRegency_Code *string `json:"birthRegency_code"` + Gender_Code *erp.GenderCode `json:"gender_code"` + ResidentIdentityNumber *string `json:"residentIdentityNumber"` + PassportNumber *string `json:"passportNumber"` + DrivingLicenseNumber *string `json:"drivingLicenseNumber"` + Religion_Code *erp.ReligionCode `json:"religion_code"` + Education_Code *erp.EducationCode `json:"education_code"` + Ocupation_Code *erp.OcupationCode `json:"occupation_code"` + Ocupation_Name *string `json:"occupation_name"` + Ethnic_Code *string `json:"ethnic_code"` + Ethnic *ee.Ethnic `json:"ethnic,omitempty"` + Addresses *[]epa.PersonAddress `json:"addresses,omitempty"` + Contacts *[]epc.PersonContact `json:"contacts,omitempty"` + Relatives *[]epr.PersonRelative `json:"relatives,omitempty"` + Language_Code *string `json:"language_code"` + ResidentIdentityFileUrl *string `json:"residentIdentityFileUrl"` + PassportFileUrl *string `json:"passportFileUrl"` + DrivingLicenseFileUrl *string `json:"drivingLicenseFileUrl"` + FamilyIdentityFileUrl *string `json:"familyIdentityFileUrl"` } func (d *Person) ToResponse() ResponseDto { resp := ResponseDto{ - Name: d.Name, - FrontTitle: d.FrontTitle, - EndTitle: d.EndTitle, - BirthDate: d.BirthDate, - BirthRegency_Code: d.BirthRegency_Code, - Gender_Code: d.Gender_Code, - ResidentIdentityNumber: d.ResidentIdentityNumber, - PassportNumber: d.PassportNumber, - DrivingLicenseNumber: d.DrivingLicenseNumber, - Religion_Code: d.Religion_Code, - Education_Code: d.Education_Code, - Ocupation_Code: d.Ocupation_Code, - Ocupation_Name: d.Ocupation_Name, - Ethnic_Code: d.Ethnic_Code, - Ethnic: d.Ethnic, - Addresses: d.Addresses, - Contacts: d.Contacts, - Relatives: d.Relatives, - Language_Code: d.Language_Code, + Name: d.Name, + FrontTitle: d.FrontTitle, + EndTitle: d.EndTitle, + BirthDate: d.BirthDate, + BirthRegency_Code: d.BirthRegency_Code, + Gender_Code: d.Gender_Code, + ResidentIdentityNumber: d.ResidentIdentityNumber, + PassportNumber: d.PassportNumber, + DrivingLicenseNumber: d.DrivingLicenseNumber, + Religion_Code: d.Religion_Code, + Education_Code: d.Education_Code, + Ocupation_Code: d.Ocupation_Code, + Ocupation_Name: d.Ocupation_Name, + Ethnic_Code: d.Ethnic_Code, + Ethnic: d.Ethnic, + Addresses: d.Addresses, + Contacts: d.Contacts, + Relatives: d.Relatives, + Language_Code: d.Language_Code, + ResidentIdentityFileUrl: d.ResidentIdentityFileUrl, + PassportFileUrl: d.PassportFileUrl, + DrivingLicenseFileUrl: d.DrivingLicenseFileUrl, + FamilyIdentityFileUrl: d.FamilyIdentityFileUrl, } resp.Main = d.Main return resp diff --git a/internal/domain/main-entities/person/entity.go b/internal/domain/main-entities/person/entity.go index eb3bab27..37e2086c 100644 --- a/internal/domain/main-entities/person/entity.go +++ b/internal/domain/main-entities/person/entity.go @@ -13,27 +13,31 @@ import ( ) type Person struct { - ecore.Main // adjust this according to the needs - Name string `json:"name" gorm:"not null;size:150"` - FrontTitle *string `json:"frontTitle" gorm:"size:50"` - EndTitle *string `json:"endTitle" gorm:"size:50"` - BirthDate *time.Time `json:"birthDate,omitempty"` - BirthRegency_Code *string `json:"birthRegency_code" gorm:"size:4"` - Gender_Code *erp.GenderCode `json:"gender_code" gorm:"size:10"` - ResidentIdentityNumber *string `json:"residentIdentityNumber" gorm:"unique;size:16"` - PassportNumber *string `json:"passportNumber" gorm:"unique;size:20"` - DrivingLicenseNumber *string `json:"drivingLicenseNumber" gorm:"unique;size:20"` - Religion_Code *erp.ReligionCode `json:"religion_code" gorm:"size:10"` - Education_Code *erp.EducationCode `json:"education_code" gorm:"size:10"` - Ocupation_Code *erp.OcupationCode `json:"occupation_code" gorm:"size:15"` - Ocupation_Name *string `json:"occupation_name" gorm:"size:50"` - Ethnic_Code *string `json:"ethnic_code" gorm:"size:20"` - Ethnic *ee.Ethnic `json:"ethnic,omitempty" gorm:"foreignKey:Ethnic_Code;references:Code"` - Addresses *[]epa.PersonAddress `json:"addresses" gorm:"foreignKey:Person_Id"` - Contacts *[]epc.PersonContact `json:"contacts" gorm:"foreignKey:Person_Id"` - Relatives *[]epr.PersonRelative `json:"relatives" gorm:"foreignKey:Person_Id"` - Language_Code *string `json:"language_code" gorm:"size:10"` - Language *el.Language `json:"language,omitempty" gorm:"foreignKey:Language_Code;references:Code"` + ecore.Main // adjust this according to the needs + Name string `json:"name" gorm:"not null;size:150"` + FrontTitle *string `json:"frontTitle" gorm:"size:50"` + EndTitle *string `json:"endTitle" gorm:"size:50"` + BirthDate *time.Time `json:"birthDate,omitempty"` + BirthRegency_Code *string `json:"birthRegency_code" gorm:"size:4"` + Gender_Code *erp.GenderCode `json:"gender_code" gorm:"size:10"` + ResidentIdentityNumber *string `json:"residentIdentityNumber" gorm:"unique;size:16"` + PassportNumber *string `json:"passportNumber" gorm:"unique;size:20"` + DrivingLicenseNumber *string `json:"drivingLicenseNumber" gorm:"unique;size:20"` + Religion_Code *erp.ReligionCode `json:"religion_code" gorm:"size:10"` + Education_Code *erp.EducationCode `json:"education_code" gorm:"size:10"` + Ocupation_Code *erp.OcupationCode `json:"occupation_code" gorm:"size:15"` + Ocupation_Name *string `json:"occupation_name" gorm:"size:50"` + Ethnic_Code *string `json:"ethnic_code" gorm:"size:20"` + Ethnic *ee.Ethnic `json:"ethnic,omitempty" gorm:"foreignKey:Ethnic_Code;references:Code"` + Addresses *[]epa.PersonAddress `json:"addresses" gorm:"foreignKey:Person_Id"` + Contacts *[]epc.PersonContact `json:"contacts" gorm:"foreignKey:Person_Id"` + Relatives *[]epr.PersonRelative `json:"relatives" gorm:"foreignKey:Person_Id"` + Language_Code *string `json:"language_code" gorm:"size:10"` + Language *el.Language `json:"language,omitempty" gorm:"foreignKey:Language_Code;references:Code"` + ResidentIdentityFileUrl *string `json:"residentIdentityFileUrl" gorm:"size:1024"` + PassportFileUrl *string `json:"passportFileUrl" gorm:"size:1024"` + DrivingLicenseFileUrl *string `json:"drivingLicenseFileUrl" gorm:"size:1024"` + FamilyIdentityFileUrl *string `json:"familyIdentityFileUrl" gorm:"size:1024"` } func (d Person) IsSameResidentIdentityNumber(input *string) bool { From f618a2d6d0c94e61a5662c4c71bcda2a328bbe89 Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Wed, 24 Sep 2025 14:24:57 +0700 Subject: [PATCH 2/3] feat (patient): upload done --- internal/domain/main-entities/patient/dto.go | 16 +++- .../domain/references/encounter/encounter.go | 9 +++ internal/infra/minio/minio.go | 25 ------- .../interface/main-handler/main-handler.go | 1 + .../interface/main-handler/patient/handler.go | 36 +++++++++ .../use-case/main-use-case/patient/case.go | 73 +++++++++++++++++++ .../use-case/main-use-case/patient/helper.go | 40 +++++++++- pkg/minio-helper/minio-helper.go | 34 +++++++-- pkg/minio-helper/tycovar.go | 29 ++++++++ pkg/upload-helper/upload-helper.go | 3 +- 10 files changed, 229 insertions(+), 37 deletions(-) create mode 100644 pkg/minio-helper/tycovar.go diff --git a/internal/domain/main-entities/patient/dto.go b/internal/domain/main-entities/patient/dto.go index 02e9b0e7..b8630a0f 100644 --- a/internal/domain/main-entities/patient/dto.go +++ b/internal/domain/main-entities/patient/dto.go @@ -1,13 +1,16 @@ package patient import ( + "mime/multipart" + "time" + ecore "simrs-vx/internal/domain/base-entities/core" ep "simrs-vx/internal/domain/main-entities/person" epa "simrs-vx/internal/domain/main-entities/person-address" epc "simrs-vx/internal/domain/main-entities/person-contact" epr "simrs-vx/internal/domain/main-entities/person-relative" erc "simrs-vx/internal/domain/references/common" - "time" + ere "simrs-vx/internal/domain/references/encounter" ) type CreateDto struct { @@ -56,6 +59,17 @@ type SearchDto struct { Search string `json:"search"` } +type UploadDto struct { + Id uint `json:"-"` + Code ere.UploadCode `json:"-"` + File multipart.File `json:"-"` + FileHeader *multipart.FileHeader `json:"-"` + Filename string `json:"-"` + Size int64 `json:"-"` + MimeType string `json:"-"` + MedRecNumber string `json:"-"` +} + type MetaDto struct { PageNumber int `json:"page_number"` PageSize int `json:"page_size"` diff --git a/internal/domain/references/encounter/encounter.go b/internal/domain/references/encounter/encounter.go index 8ed4dd81..14e6a269 100644 --- a/internal/domain/references/encounter/encounter.go +++ b/internal/domain/references/encounter/encounter.go @@ -85,3 +85,12 @@ func (ec EncounterClassCode) Code() string { return "UNKNOWN" } } + +func IsValidUploadCode(code UploadCode) bool { + switch UploadCode(code) { + case UCPRN, UCPDL, UCPP, UCPFC, UCMIR: + return true + default: + return false + } +} diff --git a/internal/infra/minio/minio.go b/internal/infra/minio/minio.go index 36e9c546..e403b964 100644 --- a/internal/infra/minio/minio.go +++ b/internal/infra/minio/minio.go @@ -2,9 +2,6 @@ package minio import ( "errors" - "io" - "net/url" - "time" a "github.com/karincake/apem" lo "github.com/karincake/apem/loggero" @@ -24,28 +21,6 @@ type MinioCfg struct { BucketName []string `yaml:"bucketName"` } -type UploadReaderInput struct { - File io.Reader - Name string - Size int64 - ContentType string - BucketName string -} - -type UploadPathInput struct { - BucketName string - ContentType string - Name string - Path string -} - -type PresignedGetInput struct { - Bucket string - Object string - Expiry time.Duration - ReqParams url.Values -} - type ResponsePostPolicy struct { Url string `json:"url"` FormData map[string]string `json:"form-data"` diff --git a/internal/interface/main-handler/main-handler.go b/internal/interface/main-handler/main-handler.go index f62f7036..7b02de54 100644 --- a/internal/interface/main-handler/main-handler.go +++ b/internal/interface/main-handler/main-handler.go @@ -235,6 +235,7 @@ func SetRoutes() http.Handler { "PATCH /{id}": patient.O.Update, "DELETE /{id}": patient.O.Delete, "GET /by-identifier": patient.O.Search, + "POST /{id}/upload": patient.O.Upload, }) /******************** sources ********************/ diff --git a/internal/interface/main-handler/patient/handler.go b/internal/interface/main-handler/patient/handler.go index c392ee4c..1bcdacee 100644 --- a/internal/interface/main-handler/patient/handler.go +++ b/internal/interface/main-handler/patient/handler.go @@ -10,6 +10,8 @@ import ( e "simrs-vx/internal/domain/main-entities/patient" u "simrs-vx/internal/use-case/main-use-case/patient" + + ere "simrs-vx/internal/domain/references/encounter" ) type myBase struct{} @@ -76,3 +78,37 @@ func (obj myBase) Search(w http.ResponseWriter, r *http.Request) { res, err := u.Search(dto) rw.DataResponse(w, res, err) } + +func (obj myBase) Upload(w http.ResponseWriter, r *http.Request) { + id := rw.ValidateInt(w, "id", r.PathValue("id")) + if id <= 0 { + return + } + + err := r.ParseMultipartForm(10 << 20) // 10 MB + if err != nil { + rw.DataResponse(w, nil, err) + return + } + + code := r.FormValue("code") + + file, header, err := r.FormFile("content") + if err != nil { + rw.DataResponse(w, nil, err) + return + } + defer file.Close() + + dto := e.UploadDto{} + dto.Id = uint(id) + dto.Code = ere.UploadCode(code) + dto.File = file + dto.FileHeader = header + dto.Filename = header.Filename + dto.Size = header.Size + dto.MimeType = header.Header.Get("Content-Type") + + res, err := u.Upload(dto) + rw.DataResponse(w, res, err) +} diff --git a/internal/use-case/main-use-case/patient/case.go b/internal/use-case/main-use-case/patient/case.go index b17c756a..6fca5ab1 100644 --- a/internal/use-case/main-use-case/patient/case.go +++ b/internal/use-case/main-use-case/patient/case.go @@ -1,6 +1,7 @@ package patient import ( + "errors" "strconv" e "simrs-vx/internal/domain/main-entities/patient" @@ -10,6 +11,8 @@ import ( upc "simrs-vx/internal/use-case/main-use-case/person-contact" upr "simrs-vx/internal/use-case/main-use-case/person-relative" + ere "simrs-vx/internal/domain/references/encounter" + pl "simrs-vx/pkg/logger" pu "simrs-vx/pkg/use-case-helper" @@ -359,3 +362,73 @@ func Search(input e.SearchDto) (*d.Data, error) { Data: data.ToResponse(), }, nil } + +func Upload(input e.UploadDto) (*d.Data, error) { + rdDto := e.ReadDetailDto{Id: uint16(input.Id)} + var data *e.Patient + var err error + + event := pl.Event{ + Feature: "Upload", + Source: source, + } + + // Start log + pl.SetLogInfo(&event, input, "started", "upload") + + err = dg.I.Transaction(func(tx *gorm.DB) error { + if !ere.IsValidUploadCode(input.Code) { + return errors.New("invalid upload code") + } + + pl.SetLogInfo(&event, rdDto, "started", "DBReadDetail") + if data, err = ReadDetailData(rdDto, &event, tx); err != nil { + return err + } + + if data.Person == nil { + return errors.New("person not found") + } + + person := data.Person + + input.MedRecNumber = *data.Number + pubUrl, err := uploadAndGenerateFileUrl(input, &event) + if err != nil { + event.Action = "" + } + switch input.Code { + case ere.UCPRN: + person.ResidentIdentityFileUrl = &pubUrl + case ere.UCPDL: + person.DrivingLicenseFileUrl = &pubUrl + case ere.UCPP: + person.PassportFileUrl = &pubUrl + case ere.UCPFC: + person.FamilyIdentityFileUrl = &pubUrl + default: + return errors.New("invalid upload code") + } + + if err := tx.Save(&person).Error; err != nil { + return err + } + + pl.SetLogInfo(&event, nil, "complete") + + return nil + }) + + if err != nil { + return nil, err + } + + return &d.Data{ + Meta: d.IS{ + "source": source, + "structure": "single-data", + "status": "uploaded", + }, + Data: data.ToResponse(), + }, nil +} diff --git a/internal/use-case/main-use-case/patient/helper.go b/internal/use-case/main-use-case/patient/helper.go index 09f94ef6..be1a4355 100644 --- a/internal/use-case/main-use-case/patient/helper.go +++ b/internal/use-case/main-use-case/patient/helper.go @@ -6,8 +6,16 @@ package patient import ( "fmt" - e "simrs-vx/internal/domain/main-entities/patient" + "path/filepath" "strconv" + "strings" + "time" + + e "simrs-vx/internal/domain/main-entities/patient" + + pl "simrs-vx/pkg/logger" + pmh "simrs-vx/pkg/minio-helper" + puh "simrs-vx/pkg/upload-helper" dg "github.com/karincake/apem/db-gorm-pg" ) @@ -81,3 +89,33 @@ func GenerateNextMedicalRecordNumber() (string, error) { return fmt.Sprintf(format, nextInt), nil } + +func uploadAndGenerateFileUrl(input e.UploadDto, event *pl.Event) (string, error) { + pl.SetLogInfo(event, input, "started", "uploadAndGenerateFileUrl") + bucket := string(input.Code) + ext := strings.ToLower(filepath.Ext(input.Filename)) + + if !puh.IsValidFileType(ext, bucket) { + return "", fmt.Errorf("invalid file type: %s", input.Filename) + } + objectName := fmt.Sprintf("%s%d%s", input.MedRecNumber, time.Now().UnixNano(), ext) + + uploadInput := pmh.UploadReaderInput{ + BucketName: bucket, + Name: objectName, + File: input.File, + Size: input.Size, + ContentType: input.MimeType, + } + + _, err := pmh.I.PutObject(uploadInput) + if err != nil { + return "", err + } + + // Build URL for access + publicUrl := pmh.I.GenerateUrl(bucket, objectName) + + pl.SetLogInfo(event, nil, "complete") + return publicUrl, nil +} diff --git a/pkg/minio-helper/minio-helper.go b/pkg/minio-helper/minio-helper.go index 637ca1b6..4c5c3952 100644 --- a/pkg/minio-helper/minio-helper.go +++ b/pkg/minio-helper/minio-helper.go @@ -27,17 +27,35 @@ func (repo *minioRepository) createBucket(bucketName string, region string) erro if err != nil { return err } - if exist { - return nil - } - if err := repo.client.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: region, ObjectLocking: true}); err != nil { - return err + if !exist { + // create bucket + if err := repo.client.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: region}); err != nil { + return err + } + + // set bucket policy to public read + policy := fmt.Sprintf(`{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"AWS": ["*"]}, + "Action": ["s3:GetObject"], + "Resource": ["arn:aws:s3:::%s/*"] + } + ] + }`, bucketName) + + if err := repo.client.SetBucketPolicy(context.Background(), bucketName, policy); err != nil { + return err + } } + return nil } // Upload file reader to MinIO -func (repo *minioRepository) PutObject(input m.UploadReaderInput) (*minio.UploadInfo, error) { +func (repo *minioRepository) PutObject(input UploadReaderInput) (*minio.UploadInfo, error) { if err := repo.createBucket(input.BucketName, m.O.GetRegion()); err != nil { return nil, err } @@ -55,7 +73,7 @@ func (repo *minioRepository) PutObject(input m.UploadReaderInput) (*minio.Upload } // Upload file path to MinIO -func (repo *minioRepository) FPutObject(input m.UploadPathInput) (*minio.UploadInfo, error) { +func (repo *minioRepository) FPutObject(input UploadPathInput) (*minio.UploadInfo, error) { if err := repo.createBucket(input.BucketName, m.O.GetRegion()); err != nil { return nil, err } @@ -120,7 +138,7 @@ func (repo *minioRepository) GeneratePresignedPost(policy *minio.PostPolicy) (*u } // create presigned url to get object -func (repo *minioRepository) GeneratePresignedGetObject(input m.PresignedGetInput) (*url.URL, error) { +func (repo *minioRepository) GeneratePresignedGetObject(input PresignedGetInput) (*url.URL, error) { presignedUrl, err := repo.client.PresignedGetObject(context.Background(), input.Bucket, input.Object, input.Expiry, input.ReqParams) if err != nil { return nil, err diff --git a/pkg/minio-helper/tycovar.go b/pkg/minio-helper/tycovar.go new file mode 100644 index 00000000..a9ac20dc --- /dev/null +++ b/pkg/minio-helper/tycovar.go @@ -0,0 +1,29 @@ +package miniohelper + +import ( + "io" + "net/url" + "time" +) + +type UploadReaderInput struct { + File io.Reader + Name string + Size int64 + ContentType string + BucketName string +} + +type UploadPathInput struct { + BucketName string + ContentType string + Name string + Path string +} + +type PresignedGetInput struct { + Bucket string + Object string + Expiry time.Duration + ReqParams url.Values +} diff --git a/pkg/upload-helper/upload-helper.go b/pkg/upload-helper/upload-helper.go index 3c80a176..0ce72ff1 100644 --- a/pkg/upload-helper/upload-helper.go +++ b/pkg/upload-helper/upload-helper.go @@ -38,9 +38,8 @@ func getValidFileTypesForBucket(bucketName string) []string { } // isValidFileType checks if the uploaded file type is allowed for the specific bucket -func isValidFileType(filename, bucketName string) bool { +func IsValidFileType(ext, bucketName string) bool { allowedTypes := getValidFileTypesForBucket(bucketName) - ext := strings.ToLower(filepath.Ext(filename)) for _, allowedExt := range allowedTypes { if ext == allowedExt { From a9e380f9571597e54278f9e9f27e5d46fe69b1fd Mon Sep 17 00:00:00 2001 From: dpurbosakti Date: Thu, 25 Sep 2025 11:14:47 +0700 Subject: [PATCH 3/3] feat (patient): remove previously uploaded file when a new one is created --- .../use-case/main-use-case/patient/case.go | 20 +++++++++++++++++++ .../use-case/main-use-case/patient/helper.go | 15 ++++++++++++++ pkg/minio-helper/minio-helper.go | 9 +++++++++ 3 files changed, 44 insertions(+) diff --git a/internal/use-case/main-use-case/patient/case.go b/internal/use-case/main-use-case/patient/case.go index 6fca5ab1..2d874b8b 100644 --- a/internal/use-case/main-use-case/patient/case.go +++ b/internal/use-case/main-use-case/patient/case.go @@ -397,19 +397,39 @@ func Upload(input e.UploadDto) (*d.Data, error) { if err != nil { event.Action = "" } + + fileUrl := "" switch input.Code { case ere.UCPRN: + if person.ResidentIdentityFileUrl != nil { + fileUrl = *person.ResidentIdentityFileUrl + } person.ResidentIdentityFileUrl = &pubUrl case ere.UCPDL: + if person.DrivingLicenseFileUrl != nil { + fileUrl = *person.DrivingLicenseFileUrl + } person.DrivingLicenseFileUrl = &pubUrl case ere.UCPP: + if person.PassportFileUrl != nil { + fileUrl = *person.PassportFileUrl + } person.PassportFileUrl = &pubUrl case ere.UCPFC: + if person.FamilyIdentityFileUrl != nil { + fileUrl = *person.FamilyIdentityFileUrl + } person.FamilyIdentityFileUrl = &pubUrl default: return errors.New("invalid upload code") } + if fileUrl != "" { + if err := removeUploadedFile(string(input.Code), fileUrl, &event); err != nil { + return err + } + } + if err := tx.Save(&person).Error; err != nil { return err } diff --git a/internal/use-case/main-use-case/patient/helper.go b/internal/use-case/main-use-case/patient/helper.go index be1a4355..82154b47 100644 --- a/internal/use-case/main-use-case/patient/helper.go +++ b/internal/use-case/main-use-case/patient/helper.go @@ -119,3 +119,18 @@ func uploadAndGenerateFileUrl(input e.UploadDto, event *pl.Event) (string, error pl.SetLogInfo(event, nil, "complete") return publicUrl, nil } + +func removeUploadedFile(bucket, fileUrl string, event *pl.Event) error { + pl.SetLogInfo(event, nil, "started", "removeUploadedFile") + filename, err := pmh.GetFilename(fileUrl) + if err != nil { + return err + } + + err = pmh.I.RemoveObject(bucket, filename) + if err != nil { + return err + } + pl.SetLogInfo(event, nil, "complete") + return nil +} diff --git a/pkg/minio-helper/minio-helper.go b/pkg/minio-helper/minio-helper.go index 4c5c3952..15c52ca5 100644 --- a/pkg/minio-helper/minio-helper.go +++ b/pkg/minio-helper/minio-helper.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/url" + "path" m "simrs-vx/internal/infra/minio" @@ -165,3 +166,11 @@ func GetEndpointUrl(bucket string) string { } return fmt.Sprintf("%s://%s/%s", mode, m.O.GetEndpoint(), bucket) } + +func GetFilename(rawUrl string) (string, error) { + parsed, err := url.Parse(rawUrl) + if err != nil { + return "", err + } + return path.Base(parsed.Path), nil +}