Skip to content
Merged
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
74 changes: 70 additions & 4 deletions backend/api/projects/activities.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from typing import Optional
from backend.models.dtos.user_dto import AuthUserDTO
from backend.models.postgis.statuses import ProjectStatus
from backend.services.users.authentication_service import login_required_optional
from databases import Database
from fastapi import APIRouter, Depends, Request, Query

from fastapi.responses import JSONResponse
from backend.db import get_db
from backend.services.project_service import ProjectService
from backend.services.stats_service import StatsService
Expand All @@ -16,6 +20,7 @@
async def get_activities(
project_id: int,
page: int = Query(1, description="Page of results user requested", ge=1),
user: Optional[AuthUserDTO] = Depends(login_required_optional),
db: Database = Depends(get_db),
):
"""
Expand Down Expand Up @@ -44,14 +49,46 @@ async def get_activities(
500:
description: Internal Server Error
"""
await ProjectService.exists(project_id, db)

is_private, status = await ProjectService.get_project_privacy_and_status(
project_id, db
)
# If private or draft, enforce login + permission
if is_private or status == ProjectStatus.DRAFT.value:
user_id = user.id if user else None
if user is None:
return JSONResponse(
content={
"Error": "User not permitted: Private Project",
"SubCode": "PrivateProject",
},
status_code=403,
)

project_dto = await ProjectService.get_project_dto_for_mapper(
project_id,
user_id,
db,
)
if not project_dto:

return JSONResponse(
content={
"Error": "User not permitted: Private Project",
"SubCode": "PrivateProject",
},
status_code=403,
)
activity = await StatsService.get_latest_activity(project_id, page, db)
return activity


@router.get("/{project_id}/activities/latest/")
async def get_latest_activities(
request: Request, project_id: int, db: Database = Depends(get_db)
request: Request,
project_id: int,
user: Optional[AuthUserDTO] = Depends(login_required_optional),
db: Database = Depends(get_db),
):
"""
Get latest user activity on all of project task
Expand All @@ -74,6 +111,35 @@ async def get_latest_activities(
500:
description: Internal Server Error
"""
await ProjectService.exists(project_id, db)

is_private, status = await ProjectService.get_project_privacy_and_status(
project_id, db
)
# If private or draft, enforce login + permission
if is_private or status == ProjectStatus.DRAFT.value:
user_id = user.id if user else None
if user is None:
return JSONResponse(
content={
"Error": "User not permitted: Private Project",
"SubCode": "PrivateProject",
},
status_code=403,
)

project_dto = await ProjectService.get_project_dto_for_mapper(
project_id,
user_id,
db,
)
if not project_dto:

return JSONResponse(
content={
"Error": "User not permitted: Private Project",
"SubCode": "PrivateProject",
},
status_code=403,
)
activity = await StatsService.get_last_activity(project_id, db)
return activity
44 changes: 40 additions & 4 deletions backend/api/projects/contributions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from typing import Optional
from backend.models.dtos.user_dto import AuthUserDTO
from backend.models.postgis.statuses import ProjectStatus
from backend.services.users.authentication_service import login_required_optional
from databases import Database
from fastapi import APIRouter, Depends

from fastapi.responses import JSONResponse
from backend.db import get_db
from backend.models.postgis.project import Project
from backend.services.project_service import ProjectService
from backend.services.stats_service import StatsService

Expand All @@ -14,7 +17,11 @@


@router.get("/{project_id}/contributions/")
async def get_project_contributions(project_id: int, db: Database = Depends(get_db)):
async def get_project_contributions(
project_id: int,
user: Optional[AuthUserDTO] = Depends(login_required_optional),
db: Database = Depends(get_db),
):
"""
Get all user contributions on a project
---
Expand All @@ -37,7 +44,36 @@ async def get_project_contributions(project_id: int, db: Database = Depends(get_
500:
description: Internal Server Error
"""
await Project.exists(project_id, db)

is_private, status = await ProjectService.get_project_privacy_and_status(
project_id, db
)
# If private or draft, enforce login + permission
if is_private or status == ProjectStatus.DRAFT.value:
user_id = user.id if user else None
if user is None:
return JSONResponse(
content={
"Error": "User not permitted: Private Project",
"SubCode": "PrivateProject",
},
status_code=403,
)

project_dto = await ProjectService.get_project_dto_for_mapper(
project_id,
user_id,
db,
)
if not project_dto:

return JSONResponse(
content={
"Error": "User not permitted: Private Project",
"SubCode": "PrivateProject",
},
status_code=403,
)
contributions = await StatsService.get_user_contributions(project_id, db)
return contributions

Expand Down
38 changes: 36 additions & 2 deletions backend/api/projects/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
ProjectSearchDTO,
)
from backend.models.dtos.user_dto import AuthUserDTO
from backend.models.postgis.statuses import UserRole
from backend.models.postgis.statuses import ProjectStatus, UserRole
from backend.services.organisation_service import OrganisationService
from backend.services.project_admin_service import (
InvalidData,
Expand Down Expand Up @@ -1104,7 +1104,10 @@ async def get_mapped_projects(

@router.get("/{project_id}/queries/summary/")
async def get_project_summary(
request: Request, project_id: int, db: Database = Depends(get_db)
request: Request,
project_id: int,
user: Optional[AuthUserDTO] = Depends(login_required_optional),
db: Database = Depends(get_db),
):
"""
Gets project summary
Expand Down Expand Up @@ -1135,6 +1138,37 @@ async def get_project_summary(
description: Internal Server Error
"""
preferred_locale = request.headers.get("accept-language")

is_private, status = await ProjectService.get_project_privacy_and_status(
project_id, db
)
# If private or draft, enforce login + permission
if is_private or status == ProjectStatus.DRAFT.value:
user_id = user.id if user else None
if user is None:
return JSONResponse(
content={
"Error": "User not permitted: Private Project",
"SubCode": "PrivateProject",
},
status_code=403,
)

project_dto = await ProjectService.get_project_dto_for_mapper(
project_id,
user_id,
db,
)
if not project_dto:

return JSONResponse(
content={
"Error": "User not permitted: Private Project",
"SubCode": "PrivateProject",
},
status_code=403,
)

summary = await ProjectService.get_project_summary(project_id, db, preferred_locale)
return summary

Expand Down
39 changes: 36 additions & 3 deletions backend/api/tasks/resources.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import io
import json
from typing import Optional

from backend.models.dtos.user_dto import AuthUserDTO
from databases import Database
from fastapi import APIRouter, Depends, Request, Query
from fastapi.responses import JSONResponse, Response, StreamingResponse
Expand All @@ -9,12 +11,12 @@

from backend.db import get_db
from backend.models.dtos.grid_dto import GridDTO
from backend.models.postgis.statuses import UserRole
from backend.models.postgis.statuses import ProjectStatus, UserRole
from backend.models.postgis.utils import InvalidGeoJson
from backend.services.grid.grid_service import GridService
from backend.services.mapping_service import MappingService
from backend.services.project_service import ProjectService, ProjectServiceError
from backend.services.users.authentication_service import tm
from backend.services.users.authentication_service import login_required_optional, tm
from backend.services.users.user_service import UserService
from backend.services.validator_service import ValidatorService

Expand Down Expand Up @@ -75,6 +77,7 @@ async def get_project_tasks(
project_id: int,
tasks: str = Query(default=None),
as_file: bool = Query(default=False, alias="as_file"),
user: Optional[AuthUserDTO] = Depends(login_required_optional),
db: Database = Depends(get_db),
):
"""
Expand Down Expand Up @@ -112,8 +115,38 @@ async def get_project_tasks(
description: Internal Server Error
"""
try:
tasks_json = await ProjectService.get_project_tasks(db, project_id, tasks)

is_private, status = await ProjectService.get_project_privacy_and_status(
project_id, db
)
# If private or draft, enforce login + permission
if is_private or status == ProjectStatus.DRAFT.value:
user_id = user.id if user else None
if user is None:
return JSONResponse(
content={
"Error": "User not permitted: Private Project",
"SubCode": "PrivateProject",
},
status_code=403,
)

project_dto = await ProjectService.get_project_dto_for_mapper(
project_id,
user_id,
db,
)
if not project_dto:

return JSONResponse(
content={
"Error": "User not permitted: Private Project",
"SubCode": "PrivateProject",
},
status_code=403,
)

tasks_json = await ProjectService.get_project_tasks(db, project_id, tasks)
if as_file:
tasks_str = json.dumps(tasks_json, indent=4)
return Response(
Expand Down
17 changes: 17 additions & 0 deletions backend/services/project_service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
from datetime import datetime, timedelta, timezone
from typing import Tuple

import geojson
from aiocache import Cache, cached
Expand Down Expand Up @@ -85,6 +86,22 @@ def get_project_by_name(project_id: int) -> Project:

return project

@staticmethod
async def get_project_privacy_and_status(
project_id: int, db: Database
) -> Tuple[bool, int]:
query = """
SELECT private, status
FROM projects
WHERE id = :project_id
"""
row = await db.fetch_one(query=query, values={"project_id": project_id})

if row is None:
raise NotFound(sub_code="PROJECT_NOT_FOUND", project_id=project_id)

return row["private"], row["status"]

@staticmethod
async def auto_unlock_tasks(project_id: int, db: Database):
await Task.auto_unlock_tasks(project_id, db)
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/api/projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ export const useProjectSummaryQuery = (projectId, otherOptions = {}) => {
};

export const useProjectContributionsQuery = (projectId, otherOptions = {}) => {
const token = useSelector((state) => state.auth.token);
const fetchProjectContributions = ({ signal }) => {
return api().get(`projects/${projectId}/contributions/`, {
return api(token).get(`projects/${projectId}/contributions/`, {
signal,
});
};
Expand All @@ -86,9 +87,10 @@ export const useProjectContributionsQuery = (projectId, otherOptions = {}) => {
};

export const useActivitiesQuery = (projectId) => {
const token = useSelector((state) => state.auth.token);
const ACTIVITIES_REFETCH_INTERVAL = 1000 * 60;
const fetchProjectActivities = ({ signal }) => {
return api().get(`projects/${projectId}/activities/latest/`, {
return api(token).get(`projects/${projectId}/activities/latest/`, {
signal,
});
};
Expand All @@ -105,8 +107,9 @@ export const useActivitiesQuery = (projectId) => {
};

export const useTasksQuery = (projectId, otherOptions = {}) => {
const token = useSelector((state) => state.auth.token);
const fetchProjectTasks = ({ signal }) => {
return api().get(`projects/${projectId}/tasks/`, {
return api(token).get(`projects/${projectId}/tasks/`, {
signal,
});
};
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/views/taskSelection.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function SelectTask() {
error: projectSummaryError,
status: projectSummaryStatus,
} = useProjectSummaryQuery(id, {
useErrorBoundary: (error) => error.response.status !== 404,
useErrorBoundary: (error) => error.response.status !== 403,
enabled: !!token,
});
const {
Expand Down
Loading