package issues import ( "encoding/base64" "errors" "fmt" "strconv" "strings" "time" "github.com/sophotechlabs/spinoza/internal/api" ) const Shown = 50 const ( ByWorst = "worst" ByNewest = "newest" ByOldest = "oldest" ) func OrderOf(asked string) string { if asked == ByNewest || asked == ByOldest { return asked } return ByWorst } const ( foldCeiling = 999999999 timeCeiling = 9999999999 ) func issueKey(row api.Issue, order string) string { if order == ByNewest { return strings.Join([]string{ countDown(secondsOf(seenAt(row.Since)), timeCeiling), row.Cluster, row.ID, }, "\x00") } if order == ByOldest { return strings.Join([]string{ countUp(secondsOf(seenAt(row.Since)), timeCeiling), row.Cluster, row.ID, }, "\x00") } return strings.Join([]string{ strconv.Itoa(severityFatal - severityRank(row.Severity)), countDown(row.Folded, foldCeiling), countDown(secondsOf(seenAt(row.Since)), timeCeiling), row.Cluster, row.ID, }, "\x00") } func countUp(value, ceiling int) string { if value < 0 { value = 0 } if value > ceiling { value = ceiling } width := len(strconv.Itoa(ceiling)) return fmt.Sprintf("%0*d", width, value) } func countDown(value, ceiling int) string { if value < 0 { value = 0 } if value > ceiling { value = ceiling } width := len(strconv.Itoa(ceiling)) return fmt.Sprintf("%0*d", width, ceiling-value) } func secondsOf(at time.Time) int { if at.IsZero() { return 0 } return int(at.Unix()) } var ( ErrInvalidCursor = errors.New("that page marker is invalid") ErrCursorOrder = errors.New("that page marker belongs to a different sort order") ) func EncodeCursor(key, order string) string { if key == "" { return "" } return base64.RawURLEncoding.EncodeToString([]byte(order + "\x00" + key)) } func DecodeCursor(cursor, order string) (string, error) { if cursor == "" { return "", nil } raw, err := base64.RawURLEncoding.DecodeString(cursor) if err != nil { return "", ErrInvalidCursor } minted, key, found := strings.Cut(string(raw), "\x00") if !found || minted == "" || key == "" { return "", ErrInvalidCursor } if minted != order { return "", ErrCursorOrder } return key, nil } func Page(rows []api.Issue, after string, limit int, order string) ([]api.Issue, string) { limit = PageSize(limit) out := make([]api.Issue, 0, min(limit, len(rows))) last := "" for _, row := range rows { key := issueKey(row, order) if key <= after { continue } if len(out) == limit { return out, EncodeCursor(last, order) } out = append(out, row) last = key } return out, "" } func PageSize(shown int) int { if shown <= 0 { return Shown } return shown }