From e8994706aea439f6e9cdf51cc3fda993e1f04930 Mon Sep 17 00:00:00 2001 From: thenav56 Date: Wed, 15 Oct 2025 13:35:10 +0545 Subject: [PATCH 1/2] feat!(firebase): add FirebaseOrInternalIdInputType for firebase and internal id support for id for Contributor user and userGroup BREAKING CHANGE: userId, userGroupId and id for contributor and community queries are changed --- apps/community_dashboard/graphql/queries.py | 14 +++++++------ apps/community_dashboard/graphql/types.py | 14 ++++++++----- apps/community_dashboard/tests/query_test.py | 10 +++++----- apps/contributor/graphql/queries.py | 21 +++++++++++++------- schema.graphql | 15 +++++++++----- utils/graphql/inputs.py | 19 ++++++++++++++++++ 6 files changed, 65 insertions(+), 28 deletions(-) diff --git a/apps/community_dashboard/graphql/queries.py b/apps/community_dashboard/graphql/queries.py index 9d08ad7e..a0e74715 100644 --- a/apps/community_dashboard/graphql/queries.py +++ b/apps/community_dashboard/graphql/queries.py @@ -5,11 +5,11 @@ import strawberry from asgiref.sync import sync_to_async from django.db import models -from django.shortcuts import aget_object_or_404 from django.utils import timezone from apps.community_dashboard.models import AggregatedUserGroupStatData, AggregatedUserStatData -from apps.contributor.models import ContributorUser +from apps.contributor.models import ContributorUser, ContributorUserGroup +from utils.graphql.inputs import FirebaseOrInternalIdInputType from .types import ( AggregateHelper, @@ -117,17 +117,19 @@ async def community_filtered_stats( ) -> CommunityFilteredStats: return CommunityFilteredStats(date_range=date_range) + # By Internal ID @strawberry.field async def community_user_stats( self, - firebase_id: strawberry.ID, + user_id: FirebaseOrInternalIdInputType, ) -> ContributorUserStats: - user = await aget_object_or_404(ContributorUser, firebase_id=firebase_id) + user = await FirebaseOrInternalIdInputType.aget_object_or_404(ContributorUser, object_id=user_id) return ContributorUserStats(user=user) @strawberry.field async def community_user_group_stats( self, - user_group_id: strawberry.ID, + user_group_id: FirebaseOrInternalIdInputType, ) -> ContributorUserGroupStats: - return ContributorUserGroupStats(user_group_id=int(user_group_id)) + user_group = await FirebaseOrInternalIdInputType.aget_object_or_404(ContributorUserGroup, object_id=user_group_id) + return ContributorUserGroupStats(user_group=user_group) diff --git a/apps/community_dashboard/graphql/types.py b/apps/community_dashboard/graphql/types.py index e0c50093..8ed20f90 100644 --- a/apps/community_dashboard/graphql/types.py +++ b/apps/community_dashboard/graphql/types.py @@ -11,7 +11,7 @@ from django_cte import With # type: ignore[reportMissingTypeStubs] from apps.community_dashboard.models import AggregatedUserGroupStatData, AggregatedUserStatData -from apps.contributor.models import ContributorUser +from apps.contributor.models import ContributorUser, ContributorUserGroup from apps.project.models import Project, ProjectTypeEnum from utils.graphql.inputs import DateRangeInput from utils.graphql.types import AreaSqKm, GenericJSON @@ -349,6 +349,10 @@ def __post_init__(self, user: ContributorUser): async def id(self) -> strawberry.ID: return typing.cast("strawberry.ID", self._user.pk) + @strawberry.field + async def firebase_id(self) -> strawberry.ID: + return typing.cast("strawberry.ID", self._user.firebase_id) + @strawberry.field async def stats(self) -> ContributorUserStatType: # TODO: Cache this @@ -423,15 +427,15 @@ def __post_init__(self, date_range: DateRangeInput | None, user_group_id: int): @strawberry.type class ContributorUserGroupStats: - user_group_id: InitVar[int] + user_group: InitVar[ContributorUserGroup] _user_group_id: strawberry.Private[int] = dataclass_field(init=False) _ug_qs: strawberry.Private[models.QuerySet[AggregatedUserGroupStatData]] = dataclass_field(init=False) - def __post_init__(self, user_group_id: int): - self._user_group_id = user_group_id + def __post_init__(self, user_group: ContributorUserGroup): + self._user_group_id = user_group.pk self._ug_qs = AggregatedUserGroupStatData.objects.filter( - user_group_id=user_group_id, + user_group_id=user_group.pk, ) @strawberry.field diff --git a/apps/community_dashboard/tests/query_test.py b/apps/community_dashboard/tests/query_test.py index f4ca7431..2fad9a32 100644 --- a/apps/community_dashboard/tests/query_test.py +++ b/apps/community_dashboard/tests/query_test.py @@ -256,7 +256,7 @@ def test_filtered_community_stats(self): def test_user_group_aggregated_calc(self): query = """ query MyQuery($userGroupId: ID!) { - communityUserGroupStats(userGroupId: $userGroupId) { + communityUserGroupStats(userGroupId: {id: $userGroupId}) { stats { totalAreaSwiped totalContributors @@ -306,7 +306,7 @@ def test_user_group_aggregated_calc(self): def test_user_group_query(self): query = """ query MyQuery($userGroupId: ID!, $pagination: OffsetPaginationInput!) { - contributorUserGroup(id: $userGroupId) { + contributorUserGroup(userGroupId: {id: $userGroupId}) { id name createdAt @@ -502,13 +502,13 @@ def test_user_query(self): $toDate: Date!, ) { - contributorUserByFirebaseId(firebaseId: $firebaseId) { + contributorUser(userId: {firebaseId: $firebaseId}) { id firebaseId username } - communityUserStats(firebaseId: $firebaseId) { + communityUserStats(userId: {firebaseId: $firebaseId}) { id stats { totalSwipes @@ -644,7 +644,7 @@ def test_user_query(self): ) assert { - "contributorUserByFirebaseId": { + "contributorUser": { "id": self.gID(contributor_user.pk), "firebaseId": contributor_user.firebase_id, "username": contributor_user.username, diff --git a/apps/contributor/graphql/queries.py b/apps/contributor/graphql/queries.py index 1258057d..1738222f 100644 --- a/apps/contributor/graphql/queries.py +++ b/apps/contributor/graphql/queries.py @@ -3,11 +3,11 @@ import strawberry import strawberry_django from django.db.models import QuerySet -from django.shortcuts import aget_object_or_404 from strawberry_django.pagination import OffsetPaginated from strawberry_django.permissions import IsAuthenticated from apps.contributor.models import ContributorTeam, ContributorUser, ContributorUserGroup, ContributorUserGroupMembership +from utils.graphql.inputs import FirebaseOrInternalIdInputType from .filters import ( ContributorTeamFilter, @@ -31,18 +31,25 @@ class Query: filters=ContributorUserFilter, ) - contributor_user: ContributorUserType = strawberry_django.field() - - contributor_user_group: ContributorUserGroupType = strawberry_django.field() - # Team contributor_team: ContributorTeamType = strawberry_django.field() @strawberry.field - async def contributor_user_by_firebase_id(self, firebase_id: strawberry.ID) -> ContributorUserType: - obj = await aget_object_or_404(ContributorUser, firebase_id=firebase_id) + async def contributor_user( + self, + user_id: FirebaseOrInternalIdInputType, + ) -> ContributorUserType: + obj = await FirebaseOrInternalIdInputType.aget_object_or_404(ContributorUser, object_id=user_id) return typing.cast("ContributorUserType", obj) + @strawberry.field + async def contributor_user_group( + self, + user_group_id: FirebaseOrInternalIdInputType, + ) -> ContributorUserGroupType: + obj = await FirebaseOrInternalIdInputType.aget_object_or_404(ContributorUserGroup, object_id=user_group_id) + return typing.cast("ContributorUserGroupType", obj) + # --- Paginated # --- ContributorUserGroup @strawberry_django.offset_paginated( diff --git a/schema.graphql b/schema.graphql index 4f2df8e9..e536cc8f 100644 --- a/schema.graphql +++ b/schema.graphql @@ -663,6 +663,7 @@ type ContributorUserStatType { type ContributorUserStats { filteredStats(dateRange: DateRangeInput = null): ContributorUserFilteredStats! + firebaseId: ID! id: ID! stats: ContributorUserStatType! @@ -804,6 +805,11 @@ type FindTutorialTaskPropertyType { tileZ: Int! } +input FirebaseOrInternalIdInputType @oneOf { + firebaseId: ID + id: ID +} + interface FirebasePushResourceTypeMixin { firebaseId: String! firebaseLastPushed: DateTime @@ -2009,13 +2015,12 @@ type Query { """Stats from last 30 days.""" communityStatsLatest: CommunityStatsType! - communityUserGroupStats(userGroupId: ID!): ContributorUserGroupStats! - communityUserStats(firebaseId: ID!): ContributorUserStats! + communityUserGroupStats(userGroupId: FirebaseOrInternalIdInputType!): ContributorUserGroupStats! + communityUserStats(userId: FirebaseOrInternalIdInputType!): ContributorUserStats! contributorTeam(id: ID!): ContributorTeamType! contributorTeams(includeAll: Boolean! = false, filters: ContributorTeamFilter, order: ContributorTeamOrder, pagination: OffsetPaginationInput): ContributorTeamTypeOffsetPaginated! @isAuthenticated - contributorUser(id: ID!): ContributorUserType! - contributorUserByFirebaseId(firebaseId: ID!): ContributorUserType! - contributorUserGroup(id: ID!): ContributorUserGroupType! + contributorUser(userId: FirebaseOrInternalIdInputType!): ContributorUserType! + contributorUserGroup(userGroupId: FirebaseOrInternalIdInputType!): ContributorUserGroupType! contributorUserGroupMembers(includeAll: Boolean! = false, filters: ContributorUserGroupMembershipFilter, order: ContributorUserGroupMembershipOrder, pagination: OffsetPaginationInput): ContributorUserGroupMembershipTypeOffsetPaginated! contributorUserGroups(includeAll: Boolean! = false, filters: ContributorUserGroupFilter, order: ContributorUserGroupOrder, pagination: OffsetPaginationInput): ContributorUserGroupTypeOffsetPaginated! contributorUsers(pagination: OffsetPaginationInput, filters: ContributorUserFilter, order: ContributorUserOrder): ContributorUserTypeOffsetPaginated! diff --git a/utils/graphql/inputs.py b/utils/graphql/inputs.py index 69501658..8f219c9e 100644 --- a/utils/graphql/inputs.py +++ b/utils/graphql/inputs.py @@ -1,9 +1,28 @@ import datetime import strawberry +from django.db import models +from django.shortcuts import aget_object_or_404 @strawberry.input class DateRangeInput: from_date: datetime.date to_date: datetime.date + + +@strawberry.input(one_of=True) +class FirebaseOrInternalIdInputType: + id: strawberry.Maybe[strawberry.ID] + firebase_id: strawberry.Maybe[strawberry.ID] + + @staticmethod + async def aget_object_or_404[M: models.Model]( + model: type[M], + object_id: "FirebaseOrInternalIdInputType", + ) -> M: + if object_id.id is not None: + return await aget_object_or_404(model, id=object_id.id.value) + if object_id.firebase_id is not None: + return await aget_object_or_404(model, firebase_id=object_id.firebase_id.value) + raise Exception("This should never be called") From 8baf12939ffe117ed14a44ee82da301e00ba5804 Mon Sep 17 00:00:00 2001 From: thenav56 Date: Wed, 15 Oct 2025 15:32:40 +0545 Subject: [PATCH 2/2] feat(public): show PAUSED projects in public endpoint --- apps/project/graphql/queries.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/project/graphql/queries.py b/apps/project/graphql/queries.py index e66c51cf..adeb2025 100644 --- a/apps/project/graphql/queries.py +++ b/apps/project/graphql/queries.py @@ -155,8 +155,9 @@ def public_projects( ) -> QuerySet[Project]: return Project.objects.filter( status__in=[ - Project.Status.FINISHED, Project.Status.PUBLISHED, + Project.Status.PAUSED, + Project.Status.FINISHED, ], ).all()