Add API support for get_document_by_id

This commit is contained in:
jetli 2024-10-19 20:56:19 +00:00
parent c25252ceda
commit 378ae41b39
14 changed files with 154 additions and 49 deletions

5
.gitignore vendored
View File

@ -1 +1,6 @@
.venv/ .venv/
*__pycache__*
.vscode
/deploy/.*
*.log
*.pyc

View File

@ -1,5 +1,5 @@
# Dockerfile for Python Service # Dockerfile for Python Service
FROM python:3.11-slim FROM python:3.10-slim
# Set the working directory inside the container # Set the working directory inside the container
WORKDIR /app WORKDIR /app
@ -16,4 +16,4 @@ EXPOSE 8005
# Run the application using the start script # Run the application using the start script
CMD ["uvicorn", "webapi.main:app", "--reload", "--port=8003", "--host=0.0.0.0"] CMD ["uvicorn", "app.central_storage.webapi.main:app", "--reload", "--port=8005", "--host=0.0.0.0"]

View File

@ -92,6 +92,9 @@ class AzureBlobManager:
app_settings.AZURE_STORAGE_DOCUMENT_API_ENDPOINT, app_settings.AZURE_STORAGE_DOCUMENT_API_ENDPOINT,
credential=app_settings.AZURE_STORAGE_DOCUMENT_API_KEY, credential=app_settings.AZURE_STORAGE_DOCUMENT_API_KEY,
) as blob_service_client: ) as blob_service_client:
# user_delegation_key = await blob_service_client.get_user_delegation_key(
# key_start_time=key_start_time, key_expiry_time=key_expiry_time
# )
blob_client = blob_service_client.get_blob_client( blob_client = blob_service_client.get_blob_client(
container=container_name, blob=file_name container=container_name, blob=file_name
) )

View File

@ -1,3 +1,13 @@
fastapi==0.114.0
fastapi-jwt==0.2.0
pika==1.3.2 pika==1.3.2
fastapi pydantic==2.9.2
uvicorn loguru==0.7.2
uvicorn==0.23.2
beanie==1.21.0
aio-pika
pydantic-settings
python-jose
azure-storage-blob==12.22.0
azure-identity
azure-core[aio]

View File

@ -2,12 +2,12 @@ import logging
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi from fastapi.openapi.utils import get_openapi
from webapi.providers import common from app.central_storage.webapi.providers import common
from webapi.providers import logger from app.central_storage.webapi.providers import logger
from webapi.providers import router from app.central_storage.webapi.providers import router
from webapi.providers import database from app.central_storage.webapi.providers import database
from webapi.providers import scheduler from app.central_storage.webapi.providers import scheduler
from webapi.providers import exception_handler from app.central_storage.webapi.providers import exception_handler
from .freeleaps_app import FreeleapsApp from .freeleaps_app import FreeleapsApp
@ -49,11 +49,7 @@ def customize_openapi_security(app: FastAPI) -> None:
# Add security scheme to components # Add security scheme to components
openapi_schema["components"]["securitySchemes"] = { openapi_schema["components"]["securitySchemes"] = {
"bearerAuth": { "bearerAuth": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"}
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
} }
# Add security requirement globally # Add security requirement globally

View File

@ -1,15 +1,14 @@
from webapi.bootstrap.application import create_app from app.central_storage.webapi.bootstrap.application import create_app
from webapi.config.site_settings import site_settings from app.central_storage.webapi.config.site_settings import site_settings
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from strawberry.fastapi import GraphQLRouter
from strawberry.fastapi.handlers import GraphQLTransportWSHandler, GraphQLWSHandler
import uvicorn import uvicorn
from typing import Any from typing import Any
app = create_app() app = create_app()
@app.get("/", status_code=301) @app.get("/", status_code=301)
async def root(): async def root():
""" """
@ -19,12 +18,16 @@ async def root():
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run(app="main:app", host=site_settings.SERVER_HOST, port=site_settings.SERVER_PORT) uvicorn.run(
app="main:app", host=site_settings.SERVER_HOST, port=site_settings.SERVER_PORT
)
def get_context() -> Any: def get_context() -> Any:
# Define your context function. This is where you can set up authentication, database connections, etc. # Define your context function. This is where you can set up authentication, database connections, etc.
return {} return {}
def get_root_value() -> Any: def get_root_value() -> Any:
# Define your root value function. This can be used to customize the root value for GraphQL operations. # Define your root value function. This can be used to customize the root value for GraphQL operations.
return {} return {}

View File

@ -1,5 +1,5 @@
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from webapi.config.site_settings import site_settings from app.central_storage.webapi.config.site_settings import site_settings
def register(app): def register(app):

View File

@ -1,11 +1,12 @@
from webapi.config.site_settings import site_settings from infra.config.app_settings import app_settings
from beanie import init_beanie from beanie import init_beanie
from motor.motor_asyncio import AsyncIOMotorClient from motor.motor_asyncio import AsyncIOMotorClient
from app.central_storage.backend.models.models import DocumentDoc
def register(app): def register(app):
app.debug = site_settings.DEBUG app.debug = "mongo_debug"
app.title = site_settings.NAME app.title = "mongo_name"
@app.on_event("startup") @app.on_event("startup")
async def start_database(): async def start_database():
@ -13,5 +14,12 @@ def register(app):
async def initiate_database(): async def initiate_database():
#init your database here client = AsyncIOMotorClient(
pass app_settings.MONGODB_URI,
serverSelectionTimeoutMS=60000,
minPoolSize=5, # Minimum number of connections in the pool
maxPoolSize=20, # Maximum number of connections in the pool
)
await init_beanie(
database=client[app_settings.MONGODB_NAME], document_models=[DocumentDoc]
)

View File

@ -1,7 +1,7 @@
import logging import logging
import sys import sys
from loguru import logger from loguru import logger
from common.config.log_settings import log_settings from app.central_storage.common.config.log_settings import log_settings
def register(app=None): def register(app=None):
@ -21,15 +21,8 @@ def register(app=None):
logging.getLogger(name).propagate = True logging.getLogger(name).propagate = True
# configure loguru # configure loguru
logger.add( logger.add(sink=sys.stdout)
sink=sys.stdout logger.add(sink=file_path, level=level, retention=retention, rotation=rotation)
)
logger.add(
sink=file_path,
level=level,
retention=retention,
rotation=rotation
)
logger.disable("pika.adapters") logger.disable("pika.adapters")
logger.disable("pika.connection") logger.disable("pika.connection")

View File

@ -1,4 +1,4 @@
from webapi.routes import api_router from app.central_storage.webapi.routes import api_router
from starlette import routing from starlette import routing

View File

@ -1,5 +1,7 @@
from fastapi import APIRouter from fastapi import APIRouter
from .get_document_by_id import router as doc_router
api_router = APIRouter() api_router = APIRouter()
api_router.include_router(doc_router, tags=["attachment"])
websocket_router = APIRouter() websocket_router = APIRouter()

View File

@ -1,11 +1,11 @@
from fastapi import APIRouter, Security, HTTPException from fastapi import APIRouter, HTTPException, Request
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi_jwt import JwtAuthorizationCredentials from infra.token.token_manager import TokenManager
from backend.infra.authentication.auth import access_security
from app.central_storage.backend.application.document_app import DocumentHub from app.central_storage.backend.application.document_app import DocumentHub
router = APIRouter() router = APIRouter()
token_manager = TokenManager()
# Web API # Web API
@ -19,9 +19,32 @@ router = APIRouter()
) )
async def get_document_by_id( async def get_document_by_id(
document_id: str, document_id: str,
credentials: JwtAuthorizationCredentials = Security(access_security), request: Request,
): ):
user_id = credentials["id"] # Extract the Authorization header
auth_header = request.headers.get("Authorization")
if not auth_header:
raise HTTPException(status_code=401, detail="Authorization header missing")
# Ensure the header starts with 'Bearer'
if not auth_header.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Invalid authorization header")
token = auth_header.split(" ")[1]
try:
# Decode the token using the TokenManager
credentials = token_manager.decode_token(token)
except Exception as e:
raise HTTPException(
status_code=401, detail=f"Invalid or expired token: {str(e)}"
)
# Get the user_id from the decoded token
user_id = credentials.get("id")
if not user_id:
raise HTTPException(status_code=401, detail="Invalid token payload")
# Fetch the document using DocumentHub # Fetch the document using DocumentHub
document = await DocumentHub(user_id).get_document_by_id(document_id) document = await DocumentHub(user_id).get_document_by_id(document_id)

View File

@ -1,15 +1,18 @@
import os
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
class AppSettings():
NAME: str = "myapp" class AppSettings(BaseSettings):
JWT_SECRET_KEY: str = ""
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
REFRESH_TOKEN_EXPIRE_DAYS: int = 30
MONGODB_NAME: str = "freeleaps2"
MONGODB_URI: str = (
"mongodb+srv://freeadmin:0eMV0bt8oyaknA0m@freeleaps2.zmsmpos.mongodb.net/?retryWrites=true&w=majority"
)
class Config: class Config:
env_file = ".myapp.env" env_file = ".myapp.env"
env_file_encoding = "utf-8" env_file_encoding = "utf-8"
APPLICATION_ACTIVITY_LOG: str = "myapp-application-activity"
USER_ACTIVITY_LOG: str = "myapp-user-activity"
BUSINESS_METRIC_LOG: str = "myapp-business-metrics"
app_settings = AppSettings() app_settings = AppSettings()

View File

@ -0,0 +1,59 @@
# application/auth/token/token_manager.py
from datetime import datetime, timedelta, timezone
from typing import Dict
from jose import jwt
from infra.config.app_settings import app_settings
class TokenManager:
def __init__(self):
self.secret_key = app_settings.JWT_SECRET_KEY
self.algorithm = "HS256"
self.access_token_expire_minutes = app_settings.ACCESS_TOKEN_EXPIRE_MINUTES
self.refresh_token_expire_days = app_settings.REFRESH_TOKEN_EXPIRE_DAYS
def create_access_token(self, subject: Dict[str, str]) -> str:
"""
Generates an access token with a short expiration time.
Args:
subject (Dict[str, str]): A dictionary containing user information like 'id' and 'role'.
Returns:
str: Encoded JWT access token.
"""
expire = datetime.now(timezone.utc) + timedelta(
minutes=self.access_token_expire_minutes
)
to_encode = subject.copy()
to_encode.update({"exp": expire})
return jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm)
def create_refresh_token(self, subject: Dict[str, str]) -> str:
"""
Generates a refresh token with a longer expiration time.
Args:
subject (Dict[str, str]): A dictionary containing user information like 'id' and 'role'.
Returns:
str: Encoded JWT refresh token.
"""
expire = datetime.now(timezone.utc) + timedelta(
days=self.refresh_token_expire_days
)
to_encode = subject.copy()
to_encode.update({"exp": expire})
return jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm)
def decode_token(self, token: str) -> Dict:
"""
Decodes a JWT token and returns the payload.
Args:
token (str): Encoded JWT token.
Returns:
Dict: Decoded token payload.
Raises:
JWTError: If the token is invalid or expired.
"""
return jwt.decode(token, self.secret_key, algorithms=[self.algorithm])