package storage import ( "context" "encoding/json" "errors" "fmt" "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgxpool" "github.com/alchemistkay/guestguard/internal/domain" ) type EventRepo struct { pool *pgxpool.Pool } func NewEventRepo(db *DB) *EventRepo { return &EventRepo{pool: db.Pool} } type CreateEventParams struct { HostID uuid.UUID Name string Slug string EventDate time.Time Venue string MaxCapacity int Settings map[string]any Status domain.EventStatus } func (r *EventRepo) Create(ctx context.Context, p CreateEventParams) (*domain.Event, error) { settings := p.Settings if settings == nil { settings = map[string]any{} } settingsJSON, err := json.Marshal(settings) if err != nil { return nil, fmt.Errorf("marshal settings: %w", err) } const q = ` INSERT INTO events (host_id, name, slug, event_date, venue, max_capacity, settings, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, host_id, name, slug, event_date, venue, max_capacity, settings, status, created_at, updated_at ` row := r.pool.QueryRow(ctx, q, p.HostID, p.Name, p.Slug, p.EventDate, p.Venue, p.MaxCapacity, settingsJSON, p.Status, ) ev, err := scanEvent(row) if err != nil { var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == "23505" { return nil, domain.ErrSlugTaken } return nil, err } return ev, nil } func (r *EventRepo) Get(ctx context.Context, id uuid.UUID) (*domain.Event, error) { const q = ` SELECT id, host_id, name, slug, event_date, venue, max_capacity, settings, status, created_at, updated_at FROM events WHERE id = $1 ` ev, err := scanEvent(r.pool.QueryRow(ctx, q, id)) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, domain.ErrEventNotFound } return nil, err } return ev, nil } func (r *EventRepo) List(ctx context.Context, hostID uuid.UUID, limit, offset int) ([]*domain.Event, error) { if limit <= 0 || limit > 200 { limit = 50 } if offset < 0 { offset = 0 } var ( rows pgx.Rows err error ) if hostID == uuid.Nil { rows, err = r.pool.Query(ctx, ` SELECT id, host_id, name, slug, event_date, venue, max_capacity, settings, status, created_at, updated_at FROM events ORDER BY created_at DESC LIMIT $1 OFFSET $2 `, limit, offset) } else { rows, err = r.pool.Query(ctx, ` SELECT id, host_id, name, slug, event_date, venue, max_capacity, settings, status, created_at, updated_at FROM events WHERE host_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3 `, hostID, limit, offset) } if err != nil { return nil, err } defer rows.Close() var out []*domain.Event for rows.Next() { ev, err := scanEvent(rows) if err != nil { return nil, err } out = append(out, ev) } return out, rows.Err() } type UpdateEventParams struct { Name *string Slug *string EventDate *time.Time Venue *string MaxCapacity *int Settings *map[string]any Status *domain.EventStatus } func (r *EventRepo) Update(ctx context.Context, id uuid.UUID, p UpdateEventParams) (*domain.Event, error) { const q = ` UPDATE events SET name = COALESCE($2, name), slug = COALESCE($3, slug), event_date = COALESCE($4, event_date), venue = COALESCE($5, venue), max_capacity = COALESCE($6, max_capacity), settings = COALESCE($7, settings), status = COALESCE($8, status), updated_at = now() WHERE id = $1 RETURNING id, host_id, name, slug, event_date, venue, max_capacity, settings, status, created_at, updated_at ` var settingsJSON []byte if p.Settings != nil { b, err := json.Marshal(*p.Settings) if err != nil { return nil, fmt.Errorf("marshal settings: %w", err) } settingsJSON = b } row := r.pool.QueryRow(ctx, q, id, p.Name, p.Slug, p.EventDate, p.Venue, p.MaxCapacity, settingsJSON, p.Status, ) ev, err := scanEvent(row) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, domain.ErrEventNotFound } var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == "23505" { return nil, domain.ErrSlugTaken } return nil, err } return ev, nil } func (r *EventRepo) Delete(ctx context.Context, id uuid.UUID) error { tag, err := r.pool.Exec(ctx, `DELETE FROM events WHERE id = $1`, id) if err != nil { return err } if tag.RowsAffected() == 0 { return domain.ErrEventNotFound } return nil } type rowScanner interface { Scan(dest ...any) error } func scanEvent(s rowScanner) (*domain.Event, error) { var ( ev domain.Event settingsJSON []byte ) err := s.Scan( &ev.ID, &ev.HostID, &ev.Name, &ev.Slug, &ev.EventDate, &ev.Venue, &ev.MaxCapacity, &settingsJSON, &ev.Status, &ev.CreatedAt, &ev.UpdatedAt, ) if err != nil { return nil, err } if len(settingsJSON) > 0 { if err := json.Unmarshal(settingsJSON, &ev.Settings); err != nil { return nil, fmt.Errorf("unmarshal settings: %w", err) } } else { ev.Settings = map[string]any{} } return &ev, nil }