mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-03 19:40:16 -04:00
Intialize API with basic user methods
This commit is contained in:
parent
5a4f04dcfc
commit
c71d5d4026
1
backend/api/__init__.py
Normal file
1
backend/api/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
"""Expose API routes via FastAPI routers from this package."""
|
0
backend/api/authentication.py
Normal file
0
backend/api/authentication.py
Normal file
19
backend/api/health.py
Normal file
19
backend/api/health.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
"""Confirm system health via monitorable API end points.
|
||||
|
||||
Production systems monitor these end points upon deployment, and at regular intervals, to ensure the service is running.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from ..services.health import HealthService
|
||||
|
||||
openapi_tags = {
|
||||
"name": "System Health",
|
||||
"description": "Production systems monitor these end points upon deployment, and at regular intervals, to ensure the service is running.",
|
||||
}
|
||||
|
||||
api = APIRouter(prefix="/api/health")
|
||||
|
||||
|
||||
@api.get("", tags=["System Health"])
|
||||
def health_check(health_svc: HealthService = Depends()) -> str:
|
||||
return health_svc.check()
|
25
backend/api/user.py
Normal file
25
backend/api/user.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from fastapi import APIRouter, Depends
|
||||
from ..services import UserService
|
||||
from ..models.user_model import User, UserTypeEnum
|
||||
|
||||
from typing import List
|
||||
|
||||
api = APIRouter(prefix="/api/user")
|
||||
|
||||
openapi_tags = {
|
||||
"name": "Users",
|
||||
"description": "User profile search and related operations.",
|
||||
}
|
||||
|
||||
|
||||
# TODO: Add security using HTTP Bearer Tokens
|
||||
# TODO: Enable authorization by passing user uuid to API
|
||||
# TODO: Create custom exceptions
|
||||
@api.get("", response_model=List[User], tags=["Users"])
|
||||
def get_all(user_id: str, user_svc: UserService = Depends()):
|
||||
subject = user_svc.get_user_by_uuid(user_id)
|
||||
|
||||
if subject.role != UserTypeEnum.ADMIN:
|
||||
raise Exception(f"Insufficient permissions for user {subject.uuid}")
|
||||
|
||||
return user_svc.all()
|
|
@ -21,7 +21,6 @@ engine = sqlalchemy.create_engine(_engine_str(), echo=True)
|
|||
|
||||
def db_session():
|
||||
"""Generator function offering dependency injection of SQLAlchemy Sessions."""
|
||||
print("ran")
|
||||
session = Session(engine)
|
||||
try:
|
||||
yield session
|
||||
|
|
|
@ -41,6 +41,7 @@ class UserEntity(EntityBase):
|
|||
)
|
||||
experience: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
group: Mapped[str] = mapped_column(String(50))
|
||||
uuid: Mapped[str] = mapped_column(String, nullable=True)
|
||||
|
||||
@classmethod
|
||||
def from_model(cls, model: User) -> Self:
|
||||
|
@ -62,6 +63,7 @@ class UserEntity(EntityBase):
|
|||
program=model.program,
|
||||
experience=model.experience,
|
||||
group=model.group,
|
||||
uuid=model.uuid,
|
||||
)
|
||||
|
||||
def to_model(self) -> User:
|
||||
|
@ -83,4 +85,5 @@ class UserEntity(EntityBase):
|
|||
program=self.program,
|
||||
role=self.role,
|
||||
created_at=self.created_at,
|
||||
uuid=self.uuid,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.middleware.gzip import GZipMiddleware
|
||||
|
||||
from .api import user, health
|
||||
|
||||
description = """
|
||||
Welcome to the **COMPASS** RESTful Application Programming Interface.
|
||||
"""
|
||||
|
||||
app = FastAPI(
|
||||
title="Compass API",
|
||||
version="0.0.1",
|
||||
description=description,
|
||||
openapi_tags=[user.openapi_tags, health.openapi_tags],
|
||||
)
|
||||
|
||||
app.add_middleware(GZipMiddleware)
|
||||
|
||||
feature_apis = [user, health]
|
||||
|
||||
for feature_api in feature_apis:
|
||||
app.include_router(feature_api.api)
|
||||
|
||||
|
||||
# Add application-wide exception handling middleware for commonly encountered API Exceptions
|
||||
@app.exception_handler(Exception)
|
||||
def permission_exception_handler(request: Request, e: Exception):
|
||||
return JSONResponse(status_code=403, content={"message": str(e)})
|
|
@ -15,3 +15,4 @@ class User(BaseModel):
|
|||
program: List[ProgramTypeEnum]
|
||||
role: UserTypeEnum
|
||||
created_at: Optional[datetime]
|
||||
uuid: str | None = None
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import Session
|
||||
import subprocess
|
||||
|
||||
from ..database import engine, _engine_str
|
||||
from ..env import getenv
|
||||
from .. import entities
|
||||
|
||||
from ..test.services import user_test_data
|
||||
|
||||
database = getenv("POSTGRES_DATABASE")
|
||||
|
||||
engine = create_engine(_engine_str(), echo=True)
|
||||
|
@ -16,3 +19,6 @@ subprocess.run(["python3", "-m", "backend.script.create_database"])
|
|||
|
||||
entities.EntityBase.metadata.drop_all(engine)
|
||||
entities.EntityBase.metadata.create_all(engine)
|
||||
|
||||
with Session(engine) as session:
|
||||
user_test_data.insert_fake_data(session)
|
||||
|
|
27
backend/services/health.py
Normal file
27
backend/services/health.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
"""
|
||||
Verify connectivity to the database from the service layer for health check purposes.
|
||||
|
||||
The production system will regularly check the health of running containers via accessing an API endpoint.
|
||||
The API endpoint is backed by this service which executes a simple statement against our backing database.
|
||||
In more complex deployments, where multiple backing services may be depended upon, the health check process
|
||||
would necessarily also become more complex to reflect the health of all subsystems.
|
||||
|
||||
In this context health does not refer to correctness as much as running, connected, and responsive.
|
||||
"""
|
||||
|
||||
from fastapi import Depends
|
||||
from sqlalchemy import text
|
||||
from ..database import Session, db_session
|
||||
|
||||
|
||||
class HealthService:
|
||||
_session: Session
|
||||
|
||||
def __init__(self, session: Session = Depends(db_session)):
|
||||
self._session = session
|
||||
|
||||
def check(self):
|
||||
stmt = text("SELECT 'OK', NOW()")
|
||||
result = self._session.execute(stmt)
|
||||
row = result.all()[0]
|
||||
return str(f"{row[0]} @ {row[1]}")
|
|
@ -26,6 +26,21 @@ class UserService:
|
|||
|
||||
return user_entity.to_model()
|
||||
|
||||
def get_user_by_uuid(self, uuid: str) -> User:
|
||||
"""
|
||||
Gets a user by uuid from the database
|
||||
|
||||
Returns: A User Pydantic model
|
||||
|
||||
"""
|
||||
query = select(UserEntity).where(UserEntity.uuid == uuid)
|
||||
user_entity: UserEntity | None = self._session.scalar(query)
|
||||
|
||||
if user_entity is None:
|
||||
raise Exception(f"No user found with matching uuid: {uuid}")
|
||||
|
||||
return user_entity.to_model()
|
||||
|
||||
def all(self) -> list[User]:
|
||||
"""
|
||||
Returns a list of all Users
|
||||
|
|
|
@ -13,6 +13,7 @@ roles = UserTypeEnum
|
|||
|
||||
volunteer = User(
|
||||
id=1,
|
||||
uuid="test1",
|
||||
username="volunteer",
|
||||
email="volunteer@compass.com",
|
||||
experience=1,
|
||||
|
@ -24,6 +25,7 @@ volunteer = User(
|
|||
|
||||
employee = User(
|
||||
id=2,
|
||||
uuid="test2",
|
||||
username="employee",
|
||||
email="employee@compass.com",
|
||||
experience=5,
|
||||
|
@ -35,6 +37,7 @@ employee = User(
|
|||
|
||||
admin = User(
|
||||
id=3,
|
||||
uuid="test3",
|
||||
username="admin",
|
||||
email="admin@compass.com",
|
||||
experience=10,
|
||||
|
@ -51,6 +54,7 @@ admin = User(
|
|||
newUser = User(
|
||||
id=4,
|
||||
username="new",
|
||||
uuid="test4",
|
||||
email="new@compass.com",
|
||||
experience=1,
|
||||
group="volunteer",
|
||||
|
|
Loading…
Reference in New Issue
Block a user