feat (generate-file): not tested yet

This commit is contained in:
dpurbosakti
2025-11-17 17:18:22 +07:00
parent 8073236d06
commit 69a13dd37d
9 changed files with 340 additions and 16 deletions
+1
View File
@@ -25,6 +25,7 @@ require (
require (
ariga.io/atlas v0.36.2-0.20250806044935-5bb51a0a956e // indirect
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.3 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-redis/redis v6.15.9+incompatible // indirect
+2
View File
@@ -19,6 +19,8 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.3 h1:vrA6+R1BMLKMTbos8jAeuBrImHPGtY4gTlcue3OIej8=
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.3/go.mod h1:SQq4xfIdvf6WYKSDxAJc+xOJdolt+/bc1jnQKMtPMvQ=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -0,0 +1,18 @@
package generatefile
import (
"net/http"
rw "github.com/karincake/risoles"
u "simrs-vx/internal/use-case/main-use-case/generate-file"
)
func Generate(w http.ResponseWriter, r *http.Request) {
dto := u.GenerateDto{}
if res := rw.ValidateStructByIOR(w, r.Body, &dto); !res {
return
}
res, err := u.Generate(dto)
rw.DataResponse(w, res, err)
}
@@ -18,6 +18,7 @@ import (
encounter "simrs-vx/internal/interface/main-handler/encounter"
encounterdocument "simrs-vx/internal/interface/main-handler/encounter-document"
generalconsent "simrs-vx/internal/interface/main-handler/general-consent"
generatefile "simrs-vx/internal/interface/main-handler/generate-file"
internalreference "simrs-vx/internal/interface/main-handler/internal-reference"
materialorder "simrs-vx/internal/interface/main-handler/material-order"
materialorderitem "simrs-vx/internal/interface/main-handler/material-order-item"
@@ -280,6 +281,7 @@ func SetRoutes() http.Handler {
hc.RegCrud(r, "/v1/upload-file", uploadfile.O)
hc.RegCrud(r, "/v1/encounter-document", encounterdocument.O)
hc.RegCrud(r, "/v1/general-consent", generalconsent.O)
r.HandleFunc("POST /v1/generate-file", generatefile.Generate)
/******************** actor ********************/
hc.RegCrud(r, "/v1/person", person.O)
@@ -1,15 +1,18 @@
package generatefile
import (
// "encoding/json"
"encoding/json"
"errors"
// egc "simrs-vx/internal/domain/main-entities/general-consent"
// ugc "simrs-vx/internal/use-case/main-use-case/general-consent"
egc "simrs-vx/internal/domain/main-entities/general-consent"
ugc "simrs-vx/internal/use-case/main-use-case/general-consent"
pl "simrs-vx/pkg/logger"
dg "github.com/karincake/apem/db-gorm-pg"
d "github.com/karincake/dodol"
erc "simrs-vx/internal/domain/references/common"
ere "simrs-vx/internal/domain/references/encounter"
)
@@ -28,18 +31,55 @@ func Generate(input GenerateDto) (*d.Data, error) {
// general-consent
case ere.DTCGC:
// get value from general consent by ref_id
// gc, err := ugc.ReadDetailData(ugc.ReadDetailDto{Ref_Id: input.Ref_Id}, &event, nil)
// if err != nil {
// return nil, err
// }
gc, err := ugc.ReadDetailData(egc.ReadDetailDto{Id: *input.Ref_Id}, &event, nil)
if err != nil {
return nil, err
}
// map template data
// gc
// templateData := GeneralConsentPDF{}
// gcUnmarshalled := json.Unmarshal(g)
templateData := GeneralConsentPDF{}
if gc.Value != nil {
err := json.Unmarshal([]byte(*gc.Value), &templateData)
if err != nil {
event.ErrInfo = pl.ErrorInfo{
Code: "data-unmarshal-fail",
Detail: err.Error(),
Raw: err,
}
return nil, err
}
} else {
return nil, errors.New("no value in this general consent")
}
return nil, nil
input.FormatType = erc.DFTCPDF
input.TemplateName = TDNGC
// generate file
urlPub, err := generateFile(input, templateData)
if err != nil {
return nil, err
}
gc.FileUrl = &urlPub
if err := dg.I.Save(&gc).Error; err != nil {
return nil, err
}
response := ResponseDto{
FileUrl: urlPub,
}
return &d.Data{
Meta: d.II{
"source": source,
"structure": "single-data",
"status": "created",
},
Data: response,
}, nil
default:
return nil, errors.New("invalid type code")
}
return nil, nil
}
@@ -0,0 +1,83 @@
package generatefile
import (
"errors"
"fmt"
"mime"
"path/filepath"
"time"
erc "simrs-vx/internal/domain/references/common"
docscfg "simrs-vx/internal/infra/docs-cfg"
pf "simrs-vx/pkg/file-helper"
pm "simrs-vx/pkg/minio-helper"
pp "simrs-vx/pkg/pdf-helper"
)
// generate temporary file, upload to minio, generate public url, delete temporary file
func generateFile(input GenerateDto, templateData any) (string, error) {
newPath, err := pf.PathToSaveFile(fmt.Sprintf("./public/%s/%d/%s", input.EntityType_Code, *input.Ref_Id, input.Type_Code))
if err != nil {
return "", err
}
fPath := fmt.Sprintf("%s/%s_%s.%s", newPath, input.Type_Code, time.Now().Format("20060102150405"), input.FormatType)
templatePath := docscfg.O.GetPath() + string(input.TemplateName)
switch input.FormatType {
case erc.DFTCPDF:
if err := generatePDF(GeneratePDFdto{
TemplatePath: templatePath,
TemplateData: templateData,
PdfPath: fPath,
}); err != nil {
return "", err
}
case erc.DFTCTXLSX:
// TODO: generate xlsx
case erc.DFTCTCSV:
// TODO: generate csv
default:
return "", errors.New("invalid format type")
}
bucketName := input.EntityType_Code
objectName := fmt.Sprintf("%v/%s-%d", *input.Ref_Id, input.Type_Code, time.Now().UnixNano())
pdfUpload := pm.UploadPathInput{
BucketName: string(bucketName),
Name: objectName,
Path: fPath,
ContentType: mime.TypeByExtension(filepath.Ext(fPath)),
}
// create bucket if not exist, create object in bucket
info, err := pm.I.FPutObject(pdfUpload)
if err != nil {
return "", err
}
// generate public url
urlPub := pm.I.GenerateUrl(info.Bucket, info.Key)
if err := pf.DeleteFolder(fPath); err != nil {
return "", err
}
return urlPub, nil
}
func generatePDF(input GeneratePDFdto) error {
// parse template data into html template
r := pp.NewRequestPdf("")
if err := r.ParseTemplate(input.TemplatePath, input.TemplateData); err == nil {
_, err := r.GenerateByCommand(input.PdfPath, input.TemplatePath)
if err != nil {
return errors.New("generate pdf by command error : " + err.Error())
}
} else {
return errors.New("parse template error : " + err.Error())
}
return nil
}
@@ -14,12 +14,26 @@ type GeneralConsentPDF struct {
}
type GenerateDto struct {
EntityType_Code ere.EntityTypeCode `form:"entityType_code"`
Ref_Id *uint `form:"ref_id"`
Type_Code ere.DocTypeCode `form:"type_code"`
FormatType erc.DocFormatTypeCode `form:"formatType"`
EntityType_Code ere.EntityTypeCode `json:"entityType_code"`
Ref_Id *uint `json:"ref_id"`
Type_Code ere.DocTypeCode `json:"type_code"`
FormatType erc.DocFormatTypeCode `json:"formatType"`
TemplateName TemplateDocsName `json:"-"`
}
type GeneratePDFdto struct {
TemplatePath string
TemplateData any
PdfPath string
}
type ResponseDto struct {
FileUrl string `json:"fileUrl"`
}
type TemplateDocsName string
// TemplateDocsName is the name of the template file in the assets/docs folder
const (
TDNGC TemplateDocsName = "general-consent.html"
)
+33
View File
@@ -0,0 +1,33 @@
package filehelper
import (
"fmt"
"os"
"path/filepath"
)
// const DEFAULT_EXPIRY_FILES = time.Hour * 24 * 7
func PathAgreement(medicalNumber string) (string, error) {
outputPath := fmt.Sprintf("./public/patient/%s", medicalNumber)
err := os.MkdirAll(outputPath, os.ModePerm)
return outputPath, err
}
func PathToSaveFile(outputPath string) (string, error) {
err := os.MkdirAll(outputPath, os.ModePerm)
return outputPath, err
}
func RenameFile(srcPath, dstPath string) error {
return os.Rename(srcPath, dstPath)
}
func DeleteFolder(path string) error {
return os.RemoveAll(path)
}
func PathToUrl(fileName string) *string {
fileUrl := filepath.ToSlash(fmt.Sprintf("%c%s", os.PathSeparator, fileName))
return &fileUrl
}
+131
View File
@@ -0,0 +1,131 @@
package pdfhelper
import (
"bytes"
"html/template"
"log"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/SebastiaanKlippert/go-wkhtmltopdf"
)
// pdf requestpdf struct
type RequestPdf struct {
body string
}
// new request to pdf function
func NewRequestPdf(body string) *RequestPdf {
return &RequestPdf{
body: body,
}
}
// parsing template function
func (r *RequestPdf) ParseTemplate(templatePath string, data interface{}) error {
f := strings.Split(templatePath, "/")
fileName := f[len(f)-1]
funcs := template.FuncMap{
"nl2br": func(text string) template.HTML {
return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "<br>", -1))
},
}
t, err := template.New(fileName).Funcs(funcs).ParseFiles(templatePath)
if err != nil {
return err
}
buf := new(bytes.Buffer)
if err = t.Execute(buf, data); err != nil {
return err
}
r.body = buf.String()
return nil
}
func (r *RequestPdf) GenerateByCommand(pdfPath string, templatePath string) (bool, error) {
// wkhtmltopdf -L 0 -R 0 -B 0 -s Legal --enable-local-file-access resultAntigen2.html out.pdf
t := time.Now().Unix()
if _, err := os.Stat("cloneTemplate/"); os.IsNotExist(err) {
errDir := os.Mkdir("cloneTemplate/", 0777)
if errDir != nil {
log.Fatal(errDir)
}
}
htmlName := strconv.FormatInt(int64(t), 10) + ".html"
err := os.WriteFile("cloneTemplate/"+htmlName, []byte(r.body), 0644)
if err != nil {
panic(err)
}
cmd := exec.Command("wkhtmltopdf", "--enable-local-file-access", "-L", "0", "-R", "0", "-B", "0", "-s", "A4", "cloneTemplate/"+htmlName, pdfPath)
if err := cmd.Run(); err != nil {
return false, err
}
dir, err := os.Getwd()
if err != nil {
panic(err)
}
defer os.RemoveAll(dir + "/cloneTemplate")
return true, nil
}
// generate pdf function
func (r *RequestPdf) GeneratePDF(pdfPath string) (bool, error) {
t := time.Now().Unix()
if _, err := os.Stat("cloneTemplate/"); os.IsNotExist(err) {
errDir := os.Mkdir("cloneTemplate/", 0777)
if errDir != nil {
log.Fatal(errDir)
}
}
err1 := os.WriteFile("cloneTemplate/"+strconv.FormatInt(int64(t), 10)+".html", []byte(r.body), 0644)
if err1 != nil {
panic(err1)
}
f, err := os.Open("cloneTemplate/" + strconv.FormatInt(int64(t), 10) + ".html")
if f != nil {
defer f.Close()
}
if err != nil {
log.Fatal(err)
}
pdfg, err := wkhtmltopdf.NewPDFGenerator()
if err != nil {
log.Fatal(err)
}
pdfg.AddPage(wkhtmltopdf.NewPageReader(f))
pdfg.PageSize.Set(wkhtmltopdf.PageSizeA4)
pdfg.Dpi.Set(300)
err = pdfg.Create()
if err != nil {
log.Fatal(err)
}
err = pdfg.WriteFile(pdfPath)
if err != nil {
log.Fatal(err)
}
dir, err := os.Getwd()
if err != nil {
panic(err)
}
defer os.RemoveAll(dir + "/cloneTemplate")
return true, nil
}