feat(l1): account L1 category settings API (owner/admin write)

GET /accounts/me/l1-categories (require_l1_or_above) returns enabled + available
+ hard_floor; PATCH (require_account_owner_or_admin) sets the enabled set, dropping
unknown/hard-floored keys via l1_category_service. New L1CategoriesResponse/Update
schemas. 6 API tests green (incl. engineer + l1_tech write both 403); test_accounts
regression 36 passed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-30 20:01:32 -04:00
parent 04d2cfb9a5
commit 1d3f9d0a8a
3 changed files with 181 additions and 1 deletions

View File

@@ -23,9 +23,17 @@ from app.schemas.account import AccountResponse, AccountUpdate, AccountInviteCre
from app.schemas.subscription import SubscriptionResponse, PlanLimitsResponse, UsageResponse, SubscriptionDetails
from app.schemas.user import UserResponse, AccountRoleUpdate, CoverageUpdate
from app.core.security import verify_password
from app.api.deps import get_current_active_user, require_account_owner, require_engineer_or_admin
from app.api.deps import (
get_current_active_user,
require_account_owner,
require_account_owner_or_admin,
require_engineer_or_admin,
require_l1_or_above,
)
from app.services import l1_category_service
from app.services.seat_enforcement import check_seat_available, get_seat_usage
from app.schemas.seat_enforcement import SeatUsage
from app.schemas.l1_categories import L1CategoriesResponse, L1CategoriesUpdate
_SEAT_CHECKED_ROLES = frozenset({"engineer", "l1_tech"})
@@ -164,6 +172,45 @@ async def get_my_account_seat_usage(
return SeatUsage(engineer=engineer, l1_tech=l1_tech)
@router.get("/me/l1-categories", response_model=L1CategoriesResponse)
async def get_l1_categories(
db: Annotated[AsyncSession, Depends(get_db)],
current_user: Annotated[User, Depends(require_l1_or_above)],
):
"""The account's enabled L1 AI-build categories + the available + hard-floor lists.
Readable by any L1-or-above user (the walker needs to know what's buildable);
only owners/admins may change it (PATCH below).
"""
enabled = await l1_category_service.get_enabled_categories(current_user.account_id, db)
return L1CategoriesResponse(
enabled=enabled,
available=l1_category_service.DEFAULT_L1_CATEGORIES,
hard_floor=l1_category_service.HARD_FLOOR_FORBIDDEN,
)
@router.patch("/me/l1-categories", response_model=L1CategoriesResponse)
async def set_l1_categories(
payload: L1CategoriesUpdate,
db: Annotated[AsyncSession, Depends(get_db)],
current_user: Annotated[User, Depends(require_account_owner_or_admin)],
):
"""Set the account's enabled L1 categories (owner/admin only).
Unknown and hard-floored keys are dropped by the service before persisting.
"""
enabled = await l1_category_service.set_enabled_categories(
current_user.account_id, payload.enabled, db
)
await db.commit()
return L1CategoriesResponse(
enabled=enabled,
available=l1_category_service.DEFAULT_L1_CATEGORIES,
hard_floor=l1_category_service.HARD_FLOOR_FORBIDDEN,
)
@router.patch("/me", response_model=AccountResponse)
async def update_my_account(
data: AccountUpdate,