Documentation Index
Fetch the complete documentation index at: https://mintlify.com/CRISTIANCAMACH34/Zippi/llms.txt
Use this file to discover all available pages before exploring further.
Zippi uses a port/adapter pattern for file storage: the domain and application layers depend on a storage interface, and the concrete driver is selected at runtime via STORAGE_DRIVER. This means switching from local filesystem storage in development to AWS S3 in production requires only a configuration change — no application code is modified.
Storage Drivers
Two drivers are available:
| Driver | STORAGE_DRIVER value | Best for |
|---|
LocalStorage | local | Local development, single-server deployments |
S3Storage | s3 | Production, multi-instance, durable object storage |
Use Cases
The storage layer handles three main categories of files:
- Product images — catalog photos uploaded through the business portal
- Delivery evidence photos — photos taken by couriers upon delivery
- Export files — CSV/XLSX reports generated by finance and operations modules
Configuration
# .env
STORAGE_DRIVER=local
UPLOAD_FOLDER=storage/uploads # Relative to the backend working directory
MAX_CONTENT_LENGTH_MB=10 # Maximum upload size in megabytes
The LocalStorage driver stores files under UPLOAD_FOLDER, organized by subfolder. Each file is saved with a UUID-based name to avoid collisions:# app/infrastructure/external/storage/local_storage.py
class LocalStorage:
def __init__(self, base_path: Path) -> None:
self._base = ensure_dir(base_path)
def save_bytes(self, folder: str, data: bytes, suffix: str = "") -> str:
d = ensure_dir(self._base / folder)
name = f"{uuid4().hex}{suffix}"
path = d / name
path.write_bytes(data)
return str(path.relative_to(self._base))
Uploaded files are served directly by the Flask application from the UPLOAD_FOLDER path. This is suitable for local development but should not be used in multi-instance deployments because files are not shared across processes or servers.# .env
STORAGE_DRIVER=s3
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=<your_secret>
AWS_REGION=us-east-1 # e.g. us-east-1, sa-east-1
AWS_S3_BUCKET=zippi-uploads-prod
AWS_S3_BASE_URL=https://zippi-uploads-prod.s3.amazonaws.com
The S3Storage driver uploads files to the configured bucket and returns a public or pre-signed URL. The put_object interface accepts a key, raw bytes, and an optional content type:# app/infrastructure/external/storage/s3_storage.py
class S3Storage:
"""S3-compatible upload/download (implemented with boto3)."""
def put_object(
self,
key: str,
body: bytes,
content_type: str = "application/octet-stream",
) -> str:
... # returns the public URL of the stored object
The IAM user or role associated with AWS_ACCESS_KEY_ID must have at minimum the following permissions on AWS_S3_BUCKET:{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::zippi-uploads-prod/*"
}
If you are serving files publicly, also configure a bucket policy that grants s3:GetObject to "Principal": "*" for the uploads prefix. For private files, generate pre-signed URLs instead of using AWS_S3_BASE_URL directly.
Switching Between Drivers
To move from local development storage to S3:
- Set
STORAGE_DRIVER=s3 in your production .env.
- Fill in all
AWS_* variables.
- Restart the backend — no code change required.
To revert to local storage for debugging:
STORAGE_DRIVER=local
UPLOAD_FOLDER=storage/uploads
The Port/Adapter Pattern
Both drivers implement the same internal interface. Application code calls the storage port without knowing which driver is active:
Application layer
└── calls StoragePort.save(folder, data, suffix)
│
▼
Infrastructure layer resolves the driver:
STORAGE_DRIVER=local → LocalStorage.save_bytes(...)
STORAGE_DRIVER=s3 → S3Storage.put_object(...)
This design means you can add a new driver (e.g. Google Cloud Storage, Cloudflare R2) by writing a new adapter class that satisfies the interface — the rest of the application does not change.
File Size Limits
MAX_CONTENT_LENGTH_MB controls the maximum allowed upload size, enforced at the Flask request layer before the file reaches the storage driver. The default is 10 MB. For larger delivery evidence videos, increase this value and ensure your reverse proxy (nginx, ALB) allows the corresponding body size.