Skip to content

Commit

Permalink
Feat: Updated endpoints(download-assets) to fetch task assets status. (
Browse files Browse the repository at this point in the history
…#282)

* feat: added new assets download endpoint

* fix: remove await from assets download endpoint

* feat: added users authentication

* chore: update verison drone_flightplan to v0.3.1

* fix: resolve deprecated async pool initialization in psycopg

* Feat: Update assets downloading endpoint on individual project (#284)

* fix(task-description): set height to update takeoff point button section

* feat(project-dashboard): udpdate row per page options

* feat(project-dashboard): update map and project card style

* feat(task-description): update assest information api endpoint

* feat(individual-project): get assets list

* feat(individual-project):fetch all assets and modify project details to insert each assets on respective task detail

---------

Co-authored-by: Sujit <sujitkarki703@gmail.com>

---------

Co-authored-by: Sujit <90745363+suzit-10@users.noreply.github.com>
Co-authored-by: Sujit <sujitkarki703@gmail.com>
  • Loading branch information
3 people authored Oct 14, 2024
1 parent 6a42ca2 commit c5dc954
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 66 deletions.
4 changes: 3 additions & 1 deletion src/backend/app/db/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

async def get_db_connection_pool() -> AsyncConnectionPool:
"""Get the connection pool for psycopg."""
return AsyncConnectionPool(conninfo=settings.DTM_DB_URL.unicode_string())
pool = AsyncConnectionPool(conninfo=settings.DTM_DB_URL.unicode_string())
await pool.open() # Explicitly open the pool
return pool


async def get_db(request: Request) -> AsyncGenerator[Connection, None]:
Expand Down
23 changes: 9 additions & 14 deletions src/backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@
from fastapi.middleware.cors import CORSMiddleware
from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse, JSONResponse

from psycopg_pool import AsyncConnectionPool
from app.config import settings
from app.projects import project_routes
from app.drones import drone_routes
from app.waypoints import waypoint_routes
from app.users import user_routes
from app.tasks import task_routes
from app.db.database import get_db_connection_pool


root = os.path.dirname(os.path.abspath(__file__))
Expand Down Expand Up @@ -105,23 +104,19 @@ def get_application() -> FastAPI:


@asynccontextmanager
async def lifespan(
app: FastAPI,
):
async def lifespan(app: FastAPI):
"""FastAPI startup/shutdown event."""
log.debug("Starting up FastAPI server.")

db_pool = await get_db_connection_pool()
await db_pool.open()
# Create a pooled db connection and make available in app state
# NOTE we can access 'request.app.state.db_pool' in endpoints
app.state.db_pool = db_pool

yield
async with AsyncConnectionPool(
conninfo=settings.DTM_DB_URL.unicode_string()
) as db_pool:
# The pool is now used within the context manager
app.state.db_pool = db_pool
yield # FastAPI will run the application here

# Shutdown events
# Pool will be closed automatically when the context manager exits
log.debug("Shutting down FastAPI server.")
await app.state.db_pool.close()


api = get_application()
Expand Down
26 changes: 24 additions & 2 deletions src/backend/app/projects/project_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,35 @@
from fastapi import Depends, HTTPException, Path, File, UploadFile
from psycopg import Connection
from geojson import FeatureCollection

from psycopg.rows import dict_row
from app.db import database
from app.models.enums import HTTPStatus
from app.projects.project_schemas import DbProject
from app.utils import multipolygon_to_polygon


async def get_tasks_by_project_id(project_id: UUID, db: Connection):
"""Get tasks by project id."""
try:
async with db.cursor(row_factory=dict_row) as cur:
await cur.execute(
"""SELECT id FROM tasks WHERE project_id = %(project_id)s""",
{"project_id": project_id},
)

data = await cur.fetchall()

if data is None:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail="No tasks found for this project.",
)
return data

except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


async def get_project_by_id(
project_id: Annotated[
UUID,
Expand All @@ -27,7 +49,7 @@ async def get_project_by_id(
try:
return await DbProject.one(db, project_id)
except KeyError as e:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND) from e
raise HTTPException(status_code=HTTPStatus.FORBIDDEN) from e


async def geojson_upload(
Expand Down
27 changes: 22 additions & 5 deletions src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,17 +388,34 @@ async def process_imagery(


@router.get(
"/assets/{project_id}/{task_id}/",
"/assets/{project_id}/",
tags=["Image Processing"],
response_model=project_schemas.AssetsInfo,
)
async def get_assets_info(
user_data: Annotated[AuthUser, Depends(login_required)],
db: Annotated[Connection, Depends(database.get_db)],
project: Annotated[
project_schemas.DbProject, Depends(project_deps.get_project_by_id)
],
task_id: uuid.UUID,
task_id: Optional[uuid.UUID] = None,
):
"""
Endpoint to get the number of images and the URL to download the assets for a given project and task.
Endpoint to get the number of images and the URL to download the assets
for a given project and task. If no task_id is provided, returns info
for all tasks associated with the project.
"""
return project_logic.get_project_info_from_s3(project.id, task_id)
if task_id is None:
# Fetch all tasks associated with the project
tasks = await project_deps.get_tasks_by_project_id(project.id, db)

results = []

for task in tasks:
task_info = project_logic.get_project_info_from_s3(
project.id, task.get("id")
)
results.append(task_info)

return results
else:
return project_logic.get_project_info_from_s3(project.id, task_id)
19 changes: 0 additions & 19 deletions src/backend/app/projects/project_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import uuid
from typing import Annotated, Optional, List
from datetime import datetime, date
from app.projects import project_logic
import geojson
from loguru import logger as log
from pydantic import BaseModel, computed_field, Field, model_validator, root_validator
Expand Down Expand Up @@ -147,24 +146,6 @@ class TaskOut(BaseModel):
image_count: Optional[int] = None
assets_url: Optional[str] = None

@model_validator(mode="after")
def set_assets_url(cls, values):
"""Set image_url and image count before rendering the model."""
task_id = values.id
project_id = values.project_id

if task_id and project_id:
data = project_logic.get_project_info_from_s3(project_id, task_id)
if data:
return values.copy(
update={
"assets_url": data.assets_url,
"image_count": data.image_count,
}
)

return values


class DbProject(BaseModel):
"""Project model for extracting from database."""
Expand Down
44 changes: 25 additions & 19 deletions src/backend/pdm.lock

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

2 changes: 1 addition & 1 deletion src/backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ dependencies = [
"GDAL==3.6.2",
"aiosmtplib>=3.0.1",
"python-slugify>=8.0.4",
"drone-flightplan==0.3.1rc4",
"psycopg2>=2.9.9",
"pyodm>=1.5.11",
"asgiref>=3.8.1",
"drone-flightplan>=0.3.1",
]
requires-python = ">=3.11"
license = {text = "GPL-3.0-only"}
Expand Down
14 changes: 13 additions & 1 deletion src/frontend/src/api/projects.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable import/prefer-default-export */
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
import { getProjectsList, getProjectDetail } from '@Services/createproject';
import { getTaskStates } from '@Services/project';
import { getAllAssetsUrl, getTaskStates } from '@Services/project';
import { getUserProfileInfo } from '@Services/common';

export const useGetProjectsListQuery = (
Expand Down Expand Up @@ -61,3 +61,15 @@ export const useGetUserDetailsQuery = (
...queryOptions,
});
};

export const useGetAllAssetsUrlQuery = (
projectId: string,
queryOptions?: Partial<UseQueryOptions>,
) => {
return useQuery({
queryKey: ['all-assets-url'],
queryFn: () => getAllAssetsUrl(projectId),
select: (data: any) => data.data,
...queryOptions,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ export default function TableSection({ isFetching }: { isFetching: boolean }) {
user: curr?.name || '-',
task_mapped: `Task# ${curr?.project_task_index}`,
task_state: curr?.state,
assets_url: curr?.assets_url,
image_count: curr?.image_count,
assets_url: curr?.assetsDetail?.assets_url,
image_count: curr?.assetsDetail?.image_count,
},
];
}, []);
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/src/services/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ export const postTaskStatus = (payload: Record<string, any>) => {
};
export const getRequestedTasks = () =>
authenticated(api).get('/tasks/requested_tasks/pending');

export const getAllAssetsUrl = (projectId: string) =>
authenticated(api).get(`/projects/assets/${projectId}`);
2 changes: 1 addition & 1 deletion src/frontend/src/services/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const postTaskWaypoint = (payload: Record<string, any>) => {
);
};
export const getTaskAssetsInfo = (projectId: string, taskId: string) =>
authenticated(api).get(`/projects/assets/${projectId}/${taskId}/`);
authenticated(api).get(`/projects/assets/${projectId}/?task_id=${taskId}`);

export const postProcessImagery = (projectId: string, taskId: string) =>
authenticated(api).post(`/projects/process_imagery/${projectId}/${taskId}/`);
14 changes: 13 additions & 1 deletion src/frontend/src/views/IndividualProject/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/* eslint-disable jsx-a11y/interactive-supports-focus */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import { useGetProjectsDetailQuery } from '@Api/projects';
import {
useGetAllAssetsUrlQuery,
useGetProjectsDetailQuery,
} from '@Api/projects';
import Tab from '@Components/common/Tabs';
import {
Contributions,
Expand Down Expand Up @@ -43,14 +46,23 @@ const IndividualProject = () => {
state => state.project.individualProjectActiveTab,
);

const { data: allUrls, isFetching } = useGetAllAssetsUrlQuery(id as string);

const getTasksAssets = (taskID: string, assetsList: any[]) => {
if (!assetsList || !taskID) return null;
return assetsList.find((assets: any) => assets?.task_id === taskID);
};

const { data: projectData, isFetching: isProjectDataFetching } =
useGetProjectsDetailQuery(id as string, {
enabled: !!allUrls && !isFetching,
onSuccess: (res: any) => {
dispatch(
setProjectState({
// modify each task geojson and set locked user id and name to properties and save to redux state called taskData
tasksData: res.tasks?.map((task: Record<string, any>) => ({
...task,
assetsDetail: getTasksAssets(task?.id, allUrls as any[]),
outline: {
...task.outline,
properties: {
Expand Down

0 comments on commit c5dc954

Please sign in to comment.