From 422e21718011225b0882739cce5d46f13ea58553 Mon Sep 17 00:00:00 2001 From: Ian Roddis <31021769+iroddis@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:18:06 -0300 Subject: [PATCH] adding static connection --- .../5b6a7817b408_add_static_connections.py | 29 +++++ app/api/router.py | 3 +- app/api/static_connections.py | 92 ++++++++++++++ app/crud/static_connection.py | 52 ++++++++ app/models/__init__.py | 2 + app/models/static_connection.py | 13 ++ app/schemas/static_connection.py | 20 +++ app/templates/_nav.html | 3 + .../admin/_static_connection_row.html | 16 +++ .../admin/_static_connection_row_edit.html | 19 +++ .../admin/_static_connections_table.html | 19 +++ app/templates/admin/static_connections.html | 19 +++ app/web/admin.py | 115 ++++++++++++++++++ scripts/seed.py | 5 +- 14 files changed, 405 insertions(+), 2 deletions(-) create mode 100644 alembic/versions/5b6a7817b408_add_static_connections.py create mode 100644 app/api/static_connections.py create mode 100644 app/crud/static_connection.py create mode 100644 app/models/static_connection.py create mode 100644 app/schemas/static_connection.py create mode 100644 app/templates/admin/_static_connection_row.html create mode 100644 app/templates/admin/_static_connection_row_edit.html create mode 100644 app/templates/admin/_static_connections_table.html create mode 100644 app/templates/admin/static_connections.html diff --git a/alembic/versions/5b6a7817b408_add_static_connections.py b/alembic/versions/5b6a7817b408_add_static_connections.py new file mode 100644 index 0000000..505f4a1 --- /dev/null +++ b/alembic/versions/5b6a7817b408_add_static_connections.py @@ -0,0 +1,29 @@ +"""add static_connections + +Revision ID: 5b6a7817b408 +Revises: 7be5ef599d3f +Create Date: 2026-07-02 11:57:13.095417 +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '5b6a7817b408' +down_revision: Union[str, None] = '7be5ef599d3f' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/app/api/router.py b/app/api/router.py index 72b0f10..725a2a6 100644 --- a/app/api/router.py +++ b/app/api/router.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from app.api import audit_log, auth, imported_parts, iso_threads, manufacturers, part_types, roles, scope_models, sub_part_types, unc_threads, users +from app.api import audit_log, auth, imported_parts, iso_threads, manufacturers, part_types, roles, scope_models, static_connections, sub_part_types, unc_threads, users api_router = APIRouter() api_router.include_router(auth.router, prefix="/auth", tags=["auth"]) @@ -12,5 +12,6 @@ api_router.include_router(manufacturers.router, prefix="/manufacturers", tags=[" api_router.include_router(scope_models.router, prefix="/scope-models", tags=["scope-models"]) api_router.include_router(part_types.router, prefix="/part-types", tags=["part-types"]) api_router.include_router(sub_part_types.router, prefix="/sub-part-types", tags=["sub-part-types"]) +api_router.include_router(static_connections.router, prefix="/static-connections", tags=["static-connections"]) api_router.include_router(imported_parts.router, prefix="/imported-parts", tags=["imported-parts"]) api_router.include_router(audit_log.router, prefix="/audit-log", tags=["audit-log"]) diff --git a/app/api/static_connections.py b/app/api/static_connections.py new file mode 100644 index 0000000..4323c0b --- /dev/null +++ b/app/api/static_connections.py @@ -0,0 +1,92 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.database import get_db +from app.crud import audit as audit_crud +from app.crud import static_connection as static_connection_crud +from app.deps import require_permission +from app.models.user import User +from app.schemas.static_connection import ( + StaticConnectionCreate, + StaticConnectionRead, + StaticConnectionUpdate, +) + +router = APIRouter() + + +@router.get("", response_model=list[StaticConnectionRead]) +async def list_static_connections( + skip: int = 0, + limit: int = 100, + db: AsyncSession = Depends(get_db), + _: User = Depends(require_permission("static_connections:read")), +): + return await static_connection_crud.list_all(db, skip=skip, limit=limit) + + +@router.post("", response_model=StaticConnectionRead, status_code=status.HTTP_201_CREATED) +async def create_static_connection( + payload: StaticConnectionCreate, + db: AsyncSession = Depends(get_db), + actor: User = Depends(require_permission("static_connections:write")), +): + sc = await static_connection_crud.create(db, od_mm=payload.od_mm, od_in=payload.od_in) + await audit_crud.log( + db, actor=actor, action="create", resource_type="StaticConnection", + resource_id=sc.id, + description=audit_crud.describe_create("StaticConnection", sc.id, f"{sc.od_mm} mm"), + ) + return sc + + +@router.get("/{static_connection_id}", response_model=StaticConnectionRead) +async def get_static_connection( + static_connection_id: int, + db: AsyncSession = Depends(get_db), + _: User = Depends(require_permission("static_connections:read")), +): + sc = await static_connection_crud.get(db, static_connection_id) + if sc is None: + raise HTTPException(status_code=404, detail="Static connection not found") + return sc + + +@router.patch("/{static_connection_id}", response_model=StaticConnectionRead) +async def update_static_connection( + static_connection_id: int, + payload: StaticConnectionUpdate, + db: AsyncSession = Depends(get_db), + actor: User = Depends(require_permission("static_connections:write")), +): + sc = await static_connection_crud.get(db, static_connection_id) + if sc is None: + raise HTTPException(status_code=404, detail="Static connection not found") + changes = payload.model_dump(exclude_unset=True) + sc = await static_connection_crud.update(db, sc, changes) + await audit_crud.log( + db, actor=actor, action="update", resource_type="StaticConnection", + resource_id=sc.id, + description=audit_crud.describe_update("StaticConnection", sc.id, changes), + ) + return sc + + +@router.delete("/{static_connection_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_static_connection( + static_connection_id: int, + db: AsyncSession = Depends(get_db), + actor: User = Depends(require_permission("static_connections:delete")), +): + sc = await static_connection_crud.get(db, static_connection_id) + if sc is None: + raise HTTPException(status_code=404, detail="Static connection not found") + od_mm = sc.od_mm + await static_connection_crud.delete(db, sc) + await audit_crud.log( + db, actor=actor, action="delete", resource_type="StaticConnection", + resource_id=static_connection_id, + description=audit_crud.describe_delete( + "StaticConnection", static_connection_id, f"{od_mm} mm" + ), + ) diff --git a/app/crud/static_connection.py b/app/crud/static_connection.py new file mode 100644 index 0000000..b4db31b --- /dev/null +++ b/app/crud/static_connection.py @@ -0,0 +1,52 @@ +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.static_connection import StaticConnection + + +async def get(db: AsyncSession, static_connection_id: int) -> StaticConnection | None: + return await db.get(StaticConnection, static_connection_id) + + +async def get_by_od_mm(db: AsyncSession, od_mm: float) -> StaticConnection | None: + result = await db.execute( + select(StaticConnection).where(StaticConnection.od_mm == od_mm) + ) + return result.scalar_one_or_none() + + +async def list_all( + db: AsyncSession, skip: int = 0, limit: int = 100 +) -> list[StaticConnection]: + result = await db.execute( + select(StaticConnection).order_by(StaticConnection.od_mm).offset(skip).limit(limit) + ) + return list(result.scalars().all()) + + +async def create( + db: AsyncSession, + *, + od_mm: float, + od_in: float, +) -> StaticConnection: + sc = StaticConnection(od_mm=od_mm, od_in=od_in) + db.add(sc) + await db.commit() + await db.refresh(sc) + return sc + + +async def update( + db: AsyncSession, sc: StaticConnection, data: dict +) -> StaticConnection: + for field, value in data.items(): + setattr(sc, field, value) + await db.commit() + await db.refresh(sc) + return sc + + +async def delete(db: AsyncSession, sc: StaticConnection) -> None: + await db.delete(sc) + await db.commit() diff --git a/app/models/__init__.py b/app/models/__init__.py index da7ef11..536fc53 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -9,6 +9,7 @@ from app.models.manufacturer import Manufacturer from app.models.part_type import PartType from app.models.permission import Permission from app.models.scope_model import ScopeModel +from app.models.static_connection import StaticConnection from app.models.sub_part_type import SubPartType from app.models.refresh_token import RefreshToken from app.models.role import Role @@ -24,6 +25,7 @@ __all__ = [ "PartType", "Permission", "ScopeModel", + "StaticConnection", "SubPartType", "RefreshToken", "Role", diff --git a/app/models/static_connection.py b/app/models/static_connection.py new file mode 100644 index 0000000..e713ef5 --- /dev/null +++ b/app/models/static_connection.py @@ -0,0 +1,13 @@ +from sqlalchemy import Float, UniqueConstraint +from sqlalchemy.orm import Mapped, mapped_column + +from app.core.database import Base + + +class StaticConnection(Base): + __tablename__ = "static_connections" + __table_args__ = (UniqueConstraint("od_mm"),) + + id: Mapped[int] = mapped_column(primary_key=True) + od_mm: Mapped[float] = mapped_column(Float, nullable=False) + od_in: Mapped[float] = mapped_column(Float, nullable=False) diff --git a/app/schemas/static_connection.py b/app/schemas/static_connection.py new file mode 100644 index 0000000..375b55d --- /dev/null +++ b/app/schemas/static_connection.py @@ -0,0 +1,20 @@ +from pydantic import BaseModel, ConfigDict + + +class StaticConnectionBase(BaseModel): + od_mm: float + od_in: float + + +class StaticConnectionCreate(StaticConnectionBase): + pass + + +class StaticConnectionUpdate(BaseModel): + od_mm: float | None = None + od_in: float | None = None + + +class StaticConnectionRead(StaticConnectionBase): + model_config = ConfigDict(from_attributes=True) + id: int diff --git a/app/templates/_nav.html b/app/templates/_nav.html index aac8a7b..99a8a7b 100644 --- a/app/templates/_nav.html +++ b/app/templates/_nav.html @@ -29,6 +29,9 @@ {% if user.is_superuser or "imported_parts:read" in user.permissions %} Imported Parts {% endif %} + {% if user.is_superuser or "static_connections:read" in user.permissions %} + Static Connections + {% endif %} {% if user.is_superuser or "audit_log:read" in user.permissions %} Audit Log {% endif %} diff --git a/app/templates/admin/_static_connection_row.html b/app/templates/admin/_static_connection_row.html new file mode 100644 index 0000000..b1980e5 --- /dev/null +++ b/app/templates/admin/_static_connection_row.html @@ -0,0 +1,16 @@ +
| ID | +OD (mm) | +OD (in) | ++ |
|---|---|---|---|
| No static connections recorded. | |||