diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 00000000..17730a17 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,35 @@ +# Etapa de build +FROM golang:1.21-alpine AS builder + +# Instalar dependencias necesarias +RUN apk add --no-cache git + +# Establecer directorio de trabajo +WORKDIR /app/src + +# Copiar los archivos +COPY src/ . + +# Compilar el binario estático +RUN go mod init xslt-playground && \ + go get github.com/gin-gonic/gin && \ + go build -o server . + +# Etapa final (runtime) +FROM alpine:latest + +# Instalar Java (para Saxon) +RUN apk add --no-cache openjdk17-jre curl + +# Crear directorio de trabajo +WORKDIR /app + +# Copiar binario y configuración +COPY --from=builder /app/src/server . +COPY src/app.config . + +# Crear carpeta para Saxon y descargarlo +RUN mkdir -p /opt/saxon && \ + curl -L -o /opt/saxon/saxon-he.jar https://repo1.maven.org/maven2/net/sf/saxon/Saxon-HE/11.6/Saxon-HE-11.6.jar + +# Puerto por defecto (puede ser sobre diff --git a/backend/app.config b/backend/app.config new file mode 100644 index 00000000..26e34766 --- /dev/null +++ b/backend/app.config @@ -0,0 +1,4 @@ +{ + "port": "8000", + "saxon_path": "/opt/saxon/saxon-he.jar" +} diff --git a/backend/src/main.go b/backend/src/main.go new file mode 100644 index 00000000..1f52a7e7 --- /dev/null +++ b/backend/src/main.go @@ -0,0 +1,126 @@ +// xslt-playground-backend/main.go +package main + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "time" + + "github.com/gin-gonic/gin" +) + +type TransformRequest struct { + XSLT string `json:"xslt"` + Version string `json:"version"` + Parameters map[string]string `json:"parameters"` +} + +type TransformResponse struct { + Result string `json:"result"` +} + +type AppConfig struct { + Port string `json:"port"` + SaxonPath string `json:"saxon_path"` +} + +func loadConfig(filename string) (*AppConfig, error) { + data, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + var config AppConfig + if err := json.Unmarshal(data, &config); err != nil { + return nil, err + } + return &config, nil +} + +func main() { + config, err := loadConfig("app.config") + if err != nil { + log.Fatalf("failed to load config: %v", err) + } + + r := gin.Default() + + r.POST("/transform", func(c *gin.Context) { + var req TransformRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + tmpDir, err := ioutil.TempDir("", "xslt") + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "cannot create temp dir"}) + return + } + defer os.RemoveAll(tmpDir) + + xsltPath := filepath.Join(tmpDir, "transform.xsl") + inputPath := filepath.Join(tmpDir, "input.xml") + outputPath := filepath.Join(tmpDir, "result.xml") + + if err := os.WriteFile(xsltPath, []byte(req.XSLT), 0644); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "cannot write xslt"}) + return + } + + if err := os.WriteFile(inputPath, []byte(""), 0644); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "cannot write input"}) + return + } + + cmdArgs := []string{ + "-cp", config.SaxonPath, + "net.sf.saxon.Transform", + "-s:" + inputPath, + "-xsl:" + xsltPath, + "-o:" + outputPath, + } + for k, v := range req.Parameters { + cmdArgs = append(cmdArgs, k+"='"+v+"'") + } + + cmd := exec.Command("java", cmdArgs...) + var stderr bytes.Buffer + cmd.Stderr = &stderr + + timeout := time.Second * 10 + errChan := make(chan error, 1) + go func() { errChan <- cmd.Run() }() + + select { + case err := <-errChan: + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": stderr.String()}) + return + } + case <-time.After(timeout): + cmd.Process.Kill() + c.JSON(http.StatusRequestTimeout, gin.H{"error": "transformation timeout"}) + return + } + + result, err := os.ReadFile(outputPath) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "cannot read result"}) + return + } + + c.JSON(http.StatusOK, TransformResponse{Result: string(result)}) + }) + + r.GET("/", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "ok"}) + }) + + r.Run(":" + config.Port) +}