celery: add error logging for Sonauto API responses

Also update CLAUDE.md to require commit+push after changes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
michael 2026-01-31 08:20:40 -06:00
parent 1b2bc8497c
commit 3c7f4142aa
2 changed files with 100 additions and 73 deletions

169
CLAUDE.md
View file

@ -4,96 +4,119 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Important: Read the Spec First
**At the start of every session, read the full specification document at `/documents/spec.md`.** This document contains the complete technical specification including API endpoints, database schema, background worker logic, and video generation details. Understanding the spec is essential before making any changes to this project.
**At the start of every session, read `/documents/spec.md`.** It contains the complete technical specification including API endpoints, database schema, background worker logic, and video generation details.
## Project Overview
Valentine's Day 2026 AI-powered Pet Love Song Generator Microsite for Pets at Home. Users submit pet info, upload/crop a photo, and receive an AI-generated music video combining their pet photo with a custom song.
**Stack:** PHP (server-side rendering) + Alpine.js (reactive forms) + Cropper.js (image cropping)
**Stack:** PHP (server-side rendering) + Alpine.js (reactive forms) + Cropper.js (image cropping) | FastAPI + Celery + PostgreSQL + Redis (backend)
## Running the Project
```bash
# Start PHP development server
# Frontend (PHP)
php -S localhost:8000
# Backend (Docker - recommended)
cd backend && docker compose up --build
# Backend (local venv alternative)
cd backend
pip install -r requirements.txt
uvicorn app.main:app --host 0.0.0.0 --port 8000
# Run migrations (from backend/)
alembic upgrade head
```
Access at `http://localhost:8000`
## Project Structure
## Architecture
```
/ # PHP frontend pages
├── index.php # Form page (Alpine.js + Cropper.js)
├── waiting.php # Loading page with polling
├── result.php # Results UI with video player
├── header.php # Shared header
├── footer.php # Shared footer
├── assets/
│ ├── js/home.js # Alpine.js form component + SessionManager
│ └── css/style.css # Complete styling
├── backend/ # FastAPI service
│ ├── app/
│ │ ├── main.py # FastAPI app entry point
│ │ ├── routers/ # API endpoints (submissions, webhook, results, health)
│ │ ├── models.py # SQLAlchemy models
│ │ ├── schemas.py # Pydantic schemas
│ │ └── config.py # Settings
│ ├── tasks/
│ │ ├── celery_app.py # Celery configuration + Beat schedule
│ │ └── workers.py # Background tasks
│ ├── video_generator/ # Video creation script
│ ├── alembic/ # Database migrations
│ └── docker-compose.yml
├── documents/
│ └── spec.md # Full specification (1166 lines)
└── storage/ # Runtime outputs (uploads/audio/video)
```
### Current State
**Frontend (Partially Complete):**
- `index.php` - Form page with Alpine.js + Cropper.js (form submission implemented)
- `waiting.php` - Loading page with animation (polling NOT implemented)
- `result.php` - Results UI with rotating record (API integration NOT implemented)
- `header.php` / `footer.php` - Shared components
- `opengraph.php` - Open Graph and Twitter Card meta tags
- `assets/js/home.js` - Alpine.js form component + SessionManager module
**Backend (Specification Only - Not Implemented):**
- Full API specification in `/documents/spec.md` (1166 lines)
- Requires: Database, API endpoints, background workers, Sonauto API integration, FFmpeg video generation
### Data Flow
## Architecture & Data Flow
1. User submits form → POST `/api/submissions` → returns `session_id`
2. Background worker sends to Sonauto API for music generation
3. Sonauto webhook callback triggers audio download
4. Backend generates MP4 video with pet photo + audio
2. Celery Beat picks up pending submissions → sends to Sonauto API
3. Sonauto webhook callback → triggers Celery task chain
4. Task chain: fetch details → download audio → create video (FFmpeg)
5. Frontend polls `/api/submissions/{session_id}/status` until complete
6. Results page displays video with download/share options
### Key External Dependencies
- **Sonauto API** (`https://api.sonauto.ai/v1`) - AI music generation
- **Alpine.js v3.15.5** - Reactive form handling (loaded from CDN)
- **Cropper.js v1.6.2** - Client-side image cropping (loaded from CDN)
- **FFmpeg** - Video generation (backend requirement)
## Frontend Patterns
### Alpine.js Form Component (index.php + home.js)
```javascript
// Alpine.js component: petSongForm
// Three UI states for image: upload → crop → preview
// Cropper outputs 600×600px JPEG at 0.9 quality
// Base64 image stored in formData.photo
```
### Form Validation
- Pet name / Owner name: 2-100 chars, letters/spaces only
- Client-side: HTML5 attributes + JS input filter + Alpine.js validation
- Image: JPEG/PNG only, min 400×400px, max 5MB, cropped to 1:1 aspect ratio
### Session Management (localStorage)
- Uses `SessionManager` module in `assets/js/home.js`
- Storage key: `submission_data`
- Stores: `{cookie_id, entries: [{session_id, timestamp}]}`
- Client-side rate limit check: 10 submissions max
### Rate Limiting
- localStorage-based: 10 submissions per `cookie_id`
- Server also enforces limit per `cookie_id`
- 429 error should trigger "Sold Out" modal (needs implementation)
## Backend Implementation Reference
When implementing the backend, refer to `/documents/spec.md` for:
- Complete API endpoint specifications
- Database schema (17-column submissions table)
- Background worker logic (queue processor, credits monitor, timeout checker)
- Sonauto API integration details
- Video generation process with FFmpeg
- Error handling and retry logic
### Required Endpoints
- `POST /api/submissions` - Create submission
- `GET /api/submissions/{session_id}/status` - Poll status
- `POST /api/webhook` - Sonauto callback
- `GET /api/results/{session_id}` - Fetch completed result
6. Results page displays video at `/api/results/{session_id}`
### Status Flow
`pending``processing``success` / `fail`
## Key Patterns
### Frontend (Alpine.js)
- Form component `petSongForm` in `assets/js/home.js`
- `SessionManager` module handles localStorage (`submission_data` key)
- Image cropping: 600x600px JPEG at 0.9 quality
- Rate limiting: 10 submissions per `cookie_id` (client + server enforced)
### Backend (Celery Tasks)
- `process_pending_queue`: runs every 60s, sends to Sonauto API
- `check_credits`: runs every 10min, caches in Redis
- `check_timeouts`: runs every 5min, marks stale submissions as failed
- `cleanup_old_files`: runs daily at 3 AM, deletes old files
### Video Generation
- Uses FFmpeg, generates ~45 frames per rotation cycle
- Streams frames to FFmpeg (no temp files)
- Output: 720x720px MP4, 15fps, rotating vinyl record effect
## External Dependencies
- **Sonauto API** (`https://api.sonauto.ai/v1`) - AI music generation
- **FFmpeg** - Video generation (must be installed in Docker/system)
## Environment Variables
Copy `.env.example` to `.env` and configure:
```bash
SONAUTO_API_KEY=your_key_here
WEBHOOK_BASE_URL=https://your-domain.com
```
## Code Style
- PHP/HTML: 4-space indentation, lowercase filenames
- JavaScript: 4-space indentation, semicolons
- Python: snake_case, Pydantic models, FastAPI routers in `backend/app/routers/`
- Commit style: short imperative subject, sometimes scoped (e.g., `result: add video poster`)
## Workflow
**Always commit and push changes immediately after making them.** Do not wait for the user to ask - after any code modification, commit with a descriptive message and push to remote.
## Testing
No automated test suite. Validate manually:
- Frontend: run PHP server, exercise form flow
- Backend: hit `/api/health`, simulate submission/status endpoints

View file

@ -257,6 +257,10 @@ def send_to_sonauto(self, session_id: str):
return {"task_id": data["task_id"]}
except requests.RequestException as e:
# Log the error details for debugging
if hasattr(e, 'response') and e.response is not None:
logger.error(f"Sonauto API error for {session_id}: {e.response.status_code} - {e.response.text}")
logger.error(f"Payload sent: {payload}")
submission.retry_count += 1
if submission.retry_count >= settings.MAX_RETRIES:
submission.entry_status = "fail"