diff --git a/api/kiwi_vpn_api/db/__init__.py b/api/kiwi_vpn_api/db/__init__.py index aa0cb3c..ae088cd 100644 --- a/api/kiwi_vpn_api/db/__init__.py +++ b/api/kiwi_vpn_api/db/__init__.py @@ -3,7 +3,7 @@ Package `db`: ORM and schemas for database content. """ from .connection import Connection -from .device import Device, DeviceBase, DeviceCreate +from .device import Device, DeviceBase, DeviceCreate, DeviceRead from .user import User, UserBase, UserCreate, UserRead from .user_capability import UserCapabilityType @@ -12,6 +12,7 @@ __all__ = [ "Device", "DeviceBase", "DeviceCreate", + "DeviceRead", "User", "UserBase", "UserCreate", diff --git a/api/kiwi_vpn_api/db/device.py b/api/kiwi_vpn_api/db/device.py index ff791e0..7dde1b4 100644 --- a/api/kiwi_vpn_api/db/device.py +++ b/api/kiwi_vpn_api/db/device.py @@ -31,8 +31,6 @@ class DeviceCreate(DeviceBase): Representation of a newly created device """ - owner_name: str | None - class DeviceRead(DeviceBase): """ @@ -62,25 +60,47 @@ class Device(DeviceBase, table=True): ) @classmethod - def create(cls, **kwargs) -> Device | None: + def create( + cls, + *, + owner: User, + device: DeviceCreate, + ) -> Device | None: """ Create a new device in the database. """ try: with Connection.session as db: - device = cls.from_orm(DeviceCreate(**kwargs)) + new_device = cls.from_orm(device) + new_device.owner = owner - db.add(device) + db.add(new_device) db.commit() - db.refresh(device) + db.refresh(new_device) - return device + return new_device except IntegrityError: # device already existed 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: """ Update this device in the database. diff --git a/api/kiwi_vpn_api/routers/__init__.py b/api/kiwi_vpn_api/routers/__init__.py index 6cb693f..a0e21a9 100644 --- a/api/kiwi_vpn_api/routers/__init__.py +++ b/api/kiwi_vpn_api/routers/__init__.py @@ -6,11 +6,12 @@ This file: Main API router definition. from fastapi import APIRouter -from . import admin, user +from . import admin, device, user main_router = APIRouter() main_router.include_router(admin.router) +main_router.include_router(device.router) main_router.include_router(user.router) __all__ = [ diff --git a/api/kiwi_vpn_api/routers/_common.py b/api/kiwi_vpn_api/routers/_common.py index ba8c496..44d1c38 100644 --- a/api/kiwi_vpn_api/routers/_common.py +++ b/api/kiwi_vpn_api/routers/_common.py @@ -40,7 +40,7 @@ class Responses: "description": "Must be admin", "content": None, } - NEEDS_ADMIN_OR_SELF = { + NEEDS_REQUESTED_USER = { "description": "Must be the requested user", "content": None, } @@ -99,20 +99,29 @@ async def get_current_user_if_admin( return current_user -async def get_current_user_if_admin_or_self( +async def get_user_by_name( user_name: str, current_user: User = Depends(get_current_user_if_exists), ) -> User: """ - Get the currently logged-in user. + Get a user by name. - Fails a) if the currently logged-in user is not the requested user, - and b) if it is not an admin. + Works if a) the currently logged-in user is an admin, + or b) if it is the requested user. """ - # fail if not requested by an admin or self - if not (current_user.can(UserCapabilityType.admin) - or current_user.name == user_name): + # check if current user is admin + if current_user.can(UserCapabilityType.admin): + # fail if requested user doesn't exist + 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) - return current_user + return user diff --git a/api/kiwi_vpn_api/routers/device.py b/api/kiwi_vpn_api/routers/device.py new file mode 100644 index 0000000..4638016 --- /dev/null +++ b/api/kiwi_vpn_api/routers/device.py @@ -0,0 +1,44 @@ +""" +/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