admin frontend elkezdése, járművek tisztázása, frontend fejleszts

This commit is contained in:
Roo
2026-06-15 18:52:38 +00:00
parent ef8df9608c
commit 213ba3b0f1
80 changed files with 11615 additions and 2304 deletions

View File

@@ -0,0 +1,241 @@
#!/usr/bin/env python3
"""
Hard Delete Cleanup Script — Cascade törlés User és kapcsolódó rekordokra.
Használat:
docker compose exec sf_api python3 /app/backend/app/scripts/cleanup_user.py --email %test%
Az --email paraméter részleges egyezést is támogat (SQL LIKE, pl. %test%@%.
A szkript tranzakcióban törli:
- User rekordot
- Person rekordot (ha a User volt az egyetlen kapcsolódó)
- Organization rekordot (ha a User volt az egyetlen owner)
- Session tokeneket, SocialAccount-okat, Wallet-eket, stb.
"""
import argparse
import logging
import sys
from sqlalchemy import select, delete, text
from sqlalchemy.orm import joinedload
# Projekt importok
from app.database import async_session_factory
from app.models.identity import User, Person, UserRole
from app.models.identity.identity import (
SocialAccount, Wallet, ActiveVoucher, VerificationToken,
UserDeviceLink, UserTrustProfile, OneTimePassword,
)
from app.models.gamification.gamification import UserStats
from app.models.marketplace.service_request import ServiceRequest
from app.models.social import ServiceReview
from app.models.vehicle.asset import VehicleOwnership
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger("cleanup_user")
async def cleanup_user(email_pattern: str) -> None:
"""
Megkeresi a megadott email mintára illeszkedő usereket,
és cascade hard delete-et hajt végre rajtuk.
"""
async with async_session_factory() as db:
# 1. Keresés részleges email egyezéssel
stmt = (
select(User)
.where(User.email.ilike(email_pattern))
.options(
joinedload(User.person),
joinedload(User.stats),
joinedload(User.wallet),
joinedload(User.trust_profile),
joinedload(User.ownership_history),
joinedload(User.social_accounts),
joinedload(User.service_reviews),
joinedload(User.service_requests),
)
)
result = await db.execute(stmt)
users = result.unique().scalars().all()
if not users:
logger.warning("❌ Nem található user a '%s' mintára.", email_pattern)
return
logger.info("🔍 Talált userek (%d db):", len(users))
for u in users:
logger.info(" - ID=%d, Email=%s, Role=%s", u.id, u.email, u.role.value if hasattr(u.role, 'value') else u.role)
# 2. Tranzakció indítása
async with db.begin():
for user in users:
user_id = user.id
email = user.email
logger.info("🗑️ Törlés: User ID=%d, Email=%s", user_id, email)
# --- Cascade törlés ---
# UserDeviceLink
await db.execute(
delete(UserDeviceLink).where(UserDeviceLink.user_id == user_id)
)
logger.info(" ✓ UserDeviceLink törölve")
# VerificationToken
await db.execute(
delete(VerificationToken).where(VerificationToken.user_id == user_id)
)
logger.info(" ✓ VerificationToken törölve")
# OneTimePassword
await db.execute(
delete(OneTimePassword).where(OneTimePassword.email == email)
)
logger.info(" ✓ OneTimePassword törölve")
# SocialAccount (cascade all, delete-orphan)
await db.execute(
delete(SocialAccount).where(SocialAccount.user_id == user_id)
)
logger.info(" ✓ SocialAccount törölve")
# UserTrustProfile (cascade all, delete-orphan)
await db.execute(
delete(UserTrustProfile).where(UserTrustProfile.user_id == user_id)
)
logger.info(" ✓ UserTrustProfile törölve")
# UserStats (gamification)
await db.execute(
delete(UserStats).where(UserStats.user_id == user_id)
)
logger.info(" ✓ UserStats törölve")
# Wallet + ActiveVoucher (cascade)
wallet_stmt = select(Wallet).where(Wallet.user_id == user_id)
wallet_result = await db.execute(wallet_stmt)
wallet = wallet_result.scalar_one_or_none()
if wallet:
await db.execute(
delete(ActiveVoucher).where(ActiveVoucher.wallet_id == wallet.id)
)
await db.execute(
delete(Wallet).where(Wallet.id == wallet.id)
)
logger.info(" ✓ Wallet + ActiveVoucher törölve")
# ServiceReview
await db.execute(
delete(ServiceReview).where(ServiceReview.user_id == user_id)
)
logger.info(" ✓ ServiceReview törölve")
# ServiceRequest
await db.execute(
delete(ServiceRequest).where(ServiceRequest.user_id == user_id)
)
logger.info(" ✓ ServiceRequest törölve")
# VehicleOwnership
await db.execute(
delete(VehicleOwnership).where(VehicleOwnership.user_id == user_id)
)
logger.info(" ✓ VehicleOwnership törölve")
# Person rekord kezelése
person = user.person
if person:
# Ellenőrizzük, hogy más User nem hivatkozik-e erre a Person-ra
other_users_stmt = (
select(User)
.where(User.person_id == person.id, User.id != user_id)
.limit(1)
)
other_result = await db.execute(other_users_stmt)
other_user = other_result.scalar_one_or_none()
if not other_user:
# Nincs más User ehhez a Person-hoz -> törölhető
await db.execute(
delete(Person).where(Person.id == person.id)
)
logger.info(" ✓ Person ID=%d törölve (egyedüli kapcsolat)", person.id)
else:
logger.info(
" ⚠️ Person ID=%d MEGTARTVA (más User is hivatkozik rá: ID=%d)",
person.id, other_user.id
)
# Organization kezelése (ha a User volt az egyetlen owner)
from app.models.identity.organization import Organization
org_stmt = (
select(Organization)
.where(Organization.owner_id == user_id)
)
org_result = await db.execute(org_stmt)
owned_orgs = org_result.scalars().all()
for org in owned_orgs:
# Ellenőrizzük, hogy más User nem owner-e
other_owner_stmt = (
select(User)
.where(User.id != user_id, User.id.isnot(None))
.limit(1)
)
# Egyszerűbb: csak töröljük, ha nincs más owner
# (a gyakorlatban az Organization-nak lehet több tagja)
from app.models.identity.organization import OrganizationMember
member_stmt = (
select(OrganizationMember)
.where(
OrganizationMember.organization_id == org.id,
OrganizationMember.user_id != user_id,
)
.limit(1)
)
member_result = await db.execute(member_stmt)
other_member = member_result.scalar_one_or_none()
if not other_member:
await db.execute(
delete(Organization).where(Organization.id == org.id)
)
logger.info(" ✓ Organization ID=%d törölve (egyedüli owner)", org.id)
else:
logger.info(
" ⚠️ Organization ID=%d MEGTARTVA (van más tag)",
org.id
)
# VÉGÜL: Maga a User rekord törlése
await db.execute(
delete(User).where(User.id == user_id)
)
logger.info(" ✅ User ID=%d (%s) törölve.", user_id, email)
logger.info("🎉 Tranzakció sikeresen végrehajtva.")
def main():
parser = argparse.ArgumentParser(
description="Hard Delete Cleanup — User és kapcsolódó rekordok cascade törlése."
)
parser.add_argument(
"--email",
required=True,
help="Email minta (SQL LIKE, pl. %%test%% vagy teszt%%).",
)
args = parser.parse_args()
import asyncio
asyncio.run(cleanup_user(args.email))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,363 @@
#!/usr/bin/env python3
"""
📦 Nagy Előfizetés Migráció (Subscription Migration Script)
Cél:
A régi, denormalizált subscription_plan mezők (identity.users, fleet.organizations)
átvezetése a modern, normalizált subscription_tier + org_subscriptions / user_subscriptions
architektúrába.
Feladatok:
A) Létrehozza a corp_free_v1 csomagot, ha még nem létezik (0 áras, alap korlátokkal).
B) Létrehozza a user_subscriptions táblát (finance.user_subscriptions), ha még nem létezik.
C) Adatkonverzió: a meglévő subscription_plan string-eket átalakítja tier_id hivatkozásokká.
Használat:
docker compose exec sf_api python3 /app/backend/app/scripts/migrate_subscriptions.py
Architektúra:
- system.subscription_tiers: A csomagdefiníciók (SubscriptionTier modell)
- finance.org_subscriptions: Szervezeti előfizetések (OrganizationSubscription modell)
- finance.user_subscriptions: Felhasználói előfizetések (UserSubscription modell - ÚJ!)
"""
import asyncio
import logging
from datetime import datetime, timezone
from sqlalchemy import text, select, Integer, String, Boolean, DateTime, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql import func
from app.database import AsyncSessionLocal, Base
from app.models.core_logic import SubscriptionTier, OrganizationSubscription
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger("Migrate-Subscriptions")
# ---------------------------------------------------------------------------
# Tiers név -> ID mapping (feltöltés után)
# ---------------------------------------------------------------------------
TIER_MAP: dict[str, int] = {}
# ---------------------------------------------------------------------------
# A) corp_free_v1 definíció
# ---------------------------------------------------------------------------
CORP_FREE_V1 = {
"name": "corp_free_v1",
"rules": {
"type": "corporate",
"display_name": "Céges Ingyenes",
"pricing": {
"monthly_price": 0.0,
"yearly_price": 0.0,
"currency": "EUR",
"credit_price": 0,
},
"allowances": {
"max_vehicles": 3,
"max_garages": 1,
"monthly_free_credits": 0,
},
"entitlements": [],
"affiliate": {
"commission_rate_percent": 0,
"referral_bonus_credits": 0,
},
"lifecycle": {
"is_public": True,
},
},
"is_custom": False,
}
# ---------------------------------------------------------------------------
# Régi plan string -> tier név mapping
# ---------------------------------------------------------------------------
PLAN_TO_TIER: dict[str, str] = {
"FREE": "private_free_v1",
"free": "private_free_v1",
"PRO": "private_pro_v1",
"PREMIUM": "corp_premium_v1",
"ENTERPRISE": "corp_vip_v1",
}
# ---------------------------------------------------------------------------
# Segédfüggvények
# ---------------------------------------------------------------------------
async def _ensure_corp_free_v1(db) -> None:
"""A) Létrehozza a corp_free_v1 csomagot, ha még nem létezik."""
result = await db.execute(
select(SubscriptionTier).where(SubscriptionTier.name == "corp_free_v1")
)
existing = result.scalar_one_or_none()
if existing:
logger.info("✅ corp_free_v1 már létezik (ID=%d). Kihagyva.", existing.id)
TIER_MAP["corp_free_v1"] = existing.id
return
tier = SubscriptionTier(
name=CORP_FREE_V1["name"],
rules=CORP_FREE_V1["rules"],
is_custom=CORP_FREE_V1["is_custom"],
)
db.add(tier)
await db.commit()
await db.refresh(tier)
TIER_MAP["corp_free_v1"] = tier.id
logger.info("✅ corp_free_v1 létrehozva (ID=%d).", tier.id)
async def _load_tier_map(db) -> None:
"""Betölti az összes tier nevét és ID-ját a TIER_MAP-ba."""
result = await db.execute(select(SubscriptionTier))
tiers = result.scalars().all()
for t in tiers:
TIER_MAP[t.name] = t.id
logger.info("📋 Betöltött tier-ek: %s", {n: i for n, i in TIER_MAP.items()})
def _resolve_tier_id(plan: str | None) -> int | None:
"""Egy régi plan string-ből kikeresi a megfelelő tier ID-t."""
if not plan:
return None
tier_name = PLAN_TO_TIER.get(plan)
if not tier_name:
logger.warning("⚠️ Ismeretlen plan: '%s' -> private_free_v1 lesz.", plan)
tier_name = "private_free_v1"
return TIER_MAP.get(tier_name)
# ---------------------------------------------------------------------------
# B) UserSubscription tábla létrehozása (ha nem létezik)
# ---------------------------------------------------------------------------
async def _ensure_user_subscriptions_table(db) -> None:
"""Létrehozza a finance.user_subscriptions táblát, ha még nem létezik."""
result = await db.execute(
text(
"SELECT EXISTS (SELECT FROM information_schema.tables "
"WHERE table_schema = 'finance' AND table_name = 'user_subscriptions')"
)
)
exists = result.scalar()
if exists:
logger.info("✅ finance.user_subscriptions tábla már létezik.")
return
logger.info("🛠️ Létrehozom a finance.user_subscriptions táblát...")
await db.execute(
text("""
CREATE TABLE finance.user_subscriptions (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES identity.users(id) ON DELETE CASCADE,
tier_id INTEGER NOT NULL REFERENCES system.subscription_tiers(id),
valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
valid_until TIMESTAMPTZ,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ
)
""")
)
# Index a gyors lekérdezéshez
await db.execute(
text("""
CREATE INDEX idx_user_subscriptions_user_id
ON finance.user_subscriptions(user_id)
""")
)
await db.commit()
logger.info("✅ finance.user_subscriptions tábla létrehozva.")
# ---------------------------------------------------------------------------
# C) Adatkonverzió
# ---------------------------------------------------------------------------
async def _migrate_organizations(db) -> None:
"""Szervezetek subscription_plan mezőjének átvezetése org_subscriptions-ba."""
logger.info("=" * 60)
logger.info("Szervezetek migrálása...")
logger.info("=" * 60)
# 1. Lekérdezzük az összes szervezetet, aminek nincs még org_subscription-e
result = await db.execute(
text("""
SELECT o.id, o.subscription_plan
FROM fleet.organizations o
WHERE o.is_deleted = FALSE
AND NOT EXISTS (
SELECT 1 FROM finance.org_subscriptions os
WHERE os.org_id = o.id
)
ORDER BY o.id
""")
)
orgs = result.all()
logger.info("📊 %d szervezet vár migrálásra.", len(orgs))
inserted = 0
skipped = 0
for org in orgs:
tier_id = _resolve_tier_id(org.subscription_plan)
if tier_id is None:
logger.warning("⚠️ Org ID=%d: plan='%s' nem feloldható, kihagyva.", org.id, org.subscription_plan)
skipped += 1
continue
sub = OrganizationSubscription(
org_id=org.id,
tier_id=tier_id,
valid_from=datetime.now(timezone.utc),
valid_until=None,
is_active=True,
)
db.add(sub)
inserted += 1
await db.commit()
logger.info("%d org_subscription beszúrva. (%d kihagyva)", inserted, skipped)
async def _migrate_users(db) -> None:
"""Felhasználók subscription_plan mezőjének átvezetése user_subscriptions-ba."""
logger.info("=" * 60)
logger.info("Felhasználók migrálása...")
logger.info("=" * 60)
# 1. Lekérdezzük az összes aktív felhasználót, aminek nincs még user_subscription-e
result = await db.execute(
text("""
SELECT u.id, u.subscription_plan
FROM identity.users u
WHERE u.is_deleted = FALSE
AND NOT EXISTS (
SELECT 1 FROM finance.user_subscriptions us
WHERE us.user_id = u.id
)
ORDER BY u.id
""")
)
users = result.all()
logger.info("📊 %d felhasználó vár migrálásra.", len(users))
inserted = 0
skipped = 0
for user in users:
tier_id = _resolve_tier_id(user.subscription_plan)
if tier_id is None:
logger.warning("⚠️ User ID=%d: plan='%s' nem feloldható, kihagyva.", user.id, user.subscription_plan)
skipped += 1
continue
await db.execute(
text("""
INSERT INTO finance.user_subscriptions
(user_id, tier_id, valid_from, is_active, created_at)
VALUES
(:uid, :tid, NOW(), TRUE, NOW())
"""),
{"uid": user.id, "tid": tier_id},
)
inserted += 1
await db.commit()
logger.info("%d user_subscription beszúrva. (%d kihagyva)", inserted, skipped)
async def _verify_migration(db) -> None:
"""Ellenőrzi a migráció sikerességét."""
logger.info("=" * 60)
logger.info("🔍 Ellenőrzés")
logger.info("=" * 60)
# Tiers
result = await db.execute(
text("SELECT id, name FROM system.subscription_tiers ORDER BY id")
)
tiers = result.all()
logger.info("📋 Tiers (%d db):", len(tiers))
for t in tiers:
logger.info(" ID=%d name=%s", t.id, t.name)
# Org subscriptions
result = await db.execute(
text("""
SELECT COUNT(*) as cnt,
COALESCE(t.name, 'N/A') as tier_name
FROM finance.org_subscriptions os
LEFT JOIN system.subscription_tiers t ON t.id = os.tier_id
GROUP BY t.name
ORDER BY t.name
""")
)
rows = result.all()
total_org = sum(r.cnt for r in rows)
logger.info("📊 Org subscriptions (%d db):", total_org)
for r in rows:
logger.info(" %s: %d", r.tier_name, r.cnt)
# User subscriptions
result = await db.execute(
text("""
SELECT COUNT(*) as cnt,
COALESCE(t.name, 'N/A') as tier_name
FROM finance.user_subscriptions us
LEFT JOIN system.subscription_tiers t ON t.id = us.tier_id
GROUP BY t.name
ORDER BY t.name
""")
)
rows = result.all()
total_user = sum(r.cnt for r in rows)
logger.info("📊 User subscriptions (%d db):", total_user)
for r in rows:
logger.info(" %s: %d", r.tier_name, r.cnt)
logger.info("=" * 60)
logger.info("✅ Migráció befejeződött!")
logger.info(" Összes org subscription: %d", total_org)
logger.info(" Összes user subscription: %d", total_user)
logger.info("=" * 60)
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
async def migrate():
logger.info("=" * 60)
logger.info("📦 Nagy Előfizetés Migráció")
logger.info("=" * 60)
async with AsyncSessionLocal() as db:
# A) corp_free_v1 létrehozása
logger.info("\n[1/5] corp_free_v1 csomag ellenőrzése...")
await _ensure_corp_free_v1(db)
# Tier map betöltése
logger.info("\n[2/5] Tier-ek betöltése...")
await _load_tier_map(db)
# B) user_subscriptions tábla létrehozása
logger.info("\n[3/5] user_subscriptions tábla ellenőrzése...")
await _ensure_user_subscriptions_table(db)
# C) Adatkonverzió
logger.info("\n[4/5] Adatkonverzió...")
await _migrate_organizations(db)
await _migrate_users(db)
# Ellenőrzés
logger.info("\n[5/5] Ellenőrzés...")
await _verify_migration(db)
if __name__ == "__main__":
asyncio.run(migrate())

View File

@@ -0,0 +1,269 @@
#!/usr/bin/env python3
"""
Seed szkript a jövőbiztos SaaS csomag-architektúra 1. fázisához.
Létrehozza a 6 alap subscription tier-t a system.subscription_tiers táblában.
Használat:
docker compose exec sf_api python3 /app/backend/app/scripts/seed_packages.py
Architektúra:
A csomagok rules JSONB oszlopa egy szabványos struktúrát követ:
{
"type": "private" | "corporate",
"pricing": {"monthly_price": float, "yearly_price": float, "currency": "EUR"},
"allowances": {"max_vehicles": int, "max_garages": int, "monthly_free_credits": int},
"entitlements": ["SRV_..."],
"affiliate": {"commission_rate_percent": int, "referral_bonus_credits": int}
}
"""
import asyncio
import json
import logging
from sqlalchemy import select, delete
from app.database import AsyncSessionLocal
from app.models.core_logic import SubscriptionTier
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger("Seed-Packages")
# ---------------------------------------------------------------------------
# Csomagdefiníciók
# ---------------------------------------------------------------------------
PACKAGES = [
{
"name": "private_free_v1",
"rules": {
"type": "private",
"display_name": "Privát Ingyenes",
"pricing": {
"monthly_price": 0.0,
"yearly_price": 0.0,
"currency": "EUR",
"credit_price": 0,
},
"allowances": {
"max_vehicles": 1,
"max_garages": 1,
"monthly_free_credits": 0,
},
"entitlements": [],
"affiliate": {
"commission_rate_percent": 0,
"referral_bonus_credits": 0,
},
"lifecycle": {
"is_public": True,
},
},
"is_custom": False,
},
{
"name": "private_pro_v1",
"rules": {
"type": "private",
"display_name": "Privát Pro",
"pricing": {
"monthly_price": 4.99,
"yearly_price": 49.99,
"currency": "EUR",
"credit_price": 500,
},
"allowances": {
"max_vehicles": 3,
"max_garages": 1,
"monthly_free_credits": 50,
},
"entitlements": ["SRV_DATA_EXPORT"],
"affiliate": {
"commission_rate_percent": 10,
"referral_bonus_credits": 25,
},
"lifecycle": {
"is_public": True,
},
},
"is_custom": False,
},
{
"name": "private_vip_v1",
"rules": {
"type": "private",
"display_name": "Privát VIP",
"pricing": {
"monthly_price": 9.99,
"yearly_price": 99.99,
"currency": "EUR",
"credit_price": 1200,
},
"allowances": {
"max_vehicles": 10,
"max_garages": 1,
"monthly_free_credits": 150,
},
"entitlements": ["SRV_DATA_EXPORT", "SRV_AI_UPLOAD"],
"affiliate": {
"commission_rate_percent": 15,
"referral_bonus_credits": 50,
},
"lifecycle": {
"is_public": True,
},
},
"is_custom": False,
},
{
"name": "corp_premium_v1",
"rules": {
"type": "corporate",
"display_name": "Céges Prémium",
"pricing": {
"monthly_price": 29.99,
"yearly_price": 299.99,
"currency": "EUR",
"credit_price": 3000,
},
"allowances": {
"max_vehicles": 20,
"max_garages": 3,
"monthly_free_credits": 300,
},
"entitlements": ["SRV_DATA_EXPORT", "SRV_AI_UPLOAD"],
"affiliate": {
"commission_rate_percent": 15,
"referral_bonus_credits": 100,
},
"lifecycle": {
"is_public": True,
},
},
"is_custom": False,
},
{
"name": "corp_premium_plus_v1",
"rules": {
"type": "corporate",
"display_name": "Céges Prémium Plus",
"pricing": {
"monthly_price": 59.99,
"yearly_price": 599.99,
"currency": "EUR",
"credit_price": 6000,
},
"allowances": {
"max_vehicles": 50,
"max_garages": 10,
"monthly_free_credits": 800,
},
"entitlements": [
"SRV_DATA_EXPORT",
"SRV_AI_UPLOAD",
"SRV_ACCOUNTING_SYNC",
],
"affiliate": {
"commission_rate_percent": 20,
"referral_bonus_credits": 250,
},
"lifecycle": {
"is_public": True,
},
},
"is_custom": False,
},
{
"name": "corp_vip_v1",
"rules": {
"type": "corporate",
"display_name": "Céges VIP",
"pricing": {
"monthly_price": 149.99,
"yearly_price": 1499.99,
"currency": "EUR",
"credit_price": 15000,
},
"allowances": {
"max_vehicles": 200,
"max_garages": 50,
"monthly_free_credits": 2500,
},
"entitlements": [
"SRV_DATA_EXPORT",
"SRV_AI_UPLOAD",
"SRV_ACCOUNTING_SYNC",
"SRV_API_ACCESS",
],
"affiliate": {
"commission_rate_percent": 25,
"referral_bonus_credits": 500,
},
"lifecycle": {
"is_public": True,
},
},
"is_custom": False,
},
]
async def seed():
logger.info("=" * 60)
logger.info("Seed Packages - Advanced Subscription Tiers (Phase 1)")
logger.info("=" * 60)
async with AsyncSessionLocal() as db:
# 1. Töröljük a meglévő rekordokat (tiszta lap)
logger.info("Törlöm a meglévő subscription_tiers rekordokat...")
await db.execute(delete(SubscriptionTier))
await db.commit()
logger.info("✅ Meglévő rekordok törölve.")
# 2. Beszúrjuk a 6 új csomagot
inserted_count = 0
for pkg in PACKAGES:
tier = SubscriptionTier(
name=pkg["name"],
rules=pkg["rules"],
is_custom=pkg["is_custom"],
)
db.add(tier)
inserted_count += 1
logger.info(f" Hozzáadva: {pkg['name']}")
await db.commit()
logger.info(f"{inserted_count} csomag sikeresen beszúrva.")
# 3. Visszaigazolás: listázzuk ki az összes rekordot
logger.info("-" * 60)
logger.info("Visszaigazolás - system.subscription_tiers tartalma:")
logger.info("-" * 60)
result = await db.execute(
select(SubscriptionTier).order_by(SubscriptionTier.id)
)
tiers = result.scalars().all()
for tier in tiers:
rules_json = json.dumps(tier.rules, indent=2, ensure_ascii=False)
logger.info(f"\n ID: {tier.id}")
logger.info(f" Name: {tier.name}")
logger.info(f" Is Custom: {tier.is_custom}")
logger.info(f" Rules:\n{rules_json}")
logger.info(" " + "-" * 40)
logger.info(f"\n✅ Összesen {len(tiers)} aktív csomag a rendszerben.")
# 4. JSONB példa kiírása
if tiers:
sample = tiers[0]
logger.info("=" * 60)
logger.info("JSONB PÉLDA (private_free_v1):")
logger.info("=" * 60)
logger.info(json.dumps(sample.rules, indent=2, ensure_ascii=False))
if __name__ == "__main__":
asyncio.run(seed())

View File

@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""
Service Finder - ServiceCatalog Seed Script
=============================================
Aszinkron seed szkript a system.service_catalog tábla feltöltéséhez.
Törli a meglévő adatokat, majd beszúrja a 3 alap szolgáltatást.
Használat:
docker compose exec sf_api python3 /app/backend/app/scripts/seed_services.py
"""
import asyncio
import sys
from pathlib import Path
# Projekt gyökér hozzáadása a Python path-hoz
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from sqlalchemy import text
from app.db.session import AsyncSessionLocal
from app.models.core_logic import ServiceCatalog
SERVICES = [
{
"service_code": "SRV_DATA_EXPORT",
"name": "Adat Export",
"description": "PDF és Excel adatexport funkciók",
"credit_cost": 0,
},
{
"service_code": "SRV_AI_UPLOAD",
"name": "AI Számlafeldolgozás",
"description": "Dokumentumok és számlák AI alapú beolvasása",
"credit_cost": 50,
},
{
"service_code": "SRV_DIGITAL_BOOK",
"name": "Digitális Szervizkönyv",
"description": "Idegen járművek szerviztörténetének lekérdezése",
"credit_cost": 150,
},
]
async def seed_services():
print("=" * 70)
print(" ServiceCatalog Seed Script")
print("=" * 70)
async with AsyncSessionLocal() as db:
# 1. Tábla tartalmának törlése
print("\n🧹 Törlés: system.service_catalog összes rekordja...")
await db.execute(text("DELETE FROM system.service_catalog"))
await db.commit()
print(" ✅ Törölve.")
# 2. Szolgáltatások beszúrása
print("\n📦 Szolgáltatások beszúrása:")
for svc in SERVICES:
stmt = text("""
INSERT INTO system.service_catalog (service_code, name, description, credit_cost, is_active)
VALUES (:service_code, :name, :description, :credit_cost, TRUE)
""")
await db.execute(stmt, svc)
print(f"{svc['service_code']:20s} | {svc['name']:25s} | {svc['credit_cost']:>4} kredit")
await db.commit()
print("\n ✅ Minden rekord beszúrva.")
# 3. Lekérdezés - bizonyíték
print("\n" + "=" * 70)
print(" 📋 LEKÉRDEZÉS - system.service_catalog tartalma")
print("=" * 70)
result = await db.execute(
text("SELECT id, service_code, name, description, credit_cost, is_active FROM system.service_catalog ORDER BY id")
)
rows = result.fetchall()
if not rows:
print("\n❌ NINCSENEK REKORDOK a táblában!")
else:
print(f"\n{'ID':>4} | {'Kód':20s} | {'Név':25s} | {'Leírás':45s} | {'Ár':>5s} | {'Aktív':>5s}")
print("-" * 110)
for row in rows:
print(f"{row.id:>4} | {row.service_code:20s} | {row.name:25s} | {(row.description or ''):45s} | {row.credit_cost:>5} | {'Igen' if row.is_active else 'Nem'}")
print("\n" + "=" * 70)
print(f" Összesen: {len(rows)} rekord a system.service_catalog táblában.")
print("=" * 70)
if __name__ == "__main__":
asyncio.run(seed_services())

View File

@@ -18,15 +18,7 @@ from app.database import Base
from app.core.config import settings
def dynamic_import_models():
models_dir = Path(__file__).parent.parent / "models"
for py_file in models_dir.glob("*.py"):
if py_file.name == "__init__.py": continue
module_name = f"app.models.{py_file.stem}"
try:
importlib.import_module(module_name)
print(f"✅ Imported {module_name}")
except Exception as e:
print(f"⚠️ Could not import {module_name}: {e}")
"""Modellek betöltése a Metadata feltöltéséhez. Egyszerű import az __init__.py-n keresztül."""
import app.models
print(f"📦 Total tables in Base.metadata: {len(Base.metadata.tables)}")