Final project commit: MTGJSON data integration and backend API

This commit is contained in:
wall-o
2026-07-20 01:09:45 +00:00
parent b170dfd577
commit db01e29a54
37 changed files with 9674 additions and 198 deletions
+230
View File
@@ -0,0 +1,230 @@
#!/usr/bin/env python3
"""
Fix MTG sets endpoint - File-based changes only.
This script:
1. Reorders routes in card_router.py so /sets comes before /{card_name}
2. Adds image_url column to MtgSet model
3. Updates service layer to return image_url
"""
import re
from pathlib import Path
def fix_route_ordering():
"""Reorder routes in card_router.py so /sets comes before /{card_name}."""
route_file = Path('/home/wall-o/projects/mtgonline/backend/app/routers/card_router.py')
content = route_file.read_text()
# Find the current route order
# The issue: /{card_name} is defined before /sets
# We need to move /sets and /sets/{set_code} before /{card_name}
# Current order (problematic):
# 1. /search
# 2. /{card_name}
# 3. /set/{set_code}
# 4. /types
# 5. /rarities
# 6. /sets
# 7. /sets/{set_code}
# 8. /statistics
# New order (correct):
# 1. /search
# 2. /set/{set_code}
# 3. /types
# 4. /rarities
# 5. /sets
# 6. /sets/{set_code}
# 7. /statistics
# 8. /{card_name} (wildcard last)
# Extract route blocks using regex
# Pattern to find decorator + function
pattern = r'(@router\.\w+\([^)]+\)\s*\nasync def \w+.*?)(?=@router\.\w+\(|$)'
matches = re.findall(pattern, content, re.DOTALL)
# Identify each route
routes = {}
for match in matches:
# Extract route path
route_match = re.search(r'@router\.(\w+)\(([^)]+)\)', match)
if route_match:
method = route_match.group(1)
args = route_match.group(2)
routes[f"{method}:{args}"] = match
print("Current routes:")
for key in routes:
print(f" {key}")
# Define the correct order
correct_order = [
"get:/search",
"get:/set/{set_code}",
"get:/types",
"get:/rarities",
"get:/sets",
"get:/sets/{set_code}",
"get:/statistics",
"get:/{card_name}",
]
# Verify all routes are present
for route_key in correct_order:
if route_key not in routes:
print(f"✗ Missing route: {route_key}")
return False
print("✓ All routes found")
# Rebuild the file content
new_content = content[:content.index('@router.get("/search")')]
for route_key in correct_order:
new_content += routes[route_key]
# Add the router variable
new_content += "\n\nrouter = APIRouter(prefix=\"/mtg/cards\", tags=[\"MTG Cards\"])\n"
route_file.write_text(new_content)
print("✓ Routes reordered")
def update_mtg_set_model():
"""Update MtgSet model to include image_url."""
model_file = Path('/home/wall-o/projects/mtgonline/backend/app/models/mtg_models.py')
content = model_file.read_text()
# Add image_url column after mtgo_code
if 'image_url' in content:
print("✓ image_url already in MtgSet model")
return
# Find the Mtgo_code line
insert_point = "mtgo_code = Column(String(10), nullable=True)"
new_column = '\n image_url = Column(Text, nullable=True)'
content = content.replace(insert_point, insert_point + new_column)
model_file.write_text(content)
print("✓ image_url added to MtgSet model")
def update_service_layer():
"""Update service layer to return image_url."""
service_file = Path('/home/wall-o/projects/mtgonline/backend/app/services/card_database.py')
content = service_file.read_text()
if 'image_url' not in content:
# Update get_sets() function
old_get_sets = ''' stmt = select(MtgSet).order_by(MtgSet.release_date.desc())
result = await db.execute(stmt)
rows = result.fetchall()
return [
{
"id": s.id,
"code": s.code,
"name": s.name,
"release_date": s.release_date.isoformat() if s.release_date else None,
"total_size": s.total_size,
"base_set_size": s.base_set_size,
}
for s in rows
]'''
new_get_sets = ''' stmt = select(MtgSet).order_by(MtgSet.release_date.desc())
result = await db.execute(stmt)
rows = result.fetchall()
return [
{
"id": s.id,
"code": s.code,
"name": s.name,
"release_date": s.release_date.isoformat() if s.release_date else None,
"total_size": s.total_size,
"base_set_size": s.base_set_size,
"image_url": s.image_url,
}
for s in rows
]'''
if old_get_sets in content:
content = content.replace(old_get_sets, new_get_sets)
print("✓ Updated get_sets() to include image_url")
# Update get_set_by_code() function
old_get_by_code = ''' return {
"id": mtg_set.id,
"code": mtg_set.code,
"name": mtg_set.name,
"type": mtg_set.type,
"release_date": mtg_set.release_date.isoformat() if mtg_set.release_date else None,
"base_set_size": mtg_set.base_set_size,
"total_size": mtg_set.total_size,
"is_foil_only": mtg_set.is_foil_only,
"is_non_foil_only": mtg_set.is_non_foil_only,
"digital": mtg_set.digital,
"icon_svg_url": mtg_set.icon_svg_url,
"parent_code": mtg_set.parent_code,
"mtgo_code": mtg_set.mtgo_code,
}'''
new_get_by_code = ''' return {
"id": mtg_set.id,
"code": mtg_set.code,
"name": mtg_set.name,
"type": mtg_set.type,
"release_date": mtg_set.release_date.isoformat() if mtg_set.release_date else None,
"base_set_size": mtg_set.base_set_size,
"total_size": mtg_set.total_size,
"is_foil_only": mtg_set.is_foil_only,
"is_non_foil_only": mtg_set.is_non_foil_only,
"digital": mtg_set.digital,
"icon_svg_url": mtg_set.icon_svg_url,
"parent_code": mtg_set.parent_code,
"mtgo_code": mtg_set.mtgo_code,
"image_url": mtg_set.image_url,
}'''
if old_get_by_code in content:
content = content.replace(old_get_by_code, new_get_by_code)
print("✓ Updated get_set_by_code() to include image_url")
service_file.write_text(content)
def main():
"""Run all file-based fixes."""
print("=== MTG SETS ENDPOINT FIX (FILE CHANGES) ===\n")
# 1. Fix route ordering
print("1. Route Reordering")
fix_route_ordering()
print()
# 2. Update model
print("2. Model Update")
update_mtg_set_model()
print()
# 3. Update service layer
print("3. Service Layer Update")
update_service_layer()
print()
print("=== FILE CHANGES COMPLETE ===")
print("\nNext steps:")
print("1. Add image_url column to mtg_sets table in database")
print("2. Restart the backend Docker container")
print("3. Test /api/mtg/cards/sets endpoint")
print("4. Run refresh_mtg.py to populate image_url data")
print("5. Test /api/mtg/cards/sets/{set_code} endpoint")
if __name__ == "__main__":
main()