Compare commits

..

32 Commits

Author SHA1 Message Date
CI
e5a3eec8a1 chore: release version v1.70.4
Some checks failed
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-06-16 11:26:33 +00:00
Dmitry Popov
910352d66c Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-16 13:26:06 +02:00
Dmitry Popov
c4f02e7d55 fix(Core): Distribute tracking to minimal pool first 2025-06-16 13:26:03 +02:00
CI
6a1197ad83 chore: release version v1.70.3 2025-06-16 07:17:27 +00:00
Dmitry Popov
84c31bbb88 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-16 09:17:01 +02:00
Dmitry Popov
33f6c32306 fix(Core): Don't pause tracking for new pools 2025-06-16 09:16:58 +02:00
CI
5c71304d41 chore: release version v1.70.2
Some checks failed
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-06-15 17:22:28 +00:00
Dmitry Popov
bbaf04e977 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-15 19:21:57 +02:00
Dmitry Popov
ad5b2d2eb3 fix(Core): Invalidate character copr and ally data on map server start 2025-06-15 19:21:44 +02:00
CI
3c064baf8a chore: release version v1.70.1
Some checks failed
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-06-14 06:23:22 +00:00
Dmitry Popov
9e11b10d74 Merge pull request #424 from guarzo/guarzo/apiupdates 2025-06-14 10:22:58 +04:00
Guarzo
2fc45e00b4 fix: resolve api issue with custom name 2025-06-13 21:14:07 -04:00
CI
45eb08fc3a chore: release version v1.70.0
Some checks failed
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-06-11 16:30:02 +00:00
Dmitry Popov
fbcfae0200 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-11 18:29:36 +02:00
Dmitry Popov
9c4ce013ec feat(Core): Fix admin page error 2025-06-11 18:29:32 +02:00
CI
5dba7c12f0 chore: release version v1.69.1 2025-06-11 11:52:01 +00:00
Dmitry Popov
db793c80c8 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-11 13:51:32 +02:00
Dmitry Popov
aa4a3f1aa9 chore: release version v1.68.6 2025-06-11 13:51:29 +02:00
CI
3424639309 chore: release version v1.69.0 2025-06-11 11:43:54 +00:00
Dmitry Popov
0f309a29ba Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-11 13:43:18 +02:00
Dmitry Popov
e13b8846b8 feat(Core): Added multiple tracking pools support 2025-06-11 13:43:13 +02:00
CI
d5ea4d6129 chore: release version v1.68.6
Some checks failed
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-06-10 22:35:04 +00:00
Dmitry Popov
9d50bfedbd Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-11 00:34:36 +02:00
Dmitry Popov
b03410083c Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-11 00:34:27 +02:00
CI
a314b1e448 chore: release version v1.68.5 2025-06-10 22:34:26 +00:00
Dmitry Popov
e8a51a85c4 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-11 00:33:59 +02:00
Dmitry Popov
d4074f966c fix(Core): Fixed updating map options 2025-06-11 00:33:56 +02:00
CI
1413b41824 chore: release version v1.68.4
Some checks failed
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-06-10 07:45:48 +00:00
Dmitry Popov
379c1edec3 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-10 09:45:17 +02:00
Dmitry Popov
58b5bade9e chore: release version v1.68.2 2025-06-10 09:45:14 +02:00
CI
71aee4cd3e chore: release version v1.68.3
Some checks failed
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-06-09 17:58:52 +00:00
Dmitry Popov
10bab0cfa1 chore: release version v1.68.2 2025-06-09 19:55:36 +02:00
28 changed files with 959 additions and 107 deletions

View File

@@ -2,6 +2,89 @@
<!-- changelog -->
## [v1.70.4](https://github.com/wanderer-industries/wanderer/compare/v1.70.3...v1.70.4) (2025-06-16)
### Bug Fixes:
* Core: Distribute tracking to minimal pool first
## [v1.70.3](https://github.com/wanderer-industries/wanderer/compare/v1.70.2...v1.70.3) (2025-06-16)
### Bug Fixes:
* Core: Don't pause tracking for new pools
## [v1.70.2](https://github.com/wanderer-industries/wanderer/compare/v1.70.1...v1.70.2) (2025-06-15)
### Bug Fixes:
* Core: Invalidate character copr and ally data on map server start
## [v1.70.1](https://github.com/wanderer-industries/wanderer/compare/v1.70.0...v1.70.1) (2025-06-14)
### Bug Fixes:
* resolve api issue with custom name
## [v1.70.0](https://github.com/wanderer-industries/wanderer/compare/v1.69.1...v1.70.0) (2025-06-11)
### Features:
* Core: Fix admin page error
## [v1.69.1](https://github.com/wanderer-industries/wanderer/compare/v1.69.0...v1.69.1) (2025-06-11)
## [v1.69.0](https://github.com/wanderer-industries/wanderer/compare/v1.68.6...v1.69.0) (2025-06-11)
### Features:
* Core: Added multiple tracking pools support
## [v1.68.6](https://github.com/wanderer-industries/wanderer/compare/v1.68.5...v1.68.6) (2025-06-10)
## [v1.68.5](https://github.com/wanderer-industries/wanderer/compare/v1.68.4...v1.68.5) (2025-06-10)
### Bug Fixes:
* Core: Fixed updating map options
## [v1.68.4](https://github.com/wanderer-industries/wanderer/compare/v1.68.3...v1.68.4) (2025-06-10)
## [v1.68.3](https://github.com/wanderer-industries/wanderer/compare/v1.68.2...v1.68.3) (2025-06-09)
## [v1.68.2](https://github.com/wanderer-industries/wanderer/compare/v1.68.1...v1.68.2) (2025-06-09)

View File

@@ -117,8 +117,11 @@ config :wanderer_app,
admins: admins,
corp_id: System.get_env("WANDERER_CORP_ID", "-1") |> String.to_integer(),
corp_wallet: System.get_env("WANDERER_CORP_WALLET", ""),
corp_wallet_eve_id: System.get_env("WANDERER_CORP_WALLET_EVE_ID", "-1") |> String.to_integer(),
corp_wallet_eve_id: System.get_env("WANDERER_CORP_WALLET_EVE_ID", "-1"),
public_api_disabled: public_api_disabled,
active_tracking_pool: System.get_env("WANDERER_ACTIVE_TRACKING_POOL", "default"),
tracking_pool_max_size:
System.get_env("WANDERER_TRACKING_POOL_MAX_SIZE", "300") |> String.to_integer(),
character_tracking_pause_disabled:
System.get_env("WANDERER_CHARACTER_TRACKING_PAUSE_DISABLED", "true")
|> String.to_existing_atom(),
@@ -174,11 +177,31 @@ config :ueberauth, WandererApp.Ueberauth.Strategy.Eve.OAuth,
client_id: {WandererApp.Ueberauth, :client_id},
client_secret: {WandererApp.Ueberauth, :client_secret},
client_id_default: System.get_env("EVE_CLIENT_ID", "<EVE_CLIENT_ID>"),
client_id_1: System.get_env("EVE_CLIENT_ID_1", ""),
client_id_2: System.get_env("EVE_CLIENT_ID_2", ""),
client_id_3: System.get_env("EVE_CLIENT_ID_3", ""),
client_id_4: System.get_env("EVE_CLIENT_ID_4", ""),
client_id_5: System.get_env("EVE_CLIENT_ID_5", ""),
client_id_6: System.get_env("EVE_CLIENT_ID_6", ""),
client_id_7: System.get_env("EVE_CLIENT_ID_7", ""),
client_id_8: System.get_env("EVE_CLIENT_ID_8", ""),
client_id_9: System.get_env("EVE_CLIENT_ID_9", ""),
client_id_10: System.get_env("EVE_CLIENT_ID_10", ""),
client_id_with_wallet:
System.get_env("EVE_CLIENT_WITH_WALLET_ID", "<EVE_CLIENT_WITH_WALLET_ID>"),
client_id_with_corp_wallet:
System.get_env("EVE_CLIENT_WITH_CORP_WALLET_ID", "<EVE_CLIENT_WITH_CORP_WALLET_ID>"),
client_secret_default: System.get_env("EVE_CLIENT_SECRET", "<EVE_CLIENT_SECRET>"),
client_secret_1: System.get_env("EVE_CLIENT_SECRET_1", ""),
client_secret_2: System.get_env("EVE_CLIENT_SECRET_2", ""),
client_secret_3: System.get_env("EVE_CLIENT_SECRET_3", ""),
client_secret_4: System.get_env("EVE_CLIENT_SECRET_4", ""),
client_secret_5: System.get_env("EVE_CLIENT_SECRET_5", ""),
client_secret_6: System.get_env("EVE_CLIENT_SECRET_6", ""),
client_secret_7: System.get_env("EVE_CLIENT_SECRET_7", ""),
client_secret_8: System.get_env("EVE_CLIENT_SECRET_8", ""),
client_secret_9: System.get_env("EVE_CLIENT_SECRET_9", ""),
client_secret_10: System.get_env("EVE_CLIENT_SECRET_10", ""),
client_secret_with_wallet:
System.get_env("EVE_CLIENT_WITH_WALLET_SECRET", "<EVE_CLIENT_WITH_WALLET_SECRET>"),
client_secret_with_corp_wallet:

View File

@@ -24,6 +24,7 @@ defmodule WandererApp.Api.Character do
define(:update_alliance, action: :update_alliance)
define(:update_wallet_balance, action: :update_wallet_balance)
define(:mark_as_deleted, action: :mark_as_deleted)
define(:last_active, action: :last_active)
define(:by_id,
get_by: [:id],
@@ -47,7 +48,8 @@ defmodule WandererApp.Api.Character do
:access_token,
:refresh_token,
:expires_at,
:scopes
:scopes,
:tracking_pool
]
defaults [:create, :read, :destroy]
@@ -72,6 +74,12 @@ defmodule WandererApp.Api.Character do
filter(expr(user_id == ^arg(:user_id) and deleted == false))
end
read :last_active do
argument(:from, :utc_datetime, allow_nil?: false)
filter(expr(updated_at > ^arg(:from)))
end
update :assign do
accept []
require_atomic? false
@@ -85,7 +93,7 @@ defmodule WandererApp.Api.Character do
update :update do
require_atomic? false
accept([:name, :access_token, :refresh_token, :expires_at, :scopes])
accept([:name, :access_token, :refresh_token, :expires_at, :scopes, :tracking_pool])
change(set_attribute(:deleted, false))
end
@@ -198,6 +206,7 @@ defmodule WandererApp.Api.Character do
attribute :alliance_name, :string
attribute :alliance_ticker, :string
attribute :eve_wallet_balance, :float
attribute :tracking_pool, :string
create_timestamp(:inserted_at)
update_timestamp(:updated_at)

View File

@@ -27,6 +27,7 @@ defmodule WandererApp.Application do
}
},
WandererApp.Cache,
Supervisor.child_spec({Cachex, name: :esi_auth_cache}, id: :esi_auth_cache_worker),
Supervisor.child_spec({Cachex, name: :api_cache}, id: :api_cache_worker),
Supervisor.child_spec({Cachex, name: :system_static_info_cache},
id: :system_static_info_cache_worker
@@ -40,6 +41,7 @@ defmodule WandererApp.Application do
Supervisor.child_spec({Cachex, name: :tracked_characters},
id: :tracked_characters_cache_worker
),
WandererApp.Esi.InitClientsTask,
WandererApp.Scheduler,
{Registry, keys: :unique, name: WandererApp.MapRegistry},
{Registry, keys: :unique, name: WandererApp.Character.TrackerRegistry},

View File

@@ -213,6 +213,22 @@ defmodule WandererApp.Character do
def can_track_corp_wallet?(_), do: false
@decorate cacheable(
cache: WandererApp.Cache,
key: "can_pause_tracking-#{character_id}"
)
def can_pause_tracking?(character_id) do
case get_character(character_id) do
{:ok, %{tracking_pool: tracking_pool} = character} when not is_nil(character) ->
not WandererApp.Env.character_tracking_pause_disabled?() &&
not can_track_wallet?(character) &&
(is_nil(tracking_pool) || tracking_pool == "default")
_ ->
true
end
end
def get_ship(%{ship: ship_type_id, ship_name: ship_name} = _character)
when not is_nil(ship_type_id) and is_integer(ship_type_id) do
ship_type_id

View File

@@ -110,7 +110,7 @@ defmodule WandererApp.Character.Tracker do
end
defp pause_tracking(character_id) do
if not WandererApp.Env.character_tracking_pause_disabled?() &&
if WandererApp.Character.can_pause_tracking?(character_id) &&
not WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused") do
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
WandererApp.Cache.delete("character:#{character_id}:online_error_time")
@@ -166,7 +166,7 @@ defmodule WandererApp.Character.Tracker do
def update_online(%{track_online: true, character_id: character_id} = character_state) do
case WandererApp.Character.get_character(character_id) do
{:ok, %{eve_id: eve_id, access_token: access_token}}
{:ok, %{eve_id: eve_id, access_token: access_token, tracking_pool: tracking_pool}}
when not is_nil(access_token) ->
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
@@ -233,7 +233,7 @@ defmodule WandererApp.Character.Tracker do
{:error, :error_limited, headers} ->
reset_timeout = get_reset_timeout(headers)
Logger.warning(".")
Logger.warning("#{inspect(tracking_pool)} ..")
WandererApp.Cache.put(
"character:#{character_id}:online_forbidden",
@@ -287,15 +287,15 @@ defmodule WandererApp.Character.Tracker do
defp get_reset_timeout(_headers, default_timeout), do: default_timeout
def update_info(character_id) do
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
WandererApp.Cache.has_key?("character:#{character_id}:info_forbidden") ||
(WandererApp.Cache.has_key?("character:#{character_id}:info_forbidden") ||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
|> case do
true ->
{:error, :skipped}
false ->
{:ok, %{eve_id: eve_id}} = WandererApp.Character.get_character(character_id)
{:ok, %{eve_id: eve_id, tracking_pool: tracking_pool}} =
WandererApp.Character.get_character(character_id)
case WandererApp.Esi.get_character_info(eve_id) do
{:ok, _info} ->
@@ -320,7 +320,7 @@ defmodule WandererApp.Character.Tracker do
{:error, :error_limited, headers} ->
reset_timeout = get_reset_timeout(headers)
Logger.warning(".")
Logger.warning("#{inspect(tracking_pool)} ..")
WandererApp.Cache.put(
"character:#{character_id}:info_forbidden",
@@ -358,7 +358,8 @@ defmodule WandererApp.Character.Tracker do
character_id
|> WandererApp.Character.get_character()
|> case do
{:ok, %{eve_id: eve_id, access_token: access_token}} when not is_nil(access_token) ->
{:ok, %{eve_id: eve_id, access_token: access_token, tracking_pool: tracking_pool}}
when not is_nil(access_token) ->
(WandererApp.Cache.has_key?("character:#{character_id}:online_forbidden") ||
WandererApp.Cache.has_key?("character:#{character_id}:ship_forbidden") ||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
@@ -397,7 +398,7 @@ defmodule WandererApp.Character.Tracker do
{:error, :error_limited, headers} ->
reset_timeout = get_reset_timeout(headers)
Logger.warning(".")
Logger.warning("#{inspect(tracking_pool)} ..")
WandererApp.Cache.put(
"character:#{character_id}:ship_forbidden",
@@ -462,7 +463,8 @@ defmodule WandererApp.Character.Tracker do
%{track_location: true, is_online: true, character_id: character_id} = character_state
) do
case WandererApp.Character.get_character(character_id) do
{:ok, %{eve_id: eve_id, access_token: access_token}} when not is_nil(access_token) ->
{:ok, %{eve_id: eve_id, access_token: access_token, tracking_pool: tracking_pool}}
when not is_nil(access_token) ->
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused")
|> case do
true ->
@@ -494,7 +496,7 @@ defmodule WandererApp.Character.Tracker do
{:error, :skipped}
{:error, :error_limited, headers} ->
Logger.warning(".")
Logger.warning("#{inspect(tracking_pool)} ..")
reset_timeout = get_reset_timeout(headers, @location_limit_ttl)
@@ -550,7 +552,8 @@ defmodule WandererApp.Character.Tracker do
character_id
|> WandererApp.Character.get_character()
|> case do
{:ok, %{eve_id: eve_id, access_token: access_token} = character}
{:ok,
%{eve_id: eve_id, access_token: access_token, tracking_pool: tracking_pool} = character}
when not is_nil(access_token) ->
character
|> WandererApp.Character.can_track_wallet?()
@@ -589,7 +592,7 @@ defmodule WandererApp.Character.Tracker do
{:error, :error_limited, headers} ->
reset_timeout = get_reset_timeout(headers)
Logger.warning(".")
Logger.warning("#{inspect(tracking_pool)} ..")
WandererApp.Cache.put(
"character:#{character_id}:wallet_forbidden",

View File

@@ -236,13 +236,17 @@ defmodule WandererApp.Character.TrackerPool do
characters
|> Task.async_stream(
fn character_id ->
WandererApp.TaskWrapper.start_link(
WandererApp.Character.Tracker,
:check_offline,
[
character_id
]
)
if WandererApp.Character.can_pause_tracking?(character_id) do
WandererApp.TaskWrapper.start_link(
WandererApp.Character.Tracker,
:check_offline,
[
character_id
]
)
else
:ok
end
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),

View File

@@ -0,0 +1,142 @@
defmodule WandererApp.Character.TrackingConfigUtils do
use Nebulex.Caching
@moduledoc false
@ttl :timer.minutes(5)
@last_active_character_minutes -1 * 60 * 24 * 7
@decorate cacheable(
cache: WandererApp.Cache,
key: "tracker-stats",
opts: [ttl: @ttl]
)
def load_tracker_stats() do
{:ok, characters} = get_active_characters()
admins_count =
characters |> Enum.filter(&WandererApp.Character.can_track_corp_wallet?/1) |> Enum.count()
with_wallets_count =
characters
|> Enum.filter(
&(WandererApp.Character.can_track_wallet?(&1) and
not WandererApp.Character.can_track_corp_wallet?(&1))
)
|> Enum.count()
default_count =
characters
|> Enum.filter(
&(is_nil(&1.tracking_pool) and not WandererApp.Character.can_track_wallet?(&1) and
not WandererApp.Character.can_track_corp_wallet?(&1))
)
|> Enum.count()
result = [
%{id: "admins", title: "Admins", value: admins_count},
%{id: "wallet", title: "With Wallet", value: with_wallets_count},
%{id: "default", title: "Default", value: default_count}
]
{:ok, pools_count} =
Cachex.get(
:esi_auth_cache,
"configs_total_count"
)
{:ok, pools} = get_pools_info(characters)
{:ok, result ++ pools}
end
def update_active_tracking_pool() do
{:ok, pools_count} =
Cachex.get(
:esi_auth_cache,
"configs_total_count"
)
active_pool =
if not is_nil(pools_count) && pools_count != 0 do
tracking_pool_max_size = WandererApp.Env.tracking_pool_max_size()
{:ok, characters} = get_active_characters()
{:ok, pools} = get_pools_info(characters)
minimal_pool_id =
pools
|> Enum.filter(&(&1.value < tracking_pool_max_size))
|> Enum.min_by(& &1.value)
|> Map.get(:id)
if not is_nil(minimal_pool_id) do
minimal_pool_id
else
"default"
end
else
"default"
end
Cachex.put(
:esi_auth_cache,
"active_pool",
active_pool
)
end
def get_active_pool!() do
Cachex.get(
:esi_auth_cache,
"active_pool"
)
|> case do
{:ok, active_pool} when not is_nil(active_pool) ->
active_pool
_ ->
"default"
end
end
defp get_active_characters() do
WandererApp.Api.Character.last_active(%{
from:
DateTime.utc_now()
|> DateTime.add(@last_active_character_minutes, :minute)
})
end
@decorate cacheable(
cache: WandererApp.Cache,
key: "character-pools-info",
opts: [ttl: @ttl]
)
defp get_pools_info(characters) do
{:ok, pools_count} =
Cachex.get(
:esi_auth_cache,
"configs_total_count"
)
if not is_nil(pools_count) && pools_count != 0 do
pools =
1..pools_count
|> Enum.map(fn pool_id ->
pools_character_count =
characters
|> Enum.filter(
&(&1.tracking_pool == "#{pool_id}" and
not WandererApp.Character.can_track_wallet?(&1) and
not WandererApp.Character.can_track_corp_wallet?(&1))
)
|> Enum.count()
%{id: "#{pool_id}", title: "Pool #{pool_id}", value: pools_character_count}
end)
{:ok, pools}
else
{:ok, []}
end
end
end

View File

@@ -146,7 +146,12 @@ defmodule WandererApp.Character.TransactionsTracker.Impl do
end
defp get_wallet_journal(
%{id: character_id, corporation_id: corporation_id, access_token: access_token} =
%{
id: character_id,
corporation_id: corporation_id,
access_token: access_token,
tracking_pool: tracking_pool
} =
_character,
division
)
@@ -164,7 +169,7 @@ defmodule WandererApp.Character.TransactionsTracker.Impl do
{:error, :forbidden}
{:error, :error_limited, _headers} ->
Logger.warning(".")
Logger.warning("#{inspect(tracking_pool)} ..")
{:error, :error_limited}
{:error, error} ->
@@ -176,7 +181,12 @@ defmodule WandererApp.Character.TransactionsTracker.Impl do
defp get_wallet_journal(_character, _division), do: {:error, :skipped}
defp update_corp_wallets(
%{id: character_id, corporation_id: corporation_id, access_token: access_token} =
%{
id: character_id,
corporation_id: corporation_id,
access_token: access_token,
tracking_pool: tracking_pool
} =
_character
)
when not is_nil(access_token) do
@@ -193,7 +203,7 @@ defmodule WandererApp.Character.TransactionsTracker.Impl do
{:error, :forbidden}
{:error, :error_limited, _headers} ->
Logger.warning(".")
Logger.warning("#{inspect(tracking_pool)} ..")
{:error, :error_limited}
{:error, error} ->

View File

@@ -16,6 +16,18 @@ defmodule WandererApp.Env do
def invites, do: get_key(:invites, false)
def map_subscriptions_enabled?, do: get_key(:map_subscriptions_enabled, false)
def public_api_disabled?, do: get_key(:public_api_disabled, false)
@decorate cacheable(
cache: WandererApp.Cache,
key: "active_tracking_pool"
)
def active_tracking_pool, do: get_key(:active_tracking_pool, "default")
@decorate cacheable(
cache: WandererApp.Cache,
key: "tracking_pool_max_size"
)
def tracking_pool_max_size, do: get_key(:tracking_pool_max_size, 300)
def character_tracking_pause_disabled?, do: get_key(:character_tracking_pause_disabled, true)
def character_api_disabled?, do: get_key(:character_api_disabled, false)
def zkill_preload_disabled?, do: get_key(:zkill_preload_disabled, false)
@@ -24,7 +36,7 @@ defmodule WandererApp.Env do
def admin_username, do: get_key(:admin_username)
def admin_password, do: get_key(:admin_password)
def corp_wallet, do: get_key(:corp_wallet, "")
def corp_wallet_eve_id, do: get_key(:corp_wallet_eve_id, -1)
def corp_wallet_eve_id, do: get_key(:corp_wallet_eve_id, "-1")
def corp_eve_id, do: get_key(:corp_id, -1)
def subscription_settings, do: get_key(:subscription_settings)

View File

@@ -671,13 +671,20 @@ defmodule WandererApp.Esi.ApiClient do
end
defp refresh_token(character_id) do
{:ok, %{expires_at: expires_at, refresh_token: refresh_token, scopes: scopes} = character} =
{:ok,
%{
expires_at: expires_at,
refresh_token: refresh_token,
scopes: scopes,
tracking_pool: tracking_pool
} = character} =
WandererApp.Character.get_character(character_id)
refresh_token_result =
WandererApp.Ueberauth.Strategy.Eve.OAuth.get_refresh_token([],
with_wallet: WandererApp.Character.can_track_wallet?(character),
is_admin?: WandererApp.Character.can_track_corp_wallet?(character),
tracking_pool: tracking_pool,
token: %OAuth2.AccessToken{refresh_token: refresh_token}
)

View File

@@ -138,13 +138,8 @@ defmodule WandererApp.Map do
def add_characters!(map, []), do: map
def add_characters!(%{map_id: map_id} = map, [character | rest]) do
case add_character(map_id, character) do
:ok ->
add_characters!(map, rest)
{:error, :already_exists} ->
add_characters!(map, rest)
end
add_character(map_id, character)
add_characters!(map, rest)
end
def add_character(
@@ -172,15 +167,15 @@ defmodule WandererApp.Map do
map_id
|> update_map(%{characters: [character_id | characters]})
WandererApp.Cache.insert(
"map:#{map_id}:character:#{character_id}:alliance_id",
alliance_id
)
# WandererApp.Cache.insert(
# "map:#{map_id}:character:#{character_id}:alliance_id",
# alliance_id
# )
WandererApp.Cache.insert(
"map:#{map_id}:character:#{character_id}:corporation_id",
corporation_id
)
# WandererApp.Cache.insert(
# "map:#{map_id}:character:#{character_id}:corporation_id",
# corporation_id
# )
# WandererApp.Cache.insert(
# "map:#{map_id}:character:#{character_id}:solar_system_id",
@@ -294,14 +289,16 @@ defmodule WandererApp.Map do
map_id
|> update_map(%{characters_limit: characters_limit, hubs_limit: hubs_limit})
map
map_id
|> get_map!()
end
def update_options!(%{map_id: map_id} = map, options) do
map_id
|> update_map(%{options: options})
map
map_id
|> get_map!()
end
def add_systems!(map, []), do: map

View File

@@ -76,6 +76,11 @@ defmodule WandererApp.Map.Operations do
{:ok, map()} | {:skip, :exists} | {:error, String.t()}
defdelegate create_connection(map_id, attrs, char_id), to: Connections
@doc "Create a connection from a Plug.Conn"
@spec create_connection(Plug.Conn.t(), map()) ::
{:ok, :created} | {:skip, :exists} | {:error, atom()}
defdelegate create_connection(conn, attrs), to: Connections
@doc "Update a connection"
@spec update_connection(String.t(), String.t(), map()) ::
{:ok, map()} | {:error, String.t()}

View File

@@ -212,7 +212,7 @@ defmodule WandererApp.Map.Server do
do:
map_id
|> map_pid!
|> GenServer.call({&Impl.update_subscription_settings/2, [settings]})
|> GenServer.cast({&Impl.update_subscription_settings/2, [settings]})
def delete_connection(map_id, connection_info) when is_binary(map_id),
do:

View File

@@ -5,9 +5,10 @@ defmodule WandererApp.Map.Operations.Connections do
"""
require Logger
alias WandererApp.Map.Server.{ConnectionsImpl, Server}
alias WandererApp.Map.Server
alias Ash.Error.Invalid
alias WandererApp.MapConnectionRepo
alias WandererApp.CachedInfo
# Connection type constants
@connection_type_wormhole 0
@@ -20,7 +21,7 @@ defmodule WandererApp.Map.Operations.Connections do
@xlarge_ship_size 3
# System class constants
@c1_system_class "C1"
@c1_system_class 1
@doc """
Creates a connection between two systems, applying special rules for C1 wormholes.
@@ -34,8 +35,8 @@ defmodule WandererApp.Map.Operations.Connections do
defp do_create(attrs, map_id, char_id) do
with {:ok, source} <- parse_int(attrs["solar_system_source"], "solar_system_source"),
{:ok, target} <- parse_int(attrs["solar_system_target"], "solar_system_target"),
{:ok, src_info} <- ConnectionsImpl.get_system_static_info(source),
{:ok, tgt_info} <- ConnectionsImpl.get_system_static_info(target) do
{:ok, src_info} <- CachedInfo.get_system_static_info(source),
{:ok, tgt_info} <- CachedInfo.get_system_static_info(target) do
build_and_add_connection(attrs, map_id, char_id, src_info, tgt_info)
else
{:error, reason} -> handle_precondition_error(reason, attrs)
@@ -45,21 +46,28 @@ defmodule WandererApp.Map.Operations.Connections do
end
defp build_and_add_connection(attrs, map_id, char_id, src_info, tgt_info) do
info = %{
solar_system_source_id: src_info.solar_system_id,
solar_system_target_id: tgt_info.solar_system_id,
character_id: char_id,
type: parse_type(attrs["type"]),
ship_size_type: resolve_ship_size(attrs, src_info, tgt_info)
}
Logger.debug("[Connections] build_and_add_connection called with src_info: #{inspect(src_info)}, tgt_info: #{inspect(tgt_info)}")
case Server.add_connection(map_id, info) do
:ok -> {:ok, :created}
{:ok, []} -> log_warn_and(:inconsistent_state, info)
{:error, %Invalid{errors: errs}} = err ->
if Enum.any?(errs, &is_unique_constraint_error?/1), do: {:skip, :exists}, else: err
{:error, _} = err -> Logger.error("[add_connection] #{inspect(err)}"); {:error, :server_error}
other -> Logger.error("[add_connection] unexpected: #{inspect(other)}"); {:error, :unexpected_error}
# Guard against nil info
if is_nil(src_info) or is_nil(tgt_info) do
{:error, :invalid_system_info}
else
info = %{
solar_system_source_id: src_info.solar_system_id,
solar_system_target_id: tgt_info.solar_system_id,
character_id: char_id,
type: parse_type(attrs["type"]),
ship_size_type: resolve_ship_size(attrs, src_info, tgt_info)
}
case Server.add_connection(map_id, info) do
:ok -> {:ok, :created}
{:ok, []} -> log_warn_and(:inconsistent_state, info)
{:error, %Invalid{errors: errs}} = err ->
if Enum.any?(errs, &is_unique_constraint_error?/1), do: {:skip, :exists}, else: err
{:error, _} = err -> Logger.error("[add_connection] #{inspect(err)}"); {:error, :server_error}
other -> Logger.error("[add_connection] unexpected: #{inspect(other)}"); {:error, :unexpected_error}
end
end
end

View File

@@ -192,10 +192,13 @@ defmodule WandererApp.Ueberauth.Strategy.Eve do
end
defp oauth_client_options_from_conn(conn, with_wallet, is_admin?) do
tracking_pool = WandererApp.Character.TrackingConfigUtils.get_active_pool!()
base_options = [
redirect_uri: callback_url(conn),
with_wallet: with_wallet,
is_admin?: is_admin?
is_admin?: is_admin?,
tracking_pool: tracking_pool
]
request_options = conn.private[:ueberauth_request_options].options

View File

@@ -0,0 +1,79 @@
defmodule WandererApp.Esi.InitClientsTask do
use Task
require Logger
def start_link(arg) do
Task.start_link(__MODULE__, :run, [arg])
end
def run(_arg) do
Logger.info("starting")
cache_clients()
WandererApp.Character.TrackingConfigUtils.update_active_tracking_pool()
end
defp cache_clients() do
config = Application.get_env(:ueberauth, WandererApp.Ueberauth.Strategy.Eve.OAuth, [])
cache_client("default", %{
client_id: config[:client_id_default],
client_secret: config[:client_secret_default]
})
Enum.each(1..10, fn index ->
client_id = config["client_id_#{index}" |> String.to_atom()]
client_secret = config["client_secret_#{index}" |> String.to_atom()]
if client_id != "" && client_secret != "" do
cache_client(index, %{
client_id: client_id,
client_secret: client_secret
})
end
end)
end
defp cache_client(id, config) do
config_uuid = UUID.uuid4()
config =
config
|> Map.merge(%{
id: id,
uuid: config_uuid
})
Cachex.put(
:esi_auth_cache,
"config_#{id}",
config
)
Cachex.put(
:esi_auth_cache,
config_uuid,
config
)
# Cachex.put(
# :esi_auth_cache,
# "config_uuid_#{id}",
# config_uuid
# )
configs_total_count =
if id == "default" do
0
else
id
end
Cachex.put(
:esi_auth_cache,
"configs_total_count",
configs_total_count
)
end
end

View File

@@ -2,26 +2,50 @@ defmodule WandererApp.Ueberauth do
@moduledoc false
def client_id(opts \\ []) do
config = _get_config()
config = get_config()
tracking_pool = Keyword.get(opts, :tracking_pool)
cond do
Keyword.get(opts, :is_admin?) -> config[:client_id_with_corp_wallet]
Keyword.get(opts, :with_wallet) -> config[:client_id_with_wallet]
not is_nil(tracking_pool) -> get_settings(tracking_pool)[:client_id]
true -> config[:client_id_default]
end
end
def client_secret(opts \\ []) do
config = _get_config()
config = get_config()
tracking_pool = Keyword.get(opts, :tracking_pool)
cond do
Keyword.get(opts, :is_admin?) -> config[:client_secret_with_corp_wallet]
Keyword.get(opts, :with_wallet) -> config[:client_secret_with_wallet]
not is_nil(tracking_pool) -> get_settings(tracking_pool)[:client_secret]
true -> config[:client_secret_default]
end
end
defp _get_config() do
defp get_settings(nil) do
{:ok, esi_config} =
Cachex.get(
:esi_auth_cache,
"config_default"
)
esi_config
end
defp get_settings(tracking_pool) do
{:ok, esi_config} =
Cachex.get(
:esi_auth_cache,
"config_#{tracking_pool}"
)
esi_config
end
defp get_config() do
Application.get_env(:ueberauth, WandererApp.Ueberauth.Strategy.Eve.OAuth, [])
end
end

View File

@@ -8,13 +8,16 @@ defmodule WandererAppWeb.AuthController do
require Logger
def callback(%{assigns: %{ueberauth_auth: auth, current_user: user} = _assigns} = conn, _params) do
active_tracking_pool = WandererApp.Character.TrackingConfigUtils.get_active_pool!()
character_data = %{
eve_id: "#{auth.info.email}",
name: auth.info.name,
access_token: auth.credentials.token,
refresh_token: auth.credentials.refresh_token,
expires_at: auth.credentials.expires_at,
scopes: auth.credentials.scopes
scopes: auth.credentials.scopes,
tracking_pool: active_tracking_pool
}
%{
@@ -29,7 +32,8 @@ defmodule WandererAppWeb.AuthController do
access_token: auth.credentials.token,
refresh_token: auth.credentials.refresh_token,
expires_at: auth.credentials.expires_at,
scopes: auth.credentials.scopes
scopes: auth.credentials.scopes,
tracking_pool: active_tracking_pool
}
{:ok, character} =
@@ -78,6 +82,8 @@ defmodule WandererAppWeb.AuthController do
maybe_update_character_user_id(character, user_id)
WandererApp.Character.TrackingConfigUtils.update_active_tracking_pool()
conn
|> put_session(:user_id, user_id)
|> redirect(to: "/characters")

View File

@@ -266,6 +266,10 @@ defmodule WandererAppWeb.MapConnectionAPIController do
conn
|> put_status(:bad_request)
|> json(%{error: reason})
{:error, :precondition_failed, _reason} ->
conn
|> put_status(:bad_request)
|> json(%{error: "Invalid request parameters"})
_other ->
conn
|> put_status(:internal_server_error)

View File

@@ -24,6 +24,7 @@ defmodule WandererAppWeb.MapSystemAPIController do
solar_system_id: %Schema{type: :integer, description: "EVE solar system ID"},
solar_system_name: %Schema{type: :string, description: "EVE solar system name"},
region_name: %Schema{type: :string, description: "EVE region name"},
custom_name: %Schema{type: :string, nullable: true, description: "Custom name for the system"},
position_x: %Schema{type: :integer, description: "X coordinate"},
position_y: %Schema{type: :integer, description: "Y coordinate"},
status: %Schema{
@@ -137,6 +138,7 @@ defmodule WandererAppWeb.MapSystemAPIController do
solar_system_id: 30_000_142,
solar_system_name: "Jita",
region_name: "The Forge",
custom_name: "Trade Hub Central",
position_x: 100.5,
position_y: 200.3,
status: "active",
@@ -179,6 +181,7 @@ defmodule WandererAppWeb.MapSystemAPIController do
solar_system_id: 30_000_142,
solar_system_name: "Jita",
region_name: "The Forge",
custom_name: "Trade Hub Central",
position_x: 100.5,
position_y: 200.3,
status: "active",

View File

@@ -262,13 +262,18 @@ defmodule WandererAppWeb.Helpers.APIUtils do
@spec map_system_to_json(struct()) :: map()
def map_system_to_json(system) do
original = get_original_name(system.solar_system_id)
# Determine the actual custom_name: if name differs from original, use it as custom_name
actual_custom_name = if system.name != original and system.name not in [nil, ""], do: system.name, else: system.custom_name
base =
Map.take(system, ~w(
id map_id solar_system_id custom_name temporary_name description tag labels
id map_id solar_system_id temporary_name description tag labels
locked visible status position_x position_y inserted_at updated_at
)a)
|> Map.put(:custom_name, actual_custom_name)
original = get_original_name(system.solar_system_id)
name = pick_name(system)
base
@@ -283,11 +288,15 @@ defmodule WandererAppWeb.Helpers.APIUtils do
end
end
defp pick_name(%{temporary_name: t, custom_name: c, solar_system_id: id}) do
defp pick_name(%{temporary_name: t, custom_name: c, name: n, solar_system_id: id} = system) do
original = get_original_name(id)
cond do
t not in [nil, ""] -> t
c not in [nil, ""] -> c
true -> get_original_name(id)
# If name differs from original, it's a custom name
n not in [nil, ""] and n != original -> n
true -> original
end
end

View File

@@ -62,6 +62,8 @@ defmodule WandererAppWeb.AdminLive do
user_character_ids: user_character_ids,
user_id: user_id,
invite_link: nil,
tracker_stats: [],
active_tracking_pool: "default",
map_subscriptions_enabled?: WandererApp.Env.map_subscriptions_enabled?(),
restrict_maps_creation?: WandererApp.Env.restrict_maps_creation?()
)}
@@ -294,6 +296,9 @@ defmodule WandererAppWeb.AdminLive do
defp apply_action(socket, :index, _params, uri) do
{:ok, invites} = WandererApp.Api.MapInvite.read()
{:ok, tracker_stats} = WandererApp.Character.TrackingConfigUtils.load_tracker_stats()
active_tracking_pool = WandererApp.Character.TrackingConfigUtils.get_active_pool!()
socket
|> assign(:active_page, :admin)
|> assign(:uri, URI.parse(uri))
@@ -308,6 +313,8 @@ defmodule WandererAppWeb.AdminLive do
|> assign(:form, to_form(%{"amount" => 500_000_000}))
|> assign(:unlink_character_form, to_form(%{}))
|> assign(:invites, invites)
|> assign(:tracker_stats, tracker_stats)
|> assign(:active_tracking_pool, active_tracking_pool)
end
defp apply_action(socket, :add_invite_link, _params, uri) do

View File

@@ -121,14 +121,14 @@
<.icon name="hero-plus-solid" class="w-6 h-6" />
<h3 class="card-title text-center text-md">New Invite</h3>
</.link>
<.table
id="invites"
rows={@invites}
class="!max-h-[40vh] !overflow-y-auto"
>
<:col :let={invite} label="Link">
<div class="flex items-center">
</h4>
<.table
id="invites"
rows={@invites}
class="!max-h-[40vh] !overflow-y-auto"
>
<:col :let={invite} label="Link">
<div class="flex items-center">
<div class="w-[200px] no-wrap truncate"><%= get_invite_link(@uri, invite.token) %></div>
<.button
phx-hook="CopyToClipboard"
@@ -141,30 +141,53 @@
Link copied
</div>
</.button>
</div>
</div>
</:col>
<:col :let={invite} label="Type">
<%= invite.type %>
</:col>
<:col :let={invite} label="Valid Until">
<div>
<p class="mb-0 text-xs text-gray-600 dark:text-zinc-100 whitespace-nowrap">
<.local_time id={invite.id} at={invite.valid_until} />
</p>
</div>
</:col>
<:action :let={invite}>
<.button
phx-click="delete-invite"
phx-value-id={invite.id}
data={[confirm: "Please confirm to delete invite!"]}
class="hover:text-white"
>
<.icon name="hero-trash-solid" class="w-4 h-4" />
</.button>
</:action>
</.table>
</div>
</div>
</div>
<div class="card dark:bg-zinc-800 dark:border-zinc-600">
<div class="card-body">
<div class="col-span-6">
<span class="text-gray-400 dark:text-gray-400">Tracking Pools</span>
<.table
id="tracking_pools"
rows={@tracker_stats}
class="!max-h-[40vh] !overflow-y-auto"
>
<:col :let={stat} label="Pool">
<div class="w-[200px] no-wrap truncate">{stat.title}</div>
</:col>
<:col :let={invite} label="Type">
<%= invite.type %>
<:col :let={stat} label="Active">
<div :if={stat.id == @active_tracking_pool}>true</div>
</:col>
<:col :let={invite} label="Valid Until">
<div>
<p class="mb-0 text-xs text-gray-600 dark:text-zinc-100 whitespace-nowrap">
<.local_time id={invite.id} at={invite.valid_until} />
</p>
</div>
<:col :let={stat} label="Count">
{stat.value}
</:col>
<:action :let={invite}>
<.button
phx-click="delete-invite"
phx-value-id={invite.id}
data={[confirm: "Please confirm to delete invite!"]}
class="hover:text-white"
>
<.icon name="hero-trash-solid" class="w-4 h-4" />
</.button>
</:action>
</.table>
</h4>
</div>
</div>
</div>

View File

@@ -56,10 +56,19 @@ defmodule WandererAppWeb.CharactersLive do
@impl true
def handle_event("authorize", form, socket) do
track_wallet = form |> Map.get("track_wallet", false)
token = UUID.uuid4(:default)
WandererApp.Cache.put("invite_#{token}", true, ttl: :timer.minutes(30))
{:noreply, socket |> push_navigate(to: ~p"/auth/eve?invite=#{token}&w=#{track_wallet}")}
active_pool = WandererApp.Character.TrackingConfigUtils.get_active_pool!()
{:ok, esi_config} =
Cachex.get(
:esi_auth_cache,
"config_#{active_pool}"
)
WandererApp.Cache.put("invite_#{esi_config.uuid}", true, ttl: :timer.minutes(30))
{:noreply,
socket |> push_navigate(to: ~p"/auth/eve?invite=#{esi_config.uuid}&w=#{track_wallet}")}
end
@impl true

View File

@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
@source_url "https://github.com/wanderer-industries/wanderer"
@version "1.68.2"
@version "1.70.4"
def project do
[

View File

@@ -0,0 +1,21 @@
defmodule WandererApp.Repo.Migrations.AddCharacterTrackingPool do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:character_v1) do
add :tracking_pool, :text
end
end
def down do
alter table(:character_v1) do
remove :tracking_pool
end
end
end

View File

@@ -0,0 +1,343 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_id",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "online",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "deleted",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "scopes",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "character_owner_hash",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "token_type",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "expires_at",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship_item_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_ticker",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_ticker",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "tracking_pool",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "character_v1_user_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "user_v1"
},
"size": null,
"source": "user_id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_eve_wallet_balance",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_location",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_ship",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_solar_system_id",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_structure_id",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_station_id",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_access_token",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_refresh_token",
"type": "binary"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "4C6974D764592ED3279CFD3FF85CE89B146C7E16628F0A4278E319A4A5E5FD8C",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "character_v1_unique_eve_id_index",
"keys": [
{
"type": "atom",
"value": "eve_id"
}
],
"name": "unique_eve_id",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "character_v1"
}