mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-13 03:06:15 +00:00
Compare commits
17 Commits
v1.84.9
...
fix-error-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98c54a3413 | ||
|
|
0439110938 | ||
|
|
8ce1e5fa3e | ||
|
|
ebaf6bcdc6 | ||
|
|
40d947bebc | ||
|
|
61d1c3848f | ||
|
|
e152ce179f | ||
|
|
7bbe387183 | ||
|
|
b1555ff03c | ||
|
|
e624499244 | ||
|
|
6a1976dec6 | ||
|
|
3db24c4344 | ||
|
|
883c09f255 | ||
|
|
ff24d80038 | ||
|
|
63cbc9c0b9 | ||
|
|
8056972a27 | ||
|
|
1759d46740 |
@@ -1,5 +1,7 @@
|
||||
export WEB_APP_URL="http://localhost:8000"
|
||||
export RELEASE_COOKIE="PDpbnyo6mEI_0T4ZsHH_ESmi1vT1toQ8PTc0vbfg5FIT4Ih-Lh98mw=="
|
||||
# Erlang node name for distributed Erlang (optional - defaults to wanderer@hostname)
|
||||
# export RELEASE_NODE="wanderer@localhost"
|
||||
export EVE_CLIENT_ID="<EVE_CLIENT_ID>"
|
||||
export EVE_CLIENT_SECRET="<EVE_CLIENT_SECRET>"
|
||||
export EVE_CLIENT_WITH_WALLET_ID="<EVE_CLIENT_WITH_WALLET_ID>"
|
||||
|
||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -2,6 +2,34 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.84.13](https://github.com/wanderer-industries/wanderer/compare/v1.84.12...v1.84.13) (2025-11-13)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.84.12](https://github.com/wanderer-industries/wanderer/compare/v1.84.11...v1.84.12) (2025-11-13)
|
||||
|
||||
|
||||
|
||||
|
||||
## [v1.84.11](https://github.com/wanderer-industries/wanderer/compare/v1.84.10...v1.84.11) (2025-11-12)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* api and doc updates
|
||||
|
||||
## [v1.84.10](https://github.com/wanderer-industries/wanderer/compare/v1.84.9...v1.84.10) (2025-11-12)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* core: Fixed adding system on character dock
|
||||
|
||||
## [v1.84.9](https://github.com/wanderer-industries/wanderer/compare/v1.84.8...v1.84.9) (2025-11-12)
|
||||
|
||||
|
||||
|
||||
@@ -4,10 +4,13 @@ import { DEFAULT_WIDGETS } from '@/hooks/Mapper/components/mapInterface/constant
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export const MapInterface = () => {
|
||||
// const [items, setItems] = useState<WindowProps[]>(restoreWindowsFromLS);
|
||||
const { windowsSettings, updateWidgetSettings } = useMapRootState();
|
||||
|
||||
const items = useMemo(() => {
|
||||
if (Object.keys(windowsSettings).length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return windowsSettings.windows
|
||||
.map(x => {
|
||||
const content = DEFAULT_WIDGETS.find(y => y.id === x.id)?.content;
|
||||
|
||||
@@ -10,9 +10,14 @@ import { useCallback } from 'react';
|
||||
import { TooltipPosition, WdButton, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
|
||||
import { ConfirmPopup } from 'primereact/confirmpopup';
|
||||
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
|
||||
export const CommonSettings = () => {
|
||||
const { renderSettingItem } = useMapSettings();
|
||||
const {
|
||||
storedSettings: { resetSettings },
|
||||
} = useMapRootState();
|
||||
|
||||
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
|
||||
|
||||
const renderSettingsList = useCallback(
|
||||
@@ -22,7 +27,7 @@ export const CommonSettings = () => {
|
||||
[renderSettingItem],
|
||||
);
|
||||
|
||||
const handleResetSettings = () => {};
|
||||
const handleResetSettings = useCallback(() => resetSettings(), [resetSettings]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full gap-1">
|
||||
|
||||
@@ -6,9 +6,11 @@ import {
|
||||
MapUnionTypes,
|
||||
OutCommandHandler,
|
||||
SolarSystemConnection,
|
||||
StringBoolean,
|
||||
TrackingCharacter,
|
||||
UseCharactersCacheData,
|
||||
UseCommentsData,
|
||||
UserPermission,
|
||||
} from '@/hooks/Mapper/types';
|
||||
import { useCharactersCache, useComments, useMapRootHandlers } from '@/hooks/Mapper/mapRootProvider/hooks';
|
||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||
@@ -80,7 +82,16 @@ const INITIAL_DATA: MapRootData = {
|
||||
selectedSystems: [],
|
||||
selectedConnections: [],
|
||||
userPermissions: {},
|
||||
options: {},
|
||||
options: {
|
||||
allowed_copy_for: UserPermission.VIEW_SYSTEM,
|
||||
allowed_paste_for: UserPermission.VIEW_SYSTEM,
|
||||
layout: '',
|
||||
restrict_offline_showing: 'false',
|
||||
show_linked_signature_id: 'false',
|
||||
show_linked_signature_id_temp_name: 'false',
|
||||
show_temp_system_name: 'false',
|
||||
store_custom_labels: 'false',
|
||||
},
|
||||
isSubscriptionActive: false,
|
||||
linkSignatureToSystem: null,
|
||||
mainCharacterEveId: null,
|
||||
@@ -135,7 +146,7 @@ export interface MapRootContextProps {
|
||||
hasOldSettings: boolean;
|
||||
getSettingsForExport(): string | undefined;
|
||||
applySettings(settings: MapUserSettings): boolean;
|
||||
resetSettings(settings: MapUserSettings): void;
|
||||
resetSettings(): void;
|
||||
checkOldSettings(): void;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -148,10 +148,6 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
|
||||
setHasOldSettings(!!(widgetsOld || interfaceSettings || widgetRoutes || widgetLocal || widgetKills || onTheMapOld));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
checkOldSettings();
|
||||
}, [checkOldSettings]);
|
||||
|
||||
const getSettingsForExport = useCallback(() => {
|
||||
const { map_slug } = ref.current;
|
||||
|
||||
@@ -166,6 +162,24 @@ export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCom
|
||||
applySettings(createDefaultStoredSettings());
|
||||
}, [applySettings]);
|
||||
|
||||
useEffect(() => {
|
||||
checkOldSettings();
|
||||
}, [checkOldSettings]);
|
||||
|
||||
// IN Case if in runtime someone clear settings
|
||||
useEffect(() => {
|
||||
if (Object.keys(windowsSettings).length !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
resetSettings();
|
||||
location.reload();
|
||||
}, [isReady, resetSettings, windowsSettings]);
|
||||
|
||||
return {
|
||||
isReady,
|
||||
hasOldSettings,
|
||||
|
||||
@@ -16,7 +16,7 @@ defmodule WandererApp.Map.MapPool do
|
||||
@registry :map_pool_registry
|
||||
@unique_registry :unique_map_pool_registry
|
||||
|
||||
@garbage_collection_interval :timer.hours(12)
|
||||
@garbage_collection_interval :timer.hours(4)
|
||||
@systems_cleanup_timeout :timer.minutes(30)
|
||||
@characters_cleanup_timeout :timer.minutes(5)
|
||||
@connections_cleanup_timeout :timer.minutes(5)
|
||||
|
||||
@@ -8,22 +8,23 @@ defmodule WandererApp.Map.Operations.Signatures do
|
||||
alias WandererApp.Api.{Character, MapSystem, MapSystemSignature}
|
||||
alias WandererApp.Map.Server
|
||||
|
||||
# Private helper to validate character_eve_id from params
|
||||
# If character_eve_id is provided in params, validates it exists in the system
|
||||
# If not provided, falls back to the owner's character ID
|
||||
# Private helper to validate character_eve_id from params and return internal character ID
|
||||
# If character_eve_id is provided in params, validates it exists and returns the internal UUID
|
||||
# If not provided, falls back to the owner's character ID (which is already the internal UUID)
|
||||
@spec validate_character_eve_id(map() | nil, String.t()) ::
|
||||
{:ok, String.t()} | {:error, :invalid_character}
|
||||
defp validate_character_eve_id(params, fallback_char_id) when is_map(params) do
|
||||
case Map.get(params, "character_eve_id") do
|
||||
nil ->
|
||||
# No character_eve_id provided, use fallback (owner's character)
|
||||
# No character_eve_id provided, use fallback (owner's internal character UUID)
|
||||
{:ok, fallback_char_id}
|
||||
|
||||
provided_char_id when is_binary(provided_char_id) ->
|
||||
# Validate the provided character_eve_id exists
|
||||
case Character.by_eve_id(provided_char_id) do
|
||||
{:ok, _character} ->
|
||||
{:ok, provided_char_id}
|
||||
provided_char_eve_id when is_binary(provided_char_eve_id) ->
|
||||
# Validate the provided character_eve_id exists and get internal UUID
|
||||
case Character.by_eve_id(provided_char_eve_id) do
|
||||
{:ok, character} ->
|
||||
# Return the internal character UUID, not the eve_id
|
||||
{:ok, character.id}
|
||||
|
||||
_ ->
|
||||
{:error, :invalid_character}
|
||||
@@ -74,11 +75,13 @@ defmodule WandererApp.Map.Operations.Signatures do
|
||||
)
|
||||
when is_integer(solar_system_id) do
|
||||
# Validate character first, then convert solar_system_id to system_id
|
||||
with {:ok, validated_char_id} <- validate_character_eve_id(params, char_id),
|
||||
# validated_char_uuid is the internal character UUID for Server.update_signatures
|
||||
with {:ok, validated_char_uuid} <- validate_character_eve_id(params, char_id),
|
||||
{:ok, system} <- MapSystem.by_map_id_and_solar_system_id(map_id, solar_system_id) do
|
||||
# Keep character_eve_id in attrs if provided by user (parse_signatures will use it)
|
||||
# If not provided, parse_signatures will use the character_eve_id from validated_char_uuid lookup
|
||||
attrs =
|
||||
params
|
||||
|> Map.put("character_eve_id", validated_char_id)
|
||||
|> Map.put("system_id", system.id)
|
||||
|> Map.delete("solar_system_id")
|
||||
|
||||
@@ -87,7 +90,7 @@ defmodule WandererApp.Map.Operations.Signatures do
|
||||
updated_signatures: [],
|
||||
removed_signatures: [],
|
||||
solar_system_id: solar_system_id,
|
||||
character_id: validated_char_id,
|
||||
character_id: validated_char_uuid, # Pass internal UUID here
|
||||
user_id: user_id,
|
||||
delete_connection_with_sigs: false
|
||||
}) do
|
||||
@@ -149,7 +152,8 @@ defmodule WandererApp.Map.Operations.Signatures do
|
||||
params
|
||||
) do
|
||||
# Validate character first, then look up signature and system
|
||||
with {:ok, validated_char_id} <- validate_character_eve_id(params, char_id),
|
||||
# validated_char_uuid is the internal character UUID
|
||||
with {:ok, validated_char_uuid} <- validate_character_eve_id(params, char_id),
|
||||
{:ok, sig} <- MapSystemSignature.by_id(sig_id),
|
||||
{:ok, system} <- MapSystem.by_id(sig.system_id) do
|
||||
base = %{
|
||||
@@ -159,11 +163,11 @@ defmodule WandererApp.Map.Operations.Signatures do
|
||||
"group" => sig.group,
|
||||
"type" => sig.type,
|
||||
"custom_info" => sig.custom_info,
|
||||
"character_eve_id" => validated_char_id,
|
||||
"description" => sig.description,
|
||||
"linked_system_id" => sig.linked_system_id
|
||||
}
|
||||
|
||||
# Merge user params (which may include character_eve_id) with base
|
||||
attrs = Map.merge(base, params)
|
||||
|
||||
:ok =
|
||||
@@ -172,7 +176,7 @@ defmodule WandererApp.Map.Operations.Signatures do
|
||||
updated_signatures: [attrs],
|
||||
removed_signatures: [],
|
||||
solar_system_id: system.solar_system_id,
|
||||
character_id: validated_char_id,
|
||||
character_id: validated_char_uuid, # Pass internal UUID here
|
||||
user_id: user_id,
|
||||
delete_connection_with_sigs: false
|
||||
})
|
||||
|
||||
@@ -310,8 +310,8 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
start_solar_system_id =
|
||||
WandererApp.Cache.take("map:#{map_id}:character:#{character_id}:start_solar_system_id")
|
||||
|
||||
case is_nil(old_location.solar_system_id) and
|
||||
is_nil(start_solar_system_id) and
|
||||
case is_nil(old_location.solar_system_id) &&
|
||||
is_nil(start_solar_system_id) &&
|
||||
ConnectionsImpl.can_add_location(scope, location.solar_system_id) do
|
||||
true ->
|
||||
:ok = SystemsImpl.maybe_add_system(map_id, location, nil, map_opts)
|
||||
|
||||
@@ -657,12 +657,14 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
|
||||
)
|
||||
)
|
||||
|
||||
def is_connection_valid(:all, _from_solar_system_id, _to_solar_system_id), do: true
|
||||
def is_connection_valid(:all, from_solar_system_id, to_solar_system_id),
|
||||
do: from_solar_system_id != to_solar_system_id
|
||||
|
||||
def is_connection_valid(:none, _from_solar_system_id, _to_solar_system_id), do: false
|
||||
|
||||
def is_connection_valid(scope, from_solar_system_id, to_solar_system_id)
|
||||
when not is_nil(from_solar_system_id) and not is_nil(to_solar_system_id) do
|
||||
when not is_nil(from_solar_system_id) and not is_nil(to_solar_system_id) and
|
||||
from_solar_system_id != to_solar_system_id do
|
||||
with {:ok, known_jumps} <- find_solar_system_jump(from_solar_system_id, to_solar_system_id),
|
||||
{:ok, from_system_static_info} <- get_system_static_info(from_solar_system_id),
|
||||
{:ok, to_system_static_info} <- get_system_static_info(to_solar_system_id) do
|
||||
|
||||
@@ -279,7 +279,8 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
|
||||
group: sig["group"],
|
||||
type: Map.get(sig, "type"),
|
||||
custom_info: Map.get(sig, "custom_info"),
|
||||
character_eve_id: character_eve_id,
|
||||
# Use character_eve_id from sig if provided, otherwise use the default
|
||||
character_eve_id: Map.get(sig, "character_eve_id", character_eve_id),
|
||||
deleted: false
|
||||
}
|
||||
end)
|
||||
|
||||
@@ -12,28 +12,32 @@ defmodule WandererAppWeb.MapSystemSignatureAPIController do
|
||||
# Inlined OpenAPI schema for a map system signature
|
||||
@signature_schema %OpenApiSpex.Schema{
|
||||
title: "MapSystemSignature",
|
||||
description: "A cosmic signature scanned in an EVE Online solar system",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %OpenApiSpex.Schema{type: :string, format: :uuid},
|
||||
solar_system_id: %OpenApiSpex.Schema{type: :integer},
|
||||
eve_id: %OpenApiSpex.Schema{type: :string},
|
||||
character_eve_id: %OpenApiSpex.Schema{type: :string},
|
||||
name: %OpenApiSpex.Schema{type: :string, nullable: true},
|
||||
description: %OpenApiSpex.Schema{type: :string, nullable: true},
|
||||
type: %OpenApiSpex.Schema{type: :string, nullable: true},
|
||||
linked_system_id: %OpenApiSpex.Schema{type: :integer, nullable: true},
|
||||
kind: %OpenApiSpex.Schema{type: :string, nullable: true},
|
||||
group: %OpenApiSpex.Schema{type: :string, nullable: true},
|
||||
custom_info: %OpenApiSpex.Schema{type: :string, nullable: true},
|
||||
updated: %OpenApiSpex.Schema{type: :integer, nullable: true},
|
||||
inserted_at: %OpenApiSpex.Schema{type: :string, format: :date_time},
|
||||
updated_at: %OpenApiSpex.Schema{type: :string, format: :date_time}
|
||||
id: %OpenApiSpex.Schema{type: :string, format: :uuid, description: "Unique signature identifier"},
|
||||
solar_system_id: %OpenApiSpex.Schema{type: :integer, description: "EVE Online solar system ID"},
|
||||
eve_id: %OpenApiSpex.Schema{type: :string, description: "In-game signature ID (e.g., ABC-123)"},
|
||||
character_eve_id: %OpenApiSpex.Schema{
|
||||
type: :string,
|
||||
description: "EVE character ID who scanned/updated this signature. Must be a valid character in the database. If not provided, defaults to the map owner's character.",
|
||||
nullable: true
|
||||
},
|
||||
name: %OpenApiSpex.Schema{type: :string, nullable: true, description: "Signature name"},
|
||||
description: %OpenApiSpex.Schema{type: :string, nullable: true, description: "Additional notes"},
|
||||
type: %OpenApiSpex.Schema{type: :string, nullable: true, description: "Signature type"},
|
||||
linked_system_id: %OpenApiSpex.Schema{type: :integer, nullable: true, description: "Connected solar system ID for wormholes"},
|
||||
kind: %OpenApiSpex.Schema{type: :string, nullable: true, description: "Signature kind (e.g., cosmic_signature)"},
|
||||
group: %OpenApiSpex.Schema{type: :string, nullable: true, description: "Signature group (e.g., wormhole, data, relic)"},
|
||||
custom_info: %OpenApiSpex.Schema{type: :string, nullable: true, description: "Custom metadata"},
|
||||
updated: %OpenApiSpex.Schema{type: :integer, nullable: true, description: "Update counter"},
|
||||
inserted_at: %OpenApiSpex.Schema{type: :string, format: :date_time, description: "Creation timestamp"},
|
||||
updated_at: %OpenApiSpex.Schema{type: :string, format: :date_time, description: "Last update timestamp"}
|
||||
},
|
||||
required: [
|
||||
:id,
|
||||
:solar_system_id,
|
||||
:eve_id,
|
||||
:character_eve_id
|
||||
:eve_id
|
||||
],
|
||||
example: %{
|
||||
id: "sig-uuid-1",
|
||||
@@ -143,6 +147,10 @@ defmodule WandererAppWeb.MapSystemSignatureAPIController do
|
||||
|
||||
@doc """
|
||||
Create a new signature.
|
||||
|
||||
The `character_eve_id` field is optional. If provided, it must be a valid character
|
||||
that exists in the database, otherwise a 422 error will be returned. If not provided,
|
||||
the signature will be associated with the map owner's character.
|
||||
"""
|
||||
operation(:create,
|
||||
summary: "Create a new signature",
|
||||
@@ -162,6 +170,18 @@ defmodule WandererAppWeb.MapSystemSignatureAPIController do
|
||||
type: :object,
|
||||
properties: %{data: @signature_schema},
|
||||
example: %{data: @signature_schema.example}
|
||||
}},
|
||||
unprocessable_entity:
|
||||
{"Validation error", "application/json",
|
||||
%OpenApiSpex.Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
error: %OpenApiSpex.Schema{
|
||||
type: :string,
|
||||
description: "Error type (e.g., 'invalid_character', 'system_not_found', 'missing_params')"
|
||||
}
|
||||
},
|
||||
example: %{error: "invalid_character"}
|
||||
}}
|
||||
]
|
||||
)
|
||||
@@ -175,6 +195,9 @@ defmodule WandererAppWeb.MapSystemSignatureAPIController do
|
||||
|
||||
@doc """
|
||||
Update a signature by ID.
|
||||
|
||||
The `character_eve_id` field is optional. If provided, it must be a valid character
|
||||
that exists in the database, otherwise a 422 error will be returned.
|
||||
"""
|
||||
operation(:update,
|
||||
summary: "Update a signature by ID",
|
||||
@@ -195,6 +218,18 @@ defmodule WandererAppWeb.MapSystemSignatureAPIController do
|
||||
type: :object,
|
||||
properties: %{data: @signature_schema},
|
||||
example: %{data: @signature_schema.example}
|
||||
}},
|
||||
unprocessable_entity:
|
||||
{"Validation error", "application/json",
|
||||
%OpenApiSpex.Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
error: %OpenApiSpex.Schema{
|
||||
type: :string,
|
||||
description: "Error type (e.g., 'invalid_character', 'unexpected_error')"
|
||||
}
|
||||
},
|
||||
example: %{error: "invalid_character"}
|
||||
}}
|
||||
]
|
||||
)
|
||||
|
||||
@@ -149,12 +149,12 @@ defmodule WandererAppWeb.Plugs.CheckJsonApiAuth do
|
||||
end
|
||||
|
||||
defp validate_api_token(conn, token) do
|
||||
# Check for map identifier in path params
|
||||
# According to PR feedback, routes supply params["map_identifier"]
|
||||
case conn.params["map_identifier"] do
|
||||
# Try to get map identifier from multiple sources
|
||||
map_identifier = get_map_identifier(conn)
|
||||
|
||||
case map_identifier do
|
||||
nil ->
|
||||
# No map identifier in path - this might be a general API endpoint
|
||||
# For now, we'll return an error since we need to validate against a specific map
|
||||
# No map identifier found - this might be a general API endpoint
|
||||
{:error, "Authentication failed", :no_map_context}
|
||||
|
||||
identifier ->
|
||||
@@ -182,6 +182,37 @@ defmodule WandererAppWeb.Plugs.CheckJsonApiAuth do
|
||||
end
|
||||
end
|
||||
|
||||
# Extract map identifier from multiple sources
|
||||
defp get_map_identifier(conn) do
|
||||
# 1. Check path params (e.g., /api/v1/maps/:map_identifier/systems)
|
||||
case conn.params["map_identifier"] do
|
||||
id when is_binary(id) and id != "" -> id
|
||||
_ ->
|
||||
# 2. Check request body for map_id (JSON:API format)
|
||||
case conn.body_params do
|
||||
%{"data" => %{"attributes" => %{"map_id" => map_id}}} when is_binary(map_id) and map_id != "" ->
|
||||
map_id
|
||||
|
||||
%{"data" => %{"relationships" => %{"map" => %{"data" => %{"id" => map_id}}}}} when is_binary(map_id) and map_id != "" ->
|
||||
map_id
|
||||
|
||||
# 3. Check flat body params (non-JSON:API format)
|
||||
%{"map_id" => map_id} when is_binary(map_id) and map_id != "" ->
|
||||
map_id
|
||||
|
||||
_ ->
|
||||
# 4. Check query params (e.g., ?filter[map_id]=...)
|
||||
case conn.params do
|
||||
%{"filter" => %{"map_id" => map_id}} when is_binary(map_id) and map_id != "" ->
|
||||
map_id
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Helper to resolve map by ID or slug
|
||||
defp resolve_map_identifier(identifier) do
|
||||
# Try as UUID first
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
defmodule WandererAppWeb.Plugs.ConditionalAssignMapOwner do
|
||||
@moduledoc """
|
||||
Conditionally assigns map owner information to conn.assigns for V1 API routes.
|
||||
|
||||
This plug enables PubSub broadcasting for map operations by ensuring owner_character_id
|
||||
and owner_user_id are available when map context exists.
|
||||
|
||||
Unlike the standard :api_map pipeline plugs (CheckMapApiKey, CheckMapSubscription),
|
||||
this plug does NOT halt the request if map context is missing, making it safe to use
|
||||
for both map-specific and user-level resources.
|
||||
|
||||
Map context detection (in order of priority):
|
||||
1. conn.assigns[:map_id] - Set by CheckJsonApiAuth for Bearer token requests with map_identifier
|
||||
2. filter[map_id] - JSON:API filter parameter for map-specific queries
|
||||
3. Request body map_id - For create/update operations on map resources
|
||||
|
||||
If no map context is found, the plug simply continues without setting owner fields.
|
||||
This allows user-level resources (AccessList, UserActivity, etc.) to work normally.
|
||||
"""
|
||||
|
||||
import Plug.Conn
|
||||
|
||||
alias WandererApp.Map.Operations
|
||||
|
||||
def init(opts), do: opts
|
||||
|
||||
def call(conn, _opts) do
|
||||
case get_map_id(conn) do
|
||||
{:ok, map_id} ->
|
||||
# Map context found - fetch and assign owner information
|
||||
case Operations.get_owner_character_id(map_id) do
|
||||
{:ok, %{id: char_id, user_id: user_id}} ->
|
||||
conn
|
||||
|> assign(:map_id, map_id)
|
||||
|> assign(:owner_character_id, char_id)
|
||||
|> assign(:owner_user_id, user_id)
|
||||
|
||||
_ ->
|
||||
# Map exists but owner not found - set nil values
|
||||
conn
|
||||
|> assign(:map_id, map_id)
|
||||
|> assign(:owner_character_id, nil)
|
||||
|> assign(:owner_user_id, nil)
|
||||
end
|
||||
|
||||
:no_map_context ->
|
||||
# No map context - this is okay for user-level resources
|
||||
# Don't halt, just continue without setting map fields
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
# Try to extract map_id from various sources
|
||||
defp get_map_id(conn) do
|
||||
# 1. Check if already set by CheckJsonApiAuth (Bearer token with map_identifier)
|
||||
case conn.assigns[:map_id] do
|
||||
map_id when is_binary(map_id) and map_id != "" ->
|
||||
{:ok, map_id}
|
||||
|
||||
_ ->
|
||||
# 2. Check JSON:API filter parameters (e.g., filter[map_id]=uuid)
|
||||
case get_filter_map_id(conn) do
|
||||
{:ok, map_id} -> {:ok, map_id}
|
||||
:not_found -> check_body_map_id(conn)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Extract map_id from JSON:API filter parameters
|
||||
defp get_filter_map_id(conn) do
|
||||
# JSON:API filters come as filter[map_id]=value
|
||||
case conn.params do
|
||||
%{"filter" => %{"map_id" => map_id}} when is_binary(map_id) and map_id != "" ->
|
||||
{:ok, map_id}
|
||||
|
||||
_ ->
|
||||
:not_found
|
||||
end
|
||||
end
|
||||
|
||||
# Extract map_id from request body (for create/update operations)
|
||||
defp check_body_map_id(conn) do
|
||||
case conn.body_params do
|
||||
%{"data" => %{"attributes" => %{"map_id" => map_id}}}
|
||||
when is_binary(map_id) and map_id != "" ->
|
||||
{:ok, map_id}
|
||||
|
||||
%{"data" => %{"relationships" => %{"map" => %{"data" => %{"id" => map_id}}}}}
|
||||
when is_binary(map_id) and map_id != "" ->
|
||||
{:ok, map_id}
|
||||
|
||||
# Also check flat params for non-JSON:API formatted requests
|
||||
%{"map_id" => map_id} when is_binary(map_id) and map_id != "" ->
|
||||
{:ok, map_id}
|
||||
|
||||
_ ->
|
||||
:no_map_context
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -10,529 +10,8 @@ defmodule WandererAppWeb.OpenApiV1Spec do
|
||||
@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
|
||||
# We delegate to WandererAppWeb.OpenApi module which generates
|
||||
# the spec from AshJsonApi with custom endpoints merged in
|
||||
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
|
||||
|
||||
@@ -234,7 +234,6 @@ defmodule WandererAppWeb.Router do
|
||||
plug WandererAppWeb.Plugs.CheckApiDisabled
|
||||
plug WandererAppWeb.Plugs.JsonApiPerformanceMonitor
|
||||
plug WandererAppWeb.Plugs.CheckJsonApiAuth
|
||||
plug WandererAppWeb.Plugs.ConditionalAssignMapOwner
|
||||
# Future: Add rate limiting, advanced permissions, etc.
|
||||
end
|
||||
|
||||
@@ -598,7 +597,7 @@ defmodule WandererAppWeb.Router do
|
||||
scope "/api/v1" do
|
||||
pipe_through :api_v1
|
||||
|
||||
# Custom combined endpoints
|
||||
# Custom combined endpoint with map_id in path
|
||||
get "/maps/:map_id/systems_and_connections",
|
||||
WandererAppWeb.Api.MapSystemsConnectionsController,
|
||||
:show
|
||||
@@ -606,6 +605,18 @@ defmodule WandererAppWeb.Router do
|
||||
# Forward all v1 requests to AshJsonApi router
|
||||
# This will automatically generate RESTful JSON:API endpoints
|
||||
# for all Ash resources once they're configured with the AshJsonApi extension
|
||||
#
|
||||
# NOTE: AshJsonApi generates flat routes (e.g., /api/v1/map_systems)
|
||||
# Phoenix's `forward` cannot be used with dynamic path segments, so proper
|
||||
# nested routes like /api/v1/maps/{id}/systems would require custom controllers.
|
||||
#
|
||||
# Current approach: Use flat routes with map_id in request body or filters:
|
||||
# - POST /api/v1/map_systems with {"data": {"attributes": {"map_id": "..."}}}
|
||||
# - GET /api/v1/map_systems?filter[map_id]=...
|
||||
# - PATCH /api/v1/map_systems/{id} with map_id in body
|
||||
#
|
||||
# Authentication is handled by CheckJsonApiAuth which validates the Bearer
|
||||
# token against the map's API key.
|
||||
forward "/", WandererAppWeb.ApiV1Router
|
||||
end
|
||||
end
|
||||
|
||||
2
mix.exs
2
mix.exs
@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
|
||||
|
||||
@source_url "https://github.com/wanderer-industries/wanderer"
|
||||
|
||||
@version "1.84.9"
|
||||
@version "1.84.13"
|
||||
|
||||
def project do
|
||||
[
|
||||
|
||||
@@ -144,33 +144,28 @@ The API v1 provides access to over 25 resources through the Ash Framework. Here
|
||||
|
||||
### Core Resources
|
||||
- **Maps** (`/api/v1/maps`) - Map management with full CRUD operations
|
||||
- **Characters** (`/api/v1/characters`) - Character tracking and management (GET, DELETE only)
|
||||
- **Access Lists** (`/api/v1/access_lists`) - ACL management and permissions
|
||||
- **Access List Members** (`/api/v1/access_list_members`) - ACL member management
|
||||
- **Access Lists** (`/api/v1/access_lists`) - ACL management and permissions with full CRUD operations
|
||||
- **Access List Members** (`/api/v1/access_list_members`) - ACL member management with full CRUD operations
|
||||
- **Map Access Lists** (`/api/v1/map_access_lists`) - Map-ACL associations with full CRUD operations
|
||||
|
||||
### Map Resources
|
||||
- **Map Systems** (`/api/v1/map_systems`) - Solar system data and metadata
|
||||
- **Map Connections** (`/api/v1/map_connections`) - Wormhole connections
|
||||
- **Map Signatures** (`/api/v1/map_system_signatures`) - Signature scanning data (GET, DELETE only)
|
||||
- **Map Structures** (`/api/v1/map_system_structures`) - Structure information
|
||||
- **Map Subscriptions** (`/api/v1/map_subscriptions`) - Subscription management (GET only)
|
||||
- **Map Systems and Connections** (`/api/v1/maps/{map_id}/systems_and_connections`) - Combined endpoint (GET only)
|
||||
- **Map Systems** (`/api/v1/map_systems`) - Solar system data and metadata with full CRUD operations (paginated: default 100, max 500)
|
||||
- **Map Connections** (`/api/v1/map_connections`) - Wormhole connections with full CRUD operations
|
||||
- **Map Signatures** (`/api/v1/map_system_signatures`) - Signature scanning data (read and delete only, paginated: default 50, max 200)
|
||||
- **Map Structures** (`/api/v1/map_system_structures`) - Structure information with full CRUD operations
|
||||
- **Map Subscriptions** (`/api/v1/map_subscriptions`) - Subscription management (read-only)
|
||||
- **Map Default Settings** (`/api/v1/map_default_settings`) - Default map configurations with full CRUD operations
|
||||
- **Map Systems and Connections** (`/api/v1/maps/{map_id}/systems_and_connections`) - Combined endpoint (read-only)
|
||||
|
||||
### System Resources
|
||||
- **Map System Comments** (`/api/v1/map_system_comments`) - System annotations (GET only)
|
||||
- **Map System Comments** (`/api/v1/map_system_comments`) - System annotations (read-only)
|
||||
|
||||
### User Resources
|
||||
- **User Activities** (`/api/v1/user_activities`) - User activity tracking (GET only)
|
||||
- **Map Character Settings** (`/api/v1/map_character_settings`) - Character preferences (GET only)
|
||||
- **Map User Settings** (`/api/v1/map_user_settings`) - User map preferences (GET only)
|
||||
- **User Activities** (`/api/v1/user_activities`) - User activity tracking (read-only, paginated: default 15)
|
||||
- **Map Character Settings** (`/api/v1/map_character_settings`) - Character preferences (read-only)
|
||||
- **Map User Settings** (`/api/v1/map_user_settings`) - User map preferences (read-only)
|
||||
|
||||
### Additional Resources
|
||||
- **Map Webhook Subscriptions** (`/api/v1/map_webhook_subscriptions`) - Webhook management
|
||||
- **Map Invites** (`/api/v1/map_invites`) - Map invitation system
|
||||
- **Map Pings** (`/api/v1/map_pings`) - In-game ping tracking
|
||||
- **Corp Wallet Transactions** (`/api/v1/corp_wallet_transactions`) - Corporation finances
|
||||
|
||||
*Note: Some resources have been restricted to read-only access for security and consistency. Resources marked as "(GET only)" support only read operations, while "(GET, DELETE only)" support read and delete operations.*
|
||||
*Note: Resources marked as "full CRUD operations" support create, read, update, and delete. Resources marked as "read-only" support only GET operations. Resources marked as "read and delete only" support GET and DELETE operations. Pagination limits are configurable via `page[limit]` and `page[offset]` parameters where supported.*
|
||||
|
||||
## API v1 Feature Set
|
||||
|
||||
|
||||
@@ -2,4 +2,15 @@
|
||||
|
||||
export ERL_AFLAGS="-proto_dist inet6_tcp"
|
||||
export RELEASE_DISTRIBUTION="name"
|
||||
export RELEASE_NODE="${FLY_APP_NAME}-${FLY_IMAGE_REF##*-}@${FLY_PRIVATE_IP}"
|
||||
|
||||
# Use custom RELEASE_NODE if set, otherwise detect environment
|
||||
if [ -n "$RELEASE_NODE" ]; then
|
||||
# RELEASE_NODE already set, use as-is
|
||||
export RELEASE_NODE
|
||||
elif [ -n "$FLY_APP_NAME" ] && [ -n "$FLY_IMAGE_REF" ] && [ -n "$FLY_PRIVATE_IP" ]; then
|
||||
# Fly.io environment detected
|
||||
export RELEASE_NODE="${FLY_APP_NAME}-${FLY_IMAGE_REF##*-}@${FLY_PRIVATE_IP}"
|
||||
else
|
||||
# Generic deployment - use hostname
|
||||
export RELEASE_NODE="wanderer@$(hostname)"
|
||||
fi
|
||||
|
||||
18
test/manual/api/.env.example
Normal file
18
test/manual/api/.env.example
Normal file
@@ -0,0 +1,18 @@
|
||||
# Example environment file for manual API tests
|
||||
# Copy this to .env and fill in your values
|
||||
|
||||
# Your Wanderer server URL
|
||||
API_BASE_URL=http://localhost:8000
|
||||
|
||||
# Your map's slug (found in the map URL: /your-map-slug)
|
||||
MAP_SLUG=your-map-slug
|
||||
|
||||
# Your map's public API token (found in map settings)
|
||||
API_TOKEN=your_map_public_api_key_here
|
||||
|
||||
# For character_eve_id testing:
|
||||
# Find a valid character EVE ID from your database
|
||||
VALID_CHAR_ID=111111111
|
||||
|
||||
# Use any non-existent character ID for invalid tests
|
||||
INVALID_CHAR_ID=999999999
|
||||
249
test/manual/api/CURL_EXAMPLES.md
Normal file
249
test/manual/api/CURL_EXAMPLES.md
Normal file
@@ -0,0 +1,249 @@
|
||||
# Manual cURL Testing for Character EVE ID Fix (Issue #539)
|
||||
|
||||
This guide provides standalone curl commands to manually test the character_eve_id fix.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Get your Map's Public API Token:**
|
||||
- Log into Wanderer
|
||||
- Go to your map settings
|
||||
- Find the "Public API Key" section
|
||||
- Copy your API token
|
||||
|
||||
2. **Find your Map Slug:**
|
||||
- Look at your map URL: `https://your-instance.com/your-map-slug`
|
||||
- The slug is the last part of the URL
|
||||
|
||||
3. **Get a valid Character EVE ID:**
|
||||
```bash
|
||||
# Option 1: Query your database
|
||||
psql $DATABASE_URL -c "SELECT eve_id, name FROM character_v1 WHERE deleted = false LIMIT 5;"
|
||||
|
||||
# Option 2: Use the characters API
|
||||
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
http://localhost:8000/api/characters
|
||||
```
|
||||
|
||||
4. **Get a Solar System ID from your map:**
|
||||
```bash
|
||||
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
http://localhost:8000/api/maps/YOUR_SLUG/systems \
|
||||
| jq '.data[0].solar_system_id'
|
||||
```
|
||||
|
||||
## Set Environment Variables (for convenience)
|
||||
|
||||
```bash
|
||||
export API_BASE_URL="http://localhost:8000"
|
||||
export MAP_SLUG="your-map-slug"
|
||||
export API_TOKEN="your_api_token_here"
|
||||
export SOLAR_SYSTEM_ID="30000142" # Replace with actual system ID from your map
|
||||
export VALID_CHAR_ID="111111111" # Replace with real character eve_id
|
||||
export INVALID_CHAR_ID="999999999" # Non-existent character
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test 1: Create Signature with Valid character_eve_id
|
||||
|
||||
**Expected Result:** HTTP 201, returned object has the submitted character_eve_id
|
||||
|
||||
```bash
|
||||
curl -v -X POST \
|
||||
-H "Authorization: Bearer $API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"solar_system_id": '"$SOLAR_SYSTEM_ID"',
|
||||
"eve_id": "TEST-001",
|
||||
"character_eve_id": "'"$VALID_CHAR_ID"'",
|
||||
"group": "wormhole",
|
||||
"kind": "cosmic_signature",
|
||||
"name": "Test Signature 1"
|
||||
}' \
|
||||
"$API_BASE_URL/api/maps/$MAP_SLUG/signatures" | jq '.'
|
||||
```
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# The response should contain:
|
||||
# "character_eve_id": "111111111" (your VALID_CHAR_ID)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test 2: Create Signature with Invalid character_eve_id
|
||||
|
||||
**Expected Result:** HTTP 422 with error "invalid_character"
|
||||
|
||||
```bash
|
||||
curl -v -X POST \
|
||||
-H "Authorization: Bearer $API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"solar_system_id": '"$SOLAR_SYSTEM_ID"',
|
||||
"eve_id": "TEST-002",
|
||||
"character_eve_id": "'"$INVALID_CHAR_ID"'",
|
||||
"group": "wormhole",
|
||||
"kind": "cosmic_signature"
|
||||
}' \
|
||||
"$API_BASE_URL/api/maps/$MAP_SLUG/signatures" | jq '.'
|
||||
```
|
||||
|
||||
**Expected Response:**
|
||||
```json
|
||||
{
|
||||
"error": "invalid_character"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test 3: Create Signature WITHOUT character_eve_id (Backward Compatibility)
|
||||
|
||||
**Expected Result:** HTTP 201, uses map owner's character_eve_id as fallback
|
||||
|
||||
```bash
|
||||
curl -v -X POST \
|
||||
-H "Authorization: Bearer $API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"solar_system_id": '"$SOLAR_SYSTEM_ID"',
|
||||
"eve_id": "TEST-003",
|
||||
"group": "data",
|
||||
"kind": "cosmic_signature",
|
||||
"name": "Test Signature 3"
|
||||
}' \
|
||||
"$API_BASE_URL/api/maps/$MAP_SLUG/signatures" | jq '.'
|
||||
```
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# The response should contain the map owner's character_eve_id
|
||||
# This proves backward compatibility is maintained
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test 4: Update Signature with Valid character_eve_id
|
||||
|
||||
**Expected Result:** HTTP 200, returned object has the submitted character_eve_id
|
||||
|
||||
```bash
|
||||
# First, save a signature ID from Test 1 or 3
|
||||
export SIG_ID="paste-signature-id-here"
|
||||
|
||||
curl -v -X PUT \
|
||||
-H "Authorization: Bearer $API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Updated Signature Name",
|
||||
"character_eve_id": "'"$VALID_CHAR_ID"'",
|
||||
"description": "Updated via API"
|
||||
}' \
|
||||
"$API_BASE_URL/api/maps/$MAP_SLUG/signatures/$SIG_ID" | jq '.'
|
||||
```
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# The response should contain:
|
||||
# "character_eve_id": "111111111" (your VALID_CHAR_ID)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test 5: Update Signature with Invalid character_eve_id
|
||||
|
||||
**Expected Result:** HTTP 422 with error "invalid_character"
|
||||
|
||||
```bash
|
||||
curl -v -X PUT \
|
||||
-H "Authorization: Bearer $API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Should Fail",
|
||||
"character_eve_id": "'"$INVALID_CHAR_ID"'"
|
||||
}' \
|
||||
"$API_BASE_URL/api/maps/$MAP_SLUG/signatures/$SIG_ID" | jq '.'
|
||||
```
|
||||
|
||||
**Expected Response:**
|
||||
```json
|
||||
{
|
||||
"error": "invalid_character"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cleanup
|
||||
|
||||
Delete test signatures:
|
||||
|
||||
```bash
|
||||
# List all signatures to find IDs
|
||||
curl -H "Authorization: Bearer $API_TOKEN" \
|
||||
"$API_BASE_URL/api/maps/$MAP_SLUG/signatures" | jq '.data[] | {id, eve_id, name}'
|
||||
|
||||
# Delete specific signature
|
||||
export SIG_ID="signature-uuid-here"
|
||||
curl -v -X DELETE \
|
||||
-H "Authorization: Bearer $API_TOKEN" \
|
||||
"$API_BASE_URL/api/maps/$MAP_SLUG/signatures/$SIG_ID"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Debugging Tips
|
||||
|
||||
### View All Signatures
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $API_TOKEN" \
|
||||
"$API_BASE_URL/api/maps/$MAP_SLUG/signatures" \
|
||||
| jq '.data[] | {id, eve_id, character_eve_id, name}'
|
||||
```
|
||||
|
||||
### View All Characters in Database
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $API_TOKEN" \
|
||||
"$API_BASE_URL/api/characters" \
|
||||
| jq '.[] | {eve_id, name}'
|
||||
```
|
||||
|
||||
### View All Systems in Map
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $API_TOKEN" \
|
||||
"$API_BASE_URL/api/maps/$MAP_SLUG/systems" \
|
||||
| jq '.data[] | {id, solar_system_id, name}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Expected Behavior Summary
|
||||
|
||||
| Test Case | HTTP Status | character_eve_id in Response |
|
||||
|-----------|-------------|------------------------------|
|
||||
| Create with valid char ID | 201 | Matches submitted value |
|
||||
| Create with invalid char ID | 422 | N/A (error returned) |
|
||||
| Create without char ID | 201 | Map owner's char ID (fallback) |
|
||||
| Update with valid char ID | 200 | Matches submitted value |
|
||||
| Update with invalid char ID | 422 | N/A (error returned) |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Unauthorized (invalid token for map)"
|
||||
- Double-check your API_TOKEN matches the map's public API key
|
||||
- Verify the token doesn't have extra spaces or newlines
|
||||
|
||||
### "Map not found"
|
||||
- Verify your MAP_SLUG is correct
|
||||
- Try using the map UUID instead of slug
|
||||
|
||||
### "System not found for solar_system_id"
|
||||
- The system must already exist in your map
|
||||
- Run the "View All Systems" command to find valid system IDs
|
||||
|
||||
### "invalid_character" when using what should be valid
|
||||
- Verify the character exists: `SELECT * FROM character_v1 WHERE eve_id = 'YOUR_ID';`
|
||||
- Make sure `deleted = false` for the character
|
||||
289
test/manual/api/test_character_eve_id_fix.sh
Executable file
289
test/manual/api/test_character_eve_id_fix.sh
Executable file
@@ -0,0 +1,289 @@
|
||||
#!/bin/bash
|
||||
# test/manual/api/test_character_eve_id_fix.sh
|
||||
# ─── Manual Test for Character EVE ID Fix (Issue #539) ────────────────────────
|
||||
#
|
||||
# This script tests the fix for GitHub issue #539 where character_eve_id
|
||||
# was being ignored when creating/updating signatures via the REST API.
|
||||
#
|
||||
# Usage:
|
||||
# 1. Create a .env file in this directory with:
|
||||
# API_TOKEN=your_map_public_api_key
|
||||
# API_BASE_URL=http://localhost:8000 # or your server URL
|
||||
# MAP_SLUG=your_map_slug
|
||||
# VALID_CHAR_ID=111111111 # A character that exists in your database
|
||||
# INVALID_CHAR_ID=999999999 # A character that does NOT exist
|
||||
#
|
||||
# 2. Run: ./test_character_eve_id_fix.sh
|
||||
#
|
||||
# Prerequisites:
|
||||
# - curl and jq must be installed
|
||||
# - A map must exist with a valid API token
|
||||
# - At least one system must be added to the map
|
||||
|
||||
set -eu
|
||||
|
||||
source "$(dirname "$0")/utils.sh"
|
||||
|
||||
echo "═══════════════════════════════════════════════════════════════════"
|
||||
echo "Testing Character EVE ID Fix (GitHub Issue #539)"
|
||||
echo "═══════════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
# Check required environment variables
|
||||
: "${API_BASE_URL:?Error: API_BASE_URL not set}"
|
||||
: "${MAP_SLUG:?Error: MAP_SLUG not set}"
|
||||
: "${VALID_CHAR_ID:?Error: VALID_CHAR_ID not set (provide a character eve_id that exists in DB)}"
|
||||
: "${INVALID_CHAR_ID:?Error: INVALID_CHAR_ID not set (provide a non-existent character eve_id)}"
|
||||
|
||||
# Get a system to use for testing
|
||||
echo "📋 Fetching available systems from map..."
|
||||
SYSTEMS_RAW=$(make_request GET "$API_BASE_URL/api/maps/$MAP_SLUG/systems")
|
||||
SYSTEMS_STATUS=$(parse_status "$SYSTEMS_RAW")
|
||||
SYSTEMS_RESPONSE=$(parse_response "$SYSTEMS_RAW")
|
||||
|
||||
if [ "$SYSTEMS_STATUS" != "200" ]; then
|
||||
echo "❌ Failed to fetch systems (HTTP $SYSTEMS_STATUS)"
|
||||
echo "$SYSTEMS_RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract first system's solar_system_id
|
||||
SOLAR_SYSTEM_ID=$(echo "$SYSTEMS_RESPONSE" | jq -r '.data[0].solar_system_id // empty')
|
||||
|
||||
if [ -z "$SOLAR_SYSTEM_ID" ]; then
|
||||
echo "❌ No systems found in map. Please add at least one system first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Using solar_system_id: $SOLAR_SYSTEM_ID"
|
||||
echo ""
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Test 1: Create signature with valid character_eve_id
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
echo "Test 1: Create signature with VALID character_eve_id"
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
|
||||
PAYLOAD1=$(cat <<EOF
|
||||
{
|
||||
"solar_system_id": $SOLAR_SYSTEM_ID,
|
||||
"eve_id": "TEST-001",
|
||||
"character_eve_id": "$VALID_CHAR_ID",
|
||||
"group": "wormhole",
|
||||
"kind": "cosmic_signature",
|
||||
"name": "Test Sig 1"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "Request:"
|
||||
echo "$PAYLOAD1" | jq '.'
|
||||
echo ""
|
||||
|
||||
RAW1=$(make_request POST "$API_BASE_URL/api/maps/$MAP_SLUG/signatures" "$PAYLOAD1")
|
||||
STATUS1=$(parse_status "$RAW1")
|
||||
RESPONSE1=$(parse_response "$RAW1")
|
||||
|
||||
echo "Response (HTTP $STATUS1):"
|
||||
echo "$RESPONSE1" | jq '.'
|
||||
echo ""
|
||||
|
||||
if [ "$STATUS1" = "201" ]; then
|
||||
RETURNED_CHAR_ID=$(echo "$RESPONSE1" | jq -r '.data.character_eve_id')
|
||||
if [ "$RETURNED_CHAR_ID" = "$VALID_CHAR_ID" ]; then
|
||||
echo "✅ PASS: Signature created with correct character_eve_id: $RETURNED_CHAR_ID"
|
||||
SIG_ID_1=$(echo "$RESPONSE1" | jq -r '.data.id')
|
||||
else
|
||||
echo "❌ FAIL: Expected character_eve_id=$VALID_CHAR_ID, got $RETURNED_CHAR_ID"
|
||||
fi
|
||||
else
|
||||
echo "❌ FAIL: Expected HTTP 201, got $STATUS1"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Test 2: Create signature with invalid character_eve_id
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
echo "Test 2: Create signature with INVALID character_eve_id"
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
|
||||
PAYLOAD2=$(cat <<EOF
|
||||
{
|
||||
"solar_system_id": $SOLAR_SYSTEM_ID,
|
||||
"eve_id": "TEST-002",
|
||||
"character_eve_id": "$INVALID_CHAR_ID",
|
||||
"group": "wormhole",
|
||||
"kind": "cosmic_signature"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "Request:"
|
||||
echo "$PAYLOAD2" | jq '.'
|
||||
echo ""
|
||||
|
||||
RAW2=$(make_request POST "$API_BASE_URL/api/maps/$MAP_SLUG/signatures" "$PAYLOAD2")
|
||||
STATUS2=$(parse_status "$RAW2")
|
||||
RESPONSE2=$(parse_response "$RAW2")
|
||||
|
||||
echo "Response (HTTP $STATUS2):"
|
||||
echo "$RESPONSE2" | jq '.'
|
||||
echo ""
|
||||
|
||||
if [ "$STATUS2" = "422" ]; then
|
||||
ERROR_MSG=$(echo "$RESPONSE2" | jq -r '.error // empty')
|
||||
if [ "$ERROR_MSG" = "invalid_character" ]; then
|
||||
echo "✅ PASS: Correctly rejected invalid character_eve_id with error: $ERROR_MSG"
|
||||
else
|
||||
echo "⚠️ PARTIAL: Got HTTP 422 but unexpected error message: $ERROR_MSG"
|
||||
fi
|
||||
else
|
||||
echo "❌ FAIL: Expected HTTP 422, got $STATUS2"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Test 3: Create signature WITHOUT character_eve_id (fallback test)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
echo "Test 3: Create signature WITHOUT character_eve_id (fallback)"
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
|
||||
PAYLOAD3=$(cat <<EOF
|
||||
{
|
||||
"solar_system_id": $SOLAR_SYSTEM_ID,
|
||||
"eve_id": "TEST-003",
|
||||
"group": "data",
|
||||
"kind": "cosmic_signature",
|
||||
"name": "Test Sig 3"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "Request:"
|
||||
echo "$PAYLOAD3" | jq '.'
|
||||
echo ""
|
||||
|
||||
RAW3=$(make_request POST "$API_BASE_URL/api/maps/$MAP_SLUG/signatures" "$PAYLOAD3")
|
||||
STATUS3=$(parse_status "$RAW3")
|
||||
RESPONSE3=$(parse_response "$RAW3")
|
||||
|
||||
echo "Response (HTTP $STATUS3):"
|
||||
echo "$RESPONSE3" | jq '.'
|
||||
echo ""
|
||||
|
||||
if [ "$STATUS3" = "201" ]; then
|
||||
RETURNED_CHAR_ID=$(echo "$RESPONSE3" | jq -r '.data.character_eve_id')
|
||||
echo "✅ PASS: Signature created with fallback character_eve_id: $RETURNED_CHAR_ID"
|
||||
echo " (This should be the map owner's character)"
|
||||
SIG_ID_3=$(echo "$RESPONSE3" | jq -r '.data.id')
|
||||
else
|
||||
echo "❌ FAIL: Expected HTTP 201, got $STATUS3"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Test 4: Update signature with valid character_eve_id
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
if [ -n "${SIG_ID_1:-}" ]; then
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
echo "Test 4: Update signature with VALID character_eve_id"
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
|
||||
PAYLOAD4=$(cat <<EOF
|
||||
{
|
||||
"name": "Updated Test Sig 1",
|
||||
"character_eve_id": "$VALID_CHAR_ID",
|
||||
"description": "Updated via API"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "Request:"
|
||||
echo "$PAYLOAD4" | jq '.'
|
||||
echo ""
|
||||
|
||||
RAW4=$(make_request PUT "$API_BASE_URL/api/maps/$MAP_SLUG/signatures/$SIG_ID_1" "$PAYLOAD4")
|
||||
STATUS4=$(parse_status "$RAW4")
|
||||
RESPONSE4=$(parse_response "$RAW4")
|
||||
|
||||
echo "Response (HTTP $STATUS4):"
|
||||
echo "$RESPONSE4" | jq '.'
|
||||
echo ""
|
||||
|
||||
if [ "$STATUS4" = "200" ]; then
|
||||
RETURNED_CHAR_ID=$(echo "$RESPONSE4" | jq -r '.data.character_eve_id')
|
||||
if [ "$RETURNED_CHAR_ID" = "$VALID_CHAR_ID" ]; then
|
||||
echo "✅ PASS: Signature updated with correct character_eve_id: $RETURNED_CHAR_ID"
|
||||
else
|
||||
echo "❌ FAIL: Expected character_eve_id=$VALID_CHAR_ID, got $RETURNED_CHAR_ID"
|
||||
fi
|
||||
else
|
||||
echo "❌ FAIL: Expected HTTP 200, got $STATUS4"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Test 5: Update signature with invalid character_eve_id
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
if [ -n "${SIG_ID_3:-}" ]; then
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
echo "Test 5: Update signature with INVALID character_eve_id"
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
|
||||
PAYLOAD5=$(cat <<EOF
|
||||
{
|
||||
"name": "Should Fail",
|
||||
"character_eve_id": "$INVALID_CHAR_ID"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "Request:"
|
||||
echo "$PAYLOAD5" | jq '.'
|
||||
echo ""
|
||||
|
||||
RAW5=$(make_request PUT "$API_BASE_URL/api/maps/$MAP_SLUG/signatures/$SIG_ID_3" "$PAYLOAD5")
|
||||
STATUS5=$(parse_status "$RAW5")
|
||||
RESPONSE5=$(parse_response "$RAW5")
|
||||
|
||||
echo "Response (HTTP $STATUS5):"
|
||||
echo "$RESPONSE5" | jq '.'
|
||||
echo ""
|
||||
|
||||
if [ "$STATUS5" = "422" ]; then
|
||||
ERROR_MSG=$(echo "$RESPONSE5" | jq -r '.error // empty')
|
||||
if [ "$ERROR_MSG" = "invalid_character" ]; then
|
||||
echo "✅ PASS: Correctly rejected invalid character_eve_id with error: $ERROR_MSG"
|
||||
else
|
||||
echo "⚠️ PARTIAL: Got HTTP 422 but unexpected error message: $ERROR_MSG"
|
||||
fi
|
||||
else
|
||||
echo "❌ FAIL: Expected HTTP 422, got $STATUS5"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Cleanup (optional)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
echo "Cleanup"
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
echo "Created signature IDs: ${SIG_ID_1:-none} ${SIG_ID_3:-none}"
|
||||
echo ""
|
||||
echo "To clean up manually, delete these signatures via the UI or API:"
|
||||
for sig_id in ${SIG_ID_1:-} ${SIG_ID_3:-}; do
|
||||
if [ -n "$sig_id" ]; then
|
||||
echo " curl -X DELETE -H 'Authorization: Bearer \$API_TOKEN' \\"
|
||||
echo " $API_BASE_URL/api/maps/$MAP_SLUG/signatures/$sig_id"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
echo "═══════════════════════════════════════════════════════════════════"
|
||||
echo "Test Complete!"
|
||||
echo "═══════════════════════════════════════════════════════════════════"
|
||||
Reference in New Issue
Block a user