Formatting everything with black
This commit is contained in:
1 parent
42a11c0dc6
commit
a3539beda9
20 files changed
+173
-18
No files matched your search
+9
-3
@@ -52,7 +52,9 @@ async def get_role(
|
||||
):
|
||||
role = await role_crud.get(db, role_id)
|
||||
if role is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Role not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Role not found"
|
||||
)
|
||||
return role
|
||||
|
||||
|
||||
@@ -65,7 +67,9 @@ async def update_role(
|
||||
):
|
||||
role = await role_crud.get(db, role_id)
|
||||
if role is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Role not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Role not found"
|
||||
)
|
||||
return await role_crud.update(db, role, payload.model_dump(exclude_unset=True))
|
||||
|
||||
|
||||
@@ -77,5 +81,7 @@ async def delete_role(
|
||||
):
|
||||
role = await role_crud.get(db, role_id)
|
||||
if role is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Role not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Role not found"
|
||||
)
|
||||
await role_crud.delete(db, role)
|
||||
+9
-3
@@ -53,7 +53,9 @@ async def get_user(
|
||||
):
|
||||
user = await user_crud.get(db, user_id)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
@@ -66,7 +68,9 @@ async def update_user(
|
||||
):
|
||||
user = await user_crud.get(db, user_id)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||
)
|
||||
return await user_crud.update(db, user, payload.model_dump(exclude_unset=True))
|
||||
|
||||
|
||||
@@ -78,5 +82,7 @@ async def delete_user(
|
||||
):
|
||||
user = await user_crud.get(db, user_id)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||
)
|
||||
await user_crud.delete(db, user)
|
||||
@@ -3,6 +3,7 @@
|
||||
Settings are read from environment variables first, then from a `.env` file.
|
||||
Environment variables always take precedence over the file.
|
||||
"""
|
||||
|
||||
from functools import lru_cache
|
||||
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
Works with SQLite (aiosqlite) or Postgres (asyncpg) depending on DATABASE_URL.
|
||||
"""
|
||||
|
||||
from collections.abc import AsyncGenerator
|
||||
|
||||
from sqlalchemy.ext.asyncio import (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Password hashing and JWT token helpers."""
|
||||
|
||||
import hashlib
|
||||
import uuid
|
||||
from datetime import UTC, datetime, timedelta
|
||||
@@ -14,6 +15,7 @@ REFRESH_TOKEN_TYPE = "refresh"
|
||||
|
||||
# --- Passwords ---------------------------------------------------------------
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
||||
|
||||
@@ -27,6 +29,7 @@ def verify_password(password: str, hashed: str) -> bool:
|
||||
|
||||
# --- JWT ---------------------------------------------------------------------
|
||||
|
||||
|
||||
def _create_token(
|
||||
subject: str | int,
|
||||
expires_delta: timedelta,
|
||||
|
||||
@@ -66,6 +66,7 @@ async def delete(db: AsyncSession, role: Role) -> None:
|
||||
|
||||
# --- Permissions -------------------------------------------------------------
|
||||
|
||||
|
||||
async def list_permissions(db: AsyncSession) -> list[Permission]:
|
||||
result = await db.execute(select(Permission).order_by(Permission.name))
|
||||
return list(result.scalars().all())
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Shared FastAPI dependencies: DB session, current user, RBAC enforcement."""
|
||||
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, Request, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
@@ -44,6 +45,7 @@ async def _user_from_token(token: str | None, db: AsyncSession) -> User | None:
|
||||
|
||||
# --- JSON API auth -----------------------------------------------------------
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
request: Request,
|
||||
bearer: str | None = Depends(oauth2_scheme),
|
||||
@@ -86,6 +88,7 @@ def require_superuser(user: User = Depends(get_current_user)) -> User:
|
||||
|
||||
# --- Web (HTMX) auth ---------------------------------------------------------
|
||||
|
||||
|
||||
async def get_current_web_user(
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Import all models so they register with ``Base.metadata``."""
|
||||
|
||||
from app.models.associations import role_permissions, user_roles
|
||||
from app.models.permission import Permission
|
||||
from app.models.refresh_token import RefreshToken
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Many-to-many association tables for RBAC."""
|
||||
|
||||
from sqlalchemy import Column, ForeignKey, Table
|
||||
|
||||
from app.core.database import Base
|
||||
@@ -14,5 +15,9 @@ role_permissions = Table(
|
||||
"role_permissions",
|
||||
Base.metadata,
|
||||
Column("role_id", ForeignKey("roles.id", ondelete="CASCADE"), primary_key=True),
|
||||
Column("permission_id", ForeignKey("permissions.id", ondelete="CASCADE"), primary_key=True),
|
||||
Column(
|
||||
"permission_id",
|
||||
ForeignKey("permissions.id", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
),
|
||||
)
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Token issuance / rotation shared by the JSON API and the HTMX UI."""
|
||||
|
||||
import jwt
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -30,7 +31,9 @@ class RefreshError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
async def rotate_refresh_token(db: AsyncSession, raw_refresh: str) -> tuple[str, str, User]:
|
||||
async def rotate_refresh_token(
|
||||
db: AsyncSession, raw_refresh: str
|
||||
) -> tuple[str, str, User]:
|
||||
"""Validate a refresh token, revoke it, and issue a fresh pair.
|
||||
|
||||
Raises ``RefreshError`` if the token is invalid, expired, or already revoked.
|
||||
|
||||
+7
-1
@@ -1,4 +1,5 @@
|
||||
"""HTMX-driven admin dashboard for users and roles."""
|
||||
|
||||
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse, Response
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -34,6 +35,7 @@ async def admin_home():
|
||||
|
||||
# --- Users -------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.get("/users", response_class=HTMLResponse)
|
||||
async def users_page(
|
||||
request: Request,
|
||||
@@ -74,7 +76,10 @@ async def create_user(
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
"admin/_users_table.html",
|
||||
{"users": await user_crud.list_users(db), "roles": await role_crud.list_roles(db)},
|
||||
{
|
||||
"users": await user_crud.list_users(db),
|
||||
"roles": await role_crud.list_roles(db),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -152,6 +157,7 @@ async def delete_user(
|
||||
|
||||
# --- Roles -------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.get("/roles", response_class=HTMLResponse)
|
||||
async def roles_page(
|
||||
request: Request,
|
||||
|
||||
+1
-3
@@ -30,9 +30,7 @@ async def dashboard(
|
||||
request: Request,
|
||||
user: User = Depends(get_current_web_user),
|
||||
):
|
||||
return templates.TemplateResponse(
|
||||
request, "dashboard.html", {"user": user}
|
||||
)
|
||||
return templates.TemplateResponse(request, "dashboard.html", {"user": user})
|
||||
|
||||
|
||||
@router.get("/login", response_class=HTMLResponse)
|
||||
|
||||
Reference in new issue
Block a user