diff --git a/backend/api/resource.py b/backend/api/resource.py index ff449e2..d698a0d 100644 --- a/backend/api/resource.py +++ b/backend/api/resource.py @@ -60,12 +60,12 @@ def update( return resource_svc.update(subject, resource) -@api.delete("", response_model=None, tags=["Resource"]) +@api.delete("", response_model=dict, tags=["Resource"]) def delete( uuid: str, - resource: Resource, + id: int, user_svc: UserService = Depends(), resource_svc: ResourceService = Depends(), ): subject = user_svc.get_user_by_uuid(uuid) - resource_svc.delete(subject, resource) + return resource_svc.delete(subject, id) diff --git a/backend/api/service.py b/backend/api/service.py index bd3c4dc..844c840 100644 --- a/backend/api/service.py +++ b/backend/api/service.py @@ -19,7 +19,10 @@ openapi_tags = { # TODO: Create custom exceptions @api.post("", response_model=Service, tags=["Service"]) def create( - uuid: str, service: Service, user_svc: UserService = Depends(), service_svc: ServiceService = Depends() + uuid: str, + service: Service, + user_svc: UserService = Depends(), + service_svc: ServiceService = Depends(), ): subject = user_svc.get_user_by_uuid(uuid) return service_svc.create(subject, service) @@ -27,28 +30,42 @@ def create( @api.get("", response_model=List[Service], tags=["Service"]) def get_all( - uuid: str, user_svc: UserService = Depends(), service_svc: ServiceService = Depends() + uuid: str, + user_svc: UserService = Depends(), + service_svc: ServiceService = Depends(), ): subject = user_svc.get_user_by_uuid(uuid) return service_svc.get_service_by_user(subject) + @api.get("/{name}", response_model=Service, tags=["Service"]) def get_by_name( - name: str, uuid: str, user_svc: UserService = Depends(), service_svc: ServiceService = Depends() + name: str, + uuid: str, + user_svc: UserService = Depends(), + service_svc: ServiceService = Depends(), ): subject = user_svc.get_user_by_uuid(uuid) return service_svc.get_service_by_name(name, subject) + @api.put("", response_model=Service, tags=["Service"]) def update( - uuid: str, service: Service, user_svc: UserService = Depends(), service_svc: ServiceService = Depends() + uuid: str, + service: Service, + user_svc: UserService = Depends(), + service_svc: ServiceService = Depends(), ): subject = user_svc.get_user_by_uuid(uuid) return service_svc.update(subject, service) -@api.delete("", response_model=None, tags=["Service"]) + +@api.delete("", response_model=dict, tags=["Service"]) def delete( - uuid: str, service: Service, user_svc: UserService = Depends(), service_svc: ServiceService = Depends() + uuid: str, + id: int, + user_svc: UserService = Depends(), + service_svc: ServiceService = Depends(), ): subject = user_svc.get_user_by_uuid(uuid) - service_svc.delete(subject, service) + return service_svc.delete(subject, id) diff --git a/backend/api/user.py b/backend/api/user.py index bcf0eaa..36e2d15 100644 --- a/backend/api/user.py +++ b/backend/api/user.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, HTTPException from ..services import UserService from ..models.user_model import User, UserTypeEnum @@ -46,3 +46,14 @@ def update_user(uuid: str, user: User, user_svc: UserService = Depends()): raise Exception(f"Insufficient permissions for user {subject.uuid}") return user_svc.update(user) + + +@api.delete("/", response_model=dict, tags=["Users"]) +def delete_user(uuid: str, id: int, user_svc: UserService = Depends()): + subject = user_svc.get_user_by_uuid(uuid) + + try: + user_svc.delete_by_id(id, subject) + return {"message": "User deleted successfully"} + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) diff --git a/backend/services/resource.py b/backend/services/resource.py index eefb3b7..de8e657 100644 --- a/backend/services/resource.py +++ b/backend/services/resource.py @@ -93,7 +93,7 @@ class ResourceService: self._session.commit() return entity.to_model() - def delete(self, subject: User, resource: Resource) -> None: + def delete(self, subject: User, id: int) -> None: """ Delete resource based on id that the user has access to Parameters: @@ -106,15 +106,16 @@ class ResourceService: raise ProgramNotAssignedException( f"User is not {UserTypeEnum.ADMIN}, cannot update service" ) - query = select(ResourceEntity).where(ResourceEntity.id == resource.id) + + query = select(ResourceEntity).where(ResourceEntity.id == id) entity = self._session.scalars(query).one_or_none() if entity is None: - raise ResourceNotFoundException( - f"No resource found with matching id: {resource.id}" - ) + raise ResourceNotFoundException(f"No resource found with matching id: {id}") self._session.delete(entity) self._session.commit() + return {"message": "Resource deleted successfully"} + def get_by_slug(self, user: User, search_string: str) -> list[Resource]: """ Get a list of resources given a search string that the user has access to diff --git a/backend/services/service.py b/backend/services/service.py index e82b305..28ad493 100644 --- a/backend/services/service.py +++ b/backend/services/service.py @@ -90,12 +90,12 @@ class ServiceService: return entity.to_model() - def delete(self, subject: User, service: Service) -> None: + def delete(self, subject: User, id: int) -> None: """Deletes a service from the table.""" if subject.role != UserTypeEnum.ADMIN: raise ProgramNotAssignedException(f"User is not {UserTypeEnum.ADMIN}") - query = select(ServiceEntity).where(ServiceEntity.id == service.id) + query = select(ServiceEntity).where(ServiceEntity.id == id) entity = self._session.scalars(query).one_or_none() if entity is None: @@ -105,3 +105,5 @@ class ServiceService: self._session.delete(entity) self._session.commit() + + return {"message": "Service deleted successfully"} diff --git a/backend/services/user.py b/backend/services/user.py index af91309..119fafa 100644 --- a/backend/services/user.py +++ b/backend/services/user.py @@ -4,6 +4,7 @@ from sqlalchemy.orm import Session from ..entities.user_entity import UserEntity from ..models.user_model import User from sqlalchemy import select +from ..models.enum_for_models import UserTypeEnum class UserService: @@ -89,6 +90,22 @@ class UserService: self._session.delete(obj) self._session.commit() + def delete_by_id(self, id: int, user: User) -> None: + """ + Delete a user by id + + Args: the id of the user to delete + + Returns: none + """ + + if user.role != UserTypeEnum.ADMIN: + raise Exception(f"Insufficient permissions for user {user.uuid}") + + obj = self._session.get(UserEntity, id) + self._session.delete(obj) + self._session.commit() + def update(self, user: User) -> User: """ Updates a user diff --git a/compass/app/api/resource/delete/route.ts b/compass/app/api/resource/delete/route.ts new file mode 100644 index 0000000..5e908e0 --- /dev/null +++ b/compass/app/api/resource/delete/route.ts @@ -0,0 +1,39 @@ +import { NextResponse } from "next/server"; + +export async function DELETE(request: Request) { + const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/resource`; + + try { + const { searchParams } = new URL(request.url); + const uuid = searchParams.get("uuid"); + const resource_id = searchParams.get("id"); + + console.log("Resource to be deleted", resource_id); + + // Send the POST request to the backend + const response = await fetch( + `${apiEndpoint}?uuid=${uuid}&id=${resource_id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return NextResponse.json( + { message: "Resource deleted successfully" }, + { status: response.status } + ); + } catch (error) { + console.error("Error deleting resource:", error); + return NextResponse.json( + { error: "Failed to delete resource" }, + { status: 500 } + ); + } +} diff --git a/compass/app/api/service/delete/route.ts b/compass/app/api/service/delete/route.ts new file mode 100644 index 0000000..088e5e4 --- /dev/null +++ b/compass/app/api/service/delete/route.ts @@ -0,0 +1,39 @@ +import { NextResponse } from "next/server"; + +export async function DELETE(request: Request) { + const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/service`; + + try { + const { searchParams } = new URL(request.url); + const uuid = searchParams.get("uuid"); + const service_id = searchParams.get("id"); + + console.log("Service to be deleted", service_id); + + // Send the POST request to the backend + const response = await fetch( + `${apiEndpoint}?uuid=${uuid}&id=${service_id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return NextResponse.json( + { message: "Service deleted successfully" }, + { status: response.status } + ); + } catch (error) { + console.error("Error deleting service:", error); + return NextResponse.json( + { error: "Failed to delete service" }, + { status: 500 } + ); + } +} diff --git a/compass/app/api/user/delete/route.ts b/compass/app/api/user/delete/route.ts new file mode 100644 index 0000000..ac24724 --- /dev/null +++ b/compass/app/api/user/delete/route.ts @@ -0,0 +1,39 @@ +import { NextResponse } from "next/server"; + +export async function DELETE(request: Request) { + const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/user`; + + try { + const { searchParams } = new URL(request.url); + const uuid = searchParams.get("uuid"); + const user_id = searchParams.get("id"); + + console.log("User to be deleted", user_id); + + // Send the POST request to the backend + const response = await fetch( + `${apiEndpoint}?uuid=${uuid}&id=${user_id}`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return NextResponse.json( + { message: "User deleted successfully" }, + { status: response.status } + ); + } catch (error) { + console.error("Error deleting user:", error); + return NextResponse.json( + { error: "Failed to delete user" }, + { status: 500 } + ); + } +} diff --git a/compass/components/Table/ResourceTable.tsx b/compass/components/Table/ResourceTable.tsx index a1e6391..e5076b2 100644 --- a/compass/components/Table/ResourceTable.tsx +++ b/compass/components/Table/ResourceTable.tsx @@ -143,6 +143,7 @@ export default function ResourceTable({ columns={columns} details={resourceDetails} createEndpoint={`/api/resource/create?uuid=${user?.uuid}`} + deleteEndpoint={`/api/resource/delete?uuid=${user?.uuid}`} isAdmin={user?.role === "ADMIN"} /> ); diff --git a/compass/components/Table/RowOptionMenu.tsx b/compass/components/Table/RowOptionMenu.tsx index 850b129..4f4a51e 100644 --- a/compass/components/Table/RowOptionMenu.tsx +++ b/compass/components/Table/RowOptionMenu.tsx @@ -12,7 +12,7 @@ import { useState, useEffect, useRef } from "react"; import { RowOption } from "./RowOption"; interface RowOptionMenuProps { - onDelete?: () => void; + onDelete: () => void; onHide: () => void; visible: boolean; } @@ -26,6 +26,13 @@ export const RowOptionMenu = ({ const menuRef = useRef(null); const buttonRef = useRef(null); + const handleDelete = () => { + if (window.confirm("Are you sure you want to delete this item?")) { + onDelete(); + setMenuOpen(false); + } + }; + useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( @@ -60,18 +67,11 @@ export const RowOptionMenu = ({ (!menuOpen ? " invisible" : "") } > - {onDelete && ( - - )} - {/* {}} - /> */} + ); diff --git a/compass/components/Table/Table.tsx b/compass/components/Table/Table.tsx index b20dc00..05817bc 100644 --- a/compass/components/Table/Table.tsx +++ b/compass/components/Table/Table.tsx @@ -28,6 +28,7 @@ type TableProps = { columns: ColumnDef[]; details: Details[]; createEndpoint: string; + deleteEndpoint: string; isAdmin?: boolean; }; @@ -79,12 +80,24 @@ export default function Table({ columns, details, createEndpoint, + deleteEndpoint, isAdmin = false, }: TableProps) { console.log(data); const columnHelper = createColumnHelper(); + const deleteRow = async (id: number) => { + const response = await fetch(`${deleteEndpoint}&id=${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + + return response.ok; + }; + const createRow = async (newItem: any) => { const response = await fetch(createEndpoint, { method: "POST", @@ -144,7 +157,23 @@ export default function Table({ id: "options", cell: (props) => ( console.log("delete")} + onDelete={() => { + deleteRow(props.row.original.id).then( + (response) => { + if (response) { + setData((prev) => + prev.filter( + (data) => + data.id !== + props.row.original.id + ) + ); + } else { + alert("Failed to delete row!"); + } + } + ); + }} onHide={() => hideData(props.row.original.id)} visible={props.row.original.visible} /> diff --git a/compass/components/Table/UserTable.tsx b/compass/components/Table/UserTable.tsx index 3754d83..9665b41 100644 --- a/compass/components/Table/UserTable.tsx +++ b/compass/components/Table/UserTable.tsx @@ -143,6 +143,7 @@ export default function UserTable({ data, setData, user }: UserTableProps) { columns={columns} details={userDetails} createEndpoint={`/api/user/create?uuid=${user?.uuid}`} + deleteEndpoint={`/api/user/delete?uuid=${user?.uuid}`} isAdmin={user?.role === "ADMIN"} /> );