Move all the APIs under the same router

This commit is contained in:
Joakim Hellsén 2026-07-21 04:05:35 +02:00
commit c54ceeb7a8
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
27 changed files with 1289 additions and 351 deletions

View file

@ -203,6 +203,7 @@
<a href="{% url 'core:dataset_backups' %}">Dataset</a> |
<a href="https://github.com/sponsors/TheLovinator1">Donate</a> |
<a href="https://github.com/TheLovinator1/ttvdrops">GitHub</a> |
<a href="{% url 'api-v1:openapi-view' %}">API Docs</a> |
<form action="{% url 'core:search' %}" method="get" style="display: inline">
<input type="search"
name="q"
@ -220,7 +221,6 @@
<a href="{% url 'twitch:channel_list' %}">Channels</a> |
<a href="{% url 'twitch:badge_list' %}">Badges</a> |
<a href="{% url 'twitch:emote_gallery' %}">Emotes</a> |
<a href="{% url 'twitch:twitch-api-v1:openapi-view' %}">API Docs</a> |
<a href="https://www.twitch.tv/drops/inventory">Inventory</a>
<br />
<strong>Kick</strong>
@ -234,10 +234,7 @@
<a href="{% url 'chzzk:campaign_list' %}">Campaigns</a>
<br />
<strong>Other sites</strong>
<a href="#">Steam</a> |
<a href="{% url 'youtube:index' %}">YouTube</a> |
<a href="#">TikTok</a> |
<a href="#">Discord</a>
</nav>
<hr />
{% if messages %}

View file

@ -47,7 +47,7 @@
title="Atom feed for Twitch campaigns">[atom]</a>
<a href="{% url 'core:campaign_feed_discord' %}"
title="Discord feed for Twitch campaigns">[discord]</a>
<a href="{% url 'twitch:twitch-api-v1:list_campaigns' %}"
<a href="{% url 'api-v1:twitch-api-v1_list_campaigns' %}"
title="Twitch campaigns API">[api]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div>

View file

@ -7,40 +7,46 @@
<h1>Dataset Backups</h1>
<section>
<h2>About this dataset</h2>
<p>This site tracks and publishes open Twitch and Kick drop campaign data.</p>
<p>This site tracks and publishes open drop campaign data from Twitch and Kick.</p>
<p>
Campaign metadata in the downloadable datasets and public JSON API is released under
<strong>CC0</strong> so you can reuse it freely. The underlying source data is scraped from Twitch/Kick
APIs and pages, so we do not control the upstream content and cannot guarantee upstream accuracy or
<strong>CC0</strong> so you can reuse it freely. The underlying source data is scraped from Twitch and Kick
APIs and pages; we do not control the upstream content and cannot guarantee its accuracy or
permanence. Linked third-party images, logos, and trademarks may remain subject to separate rights.
</p>
<p>Note that some drops has missing or incomplete data due to Twitch API limitations.</p>
<p>Note that some drops have missing or incomplete data due to Twitch API limitations.</p>
<p>
Reward campaign data is sourced from
<a href="https://github.com/SunkwiBOT/twitch-drops-api">SunkwiBOT/twitch-drops-api</a>.
Raw JSON exports are available in the repository:
Raw JSON exports are available in the same repository as
<a href="https://github.com/SunkwiBOT/twitch-drops-api/blob/main/drops.json">drops.json</a>
and
<a href="https://github.com/SunkwiBOT/twitch-drops-api/blob/main/rewards.json">rewards.json</a>.
</p>
<p>
Need a special format for your workflow or research pipeline?
<a href="https://github.com/TheLovinator1/ttvdrops/issues">Contact me via GitHub issues</a>
Kick data is fetched directly from the public
<a href="https://web.kick.com/api/v1/drops/campaigns">Kick campaigns API</a>
on a recurring schedule.
</p>
<p>
Need a custom format for your workflow or research pipeline?
<a href="https://github.com/TheLovinator1/ttvdrops/issues">Open a GitHub issue</a>
and describe what you need.
</p>
</section>
<section>
<h2>Kick JSON API</h2>
<p>
Fully imported Kick campaigns are also available through the public, paginated
<a href="{% url 'kick:campaign_api_list' %}">Kick campaign JSON API</a>.
Responses include campaign dates, game and organization metadata, rewards, and the complete
Fully imported Kick campaigns are also available through the public paginated
<a href="{% url 'api-v1:kick-api-v1_list_campaigns' %}">Kick campaign JSON API</a>.
Responses include campaign dates, game and organization metadata, rewards, and the full
channel list stored for each campaign.
</p>
<p>
Use <code>page</code> and <code>page_size</code> for pagination. Filter by Kick category ID with
<code>game</code>, or by the computed campaign state with
<code>status=active</code>, <code>status=upcoming</code>, or <code>status=expired</code>.
<code>status=active</code>, <code>status=upcoming</code>, <code>status=expired</code>,
or <code>status=unknown</code>.
Page numbers and page sizes have a minimum of 1, and the maximum page size is 500.
</p>
<pre><code>{
@ -60,15 +66,60 @@
}]
}</code></pre>
<p>
Timestamps use ISO 8601 in UTC and may be <code>null</code> when the source lacks a date. Campaigns with
incomplete dates have the computed status <code>unknown</code> and are not returned by a status filter.
Timestamps use ISO 8601 in UTC and may be <code>null</code> when the source does not provide a date. Campaigns with
incomplete dates receive the computed status <code>unknown</code> and are excluded from status-based filtering.
Each item includes every stored channel and raw reward without RSS truncation or reward-name merging.
Consumers can stop pagination when <code>items</code> is empty or when
<code>page * page_size &gt;= total</code>.
</p>
<p>
An empty <code>channels</code> list means that no channel restriction data is stored. It does not,
by itself, guarantee that a campaign is available on every channel.
An empty <code>channels</code> list means no channel restriction data is stored. It does not
by itself guarantee that a campaign is available on every channel.
</p>
</section>
<section>
<h2>Twitch JSON API</h2>
<p>
Fully imported Twitch campaigns are available through the public paginated
<a href="{% url 'api-v1:twitch-api-v1_list_campaigns' %}">Twitch campaign JSON API</a>.
Responses include campaign dates, game and organization metadata, allowed channels, drops,
and rewards for each campaign.
</p>
<p>
Use <code>page</code> and <code>page_size</code> for pagination. Filter by Twitch game ID with
<code>game</code>, or by the computed campaign state with
<code>status=active</code>, <code>status=upcoming</code>, <code>status=expired</code>,
or <code>status=unknown</code>.
Page numbers and page sizes have a minimum of 1, and the maximum page size is 500.
</p>
<pre><code>{
"total": 1,
"page": 1,
"page_size": 100,
"items": [{
"twitch_id": "...",
"name": "...",
"game": { "twitch_id": "...", "display_name": "..." },
"organization": { "twitch_id": "...", "name": "...", "display_name": "..." },
"start_at": "2026-07-01T00:00:00Z",
"end_at": "2026-08-01T00:00:00Z",
"status": "active",
"allowed_channels": [],
"drops": [{ "name": "...", "benefits": ["..."] }]
}]
}</code></pre>
<p>
Timestamps use ISO 8601 in UTC and may be <code>null</code> when the source does not provide a date. Campaigns with
incomplete dates receive the computed status <code>unknown</code> and are excluded from status-based filtering.
</p>
<p>
Additional endpoints include
<a href="{% url 'api-v1:twitch-api-v1_list_games' %}">games</a>,
<a href="{% url 'api-v1:twitch-api-v1_list_organizations' %}">organizations</a>,
<a href="{% url 'api-v1:twitch-api-v1_list_channels' %}">channels</a>,
<a href="{% url 'api-v1:twitch-api-v1_list_reward_campaigns' %}">reward campaigns</a>, and
<a href="{% url 'api-v1:twitch-api-v1_list_badges' %}">chat badges</a>
all public and paginated. Each list endpoint has a corresponding detail view by ID.
</p>
</section>
{% if datasets %}
@ -97,7 +148,7 @@
{% endfor %}
</tbody>
</table>
<p>Found {{ dataset_count }} datasets.</p>
<p>{{ dataset_count }} dataset{% if dataset_count != 1 %}s{% endif %} available.</p>
{% else %}
<p>No dataset backups found.</p>
{% endif %}

View file

@ -127,6 +127,38 @@
<p>None ✅</p>
{% endif %}
</section>
<section>
<h2>Kick Campaigns With Unknown Status ({{ kick_unknown_campaigns|length }})</h2>
{% if kick_unknown_campaigns %}
<ul>
{% for c in kick_unknown_campaigns %}
<li>
<a href="{% url 'kick:campaign_detail' c.kick_id %}">{{ c.name }}</a>
(Game: <a href="{% url 'kick:game_detail' c.category.kick_id %}">{{ c.category.name }}</a>)
- Start: {{ c.starts_at|default:'(none)' }} / End: {{ c.ends_at|default:'(none)' }}
</li>
{% endfor %}
</ul>
{% else %}
<p>None ✅</p>
{% endif %}
</section>
<section>
<h2>Twitch Reward Campaigns With Unknown Status ({{ twitch_reward_unknown_campaigns|length }})</h2>
{% if twitch_reward_unknown_campaigns %}
<ul>
{% for c in twitch_reward_unknown_campaigns %}
<li>
<a href="{% url 'twitch:reward_campaign_detail' c.twitch_id %}">{{ c.name }}</a>
(Game: <a href="{% url 'twitch:game_detail' c.game.twitch_id %}">{{ c.game.display_name }}</a>)
- Start: {{ c.starts_at|default:'(none)' }} / End: {{ c.ends_at|default:'(none)' }}
</li>
{% endfor %}
</ul>
{% else %}
<p>None ✅</p>
{% endif %}
</section>
<section>
<h2>Duplicate Campaign Names Per Game ({{ duplicate_name_campaigns|length }})</h2>
{% if duplicate_name_campaigns %}

View file

@ -18,7 +18,7 @@
</p>
<p>
Twitch JSON API documentation is available at
<a href="{% url 'twitch:twitch-api-v1:openapi-view' %}">/twitch/api/v1/docs</a>.
<a href="{% url 'api-v1:openapi-view' %}">/api/v1/docs/</a>.
</p>
<p>
Twitch campaign feeds accept <code>?limit=50</code> to change item count and

View file

@ -121,7 +121,7 @@
<a href="{% url 'core:game_campaign_feed_discord' campaign.game.twitch_id %}"
title="Discord feed for {{ campaign.game.display_name }} campaigns">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
<a href="{% url 'twitch:twitch-api-v1:get_campaign' campaign.twitch_id %}"
<a href="{% url 'api-v1:twitch-api-v1_get_campaign' campaign.twitch_id %}"
title="Twitch campaign API">[api]</a>
{% endif %}
</div>

View file

@ -33,7 +33,7 @@
title="Atom feed for all campaigns">[atom]</a>
<a href="{% url 'core:campaign_feed_discord' %}"
title="Discord feed for all campaigns">[discord]</a>
<a href="{% url 'twitch:twitch-api-v1:list_campaigns' %}"
<a href="{% url 'api-v1:twitch-api-v1_list_campaigns' %}"
title="Twitch campaigns API">[api]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
<a href="{% url 'twitch:export_campaigns_csv' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}"

View file

@ -36,7 +36,7 @@
title="Atom feed for campaigns">[atom]</a>
<a href="{% url 'core:campaign_feed_discord' %}"
title="Discord feed for campaigns">[discord]</a>
<a href="{% url 'twitch:twitch-api-v1:list_campaigns' %}"
<a href="{% url 'api-v1:twitch-api-v1_list_campaigns' %}"
title="Twitch campaigns API">[api]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div>

View file

@ -91,7 +91,7 @@
title="Discord feed for {{ game.display_name }} rewards">[discord]</a>
</div>
<div>
<a href="{% url 'twitch:twitch-api-v1:get_game' game.twitch_id %}"
<a href="{% url 'api-v1:twitch-api-v1_get_game' game.twitch_id %}"
title="Twitch game API">[api]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div>

View file

@ -32,7 +32,7 @@
title="Atom feed for all games">[atom]</a>
<a href="{% url 'core:game_feed_discord' %}"
title="Discord feed for all games">[discord]</a>
<a href="{% url 'twitch:twitch-api-v1:list_games' %}"
<a href="{% url 'api-v1:twitch-api-v1_list_games' %}"
title="Twitch games API">[api]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
<a href="{% url 'twitch:export_games_csv' %}"

View file

@ -30,7 +30,7 @@
title="Atom feed for all games">[atom]</a>
<a href="{% url 'core:game_feed_discord' %}"
title="Discord feed for all games">[discord]</a>
<a href="{% url 'twitch:twitch-api-v1:list_games' %}"
<a href="{% url 'api-v1:twitch-api-v1_list_games' %}"
title="Twitch games API">[api]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
<a href="{% url 'twitch:export_games_csv' %}"

View file

@ -15,7 +15,7 @@
title="Atom feed for all organizations">[atom]</a>
<a href="{% url 'core:organization_feed_discord' %}"
title="Discord feed for all organizations">[discord]</a>
<a href="{% url 'twitch:twitch-api-v1:list_organizations' %}"
<a href="{% url 'api-v1:twitch-api-v1_list_organizations' %}"
title="Twitch organizations API">[api]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
<a href="{% url 'twitch:export_organizations_csv' %}"

View file

@ -123,7 +123,7 @@
title="Atom feed for all reward campaigns">[atom]</a>
<a href="{% url 'core:reward_campaign_feed_discord' %}"
title="Discord feed for all reward campaigns">[discord]</a>
<a href="{% url 'twitch:twitch-api-v1:get_reward_campaign' reward_campaign.twitch_id %}"
<a href="{% url 'api-v1:twitch-api-v1_get_reward_campaign' reward_campaign.twitch_id %}"
title="Twitch reward campaign API">[api]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
{% if reward_campaign.external_url %}

View file

@ -30,7 +30,7 @@
<div>
<div>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
<a href="{% url 'twitch:twitch-api-v1:list_reward_campaigns' %}"
<a href="{% url 'api-v1:twitch-api-v1_list_reward_campaigns' %}"
title="Twitch reward campaigns API">[api]</a>
</div>
<div>