Files
MineGeo/components/DeveloperDocumentation.tsx
pavel444-byte 1f254b29a2
All checks were successful
Deploy MineGeo to Dokploy / deploy (push) Successful in 5m57s
Update MineGeo domain and harden process supervision
2026-10-15 20:35:39 +05:00

220 lines
13 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Link from "next/link";
import { BookOpen, ShieldCheck } from "lucide-react";
import {
developerEndpoints,
developerPythonExample,
} from "@/shared/developer-docs";
import styles from "@/app/dev/api/page.module.css";
export function DeveloperDocumentation({ locale }: { locale: string }) {
const ru = locale === "ru";
const l = (en: string, russian: string) => (ru ? russian : en);
return (
<section
id="documentation"
className={styles.docs}
aria-labelledby="documentation-title"
>
<div className={styles.heading}>
<BookOpen size={24} aria-hidden="true" />
<h1 id="documentation-title">
{l("API documentation", "Документация API")}
</h1>
</div>
<p>
{l(
"Build integrations with ordinary HTTPS requests. There is no MineGeo SDK; the Python examples use the requests library.",
"Создавайте интеграции обычными HTTPS-запросами. SDK MineGeo пока нет; примеры на Python используют библиотеку requests.",
)}
</p>
<p>
<strong>{l("Base URL", "Базовый адрес")}</strong>:{" "}
<code>https://minegeo.site/api/dev/v1</code>
</p>
<p>
<a href="/api/dev/openapi.json">
{l(
"Download the OpenAPI specification",
"Открыть спецификацию OpenAPI",
)}
</a>
</p>
<h3>{l("1. Create a key", "1. Создайте ключ")}</h3>
<p>
<Link href="/dev/api">
{l("Open the developer panel →", "Открыть панель разработчика →")}
</Link>
</p>
<p>
{l(
"Open the developer panel, log in, name your integration and select its permissions. Public maps are selected by default. Access to your profile, private maps and game history is separate and optional. You can have up to 5 active keys, with an expiry of 1–365 days (90 by default).",
"Откройте панель разработчика, войдите, назовите интеграцию и выберите права. По умолчанию выбраны только общие карты. Доступ к профилю, личным картам и истории игр выдаётся отдельно и необязательно. Можно создать до 5 активных ключей со сроком действия от 1 до 365 дней (по умолчанию — 90).",
)}
</p>
<p>
{l(
"Store the copied key in the MINEGEO_API_KEY environment variable on your computer or server. Keep it out of source code, repositories, browser JavaScript and URLs. If it leaks, revoke it and create a replacement.",
"Сохраните скопированный ключ в переменной окружения MINEGEO_API_KEY на компьютере или сервере. Не добавляйте его в код, репозитории, JavaScript браузера или URL. При утечке отзовите ключ и создайте новый.",
)}
</p>
<h3>
{l("2. Make a request with Python", "2. Сделайте запрос на Python")}
</h3>
<pre className={styles.code}>
<code>python -m pip install requests</code>
</pre>
<pre className={styles.code}>
<code>{developerPythonExample}</code>
</pre>
<p>
{l(
"Every v1 request needs Authorization: Bearer <your-key>. MineGeo developer keys start with mg_dev_. Contributor upload tokens, metrics tokens and your browser login cookie do not authenticate this API.",
"Каждый запрос v1 требует заголовок Authorization: Bearer <ваш-ключ>. Ключи API MineGeo начинаются с mg_dev_. Токены загрузки карт, токены метрик и cookie входа в браузере не подходят для этого API.",
)}
</p>
<h3>{l("Endpoints and permissions", "Методы и права доступа")}</h3>
<div className={styles.tableWrap}>
<table className={styles.table}>
<thead>
<tr>
<th>{l("GET endpoint", "Метод GET")}</th>
<th>{l("Required permission", "Нужное право")}</th>
<th>{l("Response", "Ответ")}</th>
</tr>
</thead>
<tbody>
{developerEndpoints.map((endpoint) => (
<tr key={endpoint.path}>
<td>
<code>{endpoint.path}</code>
</td>
<td>
<code>{endpoint.scope}</code>
</td>
<td>{endpoint.description[ru ? 1 : 0]}</td>
</tr>
))}
</tbody>
</table>
</div>
<p>
{l(
"Account endpoints always refer to the owner of the API key. They cannot read another player's private data. /me returns your account ID, display name, creation date and username appearance; it does not expose your email or account role.",
"Методы аккаунта всегда относятся к владельцу ключа API. Они не позволяют читать личные данные другого игрока. /me возвращает ID аккаунта, отображаемое имя, дату создания и оформление имени; email и роль аккаунта не передаются.",
)}
</p>
<p>
{l(
"Map details include metadata, map bounds and panorama count. Raw storage paths, panorama coordinates, moderation notes and game answers are excluded. Game history exposes a score only after a game is complete.",
"Сведения о карте включают метаданные, границы карты и число панорам. Внутренние пути к файлам, координаты панорам, заметки модераторов и ответы игры не передаются. История игр содержит итоговый счёт только после завершения игры.",
)}
</p>
<h3>{l("Pagination and search", "Страницы и поиск")}</h3>
<p>
{l(
"Lists accept limit (default 20, maximum 50) and offset (default 0, maximum 10000). /maps and /me/maps also accept q (up to 80 characters) to search map names. Unknown or repeated query parameters return HTTP 400. Use pagination.nextOffset for the next request; null means the end of the supported result window. Narrow q when you reach the offset limit.",
"Списки принимают limit (по умолчанию 20, максимум 50) и offset (по умолчанию 0, максимум 10000). /maps и /me/maps также принимают q длиной до 80 символов для поиска по названию. Неизвестные или повторяющиеся параметры возвращают HTTP 400. Используйте pagination.nextOffset для следующего запроса; null означает конец доступного диапазона. При достижении предела offset уточните q.",
)}
</p>
<pre className={styles.code}>
<code>{`{
"maps": [],
"pagination": {"limit": 20, "offset": 0, "total": 0, "nextOffset": null}
}`}</code>
</pre>
<p>
{l(
"GET /maps/{id} returns {map: {...}}. GET /me returns {account: {...}}. GET /me/maps uses the maps list envelope; GET /me/games uses {games: [...], pagination: {...}}. Map url values are relative to https://minegeo.site.",
"GET /maps/{id} возвращает {map: {...}}, GET /me — {account: {...}}. GET /me/maps использует список maps; GET /me/games — {games: [...], pagination: {...}}. Поле url карты содержит путь относительно https://minegeo.site.",
)}
</p>
<h3>{l("Rate limits", "Ограничения запросов")}</h3>
<p>
{l(
"Each key can make 60 requests per minute. All keys belonging to one account share a limit of 180 requests per minute. Creating more keys does not bypass the account limit. These limits apply to authenticated requests, including requests that fail permission or input checks.",
"Каждый ключ может выполнять 60 запросов в минуту. У всех ключей одного аккаунта общий лимит — 180 запросов в минуту. Создание новых ключей не отменяет общий лимит. Лимиты применяются к запросам с действительным ключом, включая ошибки прав доступа и проверки параметров.",
)}
</p>
<ul>
<li>
<code>RateLimit-Limit</code>:{" "}
{l("per-key request limit", "лимит запросов ключа")}
</li>
<li>
<code>RateLimit-Remaining</code>:{" "}
{l("requests remaining for this key", "оставшиеся запросы ключа")}
</li>
<li>
<code>RateLimit-Reset</code>:{" "}
{l(
"seconds until the current minute window resets",
"секунды до сброса текущего минутного окна",
)}
</li>
<li>
<code>X-RateLimit-Owner-Limit</code> /{" "}
<code>X-RateLimit-Owner-Remaining</code>:{" "}
{l("shared account quota", "общая квота аккаунта")}
</li>
</ul>
<p>
{l(
"If you receive HTTP 429, wait for the number of seconds in Retry-After before retrying. Avoid tight retry loops. Cache catalog responses in your integration when appropriate.",
"При ответе HTTP 429 подождите указанное в Retry-After число секунд перед повтором. Не повторяйте запросы в бесконечном быстром цикле. При необходимости кешируйте каталог в своей интеграции.",
)}
</p>
<h3>{l("Errors", "Ошибки")}</h3>
<pre className={styles.code}>
<code>{`{"error":{"code":"insufficient_scope","message":"This API key does not have the required permission."}}`}</code>
</pre>
<ul>
<li>
<code>400</code> —{" "}
{l(
"invalid input or pagination",
"некорректные параметры или пагинация",
)}
</li>
<li>
<code>401</code> —{" "}
{l(
"missing, invalid, revoked or expired key",
"ключ отсутствует, неверен, отозван или просрочен",
)}
</li>
<li>
<code>403</code> —{" "}
{l(
"key is missing a required permission",
"у ключа нет нужного права",
)}
</li>
<li>
<code>404</code> —{" "}
{l("map or endpoint unavailable", "карта или метод недоступны")}
</li>
<li>
<code>429</code> —{" "}
{l("request quota exhausted", "лимит запросов исчерпан")}
</li>
<li>
<code>500</code> / <code>503</code> —{" "}
{l(
"temporary service failure; retry with a delay",
"временная ошибка сервиса; повторите позже",
)}
</li>
</ul>
<div className={styles.notice}>
<ShieldCheck size={20} aria-hidden="true" />
<p>
{l(
"You can revoke a key at any time in the developer panel. Keys expire automatically and cannot be recovered. A new key can use different permissions; existing keys cannot gain additional permissions.",
"Ключ можно отозвать в панели разработчика в любой момент. Ключи истекают автоматически и не подлежат восстановлению. Новый ключ может иметь другой набор прав; существующему ключу нельзя добавить права.",
)}
</p>
</div>
</section>
);
}