Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
25 changes: 19 additions & 6 deletions backend/api/endpoints/command.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from fastapi import APIRouter, Depends
from sqlmodel import Session, select
from fastapi import HTTPException

from backend.api.models.request_model import CommandRequest
from backend.api.models.response_model import CommandListResponse, CommandSingleResponse
Expand All @@ -23,23 +24,35 @@ def get_commands(db: Session = Depends(get_db)):


@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

: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
"""
# TODO:(Member) Implement this endpoint


command = Command(command_type = payload.command_type, params = payload.params)
db.add(command)
db.commit()
query = select(Command).where(Command.params == payload.params)
command_request = db.exec(query).first()
return {"data": command_request}

@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).where(Command.id == id)
item_to_delete = db.exec(query).first()
if item_to_delete == None: raise HTTPException(status_code=404, detail="Command with the following ID not found")
else:
db.delete(item_to_delete)
db.commit()

return get_commands(db=db)


18 changes: 16 additions & 2 deletions backend/api/middlewares/logger_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
from typing import Any
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware

from backend.utils.logging import logger_setup, logger_setup_file, logger_close
from loguru import logger
import time
import json

class LoggerMiddleware(BaseHTTPMiddleware):
async def dispatch(
Expand All @@ -17,6 +20,17 @@ async def dispatch(
:param call_next: Endpoint or next middleware to be called (if any, this is the next middleware in the chain of middlewares, it is supplied by FastAPI)
:return: Response from endpoint
"""
# TODO:(Member) Finish implementing this method

command_request_body = await request.body()
try: command_request_body = json.loads(command_request_body.decode("utf-8"))
except json.JSONDecodeError: command_request_body = {}
command_request_params = command_request_body.get("params")

start_time = time.perf_counter()
response = await call_next(request)
end_time = time.perf_counter()
execution_time = end_time - start_time

logger.info("Received a request with paramters: {} \n Duration of execution: {}", command_request_params, execution_time)
return response

8 changes: 6 additions & 2 deletions backend/data/data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ def validate_params_format(self):
In either of these cases return self. Otherwise raise a ValueError.
The format of the comma seperated values is "data1,data2" so no spaces between data and the commas.
"""
# TODO: (Member) Implement this method
num_values = 0
if self.params != None:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try making these ValueError more descriptive by adding a message string within brackets ValueError("Something went wrong") (of course, make the message descriptive)

if self.format == None: raise ValueError("Only parameters were given, format is None")
if len(self.format.split(",")) != len(self.params.split(",")): raise ValueError("Number of paramters does not match with format information")
else:
if self.format != None: raise ValueError("Only format was given, paramters is none")
return self


Expand All @@ -41,7 +46,6 @@ class Command(BaseSQLModel, table=True):
An instance of a MainCommand.
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
Expand Down
20 changes: 11 additions & 9 deletions frontend/src/display/command_api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios from "axios";
import { CommandListResponse } from "../data/response";
import { API_URL } from "../environment";
import { API_URL } from "../environment";

export const getCommands = async (): Promise<CommandListResponse> => {
try {
Expand All @@ -12,11 +12,13 @@ export const getCommands = async (): Promise<CommandListResponse> => {
}
}

/**
* TODO: (Member) Create a deleteCommand API function based on the following specs. You should be using axios to make the API call
*
* Deletes the command with the given id on the backend and returns the list of commands after its deletion.
*
* @param id: command to delete
* @returns Promise<CommandListResponse>: list of commands after the command with the given id was deleted
*/
export const deleteCommand = async (id: number): Promise<CommandListResponse> => {
try {
await axios.delete<CommandListResponse>(`${API_URL}/commands/${id}`)
const { data } = await axios.get<CommandListResponse>(`${API_URL}/commands/${id}`)
return data;
} catch (error) {
console.error(`Error deleting command with ID ${id}): ${error}`);
throw error
}
}
2 changes: 1 addition & 1 deletion frontend/src/display/table.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CommandResponse } from "../data/response"
import CommandRow from "./row"
import { deleteCommand } from "./command_api.ts"

interface CommandTableProp {
commands: CommandResponse[],
Expand All @@ -13,7 +14,6 @@ const CommandTable = ({

const handleDelete = (id: number) => {
return async () => {
// TODO: (Member) You will need to create a function in `command_api.ts` so you can delete a command.
const data = await deleteCommand(id)
setCommands(data.data)
}
Expand Down
56 changes: 46 additions & 10 deletions frontend/src/input/command_input.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { useState } from "react";
import { CommandResponse, MainCommandResponse } from "../data/response"
import { useState, useEffect } from "react";
import { CommandResponse, MainCommandResponse, CommandListResponse} from "../data/response"
import "./command_input.css"
import { MainCommandListResponse } from "../data/response";
import axios from "axios";
import { API_URL } from "../environment";
import { getMainCommands } from "./input_api"
import { CommandRequest } from "../data/request";

interface CommandInputProp {
setCommands: React.Dispatch<React.SetStateAction<CommandResponse[]>>
Expand All @@ -9,9 +14,15 @@ interface CommandInputProp {
const CommandInput = ({ setCommands }: CommandInputProp) => {
const [selectedCommand, setSelectedCommand] = useState<MainCommandResponse | null>(null);
const [parameters, setParameters] = useState<{ [key: string]: string }>({});
// TODO: (Member) Setup anymore states if necessary
const [mainCommands, setMainCommands] = useState<MainCommandListResponse | null>(null);

// TODO: (Member) Fetch MainCommands in a useEffect
useEffect(() => {
const fetchCommands = async() => {
const data = await getMainCommands();
setMainCommands(data);
};
fetchCommands();
}, []);

const handleParameterChange = (param: string, value: string): void => {
setParameters((prev) => ({
Expand All @@ -20,21 +31,46 @@ const CommandInput = ({ setCommands }: CommandInputProp) => {
}));
}

const handleChange = async (e: React.ChangeEvent<HTMLSelectElement>) => {
if (mainCommands == null) return;
const selectedID = e.target.value;
const command = mainCommands.data.find(c => c.id === Number(selectedID));
setSelectedCommand(command ?? null)
}

const handleSubmit = async (e: React.FormEvent) => {
// TODO:(Member) Submit to your post endpoint
e.preventDefault();
if (!selectedCommand) {
console.error(`Exited early`);
return;
}
const payload: CommandRequest = {
command_type: selectedCommand.id,
params: selectedCommand.params
}
axios.post(`${API_URL}/commands/`, payload);
}

return (
if (mainCommands) return (
<>
<form onSubmit={handleSubmit}>
<div className="spreader">
<div>
<label>Command Type: </label>
<select>{/* TODO: (Member) Display the list of commands based on the get commands request.

<select value={selectedCommand ? selectedCommand.id : ""} onChange={handleChange}

>{/* TODO: (Member) Display the list of commands based on the get commands request.
It should update the `selectedCommand` field when selecting one.*/}
<option value={"1"}>Command 1</option>
{/* <option value={"1"}>Command 1</option>
<option value={"2"}>Command 2</option>
<option value={"3"}>Command 3</option>
<option value={"3"}>Command 3</option> */}

{mainCommands?.data.map(command => (
<option value={command.id}>
{command.name}
</option>
))}
</select>
</div>
{selectedCommand?.params?.split(",").map((param) => (
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading