package handlers import ( "database/sql" "encoding/json" "fmt" "html" "log" "net/http" "os" "strings" "time" "code.springupsoftware.com/cmc/cmc-sales/internal/cmc/db" "code.springupsoftware.com/cmc/cmc-sales/internal/cmc/pdf" ) // escapeToHTML converts plain text to HTML with newlines as
func escapeToHTML(s string) string { s = html.EscapeString(s) s = strings.ReplaceAll(s, "\n", "
") return s } // InvoiceLineItemRequest is the JSON shape for a single line item. type InvoiceLineItemRequest struct { ItemNumber string `json:"item_number"` Quantity string `json:"quantity"` Title string `json:"title"` Description string `json:"description"` IsHTML bool `json:"is_html"` // Flag to indicate description contains HTML UnitPrice float64 `json:"unit_price"` TotalPrice float64 `json:"total_price"` NetUnitPrice float64 `json:"net_unit_price"` NetPrice float64 `json:"net_price"` DiscountPercent float64 `json:"discount_percent"` DiscountAmountUnit float64 `json:"discount_amount_unit"` DiscountAmountTotal float64 `json:"discount_amount_total"` Option int `json:"option"` HasTextPrices bool `json:"has_text_prices"` UnitPriceString string `json:"unit_price_string"` GrossPriceString string `json:"gross_price_string"` } // InvoicePDFRequest is the expected payload from the PHP app. type InvoicePDFRequest struct { DocumentID int32 `json:"document_id"` InvoiceNumber string `json:"invoice_number"` // e.g. "INV-001234" InvoiceTitle string `json:"invoice_title"` CustomerName string `json:"customer_name"` ContactEmail string `json:"contact_email"` ContactName string `json:"contact_name"` UserFirstName string `json:"user_first_name"` UserLastName string `json:"user_last_name"` UserEmail string `json:"user_email"` YourReference string `json:"your_reference"` ShipVia string `json:"ship_via"` FOB string `json:"fob"` IssueDate string `json:"issue_date"` // ISO date: 2006-01-02 IssueDateString string `json:"issue_date_string"` // Formatted: "12 January 2026" CurrencySymbol string `json:"currency_symbol"` // e.g. "$" CurrencyCode string `json:"currency_code"` // e.g. "AUD", "USD" ShowGST bool `json:"show_gst"` BillTo string `json:"bill_to"` ShipTo string `json:"ship_to"` ShippingDetails string `json:"shipping_details"` CustomerOrderNumber string `json:"customer_order_number"` JobTitle string `json:"job_title"` PaymentTerms string `json:"payment_terms"` CustomerABN string `json:"customer_abn"` Subtotal interface{} `json:"subtotal"` // Can be float or "TBA" GSTAmount interface{} `json:"gst_amount"` Total interface{} `json:"total"` LineItems []InvoiceLineItemRequest `json:"line_items"` OutputDir string `json:"output_dir"` // optional override } // GenerateInvoicePDF handles POST /api/pdf/invoice and writes a PDF to disk. // It returns JSON: {"filename":".pdf"} func GenerateInvoicePDF(w http.ResponseWriter, r *http.Request) { var req InvoicePDFRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON payload", http.StatusBadRequest) return } if req.InvoiceTitle == "" || req.CustomerName == "" { http.Error(w, "invoice_title and customer_name are required", http.StatusBadRequest) return } issueDate := time.Now() if req.IssueDate != "" { if parsed, err := time.Parse("2006-01-02", req.IssueDate); err == nil { issueDate = parsed } } outputDir := req.OutputDir if outputDir == "" { outputDir = os.Getenv("PDF_OUTPUT_DIR") } if outputDir == "" { outputDir = "../php/app/webroot/pdf" } if err := os.MkdirAll(outputDir, 0755); err != nil { log.Printf("GenerateInvoicePDF: failed to create output dir: %v", err) http.Error(w, "failed to prepare output directory", http.StatusInternalServerError) return } // Map request into the existing PDF generation types. doc := &db.Document{ID: req.DocumentID} inv := &db.Invoice{Title: req.InvoiceTitle} cust := &db.Customer{Name: req.CustomerName} lineItems := make([]db.GetLineItemsTableRow, len(req.LineItems)) for i, li := range req.LineItems { lineItems[i] = db.GetLineItemsTableRow{ ItemNumber: li.ItemNumber, Quantity: li.Quantity, Title: li.Title, Description: li.Description, GrossUnitPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.UnitPrice), Valid: true}, GrossPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.TotalPrice), Valid: true}, DiscountPercent: sql.NullString{String: fmt.Sprintf("%.2f", li.DiscountPercent), Valid: li.DiscountPercent > 0}, DiscountAmountUnit: sql.NullString{String: fmt.Sprintf("%.2f", li.DiscountAmountUnit), Valid: li.DiscountAmountUnit > 0}, DiscountAmountTotal: sql.NullString{String: fmt.Sprintf("%.2f", li.DiscountAmountTotal), Valid: li.DiscountAmountTotal > 0}, } } data := &pdf.InvoicePDFData{ Document: doc, Invoice: inv, Customer: cust, LineItems: lineItems, CurrencySymbol: req.CurrencySymbol, CurrencyCode: req.CurrencyCode, ShowGST: req.ShowGST, ShipVia: req.ShipVia, FOB: req.FOB, IssueDate: issueDate, IssueDateString: req.IssueDateString, EmailTo: req.ContactEmail, Attention: req.ContactName, FromName: fmt.Sprintf("%s %s", req.UserFirstName, req.UserLastName), FromEmail: req.UserEmail, YourReference: req.YourReference, BillTo: req.BillTo, ShipTo: req.ShipTo, ShippingDetails: req.ShippingDetails, CustomerOrderNumber: req.CustomerOrderNumber, JobTitle: req.JobTitle, PaymentTerms: req.PaymentTerms, CustomerABN: req.CustomerABN, Subtotal: req.Subtotal, GSTAmount: req.GSTAmount, Total: req.Total, } filename, err := pdf.GenerateInvoicePDF(data, outputDir) if err != nil { log.Printf("GenerateInvoicePDF: failed to generate PDF: %v", err) http.Error(w, "failed to generate PDF", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]string{"filename": filename}) } // GenerateInvoicePDFHTML generates invoice using HTML template and chromedp func GenerateInvoicePDFHTML(w http.ResponseWriter, r *http.Request) { var req InvoicePDFRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON payload", http.StatusBadRequest) return } if req.InvoiceTitle == "" || req.CustomerName == "" { http.Error(w, "invoice_title and customer_name are required", http.StatusBadRequest) return } issueDate := time.Now() if req.IssueDate != "" { if parsed, err := time.Parse("2006-01-02", req.IssueDate); err == nil { issueDate = parsed } } outputDir := req.OutputDir if outputDir == "" { outputDir = os.Getenv("PDF_OUTPUT_DIR") } if outputDir == "" { outputDir = "../php/app/webroot/pdf" } if err := os.MkdirAll(outputDir, 0755); err != nil { log.Printf("GenerateInvoicePDFHTML: failed to create output dir: %v", err) http.Error(w, "failed to prepare output directory", http.StatusInternalServerError) return } // Map request into the existing PDF generation types doc := &db.Document{ID: req.DocumentID, CmcReference: req.InvoiceNumber} inv := &db.Invoice{Title: req.InvoiceTitle} cust := &db.Customer{Name: req.CustomerName} log.Printf("GenerateInvoicePDFHTML: Setting invoice number to: %s", req.InvoiceNumber) lineItems := make([]db.GetLineItemsTableRow, len(req.LineItems)) for i, li := range req.LineItems { // Escape description if it's not HTML desc := li.Description if !li.IsHTML { // Escape plain text to HTML entities desc = escapeToHTML(desc) } // Calculate the final price after discount finalPrice := li.TotalPrice - li.DiscountAmountTotal lineItems[i] = db.GetLineItemsTableRow{ ItemNumber: li.ItemNumber, Quantity: li.Quantity, Title: li.Title, Description: desc, GrossUnitPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.UnitPrice), Valid: true}, GrossPrice: sql.NullString{String: fmt.Sprintf("%.2f", finalPrice), Valid: true}, DiscountAmountTotal: sql.NullString{String: fmt.Sprintf("%.2f", li.DiscountAmountTotal), Valid: li.DiscountAmountTotal > 0}, } } data := &pdf.InvoicePDFData{ Document: doc, Invoice: inv, Customer: cust, LineItems: lineItems, CurrencySymbol: req.CurrencySymbol, CurrencyCode: req.CurrencyCode, ShowGST: req.ShowGST, ShipVia: req.ShipVia, FOB: req.FOB, IssueDate: issueDate, IssueDateString: req.IssueDateString, EmailTo: req.ContactEmail, Attention: req.ContactName, FromName: fmt.Sprintf("%s %s", req.UserFirstName, req.UserLastName), FromEmail: req.UserEmail, YourReference: req.YourReference, BillTo: req.BillTo, ShipTo: req.ShipTo, ShippingDetails: req.ShippingDetails, CustomerOrderNumber: req.CustomerOrderNumber, JobTitle: req.JobTitle, PaymentTerms: req.PaymentTerms, CustomerABN: req.CustomerABN, Subtotal: req.Subtotal, GSTAmount: req.GSTAmount, Total: req.Total, } // Use HTML generator instead of gofpdf htmlGen := pdf.NewHTMLInvoiceGenerator(outputDir) filename, err := htmlGen.GenerateInvoicePDF(data) if err != nil { log.Printf("GenerateInvoicePDFHTML: failed to generate PDF: %v", err) http.Error(w, fmt.Sprintf("failed to generate PDF: %v", err), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]string{"filename": filename}) } // QuoteLineItemRequest reuses the invoice item shape type QuoteLineItemRequest = InvoiceLineItemRequest // QuotePDFRequest payload from PHP for quotes type QuotePDFRequest struct { DocumentID int32 `json:"document_id"` CmcReference string `json:"cmc_reference"` Revision int32 `json:"revision"` CreatedDate string `json:"created_date"` // YYYY-MM-DD CreatedDateString string `json:"created_date_string"` // j M Y format DateIssued string `json:"date_issued"` CustomerName string `json:"customer_name"` ContactEmail string `json:"contact_email"` ContactName string `json:"contact_name"` UserFirstName string `json:"user_first_name"` UserLastName string `json:"user_last_name"` UserEmail string `json:"user_email"` CurrencySymbol string `json:"currency_symbol"` CurrencyCode string `json:"currency_code"` ShowGST bool `json:"show_gst"` CommercialComments string `json:"commercial_comments"` DeliveryTime string `json:"delivery_time"` PaymentTerms string `json:"payment_terms"` DaysValid int32 `json:"daysValid"` DeliveryPoint string `json:"delivery_point"` ExchangeRate string `json:"exchange_rate"` CustomsDuty string `json:"customs_duty"` GSTPhrase string `json:"gst_phrase"` SalesEngineer string `json:"sales_engineer"` BillTo string `json:"bill_to"` ShipTo string `json:"ship_to"` LineItems []QuoteLineItemRequest `json:"line_items"` Pages []string `json:"pages"` OutputDir string `json:"output_dir"` } // GenerateQuotePDF handles POST /go/pdf/generate-quote func GenerateQuotePDF(w http.ResponseWriter, r *http.Request) { var req QuotePDFRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON payload", http.StatusBadRequest) return } if req.CmcReference == "" || req.CustomerName == "" { http.Error(w, "cmc_reference and customer_name are required", http.StatusBadRequest) return } created := time.Now() if req.CreatedDate != "" { if parsed, err := time.Parse("2006-01-02", req.CreatedDate); err == nil { created = parsed } } outputDir := req.OutputDir if outputDir == "" { outputDir = os.Getenv("PDF_OUTPUT_DIR") } if outputDir == "" { outputDir = "../php/app/webroot/pdf" } if err := os.MkdirAll(outputDir, 0755); err != nil { log.Printf("GenerateQuotePDF: failed to create output dir: %v", err) http.Error(w, "failed to prepare output directory", http.StatusInternalServerError) return } // Map request into PDF data doc := &db.Document{ID: req.DocumentID, CmcReference: req.CmcReference, Revision: req.Revision, Created: created} cust := &db.Customer{Name: req.CustomerName} user := &db.GetUserRow{FirstName: req.UserFirstName, LastName: req.UserLastName, Email: req.UserEmail} lineItems := make([]db.GetLineItemsTableRow, len(req.LineItems)) for i, li := range req.LineItems { lineItems[i] = db.GetLineItemsTableRow{ ItemNumber: li.ItemNumber, Quantity: li.Quantity, Title: li.Title, GrossUnitPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.UnitPrice), Valid: true}, GrossPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.TotalPrice), Valid: true}, } } data := &pdf.QuotePDFData{ Document: doc, Customer: cust, EmailTo: req.ContactEmail, Attention: req.ContactName, User: user, LineItems: lineItems, CurrencySymbol: req.CurrencySymbol, CurrencyCode: req.CurrencyCode, ShowGST: req.ShowGST, CommercialComments: req.CommercialComments, DeliveryTime: req.DeliveryTime, PaymentTerms: req.PaymentTerms, DaysValid: int(req.DaysValid), DeliveryPoint: req.DeliveryPoint, ExchangeRate: req.ExchangeRate, CustomsDuty: req.CustomsDuty, GSTPhrase: req.GSTPhrase, SalesEngineer: req.SalesEngineer, BillTo: req.BillTo, ShipTo: req.ShipTo, IssueDateString: req.CreatedDateString, Pages: req.Pages, } filename, err := pdf.GenerateQuotePDF(data, outputDir) if err != nil { log.Printf("GenerateQuotePDF: failed to generate PDF: %v", err) http.Error(w, "failed to generate PDF", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]string{"filename": filename}) } // PurchaseOrderLineItemRequest reuses the invoice item shape type PurchaseOrderLineItemRequest = InvoiceLineItemRequest // PurchaseOrderPDFRequest payload from PHP for POs type PurchaseOrderPDFRequest struct { DocumentID int32 `json:"document_id"` Title string `json:"title"` IssueDate string `json:"issue_date"` // YYYY-MM-DD IssueDateString string `json:"issue_date_string"` // formatted date PrincipleName string `json:"principle_name"` PrincipleReference string `json:"principle_reference"` OrderedFrom string `json:"ordered_from"` DispatchBy string `json:"dispatch_by"` DeliverTo string `json:"deliver_to"` ShippingInstructions string `json:"shipping_instructions"` CurrencySymbol string `json:"currency_symbol"` CurrencyCode string `json:"currency_code"` ShowGST bool `json:"show_gst"` Subtotal float64 `json:"subtotal"` GSTAmount float64 `json:"gst_amount"` Total float64 `json:"total"` LineItems []PurchaseOrderLineItemRequest `json:"line_items"` OutputDir string `json:"output_dir"` } // GeneratePurchaseOrderPDF handles POST /go/pdf/generate-po func GeneratePurchaseOrderPDF(w http.ResponseWriter, r *http.Request) { var req PurchaseOrderPDFRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON payload", http.StatusBadRequest) return } if req.Title == "" || req.PrincipleName == "" { http.Error(w, "title and principle_name are required", http.StatusBadRequest) return } issueDate := time.Now() if req.IssueDate != "" { if parsed, err := time.Parse("2006-01-02", req.IssueDate); err == nil { issueDate = parsed } } outputDir := req.OutputDir if outputDir == "" { outputDir = os.Getenv("PDF_OUTPUT_DIR") } if outputDir == "" { outputDir = "../php/app/webroot/pdf" } if err := os.MkdirAll(outputDir, 0755); err != nil { log.Printf("GeneratePurchaseOrderPDF: failed to create output dir: %v", err) http.Error(w, "failed to prepare output directory", http.StatusInternalServerError) return } doc := &db.Document{ID: req.DocumentID} po := &db.PurchaseOrder{ Title: req.Title, PrincipleReference: req.PrincipleReference, IssueDate: issueDate, OrderedFrom: req.OrderedFrom, DispatchBy: req.DispatchBy, DeliverTo: req.DeliverTo, ShippingInstructions: req.ShippingInstructions, } principle := &db.Principle{Name: req.PrincipleName} lineItems := make([]db.GetLineItemsTableRow, len(req.LineItems)) for i, li := range req.LineItems { lineItems[i] = db.GetLineItemsTableRow{ ItemNumber: li.ItemNumber, Quantity: li.Quantity, Title: li.Title, GrossUnitPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.UnitPrice), Valid: true}, GrossPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.TotalPrice), Valid: true}, } } data := &pdf.PurchaseOrderPDFData{ Document: doc, PurchaseOrder: po, Principle: principle, LineItems: lineItems, CurrencySymbol: req.CurrencySymbol, CurrencyCode: req.CurrencyCode, ShowGST: req.ShowGST, Subtotal: req.Subtotal, GSTAmount: req.GSTAmount, Total: req.Total, IssueDateString: req.IssueDateString, } filename, err := pdf.GeneratePurchaseOrderPDF(data, outputDir) if err != nil { log.Printf("GeneratePurchaseOrderPDF: failed to generate PDF: %v", err) http.Error(w, "failed to generate PDF", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]string{"filename": filename}) } // GeneratePackingListPDF handles POST /go/pdf/generate-packinglist func GeneratePackingListPDF(w http.ResponseWriter, r *http.Request) { var req PackingListPDFRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON payload", http.StatusBadRequest) return } if req.CustomerName == "" { http.Error(w, "customer_name is required", http.StatusBadRequest) return } outputDir := req.OutputDir if outputDir == "" { outputDir = os.Getenv("PDF_OUTPUT_DIR") } if outputDir == "" { outputDir = "../php/app/webroot/pdf" } if err := os.MkdirAll(outputDir, 0755); err != nil { log.Printf("GeneratePackingListPDF: failed to create output dir: %v", err) http.Error(w, "failed to prepare output directory", http.StatusInternalServerError) return } // Reuse the invoice generator structure but label as PACKING LIST via DetailsBox // Build minimal data shape doc := &db.Document{ID: req.DocumentID} cust := &db.Customer{Name: req.CustomerName} lineItems := make([]db.GetLineItemsTableRow, len(req.LineItems)) for i, li := range req.LineItems { lineItems[i] = db.GetLineItemsTableRow{ ItemNumber: li.ItemNumber, Quantity: li.Quantity, Title: li.Title, GrossUnitPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.UnitPrice), Valid: true}, GrossPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.TotalPrice), Valid: true}, } } data := &pdf.PackingListPDFData{ Document: doc, Customer: cust, Title: req.Title, JobTitle: req.JobTitle, IssueDate: req.IssueDate, IssueDateString: req.IssueDateString, ShipVia: req.ShipVia, FOB: req.FOB, LineItems: lineItems, CurrencySymbol: req.CurrencySymbol, CurrencyCode: req.CurrencyCode, ShowGST: req.ShowGST, } filename, err := pdf.GeneratePackingListPDF(data, outputDir) if err != nil { log.Printf("GeneratePackingListPDF: failed to generate PDF: %v", err) http.Error(w, "failed to generate PDF", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]string{"filename": filename}) } // GenerateOrderAckPDF handles POST /go/pdf/generate-orderack func GenerateOrderAckPDF(w http.ResponseWriter, r *http.Request) { var req OrderAckPDFRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON payload", http.StatusBadRequest) return } if req.CustomerName == "" { http.Error(w, "customer_name is required", http.StatusBadRequest) return } outputDir := req.OutputDir if outputDir == "" { outputDir = os.Getenv("PDF_OUTPUT_DIR") } if outputDir == "" { outputDir = "../php/app/webroot/pdf" } if err := os.MkdirAll(outputDir, 0755); err != nil { log.Printf("GenerateOrderAckPDF: failed to create output dir: %v", err) http.Error(w, "failed to prepare output directory", http.StatusInternalServerError) return } doc := &db.Document{ID: req.DocumentID} cust := &db.Customer{Name: req.CustomerName} lineItems := make([]db.GetLineItemsTableRow, len(req.LineItems)) for i, li := range req.LineItems { lineItems[i] = db.GetLineItemsTableRow{ ItemNumber: li.ItemNumber, Quantity: li.Quantity, Title: li.Title, GrossUnitPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.UnitPrice), Valid: true}, GrossPrice: sql.NullString{String: fmt.Sprintf("%.2f", li.TotalPrice), Valid: true}, } } data := &pdf.OrderAckPDFData{ Document: doc, Customer: cust, Title: req.Title, JobTitle: req.JobTitle, IssueDate: req.IssueDate, IssueDateString: req.IssueDateString, ShipVia: req.ShipVia, FOB: req.FOB, EstimatedDelivery: req.EstimatedDelivery, LineItems: lineItems, CurrencySymbol: req.CurrencySymbol, CurrencyCode: req.CurrencyCode, ShowGST: req.ShowGST, } filename, err := pdf.GenerateOrderAckPDF(data, outputDir) if err != nil { log.Printf("GenerateOrderAckPDF: failed to generate PDF: %v", err) http.Error(w, "failed to generate PDF", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]string{"filename": filename}) } // PackingListLineItemRequest reuses the invoice item shape type PackingListLineItemRequest = InvoiceLineItemRequest // PackingListPDFRequest payload type PackingListPDFRequest struct { DocumentID int32 `json:"document_id"` Title string `json:"title"` CustomerName string `json:"customer_name"` JobTitle string `json:"job_title"` IssueDate string `json:"issue_date"` // YYYY-MM-DD IssueDateString string `json:"issue_date_string"` // formatted date ShipVia string `json:"ship_via"` FOB string `json:"fob"` CurrencySymbol string `json:"currency_symbol"` CurrencyCode string `json:"currency_code"` ShowGST bool `json:"show_gst"` LineItems []PackingListLineItemRequest `json:"line_items"` OutputDir string `json:"output_dir"` } // OrderAckLineItemRequest reuses the invoice item shape type OrderAckLineItemRequest = InvoiceLineItemRequest // OrderAckPDFRequest payload type OrderAckPDFRequest struct { DocumentID int32 `json:"document_id"` Title string `json:"title"` CustomerName string `json:"customer_name"` JobTitle string `json:"job_title"` IssueDate string `json:"issue_date"` // YYYY-MM-DD IssueDateString string `json:"issue_date_string"` // formatted date ShipVia string `json:"ship_via"` FOB string `json:"fob"` EstimatedDelivery string `json:"estimated_delivery"` CurrencySymbol string `json:"currency_symbol"` CurrencyCode string `json:"currency_code"` ShowGST bool `json:"show_gst"` LineItems []OrderAckLineItemRequest `json:"line_items"` OutputDir string `json:"output_dir"` } // CountPagesRequest payload for page counting type CountPagesRequest struct { FilePath string `json:"file_path"` } // CountPages handles POST /go/pdf/count-pages // Returns JSON: {"page_count": } or {"error": ""} func CountPages(w http.ResponseWriter, r *http.Request) { var req CountPagesRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON payload", http.StatusBadRequest) return } if req.FilePath == "" { http.Error(w, "file_path is required", http.StatusBadRequest) return } // Count pages in the PDF file pageCount, err := pdf.CountPDFPages(req.FilePath) if err != nil { log.Printf("CountPages: error counting pages in %s: %v", req.FilePath, err) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(map[string]interface{}{ "page_count": 0, "error": err.Error(), }) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]int{"page_count": pageCount}) }