teljes backend_mentés

This commit is contained in:
Roo
2026-03-22 18:59:27 +00:00
parent 5d44339f21
commit 5d96b00f81
34 changed files with 2575 additions and 977 deletions

View File

@@ -0,0 +1,236 @@
#!/usr/bin/env python3
"""
Audit Scanner for Codebase Analysis (#42)
This script performs a comprehensive audit of the Python codebase:
1. Recursively scans the backend/app directory for .py files
2. Excludes __init__.py files and alembic/versions directory
3. Groups files by directory structure (api, services, models, etc.)
4. Extracts docstrings and class/function names from each file
5. Generates a Markdown audit ledger with checkboxes for tracking
"""
import os
import re
import ast
from pathlib import Path
from typing import Dict, List, Tuple, Set
import datetime
# Project root (relative to script location in container)
PROJECT_ROOT = Path("/app")
BACKEND_DIR = PROJECT_ROOT / "app" # /app/app is the backend root in container
OUTPUT_FILE = Path("/app/.roo/audit_ledger_94.md")
# Directories to exclude
EXCLUDE_DIRS = {"__pycache__", ".git", "alembic/versions", "migrations"}
EXCLUDE_FILES = {"__init__.py"}
def extract_python_info(file_path: Path) -> Tuple[str, List[str], List[str]]:
"""
Extract docstring and class/function names from a Python file.
Returns: (docstring, class_names, function_names)
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Try to parse with AST
try:
tree = ast.parse(content)
# Extract module docstring
docstring = ast.get_docstring(tree) or ""
# Extract class and function names
class_names = []
function_names = []
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
class_names.append(node.name)
elif isinstance(node, ast.FunctionDef):
# Only top-level functions (not methods)
if not isinstance(node.parent, ast.ClassDef):
function_names.append(node.name)
return docstring, class_names, function_names
except (SyntaxError, ValueError):
# If AST parsing fails, use simple regex extraction
docstring_match = re.search(r'"""(.*?)"""', content, re.DOTALL)
docstring = docstring_match.group(1).strip() if docstring_match else ""
# Simple regex for class and function definitions
class_matches = re.findall(r'^class\s+(\w+)', content, re.MULTILINE)
func_matches = re.findall(r'^def\s+(\w+)', content, re.MULTILINE)
return docstring, class_matches, func_matches
except Exception as e:
return f"Error reading file: {e}", [], []
def get_file_summary(docstring: str, class_names: List[str], function_names: List[str]) -> str:
"""Create a summary string from extracted information."""
parts = []
if docstring:
# Take first line of docstring, max 100 chars
first_line = docstring.split('\n')[0].strip()
if len(first_line) > 100:
first_line = first_line[:97] + "..."
parts.append(f'"{first_line}"')
if class_names:
parts.append(f"Classes: {', '.join(class_names[:5])}")
if len(class_names) > 5:
parts[-1] += f" (+{len(class_names)-5} more)"
if function_names:
parts.append(f"Functions: {', '.join(function_names[:5])}")
if len(function_names) > 5:
parts[-1] += f" (+{len(function_names)-5} more)"
return " - ".join(parts) if parts else "No docstring or definitions found"
def scan_python_files(root_dir: Path) -> Dict[str, List[Tuple[Path, str]]]:
"""
Scan for Python files and group them by directory category.
Returns: {category: [(file_path, summary), ...]}
"""
categories = {}
for py_file in root_dir.rglob("*.py"):
# Skip excluded directories
if any(excluded in str(py_file) for excluded in EXCLUDE_DIRS):
continue
# Skip excluded files
if py_file.name in EXCLUDE_FILES:
continue
# Determine category based on directory structure
rel_path = py_file.relative_to(root_dir)
path_parts = list(rel_path.parts)
# Categorize based on first few directory levels
category = "Other"
if len(path_parts) >= 2:
if path_parts[0] == "api":
category = "API Endpoints"
elif path_parts[0] == "services":
category = "Services"
elif path_parts[0] == "models":
category = "Models"
elif path_parts[0] == "core":
category = "Core"
elif path_parts[0] == "workers":
category = "Workers"
elif path_parts[0] == "scripts":
category = "Scripts"
elif path_parts[0] == "tests" or path_parts[0] == "tests_internal" or path_parts[0] == "test_outside":
category = "Tests"
elif path_parts[0] == "crud":
category = "CRUD"
elif path_parts[0] == "schemas":
category = "Schemas"
elif path_parts[0] == "templates":
category = "Templates"
elif path_parts[0] == "static":
category = "Static"
# Extract file info
docstring, class_names, function_names = extract_python_info(py_file)
summary = get_file_summary(docstring, class_names, function_names)
# Add to category
if category not in categories:
categories[category] = []
categories[category].append((rel_path, summary))
return categories
def generate_markdown(categories: Dict[str, List[Tuple[Path, str]]]) -> str:
"""Generate Markdown content from categorized files."""
lines = []
# Header
lines.append("# Codebase Audit Ledger (#42)")
lines.append("")
lines.append(f"*Generated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*")
lines.append(f"*Total files scanned: {sum(len(files) for files in categories.values())}*")
lines.append("")
lines.append("## 📋 Audit Checklist")
lines.append("")
lines.append("Check each file after audit completion. Use this ledger to track progress.")
lines.append("")
# Sort categories for consistent output
sorted_categories = sorted(categories.items(), key=lambda x: x[0])
for category, files in sorted_categories:
lines.append(f"## {category} (`backend/app/{category.lower().replace(' ', '_')}/...`)")
lines.append("")
# Sort files alphabetically
files.sort(key=lambda x: str(x[0]))
for file_path, summary in files:
# Create checkbox and file entry
lines.append(f"- [ ] `{file_path}` - {summary}")
lines.append("")
# Add statistics
lines.append("## 📊 Statistics")
lines.append("")
lines.append("| Category | File Count |")
lines.append("|----------|------------|")
for category, files in sorted_categories:
lines.append(f"| {category} | {len(files)} |")
lines.append("")
lines.append("## 🎯 Next Steps")
lines.append("")
lines.append("1. **Review each file** for functionality and dependencies")
lines.append("2. **Document findings** in individual audit reports")
lines.append("3. **Identify gaps** in test coverage and documentation")
lines.append("4. **Prioritize refactoring** based on complexity and criticality")
lines.append("")
lines.append("*This ledger is automatically generated by `audit_scanner.py`*")
return "\n".join(lines)
def main():
print("🔍 Starting codebase audit scan...")
print(f"Scanning directory: {BACKEND_DIR}")
if not BACKEND_DIR.exists():
print(f"Error: Directory {BACKEND_DIR} does not exist!")
return 1
# Scan files
categories = scan_python_files(BACKEND_DIR)
# Generate markdown
markdown_content = generate_markdown(categories)
# Write output
OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
f.write(markdown_content)
total_files = sum(len(files) for files in categories.values())
print(f"✅ Scan complete! Found {total_files} Python files.")
print(f"📄 Report generated: {OUTPUT_FILE}")
# Print summary
print("\n📊 Category breakdown:")
for category, files in sorted(categories.items(), key=lambda x: x[0]):
print(f" {category}: {len(files)} files")
return 0
if __name__ == "__main__":
exit(main())

View File

@@ -0,0 +1,263 @@
#!/usr/bin/env python3
"""
Service Finder v2.0 Seed Script (Gamification 2.0 + Mock Service Profiles)
Modern, asynchronous SQLAlchemy 2.0 seed script for development and testing.
Includes: Superadmin user, Gamification levels (-3 to +10), 15 mock service profiles.
"""
import asyncio
import sys
from datetime import datetime
from typing import List, Tuple
from sqlalchemy import select, delete, text
from sqlalchemy.dialects.postgresql import insert
from geoalchemy2 import WKTElement
from app.db.session import SessionLocal
from app.models.identity.identity import User
from app.models.gamification.gamification import LevelConfig
from app.models.marketplace.service import ServiceProfile, ServiceStatus
from app.core.security import get_password_hash
# Environment safety check
ENVIRONMENT = "development" # Change to 'production' in production deployments
async def cleanup_existing_seeds(db):
"""Clean up previously seeded data (only in non-production environments)."""
if ENVIRONMENT == "production":
print("⚠️ Production environment detected - skipping cleanup.")
return
print("🧹 Cleaning up previously seeded data...")
# Delete mock service profiles (fingerprint starts with 'MOCK-')
result = await db.execute(
delete(ServiceProfile).where(ServiceProfile.fingerprint.like("MOCK-%"))
)
print(f" Deleted {result.rowcount} mock service profiles")
# Delete gamification levels we're about to insert (levels -3 to +10)
result = await db.execute(
delete(LevelConfig).where(LevelConfig.level_number.between(-3, 10))
)
print(f" Deleted {result.rowcount} gamification level configs")
# Delete superadmin user if exists (by email)
result = await db.execute(
delete(User).where(User.email == "admin@servicefinder.hu")
)
print(f" Deleted {result.rowcount} superadmin users")
await db.commit()
async def create_superadmin(db):
"""Create superadmin user with admin@servicefinder.hu / admin123 credentials."""
stmt = select(User).where(User.email == "admin@servicefinder.hu")
existing = (await db.execute(stmt)).scalar_one_or_none()
if existing:
print("✅ Superadmin user already exists")
return existing
hashed_password = get_password_hash("admin123")
admin = User(
email="admin@servicefinder.hu",
hashed_password=hashed_password,
full_name="System Administrator",
is_active=True,
is_superuser=True,
is_verified=True,
email_verified_at=datetime.utcnow(),
)
db.add(admin)
await db.commit()
await db.refresh(admin)
print("✅ Superadmin user created: admin@servicefinder.hu / admin123")
return admin
async def seed_gamification_levels(db):
"""Create Gamification 2.0 levels from -3 (penalty) to +10 (prestige)."""
levels = [
# Penalty levels (is_penalty = True)
(-3, 0, "Börtönviselt", True),
(-2, 10, "Büntetőszint 2", True),
(-1, 25, "Büntetőszint 1", True),
# Regular levels (is_penalty = False)
(0, 0, "Újonc", False),
(1, 50, "Felfedező", False),
(2, 150, "Gyakornok", False),
(3, 300, "Szakképzett", False),
(4, 500, "Szakértő", False),
(5, 750, "Mester", False),
(6, 1050, "Legenda", False),
(7, 1400, "Hős", False),
(8, 1800, "Elit", False),
(9, 2250, "Zsoldos", False),
(10, 2750, "Kalandor", False),
]
inserted = 0
for level_num, min_points, rank_name, is_penalty in levels:
# Use PostgreSQL upsert to avoid duplicates
insert_stmt = insert(LevelConfig).values(
level_number=level_num,
min_points=min_points,
rank_name=rank_name,
is_penalty=is_penalty
)
upsert_stmt = insert_stmt.on_conflict_do_update(
index_elements=['level_number'],
set_=dict(
min_points=min_points,
rank_name=rank_name,
is_penalty=is_penalty
)
)
await db.execute(upsert_stmt)
inserted += 1
await db.commit()
print(f"{inserted} gamification levels seeded (-3 to +10)")
return inserted
def generate_hungarian_coordinates(index: int) -> Tuple[float, float]:
"""Generate realistic Hungarian coordinates for mock service profiles."""
# Major Hungarian cities with their coordinates
cities = [
(47.4979, 19.0402), # Budapest
(46.2530, 20.1482), # Szeged
(47.5316, 21.6273), # Debrecen
(46.0759, 18.2280), # Pécs
(47.2300, 16.6216), # Szombathely
(47.9025, 20.3772), # Eger
(47.1890, 18.4103), # Székesfehérvár
(46.8412, 16.8416), # Zalaegerszeg
(48.1033, 20.7786), # Miskolc
(46.3833, 18.1333), # Kaposvár
(47.4980, 19.0399), # Budapest (different district)
(47.5300, 21.6200), # Debrecen (slightly offset)
(46.2600, 20.1500), # Szeged (slightly offset)
(47.1900, 18.4200), # Székesfehérvár (slightly offset)
(46.8400, 16.8500), # Zalaegerszeg (slightly offset)
]
# Add small random offset to make each location unique
import random
base_lat, base_lon = cities[index % len(cities)]
offset_lat = random.uniform(-0.01, 0.01)
offset_lon = random.uniform(-0.01, 0.01)
return (base_lat + offset_lat, base_lon + offset_lon)
async def seed_service_profiles(db, admin_user):
"""Create 15 mock service profiles with different statuses and Hungarian coordinates."""
statuses = [ServiceStatus.ghost, ServiceStatus.active, ServiceStatus.flagged]
status_distribution = [5, 7, 3] # 5 ghost, 7 active, 3 flagged
service_names = [
"AutoCenter Budapest",
"Speedy Garage Szeged",
"MesterMűhely Debrecen",
"First Class Autószerviz Pécs",
"Profik Szerviz Szombathely",
"TopGear Eger",
"Gold Service Székesfehérvár",
"Zala Autó Zalaegerszeg",
"Borsodi Műhely Miskolc",
"Kaposvári Autó Centrum",
"Budapest East Garage",
"Debrecen North Workshop",
"Szeged South Auto",
"Fehérvári Speedy",
"Zala Pro Motors"
]
inserted = 0
status_idx = 0
for i in range(15):
# Determine status based on distribution
if i < status_distribution[0]:
status = ServiceStatus.ghost
elif i < status_distribution[0] + status_distribution[1]:
status = ServiceStatus.active
else:
status = ServiceStatus.flagged
# Generate coordinates
lat, lon = generate_hungarian_coordinates(i)
# Create WKT element for PostGIS
location = WKTElement(f'POINT({lon} {lat})', srid=4326)
# Create service profile
service = ServiceProfile(
fingerprint=f"MOCK-{i:03d}-{datetime.utcnow().timestamp():.0f}",
location=location,
status=status,
trust_score=30 if status == ServiceStatus.ghost else 75,
is_verified=(status == ServiceStatus.active),
contact_phone=f"+36 30 {1000 + i} {2000 + i}",
contact_email=f"info@{service_names[i].replace(' ', '').lower()}.hu",
website=f"https://{service_names[i].replace(' ', '').lower()}.hu",
bio=f"{service_names[i]} - Profi autószerviz Magyarországon.",
rating=4.0 + (i % 5) * 0.2,
user_ratings_total=10 + i * 5,
last_audit_at=datetime.utcnow()
)
db.add(service)
inserted += 1
# Commit in batches
if inserted % 5 == 0:
await db.commit()
await db.commit()
print(f"{inserted} mock service profiles created (ghost:5, active:7, flagged:3)")
return inserted
async def main():
"""Main seed function."""
print("🚀 Service Finder v2.0 Seed Script")
print("=" * 50)
async with SessionLocal() as db:
try:
# 1. Cleanup (only in non-production)
await cleanup_existing_seeds(db)
# 2. Create superadmin user
admin = await create_superadmin(db)
# 3. Seed gamification levels
await seed_gamification_levels(db)
# 4. Seed service profiles
await seed_service_profiles(db, admin)
print("=" * 50)
print("🎉 Seed completed successfully!")
print(" - Superadmin: admin@servicefinder.hu / admin123")
print(" - Gamification: Levels -3 to +10 configured")
print(" - Service Profiles: 15 mock profiles with Hungarian coordinates")
print(" - Status distribution: 5 ghost, 7 active, 3 flagged")
except Exception as e:
await db.rollback()
print(f"❌ Seed failed: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
asyncio.run(main())