Phase 0.1: Remove backend deprecated code

- Delete routes: counter.py, sum.py
- Delete jobs.py and worker.py
- Delete tests: test_counter.py, test_jobs.py
- Update audit.py: keep only price-history endpoints
- Update models.py: remove VIEW_COUNTER, INCREMENT_COUNTER, USE_SUM permissions
- Update models.py: remove Counter, SumRecord, CounterRecord, RandomNumberOutcome models
- Update schemas.py: remove sum/counter related schemas
- Update main.py: remove deleted router imports
- Update test_permissions.py: remove tests for deprecated features
- Update test_price_history.py: remove worker-related tests
- Update conftest.py: remove mock_enqueue_job fixture
- Update auth.py: fix example in docstring
This commit is contained in:
counterweight 2025-12-22 18:07:14 +01:00
parent ea85198171
commit 5bad1e7e17
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
14 changed files with 35 additions and 1393 deletions

View file

@ -1,98 +1,18 @@
"""Audit routes for viewing action records."""
"""Audit routes for price history."""
from collections.abc import Callable
from typing import TypeVar
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel
from sqlalchemy import desc, func, select
from fastapi import APIRouter, Depends
from sqlalchemy import desc, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession
from auth import require_permission
from database import get_db
from models import (
CounterRecord,
Permission,
PriceHistory,
RandomNumberOutcome,
SumRecord,
User,
)
from pagination import (
calculate_offset,
calculate_total_pages,
create_paginated_response,
)
from models import Permission, PriceHistory, User
from price_fetcher import PAIR_BTC_EUR, SOURCE_BITFINEX, fetch_btc_eur_price
from schemas import (
CounterRecordResponse,
PaginatedCounterRecords,
PaginatedSumRecords,
PriceHistoryResponse,
RandomNumberOutcomeResponse,
SumRecordResponse,
)
from schemas import PriceHistoryResponse
router = APIRouter(prefix="/api/audit", tags=["audit"])
R = TypeVar("R", bound=BaseModel)
async def paginate_with_user_email(
db: AsyncSession,
model: type[SumRecord] | type[CounterRecord],
page: int,
per_page: int,
row_mapper: Callable[..., R],
) -> tuple[list[R], int, int]:
"""
Generic pagination helper for audit records that need user email.
Returns: (records, total, total_pages)
"""
# Get total count
count_result = await db.execute(select(func.count(model.id)))
total = count_result.scalar() or 0
# Get paginated records with user email
offset = calculate_offset(page, per_page)
query = (
select(model, User.email)
.join(User, model.user_id == User.id)
.order_by(desc(model.created_at))
.offset(offset)
.limit(per_page)
)
result = await db.execute(query)
rows = result.all()
records: list[R] = [row_mapper(record, email) for record, email in rows]
return records, total, calculate_total_pages(total, per_page)
def _to_counter_record_response(
record: CounterRecord, email: str
) -> CounterRecordResponse:
return CounterRecordResponse(
id=record.id,
user_email=email,
value_before=record.value_before,
value_after=record.value_after,
created_at=record.created_at,
)
def _to_sum_record_response(record: SumRecord, email: str) -> SumRecordResponse:
return SumRecordResponse(
id=record.id,
user_email=email,
a=record.a,
b=record.b,
result=record.result,
created_at=record.created_at,
)
def _to_price_history_response(record: PriceHistory) -> PriceHistoryResponse:
return PriceHistoryResponse(
@ -105,64 +25,6 @@ def _to_price_history_response(record: PriceHistory) -> PriceHistoryResponse:
)
@router.get("/counter", response_model=PaginatedCounterRecords)
async def get_counter_records(
page: int = Query(1, ge=1),
per_page: int = Query(10, ge=1, le=100),
db: AsyncSession = Depends(get_db),
_current_user: User = Depends(require_permission(Permission.VIEW_AUDIT)),
) -> PaginatedCounterRecords:
"""Get paginated counter action records."""
records, total, _ = await paginate_with_user_email(
db, CounterRecord, page, per_page, _to_counter_record_response
)
return create_paginated_response(records, total, page, per_page)
@router.get("/sum", response_model=PaginatedSumRecords)
async def get_sum_records(
page: int = Query(1, ge=1),
per_page: int = Query(10, ge=1, le=100),
db: AsyncSession = Depends(get_db),
_current_user: User = Depends(require_permission(Permission.VIEW_AUDIT)),
) -> PaginatedSumRecords:
"""Get paginated sum action records."""
records, total, _ = await paginate_with_user_email(
db, SumRecord, page, per_page, _to_sum_record_response
)
return create_paginated_response(records, total, page, per_page)
@router.get("/random-jobs", response_model=list[RandomNumberOutcomeResponse])
async def get_random_job_outcomes(
db: AsyncSession = Depends(get_db),
_current_user: User = Depends(require_permission(Permission.VIEW_AUDIT)),
) -> list[RandomNumberOutcomeResponse]:
"""Get all random number job outcomes, newest first."""
# Explicit join to avoid N+1 query
query = (
select(RandomNumberOutcome, User.email)
.join(User, RandomNumberOutcome.triggered_by_user_id == User.id)
.order_by(desc(RandomNumberOutcome.created_at))
)
result = await db.execute(query)
rows = result.all()
return [
RandomNumberOutcomeResponse(
id=outcome.id,
job_id=outcome.job_id,
triggered_by_user_id=outcome.triggered_by_user_id,
triggered_by_email=email,
value=outcome.value,
duration_ms=outcome.duration_ms,
status=outcome.status,
created_at=outcome.created_at,
)
for outcome, email in rows
]
# =============================================================================
# Price History Endpoints
# =============================================================================

View file

@ -1,64 +0,0 @@
"""Counter routes."""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from auth import require_permission
from database import get_db
from jobs import enqueue_random_number_job
from models import Counter, CounterRecord, Permission, User
router = APIRouter(prefix="/api/counter", tags=["counter"])
async def get_or_create_counter(db: AsyncSession) -> Counter:
"""Get the singleton counter, creating it if it doesn't exist."""
result = await db.execute(select(Counter).where(Counter.id == 1))
counter = result.scalar_one_or_none()
if not counter:
counter = Counter(id=1, value=0)
db.add(counter)
await db.commit()
await db.refresh(counter)
return counter
@router.get("")
async def get_counter(
db: AsyncSession = Depends(get_db),
_current_user: User = Depends(require_permission(Permission.VIEW_COUNTER)),
) -> dict[str, int]:
"""Get the current counter value."""
counter = await get_or_create_counter(db)
return {"value": counter.value}
@router.post("/increment")
async def increment_counter(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_permission(Permission.INCREMENT_COUNTER)),
) -> dict[str, int]:
"""Increment the counter, record the action, and enqueue a random number job."""
counter = await get_or_create_counter(db)
value_before = counter.value
counter.value += 1
record = CounterRecord(
user_id=current_user.id,
value_before=value_before,
value_after=counter.value,
)
db.add(record)
# Enqueue random number job - if this fails, the request fails
try:
await enqueue_random_number_job(current_user.id)
except Exception as e:
await db.rollback()
raise HTTPException(
status_code=500, detail=f"Failed to enqueue job: {e}"
) from e
await db.commit()
return {"value": counter.value}

View file

@ -1,30 +0,0 @@
"""Sum calculation routes."""
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from auth import require_permission
from database import get_db
from models import Permission, SumRecord, User
from schemas import SumRequest, SumResponse
router = APIRouter(prefix="/api/sum", tags=["sum"])
@router.post("", response_model=SumResponse)
async def calculate_sum(
data: SumRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(require_permission(Permission.USE_SUM)),
) -> SumResponse:
"""Calculate the sum of two numbers and record it."""
result = data.a + data.b
record = SumRecord(
user_id=current_user.id,
a=data.a,
b=data.b,
result=result,
)
db.add(record)
await db.commit()
return SumResponse(a=data.a, b=data.b, result=result)