Files
wanderer/lib/wanderer_app_web/open_api_v1_spec.ex

539 lines
17 KiB
Elixir

defmodule WandererAppWeb.OpenApiV1Spec do
@moduledoc """
OpenAPI spec specifically for v1 JSON:API endpoints generated by AshJsonApi.
"""
@behaviour OpenApiSpex.OpenApi
alias OpenApiSpex.{OpenApi, Info, Server, Components}
@impl OpenApiSpex.OpenApi
def spec do
# This is called by the modify_open_api option in the router
# We should return the spec from WandererAppWeb.OpenApi module
WandererAppWeb.OpenApi.spec()
end
defp generate_spec_manually do
%OpenApi{
info: %Info{
title: "WandererApp v1 JSON:API",
version: "1.0.0",
description: """
JSON:API compliant endpoints for WandererApp.
## Features
- Filtering: Use `filter[attribute]=value` parameters
- Sorting: Use `sort=attribute` or `sort=-attribute` for descending
- Pagination: Use `page[limit]=n` and `page[offset]=n`
- Relationships: Include related resources with `include=relationship`
## Authentication
All endpoints require Bearer token authentication:
```
Authorization: Bearer YOUR_API_KEY
```
"""
},
servers: [
Server.from_endpoint(WandererAppWeb.Endpoint)
],
paths: get_v1_paths(),
components: %Components{
schemas: get_v1_schemas(),
securitySchemes: %{
"bearerAuth" => %{
"type" => "http",
"scheme" => "bearer",
"description" => "Map API key for authentication"
}
}
},
security: [%{"bearerAuth" => []}],
tags: get_v1_tags()
}
end
defp get_v1_tags do
[
%{"name" => "Access Lists", "description" => "Access control list management"},
%{"name" => "Access List Members", "description" => "ACL member management"},
%{"name" => "Characters", "description" => "Character management"},
%{"name" => "Maps", "description" => "Map management"},
%{"name" => "Map Systems", "description" => "Map system operations"},
%{"name" => "Map Connections", "description" => "System connection management"},
%{"name" => "Map Solar Systems", "description" => "Solar system data"},
%{"name" => "Map System Signatures", "description" => "Wormhole signature tracking"},
%{"name" => "Map System Structures", "description" => "Structure management"},
%{"name" => "Map System Comments", "description" => "System comments"},
%{"name" => "Map Character Settings", "description" => "Character map settings"},
%{"name" => "Map User Settings", "description" => "User map preferences"},
%{"name" => "Map Subscriptions", "description" => "Map subscription management"},
%{"name" => "Map Access Lists", "description" => "Map-specific ACLs"},
%{"name" => "Map States", "description" => "Map state information"},
%{"name" => "Users", "description" => "User management"},
%{"name" => "User Activities", "description" => "User activity tracking"},
%{"name" => "Ship Type Info", "description" => "Ship type information"}
]
end
defp get_v1_paths do
# Generate paths for all resources
resources = [
{"access_lists", "Access Lists"},
{"access_list_members", "Access List Members"},
{"characters", "Characters"},
{"maps", "Maps"},
{"map_systems", "Map Systems"},
{"map_connections", "Map Connections"},
{"map_solar_systems", "Map Solar Systems"},
{"map_system_signatures", "Map System Signatures"},
{"map_system_structures", "Map System Structures"},
{"map_system_comments", "Map System Comments"},
{"map_character_settings", "Map Character Settings"},
{"map_user_settings", "Map User Settings"},
{"map_subscriptions", "Map Subscriptions"},
{"map_access_lists", "Map Access Lists"},
{"map_states", "Map States"},
{"users", "Users"},
{"user_activities", "User Activities"},
{"ship_type_infos", "Ship Type Info"}
]
Enum.reduce(resources, %{}, fn {resource, tag}, acc ->
base_path = "/api/v1/#{resource}"
paths = %{
base_path => %{
"get" => %{
"summary" => "List #{resource}",
"tags" => [tag],
"parameters" => get_standard_list_parameters(resource),
"responses" => %{
"200" => %{
"description" => "List of #{resource}",
"content" => %{
"application/vnd.api+json" => %{
"schema" => %{
"$ref" => "#/components/schemas/#{String.capitalize(resource)}ListResponse"
}
}
}
}
}
},
"post" => %{
"summary" => "Create #{String.replace(resource, "_", " ")}",
"tags" => [tag],
"requestBody" => %{
"required" => true,
"content" => %{
"application/vnd.api+json" => %{
"schema" => %{
"$ref" => "#/components/schemas/#{String.capitalize(resource)}CreateRequest"
}
}
}
},
"responses" => %{
"201" => %{"description" => "Created"}
}
}
},
"#{base_path}/{id}" => %{
"get" => %{
"summary" => "Get #{String.replace(resource, "_", " ")}",
"tags" => [tag],
"parameters" => [
%{
"name" => "id",
"in" => "path",
"required" => true,
"schema" => %{"type" => "string"}
}
],
"responses" => %{
"200" => %{"description" => "Resource details"}
}
},
"patch" => %{
"summary" => "Update #{String.replace(resource, "_", " ")}",
"tags" => [tag],
"parameters" => [
%{
"name" => "id",
"in" => "path",
"required" => true,
"schema" => %{"type" => "string"}
}
],
"requestBody" => %{
"required" => true,
"content" => %{
"application/vnd.api+json" => %{
"schema" => %{
"$ref" => "#/components/schemas/#{String.capitalize(resource)}UpdateRequest"
}
}
}
},
"responses" => %{
"200" => %{"description" => "Updated"}
}
},
"delete" => %{
"summary" => "Delete #{String.replace(resource, "_", " ")}",
"tags" => [tag],
"parameters" => [
%{
"name" => "id",
"in" => "path",
"required" => true,
"schema" => %{"type" => "string"}
}
],
"responses" => %{
"204" => %{"description" => "Deleted"}
}
}
}
}
Map.merge(acc, paths)
end)
|> add_custom_paths()
end
defp add_custom_paths(paths) do
# Add custom action paths
custom_paths = %{
"/api/v1/maps/{id}/duplicate" => %{
"post" => %{
"summary" => "Duplicate map",
"tags" => ["Maps"],
"parameters" => [
%{
"name" => "id",
"in" => "path",
"required" => true,
"schema" => %{"type" => "string"}
}
],
"responses" => %{
"201" => %{"description" => "Map duplicated"}
}
}
},
"/api/v1/maps/{map_id}/systems_and_connections" => %{
"get" => %{
"summary" => "Get Map Systems and Connections",
"description" => "Retrieve both systems and connections for a map in a single response",
"tags" => ["Maps"],
"parameters" => [
%{
"name" => "map_id",
"in" => "path",
"required" => true,
"schema" => %{"type" => "string"},
"description" => "Map ID"
}
],
"responses" => %{
"200" => %{
"description" => "Combined systems and connections data",
"content" => %{
"application/json" => %{
"schema" => %{
"type" => "object",
"properties" => %{
"systems" => %{
"type" => "array",
"items" => %{
"type" => "object",
"properties" => %{
"id" => %{"type" => "string"},
"solar_system_id" => %{"type" => "integer"},
"name" => %{"type" => "string"},
"status" => %{"type" => "string"},
"visible" => %{"type" => "boolean"},
"locked" => %{"type" => "boolean"},
"position_x" => %{"type" => "integer"},
"position_y" => %{"type" => "integer"}
}
}
},
"connections" => %{
"type" => "array",
"items" => %{
"type" => "object",
"properties" => %{
"id" => %{"type" => "string"},
"solar_system_source" => %{"type" => "integer"},
"solar_system_target" => %{"type" => "integer"},
"type" => %{"type" => "string"},
"time_status" => %{"type" => "string"},
"mass_status" => %{"type" => "string"}
}
}
}
}
}
}
}
},
"404" => %{"description" => "Map not found"},
"401" => %{"description" => "Unauthorized"}
}
}
}
}
Map.merge(paths, custom_paths)
end
defp get_standard_list_parameters(resource) do
base_params = [
%{
"name" => "sort",
"in" => "query",
"description" => "Sort results (e.g., 'name', '-created_at')",
"schema" => %{"type" => "string"}
},
%{
"name" => "page[limit]",
"in" => "query",
"description" => "Number of results per page",
"schema" => %{"type" => "integer", "default" => 50}
},
%{
"name" => "page[offset]",
"in" => "query",
"description" => "Offset for pagination",
"schema" => %{"type" => "integer", "default" => 0}
},
%{
"name" => "include",
"in" => "query",
"description" => "Include related resources (comma-separated)",
"schema" => %{"type" => "string"}
}
]
# Add resource-specific filter parameters
filter_params =
case resource do
"characters" ->
[
%{
"name" => "filter[name]",
"in" => "query",
"description" => "Filter by character name",
"schema" => %{"type" => "string"}
},
%{
"name" => "filter[user_id]",
"in" => "query",
"description" => "Filter by user ID",
"schema" => %{"type" => "string"}
}
]
"maps" ->
[
%{
"name" => "filter[scope]",
"in" => "query",
"description" => "Filter by map scope",
"schema" => %{"type" => "string"}
},
%{
"name" => "filter[archived]",
"in" => "query",
"description" => "Filter by archived status",
"schema" => %{"type" => "boolean"}
}
]
"map_systems" ->
[
%{
"name" => "filter[map_id]",
"in" => "query",
"description" => "Filter by map ID",
"schema" => %{"type" => "string"}
},
%{
"name" => "filter[solar_system_id]",
"in" => "query",
"description" => "Filter by solar system ID",
"schema" => %{"type" => "integer"}
}
]
"map_connections" ->
[
%{
"name" => "filter[map_id]",
"in" => "query",
"description" => "Filter by map ID",
"schema" => %{"type" => "string"}
},
%{
"name" => "filter[source_id]",
"in" => "query",
"description" => "Filter by source system ID",
"schema" => %{"type" => "string"}
},
%{
"name" => "filter[target_id]",
"in" => "query",
"description" => "Filter by target system ID",
"schema" => %{"type" => "string"}
}
]
"map_system_signatures" ->
[
%{
"name" => "filter[system_id]",
"in" => "query",
"description" => "Filter by system ID",
"schema" => %{"type" => "string"}
},
%{
"name" => "filter[type]",
"in" => "query",
"description" => "Filter by signature type",
"schema" => %{"type" => "string"}
}
]
_ ->
[]
end
base_params ++ filter_params
end
defp get_v1_schemas do
%{
# Generic JSON:API response wrapper
"JsonApiWrapper" => %{
"type" => "object",
"properties" => %{
"data" => %{
"type" => "object",
"description" => "Primary data"
},
"included" => %{
"type" => "array",
"description" => "Included related resources"
},
"meta" => %{
"type" => "object",
"description" => "Metadata about the response"
},
"links" => %{
"type" => "object",
"description" => "Links for pagination and relationships"
}
}
},
# Character schemas
"CharacterResource" => %{
"type" => "object",
"properties" => %{
"type" => %{"type" => "string", "enum" => ["characters"]},
"id" => %{"type" => "string"},
"attributes" => %{
"type" => "object",
"properties" => %{
"name" => %{"type" => "string"},
"eve_id" => %{"type" => "integer"},
"corporation_id" => %{"type" => "integer"},
"alliance_id" => %{"type" => "integer"},
"online" => %{"type" => "boolean"},
"location" => %{"type" => "object"},
"inserted_at" => %{"type" => "string", "format" => "date-time"},
"updated_at" => %{"type" => "string", "format" => "date-time"}
}
},
"relationships" => %{
"type" => "object",
"properties" => %{
"user" => %{
"type" => "object",
"properties" => %{
"data" => %{
"type" => "object",
"properties" => %{
"type" => %{"type" => "string"},
"id" => %{"type" => "string"}
}
}
}
}
}
}
}
},
"CharactersListResponse" => %{
"type" => "object",
"properties" => %{
"data" => %{
"type" => "array",
"items" => %{"$ref" => "#/components/schemas/CharacterResource"}
},
"meta" => %{
"type" => "object",
"properties" => %{
"page" => %{
"type" => "object",
"properties" => %{
"offset" => %{"type" => "integer"},
"limit" => %{"type" => "integer"},
"total" => %{"type" => "integer"}
}
}
}
}
}
},
# Map schemas
"MapResource" => %{
"type" => "object",
"properties" => %{
"type" => %{"type" => "string", "enum" => ["maps"]},
"id" => %{"type" => "string"},
"attributes" => %{
"type" => "object",
"properties" => %{
"name" => %{"type" => "string"},
"slug" => %{"type" => "string"},
"scope" => %{"type" => "string"},
"public_key" => %{"type" => "string"},
"archived" => %{"type" => "boolean"},
"inserted_at" => %{"type" => "string", "format" => "date-time"},
"updated_at" => %{"type" => "string", "format" => "date-time"}
}
},
"relationships" => %{
"type" => "object",
"properties" => %{
"owner" => %{
"type" => "object"
},
"characters" => %{
"type" => "object"
},
"acls" => %{
"type" => "object"
}
}
}
}
}
}
end
end