Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-pdf/fpdf v0.9.0 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.4.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/
github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw=
github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE=
github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE=
github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw=
github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
Expand Down
14 changes: 12 additions & 2 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -715,8 +715,9 @@ func (ct *Controller) voteAnswer(c *gin.Context) {
// @Accept json
// @Produce json
// @Produce text/markdown
// @Produce application/pdf
// @Param export body types.RetrospectiveExportRequest true "Export Retrospective"
// @Success 200 {object} types.Retrospective "Retrospective Object (JSON) or Markdown file"
// @Success 200 {object} types.Retrospective "Retrospective Object (JSON), Markdown file, or PDF file"
// @Failure 400 {string} string "Invalid input"
// @Failure 404 {string} string "Not Found"
// @Failure 500 {string} string "Internal error"
Expand Down Expand Up @@ -751,11 +752,20 @@ func (ct *Controller) exportRetrospective(c *gin.Context) {
filename := fmt.Sprintf("retrospective-%s", strings.ReplaceAll(retro.Name, " ", "_"))
switch input.ExportType {
case types.ExportTypeMarkdown:

markdown := ct.service.ConvertRetrospectiveToMarkdown(c, retro)
c.Header("Content-Type", "text/markdown")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.md\"", filename))
c.String(http.StatusOK, markdown)
case types.ExportTypePDF:
pdfBytes, err := ct.service.ConvertRetrospectiveToPDF(c, retro)
if err != nil {
ct.logger.Error("error converting retrospective to PDF", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": "error generating PDF"})
return
}
c.Header("Content-Type", "application/pdf")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.pdf\"", filename))
c.Data(http.StatusOK, "application/pdf", pdfBytes)
default:
c.Header("Content-Type", "application/json")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.json\"", filename))
Expand Down
76 changes: 75 additions & 1 deletion internal/service/convert.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package service

import (
"api/types"
"bytes"
"context"
"fmt"
"sort"
"strings"

"api/types"

"github.com/go-pdf/fpdf"
)

func (s *Service) ConvertRetrospectiveToMarkdown(ctx context.Context, retro *types.Retrospective) string {
Expand Down Expand Up @@ -44,3 +48,73 @@ func (s *Service) ConvertRetrospectiveToMarkdown(ctx context.Context, retro *typ

return sb.String()
}

func (s *Service) ConvertRetrospectiveToPDF(ctx context.Context, retro *types.Retrospective) ([]byte, error) {
pdf := fpdf.New("P", "mm", "A4", "")
pdf.SetMargins(20, 20, 20)
pdf.AddPage()

// Title
pdf.SetFont("Arial", "B", 24)
pdf.Cell(0, 12, "Simple Retro")
pdf.Ln(16)

// Subtitle (retrospective name)
pdf.SetFont("Arial", "B", 18)
pdf.Cell(0, 10, retro.Name)
pdf.Ln(12)

// Description
if retro.Description != "" {
pdf.SetFont("Arial", "", 12)
pdf.MultiCell(0, 6, retro.Description, "", "", false)
pdf.Ln(4)
}

// Created at
pdf.SetFont("Arial", "I", 10)
pdf.SetTextColor(128, 128, 128)
pdf.Cell(0, 6, "Created on "+retro.CreatedAt.Format("January 2, 2006 at 3:04 PM"))
pdf.Ln(10)
pdf.SetTextColor(0, 0, 0)

// Separator line
pdf.Line(20, pdf.GetY(), 190, pdf.GetY())
pdf.Ln(8)

// Questions and answers
for _, question := range retro.Questions {
// Question title
pdf.SetFont("Arial", "B", 14)
pdf.MultiCell(0, 8, question.Text, "", "", false)
pdf.Ln(4)

// Sort answers by position
answers := make([]types.Answer, len(question.Answers))
copy(answers, question.Answers)
sort.Slice(answers, func(i, j int) bool {
return answers[i].Position < answers[j].Position
})

pdf.SetFont("Arial", "", 11)
for _, answer := range answers {
var text string
if answer.Votes > 0 {
text = fmt.Sprintf(" - %s (%d votes)", answer.Text, answer.Votes)
} else {
text = fmt.Sprintf(" - %s", answer.Text)
}
pdf.MultiCell(0, 6, text, "", "", false)
pdf.Ln(1)
}
pdf.Ln(6)
}

var buf bytes.Buffer
err := pdf.Output(&buf)
if err != nil {
return nil, err
}

return buf.Bytes(), nil
}
1 change: 1 addition & 0 deletions types/retrospective.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (

ExportTypeJSON ExportType = "JSON"
ExportTypeMarkdown ExportType = "MARKDOWN"
ExportTypePDF ExportType = "PDF"
)

type Retrospective struct {
Expand Down
2 changes: 1 addition & 1 deletion types/validations.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func (r RetrospectiveExportRequest) Validate() error {
}

switch r.ExportType {
case ExportTypeJSON, ExportTypeMarkdown:
case ExportTypeJSON, ExportTypeMarkdown, ExportTypePDF:
return nil
default:
return fmt.Errorf("invalid export type")
Expand Down