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
4 changes: 0 additions & 4 deletions .github/workflows/label-pull-requests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
<<<<<<< HEAD
- uses: actions/labeler@f1a63e87db0c6baf19c5713083f8d00d789ca184
=======
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b
>>>>>>> main
with:
configuration-path: .github/labeler.yml
sync-labels: true
6 changes: 4 additions & 2 deletions .github/workflows/run-ci-cd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,8 @@ jobs:
platforms: linux/amd64
push: true
secrets: |
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
RELEASE_VERSION=${{ needs.set-release-version.outputs.release_version }}
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
tags: owasp/nest:frontend-staging

- name: Get frontend image size
Expand Down Expand Up @@ -665,7 +666,8 @@ jobs:
platforms: linux/amd64
push: true
secrets: |
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
RELEASE_VERSION=${{ needs.set-release-version.outputs.release_version }}
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
tags: owasp/nest:frontend-production

- name: Get frontend image size
Expand Down
14 changes: 14 additions & 0 deletions backend/apps/github/api/internal/nodes/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import strawberry_django

from apps.github.models.user import User
from apps.nest.api.internal.nodes.badge import BadgeNode


@strawberry_django.type(
Expand All @@ -27,6 +28,19 @@
class UserNode:
"""GitHub user node."""

@strawberry.field
def badges(self) -> list[BadgeNode]:
"""Return user badges."""
user_badges = (
self.user_badges.select_related("badge")
.filter(is_active=True)
.order_by(
"badge__weight",
"badge__name",
)
)
return [ub.badge for ub in user_badges]

@strawberry.field
def created_at(self) -> float:
"""Resolve created at."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def top_contributors(
RepositoryContributorNode(
avatar_url=tc["avatar_url"],
contributions_count=tc["contributions_count"],
id=tc["id"],
id=tc["login"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Do not use login as GraphQL id (unstable; cache-colliding across contexts).

  • GitHub usernames can be renamed, breaking node identity.
  • Same user across different projects will share the same id while carrying different fields (project_key, project_name), causing GraphQL cache corruption.

Prefer a stable backend identifier (GitHub id/node_id) or, if this node represents a “contributor membership,” compose the id with scope (e.g., project/repo) to ensure uniqueness. Temporary safe fallback keeps behavior while enabling a future model fix.

Apply this diff:

-                id=tc["login"],
+                # Prefer stable unique ids; fall back to login only if unavailable.
+                id=str(tc.get("id") or tc.get("node_id") or f"{tc['login']}|{tc.get('project_key') or ''}"),

Follow-up (outside this file): reintroduce a stable identifier in RepositoryContributor.get_top_contributors (include id or node_id) so the fallback can be removed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
id=tc["login"],
# Prefer stable unique ids; fall back to login+scope only if unavailable.
# Prefer stable unique ids; fall back to login only if unavailable.
id=str(
tc.get("id")
or tc.get("node_id")
or f"{tc['login']}|{tc.get('project_key') or ''}"
),

login=tc["login"],
name=tc["name"],
project_key=tc.get("project_key"),
Expand Down
5 changes: 0 additions & 5 deletions backend/apps/github/api/rest/v1/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,8 @@ def list_members(
HTTPStatus.NOT_FOUND: MemberErrorResponse,
HTTPStatus.OK: MemberSchema,
},
<<<<<<< HEAD:backend/apps/github/api/rest/v1/user.py
summary="Get user by login",
tags=["github"],
=======
summary="Get member by login",
tags=["Community"],
>>>>>>> main:backend/apps/github/api/rest/v1/member.py
)
def get_member(request: HttpRequest, login: str) -> MemberSchema | MemberErrorResponse:
"""Get user by login."""
Expand Down
2 changes: 0 additions & 2 deletions backend/apps/github/models/repository_contributor.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ def get_top_contributors(
"user__avatar_url",
"user__login",
"user__name",
"id",
)
.annotate(
total_contributions=Sum("contributions_count"),
Expand All @@ -195,7 +194,6 @@ def get_top_contributors(
{
"avatar_url": tc["user__avatar_url"],
"contributions_count": tc["total_contributions"],
"id": tc["id"],
"login": tc["user__login"],
"name": tc["user__name"],
}
Expand Down
20 changes: 20 additions & 0 deletions backend/apps/nest/api/internal/nodes/badge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""GraphQL node for Badge model."""

import strawberry
import strawberry_django

from apps.nest.models.badge import Badge


@strawberry_django.type(
Badge,
fields=[
"css_class",
"description",
"id",
"name",
"weight",
],
)
class BadgeNode(strawberry.relay.Node):
"""GraphQL node for Badge model."""
8 changes: 5 additions & 3 deletions backend/apps/nest/management/commands/nest_update_badges.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ def update_owasp_staff_badge(self):
self.stdout.write(f"Created badge: {badge.name}")

# Assign badge to employees who don't have it.
employees_without_badge = User.objects.filter(is_owasp_staff=True).exclude(
badges__badge=badge
employees_without_badge = User.objects.filter(
is_owasp_staff=True,
).exclude(
user_badges__badge=badge,
)
count = employees_without_badge.count()

Expand All @@ -59,7 +61,7 @@ def update_owasp_staff_badge(self):
# Remove badge from non-OWASP employees.
non_employees = User.objects.filter(
is_owasp_staff=False,
badges__badge=badge,
user_badges__badge=badge,
).distinct()
removed_count = non_employees.count()

Expand Down
24 changes: 24 additions & 0 deletions backend/apps/nest/migrations/0005_alter_userbadge_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 5.2.5 on 2025-09-06 09:41

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("github", "0035_alter_user_bio_alter_user_is_owasp_staff"),
("nest", "0004_userbadge"),
]

operations = [
migrations.AlterField(
model_name="userbadge",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="user_badges",
to="github.user",
verbose_name="User",
),
),
]
2 changes: 1 addition & 1 deletion backend/apps/nest/models/user_badge.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class Meta:
)
user = models.ForeignKey(
"github.User",
related_name="badges",
related_name="user_badges",
on_delete=models.CASCADE,
verbose_name="User",
)
Expand Down
98 changes: 77 additions & 21 deletions backend/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading