116 lines
3.1 KiB
Go
116 lines
3.1 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
type ListFilter struct {
|
|
ActiveOnly bool
|
|
Search string
|
|
Limit int
|
|
}
|
|
|
|
func (r *Repo) List(ctx context.Context, f ListFilter) ([]*User, error) {
|
|
q := `SELECT ` + userCols + ` FROM users WHERE 1=1`
|
|
args := []any{}
|
|
if f.ActiveOnly {
|
|
q += ` AND is_active = 1`
|
|
}
|
|
if f.Search != "" {
|
|
q += ` AND (username LIKE ? COLLATE NOCASE OR pubkey LIKE ?)`
|
|
args = append(args, "%"+f.Search+"%", "%"+f.Search+"%")
|
|
}
|
|
q += ` ORDER BY created_at DESC`
|
|
if f.Limit > 0 {
|
|
q += ` LIMIT ?`
|
|
args = append(args, f.Limit)
|
|
}
|
|
rows, err := r.db.QueryContext(ctx, q, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.collect(rows)
|
|
}
|
|
|
|
func (r *Repo) ActiveByName(ctx context.Context) (map[string]string, error) {
|
|
rows, err := r.db.QueryContext(ctx, `SELECT username, pubkey FROM users WHERE is_active = 1`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
out := map[string]string{}
|
|
for rows.Next() {
|
|
var u, p string
|
|
if err := rows.Scan(&u, &p); err != nil {
|
|
return nil, err
|
|
}
|
|
out[u] = p
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (r *Repo) collect(rows interface {
|
|
Next() bool
|
|
Scan(...any) error
|
|
Err() error
|
|
Close() error
|
|
}) ([]*User, error) {
|
|
defer rows.Close()
|
|
out := []*User{}
|
|
for rows.Next() {
|
|
u, err := scanUser(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, u)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (r *Repo) ListPendingReminders(ctx context.Context, days int, now time.Time) ([]*User, error) {
|
|
low := now.Add(time.Duration(days)*24*time.Hour - 12*time.Hour).UTC().Format(time.RFC3339)
|
|
high := now.Add(time.Duration(days)*24*time.Hour + 12*time.Hour).UTC().Format(time.RFC3339)
|
|
rows, err := r.db.QueryContext(ctx, `SELECT `+userCols+` FROM users
|
|
WHERE is_active = 1
|
|
AND subscription_type = 'yearly'
|
|
AND expires_at BETWEEN ? AND ?
|
|
AND (expiring_reminder_sent_at IS NULL OR expiring_reminder_sent_at < ?)`,
|
|
low, high, now.Add(-23*time.Hour).UTC().Format(time.RFC3339))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.collect(rows)
|
|
}
|
|
|
|
func (r *Repo) ListExpired(ctx context.Context, now time.Time) ([]*User, error) {
|
|
rows, err := r.db.QueryContext(ctx, `SELECT `+userCols+` FROM users
|
|
WHERE is_active = 1 AND subscription_type = 'yearly' AND expires_at < ?`,
|
|
now.UTC().Format(time.RFC3339))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.collect(rows)
|
|
}
|
|
|
|
func (r *Repo) ListGraceExpired(ctx context.Context, cutoff time.Time) ([]*User, error) {
|
|
rows, err := r.db.QueryContext(ctx, `SELECT `+userCols+` FROM users
|
|
WHERE is_active = 0 AND deactivated_at IS NOT NULL AND deactivated_at < ?`,
|
|
cutoff.UTC().Format(time.RFC3339))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.collect(rows)
|
|
}
|
|
|
|
func (r *Repo) ListForSync(ctx context.Context, staleBefore time.Time) ([]*User, error) {
|
|
rows, err := r.db.QueryContext(ctx, `SELECT `+userCols+` FROM users
|
|
WHERE is_active = 1 AND manual_username = 0
|
|
AND (last_synced_at IS NULL OR last_synced_at < ?)`,
|
|
staleBefore.UTC().Format(time.RFC3339))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.collect(rows)
|
|
}
|