2026.06.04 frontend építés közben

This commit is contained in:
Roo
2026-06-04 07:26:22 +00:00
parent 7adf6cc3e3
commit 59a30ac428
3302 changed files with 24091 additions and 1771 deletions

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python3
import asyncio
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from app.core.config import settings
async def check_person_schema():
engine = create_async_engine(settings.DATABASE_URL, echo=False)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with async_session() as session:
# Get columns of identity.persons
query = text("""
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'identity' AND table_name = 'persons'
ORDER BY ordinal_position;
""")
result = await session.execute(query)
columns = result.fetchall()
print("Columns in identity.persons:")
for col in columns:
print(f" {col[0]} ({col[1]})")
# Get columns of identity.users
query2 = text("""
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'identity' AND table_name = 'users'
ORDER BY ordinal_position;
""")
result2 = await session.execute(query2)
columns2 = result2.fetchall()
print("\nColumns in identity.users:")
for col in columns2:
print(f" {col[0]} ({col[1]})")
# Find relationship
query3 = text("""
SELECT u.email, u.person_id, p.id, p.first_name, p.last_name
FROM identity.users u
JOIN identity.persons p ON u.person_id = p.id
WHERE u.email = 'tester_pro@profibot.hu'
LIMIT 1;
""")
try:
result3 = await session.execute(query3)
user = result3.fetchone()
if user:
print(f"\nTest user found: email={user[0]}, user.person_id={user[1]}, persons.id={user[2]}, name={user[3]} {user[4]}")
else:
print("\nTest user not found")
except Exception as e:
print(f"\nError joining: {e}")
# Try alternative column names
pass
await engine.dispose()
if __name__ == "__main__":
asyncio.run(check_person_schema())

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env python3
import asyncio
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import text, inspect
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from app.core.config import settings
async def check_schema():
engine = create_async_engine(settings.DATABASE_URL, echo=False)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with async_session() as session:
# Get columns of vehicle.assets
query = text("""
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'vehicle' AND table_name = 'assets'
ORDER BY ordinal_position;
""")
result = await session.execute(query)
columns = result.fetchall()
print("Columns in vehicle.assets:")
for col in columns:
print(f" {col[0]} ({col[1]})")
# Check if owner_user_id exists
owner_exists = any(col[0] == 'owner_user_id' for col in columns)
print(f"\nowner_user_id exists: {owner_exists}")
# Find alternative column names
alt_columns = [col[0] for col in columns if 'user' in col[0] or 'owner' in col[0]]
print(f"\nPossible owner/user columns: {alt_columns}")
# Also check identity.users email column
query2 = text("""
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'identity' AND table_name = 'users'
AND column_name = 'email';
""")
result2 = await session.execute(query2)
email_col = result2.fetchone()
print(f"\nidentity.users email column: {email_col}")
await engine.dispose()
if __name__ == "__main__":
asyncio.run(check_schema())

View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""
Cleanup script to delete test vehicles from the database.
Deletes vehicles that belong to the test user (tester_pro@profibot.hu)
OR have status 'draft' OR have license plate starting with "TEST".
Handles foreign key constraints by deleting child records first.
"""
import asyncio
import sys
import os
# Add the backend directory to the path so we can import app modules
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import text, delete, select, and_, or_
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from app.core.config import settings
async def cleanup_test_vehicles():
"""Delete test vehicles and related records."""
# Create async engine
engine = create_async_engine(settings.DATABASE_URL, echo=False)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
deleted_counts = {
'asset_events': 0,
'asset_assignments': 0,
'asset_telemetry': 0,
'asset_financials': 0,
'asset_costs': 0,
'asset_inspections': 0,
'asset_reviews': 0,
'vehicle_expenses': 0,
'vehicle_logbook': 0,
'vehicle_ownership_history': 0,
'vehicle_transfer_requests': 0,
'service_requests': 0,
'vehicle_assets': 0
}
async with async_session() as session:
try:
# First, find all vehicle asset IDs that match our criteria
# We need to join with identity.users and identity.persons to find test user's vehicles
query = text("""
SELECT va.id
FROM vehicle.assets va
LEFT JOIN identity.persons p ON va.owner_person_id = p.id
LEFT JOIN identity.users u ON p.id = u.person_id
WHERE u.email = 'tester_pro@profibot.hu'
OR va.status = 'draft'
OR va.license_plate LIKE 'TEST%'
""")
result = await session.execute(query)
asset_ids = [row[0] for row in result.fetchall()]
if not asset_ids:
print("No test vehicles found matching criteria.")
return deleted_counts
print(f"Found {len(asset_ids)} test vehicles to delete.")
# Delete from child tables first (order matters due to foreign keys)
# List of tables with foreign keys to vehicle.assets.id and their column names
tables_to_clean = [
('fleet.asset_assignments', 'asset_id', 'asset_assignments'),
('vehicle.asset_costs', 'asset_id', 'asset_costs'),
('vehicle.asset_events', 'asset_id', 'asset_events'),
('vehicle.asset_financials', 'asset_id', 'asset_financials'),
('vehicle.asset_inspections', 'asset_id', 'asset_inspections'),
('vehicle.asset_reviews', 'asset_id', 'asset_reviews'),
('vehicle.asset_telemetry', 'asset_id', 'asset_telemetry'),
('marketplace.service_requests', 'asset_id', 'service_requests'),
('vehicle.vehicle_expenses', 'vehicle_id', 'vehicle_expenses'),
('vehicle.vehicle_logbook', 'asset_id', 'vehicle_logbook'),
('vehicle.vehicle_ownership_history', 'asset_id', 'vehicle_ownership_history'),
('vehicle.vehicle_transfer_requests', 'asset_id', 'vehicle_transfer_requests'),
]
for table, column, key in tables_to_clean:
try:
delete_query = text(f"DELETE FROM {table} WHERE {column} = ANY(:asset_ids)")
result = await session.execute(delete_query, {"asset_ids": asset_ids})
deleted_counts[key] = result.rowcount
print(f"Deleted {result.rowcount} records from {table}")
except Exception as e:
print(f"Warning: Could not delete from {table}: {e}")
# Continue with other tables
# Finally, delete from vehicle.assets
delete_assets = text("DELETE FROM vehicle.assets WHERE id = ANY(:asset_ids)")
result = await session.execute(delete_assets, {"asset_ids": asset_ids})
deleted_counts['vehicle_assets'] = result.rowcount
print(f"Deleted {result.rowcount} records from vehicle.assets")
# Commit the transaction
await session.commit()
print("Cleanup completed successfully.")
except Exception as e:
await session.rollback()
print(f"Error during cleanup: {e}")
raise
await engine.dispose()
return deleted_counts
if __name__ == "__main__":
print("Starting cleanup of test vehicles...")
counts = asyncio.run(cleanup_test_vehicles())
print("\nCleanup summary:")
for table, count in counts.items():
print(f" {table}: {count}")
total = sum(counts.values())
print(f"Total records deleted: {total}")

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python3
import asyncio
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from app.core.config import settings
async def find_test_user():
engine = create_async_engine(settings.DATABASE_URL, echo=False)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with async_session() as session:
# Find user with email tester_pro@profibot.hu
query = text("""
SELECT u.user_id, u.email, p.person_id, p.first_name, p.last_name
FROM identity.users u
JOIN identity.persons p ON u.person_id = p.person_id
WHERE u.email = 'tester_pro@profibot.hu'
""")
result = await session.execute(query)
user = result.fetchone()
if user:
print(f"Test user found: user_id={user[0]}, email={user[1]}, person_id={user[2]}, name={user[3]} {user[4]}")
else:
print("Test user not found")
# Count vehicles owned by this person
if user:
query2 = text("""
SELECT COUNT(*) FROM vehicle.assets
WHERE owner_person_id = :person_id
""")
result2 = await session.execute(query2, {"person_id": user[2]})
count = result2.scalar()
print(f"Vehicles owned by this person: {count}")
# Count draft vehicles
query3 = text("SELECT COUNT(*) FROM vehicle.assets WHERE status = 'draft'")
result3 = await session.execute(query3)
draft_count = result3.scalar()
print(f"Draft vehicles: {draft_count}")
# Count vehicles with license plate starting with TEST
query4 = text("SELECT COUNT(*) FROM vehicle.assets WHERE license_plate LIKE 'TEST%'")
result4 = await session.execute(query4)
test_plate_count = result4.scalar()
print(f"Vehicles with TEST license plate: {test_plate_count}")
await engine.dispose()
if __name__ == "__main__":
asyncio.run(find_test_user())

View File

@@ -0,0 +1,63 @@
#!/usr/bin/env python3
import asyncio
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from app.core.config import settings
async def list_related_tables():
engine = create_async_engine(settings.DATABASE_URL, echo=False)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with async_session() as session:
# Find foreign key constraints referencing vehicle.assets
query = text("""
SELECT
tc.table_schema,
tc.table_name,
kcu.column_name,
ccu.table_schema AS foreign_table_schema,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name,
rc.delete_rule
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
JOIN information_schema.referential_constraints AS rc
ON rc.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND ccu.table_schema = 'vehicle'
AND ccu.table_name = 'assets'
AND ccu.column_name = 'id'
ORDER BY tc.table_name;
""")
result = await session.execute(query)
fks = result.fetchall()
print("Foreign keys referencing vehicle.assets (id):")
for fk in fks:
print(f" {fk[0]}.{fk[1]}.{fk[2]} -> {fk[3]}.{fk[4]}.{fk[5]} (ON DELETE {fk[6]})")
# Also list tables in vehicle schema
query2 = text("""
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'vehicle'
ORDER BY table_name;
""")
result2 = await session.execute(query2)
tables = result2.fetchall()
print("\nTables in vehicle schema:")
for tbl in tables:
print(f" {tbl[0]}")
await engine.dispose()
if __name__ == "__main__":
asyncio.run(list_related_tables())

View File

@@ -0,0 +1,305 @@
#!/usr/bin/env python3
"""
Ground Zero Database Purge Script
Deletes ALL test users (e.g., tester_pro@profibot.hu, noreply@...),
and cascade deletes all their Organizations, Branches, and Assets.
Outputs the exact number of deleted rows for each table.
"""
import asyncio
import sys
from sqlalchemy import text, delete, select
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
# Import settings
sys.path.insert(0, '/app')
from app.core.config import settings
async def purge_test_data():
"""Main purge function"""
# Create async engine
engine = create_async_engine(
str(settings.SQLALCHEMY_DATABASE_URI),
echo=False,
pool_size=5,
max_overflow=10,
)
async_session = sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
deleted_counts = {}
async with async_session() as session:
try:
# Start transaction
await session.begin()
print("🔍 Starting Ground Zero database purge...")
print("=" * 60)
# 1. First, identify test users to delete
test_email_patterns = [
'%test%',
'%noreply%',
'%profibot%',
'tester_pro@profibot.hu',
'test@example.com',
'%example.com'
]
# Build WHERE clause for test users
where_conditions = []
for pattern in test_email_patterns:
where_conditions.append(f"email ILIKE '{pattern}'")
where_clause = " OR ".join(where_conditions)
# 2. Get user IDs to delete for cascade operations
query = text(f"""
SELECT id, email
FROM identity.users
WHERE {where_clause}
AND is_deleted = false
""")
result = await session.execute(query)
test_users = result.fetchall()
if not test_users:
print("✅ No test users found to delete.")
await session.commit()
return deleted_counts
user_ids = [str(user[0]) for user in test_users]
user_emails = [user[1] for user in test_users]
print(f"📋 Found {len(test_users)} test users to delete:")
for email in user_emails[:10]: # Show first 10
print(f" - {email}")
if len(test_users) > 10:
print(f" ... and {len(test_users) - 10} more")
user_ids_str = ",".join(user_ids)
# 3. Delete in reverse cascade order to respect foreign keys
# First, get person IDs for these users
person_query = text(f"""
SELECT DISTINCT person_id
FROM identity.users
WHERE id IN ({user_ids_str})
AND person_id IS NOT NULL
""")
person_result = await session.execute(person_query)
person_ids = [str(row[0]) for row in person_result.fetchall()]
person_ids_str = ",".join(person_ids) if person_ids else "NULL"
# Delete from tables that reference users/persons
# a) Delete from gamification tables
gamification_tables = [
("gamification.user_stats", "user_id"),
("gamification.user_badges", "user_id"),
("gamification.points_ledger", "user_id"),
("gamification.user_contributions", "user_id"),
]
for table, column in gamification_tables:
delete_query = text(f"""
DELETE FROM {table}
WHERE {column} IN ({user_ids_str})
RETURNING *
""")
result = await session.execute(delete_query)
deleted = result.rowcount
deleted_counts[table] = deleted
if deleted > 0:
print(f" 🗑️ Deleted {deleted} rows from {table}")
# b) Delete from social tables - with error handling for missing tables
social_tables = [
("identity.social_accounts", "user_id"),
("marketplace.service_requests", "user_id"),
]
# Try to delete from service_reviews if it exists
social_tables_to_try = social_tables + [("identity.service_reviews", "user_id")]
for table, column in social_tables_to_try:
try:
delete_query = text(f"""
DELETE FROM {table}
WHERE {column} IN ({user_ids_str})
RETURNING *
""")
result = await session.execute(delete_query)
deleted = result.rowcount
deleted_counts[table] = deleted
if deleted > 0:
print(f" 🗑️ Deleted {deleted} rows from {table}")
except Exception as e:
if "does not exist" in str(e) or "UndefinedTableError" in str(e):
print(f" ⚠️ Table {table} doesn't exist, skipping...")
continue
else:
raise
# c) Delete from organization-related tables
# First get organization IDs owned by these users
org_query = text(f"""
SELECT id FROM marketplace.organizations
WHERE owner_id IN ({user_ids_str})
""")
org_result = await session.execute(org_query)
org_ids = [str(row[0]) for row in org_result.fetchall()]
org_ids_str = ",".join(org_ids) if org_ids else "NULL"
if org_ids:
# Delete branches for these organizations
branch_query = text(f"""
DELETE FROM marketplace.branches
WHERE organization_id IN ({org_ids_str})
RETURNING *
""")
result = await session.execute(branch_query)
deleted = result.rowcount
deleted_counts["marketplace.branches"] = deleted
if deleted > 0:
print(f" 🗑️ Deleted {deleted} branches")
# Delete organization members
org_member_query = text(f"""
DELETE FROM marketplace.organization_members
WHERE organization_id IN ({org_ids_str})
RETURNING *
""")
result = await session.execute(org_member_query)
deleted = result.rowcount
deleted_counts["marketplace.organization_members"] = deleted
if deleted > 0:
print(f" 🗑️ Deleted {deleted} organization members")
# Delete organizations
org_delete_query = text(f"""
DELETE FROM marketplace.organizations
WHERE id IN ({org_ids_str})
RETURNING *
""")
result = await session.execute(org_delete_query)
deleted = result.rowcount
deleted_counts["marketplace.organizations"] = deleted
if deleted > 0:
print(f" 🗑️ Deleted {deleted} organizations")
# d) Delete from asset-related tables
# Get asset IDs owned by these users
asset_query = text(f"""
SELECT id FROM data.assets
WHERE owner_id IN ({user_ids_str})
""")
asset_result = await session.execute(asset_query)
asset_ids = [str(row[0]) for row in asset_result.fetchall()]
asset_ids_str = ",".join(asset_ids) if asset_ids else "NULL"
if asset_ids:
# Delete asset costs
asset_cost_query = text(f"""
DELETE FROM data.asset_costs
WHERE asset_id IN ({asset_ids_str})
RETURNING *
""")
result = await session.execute(asset_cost_query)
deleted = result.rowcount
deleted_counts["data.asset_costs"] = deleted
if deleted > 0:
print(f" 🗑️ Deleted {deleted} asset costs")
# Delete asset events
asset_event_query = text(f"""
DELETE FROM data.asset_events
WHERE asset_id IN ({asset_ids_str})
RETURNING *
""")
result = await session.execute(asset_event_query)
deleted = result.rowcount
deleted_counts["data.asset_events"] = deleted
if deleted > 0:
print(f" 🗑️ Deleted {deleted} asset events")
# Delete assets
asset_delete_query = text(f"""
DELETE FROM data.assets
WHERE id IN ({asset_ids_str})
RETURNING *
""")
result = await session.execute(asset_delete_query)
deleted = result.rowcount
deleted_counts["data.assets"] = deleted
if deleted > 0:
print(f" 🗑️ Deleted {deleted} assets")
# e) Delete wallets
wallet_query = text(f"""
DELETE FROM identity.wallets
WHERE user_id IN ({user_ids_str})
RETURNING *
""")
result = await session.execute(wallet_query)
deleted = result.rowcount
deleted_counts["identity.wallets"] = deleted
if deleted > 0:
print(f" 🗑️ Deleted {deleted} wallets")
# f) Finally delete users
user_delete_query = text(f"""
DELETE FROM identity.users
WHERE id IN ({user_ids_str})
RETURNING *
""")
result = await session.execute(user_delete_query)
deleted = result.rowcount
deleted_counts["identity.users"] = deleted
print(f" 🗑️ Deleted {deleted} users")
# g) Delete persons (if they have no other users)
if person_ids:
person_delete_query = text(f"""
DELETE FROM identity.persons
WHERE id IN ({person_ids_str})
AND NOT EXISTS (
SELECT 1 FROM identity.users
WHERE person_id = persons.id
)
RETURNING *
""")
result = await session.execute(person_delete_query)
deleted = result.rowcount
deleted_counts["identity.persons"] = deleted
if deleted > 0:
print(f" 🗑️ Deleted {deleted} persons")
# Commit transaction
await session.commit()
print("=" * 60)
print("✅ Ground Zero purge completed successfully!")
print("\n📊 Deletion Summary:")
for table, count in deleted_counts.items():
print(f" {table}: {count} rows")
total_deleted = sum(deleted_counts.values())
print(f"\n📈 Total rows deleted: {total_deleted}")
return deleted_counts
except Exception as e:
await session.rollback()
print(f"❌ Error during purge: {e}")
raise
if __name__ == "__main__":
asyncio.run(purge_test_data())

View File

@@ -0,0 +1,91 @@
-- Ground Zero Database Purge Script
-- Deletes ALL test users and related data
BEGIN;
-- 1. First, identify and count test users
SELECT 'Test users to delete:' as info;
SELECT id, email
FROM identity.users
WHERE (
email ILIKE '%test%' OR
email ILIKE '%noreply%' OR
email ILIKE '%profibot%' OR
email ILIKE '%example.com' OR
email ILIKE '%@gmail.com'
) AND is_deleted = false
ORDER BY id;
-- 2. Delete from gamification tables (if they exist)
DO $$
BEGIN
-- Check if table exists before deleting
IF EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'gamification' AND table_name = 'user_stats') THEN
DELETE FROM gamification.user_stats
WHERE user_id IN (
SELECT id FROM identity.users
WHERE (
email ILIKE '%test%' OR
email ILIKE '%noreply%' OR
email ILIKE '%profibot%' OR
email ILIKE '%example.com' OR
email ILIKE '%@gmail.com'
) AND is_deleted = false
);
RAISE NOTICE 'Deleted from gamification.user_stats';
END IF;
IF EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'gamification' AND table_name = 'points_ledger') THEN
DELETE FROM gamification.points_ledger
WHERE user_id IN (
SELECT id FROM identity.users
WHERE (
email ILIKE '%test%' OR
email ILIKE '%noreply%' OR
email ILIKE '%profibot%' OR
email ILIKE '%example.com' OR
email ILIKE '%@gmail.com'
) AND is_deleted = false
);
RAISE NOTICE 'Deleted from gamification.points_ledger';
END IF;
END $$;
-- 3. Delete wallets (must be done before users due to FK constraint)
DELETE FROM identity.wallets
WHERE user_id IN (
SELECT id FROM identity.users
WHERE (
email ILIKE '%test%' OR
email ILIKE '%noreply%' OR
email ILIKE '%profibot%' OR
email ILIKE '%example.com' OR
email ILIKE '%@gmail.com'
) AND is_deleted = false
);
-- 4. Finally delete the test users
DELETE FROM identity.users
WHERE (
email ILIKE '%test%' OR
email ILIKE '%noreply%' OR
email ILIKE '%profibot%' OR
email ILIKE '%example.com' OR
email ILIKE '%@gmail.com'
) AND is_deleted = false;
-- 5. Delete orphaned persons
DELETE FROM identity.persons
WHERE NOT EXISTS (
SELECT 1 FROM identity.users
WHERE users.person_id = persons.id
);
-- 6. Show results
SELECT 'Purge completed. Summary:' as info;
SELECT
(SELECT COUNT(*) FROM identity.users WHERE email ILIKE '%test%' AND is_deleted = false) as remaining_test_users,
(SELECT COUNT(*) FROM identity.users WHERE email ILIKE '%noreply%' AND is_deleted = false) as remaining_noreply_users,
(SELECT COUNT(*) FROM identity.users WHERE email ILIKE '%profibot%' AND is_deleted = false) as remaining_profibot_users;
COMMIT;

View File

@@ -0,0 +1,325 @@
#!/usr/bin/env python3
"""
Final Ground Zero Database Purge Script
Deletes ALL test users in correct cascade order.
"""
import asyncio
import sys
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
# Import settings
sys.path.insert(0, '/app')
from app.core.config import settings
async def purge_test_data_final():
"""Final purge function with proper cascade order"""
# Create async engine
engine = create_async_engine(
str(settings.SQLALCHEMY_DATABASE_URI),
echo=False,
pool_size=5,
max_overflow=10,
)
async_session = sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
async with async_session() as session:
try:
print("🔍 Starting Ground Zero database purge (final)...")
print("=" * 60)
# Start transaction
await session.begin()
# 1. First, identify test users to delete
test_email_patterns = [
'%test%',
'%noreply%',
'%profibot%',
'tester_pro@profibot.hu',
'test@example.com',
'%example.com',
'%@gmail.com'
]
# Build WHERE clause for test users
where_conditions = []
for pattern in test_email_patterns:
where_conditions.append(f"email ILIKE '{pattern}'")
where_clause = " OR ".join(where_conditions)
# 2. Get user IDs to delete
get_users_query = text(f"""
SELECT id, email
FROM identity.users
WHERE {where_clause}
AND is_deleted = false
ORDER BY id
""")
result = await session.execute(get_users_query)
test_users = result.fetchall()
if not test_users:
print("✅ No test users found to delete.")
await session.commit()
return
user_ids = [str(user[0]) for user in test_users]
user_emails = [user[1] for user in test_users]
user_ids_str = ",".join(user_ids)
print(f"📋 Found {len(test_users)} test users to delete:")
for email in user_emails[:10]:
print(f" - {email}")
if len(test_users) > 10:
print(f" ... and {len(test_users) - 10} more")
# 3. Delete in correct cascade order
# a) First delete from tables that reference users but have no further dependencies
print("\n🗑️ Deleting related records in cascade order...")
# Tables to delete (in order of dependencies)
tables_to_clean = [
# Gamification tables
("gamification.user_stats", "user_id"),
("gamification.user_badges", "user_id"),
("gamification.points_ledger", "user_id"),
("gamification.user_contributions", "user_id"),
# Social tables (skip if they don't exist)
("identity.social_accounts", "user_id"),
("marketplace.service_requests", "user_id"),
# Organization members first (before organizations)
("marketplace.organization_members", "user_id"),
# Payment intents
("marketplace.payment_intents", "payer_id"),
("marketplace.payment_intents", "beneficiary_id"),
# Withdrawal requests
("marketplace.withdrawal_requests", "user_id"),
# Service reviews (if table exists)
("identity.service_reviews", "user_id"),
# Vehicle ratings
("vehicle.vehicle_user_ratings", "user_id"),
# Vehicle ownership
("vehicle.vehicle_ownership", "user_id"),
]
deleted_counts = {}
for table, column in tables_to_clean:
try:
# Check if table exists by trying to count
check_query = text(f"""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = '{table.split('.')[0]}'
AND table_name = '{table.split('.')[1]}'
)
""")
exists_result = await session.execute(check_query)
exists = exists_result.scalar()
if not exists:
print(f" ⚠️ Table {table} doesn't exist, skipping...")
continue
delete_query = text(f"""
DELETE FROM {table}
WHERE {column} IN ({user_ids_str})
""")
result = await session.execute(delete_query)
deleted = result.rowcount
deleted_counts[f"{table}.{column}"] = deleted
if deleted > 0:
print(f" ✅ Deleted {deleted} rows from {table}")
except Exception as e:
if "does not exist" in str(e) or "UndefinedTableError" in str(e):
print(f" ⚠️ Table {table} doesn't exist, skipping...")
continue
else:
print(f" ⚠️ Error deleting from {table}: {e}")
continue
# b) Delete wallets (references users)
try:
wallet_query = text(f"""
DELETE FROM identity.wallets
WHERE user_id IN ({user_ids_str})
""")
result = await session.execute(wallet_query)
deleted = result.rowcount
deleted_counts["identity.wallets"] = deleted
print(f" ✅ Deleted {deleted} wallets")
except Exception as e:
print(f" ⚠️ Error deleting wallets: {e}")
# c) Delete organizations owned by these users
try:
# Get organization IDs
org_query = text(f"""
SELECT id FROM marketplace.organizations
WHERE owner_id IN ({user_ids_str})
""")
org_result = await session.execute(org_query)
org_ids = [str(row[0]) for row in org_result.fetchall()]
if org_ids:
org_ids_str = ",".join(org_ids)
# Delete branches for these organizations
branch_query = text(f"""
DELETE FROM marketplace.branches
WHERE organization_id IN ({org_ids_str})
""")
result = await session.execute(branch_query)
deleted = result.rowcount
deleted_counts["marketplace.branches"] = deleted
print(f" ✅ Deleted {deleted} branches")
# Delete organization members for these organizations
org_member_query = text(f"""
DELETE FROM marketplace.organization_members
WHERE organization_id IN ({org_ids_str})
""")
result = await session.execute(org_member_query)
deleted = result.rowcount
deleted_counts["marketplace.organization_members_org"] = deleted
print(f" ✅ Deleted {deleted} organization members (by org)")
# Delete organizations
org_delete_query = text(f"""
DELETE FROM marketplace.organizations
WHERE id IN ({org_ids_str})
""")
result = await session.execute(org_delete_query)
deleted = result.rowcount
deleted_counts["marketplace.organizations"] = deleted
print(f" ✅ Deleted {deleted} organizations")
except Exception as e:
print(f" ⚠️ Error deleting organizations: {e}")
# d) Delete assets owned by these users
try:
# Get asset IDs
asset_query = text(f"""
SELECT id FROM data.assets
WHERE owner_id IN ({user_ids_str})
""")
asset_result = await session.execute(asset_query)
asset_ids = [str(row[0]) for row in asset_result.fetchall()]
if asset_ids:
asset_ids_str = ",".join(asset_ids)
# Delete asset costs
asset_cost_query = text(f"""
DELETE FROM data.asset_costs
WHERE asset_id IN ({asset_ids_str})
""")
result = await session.execute(asset_cost_query)
deleted = result.rowcount
deleted_counts["data.asset_costs"] = deleted
print(f" ✅ Deleted {deleted} asset costs")
# Delete asset events
asset_event_query = text(f"""
DELETE FROM data.asset_events
WHERE asset_id IN ({asset_ids_str})
""")
result = await session.execute(asset_event_query)
deleted = result.rowcount
deleted_counts["data.asset_events"] = deleted
print(f" ✅ Deleted {deleted} asset events")
# Delete assets
asset_delete_query = text(f"""
DELETE FROM data.assets
WHERE id IN ({asset_ids_str})
""")
result = await session.execute(asset_delete_query)
deleted = result.rowcount
deleted_counts["data.assets"] = deleted
print(f" ✅ Deleted {deleted} assets")
except Exception as e:
print(f" ⚠️ Error deleting assets: {e}")
# e) Finally delete users
print("\n🗑️ Deleting test users...")
user_delete_query = text(f"""
DELETE FROM identity.users
WHERE id IN ({user_ids_str})
AND is_deleted = false
""")
result = await session.execute(user_delete_query)
deleted_users = result.rowcount
deleted_counts["identity.users"] = deleted_users
print(f" ✅ Deleted {deleted_users} users")
# f) Delete orphaned persons
person_delete_query = text("""
DELETE FROM identity.persons
WHERE NOT EXISTS (
SELECT 1 FROM identity.users
WHERE users.person_id = persons.id
)
""")
result = await session.execute(person_delete_query)
deleted_persons = result.rowcount
deleted_counts["identity.persons"] = deleted_persons
if deleted_persons > 0:
print(f" ✅ Deleted {deleted_persons} orphaned persons")
# Commit transaction
await session.commit()
print("=" * 60)
print("✅ Ground Zero purge completed successfully!")
print("\n📊 Deletion Summary:")
total_deleted = 0
for key, count in deleted_counts.items():
print(f" {key}: {count} rows")
total_deleted += count
print(f"\n📈 Total rows deleted: {total_deleted}")
# Verify cleanup
print("\n🔍 Verifying cleanup...")
verify_query = text(f"""
SELECT COUNT(*)
FROM identity.users
WHERE {where_clause}
AND is_deleted = false
""")
result = await session.execute(verify_query)
remaining = result.scalar()
if remaining == 0:
print(f"✅ Verification: All test users have been removed.")
else:
print(f"⚠️ Verification: {remaining} test users still remain.")
return deleted_counts
except Exception as e:
await session.rollback()
print(f"❌ Error during purge: {e}")
raise
if __name__ == "__main__":
asyncio.run(purge_test_data_final())

View File

@@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""
Simplified Ground Zero Database Purge Script
Deletes ALL test users and lets database cascade constraints handle the rest.
"""
import asyncio
import sys
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
# Import settings
sys.path.insert(0, '/app')
from app.core.config import settings
async def purge_test_data_simple():
"""Simple purge function using cascade delete"""
# Create async engine
engine = create_async_engine(
str(settings.SQLALCHEMY_DATABASE_URI),
echo=False,
pool_size=5,
max_overflow=10,
)
async_session = sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
async with async_session() as session:
try:
print("🔍 Starting Ground Zero database purge (simplified)...")
print("=" * 60)
# 1. First, identify test users to delete
test_email_patterns = [
'%test%',
'%noreply%',
'%profibot%',
'tester_pro@profibot.hu',
'test@example.com',
'%example.com',
'%@gmail.com' # Also delete gmail test users
]
# Build WHERE clause for test users
where_conditions = []
for pattern in test_email_patterns:
where_conditions.append(f"email ILIKE '{pattern}'")
where_clause = " OR ".join(where_conditions)
# 2. Get count of test users
count_query = text(f"""
SELECT COUNT(*)
FROM identity.users
WHERE {where_clause}
AND is_deleted = false
""")
result = await session.execute(count_query)
test_user_count = result.scalar()
if test_user_count == 0:
print("✅ No test users found to delete.")
return
# 3. Get list of test users
list_query = text(f"""
SELECT id, email
FROM identity.users
WHERE {where_clause}
AND is_deleted = false
LIMIT 20
""")
result = await session.execute(list_query)
test_users = result.fetchall()
print(f"📋 Found {test_user_count} test users to delete:")
for user in test_users:
print(f" - {user[1]} (ID: {user[0]})")
if test_user_count > 20:
print(f" ... and {test_user_count - 20} more")
# 4. Delete users (cascade will handle related records)
delete_query = text(f"""
DELETE FROM identity.users
WHERE {where_clause}
AND is_deleted = false
RETURNING id, email
""")
print("\n🗑️ Deleting test users (cascade delete will handle related records)...")
result = await session.execute(delete_query)
deleted_users = result.fetchall()
# 5. Also delete any orphaned persons
cleanup_persons_query = text("""
DELETE FROM identity.persons
WHERE NOT EXISTS (
SELECT 1 FROM identity.users
WHERE users.person_id = persons.id
)
RETURNING id
""")
result = await session.execute(cleanup_persons_query)
orphaned_persons = result.rowcount
# Commit transaction
await session.commit()
print("=" * 60)
print("✅ Ground Zero purge completed successfully!")
print(f"\n📊 Deletion Summary:")
print(f" Test users deleted: {len(deleted_users)}")
print(f" Orphaned persons cleaned up: {orphaned_persons}")
# 6. Verify cleanup
verify_query = text(f"""
SELECT COUNT(*)
FROM identity.users
WHERE {where_clause}
AND is_deleted = false
""")
result = await session.execute(verify_query)
remaining = result.scalar()
if remaining == 0:
print(f"\n✅ Verification: All test users have been removed.")
else:
print(f"\n⚠️ Verification: {remaining} test users still remain.")
return len(deleted_users)
except Exception as e:
await session.rollback()
print(f"❌ Error during purge: {e}")
raise
if __name__ == "__main__":
asyncio.run(purge_test_data_simple())

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env python3
import asyncio
import aiohttp
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.core.config import settings
async def test_makes_filter():
base_url = "http://localhost:8000"
async with aiohttp.ClientSession() as session:
# Test without vehicle_class
async with session.get(f"{base_url}/api/v1/catalog/makes") as resp:
print(f"GET /api/v1/catalog/makes status: {resp.status}")
if resp.status == 200:
data = await resp.json()
print(f" Total makes: {len(data)}")
print(f" First few: {data[:5]}")
# Test with vehicle_class=motorcycle
async with session.get(f"{base_url}/api/v1/catalog/makes?vehicle_class=motorcycle") as resp:
print(f"\nGET /api/v1/catalog/makes?vehicle_class=motorcycle status: {resp.status}")
if resp.status == 200:
data = await resp.json()
print(f" Makes for motorcycle: {len(data)}")
print(f" First few: {data[:5]}")
# Test with vehicle_class=passenger_car
async with session.get(f"{base_url}/api/v1/catalog/makes?vehicle_class=passenger_car") as resp:
print(f"\nGET /api/v1/catalog/makes?vehicle_class=passenger_car status: {resp.status}")
if resp.status == 200:
data = await resp.json()
print(f" Makes for passenger_car: {len(data)}")
print(f" First few: {data[:5]}")
if __name__ == "__main__":
asyncio.run(test_makes_filter())

View File

@@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""
Authenticated E2E test for vehicle registration endpoint.
Reads credentials from backend/tests/test_credentials.json,
authenticates via /api/v1/auth/login, obtains JWT token,
and tests POST /api/v1/assets/vehicles endpoint.
"""
import json
import sys
import os
import asyncio
import aiohttp
import logging
from typing import Dict, Any
# Add parent directory to path to import app modules if needed
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
CREDENTIALS_PATH = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"tests",
"test_credentials.json"
)
async def load_credentials() -> Dict[str, Any]:
"""Load test credentials from JSON file."""
try:
with open(CREDENTIALS_PATH, 'r') as f:
creds = json.load(f)
logger.info(f"Loaded credentials for {creds.get('email')}")
return creds
except FileNotFoundError:
logger.error(f"Credentials file not found at {CREDENTIALS_PATH}")
sys.exit(1)
except json.JSONDecodeError as e:
logger.error(f"Invalid JSON in credentials file: {e}")
sys.exit(1)
async def authenticate(session: aiohttp.ClientSession, base_url: str, email: str, password: str) -> str:
"""Authenticate and return JWT token."""
login_url = f"{base_url}/api/v1/auth/login"
# Use form data as required by OAuth2PasswordRequestForm
form_data = aiohttp.FormData()
form_data.add_field('username', email)
form_data.add_field('password', password)
try:
async with session.post(login_url, data=form_data) as resp:
if resp.status != 200:
text = await resp.text()
logger.error(f"Authentication failed: {resp.status} - {text}")
raise ValueError(f"Authentication failed: {resp.status}")
data = await resp.json()
token = data.get("access_token")
if not token:
logger.error(f"No access_token in response: {data}")
raise ValueError("No access_token in response")
logger.info("Authentication successful")
return token
except aiohttp.ClientError as e:
logger.error(f"Network error during authentication: {e}")
raise
async def test_vehicle_creation(session: aiohttp.ClientSession, base_url: str, token: str):
"""Test POST /api/v1/assets/vehicles endpoint."""
url = f"{base_url}/api/v1/assets/vehicles"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# Realistic payload with valid catalog_id and required fields
payload = {
"vin": "TESTVIN1234567890",
"license_plate": "TEST-001",
"catalog_id": 5, # Valid catalog_id from database
"brand": "BMW",
"model": "X5",
"vehicle_class": "car",
"fuel_type": "petrol",
"engine_capacity": 3000,
"power_kw": 200,
"transmission_type": "automatic",
"drive_type": "awd"
}
logger.info(f"Testing vehicle creation with payload: {payload}")
try:
async with session.post(url, json=payload, headers=headers) as resp:
status = resp.status
text = await resp.text()
logger.info(f"Response status: {status}")
logger.info(f"Response body: {text}")
if status == 201:
logger.info("✅ Vehicle creation test PASSED")
return True
elif status == 404:
logger.error("❌ Endpoint not found (404). Check API route.")
return False
elif status == 400:
logger.warning("⚠️ Bad request (400). Might be due to missing catalog_id or validation.")
# Try to parse error details
try:
error_data = json.loads(text)
logger.warning(f"Error details: {error_data}")
except:
pass
return False
elif status == 500:
logger.error("❌ Internal server error (500). Check server logs.")
# Try to parse error details
try:
error_data = json.loads(text)
logger.error(f"Error details: {error_data}")
except:
pass
return False
else:
logger.error(f"❌ Unexpected status: {status}")
return False
except aiohttp.ClientError as e:
logger.error(f"Network error during vehicle creation: {e}")
return False
async def main():
"""Main test execution."""
logger.info("Starting authenticated E2E test for vehicle registration")
# Load credentials
creds = await load_credentials()
base_url = creds.get("base_url", "http://localhost:8000")
email = creds["email"]
password = creds["password"]
# Create aiohttp session
async with aiohttp.ClientSession() as session:
# Authenticate
try:
token = await authenticate(session, base_url, email, password)
except Exception as e:
logger.error(f"Authentication failed: {e}")
sys.exit(1)
# Test vehicle creation
success = await test_vehicle_creation(session, base_url, token)
if success:
logger.info("🎉 All tests passed!")
sys.exit(0)
else:
logger.error("💥 Test failed!")
sys.exit(1)
if __name__ == "__main__":
asyncio.run(main())