Changeset 0.22.4 (#146)
This commit is contained in:
110
server/handlers/export.go
Normal file
110
server/handlers/export.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ExportHandler converts markdown content to PDF or DOCX via pandoc.
|
||||
type ExportHandler struct{}
|
||||
|
||||
func NewExportHandler() *ExportHandler { return &ExportHandler{} }
|
||||
|
||||
type exportRequest struct {
|
||||
Content string `json:"content" binding:"required"`
|
||||
Format string `json:"format" binding:"required"` // "pdf" or "docx"
|
||||
Filename string `json:"filename"` // optional base name
|
||||
}
|
||||
|
||||
// Convert handles POST /api/v1/export.
|
||||
// Converts markdown content to the requested format and returns the file.
|
||||
func (h *ExportHandler) Convert(c *gin.Context) {
|
||||
var req exportRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate format
|
||||
switch req.Format {
|
||||
case "pdf", "docx":
|
||||
// ok
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "format must be pdf or docx"})
|
||||
return
|
||||
}
|
||||
|
||||
// Check pandoc availability
|
||||
if _, err := exec.LookPath("pandoc"); err != nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "pandoc is not installed on the server"})
|
||||
return
|
||||
}
|
||||
|
||||
// Create temp dir for conversion
|
||||
tmpDir, err := os.MkdirTemp("", "export-*")
|
||||
if err != nil {
|
||||
log.Printf("export: failed to create temp dir: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "export failed"})
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Write markdown to temp file
|
||||
inputPath := filepath.Join(tmpDir, "input.md")
|
||||
if err := os.WriteFile(inputPath, []byte(req.Content), 0644); err != nil {
|
||||
log.Printf("export: failed to write input: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "export failed"})
|
||||
return
|
||||
}
|
||||
|
||||
// Determine output filename
|
||||
baseName := "document"
|
||||
if req.Filename != "" {
|
||||
baseName = strings.TrimSuffix(req.Filename, filepath.Ext(req.Filename))
|
||||
}
|
||||
outputFile := baseName + "." + req.Format
|
||||
outputPath := filepath.Join(tmpDir, outputFile)
|
||||
|
||||
// Build pandoc command
|
||||
args := []string{inputPath, "-o", outputPath, "--standalone"}
|
||||
if req.Format == "pdf" {
|
||||
// Try to use a lightweight PDF engine
|
||||
for _, engine := range []string{"weasyprint", "wkhtmltopdf", "pdflatex"} {
|
||||
if _, err := exec.LookPath(engine); err == nil {
|
||||
args = append(args, "--pdf-engine="+engine)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(c.Request.Context(), "pandoc", args...)
|
||||
cmd.Dir = tmpDir
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
log.Printf("export: pandoc failed: %v\n%s", err, string(output))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "pandoc conversion failed",
|
||||
"details": string(output),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Serve the file
|
||||
contentType := "application/octet-stream"
|
||||
switch req.Format {
|
||||
case "pdf":
|
||||
contentType = "application/pdf"
|
||||
case "docx":
|
||||
contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
}
|
||||
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%q", outputFile))
|
||||
c.File(outputPath)
|
||||
c.Header("Content-Type", contentType)
|
||||
}
|
||||
Reference in New Issue
Block a user