feat(api): document the client geolocation route in OpenAPI (#1617)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-15 13:13:22 +02:00
1 parent fca5f63b9e
commit 4e9b8fa1a6
3 files changed
+159 -11

No files matched your search

@@ -1,24 +1,40 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {GeolocationResponse} from '@fluxer/schema/src/domains/geolocation/GeolocationSchemas';
import {resolveClientGeoip} from '@pkgs/geoip/src/ResolveClientGeoip';
import type {Hono} from 'hono';
import {Config} from '../Config';
import {RateLimitMiddleware} from '../middleware/RateLimitMiddleware';
import {OpenAPI} from '../middleware/ResponseTypeMiddleware';
import {RateLimitConfigs} from '../RateLimitConfig';
import type {HonoEnv} from '../types/HonoEnv';
export function GeolocationController(app: Hono<HonoEnv>): void {
app.get('/ip', RateLimitMiddleware(RateLimitConfigs.IP_GEO_LOOKUP), async (ctx) => {
ctx.header('Access-Control-Allow-Origin', '*');
ctx.header('Access-Control-Allow-Headers', 'Content-Type');
ctx.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
const response = await resolveClientGeoip(ctx.req.raw, {
maxmindDbPath: Config.geoip.maxmindDbPath,
trustClientIpHeader: Config.proxy.trust_client_ip_header,
clientIpHeaderName: Config.proxy.client_ip_header,
});
return ctx.json(response);
});
app.get(
'/ip',
RateLimitMiddleware(RateLimitConfigs.IP_GEO_LOOKUP),
OpenAPI({
operationId: 'get_client_geolocation',
summary: 'Get client geolocation',
responseSchema: GeolocationResponse,
statusCode: 200,
security: [],
tags: ['Geolocation'],
description:
'Resolves the approximate location of the requesting client from its IP address, together with the locations where age restricted content is gated or unavailable.',
}),
async (ctx) => {
ctx.header('Access-Control-Allow-Origin', '*');
ctx.header('Access-Control-Allow-Headers', 'Content-Type');
ctx.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
const response = await resolveClientGeoip(ctx.req.raw, {
maxmindDbPath: Config.geoip.maxmindDbPath,
trustClientIpHeader: Config.proxy.trust_client_ip_header,
clientIpHeaderName: Config.proxy.client_ip_header,
});
return ctx.json(response);
},
);
app.options('/ip', (ctx) => {
ctx.header('Access-Control-Allow-Origin', '*');
ctx.header('Access-Control-Allow-Headers', 'Content-Type');
+102
View File
@@ -10800,6 +10800,64 @@
]
}
},
"/ip": {
"get": {
"operationId": "get_client_geolocation",
"summary": "Get client geolocation",
"tags": ["Geolocation"],
"responses": {
"200": {
"description": "Success",
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/GeolocationResponse"}}}
},
"400": {
"description": "Bad Request - The request was malformed or contained invalid data",
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
},
"429": {
"description": "Too Many Requests - You are being rate limited",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"code": {"type": "string", "enum": ["RATE_LIMITED"]},
"message": {"type": "string"},
"retry_after": {"type": "number", "description": "Seconds to wait before retrying"},
"global": {"type": "boolean", "description": "Whether this is a global rate limit"}
},
"required": ["code", "message", "retry_after"]
}
}
},
"headers": {
"Retry-After": {
"description": "Number of seconds to wait before retrying (only on 429)",
"schema": {"type": "integer"}
},
"X-RateLimit-Limit": {
"description": "The number of requests that can be made in the current window",
"schema": {"type": "integer"}
},
"X-RateLimit-Remaining": {
"description": "The number of remaining requests that can be made",
"schema": {"type": "integer"}
},
"X-RateLimit-Reset": {
"description": "Unix timestamp when the rate limit resets",
"schema": {"type": "integer"}
}
}
},
"500": {
"description": "Internal Server Error - An unexpected error occurred",
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
}
},
"x-mint": {"metadata": {"title": "Get client geolocation"}},
"description": "Resolves the approximate location of the requesting client from its IP address, together with the locations where age restricted content is gated or unavailable."
}
},
"/oauth2/@me": {
"get": {
"operationId": "get_current_user_oauth2",
@@ -31926,6 +31984,49 @@
},
"required": ["code", "type", "pack", "temporary"]
},
"GeolocationResponse": {
"type": "object",
"properties": {
"countryCode": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "ISO 3166-1 alpha-2 country code resolved for the client, or null when it cannot be resolved"
},
"regionCode": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "ISO 3166-2 subdivision code resolved for the client, or null when it cannot be resolved"
},
"latitude": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Approximate latitude of the client, or null when it cannot be resolved"
},
"longitude": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Approximate longitude of the client, or null when it cannot be resolved"
},
"ageRestrictedGeos": {
"type": "array",
"items": {"$ref": "#/components/schemas/GeoEntry"},
"description": "Locations where age restricted content requires an age check"
},
"ageBlockedGeos": {
"type": "array",
"items": {"$ref": "#/components/schemas/GeoEntry"},
"description": "Locations where age restricted content is unavailable"
}
},
"required": ["countryCode", "regionCode", "latitude", "longitude", "ageRestrictedGeos", "ageBlockedGeos"]
},
"GeoEntry": {
"type": "object",
"properties": {
"countryCode": {"type": "string", "description": "ISO 3166-1 alpha-2 country code"},
"regionCode": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "ISO 3166-2 subdivision code, or null when the entry covers the whole country"
}
},
"required": ["countryCode", "regionCode"]
},
"OAuth2MeResponse": {
"type": "object",
"properties": {
@@ -38163,6 +38264,7 @@
{"name": "Connections"},
{"name": "Messages"},
{"name": "Donations"},
{"name": "Geolocation"},
{"name": "Discovery"},
{"name": "Emojis"},
{"name": "Stickers"},