From 60ea72ba1bef04b1de54f8fdbaf97fb3a7f309b5 Mon Sep 17 00:00:00 2001 From: Anurag Yadav Date: Fri, 23 Jan 2026 01:26:10 +0530 Subject: [PATCH 1/6] Fix negative indexing --- .../github/api/internal/nodes/repository.py | 11 +++++++ .../github/api/internal/queries/milestone.py | 16 +++++++--- .../apps/owasp/api/internal/nodes/project.py | 32 +++++++++++++------ .../owasp/api/internal/queries/project.py | 16 +++++++--- 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/backend/apps/github/api/internal/nodes/repository.py b/backend/apps/github/api/internal/nodes/repository.py index 6a8c687d01..40ada28a6e 100644 --- a/backend/apps/github/api/internal/nodes/repository.py +++ b/backend/apps/github/api/internal/nodes/repository.py @@ -15,6 +15,7 @@ if TYPE_CHECKING: from apps.owasp.api.internal.nodes.project import ProjectNode +MAX_LIMIT = 1000 RECENT_ISSUES_LIMIT = 5 RECENT_RELEASES_LIMIT = 5 @@ -69,6 +70,16 @@ def project( @strawberry_django.field(prefetch_related=["milestones"]) def recent_milestones(self, root: Repository, limit: int = 5) -> list[MilestoneNode]: """Resolve recent milestones.""" + try: + limit = int(limit) + except (TypeError, ValueError): + return [] + + if limit <= 0: + return [] + + limit = min(limit, MAX_LIMIT) + return root.recent_milestones.order_by("-created_at")[:limit] @strawberry_django.field(prefetch_related=["releases"]) diff --git a/backend/apps/github/api/internal/queries/milestone.py b/backend/apps/github/api/internal/queries/milestone.py index bb87fab3a4..685a735d2b 100644 --- a/backend/apps/github/api/internal/queries/milestone.py +++ b/backend/apps/github/api/internal/queries/milestone.py @@ -76,8 +76,14 @@ def recent_milestones( id__in=Subquery(latest_milestone_per_author), ) - return ( - milestones.order_by("-created_at")[:limit] - if (limit := min(limit, MAX_LIMIT)) > 0 - else [] - ) + try: + limit = int(limit) + except (TypeError, ValueError): + return [] + + if limit <= 0: + return [] + + limit = min(limit, MAX_LIMIT) + + return milestones.order_by("-created_at")[:limit] diff --git a/backend/apps/owasp/api/internal/nodes/project.py b/backend/apps/owasp/api/internal/nodes/project.py index f5da84653f..e4ac7570bc 100644 --- a/backend/apps/owasp/api/internal/nodes/project.py +++ b/backend/apps/owasp/api/internal/nodes/project.py @@ -53,11 +53,17 @@ def health_metrics_list( self, root: Project, limit: int = 30 ) -> list[ProjectHealthMetricsNode]: """Resolve project health metrics.""" - return ( - root.health_metrics.order_by("nest_created_at")[:limit] - if (limit := min(limit, MAX_LIMIT)) > 0 - else [] - ) + try: + limit = int(limit) + except (TypeError, ValueError): + return [] + + if limit <= 0: + return [] + + limit = min(limit, MAX_LIMIT) + + return root.health_metrics.order_by("nest_created_at")[:limit] @strawberry_django.field(prefetch_related=["health_metrics"]) def health_metrics_latest(self, root: Project) -> ProjectHealthMetricsNode | None: @@ -87,6 +93,16 @@ def recent_issues(self, root: Project) -> list[IssueNode]: @strawberry_django.field def recent_milestones(self, root: Project, limit: int = 5) -> list[MilestoneNode]: """Resolve recent milestones.""" + try: + limit = int(limit) + except (TypeError, ValueError): + return [] + + if limit <= 0: + return [] + + limit = min(limit, MAX_LIMIT) + return ( Milestone.objects.filter( repository__in=root.repositories.all(), @@ -95,12 +111,8 @@ def recent_milestones(self, root: Project, limit: int = 5) -> list[MilestoneNode "repository__organization", "author__owasp_profile", ) - .prefetch_related( - "labels", - ) + .prefetch_related("labels") .order_by("-created_at")[:limit] - if (limit := min(limit, MAX_LIMIT)) > 0 - else [] ) @strawberry_django.field diff --git a/backend/apps/owasp/api/internal/queries/project.py b/backend/apps/owasp/api/internal/queries/project.py index af4db3c009..d5a863e1a4 100644 --- a/backend/apps/owasp/api/internal/queries/project.py +++ b/backend/apps/owasp/api/internal/queries/project.py @@ -45,11 +45,17 @@ def recent_projects(self, limit: int = 8) -> list[ProjectNode]: list[ProjectNode]: A list of recent active projects. """ - return ( - Project.objects.filter(is_active=True).order_by("-created_at")[:limit] - if (limit := min(limit, MAX_RECENT_PROJECTS_LIMIT)) > 0 - else [] - ) + try: + limit = int(limit) + except (TypeError, ValueError): + return [] + + if limit <= 0: + return [] + + limit = min(limit, MAX_RECENT_PROJECTS_LIMIT) + + return Project.objects.filter(is_active=True).order_by("-created_at")[:limit] @strawberry_django.field def search_projects(self, query: str) -> list[ProjectNode]: From d60ab50a5c49244a56fe326a521ae701f09b4be3 Mon Sep 17 00:00:00 2001 From: Anurag Yadav Date: Wed, 28 Jan 2026 10:03:20 +0530 Subject: [PATCH 2/6] Used reusable code instead of repetitve code --- backend/apps/common/utils.py | 22 ++++++++++++++++ .../github/api/internal/nodes/repository.py | 13 +++------- .../github/api/internal/queries/milestone.py | 13 +++------- .../apps/owasp/api/internal/nodes/project.py | 25 ++++++------------- .../owasp/api/internal/queries/project.py | 13 +++------- 5 files changed, 41 insertions(+), 45 deletions(-) diff --git a/backend/apps/common/utils.py b/backend/apps/common/utils.py index 7e434beb97..4c2666b340 100644 --- a/backend/apps/common/utils.py +++ b/backend/apps/common/utils.py @@ -215,6 +215,28 @@ def truncate(text: str, limit: int, truncate: str = "...") -> str: return Truncator(text).chars(limit, truncate=truncate) +def normalize_limit(limit: int, max_limit: int = 1000) -> int | None: + """Normalize and validate a limit parameter. + + Args: + limit (int): The requested limit. + max_limit (int): The maximum allowed limit. Defaults to 1000. + + Returns: + int | None: The normalized limit capped at max_limit, or None if invalid. + + """ + try: + limit = int(limit) + except (TypeError, ValueError): + return None + + if limit <= 0: + return None + + return min(limit, max_limit) + + def validate_url(url: str | None) -> bool: """Validate that a URL has proper scheme and netloc. diff --git a/backend/apps/github/api/internal/nodes/repository.py b/backend/apps/github/api/internal/nodes/repository.py index 40ada28a6e..15c92f86be 100644 --- a/backend/apps/github/api/internal/nodes/repository.py +++ b/backend/apps/github/api/internal/nodes/repository.py @@ -5,6 +5,7 @@ import strawberry import strawberry_django +from apps.common.utils import normalize_limit from apps.github.api.internal.nodes.issue import IssueNode from apps.github.api.internal.nodes.milestone import MilestoneNode from apps.github.api.internal.nodes.organization import OrganizationNode @@ -70,17 +71,11 @@ def project( @strawberry_django.field(prefetch_related=["milestones"]) def recent_milestones(self, root: Repository, limit: int = 5) -> list[MilestoneNode]: """Resolve recent milestones.""" - try: - limit = int(limit) - except (TypeError, ValueError): + validated_limit = normalize_limit(limit, MAX_LIMIT) + if validated_limit is None: return [] - if limit <= 0: - return [] - - limit = min(limit, MAX_LIMIT) - - return root.recent_milestones.order_by("-created_at")[:limit] + return root.recent_milestones.order_by("-created_at")[:validated_limit] @strawberry_django.field(prefetch_related=["releases"]) def releases(self, root: Repository) -> list[ReleaseNode]: diff --git a/backend/apps/github/api/internal/queries/milestone.py b/backend/apps/github/api/internal/queries/milestone.py index 685a735d2b..19a003cfe8 100644 --- a/backend/apps/github/api/internal/queries/milestone.py +++ b/backend/apps/github/api/internal/queries/milestone.py @@ -6,6 +6,7 @@ import strawberry_django from django.db.models import OuterRef, Subquery +from apps.common.utils import normalize_limit from apps.github.api.internal.nodes.milestone import MilestoneNode from apps.github.models.generic_issue_model import GenericIssueModel from apps.github.models.milestone import Milestone @@ -76,14 +77,8 @@ def recent_milestones( id__in=Subquery(latest_milestone_per_author), ) - try: - limit = int(limit) - except (TypeError, ValueError): + validated_limit = normalize_limit(limit, MAX_LIMIT) + if validated_limit is None: return [] - if limit <= 0: - return [] - - limit = min(limit, MAX_LIMIT) - - return milestones.order_by("-created_at")[:limit] + return milestones.order_by("-created_at")[:validated_limit] diff --git a/backend/apps/owasp/api/internal/nodes/project.py b/backend/apps/owasp/api/internal/nodes/project.py index e4ac7570bc..607432cb21 100644 --- a/backend/apps/owasp/api/internal/nodes/project.py +++ b/backend/apps/owasp/api/internal/nodes/project.py @@ -3,6 +3,7 @@ import strawberry import strawberry_django +from apps.common.utils import normalize_limit from apps.core.utils.index import deep_camelize from apps.github.api.internal.nodes.issue import IssueNode from apps.github.api.internal.nodes.milestone import MilestoneNode @@ -53,17 +54,11 @@ def health_metrics_list( self, root: Project, limit: int = 30 ) -> list[ProjectHealthMetricsNode]: """Resolve project health metrics.""" - try: - limit = int(limit) - except (TypeError, ValueError): + validated_limit = normalize_limit(limit, MAX_LIMIT) + if validated_limit is None: return [] - if limit <= 0: - return [] - - limit = min(limit, MAX_LIMIT) - - return root.health_metrics.order_by("nest_created_at")[:limit] + return root.health_metrics.order_by("nest_created_at")[:validated_limit] @strawberry_django.field(prefetch_related=["health_metrics"]) def health_metrics_latest(self, root: Project) -> ProjectHealthMetricsNode | None: @@ -93,16 +88,10 @@ def recent_issues(self, root: Project) -> list[IssueNode]: @strawberry_django.field def recent_milestones(self, root: Project, limit: int = 5) -> list[MilestoneNode]: """Resolve recent milestones.""" - try: - limit = int(limit) - except (TypeError, ValueError): + validated_limit = normalize_limit(limit, MAX_LIMIT) + if validated_limit is None: return [] - if limit <= 0: - return [] - - limit = min(limit, MAX_LIMIT) - return ( Milestone.objects.filter( repository__in=root.repositories.all(), @@ -112,7 +101,7 @@ def recent_milestones(self, root: Project, limit: int = 5) -> list[MilestoneNode "author__owasp_profile", ) .prefetch_related("labels") - .order_by("-created_at")[:limit] + .order_by("-created_at")[:validated_limit] ) @strawberry_django.field diff --git a/backend/apps/owasp/api/internal/queries/project.py b/backend/apps/owasp/api/internal/queries/project.py index d5a863e1a4..5b94412915 100644 --- a/backend/apps/owasp/api/internal/queries/project.py +++ b/backend/apps/owasp/api/internal/queries/project.py @@ -4,6 +4,7 @@ import strawberry_django from django.db.models import Q +from apps.common.utils import normalize_limit from apps.github.models.user import User as GithubUser from apps.owasp.api.internal.nodes.project import ProjectNode from apps.owasp.models.project import Project @@ -45,17 +46,11 @@ def recent_projects(self, limit: int = 8) -> list[ProjectNode]: list[ProjectNode]: A list of recent active projects. """ - try: - limit = int(limit) - except (TypeError, ValueError): - return [] - - if limit <= 0: + validated_limit = normalize_limit(limit, MAX_RECENT_PROJECTS_LIMIT) + if validated_limit is None: return [] - limit = min(limit, MAX_RECENT_PROJECTS_LIMIT) - - return Project.objects.filter(is_active=True).order_by("-created_at")[:limit] + return Project.objects.filter(is_active=True).order_by("-created_at")[:validated_limit] @strawberry_django.field def search_projects(self, query: str) -> list[ProjectNode]: From dc02ae01eb0ec49700ae891e4036c528da43b128 Mon Sep 17 00:00:00 2001 From: Anurag Yadav Date: Thu, 29 Jan 2026 13:24:00 +0530 Subject: [PATCH 3/6] added test --- .../github/api/internal/nodes/repository.py | 3 +- .../github/api/internal/queries/milestone.py | 5 +-- .../apps/owasp/api/internal/nodes/project.py | 10 ++--- .../owasp/api/internal/queries/project.py | 5 +-- backend/tests/apps/common/utils_test.py | 43 +++++++++++++++++++ frontend/__tests__/mockData/mockAboutData.ts | 23 ++++++++++ 6 files changed, 75 insertions(+), 14 deletions(-) diff --git a/backend/apps/github/api/internal/nodes/repository.py b/backend/apps/github/api/internal/nodes/repository.py index 15c92f86be..ec47c859f6 100644 --- a/backend/apps/github/api/internal/nodes/repository.py +++ b/backend/apps/github/api/internal/nodes/repository.py @@ -71,8 +71,7 @@ def project( @strawberry_django.field(prefetch_related=["milestones"]) def recent_milestones(self, root: Repository, limit: int = 5) -> list[MilestoneNode]: """Resolve recent milestones.""" - validated_limit = normalize_limit(limit, MAX_LIMIT) - if validated_limit is None: + if (validated_limit := normalize_limit(limit, MAX_LIMIT)) is None: return [] return root.recent_milestones.order_by("-created_at")[:validated_limit] diff --git a/backend/apps/github/api/internal/queries/milestone.py b/backend/apps/github/api/internal/queries/milestone.py index 19a003cfe8..6128be8c2e 100644 --- a/backend/apps/github/api/internal/queries/milestone.py +++ b/backend/apps/github/api/internal/queries/milestone.py @@ -77,8 +77,7 @@ def recent_milestones( id__in=Subquery(latest_milestone_per_author), ) - validated_limit = normalize_limit(limit, MAX_LIMIT) - if validated_limit is None: + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: return [] - return milestones.order_by("-created_at")[:validated_limit] + return milestones.order_by("-created_at")[:normalized_limit] diff --git a/backend/apps/owasp/api/internal/nodes/project.py b/backend/apps/owasp/api/internal/nodes/project.py index 607432cb21..b6f2335c53 100644 --- a/backend/apps/owasp/api/internal/nodes/project.py +++ b/backend/apps/owasp/api/internal/nodes/project.py @@ -54,11 +54,10 @@ def health_metrics_list( self, root: Project, limit: int = 30 ) -> list[ProjectHealthMetricsNode]: """Resolve project health metrics.""" - validated_limit = normalize_limit(limit, MAX_LIMIT) - if validated_limit is None: + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: return [] - return root.health_metrics.order_by("nest_created_at")[:validated_limit] + return root.health_metrics.order_by("nest_created_at")[:normalized_limit] @strawberry_django.field(prefetch_related=["health_metrics"]) def health_metrics_latest(self, root: Project) -> ProjectHealthMetricsNode | None: @@ -88,8 +87,7 @@ def recent_issues(self, root: Project) -> list[IssueNode]: @strawberry_django.field def recent_milestones(self, root: Project, limit: int = 5) -> list[MilestoneNode]: """Resolve recent milestones.""" - validated_limit = normalize_limit(limit, MAX_LIMIT) - if validated_limit is None: + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: return [] return ( @@ -101,7 +99,7 @@ def recent_milestones(self, root: Project, limit: int = 5) -> list[MilestoneNode "author__owasp_profile", ) .prefetch_related("labels") - .order_by("-created_at")[:validated_limit] + .order_by("-created_at")[:normalized_limit] ) @strawberry_django.field diff --git a/backend/apps/owasp/api/internal/queries/project.py b/backend/apps/owasp/api/internal/queries/project.py index 5b94412915..6849c229a9 100644 --- a/backend/apps/owasp/api/internal/queries/project.py +++ b/backend/apps/owasp/api/internal/queries/project.py @@ -46,11 +46,10 @@ def recent_projects(self, limit: int = 8) -> list[ProjectNode]: list[ProjectNode]: A list of recent active projects. """ - validated_limit = normalize_limit(limit, MAX_RECENT_PROJECTS_LIMIT) - if validated_limit is None: + if (normalized_limit := normalize_limit(limit, MAX_RECENT_PROJECTS_LIMIT)) is None: return [] - return Project.objects.filter(is_active=True).order_by("-created_at")[:validated_limit] + return Project.objects.filter(is_active=True).order_by("-created_at")[:normalized_limit] @strawberry_django.field def search_projects(self, query: str) -> list[ProjectNode]: diff --git a/backend/tests/apps/common/utils_test.py b/backend/tests/apps/common/utils_test.py index 9b5d58dacd..e77fb12fb5 100644 --- a/backend/tests/apps/common/utils_test.py +++ b/backend/tests/apps/common/utils_test.py @@ -13,6 +13,7 @@ join_values, natural_date, natural_number, + normalize_limit, round_down, validate_url, ) @@ -196,3 +197,45 @@ def test_validate_url(self, url, expected): """Test the validate_url function.""" result = validate_url(url) assert result == expected + + @pytest.mark.parametrize( + ("limit", "max_limit", "expected"), + [ + (5, 1000, 5), + (100, 1000, 100), + (1000, 1000, 1000), + (1500, 1000, 1000), + (999, 1000, 999), + (0, 1000, None), + (-5, 1000, None), + (5, 10, 5), + (15, 10, 10), + (100, 50, 50), + (1, 1, 1), + ], + ) + def test_normalize_limit(self, limit, max_limit, expected): + """Test the normalize_limit function with valid integers.""" + assert normalize_limit(limit, max_limit) == expected + + @pytest.mark.parametrize( + ("limit", "max_limit"), + [ + ("invalid", 1000), + ("5.5", 1000), + (None, 1000), + ([], 1000), + ({}, 1000), + ], + ) + def test_normalize_limit_invalid_types(self, limit, max_limit): + """Test the normalize_limit function with invalid types.""" + assert normalize_limit(limit, max_limit) is None + + def test_normalize_limit_default_max_limit(self): + """Test the normalize_limit function with default max_limit.""" + assert normalize_limit(500) == 500 + assert normalize_limit(1000) == 1000 + assert normalize_limit(1500) == 1000 + assert normalize_limit(-1) is None + assert normalize_limit(0) is None diff --git a/frontend/__tests__/mockData/mockAboutData.ts b/frontend/__tests__/mockData/mockAboutData.ts index 47c329613d..6851b8b2e5 100644 --- a/frontend/__tests__/mockData/mockAboutData.ts +++ b/frontend/__tests__/mockData/mockAboutData.ts @@ -1,62 +1,85 @@ export const mockAboutData = { project: { + id: 'project-nest', + name: 'OWASP Nest', contributorsCount: 1200, issuesCount: 40, forksCount: 60, starsCount: 890, + summary: 'A community-first platform for OWASP collaboration', recentMilestones: [ { + id: 'milestone-1', title: 'NestBot title', body: 'NestBot Idea', url: 'http/github.com/milestones/5', progress: 58, + state: 'open', }, { + id: 'milestone-2', title: 'Contribution Hub title', body: 'Contribution Hub Idea', url: 'http/github.com/milestones/8', progress: 75, + state: 'open', }, { + id: 'milestone-3', title: 'Project Dashboard title', body: 'Project Dashboard Idea', url: 'http/github.com/milestones/10', progress: 80, + state: 'open', }, { + id: 'milestone-4', title: 'Milestone 4', body: 'Milestone 4 Idea', url: 'http/github.com/milestones/11', progress: 20, + state: 'open', }, { + id: 'milestone-5', title: 'Milestone 5', body: 'Milestone 5 Idea', url: 'http/github.com/milestones/12', progress: 40, + state: 'open', }, ], }, topContributors: Array.from({ length: 15 }, (_, i) => ({ + id: `contributor-${i + 1}`, avatarUrl: `https://avatars.githubusercontent.com/avatar${i + 1}.jpg`, login: `contributor${i + 1}`, name: `Contributor ${i + 1}`, })), users: { arkid15r: { + id: 'user-arkid15r', avatarUrl: 'https://avatars.githubusercontent.com/u/2201626?v=4', login: 'arkid15r', name: 'Arkadii Yakovets', + badgeCount: 0, + badges: [], }, kasya: { + id: 'user-kasya', avatarUrl: 'https://avatars.githubusercontent.com/u/5873153?v=4', login: 'kasya', name: 'Kate Golovanova', + badgeCount: 0, + badges: [], }, mamicidal: { + id: 'user-mamicidal', avatarUrl: 'https://avatars.githubusercontent.com/u/112129498?v=4', login: 'mamicidal', name: 'Starr Brown', + badgeCount: 0, + badges: [], }, }, } From 7d061333366855550cfb683e23613a070fa2fa85 Mon Sep 17 00:00:00 2001 From: Anurag Yadav Date: Thu, 29 Jan 2026 13:30:28 +0530 Subject: [PATCH 4/6] used comman name --- backend/apps/github/api/internal/nodes/repository.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/apps/github/api/internal/nodes/repository.py b/backend/apps/github/api/internal/nodes/repository.py index ec47c859f6..189bb3b514 100644 --- a/backend/apps/github/api/internal/nodes/repository.py +++ b/backend/apps/github/api/internal/nodes/repository.py @@ -71,10 +71,10 @@ def project( @strawberry_django.field(prefetch_related=["milestones"]) def recent_milestones(self, root: Repository, limit: int = 5) -> list[MilestoneNode]: """Resolve recent milestones.""" - if (validated_limit := normalize_limit(limit, MAX_LIMIT)) is None: + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: return [] - return root.recent_milestones.order_by("-created_at")[:validated_limit] + return root.recent_milestones.order_by("-created_at")[:normalized_limit] @strawberry_django.field(prefetch_related=["releases"]) def releases(self, root: Repository) -> list[ReleaseNode]: From 25538f4cb7c0088a756d536b1859d5fb1ae451b3 Mon Sep 17 00:00:00 2001 From: Anurag Yadav Date: Thu, 29 Jan 2026 13:38:10 +0530 Subject: [PATCH 5/6] revert uunchanged file --- frontend/__tests__/mockData/mockAboutData.ts | 23 -------------------- 1 file changed, 23 deletions(-) diff --git a/frontend/__tests__/mockData/mockAboutData.ts b/frontend/__tests__/mockData/mockAboutData.ts index 6851b8b2e5..47c329613d 100644 --- a/frontend/__tests__/mockData/mockAboutData.ts +++ b/frontend/__tests__/mockData/mockAboutData.ts @@ -1,85 +1,62 @@ export const mockAboutData = { project: { - id: 'project-nest', - name: 'OWASP Nest', contributorsCount: 1200, issuesCount: 40, forksCount: 60, starsCount: 890, - summary: 'A community-first platform for OWASP collaboration', recentMilestones: [ { - id: 'milestone-1', title: 'NestBot title', body: 'NestBot Idea', url: 'http/github.com/milestones/5', progress: 58, - state: 'open', }, { - id: 'milestone-2', title: 'Contribution Hub title', body: 'Contribution Hub Idea', url: 'http/github.com/milestones/8', progress: 75, - state: 'open', }, { - id: 'milestone-3', title: 'Project Dashboard title', body: 'Project Dashboard Idea', url: 'http/github.com/milestones/10', progress: 80, - state: 'open', }, { - id: 'milestone-4', title: 'Milestone 4', body: 'Milestone 4 Idea', url: 'http/github.com/milestones/11', progress: 20, - state: 'open', }, { - id: 'milestone-5', title: 'Milestone 5', body: 'Milestone 5 Idea', url: 'http/github.com/milestones/12', progress: 40, - state: 'open', }, ], }, topContributors: Array.from({ length: 15 }, (_, i) => ({ - id: `contributor-${i + 1}`, avatarUrl: `https://avatars.githubusercontent.com/avatar${i + 1}.jpg`, login: `contributor${i + 1}`, name: `Contributor ${i + 1}`, })), users: { arkid15r: { - id: 'user-arkid15r', avatarUrl: 'https://avatars.githubusercontent.com/u/2201626?v=4', login: 'arkid15r', name: 'Arkadii Yakovets', - badgeCount: 0, - badges: [], }, kasya: { - id: 'user-kasya', avatarUrl: 'https://avatars.githubusercontent.com/u/5873153?v=4', login: 'kasya', name: 'Kate Golovanova', - badgeCount: 0, - badges: [], }, mamicidal: { - id: 'user-mamicidal', avatarUrl: 'https://avatars.githubusercontent.com/u/112129498?v=4', login: 'mamicidal', name: 'Starr Brown', - badgeCount: 0, - badges: [], }, }, } From 5cc7c3dd5e58bc31b8429034f155f24e2196ae8a Mon Sep 17 00:00:00 2001 From: Anurag Yadav Date: Mon, 2 Feb 2026 22:44:01 +0530 Subject: [PATCH 6/6] added function in all queries and nodes --- .../apps/github/api/internal/queries/issue.py | 6 +++++- .../github/api/internal/queries/pull_request.py | 6 +++++- .../apps/github/api/internal/queries/release.py | 6 +++++- .../github/api/internal/queries/repository.py | 16 +++++++--------- .../internal/queries/repository_contributor.py | 5 +++-- .../apps/mentorship/api/internal/nodes/module.py | 13 +++++++++++-- .../api/internal/queries/mentorship.py | 7 ++++++- .../mentorship/api/internal/queries/program.py | 13 ++++++++++--- .../api/internal/queries/board_of_directors.py | 10 +++++----- .../apps/owasp/api/internal/queries/chapter.py | 10 +++++----- backend/apps/owasp/api/internal/queries/event.py | 6 +++++- .../api/internal/queries/member_snapshot.py | 8 +++++--- backend/apps/owasp/api/internal/queries/post.py | 6 +++++- .../apps/owasp/api/internal/queries/snapshot.py | 16 +++++++--------- 14 files changed, 84 insertions(+), 44 deletions(-) diff --git a/backend/apps/github/api/internal/queries/issue.py b/backend/apps/github/api/internal/queries/issue.py index c1c077c2b3..2eef9a38ab 100644 --- a/backend/apps/github/api/internal/queries/issue.py +++ b/backend/apps/github/api/internal/queries/issue.py @@ -5,6 +5,7 @@ from django.db.models import F, Window from django.db.models.functions import Rank +from apps.common.utils import normalize_limit from apps.github.api.internal.nodes.issue import IssueNode from apps.github.models.issue import Issue @@ -61,4 +62,7 @@ def recent_issues( .order_by("-created_at") ) - return queryset[:limit] if (limit := min(limit, MAX_LIMIT)) > 0 else [] + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + return [] + + return queryset[:normalized_limit] diff --git a/backend/apps/github/api/internal/queries/pull_request.py b/backend/apps/github/api/internal/queries/pull_request.py index d001331003..0fe7ff8e82 100644 --- a/backend/apps/github/api/internal/queries/pull_request.py +++ b/backend/apps/github/api/internal/queries/pull_request.py @@ -5,6 +5,7 @@ from django.db.models import F, Window from django.db.models.functions import Rank +from apps.common.utils import normalize_limit from apps.github.api.internal.nodes.pull_request import PullRequestNode from apps.github.models.pull_request import PullRequest from apps.owasp.models.project import Project @@ -82,4 +83,7 @@ def recent_pull_requests( .order_by("-created_at") ) - return queryset[:limit] if (limit := min(limit, MAX_LIMIT)) > 0 else [] + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + return [] + + return queryset[:normalized_limit] diff --git a/backend/apps/github/api/internal/queries/release.py b/backend/apps/github/api/internal/queries/release.py index 928d78a9a8..ddd4a6cb4e 100644 --- a/backend/apps/github/api/internal/queries/release.py +++ b/backend/apps/github/api/internal/queries/release.py @@ -5,6 +5,7 @@ from django.db.models import F, Window from django.db.models.functions import Rank +from apps.common.utils import normalize_limit from apps.github.api.internal.nodes.release import ReleaseNode from apps.github.models.release import Release @@ -65,4 +66,7 @@ def recent_releases( .order_by("-published_at") ) - return queryset[:limit] if (limit := min(limit, MAX_LIMIT)) > 0 else [] + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + return [] + + return queryset[:normalized_limit] diff --git a/backend/apps/github/api/internal/queries/repository.py b/backend/apps/github/api/internal/queries/repository.py index 70a0a033b8..8efd5b4c2e 100644 --- a/backend/apps/github/api/internal/queries/repository.py +++ b/backend/apps/github/api/internal/queries/repository.py @@ -3,6 +3,7 @@ import strawberry import strawberry_django +from apps.common.utils import normalize_limit from apps.github.api.internal.nodes.repository import RepositoryNode from apps.github.models.repository import Repository @@ -54,12 +55,9 @@ def repositories( list[RepositoryNode]: A list of repositories. """ - return ( - ( - Repository.objects.filter( - organization__login__iexact=organization, - ).order_by("-stars_count")[:limit] - ) - if (limit := min(limit, MAX_LIMIT)) > 0 - else [] - ) + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + return [] + + return Repository.objects.filter( + organization__login__iexact=organization, + ).order_by("-stars_count")[:normalized_limit] diff --git a/backend/apps/github/api/internal/queries/repository_contributor.py b/backend/apps/github/api/internal/queries/repository_contributor.py index 9ae9c8f0b5..40103d19cc 100644 --- a/backend/apps/github/api/internal/queries/repository_contributor.py +++ b/backend/apps/github/api/internal/queries/repository_contributor.py @@ -3,6 +3,7 @@ import strawberry import strawberry_django +from apps.common.utils import normalize_limit from apps.github.api.internal.nodes.repository_contributor import RepositoryContributorNode from apps.github.models.repository_contributor import RepositoryContributor @@ -42,7 +43,7 @@ def top_contributors( list: List of top contributors with their details. """ - if (limit := min(limit, MAX_LIMIT)) <= 0: + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: return [] top_contributors = RepositoryContributor.get_top_contributors( @@ -50,7 +51,7 @@ def top_contributors( committee=committee, excluded_usernames=excluded_usernames, has_full_name=has_full_name, - limit=limit, + limit=normalized_limit, organization=organization, project=project, repository=repository, diff --git a/backend/apps/mentorship/api/internal/nodes/module.py b/backend/apps/mentorship/api/internal/nodes/module.py index 0a224089d3..56edf42a32 100644 --- a/backend/apps/mentorship/api/internal/nodes/module.py +++ b/backend/apps/mentorship/api/internal/nodes/module.py @@ -4,6 +4,7 @@ import strawberry +from apps.common.utils import normalize_limit from apps.github.api.internal.nodes.issue import IssueNode from apps.github.api.internal.nodes.pull_request import PullRequestNode from apps.github.api.internal.nodes.user import UserNode @@ -16,6 +17,8 @@ from apps.mentorship.models.issue_user_interest import IssueUserInterest from apps.mentorship.models.task import Task +MAX_LIMIT = 1000 + @strawberry.type class ModuleNode: @@ -79,6 +82,9 @@ def issues( self, limit: int = 20, offset: int = 0, label: str | None = None ) -> list[IssueNode]: """Return paginated issues linked to this module, optionally filtered by label.""" + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + return [] + queryset = self.issues.select_related("repository", "author").prefetch_related( "assignees", "labels" ) @@ -86,7 +92,7 @@ def issues( if label and label != "all": queryset = queryset.filter(labels__name=label) - return list(queryset.order_by("-updated_at")[offset : offset + limit]) + return list(queryset.order_by("-updated_at")[offset : offset + normalized_limit]) @strawberry.field def issues_count(self, label: str | None = None) -> int: @@ -163,12 +169,15 @@ def task_assigned_at(self, issue_number: int) -> datetime | None: @strawberry.field def recent_pull_requests(self, limit: int = 5) -> list[PullRequestNode]: """Return recent pull requests linked to issues in this module.""" + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + return [] + issue_ids = self.issues.values_list("id", flat=True) return list( PullRequest.objects.filter(related_issues__id__in=issue_ids) .select_related("author") .distinct() - .order_by("-created_at")[:limit] + .order_by("-created_at")[:normalized_limit] ) diff --git a/backend/apps/mentorship/api/internal/queries/mentorship.py b/backend/apps/mentorship/api/internal/queries/mentorship.py index a0ae2df5eb..970b8b6622 100644 --- a/backend/apps/mentorship/api/internal/queries/mentorship.py +++ b/backend/apps/mentorship/api/internal/queries/mentorship.py @@ -8,6 +8,7 @@ import strawberry from django.db.models import Prefetch +from apps.common.utils import normalize_limit from apps.github.api.internal.nodes.issue import IssueNode from apps.github.models import Label from apps.github.models.user import User as GithubUser @@ -21,6 +22,7 @@ from apps.github.api.internal.nodes.issue import IssueNode logger = logging.getLogger(__name__) +MAX_LIMIT = 1000 @strawberry.type @@ -97,6 +99,9 @@ def get_mentee_module_issues( offset: int = 0, ) -> list[IssueNode]: """Get issues assigned to a mentee in a specific module.""" + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + return [] + try: module = Module.objects.only("id").get(key=module_key, program__key=program_key) @@ -122,7 +127,7 @@ def get_mentee_module_issues( ) .order_by("-created_at") ) - issues = issues_qs[offset : offset + limit] + issues = issues_qs[offset : offset + normalized_limit] return list(issues) diff --git a/backend/apps/mentorship/api/internal/queries/program.py b/backend/apps/mentorship/api/internal/queries/program.py index fb272e8165..a785a08f36 100644 --- a/backend/apps/mentorship/api/internal/queries/program.py +++ b/backend/apps/mentorship/api/internal/queries/program.py @@ -5,12 +5,14 @@ import strawberry from django.db.models import Q +from apps.common.utils import normalize_limit from apps.mentorship.api.internal.nodes.program import PaginatedPrograms, ProgramNode from apps.mentorship.models import Program from apps.mentorship.models.mentor import Mentor from apps.nest.api.internal.permissions import IsAuthenticated PAGE_SIZE = 25 +MAX_LIMIT = 1000 logger = logging.getLogger(__name__) @@ -47,6 +49,9 @@ def my_programs( logger.warning("Mentor for user '%s' not found.", user.username) return PaginatedPrograms(programs=[], total_pages=0, current_page=page) + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + normalized_limit = PAGE_SIZE + queryset = ( Program.objects.prefetch_related( "admins__github_user", "modules__mentors__github_user" @@ -59,11 +64,13 @@ def my_programs( queryset = queryset.filter(name__icontains=search) total_count = queryset.count() - total_pages = max(1, (total_count + limit - 1) // limit) + total_pages = max(1, (total_count + normalized_limit - 1) // normalized_limit) page = max(1, min(page, total_pages)) - offset = (page - 1) * limit + offset = (page - 1) * normalized_limit - paginated_programs = queryset.order_by("-nest_created_at")[offset : offset + limit] + paginated_programs = queryset.order_by("-nest_created_at")[ + offset : offset + normalized_limit + ] results = [] mentor_id = mentor.id diff --git a/backend/apps/owasp/api/internal/queries/board_of_directors.py b/backend/apps/owasp/api/internal/queries/board_of_directors.py index df611022c3..fec99563a7 100644 --- a/backend/apps/owasp/api/internal/queries/board_of_directors.py +++ b/backend/apps/owasp/api/internal/queries/board_of_directors.py @@ -3,6 +3,7 @@ import strawberry import strawberry_django +from apps.common.utils import normalize_limit from apps.owasp.api.internal.nodes.board_of_directors import BoardOfDirectorsNode from apps.owasp.models.board_of_directors import BoardOfDirectors @@ -40,8 +41,7 @@ def boards_of_directors(self, limit: int = 10) -> list[BoardOfDirectorsNode]: List of BoardOfDirectorsNode objects. """ - return ( - BoardOfDirectors.objects.order_by("-year")[:limit] - if (limit := min(limit, MAX_LIMIT)) > 0 - else [] - ) + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + return [] + + return BoardOfDirectors.objects.order_by("-year")[:normalized_limit] diff --git a/backend/apps/owasp/api/internal/queries/chapter.py b/backend/apps/owasp/api/internal/queries/chapter.py index 64ecfd7f41..3bfa798dcd 100644 --- a/backend/apps/owasp/api/internal/queries/chapter.py +++ b/backend/apps/owasp/api/internal/queries/chapter.py @@ -3,6 +3,7 @@ import strawberry import strawberry_django +from apps.common.utils import normalize_limit from apps.owasp.api.internal.nodes.chapter import ChapterNode from apps.owasp.models.chapter import Chapter @@ -24,8 +25,7 @@ def chapter(self, key: str) -> ChapterNode | None: @strawberry_django.field def recent_chapters(self, limit: int = 8) -> list[ChapterNode]: """Resolve recent chapters.""" - return ( - Chapter.active_chapters.order_by("-created_at")[:limit] - if (limit := min(limit, MAX_LIMIT)) > 0 - else [] - ) + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + return [] + + return Chapter.active_chapters.order_by("-created_at")[:normalized_limit] diff --git a/backend/apps/owasp/api/internal/queries/event.py b/backend/apps/owasp/api/internal/queries/event.py index 68dad6941a..f72d09fcb9 100644 --- a/backend/apps/owasp/api/internal/queries/event.py +++ b/backend/apps/owasp/api/internal/queries/event.py @@ -3,6 +3,7 @@ import strawberry import strawberry_django +from apps.common.utils import normalize_limit from apps.owasp.api.internal.nodes.event import EventNode from apps.owasp.models.event import Event @@ -16,4 +17,7 @@ class EventQuery: @strawberry_django.field def upcoming_events(self, limit: int = 6) -> list[EventNode]: """Resolve upcoming events.""" - return Event.upcoming_events()[:limit] if (limit := min(limit, MAX_LIMIT)) > 0 else [] + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + return [] + + return Event.upcoming_events()[:normalized_limit] diff --git a/backend/apps/owasp/api/internal/queries/member_snapshot.py b/backend/apps/owasp/api/internal/queries/member_snapshot.py index 4aac59e04e..cc896f304a 100644 --- a/backend/apps/owasp/api/internal/queries/member_snapshot.py +++ b/backend/apps/owasp/api/internal/queries/member_snapshot.py @@ -3,6 +3,7 @@ import strawberry import strawberry_django +from apps.common.utils import normalize_limit from apps.github.models.user import User from apps.owasp.api.internal.nodes.member_snapshot import MemberSnapshotNode from apps.owasp.models.member_snapshot import MemberSnapshot @@ -67,6 +68,7 @@ def member_snapshots( except User.DoesNotExist: return [] - return ( - snapshots.order_by("-start_at")[:limit] if (limit := min(limit, MAX_LIMIT)) > 0 else [] - ) + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + return [] + + return snapshots.order_by("-start_at")[:normalized_limit] diff --git a/backend/apps/owasp/api/internal/queries/post.py b/backend/apps/owasp/api/internal/queries/post.py index a7f4ca3d23..f520359445 100644 --- a/backend/apps/owasp/api/internal/queries/post.py +++ b/backend/apps/owasp/api/internal/queries/post.py @@ -3,6 +3,7 @@ import strawberry import strawberry_django +from apps.common.utils import normalize_limit from apps.owasp.api.internal.nodes.post import PostNode from apps.owasp.models.post import Post @@ -16,4 +17,7 @@ class PostQuery: @strawberry_django.field def recent_posts(self, limit: int = 5) -> list[PostNode]: """Return the most recent posts.""" - return Post.recent_posts()[:limit] if (limit := min(limit, MAX_LIMIT)) > 0 else [] + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + return [] + + return Post.recent_posts()[:normalized_limit] diff --git a/backend/apps/owasp/api/internal/queries/snapshot.py b/backend/apps/owasp/api/internal/queries/snapshot.py index d649acad34..60cea2d366 100644 --- a/backend/apps/owasp/api/internal/queries/snapshot.py +++ b/backend/apps/owasp/api/internal/queries/snapshot.py @@ -3,6 +3,7 @@ import strawberry import strawberry_django +from apps.common.utils import normalize_limit from apps.owasp.api.internal.nodes.snapshot import SnapshotNode from apps.owasp.models.snapshot import Snapshot @@ -27,12 +28,9 @@ def snapshot(self, key: str) -> SnapshotNode | None: @strawberry_django.field def snapshots(self, limit: int = 12) -> list[SnapshotNode]: """Resolve snapshots.""" - return ( - Snapshot.objects.filter( - status=Snapshot.Status.COMPLETED, - ).order_by( - "-created_at", - )[:limit] - if (limit := min(limit, MAX_LIMIT)) > 0 - else [] - ) + if (normalized_limit := normalize_limit(limit, MAX_LIMIT)) is None: + return [] + + return Snapshot.objects.filter( + status=Snapshot.Status.COMPLETED, + ).order_by("-created_at")[:normalized_limit]