feat (generate-file): not tested yet
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user