Files

101 lines
1.9 KiB
Go

package auth
import (
"context"
"fmt"
"sync"
"time"
)
type Pool struct {
creds []*Credential
cursor int
mu sync.Mutex
}
func NewPool(creds []*Credential) *Pool {
return &Pool{creds: creds}
}
func (p *Pool) Pick() (*Credential, error) {
p.mu.Lock()
defer p.mu.Unlock()
n := len(p.creds)
if n == 0 {
return nil, fmt.Errorf("no credentials available")
}
for i := 0; i < n; i++ {
idx := (p.cursor + i) % n
cred := p.creds[idx]
if !cred.IsOnCooldown() {
p.cursor = (idx + 1) % n
return cred, nil
}
}
return nil, fmt.Errorf("all %d credentials are on cooldown", n)
}
func (p *Pool) MarkFailure(cred *Credential, statusCode int) {
switch {
case statusCode == 429:
cred.SetCooldown(30 * time.Second)
case statusCode >= 500:
cred.SetCooldown(5 * time.Second)
}
}
func (p *Pool) MarkSuccess(cred *Credential) {
cred.ClearCooldown()
}
func (p *Pool) RefreshExpiring(ctx context.Context) {
refreshExpiring(p)
}
func (p *Pool) RefreshAll(ctx context.Context) []map[string]string {
p.mu.Lock()
creds := make([]*Credential, len(p.creds))
copy(creds, p.creds)
p.mu.Unlock()
var results []map[string]string
for _, cred := range creds {
cred.mu.Lock()
id := cred.ID
email := cred.Email
oldExpiry := cred.ExpiresAt
hasRefresh := cred.RefreshToken != ""
cred.mu.Unlock()
r := map[string]string{
"id": id,
"email": email,
"old_expiry": oldExpiry.Format(time.RFC3339),
}
if !hasRefresh {
r["status"] = "skipped"
r["reason"] = "no refresh token"
results = append(results, r)
continue
}
err := RefreshToken(ctx, cred)
if err != nil {
r["status"] = "error"
r["error"] = err.Error()
} else {
cred.mu.Lock()
r["status"] = "ok"
r["new_expiry"] = cred.ExpiresAt.Format(time.RFC3339)
r["new_token_prefix"] = cred.AccessToken[:20] + "..."
cred.mu.Unlock()
}
results = append(results, r)
}
return results
}