Skip to content

Commit c3aa10c

Browse files
authored
Merge branch 'main' into improve-about
2 parents 244bc08 + b58c2ae commit c3aa10c

File tree

20 files changed

+2216
-1972
lines changed

20 files changed

+2216
-1972
lines changed

.github/workflows/run-code-ql.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
3232

3333
- name: Initialize CodeQL
34-
uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3
34+
uses: github/codeql-action/init@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9
3535
with:
3636
languages: ${{ matrix.language }}
3737

@@ -55,6 +55,6 @@ jobs:
5555
run: pnpm install --frozen-lockfile
5656

5757
- name: Perform CodeQL analysis
58-
uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3
58+
uses: github/codeql-action/analyze@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9
5959
with:
6060
category: /language:${{ matrix.language }}

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ repos:
1010
exclude: (.github|pnpm-lock.yaml)
1111

1212
- repo: https://github.com/astral-sh/ruff-pre-commit
13-
rev: v0.13.0
13+
rev: v0.13.2
1414
hooks:
1515
- id: ruff
1616
args:
@@ -38,7 +38,7 @@ repos:
3838
exclude: (.github|pnpm-lock.yaml)
3939

4040
- repo: https://github.com/pre-commit/mirrors-mypy
41-
rev: v1.18.1
41+
rev: v1.18.2
4242
hooks:
4343
- id: mypy
4444
additional_dependencies:

backend/apps/api/rest/v0/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
# OWASP API v0
3+
4+
## Important points
5+
6+
When working on this API, **always follow these rules** to avoid breaking clients:
7+
8+
- The authentication class in `__init__.py` **must be named `ApiKey`**.
9+
- The client’s `api_key` parameter is automatically derived from this name.
10+
- **Do not rename this class**, only update its implementation if needed.
11+
12+
- Each API endpoint must have a **unique `operationId`** in the OpenAPI specification.
13+
- Duplicate `operationId`s will break client SDK generation and cause method conflicts.
14+
15+
- Endpoint naming documentation:
16+
- [Customize methods](https://www.speakeasy.com/docs/customize/methods)
17+
- [Customize namespaces](https://www.speakeasy.com/docs/customize/structure/namespaces)

backend/apps/api/rest/v0/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from apps.api.rest.v0.project import router as project_router
1414
from apps.api.rest.v0.release import router as release_router
1515
from apps.api.rest.v0.repository import router as repository_router
16+
from apps.api.rest.v0.sponsor import router as sponsor_router
1617

1718
from .chapter import router as chapter_router
1819

@@ -34,6 +35,8 @@
3435
"/releases": release_router,
3536
# Repositories.
3637
"/repositories": repository_router,
38+
# Sponsors.
39+
"/sponsors": sponsor_router,
3740
}
3841

3942

@@ -43,7 +46,7 @@
4346
"docs": Swagger(settings={"persistAuthorization": True}),
4447
"throttle": [AuthRateThrottle("10/s")],
4548
"title": "OWASP Nest",
46-
"version": "0.2.1",
49+
"version": "0.2.3",
4750
}
4851

4952
api_settings_customization = {}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""Sponsor API."""
2+
3+
from http import HTTPStatus
4+
from typing import Literal
5+
6+
from django.conf import settings
7+
from django.http import HttpRequest
8+
from django.views.decorators.cache import cache_page
9+
from ninja import Field, FilterSchema, Path, Query, Router, Schema
10+
from ninja.decorators import decorate_view
11+
from ninja.pagination import PageNumberPagination, paginate
12+
from ninja.responses import Response
13+
14+
from apps.owasp.models.sponsor import Sponsor
15+
16+
router = Router()
17+
18+
19+
class SponsorErrorResponse(Schema):
20+
"""Sponsor error response schema."""
21+
22+
message: str
23+
24+
25+
class SponsorFilterSchema(FilterSchema):
26+
"""Filter schema for Sponsor."""
27+
28+
is_member: bool | None = Field(
29+
None,
30+
description="Member status of the sponsor",
31+
)
32+
member_type: Sponsor.MemberType | None = Field(
33+
None,
34+
description="Member type of the sponsor",
35+
)
36+
37+
sponsor_type: str | None = Field(
38+
None,
39+
description="Filter by the type of sponsorship (e.g., Gold, Silver, Platinum).",
40+
example="Silver",
41+
)
42+
43+
44+
class SponsorSchema(Schema):
45+
"""Schema for Sponsor."""
46+
47+
description: str
48+
image_url: str
49+
is_member: bool
50+
job_url: str
51+
key: str
52+
member_type: str
53+
name: str
54+
sponsor_type: str
55+
url: str
56+
57+
58+
@router.get(
59+
"/",
60+
description="Retrieve a paginated list of OWASP sponsors.",
61+
operation_id="list_sponsors",
62+
response={HTTPStatus.OK: list[SponsorSchema]},
63+
summary="List sponsors",
64+
tags=["Sponsors"],
65+
)
66+
@decorate_view(cache_page(settings.API_CACHE_TIME_SECONDS))
67+
@paginate(PageNumberPagination, page_size=settings.API_PAGE_SIZE)
68+
def list_sponsors(
69+
request: HttpRequest,
70+
filters: SponsorFilterSchema = Query(...),
71+
ordering: Literal["name", "-name"] | None = Query(
72+
None,
73+
description="Ordering field",
74+
),
75+
) -> list[SponsorSchema]:
76+
"""Get sponsors."""
77+
return filters.filter(Sponsor.objects.order_by(ordering or "name"))
78+
79+
80+
@router.get(
81+
"/{str:sponsor_key}",
82+
description="Retrieve a sponsor details.",
83+
operation_id="get_sponsor",
84+
response={
85+
HTTPStatus.NOT_FOUND: SponsorErrorResponse,
86+
HTTPStatus.OK: SponsorSchema,
87+
},
88+
summary="Get sponsor",
89+
tags=["Sponsors"],
90+
)
91+
def get_sponsor(
92+
request: HttpRequest,
93+
sponsor_key: str = Path(..., example="adobe"),
94+
) -> SponsorSchema | SponsorErrorResponse:
95+
"""Get sponsor."""
96+
if sponsor := Sponsor.objects.filter(key__iexact=sponsor_key).first():
97+
return sponsor
98+
99+
return Response({"message": "Sponsor not found"}, status=HTTPStatus.NOT_FOUND)

backend/apps/owasp/models/common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def get_leaders_emails(self):
220220
leaders = {}
221221
for line in content.split("\n"):
222222
matches = re.findall(
223-
r"^[-*]\s*\[([^\]]+)\]\(mailto:([^)]+)(\)|([^[<\n]))", line.strip()
223+
r"^[-*]\s*\[([^\]]+)\]\((?:mailto:)?([^)]+)(\)|([^[<\n]))", line.strip()
224224
)
225225

226226
for match in matches:

0 commit comments

Comments
 (0)