121 lines
3.6 KiB
Go
121 lines
3.6 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/wotra/wotra/internal/service"
|
|
)
|
|
|
|
// ExportHandler serves /api/export routes.
|
|
type ExportHandler struct {
|
|
entrySvc *service.EntryService
|
|
daySvc *service.DayService
|
|
weekSvc *service.WeekService
|
|
}
|
|
|
|
func NewExportHandler(e *service.EntryService, d *service.DayService, w *service.WeekService) *ExportHandler {
|
|
return &ExportHandler{entrySvc: e, daySvc: d, weekSvc: w}
|
|
}
|
|
|
|
func (h *ExportHandler) Routes(r chi.Router) {
|
|
r.Get("/export/entries.csv", h.EntriesCSV)
|
|
r.Get("/export/days.csv", h.DaysCSV)
|
|
r.Get("/export/weeks.csv", h.WeeksCSV)
|
|
}
|
|
|
|
// EntriesCSV GET /api/export/entries.csv?from=YYYY-MM-DD&to=YYYY-MM-DD
|
|
func (h *ExportHandler) EntriesCSV(w http.ResponseWriter, r *http.Request) {
|
|
from := r.URL.Query().Get("from")
|
|
to := r.URL.Query().Get("to")
|
|
if from == "" { from = "0000-01-01" }
|
|
if to == "" { to = "9999-12-31" }
|
|
|
|
es, err := h.entrySvc.List(r.Context(), from, to)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/csv")
|
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="entries-%s.csv"`, time.Now().Format("20060102")))
|
|
cw := csv.NewWriter(w)
|
|
cw.Write([]string{"id", "day_key", "start_time", "end_time", "duration_ms", "note", "auto_stopped"})
|
|
for _, e := range es {
|
|
endStr := ""
|
|
durStr := "0"
|
|
if e.EndTime != nil {
|
|
endStr = time.UnixMilli(*e.EndTime).UTC().Format(time.RFC3339)
|
|
durStr = fmt.Sprintf("%d", *e.EndTime - e.StartTime)
|
|
}
|
|
cw.Write([]string{
|
|
e.ID,
|
|
e.DayKey,
|
|
time.UnixMilli(e.StartTime).UTC().Format(time.RFC3339),
|
|
endStr,
|
|
durStr,
|
|
e.Note,
|
|
fmt.Sprintf("%v", e.AutoStopped),
|
|
})
|
|
}
|
|
cw.Flush()
|
|
}
|
|
|
|
// DaysCSV GET /api/export/days.csv?from=YYYY-MM-DD&to=YYYY-MM-DD
|
|
func (h *ExportHandler) DaysCSV(w http.ResponseWriter, r *http.Request) {
|
|
from := r.URL.Query().Get("from")
|
|
to := r.URL.Query().Get("to")
|
|
if from == "" { from = "0000-01-01" }
|
|
if to == "" { to = "9999-12-31" }
|
|
|
|
ds, err := h.daySvc.ListDays(r.Context(), from, to)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/csv")
|
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="days-%s.csv"`, time.Now().Format("20060102")))
|
|
cw := csv.NewWriter(w)
|
|
cw.Write([]string{"day_key", "kind", "worked_ms", "worked_hours"})
|
|
for _, d := range ds {
|
|
workedH := fmt.Sprintf("%.2f", float64(d.WorkedMs)/3_600_000)
|
|
cw.Write([]string{d.DayKey, string(d.Kind), fmt.Sprintf("%d", d.WorkedMs), workedH})
|
|
}
|
|
cw.Flush()
|
|
}
|
|
|
|
// WeeksCSV GET /api/export/weeks.csv
|
|
func (h *ExportHandler) WeeksCSV(w http.ResponseWriter, r *http.Request) {
|
|
from := r.URL.Query().Get("from")
|
|
to := r.URL.Query().Get("to")
|
|
if from == "" { from = "0000-W01" }
|
|
if to == "" { to = "9999-W53" }
|
|
|
|
ws, err := h.weekSvc.ListWeeks(r.Context(), from, to)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/csv")
|
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="weeks-%s.csv"`, time.Now().Format("20060102")))
|
|
cw := csv.NewWriter(w)
|
|
cw.Write([]string{"week_key", "expected_ms", "worked_ms", "delta_ms", "expected_h", "worked_h", "delta_h"})
|
|
for _, wk := range ws {
|
|
cw.Write([]string{
|
|
wk.WeekKey,
|
|
fmt.Sprintf("%d", wk.ExpectedMs),
|
|
fmt.Sprintf("%d", wk.WorkedMs),
|
|
fmt.Sprintf("%d", wk.DeltaMs),
|
|
fmt.Sprintf("%.2f", float64(wk.ExpectedMs)/3_600_000),
|
|
fmt.Sprintf("%.2f", float64(wk.WorkedMs)/3_600_000),
|
|
fmt.Sprintf("%.2f", float64(wk.DeltaMs)/3_600_000),
|
|
})
|
|
}
|
|
cw.Flush()
|
|
}
|