package db import ( "context" "encoding/json" "errors" "time" "github.com/jackc/pgx/v5" ) type WizardData map[string]any type AdminWizard struct { AdminID int64 Step string Data WizardData } func (d *DB) GetWizard(ctx context.Context, adminID int64) (*AdminWizard, error) { row := d.pool.QueryRow(ctx, ` SELECT admin_telegram_id, step, data FROM admin_wizard WHERE admin_telegram_id = $1`, adminID) var w AdminWizard var raw []byte if err := row.Scan(&w.AdminID, &w.Step, &raw); err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, nil } return nil, err } if len(raw) > 0 { _ = json.Unmarshal(raw, &w.Data) } if w.Data == nil { w.Data = WizardData{} } return &w, nil } func (d *DB) SetWizard(ctx context.Context, adminID int64, step string, data WizardData) error { raw, _ := json.Marshal(data) _, err := d.pool.Exec(ctx, ` INSERT INTO admin_wizard (admin_telegram_id, step, data, updated_at) VALUES ($1, $2, $3, NOW()) ON CONFLICT (admin_telegram_id) DO UPDATE SET step = EXCLUDED.step, data = EXCLUDED.data, updated_at = NOW()`, adminID, step, raw) return err } func (d *DB) ClearWizard(ctx context.Context, adminID int64) error { _, err := d.pool.Exec(ctx, `DELETE FROM admin_wizard WHERE admin_telegram_id = $1`, adminID) return err } func (w WizardData) String(key string) string { v, _ := w[key].(string) return v } func (w WizardData) Int(key string) int { switch v := w[key].(type) { case float64: return int(v) case int: return v default: return 0 } } func (w WizardData) StringSlice(key string) []string { raw, ok := w[key].([]any) if !ok { if ss, ok := w[key].([]string); ok { return ss } return nil } out := make([]string, 0, len(raw)) for _, x := range raw { if s, ok := x.(string); ok { out = append(out, s) } } return out } func (w WizardData) Set(key string, val any) { w[key] = val } func (w WizardData) ToggleUUID(key, uuid string) { cur := w.StringSlice(key) for i, id := range cur { if id == uuid { cur = append(cur[:i], cur[i+1:]...) w[key] = cur return } } w[key] = append(cur, uuid) } const ( StepIdle = "" StepAwaitUsername = "await_username" StepAwaitDays = "await_days" StepPickExternalSquad = "pick_external" StepPickInternalSquads = "pick_internal" StepConfirm = "confirm" ) func DefaultExpireAt(days int) time.Time { if days <= 0 { days = 30 } return time.Now().UTC().AddDate(0, 0, days) }