Files
PromptTech/docs/reports/ADMIN_SERVICES_FIX.md

5.4 KiB

Admin Services Fixed - January 11, 2026

🔴 Problem

The Admin Dashboard was unable to load services, showing:

Access to XMLHttpRequest at 'http://localhost:8181/api/admin/services?include_inactive=true' 
from origin 'http://localhost:5300' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

GET http://localhost:8181/api/admin/services?include_inactive=true net::ERR_FAILED 500 (Internal Server Error)

🔍 Root Cause

The /api/admin/services endpoint was throwing a 500 Internal Server Error because:

  • The service_to_dict() function tries to access service.images relationship
  • The admin endpoint query did NOT eager-load the images relationship
  • When SQLAlchemy tried to access the lazy-loaded relationship, it failed in async context
  • This caused the 500 error, which then prevented CORS headers from being sent

Solution

Added eager loading to the admin services endpoint query:

Before:

@api_router.get("/admin/services")
async def admin_get_services(include_inactive: bool = False, user: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db)):
    query = select(Service)
    if not include_inactive:
        query = query.where(Service.is_active == True)
    query = query.order_by(desc(Service.created_at))
    result = await db.execute(query)
    services = result.scalars().all()
    return [service_to_dict(s) for s in services]

After:

@api_router.get("/admin/services")
async def admin_get_services(include_inactive: bool = False, user: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db)):
    query = select(Service).options(selectinload(Service.images))  # ✅ ADDED THIS
    if not include_inactive:
        query = query.where(Service.is_active == True)
    query = query.order_by(desc(Service.created_at))
    result = await db.execute(query)
    services = result.scalars().all()
    return [service_to_dict(s) for s in services]

📊 Verification

Admin Services Endpoint Test

$ curl http://localhost:8181/api/admin/services?include_inactive=true -H "Authorization: Bearer $TOKEN"

✅ SUCCESS! Returns all 8 services:
1. Updated Test Service ($149.99) - setup
2. Updated Repair Service ($149.99) - repair
3. Data Recovery ($199.99) - data
4. Virus Removal ($89.99) - software
5. Screen Repair ($149.99) - repair
6. Device Setup ($59.99) - setup
7. Hardware Upgrade ($49.99) - upgrade
8. Battery Replacement ($79.99) - repair

Backend Logs

INFO: 127.0.0.1:31132 - "GET /api/admin/services?include_inactive=true HTTP/1.1" 200 OK

🎯 Status

Endpoint Status Response Services
/api/services Working 200 OK 8 services
/api/admin/services NOW FIXED 200 OK 8 services

🧪 Testing

Test Admin Dashboard

http://localhost:5300/admin
  1. Login with admin credentials
  2. Navigate to Services tab
  3. Services should now load correctly with all 8 services displayed

Backend Direct Test

cd /media/pts/Website/PromptTech_Solution_Site/backend
source venv/bin/activate
TOKEN=$(python test_upload.py 2>/dev/null | grep "eyJ" | head -1 | tr -d ' ')
curl -s http://localhost:8181/api/admin/services?include_inactive=true \
  -H "Authorization: Bearer $TOKEN" | jq

📝 Technical Details

The Issue with Async SQLAlchemy

  • SQLAlchemy async sessions cannot lazy-load relationships
  • When accessing service.images without eager loading, it tries to issue a new query
  • In async context, this fails because the session is not in an active transaction
  • Result: AttributeError or DetachedInstanceError

The Solution

  • Eager Loading: Use .options(selectinload(Service.images))
  • This loads all images in a single additional query (efficient)
  • Images are available immediately when service_to_dict() accesses them
  • No lazy loading = No async errors

Why CORS Error Appeared

  • The 500 error prevented the response from being sent
  • FastAPI's CORS middleware only adds headers to successful responses
  • Browser saw no CORS headers and blocked the request
  • User saw CORS error, but real issue was the 500 error underneath

🔧 Files Modified

  • backend/server.py (line 1363): Added selectinload(Service.images) to admin services query

Comparison with Public Endpoint

Public Services Endpoint (Already Working):

@api_router.get("/services")
async def get_services(category: Optional[str] = None, db: AsyncSession = Depends(get_db)):
    query = select(Service).where(Service.is_active == True)
    if category and category != "all":
        query = query.where(Service.category == category)
    query = query.options(selectinload(Service.reviews).selectinload(Review.user))
    # ... rest of query

This one already had eager loading for reviews, so it worked fine.

Admin Endpoint (Was Missing Eager Loading):

  • Was not loading images relationship
  • Now fixed with same pattern

🎉 Result

  • Admin services endpoint returns 200 OK
  • All 8 services load correctly
  • CORS headers present
  • Admin Dashboard services tab functional
  • No more 500 errors

Fixed By: GitHub Copilot
Date: January 11, 2026, 10:59 PM CST
Issue: Admin services 500 error + CORS blocking
Resolution: Added eager loading for Service.images relationship