package users import ( "context" "errors" "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "github.com/ultisuite/ulti-backend/internal/auth" "github.com/ultisuite/ulti-backend/internal/permission" ) var ErrLastPlatformAdmin = errors.New("cannot remove the last platform admin") // AccountState is the persisted user lifecycle used for auth and admin APIs. type AccountState struct { Status string PlatformAdmin bool } // GetAccountState loads status and platform_admin for an external_id. func GetAccountState(ctx context.Context, db *pgxpool.Pool, externalID string) (AccountState, error) { if db == nil || externalID == "" { return AccountState{Status: "active"}, nil } var state AccountState err := db.QueryRow(ctx, ` SELECT status, platform_admin FROM users WHERE external_id = $1 `, externalID).Scan(&state.Status, &state.PlatformAdmin) if errors.Is(err, pgx.ErrNoRows) { return AccountState{Status: "active"}, nil } if err != nil { return AccountState{}, err } return state, nil } // ApplyAccountGroups adjusts OIDC groups from the persisted account role. func ApplyAccountGroups(ctx context.Context, db *pgxpool.Pool, claims *auth.Claims) error { if claims == nil || db == nil { return nil } state, err := GetAccountState(ctx, db, claims.Sub) if err != nil { return err } switch permission.DeriveAccountRole(state.PlatformAdmin, state.Status) { case permission.AccountRoleGuest: claims.Groups = permission.WithGuestAccess(claims.Groups) case permission.AccountRoleSuspended: // Auth middleware rejects disabled accounts before handlers run. default: claims.Groups = permission.WithSuiteDefaults(claims.Groups) } if state.PlatformAdmin { claims.Groups = permission.WithPlatformAdmin(claims.Groups) } return nil } // CountPlatformAdmins returns active platform admins. func CountPlatformAdmins(ctx context.Context, db *pgxpool.Pool) (int64, error) { if db == nil { return 0, fmt.Errorf("database not configured") } var count int64 err := db.QueryRow(ctx, ` SELECT COUNT(*) FROM users WHERE platform_admin = true AND status = 'active' `).Scan(&count) return count, err } // RevokePlatformAdmin clears platform_admin for external_id. func RevokePlatformAdmin(ctx context.Context, db *pgxpool.Pool, externalID string) error { if db == nil { return fmt.Errorf("database not configured") } if externalID == "" { return fmt.Errorf("missing external id") } tag, err := db.Exec(ctx, ` UPDATE users SET platform_admin = false, updated_at = NOW() WHERE external_id = $1 `, externalID) if err != nil { return err } if tag.RowsAffected() == 0 { return pgx.ErrNoRows } return nil }