204 lines
5.7 KiB
Go
204 lines
5.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"slices"
|
|
|
|
"git.odit.services/lfk/document-server/models"
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/pdfcpu/pdfcpu/pkg/api"
|
|
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
|
|
)
|
|
|
|
// GenerateCard godoc
|
|
//
|
|
// @Summary Generate runner cards
|
|
// @Description Generate cards based on the provided data
|
|
// @Tags pdfs
|
|
// @Accept json
|
|
// @Param data body models.CardRequest true "Card data"
|
|
// @Produce application/pdf
|
|
// @Security ApiKeyAuth
|
|
// @Router /v1/pdfs/cards [post]
|
|
func (h *DefaultHandler) GenerateCard(c *fiber.Ctx) error {
|
|
logger := h.Logger.Named("GenerateCard")
|
|
|
|
cardRequest := new(models.CardRequest)
|
|
if err := c.BodyParser(cardRequest); err != nil {
|
|
logger.Errorw("Invalid request", "error", err)
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
if !slices.Contains([]string{"en", "de"}, cardRequest.Locale) {
|
|
logger.Errorw("Invalid locale", "locale", cardRequest.Locale)
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Invalid locale",
|
|
})
|
|
}
|
|
|
|
logger = logger.With("locale", cardRequest.Locale)
|
|
|
|
templateString, err := h.StaticService.GetTemplate(cardRequest.Locale, "card")
|
|
if err != nil {
|
|
logger.Errorw("Template not found", "error", err)
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Template not found",
|
|
})
|
|
}
|
|
template, err := h.Templater.StringToTemplate(templateString)
|
|
if err != nil {
|
|
logger.Errorw("Error parsing template", "error", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
segmentLength := calculateOptimalSegmentSize(len(cardRequest.Cards))
|
|
pdfs := []string{}
|
|
for i := 0; i < len(cardRequest.Cards); i += segmentLength {
|
|
|
|
segment := cardRequest.Cards[i:]
|
|
if len(segment) > segmentLength {
|
|
segment = cardRequest.Cards[i : i+segmentLength]
|
|
}
|
|
|
|
genConfig := &models.CardTemplateOptions{
|
|
CardSegments: splitCardSegments(segment),
|
|
EventName: h.Config.EventName,
|
|
CardSubtitle: h.Config.CardSubtitle,
|
|
BarcodeFormat: h.Config.CardBarcodeFormat,
|
|
BarcodePrefix: h.Config.CardBarcodePrefix,
|
|
}
|
|
|
|
logger.Info("Generating card html")
|
|
result, err := h.Templater.Execute(template, genConfig)
|
|
if err != nil {
|
|
logger.Errorw("Error executing template", "error", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
logger.Info("Generated card html")
|
|
c.Set(fiber.HeaderContentType, "text/html")
|
|
|
|
logger.Info("Converting html to pdf")
|
|
pdf, err := h.Converter.ToPdf(result, "a4", false)
|
|
if err != nil {
|
|
logger.Errorw("Error converting html to pdf", "error", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
tempFile, err := os.CreateTemp("", fmt.Sprintf("cards-%d-*.pdf", i/segmentLength))
|
|
if err != nil {
|
|
logger.Errorw("Error creating temp file", "error", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
defer os.Remove(tempFile.Name()) // Ensure cleanup even on error paths
|
|
|
|
if _, err := tempFile.Write(pdf); err != nil {
|
|
logger.Errorw("Error writing pdf to temp file", "error", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
tempFile.Close()
|
|
pdfs = append(pdfs, tempFile.Name())
|
|
}
|
|
|
|
outputFile := "./output.pdf"
|
|
conf := model.NewDefaultConfiguration()
|
|
err = api.MergeCreateFile(pdfs, outputFile, false, conf)
|
|
if err != nil {
|
|
logger.Errorw("Failed to merge PDFs", "error", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
// Clean up individual PDF files
|
|
for _, file := range pdfs {
|
|
if err := os.Remove(file); err != nil {
|
|
logger.Warnw("Failed to remove temporary PDF file", "file", file, "error", err)
|
|
}
|
|
}
|
|
|
|
// Set headers and return the merged PDF
|
|
c.Set(fiber.HeaderContentType, "application/pdf")
|
|
c.Set(fiber.HeaderContentDisposition, "attachment; filename=runner-cards.pdf")
|
|
pdfBytes, err := os.ReadFile(outputFile)
|
|
if err != nil {
|
|
logger.Errorw("Failed to read merged PDF", "error", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
os.Remove(outputFile) // Clean up the merged file
|
|
|
|
logger.Info("Converted html to pdf")
|
|
|
|
c.Set(fiber.HeaderContentType, "application/pdf")
|
|
c.Set(fiber.HeaderContentDisposition, "attachment; filename=runner-cards.pdf")
|
|
return c.Send(pdfBytes)
|
|
}
|
|
|
|
func calculateOptimalSegmentSize(totalCards int) int {
|
|
if totalCards < 30 {
|
|
return 25 // Reduces overhead for really small batches
|
|
}
|
|
|
|
// Base size for small batches
|
|
if totalCards < 100 {
|
|
return 50
|
|
}
|
|
|
|
// For medium batches
|
|
if totalCards < 500 {
|
|
return 75
|
|
}
|
|
|
|
// For large batches, be more conservative
|
|
return 100
|
|
}
|
|
|
|
func invertCardArrayItemPairs(cards []models.Card) []models.Card {
|
|
inverted := make([]models.Card, 0)
|
|
for i := 0; i < len(cards); i += 2 {
|
|
if i+1 < len(cards) {
|
|
inverted = append(inverted, cards[i+1])
|
|
}
|
|
inverted = append(inverted, cards[i])
|
|
}
|
|
return inverted
|
|
}
|
|
|
|
func splitCardSegments(cards []models.Card) []models.CardTemplateSegment {
|
|
cardSegments := make([]models.CardTemplateSegment, 0)
|
|
const currentCards = 0
|
|
for i := 0; i < len(cards); i += 10 {
|
|
segmentLength := 10
|
|
if len(cards)-i < 10 {
|
|
segmentLength = len(cards) - i
|
|
}
|
|
segment := cards[i : i+segmentLength]
|
|
if segmentLength%2 != 0 {
|
|
segment = append(segment, models.Card{
|
|
ID: 0,
|
|
Enabled: false,
|
|
Runner: models.Runner{},
|
|
Code: "",
|
|
})
|
|
}
|
|
cardSegments = append(cardSegments, models.CardTemplateSegment{
|
|
Cards: segment,
|
|
CardsSwapped: invertCardArrayItemPairs(segment),
|
|
})
|
|
}
|
|
return cardSegments
|
|
}
|