Skip to content

Commit

Permalink
Merge pull request #251 from hotosm/develop
Browse files Browse the repository at this point in the history
Production release
  • Loading branch information
nrjadkry authored Sep 30, 2024
2 parents da22514 + 1903f36 commit 8c3bf01
Show file tree
Hide file tree
Showing 16 changed files with 129 additions and 57 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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 ###
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/build_and_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
build_target: service
image_name: ghcr.io/${{ github.repository }}/backend
dockerfile: Dockerfile
scan_image: false
secrets: inherit

encode-envs:
Expand Down
15 changes: 10 additions & 5 deletions src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"}


Expand Down Expand Up @@ -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(
Expand Down
32 changes: 16 additions & 16 deletions src/backend/app/projects/project_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 4 additions & 6 deletions src/backend/app/users/user_deps.py
Original file line number Diff line number Diff line change
@@ -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():
Expand All @@ -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:
Expand All @@ -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:
Expand Down
8 changes: 8 additions & 0 deletions src/backend/app/users/user_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
11 changes: 11 additions & 0 deletions src/backend/app/users/user_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export default function GenerateTask({ formProps }: { formProps: any }) {
onFocus={() => setError('')}
/>
{error && <ErrorMessage message={error} />}
<p className="naxatw-text-[#68707F]">Recommended : 50-700</p>
</FormControl>
<Button
withLoader
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import { format } from 'date-fns';
import { toast } from 'react-toastify';
import {
useGetIndividualTaskQuery,
Expand Down Expand Up @@ -42,7 +41,7 @@ const DescriptionBox = () => {
{
name: 'Created date',
value: taskData?.created_at
? format(new Date(taskData?.created_at), 'yyyy-mm-dd')
? taskData?.created_at?.slice(0, 10) || '-'
: null,
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable no-console */
/* eslint-disable no-unused-vars */
import { useMutation } from '@tanstack/react-query';
import { motion } from 'framer-motion';
import { useEffect, useRef, useState } from 'react';

import { motion } from 'framer-motion';
import { toast } from 'react-toastify';
import { Button } from '@Components/RadixComponents/Button';
import { getImageUploadLink } from '@Services/droneOperator';
import { useMutation } from '@tanstack/react-query';
import {
checkAllImages,
setCheckedImages,
Expand All @@ -19,6 +19,8 @@ import callApiSimultaneously from '@Utils/callApiSimultaneously';
import chunkArray from '@Utils/createChunksOfArray';
import delay from '@Utils/createDelay';
import widthCalulator from '@Utils/percentageCalculator';
import { postProcessImagery } from '@Services/tasks';

import FilesUploadingPopOver from '../LoadingBox';
import ImageCard from './ImageCard';
import PreviewImage from './PreviewImage';
Expand All @@ -31,7 +33,6 @@ import PreviewImage from './PreviewImage';
const ImageBoxPopOver = () => {
const dispatch = useTypedDispatch();

// const { taskId, projectId } = useParams();
const pathname = window.location.pathname?.split('/');
const projectId = pathname?.[2];
const taskId = pathname?.[4];
Expand All @@ -50,6 +51,13 @@ const ImageBoxPopOver = () => {
state => state.droneOperatorTask.checkedImages,
);

const { mutate: startImageryProcess } = useMutation({
mutationFn: () => postProcessImagery(projectId, taskId),
onSuccess: () => toast.success('Image processing started'),
// retry: (failureCount: any, error: any) =>
// error.status === 307 && failureCount < 5,
});

// function that gets the signed urls for the images and again puts them in chunks of 4
const { mutate } = useMutation({
mutationFn: async (data: any) => {
Expand All @@ -75,6 +83,7 @@ const ImageBoxPopOver = () => {
await delay(500);
}
}
startImageryProcess();
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ const MapSection = ({ className }: { className?: string }) => {
isLoading={isUpdatingTakeOffPoint}
>
{newTakeOffPoint
? 'Save Starting Point'
: 'Change Starting Point'}
? 'Save Take off Point'
: 'Change Take off Point'}
</Button>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -34,7 +33,6 @@ export default function ClientAndPartners() {
>
<Image src={worldBankLogo} alt="world bank logo" />
<Image src={gfdrrLogo} alt="gfdrrLogo" width={260} />
<Image src={JamaicaFlyingLabsLogo} alt="gfdrrLogo" width={200} />
</FlexRow>
</motion.div>
</div>
Expand Down
15 changes: 13 additions & 2 deletions src/frontend/src/components/LandingPage/Footer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable no-unused-vars */
import { Flex, FlexRow } from '@Components/common/Layouts';
import Icon from '@Components/common/Icon';
import { FlexRow } from '@Components/common/Layouts';
// import Icon from '@Components/common/Icon';
import Image from '@Components/RadixComponents/Image';
import JamaicaFlyingLabsLogo from '@Assets/images/LandingPage/JamaicaFlyingLabs_Logo.png';
import naxaLogo from '@Assets/images/LandingPage/Naxa-logo.png';
import hotLogo from '@Assets/images/LandingPage/HOT-logo.png';
import { Button } from '@Components/RadixComponents/Button';
Expand Down Expand Up @@ -57,6 +58,16 @@ export default function Footer() {
<span className="naxatw-cursor-pointer">FAQs</span>
<span className="naxatw-cursor-pointer">Cookies</span>
</div> */}
<div className="naxatw-flex naxatw-w-[200px] naxatw-flex-col naxatw-items-start">
<p className="naxatw-mt-2 naxatw-text-center naxatw-text-base naxatw-text-landing-grey">
Official Training Partner
</p>
<Image
src={JamaicaFlyingLabsLogo}
alt="Jamaica-Flying-Labs-Logo"
width={200}
/>
</div>
</motion.div>
<p className="naxatw-mt-2 naxatw-text-center naxatw-text-base naxatw-text-landing-grey">
© Drone Arial Tasking Manager. All Rights Reserved 2024
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/src/services/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ export const postTaskWaypoint = (payload: Record<string, any>) => {
};
export const getTaskAssetsInfo = (projectId: string, taskId: string) =>
authenticated(api).get(`/projects/assets/${projectId}/${taskId}/`);

export const postProcessImagery = (projectId: string, taskId: string) =>
authenticated(api).post(`/projects/process_imagery/${projectId}/${taskId}/`);
33 changes: 29 additions & 4 deletions src/frontend/src/utils/callApiSimultaneously.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
import axios from 'axios';
import { toast } from 'react-toastify';

// function that calls the api simultaneously
export default async function callApiSimultaneously(urls: any, data: any) {
const promises = urls.map((url: any, index: any) =>
axios.put(url, data[index]),
// eslint-disable-next-line no-promise-executor-return
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

const retryFc = async (
url: string,
singleData: any,
n: number,
): Promise<any> => {
try {
return await axios.put(url, singleData);
} catch (err) {
if (n === 1) throw err;
delay(1000); // 1 sec delay
// eslint-disable-next-line no-return-await
return await retryFc(url, singleData, n - 1);
}
};

const promises = urls.map(
(url: any, index: any) => retryFc(url, data[index], 3), // 3 entries for each api call
);
const responses = await Promise.all(promises);
return responses;

try {
const responses = await Promise.all(promises);
return responses;
} catch (err) {
toast.error('Error occurred on image upload');
throw err;
}
}
30 changes: 16 additions & 14 deletions src/frontend/src/views/Projects/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ const Projects = () => {
);

// fetch api for projectsList
const { data: projectsList, isLoading } = useGetProjectsListQuery(
projectsFilterByOwner,
);
const { data: projectsList, isLoading }: Record<string, any> =
useGetProjectsListQuery(projectsFilterByOwner);

const { data: userDetails } = useGetUserDetailsQuery();
const localStorageUserDetails = getLocalStorageValue('userprofile');
Expand Down Expand Up @@ -54,17 +53,20 @@ const Projects = () => {
))}
</>
) : (
(projectsList as Record<string, any>[])?.map(
(project: Record<string, any>) => (
<ProjectCard
key={project.id}
id={project.id}
imageUrl={project?.image_url}
title={project.name}
description={project.description}
/>
),
)
<>
{!projectsList?.length && <div>No projects available</div>}
{(projectsList as Record<string, any>[])?.map(
(project: Record<string, any>) => (
<ProjectCard
key={project.id}
id={project.id}
imageUrl={project?.image_url}
title={project.name}
description={project.description}
/>
),
)}
</>
)}
</div>
{showMap && (
Expand Down

0 comments on commit 8c3bf01

Please sign in to comment.