Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
27 changes: 23 additions & 4 deletions backend/api/endpoints/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,42 @@ 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(**payload.model_dump())
db.add(command)
db.commit()

db.refresh(command)

return {"data" : command}



@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)
result = db.exec(query).first()

if result is None:
raise HTTPException(status_code=404, detail=f"Command with id {id} not found.")

db.delete(result)
db.commit()

query = select(Command)
items = db.exec(query).all()
return {"data": items}
9 changes: 8 additions & 1 deletion backend/api/middlewares/logger_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from typing import Any
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
from loguru import logger
import time


class LoggerMiddleware(BaseHTTPMiddleware):
Expand All @@ -17,6 +19,11 @@ 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
start = time.perf_counter()
now = time.ctime(time.time())

response = await call_next(request)

duration = time.perf_counter() - start
logger.info(f"Response sent in {duration} seconds to Request params: {request.path_params} recieved at {now}")
return response
15 changes: 13 additions & 2 deletions backend/data/data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,21 @@ 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
return self

if self.params is None and self.format is None:
return self

if not (isinstance(self.params, str) and isinstance(self.format, str)):
raise ValueError("Params and format must either both be None or both be comma-separated strings.")

num_values_params = len(self.params.split(","))
num_values_format = len(self.params.split(","))

if num_values_params > 0 and num_values_params == num_values_format:
return self

raise ValueError("Params and format must either both be None or both be comma-separated strings.")

class Command(BaseSQLModel, table=True):
"""
An instance of a MainCommand.
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/data/request.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export interface CommandRequest {
name: string
command_type: string
params: string | null
format: string | null
}

9 changes: 9 additions & 0 deletions frontend/src/display/command_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,12 @@ export const getCommands = async (): Promise<CommandListResponse> => {
* @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 {
const { data } = await axios.delete(`${API_URL}/commands/${id}`)
return data;
} catch (error) {
console.error(error)
throw error
}
}
45 changes: 28 additions & 17 deletions frontend/src/display/table.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
import { useEffect, useState } from "react"
import { CommandResponse } from "../data/response"
import { getCommands } from "./command_api"
import CommandRow from "./row"
import { useEffect, useState } from "react";
import { CommandResponse } from "../data/response";
import { getCommands, deleteCommand } from "./command_api";

import CommandRow from "./row";

const CommandTable = () => {
const [commands, setCommands] = useState<CommandResponse[]>([])
const [commands, setCommands] = useState<CommandResponse[]>([]);

useEffect(() => {
const getCommandsFn = async () => {
const data = await getCommands();
setCommands(data.data)
}
try {
const data = await getCommands();
setCommands(data.data);
} catch (error) {
alert("Failed to retrieve commands");
}
};

getCommandsFn();
}, [])
}, []);

const handleDelete = (id: number) => {
return () => {
// TODO: (Member) Handle delete logic here
// You will need to create a function in `command_api.ts` before you can finish this part.
return async () => {
try {
await deleteCommand(id);
window.location.reload();
} catch (error) {
alert(`Failed to delete command with id ${id}`);
}

}
}
};
};

return (
<table>
Expand All @@ -37,10 +46,12 @@ const CommandTable = () => {
</tr>
</thead>
<thead>
{commands.map(value => (<CommandRow {...value} handleDelete={handleDelete(value.id)} />))}
{commands.map((value) => (
<CommandRow {...value} handleDelete={handleDelete(value.id)} />
))}
</thead>
</table>
)
}
);
};

export default CommandTable;
73 changes: 60 additions & 13 deletions frontend/src/input/command_input.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,77 @@
import "./command_input.css"
import { useEffect, useState } from "react";
import { MainCommandResponse } from "../data/response";
import "./command_input.css";
import { createCommand, getMainCommands } from "./input_api";
import { CommandRequest } from "../data/request";

const CommandInput = () => {
// TODO: (Member) Setup state and useEffect calls here
const [mainCommands, setMainCommands] = useState<MainCommandResponse[]>([]);

const handleSubmit = () => {
// TODO:(Member) Submit to your post endpoint
}
const [selectedCommandId, setSelectedCommandId] = useState<string | null>(
null
);
const [commandParams, setCommandParams] = useState<string | null>(null);

useEffect(() => {
const getMainCommandsFn = async () => {
try {
const data = await getMainCommands();
setMainCommands(data.data);
setSelectedCommandId(data.data[0].id.toString());
} catch (error) {
alert("Failed to retrieve main commands");
}
};

getMainCommandsFn();
}, []);

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();

console.log("submitting command");

if (!selectedCommandId) {
alert(`Select a command with an existing ID before submitting`);
return;
}

const command: CommandRequest = {
command_type: selectedCommandId,
params: commandParams,
};

try {
createCommand(command);
window.location.reload();
} catch (err) {
console.log("Failed to create command:", err);
}
};

return (
<>
<form>
<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*/}
<option value={"1"}>Command 1</option>
<option value={"2"}>Command 2</option>
<option value={"3"}>Command 3</option>
<select onChange={(e) => setSelectedCommandId(e.target.value)}>
{/* TODO: (Member) Display the list of commands based on the get commands request*/}
{mainCommands.map((command, index) => (
<option key={index} value={command.id}>
{command.name}
</option>
))}
</select>
</div>
<input /> {/* TODO: (Member) Add input handling here if the selected command has a param input*/}
<button onClick={handleSubmit}>Submit</button>
<input onChange={(e) => setCommandParams(e.target.value)} />{" "}
Copy link
Contributor

Choose a reason for hiding this comment

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

Each command can have different types and number of arguments and the frontend should be able to handle that. They are encoded in the mainCommands

{/* TODO: (Member) Add input handling here if the selected command has a param input*/}
<button type="submit">Submit</button>
</div>
</form>
</>
)
}
);
};

export default CommandInput;