-
Notifications
You must be signed in to change notification settings - Fork 0
Implement admins CRUD endpoints (DEV-64) #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,15 +3,81 @@ | |
| Endpoints for Admin users (Supabase Auth; link via supabase_user_id). | ||
| """ | ||
|
|
||
| from fastapi import APIRouter, Response, status | ||
| from fastapi import APIRouter, Depends, HTTPException, Response, status | ||
| from sqlalchemy.ext.asyncio import AsyncSession | ||
|
|
||
| from ..database import get_db | ||
| from ..schemas import Admin as AdminSchema | ||
| from ..schemas import AdminCreate, AdminUpdate | ||
| from ..services import admins | ||
|
|
||
| router = APIRouter() | ||
|
|
||
|
|
||
| @router.get("") | ||
| async def list_admins(): | ||
| """List admins. Placeholder — implement with Admin model and schemas.""" | ||
| return Response( | ||
| content="Not implemented — see backend/STARTER_BACKEND_GUIDE.md", | ||
| status_code=status.HTTP_501_NOT_IMPLEMENTED, | ||
| ) | ||
| @router.get("", response_model=list[AdminSchema], status_code=status.HTTP_200_OK) | ||
| async def list_admins(db: AsyncSession = Depends(get_db)): | ||
| """List all admins.""" | ||
| return await admins.list_admins(db) | ||
|
|
||
|
|
||
| @router.post("", response_model=AdminSchema, status_code=status.HTTP_201_CREATED) | ||
| async def create_admin( | ||
| payload: AdminCreate, | ||
| db: AsyncSession = Depends(get_db), | ||
| ): | ||
| """Create a new admin.""" | ||
| return await admins.create_admin(db, payload) | ||
|
|
||
|
|
||
| @router.get("/{id}", response_model=AdminSchema, status_code=status.HTTP_200_OK) | ||
| async def get_admin( | ||
| id: str, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. id is a Python built-in function; prob shouldn't be using it as a parameter name. Consider admin_id instead? Also applies to PUT and DELETE endpoints! |
||
| db: AsyncSession = Depends(get_db), | ||
| ): | ||
| """Get a single admin.""" | ||
| admin = await admins.get_admin(db, id) | ||
|
|
||
| if not admin: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_404_NOT_FOUND, | ||
| detail="Admin not found", | ||
| ) | ||
|
|
||
| return admin | ||
|
|
||
|
|
||
| @router.put("/{id}", response_model=AdminSchema, status_code=status.HTTP_200_OK) | ||
| async def update_admin( | ||
| id: str, | ||
| payload: AdminUpdate, | ||
| db: AsyncSession = Depends(get_db), | ||
| ): | ||
| """Update an admin.""" | ||
| admin = await admins.get_admin(db, id) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This 404 block is repeated for GET, PUT, DELETE. Consider a helper function to avoid this repetition |
||
|
|
||
| if not admin: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_404_NOT_FOUND, | ||
| detail="Admin not found", | ||
| ) | ||
|
|
||
| return await admins.update_admin(db, admin, payload) | ||
|
|
||
|
|
||
| @router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) | ||
| async def delete_admin( | ||
| id: str, | ||
| db: AsyncSession = Depends(get_db), | ||
| ): | ||
| """Delete an admin.""" | ||
| admin = await admins.get_admin(db, id) | ||
|
|
||
| if not admin: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_404_NOT_FOUND, | ||
| detail="Admin not found", | ||
| ) | ||
|
|
||
| await admins.delete_admin(db, admin) | ||
|
|
||
| return Response(status_code=status.HTTP_204_NO_CONTENT) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Decorator alr sets status_code=status.HTTP_204_NO_CONTENT, so explicit return is perhaps redundant as well. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| """Services package. | ||
|
|
||
| Business logic layer; API routes depend on services, not the reverse. | ||
| """ | ||
|
|
||
| from . import admins as admins_service | ||
|
|
||
| __all__ = ["admins_service"] |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||
| """Admin service layer. | ||||||
|
|
||||||
| Handles database operations for Admins. | ||||||
| """ | ||||||
|
|
||||||
| from sqlalchemy import select | ||||||
| from sqlalchemy.ext.asyncio import AsyncSession | ||||||
|
|
||||||
| from ..models import Admin | ||||||
| from ..schemas import AdminCreate, AdminUpdate | ||||||
|
|
||||||
|
|
||||||
| async def list_admins(db: AsyncSession): | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All functions are missing return types. Return types makes it easier to understand and safer to use without needing to inspect the implementation! |
||||||
| """Return all admins ordered by first name.""" | ||||||
| result = await db.execute(select(Admin).order_by(Admin.first_name)) | ||||||
| return list(result.scalars().all()) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Already returns a list! Saw this in one of Kenzys comments LOL |
||||||
|
|
||||||
|
|
||||||
| async def create_admin(db: AsyncSession, payload: AdminCreate): | ||||||
| """Create a new admin.""" | ||||||
| db_admin = Admin(**payload.model_dump()) | ||||||
| db.add(db_admin) | ||||||
| await db.commit() | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
if a database constraint fails (e.g., a foreign key or uniqueness constraint), let's catch these and convert them into a but for each of #16 for reference |
||||||
| await db.refresh(db_admin) | ||||||
| return db_admin | ||||||
|
|
||||||
|
|
||||||
| async def get_admin(db: AsyncSession, admin_id: str): | ||||||
| """Return a single admin by ID or None.""" | ||||||
| result = await db.execute(select(Admin).where(Admin.id == admin_id)) | ||||||
| return result.scalar_one_or_none() | ||||||
|
|
||||||
|
|
||||||
| async def update_admin(db: AsyncSession, admin: Admin, payload: AdminUpdate): | ||||||
| """Update an admin.""" | ||||||
| data = payload.model_dump(exclude_unset=True) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. potentially add a check like to avoid committing without modifying anything (#17 for reference) |
||||||
|
|
||||||
| for key, value in data.items(): | ||||||
| setattr(admin, key, value) | ||||||
|
|
||||||
| await db.commit() | ||||||
| await db.refresh(admin) | ||||||
| return admin | ||||||
|
|
||||||
|
|
||||||
| async def delete_admin(db: AsyncSession, admin: Admin): | ||||||
| """Delete an admin.""" | ||||||
| await db.delete(admin) | ||||||
| await db.commit() | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| from setuptools import setup, find_packages | ||
| from setuptools import find_packages, setup | ||
|
|
||
| setup(name="app", packages=find_packages()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,4 @@ | ||
| import pytest | ||
|
|
||
| from app import create_app | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| import inflection | ||
| import os | ||
|
|
||
| import inflection | ||
| import pytest | ||
| import requests | ||
| from dotenv import load_dotenv | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,5 @@ | ||
| import inflection | ||
| import requests | ||
|
|
||
| from test_user import delete_user | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,5 @@ | ||
| import inflection | ||
| import requests | ||
|
|
||
| from test_user_gql import delete_user | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
status_code=status.HTTP_200_OK is default, so this is a bit redundant. also applies to GET!