Add registry, ingest, timeline, tickets features
This commit is contained in:
106
internal/repository/timeline/events.go
Normal file
106
internal/repository/timeline/events.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package timeline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"reanimator/internal/domain"
|
||||
)
|
||||
|
||||
type EventRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewEventRepository(db *sql.DB) *EventRepository {
|
||||
return &EventRepository{db: db}
|
||||
}
|
||||
|
||||
type Cursor struct {
|
||||
Time time.Time
|
||||
ID int64
|
||||
}
|
||||
|
||||
func EncodeCursor(cursor Cursor) string {
|
||||
return fmt.Sprintf("%d:%d", cursor.Time.UnixNano(), cursor.ID)
|
||||
}
|
||||
|
||||
func DecodeCursor(value string) (Cursor, error) {
|
||||
parts := strings.Split(value, ":")
|
||||
if len(parts) != 2 {
|
||||
return Cursor{}, fmt.Errorf("invalid cursor")
|
||||
}
|
||||
ts, err := strconv.ParseInt(parts[0], 10, 64)
|
||||
if err != nil {
|
||||
return Cursor{}, fmt.Errorf("invalid cursor")
|
||||
}
|
||||
id, err := strconv.ParseInt(parts[1], 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
return Cursor{}, fmt.Errorf("invalid cursor")
|
||||
}
|
||||
return Cursor{Time: time.Unix(0, ts).UTC(), ID: id}, nil
|
||||
}
|
||||
|
||||
func (r *EventRepository) List(ctx context.Context, subjectType string, subjectID int64, limit int, cursor *Cursor) ([]domain.TimelineEvent, *Cursor, error) {
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
if limit > 200 {
|
||||
limit = 200
|
||||
}
|
||||
|
||||
args := []any{subjectType, subjectID}
|
||||
query := `
|
||||
SELECT id, subject_type, subject_id, event_type, event_time, asset_id, component_id, firmware_version, created_at
|
||||
FROM timeline_events
|
||||
WHERE subject_type = ? AND subject_id = ?`
|
||||
if cursor != nil {
|
||||
query += " AND (event_time > ? OR (event_time = ? AND id > ?))"
|
||||
args = append(args, cursor.Time, cursor.Time, cursor.ID)
|
||||
}
|
||||
query += " ORDER BY event_time, id LIMIT ?"
|
||||
args = append(args, limit+1)
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
events := make([]domain.TimelineEvent, 0)
|
||||
for rows.Next() {
|
||||
var event domain.TimelineEvent
|
||||
var assetID sql.NullInt64
|
||||
var componentID sql.NullInt64
|
||||
var firmware sql.NullString
|
||||
|
||||
if err := rows.Scan(&event.ID, &event.SubjectType, &event.SubjectID, &event.EventType, &event.EventTime, &assetID, &componentID, &firmware, &event.CreatedAt); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if assetID.Valid {
|
||||
event.AssetID = &assetID.Int64
|
||||
}
|
||||
if componentID.Valid {
|
||||
event.ComponentID = &componentID.Int64
|
||||
}
|
||||
if firmware.Valid {
|
||||
event.FirmwareVersion = &firmware.String
|
||||
}
|
||||
events = append(events, event)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var nextCursor *Cursor
|
||||
if len(events) > limit {
|
||||
last := events[limit-1]
|
||||
nextCursor = &Cursor{Time: last.EventTime.UTC(), ID: last.ID}
|
||||
events = events[:limit]
|
||||
}
|
||||
|
||||
return events, nextCursor, nil
|
||||
}
|
||||
Reference in New Issue
Block a user