chart/app/api/meta.py
Chris Amow 3b3c06a1f3 - Drag a selected trendline body to reposition the entire line.
- Duplicate and delete from the line context menu.
     - Copies shift ten bars right.
     - Default names are up and down; copies become up 2, down 2, etc.
     - Exact local data receipt time including seconds.
     - Deployment timestamp removed.
     - Test cleanup no longer deletes drawings created from your browser.
     - JWT password session flow.
2026-08-11 05:46:09 -05:00

65 lines
2 KiB
Python

"""Endpoints that stay reachable without a token.
`bin/wait-deploy` polls /api/version from whatever machine you pushed from, and
the browser needs /api/login before it has a session, so these routes stay
outside the protected API router.
"""
import os
from datetime import datetime, timezone
from fastapi import APIRouter, HTTPException, Request, Response, status
from pydantic import BaseModel
from app.api.deps import (
SESSION_COOKIE,
SESSION_MAX_AGE,
configured_token,
create_session,
password_matches,
token_matches,
)
router = APIRouter(prefix="/api")
# Coolify injects the deployed commit; absent when running locally.
SOURCE_COMMIT = os.environ.get("SOURCE_COMMIT", "dev")
STARTED_AT = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
class LoginRequest(BaseModel):
password: str
@router.get("/health")
def health():
return {"status": "ok", "service": "chart"}
@router.get("/version")
def version():
return {"commit": SOURCE_COMMIT, "started_at": STARTED_AT}
@router.post("/login", status_code=status.HTTP_204_NO_CONTENT)
def login(credentials: LoginRequest, request: Request, response: Response):
presented_token = request.headers.get("x-chart-token", "")
token_login = bool(configured_token(request.app)) and token_matches(
request.app, presented_token
)
if not token_login and not password_matches(request.app, credentials.password):
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Incorrect password")
forwarded_proto = request.headers.get("x-forwarded-proto", "").split(",", 1)[0]
response.set_cookie(
SESSION_COOKIE,
create_session(request.app),
max_age=SESSION_MAX_AGE,
httponly=True,
secure=request.url.scheme == "https" or forwarded_proto == "https",
samesite="strict",
path="/",
)
@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
def logout(response: Response):
response.delete_cookie(SESSION_COOKIE, path="/", httponly=True, samesite="strict")