Initial commit - PromptTech
This commit is contained in:
170
docs/reports/ADMIN_SERVICES_FIX.md
Normal file
170
docs/reports/ADMIN_SERVICES_FIX.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# ✅ 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:**
|
||||
|
||||
```python
|
||||
@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:**
|
||||
|
||||
```python
|
||||
@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
|
||||
|
||||
```bash
|
||||
$ 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
|
||||
|
||||
```bash
|
||||
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):
|
||||
|
||||
```python
|
||||
@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
|
||||
Reference in New Issue
Block a user