first commit
This commit is contained in:
15
backend/app/routes/__init__.py
Normal file
15
backend/app/routes/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from .analytics import analytics_blueprint
|
||||
from .auth import auth_blueprint
|
||||
from .dashboard import dashboard_blueprint
|
||||
from .health import health_blueprint
|
||||
from .historical import historical_blueprint
|
||||
from .realtime import realtime_blueprint
|
||||
|
||||
__all__ = [
|
||||
"auth_blueprint",
|
||||
"analytics_blueprint",
|
||||
"dashboard_blueprint",
|
||||
"health_blueprint",
|
||||
"historical_blueprint",
|
||||
"realtime_blueprint",
|
||||
]
|
||||
63
backend/app/routes/analytics.py
Normal file
63
backend/app/routes/analytics.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from flask import Blueprint, jsonify, request
|
||||
|
||||
from app.services.analytics import AnalyticsService
|
||||
from app.utils.serialization import to_plain
|
||||
|
||||
analytics_blueprint = Blueprint("analytics", __name__)
|
||||
service = AnalyticsService()
|
||||
|
||||
|
||||
@analytics_blueprint.get("/analytics/production")
|
||||
def production_analytics():
|
||||
range_key = request.args.get("range", "30d")
|
||||
bucket = request.args.get("bucket", "day")
|
||||
compare = request.args.get("compare", "none")
|
||||
start = request.args.get("start")
|
||||
end = request.args.get("end")
|
||||
compare_ranges_raw = request.args.get("compare_ranges", "")
|
||||
compare_ranges = []
|
||||
if compare_ranges_raw:
|
||||
try:
|
||||
compare_ranges = json.loads(compare_ranges_raw)
|
||||
except json.JSONDecodeError as exc:
|
||||
return jsonify({"detail": f"Invalid compare_ranges payload: {exc}"}), 400
|
||||
try:
|
||||
return jsonify(
|
||||
to_plain(
|
||||
service.production(
|
||||
range_key=range_key,
|
||||
bucket=bucket,
|
||||
compare_mode=compare,
|
||||
start=start,
|
||||
end=end,
|
||||
compare_ranges=compare_ranges,
|
||||
)
|
||||
)
|
||||
)
|
||||
except ValueError as exc:
|
||||
return jsonify({"detail": str(exc)}), 400
|
||||
|
||||
|
||||
@analytics_blueprint.get("/analytics/distribution")
|
||||
def production_distribution():
|
||||
range_key = request.args.get("range", "30d")
|
||||
bucket = request.args.get("bucket", "day")
|
||||
start = request.args.get("start")
|
||||
end = request.args.get("end")
|
||||
try:
|
||||
return jsonify(
|
||||
to_plain(
|
||||
service.distribution(
|
||||
range_key=range_key,
|
||||
bucket=bucket,
|
||||
start=start,
|
||||
end=end,
|
||||
)
|
||||
)
|
||||
)
|
||||
except ValueError as exc:
|
||||
return jsonify({"detail": str(exc)}), 400
|
||||
80
backend/app/routes/auth.py
Normal file
80
backend/app/routes/auth.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from flask import Blueprint, jsonify, request
|
||||
|
||||
from app.services.auth import get_auth_service
|
||||
from app.utils.serialization import to_plain
|
||||
|
||||
|
||||
auth_blueprint = Blueprint("auth", __name__)
|
||||
service = get_auth_service()
|
||||
|
||||
|
||||
@auth_blueprint.get("/auth/status")
|
||||
def auth_status():
|
||||
return jsonify(to_plain(service.status()))
|
||||
|
||||
|
||||
@auth_blueprint.post("/auth/login")
|
||||
def auth_login():
|
||||
payload = request.get_json(silent=True) or {}
|
||||
try:
|
||||
status = service.login(payload.get("username", ""), payload.get("password", ""))
|
||||
return jsonify(to_plain(status))
|
||||
except ValueError as exc:
|
||||
return jsonify({"detail": str(exc)}), 401
|
||||
|
||||
|
||||
@auth_blueprint.post("/auth/logout")
|
||||
def auth_logout():
|
||||
return jsonify(to_plain(service.logout()))
|
||||
|
||||
|
||||
@auth_blueprint.get("/auth/users")
|
||||
def list_users():
|
||||
try:
|
||||
service.require_admin()
|
||||
return jsonify(to_plain({"items": service.list_users()}))
|
||||
except PermissionError as exc:
|
||||
return jsonify({"detail": str(exc)}), 403
|
||||
|
||||
|
||||
@auth_blueprint.post("/auth/users")
|
||||
def create_user():
|
||||
payload = request.get_json(silent=True) or {}
|
||||
try:
|
||||
service.require_admin()
|
||||
user = service.create_user(
|
||||
username=payload.get("username", ""),
|
||||
password=payload.get("password", ""),
|
||||
role=payload.get("role", "user"),
|
||||
display_name=payload.get("display_name") or payload.get("username") or "",
|
||||
)
|
||||
return jsonify(to_plain({
|
||||
"username": user.username,
|
||||
"display_name": user.display_name,
|
||||
"role": user.role,
|
||||
"is_active": user.is_active,
|
||||
}))
|
||||
except PermissionError as exc:
|
||||
return jsonify({"detail": str(exc)}), 403
|
||||
except ValueError as exc:
|
||||
return jsonify({"detail": str(exc)}), 400
|
||||
|
||||
|
||||
@auth_blueprint.post("/auth/users/<username>/reset-password")
|
||||
def reset_password(username: str):
|
||||
payload = request.get_json(silent=True) or {}
|
||||
try:
|
||||
service.require_admin()
|
||||
user = service.reset_password(username=username, new_password=payload.get("password", ""))
|
||||
return jsonify(to_plain({
|
||||
"username": user.username,
|
||||
"display_name": user.display_name,
|
||||
"role": user.role,
|
||||
"is_active": user.is_active,
|
||||
}))
|
||||
except PermissionError as exc:
|
||||
return jsonify({"detail": str(exc)}), 403
|
||||
except ValueError as exc:
|
||||
return jsonify({"detail": str(exc)}), 400
|
||||
80
backend/app/routes/dashboard.py
Normal file
80
backend/app/routes/dashboard.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from flask import Blueprint, jsonify, request
|
||||
|
||||
from app.core_settings import get_settings
|
||||
from app.services.capabilities import build_capabilities
|
||||
from app.services.catalog import get_catalog
|
||||
from app.services.kiosk_settings import get_kiosk_settings_service
|
||||
from app.services.auth import get_auth_service
|
||||
from app.utils.serialization import to_plain
|
||||
|
||||
|
||||
dashboard_blueprint = Blueprint("dashboard", __name__)
|
||||
|
||||
|
||||
@dashboard_blueprint.get("/dashboard/config")
|
||||
def dashboard_config():
|
||||
settings = get_settings()
|
||||
catalog = get_catalog()
|
||||
capabilities = build_capabilities(catalog)
|
||||
|
||||
payload = {
|
||||
"app": {
|
||||
"name": settings.app_name,
|
||||
"version": settings.version,
|
||||
"site_name": settings.site_name,
|
||||
"timezone": settings.timezone,
|
||||
"installed_power_kwp": settings.installed_power_kwp,
|
||||
},
|
||||
"defaults": {
|
||||
"realtime_range": settings.realtime["history_default_range"],
|
||||
"analytics_range": settings.analytics["default_range"],
|
||||
"analytics_bucket": settings.analytics["default_bucket"],
|
||||
"tab": settings.frontend_defaults["tab"],
|
||||
"theme": settings.frontend_defaults["theme"],
|
||||
"language": settings.frontend_defaults["language"],
|
||||
},
|
||||
"auth": {
|
||||
"enabled": settings.auth["enabled"],
|
||||
},
|
||||
"i18n": settings.i18n,
|
||||
"capabilities": capabilities,
|
||||
"visible_entities": [
|
||||
{
|
||||
"metric_id": metric.id,
|
||||
"label": metric.label,
|
||||
"entity_id": metric.entity_id,
|
||||
"measurement": metric.measurement,
|
||||
"unit": metric.unit,
|
||||
"kind": metric.kind,
|
||||
}
|
||||
for metric in catalog.visible_entities()
|
||||
],
|
||||
}
|
||||
return jsonify(to_plain(payload))
|
||||
|
||||
|
||||
@dashboard_blueprint.get("/dashboard/kiosk-settings")
|
||||
def dashboard_kiosk_settings():
|
||||
requested_mode = request.args.get("mode") or ("public" if request.args.get("publicKiosk") == "1" else "private")
|
||||
try:
|
||||
payload = get_kiosk_settings_service().get(requested_mode)
|
||||
return jsonify(to_plain(payload))
|
||||
except ValueError as exc:
|
||||
return jsonify({"detail": str(exc)}), 400
|
||||
|
||||
|
||||
@dashboard_blueprint.put("/dashboard/kiosk-settings")
|
||||
def update_dashboard_kiosk_settings():
|
||||
payload = request.get_json(silent=True) or {}
|
||||
mode = payload.get("mode", "private")
|
||||
auth_service = get_auth_service()
|
||||
try:
|
||||
auth_service.require_admin()
|
||||
updated = get_kiosk_settings_service().update_from_session(mode, payload)
|
||||
return jsonify(to_plain(updated))
|
||||
except PermissionError as exc:
|
||||
return jsonify({"detail": str(exc)}), 403
|
||||
except ValueError as exc:
|
||||
return jsonify({"detail": str(exc)}), 400
|
||||
17
backend/app/routes/health.py
Normal file
17
backend/app/routes/health.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from flask import Blueprint, jsonify
|
||||
|
||||
from app.core_settings import get_settings
|
||||
|
||||
health_blueprint = Blueprint("health", __name__)
|
||||
|
||||
|
||||
@health_blueprint.get("/health")
|
||||
def health():
|
||||
settings = get_settings()
|
||||
return jsonify({
|
||||
"status": "ok",
|
||||
"app": settings.app_name,
|
||||
"version": settings.version,
|
||||
})
|
||||
54
backend/app/routes/historical.py
Normal file
54
backend/app/routes/historical.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
from flask import Blueprint, jsonify, request
|
||||
|
||||
from app.services.historical_sync import get_historical_sync_service
|
||||
from app.utils.serialization import to_plain
|
||||
|
||||
historical_blueprint = Blueprint("historical", __name__)
|
||||
service = get_historical_sync_service()
|
||||
|
||||
|
||||
@historical_blueprint.get("/historical/status")
|
||||
def historical_status():
|
||||
return jsonify(to_plain(service.status()))
|
||||
|
||||
|
||||
@historical_blueprint.post("/historical/start")
|
||||
def historical_start():
|
||||
payload = request.get_json(silent=True) or {}
|
||||
try:
|
||||
status = service.start(
|
||||
start_date=_parse_date(payload.get("start_date")),
|
||||
end_date=_parse_date(payload.get("end_date")),
|
||||
chunk_days=payload.get("chunk_days"),
|
||||
force=bool(payload.get("force", False)),
|
||||
)
|
||||
return jsonify(to_plain(status))
|
||||
except ValueError as exc:
|
||||
return jsonify({"detail": str(exc)}), 400
|
||||
except RuntimeError as exc:
|
||||
return jsonify({"detail": str(exc)}), 400
|
||||
|
||||
|
||||
@historical_blueprint.post("/historical/sync-now")
|
||||
def historical_sync_now():
|
||||
try:
|
||||
status = service.start(auto=True)
|
||||
return jsonify(to_plain(status))
|
||||
except RuntimeError as exc:
|
||||
return jsonify({"detail": str(exc)}), 400
|
||||
|
||||
|
||||
@historical_blueprint.post("/historical/cancel")
|
||||
def historical_cancel():
|
||||
return jsonify(to_plain(service.cancel()))
|
||||
|
||||
|
||||
|
||||
def _parse_date(value: str | None) -> date | None:
|
||||
if not value:
|
||||
return None
|
||||
return date.fromisoformat(value)
|
||||
26
backend/app/routes/realtime.py
Normal file
26
backend/app/routes/realtime.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from flask import Blueprint, jsonify, request
|
||||
|
||||
from app.services.realtime import RealtimeService
|
||||
from app.utils.serialization import to_plain
|
||||
|
||||
realtime_blueprint = Blueprint("realtime", __name__)
|
||||
service = RealtimeService()
|
||||
|
||||
|
||||
@realtime_blueprint.get("/realtime/snapshot")
|
||||
def realtime_snapshot():
|
||||
return jsonify(to_plain(service.snapshot()))
|
||||
|
||||
|
||||
@realtime_blueprint.get("/realtime/history")
|
||||
def realtime_history():
|
||||
range_key = request.args.get("range", "6h")
|
||||
start = request.args.get("start")
|
||||
end = request.args.get("end")
|
||||
metrics = [item.strip() for item in request.args.get("metrics", "").split(",") if item.strip()]
|
||||
try:
|
||||
return jsonify(to_plain(service.history(range_key=range_key, start=start, end=end, metric_ids=metrics or None)))
|
||||
except ValueError as exc:
|
||||
return jsonify({"detail": str(exc)}), 400
|
||||
Reference in New Issue
Block a user