refactor(config): consolidate into single config.json, storage backend selectable
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@ uploads/*
|
||||
.env
|
||||
server.log
|
||||
s3_config.json
|
||||
config.json
|
||||
scripts/fix_*.py
|
||||
scripts/migrate_*.py
|
||||
scripts/test_*.py
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
"""Image storage abstraction: local filesystem or S3-compatible object storage.
|
||||
|
||||
S3 config is read from ``s3_config.json`` in the project root::
|
||||
All system configuration lives in ``config.json`` in the project root::
|
||||
|
||||
{
|
||||
"enabled": true,
|
||||
"endpoint": "https://s3.amazonaws.com",
|
||||
"bucket": "my-mailova-bucket",
|
||||
"region": "auto",
|
||||
"access_key": "AKIA...",
|
||||
"secret_key": "...",
|
||||
"public_url": "https://cdn.example.com/mailova"
|
||||
"storage": {
|
||||
"backend": "local",
|
||||
"s3": {
|
||||
"endpoint": "https://s3.amazonaws.com",
|
||||
"bucket": "my-mailova-bucket",
|
||||
"region": "auto",
|
||||
"access_key": "AKIA...",
|
||||
"secret_key": "...",
|
||||
"public_url": "https://cdn.example.com/mailova"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
When the file is missing or ``enabled`` is false, local storage is used.
|
||||
``storage.backend``: ``"local"`` (default) or ``"s3"``.
|
||||
|
||||
URL scheme
|
||||
~~~~~~~~~~
|
||||
@@ -33,7 +37,7 @@ from typing import Protocol
|
||||
|
||||
from app.config import UPLOAD_DIR
|
||||
|
||||
S3_CONFIG_PATH = Path(__file__).resolve().parent.parent / "s3_config.json"
|
||||
CONFIG_PATH = Path(__file__).resolve().parent.parent / "config.json"
|
||||
|
||||
|
||||
class StorageBackend(Protocol):
|
||||
@@ -128,40 +132,46 @@ def _strip_prefix(key: str) -> str:
|
||||
return key
|
||||
|
||||
|
||||
# ── Config loading ─────────────────────────────────────────────────
|
||||
|
||||
def load_config() -> dict:
|
||||
"""Load config.json. Returns empty dict if missing."""
|
||||
if not CONFIG_PATH.exists():
|
||||
return {}
|
||||
try:
|
||||
return json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
# ── Singleton accessor ─────────────────────────────────────────────
|
||||
|
||||
_backend: StorageBackend | None = None
|
||||
|
||||
|
||||
def _load_s3_config() -> dict | None:
|
||||
"""Read s3_config.json. Returns None if not configured / disabled."""
|
||||
if not S3_CONFIG_PATH.exists():
|
||||
return None
|
||||
try:
|
||||
cfg = json.loads(S3_CONFIG_PATH.read_text(encoding="utf-8"))
|
||||
if not cfg.get("enabled") or not cfg.get("bucket"):
|
||||
return None
|
||||
return cfg
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def get_storage() -> StorageBackend:
|
||||
"""Get the active storage backend (S3 if configured, otherwise local)."""
|
||||
"""Get the active storage backend based on config.json."""
|
||||
global _backend
|
||||
if _backend is not None:
|
||||
return _backend
|
||||
|
||||
s3_cfg = _load_s3_config()
|
||||
if s3_cfg:
|
||||
_backend = S3Storage(
|
||||
endpoint_url=s3_cfg.get("endpoint", ""),
|
||||
bucket=s3_cfg["bucket"],
|
||||
access_key=s3_cfg.get("access_key", ""),
|
||||
secret_key=s3_cfg.get("secret_key", ""),
|
||||
region=s3_cfg.get("region", "auto"),
|
||||
public_url=s3_cfg.get("public_url", ""),
|
||||
)
|
||||
cfg = load_config()
|
||||
backend = cfg.get("storage", {}).get("backend", "local")
|
||||
|
||||
if backend == "s3":
|
||||
s3 = cfg.get("storage", {}).get("s3", {})
|
||||
if not s3.get("bucket"):
|
||||
# fallback to local if S3 is misconfigured
|
||||
_backend = LocalStorage()
|
||||
else:
|
||||
_backend = S3Storage(
|
||||
endpoint_url=s3.get("endpoint", ""),
|
||||
bucket=s3["bucket"],
|
||||
access_key=s3.get("access_key", ""),
|
||||
secret_key=s3.get("secret_key", ""),
|
||||
region=s3.get("region", "auto"),
|
||||
public_url=s3.get("public_url", ""),
|
||||
)
|
||||
else:
|
||||
_backend = LocalStorage()
|
||||
return _backend
|
||||
|
||||
13
config.example.json
Normal file
13
config.example.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"storage": {
|
||||
"backend": "local",
|
||||
"s3": {
|
||||
"endpoint": "https://s3.amazonaws.com",
|
||||
"bucket": "my-mailova-bucket",
|
||||
"region": "auto",
|
||||
"access_key": "AKIA...",
|
||||
"secret_key": "...",
|
||||
"public_url": "https://cdn.example.com/mailova"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"enabled": false,
|
||||
"endpoint": "https://s3.amazonaws.com",
|
||||
"bucket": "my-mailova-bucket",
|
||||
"region": "auto",
|
||||
"access_key": "AKIA...",
|
||||
"secret_key": "...",
|
||||
"public_url": "https://cdn.example.com/mailova"
|
||||
}
|
||||
Reference in New Issue
Block a user