487 lines
10 KiB
Markdown
487 lines
10 KiB
Markdown
# 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! 😴
|