package storage import ( "context" "fmt" "time" "github.com/jackc/pgx/v5/pgxpool" ) // LogEntry represents a single adventure log message. type LogEntry struct { ID int64 `json:"id"` HeroID int64 `json:"heroId"` Message string `json:"message"` CreatedAt time.Time `json:"createdAt"` } // LogStore handles adventure log CRUD operations against PostgreSQL. type LogStore struct { pool *pgxpool.Pool } // NewLogStore creates a new LogStore backed by the given connection pool. func NewLogStore(pool *pgxpool.Pool) *LogStore { return &LogStore{pool: pool} } // Add inserts a new adventure log entry for the given hero. func (s *LogStore) Add(ctx context.Context, heroID int64, message string) error { _, err := s.pool.Exec(ctx, `INSERT INTO adventure_log (hero_id, message) VALUES ($1, $2)`, heroID, message, ) if err != nil { return fmt.Errorf("add log entry: %w", err) } return nil } // GetSince returns log entries for a hero created after the given timestamp, // ordered oldest-first (chronological). Used to build offline reports from // real adventure log entries written by the offline simulator. func (s *LogStore) GetSince(ctx context.Context, heroID int64, since time.Time, limit int) ([]LogEntry, error) { if limit <= 0 { limit = 200 } if limit > 500 { limit = 500 } rows, err := s.pool.Query(ctx, ` SELECT id, hero_id, message, created_at FROM adventure_log WHERE hero_id = $1 AND created_at > $2 ORDER BY created_at ASC LIMIT $3 `, heroID, since, limit) if err != nil { return nil, fmt.Errorf("get log since: %w", err) } defer rows.Close() var entries []LogEntry for rows.Next() { var e LogEntry if err := rows.Scan(&e.ID, &e.HeroID, &e.Message, &e.CreatedAt); err != nil { return nil, fmt.Errorf("scan log entry: %w", err) } entries = append(entries, e) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("log since rows: %w", err) } if entries == nil { entries = []LogEntry{} } return entries, nil } // GetRecent returns the most recent log entries for a hero, ordered newest-first. func (s *LogStore) GetRecent(ctx context.Context, heroID int64, limit int) ([]LogEntry, error) { if limit <= 0 { limit = 50 } if limit > 200 { limit = 200 } rows, err := s.pool.Query(ctx, ` SELECT id, hero_id, message, created_at FROM adventure_log WHERE hero_id = $1 ORDER BY created_at DESC LIMIT $2 `, heroID, limit) if err != nil { return nil, fmt.Errorf("get recent log: %w", err) } defer rows.Close() var entries []LogEntry for rows.Next() { var e LogEntry if err := rows.Scan(&e.ID, &e.HeroID, &e.Message, &e.CreatedAt); err != nil { return nil, fmt.Errorf("scan log entry: %w", err) } entries = append(entries, e) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("log entries rows: %w", err) } if entries == nil { entries = []LogEntry{} } return entries, nil }