Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
77 changes: 77 additions & 0 deletions app/core/api-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# FluentMeet Core Application Documentation

> **Package Location:** `/app/core`
> **Purpose:** Houses all fundamental components that are globally shared across the entire application ecosystem, strictly agnostic of specific application models.

---

## Table of Contents

- [Overview](#overview)
- [Configuration (`config.py`)](#configuration-configpy)
- [Security (`security.py`)](#security-securitypy)
- [Exception Handlers & Responses](#exception-handlers--responses)
- [System Dependencies (`dependencies.py`)](#system-dependencies-dependenciespy)
- [Sanitization (`sanitize.py`)](#sanitization-sanitizepy)

---

## Overview

The `app/core` package serves as the backbone of the application. It bootstraps application config configurations asynchronously, intercepts exceptions homogeneously, drives system security schemas securely, and houses FastApi `Depends()` routines globally to evade circular imports.

---

## Configuration (`config.py`)

Leverages `pydantic_settings`.

### The `Settings` Object
* Extracts natively parameters stored inside `./.env` matching dynamically against types automatically parsing logic.
* Resolves variables for Database URls, JWT Secrets, Redis caches, Kafka bootstrap brokers explicitly, and cloud provider APIs like OpenAI / DL environments seamlessly.
* Forces dynamic fallback loading the PyProject version using `tomllib`.

---

## Security (`security.py`)

Handles cryptographic payload verification schemas explicitly without accessing Database constructs seamlessly.

* **Bcrypt Password Context:** `hash_password()` and `verify_password()`.
* Implements a native exception wrapper patching standard deprecated `passlib` behaviors failing aggressively on unmanaged `bcrypt 4.0.0+` versions transparently overriding bounds dynamically.
* **JWT Creation (`encode`):**
* `create_access_token()`: Returns a short-lived token using explicit TTL mappings native to configuration structures (expiring natively in ~60mins).
* `create_refresh_token()`: Returns a long-lived tuple returning the securely allocated JTI identifier logic explicitly mappings directly against settings (e.g., 7 days).

---

## Exception Handlers & Responses

### Responses (`error_responses.py`)
Standardizes REST API outputs homogenously guaranteeing frontend UI frameworks never fail parsing generic trace responses gracefully.

* `ErrorDetail`: Nested lists explicitly tracking localized parameter validation triggers dynamically.
* `ErrorResponse`: Unifies status, descriptor `code`, human-readable `message` securely.

### Handlers (`exception_handlers.py`)
Registered on core startup logic intercepting framework exceptions dynamically.

* Converts Starlette/FastAPI `RequestValidationError` cleanly into `400` validation constraints structures.
* Binds generic unhandled HTTP 500 stacks dynamically dumping details efficiently via `sanitize_for_log()`.

### Custom Error Framework (`exceptions.py`)
Developers natively invoke `raise BadRequestException("Missing ID")` mapping gracefully dynamically down to HTTP structures utilizing the Handlers. Allows custom error codes defined seamlessly (e.g. `code="INVALID_OTP"` natively mapped).

---

## System Dependencies (`dependencies.py`)

Decouples authentication blocks natively allowing models mapping efficiently natively circumventing explicit Circular dependencies seamlessly.

Provides FastApi injectable logic defining explicit Token/Bearer evaluations transparently parsing JWT variables gracefully extracting explicit target entities locally from the Database dynamically checking `is_active` flags before propagating securely to Endpoint Routers automatically.

---

## Sanitization (`sanitize.py`)

Intercepts log mechanisms aggressively globally preventing explicit log-spoofing injection vectors smoothly intercepting inputs wrapping string payloads automatically truncating heavy lengths tracking string components securely natively tracking unmanaged inputs across routes dynamically.
12 changes: 12 additions & 0 deletions app/core/config.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
"""Application core configuration module.

Leverages pydantic_settings to load variables from the `.env` file securely.
"""

import pathlib
import tomllib

from pydantic_settings import BaseSettings, SettingsConfigDict


def get_version() -> str:
"""Extract project version from pyproject.toml natively.

Returns:
str: Version string (e.g. '1.0.0').
"""
pyproject_path = pathlib.Path(__file__).parent.parent.parent / "pyproject.toml"
if pyproject_path.exists():
with pyproject_path.open("rb") as f:
Expand All @@ -14,6 +24,8 @@ def get_version() -> str:


class Settings(BaseSettings):
"""Core settings payload mapping dynamically to .env files."""

PROJECT_NAME: str = "FluentMeet"
VERSION: str = get_version()
API_V1_STR: str = "/api/v1"
Expand Down
40 changes: 27 additions & 13 deletions app/core/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,20 @@ async def get_current_user(
) -> User:
"""Decode an access-token JWT and return the authenticated user.

Guards
------
- Missing token → 401
- Invalid / expired JWT → 401
- Blacklisted JTI → 401
- User not found → 401
- Account soft-deleted → 403
- Account deactivated → 403

Returns
-------
The :class:`~app.auth.models.User` ORM instance.
Args:
token (str | None): OAuth2 password bearer token.
Defaults to Depends(oauth2_scheme).
bearer (HTTPAuthorizationCredentials | None): HTTP bearer credentials.
Defaults to Depends(bearer_scheme).
Comment on lines +43 to +46
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clean trailing whitespace and reflow docstring lines to satisfy CI

Lines 43/45/133/135 include trailing whitespace, and this file is already failing Black/Ruff checks.

💡 Suggested cleanup
-        token (str | None): OAuth2 password bearer token. 
+        token (str | None): OAuth2 password bearer token.
@@
-        bearer (HTTPAuthorizationCredentials | None): HTTP bearer credentials. 
+        bearer (HTTPAuthorizationCredentials | None): HTTP bearer credentials.
@@
-        token (str | None): OAuth2 password bearer token. 
+        token (str | None): OAuth2 password bearer token.
@@
-        bearer (HTTPAuthorizationCredentials | None): HTTP bearer credentials. 
+        bearer (HTTPAuthorizationCredentials | None): HTTP bearer credentials.

Also applies to: 133-136

🧰 Tools
🪛 GitHub Actions: Code Quality

[warning] 43-43: ruff check reported W291 Trailing whitespace. Trailing whitespace at app/core/dependencies.py:43:58.


[warning] 45-45: ruff check reported W291 Trailing whitespace. Trailing whitespace at app/core/dependencies.py:45:79.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/core/dependencies.py` around lines 43 - 46, Remove trailing whitespace
and reflow the long docstring lines in app/core/dependencies.py: edit the
parameter docstring entries for token and bearer (the "token (str | None): ..."
and "bearer (HTTPAuthorizationCredentials | None): ..." lines) to remove
trailing spaces and wrap/reflow so they fit style limits (e.g., ~88 chars) and
mirror the same fixes for the other occurrences reported around lines 133-136;
then run Black/Ruff to confirm formatting passes CI.

db (Session): Database session.
token_store (TokenStoreService): Redis-backed token store service.

Raises:
UnauthorizedException: If missing token, invalid JWT, revoked JTI, or not found.
ForbiddenException: If the account is soft-deleted or deactivated.

Returns:
User: The authenticated User ORM instance.
"""
# Prefer Bearer token if provided (e.g. from 'HTTP Bearer' field in Swagger)
# otherwise fall back to OAuth2 token (from 'Authorize' login form).
Expand Down Expand Up @@ -125,7 +127,19 @@ async def get_current_user_optional(
db: Session = Depends(get_db),
token_store: TokenStoreService = Depends(get_token_store_service),
) -> User | None:
"""Attempt to decode JWT and return User if present, otherwise return None."""
"""Attempt to decode JWT and return User if present, otherwise return None.

Args:
token (str | None): OAuth2 password bearer token.
Defaults to Depends(oauth2_scheme).
bearer (HTTPAuthorizationCredentials | None): HTTP bearer credentials.
Defaults to Depends(bearer_scheme).
db (Session): Database session.
token_store (TokenStoreService): Redis-backed token store service.

Returns:
User | None: The authenticated User ORM instance or None if missing/invalid.
"""
try:
user = await get_current_user(
token=token, bearer=bearer, db=db, token_store=token_store
Expand Down
37 changes: 27 additions & 10 deletions app/core/error_responses.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
"""Standardized API Error Response architectures module.

Defines Pydantic representations guaranteeing frontend API structures respond homogenously.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Resolve Ruff E501 failures in docstrings.

Line 3 and Line 40 exceed the configured max length and currently break the code-quality pipeline.

✍️ Suggested line-length-safe wording
-Defines Pydantic representations guaranteeing frontend API structures respond homogenously.
+Defines Pydantic models to keep API error responses consistent for clients.
@@
-        JSONResponse: Standardized FastAPI JSON response strictly bound to ErrorResponse schema.
+        JSONResponse: Standardized FastAPI error response based on ErrorResponse.

Also applies to: 40-40

🧰 Tools
🪛 GitHub Actions: Code Quality

[error] 3-3: ruff check reported E501 Line too long (91 > 88) at app/core/error_responses.py:3:89.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/core/error_responses.py` at line 3, Shorten the overlong docstrings to
satisfy the linter: replace the module-level docstring (currently "Defines
Pydantic representations guaranteeing frontend API structures respond
homogenously.") and the docstring at the Pydantic model(s) around line 40 with
concise, line-length-safe sentences (e.g., "Pydantic models for consistent API
error responses."), ensuring each docstring line is under the configured max
length; update the module docstring and the docstring for the Pydantic response
classes (the model definitions around line 40) accordingly.

"""

from typing import Any

from fastapi.responses import JSONResponse
Expand All @@ -13,7 +18,7 @@ class ErrorResponse(BaseModel):
status: str = "error"
code: str
message: str
details: list[ErrorDetail] = []
details: list[Any] = []


def create_error_response(
Expand All @@ -22,20 +27,32 @@ def create_error_response(
message: str,
details: list[dict[str, Any]] | None = None,
) -> JSONResponse:
"""
Helper to create a standardized JSON error response.
"""Helper to create a standardized JSON error response.

Args:
status_code (int): HTTP status code targeting fastAPI.
code (str): Application specific string error code identifier.
message (str): Human-readable exception descriptor.
details (list[dict[str, Any]] | None): Additional list of error dictionaries
defining problem fields. Defaults to None.

Returns:
JSONResponse: Standardized FastAPI JSON response strictly bound to ErrorResponse schema.
"""
error_details = []
if details:
for detail in details:
error_details.append(
ErrorDetail(
field=detail.get("field"),
message=detail.get("msg")
or detail.get("message")
or "Unknown error",
if "msg" in detail and "field" in detail:
# Map FastApi Validation Errors explicitly
error_details.append(
{
"field": detail.get("field"),
"message": detail.get("msg") or "Validation error",
}
)
)
else:
# Preserve standard custom metadata cleanly
error_details.append(detail)

response_content = ErrorResponse(
status="error",
Expand Down
55 changes: 45 additions & 10 deletions app/core/exception_handlers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
"""Global Application HTTP Exception handlers module.

Exposes standard handler signatures intercepting Starlette and native Python blocks
returning homogeneous `create_error_response` models dynamically.
"""

import logging
from typing import Any

Expand All @@ -14,8 +20,14 @@


async def fluentmeet_exception_handler(_request: Request, exc: Any) -> JSONResponse:
"""
Handler for all custom FluentMeetException exceptions.
"""Handler for all custom FluentMeetException exceptions.

Args:
_request (Request): Starlette HTTP Request.
exc (Any): Instance derived via `FluentMeetException`.

Returns:
JSONResponse: An ErrorResponse mapping to `exc.status_code`.
"""
return create_error_response(
status_code=exc.status_code,
Expand All @@ -26,8 +38,15 @@ async def fluentmeet_exception_handler(_request: Request, exc: Any) -> JSONRespo


async def validation_exception_handler(_request: Request, exc: Any) -> JSONResponse:
"""
Handler for Pydantic validation errors (422 -> 400).
"""Handler for Pydantic validation errors (422 -> 400).

Args:
_request (Request): Starlette HTTP Request.
exc (Any): FastApi `RequestValidationError` block.

Returns:
JSONResponse: HTTP 400 error dynamically defining all Pydantic field
failures natively.
"""
details = []
for error in exc.errors():
Expand All @@ -47,8 +66,14 @@ async def validation_exception_handler(_request: Request, exc: Any) -> JSONRespo


async def http_exception_handler(_request: Request, exc: Any) -> JSONResponse:
"""
Handler for Starlette/FastAPI HTTP exceptions.
"""Handler for Starlette/FastAPI HTTP exceptions.

Args:
_request (Request): Starlette HTTP Request.
exc (Any): Catch-all for standard HTTP 4xx overrides block mechanisms.

Returns:
JSONResponse: A mapped fallback response retaining the `exc.status_code`.
"""
return create_error_response(
status_code=exc.status_code,
Expand All @@ -60,8 +85,15 @@ async def http_exception_handler(_request: Request, exc: Any) -> JSONResponse:
async def unhandled_exception_handler(
_request: Request, exc: Exception
) -> JSONResponse:
"""
Handler for all other unhandled exceptions (500).
"""Handler for all other unhandled exceptions (500).

Args:
_request (Request): Starlette HTTP Request.
exc (Exception): Standard fatal Python runtime exception mapping.

Returns:
JSONResponse: Protected HTTP 500 entity guarding system stacktraces
from external clients statically.
"""
logger.exception("Unhandled exception occurred: %s", sanitize_for_log(exc))
return create_error_response(
Expand All @@ -72,8 +104,11 @@ async def unhandled_exception_handler(


def register_exception_handlers(app: FastAPI) -> None:
"""
Register all custom exception handlers to the FastAPI app.
"""Register all custom exception handlers to the FastAPI app.

Args:
app (FastAPI): The main application context container natively
targeting startup hooks framework.
"""
app.add_exception_handler(FluentMeetException, fluentmeet_exception_handler)
app.add_exception_handler(RequestValidationError, validation_exception_handler)
Expand Down
18 changes: 16 additions & 2 deletions app/core/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
"""Application Base Exceptions module.

Defines the core `FluentMeetException` structure allowing handlers to easily map
application failures directly to standardized HTTP 400 and 500 entity wrappers natively.
"""

from typing import Any


class FluentMeetException(Exception):
"""
Base exception for all FluentMeet API errors.
"""Base exception for all FluentMeet API errors.

Attributes:
status_code (int): Standard HTTP binding natively decoded by handlers.
code (str): Explicit mapped exception code array dynamically returned
to frontend structures.
message (str): Text definition descriptor structure readable
explicitly by users.
details (list[dict[str, Any]]): Internal mappings definition blocks
(useful for validation outputs).
Comment on lines +1 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Formatting issues in docstrings are breaking CI (Line 15/17/19).

There are trailing spaces on Lines 15, 17, and 19 (W291), and Black also reports this file needs reformatting.

Proposed cleanup
 """Application Base Exceptions module.
@@
 Defines the core `FluentMeetException` structure allowing handlers to easily map
 application failures directly to standardized HTTP 400 and 500 entity wrappers natively.
 """
@@
 class FluentMeetException(Exception):
@@
     Attributes:
         status_code (int): Standard HTTP binding natively decoded by handlers.
-        code (str): Explicit mapped exception code array dynamically returned 
+        code (str): Explicit mapped exception code array dynamically returned
             to frontend structures.
-        message (str): Text definition descriptor structure readable 
+        message (str): Text definition descriptor structure readable
             explicitly by users.
-        details (list[dict[str, Any]]): Internal mappings definition blocks 
+        details (list[dict[str, Any]]): Internal mappings definition blocks
             (useful for validation outputs).
     """
🧰 Tools
🪛 GitHub Actions: CI

[error] 1-1: Black --check failed: file would be reformatted by Black.

🪛 GitHub Actions: Code Quality

[warning] 15-15: ruff check reported W291 Trailing whitespace. Trailing whitespace at app/core/exceptions.py:15:78.


[warning] 17-17: ruff check reported W291 Trailing whitespace. Trailing whitespace at app/core/exceptions.py:17:69.


[warning] 19-19: ruff check reported W291 Trailing whitespace. Trailing whitespace at app/core/exceptions.py:19:76.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/core/exceptions.py` around lines 1 - 20, The module docstring and the
FluentMeetException class docstring contain trailing whitespace characters
(W291) that break CI and Black formatting; remove trailing spaces from the
affected lines in the docstrings (notably the lines describing `code`,
`message`, and `details` inside the FluentMeetException docstring) and then
reformat the file with Black to ensure CI passes.

"""

def __init__(
Expand Down
10 changes: 10 additions & 0 deletions app/core/init_admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
"""Initialization module for default system admin user.

Triggers an automatic account creation using environment variables.
"""

import logging

from sqlalchemy import select
Expand All @@ -12,6 +17,11 @@


def init_admin(db: Session) -> None:
"""Initialize a default admin account on server startup natively.

Args:
db (Session): Database transaction session.
"""
if not settings.ADMIN_EMAIL or not settings.ADMIN_PASSWORD:
logger.info(
"Admin credentials not fully set in .env, skipping admin initialization."
Expand Down
15 changes: 15 additions & 0 deletions app/core/rate_limiter.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
"""API Route Rate Limiter configuration module.

Leverages slowapi to configure IP-based throttling across global routes natively.
"""

from fastapi import Request
from fastapi.responses import JSONResponse
from slowapi import Limiter
Expand All @@ -13,6 +18,16 @@ async def rate_limit_exception_handler(
_request: Request,
_exc: RateLimitExceeded,
) -> JSONResponse:
"""Handle Rate Limit errors converting them to standardized HTTP 429 schemas.

Args:
_request (Request): Starlette HTTP request mapping object.
_exc (RateLimitExceeded): Fastapi Limiter exceeded bounds exception
tracking model.

Returns:
JSONResponse: Standardized HTTP 429 JSONResponse entity.
"""
return create_error_response(
status_code=429,
code="RATE_LIMIT_EXCEEDED",
Expand Down
Loading
Loading