Add developer API, Prometheus metrics, and community links
This commit is contained in:
208
shared/developer-openapi.ts
Normal file
208
shared/developer-openapi.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
const string = { type: "string" };
|
||||
const date = { type: "string", format: "date-time" };
|
||||
const id = { type: "string", format: "uuid" };
|
||||
const ref = (name: string) => ({ $ref: `#/components/schemas/${name}` });
|
||||
const pagination = [
|
||||
{
|
||||
name: "limit",
|
||||
in: "query",
|
||||
schema: { type: "integer", minimum: 1, maximum: 50, default: 20 },
|
||||
},
|
||||
{
|
||||
name: "offset",
|
||||
in: "query",
|
||||
schema: { type: "integer", minimum: 0, maximum: 10000, default: 0 },
|
||||
},
|
||||
];
|
||||
const success = (schema: unknown) => ({
|
||||
description: "Success",
|
||||
content: { "application/json": { schema } },
|
||||
});
|
||||
const errors = Object.fromEntries(
|
||||
[
|
||||
[400, "Invalid query or map ID"],
|
||||
[401, "Missing, invalid, expired or revoked key"],
|
||||
[403, "Required permission missing"],
|
||||
[404, "Resource or endpoint not found"],
|
||||
[429, "Quota exceeded; wait Retry-After seconds"],
|
||||
[503, "Temporarily unavailable"],
|
||||
].map(([status, description]) => [
|
||||
status,
|
||||
{ description, content: { "application/json": { schema: ref("Error") } } },
|
||||
]),
|
||||
);
|
||||
const list = (name: string, item: string) => ({
|
||||
type: "object",
|
||||
required: [name, "pagination"],
|
||||
properties: {
|
||||
[name]: { type: "array", items: ref(item) },
|
||||
pagination: ref("Pagination"),
|
||||
},
|
||||
});
|
||||
const operation = (
|
||||
summary: string,
|
||||
scope: string,
|
||||
schema: unknown,
|
||||
parameters: unknown[] = [],
|
||||
) => ({
|
||||
summary,
|
||||
description: `Requires the ${scope} key permission.`,
|
||||
"x-required-scope": scope,
|
||||
security: [{ DeveloperKey: [] }],
|
||||
parameters,
|
||||
responses: { 200: success(schema), ...errors },
|
||||
});
|
||||
export const developerOpenApi = {
|
||||
openapi: "3.1.0",
|
||||
info: {
|
||||
title: "MineGeo Developer API",
|
||||
version: "1.0.0",
|
||||
description:
|
||||
"Read-only HTTPS API. Bearer keys are managed at /dev/api. 60 requests per key per fixed minute; 180 requests per account per minute shared across keys. All authenticated requests, including validation/permission errors, consume quota. A 429 response includes Retry-After; successful authorized responses include RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until reset). X-RateLimit-Reset is a Unix timestamp. Requests with invalid keys cannot access account data. No upload, publication, gameplay writes or moderator access is granted.",
|
||||
},
|
||||
servers: [{ url: "/api/dev/v1" }],
|
||||
paths: {
|
||||
"/maps": {
|
||||
get: operation("List published maps", "maps:read", list("maps", "Map"), [
|
||||
...pagination,
|
||||
{
|
||||
name: "q",
|
||||
in: "query",
|
||||
schema: { type: "string", maxLength: 80 },
|
||||
description: "Case-insensitive map-name search",
|
||||
},
|
||||
]),
|
||||
},
|
||||
"/maps/{id}": {
|
||||
get: operation(
|
||||
"Get a published map",
|
||||
"maps:read",
|
||||
{
|
||||
type: "object",
|
||||
required: ["map"],
|
||||
properties: { map: ref("Map") },
|
||||
},
|
||||
[{ name: "id", in: "path", required: true, schema: id }],
|
||||
),
|
||||
},
|
||||
"/me": {
|
||||
get: operation("Get the key owner's profile", "account:read", {
|
||||
type: "object",
|
||||
required: ["account"],
|
||||
properties: { account: ref("Account") },
|
||||
}),
|
||||
},
|
||||
"/me/maps": {
|
||||
get: operation(
|
||||
"List the owner's maps, including private maps",
|
||||
"account:maps:read",
|
||||
list("maps", "Map"),
|
||||
[
|
||||
...pagination,
|
||||
{ name: "q", in: "query", schema: { type: "string", maxLength: 80 } },
|
||||
],
|
||||
),
|
||||
},
|
||||
"/me/games": {
|
||||
get: operation(
|
||||
"List the owner's solo-game history",
|
||||
"account:games:read",
|
||||
list("games", "Game"),
|
||||
pagination,
|
||||
),
|
||||
},
|
||||
},
|
||||
components: {
|
||||
securitySchemes: {
|
||||
DeveloperKey: {
|
||||
type: "http",
|
||||
scheme: "bearer",
|
||||
bearerFormat: "mg_dev_<64 hex characters>",
|
||||
},
|
||||
},
|
||||
schemas: {
|
||||
Error: {
|
||||
type: "object",
|
||||
required: ["error"],
|
||||
properties: {
|
||||
error: {
|
||||
type: "object",
|
||||
required: ["code", "message"],
|
||||
properties: { code: string, message: string },
|
||||
},
|
||||
},
|
||||
},
|
||||
Pagination: {
|
||||
type: "object",
|
||||
properties: {
|
||||
limit: { type: "integer" },
|
||||
offset: { type: "integer" },
|
||||
total: { type: "integer" },
|
||||
nextOffset: { type: ["integer", "null"] },
|
||||
},
|
||||
},
|
||||
Map: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id,
|
||||
name: string,
|
||||
description: string,
|
||||
source: string,
|
||||
state: string,
|
||||
bounds: {
|
||||
type: "object",
|
||||
properties: {
|
||||
minX: { type: "number" },
|
||||
minZ: { type: "number" },
|
||||
maxX: { type: "number" },
|
||||
maxZ: { type: "number" },
|
||||
},
|
||||
},
|
||||
panoramaCount: { type: "integer" },
|
||||
createdAt: date,
|
||||
updatedAt: date,
|
||||
url: {
|
||||
type: "string",
|
||||
description:
|
||||
"Relative MineGeo map page path; private maps still require their owner's browser login.",
|
||||
},
|
||||
},
|
||||
},
|
||||
Account: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id,
|
||||
displayName: string,
|
||||
createdAt: date,
|
||||
appearance: {
|
||||
type: "object",
|
||||
properties: {
|
||||
nameGlow: { type: "boolean" },
|
||||
glowColor: string,
|
||||
bugFinder: { type: "boolean" },
|
||||
customPrefix: { type: ["string", "null"] },
|
||||
customPrefixIcon: string,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Game: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id,
|
||||
difficulty: { type: "string", enum: ["easy", "normal", "hard"] },
|
||||
createdAt: date,
|
||||
currentRound: { type: "integer", minimum: 0, maximum: 5 },
|
||||
complete: { type: "boolean" },
|
||||
score: {
|
||||
type: ["integer", "null"],
|
||||
minimum: 0,
|
||||
maximum: 25000,
|
||||
description:
|
||||
"Final score only; null until a complete five-round result is available.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user