111 lines
3.0 KiB
Go
111 lines
3.0 KiB
Go
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)
|
|
}
|