Skip to content

Commit

Permalink
Merge pull request #101 from hotosm/feat/drone-crud-operations
Browse files Browse the repository at this point in the history
Feat/drone crud operations
  • Loading branch information
nrjadkry authored Jul 29, 2024
2 parents 4f95a66 + 2bd16ca commit 982defd
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 2 deletions.
130 changes: 130 additions & 0 deletions src/backend/app/drones/drone_crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from app.drones import drone_schemas
from app.models.enums import HTTPStatus
from databases import Database
from loguru import logger as log
from fastapi import HTTPException
from asyncpg import UniqueViolationError
from typing import List
from app.drones.drone_schemas import DroneOut


async def read_all_drones(db: Database) -> List[DroneOut]:
"""
Retrieves all drone records from the database.
Args:
db (Database): The database connection object.
Returns:
List[DroneOut]: A list of all drone records.
"""
try:
select_query = """
SELECT * FROM drones
"""
results = await db.fetch_all(select_query)
return results

except Exception as e:
log.exception(e)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Retrieval failed"
) from e


async def delete_drone(db: Database, drone_id: int) -> bool:
"""
Deletes a drone record from the database, along with associated drone flights.
Args:
db (Database): The database connection object.
drone_id (int): The ID of the drone to be deleted.
Returns:
bool: True if the drone was successfully deleted, False otherwise.
"""
try:
delete_query = """
WITH deleted_flights AS (
DELETE FROM drone_flights
WHERE drone_id = :drone_id
RETURNING drone_id
)
DELETE FROM drones
WHERE id = :drone_id
"""
await db.execute(delete_query, {"drone_id": drone_id})
return True

except Exception as e:
log.exception(e)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Deletion failed"
) from e


async def get_drone(db: Database, drone_id: int):
"""
Retrieves a drone record from the database.
Args:
db (Database): The database connection object.
drone_id (int): The ID of the drone to be retrieved.
Returns:
dict: The drone record if found, otherwise None.
"""
try:
select_query = """
SELECT * FROM drones
WHERE id = :id
"""
result = await db.fetch_one(select_query, {"id": drone_id})
return result

except Exception as e:
log.exception(e)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Retrieval failed"
) from e


async def create_drone(db: Database, drone_info: drone_schemas.DroneIn):
"""
Creates a new drone record in the database.
Args:
db (Database): The database connection object.
drone (drone_schemas.DroneIn): The schema object containing drone details.
Returns:
The ID of the newly created drone record.
"""
try:
insert_query = """
INSERT INTO drones (
model, manufacturer, camera_model, sensor_width, sensor_height,
max_battery_health, focal_length, image_width, image_height,
max_altitude, max_speed, weight, created
) VALUES (
:model, :manufacturer, :camera_model, :sensor_width, :sensor_height,
:max_battery_health, :focal_length, :image_width, :image_height,
:max_altitude, :max_speed, :weight, CURRENT_TIMESTAMP
)
RETURNING id
"""
result = await db.execute(insert_query, drone_info.__dict__)
return result

except UniqueViolationError as e:
log.exception("Unique constraint violation: %s", e)
raise HTTPException(
status_code=HTTPStatus.CONFLICT,
detail="A drone with this model already exists",
)

except Exception as e:
log.exception(e)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Drone creation failed"
) from e
106 changes: 106 additions & 0 deletions src/backend/app/drones/drone_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from app.users.user_deps import login_required
from app.users.user_schemas import AuthUser
from app.models.enums import HTTPStatus
from fastapi import APIRouter, Depends, HTTPException
from app.db.database import get_db
from app.config import settings
from app.drones import drone_schemas
from databases import Database
from app.drones import drone_crud
from typing import List


router = APIRouter(
prefix=f"{settings.API_PREFIX}/drones",
responses={404: {"description": "Not found"}},
)


@router.get("/", tags=["Drones"], response_model=List[drone_schemas.DroneOut])
async def read_drones(
db: Database = Depends(get_db),
user_data: AuthUser = Depends(login_required),
):
"""
Retrieves all drone records from the database.
Args:
db (Database, optional): The database session object.
user_data (AuthUser, optional): The authenticated user data.
Returns:
List[drone_schemas.DroneOut]: A list of all drone records.
"""
drones = await drone_crud.read_all_drones(db)
return drones


@router.delete("/{drone_id}", tags=["Drones"])
async def delete_drone(
drone_id: int,
db: Database = Depends(get_db),
user_data: AuthUser = Depends(login_required),
):
"""
Deletes a drone record from the database.
Args:
drone_id (int): The ID of the drone to be deleted.
db (Database, optional): The database session object.
user_data (AuthUser, optional): The authenticated user data.
Returns:
dict: A success message if the drone was deleted.
"""
success = await drone_crud.delete_drone(db, drone_id)
if not success:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Drone not found")
return {"message": "Drone deleted successfully"}


@router.post("/create_drone", tags=["Drones"])
async def create_drone(
drone_info: drone_schemas.DroneIn,
db: Database = Depends(get_db),
user_data: AuthUser = Depends(login_required),
):
"""
Creates a new drone record in the database.
Args:
drone_info (drone_schemas.DroneIn): The schema object containing drone details.
db (Database, optional): The database session object.
user_data (AuthUser, optional): The authenticated user data.
Returns:
dict: A dictionary containing a success message and the ID of the newly created drone.
"""
drone_id = await drone_crud.create_drone(db, drone_info)
if not drone_id:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Drone creation failed"
)
return {"message": "Drone created successfully", "drone_id": drone_id}


@router.get("/{drone_id}", tags=["Drones"], response_model=drone_schemas.DroneOut)
async def read_drone(
drone_id: int,
db: Database = Depends(get_db),
user_data: AuthUser = Depends(login_required),
):
"""
Retrieves a drone record from the database.
Args:
drone_id (int): The ID of the drone to be retrieved.
db (Database, optional): The database session object.
user_data (AuthUser, optional): The authenticated user data.
Returns:
dict: The drone record if found.
"""
drone = await drone_crud.get_drone(db, drone_id)
if not drone:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Drone not found")
return drone
21 changes: 21 additions & 0 deletions src/backend/app/drones/drone_schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from pydantic import BaseModel


class DroneIn(BaseModel):
model: str
manufacturer: str
camera_model: str
sensor_width: float
sensor_height: float
max_battery_health: float
focal_length: float
image_width: int
image_height: int
max_altitude: float
max_speed: float
weight: float


class DroneOut(BaseModel):
id: int
model: str
3 changes: 2 additions & 1 deletion src/backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from app.config import settings
from app.projects import project_routes
from app.drones import drone_routes
from app.waypoints import waypoint_routes
from app.users import oauth_routes
from app.users import user_routes
Expand Down Expand Up @@ -94,7 +95,7 @@ def get_application() -> FastAPI:
allow_headers=["*"],
expose_headers=["Content-Disposition"],
)

_app.include_router(drone_routes.router)
_app.include_router(project_routes.router)
_app.include_router(waypoint_routes.router)
_app.include_router(user_routes.router)
Expand Down
6 changes: 5 additions & 1 deletion src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ async def delete_project_by_id(
), deleted_tasks AS (
DELETE FROM tasks
WHERE project_id = :project_id
RETURNING project_id
RETURNING id
), deleted_task_events AS (
DELETE FROM task_events
WHERE project_id = :project_id
RETURNING id
)
SELECT id FROM deleted_project
"""
Expand Down

0 comments on commit 982defd

Please sign in to comment.