Files
geoip_block_generator/templates/api.html
Mateusz Gruszczyński 1d8071966b api update
2026-02-25 10:02:37 +01:00

948 lines
40 KiB
HTML

{% extends "base.html" %}
{% block title %}API Documentation - {{ app_name }}{% endblock %}
{% block content %}
<div class="container mt-4">
<div class="row">
<div class="col-lg-10 mx-auto">
<div class="mb-4">
<h2>API Documentation</h2>
<p class="lead">RESTful API for programmatic access to geo-blocking configuration generation.</p>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
<strong>Base URL:</strong> <code id="baseUrl"></code>
</div>
</div>
<!-- Endpoint 1: Get Countries -->
<div class="card mb-3">
<div class="card-header api-header-get" onclick="toggleEndpoint('endpoint1')">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="badge bg-info me-2">GET</span>
<code class="api-path">/api/countries</code>
<span class="ms-3 text-muted">Get available countries</span>
</div>
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="card-body collapse" id="endpoint1">
<h6 class="fw-bold">Description</h6>
<p>Returns a list of all available countries with their ISO codes and flag emojis.</p>
<h6 class="fw-bold mt-3">Response Schema</h6>
<pre><code>{
"success": <span class="text-success">true</span>,
"countries": [
{
"code": <span class="text-warning">"CN"</span>,
"name": <span class="text-warning">"China"</span>,
"flag": <span class="text-warning">"🇨🇳"</span>
}
]
}</code></pre>
<h6 class="fw-bold mt-3">Try it out</h6>
<button class="btn btn-sm btn-primary" onclick="tryEndpoint('countries')">
<i class="fas fa-play me-1"></i>Execute
</button>
<div id="response-countries" class="mt-3" style="display:none;">
<h6 class="fw-bold">Response</h6>
<pre><code id="response-countries-body"></code></pre>
</div>
</div>
</div>
<!-- Endpoint 2: Database Status -->
<div class="card mb-3">
<div class="card-header api-header-get" onclick="toggleEndpoint('endpoint2')">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="badge bg-info me-2">GET</span>
<code class="api-path">/api/database/status</code>
<span class="ms-3 text-muted">Check database status</span>
</div>
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="card-body collapse" id="endpoint2">
<h6 class="fw-bold">Description</h6>
<p>Returns the current status of the MaxMind GeoIP database, including last update time and whether an update is needed.</p>
<h6 class="fw-bold mt-3">Response Schema</h6>
<pre><code>{
"success": <span class="text-success">true</span>,
"exists": <span class="text-success">true</span>,
"needs_update": <span class="text-danger">false</span>,
"last_update": <span class="text-warning">"2026-02-10T08:00:00"</span>,
"file_size": <span class="text-info">5242880</span>,
"auto_update": <span class="text-success">true</span>
}</code></pre>
<h6 class="fw-bold mt-3">Try it out</h6>
<button class="btn btn-sm btn-primary" onclick="tryEndpoint('database/status')">
<i class="fas fa-play me-1"></i>Execute
</button>
<div id="response-database-status" class="mt-3" style="display:none;">
<h6 class="fw-bold">Response</h6>
<pre><code id="response-database-status-body"></code></pre>
</div>
</div>
</div>
<!-- Cache Status - Redis-->
<div class="card mb-3">
<div class="card-header api-header-get" onclick="toggleEndpoint('endpoint-cache-redis-status')">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="badge bg-info me-2">GET</span>
<code class="api-path">/api/cache/redis/status</code>
<span class="ms-3 text-muted">Redis L1 cache</span>
</div>
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="card-body collapse" id="endpoint-cache-redis-status">
<h6 class="fw-bold">Description</h6>
<p>Redis (L1: configs/networks)</p>
<h6 class="fw-bold mt-3">Response Schema</h6>
<pre><code>{
"success": <span class="text-success">true</span>,
"enabled": <span class="text-success">true</span>,
"health": {
"connected": <span class="text-success">true</span>,
"memory_peak_mb": <span class="text-info">474.25</span>,
"memory_used_mb": <span class="text-info">241.38</span>,
"status": <span class="text-success">"healthy"</span>
},
"stats": {
"country_keys": <span class="text-info">119</span>,
"config_keys": <span class="text-info">0</span>,
"total_keys": <span class="text-info">119</span>,
"total_size_mb": <span class="text-info">240.4</span>
}
}
</code></pre>
<h6 class="fw-bold mt-3">Fields</h6>
<table class="table table-sm">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>enabled</code></td>
<td>boolean</td>
<td>Redis configured (REDISENABLED=false)</td>
</tr>
<tr>
<td><code>health.connected</code></td>
<td>boolean</td>
<td>TCP connection OK</td>
</tr>
<tr>
<td><code>health.memory_peak_mb</code></td>
<td>float</td>
<td>Redis peak memory usage</td>
</tr>
<tr>
<td><code>stats.country_keys</code></td>
<td>integer</td>
<td><code>geobancountry*</code> keys (119 networks cached)</td>
</tr>
<tr>
<td><code>stats.config_keys</code></td>
<td>integer</td>
<td><code>geoipconfig*</code> + <code>geobanconfig*</code> (0 configs)</td>
</tr>
</tbody>
</table>
<h6 class="fw-bold mt-3">Cache Flow</h6>
<div class="alert alert-info">
<strong>L1 Redis:</strong> configs/networks (&lt;1s) →
<strong>L2 SQLite:</strong> fallback networks →
<strong>L3 MaxMind:</strong> live scan
</div>
<h6 class="fw-bold mt-3">Error Handling</h6>
<div class="alert alert-warning">
Redis offline? → Returns partial stats + <code>"error": "Connection refused"</code><br>
Logs: <code>ConnectionError: Error 111 connecting to localhost:6379</code>
</div>
<h6 class="fw-bold mt-3">Try it out</h6>
<button class="btn btn-sm btn-primary" onclick="tryEndpoint('cache/redis/status')">
<i class="fas fa-play me-1"></i>Execute
</button>
<div id="response-cache-redis-status" class="mt-3" style="display:none;">
<h6 class="fw-bold">Response</h6>
<pre><code id="response-cache-redis-status-body"></code></pre>
</div>
</div>
</div>
<!-- SQLite Status (L2 Cache) -->
<div class="card mb-3">
<div class="card-header api-header-get" onclick="toggleEndpoint('endpoint-cache-sqlite-status')">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="badge bg-info me-2">GET</span>
<code class="api-path">/api/cache/sqlite/status</code>
<span class="ms-3 text-muted">SQLite L2 cache</span>
</div>
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="card-body collapse" id="endpoint-cache-sqlite-status">
<h6 class="fw-bold">Description</h6>
<p>SQLite L2 cache (`networks_cache.db`): country networks from MaxMind scans.</p>
<h6 class="fw-bold mt-3">Response Schema</h6>
<pre><code>{
"success": <span class="text-success">true</span>,
"exists": <span class="text-success">true</span>,
"file_size_mb": <span class="text-info">1439.54</span>,
"total_countries": <span class="text-info">123</span>,
"total_networks": <span class="text-info">13728494</span>,
"top_countries": [
{"code": <span class="text-warning">"US"</span>, "networks": <span class="text-info">5801506</span>}
]
}</code></pre>
<h6 class="fw-bold mt-3">Fields</h6>
<table class="table table-sm">
<thead>
<tr><th>Name</th><th>Type</th><th>Description</th></tr>
</thead>
<tbody>
<tr><td><code>exists</code></td><td>boolean</td><td>DB file exists</td></tr>
<tr><td><code>file_size_mb</code></td><td>float</td><td>DB size on disk</td></tr>
<tr><td><code>total_countries</code></td><td>integer</td><td>Countries with cached networks</td></tr>
<tr><td><code>top_countries</code></td><td>array</td><td>Top 5 by network count</td></tr>
</tbody>
</table>
<h6 class="fw-bold mt-3">Try it out</h6>
<button class="btn btn-sm btn-primary" onclick="tryEndpoint('cache/sqlite/status')">
<i class="fas fa-play me-1"></i>Execute
</button>
<!-- ID: response-cache-sqlite-status -->
<div id="response-cache-sqlite-status" class="mt-3" style="display:none;">
<h6 class="fw-bold">Response</h6>
<pre><code id="response-cache-sqlite-status-body"></code></pre>
</div>
</div>
</div>
<!-- Cache Invalidate -->
<div class="card mb-3">
<div class="card-header api-header-post" onclick="toggleEndpoint('endpoint-cache-invalidate')">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="badge bg-success me-2">POST</span>
<code class="api-path">/api/cache/invalidate/&lt;country_code&gt;</code>
<span class="ms-3 text-muted">Invalidate country cache</span>
</div>
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="card-body collapse" id="endpoint-cache-invalidate">
<h6 class="fw-bold">Description</h6>
<p>Clears Redis L1 cache for specific country: networks + all configs containing it. Forces fresh SQLite/MaxMind scan next time.</p>
<h6 class="fw-bold mt-3">Path Parameter</h6>
<table class="table table-sm">
<thead><tr><th>Name</th><th>Type</th><th>Description</th></tr></thead>
<tbody>
<tr><td><code>country_code</code></td><td>string</td><td>ISO 3166-1 alpha-2 (e.g. <code>CN</code>, <code>RU</code>)</td></tr>
</tbody>
</table>
<h6 class="fw-bold mt-3">Response Schema</h6>
<pre><code>{
"success": <span class="text-success">true</span>,
"deleted": <span class="text-info">5</span>,
"country": <span class="text-warning">"CN"</span>,
"details": {
"country_cache": <span class="text-info">1</span>,
"config_caches": <span class="text-info">4</span>
}
}</code></pre>
<h6 class="fw-bold mt-3">What it deletes</h6>
<ul>
<li><code>geoban:country:CN</code> - country networks</li>
<li><code>geoban:config:*[CN]</code> - configs with CN</li>
</ul>
<h6 class="fw-bold mt-3">Try it out</h6>
<p>Enter country code (e.g. <code>CN</code>):
<input type="text" id="invalidateCountry" class="form-control form-control-sm d-inline w-auto" maxlength="2" placeholder="CN">
<button class="btn btn-sm btn-warning ms-2" onclick="tryInvalidateCountry()">
<i class="fas fa-trash me-1"></i>Invalidate
</button>
</p>
<div id="response-cache-invalidate" class="mt-3" style="display:none;">
<h6 class="fw-bold">Response</h6>
<pre><code id="response-cache-invalidate-body"></code></pre>
</div>
<h6 class="fw-bold mt-3">cURL Example</h6>
<pre><code>curl -X POST <span id="curlUrl-invalidate"></span>/api/cache/invalidate/CN</code></pre>
</div>
</div>
<!-- Endpoint 3: Update Database -->
<div class="card mb-3">
<div class="card-header api-header-post" onclick="toggleEndpoint('endpoint3')">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="badge bg-success me-2">POST</span>
<code class="api-path">/api/database/update</code>
<span class="ms-3 text-muted">Update database manually</span>
</div>
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="card-body collapse" id="endpoint3">
<h6 class="fw-bold">Description</h6>
<p>Manually triggers a download and update of the MaxMind GeoIP database from configured sources.</p>
<h6 class="fw-bold mt-3">Response Schema</h6>
<pre><code>{
"success": <span class="text-success">true</span>,
"url": <span class="text-warning">"https://github.com/..."</span>,
"size": <span class="text-info">5242880</span>
}</code></pre>
<h6 class="fw-bold mt-3">Try it out</h6>
<button class="btn btn-sm btn-success" onclick="tryEndpoint('database/update', 'POST')">
<i class="fas fa-play me-1"></i>Execute
</button>
<div id="response-database-update" class="mt-3" style="display:none;">
<h6 class="fw-bold">Response</h6>
<pre><code id="response-database-update-body"></code></pre>
</div>
</div>
</div>
<!-- Endpoint 4: Progress Status -->
<div class="card mb-3">
<div class="card-header api-header-get" onclick="toggleEndpoint('endpoint4')">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="badge bg-info me-2">GET</span>
<code class="api-path">/api/progress</code>
<span class="ms-3 text-muted">Get current generation progress</span>
</div>
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="card-body collapse" id="endpoint4">
<h6 class="fw-bold">Description</h6>
<p>Returns the current progress status of any active configuration generation process. Poll this endpoint to monitor long-running operations.</p>
<h6 class="fw-bold mt-3">Response Schema</h6>
<pre><code>{
"active": <span class="text-success">true</span>,
"message": <span class="text-warning">"[1/3] CN: Scanning MaxMind: 234 networks found"</span>,
"progress": <span class="text-info">30</span>,
"total": <span class="text-info">100</span>
}</code></pre>
<h6 class="fw-bold mt-3">Fields</h6>
<table class="table table-sm">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>active</code></td>
<td>boolean</td>
<td>Whether a generation process is currently active</td>
</tr>
<tr>
<td><code>message</code></td>
<td>string</td>
<td>Current progress message with detailed status</td>
</tr>
<tr>
<td><code>progress</code></td>
<td>integer</td>
<td>Current progress value (0-100)</td>
</tr>
<tr>
<td><code>total</code></td>
<td>integer</td>
<td>Total progress value (always 100)</td>
</tr>
</tbody>
</table>
<h6 class="fw-bold mt-3">Try it out</h6>
<button class="btn btn-sm btn-primary" onclick="tryEndpoint('progress')">
<i class="fas fa-play me-1"></i>Execute
</button>
<div id="response-progress" class="mt-3" style="display:none;">
<h6 class="fw-bold">Response</h6>
<pre><code id="response-progress-body"></code></pre>
</div>
<h6 class="fw-bold mt-3">Polling Example</h6>
<pre><code>// Poll every 500ms during generation
const pollProgress = setInterval(async () => {
const response = await fetch('/api/progress');
const data = await response.json();
if (data.active) {
console.log(`Progress: ${data.progress}% - ${data.message}`);
} else {
clearInterval(pollProgress);
console.log('Generation complete!');
}
}, 500);</code></pre>
</div>
</div>
<!-- Endpoint 5: Generate Preview -->
<div class="card mb-3">
<div class="card-header api-header-post" onclick="toggleEndpoint('endpoint5')">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="badge bg-success me-2">POST</span>
<code class="api-path">/api/generate/preview</code>
<span class="ms-3 text-muted">Preview configuration (JSON response)</span>
</div>
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="card-body collapse" id="endpoint5">
<h6 class="fw-bold">Description</h6>
<p>Generates configuration and returns it as JSON (instead of file download). Perfect for previewing or integrating into other applications.</p>
<h6 class="fw-bold mt-3">Request Body</h6>
<pre><code>{
"countries": [<span class="text-warning">"CN"</span>, <span class="text-warning">"RU"</span>],
"app_type": <span class="text-warning">"nginx"</span>,
"app_variant": <span class="text-warning">"map"</span>,
"aggregate": <span class="text-success">true</span>,
"use_cache": <span class="text-success">true</span>
}</code></pre>
<h6 class="fw-bold mt-3">Parameters</h6>
<table class="table table-sm">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Required</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>countries</code></td>
<td>array</td>
<td><span class="badge bg-danger">required</span></td>
<td>List of ISO 3166-1 alpha-2 country codes</td>
</tr>
<tr>
<td><code>app_type</code></td>
<td>string</td>
<td><span class="badge bg-danger">required</span></td>
<td>One of: <code>nginx</code>, <code>apache</code>, <code>haproxy</code>, <code>raw-cidr</code></td>
</tr>
<tr>
<td><code>app_variant</code></td>
<td>string</td>
<td><span class="badge bg-danger">required</span></td>
<td>Configuration style (depends on app_type)</td>
</tr>
<tr>
<td><code>aggregate</code></td>
<td>boolean</td>
<td><span class="badge bg-secondary">optional</span></td>
<td>Aggregate IP networks to reduce count (default: true)</td>
</tr>
<tr>
<td><code>use_cache</code></td>
<td>boolean</td>
<td><span class="badge bg-secondary">optional</span></td>
<td>Use Redis cache if available (default: true). Set to <code>false</code> to force fresh data from SQLite/MaxMind</td>
</tr>
</tbody>
</table>
<h6 class="fw-bold mt-3">Response Schema</h6>
<pre><code>{
"success": <span class="text-success">true</span>,
"config": <span class="text-warning">"# Nginx Map Module Configuration\n..."</span>,
"stats": {
"countries": <span class="text-info">2</span>,
"total_networks": <span class="text-info">4567</span>,
"per_country": {
"CN": <span class="text-info">2834</span>,
"RU": <span class="text-info">1733</span>
}
},
"from_cache": <span class="text-success">true</span>,
"cache_type": <span class="text-warning">"redis"</span>,
"generated_at": <span class="text-warning">"2026-02-16T10:30:00"</span>
}</code></pre>
<h6 class="fw-bold mt-3">Response Fields</h6>
<table class="table table-sm">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>from_cache</code></td>
<td>boolean</td>
<td>Whether the config was served from cache or freshly generated</td>
</tr>
<tr>
<td><code>cache_type</code></td>
<td>string</td>
<td><code>redis</code> (from Redis cache) or <code>sqlite</code> (from SQLite/fresh scan)</td>
</tr>
<tr>
<td><code>generated_at</code></td>
<td>string</td>
<td>ISO 8601 timestamp when the config was generated</td>
</tr>
</tbody>
</table>
<h6 class="fw-bold mt-3">cURL Examples</h6>
<p class="mb-2"><strong>With cache (default):</strong></p>
<pre><code>curl -X POST <span id="curlUrl1"></span>/api/generate/preview \
-H "Content-Type: application/json" \
-d '{
"countries": ["CN", "RU"],
"app_type": "nginx",
"app_variant": "map",
"aggregate": true,
"use_cache": true
}' | jq .</code></pre>
<p class="mb-2 mt-3"><strong>Force fresh data (bypass cache):</strong></p>
<pre><code>curl -X POST <span id="curlUrl1b"></span>/api/generate/preview \
-H "Content-Type: application/json" \
-d '{
"countries": ["CN", "RU"],
"app_type": "nginx",
"app_variant": "map",
"aggregate": true,
"use_cache": false
}' | jq .</code></pre>
</div>
</div>
<!-- Endpoint 6: Generate Raw CIDR -->
<div class="card mb-3">
<div class="card-header api-header-post" onclick="toggleEndpoint('endpoint6')">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="badge bg-success me-2">POST</span>
<code class="api-path">/api/generate/raw</code>
<span class="ms-3 text-muted">Generate raw blocklist (TXT/CSV/JSON/JS)</span>
</div>
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="card-body collapse" id="endpoint6">
<h6 class="fw-bold">Description</h6>
<p>
Generates a raw blocklist without application-specific configuration.
Use this endpoint for programmatic integrations when you need structured output (JSON) or a JS-wrapped JSON payload.
</p>
<h6 class="fw-bold mt-3">Request Body</h6>
<pre><code>{
"countries": [<span class="text-warning">"CN"</span>, <span class="text-warning">"RU"</span>],
"app_type": <span class="text-warning">"raw-json"</span>,
"aggregate": <span class="text-success">true</span>,
"use_cache": <span class="text-success">true</span>,
"as_js": <span class="text-success">false</span>,
"js_var": <span class="text-warning">"geoipBlocklist"</span>
}</code></pre>
<h6 class="fw-bold mt-3">Parameters</h6>
<table class="table table-sm">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Required</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>countries</code></td>
<td>array</td>
<td><span class="badge bg-danger">required</span></td>
<td>List of ISO 3166-1 alpha-2 country codes</td>
</tr>
<tr>
<td><code>app_type</code></td>
<td>string</td>
<td><span class="badge bg-secondary">optional</span></td>
<td>
Output format selector. Examples:
<code>raw-json</code> (JSON), <code>raw-json</code> + <code>as_js=true</code> (JS wrapper).
</td>
</tr>
<tr>
<td><code>aggregate</code></td>
<td>boolean</td>
<td><span class="badge bg-secondary">optional</span></td>
<td>Aggregate IP networks (default: true)</td>
</tr>
<tr>
<td><code>use_cache</code></td>
<td>boolean</td>
<td><span class="badge bg-secondary">optional</span></td>
<td>Use Redis cache if available (default: true)</td>
</tr>
<tr>
<td>
Only for <code>raw-json</code>.
When <code>true</code>, wraps JSON into JavaScript and returns <code>application/javascript</code> as:
<code>const &lt;js_var&gt; = {...};</code>
</td>
</tr>
<tr>
<td><code>js_var</code></td>
<td>string</td>
<td><span class="badge bg-secondary">optional</span></td>
<td>
Variable name used by <code>as_js</code> wrapper (default: <code>geoipBlocklist</code>).
</td>
</tr>
</tbody>
</table>
<h6 class="fw-bold mt-3">Responses</h6>
<table class="table table-sm">
<thead>
<tr>
<th>Mode</th>
<th>Content-Type</th>
<th>Body</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>raw-cidr_txt</code></td>
<td><code>text/plain</code></td>
<td>One CIDR per line</td>
</tr>
<tr>
<td><code>raw-cidr_csv</code></td>
<td><code>text/csv</code></td>
<td>CSV export</td>
</tr>
<tr>
<td><code>raw-cidr_json</code></td>
<td><code>application/json</code></td>
<td>JSON object with <code>countries</code>, <code>networks</code>, <code>count</code>, <code>aggregated</code></td>
</tr>
<tr>
<td><code>raw-cidr_json</code> + <code>as_js=true</code></td>
<td><code>application/javascript</code></td>
<td><code>const &lt;js_var&gt; = {...};</code></td>
</tr>
</tbody>
</table>
<h6 class="fw-bold mt-3">Responses</h6>
<table class="table table-sm">
<thead>
<tr>
<th>Mode</th>
<th>Content-Type</th>
<th>Body</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>raw-cidr_txt</code></td>
<td><code>text/plain</code></td>
<td>One CIDR per line</td>
</tr>
<tr>
<td><code>raw-cidr_csv</code></td>
<td><code>text/csv</code></td>
<td>CSV export</td>
</tr>
<tr>
<td><code>raw-json</code></td>
<td><code>application/json</code></td>
<td>JSON object with <code>generated_at</code>, <code>countries</code>, <code>networks</code>, <code>total_networks</code></td>
</tr>
<tr>
<td><code>raw-json</code> + <code>as_js=true</code></td>
<td><code>application/javascript</code></td>
<td><code>const &lt;js_var&gt; = {...};</code></td>
</tr>
</tbody>
</table>
<h6 class="fw-bold mt-3">Response Headers</h6>
<table class="table table-sm">
<thead>
<tr>
<th>Header</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>X-From-Cache</code></td>
<td><code>true</code> or <code>false</code> - indicates if served from Redis</td>
</tr>
<tr>
<td><code>X-Cache-Type</code></td>
<td><code>redis-full</code> or computed cache type (e.g. <code>hybrid</code>)</td>
</tr>
<tr>
<td><code>X-Generated-At</code></td>
<td>Timestamp when config was generated</td>
</tr>
</tbody>
</table>
<h6 class="fw-bold mt-3">cURL Examples</h6>
<p class="mb-2"><strong>TXT (default):</strong></p>
<pre><code>curl -X POST <span id="curlUrl2"></span>/api/generate/raw \
-H "Content-Type: application/json" \
-d '{
"countries": ["CN", "RU"],
"app_type": "raw-cidr_txt",
"aggregate": true,
"use_cache": true
}' \
-o blocklist.txt</code></pre>
<p class="mb-2 mt-3"><strong>CSV:</strong></p>
<pre><code>curl -X POST <span id="curlUrl2csv"></span>/api/generate/raw \
-H "Content-Type: application/json" \
-d '{
"countries": ["CN", "RU"],
"app_type": "raw-cidr_csv",
"aggregate": true,
"use_cache": true
}' \
-o blocklist.csv</code></pre>
<p class="mb-2 mt-3"><strong>JSON:</strong></p>
<pre><code>curl -X POST <span id="curlUrl2b"></span>/api/generate/raw \
-H "Content-Type: application/json" \
-d '{
"countries": ["CN", "RU"],
"app_type": "raw-json",
"aggregate": true,
"use_cache": true
}' \
-o blocklist.json</code></pre>
<p class="mb-2 mt-3"><strong>JS-wrapped JSON:</strong></p>
<pre><code>curl -X POST <span id="curlUrl2c"></span>/api/generate/raw \
-H "Content-Type: application/json" \
-d '{
"countries": ["CN", "RU"],
"app_type": "raw-json",
"aggregate": true,
"use_cache": true,
"as_js": true,
"js_var": "geoipBlocklist"
}' \
-o blocklist.js</code></pre>
</div>
</div>
<!-- Endpoint 7: Generate Configuration -->
<div class="card mb-3">
<div class="card-header api-header-post" onclick="toggleEndpoint('endpoint7')">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="badge bg-success me-2">POST</span>
<code class="api-path">/api/generate</code>
<span class="ms-3 text-muted">Generate application configuration</span>
</div>
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="card-body collapse" id="endpoint7">
<h6 class="fw-bold">Description</h6>
<p>Generates application-specific geo-blocking configuration for Nginx, Apache, or HAProxy and returns it as a downloadable file.</p>
<h6 class="fw-bold mt-3">Request Body</h6>
<pre><code>{
"countries": [<span class="text-warning">"CN"</span>, <span class="text-warning">"RU"</span>],
"app_type": <span class="text-warning">"nginx"</span>,
"app_variant": <span class="text-warning">"map"</span>,
"aggregate": <span class="text-success">true</span>,
"use_cache": <span class="text-success">true</span>
}</code></pre>
<h6 class="fw-bold mt-3">Parameters</h6>
<table class="table table-sm">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Required</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>countries</code></td>
<td>array</td>
<td><span class="badge bg-danger">required</span></td>
<td>List of ISO 3166-1 alpha-2 country codes</td>
</tr>
<tr>
<td><code>app_type</code></td>
<td>string</td>
<td><span class="badge bg-danger">required</span></td>
<td>One of: <code>nginx</code>, <code>apache</code>, <code>haproxy</code></td>
</tr>
<tr>
<td><code>app_variant</code></td>
<td>string</td>
<td><span class="badge bg-danger">required</span></td>
<td>Configuration style (depends on app_type)</td>
</tr>
<tr>
<td><code>aggregate</code></td>
<td>boolean</td>
<td><span class="badge bg-secondary">optional</span></td>
<td>Aggregate IP networks (default: true)</td>
</tr>
<tr>
<td><code>use_cache</code></td>
<td>boolean</td>
<td><span class="badge bg-secondary">optional</span></td>
<td>Use Redis cache if available (default: true)</td>
</tr>
</tbody>
</table>
<h6 class="fw-bold mt-3">Available Variants</h6>
<ul>
<li><strong>nginx:</strong> <code>geo</code>, <code>map</code>, <code>deny</code></li>
<li><strong>apache:</strong> <code>22</code> (Apache 2.2), <code>24</code> (Apache 2.4)</li>
<li><strong>haproxy:</strong> <code>acl</code>, <code>lua</code>, <code>map</code></li>
</ul>
<h6 class="fw-bold mt-3">Response</h6>
<p>Returns configuration file as <code>text/plain</code> with Content-Disposition header for download.</p>
<h6 class="fw-bold mt-3">Response Headers</h6>
<table class="table table-sm">
<thead>
<tr>
<th>Header</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>X-From-Cache</code></td>
<td><code>true</code> or <code>false</code></td>
</tr>
<tr>
<td><code>X-Cache-Type</code></td>
<td><code>redis</code> or <code>sqlite</code></td>
</tr>
<tr>
<td><code>X-Generated-At</code></td>
<td>ISO 8601 timestamp</td>
</tr>
</tbody>
</table>
<h6 class="fw-bold mt-3">Cache Behavior</h6>
<div class="alert alert-info">
<strong>With Redis enabled:</strong>
<ul class="mb-0">
<li><code>use_cache: true</code> - Check Redis first, return cached config if available (fast, <1s)</li>
<li><code>use_cache: false</code> - Bypass Redis, fetch from SQLite cache or scan MaxMind (slower, 5-30s)</li>
</ul>
</div>
<h6 class="fw-bold mt-3">cURL Examples</h6>
<p class="mb-2"><strong>With cache (recommended for production):</strong></p>
<pre><code>curl -X POST <span id="curlUrl3"></span>/api/generate \
-H "Content-Type: application/json" \
-d '{
"countries": ["CN", "RU"],
"app_type": "nginx",
"app_variant": "map",
"aggregate": true,
"use_cache": true
}' \
-o geoblock.conf
# Check if it was cached:
curl -I -X POST <span id="curlUrl3b"></span>/api/generate \
-H "Content-Type: application/json" \
-d '{"countries":["CN"],"app_type":"nginx","app_variant":"map"}' \
| grep "X-From-Cache"</code></pre>
<p class="mb-2 mt-3"><strong>Force fresh scan (for testing or updates):</strong></p>
<pre><code>curl -X POST <span id="curlUrl3c"></span>/api/generate \
-H "Content-Type: application/json" \
-d '{
"countries": ["CN", "RU"],
"app_type": "nginx",
"app_variant": "map",
"aggregate": true,
"use_cache": false
}' \
-o geoblock_fresh.conf</code></pre>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/api.js') }}?v={{ js_hash }}"></script>
{% endblock %}