package db import ( "context" "database/sql" "time" ) type PanelAdmin struct { ID int64 DiscordID string DiscordUsername string DiscordAvatar string PasswordHash string TOTPSecret sql.NullString TOTPEnabled bool IsSuperadmin bool CreatedAt time.Time UpdatedAt time.Time } type PanelAdminRepo struct{ db *sql.DB } func NewPanelAdminRepo(db *sql.DB) *PanelAdminRepo { return &PanelAdminRepo{db: db} } const adminCols = `id,discord_id,discord_username,discord_avatar,password_hash,totp_secret,totp_enabled,is_superadmin,created_at,updated_at` func (r *PanelAdminRepo) Create(ctx context.Context, discordID, username, passwordHash string, isSuperadmin bool) (*PanelAdmin, error) { now := time.Now().UTC() res, err := r.db.ExecContext(ctx, `INSERT INTO panel_admins(discord_id,discord_username,password_hash,is_superadmin,created_at,updated_at) VALUES(?,?,?,?,?,?)`, discordID, username, passwordHash, boolInt(isSuperadmin), now, now) if err != nil { return nil, err } id, _ := res.LastInsertId() return r.GetByID(ctx, id) } func (r *PanelAdminRepo) GetByID(ctx context.Context, id int64) (*PanelAdmin, error) { row := r.db.QueryRowContext(ctx, `SELECT `+adminCols+` FROM panel_admins WHERE id=?`, id) return scanAdmin(row) } func (r *PanelAdminRepo) GetByDiscordID(ctx context.Context, discordID string) (*PanelAdmin, error) { row := r.db.QueryRowContext(ctx, `SELECT `+adminCols+` FROM panel_admins WHERE discord_id=?`, discordID) return scanAdmin(row) } func (r *PanelAdminRepo) List(ctx context.Context) ([]*PanelAdmin, error) { rows, err := r.db.QueryContext(ctx, `SELECT `+adminCols+` FROM panel_admins ORDER BY created_at`) if err != nil { return nil, err } defer rows.Close() var out []*PanelAdmin for rows.Next() { a, err := scanAdminRows(rows) if err != nil { return nil, err } out = append(out, a) } return out, rows.Err() } func (r *PanelAdminRepo) UpdateDiscordProfile(ctx context.Context, id int64, username, avatar string) error { _, err := r.db.ExecContext(ctx, `UPDATE panel_admins SET discord_username=?,discord_avatar=?,updated_at=? WHERE id=?`, username, avatar, time.Now().UTC(), id) return err } func (r *PanelAdminRepo) UpdatePassword(ctx context.Context, id int64, hash string) error { _, err := r.db.ExecContext(ctx, `UPDATE panel_admins SET password_hash=?,updated_at=? WHERE id=?`, hash, time.Now().UTC(), id) return err } func (r *PanelAdminRepo) UpdateTOTP(ctx context.Context, id int64, secret string, enabled bool) error { _, err := r.db.ExecContext(ctx, `UPDATE panel_admins SET totp_secret=?,totp_enabled=?,updated_at=? WHERE id=?`, secret, boolInt(enabled), time.Now().UTC(), id) return err } func (r *PanelAdminRepo) ResetTOTP(ctx context.Context, id int64) error { _, err := r.db.ExecContext(ctx, `UPDATE panel_admins SET totp_secret=NULL,totp_enabled=0,updated_at=? WHERE id=?`, time.Now().UTC(), id) return err } func (r *PanelAdminRepo) Delete(ctx context.Context, discordID string) error { _, err := r.db.ExecContext(ctx, `DELETE FROM panel_admins WHERE discord_id=?`, discordID) return err } func scanAdmin(row *sql.Row) (*PanelAdmin, error) { a, err := scanAdminFields(func(dest ...any) error { return row.Scan(dest...) }) if err == sql.ErrNoRows { return nil, nil } return a, err } func scanAdminRows(rows *sql.Rows) (*PanelAdmin, error) { return scanAdminFields(func(dest ...any) error { return rows.Scan(dest...) }) } func scanAdminFields(scan func(...any) error) (*PanelAdmin, error) { var a PanelAdmin var totpEnabled, isSuperadmin int err := scan(&a.ID, &a.DiscordID, &a.DiscordUsername, &a.DiscordAvatar, &a.PasswordHash, &a.TOTPSecret, &totpEnabled, &isSuperadmin, &a.CreatedAt, &a.UpdatedAt) if err != nil { return nil, err } a.TOTPEnabled = totpEnabled != 0 a.IsSuperadmin = isSuperadmin != 0 return &a, nil } func boolInt(b bool) int { if b { return 1 } return 0 }