prototyped svg reports
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 4s

This commit is contained in:
2026-08-19 23:19:41 -06:00
parent 00eed86bb2
commit 82efe19ae0
106 changed files with 5450 additions and 46 deletions
+145 -6
View File
@@ -3,36 +3,43 @@ package accounts
import (
"errors"
"fmt"
"slices"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"ruben/inventory2/consts"
"ruben/inventory2/domains/accounts"
"ruben/inventory2/domains/reports"
"ruben/inventory2/logging"
"ruben/inventory2/server/auth"
"ruben/inventory2/server/param"
"ruben/inventory2/server/response"
"ruben/inventory2/server/sse"
"ruben/inventory2/server/ui/charts"
)
type accountSubrouter struct {
log *logging.Logger
accts *accounts.Store
pub *sse.UpdateNotificationPublisher
log *logging.Logger
accts *accounts.Store
reports *reports.Store
pub *sse.UpdateNotificationPublisher
}
func Routes(
r *gin.RouterGroup,
logger *logging.Logger,
accts *accounts.Store,
reports *reports.Store,
pub *sse.UpdateNotificationPublisher,
) {
as := &accountSubrouter{
log: logger,
accts: accts,
pub: pub,
log: logger,
accts: accts,
reports: reports,
pub: pub,
}
r.POST("", response.Handler(as.createAccount))
@@ -46,6 +53,9 @@ func Routes(
mockShops.PUT("/listings/:listing-id", response.Handler(as.updateMockListing))
mockShops.DELETE("/listings/:listing-id", response.Handler(as.deleteMockListing))
mockListingCharts := r.Group("/:acctID/platforms/:platform/shops/mocks/:shop-id/listings/:listing-id/charts")
mockListingCharts.GET("/counts", response.Handler(as.getCountsChartForMockListing))
syncGroups := r.Group("/:acctID/inventory/sync-groups", pub.Publish("/:acctID/inventory/sync-groups"))
syncGroups.POST("", response.Handler(as.saveNewSyncGroup))
@@ -421,6 +431,135 @@ func (s *accountSubrouter) deleteMockListing(c *gin.Context) (response.Response,
return response.StatusNoContent(), nil
}
// TODO: accept dimensions
// GET /:acctID/platforms/:platform/shops/mocks/:shop-id/charts/counts
func (s *accountSubrouter) getCountsChartForMockListing(c *gin.Context) (response.Response, error) {
s.log.Debug("ENDPOINT HIT")
acctID := auth.GetIdentity(c).Account.AccountID
var (
platform accounts.Platform
shopID string
listingID string
width float64
height float64
timezone string
)
err := param.Path("platform", param.Platform(&platform)).
Path("shop-id", param.Text(&shopID)).
Path("listing-id", param.Text(&listingID)).
Form("width", param.Float64(&width)).
Form("height", param.Float64(&height)).
Form("timezone", param.Text(&timezone)).
Unmarshal(c)
if err != nil {
return nil, err
}
if width <= 0 {
return nil, response.BadRequest().Msg("width must be positive")
}
if height <= 0 {
return nil, response.BadRequest().Msg("height must be positive")
}
report, err := s.reports.GetListingCountsReport(c, acctID, platform, shopID, listingID)
if err != nil {
return nil, fmt.Errorf("failed to get report: %w", err)
}
/*
bvs := make([]charts.BarValue, len(report.Counts))
for i, v := range report.Counts {
label := "start"
if v.EventTimestamp != nil {
label = v.EventTimestamp.Format(time.RFC822)
}
bvs[i] = charts.BarValue{
Label: label,
Value: float64(v.Count),
}
}
return response.StatusOK().
HTMLReader(charts.NewBar(bvs...).SVG().GetMarkupReader()), nil
*/
/*
lvs := make([]charts.LineValue, len(report.Counts))
for i, v := range report.Counts {
label := "initial"
if v.EventTimestamp != nil {
label = v.EventTimestamp.Format(time.RFC822)
}
lvs[i] = charts.LineValue{
Label: label,
Value: float64(v.Count),
}
}
*/
l, err := time.LoadLocation(timezone)
if err != nil {
s.log.Errorf("failed to parse time zone %s: %v; defaulting to UTC", timezone, err)
l = time.UTC
}
var times []time.Time
for _, v := range report.Counts {
if v.EventTimestamp == nil {
continue
}
times = append(times, *v.EventTimestamp)
}
var (
minTime time.Time
maxTime time.Time
initTime time.Time
)
if len(times) > 0 {
minTime = slices.MinFunc(times, func(a, b time.Time) int {
return int(a.UnixNano() - b.UnixNano())
}).In(l)
maxTime = slices.MaxFunc(times, func(a, b time.Time) int {
return int(a.UnixNano() - b.UnixNano())
}).In(l)
if len(times) > 1 {
avgIntervalPerUpdate := time.Duration((maxTime.UnixNano() - minTime.UnixNano())) / time.Duration(len(times)-1)
initTime = minTime.Add(-avgIntervalPerUpdate)
} else {
initTime = minTime.Add(-time.Minute)
}
}
lvs := make([]charts.TimeLineValue, len(report.Counts))
for i, v := range report.Counts {
ts := initTime
label := fmt.Sprint(v.Count)
if v.EventTimestamp != nil {
ts = v.EventTimestamp.In(l)
}
lvs[i] = charts.TimeLineValue{
Label: label,
Time: ts,
Value: float64(v.Count),
}
}
return response.StatusOK().
HTMLReader(
//charts.NewLineChart(width, height, lvs...).
charts.NewTimeLineChart(width, height, lvs...).
Foreground("var(--foreground)").
Background("var(--muted)").
SVG().
GetMarkupReader(),
), nil
}
// POST /:acctID/inventory/sync-groups/mock/draft/listings
func (s *accountSubrouter) createMockSyncGroupListingDraft(c *gin.Context) (response.Response, error) {
r := c.Request