252 lines
10 KiB
Markdown
252 lines
10 KiB
Markdown
# 🔧 Admin OOM Crash Fix & User Relations Repair — Code Mode Hand-off
|
|
|
|
**Author:** Architect Mode
|
|
**Date:** 2026-07-26
|
|
**Target Mode:** `code` (Fast Coder)
|
|
|
|
---
|
|
|
|
## 📊 Diagnosztikai Összefoglaló
|
|
|
|
### Issue 1: Admin Frontend OOM (`admin.servicefinder.hu` → 500)
|
|
|
|
**Jelenlegi állapot:**
|
|
- A [`sf_admin_frontend`](docker-compose.yml:281) konténer jelenleg FUT (742MiB RAM, 0.42%), az OOM korábban történt.
|
|
- A [`Dockerfile.dev`](frontend_admin/Dockerfile.dev:1) `node:20-slim`-et használ, a CMD `npm run dev` (Nuxt dev mode SSR + HMR).
|
|
- A [`docker-compose.yml`](docker-compose.yml:289) szekcióban **nincs** `NODE_OPTIONS` környezeti változó és **nincs** `deploy.resources.limits.memory` beállítva.
|
|
- A bot-scanner forgalom (`.env` probing) csak Vue Router warningokat generál, nem ez okozza az OOM-t.
|
|
|
|
**Root Cause:** A Nuxt 3 dev módban az SSR (Server-Side Rendering) + HMR (Hot Module Replacement) + a dashboard oldal API hívása (`/api/v1/admin/users/stats`) együtt képes kimeríteni a Node.js default heap limitjét (~2GB). A `node:20-slim` image-nek nincs explicit `--max-old-space-size` beállítása.
|
|
|
|
### Issue 2: User 86 Wallet Status & Missing User Relations
|
|
|
|
**User 86 (`zs.gyongyossy@gmail.com`) státusza:**
|
|
- Wallet: **RENDBEN** — `identity.wallets` id=12, earned=0, purchased=0, service_coins=0, currency=HUF, org_id=45
|
|
- Subscription: **RENDBEN** — `finance.user_subscriptions` id=9, tier_id=13 (`private_free_v1`), is_active=true
|
|
|
|
**6 aktív user-nek NINCS walletje és subscription-je:**
|
|
|
|
| ID | Email | Wallet | Subscription |
|
|
|----|-------|--------|-------------|
|
|
| **1** | `superadmin@profibot.hu` | ❌ MISSING | ❌ MISSING |
|
|
| 136 | `no_perm_1782770270_8c9970@test.com` | ❌ MISSING | ❌ MISSING |
|
|
| 138 | `no_perm_1782778366_243628@test.com` | ❌ MISSING | ❌ MISSING |
|
|
| 153 | `commission_test_gen2@test.com` | ❌ MISSING | ❌ MISSING |
|
|
| 154 | `commission_test_gen1@test.com` | ❌ MISSING | ❌ MISSING |
|
|
| 155 | `commission_test_buyer@test.com` | ❌ MISSING | ❌ MISSING |
|
|
|
|
**Végpont viselkedés:** [`get_wallet_summary`](backend/app/services/billing_engine.py:598) `ValueError`-t dob ha nincs wallet → a végpont 404-et ad. Ez helyes.
|
|
|
|
---
|
|
|
|
## 🛠️ Javítási Terv
|
|
|
|
### FIX-1: Admin Frontend Memory Limit
|
|
|
|
**Fájl:** [`docker-compose.yml`](docker-compose.yml:281-299)
|
|
|
|
**Módosítás — a `sf_admin_frontend` szekcióban:**
|
|
|
|
1. Az `environment` blokkhoz add hozzá:
|
|
```yaml
|
|
- NODE_OPTIONS=--max-old-space-size=4096
|
|
```
|
|
|
|
2. Adj hozzá egy `deploy` blokkot:
|
|
```yaml
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 8G
|
|
```
|
|
|
|
**A módosított szekció:**
|
|
```yaml
|
|
sf_admin_frontend:
|
|
build:
|
|
context: ./frontend_admin
|
|
dockerfile: Dockerfile.dev
|
|
container_name: sf_admin_frontend
|
|
env_file: .env
|
|
ports:
|
|
- "8502:8502"
|
|
environment:
|
|
- NUXT_PORT=8502
|
|
- NUXT_HOST=0.0.0.0
|
|
- NUXT_PUBLIC_API_BASE_URL=https://dev.servicefinder.hu
|
|
- NODE_OPTIONS=--max-old-space-size=4096 # ← NEW
|
|
volumes:
|
|
- ./frontend_admin:/app
|
|
- /app/node_modules
|
|
networks:
|
|
- sf_net
|
|
- shared_db_net
|
|
restart: unless-stopped
|
|
deploy: # ← NEW
|
|
resources:
|
|
limits:
|
|
memory: 8G
|
|
```
|
|
|
|
**Futtatás:**
|
|
```bash
|
|
docker compose up -d --force-recreate sf_admin_frontend
|
|
docker compose logs --tail=20 sf_admin_frontend
|
|
```
|
|
|
|
---
|
|
|
|
### FIX-2: User Relations Repair Script
|
|
|
|
**Fájl létrehozása:** [`backend/scripts/repair_user_relations.py`](backend/scripts/repair_user_relations.py)
|
|
|
|
**Cél:** Minden `is_active = true` user számára biztosítja:
|
|
1. Personal Organization (`fleet.organizations`) — ha még nincs
|
|
2. Wallet (`identity.wallets`) — ha még nincs
|
|
3. User Subscription (`finance.user_subscriptions`, tier=private_free_v1) — ha még nincs
|
|
|
|
**Adatbázis séma referencia:**
|
|
|
|
| Tábla | Séma | Kulcs mezők |
|
|
|-------|------|-------------|
|
|
| `wallets` | `identity` | id (serial PK), user_id (FK), earned_credits, purchased_credits, service_coins, currency, organization_id |
|
|
| `user_subscriptions` | `finance` | id (serial PK), user_id (FK), tier_id (FK→system.subscription_tiers), valid_from, is_active |
|
|
| `organizations` | `fleet` | id (serial PK), name, full_name, display_name, folder_slug, owner_id (FK), org_type, status, is_active, ... |
|
|
|
|
**Alapértelmezett értékek:**
|
|
- **Wallet:** `earned_credits=0`, `purchased_credits=0`, `service_coins=0`, `currency='HUF'`
|
|
- **Subscription:** `tier_id=13` (`private_free_v1`), `valid_from=now()`, `is_active=true`
|
|
- **Organization:** `name="{user.email}'s Garage"`, `org_type='individual'`, `status='active'`, `owner_id=user.id`
|
|
|
|
**Script pszeudokód:**
|
|
```python
|
|
#!/usr/bin/env python3
|
|
"""
|
|
User Relations Repair Script — Idempotens: csak hiányzó rekordokat hoz létre.
|
|
Futtatás: docker compose exec sf_api python3 /app/backend/scripts/repair_user_relations.py
|
|
"""
|
|
import asyncio, sys
|
|
sys.path.insert(0, '/app')
|
|
sys.path.insert(0, '/app/backend')
|
|
|
|
from app.core.database import async_session_factory
|
|
from sqlalchemy import select
|
|
from app.models.identity import User, Wallet
|
|
from app.models.finance import UserSubscription
|
|
from app.models.fleet import Organization
|
|
from datetime import datetime, timezone
|
|
from decimal import Decimal
|
|
|
|
DEFAULT_TIER_ID = 13
|
|
DEFAULT_CURRENCY = "HUF"
|
|
|
|
async def ensure_organization(db, user):
|
|
result = await db.execute(select(Organization).where(Organization.owner_id == user.id))
|
|
org = result.scalar_one_or_none()
|
|
if org:
|
|
return org.id
|
|
folder_slug = f"user-{user.id}-{int(datetime.now(timezone.utc).timestamp())}"
|
|
org = Organization(
|
|
name=f"{user.email}'s Garage", full_name=f"{user.email}'s Personal Garage",
|
|
display_name=f"{user.email}'s Garage", folder_slug=folder_slug,
|
|
owner_id=user.id, org_type="individual", status="active",
|
|
is_active=True, is_deleted=False, subscription_plan="FREE",
|
|
base_asset_limit=5, purchased_extra_slots=0,
|
|
notification_settings={}, external_integration_config={},
|
|
is_verified=False, first_registered_at=datetime.now(timezone.utc),
|
|
current_lifecycle_started_at=datetime.now(timezone.utc),
|
|
lifecycle_index=1, is_anonymized=False, default_currency="HUF",
|
|
country_code="HU", language="hu", is_ownership_transferable=False,
|
|
visual_settings={}, is_affiliate_partner=False, aliases=[],
|
|
tags=[], settings={}, custom_features={},
|
|
)
|
|
db.add(org)
|
|
await db.flush()
|
|
return org.id
|
|
|
|
async def ensure_wallet(db, user, org_id):
|
|
result = await db.execute(select(Wallet).where(Wallet.user_id == user.id))
|
|
if result.scalar_one_or_none():
|
|
return False
|
|
wallet = Wallet(user_id=user.id, earned_credits=Decimal('0'),
|
|
purchased_credits=Decimal('0'), service_coins=Decimal('0'),
|
|
currency=DEFAULT_CURRENCY, organization_id=org_id)
|
|
db.add(wallet)
|
|
await db.flush()
|
|
return True
|
|
|
|
async def ensure_subscription(db, user):
|
|
result = await db.execute(select(UserSubscription).where(UserSubscription.user_id == user.id))
|
|
if result.scalar_one_or_none():
|
|
return False
|
|
sub = UserSubscription(user_id=user.id, tier_id=DEFAULT_TIER_ID,
|
|
valid_from=datetime.now(timezone.utc), is_active=True)
|
|
db.add(sub)
|
|
await db.flush()
|
|
return True
|
|
|
|
async def repair_all_users():
|
|
async with async_session_factory() as db:
|
|
result = await db.execute(select(User).where(User.is_active == True))
|
|
users = result.scalars().all()
|
|
stats = {"total": len(users), "wallets_created": 0, "subs_created": 0, "errors": []}
|
|
for user in users:
|
|
try:
|
|
org_id = await ensure_organization(db, user)
|
|
if await ensure_wallet(db, user, org_id):
|
|
stats["wallets_created"] += 1
|
|
if await ensure_subscription(db, user):
|
|
stats["subs_created"] += 1
|
|
except Exception as e:
|
|
stats["errors"].append(f"User {user.id}: {e}")
|
|
await db.commit()
|
|
print(f"=== REPAIR: total={stats['total']} wallets_new={stats['wallets_created']} subs_new={stats['subs_created']} errors={len(stats['errors'])}")
|
|
for e in stats["errors"]: print(f" ERR: {e}")
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(repair_all_users())
|
|
```
|
|
|
|
**Futtatás:**
|
|
```bash
|
|
docker compose exec sf_api python3 /app/backend/scripts/repair_user_relations.py
|
|
```
|
|
|
|
**Ellenőrzés:**
|
|
```sql
|
|
SELECT u.id, u.email, w.id as wallet_id, s.id as sub_id
|
|
FROM identity.users u
|
|
LEFT JOIN identity.wallets w ON u.id = w.user_id
|
|
LEFT JOIN finance.user_subscriptions s ON u.id = s.user_id
|
|
WHERE u.id IN (1, 136, 138, 153, 154, 155);
|
|
```
|
|
|
|
---
|
|
|
|
### FIX-3 (OPTIONAL): get_wallet_summary Auto-Create
|
|
|
|
**Architect döntés:** **NEM módosítjuk.** A 404 helyes válasz. A repair script után a probléma megszűnik. A wallet auto-create a regisztrációs flow-ban garantálandó — külön feladat.
|
|
|
|
---
|
|
|
|
## 📋 Végrehajtási Sorrend (Code Mode)
|
|
|
|
| # | Feladat | Fájl | Parancs |
|
|
|---|---------|------|---------|
|
|
| 1 | `NODE_OPTIONS` + memory limit | [`docker-compose.yml`](docker-compose.yml:289) | Szerkesztés |
|
|
| 2 | Admin restart | — | `docker compose up -d --force-recreate sf_admin_frontend` |
|
|
| 3 | Repair script létrehozása | [`backend/scripts/repair_user_relations.py`](backend/scripts/repair_user_relations.py) | Fájl létrehozása |
|
|
| 4 | Repair script futtatása | — | `docker compose exec sf_api python3 /app/backend/scripts/repair_user_relations.py` |
|
|
| 5 | SQL ellenőrzés | — | MCP Postgres query |
|
|
| 6 | Böngésző ellenőrzés | — | `https://admin.servicefinder.hu/` |
|
|
|
|
---
|
|
|
|
## ⚠️ Kockázatok
|
|
|
|
1. **superadmin (ID=1):** Lehet hogy szándékosan nincs walletje. Ha nem kívánatos, skip-elni kell ID=1-et a scriptben.
|
|
2. **Organization folder_slug:** Egyedi kell legyen. A `user-{id}-{timestamp}` minta garantálja.
|
|
3. **Tier ID=13:** Ellenőrizve — létezik `system.subscription_tiers`-ben, `tier_level=0`.
|
|
4. **Docker restart:** ~30-60 másodperc downtime a Nuxt újraépítése miatt.
|
|
5. **A repair script IDEMPOTENS:** Többször futtatható, nem duplikál.
|