This commit is contained in:
parent
d4fd35769d
commit
06c0af7009
6 changed files with 100 additions and 49 deletions
|
|
@ -7,6 +7,8 @@ from urllib.parse import urlsplit
|
|||
from django.conf import settings
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from urllib.parse import SplitResult
|
||||
|
||||
from django.http import HttpRequest
|
||||
|
||||
|
||||
|
|
@ -16,7 +18,7 @@ def _get_base_url() -> str:
|
|||
Returns:
|
||||
str: The configured BASE_URL without trailing slash.
|
||||
"""
|
||||
base_url = getattr(settings, "BASE_URL", "")
|
||||
base_url: str = getattr(settings, "BASE_URL", "")
|
||||
return base_url.rstrip("/") if base_url else ""
|
||||
|
||||
|
||||
|
|
@ -33,7 +35,7 @@ def build_absolute_uri(
|
|||
Returns:
|
||||
str: Fully resolved absolute URL.
|
||||
"""
|
||||
base_url = _get_base_url()
|
||||
base_url: str = _get_base_url()
|
||||
|
||||
if location is None:
|
||||
if request is not None:
|
||||
|
|
@ -41,7 +43,7 @@ def build_absolute_uri(
|
|||
else:
|
||||
return f"{base_url}/" if base_url else "/"
|
||||
|
||||
parsed = urlsplit(location)
|
||||
parsed: SplitResult = urlsplit(location)
|
||||
if parsed.scheme and parsed.netloc:
|
||||
return location
|
||||
|
||||
|
|
@ -58,7 +60,7 @@ def build_absolute_uri(
|
|||
|
||||
def is_secure() -> bool:
|
||||
"""Return whether the configured BASE_URL uses HTTPS."""
|
||||
base_url = _get_base_url()
|
||||
base_url: str = _get_base_url()
|
||||
return base_url.startswith("https://") if base_url else False
|
||||
|
||||
|
||||
|
|
@ -69,9 +71,9 @@ class _TTVDropsSite:
|
|||
|
||||
def get_current_site(request: object) -> _TTVDropsSite:
|
||||
"""Return a site-like object with domain derived from BASE_URL."""
|
||||
base_url = _get_base_url()
|
||||
parts = urlsplit(base_url)
|
||||
domain = parts.netloc or parts.path
|
||||
base_url: str = _get_base_url()
|
||||
parts: SplitResult = urlsplit(base_url)
|
||||
domain: str = parts.netloc or parts.path
|
||||
return _TTVDropsSite(domain=domain)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,4 @@ def base_url(request: HttpRequest) -> dict[str, str]:
|
|||
Returns:
|
||||
dict[str, str]: A dictionary containing the BASE_URL.
|
||||
"""
|
||||
return {
|
||||
"BASE_URL": getattr(settings, "BASE_URL", ""),
|
||||
}
|
||||
return {"BASE_URL": getattr(settings, "BASE_URL", "")}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.test import RequestFactory
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
|
@ -5,6 +7,9 @@ from django.urls import reverse
|
|||
|
||||
from core.views import _build_base_url
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django.test.client import _MonkeyPatchedWSGIResponse
|
||||
|
||||
|
||||
@override_settings(ALLOWED_HOSTS=["example.com"])
|
||||
class TestBuildBaseUrl(TestCase):
|
||||
|
|
@ -16,7 +21,7 @@ class TestBuildBaseUrl(TestCase):
|
|||
|
||||
def test_valid_base_url(self) -> None:
|
||||
"""Test that the base URL is built correctly."""
|
||||
base_url = _build_base_url()
|
||||
base_url: str = _build_base_url()
|
||||
assert base_url == "https://ttvdrops.lovinator.space"
|
||||
|
||||
|
||||
|
|
@ -25,12 +30,16 @@ class TestSitemapViews(TestCase):
|
|||
|
||||
def test_sitemap_twitch_channels_view(self) -> None:
|
||||
"""Test that the Twitch channels sitemap view returns a valid XML response."""
|
||||
response = self.client.get(reverse("sitemap-twitch-channels"))
|
||||
response: _MonkeyPatchedWSGIResponse = self.client.get(
|
||||
reverse("sitemap-twitch-channels"),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert "<urlset" in response.content.decode()
|
||||
|
||||
def test_sitemap_twitch_drops_view(self) -> None:
|
||||
"""Test that the Twitch drops sitemap view returns a valid XML response."""
|
||||
response = self.client.get(reverse("sitemap-twitch-drops"))
|
||||
response: _MonkeyPatchedWSGIResponse = self.client.get(
|
||||
reverse("sitemap-twitch-drops"),
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert "<urlset" in response.content.decode()
|
||||
|
|
|
|||
102
core/urls.py
102
core/urls.py
|
|
@ -33,82 +33,122 @@ app_name = "core"
|
|||
|
||||
urlpatterns: list[URLPattern | URLResolver] = [
|
||||
# /
|
||||
path("", dashboard, name="dashboard"),
|
||||
path(
|
||||
route="",
|
||||
view=dashboard,
|
||||
name="dashboard",
|
||||
),
|
||||
# /search/
|
||||
path("search/", search_view, name="search"),
|
||||
path(
|
||||
route="search/",
|
||||
view=search_view,
|
||||
name="search",
|
||||
),
|
||||
# /debug/
|
||||
path("debug/", debug_view, name="debug"),
|
||||
path(
|
||||
route="debug/",
|
||||
view=debug_view,
|
||||
name="debug",
|
||||
),
|
||||
# /datasets/
|
||||
path("datasets/", dataset_backups_view, name="dataset_backups"),
|
||||
path(
|
||||
route="datasets/",
|
||||
view=dataset_backups_view,
|
||||
name="dataset_backups",
|
||||
),
|
||||
# /datasets/download/<relative_path>/
|
||||
path(
|
||||
"datasets/download/<path:relative_path>/",
|
||||
dataset_backup_download_view,
|
||||
route="datasets/download/<path:relative_path>/",
|
||||
view=dataset_backup_download_view,
|
||||
name="dataset_backup_download",
|
||||
),
|
||||
# /docs/rss/
|
||||
path("docs/rss/", docs_rss_view, name="docs_rss"),
|
||||
path(
|
||||
route="docs/rss/",
|
||||
view=docs_rss_view,
|
||||
name="docs_rss",
|
||||
),
|
||||
# RSS feeds
|
||||
# /rss/campaigns/ - all active campaigns
|
||||
path("rss/campaigns/", DropCampaignFeed(), name="campaign_feed"),
|
||||
path(
|
||||
route="rss/campaigns/",
|
||||
view=DropCampaignFeed(),
|
||||
name="campaign_feed",
|
||||
),
|
||||
# /rss/games/ - newly added games
|
||||
path("rss/games/", GameFeed(), name="game_feed"),
|
||||
path(
|
||||
route="rss/games/",
|
||||
view=GameFeed(),
|
||||
name="game_feed",
|
||||
),
|
||||
# /rss/games/<twitch_id>/campaigns/ - active campaigns for a specific game
|
||||
path(
|
||||
"rss/games/<str:twitch_id>/campaigns/",
|
||||
GameCampaignFeed(),
|
||||
route="rss/games/<str:twitch_id>/campaigns/",
|
||||
view=GameCampaignFeed(),
|
||||
name="game_campaign_feed",
|
||||
),
|
||||
# /rss/organizations/ - newly added organizations
|
||||
path(
|
||||
"rss/organizations/",
|
||||
OrganizationRSSFeed(),
|
||||
route="rss/organizations/",
|
||||
view=OrganizationRSSFeed(),
|
||||
name="organization_feed",
|
||||
),
|
||||
# /rss/reward-campaigns/ - all active reward campaigns
|
||||
path(
|
||||
"rss/reward-campaigns/",
|
||||
RewardCampaignFeed(),
|
||||
route="rss/reward-campaigns/",
|
||||
view=RewardCampaignFeed(),
|
||||
name="reward_campaign_feed",
|
||||
),
|
||||
# Atom feeds (added alongside RSS to preserve backward compatibility)
|
||||
path("atom/campaigns/", DropCampaignAtomFeed(), name="campaign_feed_atom"),
|
||||
path("atom/games/", GameAtomFeed(), name="game_feed_atom"),
|
||||
path(
|
||||
"atom/games/<str:twitch_id>/campaigns/",
|
||||
route="atom/campaigns/",
|
||||
view=DropCampaignAtomFeed(),
|
||||
name="campaign_feed_atom",
|
||||
),
|
||||
path(
|
||||
route="atom/games/",
|
||||
view=GameAtomFeed(),
|
||||
name="game_feed_atom",
|
||||
),
|
||||
path(
|
||||
route="atom/games/<str:twitch_id>/campaigns/",
|
||||
view=GameCampaignAtomFeed(),
|
||||
name="game_campaign_feed_atom",
|
||||
),
|
||||
path(
|
||||
"atom/organizations/",
|
||||
OrganizationAtomFeed(),
|
||||
route="atom/organizations/",
|
||||
view=OrganizationAtomFeed(),
|
||||
name="organization_feed_atom",
|
||||
),
|
||||
path(
|
||||
"atom/reward-campaigns/",
|
||||
RewardCampaignAtomFeed(),
|
||||
route="atom/reward-campaigns/",
|
||||
view=RewardCampaignAtomFeed(),
|
||||
name="reward_campaign_feed_atom",
|
||||
),
|
||||
# Discord feeds (Atom feeds with Discord relative timestamps)
|
||||
path(
|
||||
"discord/campaigns/",
|
||||
DropCampaignDiscordFeed(),
|
||||
route="discord/campaigns/",
|
||||
view=DropCampaignDiscordFeed(),
|
||||
name="campaign_feed_discord",
|
||||
),
|
||||
path("discord/games/", GameDiscordFeed(), name="game_feed_discord"),
|
||||
path(
|
||||
"discord/games/<str:twitch_id>/campaigns/",
|
||||
GameCampaignDiscordFeed(),
|
||||
route="discord/games/",
|
||||
view=GameDiscordFeed(),
|
||||
name="game_feed_discord",
|
||||
),
|
||||
path(
|
||||
route="discord/games/<str:twitch_id>/campaigns/",
|
||||
view=GameCampaignDiscordFeed(),
|
||||
name="game_campaign_feed_discord",
|
||||
),
|
||||
path(
|
||||
"discord/organizations/",
|
||||
OrganizationDiscordFeed(),
|
||||
route="discord/organizations/",
|
||||
view=OrganizationDiscordFeed(),
|
||||
name="organization_feed_discord",
|
||||
),
|
||||
path(
|
||||
"discord/reward-campaigns/",
|
||||
RewardCampaignDiscordFeed(),
|
||||
route="discord/reward-campaigns/",
|
||||
view=RewardCampaignDiscordFeed(),
|
||||
name="reward_campaign_feed_discord",
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -24,14 +24,14 @@ def _render_urlset_xml(sitemap_urls: list[dict[str, Any]]) -> str:
|
|||
Returns:
|
||||
str: Serialized XML for a <urlset> containing the provided URLs.
|
||||
"""
|
||||
urlset = Element("urlset")
|
||||
urlset: Element[str] = Element("urlset")
|
||||
urlset.set("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")
|
||||
|
||||
for url_entry in sitemap_urls:
|
||||
url = SubElement(urlset, "url")
|
||||
loc = url_entry.get("loc")
|
||||
url: Element[str] = SubElement(urlset, "url")
|
||||
loc: str | None = url_entry.get("loc")
|
||||
if loc:
|
||||
child = SubElement(url, "loc")
|
||||
child: Element[str] = SubElement(url, "loc")
|
||||
child.text = loc
|
||||
|
||||
return tostring(urlset, encoding="unicode")
|
||||
|
|
|
|||
|
|
@ -141,7 +141,9 @@ def _render_urlset_xml(
|
|||
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
||||
for url_entry in url_entries:
|
||||
xml += " <url>\n"
|
||||
loc = url_entry.get("loc") or url_entry.get("url") # Handle both keys
|
||||
loc: str | None = url_entry.get("loc") or url_entry.get(
|
||||
"url",
|
||||
) # Handle both keys
|
||||
if loc:
|
||||
xml += f" <loc>{loc}</loc>\n"
|
||||
if "lastmod" in url_entry:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue