# Deployment Guide - Meta API Grabber ## Quick Start (Test Deployment for Tonight) ### 1. Get a Fresh Access Token Run the OAuth flow to get a new long-lived token (60 days): ```bash uv run python -m meta_api_grabber.auth ``` This will: - Open browser for OAuth authorization - Exchange short-lived token for long-lived token (60 days) - Save token to `.env` and `.meta_token.json` - Token will auto-refresh before expiry ✅ ### 2. Verify Your `.env` File Ensure your `.env` has these variables: ```bash # Meta API Credentials META_APP_ID=your_app_id META_APP_SECRET=your_app_secret META_ACCESS_TOKEN=your_long_lived_token # From step 1 # Database (docker-compose handles this) DATABASE_URL=postgresql://meta_user:meta_password@localhost:5555/meta_insights ``` ### 3. Build and Start Everything ```bash # Build the Docker image and start all services docker-compose up -d --build ``` This starts: - **timescaledb**: Database for storing insights - **meta-grabber**: Your data collection service ⭐ - **grafana**: Visualization dashboard (optional) ### 4. Monitor the Logs ```bash # Watch the grabber logs in real-time docker-compose logs -f meta-grabber # Expected output: # ============================================================ # SCHEDULED INSIGHTS GRABBER STARTED # ============================================================ # ✅ Token valid (X days remaining) # Loading accessible ad accounts... # Loaded X ad account(s) # Collection interval: 2.0 hours # ============================================================ # # COLLECTION CYCLE - 2025-10-21T... # ============================================================ # Processing X ad account(s) # ... ``` ### 5. Verify It's Running ```bash # Check container status docker-compose ps # Should show: # NAME STATUS PORTS # meta_timescaledb Up (healthy) 0.0.0.0:5555->5432/tcp # meta_api_grabber Up # meta_grafana Up 0.0.0.0:3555->3000/tcp ``` ### 6. Let It Run Overnight The service will: - ✅ Collect "today" data every 2 hours - ✅ Detect when a new day starts - ✅ Fetch "yesterday" data immediately when new day is detected - ✅ Update "yesterday" data every 12 hours - ✅ Auto-refresh the access token before it expires - ✅ Restart automatically if it crashes (`restart: unless-stopped`) ## Token Auto-Refresh ### How It Works The system uses `MetaTokenManager` which: 1. **On startup**: Checks if token expires within 7 days 2. **If expiring soon**: Exchanges current token for a new long-lived token 3. **Saves new token**: Updates both `.env` and `.meta_token.json` 4. **Every cycle**: Re-checks token validity before fetching data ### Token Lifecycle ``` New Token (via OAuth) ↓ 60 days validity ↓ Day 53 (7 days before expiry) ↓ Auto-refresh triggered ↓ New 60-day token issued ↓ Cycle repeats indefinitely ♾️ ``` ### What If Token Expires? If the token somehow expires (e.g., manual revocation): - Container will **error out immediately** with clear message - Logs will show: `❌ Fatal error - Token validation failed` - Container stops (won't waste API calls) - You'll see it in: `docker-compose logs meta-grabber` **To fix**: 1. Stop the container: `docker-compose stop meta-grabber` 2. Get new token: `uv run python -m meta_api_grabber.auth` 3. Restart: `docker-compose up -d meta-grabber` ## Data Collection Schedule ### Normal Operation (Same Day) ``` 00:00 - Cycle 1: Fetch "today" (2025-10-21) 02:00 - Cycle 2: Fetch "today" (2025-10-21) 04:00 - Cycle 3: Fetch "today" (2025-10-21) ... 22:00 - Cycle 12: Fetch "today" (2025-10-21) ``` ### When New Day Starts ``` 00:00 - Cycle 13: - Fetch "today" (2025-10-22) ← New date detected! - 📅 New day detected: 2025-10-21 -> 2025-10-22 - Fetch "yesterday" (2025-10-21) immediately 02:00 - Cycle 14: - Fetch "today" (2025-10-22) - Skip "yesterday" (< 12h since last fetch) ... 12:00 - Cycle 19: - Fetch "today" (2025-10-22) - Update "yesterday" (12h passed since last fetch) ``` ## Checking Data in Database ### Connect to Database ```bash # From host machine docker exec -it meta_timescaledb psql -U meta_user -d meta_insights # Or using psql directly psql -h localhost -p 5555 -U meta_user -d meta_insights # Password: meta_password ``` ### Query Today's Data ```sql SELECT time, account_id, date_preset, date_start, impressions, spend FROM account_insights WHERE date_preset = 'today' ORDER BY time DESC LIMIT 10; ``` ### Query Yesterday's Data ```sql SELECT time, account_id, date_preset, date_start, impressions, spend FROM account_insights WHERE date_preset = 'yesterday' ORDER BY time DESC LIMIT 10; ``` ### Check Last Collection Time ```sql SELECT date_preset, MAX(fetched_at) as last_fetch, COUNT(*) as total_records FROM account_insights GROUP BY date_preset; ``` ## Stopping and Restarting ### Stop Everything ```bash docker-compose down ``` This stops all containers but **preserves data**: - ✅ Database data (in volume `timescale_data`) - ✅ Token files (mounted from host: `.env`, `.meta_token.json`) - ✅ Grafana dashboards (in volume `grafana_data`) ### Stop Just the Grabber ```bash docker-compose stop meta-grabber ``` ### Restart the Grabber ```bash docker-compose restart meta-grabber ``` ### View Logs ```bash # Follow logs in real-time docker-compose logs -f meta-grabber # Last 100 lines docker-compose logs --tail=100 meta-grabber # All services docker-compose logs -f ``` ## Configuration ### Adjusting Collection Interval Edit [scheduled_grabber.py](src/meta_api_grabber/scheduled_grabber.py) line 522: ```python await grabber.run_scheduled( interval_hours=2.0, # ← Change this (in hours) refresh_metadata_every_n_cycles=12, ) ``` Then rebuild: ```bash docker-compose up -d --build meta-grabber ``` ### Adjusting Number of Accounts Edit [scheduled_grabber.py](src/meta_api_grabber/scheduled_grabber.py) line 519: ```python grabber = ScheduledInsightsGrabber( max_accounts=3, # ← Change this (None = all accounts) ) ``` ### Adjusting Yesterday Fetch Interval Currently hardcoded to 12 hours in `_should_fetch_yesterday()` method at line 175. To change, edit: ```python return hours_since_last_fetch >= 12.0 # ← Change to 6.0 for 6 hours, etc. ``` ## Troubleshooting ### Container Keeps Restarting ```bash # Check logs for error docker-compose logs meta-grabber # Common issues: # 1. Token invalid → Get new token # 2. Database not ready → Wait for timescaledb health check # 3. Missing .env file → Create from .env.example ``` ### No Data Being Collected ```bash # Check if grabber is running docker-compose ps # Check logs for API errors docker-compose logs meta-grabber | grep "Error" # Verify token uv run python -m meta_api_grabber.token_manager ``` ### Database Connection Failed ```bash # Check if TimescaleDB is healthy docker-compose ps timescaledb # Should show: "Up (healthy)" # If not healthy, check TimescaleDB logs docker-compose logs timescaledb ``` ### Yesterday Data Not Appearing Check logs for: ``` 📅 New day detected: YYYY-MM-DD -> YYYY-MM-DD Fetching yesterday's data (first time) ``` If you don't see this, the system hasn't detected a new day yet. To force a test: 1. Stop grabber: `docker-compose stop meta-grabber` 2. Manually insert yesterday data (see manual testing section) 3. Restart: `docker-compose start meta-grabber` ## Manual Testing (Before Overnight Run) ### Test Token Validity ```bash # This will check token and auto-refresh if needed uv run python -m meta_api_grabber.token_manager ``` ### Test Single Collection Cycle ```bash # Run one cycle without Docker uv run python -c " import asyncio from src.meta_api_grabber.scheduled_grabber import ScheduledInsightsGrabber async def test(): grabber = ScheduledInsightsGrabber(max_accounts=1) await grabber.db.connect() await grabber.db.initialize_schema() await grabber.load_ad_accounts() await grabber.run_collection_cycle() await grabber.db.close() asyncio.run(test()) " ``` ### Verify Database Schema ```bash docker exec -it meta_timescaledb psql -U meta_user -d meta_insights -c "\dt" # Should show: # account_insights # campaign_insights # adset_insights # ad_accounts # campaigns # adsets ``` ## Monitoring in Production ### Health Checks The container has a built-in health check: ```bash docker inspect meta_api_grabber | grep -A 5 Health ``` ### Resource Usage ```bash # Monitor container resources docker stats meta_api_grabber ``` ### Log Rotation Logs are automatically rotated (see docker-compose.yml): - Max size: 10MB per file - Max files: 3 - Total max: ~30MB of logs ## Backup Considerations ### What to Backup 1. **Database** (most important): ```bash docker exec meta_timescaledb pg_dump -U meta_user meta_insights > backup.sql ``` 2. **Token files**: ```bash cp .env .env.backup cp .meta_token.json .meta_token.json.backup ``` 3. **Configuration**: - `.env` - `docker-compose.yml` ### Restore from Backup ```bash # Restore database docker exec -i meta_timescaledb psql -U meta_user meta_insights < backup.sql # Restore token files cp .env.backup .env cp .meta_token.json.backup .meta_token.json # Restart docker-compose restart meta-grabber ``` ## Production Checklist Before leaving it running overnight: - [ ] Fresh access token obtained (60 days validity) - [ ] `.env` file has all required variables - [ ] `.meta_token.json` exists with token metadata - [ ] `docker-compose up -d --build` succeeded - [ ] All containers show "Up" in `docker-compose ps` - [ ] Logs show successful data collection - [ ] Database contains data (`SELECT COUNT(*) FROM account_insights`) - [ ] Token auto-refresh is enabled (`auto_refresh_token=True`) - [ ] Restart policy is set (`restart: unless-stopped`) ## Summary To deploy for overnight testing: ```bash # 1. Get token uv run python -m meta_api_grabber.auth # 2. Start everything docker-compose up -d --build # 3. Verify it's working docker-compose logs -f meta-grabber # 4. Let it run! # Come back tomorrow and check: docker-compose logs meta-grabber | grep "New day detected" ``` The system will handle everything automatically: - ✅ Data collection every 2 hours - ✅ New day detection - ✅ Yesterday data collection - ✅ Token auto-refresh - ✅ Auto-restart on failures Sleep well! 😴