diff --git a/.forgejo/workflows/deploy_backend.yml b/.forgejo/workflows/deploy_backend.yml new file mode 100644 index 0000000..629e6d2 --- /dev/null +++ b/.forgejo/workflows/deploy_backend.yml @@ -0,0 +1,60 @@ +name: Build and Deploy Backend + +on: + push: + branches: + - main + paths: + - "backend/**" + - "pyproject.toml" + - "uv.lock" + - "deploy/deploy-backend.sh" + +jobs: + build_and_deploy: + runs-on: roadrunner + + container: + volumes: + - /opt/blauwasser-api:/opt/blauwasser-api + - /var/lib/blauwasser-api:/var/lib/blauwasser-api + + steps: + - name: Repository auschecken + uses: actions/checkout@v4 + + - name: Python einrichten + uses: actions/setup-python@v5 + with: + python-version: "3.14" + + # - name: Backend testen + # working-directory: backend + # run: | + # python -m pip install -U pip + # python -m pip install -e . + # pytest + + - name: Backend kopieren + run: | + rsync -a --delete \ + --exclude '.venv' \ + --exclude '__pycache__' \ + --exclude '*.db' \ + backend/ /opt/blauwasser-api/backend/ + cp pyproject.toml /opt/blauwasser-api/ + cp uv.lock /opt/blauwasser-api/ + + - name: Abhängigkeiten aktualisieren + run: | + cd /opt/blauwasser-api + uv sync --frozen + + - name: Backend neu starten + run: | + systemctl restart blauwasser-api + systemctl is-active --quiet blauwasser-api + + - name: Deployment prüfen + run: | + curl --fail https://api.blauwasser-mausis.de/health diff --git a/backend/pyproject.toml b/backend/pyproject.toml index f989521..51c677d 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -17,6 +17,7 @@ dependencies = [ "fastapi-crons==2.5.0", "feedparser==6.0.14", "PyJWT==2.15.0", + "python-dotenv>=1.0.0", ] [project.scripts] diff --git a/backend/src/backend/.env b/backend/src/backend/.env index 5373d21..7adc803 100644 --- a/backend/src/backend/.env +++ b/backend/src/backend/.env @@ -1,6 +1,6 @@ BACKEND_URL="http://localhost:8000" FRONTEND_URL="http://localhost:4321" -SECRET="IamALocalSecretAndMustBeReplacedInProduction" +JWT_SECRET="IamALocalSecretAndMustBeReplacedInProduction" ENVIRONMENT="local" MAILTRAP_API_TOKEN="FakedyFake" -DB_NAME="dev" \ No newline at end of file +DATABASE_URL="sqlite:///dev.db" \ No newline at end of file diff --git a/backend/src/backend/__pycache__/config.cpython-314.pyc b/backend/src/backend/__pycache__/config.cpython-314.pyc index 72a94f4..35f9faa 100644 Binary files a/backend/src/backend/__pycache__/config.cpython-314.pyc and b/backend/src/backend/__pycache__/config.cpython-314.pyc differ diff --git a/backend/src/backend/__pycache__/main.cpython-314.pyc b/backend/src/backend/__pycache__/main.cpython-314.pyc index 76bdec7..a6b028d 100644 Binary files a/backend/src/backend/__pycache__/main.cpython-314.pyc and b/backend/src/backend/__pycache__/main.cpython-314.pyc differ diff --git a/backend/src/backend/config.py b/backend/src/backend/config.py index ecc7090..fe5f0c7 100644 --- a/backend/src/backend/config.py +++ b/backend/src/backend/config.py @@ -1,8 +1,8 @@ import os ENVIRONMENT = os.environ["ENVIRONMENT"] -SECRET_KEY = os.environ["SECRET"] +JWT_SECRET = os.environ["JWT_SECRET"] FRONTEND_URL = os.environ["FRONTEND_URL"] BACKEND_URL = os.environ["BACKEND_URL"] MAILTRAP_API_TOKEN = os.environ["MAILTRAP_API_TOKEN"] -DB_NAME = os.environ["DB_NAME"] \ No newline at end of file +DATABASE_URL = os.environ["DATABASE_URL"] \ No newline at end of file diff --git a/backend/src/backend/internal/__pycache__/authentication.cpython-314.pyc b/backend/src/backend/internal/__pycache__/authentication.cpython-314.pyc index 22f024c..5733d35 100644 Binary files a/backend/src/backend/internal/__pycache__/authentication.cpython-314.pyc and b/backend/src/backend/internal/__pycache__/authentication.cpython-314.pyc differ diff --git a/backend/src/backend/internal/__pycache__/database.cpython-314.pyc b/backend/src/backend/internal/__pycache__/database.cpython-314.pyc index d9d2f91..1cc3525 100644 Binary files a/backend/src/backend/internal/__pycache__/database.cpython-314.pyc and b/backend/src/backend/internal/__pycache__/database.cpython-314.pyc differ diff --git a/backend/src/backend/internal/authentication.py b/backend/src/backend/internal/authentication.py index ab3c901..3546dad 100644 --- a/backend/src/backend/internal/authentication.py +++ b/backend/src/backend/internal/authentication.py @@ -1,6 +1,5 @@ -from typing import Any -from backend.config import SECRET_KEY -import os + +from backend.config import JWT_SECRET from fastapi.security import HTTPBasicCredentials from fastapi import Security from fastapi.security import HTTPBearer @@ -19,13 +18,13 @@ from datetime import timedelta ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_DAYS = 360 +ACCESS_TOKEN_EXPIRE_DAYS = 180 def create_access_token(data: dict, expires_delta: timedelta | None = None): to_encode = data.copy() expire = datetime.now(timezone.utc) + (expires_delta or timedelta(days=ACCESS_TOKEN_EXPIRE_DAYS)) to_encode.update({"exp": expire}) - return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + return jwt.encode(to_encode, JWT_SECRET, algorithm=ALGORITHM) security = HTTPBearer() @@ -36,7 +35,7 @@ def get_token(credentials = Security(security)) -> str: raise HTTPException(status_code=403, detail="Invalid authorization format") def decode_token(token: str)-> str | None: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + payload = jwt.decode(token, JWT_SECRET, algorithms=[ALGORITHM]) return payload.get("sub") async def get_current_user(token: str = Depends(get_token), session: Session = Depends(get_session)): diff --git a/backend/src/backend/internal/database.py b/backend/src/backend/internal/database.py index 12ad585..2d051b1 100644 --- a/backend/src/backend/internal/database.py +++ b/backend/src/backend/internal/database.py @@ -1,10 +1,10 @@ -from backend.config import DB_NAME +from backend.config import DATABASE_URL from collections.abc import Generator from sqlmodel import Session from sqlmodel import SQLModel from sqlmodel import create_engine -sqlite_url = f"sqlite:///{DB_NAME}.db" +sqlite_url = DATABASE_URL connect_args = {"check_same_thread": False} engine = create_engine(sqlite_url, connect_args=connect_args) diff --git a/backend/src/backend/main.py b/backend/src/backend/main.py index d81fb80..3bcb65b 100644 --- a/backend/src/backend/main.py +++ b/backend/src/backend/main.py @@ -1,7 +1,8 @@ -from dotenv import load_dotenv + +from dotenv.main import load_dotenv load_dotenv() -from .routers import users, comments, newsletters +from .routers import users, comments, newsletters, health from backend.internal.database import create_db_and_tables from fastapi import FastAPI from fastapi_crons import Crons @@ -26,4 +27,5 @@ def on_startup(): app.include_router(users.router) app.include_router(comments.router) -app.include_router(newsletters.router) \ No newline at end of file +app.include_router(newsletters.router) +app.include_router(health.router) \ No newline at end of file diff --git a/backend/src/backend/routers/health.py b/backend/src/backend/routers/health.py new file mode 100644 index 0000000..fcfe611 --- /dev/null +++ b/backend/src/backend/routers/health.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/health", include_in_schema=False) +def health(): + return {"status": "ok"} \ No newline at end of file diff --git a/backend/uv.lock b/backend/uv.lock index 573bc4d..6b0c411 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -152,6 +152,7 @@ dependencies = [ { name = "mailtrap" }, { name = "pyjwt" }, { name = "pytest" }, + { name = "python-dotenv" }, { name = "sqlmodel" }, ] @@ -165,6 +166,7 @@ requires-dist = [ { name = "mailtrap", specifier = "==2.10.0" }, { name = "pyjwt", specifier = "==2.15.0" }, { name = "pytest", specifier = ">=9.1.1" }, + { name = "python-dotenv", specifier = ">=1.0.0" }, { name = "sqlmodel", specifier = ">=0.0.42" }, ]