Compare commits

...

79 Commits

Author SHA1 Message Date
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
CI
698350b0f7 chore: release version v1.68.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-09 14:56:49 +00:00
Dmitry Popov
a97cf25031 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-09 16:56:11 +02:00
Dmitry Popov
8302d088bd fix(Core): Fixed character auth with wallet (on characters page) 2025-06-09 16:56:08 +02:00
CI
64788e73de chore: release version v1.68.1 2025-06-09 12:25:10 +00:00
Dmitry Popov
114fd471e8 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-09 14:24:37 +02:00
Dmitry Popov
b24a3120d3 fix(Core): Fixed auth from welcome page if invites disabled 2025-06-09 14:24:35 +02:00
CI
c5f93b3d0a chore: release version v1.68.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-09 08:20:31 +00:00
Dmitry Popov
79290e4721 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-09 10:19:56 +02:00
Dmitry Popov
984e126f23 feat(Core): Added invites store support 2025-06-09 10:19:53 +02:00
CI
cd1ad31aed chore: release version v1.67.5
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-08 19:05:08 +00:00
Dmitry Popov
1e3f6cf9e7 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-08 21:04:38 +02:00
Dmitry Popov
9c6ccd9a8a fix(Core): Added back ARM docker image build 2025-06-08 21:04:34 +02:00
CI
681ba21d39 chore: release version v1.67.4 2025-06-08 18:56:40 +00:00
Dmitry Popov
aef62189ee Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-08 20:56:07 +02:00
Dmitry Popov
09f70ac817 fix(Core): Fixed issue with system splash updates 2025-06-08 20:55:57 +02:00
CI
1eacb22143 chore: release version v1.67.3 2025-06-08 18:55:40 +00:00
Dmitry Popov
8524bad377 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-08 20:55:10 +02:00
Dmitry Popov
9d899243d1 fix(Core): Fixed issue with system splash updates 2025-06-08 20:54:59 +02:00
CI
9acf20a639 chore: release version v1.67.2 2025-06-08 16:57:01 +00:00
Dmitry Popov
71ef6b2e82 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-08 18:56:35 +02:00
Dmitry Popov
5e34d95dd2 chore: release version v1.66.25 2025-06-08 18:56:32 +02:00
CI
25a809c064 chore: release version v1.67.1 2025-06-08 16:46:28 +00:00
Dmitry Popov
f760498150 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-08 18:45:56 +02:00
Dmitry Popov
328301a375 chore: release version v1.66.25 2025-06-08 18:45:53 +02:00
CI
f28e7ebbbb chore: release version v1.67.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-08 14:27:48 +00:00
Dmitry Popov
bfa84af71e Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-08 16:24:32 +02:00
Dmitry Popov
42cd1ba976 feat(Core): Added support for WANDERER_CHARACTER_TRACKING_PAUSE_DISABLED env variable to pause inactive character trackers 2025-06-08 16:24:29 +02:00
CI
88cba866fd chore: release version v1.66.25 2025-06-08 11:33:36 +00:00
Dmitry Popov
af2876a84b Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-08 13:33:09 +02:00
Dmitry Popov
c5b15bfa78 fix(Core): Disabled kills fetching based on env settings 2025-06-08 13:33:05 +02:00
CI
85f00a63c2 chore: release version v1.66.24 2025-06-08 09:27:32 +00:00
Dmitry Popov
05f427bcd7 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-08 11:26:53 +02:00
Dmitry Popov
69f4c41534 chore: release version v1.66.15 2025-06-08 11:26:50 +02:00
CI
30b9239a8b chore: release version v1.66.23 2025-06-08 09:15:07 +00:00
Dmitry Popov
2061a83c59 chore: release version v1.66.15 2025-06-08 11:14:30 +02:00
Dmitry Popov
24e723de07 Merge branch 'tracking-controls' 2025-06-08 11:10:22 +02:00
CI
5a927e5ba5 chore: release version v1.66.22
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-07 16:11:48 +00:00
Dmitry Popov
10fafcf59f Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-07 18:11:17 +02:00
Dmitry Popov
be87591801 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-07 18:11:14 +02:00
CI
086d4378d3 chore: release version v1.66.21 2025-06-07 16:03:22 +00:00
Dmitry Popov
e982275905 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-07 18:02:37 +02:00
Dmitry Popov
77c02703e9 fix(Core): Fixed kills fetching based on env settings 2025-06-07 18:02:34 +02:00
CI
0ef27d4f95 chore: release version v1.66.20
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-07 15:41:18 +00:00
Dmitry Popov
5edc27744e Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-07 17:40:52 +02:00
Dmitry Popov
02ff887fee Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-07 17:40:48 +02:00
CI
3a30eeb59f chore: release version v1.66.19 2025-06-07 15:31:53 +00:00
Dmitry Popov
79af8fb601 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-07 17:31:09 +02:00
Dmitry Popov
f9f00faa0e fix(Core): Added check for offline characters timeouts 2025-06-07 17:31:05 +02:00
CI
a3c41e84e4 chore: release version v1.66.18 2025-06-07 14:55:06 +00:00
Dmitry Popov
7f21f33351 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-06-07 16:54:40 +02:00
Dmitry Popov
568f682cee chore: release version v1.66.16 2025-06-07 16:54:36 +02:00
CI
901c4c8ca4 chore: release version v1.66.17 2025-06-07 14:44:50 +00:00
Dmitry Popov
3dbba97f9c fix(Core): Increased tracking pause timeout for offline characters up to 10 hours 2025-06-07 16:44:18 +02:00
CI
3475620267 chore: release version v1.66.16 2025-06-07 13:41:56 +00:00
Dmitry Popov
8936a5e5d8 chore: release version v1.66.15 2025-06-07 15:41:22 +02:00
Dmitry Popov
719e34f9bc fix(Core): Increased tracking pause timeout for offline characters up to 10 hours 2025-06-07 15:41:06 +02:00
42 changed files with 1831 additions and 222 deletions

View File

@@ -2,6 +2,205 @@
<!-- changelog -->
## [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)
### Bug Fixes:
* Core: Fixed character auth with wallet (on characters page)
## [v1.68.1](https://github.com/wanderer-industries/wanderer/compare/v1.68.0...v1.68.1) (2025-06-09)
### Bug Fixes:
* Core: Fixed auth from welcome page if invites disabled
## [v1.68.0](https://github.com/wanderer-industries/wanderer/compare/v1.67.5...v1.68.0) (2025-06-09)
### Features:
* Core: Added invites store support
## [v1.67.5](https://github.com/wanderer-industries/wanderer/compare/v1.67.4...v1.67.5) (2025-06-08)
### Bug Fixes:
* Core: Added back ARM docker image build
## [v1.67.4](https://github.com/wanderer-industries/wanderer/compare/v1.67.3...v1.67.4) (2025-06-08)
### Bug Fixes:
* Core: Fixed issue with system splash updates
## [v1.67.3](https://github.com/wanderer-industries/wanderer/compare/v1.67.2...v1.67.3) (2025-06-08)
### Bug Fixes:
* Core: Fixed issue with system splash updates
## [v1.67.2](https://github.com/wanderer-industries/wanderer/compare/v1.67.1...v1.67.2) (2025-06-08)
## [v1.67.1](https://github.com/wanderer-industries/wanderer/compare/v1.67.0...v1.67.1) (2025-06-08)
## [v1.67.0](https://github.com/wanderer-industries/wanderer/compare/v1.66.25...v1.67.0) (2025-06-08)
### Features:
* Core: Added support for WANDERER_CHARACTER_TRACKING_PAUSE_DISABLED env variable to pause inactive character trackers
## [v1.66.25](https://github.com/wanderer-industries/wanderer/compare/v1.66.24...v1.66.25) (2025-06-08)
### Bug Fixes:
* Core: Disabled kills fetching based on env settings
## [v1.66.24](https://github.com/wanderer-industries/wanderer/compare/v1.66.23...v1.66.24) (2025-06-08)
## [v1.66.23](https://github.com/wanderer-industries/wanderer/compare/v1.66.22...v1.66.23) (2025-06-08)
## [v1.66.22](https://github.com/wanderer-industries/wanderer/compare/v1.66.21...v1.66.22) (2025-06-07)
## [v1.66.21](https://github.com/wanderer-industries/wanderer/compare/v1.66.20...v1.66.21) (2025-06-07)
### Bug Fixes:
* Core: Fixed kills fetching based on env settings
## [v1.66.20](https://github.com/wanderer-industries/wanderer/compare/v1.66.19...v1.66.20) (2025-06-07)
## [v1.66.19](https://github.com/wanderer-industries/wanderer/compare/v1.66.18...v1.66.19) (2025-06-07)
### Bug Fixes:
* Core: Added check for offline characters timeouts
## [v1.66.18](https://github.com/wanderer-industries/wanderer/compare/v1.66.17...v1.66.18) (2025-06-07)
## [v1.66.17](https://github.com/wanderer-industries/wanderer/compare/v1.66.16...v1.66.17) (2025-06-07)
### Bug Fixes:
* Core: Increased tracking pause timeout for offline characters up to 10 hours
## [v1.66.16](https://github.com/wanderer-industries/wanderer/compare/v1.66.15...v1.66.16) (2025-06-07)
### Bug Fixes:
* Core: Increased tracking pause timeout for offline characters up to 10 hours
## [v1.66.15](https://github.com/wanderer-industries/wanderer/compare/v1.66.14...v1.66.15) (2025-06-07)

View File

@@ -57,8 +57,8 @@ export const Characters = ({ data }: CharactersProps) => {
<>
<span
className={clsx(
'absolute top-[2px] left-[2px] w-[9px] h-[9px]',
'text-yellow-500 text-[9px] rounded-[1px] z-10 hover:hidden',
'absolute flex flex-col p-[2px] top-[0px] left-[0px] w-[35px] h-[35px]',
'text-yellow-500 text-[9px] z-10 bg-gray-800/40',
'pi',
PrimeIcons.PAUSE,
)}

View File

@@ -117,7 +117,12 @@ 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"),
public_api_disabled: public_api_disabled,
active_tracking_pool: System.get_env("WANDERER_ACTIVE_TRACKING_POOL", "default"),
character_tracking_pause_disabled:
System.get_env("WANDERER_CHARACTER_TRACKING_PAUSE_DISABLED", "true")
|> String.to_existing_atom(),
character_api_disabled:
System.get_env("WANDERER_CHARACTER_API_DISABLED", "true") |> String.to_existing_atom(),
zkill_preload_disabled:
@@ -170,11 +175,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

@@ -29,5 +29,6 @@ defmodule WandererApp.Api do
resource WandererApp.Api.CorpWalletTransaction
resource WandererApp.Api.License
resource WandererApp.Api.MapPing
resource WandererApp.Api.MapInvite
end
end

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

@@ -0,0 +1,96 @@
defmodule WandererApp.Api.MapInvite do
@moduledoc false
use Ash.Resource,
domain: WandererApp.Api,
data_layer: AshPostgres.DataLayer
postgres do
repo(WandererApp.Repo)
table("map_invites_v1")
end
code_interface do
define(:new, action: :new)
define(:read, action: :read)
define(:destroy, action: :destroy)
define(:by_id,
get_by: [:id],
action: :read
)
define(:by_map,
action: :by_map
)
end
actions do
default_accept [
:token
]
defaults [:read, :update, :destroy]
create :new do
accept [
:map_id,
:token,
:type,
:valid_until
]
primary?(true)
argument :map_id, :uuid, allow_nil?: true
change manage_relationship(:map_id, :map, on_lookup: :relate, on_no_match: nil)
end
read :by_map do
argument(:map_id, :string, allow_nil?: false)
filter(expr(map_id == ^arg(:map_id)))
end
end
attributes do
uuid_primary_key :id
attribute :token, :string do
allow_nil? true
end
attribute :type, :atom do
default "user"
constraints(
one_of: [
:user,
:admin
]
)
allow_nil?(false)
end
attribute :valid_until, :utc_datetime do
allow_nil? true
end
create_timestamp(:inserted_at)
update_timestamp(:updated_at)
end
relationships do
belongs_to :map, WandererApp.Api.Map do
attribute_writable? true
end
end
postgres do
references do
reference :map, on_delete: :delete
end
end
end

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},
@@ -47,17 +49,16 @@ defmodule WandererApp.Application do
child_spec: DynamicSupervisor, name: WandererApp.Map.DynamicSupervisors},
{PartitionSupervisor,
child_spec: DynamicSupervisor, name: WandererApp.Character.DynamicSupervisors},
WandererApp.Zkb.Supervisor,
WandererApp.Server.ServerStatusTracker,
WandererApp.Server.TheraDataFetcher,
{WandererApp.Character.TrackerPoolSupervisor, []},
WandererApp.Character.TrackerManager,
WandererApp.Map.Manager,
WandererApp.Map.ZkbDataFetcher,
WandererAppWeb.Presence,
WandererAppWeb.Endpoint
] ++
maybe_start_corp_wallet_tracker(WandererApp.Env.map_subscriptions_enabled?())
maybe_start_corp_wallet_tracker(WandererApp.Env.map_subscriptions_enabled?()) ++
maybe_start_zkb(WandererApp.Env.zkill_preload_disabled?())
opts = [strategy: :one_for_one, name: WandererApp.Supervisor]
@@ -78,6 +79,12 @@ defmodule WandererApp.Application do
:ok
end
defp maybe_start_zkb(false),
do: [WandererApp.Zkb.Supervisor, WandererApp.Map.ZkbDataFetcher]
defp maybe_start_zkb(_),
do: []
defp maybe_start_corp_wallet_tracker(true),
do: [
WandererApp.StartCorpWalletTrackerTask

View File

@@ -179,20 +179,24 @@ defmodule WandererApp.Character do
end
def search(character_id, opts \\ []) do
{:ok, %{access_token: access_token, eve_id: eve_id} = _character} =
get_character(character_id)
get_character(character_id)
|> case do
{:ok, %{access_token: access_token, eve_id: eve_id} = _character} ->
case WandererApp.Esi.search(eve_id |> String.to_integer(),
access_token: access_token,
character_id: character_id,
refresh_token?: true,
params: opts[:params]
) do
{:ok, result} ->
{:ok, result |> prepare_search_results()}
case WandererApp.Esi.search(eve_id |> String.to_integer(),
access_token: access_token,
character_id: character_id,
refresh_token?: true,
params: opts[:params]
) do
{:ok, result} ->
{:ok, result |> prepare_search_results()}
error ->
Logger.warning("#{__MODULE__} failed search: #{inspect(error)}")
{:ok, []}
end
error ->
Logger.warning("#{__MODULE__} failed search: #{inspect(error)}")
{:ok, []}
end
end
@@ -203,12 +207,27 @@ defmodule WandererApp.Character do
def can_track_wallet?(_), do: false
def can_track_corp_wallet?(%{scopes: scopes} = _character) when not is_nil(scopes) do
scopes |> String.split(" ") |> Enum.member?(@read_corp_wallet_scope)
end
def can_track_corp_wallet?(%{scopes: scopes} = _character)
when not is_nil(scopes),
do: scopes |> String.split(" ") |> Enum.member?(@read_corp_wallet_scope)
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, character} when not is_nil(character) ->
not WandererApp.Env.character_tracking_pause_disabled?() &&
not can_track_corp_wallet?(character)
_ ->
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

@@ -33,7 +33,7 @@ defmodule WandererApp.Character.Tracker do
status: binary()
}
@pause_tracking_timeout :timer.minutes(60 * 24)
@pause_tracking_timeout :timer.minutes(60 * 10)
@offline_timeout :timer.minutes(5)
@online_error_timeout :timer.minutes(2)
@ship_error_timeout :timer.minutes(2)
@@ -61,7 +61,11 @@ defmodule WandererApp.Character.Tracker do
WandererApp.Cache.lookup!("character:#{character_id}:last_online_time")
|> case do
nil ->
pause_tracking(character_id)
WandererApp.Cache.insert(
"character:#{character_id}:last_online_time",
DateTime.utc_now()
)
:ok
last_online_time ->
@@ -106,7 +110,8 @@ defmodule WandererApp.Character.Tracker do
end
defp pause_tracking(character_id) do
if not WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused") do
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")
WandererApp.Cache.delete("character:#{character_id}:ship_error_time")
@@ -182,8 +187,6 @@ defmodule WandererApp.Character.Tracker do
"character:#{character_id}:last_online_time",
DateTime.utc_now()
)
else
WandererApp.Cache.delete("character:#{character_id}:last_online_time")
end
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
@@ -230,9 +233,7 @@ defmodule WandererApp.Character.Tracker do
{:error, :error_limited, headers} ->
reset_timeout = get_reset_timeout(headers)
Logger.warning(
"#{__MODULE__} failed to update_online: #{inspect(:error_limited)}"
)
Logger.warning(".")
WandererApp.Cache.put(
"character:#{character_id}:online_forbidden",
@@ -319,9 +320,7 @@ defmodule WandererApp.Character.Tracker do
{:error, :error_limited, headers} ->
reset_timeout = get_reset_timeout(headers)
Logger.warning(
"#{__MODULE__} failed to get_character_info: #{inspect(:error_limited)}"
)
Logger.warning(".")
WandererApp.Cache.put(
"character:#{character_id}:info_forbidden",
@@ -398,7 +397,7 @@ defmodule WandererApp.Character.Tracker do
{:error, :error_limited, headers} ->
reset_timeout = get_reset_timeout(headers)
Logger.warning("#{__MODULE__} failed to update_ship: #{inspect(:error_limited)}")
Logger.warning(".")
WandererApp.Cache.put(
"character:#{character_id}:ship_forbidden",
@@ -464,8 +463,7 @@ defmodule WandererApp.Character.Tracker do
) do
case WandererApp.Character.get_character(character_id) do
{:ok, %{eve_id: eve_id, access_token: access_token}} when not is_nil(access_token) ->
(WandererApp.Cache.has_key?("character:#{character_id}:location_forbidden") ||
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused"))
WandererApp.Cache.has_key?("character:#{character_id}:tracking_paused")
|> case do
true ->
{:error, :skipped}
@@ -496,9 +494,7 @@ defmodule WandererApp.Character.Tracker do
{:error, :skipped}
{:error, :error_limited, headers} ->
Logger.warning(
"#{__MODULE__} failed to update_location: #{inspect(:error_limited)}"
)
Logger.warning(".")
reset_timeout = get_reset_timeout(headers, @location_limit_ttl)
@@ -593,9 +589,7 @@ defmodule WandererApp.Character.Tracker do
{:error, :error_limited, headers} ->
reset_timeout = get_reset_timeout(headers)
Logger.warning(
"#{__MODULE__} failed to update_wallet: #{inspect(:error_limited)}"
)
Logger.warning(".")
WandererApp.Cache.put(
"character:#{character_id}:wallet_forbidden",

View File

@@ -14,7 +14,8 @@ defmodule WandererApp.Character.TrackerManager.Impl do
@garbage_collection_interval :timer.minutes(15)
@untrack_characters_interval :timer.minutes(1)
@inactive_character_timeout :timer.minutes(5)
@inactive_character_timeout :timer.minutes(10)
@untrack_character_timeout :timer.minutes(10)
@logger Application.compile_env(:wanderer_app, :logger)
@@ -107,32 +108,49 @@ defmodule WandererApp.Character.TrackerManager.Impl do
} = track_settings
) do
if track do
WandererApp.Cache.insert_or_update(
"character_untrack_queue",
[],
fn untrack_queue ->
untrack_queue
|> Enum.reject(fn {m_id, c_id} -> m_id == map_id and c_id == character_id end)
end
)
remove_from_untrack_queue(map_id, character_id)
{:ok, character_state} =
WandererApp.Character.Tracker.update_settings(character_id, track_settings)
WandererApp.Character.update_character_state(character_id, character_state)
else
WandererApp.Cache.insert_or_update(
"character_untrack_queue",
[{map_id, character_id}],
fn untrack_queue ->
[{map_id, character_id} | untrack_queue] |> Enum.uniq()
end
)
add_to_untrack_queue(map_id, character_id)
end
state
end
def add_to_untrack_queue(map_id, character_id) do
if not WandererApp.Cache.has_key?("#{map_id}:#{character_id}:untrack_requested") do
WandererApp.Cache.insert(
"#{map_id}:#{character_id}:untrack_requested",
DateTime.utc_now()
)
end
WandererApp.Cache.insert_or_update(
"character_untrack_queue",
[{map_id, character_id}],
fn untrack_queue ->
[{map_id, character_id} | untrack_queue] |> Enum.uniq()
end
)
end
def remove_from_untrack_queue(map_id, character_id) do
WandererApp.Cache.delete("#{map_id}:#{character_id}:untrack_requested")
WandererApp.Cache.insert_or_update(
"character_untrack_queue",
[],
fn untrack_queue ->
untrack_queue
|> Enum.reject(fn {m_id, c_id} -> m_id == map_id and c_id == character_id end)
end
)
end
def get_characters(
state,
_opts \\ []
@@ -208,10 +226,28 @@ defmodule WandererApp.Character.TrackerManager.Impl do
) do
Process.send_after(self(), :untrack_characters, @untrack_characters_interval)
WandererApp.Cache.get_and_remove!("character_untrack_queue", [])
WandererApp.Cache.lookup!("character_untrack_queue", [])
|> Task.async_stream(
fn {map_id, character_id} ->
if not character_is_present(map_id, character_id) do
untrack_timeout_reached =
if WandererApp.Cache.has_key?("#{map_id}:#{character_id}:untrack_requested") do
untrack_requested =
WandererApp.Cache.lookup!(
"#{map_id}:#{character_id}:untrack_requested",
DateTime.utc_now()
)
duration = DateTime.diff(DateTime.utc_now(), untrack_requested, :millisecond)
duration >= @untrack_character_timeout
else
false
end
Logger.debug(fn -> "Untrack timeout reached: #{inspect(untrack_timeout_reached)}" end)
if untrack_timeout_reached do
remove_from_untrack_queue(map_id, character_id)
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:solar_system_id")
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:station_id")
WandererApp.Cache.delete("map:#{map_id}:character:#{character_id}:structure_id")
@@ -235,6 +271,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
})
WandererApp.Character.update_character_state(character_id, character_state)
WandererApp.Map.Server.Impl.broadcast!(map_id, :untrack_character, character_id)
end
end,
max_concurrency: System.schedulers_online(),

View File

@@ -18,7 +18,10 @@ defmodule WandererApp.Character.TrackerPool do
@update_location_interval :timer.seconds(1)
@update_online_interval :timer.seconds(5)
@check_online_errors_interval :timer.seconds(30)
@check_offline_characters_interval :timer.minutes(2)
@check_online_errors_interval :timer.minutes(1)
@check_ship_errors_interval :timer.minutes(1)
@check_location_errors_interval :timer.minutes(1)
@update_ship_interval :timer.seconds(2)
@update_info_interval :timer.minutes(1)
@update_wallet_interval :timer.minutes(1)
@@ -130,7 +133,10 @@ defmodule WandererApp.Character.TrackerPool do
)
Process.send_after(self(), :update_online, 100)
Process.send_after(self(), :check_online_errors, @check_online_errors_interval)
Process.send_after(self(), :check_online_errors, :timer.seconds(60))
Process.send_after(self(), :check_ship_errors, :timer.seconds(90))
Process.send_after(self(), :check_location_errors, :timer.seconds(120))
Process.send_after(self(), :check_offline_characters, @check_offline_characters_interval)
Process.send_after(self(), :update_location, 300)
Process.send_after(self(), :update_ship, 500)
Process.send_after(self(), :update_info, 1500)
@@ -217,6 +223,50 @@ defmodule WandererApp.Character.TrackerPool do
{:noreply, state}
end
def handle_info(
:check_offline_characters,
%{
characters: characters
} =
state
) do
Process.send_after(self(), :check_offline_characters, @check_offline_characters_interval)
try do
characters
|> Task.async_stream(
fn 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(),
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> @logger.error("Error in check_offline: #{inspect(reason)}")
end)
rescue
e ->
Logger.error("""
[Tracker Pool] check_offline => exception: #{Exception.message(e)}
#{Exception.format_stacktrace(__STACKTRACE__)}
""")
end
{:noreply, state}
end
def handle_info(
:check_online_errors,
%{
@@ -257,6 +307,86 @@ defmodule WandererApp.Character.TrackerPool do
{:noreply, state}
end
def handle_info(
:check_ship_errors,
%{
characters: characters
} =
state
) do
Process.send_after(self(), :check_ship_errors, @check_ship_errors_interval)
try do
characters
|> Task.async_stream(
fn character_id ->
WandererApp.TaskWrapper.start_link(
WandererApp.Character.Tracker,
:check_ship_errors,
[
character_id
]
)
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> @logger.error("Error in check_ship_errors: #{inspect(reason)}")
end)
rescue
e ->
Logger.error("""
[Tracker Pool] check_ship_errors => exception: #{Exception.message(e)}
#{Exception.format_stacktrace(__STACKTRACE__)}
""")
end
{:noreply, state}
end
def handle_info(
:check_location_errors,
%{
characters: characters
} =
state
) do
Process.send_after(self(), :check_location_errors, @check_location_errors_interval)
try do
characters
|> Task.async_stream(
fn character_id ->
WandererApp.TaskWrapper.start_link(
WandererApp.Character.Tracker,
:check_location_errors,
[
character_id
]
)
end,
timeout: :timer.seconds(15),
max_concurrency: System.schedulers_online(),
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, _result} -> :ok
{:error, reason} -> @logger.error("Error in check_location_errors: #{inspect(reason)}")
end)
rescue
e ->
Logger.error("""
[Tracker Pool] check_location_errors => exception: #{Exception.message(e)}
#{Exception.format_stacktrace(__STACKTRACE__)}
""")
end
{:noreply, state}
end
def handle_info(
:update_location,
%{

View File

@@ -54,7 +54,8 @@ defmodule WandererApp.Character.TransactionsTracker.Impl do
{:ok, latest_transactions} = WandererApp.Api.CorpWalletTransaction.latest()
case WandererApp.Character.can_track_corp_wallet?(character) do
case character.eve_id == WandererApp.Env.corp_wallet_eve_id() &&
WandererApp.Character.can_track_corp_wallet?(character) do
true ->
Process.send_after(self(), :update_corp_wallets, 500)
Process.send_after(self(), :check_wallets, 500)
@@ -163,7 +164,7 @@ defmodule WandererApp.Character.TransactionsTracker.Impl do
{:error, :forbidden}
{:error, :error_limited, _headers} ->
Logger.warning("#{__MODULE__} failed to get_wallet_journal: error_limited")
Logger.warning(".")
{:error, :error_limited}
{:error, error} ->
@@ -192,7 +193,7 @@ defmodule WandererApp.Character.TransactionsTracker.Impl do
{:error, :forbidden}
{:error, :error_limited, _headers} ->
Logger.warning("#{__MODULE__} failed to update_corp_wallets: error_limited")
Logger.warning(".")
{:error, :error_limited}
{:error, error} ->

View File

@@ -16,6 +16,13 @@ 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")
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)
def wallet_tracking_enabled?, do: get_key(:wallet_tracking_enabled, false)
@@ -23,6 +30,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_eve_id, do: get_key(:corp_id, -1)
def subscription_settings, do: get_key(:subscription_settings)

View File

@@ -536,7 +536,6 @@ defmodule WandererApp.Esi.ApiClient do
{:error, :not_found}
{:ok, %{status: 420, headers: headers} = _error} ->
Logger.warning("#{path} error_limited error: #{inspect(headers)}")
{:error, :error_limited, headers}
{:ok, %{status: status} = _error} when status in [401, 403] ->
@@ -593,7 +592,6 @@ defmodule WandererApp.Esi.ApiClient do
{:error, :forbidden}
{:ok, %{status: 420, headers: headers} = _error} ->
Logger.warning("#{url} error_limited error: #{inspect(headers)}")
{:error, :error_limited, headers}
{:ok, %{status: status}} ->
@@ -632,7 +630,6 @@ defmodule WandererApp.Esi.ApiClient do
{:error, :forbidden}
{:ok, %{status: 420, headers: headers} = _error} ->
Logger.warning("#{url} error_limited error: #{inspect(headers)}")
{:error, :error_limited, headers}
{:ok, %{status: status}} ->
@@ -674,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

@@ -16,7 +16,7 @@ defmodule WandererApp.StartCorpWalletTrackerTask do
user_hash ->
user_hash
|> _get_user_characters()
|> get_user_characters()
|> maybe_start_corp_wallet_tracker()
end
end
@@ -25,7 +25,8 @@ defmodule WandererApp.StartCorpWalletTrackerTask do
admin_character =
user_characters
|> Enum.find(fn character ->
WandererApp.Character.can_track_corp_wallet?(character)
character.eve_id == WandererApp.Env.corp_wallet_eve_id() &&
WandererApp.Character.can_track_corp_wallet?(character)
end)
if not is_nil(admin_character) do
@@ -41,12 +42,12 @@ defmodule WandererApp.StartCorpWalletTrackerTask do
def maybe_start_corp_wallet_tracker(_), do: :ok
defp _get_user_characters(user_hash) when not is_nil(user_hash) and is_binary(user_hash) do
defp get_user_characters(user_hash) when not is_nil(user_hash) and is_binary(user_hash) do
case WandererApp.Api.User.by_hash(user_hash, load: :characters) do
{:ok, user} -> {:ok, user.characters}
{:error, _} -> {:ok, []}
end
end
defp _get_user_characters(_), do: {:ok, []}
defp get_user_characters(_), do: {:ok, []}
end

View File

@@ -294,14 +294,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

@@ -86,19 +86,24 @@ defmodule WandererApp.Map.Server.CharactersImpl do
end)
end
def untrack_characters(map_id, character_ids),
do:
character_ids
|> Enum.each(fn character_id ->
if is_character_map_active?(map_id, character_id) do
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
map_id: map_id,
track: false
})
def untrack_characters(map_id, character_ids) do
character_ids
|> Enum.each(fn character_id ->
is_character_map_active?(map_id, character_id)
|> untrack_character(map_id, character_id)
end)
end
Impl.broadcast!(map_id, :untrack_character, character_id)
end
end)
def untrack_character(true, map_id, character_id) do
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
map_id: map_id,
track: false
})
end
def untrack_character(_is_character_map_active, _map_id, character_id) do
:ok
end
def is_character_map_active?(map_id, character_id) do
case WandererApp.Character.get_character_state(character_id) do
@@ -219,12 +224,11 @@ defmodule WandererApp.Map.Server.CharactersImpl do
Task.start_link(fn ->
character_updates =
maybe_update_online(map_id, character_id) ++
maybe_update_tracking_status(map_id, character_id)
maybe_update_location(map_id, character_id) ++
maybe_update_ship(map_id, character_id) ++
maybe_update_alliance(map_id, character_id) ++
maybe_update_corporation(map_id, character_id)
maybe_update_tracking_status(map_id, character_id) ++
maybe_update_location(map_id, character_id) ++
maybe_update_ship(map_id, character_id) ++
maybe_update_alliance(map_id, character_id) ++
maybe_update_corporation(map_id, character_id)
character_updates
|> Enum.filter(fn update -> update != :skip end)

View File

@@ -30,7 +30,7 @@ defmodule WandererApp.Map.Server.Impl do
@pubsub_client Application.compile_env(:wanderer_app, :pubsub_client)
@backup_state_timeout :timer.minutes(1)
@update_presence_timeout :timer.seconds(1)
@update_presence_timeout :timer.seconds(5)
@update_characters_timeout :timer.seconds(1)
@update_tracked_characters_timeout :timer.seconds(1)

View File

@@ -20,17 +20,9 @@ defmodule WandererApp.Ueberauth.Strategy.Eve do
is_admin? = Map.get(params, "admin", "false") in ~w(true 1)
invite_token = Map.get(params, "invite", nil)
invite_token_valid =
case WandererApp.Env.invites() do
true ->
case invite_token do
nil -> false
token -> WandererApp.Cache.lookup!("invite_#{token}", false)
end
{invite_token_valid, invite_type} = check_invite_valid(invite_token)
_ ->
true
end
is_admin? = is_admin? || invite_type == :admin
case invite_token_valid do
true ->
@@ -200,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.Env.active_tracking_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
@@ -218,4 +213,33 @@ defmodule WandererApp.Ueberauth.Strategy.Eve do
defp option(conn, key) do
Keyword.get(options(conn), key, Keyword.get(default_options(), key))
end
defp check_invite_valid(invite_token) do
case invite_token do
token when not is_nil(token) and token != "" ->
check_token_valid(token)
_ ->
{not WandererApp.Env.invites(), :user}
end
end
defp check_token_valid(token) do
WandererApp.Cache.lookup!("invite_#{token}", false)
|> case do
true -> {true, :user}
_ -> check_map_token_valid(token)
end
end
def check_map_token_valid(token) do
{:ok, invites} = WandererApp.Api.MapInvite.read()
invites
|> Enum.find(fn invite -> invite.token == token end)
|> case do
nil -> {false, nil}
invite -> {true, invite.type}
end
end
end

View File

@@ -0,0 +1,84 @@
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")
Cachex.put(
:esi_auth_cache,
:active_config,
"config_#{WandererApp.Env.active_tracking_pool()}"
)
cache_clients()
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

@@ -62,62 +62,68 @@ defmodule WandererApp.Zkb.KillsProvider.Fetcher do
Returns `{:ok, kills, updated_state}` on success, or `{:error, reason, updated_state}`.
"""
def fetch_kills_for_system(system_id, since_hours, state, opts \\ []) do
limit = Keyword.get(opts, :limit, nil)
force? = Keyword.get(opts, :force, false)
zkill_preload_disabled = WandererApp.Env.zkill_preload_disabled?()
log_prefix = "[Fetcher] fetch_kills_for_system => system=#{system_id}"
if not zkill_preload_disabled do
limit = Keyword.get(opts, :limit, nil)
force? = Keyword.get(opts, :force, false)
# Check the "recently fetched" cache if not forced
if not force? and KillsCache.recently_fetched?(system_id) do
cached_kills = KillsCache.fetch_cached_kills(system_id)
final = maybe_take(cached_kills, limit)
log_prefix = "[Fetcher] fetch_kills_for_system => system=#{system_id}"
Logger.debug(fn ->
"#{log_prefix}, recently_fetched?=true => returning #{length(final)} cached kills"
end)
# Check the "recently fetched" cache if not forced
if not force? and KillsCache.recently_fetched?(system_id) do
cached_kills = KillsCache.fetch_cached_kills(system_id)
final = maybe_take(cached_kills, limit)
{:ok, final, state}
else
Logger.debug(fn ->
"#{log_prefix}, hours=#{since_hours}, limit=#{inspect(limit)}, force=#{force?}"
end)
Logger.debug(fn ->
"#{log_prefix}, recently_fetched?=true => returning #{length(final)} cached kills"
end)
cutoff_dt = hours_ago(since_hours)
{:ok, final, state}
else
Logger.debug(fn ->
"#{log_prefix}, hours=#{since_hours}, limit=#{inspect(limit)}, force=#{force?}"
end)
result =
retry with:
exponential_backoff(300)
|> randomize()
|> cap(5_000)
|> expiry(120_000) do
case do_multi_page_fetch(system_id, cutoff_dt, 1, 0, limit, state) do
{:ok, new_st, total_fetched} ->
# Mark system as fully fetched (to prevent repeated calls).
KillsCache.put_full_fetched_timestamp(system_id)
final_kills = KillsCache.fetch_cached_kills(system_id) |> maybe_take(limit)
cutoff_dt = hours_ago(since_hours)
Logger.debug(fn ->
"#{log_prefix}, total_fetched=#{total_fetched}, final_cached=#{length(final_kills)}, calls_count=#{new_st.calls_count}"
end)
result =
retry with:
exponential_backoff(300)
|> randomize()
|> cap(5_000)
|> expiry(120_000) do
case do_multi_page_fetch(system_id, cutoff_dt, 1, 0, limit, state) do
{:ok, new_st, total_fetched} ->
# Mark system as fully fetched (to prevent repeated calls).
KillsCache.put_full_fetched_timestamp(system_id)
final_kills = KillsCache.fetch_cached_kills(system_id) |> maybe_take(limit)
{:ok, final_kills, new_st}
Logger.debug(fn ->
"#{log_prefix}, total_fetched=#{total_fetched}, final_cached=#{length(final_kills)}, calls_count=#{new_st.calls_count}"
end)
{:error, :rate_limited, _new_st} ->
raise ":rate_limited"
{:ok, final_kills, new_st}
{:error, reason, _new_st} ->
raise "#{log_prefix}, reason=#{inspect(reason)}"
{:error, :rate_limited, _new_st} ->
raise ":rate_limited"
{:error, reason, _new_st} ->
raise "#{log_prefix}, reason=#{inspect(reason)}"
end
end
case result do
{:ok, kills, new_st} ->
{:ok, kills, new_st}
error ->
Logger.error("#{log_prefix}, EXHAUSTED => error=#{inspect(error)}")
{:error, error, state}
end
case result do
{:ok, kills, new_st} ->
{:ok, kills, new_st}
error ->
Logger.error("#{log_prefix}, EXHAUSTED => error=#{inspect(error)}")
{:error, error, state}
end
else
raise ":kills_disabled"
end
rescue
e ->

View File

@@ -14,7 +14,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: WandererApp.Env.active_tracking_pool()
}
%{
@@ -29,7 +30,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: WandererApp.Env.active_tracking_pool()
}
{:ok, character} =

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

@@ -15,7 +15,8 @@ defmodule WandererAppWeb.AdminLive do
corp_wallet_character =
socket.assigns.current_user.characters
|> Enum.find(fn character ->
WandererApp.Character.can_track_corp_wallet?(character)
character.eve_id == WandererApp.Env.corp_wallet_eve_id() &&
WandererApp.Character.can_track_corp_wallet?(character)
end)
Phoenix.PubSub.subscribe(
@@ -58,10 +59,10 @@ defmodule WandererAppWeb.AdminLive do
socket
|> assign(
active_map_subscriptions: active_map_subscriptions,
show_invites?: WandererApp.Env.invites(),
user_character_ids: user_character_ids,
user_id: user_id,
invite_link: nil,
tracker_stats: [],
map_subscriptions_enabled?: WandererApp.Env.map_subscriptions_enabled?(),
restrict_maps_creation?: WandererApp.Env.restrict_maps_creation?()
)}
@@ -77,21 +78,6 @@ defmodule WandererAppWeb.AdminLive do
{:noreply, apply_action(socket, socket.assigns.live_action, params, uri)}
end
def handle_event("generate-invite-link", _params, socket) do
uuid = UUID.uuid4(:default)
WandererApp.Cache.put("invite_#{uuid}", true, ttl: @invite_link_ttl)
invite_link =
socket.assigns.uri
|> Map.put(:path, "/welcome")
|> Map.put(:query, URI.encode_query(%{invite: uuid}))
|> URI.to_string()
{:noreply,
socket
|> assign(invite_link: invite_link)}
end
@impl true
def handle_event("update-eve-db-data", _params, socket) do
WandererApp.EveDataService.update_eve_data()
@@ -224,6 +210,58 @@ defmodule WandererAppWeb.AdminLive do
|> push_navigate(to: ~p"/maps/new")}
end
def handle_event("validate", %{"form" => params}, socket) do
form = AshPhoenix.Form.validate(socket.assigns.form, params)
{:noreply, assign(socket, form: form)}
end
def handle_event("generate-invite-link", _params, socket) do
token = UUID.uuid4()
new_params = Map.put(socket.assigns.form.params || %{}, "token", token)
form = AshPhoenix.Form.validate(socket.assigns.form, new_params)
invite_link =
socket.assigns.uri
|> get_invite_link(token)
{:noreply, assign(socket, form: form, invite_link: invite_link)}
end
def handle_event(
"add_invite_link",
%{"form" => %{"type" => type, "valid_until" => valid_until}},
socket
) do
%{
type: type |> String.to_existing_atom(),
valid_until: get_valid_until(valid_until),
token: UUID.uuid4(),
map_id: nil
}
|> WandererApp.Api.MapInvite.new()
|> case do
{:ok, _invite} ->
{:noreply, socket |> push_patch(to: ~p"/admin")}
error ->
{:noreply, socket |> put_flash(:error, "Failed to add invite. Try again.")}
end
end
def handle_event(
"delete-invite",
%{"id" => id},
socket
) do
id
|> WandererApp.Api.MapInvite.by_id!()
|> WandererApp.Api.MapInvite.destroy!()
{:ok, invites} = WandererApp.Api.MapInvite.read()
{:noreply, socket |> assign(:invites, invites)}
end
@impl true
def handle_event(event, body, socket) do
Logger.warning(fn -> "unhandled event: #{event} #{inspect(body)}" end)
@@ -255,6 +293,10 @@ defmodule WandererAppWeb.AdminLive do
end
defp apply_action(socket, :index, _params, uri) do
{:ok, invites} = WandererApp.Api.MapInvite.read()
{:ok, tracker_stats} = load_tracker_stats()
socket
|> assign(:active_page, :admin)
|> assign(:uri, URI.parse(uri))
@@ -268,6 +310,113 @@ 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)
end
defp apply_action(socket, :add_invite_link, _params, uri) do
socket
|> assign(:active_page, :admin)
|> assign(:uri, URI.parse(uri))
|> assign(:page_title, "Add Invite Link")
|> assign(:invite_types, [%{label: "User", id: :user}, %{label: "Admin", id: :admin}])
|> assign(:valid_types, [
%{label: "1D", id: 1},
%{label: "1W", id: 7},
%{label: "1M", id: 30},
%{label: "1Y", id: 365}
])
|> assign(:unlink_character_form, to_form(%{}))
|> assign(:character_search_options, [])
|> assign(:amounts, [
%{label: "500M", value: 500_000_000},
%{label: "1B", value: 1_000_000_000},
%{label: "5B", value: 5_000_000_000},
%{label: "10B", value: 10_000_000_000}
])
|> assign(:form, to_form(%{"amount" => 500_000_000}))
|> assign(:invite_token, UUID.uuid4())
|> assign(
:form,
AshPhoenix.Form.for_create(WandererApp.Api.MapInvite, :new,
forms: [
auto?: true
]
)
|> to_form()
)
|> assign(:invites, [])
end
defp load_tracker_stats() do
{:ok, characters} =
WandererApp.Api.Character.last_active(%{
from:
DateTime.utc_now()
|> DateTime.add(-1 * 60 * 24 * 7, :minute)
})
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 = [
%{title: "Admins", value: admins_count},
%{title: "With Wallet", value: with_wallets_count},
%{title: "Default", value: default_count}
]
{:ok, pools_count} =
Cachex.get(
:esi_auth_cache,
"configs_total_count"
)
result =
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()
%{title: "Pool #{pool_id}", value: pools_character_count}
end)
result ++ pools
else
result
end
{:ok, result}
end
defp get_invite_link(uri, token) do
uri
|> Map.put(:path, "/auth/eve")
|> Map.put(:query, URI.encode_query(%{invite: token}))
|> URI.to_string()
end
defp search(search) do
@@ -290,11 +439,29 @@ defmodule WandererAppWeb.AdminLive do
</div>
</div>
<span :if={@option.value == :loading} <span class="loading loading-spinner loading-xs"></span>
&nbsp; <%= @option.label %>
&nbsp; {@option.label}
</div>
"""
end
defp get_valid_until("1") do
DateTime.utc_now() |> DateTime.add(24 * 3600, :second)
end
defp get_valid_until("7") do
DateTime.utc_now() |> DateTime.add(24 * 3600 * 7, :second)
end
defp get_valid_until("30") do
DateTime.utc_now() |> DateTime.add(24 * 3600 * 30, :second)
end
defp get_valid_until("365") do
DateTime.utc_now() |> DateTime.add(24 * 3600 * 365, :second)
end
defp get_valid_until(_), do: get_valid_until("1")
def search_member_icon_url(%{character: true} = option),
do: member_icon_url(%{eve_character_id: option.value})

View File

@@ -112,35 +112,78 @@
</div>
</div>
<div :if={@show_invites?} class="card dark:bg-zinc-800 dark:border-zinc-600">
<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">Invite Link</span>
<span class="text-gray-400 dark:text-gray-400">Invites</span>
<h4 class="my-4 font-medium text-gray-800 text-4xl dark:text-gray-100">
<.button class="btn btn-primary" phx-click="generate-invite-link">
Generate
</.button>
<div :if={not is_nil(@invite_link)} class="join">
<input
class="input input-bordered join-item"
readonly
type="text"
value={@invite_link}
/>
<.link class="btn mt-2 w-full btn-neutral rounded-none" patch={~p"/admin/invite"}>
<.icon name="hero-plus-solid" class="w-6 h-6" />
<h3 class="card-title text-center text-md">New Invite</h3>
</.link>
</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"
id="copy-to-clipboard"
class="copy-link btn join-item rounded-r-full"
data-url={@invite_link}
class="copy-link btn btn-neutral rounded-none"
data-url={get_invite_link(@uri, invite.token)}
>
Copy
<div class="absolute w-[100px] !mr-[-170px] link-copied hidden">
Link copied
</div>
</.button>
</div>
</h4>
</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={stat} label="Count">
{stat.value}
</:col>
</.table>
</div>
</div>
</div>
@@ -261,4 +304,40 @@
</div>
</div>
</div>
<.modal
:if={@live_action in [:add_invite_link]}
title={"New Invite"}
class="!w-[500px]"
id="add_invite_link_modal"
show
on_cancel={JS.patch(~p"/admin")}
>
<.form :let={f} for={@form} phx-change="validate" phx-submit={@live_action}>
<.input
type="select"
field={f[:type]}
class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
wrapper_class="mt-2"
label="Type"
options={Enum.map(@invite_types, fn invite_type -> {invite_type.label, invite_type.id} end)}
/>
<.input
type="select"
field={f[:valid_until]}
class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
wrapper_class="mt-2"
label="Valid"
options={Enum.map(@valid_types, fn valid_type -> {valid_type.label, valid_type.id} end)}
/>
<!-- API Key Section with grid layout -->
<div class="modal-action">
<.button class="mt-2" type="submit" phx-disable-with="Saving...">
<%= (@live_action == :add_invite_link && "Add") || "Save" %>
</.button>
</div>
</.form>
</.modal>
</main>

View File

@@ -5,6 +5,8 @@ defmodule WandererAppWeb.CharactersLive do
alias BetterNumber, as: Number
@active_tracking_pool 1
def mount(_params, %{"user_id" => user_id} = _session, socket)
when not is_nil(user_id) do
{:ok, characters} = WandererApp.Api.Character.active_by_user(%{user_id: user_id})
@@ -56,17 +58,25 @@ 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}")}
{:ok, esi_config} =
Cachex.get(
:esi_auth_cache,
"config_#{WandererApp.Env.active_tracking_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
def handle_event("delete", %{"character_id" => character_id}, socket) do
WandererApp.Character.TrackerManager.stop_tracking(character_id)
{:ok, map_user_settings} = WandererApp.Api.MapCharacterSettings.tracked_by_character(%{character_id: character_id})
{:ok, map_user_settings} =
WandererApp.Api.MapCharacterSettings.tracked_by_character(%{character_id: character_id})
map_user_settings
|> Enum.each(fn settings ->
@@ -87,6 +97,15 @@ defmodule WandererAppWeb.CharactersLive do
{:noreply, socket |> assign(characters: characters |> Enum.map(&map_ui_character/1))}
end
@impl true
def handle_event(
"validate",
params,
socket
) do
{:noreply, assign(socket, form: params)}
end
@impl true
def handle_event("show_table", %{"value" => "on"}, socket) do
{:noreply, socket |> assign(mode: :table)}
@@ -148,7 +167,7 @@ defmodule WandererAppWeb.CharactersLive do
socket
|> assign(:active_page, :characters)
|> assign(:page_title, "Authorize Character - Characters")
|> assign(:form, to_form(%{"track_wallet" => false}))
|> assign(:form, to_form(%{}))
end
defp map_ui_character(character) do

View File

@@ -242,10 +242,13 @@
show
on_cancel={JS.patch(~p"/characters")}
>
<.form :let={f} for={@form} phx-submit="authorize">
<div :if={@wallet_tracking_enabled?} class="pb-2 -mt-8">
<div class="flex flex-col gap-3">
<.form :let={f} for={@form} phx-submit="authorize" phx-change="validate">
<div :if={@wallet_tracking_enabled?} class="pb-2">
<.input
type="checkbox"
field={f[:track_wallet]}
label="Access to character wallet information"
/>
@@ -254,5 +257,6 @@
<.button type="submit">AUTHORIZE</.button>
</div>
</.form>
</div>
</.modal>
</main>

View File

@@ -11,7 +11,7 @@ defmodule WandererAppWeb.ServerStatusLive do
"server_status"
)
{:ok, socket |> assign(server_online: false)}
{:ok, socket |> assign(server_online: true)}
end
@impl true

View File

@@ -230,7 +230,11 @@ defmodule WandererAppWeb.Router do
delete "/connections", MapConnectionAPIController, :delete
delete "/systems", MapSystemAPIController, :delete
resources "/systems", MapSystemAPIController, only: [:index, :show, :create, :update, :delete]
resources "/connections", MapConnectionAPIController, only: [:index, :show, :create, :update, :delete], param: "id"
resources "/connections", MapConnectionAPIController,
only: [:index, :show, :create, :update, :delete],
param: "id"
resources "/structures", MapSystemStructureAPIController, except: [:new, :edit]
get "/structure-timers", MapSystemStructureAPIController, :structure_timers
resources "/signatures", MapSystemSignatureAPIController, except: [:new, :edit]
@@ -238,8 +242,6 @@ defmodule WandererAppWeb.Router do
get "/tracked-characters", MapAPIController, :show_tracked_characters
end
#
# Other API routes
#
@@ -359,6 +361,7 @@ defmodule WandererAppWeb.Router do
WandererAppWeb.Nav
] do
live("/", AdminLive, :index)
live("/invite", AdminLive, :add_invite_link)
end
error_tracker_dashboard("/errors",

View File

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

View File

@@ -0,0 +1,40 @@
defmodule WandererApp.Repo.Migrations.AddMapInvites 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
create table(:map_invites_v1, primary_key: false) do
add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true
add :token, :text
add :valid_until, :utc_datetime
add :inserted_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
add :updated_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
add :map_id,
references(:maps_v1,
column: :id,
name: "map_invites_v1_map_id_fkey",
type: :uuid,
prefix: "public",
on_delete: :delete_all
)
end
end
def down do
drop constraint(:map_invites_v1, "map_invites_v1_map_id_fkey")
drop table(:map_invites_v1)
end
end

View File

@@ -0,0 +1,21 @@
defmodule WandererApp.Repo.Migrations.AddMapInviteType 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(:map_invites_v1) do
add :type, :text, null: false, default: "user"
end
end
def down do
alter table(:map_invites_v1) do
remove :type
end
end
end

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"
}

View File

@@ -0,0 +1,98 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "valid_until",
"type": "utc_datetime"
},
{
"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": "map_invites_v1_map_id_fkey",
"on_delete": "delete",
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "maps_v1"
},
"size": null,
"source": "map_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "B4122C666C9DCF20E420209F604765AB1A3C4979D4134916F7EF9292162B250C",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "map_invites_v1"
}

View File

@@ -0,0 +1,108 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "token",
"type": "text"
},
{
"allow_nil?": false,
"default": "\"user\"",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "type",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "valid_until",
"type": "utc_datetime"
},
{
"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": "map_invites_v1_map_id_fkey",
"on_delete": "delete",
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "maps_v1"
},
"size": null,
"source": "map_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "99662C7392D745B0B2D22445A3A703DC2287EBBA185BB7818D58F472B5D033D3",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "map_invites_v1"
}