Allowing unauthenticated users to see scopes

This commit is contained in:
Ian Roddis committed 2026-08-18 13:44:55 -03:00
1 parent 2ce4f91e4f
commit 3253d8e858
8 files changed
+136 -27

No files matched your search

+45 -10
View File
@@ -5,7 +5,7 @@ non-Scope part type listing the accessories that fit each scope. Supports HTMX
search/filter over scope attributes and required accessory fits.
"""
from fastapi import APIRouter, Depends, Form, Request
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
from fastapi.responses import HTMLResponse
from sqlalchemy.ext.asyncio import AsyncSession
@@ -16,6 +16,7 @@ from app.crud import manufacturer as manufacturer_crud
from app.crud import part_type as part_type_crud
from app.crud import scope_model as scope_model_crud
from app.crud import sub_part_type as sub_part_type_crud
from app.deps import get_optional_web_user
from app.models.user import User
from app.services import scope_guide as scope_guide_service
from app.web.admin import web_require
@@ -27,17 +28,39 @@ router = APIRouter(include_in_schema=False)
_PAGE_SIZE = 50
_TABLE_TARGET = "#scopes-table-card"
_FILTER_PATH = "/scopes/filter"
_REQUIRED_PERMS = {"augmented_parts:read", "fits:read"}
def _to_int(value: str) -> int | None:
return int(value) if value.strip() else None
def _can_write_scopes(user: User) -> bool:
return user.is_superuser or "augmented_parts:write" in user.permissions
def _can_write_scopes(user: User | None) -> bool:
return user is not None and (
user.is_superuser or "augmented_parts:write" in user.permissions
)
async def _build(db: AsyncSession, filters: dict) -> dict:
async def _optional_scopes_user(
user: User | None = Depends(get_optional_web_user),
) -> User | None:
"""Anyone may view the scopes page; logged-in users still need the read perms.
Anonymous visitors are allowed through (and get an exact-fits-only view —
see ``only_exact`` in ``scope_guide.build``); a logged-in user missing the
permissions gets the usual 403, matching ``web_require``'s behaviour.
"""
if user is not None and not (
user.is_superuser or _REQUIRED_PERMS.issubset(user.permissions)
):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have access to this page.",
)
return user
async def _build(db: AsyncSession, filters: dict, only_exact: bool = False) -> dict:
"""Run the guide query for the given raw (string) filter values.
Always returns the full matching row set — pagination is applied by the
@@ -50,17 +73,22 @@ async def _build(db: AsyncSession, filters: dict) -> dict:
scope_model_id=_to_int(filters["scope_model_id"]),
sub_part_type_id=_to_int(filters["sub_part_type_id"]),
fits_part_type_id=_to_int(filters["fits_part_type_id"]),
only_exact=only_exact,
)
async def _paginated_guide(
db: AsyncSession, filters: dict, page: int, page_size: int
db: AsyncSession,
filters: dict,
page: int,
page_size: int,
only_exact: bool = False,
) -> tuple[dict, dict]:
"""Run the guide query, then slice its rows to the requested page.
Returns ``(guide, pagination)`` where ``guide["rows"]`` is already sliced.
"""
guide = await _build(db, filters)
guide = await _build(db, filters, only_exact=only_exact)
all_rows = guide["rows"]
pagination = paginate(
_FILTER_PATH, filters, page, page_size, len(all_rows), _TABLE_TARGET
@@ -95,7 +123,7 @@ async def scopes_page(
sub_part_type_id: str = "",
fits_part_type_id: str = "",
db: AsyncSession = Depends(get_db),
user: User = Depends(web_require("augmented_parts:read", "fits:read")),
user: User | None = Depends(_optional_scopes_user),
):
filters = {
"description": description,
@@ -104,7 +132,10 @@ async def scopes_page(
"sub_part_type_id": sub_part_type_id,
"fits_part_type_id": fits_part_type_id,
}
guide, pagination = await _paginated_guide(db, filters, 1, _PAGE_SIZE)
only_exact = user is None
guide, pagination = await _paginated_guide(
db, filters, 1, _PAGE_SIZE, only_exact=only_exact
)
return templates.TemplateResponse(
request,
"scopes.html",
@@ -112,6 +143,7 @@ async def scopes_page(
"user": user,
"filters": filters,
"can_write_scopes": _can_write_scopes(user),
"only_exact": only_exact,
"pagination": pagination,
**guide,
**(await _filter_options(db)),
@@ -130,7 +162,7 @@ async def scopes_filter(
page: int = 1,
page_size: int = _PAGE_SIZE,
db: AsyncSession = Depends(get_db),
user: User = Depends(web_require("augmented_parts:read", "fits:read")),
user: User | None = Depends(_optional_scopes_user),
):
filters = {
"description": description,
@@ -139,7 +171,10 @@ async def scopes_filter(
"sub_part_type_id": sub_part_type_id,
"fits_part_type_id": fits_part_type_id,
}
guide, pagination = await _paginated_guide(db, filters, page, page_size)
only_exact = user is None
guide, pagination = await _paginated_guide(
db, filters, page, page_size, only_exact=only_exact
)
return templates.TemplateResponse(
request,
"_scopes_table_card.html",