Initial commit - Dutchie dispensary scraper

This commit is contained in:
Kelly
2025-11-28 19:45:44 -07:00
commit 5757a8e9bd
23375 changed files with 3788799 additions and 0 deletions

414
backend/API_USAGE.md Normal file
View File

@@ -0,0 +1,414 @@
# Dutchie Analytics API - Usage Guide
## Base URL
```
http://localhost:3010/api
```
## Authentication
All endpoints require JWT authentication via Bearer token:
```
Authorization: Bearer YOUR_JWT_TOKEN
```
## Products API
### Get Products with Filtering, Sorting & Field Selection
**Endpoint:** `GET /products`
#### Basic Usage
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&limit=10"
```
#### Field Selection (Reduce Payload Size)
Only return specific fields to reduce bandwidth and improve performance:
```bash
# Only get essential fields
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&fields=id,name,price,brand,in_stock"
# Get fields needed for product cards
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&fields=id,name,price,brand,thc_percentage,image_url_full,in_stock"
```
#### Advanced Filtering
**Filter by Category:**
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&category_id=5"
```
**Filter by Stock Status:**
```bash
# Only in-stock products
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&in_stock=true"
```
**Search Products:**
```bash
# Search in name, brand, and description
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?search=blue+dream"
```
**Filter by Brand:**
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&brand=Cresco"
```
**Filter by Price Range:**
```bash
# Products between $20 and $50
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&min_price=20&max_price=50"
```
**Filter by THC Percentage:**
```bash
# High THC products (>20%)
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&min_thc=20"
```
**Filter by Strain Type:**
```bash
# Get only indica strains
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&strain_type=indica"
```
#### Sorting
**Sort by Price:**
```bash
# Lowest price first
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&sort_by=price&sort_order=asc"
# Highest price first
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&sort_by=price&sort_order=desc"
```
**Sort by THC:**
```bash
# Highest THC first
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&sort_by=thc_percentage&sort_order=desc"
```
**Sort by Name:**
```bash
# Alphabetical
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&sort_by=name&sort_order=asc"
```
**Available Sort Fields:**
- `id`
- `name`
- `brand`
- `price`
- `thc_percentage`
- `cbd_percentage`
- `last_seen_at`
- `created_at`
#### Pagination
```bash
# Get first page (50 items)
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&limit=50&offset=0"
# Get second page
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?store_id=1&limit=50&offset=50"
```
#### Complex Query Example
Get affordable indica products sorted by THC:
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products?\
store_id=1&\
strain_type=indica&\
max_price=40&\
min_thc=15&\
in_stock=true&\
sort_by=thc_percentage&\
sort_order=desc&\
limit=20&\
fields=id,name,brand,price,thc_percentage,strain_type,image_url_full"
```
#### Response Format
```json
{
"products": [
{
"id": 123,
"name": "Blue Dream 3.5g",
"brand": "Cresco Labs",
"price": 45.00,
"thc_percentage": 24.5,
"cbd_percentage": 0.5,
"strain_type": "hybrid",
"in_stock": true,
"image_url_full": "http://localhost:9020/dutchie/products/123/full.jpg",
"thumbnail_url": "http://localhost:9020/dutchie/products/123/thumb.jpg",
"medium_url": "http://localhost:9020/dutchie/products/123/medium.jpg",
"store_name": "Curaleaf - Phoenix",
"category_name": "Flower"
}
],
"total": 145,
"limit": 50,
"offset": 0,
"filters": {
"store_id": "1",
"category_id": null,
"in_stock": "true",
"search": null,
"brand": null,
"min_price": null,
"max_price": null,
"min_thc": null,
"max_thc": null,
"strain_type": null,
"sort_by": "last_seen_at",
"sort_order": "DESC"
}
}
```
### Get Single Product
**Endpoint:** `GET /products/:id`
```bash
# Full product data
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products/123"
# With field selection
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products/123?fields=id,name,price,description,image_url_full"
```
### Get Available Brands (Meta Endpoint)
**Endpoint:** `GET /products/meta/brands`
Get list of all brands for filter dropdowns:
```bash
# All brands
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products/meta/brands"
# Brands for specific store
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products/meta/brands?store_id=1"
```
**Response:**
```json
{
"brands": [
"Cresco Labs",
"Select",
"Timeless",
"Canamo",
"Item 9"
]
}
```
### Get Price Range (Meta Endpoint)
**Endpoint:** `GET /products/meta/price-range`
Get min/max/avg prices for filter sliders:
```bash
# All products
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products/meta/price-range"
# For specific store
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/products/meta/price-range?store_id=1"
```
**Response:**
```json
{
"min_price": 15.00,
"max_price": 120.00,
"avg_price": 42.50
}
```
## Stores API
### Get All Stores
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/stores"
```
### Get Single Store
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/stores/1"
```
### Get Store Brands
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/stores/1/brands"
```
### Get Store Specials
```bash
# Today's specials
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/stores/1/specials"
# Specials for specific date
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/stores/1/specials?date=2025-01-15"
```
### Trigger Store Scrape (Admin)
```bash
curl -X POST -H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"parallel": 3}' \
"http://localhost:3010/api/stores/1/scrape"
```
## Categories API
### Get Categories (Flat List)
```bash
# All categories
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/categories"
# For specific store
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/categories?store_id=1"
```
### Get Category Tree (Hierarchical)
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:3010/api/categories/tree?store_id=1"
```
## Real-World Usage Examples
### Build a Product Filter UI
1. **Get filter options:**
```javascript
// Get brands for dropdown
const brands = await fetch('/api/products/meta/brands?store_id=1');
// Get price range for slider
const priceRange = await fetch('/api/products/meta/price-range?store_id=1');
// Get categories for checkboxes
const categories = await fetch('/api/categories?store_id=1');
```
2. **Fetch filtered products:**
```javascript
const params = new URLSearchParams({
store_id: '1',
brand: selectedBrand,
min_price: minPrice,
max_price: maxPrice,
category_id: selectedCategory,
in_stock: 'true',
sort_by: 'price',
sort_order: 'asc',
fields: 'id,name,price,brand,thc_percentage,image_url_full,in_stock'
});
const products = await fetch(`/api/products?${params}`);
```
### Build a WordPress Product Grid
```php
// Efficient field selection for grid display
$fields = 'id,name,price,brand,thc_percentage,image_url_full,in_stock';
$response = wp_remote_get(
"http://localhost:3010/api/products?store_id=1&in_stock=true&limit=12&fields=$fields",
['headers' => ['Authorization' => 'Bearer ' . $token]]
);
$data = json_decode(wp_remote_retrieve_body($response));
foreach ($data->products as $product) {
// Only contains requested fields = smaller payload
echo render_product_card($product);
}
```
### Build Price Comparison Tool
```javascript
// Get cheapest products across all stores
const cheapProducts = await fetch(
'/api/products?in_stock=true&sort_by=price&sort_order=asc&limit=50&fields=id,name,price,store_name'
);
// Get highest THC products
const strongProducts = await fetch(
'/api/products?in_stock=true&min_thc=25&sort_by=thc_percentage&sort_order=desc&limit=50'
);
```
## Performance Tips
1. **Use Field Selection:** Only request fields you need to reduce bandwidth
2. **Pagination:** Use reasonable `limit` values (50-100)
3. **Caching:** Cache brand lists, price ranges, and category data
4. **Combine Filters:** Use multiple filters to reduce result set size
5. **Index Optimization:** The API uses indexed fields for filtering
## Rate Limits
- Standard users: 100 requests/minute
- Admin users: 500 requests/minute
- Exceeded limits return `429 Too Many Requests`
## Error Responses
```json
{
"error": "Failed to fetch products"
}
```
HTTP Status Codes:
- `200` - Success
- `400` - Bad Request
- `401` - Unauthorized
- `404` - Not Found
- `429` - Too Many Requests
- `500` - Server Error