Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
17 changes: 17 additions & 0 deletions backend/apps/api/rest/v0/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@

from apps.api.decorators.cache import cache_response
from apps.api.rest.v0.common import Leader, ValidationErrorSchema
from apps.api.rest.v0.sponsor import Sponsor
from apps.api.rest.v0.structured_search import FieldConfig, apply_structured_search
from apps.owasp.models.enums.project import ProjectLevel, ProjectType
from apps.owasp.models.project import Project as ProjectModel
from apps.owasp.models.sponsor import Sponsor as SponsorModel

PROJECT_SEARCH_FIELDS: dict[str, FieldConfig] = {
"name": {
Expand Down Expand Up @@ -145,3 +147,18 @@ def get_project(
return project

return Response({"message": "Project not found"}, status=HTTPStatus.NOT_FOUND)


@router.get(
"/nest/sponsors",
description="Retrieve sponsors associated with project nest.",
operation_id="list_nest_sponsors",
response=list[Sponsor],
summary="List nest sponsors",
)
@decorate_view(cache_response())
def list_nest_sponsors(
request: HttpRequest,
) -> list[Sponsor]:
"""Get sponsors for a project."""
return SponsorModel.objects.filter(status=SponsorModel.StatusType.ACTIVE)
Comment thread
Utkarsh-0304 marked this conversation as resolved.
Comment thread
Utkarsh-0304 marked this conversation as resolved.
8 changes: 1 addition & 7 deletions backend/apps/owasp/admin/sponsor.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,7 @@ class SponsorAdmin(admin.ModelAdmin, StandardOwaspAdminMixin):
),
(
"Status",
{
"fields": (
"is_member",
"member_type",
"sponsor_type",
)
},
{"fields": ("is_member", "member_type", "sponsor_type", "status")},
),
)

Expand Down
3 changes: 3 additions & 0 deletions backend/apps/owasp/api/internal/mutations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Sponsor Mutations."""

from .sponsor import SponsorMutation
40 changes: 40 additions & 0 deletions backend/apps/owasp/api/internal/mutations/sponsor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""OWASP sponsors GraphQL mutations."""

import logging

import strawberry
from django.db import IntegrityError
from django.utils import timezone

from apps.common.utils import slugify
from apps.owasp.api.internal.nodes.sponsor import CreateSponsorInput, SponsorNode
from apps.owasp.models import Sponsor

logger = logging.getLogger(__name__)

SPONSOR_EXISTS_ERROR = "A sponsor with this name already exists."


@strawberry.type
class SponsorMutation:
"""GraphQL mutations related to sponsor."""

@strawberry.mutation
def create_sponsor(self, input_data: CreateSponsorInput) -> SponsorNode:
"""Create a new sponsor."""
try:
sponsor = Sponsor.objects.create(
key=slugify(input_data.name),
sort_name=input_data.name,
name=input_data.name,
contact_email=input_data.contact_email,
message=input_data.message,
url=input_data.url,
created_at=timezone.now(),
)
except IntegrityError as err:
raise ValueError(SPONSOR_EXISTS_ERROR) from err

logger.info("Sponsor created successfully")

return sponsor
17 changes: 11 additions & 6 deletions backend/apps/owasp/api/internal/nodes/sponsor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@

@strawberry_django.type(
Sponsor,
fields=[
"image_url",
"name",
"sponsor_type",
"url",
],
fields=["image_url", "name", "sponsor_type", "url", "description"],
)
class SponsorNode(strawberry.relay.Node):
"""Sponsor node."""


@strawberry.input
class CreateSponsorInput:
"""Input Node for creating a sponsor."""

message: str
name: str
url: str
contact_email: str
15 changes: 15 additions & 0 deletions backend/apps/owasp/api/internal/queries/sponsor.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,18 @@ def sponsors(self) -> list[SponsorNode]:
Sponsor.SponsorType.NOT_SPONSOR: 6,
}[x.sponsor_type],
)

@strawberry_django.field
def active_sponsors(self) -> list[SponsorNode]:
"""Resolve active sponsors ordered by Sponsor level."""
return sorted(
Sponsor.objects.filter(status=Sponsor.StatusType.ACTIVE),
key=lambda x: {
Sponsor.SponsorType.DIAMOND: 1,
Sponsor.SponsorType.PLATINUM: 2,
Sponsor.SponsorType.GOLD: 3,
Sponsor.SponsorType.SILVER: 4,
Sponsor.SponsorType.SUPPORTER: 5,
Sponsor.SponsorType.NOT_SPONSOR: 6,
}[x.sponsor_type],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 6.0.3 on 2026-03-13 11:38

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("owasp", "0072_project_project_name_gin_idx_and_more"),
]

operations = [
migrations.AddField(
model_name="sponsor",
name="contact_email",
field=models.EmailField(blank=True, max_length=254),
),
migrations.AddField(
model_name="sponsor",
name="created_at",
field=models.DateTimeField(blank=True, null=True, verbose_name="Created At"),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 6.0.3 on 2026-03-13 11:58

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


class Migration(migrations.Migration):
dependencies = [
("owasp", "0073_sponsor_contact_email_sponsor_created_at"),
]

operations = [
migrations.AddField(
model_name="sponsor",
name="chapter",
field=models.ForeignKey(
null=True, on_delete=django.db.models.deletion.SET_NULL, to="owasp.chapter"
),
),
migrations.AddField(
model_name="sponsor",
name="project",
field=models.ForeignKey(
null=True, on_delete=django.db.models.deletion.SET_NULL, to="owasp.project"
),
),
migrations.AddField(
model_name="sponsor",
name="status",
field=models.CharField(
choices=[("Draft", "Draft"), ("Active", "Active"), ("Archived", "Archived")],
default="Draft",
max_length=20,
),
),
]
17 changes: 17 additions & 0 deletions backend/apps/owasp/migrations/0075_sponsor_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 6.0.3 on 2026-03-21 14:43

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("owasp", "0074_sponsor_chapter_sponsor_project_sponsor_status"),
]

operations = [
migrations.AddField(
model_name="sponsor",
name="message",
field=models.TextField(blank=True, verbose_name="Message"),
),
]
16 changes: 16 additions & 0 deletions backend/apps/owasp/models/sponsor.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,21 @@ class MemberType(models.TextChoices):
GOLD = "Gold"
SILVER = "Silver"

class StatusType(models.TextChoices):
"""Status type choices."""

DRAFT = "Draft"
ACTIVE = "Active"
ARCHIVED = "Archived"

# Basic information
description = models.TextField(verbose_name="Description", blank=True)
key = models.CharField(verbose_name="Key", max_length=100, unique=True)
name = models.CharField(verbose_name="Name", max_length=255)
sort_name = models.CharField(verbose_name="Sort Name", max_length=255)
created_at = models.DateTimeField(verbose_name="Created At", null=True, blank=True)
contact_email = models.EmailField(blank=True)
message = models.TextField(verbose_name="Message", blank=True)

# URLs and images
url = models.URLField(verbose_name="Website URL", blank=True)
Expand All @@ -63,6 +73,12 @@ class MemberType(models.TextChoices):
choices=SponsorType.choices,
default=SponsorType.NOT_SPONSOR,
)
status = models.CharField(max_length=20, choices=StatusType.choices, default=StatusType.DRAFT)

# FKs
chapter = models.ForeignKey("owasp.Chapter", on_delete=models.SET_NULL, null=True, blank=True)

project = models.ForeignKey("owasp.Project", on_delete=models.SET_NULL, null=True, blank=True)

def __str__(self) -> str:
"""Sponsor human readable representation."""
Expand Down
8 changes: 2 additions & 6 deletions backend/settings/graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,12 @@
ProgramQuery,
)
from apps.nest.api.internal.mutations import NestMutations
from apps.owasp.api.internal.mutations import SponsorMutation
from apps.owasp.api.internal.queries import OwaspQuery


@strawberry.type
class Mutation(
ApiMutations,
ModuleMutation,
NestMutations,
ProgramMutation,
):
class Mutation(ApiMutations, ModuleMutation, NestMutations, ProgramMutation, SponsorMutation):
"""Schema mutations."""


Expand Down
26 changes: 13 additions & 13 deletions docker-compose/local/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ services:
- 8000:8000
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Comment thread
Utkarsh-0304 marked this conversation as resolved.
volumes:
- ../../backend:/home/owasp
- backend-venv:/home/owasp/.venv
- backend-venv_4259:/home/owasp/.venv
Comment thread
Utkarsh-0304 marked this conversation as resolved.

cache:
command: >
Expand All @@ -50,7 +50,7 @@ services:
networks:
- nest-network
volumes:
- cache-data:/data
- cache-data_4259:/data

db:
container_name: nest-db
Expand All @@ -67,7 +67,7 @@ services:
networks:
- nest-network
volumes:
- db-data:/var/lib/postgresql/data
- db-data_4259:/var/lib/postgresql/data

docs:
container_name: nest-docs
Expand All @@ -89,7 +89,7 @@ services:
- ../../README.md:/home/owasp/README.md:ro
- ../../CODE_OF_CONDUCT.md:/home/owasp/CODE_OF_CONDUCT.md:ro
- ../../CONTRIBUTING.md:/home/owasp/CONTRIBUTING.md:ro
- docs-venv:/home/owasp/.venv
- docs-venv_4259:/home/owasp/.venv

frontend:
container_name: nest-frontend
Expand All @@ -111,8 +111,8 @@ services:
- 3000:3000
volumes:
- ../../frontend:/home/owasp
- frontend-next:/home/owasp/.next
- frontend-node-modules:/home/owasp/node_modules
- frontend-next_4259:/home/owasp/.next
- frontend-node-modules_4259:/home/owasp/node_modules

worker:
container_name: nest-worker
Expand Down Expand Up @@ -141,15 +141,15 @@ services:
- nest-network
volumes:
- ../../backend:/home/owasp
- backend-venv:/home/owasp/.venv
- backend-venv_4259:/home/owasp/.venv

networks:
nest-network:

volumes:
backend-venv:
cache-data:
db-data:
docs-venv:
frontend-next:
frontend-node-modules:
backend-venv_4259:
cache-data_4259:
db-data_4259:
docs-venv_4259:
frontend-next_4259:
frontend-node-modules_4259:
4 changes: 3 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
"react-icons": "^5.6.0",
"react-leaflet": "^5.0.0",
"react-leaflet-cluster": "^4.0.0",
"tailwind-merge": "^3.5.0"
"tailwind-merge": "^3.5.0",
"validator": "^13.15.26"
},
"devDependencies": {
"@axe-core/react": "^4.11.1",
Expand Down Expand Up @@ -90,6 +91,7 @@
"@types/node": "^25.5.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/validator": "^13.15.10",
"@typescript-eslint/eslint-plugin": "^8.57.2",
"@typescript-eslint/parser": "^8.57.2",
"eslint": "^9.39.4",
Expand Down
Loading