Compare commits

..

4 Commits

Author SHA1 Message Date
CI
de6205f860 chore: release version v1.58.0
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / Manual Approval (push) Blocked by required conditions
Build / 🛠 Build (1.17, 18.x, 27) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2025-03-22 08:38:37 +00:00
Dmitry Popov
f994255091 feat(Core): Show online state on map characters page 2025-03-22 09:29:13 +01:00
Tyson GG
6d4981a3db fix (routes) fix query parameter formatting when calling esi routes endpoint (#302) 2025-03-22 11:53:12 +04:00
guarzo
06fef2296f feat (api): update character activity and api to allow date range (#299)
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / Manual Approval (push) Blocked by required conditions
Build / 🛠 Build (1.17, 18.x, 27) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🛠 Build Docker Images (linux/arm64) (push) Blocked by required conditions
Build / merge (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
* feat (api): update character activity and api to allow date range
2025-03-21 21:05:48 +04:00
8 changed files with 145 additions and 31 deletions

View File

@@ -2,6 +2,19 @@
<!-- changelog -->
## [v1.58.0](https://github.com/wanderer-industries/wanderer/compare/v1.57.1...v1.58.0) (2025-03-22)
### Features:
* Core: Show online state on map characters page
* api: update character activity and api to allow date range (#299)
* api: update character activity and api to allow date range
## [v1.57.1](https://github.com/wanderer-industries/wanderer/compare/v1.57.0...v1.57.1) (2025-03-20)

View File

@@ -401,12 +401,17 @@ defmodule WandererApp.Esi.ApiClient do
defp _get_routes(origin, destination, params, opts),
do: _get_routes_eve(origin, destination, params, opts)
defp _get_routes_eve(origin, destination, params, opts),
do:
get(
"/route/#{origin}/#{destination}/?#{params |> Plug.Conn.Query.encode()}",
opts
)
defp _get_routes_eve(origin, destination, params, opts) do
esi_params = Map.merge(params, %{
connections: params.connections |> Enum.join(","),
avoid: params.avoid |> Enum.join(",")
})
get(
"/route/#{origin}/#{destination}/?#{esi_params |> Plug.Conn.Query.encode()}",
opts
)
end
defp get_auth_opts(opts), do: [auth: {:bearer, opts[:access_token]}]

View File

@@ -527,20 +527,22 @@ defmodule WandererApp.Map do
@doc """
Returns the raw activity data that can be processed by WandererApp.Character.Activity.
Only includes characters that are on the map's ACL.
If days parameter is provided, filters activity to that time period.
"""
def get_character_activity(map_id) do
def get_character_activity(map_id, days \\ nil) do
{:ok, map} = WandererApp.Api.Map.by_id(map_id)
_map_with_acls = Ash.load!(map, :acls)
{:ok, jumps} = WandererApp.Api.MapChainPassages.by_map_id(%{map_id: map_id})
thirty_days_ago = DateTime.utc_now() |> DateTime.add(-30 * 24 * 3600, :second)
# Calculate cutoff date if days is provided
cutoff_date = if days, do: DateTime.utc_now() |> DateTime.add(-days * 24 * 3600, :second), else: nil
# Get activity data
connections_activity = get_connections_activity(map_id, thirty_days_ago)
signatures_activity = get_signatures_activity(map_id, thirty_days_ago)
passages_activity = get_passages_activity(map_id, cutoff_date)
connections_activity = get_connections_activity(map_id, cutoff_date)
signatures_activity = get_signatures_activity(map_id, cutoff_date)
# Return raw activity data
jumps
# Return activity data
passages_activity
|> Enum.map(fn passage ->
%{
character: passage.character,
@@ -554,14 +556,40 @@ defmodule WandererApp.Map do
end)
end
defp get_connections_activity(map_id, thirty_days_ago) do
defp get_passages_activity(map_id, nil) do
# Query all map chain passages without time filter
from(p in WandererApp.Api.MapChainPassages,
join: c in assoc(p, :character),
where: p.map_id == ^map_id,
group_by: [c.id],
select: {c, count(p.id)}
)
|> WandererApp.Repo.all()
|> Enum.map(fn {character, count} -> %{character: character, count: count} end)
end
defp get_passages_activity(map_id, cutoff_date) do
# Query map chain passages with time filter
from(p in WandererApp.Api.MapChainPassages,
join: c in assoc(p, :character),
where:
p.map_id == ^map_id and
p.inserted_at > ^cutoff_date,
group_by: [c.id],
select: {c, count(p.id)}
)
|> WandererApp.Repo.all()
|> Enum.map(fn {character, count} -> %{character: character, count: count} end)
end
defp get_connections_activity(map_id, nil) do
# Query all connection activity without time filter
from(ua in WandererApp.Api.UserActivity,
join: c in assoc(ua, :character),
where:
ua.entity_id == ^map_id and
ua.entity_type == :map and
ua.event_type == :map_connection_added and
ua.inserted_at > ^thirty_days_ago,
ua.event_type == :map_connection_added,
group_by: [c.id],
select: {c.id, count(ua.id)}
)
@@ -569,14 +597,43 @@ defmodule WandererApp.Map do
|> Map.new()
end
defp get_signatures_activity(map_id, thirty_days_ago) do
defp get_connections_activity(map_id, cutoff_date) do
from(ua in WandererApp.Api.UserActivity,
join: c in assoc(ua, :character),
where:
ua.entity_id == ^map_id and
ua.entity_type == :map and
ua.event_type == :map_connection_added and
ua.inserted_at > ^cutoff_date,
group_by: [c.id],
select: {c.id, count(ua.id)}
)
|> WandererApp.Repo.all()
|> Map.new()
end
defp get_signatures_activity(map_id, nil) do
# Query all signature activity without time filter
from(ua in WandererApp.Api.UserActivity,
join: c in assoc(ua, :character),
where:
ua.entity_id == ^map_id and
ua.entity_type == :map and
ua.event_type == :signatures_added,
select: {ua.character_id, ua.event_data}
)
|> WandererApp.Repo.all()
|> process_signatures_data()
end
defp get_signatures_activity(map_id, cutoff_date) do
from(ua in WandererApp.Api.UserActivity,
join: c in assoc(ua, :character),
where:
ua.entity_id == ^map_id and
ua.entity_type == :map and
ua.event_type == :signatures_added and
ua.inserted_at > ^thirty_days_ago,
ua.inserted_at > ^cutoff_date,
select: {ua.character_id, ua.event_data}
)
|> WandererApp.Repo.all()

View File

@@ -648,15 +648,17 @@ defmodule WandererAppWeb.MapAPIController do
Returns character activity data for a map.
Requires either `?map_id=<UUID>` or `?slug=<map-slug>`.
Optional `days` parameter to filter activity to a specific time period.
Example:
GET /api/map/character_activity?map_id=<uuid>
GET /api/map/character_activity?slug=<map-slug>
GET /api/map/character_activity?map_id=<uuid>&days=7
"""
@spec character_activity(Plug.Conn.t(), map()) :: Plug.Conn.t()
operation :character_activity,
summary: "Get Character Activity",
description: "Returns character activity data for a map. Requires either 'map_id' or 'slug' as a query parameter to identify the map.",
description: "Returns character activity data for a map. If days parameter is provided, filters activity to that time period, otherwise returns all activity. Requires either 'map_id' or 'slug' as a query parameter to identify the map.",
parameters: [
map_id: [
in: :query,
@@ -671,6 +673,13 @@ defmodule WandererAppWeb.MapAPIController do
type: :string,
required: false,
example: "map-name"
],
days: [
in: :query,
description: "Optional: Number of days to look back for activity data. If not provided, returns all activity history.",
type: :integer,
required: false,
example: "7"
]
],
responses: [
@@ -691,9 +700,10 @@ defmodule WandererAppWeb.MapAPIController do
}}
]
def character_activity(conn, params) do
with {:ok, map_id} <- Util.fetch_map_id(params) do
# Get raw activity data directly from the Map module instead of the Activity processor
raw_activity = WandererApp.Map.get_character_activity(map_id)
with {:ok, map_id} <- Util.fetch_map_id(params),
{:ok, days} <- parse_days(params["days"]) do
# Get raw activity data (filtered by days if provided, otherwise all activity)
raw_activity = WandererApp.Map.get_character_activity(map_id, days)
# Group activities by user_id and summarize
summarized_result =
@@ -744,6 +754,15 @@ defmodule WandererAppWeb.MapAPIController do
end
end
# Parse days parameter, return nil if not provided to show all activity
defp parse_days(nil), do: {:ok, nil}
defp parse_days(days_str) do
case Integer.parse(days_str) do
{days, ""} when days > 0 -> {:ok, days}
_ -> {:ok, nil} # Return nil if invalid to show all activity
end
end
# If hours_str is present and valid, parse it. Otherwise return nil (no filter).
defp parse_hours_ago(nil), do: nil
defp parse_hours_ago(hours_str) do

View File

@@ -54,14 +54,20 @@ defmodule WandererAppWeb.MapCharacters do
>
Tracked
</span>
<span :if={is_online?(@character.id)} class="text-green-500 rounded-full px-2 py-1">
Online
</span>
<span :if={not is_online?(@character.id)} class="text-red-500 rounded-full px-2 py-1">
Offline
</span>
<div class="avatar">
<div class="rounded-md w-8 h-8">
<img src={member_icon_url(@character.eve_id)} alt={@character.name} />
</div>
</div>
<span><%= @character.name %></span>
<span :if={@character.alliance_ticker}>[<%= @character.alliance_ticker %>]</span>
<span :if={@character.corporation_ticker}>[<%= @character.corporation_ticker %>]</span>
<span>{@character.name}</span>
<span :if={@character.alliance_ticker}>[{@character.alliance_ticker}]</span>
<span :if={@character.corporation_ticker}>[{@character.corporation_ticker}]</span>
</div>
"""
end
@@ -79,4 +85,8 @@ defmodule WandererAppWeb.MapCharacters do
end)
end
defp is_online?(character_id) do
{:ok, state} = WandererApp.Character.get_character_state(character_id)
state.is_online
end
end

View File

@@ -10,6 +10,14 @@
id="map-character-list"
class="pt-20 w-full h-full col-span-2 lg:col-span-1 p-4 pl-20 pb-20 overflow-auto"
>
<div class="mb-6 p-4 border rounded-md flex gap-2 items-center">
<.icon name="hero-information-circle-mini" class="h-5 w-5" />
<p>
'Untrack' characters leading to completely remove them from the map, required manually enable the tracking by users later.
</p>
</div>
<div class="flex flex-col gap-4 w-full">
<.live_component
module={MapCharacters}

View File

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

View File

@@ -338,6 +338,7 @@ curl -H "Authorization: Bearer <REDACTED_TOKEN>" \
```bash
GET /api/map/character-activity?map_id=<UUID>
GET /api/map/character-activity?slug=<map-slug>
GET /api/map/character-activity?map_id=<UUID>&days=7
```
- **Description:** Retrieves character activity data for a map, including passages, connections, and signatures.
@@ -345,12 +346,13 @@ GET /api/map/character-activity?slug=<map-slug>
- **Parameters:**
- `map_id` (optional if `slug` is provided) — the UUID of the map.
- `slug` (optional if `map_id` is provided) — the slug identifier of the map.
- `days` (optional) — if provided, filters activity data to only include records from the specified number of days. If not provided, returns all activity history.
#### Example Request
```bash
curl -H "Authorization: Bearer <REDACTED_TOKEN>" \
"https://wanderer.example.com/api/map/character-activity?slug=some-slug"
"https://wanderer.example.com/api/map/character-activity?slug=some-slug&days=7"
```
#### Example Response
@@ -859,7 +861,7 @@ curl -X DELETE \
{ "ok": true }
```
---
----
## Conclusion
@@ -876,9 +878,9 @@ For the most up-to-date and interactive documentation, we recommend using the Sw
If you have any questions or need assistance with the API, please reach out to the Wanderer Team.
---
----
Fly safe,
Fly safe,
**The Wanderer Team**
---
----