diff --git a/.env.example b/.env.example
index fa029164..19e6f0e1 100644
--- a/.env.example
+++ b/.env.example
@@ -41,6 +41,7 @@ FRONTEND_TARGET_OVERRIDE=${FRONTEND_TARGET_OVERRIDE:-development}
SITE_NAME=${SITE_NAME:-"DTM-Drone Tasking Manager"}
BASE_URL=${BASE_URL:-http://localhost:8000/api}
API_URL_V1=${API_URL_V1:-http://localhost:8000/api}
+NODE_ODM_URL=${NODE_ODM_URL:-http://odm-api:3000}
### ODM ###
diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml
index 26fd44f7..712165c0 100644
--- a/.github/workflows/build_and_deploy.yml
+++ b/.github/workflows/build_and_deploy.yml
@@ -21,6 +21,7 @@ jobs:
build_target: service
image_name: ghcr.io/${{ github.repository }}/backend
dockerfile: Dockerfile
+ scan_image: false
secrets: inherit
encode-envs:
diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py
index 63b29b80..f6f4e0cc 100644
--- a/src/backend/app/projects/project_routes.py
+++ b/src/backend/app/projects/project_routes.py
@@ -151,7 +151,14 @@ async def delete_project_by_id(
Raises:
HTTPException: If the project is not found.
"""
- project_id = await project_schemas.DbProject.delete(db, project.id)
+ user_id = user_data.id
+ project_id = await project_schemas.DbProject.delete(db, project.id, user_id)
+ if not project_id:
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN,
+ detail="Project not found or user not authorized to delete it.",
+ )
+
return {"message": f"Project successfully deleted {project_id}"}
@@ -311,13 +318,11 @@ async def read_projects(
db, user_id=user_id, skip=skip, limit=limit
)
if not projects:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="No projects found."
- )
+ return []
return projects
except KeyError as e:
- raise HTTPException(status_code=HTTPStatus.NOT_FOUND) from e
+ raise HTTPException(status_code=HTTPStatus.UNPROCESSABLE_ENTITY) from e
@router.get(
diff --git a/src/backend/app/projects/project_schemas.py b/src/backend/app/projects/project_schemas.py
index be52d3c7..b06b4dc6 100644
--- a/src/backend/app/projects/project_schemas.py
+++ b/src/backend/app/projects/project_schemas.py
@@ -395,27 +395,27 @@ async def create(db: Connection, project: ProjectIn, user_id: str) -> uuid.UUID:
return new_project_id[0]
@staticmethod
- async def delete(db: Connection, project_id: uuid.UUID) -> uuid.UUID:
+ async def delete(db: Connection, project_id: uuid.UUID, user_id: str) -> uuid.UUID:
"""Delete a single project."""
sql = """
- WITH deleted_project AS (
- DELETE FROM projects
- WHERE id = %(project_id)s
- RETURNING id
- ), deleted_tasks AS (
- DELETE FROM tasks
- WHERE project_id = %(project_id)s
- RETURNING project_id
- ), deleted_task_events AS (
- DELETE FROM task_events
- WHERE project_id = %(project_id)s
- RETURNING project_id
- )
- SELECT id FROM deleted_project
+ WITH deleted_project AS (
+ DELETE FROM projects
+ WHERE id = %(project_id)s AND author_id = %(user_id)s
+ RETURNING id
+ ), deleted_tasks AS (
+ DELETE FROM tasks
+ WHERE project_id = %(project_id)s
+ RETURNING project_id
+ ), deleted_task_events AS (
+ DELETE FROM task_events
+ WHERE project_id = %(project_id)s
+ RETURNING project_id
+ )
+ SELECT id FROM deleted_project
"""
async with db.cursor() as cur:
- await cur.execute(sql, {"project_id": project_id})
+ await cur.execute(sql, {"project_id": project_id, "user_id": user_id})
deleted_project_id = await cur.fetchone()
if not deleted_project_id:
diff --git a/src/backend/app/users/user_deps.py b/src/backend/app/users/user_deps.py
index 82984d2c..29fd0c2c 100644
--- a/src/backend/app/users/user_deps.py
+++ b/src/backend/app/users/user_deps.py
@@ -1,11 +1,12 @@
+import jwt
from app.users.user_logic import verify_token
-from fastapi import HTTPException, Request, Header
+from fastapi import HTTPException, Request, Security
+from fastapi.security.api_key import APIKeyHeader
from app.config import settings
from app.users.auth import Auth
from app.users.user_schemas import AuthUser
from loguru import logger as log
from datetime import datetime, timedelta
-import jwt
async def init_google_auth():
@@ -27,7 +28,7 @@ async def init_google_auth():
async def login_required(
- request: Request, access_token: str = Header(None)
+ request: Request, access_token: str = Security(APIKeyHeader(name="access-token"))
) -> AuthUser:
"""Dependency to inject into endpoints requiring login."""
if settings.DEBUG:
@@ -41,9 +42,6 @@ async def login_required(
if not access_token:
raise HTTPException(status_code=401, detail="No access token provided")
- if not access_token:
- raise HTTPException(status_code=401, detail="No access token provided")
-
try:
user = verify_token(access_token)
except HTTPException as e:
diff --git a/src/backend/app/users/user_routes.py b/src/backend/app/users/user_routes.py
index fdd8384f..a3fcb185 100644
--- a/src/backend/app/users/user_routes.py
+++ b/src/backend/app/users/user_routes.py
@@ -61,6 +61,14 @@ async def login_access_token(
return Token(access_token=access_token, refresh_token=refresh_token)
+@router.get("/", tags=["users"], response_model=list[user_schemas.DbUser])
+async def get_user(
+ db: Annotated[Connection, Depends(database.get_db)],
+ user_data: Annotated[AuthUser, Depends(login_required)],
+):
+ return await user_schemas.DbUser.all(db)
+
+
@router.patch("/{user_id}/profile")
@router.post("/{user_id}/profile")
async def update_user_profile(
diff --git a/src/backend/app/users/user_schemas.py b/src/backend/app/users/user_schemas.py
index fb61a6fd..63c26df9 100644
--- a/src/backend/app/users/user_schemas.py
+++ b/src/backend/app/users/user_schemas.py
@@ -188,6 +188,17 @@ class DbUser(BaseModel):
name: str
profile_img: Optional[str] = None
+ @staticmethod
+ async def all(db: Connection):
+ "Fetch all users."
+ async with db.cursor(row_factory=class_row(DbUser)) as cur:
+ await cur.execute(
+ """
+ SELECT * FROM users;
+ """
+ )
+ return await cur.fetchall()
+
@staticmethod
async def one(db: Connection, user_id: str):
"""Fetch user from the database by user_id."""
diff --git a/src/frontend/src/components/CreateProject/FormContents/GenerateTask/index.tsx b/src/frontend/src/components/CreateProject/FormContents/GenerateTask/index.tsx
index 36cf3f57..0357e2db 100644
--- a/src/frontend/src/components/CreateProject/FormContents/GenerateTask/index.tsx
+++ b/src/frontend/src/components/CreateProject/FormContents/GenerateTask/index.tsx
@@ -65,6 +65,7 @@ export default function GenerateTask({ formProps }: { formProps: any }) {
onFocus={() => setError('')}
/>
{error &&
Recommended : 50-700
diff --git a/src/frontend/src/components/LandingPage/ClientsAndPartners/index.tsx b/src/frontend/src/components/LandingPage/ClientsAndPartners/index.tsx index 23b1a33d..546a97b7 100644 --- a/src/frontend/src/components/LandingPage/ClientsAndPartners/index.tsx +++ b/src/frontend/src/components/LandingPage/ClientsAndPartners/index.tsx @@ -3,7 +3,6 @@ import { motion } from 'framer-motion'; import worldBankLogo from '@Assets/images/LandingPage/WorldbankLogo.png'; import { fadeUpVariant } from '@Constants/animations'; import gfdrrLogo from '@Assets/images/GFDRR-logo.png'; -import JamaicaFlyingLabsLogo from '@Assets/images/LandingPage/JamaicaFlyingLabs_Logo.png'; import { FlexRow } from '@Components/common/Layouts'; export default function ClientAndPartners() { @@ -34,7 +33,6 @@ export default function ClientAndPartners() { >+ Official Training Partner +
+
© Drone Arial Tasking Manager. All Rights Reserved 2024
diff --git a/src/frontend/src/services/tasks.ts b/src/frontend/src/services/tasks.ts
index 47a7f4d1..d24703ad 100644
--- a/src/frontend/src/services/tasks.ts
+++ b/src/frontend/src/services/tasks.ts
@@ -20,3 +20,6 @@ export const postTaskWaypoint = (payload: Record