diff --git a/backend/api/endpoints/command.py b/backend/api/endpoints/command.py index 583ea5a..34b945b 100644 --- a/backend/api/endpoints/command.py +++ b/backend/api/endpoints/command.py @@ -2,20 +2,25 @@ from fastapi.exceptions import HTTPException from sqlmodel import Session, select + from backend.api.models.request_model import CommandRequest from backend.api.models.response_model import CommandListResponse, CommandSingleResponse from backend.data.data_models import Command from backend.data.engine import get_db + # Prefix: "/commands" command_router = APIRouter(tags=["Commands"]) + + @command_router.get("/", response_model=CommandListResponse) def get_commands(db: Session = Depends(get_db)): """ Gets all the items + @return Returns a list of commands """ query = select(Command) @@ -23,24 +28,53 @@ def get_commands(db: Session = Depends(get_db)): return {"data": items} + + @command_router.post("/", response_model=CommandSingleResponse) -def create_command(payload: CommandRequest): +def create_command(payload: CommandRequest, db: Session = Depends(get_db)): """ - Creates an item with the given payload in the database and returns this payload after pulling it from the database + Creates an item with the given payload in the database and returns this payload after pulling it from the database + @param payload: The data used to create an item - @return returns a json object with field of "data" under which there is the payload now pulled from the database + @return returns a json object with field of "data" under which there is the payload now pulled from the database """ # TODO:(Member) Implement this endpoint - + try: + new_command = Command(**payload.model_dump()) + + db.add(new_command) + db.commit() + db.refresh(new_command) + + return {"data": new_command} + except Exception as e: + db.rollback() + raise HTTPException(status_code=500, detail="Fail to create command {str(e)}") + + @command_router.delete("/{id}", response_model=CommandListResponse) -def delete_command(id: int): +def delete_command(id: int, db: Session = Depends(get_db)): """ Deletes the item with the given id if it exists. Otherwise raises a 404 error. + @param id: The id of the item to delete @return returns the list of commands after deleting the item """ # TODO:(Member) Implement this endpoint + query = select(Command) + command = db.exec(query.where(Command.id == id)).first() + + if command is None: + raise HTTPException(status_code=404, detail="Item not found") + + db.delete(command) + db.commit() + + return get_commands(db) + + + diff --git a/backend/api/middlewares/logger_middleware.py b/backend/api/middlewares/logger_middleware.py index 44daa5d..dcf4070 100644 --- a/backend/api/middlewares/logger_middleware.py +++ b/backend/api/middlewares/logger_middleware.py @@ -1,8 +1,10 @@ +from time import time, strftime from collections.abc import Callable from typing import Any from fastapi import Request, Response from starlette.middleware.base import BaseHTTPMiddleware +from backend.utils.logging import logger class LoggerMiddleware(BaseHTTPMiddleware): async def dispatch( @@ -18,5 +20,18 @@ async def dispatch( @return Response from endpoint """ # TODO:(Member) Finish implementing this method + start_time = time() + request_time = strftime('%Y-%m-%d %H:%M:%S') + logger.info(f"Request started: [{request_time}] | {request.method} | {request.url.path} | Params: {request.query_params}") + response = await call_next(request) - return response + + raw_byte = b"" + async for chunk in response.body_iterator: + raw_byte += chunk + + # Log response details and duration + duration = time() - start_time + + logger.info(f"Response sent: {raw_byte} {response.status_code} | Duration: {duration:.4f}s") + return Response(content=raw_byte, status_code=response.status_code, headers=dict(response.headers), media_type=response.media_type) diff --git a/backend/data/data_models.py b/backend/data/data_models.py index 68adddb..583ccad 100644 --- a/backend/data/data_models.py +++ b/backend/data/data_models.py @@ -4,18 +4,23 @@ from pydantic import model_validator from sqlmodel import Field + from backend.data.base_model import BaseSQLModel from backend.data.enums import CommandStatus + + class MainCommand(BaseSQLModel, table=True): """ Main command model. This table represents all the possible commands that can be issued. + List of commands: https://docs.google.com/spreadsheets/d/1XWXgp3--NHZ4XlxOyBYPS-M_LOU_ai-I6TcvotKhR1s/edit?gid=564815068#gid=564815068 """ + id: int | None = Field( default=None, primary_key=True ) # NOTE: Must be None for autoincrement @@ -25,6 +30,7 @@ class MainCommand(BaseSQLModel, table=True): data_size: int total_size: int + @model_validator(mode="after") def validate_params_format(self): """ @@ -33,7 +39,17 @@ def validate_params_format(self): The format of the comma seperated values is "data1,data2" so no spaces between data and the commas. """ # TODO: (Member) Implement this method - return self + if self.params is None and self.format is None: + return self + elif self.params is None or self.format is None: + raise ValueError("One of params or format is not initialized.") + + if len(self.params.split(",")) == len(self.format.split(",")): + return self + else: + raise ValueError("Number of comma separated value of params and format does not match.") + + class Command(BaseSQLModel, table=True): @@ -42,6 +58,7 @@ class Command(BaseSQLModel, table=True): This table holds the data related to actual commands sent from the ground station up to the OBC. """ + id: int | None = Field( default=None, primary_key=True ) # NOTE: Must be None for autoincrement @@ -52,3 +69,6 @@ class Command(BaseSQLModel, table=True): params: str | None = None created_on: datetime = datetime.now() updated_on: datetime = datetime.now() + + +