Files
2026-03-29 09:04:15 -07:00

126 lines
3.3 KiB
Go

package waitlist
import (
"context"
"encoding/json"
"errors"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgxpool"
)
var errNotFound = errors.New("not found")
var errAlreadyExists = errors.New("already exists")
type repository interface {
create(ctx context.Context, email string, metadata map[string]string) (*WaitlistEntry, error)
getAll(ctx context.Context, filter GetWaitlistFilter) ([]*WaitlistEntry, error)
getByEmail(ctx context.Context, email string) (*WaitlistEntry, error)
markInvited(ctx context.Context, email string) error
}
type repositoryImpl struct {
pool *pgxpool.Pool
}
func newRepository(pool *pgxpool.Pool) repository {
return &repositoryImpl{pool: pool}
}
func (r *repositoryImpl) create(ctx context.Context, email string, metadata map[string]string) (*WaitlistEntry, error) {
metadataJSON, err := json.Marshal(metadata)
if err != nil {
return nil, err
}
var entry WaitlistEntry
var metadataBytes []byte
err = r.pool.QueryRow(ctx,
`INSERT INTO waitlist_entries (email, metadata)
VALUES ($1, $2)
RETURNING id, email, metadata, created_at, invited_at`,
email, metadataJSON,
).Scan(&entry.Id, &entry.Email, &metadataBytes, &entry.CreatedAt, &entry.InvitedAt)
if err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == "23505" {
return nil, errAlreadyExists
}
return nil, err
}
if err := json.Unmarshal(metadataBytes, &entry.Metadata); err != nil {
return nil, err
}
return &entry, nil
}
func (r *repositoryImpl) getAll(ctx context.Context, filter GetWaitlistFilter) ([]*WaitlistEntry, error) {
query := `SELECT id, email, metadata, created_at, invited_at FROM waitlist_entries`
switch filter {
case GetWaitlistFilterInvitedOnly:
query += ` WHERE invited_at IS NOT NULL`
case GetWaitlistFilterUninvitedOnly:
query += ` WHERE invited_at IS NULL`
}
query += ` ORDER BY created_at DESC`
rows, err := r.pool.Query(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var entries []*WaitlistEntry
for rows.Next() {
var entry WaitlistEntry
var metadataBytes []byte
if err := rows.Scan(&entry.Id, &entry.Email, &metadataBytes, &entry.CreatedAt, &entry.InvitedAt); err != nil {
return nil, err
}
if err := json.Unmarshal(metadataBytes, &entry.Metadata); err != nil {
return nil, err
}
entries = append(entries, &entry)
}
return entries, rows.Err()
}
func (r *repositoryImpl) getByEmail(ctx context.Context, email string) (*WaitlistEntry, error) {
var entry WaitlistEntry
var metadataBytes []byte
err := r.pool.QueryRow(ctx,
`SELECT id, email, metadata, created_at, invited_at FROM waitlist_entries WHERE email = $1`,
email,
).Scan(&entry.Id, &entry.Email, &metadataBytes, &entry.CreatedAt, &entry.InvitedAt)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, errNotFound
}
return nil, err
}
if err := json.Unmarshal(metadataBytes, &entry.Metadata); err != nil {
return nil, err
}
return &entry, nil
}
func (r *repositoryImpl) markInvited(ctx context.Context, email string) error {
result, err := r.pool.Exec(ctx,
`UPDATE waitlist_entries SET invited_at = NOW() WHERE email = $1`,
email,
)
if err != nil {
return err
}
if result.RowsAffected() == 0 {
return errNotFound
}
return nil
}