Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart with Orion/orion/ORION across 184 files. This includes database identifiers, email addresses, domain references, R2 bucket names, DNS prefixes, encryption salt, Celery app name, config defaults, Docker configs, CI configs, documentation, seed data, and templates. Renames homepage-wizamart.html template to homepage-orion.html. Fixes duplicate file_pattern key in api.yaml architecture rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
247 lines
5.7 KiB
Markdown
247 lines
5.7 KiB
Markdown
# Image Storage System
|
||
|
||
Documentation for the platform's image storage and management system.
|
||
|
||
## Overview
|
||
|
||
The Orion platform uses a self-hosted image storage system with:
|
||
|
||
- **Sharded directory structure** for filesystem performance
|
||
- **Automatic WebP conversion** for optimization
|
||
- **Multiple size variants** for different use cases
|
||
- **CDN-ready architecture** for scaling
|
||
|
||
## Storage Architecture
|
||
|
||
### Directory Structure
|
||
|
||
Images are stored in a sharded directory structure to prevent filesystem performance degradation:
|
||
|
||
```
|
||
/static/uploads/
|
||
└── products/
|
||
├── 00/ # First 2 chars of hash
|
||
│ ├── 1a/ # Next 2 chars
|
||
│ │ ├── 001a2b3c_original.webp
|
||
│ │ ├── 001a2b3c_800.webp
|
||
│ │ └── 001a2b3c_200.webp
|
||
│ └── 2b/
|
||
│ └── ...
|
||
├── 01/
|
||
│ └── ...
|
||
└── ff/
|
||
└── ...
|
||
```
|
||
|
||
### Hash Generation
|
||
|
||
The file hash is generated from:
|
||
```python
|
||
hash = md5(f"{store_id}:{product_id}:{timestamp}:{original_filename}")[:8]
|
||
```
|
||
|
||
This ensures:
|
||
- Unique file paths
|
||
- Even distribution across directories
|
||
- Predictable file locations
|
||
|
||
## Image Variants
|
||
|
||
Each uploaded image generates multiple variants:
|
||
|
||
| Variant | Max Dimensions | Format | Use Case |
|
||
|---------|---------------|--------|----------|
|
||
| `original` | As uploaded (max 2000px) | WebP | Detail view, zoom |
|
||
| `800` | 800×800 | WebP | Product cards |
|
||
| `200` | 200×200 | WebP | Thumbnails, grids |
|
||
|
||
### Size Estimates
|
||
|
||
| Original Size | After Processing | Storage per Image |
|
||
|---------------|------------------|-------------------|
|
||
| 2MB JPEG | ~200KB (original) + 80KB (800) + 15KB (200) | ~295KB |
|
||
| 500KB JPEG | ~150KB (original) + 60KB (800) + 12KB (200) | ~222KB |
|
||
| 100KB JPEG | ~80KB (original) + 40KB (800) + 10KB (200) | ~130KB |
|
||
|
||
**Average: ~200KB per image (all variants)**
|
||
|
||
## Upload Process
|
||
|
||
### API Endpoint
|
||
|
||
```http
|
||
POST /api/v1/admin/images/upload
|
||
Content-Type: multipart/form-data
|
||
|
||
file: <binary>
|
||
store_id: 123
|
||
product_id: 456 (optional, for product images)
|
||
type: product|category|banner
|
||
```
|
||
|
||
### Response
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"image": {
|
||
"id": "001a2b3c",
|
||
"urls": {
|
||
"original": "/uploads/products/00/1a/001a2b3c_original.webp",
|
||
"medium": "/uploads/products/00/1a/001a2b3c_800.webp",
|
||
"thumb": "/uploads/products/00/1a/001a2b3c_200.webp"
|
||
},
|
||
"size_bytes": 295000,
|
||
"dimensions": {
|
||
"width": 1200,
|
||
"height": 1200
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## Configuration
|
||
|
||
### Environment Variables
|
||
|
||
```bash
|
||
# Image storage configuration
|
||
IMAGE_UPLOAD_DIR=/var/www/uploads
|
||
IMAGE_MAX_SIZE_MB=10
|
||
IMAGE_ALLOWED_TYPES=jpg,jpeg,png,gif,webp
|
||
IMAGE_QUALITY=85
|
||
IMAGE_MAX_DIMENSION=2000
|
||
```
|
||
|
||
### Python Configuration
|
||
|
||
```python
|
||
# app/core/config.py
|
||
class ImageSettings:
|
||
UPLOAD_DIR: str = "/static/uploads"
|
||
MAX_SIZE_MB: int = 10
|
||
ALLOWED_TYPES: list = ["jpg", "jpeg", "png", "gif", "webp"]
|
||
QUALITY: int = 85
|
||
MAX_DIMENSION: int = 2000
|
||
|
||
# Generated sizes
|
||
SIZES: dict = {
|
||
"original": None, # No resize, just optimize
|
||
"medium": 800,
|
||
"thumb": 200,
|
||
}
|
||
```
|
||
|
||
## Performance Guidelines
|
||
|
||
### Filesystem Limits
|
||
|
||
| Files per Directory | Status | Action |
|
||
|---------------------|--------|--------|
|
||
| < 10,000 | OK | None needed |
|
||
| 10,000 - 50,000 | Monitor | Plan migration |
|
||
| 50,000 - 100,000 | Warning | Increase sharding depth |
|
||
| > 100,000 | Critical | Migrate to object storage |
|
||
|
||
### Capacity Planning
|
||
|
||
| Products | Images (5/product) | Total Files (3 sizes) | Storage |
|
||
|----------|--------------------|-----------------------|---------|
|
||
| 10,000 | 50,000 | 150,000 | 30 GB |
|
||
| 50,000 | 250,000 | 750,000 | 150 GB |
|
||
| 100,000 | 500,000 | 1,500,000 | 300 GB |
|
||
| 500,000 | 2,500,000 | 7,500,000 | 1.5 TB |
|
||
|
||
## CDN Integration
|
||
|
||
For production deployments, configure a CDN for image delivery:
|
||
|
||
### Cloudflare (Recommended)
|
||
|
||
1. Set up Cloudflare for your domain
|
||
2. Configure page rules for `/uploads/*`:
|
||
- Cache Level: Cache Everything
|
||
- Edge Cache TTL: 1 month
|
||
- Browser Cache TTL: 1 week
|
||
|
||
### nginx Configuration
|
||
|
||
```nginx
|
||
location /uploads/ {
|
||
alias /var/www/uploads/;
|
||
expires 30d;
|
||
add_header Cache-Control "public, immutable";
|
||
add_header X-Content-Type-Options nosniff;
|
||
|
||
# WebP fallback for older browsers
|
||
location ~ \.(jpg|jpeg|png)$ {
|
||
try_files $uri$webp_suffix $uri =404;
|
||
}
|
||
}
|
||
```
|
||
|
||
## Maintenance
|
||
|
||
### Cleanup Orphaned Images
|
||
|
||
Remove images not referenced by any product:
|
||
|
||
```bash
|
||
# Run via admin CLI
|
||
python -m scripts.cleanup_orphaned_images --dry-run
|
||
python -m scripts.cleanup_orphaned_images --execute
|
||
```
|
||
|
||
### Regenerate Variants
|
||
|
||
If image quality settings change:
|
||
|
||
```bash
|
||
# Regenerate all variants for a store
|
||
python -m scripts.regenerate_images --store-id 123
|
||
|
||
# Regenerate all variants (use with caution)
|
||
python -m scripts.regenerate_images --all
|
||
```
|
||
|
||
## Monitoring
|
||
|
||
### Metrics to Track
|
||
|
||
- Total file count
|
||
- Storage used (GB)
|
||
- Files per directory (max)
|
||
- Upload success rate
|
||
- Average processing time
|
||
|
||
### Health Checks
|
||
|
||
The platform health page includes image storage metrics:
|
||
|
||
- Current file count
|
||
- Storage usage
|
||
- Directory distribution
|
||
- Processing queue status
|
||
|
||
## Troubleshooting
|
||
|
||
### Common Issues
|
||
|
||
**Upload fails with "File too large"**
|
||
- Check `IMAGE_MAX_SIZE_MB` setting
|
||
- Verify nginx `client_max_body_size`
|
||
|
||
**Images not displaying**
|
||
- Check file permissions (should be readable by web server)
|
||
- Verify URL paths match actual file locations
|
||
|
||
**Slow uploads**
|
||
- Check disk I/O performance
|
||
- Consider async processing queue
|
||
|
||
## Related Documentation
|
||
|
||
- [Capacity Planning](../architecture/capacity-planning.md)
|
||
- [Platform Health](platform-health.md)
|
||
- [Capacity Monitoring](capacity-monitoring.md)
|