Vorbereitung Release 0.2.0 #83
+6
-2
@@ -41,6 +41,11 @@ services:
|
|||||||
- kontor-api:0.2.0-SNAPSHOT
|
- kontor-api:0.2.0-SNAPSHOT
|
||||||
image: kontor-api:0.2.0-SNAPSHOT
|
image: kontor-api:0.2.0-SNAPSHOT
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://kontor-api:8800/health"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
networks:
|
networks:
|
||||||
- database
|
- database
|
||||||
- integration
|
- integration
|
||||||
@@ -67,7 +72,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 8200:80
|
- 8200:80
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
kontor-api:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
kontor-vue:
|
kontor-vue:
|
||||||
build:
|
build:
|
||||||
@@ -95,4 +100,3 @@ networks:
|
|||||||
volumes:
|
volumes:
|
||||||
activemq-data:
|
activemq-data:
|
||||||
images-data:
|
images-data:
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ FROM python:3.13-slim-bookworm AS production
|
|||||||
|
|
||||||
#RUN --mount=type=secret,id=secret-key,target=secrets.json
|
#RUN --mount=type=secret,id=secret-key,target=secrets.json
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install --no-install-recommends -y curl
|
||||||
|
|
||||||
RUN useradd --create-home appuser
|
RUN useradd --create-home appuser
|
||||||
USER appuser
|
USER appuser
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from fastapi.security import OAuth2PasswordRequestForm
|
|||||||
from src.core.config import settings
|
from src.core.config import settings
|
||||||
from src.core.security import create_access_token, authenticate_user, get_current_active_user
|
from src.core.security import create_access_token, authenticate_user, get_current_active_user
|
||||||
from src.db.models.admin import Profile
|
from src.db.models.admin import Profile
|
||||||
from src.db.session import SessionDep
|
|
||||||
from src.schema.admin import Token, ProfileModel
|
from src.schema.admin import Token, ProfileModel
|
||||||
from src.webapps.auth.forms import LoginForm
|
from src.webapps.auth.forms import LoginForm
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
from fastapi import APIRouter, status
|
||||||
|
|
||||||
|
from src.schema.admin import HealthCheck
|
||||||
|
|
||||||
|
health_router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@health_router.get(
|
||||||
|
"/health",
|
||||||
|
tags=["healthcheck"],
|
||||||
|
summary="Perform a health check",
|
||||||
|
response_description="Return HTTP status code 200 (OK)",
|
||||||
|
status_code=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
def health_check() -> HealthCheck:
|
||||||
|
"""
|
||||||
|
## Perform a health check
|
||||||
|
Endpoint to perform a healthcheck on. This endpoint can primarily be used Docker
|
||||||
|
to ensure a robust container orchestration and management is in place. Other
|
||||||
|
services which rely on proper functioning of the API service will not deploy if this
|
||||||
|
endpoint returns any other HTTP status code except 200 (OK).
|
||||||
|
:return:
|
||||||
|
HealthCheck: Returns a JSON response with the health status
|
||||||
|
"""
|
||||||
|
return HealthCheck(status="ok")
|
||||||
@@ -34,11 +34,12 @@ LOGGING_CONFIG: dict[str, Any] = {
|
|||||||
},
|
},
|
||||||
"loggers": {
|
"loggers": {
|
||||||
"root": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
"root": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
||||||
|
"kontor": {"handlers": ["default"], "level": "INFO", "propagate": True},
|
||||||
"uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
"uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
||||||
"uvicorn.error": {"level": "INFO"},
|
"uvicorn.error": {"level": "INFO"},
|
||||||
"uvicorn.access": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
"uvicorn.access": {"handlers": ["default"], "level": "WARNING", "propagate": False},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
logging.config.dictConfig(LOGGING_CONFIG)
|
logging.config.dictConfig(LOGGING_CONFIG)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger('kontor')
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class OAuth2PasswordBearerWithCookie(OAuth2):
|
|||||||
def authenticate_user(username: str, password: str) -> Optional[Profile]:
|
def authenticate_user(username: str, password: str) -> Optional[Profile]:
|
||||||
with SessionLocal() as db:
|
with SessionLocal() as db:
|
||||||
user = get_profile(username=username, db=db)
|
user = get_profile(username=username, db=db)
|
||||||
print(user)
|
logger.info(user)
|
||||||
if not user:
|
if not user:
|
||||||
return None
|
return None
|
||||||
if bcrypt.checkpw(password.encode(), user.password.encode()):
|
if bcrypt.checkpw(password.encode(), user.password.encode()):
|
||||||
@@ -103,7 +103,7 @@ async def get_current_user(security_scopes: SecurityScopes, token: Annotated[str
|
|||||||
try:
|
try:
|
||||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
||||||
username: str = payload.get("sub")
|
username: str = payload.get("sub")
|
||||||
print("username/email extracted is ", username)
|
logger.info("username/email extracted is ", username)
|
||||||
if username is None:
|
if username is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
scope: str = payload.get("scope", "")
|
scope: str = payload.get("scope", "")
|
||||||
@@ -147,7 +147,7 @@ def get_current_user_from_token(token: str = Depends(oauth2_scheme)):
|
|||||||
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
||||||
)
|
)
|
||||||
username: str = payload.get("sub")
|
username: str = payload.get("sub")
|
||||||
print("username/email extracted is ", username)
|
logger.info("username/email extracted is ", username)
|
||||||
if username is None:
|
if username is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
except JWTError:
|
except JWTError:
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import databases
|
import databases
|
||||||
|
|
||||||
|
from src.core.log_conf import logger
|
||||||
from src.db.session import SQLALCHEMY_DATABASE_URL
|
from src.db.session import SQLALCHEMY_DATABASE_URL
|
||||||
|
|
||||||
|
|
||||||
@@ -9,7 +11,7 @@ async def check_db_connected():
|
|||||||
if not database.is_connected:
|
if not database.is_connected:
|
||||||
await database.connect()
|
await database.connect()
|
||||||
await database.execute("SELECT 1")
|
await database.execute("SELECT 1")
|
||||||
print("Database is connected (^_^)")
|
logger.info("Database is connected (^_^)")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(
|
print(
|
||||||
"Looks like db is missing or is there is some problem in connection,see below traceback"
|
"Looks like db is missing or is there is some problem in connection,see below traceback"
|
||||||
@@ -23,6 +25,6 @@ async def check_db_disconnected():
|
|||||||
database = databases.Database(SQLALCHEMY_DATABASE_URL)
|
database = databases.Database(SQLALCHEMY_DATABASE_URL)
|
||||||
if database.is_connected:
|
if database.is_connected:
|
||||||
await database.disconnect()
|
await database.disconnect()
|
||||||
print("Database is Disconnected (-_-) zZZ")
|
logger.info("Database is Disconnected (-_-) zZZ")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import logging
|
|
||||||
import logging.config
|
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
@@ -7,7 +5,8 @@ from fastapi.staticfiles import StaticFiles
|
|||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from src.apis.base import api_router
|
from src.apis.base import api_router
|
||||||
from src.core.log_conf import LOGGING_CONFIG, logger
|
from src.apis.version1.healthcheck import health_router
|
||||||
|
from src.core.log_conf import logger
|
||||||
from src.db.session import engine
|
from src.db.session import engine
|
||||||
from src.db.utils import check_db_connected, check_db_disconnected
|
from src.db.utils import check_db_connected, check_db_disconnected
|
||||||
from src.webapps.base import api_router as web_app_router
|
from src.webapps.base import api_router as web_app_router
|
||||||
@@ -24,6 +23,7 @@ async def lifespan(app: FastAPI):
|
|||||||
def include_router(app: FastAPI):
|
def include_router(app: FastAPI):
|
||||||
app.include_router(api_router)
|
app.include_router(api_router)
|
||||||
app.include_router(web_app_router)
|
app.include_router(web_app_router)
|
||||||
|
app.include_router(health_router)
|
||||||
|
|
||||||
def configure_static(app: FastAPI):
|
def configure_static(app: FastAPI):
|
||||||
app.mount("/static", StaticFiles(directory="src/static"), name="static")
|
app.mount("/static", StaticFiles(directory="src/static"), name="static")
|
||||||
|
|||||||
@@ -19,3 +19,9 @@ class ProfileModel(BaseModel):
|
|||||||
first_name: str
|
first_name: str
|
||||||
last_name: str
|
last_name: str
|
||||||
active: bool
|
active: bool
|
||||||
|
|
||||||
|
class HealthCheck(BaseModel):
|
||||||
|
"""
|
||||||
|
Health check model
|
||||||
|
"""
|
||||||
|
status: str = "ok"
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
from typing import AnyStr
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Request, status, responses
|
from fastapi import APIRouter, Request, status, responses
|
||||||
from fastapi.security.utils import get_authorization_scheme_param
|
from fastapi.security.utils import get_authorization_scheme_param
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
from src.core.security import get_current_user_from_token
|
||||||
from src.db.models.media import MediaVideo
|
from src.db.models.media import MediaVideo
|
||||||
from src.db.repository.media import create_new_video
|
from src.db.repository.media import create_new_video
|
||||||
#from src.apis.version1.admin import get_current_user_from_token
|
|
||||||
from src.db.models.admin import Profile
|
from src.db.models.admin import Profile
|
||||||
from src.db.session import SessionDep
|
from src.db.session import SessionDep
|
||||||
from src.schema.media.video import AddLink
|
from src.schema.media.video import AddLink
|
||||||
@@ -28,7 +26,7 @@ def get_mediavideos(db: SessionDep, request: Request, msg: str = None):
|
|||||||
return templates.TemplateResponse("media/videos.html", {"request": request, "msg": msg, "user": None, "mediavideos": mediavideos})
|
return templates.TemplateResponse("media/videos.html", {"request": request, "msg": msg, "user": None, "mediavideos": mediavideos})
|
||||||
|
|
||||||
@router.get("/videos/{video_id}")
|
@router.get("/videos/{video_id}")
|
||||||
def video_details(video_id: AnyStr, request: Request, db: SessionDep):
|
def video_details(video_id: str, request: Request, db: SessionDep):
|
||||||
mediavideo = db.get(MediaVideo, video_id)
|
mediavideo = db.get(MediaVideo, video_id)
|
||||||
return templates.TemplateResponse("media/video_detail.html", {"request": request, "mediavideo":mediavideo})
|
return templates.TemplateResponse("media/video_detail.html", {"request": request, "mediavideo":mediavideo})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user