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 }