mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-11-28 12:03:22 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64788e73de | ||
|
|
114fd471e8 | ||
|
|
b24a3120d3 | ||
|
|
c5f93b3d0a | ||
|
|
79290e4721 | ||
|
|
984e126f23 | ||
|
|
cd1ad31aed | ||
|
|
1e3f6cf9e7 | ||
|
|
9c6ccd9a8a | ||
|
|
681ba21d39 | ||
|
|
aef62189ee | ||
|
|
09f70ac817 | ||
|
|
1eacb22143 | ||
|
|
8524bad377 | ||
|
|
9d899243d1 | ||
|
|
9acf20a639 | ||
|
|
71ef6b2e82 | ||
|
|
5e34d95dd2 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -116,7 +116,7 @@ jobs:
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
|
||||
- linux/arm64
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
|
||||
50
CHANGELOG.md
50
CHANGELOG.md
@@ -2,6 +2,56 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ 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(),
|
||||
public_api_disabled: public_api_disabled,
|
||||
character_tracking_pause_disabled:
|
||||
System.get_env("WANDERER_CHARACTER_TRACKING_PAUSE_DISABLED", "true")
|
||||
|
||||
@@ -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
|
||||
|
||||
96
lib/wanderer_app/api/map_invite.ex
Normal file
96
lib/wanderer_app/api/map_invite.ex
Normal 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
|
||||
@@ -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,9 +207,9 @@ 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
|
||||
|
||||
|
||||
@@ -463,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}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -24,6 +24,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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -224,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)
|
||||
|
||||
@@ -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 ->
|
||||
@@ -218,4 +210,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
|
||||
|
||||
@@ -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,7 +59,6 @@ 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,
|
||||
@@ -77,21 +77,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 +209,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 +292,8 @@ defmodule WandererAppWeb.AdminLive do
|
||||
end
|
||||
|
||||
defp apply_action(socket, :index, _params, uri) do
|
||||
{:ok, invites} = WandererApp.Api.MapInvite.read()
|
||||
|
||||
socket
|
||||
|> assign(:active_page, :admin)
|
||||
|> assign(:uri, URI.parse(uri))
|
||||
@@ -268,6 +307,48 @@ defmodule WandererAppWeb.AdminLive do
|
||||
])
|
||||
|> assign(:form, to_form(%{"amount" => 500_000_000}))
|
||||
|> assign(:unlink_character_form, to_form(%{}))
|
||||
|> assign(:invites, invites)
|
||||
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 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 +371,29 @@ defmodule WandererAppWeb.AdminLive do
|
||||
</div>
|
||||
</div>
|
||||
<span :if={@option.value == :loading} <span class="loading loading-spinner loading-xs"></span>
|
||||
<%= @option.label %>
|
||||
{@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})
|
||||
|
||||
|
||||
@@ -112,34 +112,58 @@
|
||||
</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>
|
||||
<.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>
|
||||
|
||||
<div :if={not is_nil(@invite_link)} class="join">
|
||||
<input
|
||||
class="input input-bordered join-item"
|
||||
readonly
|
||||
type="text"
|
||||
value={@invite_link}
|
||||
/>
|
||||
<.button
|
||||
phx-hook="CopyToClipboard"
|
||||
id="copy-to-clipboard"
|
||||
class="copy-link btn join-item rounded-r-full"
|
||||
data-url={@invite_link}
|
||||
>
|
||||
Copy
|
||||
<div class="absolute w-[100px] !mr-[-170px] link-copied hidden">
|
||||
Link copied
|
||||
<.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 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>
|
||||
</.button>
|
||||
</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>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
@@ -261,4 +285,39 @@
|
||||
</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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
2
mix.exs
2
mix.exs
@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
|
||||
|
||||
@source_url "https://github.com/wanderer-industries/wanderer"
|
||||
|
||||
@version "1.67.1"
|
||||
@version "1.68.1"
|
||||
|
||||
def project do
|
||||
[
|
||||
|
||||
40
priv/repo/migrations/20250608220906_add_map_invites.exs
Normal file
40
priv/repo/migrations/20250608220906_add_map_invites.exs
Normal 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
|
||||
21
priv/repo/migrations/20250608221356_add_map_invite_type.exs
Normal file
21
priv/repo/migrations/20250608221356_add_map_invite_type.exs
Normal 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
|
||||
@@ -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"
|
||||
}
|
||||
108
priv/resource_snapshots/repo/map_invites_v1/20250608221356.json
Normal file
108
priv/resource_snapshots/repo/map_invites_v1/20250608221356.json
Normal 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"
|
||||
}
|
||||
Reference in New Issue
Block a user