Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
58c6340
Default changeset to discard previous default changeset commentwhen t…
prabinoid Jun 30, 2025
9df2363
Test configuration updated for test isolation and system integration …
prabinoid Jun 26, 2025
8b8507a
Add back new relic integration into develop environment
dakotabenjamin Jul 2, 2025
bc4f38f
Active projects list to include trigger from project chats
prabinoid Jul 8, 2025
14e49af
Merge pull request #6913 from hotosm/tests/integration-setup
ramyaragupathy Jul 8, 2025
2804268
Merge pull request #6916 from hotosm/fix/clone-changesets
ramyaragupathy Jul 8, 2025
eb2c0a0
Ohsome statistics function updated to implement new ohsome release
prabinoid Jul 9, 2025
c52b051
Update `useOsmStatsQuery` to accept topics as an array and convert th…
suzit-10 Jul 9, 2025
33c9076
Pass topics in form of query params and adapt stat values to udpated …
suzit-10 Jul 9, 2025
15b57ad
Refactor `ohsomeProxyAPI` to accept url, userId, and topics as parame…
suzit-10 Jul 9, 2025
62f9958
Adapt stat values to udpated API response format
suzit-10 Jul 9, 2025
a5f4e68
Ordering made deterministic to avoid redundancies during pagination
prabinoid Jul 9, 2025
cccff4b
Migrate `mapbox-gl` to `maplibre-gl`
royallsilwallz Jun 19, 2025
93b90dc
Use httpx client for non blocking request and url constructed from se…
prabinoid Jul 15, 2025
6920e79
Update mock API stat values to match updated API response and fix tes…
suzit-10 Jul 15, 2025
f454b5d
Update ohsomeProxyAPI query params
suzit-10 Jul 15, 2025
82cb25f
Fix page crash while webgl support check in maplibre
royallsilwallz Jul 15, 2025
c8c357c
Update mock API to match new endpoint structure
suzit-10 Jul 15, 2025
f714db4
Add confirmation dialog on last team member remove
suzit-10 Jul 16, 2025
ee0c66c
Add i18n messages for member removal confirmation dialog
suzit-10 Jul 16, 2025
cd3bcd5
Project list all results fetched only during csv generation and while…
prabinoid Jul 17, 2025
7d50e00
Merge pull request #6940 from hotosm/fix/project-pagination
nischalstha9 Jul 17, 2025
e35ee09
Private and draft project info restriction on project tasks,contribut…
prabinoid Jul 17, 2025
aca3fe4
Applied conditional formatting to numeric values on `StatsCardContent`
suzit-10 Jul 17, 2025
13cd4d5
Replace manual axios call with useOsmStatsQuery hook
suzit-10 Jul 17, 2025
363a031
Add poi and waterway on ohsome static mock data
suzit-10 Jul 17, 2025
5b01e22
Remove axios mock and structure tests using describe/it block
suzit-10 Jul 17, 2025
f96cbba
Display "Access Denied" page for 403 errors in project summary query
suzit-10 Jul 17, 2025
e923116
Include tokens in fetch requests for contributions, activity, and tasks
suzit-10 Jul 21, 2025
63cab42
Add `totalMemberOnTeam` prop to Member component that accepts the tot…
suzit-10 Jul 21, 2025
dd12735
Enable team member edit only if there is more than 1 member on team i…
suzit-10 Jul 21, 2025
343f471
fix: new relic integration command
nischalstha9 Jul 22, 2025
436627d
Merge pull request #6924 from hotosm/infra/add-newrelic
nischalstha9 Jul 22, 2025
625c61b
Merge pull request #6915 from hotosm/feat/6719-migrate-mapboxgl-to-ma…
ramyaragupathy Jul 23, 2025
66f0a4a
Merge pull request #6923 from hotosm/fix/active-projects
ramyaragupathy Jul 23, 2025
c1e5fc3
Add hastag paramter on `ohsomeProxyAPI`
suzit-10 Jul 23, 2025
14884b0
Merge pull request #6951 from hotosm/fix/private-projects
ramyaragupathy Jul 25, 2025
38f437b
Merge pull request #6943 from hotosm/fix/ohsome-user-stats
ramyaragupathy Jul 25, 2025
41e4dc2
Merge pull request #6948 from hotosm/fix/6909-unable-to-remove-last-m…
ramyaragupathy Jul 25, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/pr_test_backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:

- name: Run PEP8 checks
run: |
flake8 manage.py backend tests migrations
flake8 manage.py backend tests migrations --ignore=E203,W503
black --check manage.py backend tests migrations

pytest:
Expand Down
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
Loading
Loading