Skip to content
Closed
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
4 changes: 4 additions & 0 deletions backend/apps/github/api/internal/nodes/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

RECENT_ISSUES_LIMIT = 5
RECENT_RELEASES_LIMIT = 5
MAX_RECENT_MILESTONES_LIMIT = 100


@strawberry_django.type(
Expand Down Expand Up @@ -69,6 +70,9 @@ def project(
@strawberry_django.field(prefetch_related=["milestones"])
def recent_milestones(self, root: Repository, limit: int = 5) -> list[MilestoneNode]:
"""Resolve recent milestones."""
if limit < 0:
return []
limit = min(limit, MAX_RECENT_MILESTONES_LIMIT)
return root.recent_milestones.order_by("-created_at")[:limit]

@strawberry_django.field(prefetch_related=["releases"])
Expand Down
4 changes: 2 additions & 2 deletions backend/apps/owasp/api/internal/nodes/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
RECENT_RELEASES_LIMIT = 5
RECENT_PULL_REQUESTS_LIMIT = 5

MAX_LIMIT = 1000
MAX_LIMIT = 100


@strawberry_django.type(
Expand Down Expand Up @@ -54,7 +54,7 @@ def health_metrics_list(
) -> list[ProjectHealthMetricsNode]:
"""Resolve project health metrics."""
return (
root.health_metrics.order_by("nest_created_at")[:limit]
root.health_metrics.order_by("-nest_created_at")[:limit]
if (limit := min(limit, MAX_LIMIT)) > 0
else []
)
Expand Down
17 changes: 12 additions & 5 deletions backend/apps/owasp/api/internal/nodes/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
from apps.owasp.models.snapshot import Snapshot

RECENT_ISSUES_LIMIT = 100
RECENT_RELEASES_LIMIT = 50
RECENT_USERS_LIMIT = 50
RECENT_PROJECTS_LIMIT = 50
RECENT_CHAPTERS_LIMIT = 50


@strawberry_django.type(
Expand All @@ -25,8 +29,6 @@
class SnapshotNode(strawberry.relay.Node):
"""Snapshot node."""

new_chapters: list[ChapterNode] = strawberry_django.field()

@strawberry_django.field
def key(self, root: Snapshot) -> str:
"""Resolve key."""
Expand All @@ -40,14 +42,19 @@ def new_issues(self, root: Snapshot) -> list[IssueNode]:
@strawberry_django.field(prefetch_related=["new_projects"])
def new_projects(self, root: Snapshot) -> list[ProjectNode]:
"""Resolve new projects."""
return root.new_projects.order_by("-created_at")
return root.new_projects.order_by("-created_at")[:RECENT_PROJECTS_LIMIT]

@strawberry_django.field(prefetch_related=["new_releases"])
def new_releases(self, root: Snapshot) -> list[ReleaseNode]:
"""Resolve new releases."""
return root.new_releases.order_by("-published_at")
return root.new_releases.order_by("-published_at")[:RECENT_RELEASES_LIMIT]

@strawberry_django.field(prefetch_related=["new_users"])
def new_users(self, root: Snapshot) -> list[UserNode]:
"""Resolve new users."""
return root.new_users.order_by("-created_at")
return root.new_users.order_by("-created_at")[:RECENT_USERS_LIMIT]

@strawberry_django.field(prefetch_related=["new_chapters"])
def new_chapters(self, root: Snapshot) -> list[ChapterNode]:
"""Resolve new chapters."""
return root.new_chapters.order_by("-created_at")[:RECENT_CHAPTERS_LIMIT]
42 changes: 31 additions & 11 deletions backend/apps/owasp/api/internal/queries/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
from apps.owasp.api.internal.nodes.project import ProjectNode
from apps.owasp.models.project import Project

MAX_RECENT_PROJECTS_LIMIT = 1000
MAX_RECENT_PROJECTS_LIMIT = 100
MAX_SEARCH_QUERY_LENGTH = 100
MIN_SEARCH_QUERY_LENGTH = 3
SEARCH_PROJECTS_LIMIT = 3
SEARCH_PROJECTS_LIMIT = 100
MAX_PROJECT_KEY_LENGTH = 50


@strawberry.type
Expand All @@ -29,8 +30,15 @@ def project(self, key: str) -> ProjectNode | None:
ProjectNode | None: The project node if found, otherwise None.

"""
normalized_key = key.strip()

if not normalized_key or len(normalized_key) > MAX_PROJECT_KEY_LENGTH:
return None

try:
return Project.objects.get(key=f"www-project-{key}")
return Project.objects.only("id", "key", "name", "is_active", "created_at").get(
key=f"www-project-{normalized_key}"
)
except Project.DoesNotExist:
return None

Expand All @@ -45,10 +53,15 @@ 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 []
if limit <= 0:
return []

limit = min(max(limit, 1), MAX_RECENT_PROJECTS_LIMIT)

return list(
Project.objects.filter(is_active=True)
.only("id", "key", "name", "created_at", "is_active")
.order_by("-created_at")[:limit]
)

@strawberry_django.field
Expand All @@ -61,14 +74,21 @@ def search_projects(self, query: str) -> list[ProjectNode]:
):
return []

return Project.objects.filter(
is_active=True,
name__icontains=cleaned_query,
).order_by("name")[:SEARCH_PROJECTS_LIMIT]
return list(
Project.objects.filter(
is_active=True,
name__icontains=cleaned_query,
)
.only("id", "key", "name", "is_active")
.order_by("name")[:SEARCH_PROJECTS_LIMIT]
)

@strawberry_django.field
def is_project_leader(self, info: strawberry.Info, login: str) -> bool:
"""Check if a GitHub login or name is listed as a project leader."""
if not login or not login.strip():
return False

try:
github_user = GithubUser.objects.get(login=login)
except GithubUser.DoesNotExist:
Expand Down
17 changes: 10 additions & 7 deletions backend/apps/owasp/api/internal/queries/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from apps.owasp.api.internal.nodes.snapshot import SnapshotNode
from apps.owasp.models.snapshot import Snapshot

MAX_LIMIT = 100
MAX_LIMIT = 10


@strawberry.type
Expand All @@ -27,12 +27,15 @@ def snapshot(self, key: str) -> SnapshotNode | None:
@strawberry_django.field
def snapshots(self, limit: int = 12) -> list[SnapshotNode]:
"""Resolve snapshots."""
return (
if limit <= 0:
return []

limit = min(max(limit, 1), MAX_LIMIT)

return list(
Snapshot.objects.filter(
status=Snapshot.Status.COMPLETED,
).order_by(
"-created_at",
)[:limit]
if (limit := min(limit, MAX_LIMIT)) > 0
else []
)
.only("id", "key", "title", "created_at")
.order_by("-created_at")[:limit]
)
16 changes: 10 additions & 6 deletions backend/tests/apps/owasp/api/internal/queries/project_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,26 @@ def mock_project(self):

def test_resolve_project_existing(self, mock_project, mock_info):
"""Test resolving an existing project."""
with patch("apps.owasp.models.project.Project.objects.get") as mock_get:
mock_get.return_value = mock_project
with patch("apps.owasp.models.project.Project.objects.only") as mock_only:
mock_qs = Mock()
mock_qs.get.return_value = mock_project
mock_only.return_value = mock_qs

query = ProjectQuery()
result = query.__class__.__dict__["project"](query, key="test-project")

assert result == mock_project
mock_get.assert_called_once_with(key="www-project-test-project")
mock_qs.get.assert_called_once_with(key="www-project-test-project")

def test_resolve_project_not_found(self, mock_info):
"""Test resolving a non-existent project."""
with patch("apps.owasp.models.project.Project.objects.get") as mock_get:
mock_get.side_effect = Project.DoesNotExist
with patch("apps.owasp.models.project.Project.objects.only") as mock_only:
mock_qs = Mock()
mock_qs.get.side_effect = Project.DoesNotExist
mock_only.return_value = mock_qs

query = ProjectQuery()
result = query.__class__.__dict__["project"](query, key="non-existent")

assert result is None
mock_get.assert_called_once_with(key="www-project-non-existent")
mock_qs.get.assert_called_once_with(key="www-project-non-existent")