| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- package repository
- import (
- "context"
- "database/sql"
- "errors"
- "fmt"
- "strings"
- "time"
- "github.com/Wei-Shaw/sub2api/internal/service"
- )
- type authCacheInvalidationOutboxRepository struct {
- db *sql.DB
- }
- func NewAuthCacheInvalidationOutboxRepository(db *sql.DB) service.AuthCacheInvalidationOutboxRepository {
- return &authCacheInvalidationOutboxRepository{db: db}
- }
- func (r *authCacheInvalidationOutboxRepository) Claim(ctx context.Context, workerID string, limit int, lease time.Duration) ([]service.AuthCacheInvalidationEvent, error) {
- if r == nil || r.db == nil {
- return nil, errors.New("nil auth cache invalidation outbox database")
- }
- if limit <= 0 {
- limit = 100
- }
- leaseSeconds := int64(lease / time.Second)
- if leaseSeconds < 1 {
- leaseSeconds = 30
- }
- rows, err := r.db.QueryContext(ctx, `
- WITH candidates AS (
- SELECT id
- FROM auth_cache_invalidation_outbox
- WHERE available_at <= NOW()
- AND (claimed_at IS NULL OR claimed_at < NOW() - ($3 * INTERVAL '1 second'))
- ORDER BY id ASC
- LIMIT $2
- FOR UPDATE SKIP LOCKED
- )
- UPDATE auth_cache_invalidation_outbox AS o
- SET claimed_at = NOW(), claimed_by = $1
- FROM candidates AS c
- WHERE o.id = c.id
- RETURNING o.id, o.cache_key, o.attempts, o.delivery_stage, o.created_at
- `, workerID, limit, leaseSeconds)
- if err != nil {
- return nil, err
- }
- defer func() { _ = rows.Close() }()
- events := make([]service.AuthCacheInvalidationEvent, 0, limit)
- for rows.Next() {
- var event service.AuthCacheInvalidationEvent
- if err := rows.Scan(&event.ID, &event.CacheKey, &event.Attempts, &event.Stage, &event.CreatedAt); err != nil {
- return nil, err
- }
- event.CacheKey = strings.TrimSpace(event.CacheKey)
- events = append(events, event)
- }
- if err := rows.Err(); err != nil {
- return nil, err
- }
- return events, nil
- }
- func (r *authCacheInvalidationOutboxRepository) ScheduleSecondPass(ctx context.Context, id int64, workerID string, availableAt time.Time) error {
- result, err := r.db.ExecContext(ctx, `
- UPDATE auth_cache_invalidation_outbox
- SET delivery_stage = 1,
- available_at = $3,
- last_error = NULL,
- claimed_at = NULL,
- claimed_by = NULL
- WHERE id = $1 AND claimed_by = $2 AND delivery_stage = 0
- `, id, workerID, availableAt)
- if err != nil {
- return err
- }
- affected, err := result.RowsAffected()
- if err != nil {
- return err
- }
- if affected != 1 {
- return fmt.Errorf("auth cache invalidation claim %d cannot schedule second pass", id)
- }
- return nil
- }
- func (r *authCacheInvalidationOutboxRepository) DeleteClaimed(ctx context.Context, id int64, workerID string) error {
- result, err := r.db.ExecContext(ctx, `
- DELETE FROM auth_cache_invalidation_outbox
- WHERE id = $1 AND claimed_by = $2
- `, id, workerID)
- if err != nil {
- return err
- }
- affected, err := result.RowsAffected()
- if err != nil {
- return err
- }
- if affected != 1 {
- return fmt.Errorf("auth cache invalidation claim %d is no longer owned by %s", id, workerID)
- }
- return nil
- }
- func (r *authCacheInvalidationOutboxRepository) RetryClaimed(ctx context.Context, id int64, workerID string, availableAt time.Time, lastError string) error {
- result, err := r.db.ExecContext(ctx, `
- UPDATE auth_cache_invalidation_outbox
- SET attempts = attempts + 1,
- available_at = $3,
- last_error = $4,
- claimed_at = NULL,
- claimed_by = NULL
- WHERE id = $1 AND claimed_by = $2
- `, id, workerID, availableAt, lastError)
- if err != nil {
- return err
- }
- affected, err := result.RowsAffected()
- if err != nil {
- return err
- }
- if affected != 1 {
- return fmt.Errorf("auth cache invalidation claim %d is no longer owned by %s", id, workerID)
- }
- return nil
- }
- func (r *authCacheInvalidationOutboxRepository) Stats(ctx context.Context) (service.AuthCacheInvalidationOutboxStats, error) {
- var (
- stats service.AuthCacheInvalidationOutboxStats
- oldest sql.NullTime
- lastError sql.NullString
- )
- err := r.db.QueryRowContext(ctx, `
- SELECT COUNT(*), MIN(created_at), COALESCE(MAX(attempts), 0),
- (SELECT last_error
- FROM auth_cache_invalidation_outbox
- WHERE last_error IS NOT NULL
- ORDER BY available_at DESC, id DESC
- LIMIT 1)
- FROM auth_cache_invalidation_outbox
- `).Scan(&stats.Pending, &oldest, &stats.MaxAttempts, &lastError)
- if err != nil {
- return stats, err
- }
- if oldest.Valid {
- value := oldest.Time
- stats.OldestCreatedAt = &value
- }
- if lastError.Valid {
- stats.LastError = lastError.String
- }
- return stats, nil
- }
|