Compare commits

..

No commits in common. "186ac0eab38214aa8803265afbf34af4b6b58d46" and "e4548aab3a364c173fa1bc8201cb110858e2a2b4" have entirely different histories.

8 changed files with 28 additions and 122 deletions

View file

@ -3,7 +3,7 @@ Package `db`: ORM and schemas for database content.
""" """
from .connection import Connection from .connection import Connection
from .device import Device, DeviceBase, DeviceCreate, DeviceRead from .device import Device, DeviceBase, DeviceCreate
from .user import User, UserBase, UserCreate, UserRead from .user import User, UserBase, UserCreate, UserRead
from .user_capability import UserCapabilityType from .user_capability import UserCapabilityType
@ -12,7 +12,6 @@ __all__ = [
"Device", "Device",
"DeviceBase", "DeviceBase",
"DeviceCreate", "DeviceCreate",
"DeviceRead",
"User", "User",
"UserBase", "UserBase",
"UserCreate", "UserCreate",

View file

@ -31,6 +31,8 @@ class DeviceCreate(DeviceBase):
Representation of a newly created device Representation of a newly created device
""" """
owner_name: str | None
class DeviceRead(DeviceBase): class DeviceRead(DeviceBase):
""" """
@ -60,47 +62,25 @@ class Device(DeviceBase, table=True):
) )
@classmethod @classmethod
def create( def create(cls, **kwargs) -> Device | None:
cls,
*,
owner: User,
device: DeviceCreate,
) -> Device | None:
""" """
Create a new device in the database. Create a new device in the database.
""" """
try: try:
with Connection.session as db: with Connection.session as db:
new_device = cls.from_orm(device) device = cls.from_orm(DeviceCreate(**kwargs))
new_device.owner = owner
db.add(new_device) db.add(device)
db.commit() db.commit()
db.refresh(new_device) db.refresh(device)
return new_device return device
except IntegrityError: except IntegrityError:
# device already existed # device already existed
return None return None
@classmethod
def create_kwargs(
cls,
*,
owner: User,
**kwargs
) -> Device | None:
"""
Create a new device in the database. Keywords version.
"""
return cls.create(
owner=owner,
device=DeviceCreate(**kwargs),
)
def update(self) -> None: def update(self) -> None:
""" """
Update this device in the database. Update this device in the database.

View file

@ -90,42 +90,25 @@ class User(UserBase, table=True):
) )
@classmethod @classmethod
def create( def create(cls, **kwargs) -> User | None:
cls,
*,
user: UserCreate,
) -> User | None:
""" """
Create a new user in the database. Create a new user in the database.
""" """
try: try:
with Connection.session as db: with Connection.session as db:
new_user = cls.from_orm(user) user = cls.from_orm(UserCreate(**kwargs))
db.add(new_user) db.add(user)
db.commit() db.commit()
db.refresh(new_user) db.refresh(user)
return new_user return user
except IntegrityError: except IntegrityError:
# user already existed # user already existed
return None return None
@classmethod
def create_kwargs(
cls,
**kwargs
) -> User | None:
"""
Create a new user in the database. Keywords version.
"""
return cls.create(
user=UserCreate(**kwargs),
)
@classmethod @classmethod
def get(cls, name: str) -> User | None: def get(cls, name: str) -> User | None:
""" """

View file

@ -6,12 +6,11 @@ This file: Main API router definition.
from fastapi import APIRouter from fastapi import APIRouter
from . import admin, device, user from . import admin, user
main_router = APIRouter() main_router = APIRouter()
main_router.include_router(admin.router) main_router.include_router(admin.router)
main_router.include_router(device.router)
main_router.include_router(user.router) main_router.include_router(user.router)
__all__ = [ __all__ = [

View file

@ -40,7 +40,7 @@ class Responses:
"description": "Must be admin", "description": "Must be admin",
"content": None, "content": None,
} }
NEEDS_REQUESTED_USER = { NEEDS_ADMIN_OR_SELF = {
"description": "Must be the requested user", "description": "Must be the requested user",
"content": None, "content": None,
} }
@ -99,29 +99,20 @@ async def get_current_user_if_admin(
return current_user return current_user
async def get_user_by_name( async def get_current_user_if_admin_or_self(
user_name: str, user_name: str,
current_user: User = Depends(get_current_user_if_exists), current_user: User = Depends(get_current_user_if_exists),
) -> User: ) -> User:
""" """
Get a user by name. Get the currently logged-in user.
Works if a) the currently logged-in user is an admin, Fails a) if the currently logged-in user is not the requested user,
or b) if it is the requested user. and b) if it is not an admin.
""" """
# check if current user is admin # fail if not requested by an admin or self
if current_user.can(UserCapabilityType.admin): if not (current_user.can(UserCapabilityType.admin)
# fail if requested user doesn't exist or current_user.name == user_name):
if (user := User.get(user_name)) is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
# check if current user is requested user
elif current_user.name == user_name:
pass
# current user is neither admin nor the requested user
else:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
return user return current_user

View file

@ -63,7 +63,7 @@ async def create_initial_admin(
raise HTTPException(status_code=status.HTTP_409_CONFLICT) raise HTTPException(status_code=status.HTTP_409_CONFLICT)
# create an administrative user # create an administrative user
new_user = User.create(admin_user) new_user = User.create(**admin_user.dict())
new_user.set_capabilities([UserCapabilityType.admin]) new_user.set_capabilities([UserCapabilityType.admin])
new_user.update() new_user.update()

View file

@ -1,44 +0,0 @@
"""
/device endpoints.
"""
from fastapi import APIRouter, Depends, HTTPException, status
from ..db import Device, DeviceCreate, DeviceRead, User
from ._common import Responses, get_user_by_name
router = APIRouter(prefix="/device", tags=["device"])
@router.post(
"",
responses={
status.HTTP_200_OK: Responses.OK,
status.HTTP_400_BAD_REQUEST: Responses.NOT_INSTALLED,
status.HTTP_401_UNAUTHORIZED: Responses.NEEDS_USER,
status.HTTP_403_FORBIDDEN: Responses.NEEDS_REQUESTED_USER,
status.HTTP_404_NOT_FOUND: Responses.ENTRY_DOESNT_EXIST,
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
},
response_model=DeviceRead,
)
async def add_device(
device: DeviceCreate,
user: User = Depends(get_user_by_name),
) -> Device:
"""
POST ./: Create a new device in the database.
"""
# create the new device
new_device = Device.create(
owner=user,
device=device,
)
# fail if creation was unsuccessful
if new_device is None:
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
# return the created device on success
return new_device

View file

@ -76,26 +76,24 @@ async def get_current_user(
status.HTTP_403_FORBIDDEN: Responses.NEEDS_ADMIN, status.HTTP_403_FORBIDDEN: Responses.NEEDS_ADMIN,
status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS, status.HTTP_409_CONFLICT: Responses.ENTRY_EXISTS,
}, },
response_model=UserRead, response_model=User,
) )
async def add_user( async def add_user(
user: UserCreate, user: UserCreate,
_: User = Depends(get_current_user_if_admin), _: User = Depends(get_current_user_if_admin),
) -> User: ):
""" """
POST ./: Create a new user in the database. POST ./: Create a new user in the database.
""" """
# actually create the new user # actually create the new user
new_user = User.create(user=user) new_user = User.create(**user.dict())
new_user.set_capabilities([UserCapabilityType.login])
# fail if creation was unsuccessful # fail if creation was unsuccessful
if new_user is None: if new_user is None:
raise HTTPException(status_code=status.HTTP_409_CONFLICT) raise HTTPException(status_code=status.HTTP_409_CONFLICT)
new_user.set_capabilities([UserCapabilityType.login])
new_user.update()
# return the created user on success # return the created user on success
return new_user return new_user