Compare commits

...

55 Commits

Author SHA1 Message Date
CI
d3b9b36332 chore: release version v1.15.3
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-13 07:47:06 +00:00
Dmitry Popov
90bbf29ea1 Db unix socket (#62)
* feat(Core): Add support for Unix sockets for DB connection

* chore: release version v1.15.2

* chore: release version v1.15.2
2024-11-13 11:46:39 +04:00
CI
57f73684e8 chore: release version v1.15.2
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-07 21:45:12 +00:00
Dmitry Popov
7833cdebb2 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-07 22:44:22 +01:00
Dmitry Popov
67a5ae2985 chore: release version v1.15.0 2024-11-07 22:44:19 +01:00
CI
f58c52d26b chore: release version v1.15.1 2024-11-07 21:42:31 +00:00
Dmitry Popov
41e7739461 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-07 22:41:52 +01:00
Dmitry Popov
332152b677 fix(Dev): Update .devcontainer instructions 2024-11-07 22:41:43 +01:00
CI
85b49fe1f0 chore: release version v1.15.0 2024-11-07 21:40:09 +00:00
Dmitry Popov
e7924532be feat(Connections): Add connection mark EOL time (#56) 2024-11-08 01:39:44 +04:00
CI
475d950ad6 chore: release version v1.14.1
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-06 14:13:14 +00:00
Dmitry Popov
e6cfb29c6f fix(Core): Fix character tracking permissions 2024-11-06 15:12:44 +01:00
CI
dee6e86db1 chore: release version v1.14.0
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2024-11-05 07:23:19 +00:00
Dmitry Popov
72f088331f Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-11-05 08:22:50 +01:00
Dmitry Popov
bbf536d10e feat(ACL): Add an ability to assign member role without DnD 2024-11-05 08:22:46 +01:00
CI
149ac98297 chore: release version v1.13.12
Some checks are pending
Build / 🚀 Deploy to test env (fly.io) (push) Waiting to run
Build / 🛠 Build (1.17, 18.x, 27) (push) Waiting to run
Build / 🛠 Build Docker Images (linux/amd64) (push) Blocked by required conditions
Build / 🏷 Create Release (push) Blocked by required conditions
2024-11-04 07:24:03 +00:00
Dmitry Popov
b90a2910c9 fix(Map): Fix system revert issues 2024-11-04 08:23:26 +01:00
CI
c4da8a3a8d chore: release version v1.13.11 2024-11-02 21:31:48 +00:00
Dmitry Popov
3ca75583d2 fix(Map): Fix system revert issues 2024-11-02 22:31:20 +01:00
CI
5f4607ae6f chore: release version v1.13.10 2024-11-01 14:54:49 +00:00
Dmitry Popov
d880c6873f Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/wanderer_app/map/map_server_impl.ex
2024-11-01 15:53:05 +01:00
Dmitry Popov
937649b2ed fix(Map): Fix system revert issues 2024-11-01 15:51:15 +01:00
CI
78e912c886 chore: release version v1.13.9 2024-11-01 10:55:05 +00:00
Dmitry Popov
696c7d2cd1 Map events refactoring (#41)
* refactor(Map): Map event handling refactoring
2024-11-01 14:54:34 +04:00
CI
49c0cb026b chore: release version v1.13.8 2024-10-28 16:21:55 +00:00
Dmitry Popov
7091341b4b chore: release version v1.13.7 2024-10-28 17:21:26 +01:00
CI
8795ce6af3 chore: release version v1.13.7 2024-10-28 16:01:28 +00:00
Dmitry Popov
239b34d15a chore: release version v1.13.6 2024-10-28 17:00:53 +01:00
CI
729a5ad1a9 chore: release version v1.13.6 2024-10-28 15:44:15 +00:00
Dmitry Popov
8febc2476b Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 16:43:45 +01:00
Dmitry Popov
84b1317927 chore: release version v1.12.11 2024-10-28 16:43:42 +01:00
CI
bfb504e5db chore: release version v1.13.5 2024-10-28 14:31:58 +00:00
Dmitry Popov
9975deacfb Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 15:31:28 +01:00
Dmitry Popov
f77f071003 chore: release version v1.12.11 2024-10-28 15:31:25 +01:00
CI
4a8d55e83d chore: release version v1.13.4 2024-10-28 13:10:01 +00:00
Dmitry Popov
9a99f40e2a Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 14:09:34 +01:00
Dmitry Popov
428fa8035c chore: release version v1.12.11 2024-10-28 14:09:31 +01:00
CI
745f3dee17 chore: release version v1.13.3 2024-10-28 10:59:00 +00:00
Dmitry Popov
9907cc1875 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 11:58:33 +01:00
Dmitry Popov
130cd780a2 chore: release version v1.12.11 2024-10-28 11:58:30 +01:00
CI
a808e5d1a5 chore: release version v1.13.2 2024-10-28 10:52:13 +00:00
Dmitry Popov
b926117e26 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 11:51:45 +01:00
Dmitry Popov
fdf238accf chore: release version v1.12.11 2024-10-28 11:51:42 +01:00
CI
4e1c27e8a3 chore: release version v1.13.1 2024-10-28 10:35:24 +00:00
Dmitry Popov
a3e51a0ac5 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 11:34:55 +01:00
Dmitry Popov
d27bb0d54f chore: release version v1.12.11 2024-10-28 11:34:52 +01:00
CI
f6a750f06b chore: release version v1.13.0 2024-10-28 10:18:27 +00:00
Dmitry Popov
c4e2f63e69 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-28 11:17:52 +01:00
Dmitry Popov
675ffc8f42 feat(Core): Use ESI /characters/affiliation API
Use ESI /characters/affiliation to fetch characters corporation changes
(1H cached instead of 7D)
2024-10-28 11:17:49 +01:00
CI
cdc4221175 chore: release version v1.12.11 2024-10-25 12:17:07 +00:00
Dmitry Popov
843f3363fd Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-25 14:16:32 +02:00
Dmitry Popov
17653a6374 chore: release version v1.12.6 2024-10-25 14:16:28 +02:00
CI
7d860533a0 chore: release version v1.12.10 2024-10-24 20:01:03 +00:00
Dmitry Popov
0c751b3ced Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-24 22:00:36 +02:00
Dmitry Popov
80a522ab06 chore: release version v1.12.6 2024-10-24 22:00:33 +02:00
46 changed files with 3357 additions and 2613 deletions

View File

@@ -1,12 +1,7 @@
FROM elixir:1.16-otp-25
FROM elixir:1.17-otp-27
RUN apt update -yq
RUN apt install -yq curl gnupg mc inotify-tools
RUN apt install -yq curl gnupg
RUN apt --fix-broken install
RUN apt remove -y nodejs nodejs-doc
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -
RUN apt install -y nodejs
RUN npm install --global yarn
RUN mix local.hex --force

View File

@@ -2,7 +2,7 @@ version: "0.1"
services:
db:
image: postgres:14.3
image: postgres:13-alpine
restart: always
environment:
POSTGRES_USER: postgres
@@ -10,13 +10,13 @@ services:
ports:
- "5432:5432"
volumes:
- db:/var/lib/postgresql/data
- db-new:/var/lib/postgresql/data
wanderer:
environment:
PORT: 8000
DB_HOST: db
WEB_APP_URL: "http://localhost:4444"
WEB_APP_URL: "http://localhost:8000"
ERL_AFLAGS: "-kernel shell_history enabled"
build:
context: .
@@ -33,4 +33,4 @@ services:
volumes:
elixir-artifacts: {}
db: {}
db-new: {}

View File

@@ -58,6 +58,8 @@ jobs:
otp: ["27"]
elixir: ["1.17"]
node-version: ["18.x"]
outputs:
commit_hash: ${{ steps.generate-changelog.outputs.commit_hash }}
steps:
- name: Prepare
run: |
@@ -108,16 +110,17 @@ jobs:
run: mix compile
- name: Generate Changelog & Update Tag Version
id: generate-changelog
run: |
git config --global user.name 'CI'
git config --global user.email 'ci@users.noreply.github.com'
mix git_ops.release --force-patch --yes
git push --follow-tags
echo "commit_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
docker:
name: 🛠 Build Docker Images
needs:
- build
needs: build
runs-on: ubuntu-22.04
permissions:
checks: write
@@ -141,6 +144,7 @@ jobs:
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
ref: ${{ needs.build.outputs.commit_hash }}
fetch-depth: 0
- name: Prepare Changelog
@@ -189,6 +193,30 @@ jobs:
- name: Image digest
run: echo ${{ steps.build.outputs.digest }}
- uses: markpatterson27/markdown-to-output@v1
id: extract-changelog
with:
filepath: CHANGELOG.md
- name: Get content
uses: 2428392/gh-truncate-string-action@v1.3.0
id: get-content
with:
stringToTruncate: |
📣 Wanderer new release available 🎉
**Version**: ${{ steps.get-latest-tag.outputs.tag }}
${{ steps.extract-changelog.outputs.body }}
maxLength: 500
truncationSymbol: "…"
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: ${{ steps.get-content.outputs.string }}
create-release:
name: 🏷 Create Release
runs-on: ubuntu-22.04

View File

@@ -18,4 +18,4 @@ jobs:
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.PORT }}
script: |
/app/release/linux/deploy.sh ${{ github.event.release.tag_name }}
/home/wanderer/app/deploy.sh ${{ github.event.release.tag_name }}

View File

@@ -1,3 +1,3 @@
erlang 25.3
elixir 1.16-otp-25
erlang 26.2.5.5
elixir 1.17.3-otp-26
nodejs 18.0.0

View File

@@ -2,31 +2,97 @@
<!-- changelog -->
## [v1.12.9](https://github.com/wanderer-industries/wanderer/compare/v1.12.8...v1.12.9) (2024-10-24)
## [v1.15.3](https://github.com/wanderer-industries/wanderer/compare/v1.15.2...v1.15.3) (2024-11-13)
## [v1.12.8](https://github.com/wanderer-industries/wanderer/compare/v1.12.7...v1.12.8) (2024-10-24)
## [v1.15.2](https://github.com/wanderer-industries/wanderer/compare/v1.15.1...v1.15.2) (2024-11-07)
## [v1.12.7](https://github.com/wanderer-industries/wanderer/compare/v1.12.6...v1.12.7) (2024-10-24)
## [v1.15.1](https://github.com/wanderer-industries/wanderer/compare/v1.15.0...v1.15.1) (2024-11-07)
## [v1.12.6](https://github.com/wanderer-industries/wanderer/compare/v1.12.5...v1.12.6) (2024-10-24)
### Bug Fixes:
* Dev: Update .devcontainer instructions
## [v1.15.0](https://github.com/wanderer-industries/wanderer/compare/v1.14.1...v1.15.0) (2024-11-07)
## [v1.12.5](https://github.com/wanderer-industries/wanderer/compare/v1.12.4...v1.12.5) (2024-10-22)
### Features:
* Connections: Add connection mark EOL time (#56)
## [v1.14.1](https://github.com/wanderer-industries/wanderer/compare/v1.14.0...v1.14.1) (2024-11-06)
### Bug Fixes:
* Core: Fix character tracking permissions
## [v1.14.0](https://github.com/wanderer-industries/wanderer/compare/v1.13.12...v1.14.0) (2024-11-05)
### Features:
* ACL: Add an ability to assign member role without DnD
## [v1.13.12](https://github.com/wanderer-industries/wanderer/compare/v1.13.11...v1.13.12) (2024-11-04)
### Bug Fixes:
* Map: Fix system revert issues
## [v1.13.11](https://github.com/wanderer-industries/wanderer/compare/v1.13.10...v1.13.11) (2024-11-02)
### Bug Fixes:
* Map: Fix system revert issues
## [v1.13.10](https://github.com/wanderer-industries/wanderer/compare/v1.13.9...v1.13.10) (2024-11-01)
### Bug Fixes:
* Map: Fix system revert issues
## [v1.13.9](https://github.com/wanderer-industries/wanderer/compare/v1.13.8...v1.13.9) (2024-11-01)
## [v1.13.8](https://github.com/wanderer-industries/wanderer/compare/v1.13.7...v1.13.8) (2024-10-28)
## [v1.13.0](https://github.com/wanderer-industries/wanderer/compare/v1.12.11...v1.13.0) (2024-10-28)
### Features:
* Core: Use ESI /characters/affiliation API
## [v1.12.4](https://github.com/wanderer-industries/wanderer/compare/v1.12.3...v1.12.4) (2024-10-21)
@@ -45,11 +111,6 @@
* Map: Fix regression issues
## [v1.12.2](https://github.com/wanderer-industries/wanderer/compare/v1.12.1...v1.12.2) (2024-10-16)
## [v1.12.1](https://github.com/wanderer-industries/wanderer/compare/v1.12.0...v1.12.1) (2024-10-16)
@@ -68,31 +129,6 @@
* Map: Prettify user settings
## [v1.11.5](https://github.com/wanderer-industries/wanderer/compare/v1.11.4...v1.11.5) (2024-10-16)
## [v1.11.4](https://github.com/wanderer-industries/wanderer/compare/v1.11.3...v1.11.4) (2024-10-16)
## [v1.11.3](https://github.com/wanderer-industries/wanderer/compare/v1.11.2...v1.11.3) (2024-10-16)
## [v1.11.2](https://github.com/wanderer-industries/wanderer/compare/v1.11.1...v1.11.2) (2024-10-15)
## [v1.11.1](https://github.com/wanderer-industries/wanderer/compare/v1.11.0...v1.11.1) (2024-10-14)
## [v1.11.0](https://github.com/wanderer-industries/wanderer/compare/v1.10.0...v1.11.0) (2024-10-14)

View File

@@ -20,11 +20,11 @@ Interested to learn more? [Check more on our website](https://wanderer.ltd/news)
Wanderer is open source project and we have a free as in beer and self-hosted solution called [Wanderer Community Edition (CE)](https://wanderer.ltd/news/community-edition). Here are the differences between Wanderer and Wanderer CE:
| | Wanderer Cloud | Wanderer Community Edition |
| ------------- | ------------- | ------------- |
| **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you dont have to worry about anything and can focus on gameplay. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on.|
| **Release schedule** | Continuously developed and improved with new features and updates multiple times per week. | Latest features and improvements won't be immediately available.|
| **Server location** | All visitor data is exclusively processed on EU-owned cloud infrastructure. We keep your site data on a secure, encrypted and green energy powered server in Germany. This ensures that your site data is protected by the strict European Union data privacy laws and ensures compliance with GDPR. Your website data never leaves the EU. | You have full control and can host your instance on any server in any country that you wish. Host it on a server in your basement or host it with any cloud provider wherever you want, even those that are not GDPR compliant.|
| | Wanderer Cloud | Wanderer Community Edition |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Infrastructure management** | Easy and convenient. It takes 2 minutes to register your character and create a map. We manage everything so you dont have to worry about anything and can focus on gameplay. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on. |
| **Release schedule** | Continuously developed and improved with new features and updates multiple times per week. | Latest features and improvements won't be immediately available. |
| **Server location** | All visitor data is exclusively processed on EU-owned cloud infrastructure. We keep your site data on a secure, encrypted and green energy powered server in Germany. This ensures that your site data is protected by the strict European Union data privacy laws and ensures compliance with GDPR. Your website data never leaves the EU. | You have full control and can host your instance on any server in any country that you wish. Host it on a server in your basement or host it with any cloud provider wherever you want, even those that are not GDPR compliant. |
Interested in self-hosting Wanderer CE on your server? Take a look at our [Wanderer CE installation instructions](https://github.com/wanderer-industries/community-edition/).
@@ -54,7 +54,13 @@ Now you can visit [`localhost:8000`](http://localhost:8000) from your browser.
#### Using .devcontainer
- Run devcontainer
- See how to start server in #setup section
- Install additional dependencies inside Dev container
- `root@0d0a785313b6:/app# apt update`
- `root@0d0a785313b6:/app# curl -sL https://deb.nodesource.com/setup_18.x | bash -`
- `root@0d0a785313b6:/app# apt-get install nodejs inotify-tools -y`
- `root@0d0a785313b6:/app# mix setup`
- See how to run server in #Run section
#### Using nix flakes

View File

@@ -18,7 +18,7 @@ export const useTagMenu = (
ref.current = { onSystemTag, systems, systemId };
return useCallback(() => {
const { onSystemTag, systemId , systems} = ref.current;
const { onSystemTag, systemId, systems } = ref.current;
const system = systemId ? getSystemById(systems, systemId) : undefined;
const isSelectedLetters = AVAILABLE_LETTERS.includes(system?.tag ?? '');

View File

@@ -1,17 +1,18 @@
import { Node, useReactFlow } from 'reactflow';
import { useCallback } from 'react';
import { useCallback, useRef } from 'react';
import { CommandAddSystems } from '@/hooks/Mapper/types/mapHandlers.ts';
import { convertSystem2Node } from '../../helpers';
export const useMapAddSystems = () => {
const rf = useReactFlow();
return useCallback(
(systems: CommandAddSystems) => {
const nodes = rf.getNodes();
const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
rf.addNodes(prepared);
},
[rf],
);
const ref = useRef({ rf });
ref.current = { rf };
return useCallback((systems: CommandAddSystems) => {
const { rf } = ref.current;
const nodes = rf.getNodes();
const prepared: Node[] = systems.filter(x => !nodes.some(y => x.id === y.id)).map(convertSystem2Node);
rf.addNodes(prepared);
}, []);
};

View File

@@ -79,7 +79,7 @@ export const SystemCustomLabelDialog = ({ systemId, visible, setVisible }: Syste
// @ts-ignore
const handleInput = useCallback(e => {
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9[\](){}]/g, '');
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9\-[\](){}]/g, '');
}, []);
return (

View File

@@ -90,7 +90,7 @@ export const SystemSettingsDialog = ({ systemId, visible, setVisible }: SystemSe
}, []);
const handleInput = useCallback((e: any) => {
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9[\](){}]/g, '');
e.target.value = e.target.value.toUpperCase().replace(/[^A-Z0-9\-[\](){}]/g, '');
}, []);
return (

View File

@@ -1,13 +1,20 @@
import classes from './Connections.module.scss';
import { Sidebar } from 'primereact/sidebar';
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useState, useCallback } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { VirtualScroller, VirtualScrollerTemplateOptions } from 'primereact/virtualscroller';
import clsx from 'clsx';
import { ConnectionOutput, OutCommand, Passage, SolarSystemConnection } from '@/hooks/Mapper/types';
import {
ConnectionOutput,
ConnectionInfoOutput,
OutCommand,
Passage,
SolarSystemConnection,
} from '@/hooks/Mapper/types';
import { PassageCard } from './PassageCard';
import { InfoDrawer, SystemView } from '@/hooks/Mapper/components/ui-kit';
import { kgToTons } from '@/hooks/Mapper/utils/kgToTons.ts';
import { TimeAgo } from '@/hooks/Mapper/components/ui-kit';
const sortByDate = (a: string, b: string) => new Date(a).getTime() - new Date(b).getTime();
@@ -69,25 +76,44 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
}, [connections, selectedConnection]);
const [passages, setPassages] = useState<Passage[]>([]);
const [info, setInfo] = useState<ConnectionInfoOutput>(null);
const loadInfo = useCallback(
async (connection: SolarSystemConnection) => {
const result = await outCommand<ConnectionInfoOutput>({
type: OutCommand.getConnectionInfo,
data: {
from: connection.source,
to: connection.target,
},
});
setInfo(result);
},
[outCommand],
);
const loadPassages = useCallback(
async (connection: SolarSystemConnection) => {
const result = await outCommand<ConnectionOutput>({
type: OutCommand.getPassages,
data: {
from: connection.source,
to: connection.target,
},
});
setPassages(result.passages.sort((a, b) => sortByDate(b.inserted_at, a.inserted_at)));
},
[outCommand],
);
useEffect(() => {
if (!selectedConnection) {
return;
}
const loadInfo = async () => {
const result = await outCommand<ConnectionOutput>({
type: OutCommand.getPassages,
data: {
from: selectedConnection.source,
to: selectedConnection.target,
},
});
setPassages(result.passages.sort((a, b) => sortByDate(b.inserted_at, a.inserted_at)));
};
loadInfo();
loadInfo(selectedConnection);
loadPassages(selectedConnection);
}, [selectedConnection]);
const approximateMass = useMemo(() => {
@@ -132,6 +158,10 @@ export const Connections = ({ selectedConnection, onHide }: OnTheMapProps) => {
{kgToTons(approximateMass)}
</InfoDrawer>
<InfoDrawer title="Mark EOL Time" rightSide>
{info?.marl_eol_time ? <TimeAgo timestamp={info.marl_eol_time} /> : ' unknown '}
</InfoDrawer>
<div className="flex gap-2"></div>
</div>

View File

@@ -11,6 +11,10 @@ export type Passage = {
character: PassageLimitedCharacterType;
};
export type ConnectionInfoOutput = {
marl_eol_time: string;
};
export type ConnectionOutput = {
passages: Passage[];
};

View File

@@ -118,6 +118,7 @@ export enum OutCommand {
getCharacterJumps = 'get_character_jumps',
getSignatures = 'get_signatures',
getSystemStaticInfos = 'get_system_static_infos',
getConnectionInfo = 'get_connection_info',
updateConnectionTimeStatus = 'update_connection_time_status',
updateConnectionMassStatus = 'update_connection_mass_status',
updateConnectionShipSizeType = 'update_connection_ship_size_type',

View File

@@ -8,7 +8,7 @@ export default {
const selector = '#' + this.el.id;
const droppable = new Droppable(containers, {
delay: 150,
delay: 100,
draggable: '.draggable',
dropzone: '.dropzone',
mirror: {

View File

@@ -55,11 +55,11 @@ map_subscriptions_enabled =
map_subscription_characters_limit =
config_dir
|> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_CHARACTERS_LIMIT", 100)
|> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_CHARACTERS_LIMIT", 10_000)
map_subscription_hubs_limit =
config_dir
|> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_HUBS_LIMIT", 10)
|> get_int_from_path_or_env("WANDERER_MAP_SUBSCRIPTION_HUBS_LIMIT", 100)
wallet_tracking_enabled =
config_dir
@@ -138,6 +138,7 @@ config :ueberauth, WandererApp.Ueberauth.Strategy.Eve.OAuth,
System.get_env("EVE_CLIENT_WITH_CORP_WALLET_SECRET", "<EVE_CLIENT_WITH_CORP_WALLET_SECRET>")
config :logger,
truncate: :infinity,
level:
String.to_existing_atom(
System.get_env(
@@ -171,43 +172,65 @@ config :wanderer_app, WandererApp.Scheduler,
timeout: :infinity
if config_env() == :prod do
database_url =
System.get_env("DATABASE_URL") ||
raise """
environment variable DATABASE_URL is missing.
For example: ecto://USER:PASS@HOST/DATABASE
"""
database_unix_socket =
System.get_env("DATABASE_UNIX_SOCKET")
maybe_ipv6 =
config_dir
|> get_var_from_path_or_env("ECTO_IPV6", "false")
|> String.to_existing_atom()
database =
database_unix_socket
|> case do
true -> [:inet6]
_ -> []
end
nil ->
System.get_env("DATABASE_URL") ||
raise """
environment variable DATABASE_URL is missing.
For example: ecto://USER:PASS@HOST/DATABASE
"""
db_ssl_enabled =
config_dir
|> get_var_from_path_or_env("DATABASE_SSL_ENABLED", "false")
|> String.to_existing_atom()
db_ssl_verify_none =
config_dir
|> get_var_from_path_or_env("DATABASE_SSL_VERIFY_NONE", "false")
|> String.to_existing_atom()
client_opts =
if db_ssl_verify_none do
[verify: :verify_none]
_ ->
System.get_env("DATABASE_NAME") ||
raise """
environment variable DATABASE_NAME is missing.
For example: "wanderer"
"""
end
config :wanderer_app, WandererApp.Repo,
url: database_url,
ssl: db_ssl_enabled,
ssl_opts: client_opts,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
socket_options: maybe_ipv6
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
if not is_nil(database_unix_socket) do
config :wanderer_app, WandererApp.Repo,
socket_dir: database_unix_socket,
database: database
else
db_ssl_enabled =
config_dir
|> get_var_from_path_or_env("DATABASE_SSL_ENABLED", "false")
|> String.to_existing_atom()
db_ssl_verify_none =
config_dir
|> get_var_from_path_or_env("DATABASE_SSL_VERIFY_NONE", "false")
|> String.to_existing_atom()
client_opts =
if db_ssl_verify_none do
[verify: :verify_none]
end
maybe_ipv6 =
config_dir
|> get_var_from_path_or_env("ECTO_IPV6", "false")
|> String.to_existing_atom()
|> case do
true -> [:inet6]
_ -> []
end
config :wanderer_app, WandererApp.Repo,
url: database,
ssl: db_ssl_enabled,
ssl_opts: client_opts,
socket_options: maybe_ipv6
end
# The secret key base is used to sign/encrypt cookies and other secrets.
# A default value is used in config/dev.exs and config/test.exs but you

View File

@@ -6,6 +6,7 @@
app = 'wanderer'
primary_region = 'ams'
kill_signal = 'SIGTERM'
swap_size_mb = 512
[build]
@@ -35,6 +36,6 @@ path = "/metrics"
soft_limit = 1000
[[vm]]
memory = '1gb'
memory = '256mb'
cpu_kind = 'shared'
cpus = 1

View File

@@ -7,6 +7,8 @@ defmodule WandererApp do
if it comes from the database, an external API or others.
"""
require Logger
@doc """
When used, dispatch to the appropriate domain service
"""

View File

@@ -4,8 +4,6 @@ defmodule WandererApp.Api.Calculations.CalcMapPermissions do
use Ash.Resource.Calculation
require Ash.Query
import Bitwise
@impl true
def load(_query, _opts, _context) do
[
@@ -17,116 +15,8 @@ defmodule WandererApp.Api.Calculations.CalcMapPermissions do
end
@impl true
def calculate([record], _opts, %{actor: actor}) do
characters = actor.characters
character_ids = characters |> Enum.map(& &1.id)
character_eve_ids = characters |> Enum.map(& &1.eve_id)
character_corporation_ids =
characters |> Enum.map(& &1.corporation_id) |> Enum.map(&to_string/1)
character_alliance_ids = characters |> Enum.map(& &1.alliance_id) |> Enum.map(&to_string/1)
result =
record.acls
|> Enum.reduce([0, 0], fn acl, acc ->
is_owner? = acl.owner_id in character_ids
is_character_member? =
acl.members |> Enum.any?(fn member -> member.eve_character_id in character_eve_ids end)
is_corporation_member? =
acl.members
|> Enum.any?(fn member -> member.eve_corporation_id in character_corporation_ids end)
is_alliance_member? =
acl.members
|> Enum.any?(fn member -> member.eve_alliance_id in character_alliance_ids end)
if is_owner? || is_character_member? || is_corporation_member? || is_alliance_member? do
case acc do
[_, -1] ->
[-1, -1]
[-1, char_acc] ->
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[-1, char_acc]
[any_acc, char_acc] ->
any_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids ||
member.eve_corporation_id in character_corporation_ids ||
member.eve_alliance_id in character_alliance_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> WandererApp.Permissions.calc_role_mask(member.role, acc)
end
end)
any_acc =
case any_acl_mask do
-1 -> -1
_ -> any_acc ||| any_acl_mask
end
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[any_acc, char_acc]
end
else
acc
end
end)
case result do
[_, -1] ->
[-1]
[-1, char_acc] ->
[char_acc]
[any_acc, _char_acc] ->
[any_acc]
end
end
def calculate([record], _opts, %{actor: actor}),
do: WandererApp.Permissions.check_characters_access(actor.characters, record.acls)
@impl true
def calculate(_records, _opts, _context) do

View File

@@ -3,6 +3,8 @@ defmodule WandererApp.Application do
use Application
require Logger
@impl true
def start(_type, _args) do
children =
@@ -45,7 +47,16 @@ defmodule WandererApp.Application do
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: WandererApp.Supervisor]
Supervisor.start_link(children, opts)
|> case do
{:ok, _pid} = ok ->
ok
{:error, info} = e ->
Logger.error("Failed to start application: #{inspect(info)}")
e
end
end
# Tell Phoenix to update the endpoint configuration

View File

@@ -73,7 +73,8 @@ defmodule WandererApp.Character.Tracker do
case WandererApp.Esi.get_character_info(eve_id) do
{:ok, info} ->
{:ok, character_state} = WandererApp.Character.get_character_state(character_id)
update = maybe_update_corporation(character_state, info)
update = maybe_update_corporation(character_state, eve_id |> String.to_integer())
WandererApp.Character.update_character_state(character_id, update)
:ok
@@ -103,7 +104,9 @@ defmodule WandererApp.Character.Tracker do
end
def update_ship(%{character_id: character_id, track_ship: true} = character_state) do
case WandererApp.Character.get_character(character_id) do
character_id
|> WandererApp.Character.get_character()
|> case do
{:ok, %{eve_id: eve_id, access_token: access_token}} when not is_nil(access_token) ->
WandererApp.Cache.has_key?("character:#{character_id}:ship_forbidden")
|> case do
@@ -541,12 +544,17 @@ defmodule WandererApp.Character.Tracker do
defp maybe_update_corporation(
state,
%{
"corporation_id" => corporation_id
} = _info
character_eve_id
)
when not is_nil(corporation_id),
do: update_corporation(state, corporation_id)
when not is_nil(character_eve_id) and is_integer(character_eve_id) do
case WandererApp.Esi.post_characters_affiliation([character_eve_id]) do
{:ok, [character_aff_info]} when not is_nil(character_aff_info) ->
update_corporation(state, character_aff_info |> Map.get("corporation_id"))
error ->
state
end
end
defp maybe_update_corporation(
state,

View File

@@ -5,6 +5,10 @@ defmodule WandererApp.Esi do
defdelegate get_alliance_info(eve_id, opts \\ []), to: WandererApp.Esi.ApiClient
defdelegate get_corporation_info(eve_id, opts \\ []), to: WandererApp.Esi.ApiClient
defdelegate get_character_info(eve_id, opts \\ []), to: WandererApp.Esi.ApiClient
defdelegate post_characters_affiliation(character_eve_ids, opts \\ []),
to: WandererApp.Esi.ApiClient
defdelegate get_character_wallet(character_eve_id, opts \\ []), to: WandererApp.Esi.ApiClient
defdelegate get_corporation_wallets(corporation_id, opts \\ []), to: WandererApp.Esi.ApiClient

View File

@@ -37,21 +37,32 @@ defmodule WandererApp.Esi.ApiClient do
@logger Application.compile_env(:wanderer_app, :logger)
def get_server_status, do: _get("/status")
def get_server_status, do: get("/status")
def set_autopilot_waypoint(add_to_beginning, clear_other_waypoints, destination_id, opts \\ []) do
_post_esi(
"/ui/autopilot/waypoint",
opts
|> Keyword.merge(
params: %{
add_to_beginning: add_to_beginning,
clear_other_waypoints: clear_other_waypoints,
destination_id: destination_id
}
def set_autopilot_waypoint(add_to_beginning, clear_other_waypoints, destination_id, opts \\ []),
do:
post_esi(
"/ui/autopilot/waypoint",
opts
|> Keyword.merge(
params: %{
add_to_beginning: add_to_beginning,
clear_other_waypoints: clear_other_waypoints,
destination_id: destination_id
}
)
)
)
end
def post_characters_affiliation(character_eve_ids, _opts)
when is_list(character_eve_ids),
do:
post(
"#{@base_url}/characters/affiliation/",
json: character_eve_ids,
params: %{
datasource: "tranquility"
}
)
def find_routes(map_id, origin, hubs, routes_settings) do
origin = origin |> String.to_integer()
@@ -173,7 +184,7 @@ defmodule WandererApp.Esi.ApiClient do
routes =
all_routes
|> Enum.map(fn route_info ->
_map_route_info(route_info)
map_route_info(route_info)
end)
|> Enum.filter(fn route_info -> not is_nil(route_info) end)
@@ -189,7 +200,7 @@ defmodule WandererApp.Esi.ApiClient do
{:ok, result}
_ ->
case _get_all_routes_custom(hubs, origin, params) do
case get_all_routes_custom(hubs, origin, params) do
{:ok, result} ->
WandererApp.Cache.insert(
cache_key,
@@ -199,22 +210,21 @@ defmodule WandererApp.Esi.ApiClient do
{:ok, result}
{:error, error} ->
@logger.error("Error getting custom routes for #{inspect(origin)}: #{inspect(error)}")
{:error, _error} ->
@logger.error("Error getting custom routes for #{inspect(origin)}: #{inspect(hubs)}")
@logger.error(
"Error getting custom routes for #{inspect(origin)}: #{inspect(params)}"
)
_get_all_routes_eve(hubs, origin, params, opts)
get_all_routes_eve(hubs, origin, params, opts)
end
end
end
defp _get_all_routes_custom(hubs, origin, params),
defp get_all_routes_custom(hubs, origin, params),
do:
_post(
post(
"#{get_custom_route_base_url()}/route/multiple",
[
json: %{
@@ -228,7 +238,7 @@ defmodule WandererApp.Esi.ApiClient do
|> Keyword.merge(@timeout_opts)
)
def _get_all_routes_eve(hubs, origin, params, opts),
def get_all_routes_eve(hubs, origin, params, opts),
do:
{:ok,
hubs
@@ -297,7 +307,7 @@ defmodule WandererApp.Esi.ApiClient do
opts: [ttl: @ttl]
)
def get_character_info(eve_id, opts \\ []) do
case _get(
case get(
"/characters/#{eve_id}/",
opts |> _with_cache_opts()
) do
@@ -374,7 +384,7 @@ defmodule WandererApp.Esi.ApiClient do
defp _get_routes_eve(origin, destination, params, opts),
do:
_get(
get(
"/route/#{origin}/#{destination}/?#{params |> Plug.Conn.Query.encode()}",
opts |> _with_cache_opts()
)
@@ -383,14 +393,14 @@ defmodule WandererApp.Esi.ApiClient do
defp _get_alliance_info(alliance_eve_id, info_path, opts),
do:
_get(
get(
"/alliances/#{alliance_eve_id}/#{info_path}",
opts |> _with_cache_opts()
)
defp _get_corporation_info(corporation_eve_id, info_path, opts),
do:
_get(
get(
"/corporations/#{corporation_eve_id}/#{info_path}",
opts |> _with_cache_opts()
)
@@ -405,7 +415,7 @@ defmodule WandererApp.Esi.ApiClient do
character_id = opts |> Keyword.get(:character_id, nil)
if not _is_access_token_expired?(character_id) do
_get(
get(
path,
auth_opts,
opts
@@ -426,7 +436,7 @@ defmodule WandererApp.Esi.ApiClient do
defp _get_corporation_auth_data(corporation_eve_id, info_path, opts),
do:
_get(
get(
"/corporations/#{corporation_eve_id}/#{info_path}",
[params: opts[:params] || []] ++
(opts |> _get_auth_opts() |> _with_cache_opts()),
@@ -437,14 +447,14 @@ defmodule WandererApp.Esi.ApiClient do
opts |> Keyword.merge(@cache_opts) |> Keyword.merge(cache_dir: System.tmp_dir!())
end
defp _post_esi(path, opts),
defp post_esi(path, opts),
do:
_post(
post(
"#{@base_url}#{path}",
[params: opts[:params] || []] ++ (opts |> _get_auth_opts())
)
defp _get(path, api_opts \\ [], opts \\ []) do
defp get(path, api_opts \\ [], opts \\ []) do
try do
case Req.get("#{@base_url}#{path}", api_opts |> Keyword.merge(@retry_opts)) do
{:ok, %{status: 200, body: body}} ->
@@ -476,7 +486,7 @@ defmodule WandererApp.Esi.ApiClient do
end
end
defp _post(url, opts) do
defp post(url, opts) do
try do
case Req.post("#{url}", opts) do
{:ok, %{status: status, body: body}} when status in [200, 201] ->
@@ -514,7 +524,7 @@ defmodule WandererApp.Esi.ApiClient do
{:ok, token} ->
auth_opts = [access_token: token.access_token] |> _get_auth_opts()
_get(
get(
path,
api_opts |> Keyword.merge(auth_opts),
opts |> Keyword.merge(retry_count: retry_count + 1)
@@ -589,7 +599,7 @@ defmodule WandererApp.Esi.ApiClient do
end
end
defp _map_route_info(
defp map_route_info(
%{
"origin" => origin,
"destination" => destination,
@@ -598,14 +608,14 @@ defmodule WandererApp.Esi.ApiClient do
} = _route_info
),
do:
_map_route_info(%{
map_route_info(%{
origin: origin,
destination: destination,
systems: result_systems,
success: success
})
defp _map_route_info(
defp map_route_info(
%{origin: origin, destination: destination, systems: result_systems, success: success} =
_route_info
) do
@@ -627,5 +637,5 @@ defmodule WandererApp.Esi.ApiClient do
}
end
defp _map_route_info(_), do: nil
defp map_route_info(_), do: nil
end

View File

@@ -8,6 +8,7 @@ defmodule WandererApp.Map do
defstruct map_id: nil,
name: nil,
scope: :none,
owner_id: nil,
characters: [],
systems: Map.new(),
hubs: [],
@@ -16,11 +17,12 @@ defmodule WandererApp.Map do
characters_limit: nil,
hubs_limit: nil
def new(%{id: map_id, name: name, scope: scope, acls: acls, hubs: hubs}) do
def new(%{id: map_id, name: name, scope: scope, owner_id: owner_id, acls: acls, hubs: hubs}) do
map =
struct!(__MODULE__,
map_id: map_id,
scope: scope,
owner_id: owner_id,
name: name,
acls: acls,
hubs: hubs
@@ -214,7 +216,7 @@ defmodule WandererApp.Map do
%{visible: true} = system ->
system
_ ->
_system ->
nil
end
end
@@ -262,7 +264,7 @@ defmodule WandererApp.Map do
case not Map.has_key?(systems, solar_system_id) do
true ->
map_id
|> update_map(%{systems: Map.put_new(systems, solar_system_id, system)})
|> update_map(%{systems: Map.put(systems, solar_system_id, system)})
:ok

View File

@@ -75,7 +75,7 @@ defmodule WandererApp.Map.PositionCalculator do
def get_available_positions(level, x, y, opts),
do: adjusted_coordinates(1 + level * 2, x, y, opts)
defp edge_coordinates(n, opts) when n > 1 do
defp edge_coordinates(n, _opts) when n > 1 do
min = -div(n, 2)
max = div(n, 2)
# Top edge

View File

@@ -183,6 +183,12 @@ defmodule WandererApp.Map.Server do
|> map_pid!
|> GenServer.cast({&Impl.delete_connection/2, [connection_info]})
def get_connection_info(map_id, connection_info) when is_binary(map_id),
do:
map_id
|> map_pid!
|> GenServer.call({&Impl.get_connection_info/2, [connection_info]}, :timer.minutes(1))
def update_connection_time_status(map_id, connection_info) when is_binary(map_id),
do:
map_id

View File

@@ -77,6 +77,7 @@ defmodule WandererApp.Map.Server.Impl do
# @unknown 100_100
@systems_cleanup_timeout :timer.minutes(30)
@characters_cleanup_timeout :timer.minutes(1)
@connections_cleanup_timeout :timer.minutes(2)
@connection_time_status_eol 1
@@ -112,20 +113,28 @@ defmodule WandererApp.Map.Server.Impl do
end
def load_state(%__MODULE__{map_id: map_id} = state) do
with {:ok, map} <- WandererApp.MapRepo.get(map_id, [:acls, :characters]),
with {:ok, map} <-
WandererApp.MapRepo.get(map_id, [
:owner,
:characters,
acls: [
:owner_id,
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
]
]),
{:ok, systems} <- WandererApp.MapSystemRepo.get_visible_by_map(map_id),
{:ok, connections} <- WandererApp.MapConnectionRepo.get_by_map(map_id),
{:ok, subscription_settings} <-
WandererApp.Map.SubscriptionManager.get_active_map_subscription(map_id) do
state
|> _init_map(
|> init_map(
map,
subscription_settings,
systems,
connections
)
|> _init_map_systems(systems)
|> _init_map_cache()
|> init_map_systems(systems)
|> init_map_cache()
else
error ->
@logger.error("Failed to load map state: #{inspect(error, pretty: true)}")
@@ -134,7 +143,7 @@ defmodule WandererApp.Map.Server.Impl do
end
def start_map(%__MODULE__{map: map, map_id: map_id} = state) do
with :ok <- _track_acls(map.acls |> Enum.map(& &1.id)) do
with :ok <- track_acls(map.acls |> Enum.map(& &1.id)) do
@pubsub_client.subscribe(
WandererApp.PubSub,
"maps:#{map_id}"
@@ -144,7 +153,8 @@ defmodule WandererApp.Map.Server.Impl do
Process.send_after(self(), :update_tracked_characters, 100)
Process.send_after(self(), :update_presence, @update_presence_timeout)
Process.send_after(self(), :cleanup_connections, 5000)
Process.send_after(self(), :cleanup_systems, 10000)
Process.send_after(self(), :cleanup_systems, 10_000)
Process.send_after(self(), :cleanup_characters, :timer.minutes(5))
Process.send_after(self(), :backup_state, @backup_state_timeout)
WandererApp.Cache.insert("map_#{map_id}:started", true)
@@ -169,7 +179,7 @@ defmodule WandererApp.Map.Server.Impl do
:telemetry.execute([:wanderer_app, :map, :stopped], %{count: 1})
state
|> _maybe_stop_rtree()
|> maybe_stop_rtree()
end
def get_map(%{map: map} = _state), do: {:ok, map}
@@ -193,7 +203,9 @@ defmodule WandererApp.Map.Server.Impl do
:ok
else
{:error, _error} ->
_error ->
{:ok, character} = WandererApp.Character.get_character(character_id)
broadcast!(map_id, :character_added, character)
:ok
end
end)
@@ -247,37 +259,37 @@ defmodule WandererApp.Map.Server.Impl do
state,
update
),
do: state |> _update_system(:update_name, [:name], update)
do: state |> update_system(:update_name, [:name], update)
def update_system_description(
state,
update
),
do: state |> _update_system(:update_description, [:description], update)
do: state |> update_system(:update_description, [:description], update)
def update_system_status(
state,
update
),
do: state |> _update_system(:update_status, [:status], update)
do: state |> update_system(:update_status, [:status], update)
def update_system_tag(
state,
update
),
do: state |> _update_system(:update_tag, [:tag], update)
do: state |> update_system(:update_tag, [:tag], update)
def update_system_locked(
state,
update
),
do: state |> _update_system(:update_locked, [:locked], update)
do: state |> update_system(:update_locked, [:locked], update)
def update_system_labels(
state,
update
),
do: state |> _update_system(:update_labels, [:labels], update)
do: state |> update_system(:update_labels, [:labels], update)
def update_system_position(
%{rtree_name: rtree_name} = state,
@@ -285,7 +297,7 @@ defmodule WandererApp.Map.Server.Impl do
),
do:
state
|> _update_system(
|> update_system(
:update_position,
[:position_x, :position_y],
update,
@@ -433,12 +445,34 @@ defmodule WandererApp.Map.Server.Impl do
state
end
def get_connection_info(
%{map_id: map_id} = _state,
%{
solar_system_source_id: solar_system_source_id,
solar_system_target_id: solar_system_target_id
} = _connection_info
) do
WandererApp.Map.find_connection(
map_id,
solar_system_source_id,
solar_system_target_id
)
|> case do
{:ok, %{id: connection_id}} ->
connection_mark_eol_time = get_connection_mark_eol_time(map_id, connection_id, nil)
{:ok, %{marl_eol_time: connection_mark_eol_time}}
_ ->
{:error, :not_found}
end
end
def update_connection_time_status(
%{map_id: map_id} = state,
connection_update
),
do:
_update_connection(state, :update_time_status, [:time_status], connection_update, fn
update_connection(state, :update_time_status, [:time_status], connection_update, fn
%{id: connection_id, time_status: time_status} ->
case time_status == @connection_time_status_eol do
true ->
@@ -457,25 +491,25 @@ defmodule WandererApp.Map.Server.Impl do
state,
connection_update
),
do: _update_connection(state, :update_mass_status, [:mass_status], connection_update)
do: update_connection(state, :update_mass_status, [:mass_status], connection_update)
def update_connection_ship_size_type(
state,
connection_update
),
do: _update_connection(state, :update_ship_size_type, [:ship_size_type], connection_update)
do: update_connection(state, :update_ship_size_type, [:ship_size_type], connection_update)
def update_connection_locked(
state,
connection_update
),
do: _update_connection(state, :update_locked, [:locked], connection_update)
do: update_connection(state, :update_locked, [:locked], connection_update)
def update_connection_custom_info(
state,
connection_update
),
do: _update_connection(state, :update_custom_info, [:custom_info], connection_update)
do: update_connection(state, :update_custom_info, [:custom_info], connection_update)
def import_settings(%{map_id: map_id} = state, settings, user_id) do
WandererApp.Cache.put(
@@ -485,9 +519,9 @@ defmodule WandererApp.Map.Server.Impl do
state =
state
|> _maybe_import_systems(settings, user_id, nil)
|> _maybe_import_connections(settings, user_id)
|> _maybe_import_hubs(settings, user_id)
|> maybe_import_systems(settings, user_id, nil)
|> maybe_import_connections(settings, user_id)
|> maybe_import_hubs(settings, user_id)
WandererApp.Cache.take("map_#{map_id}:importing")
@@ -507,11 +541,11 @@ defmodule WandererApp.Map.Server.Impl do
|> Enum.map(fn character_id ->
Task.start_link(fn ->
character_updates =
_maybe_update_online(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_online(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)
@@ -519,7 +553,7 @@ defmodule WandererApp.Map.Server.Impl do
update
|> case do
{:character_location, location_info, old_location_info} ->
_update_location(
update_location(
character_id,
location_info,
old_location_info,
@@ -535,9 +569,25 @@ defmodule WandererApp.Map.Server.Impl do
:broadcast
{:character_alliance, _info} ->
WandererApp.Cache.insert_or_update(
"map_#{map_id}:invalidate_character_ids",
[character_id],
fn ids ->
[character_id | ids]
end
)
:broadcast
{:character_corporation, _info} ->
WandererApp.Cache.insert_or_update(
"map_#{map_id}:invalidate_character_ids",
[character_id],
fn ids ->
[character_id | ids]
end
)
:broadcast
_ ->
@@ -588,29 +638,37 @@ defmodule WandererApp.Map.Server.Impl do
def handle_event(:update_presence, %{map_id: map_id} = state) do
Process.send_after(self(), :update_presence, @update_presence_timeout)
_update_presence(map_id)
update_presence(map_id)
state
end
def handle_event(:backup_state, state) do
Process.send_after(self(), :backup_state, @backup_state_timeout)
{:ok, _map_state} = state |> _save_map_state()
{:ok, _map_state} = state |> save_map_state()
state
end
def handle_event({:map_acl_updated, added_acls, removed_acls}, %{map: old_map} = state) do
{:ok, map} = WandererApp.MapRepo.get(old_map.map_id, [:acls])
def handle_event(
{:map_acl_updated, added_acls, removed_acls},
%{map_id: map_id, map: old_map} = state
) do
{:ok, map} =
WandererApp.MapRepo.get(map_id,
acls: [
:owner_id,
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
]
)
_track_acls(added_acls)
track_acls(added_acls)
result =
[added_acls | removed_acls]
|> List.flatten()
(added_acls ++ removed_acls)
|> Task.async_stream(
fn acl_id ->
_update_acl(acl_id)
update_acl(acl_id)
end,
max_concurrency: 10,
timeout: :timer.seconds(15)
@@ -639,29 +697,45 @@ defmodule WandererApp.Map.Server.Impl do
}
error ->
@logger.error(
"Failed to update map #{old_map.map_id} acl: #{inspect(error, pretty: true)}"
)
@logger.error("Failed to update map #{map_id} acl: #{inspect(error, pretty: true)}")
acc
end
end
)
_broadcast_acl_updates({:ok, result})
map_update = %{acls: map.acls, scope: map.scope}
%{state | map: %{old_map | acls: map.acls, scope: map.scope}}
WandererApp.Map.update_map(map_id, map_update)
broadcast_acl_updates({:ok, result}, map_id)
%{state | map: Map.merge(old_map, map_update)}
end
def handle_event({:acl_updated, %{acl_id: acl_id}}, %{map: map} = state) do
def handle_event({:acl_updated, %{acl_id: acl_id}}, %{map_id: map_id, map: old_map} = state) do
{:ok, map} =
WandererApp.MapRepo.get(map_id,
acls: [
:owner_id,
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
]
)
if map.acls |> Enum.map(& &1.id) |> Enum.member?(acl_id) do
map_update = %{acls: map.acls}
WandererApp.Map.update_map(map_id, map_update)
:ok =
acl_id
|> _update_acl()
|> _broadcast_acl_updates()
end
|> update_acl()
|> broadcast_acl_updates(map_id)
state
state
else
state
end
end
def handle_event(:cleanup_connections, %{map_id: map_id} = state) do
@@ -677,7 +751,7 @@ defmodule WandererApp.Map.Server.Impl do
} ->
DateTime.diff(DateTime.utc_now(), inserted_at, :hour) >=
@connection_auto_eol_hours and
_is_connection_valid(
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
@@ -705,7 +779,7 @@ defmodule WandererApp.Map.Server.Impl do
solar_system_source: solar_system_source_id,
solar_system_target: solar_system_target_id
} ->
connection_mark_eol_time = _get_connection_mark_eol_time(map_id, connection_id)
connection_mark_eol_time = get_connection_mark_eol_time(map_id, connection_id)
reverse_connection =
WandererApp.Map.get_connection(
@@ -715,24 +789,23 @@ defmodule WandererApp.Map.Server.Impl do
)
is_connection_exist =
_is_connection_exist(
is_connection_exist(
map_id,
solar_system_source_id,
solar_system_target_id
)
) || not is_nil(reverse_connection)
is_connection_valid =
_is_connection_valid(
is_connection_valid(
:wormholes,
solar_system_source_id,
solar_system_target_id
)
not is_connection_exist or
not is_nil(reverse_connection) or
(is_connection_valid and
not is_connection_exist ||
(is_connection_valid &&
(DateTime.diff(DateTime.utc_now(), inserted_at, :hour) >=
@connection_auto_expire_hours or
@connection_auto_expire_hours ||
DateTime.diff(DateTime.utc_now(), connection_mark_eol_time, :hour) >=
@connection_auto_expire_hours - @connection_auto_eol_hours))
end)
@@ -751,6 +824,76 @@ defmodule WandererApp.Map.Server.Impl do
state
end
def handle_event(:cleanup_characters, %{map_id: map_id, map: %{owner_id: owner_id}} = state) do
Process.send_after(self(), :cleanup_characters, @characters_cleanup_timeout)
{:ok, character_ids} =
WandererApp.Cache.lookup(
"map_#{map_id}:invalidate_character_ids",
[]
)
character_ids
|> Task.async_stream(
fn character_id ->
character_id
|> WandererApp.Character.get_character()
|> case do
{:ok, character} ->
acls =
map_id
|> WandererApp.Map.get_map!()
|> Map.get(:acls, [])
[character_permissions] =
WandererApp.Permissions.check_characters_access([character], acls)
map_permissions =
WandererApp.Permissions.get_map_permissions(
character_permissions,
owner_id,
[character_id]
)
case map_permissions do
%{view_system: false} ->
{:remove_character, character_id}
%{track_character: false} ->
{:remove_character, character_id}
_ ->
:ok
end
_ ->
:ok
end
end,
timeout: :timer.seconds(60),
max_concurrency: System.schedulers_online(),
on_timeout: :kill_task
)
|> Enum.each(fn
{:ok, {:remove_character, character_id}} ->
state |> remove_and_untrack_characters([character_id])
:ok
{:ok, _result} ->
:ok
{:error, reason} ->
@logger.error("Error in cleanup_characters: #{inspect(reason)}")
end)
WandererApp.Cache.insert(
"map_#{map_id}:invalidate_character_ids",
[]
)
state
end
def handle_event(:cleanup_systems, %{map_id: map_id} = state) do
Process.send_after(self(), :cleanup_systems, @systems_cleanup_timeout)
@@ -806,12 +949,13 @@ defmodule WandererApp.Map.Server.Impl do
}
end
def handle_event({:options_updated, options}, %{map: map, map_id: map_id} = state),
def handle_event({:options_updated, options}, state),
do: %{
state
| map_opts: [
layout: options |> Map.get("layout"),
store_custom_labels: options |> Map.get("store_custom_labels")
layout: options |> Map.get("layout", "left_to_right"),
store_custom_labels:
options |> Map.get("store_custom_labels", "false") |> String.to_existing_atom()
]
}
@@ -828,41 +972,62 @@ defmodule WandererApp.Map.Server.Impl do
end
def broadcast!(map_id, event, payload \\ nil) do
if _can_broadcast?(map_id) do
if can_broadcast?(map_id) do
@pubsub_client.broadcast!(WandererApp.PubSub, map_id, %{event: event, payload: payload})
end
:ok
end
defp _get_connection_mark_eol_time(map_id, connection_id) do
case WandererApp.Cache.get("map_#{map_id}:conn_#{connection_id}:mark_eol_time") do
defp remove_and_untrack_characters(%{map_id: map_id} = state, character_ids) do
map_id
|> _untrack_characters(character_ids)
case WandererApp.Api.MapCharacterSettings.tracked_by_map(%{
map_id: map_id,
character_ids: character_ids
}) do
{:ok, settings} ->
settings
|> Enum.map(fn s ->
s |> WandererApp.Api.MapCharacterSettings.untrack()
state |> remove_character(s.character_id)
end)
_ ->
:ok
end
end
defp get_connection_mark_eol_time(map_id, connection_id, default \\ DateTime.utc_now()) do
WandererApp.Cache.get("map_#{map_id}:conn_#{connection_id}:mark_eol_time")
|> case do
nil ->
DateTime.utc_now()
default
value ->
value
end
end
defp _can_broadcast?(map_id),
defp can_broadcast?(map_id),
do:
not WandererApp.Cache.lookup!("map_#{map_id}:importing", false) and
WandererApp.Cache.lookup!("map_#{map_id}:started", false)
defp _update_location(
defp update_location(
character_id,
location,
old_location,
%{map: map, map_id: map_id, rtree_name: rtree_name, map_opts: map_opts} = _state
) do
case is_nil(old_location.solar_system_id) and
_can_add_location(map.scope, location.solar_system_id) do
can_add_location(map.scope, location.solar_system_id) do
true ->
:ok = maybe_add_system(map_id, location, nil, rtree_name, map_opts)
_ ->
case _is_connection_valid(
case is_connection_valid(
map.scope,
old_location.solar_system_id,
location.solar_system_id
@@ -878,7 +1043,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _maybe_update_location(map_id, character_id) do
defp maybe_update_location(map_id, character_id) do
WandererApp.Cache.lookup!(
"character:#{character_id}:location_started",
false
@@ -929,7 +1094,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _maybe_update_alliance(map_id, character_id) do
defp maybe_update_alliance(map_id, character_id) do
with {:ok, old_alliance_id} <-
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:alliance_id"),
{:ok, %{alliance_id: alliance_id}} <-
@@ -953,7 +1118,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _maybe_update_corporation(map_id, character_id) do
defp maybe_update_corporation(map_id, character_id) do
with {:ok, old_corporation_id} <-
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:corporation_id"),
{:ok, %{corporation_id: corporation_id}} <-
@@ -977,7 +1142,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _maybe_update_online(map_id, character_id) do
defp maybe_update_online(map_id, character_id) do
with {:ok, old_online} <-
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:online"),
{:ok, %{online: online}} <-
@@ -1001,7 +1166,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _maybe_update_ship(map_id, character_id) do
defp maybe_update_ship(map_id, character_id) do
with {:ok, old_ship_type_id} <-
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:ship_type_id"),
{:ok, old_ship_name} <-
@@ -1033,7 +1198,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _update_connection(
defp update_connection(
%{map_id: map_id} = state,
update_method,
attributes,
@@ -1049,7 +1214,7 @@ defmodule WandererApp.Map.Server.Impl do
solar_system_source_id,
solar_system_target_id
),
{:ok, update_map} <- _get_update_map(update, attributes),
{:ok, update_map} <- get_update_map(update, attributes),
:ok <-
WandererApp.Map.update_connection(
map_id,
@@ -1075,7 +1240,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _update_system(
defp update_system(
%{map_id: map_id} = state,
update_method,
attributes,
@@ -1088,7 +1253,7 @@ defmodule WandererApp.Map.Server.Impl do
map_id,
update.solar_system_id
),
{:ok, update_map} <- _get_update_map(update, attributes) do
{:ok, update_map} <- get_update_map(update, attributes) do
{:ok, updated_system} =
apply(WandererApp.MapSystemRepo, update_method, [
system,
@@ -1099,7 +1264,7 @@ defmodule WandererApp.Map.Server.Impl do
callback_fn.(updated_system)
end
_update_map_system_last_activity(map_id, updated_system)
update_map_system_last_activity(map_id, updated_system)
state
else
@@ -1109,7 +1274,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _get_update_map(update, attributes),
defp get_update_map(update, attributes),
do:
{:ok,
Enum.reduce(attributes, Map.new(), fn attribute, map ->
@@ -1153,7 +1318,8 @@ defmodule WandererApp.Map.Server.Impl do
rtree_name
)
{:ok, existing_system}
existing_system
|> WandererApp.MapSystemRepo.update_visible(%{visible: true})
else
@ddrt.insert(
{solar_system_id,
@@ -1164,11 +1330,11 @@ defmodule WandererApp.Map.Server.Impl do
rtree_name
)
{:ok,
existing_system
|> WandererApp.MapSystemRepo.update_position!(%{position_x: x, position_y: y})
|> WandererApp.MapSystemRepo.cleanup_labels(map_opts)
|> WandererApp.MapSystemRepo.cleanup_tags()}
existing_system
|> WandererApp.MapSystemRepo.update_position!(%{position_x: x, position_y: y})
|> WandererApp.MapSystemRepo.cleanup_labels!(map_opts)
|> WandererApp.MapSystemRepo.cleanup_tags!()
|> WandererApp.MapSystemRepo.update_visible(%{visible: true})
end
_ ->
@@ -1211,12 +1377,10 @@ defmodule WandererApp.Map.Server.Impl do
solar_system_id: solar_system_id
})
:telemetry.execute([:wanderer_app, :map, :system, :add], %{count: 1})
state
end
defp _save_map_state(%{map_id: map_id} = _state) do
defp save_map_state(%{map_id: map_id} = _state) do
systems_last_activity =
map_id
|> WandererApp.Map.list_systems!()
@@ -1250,7 +1414,7 @@ defmodule WandererApp.Map.Server.Impl do
})
end
defp _maybe_stop_rtree(%{rtree_name: rtree_name} = state) do
defp maybe_stop_rtree(%{rtree_name: rtree_name} = state) do
case Process.whereis(rtree_name) do
nil ->
:ok
@@ -1262,7 +1426,7 @@ defmodule WandererApp.Map.Server.Impl do
state
end
defp _init_map_cache(%__MODULE__{map_id: map_id} = state) do
defp init_map_cache(%__MODULE__{map_id: map_id} = state) do
case WandererApp.Api.MapState.by_map_id(map_id) do
{:ok,
%{
@@ -1294,9 +1458,9 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _init_map(
defp init_map(
state,
%{characters: characters} = initial_map,
%{id: map_id, characters: characters} = initial_map,
subscription_settings,
systems,
connections
@@ -1312,16 +1476,24 @@ defmodule WandererApp.Map.Server.Impl do
{:ok, map_options} = WandererApp.MapRepo.options_to_form_data(initial_map)
map_opts = [
layout: map_options |> Map.get("layout"),
store_custom_labels: map_options |> Map.get("store_custom_labels")
layout: map_options |> Map.get("layout", "left_to_right"),
store_custom_labels:
map_options |> Map.get("store_custom_labels", "false") |> String.to_existing_atom()
]
character_ids =
map_id
|> WandererApp.Map.get_map!()
|> Map.get(:characters, [])
WandererApp.Cache.insert("map_#{map_id}:invalidate_character_ids", character_ids)
%{state | map: map, map_opts: map_opts}
end
defp _init_map_systems(state, [] = _systems), do: state
defp init_map_systems(state, [] = _systems), do: state
defp _init_map_systems(%__MODULE__{map_id: map_id, rtree_name: rtree_name} = state, systems) do
defp init_map_systems(%__MODULE__{map_id: map_id, rtree_name: rtree_name} = state, systems) do
systems
|> Enum.each(fn %{id: system_id, solar_system_id: solar_system_id} = system ->
@ddrt.insert(
@@ -1339,7 +1511,7 @@ defmodule WandererApp.Map.Server.Impl do
state
end
def _maybe_import_systems(state, %{"systems" => systems} = _settings, user_id, character_id) do
def maybe_import_systems(state, %{"systems" => systems} = _settings, user_id, character_id) do
state =
systems
|> Enum.reduce(state, fn %{
@@ -1383,7 +1555,7 @@ defmodule WandererApp.Map.Server.Impl do
|> delete_systems(removed_system_ids, user_id, character_id)
end
def _maybe_import_connections(state, %{"connections" => connections} = _settings, _user_id) do
def maybe_import_connections(state, %{"connections" => connections} = _settings, _user_id) do
connections
|> Enum.reduce(state, fn %{
"source" => source,
@@ -1419,7 +1591,7 @@ defmodule WandererApp.Map.Server.Impl do
end)
end
def _maybe_import_hubs(state, %{"hubs" => hubs} = _settings, _user_id) do
def maybe_import_hubs(state, %{"hubs" => hubs} = _settings, _user_id) do
hubs
|> Enum.reduce(state, fn hub, acc ->
solar_system_id = hub |> String.to_integer()
@@ -1429,7 +1601,7 @@ defmodule WandererApp.Map.Server.Impl do
end)
end
defp _update_map_system_last_activity(
defp update_map_system_last_activity(
map_id,
updated_system
) do
@@ -1442,13 +1614,13 @@ defmodule WandererApp.Map.Server.Impl do
broadcast!(map_id, :update_system, updated_system)
end
defp _can_add_location(_scope, nil), do: false
defp can_add_location(_scope, nil), do: false
defp _can_add_location(:all, _solar_system_id), do: true
defp can_add_location(:all, _solar_system_id), do: true
defp _can_add_location(:none, _solar_system_id), do: false
defp can_add_location(:none, _solar_system_id), do: false
defp _can_add_location(scope, solar_system_id) do
defp can_add_location(scope, solar_system_id) do
system_static_info =
case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do
{:ok, system_static_info} when not is_nil(system_static_info) ->
@@ -1473,14 +1645,14 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _is_connection_exist(map_id, from_solar_system_id, to_solar_system_id),
defp is_connection_exist(map_id, from_solar_system_id, to_solar_system_id),
do:
not is_nil(
WandererApp.Map.find_system_by_location(
map_id,
%{solar_system_id: from_solar_system_id}
)
) and
) &&
not is_nil(
WandererApp.Map.find_system_by_location(
map_id,
@@ -1488,13 +1660,13 @@ defmodule WandererApp.Map.Server.Impl do
)
)
defp _is_connection_valid(_scope, nil, _to_solar_system_id), do: false
defp is_connection_valid(_scope, nil, _to_solar_system_id), do: false
defp _is_connection_valid(:all, _from_solar_system_id, _to_solar_system_id), do: true
defp is_connection_valid(:all, _from_solar_system_id, _to_solar_system_id), do: true
defp _is_connection_valid(:none, _from_solar_system_id, _to_solar_system_id), do: false
defp is_connection_valid(:none, _from_solar_system_id, _to_solar_system_id), do: false
defp _is_connection_valid(scope, from_solar_system_id, to_solar_system_id) do
defp is_connection_valid(scope, from_solar_system_id, to_solar_system_id) do
{:ok, known_jumps} =
WandererApp.Api.MapSolarSystemJumps.find(%{
before_system_id: from_solar_system_id,
@@ -1523,7 +1695,7 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _update_presence(map_id) do
defp update_presence(map_id) do
case WandererApp.Cache.lookup!("map_#{map_id}:started", false) and
WandererApp.Cache.get_and_remove!("map_#{map_id}:presence_updated", false) do
true ->
@@ -1541,7 +1713,7 @@ defmodule WandererApp.Map.Server.Impl do
not Enum.member?(presence_character_ids, character_id)
end)
_track_characters(presence_character_ids, map_id)
track_characters(presence_character_ids, map_id)
map_id
|> _untrack_characters(not_present_character_ids)
@@ -1560,34 +1732,32 @@ defmodule WandererApp.Map.Server.Impl do
end
end
defp _track_acls([]), do: :ok
defp track_acls([]), do: :ok
defp _track_acls([acl_id | rest]) do
_track_acl(acl_id)
_track_acls(rest)
defp track_acls([acl_id | rest]) do
track_acl(acl_id)
track_acls(rest)
end
defp _track_acl(acl_id),
defp track_acl(acl_id),
do: @pubsub_client.subscribe(WandererApp.PubSub, "acls:#{acl_id}")
defp track_characters([], _map_id), do: :ok
defp track_characters([character_id | rest], map_id) do
track_character(character_id, map_id)
track_characters(rest, map_id)
end
defp track_character(character_id, map_id),
do:
WandererApp.PubSub
|> @pubsub_client.subscribe("acls:#{acl_id}")
defp _track_characters([], _map_id), do: :ok
defp _track_characters([character_id | rest], map_id) do
_track_character(character_id, map_id)
_track_characters(rest, map_id)
end
defp _track_character(character_id, map_id) do
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
map_id: map_id,
track: true,
track_online: true,
track_location: true,
track_ship: true
})
end
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
map_id: map_id,
track: true,
track_online: true,
track_location: true,
track_ship: true
})
defp _update_character(map_id, character_id) do
{:ok, character} = WandererApp.Character.get_character(character_id)
@@ -1683,11 +1853,11 @@ defmodule WandererApp.Map.Server.Impl do
defp maybe_add_connection(_map_id, _location, _old_location, _character_id), do: :ok
defp maybe_add_system(map_id, location, old_location, rtree_name, opts)
defp maybe_add_system(map_id, location, old_location, rtree_name, map_opts)
when not is_nil(location) do
case WandererApp.Map.check_location(map_id, location) do
{:ok, location} ->
{:ok, position} = calc_new_system_position(map_id, old_location, rtree_name, opts)
{:ok, position} = calc_new_system_position(map_id, old_location, rtree_name, map_opts)
case WandererApp.MapSystemRepo.get_by_map_and_solar_system_id(
map_id,
@@ -1696,10 +1866,12 @@ defmodule WandererApp.Map.Server.Impl do
{:ok, existing_system} when not is_nil(existing_system) ->
{:ok, updated_system} =
existing_system
|> WandererApp.MapSystemRepo.update_position(%{
|> WandererApp.MapSystemRepo.update_position!(%{
position_x: position.x,
position_y: position.y
})
|> WandererApp.MapSystemRepo.cleanup_labels!(map_opts)
|> WandererApp.MapSystemRepo.cleanup_tags()
@ddrt.insert(
{existing_system.solar_system_id,
@@ -1721,7 +1893,7 @@ defmodule WandererApp.Map.Server.Impl do
_ ->
{:ok, solar_system_info} =
WandererApp.Api.MapSolarSystem.by_solar_system_id(location.solar_system_id)
WandererApp.CachedInfo.get_system_static_info(location.solar_system_id)
WandererApp.MapSystemRepo.create(%{
map_id: map_id,
@@ -1747,17 +1919,19 @@ defmodule WandererApp.Map.Server.Impl do
broadcast!(map_id, :add_system, new_system)
WandererApp.Map.add_system(map_id, new_system)
_ ->
error ->
@logger.debug("Failed to create system: #{inspect(error, pretty: true)}")
:ok
end
end
{:error, _} ->
error ->
@logger.debug("Skip adding system: #{inspect(error, pretty: true)}")
:ok
end
end
defp maybe_add_system(_map_id, _location, _old_location, _rtree_name, _opts), do: :ok
defp maybe_add_system(_map_id, _location, _old_location, _rtree_name, _map_opts), do: :ok
defp calc_new_system_position(map_id, old_location, rtree_name, opts),
do:
@@ -1766,13 +1940,14 @@ defmodule WandererApp.Map.Server.Impl do
|> WandererApp.Map.find_system_by_location(old_location)
|> WandererApp.Map.PositionCalculator.get_new_system_position(rtree_name, opts)}
defp _broadcast_acl_updates(
defp broadcast_acl_updates(
{:ok,
%{
eve_character_ids: eve_character_ids,
eve_corporation_ids: eve_corporation_ids,
eve_alliance_ids: eve_alliance_ids
}}
}},
map_id
) do
eve_character_ids
|> Enum.uniq()
@@ -1804,12 +1979,19 @@ defmodule WandererApp.Map.Server.Impl do
)
end)
character_ids =
map_id
|> WandererApp.Map.get_map!()
|> Map.get(:characters, [])
WandererApp.Cache.insert("map_#{map_id}:invalidate_character_ids", character_ids)
:ok
end
defp _broadcast_acl_updates(_), do: :ok
defp broadcast_acl_updates(_, _map_id), do: :ok
defp _update_acl(acl_id) do
defp update_acl(acl_id) do
{:ok, %{owner: owner, members: members}} =
WandererApp.AccessListRepo.get(acl_id, [:owner, :members])

View File

@@ -87,4 +87,113 @@ defmodule WandererApp.Permissions do
delete_map: check_permission(user_permissions, @delete_map)
}
end
def check_characters_access(characters, acls) do
character_ids = characters |> Enum.map(& &1.id)
character_eve_ids = characters |> Enum.map(& &1.eve_id)
character_corporation_ids =
characters |> Enum.map(& &1.corporation_id) |> Enum.map(&to_string/1)
character_alliance_ids = characters |> Enum.map(& &1.alliance_id) |> Enum.map(&to_string/1)
result =
acls
|> Enum.reduce([0, 0], fn acl, acc ->
is_owner? = acl.owner_id in character_ids
is_character_member? =
acl.members |> Enum.any?(fn member -> member.eve_character_id in character_eve_ids end)
is_corporation_member? =
acl.members
|> Enum.any?(fn member -> member.eve_corporation_id in character_corporation_ids end)
is_alliance_member? =
acl.members
|> Enum.any?(fn member -> member.eve_alliance_id in character_alliance_ids end)
if is_owner? || is_character_member? || is_corporation_member? || is_alliance_member? do
case acc do
[_, -1] ->
[-1, -1]
[-1, char_acc] ->
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> calc_role_mask(member.role, acc)
end
end)
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[-1, char_acc]
[any_acc, char_acc] ->
any_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids ||
member.eve_corporation_id in character_corporation_ids ||
member.eve_alliance_id in character_alliance_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> calc_role_mask(member.role, acc)
end
end)
char_acl_mask =
acl.members
|> Enum.filter(fn member ->
member.eve_character_id in character_eve_ids
end)
|> Enum.reduce(0, fn member, acc ->
case acc do
-1 -> -1
_ -> calc_role_mask(member.role, acc)
end
end)
any_acc =
case any_acl_mask do
-1 -> -1
_ -> any_acc ||| any_acl_mask
end
char_acc =
case char_acl_mask do
-1 -> -1
_ -> char_acc ||| char_acl_mask
end
[any_acc, char_acc]
end
else
acc
end
end)
case result do
[_, -1] ->
[-1]
[-1, char_acc] ->
[char_acc]
[any_acc, _char_acc] ->
[any_acc]
end
end
end

View File

@@ -33,19 +33,26 @@ defmodule WandererApp.MapSystemRepo do
{:error, error}
end
def cleanup_labels(%{labels: labels} = system, opts) do
def cleanup_labels!(%{labels: labels} = system, opts) do
store_custom_labels? =
Keyword.get(opts, :store_custom_labels, "false") |> String.to_existing_atom()
Keyword.get(opts, :store_custom_labels)
labels = get_filtered_labels(labels, store_custom_labels?)
system
|> WandererApp.Api.MapSystem.update_labels!(%{
|> update_labels!(%{
labels: labels
})
end
def cleanup_tags(system) do
system
|> WandererApp.Api.MapSystem.update_tag(%{
tag: nil
})
end
def cleanup_tags!(system) do
system
|> WandererApp.Api.MapSystem.update_tag!(%{
tag: nil
@@ -56,7 +63,7 @@ defmodule WandererApp.MapSystemRepo do
labels
|> Jason.decode!()
|> case do
%{"customLabel" => customLabel} = labels when is_binary(customLabel) ->
%{"customLabel" => customLabel} when is_binary(customLabel) ->
%{"customLabel" => customLabel, "labels" => []}
|> Jason.encode!()
@@ -97,6 +104,11 @@ defmodule WandererApp.MapSystemRepo do
system
|> WandererApp.Api.MapSystem.update_labels(update)
def update_labels!(system, update),
do:
system
|> WandererApp.Api.MapSystem.update_labels!(update)
def update_position(system, update),
do:
system
@@ -106,4 +118,14 @@ defmodule WandererApp.MapSystemRepo do
do:
system
|> WandererApp.Api.MapSystem.update_position!(update)
def update_visible(system, update),
do:
system
|> WandererApp.Api.MapSystem.update_visible(update)
def update_visible!(system, update),
do:
system
|> WandererApp.Api.MapSystem.update_visible!(update)
end

View File

@@ -12,6 +12,7 @@ defmodule WandererAppWeb.MapPicker do
{:ok, socket}
end
@impl true
def update(
%{
current_user: current_user,
@@ -29,6 +30,7 @@ defmodule WandererAppWeb.MapPicker do
end)}
end
@impl true
def render(assigns) do
~H"""
<div id={@id}>
@@ -56,6 +58,7 @@ defmodule WandererAppWeb.MapPicker do
"""
end
@impl true
def handle_event("select", %{"map_slug" => map_slug} = _params, socket) do
notify_to(socket.assigns.notify_to, socket.assigns.event_name, map_slug)

View File

@@ -1,4 +1,5 @@
defmodule WandererAppWeb.AccessListsLive do
alias Pathex.Builder.Viewer
use WandererAppWeb, :live_view
require Logger
@@ -89,7 +90,10 @@ defmodule WandererAppWeb.AccessListsLive do
|> assign(:page_title, "Access Lists - Members")
|> assign(:selected_acl_id, acl_id)
|> assign(:access_list, access_list)
|> assign(:members, members)
|> assign(
:members,
members
)
else
_ ->
socket
@@ -145,7 +149,7 @@ defmodule WandererAppWeb.AccessListsLive do
:send_after,
[self(), {:search, text}, 100],
"member_search_#{socket.assigns.selected_acl_id}",
500
250
)
[%{label: "Loading...", value: :loading, disabled: true}]
@@ -288,7 +292,11 @@ defmodule WandererAppWeb.AccessListsLive do
end
@impl true
def handle_event("dropped", %{"draggedId" => dragged_id, "dropzoneId" => dropzone_id}, socket) do
def handle_event(
"dropped",
%{"draggedId" => dragged_id, "dropzoneId" => dropzone_id},
%{assigns: %{access_list: access_list, members: members}} = socket
) do
role_atom =
[:admin, :manager, :member, :viewer, :blocked]
|> Enum.find(fn role_atom -> to_string(role_atom) == dropzone_id end)
@@ -299,13 +307,27 @@ defmodule WandererAppWeb.AccessListsLive do
role_atom ->
member =
socket.assigns.members
members
|> Enum.find(&(&1.id == dragged_id))
{:noreply, socket |> maybe_update_role(member, role_atom, socket.assigns.access_list)}
{:noreply, socket |> maybe_update_role(member, role_atom, access_list)}
end
end
@impl true
def handle_info(
{"update_role", %{member_id: member_id, role: role}},
%{assigns: %{access_list: access_list, members: members}} = socket
) do
role_atom = role |> String.to_existing_atom()
member =
members
|> Enum.find(&(&1.id == member_id))
{:noreply, socket |> maybe_update_role(member, role_atom, access_list)}
end
@impl true
def handle_event("noop", _, socket) do
{:noreply, socket}
@@ -325,10 +347,33 @@ defmodule WandererAppWeb.AccessListsLive do
|> Enum.map(& &1.id)
|> Enum.at(0)
{:ok, options} = search(active_character_id, text)
uniq_search_req_id = UUID.uuid4(:default)
send_update(LiveSelect.Component, options: options, id: socket.assigns.member_search_id)
{:noreply, socket |> assign(member_search_options: options)}
Task.async(fn ->
{:ok, options} = search(active_character_id, text)
{:search_results, uniq_search_req_id, options}
end)
{:noreply, socket |> assign(uniq_search_req_id: uniq_search_req_id)}
end
def handle_info(
{ref, result},
%{assigns: %{member_search_id: member_search_id, uniq_search_req_id: uniq_search_req_id}} =
socket
)
when is_reference(ref) do
Process.demonitor(ref, [:flush])
case result do
{:search_results, ^uniq_search_req_id, options} ->
send_update(LiveSelect.Component, options: options, id: member_search_id)
{:noreply, socket |> assign(member_search_options: options)}
_ ->
{:noreply, socket}
end
end
@impl true
@@ -403,6 +448,7 @@ defmodule WandererAppWeb.AccessListsLive do
_ ->
socket
|> put_flash(:error, "You're not allowed to assign this role")
|> push_navigate(to: ~p"/access-lists/#{socket.assigns.selected_acl_id}")
end
end
@@ -411,10 +457,11 @@ defmodule WandererAppWeb.AccessListsLive do
_member,
_role_atom,
_access_list
) do
socket
|> put_flash(:info, "Only Characters can have Admin or Manager roles")
end
),
do:
socket
|> put_flash(:info, "Only Characters can have Admin or Manager roles")
|> push_navigate(to: ~p"/access-lists/#{socket.assigns.selected_acl_id}")
defp characters_has_role?(character_eve_ids, access_list, role_atom) do
access_list.members
@@ -614,27 +661,6 @@ defmodule WandererAppWeb.AccessListsLive do
"""
end
def member_item(assigns) do
~H"""
<div class="flex items-center gap-2">
<.icon :if={not is_nil(@member.role)} name={member_role_icon(@member.role)} class="w-6 h-6" />
<div class="avatar">
<div class="rounded-md w-8 h-8">
<img src={member_icon_url(@member)} alt={@member.name} />
</div>
</div>
<%= @member.name %>
</div>
"""
end
def member_role_icon(:admin), do: "hero-user-group-solid"
def member_role_icon(:manager), do: "hero-academic-cap-solid"
def member_role_icon(:member), do: "hero-user-solid"
def member_role_icon(:viewer), do: "hero-eye-solid"
def member_role_icon(:blocked), do: "hero-no-symbol-solid text-red-500"
def member_role_icon(_), do: "hero-cake-solid"
def search_member_icon_url(%{character: true} = option),
do: member_icon_url(%{eve_character_id: option.value})

View File

@@ -82,14 +82,20 @@
id="acl_members"
>
<div
:for={member <- @members |> Enum.sort(&(&1.name < &2.name))}
:for={member <- @members |> Enum.sort_by(&{&1.role, &1.name}, &<=/2)}
draggable="true"
id={member.id}
class="draggable !p-1 h-10 cursor-move bg-black bg-opacity-25 hover:text-white"
data-dropzone="pool"
>
<div class="flex justify-between relative">
<.member_item member={member} />
<.live_component
module={WandererAppWeb.AclMember}
id={"select_role_" <> member.id}
notify_to={self()}
member={member}
event_name="update_role"
/>
<button
:if={can_delete_member?(member, @access_list, @current_user)}
class="z-10 absolute top-0 right-2"
@@ -150,6 +156,71 @@
show
on_cancel={JS.patch(~p"/access-lists/#{@selected_acl_id}")}
>
<%!-- <div class="mt-4 mb-2 p-tabmenu p-component " data-pc-section="tabmenu">
<ul
class="p-tabmenu-nav border-none h-[25px] w-full flex"
role="menubar"
data-pc-section="menu"
>
<li
id="pr_id_17_0"
class="p-tabmenuitem p-highlight"
role="presentation"
data-p-highlight="true"
data-p-disabled="false"
data-pc-section="menuitem"
>
<a
href="#"
role="menuitem"
aria-label="Router Link"
tabindex="0"
class="p-menuitem-link"
data-pc-section="action"
>
<span class="p-menuitem-text" data-pc-section="label">Character</span>
</a>
</li>
<li
id="pr_id_17_1"
class="p-tabmenuitem"
role="presentation"
data-p-highlight="false"
data-p-disabled="false"
data-pc-section="menuitem"
>
<a
href="#"
role="menuitem"
aria-label="Programmatic"
tabindex="-1"
class="p-menuitem-link"
data-pc-section="action"
>
<span class="p-menuitem-text" data-pc-section="label">Corporation</span>
</a>
</li>
<li
id="pr_id_17_2"
class="p-tabmenuitem"
role="presentation"
data-p-highlight="false"
data-p-disabled="false"
data-pc-section="menuitem"
>
<a
href="#"
role="menuitem"
aria-label="External"
tabindex="-1"
class="p-menuitem-link"
data-pc-section="action"
>
<span class="p-menuitem-text" data-pc-section="label">Alliance</span>
</a>
</li>
</ul>
</div> --%>
<.form :let={f} for={@member_form} phx-submit={@live_action}>
<.live_select
field={f[:member_id]}

View File

@@ -0,0 +1,86 @@
defmodule WandererAppWeb.AclMember do
use WandererAppWeb, :live_component
use LiveViewEvents
@roles [
:admin,
:manager,
:member,
:viewer,
:blocked
]
@impl true
def mount(socket) do
{:ok, socket |> assign(roles: get_roles())}
end
@impl true
def update(
%{
member: member
} = assigns,
socket
) do
socket = handle_info_or_assign(socket, assigns)
{:ok,
socket
|> assign(member: member, form: to_form(%{"role" => member.role}))}
end
@impl true
def render(assigns) do
~H"""
<div id={@id} class="flex items-center gap-2">
<.icon :if={not is_nil(@member.role)} name={member_role_icon(@member.role)} class="w-6 h-6" />
<.form :let={f} id={"role_form_" <> @id} for={@form} phx-change="select" phx-target={@myself}>
<.input
type="select"
field={f[:role]}
class="select h-8 min-h-[0px] !pt-1 !pb-1 text-sm bg-neutral-900 w-[70px]"
placeholder="Select a role..."
options={Enum.map(@roles, fn role -> {role.label, role.value} end)}
/>
</.form>
<div class="avatar">
<div class="rounded-md w-8 h-8">
<img src={member_icon_url(@member)} alt={@member.name} />
</div>
</div>
<%= @member.name %>
</div>
"""
end
@impl true
def handle_event(
"select",
%{"role" => role} = _params,
%{assigns: %{event_name: event_name, member: member, notify_to: notify_to}} = socket
) do
notify_to(notify_to, event_name, %{
member_id: member.id,
role: role
})
{:noreply, socket}
end
def member_role_icon(:admin), do: "hero-user-group-solid"
def member_role_icon(:manager), do: "hero-academic-cap-solid"
def member_role_icon(:member), do: "hero-user-solid"
def member_role_icon(:viewer), do: "hero-eye-solid"
def member_role_icon(:blocked), do: "hero-no-symbol-solid text-red-500"
def member_role_icon(_), do: "hero-cake-solid"
def member_role_title(:admin), do: "Admin"
def member_role_title(:manager), do: "Manager"
def member_role_title(:member), do: "Member"
def member_role_title(:viewer), do: "Viewer"
def member_role_title(:blocked), do: "-blocked-"
def member_role_title(_), do: "-"
defp get_roles(), do: @roles |> Enum.map(&%{label: member_role_title(&1), value: &1})
end

View File

@@ -0,0 +1,49 @@
defmodule WandererAppWeb.MapActivityEventHandler do
use WandererAppWeb, :live_component
use Phoenix.Component
require Logger
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
def handle_server_event(
%{
event: :character_activity,
payload: character_activity
},
socket
),
do: socket |> assign(:character_activity, character_activity)
def handle_server_event(event, socket),
do: MapCoreEventHandler.handle_server_event(event, socket)
def handle_ui_event("show_activity", _, %{assigns: %{map_id: map_id}} = socket) do
Task.async(fn ->
{:ok, character_activity} = map_id |> get_character_activity()
{:character_activity, character_activity}
end)
{:noreply,
socket
|> assign(:show_activity?, true)}
end
def handle_ui_event("hide_activity", _, socket),
do: {:noreply, socket |> assign(show_activity?: false)}
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
defp get_character_activity(map_id) do
{:ok, jumps} = WandererApp.Api.MapChainPassages.by_map_id(%{map_id: map_id})
jumps =
jumps
|> Enum.map(fn p ->
%{p | character: p.character |> MapEventHandler.map_ui_character_stat()}
end)
{:ok, %{jumps: jumps}}
end
end

View File

@@ -0,0 +1,388 @@
defmodule WandererAppWeb.MapCharactersEventHandler do
use WandererAppWeb, :live_component
use Phoenix.Component
require Logger
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
def handle_server_event(%{event: :character_added, payload: character}, socket) do
socket
|> MapEventHandler.push_map_event(
"character_added",
character |> map_ui_character()
)
end
def handle_server_event(%{event: :character_removed, payload: character}, socket) do
socket
|> MapEventHandler.push_map_event(
"character_removed",
character |> map_ui_character()
)
end
def handle_server_event(%{event: :character_updated, payload: character}, socket) do
socket
|> MapEventHandler.push_map_event(
"character_updated",
character |> map_ui_character()
)
end
def handle_server_event(
%{event: :characters_updated},
%{
assigns: %{
map_id: map_id
}
} = socket
) do
characters =
map_id
|> WandererApp.Map.list_characters()
|> Enum.map(&map_ui_character/1)
socket
|> MapEventHandler.push_map_event(
"characters_updated",
characters
)
end
def handle_server_event(
%{event: :present_characters_updated, payload: present_character_eve_ids},
socket
),
do:
socket
|> MapEventHandler.push_map_event(
"present_characters",
present_character_eve_ids
)
def handle_server_event(event, socket),
do: MapCoreEventHandler.handle_server_event(event, socket)
def handle_ui_event(
"add_character",
_,
%{
assigns: %{
current_user: current_user,
map_id: map_id,
user_permissions: %{track_character: true}
}
} = socket
) do
{:ok, character_settings} =
case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id}) do
{:ok, settings} -> {:ok, settings}
_ -> {:ok, []}
end
{:noreply,
socket
|> assign(
show_tracking?: true,
character_settings: character_settings
)
|> assign_async(:characters, fn ->
{:ok, map} =
map_id
|> WandererApp.MapRepo.get([:acls])
map
|> WandererApp.Maps.load_characters(
character_settings,
current_user.id
)
end)}
end
def handle_ui_event(
"add_character",
_,
%{
assigns: %{
user_permissions: %{track_character: false}
}
} = socket
),
do:
{:noreply,
socket
|> put_flash(
:error,
"You don't have permissions to track characters. Please contact administrator."
)}
def handle_ui_event(
"toggle_track",
%{"character-id" => character_id},
%{
assigns: %{
map_id: map_id,
character_settings: character_settings,
current_user: current_user,
only_tracked_characters: only_tracked_characters
}
} = socket
) do
socket =
case character_settings |> Enum.find(&(&1.character_id == character_id)) do
nil ->
{:ok, map_character_settings} =
WandererApp.Api.MapCharacterSettings.create(%{
character_id: character_id,
map_id: map_id,
tracked: true
})
character = map_character_settings |> Ash.load!(:character) |> Map.get(:character)
:ok = track_characters([character], map_id, true)
:ok = add_characters([character], map_id, true)
socket
character_setting ->
case character_setting.tracked do
true ->
{:ok, map_character_settings} =
character_setting
|> WandererApp.Api.MapCharacterSettings.untrack()
character = map_character_settings |> Ash.load!(:character) |> Map.get(:character)
:ok = untrack_characters([character], map_id)
:ok = remove_characters([character], map_id)
if only_tracked_characters do
Process.send_after(self(), :not_all_characters_tracked, 10)
end
socket
_ ->
{:ok, map_character_settings} =
character_setting
|> WandererApp.Api.MapCharacterSettings.track()
character = map_character_settings |> Ash.load!(:character) |> Map.get(:character)
:ok = track_characters([character], map_id, true)
:ok = add_characters([character], map_id, true)
socket
end
end
%{result: characters} = socket.assigns.characters
{:ok, map_characters} = get_tracked_map_characters(map_id, current_user)
user_character_eve_ids = map_characters |> Enum.map(& &1.eve_id)
{:ok, character_settings} =
case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id}) do
{:ok, settings} -> {:ok, settings}
_ -> {:ok, []}
end
characters =
characters
|> Enum.map(fn c ->
WandererApp.Maps.map_character(
c,
character_settings |> Enum.find(&(&1.character_id == c.id))
)
end)
{:noreply,
socket
|> assign(user_characters: user_character_eve_ids)
|> assign(has_tracked_characters?: has_tracked_characters?(user_character_eve_ids))
|> assign(character_settings: character_settings)
|> assign_async(:characters, fn ->
{:ok, %{characters: characters}}
end)
|> MapEventHandler.push_map_event(
"init",
%{
user_characters: user_character_eve_ids,
reset: false
}
)}
end
def handle_ui_event("hide_tracking", _, socket),
do: {:noreply, socket |> assign(show_tracking?: false)}
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
def has_tracked_characters?([]), do: false
def has_tracked_characters?(_user_characters), do: true
def get_tracked_map_characters(map_id, current_user) do
case WandererApp.Api.MapCharacterSettings.tracked_by_map(%{
map_id: map_id,
character_ids: current_user.characters |> Enum.map(& &1.id)
}) do
{:ok, settings} ->
{:ok,
settings
|> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)}
_ ->
{:ok, []}
end
end
def map_ui_character(character),
do:
character
|> Map.take([
:eve_id,
:name,
:online,
:corporation_id,
:corporation_name,
:corporation_ticker,
:alliance_id,
:alliance_name,
:alliance_ticker
])
|> Map.put_new(:ship, WandererApp.Character.get_ship(character))
|> Map.put_new(:location, get_location(character))
def add_characters([], _map_id, _track_character), do: :ok
def add_characters([character | characters], map_id, track_character) do
map_id
|> WandererApp.Map.Server.add_character(character, track_character)
add_characters(characters, map_id, track_character)
end
def remove_characters([], _map_id), do: :ok
def remove_characters([character | characters], map_id) do
map_id
|> WandererApp.Map.Server.remove_character(character.id)
remove_characters(characters, map_id)
end
def untrack_characters(characters, map_id) do
characters
|> Enum.each(fn character ->
WandererAppWeb.Presence.untrack(self(), map_id, character.id)
WandererApp.Cache.put(
"#{inspect(self())}_map_#{map_id}:character_#{character.id}:tracked",
false
)
:ok =
Phoenix.PubSub.unsubscribe(
WandererApp.PubSub,
"character:#{character.eve_id}"
)
end)
end
def track_characters(_, _, false), do: :ok
def track_characters([], _map_id, _is_track_character?), do: :ok
def track_characters(
[character | characters],
map_id,
true
) do
track_character(character, map_id)
track_characters(characters, map_id, true)
end
def track_character(
%{
id: character_id,
eve_id: eve_id,
corporation_id: corporation_id,
alliance_id: alliance_id
},
map_id
) do
WandererAppWeb.Presence.track(self(), map_id, character_id, %{})
case WandererApp.Cache.lookup!(
"#{inspect(self())}_map_#{map_id}:character_#{character_id}:tracked",
false
) do
true ->
:ok
_ ->
:ok =
Phoenix.PubSub.subscribe(
WandererApp.PubSub,
"character:#{eve_id}"
)
:ok =
WandererApp.Cache.put(
"#{inspect(self())}_map_#{map_id}:character_#{character_id}:tracked",
true
)
end
case WandererApp.Cache.lookup(
"#{inspect(self())}_map_#{map_id}:corporation_#{corporation_id}:tracked",
false
) do
{:ok, true} ->
:ok
{:ok, false} ->
:ok =
Phoenix.PubSub.subscribe(
WandererApp.PubSub,
"corporation:#{corporation_id}"
)
:ok =
WandererApp.Cache.put(
"#{inspect(self())}_map_#{map_id}:corporation_#{corporation_id}:tracked",
true
)
end
case WandererApp.Cache.lookup(
"#{inspect(self())}_map_#{map_id}:alliance_#{alliance_id}:tracked",
false
) do
{:ok, true} ->
:ok
{:ok, false} ->
:ok =
Phoenix.PubSub.subscribe(
WandererApp.PubSub,
"alliance:#{alliance_id}"
)
:ok =
WandererApp.Cache.put(
"#{inspect(self())}_map_#{map_id}:alliance_#{alliance_id}:tracked",
true
)
end
:ok = WandererApp.Character.TrackerManager.start_tracking(character_id)
end
defp get_location(character),
do: %{solar_system_id: character.solar_system_id, structure_id: character.structure_id}
end

View File

@@ -0,0 +1,222 @@
defmodule WandererAppWeb.MapConnectionsEventHandler do
use WandererAppWeb, :live_component
use Phoenix.Component
require Logger
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
def handle_server_event(%{event: :update_connection, payload: connection}, socket),
do:
socket
|> MapEventHandler.push_map_event(
"update_connection",
MapEventHandler.map_ui_connection(connection)
)
def handle_server_event(%{event: :remove_connections, payload: connections}, socket) do
connection_ids =
connections |> Enum.map(&MapEventHandler.map_ui_connection/1) |> Enum.map(& &1.id)
socket
|> MapEventHandler.push_map_event(
"remove_connections",
connection_ids
)
end
def handle_server_event(%{event: :add_connection, payload: connection}, socket) do
connections = [MapEventHandler.map_ui_connection(connection)]
socket
|> MapEventHandler.push_map_event(
"add_connections",
connections
)
end
def handle_server_event(event, socket),
do: MapCoreEventHandler.handle_server_event(event, socket)
def handle_ui_event(
"manual_add_connection",
%{"source" => solar_system_source_id, "target" => solar_system_target_id} = _event,
%{
assigns: %{
map_id: map_id,
current_user: current_user,
tracked_character_ids: tracked_character_ids,
has_tracked_characters?: true,
user_permissions: %{add_connection: true}
}
} =
socket
) do
map_id
|> WandererApp.Map.Server.add_connection(%{
solar_system_source_id: solar_system_source_id |> String.to_integer(),
solar_system_target_id: solar_system_target_id |> String.to_integer()
})
{:ok, _} =
WandererApp.User.ActivityTracker.track_map_event(:map_connection_added, %{
character_id: tracked_character_ids |> List.first(),
user_id: current_user.id,
map_id: map_id,
solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(),
solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer()
})
{:noreply, socket}
end
def handle_ui_event(
"manual_delete_connection",
%{"source" => solar_system_source_id, "target" => solar_system_target_id} = _event,
%{
assigns: %{
map_id: map_id,
current_user: current_user,
tracked_character_ids: tracked_character_ids,
has_tracked_characters?: true,
user_permissions: %{delete_connection: true}
}
} =
socket
) do
map_id
|> WandererApp.Map.Server.delete_connection(%{
solar_system_source_id: solar_system_source_id |> String.to_integer(),
solar_system_target_id: solar_system_target_id |> String.to_integer()
})
{:ok, _} =
WandererApp.User.ActivityTracker.track_map_event(:map_connection_removed, %{
character_id: tracked_character_ids |> List.first(),
user_id: current_user.id,
map_id: map_id,
solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(),
solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer()
})
{:noreply, socket}
end
def handle_ui_event(
"update_connection_" <> param,
%{
"source" => solar_system_source_id,
"target" => solar_system_target_id,
"value" => value
} = _event,
%{
assigns: %{
map_id: map_id,
current_user: current_user,
tracked_character_ids: tracked_character_ids,
has_tracked_characters?: true,
user_permissions: %{update_system: true}
}
} =
socket
) do
method_atom =
case param do
"time_status" -> :update_connection_time_status
"mass_status" -> :update_connection_mass_status
"ship_size_type" -> :update_connection_ship_size_type
"locked" -> :update_connection_locked
"custom_info" -> :update_connection_custom_info
_ -> nil
end
key_atom =
case param do
"time_status" -> :time_status
"mass_status" -> :mass_status
"ship_size_type" -> :ship_size_type
"locked" -> :locked
"custom_info" -> :custom_info
_ -> nil
end
{:ok, _} =
WandererApp.User.ActivityTracker.track_map_event(:map_connection_updated, %{
character_id: tracked_character_ids |> List.first(),
user_id: current_user.id,
map_id: map_id,
solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(),
solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer(),
key: key_atom,
value: value
})
apply(WandererApp.Map.Server, method_atom, [
map_id,
%{
solar_system_source_id: "#{solar_system_source_id}" |> String.to_integer(),
solar_system_target_id: "#{solar_system_target_id}" |> String.to_integer()
}
|> Map.put_new(key_atom, value)
])
{:noreply, socket}
end
def handle_ui_event(
"get_connection_info",
%{"from" => from, "to" => to} = _event,
%{assigns: %{map_id: map_id}} = socket
) do
{:ok, info} = map_id |> get_connection_info(from, to)
{:reply, info, socket}
end
def handle_ui_event(
"get_passages",
%{"from" => from, "to" => to} = _event,
%{assigns: %{map_id: map_id}} = socket
) do
{:ok, passages} = map_id |> get_connection_passages(from, to)
{:reply, passages, socket}
end
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
defp get_connection_passages(map_id, from, to) do
{:ok, passages} = WandererApp.MapChainPassagesRepo.by_connection(map_id, from, to)
passages =
passages
|> Enum.map(fn p ->
%{
p
| character: p.character |> MapEventHandler.map_ui_character_stat()
}
|> Map.put_new(
:ship,
WandererApp.Character.get_ship(%{ship: p.ship_type_id, ship_name: p.ship_name})
)
|> Map.drop([:ship_type_id, :ship_name])
end)
{:ok, %{passages: passages}}
end
defp get_connection_info(map_id, from, to) do
map_id
|> WandererApp.Map.Server.get_connection_info(%{
solar_system_source_id: "#{from}" |> String.to_integer(),
solar_system_target_id: "#{to}" |> String.to_integer()
})
|> case do
{:ok, info} ->
{:ok, info}
_ ->
{:ok, %{}}
end
end
end

View File

@@ -0,0 +1,544 @@
defmodule WandererAppWeb.MapCoreEventHandler do
use WandererAppWeb, :live_component
use Phoenix.Component
require Logger
alias WandererAppWeb.{MapEventHandler, MapCharactersEventHandler}
def handle_server_event(:update_permissions, socket) do
DebounceAndThrottle.Debounce.apply(
Process,
:send_after,
[self(), :refresh_permissions, 100],
"update_permissions_#{inspect(self())}",
1000
)
socket
end
def handle_server_event(
:refresh_permissions,
%{assigns: %{current_user: current_user, map_slug: map_slug}} = socket
) do
{:ok, %{id: map_id, user_permissions: user_permissions, owner_id: owner_id}} =
map_slug
|> WandererApp.Api.Map.get_map_by_slug!()
|> Ash.load(:user_permissions, actor: current_user)
user_permissions =
WandererApp.Permissions.get_map_permissions(
user_permissions,
owner_id,
current_user.characters |> Enum.map(& &1.id)
)
case user_permissions do
%{view_system: false} ->
socket
|> Phoenix.LiveView.put_flash(:error, "Your access to the map have been revoked.")
|> Phoenix.LiveView.push_navigate(to: ~p"/maps")
%{track_character: track_character} ->
{:ok, map_characters} =
case WandererApp.Api.MapCharacterSettings.tracked_by_map(%{
map_id: map_id,
character_ids: current_user.characters |> Enum.map(& &1.id)
}) do
{:ok, settings} ->
{:ok,
settings
|> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)}
_ ->
{:ok, []}
end
case track_character do
false ->
:ok = MapCharactersEventHandler.untrack_characters(map_characters, map_id)
:ok = MapCharactersEventHandler.remove_characters(map_characters, map_id)
_ ->
:ok = MapCharactersEventHandler.track_characters(map_characters, map_id, true)
:ok =
MapCharactersEventHandler.add_characters(map_characters, map_id, track_character)
end
socket
|> assign(user_permissions: user_permissions)
|> MapEventHandler.push_map_event(
"user_permissions",
user_permissions
)
end
end
def handle_server_event(
%{
event: :load_map
},
%{assigns: %{current_user: current_user, map_slug: map_slug}} = socket
) do
ErrorTracker.set_context(%{user_id: current_user.id})
map_slug
|> WandererApp.MapRepo.get_by_slug_with_permissions(current_user)
|> case do
{:ok, map} ->
socket |> init_map(map)
{:error, _} ->
socket
|> put_flash(
:error,
"Something went wrong. Please try one more time or submit an issue."
)
|> push_navigate(to: ~p"/maps")
end
end
def handle_server_event(
%{event: :map_server_started},
socket
),
do: socket |> handle_map_server_started()
def handle_server_event(%{event: :update_map, payload: map_diff}, socket),
do:
socket
|> MapEventHandler.push_map_event(
"map_updated",
map_diff
)
def handle_server_event(
%{event: "presence_diff"},
socket
),
do: socket
def handle_server_event(event, socket) do
Logger.warning(fn -> "unhandled map core event: #{inspect(event)}" end)
socket
end
def handle_ui_event("ui_loaded", _body, %{assigns: %{map_slug: map_slug} = assigns} = socket) do
assigns
|> Map.get(:map_id)
|> case do
map_id when not is_nil(map_id) ->
maybe_start_map(map_id)
_ ->
WandererApp.Cache.insert("map_#{map_slug}:ui_loaded", true)
end
{:noreply, socket}
end
def handle_ui_event(
"live_select_change",
%{"id" => id, "text" => text},
socket
)
when id == "_system_id_live_select_component" do
options =
WandererApp.Api.MapSolarSystem.find_by_name!(%{name: text})
|> Enum.take(100)
|> Enum.map(&map_system/1)
send_update(LiveSelect.Component, options: options, id: id)
{:noreply, socket}
end
def handle_ui_event("toggle_track_" <> character_id, _, socket),
do:
MapCharactersEventHandler.handle_ui_event(
"toggle_track",
%{"character-id" => character_id},
socket
)
def handle_ui_event(
"get_user_settings",
_,
%{assigns: %{map_id: map_id, current_user: current_user}} = socket
) do
{:ok, user_settings} =
WandererApp.MapUserSettingsRepo.get!(map_id, current_user.id)
|> WandererApp.MapUserSettingsRepo.to_form_data()
{:reply, %{user_settings: user_settings}, socket}
end
def handle_ui_event(
"update_user_settings",
user_settings_form,
%{assigns: %{map_id: map_id, current_user: current_user}} = socket
) do
settings =
user_settings_form
|> Map.take(["select_on_spash", "link_signature_on_splash", "delete_connection_with_sigs"])
|> Jason.encode!()
{:ok, user_settings} =
WandererApp.MapUserSettingsRepo.create_or_update(map_id, current_user.id, settings)
{:noreply,
socket |> assign(user_settings_form: user_settings_form, map_user_settings: user_settings)}
end
def handle_ui_event(
"log_map_error",
%{"componentStack" => component_stack, "error" => error},
socket
) do
Logger.error(fn -> "map_ui_error: #{error} \n#{component_stack} " end)
{:noreply,
socket
|> put_flash(:error, "Something went wrong. Please try refresh page or submit an issue.")
|> push_event("js-exec", %{
to: "#map-loader",
attr: "data-loading",
timeout: 100
})}
end
def handle_ui_event("noop", _, socket), do: {:noreply, socket}
def handle_ui_event(
_event,
_body,
%{assigns: %{has_tracked_characters?: false}} =
socket
),
do:
{:noreply,
socket
|> put_flash(
:error,
"You should enable tracking for at least one character."
)}
def handle_ui_event(event, body, socket) do
Logger.warning(fn -> "unhandled map ui event: #{event} #{inspect(body)}" end)
{:noreply, socket}
end
defp maybe_start_map(map_id) do
{:ok, map_server_started} = WandererApp.Cache.lookup("map_#{map_id}:started", false)
if map_server_started do
Process.send_after(self(), %{event: :map_server_started}, 10)
else
WandererApp.Map.Manager.start_map(map_id)
end
end
defp init_map(
%{assigns: %{current_user: current_user, map_slug: map_slug}} = socket,
%{
id: map_id,
deleted: false,
only_tracked_characters: only_tracked_characters,
user_permissions: user_permissions,
name: map_name,
owner_id: owner_id
} = map
) do
user_permissions =
WandererApp.Permissions.get_map_permissions(
user_permissions,
owner_id,
current_user.characters |> Enum.map(& &1.id)
)
{:ok, character_settings} =
case WandererApp.Api.MapCharacterSettings.read_by_map(%{map_id: map_id}) do
{:ok, settings} -> {:ok, settings}
_ -> {:ok, []}
end
{:ok, %{characters: availaible_map_characters}} =
WandererApp.Maps.load_characters(map, character_settings, current_user.id)
can_view? = user_permissions.view_system
can_track? = user_permissions.track_character
tracked_character_ids =
availaible_map_characters |> Enum.filter(& &1.tracked) |> Enum.map(& &1.id)
all_character_tracked? =
not (availaible_map_characters |> Enum.empty?()) and
availaible_map_characters |> Enum.all?(& &1.tracked)
cond do
(only_tracked_characters and can_track? and all_character_tracked?) or
(not only_tracked_characters and can_view?) ->
Phoenix.PubSub.subscribe(WandererApp.PubSub, map_id)
{:ok, ui_loaded} = WandererApp.Cache.get_and_remove("map_#{map_slug}:ui_loaded", false)
if ui_loaded do
maybe_start_map(map_id)
end
socket
|> assign(
map_id: map_id,
page_title: map_name,
user_permissions: user_permissions,
tracked_character_ids: tracked_character_ids,
only_tracked_characters: only_tracked_characters
)
only_tracked_characters and can_track? and not all_character_tracked? ->
Process.send_after(self(), :not_all_characters_tracked, 10)
socket
true ->
Process.send_after(self(), :no_permissions, 10)
socket
end
end
defp init_map(socket, _map) do
Process.send_after(self(), :no_access, 10)
socket
end
defp handle_map_server_started(
%{
assigns: %{
current_user: current_user,
map_id: map_id,
user_permissions:
%{view_system: true, track_character: track_character} = user_permissions
}
} = socket
) do
with {:ok, _} <- current_user |> WandererApp.Api.User.update_last_map(%{last_map_id: map_id}),
{:ok, map_user_settings} <- WandererApp.MapUserSettingsRepo.get(map_id, current_user.id),
{:ok, tracked_map_characters} <-
MapCharactersEventHandler.get_tracked_map_characters(map_id, current_user),
{:ok, characters_limit} <- map_id |> WandererApp.Map.get_characters_limit(),
{:ok, present_character_ids} <-
WandererApp.Cache.lookup("map_#{map_id}:presence_character_ids", []),
{:ok, kills} <- WandererApp.Cache.lookup("map_#{map_id}:zkb_kills", Map.new()) do
user_character_eve_ids = tracked_map_characters |> Enum.map(& &1.eve_id)
events =
case tracked_map_characters |> Enum.any?(&(&1.access_token == nil)) do
true ->
[:invalid_token_message]
_ ->
[]
end
events =
case tracked_map_characters |> Enum.empty?() do
true ->
events ++ [:empty_tracked_characters]
_ ->
events
end
events =
case present_character_ids |> Enum.count() < characters_limit do
true ->
events ++ [{:track_characters, tracked_map_characters, track_character}]
_ ->
events ++ [:map_character_limit]
end
initial_data =
map_id
|> get_map_data()
|> Map.merge(%{
kills:
kills
|> Enum.filter(fn {_, kills} -> kills > 0 end)
|> Enum.map(&MapEventHandler.map_ui_kill/1),
present_characters:
present_character_ids
|> WandererApp.Character.get_character_eve_ids!(),
user_characters: user_character_eve_ids,
user_permissions: user_permissions,
system_static_infos: nil,
wormhole_types: nil,
effects: nil,
reset: false
})
system_static_infos =
map_id
|> WandererApp.Map.list_systems!()
|> Enum.map(&WandererApp.CachedInfo.get_system_static_info!(&1.solar_system_id))
|> Enum.map(&MapEventHandler.map_ui_system_static_info/1)
initial_data =
initial_data
|> Map.put(
:wormholes,
WandererApp.CachedInfo.get_wormhole_types!()
)
|> Map.put(
:effects,
WandererApp.CachedInfo.get_effects!()
)
|> Map.put(
:system_static_infos,
system_static_infos
)
|> Map.put(:reset, true)
socket
|> map_start(%{
map_id: map_id,
map_user_settings: map_user_settings,
user_characters: user_character_eve_ids,
initial_data: initial_data,
events: events
})
else
error ->
Logger.error(fn -> "map_start_error: #{error}" end)
Process.send_after(self(), :no_access, 10)
socket
end
end
defp handle_map_server_started(socket) do
Process.send_after(self(), :no_access, 10)
socket
end
defp map_start(
socket,
%{
map_id: map_id,
map_user_settings: map_user_settings,
user_characters: user_character_eve_ids,
initial_data: initial_data,
events: events
} = _started_data
) do
socket =
socket
|> handle_map_start_events(map_id, events)
map_characters = map_id |> WandererApp.Map.list_characters()
socket
|> assign(
map_loaded?: true,
map_user_settings: map_user_settings,
user_characters: user_character_eve_ids,
has_tracked_characters?:
MapCharactersEventHandler.has_tracked_characters?(user_character_eve_ids)
)
|> MapEventHandler.push_map_event(
"init",
initial_data
|> Map.put(
:characters,
map_characters |> Enum.map(&MapCharactersEventHandler.map_ui_character/1)
)
)
|> push_event("js-exec", %{
to: "#map-loader",
attr: "data-loaded"
})
end
defp handle_map_start_events(socket, map_id, events) do
events
|> Enum.reduce(socket, fn event, socket ->
case event do
{:track_characters, map_characters, track_character} ->
:ok =
MapCharactersEventHandler.track_characters(map_characters, map_id, track_character)
:ok = MapCharactersEventHandler.add_characters(map_characters, map_id, track_character)
socket
:invalid_token_message ->
socket
|> put_flash(
:error,
"One of your characters has expired token. Please refresh it on characters page."
)
:empty_tracked_characters ->
socket
|> put_flash(
:info,
"You should enable tracking for at least one character to work with map."
)
:map_character_limit ->
socket
|> put_flash(
:error,
"Map reached its character limit, your characters won't be tracked. Please contact administrator."
)
_ ->
socket
end
end)
end
defp get_map_data(map_id, include_static_data? \\ true) do
{:ok, hubs} = map_id |> WandererApp.Map.list_hubs()
{:ok, connections} = map_id |> WandererApp.Map.list_connections()
{:ok, systems} = map_id |> WandererApp.Map.list_systems()
%{
systems:
systems
|> Enum.map(fn system -> MapEventHandler.map_ui_system(system, include_static_data?) end),
hubs: hubs,
connections: connections |> Enum.map(&MapEventHandler.map_ui_connection/1)
}
end
defp get_tracked_map_characters(map_id, current_user) do
case WandererApp.Api.MapCharacterSettings.tracked_by_map(%{
map_id: map_id,
character_ids: current_user.characters |> Enum.map(& &1.id)
}) do
{:ok, settings} ->
{:ok,
settings
|> Enum.map(fn s -> s |> Ash.load!(:character) |> Map.get(:character) end)}
_ ->
{:ok, []}
end
end
defp map_system(
%{
solar_system_name: solar_system_name,
constellation_name: constellation_name,
region_name: region_name,
solar_system_id: solar_system_id,
class_title: class_title
} = _system
),
do: %{
label: solar_system_name,
value: solar_system_id,
constellation_name: constellation_name,
region_name: region_name,
class_title: class_title
}
end

View File

@@ -0,0 +1,131 @@
defmodule WandererAppWeb.MapRoutesEventHandler do
use WandererAppWeb, :live_component
use Phoenix.Component
require Logger
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
def handle_server_event(
%{
event: :routes,
payload: {solar_system_id, %{routes: routes, systems_static_data: systems_static_data}}
},
socket
),
do:
socket
|> MapEventHandler.push_map_event(
"routes",
%{
solar_system_id: solar_system_id,
loading: false,
routes: routes,
systems_static_data: systems_static_data
}
)
def handle_server_event(event, socket),
do: MapCoreEventHandler.handle_server_event(event, socket)
def handle_ui_event(
"get_routes",
%{"system_id" => solar_system_id, "routes_settings" => routes_settings} = _event,
%{assigns: %{map_id: map_id, map_loaded?: true}} = socket
) do
Task.async(fn ->
{:ok, hubs} = map_id |> WandererApp.Map.list_hubs()
{:ok, routes} =
WandererApp.Maps.find_routes(
map_id,
hubs,
solar_system_id,
get_routes_settings(routes_settings)
)
{:routes, {solar_system_id, routes}}
end)
{:noreply, socket}
end
def handle_ui_event(
"set_autopilot_waypoint",
%{
"character_eve_ids" => character_eve_ids,
"add_to_beginning" => add_to_beginning,
"clear_other_waypoints" => clear_other_waypoints,
"destination_id" => destination_id
} = _event,
%{assigns: %{current_user: current_user, has_tracked_characters?: true}} = socket
) do
character_eve_ids
|> Task.async_stream(fn character_eve_id ->
set_autopilot_waypoint(
current_user,
character_eve_id,
add_to_beginning,
clear_other_waypoints,
destination_id
)
end)
|> Enum.map(fn _result -> :skip end)
{:noreply, socket}
end
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
defp get_routes_settings(%{
"path_type" => path_type,
"include_mass_crit" => include_mass_crit,
"include_eol" => include_eol,
"include_frig" => include_frig,
"include_cruise" => include_cruise,
"avoid_wormholes" => avoid_wormholes,
"avoid_pochven" => avoid_pochven,
"avoid_edencom" => avoid_edencom,
"avoid_triglavian" => avoid_triglavian,
"include_thera" => include_thera,
"avoid" => avoid
}),
do: %{
path_type: path_type,
include_mass_crit: include_mass_crit,
include_eol: include_eol,
include_frig: include_frig,
include_cruise: include_cruise,
avoid_wormholes: avoid_wormholes,
avoid_pochven: avoid_pochven,
avoid_edencom: avoid_edencom,
avoid_triglavian: avoid_triglavian,
include_thera: include_thera,
avoid: avoid
}
defp get_routes_settings(_), do: %{}
defp set_autopilot_waypoint(
current_user,
character_eve_id,
add_to_beginning,
clear_other_waypoints,
destination_id
) do
case current_user.characters
|> Enum.find(fn c -> c.eve_id == character_eve_id end) do
nil ->
:skip
%{id: character_id} = _character ->
character_id
|> WandererApp.Character.set_autopilot_waypoint(destination_id,
add_to_beginning: add_to_beginning,
clear_other_waypoints: clear_other_waypoints
)
:skip
end
end
end

View File

@@ -0,0 +1,350 @@
defmodule WandererAppWeb.MapSignaturesEventHandler do
use WandererAppWeb, :live_component
use Phoenix.Component
require Logger
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
def handle_server_event(
%{
event: :maybe_link_signature,
payload: %{
character_id: character_id,
solar_system_source: solar_system_source,
solar_system_target: solar_system_target
}
},
%{
assigns: %{
current_user: current_user,
map_id: map_id,
map_user_settings: map_user_settings
}
} = socket
) do
is_user_character =
current_user.characters |> Enum.map(& &1.id) |> Enum.member?(character_id)
is_link_signature_on_splash =
map_user_settings
|> WandererApp.MapUserSettingsRepo.to_form_data!()
|> WandererApp.MapUserSettingsRepo.get_boolean_setting("link_signature_on_splash")
{:ok, signatures} =
WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
map_id: map_id,
solar_system_id: solar_system_source
})
|> case do
{:ok, system} ->
{:ok, get_system_signatures(system.id)}
_ ->
{:ok, []}
end
(is_user_character && is_link_signature_on_splash && not (signatures |> Enum.empty?()))
|> case do
true ->
socket
|> MapEventHandler.push_map_event("link_signature_to_system", %{
solar_system_source: solar_system_source,
solar_system_target: solar_system_target
})
false ->
socket
end
end
def handle_server_event(
%{event: :signatures_updated, payload: solar_system_id},
socket
),
do:
socket
|> MapEventHandler.push_map_event(
"signatures_updated",
solar_system_id
)
def handle_server_event(event, socket),
do: MapCoreEventHandler.handle_server_event(event, socket)
def handle_ui_event(
"update_signatures",
%{
"system_id" => solar_system_id,
"added" => added_signatures,
"updated" => updated_signatures,
"removed" => removed_signatures
},
%{
assigns: %{
map_id: map_id,
map_user_settings: map_user_settings,
user_characters: user_characters,
user_permissions: %{update_system: true}
}
} = socket
) do
WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
map_id: map_id,
solar_system_id: solar_system_id |> String.to_integer()
})
|> case do
{:ok, system} ->
first_character_eve_id =
user_characters |> List.first()
case not is_nil(first_character_eve_id) do
true ->
added_signatures =
added_signatures
|> parse_signatures(first_character_eve_id, system.id)
updated_signatures =
updated_signatures
|> parse_signatures(first_character_eve_id, system.id)
updated_signatures_eve_ids =
updated_signatures
|> Enum.map(fn s -> s.eve_id end)
removed_signatures_eve_ids =
removed_signatures
|> parse_signatures(first_character_eve_id, system.id)
|> Enum.map(fn s -> s.eve_id end)
delete_connection_with_sigs =
map_user_settings
|> WandererApp.MapUserSettingsRepo.to_form_data!()
|> WandererApp.MapUserSettingsRepo.get_boolean_setting(
"delete_connection_with_sigs"
)
WandererApp.Api.MapSystemSignature.by_system_id!(system.id)
|> Enum.filter(fn s -> s.eve_id in removed_signatures_eve_ids end)
|> Enum.each(fn s ->
if delete_connection_with_sigs && not is_nil(s.linked_system_id) do
map_id
|> WandererApp.Map.Server.delete_connection(%{
solar_system_source_id: solar_system_id |> String.to_integer(),
solar_system_target_id: s.linked_system_id
})
end
s
|> Ash.destroy!()
end)
WandererApp.Api.MapSystemSignature.by_system_id!(system.id)
|> Enum.filter(fn s -> s.eve_id in updated_signatures_eve_ids end)
|> Enum.each(fn s ->
updated = updated_signatures |> Enum.find(fn u -> u.eve_id == s.eve_id end)
if not is_nil(updated) do
s
|> WandererApp.Api.MapSystemSignature.update(updated)
end
end)
added_signatures
|> Enum.map(fn s ->
s |> WandererApp.Api.MapSystemSignature.create!()
end)
Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{
event: :signatures_updated,
payload: system.solar_system_id
})
{:reply, %{signatures: get_system_signatures(system.id)}, socket}
_ ->
{:reply, %{signatures: []},
socket
|> put_flash(
:error,
"You should enable tracking for at least one character to work with signatures."
)}
end
_ ->
{:noreply, socket}
end
end
def handle_ui_event(
"get_signatures",
%{"system_id" => solar_system_id},
%{
assigns: %{
map_id: map_id
}
} = socket
) do
case WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
map_id: map_id,
solar_system_id: solar_system_id |> String.to_integer()
}) do
{:ok, system} ->
{:reply, %{signatures: get_system_signatures(system.id)}, socket}
_ ->
{:reply, %{signatures: []}, socket}
end
end
def handle_ui_event(
"link_signature_to_system",
%{
"signature_eve_id" => signature_eve_id,
"solar_system_source" => solar_system_source,
"solar_system_target" => solar_system_target
},
%{
assigns: %{
map_id: map_id,
user_characters: user_characters,
user_permissions: %{update_system: true}
}
} = socket
) do
case WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
map_id: map_id,
solar_system_id: solar_system_source
}) do
{:ok, system} ->
first_character_eve_id =
user_characters |> List.first()
case not is_nil(first_character_eve_id) do
true ->
WandererApp.Api.MapSystemSignature.by_system_id!(system.id)
|> Enum.filter(fn s -> s.eve_id == signature_eve_id end)
|> Enum.each(fn s ->
s
|> WandererApp.Api.MapSystemSignature.update_linked_system(%{
linked_system_id: solar_system_target
})
end)
Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{
event: :signatures_updated,
payload: solar_system_source
})
{:noreply, socket}
_ ->
{:noreply,
socket
|> put_flash(
:error,
"You should enable tracking for at least one character to work with signatures."
)}
end
_ ->
{:noreply, socket}
end
end
def handle_ui_event(
"unlink_signature",
%{
"signature_eve_id" => signature_eve_id,
"solar_system_source" => solar_system_source
},
%{
assigns: %{
map_id: map_id,
user_characters: user_characters,
user_permissions: %{update_system: true}
}
} = socket
) do
case WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
map_id: map_id,
solar_system_id: solar_system_source
}) do
{:ok, system} ->
first_character_eve_id =
user_characters |> List.first()
case not is_nil(first_character_eve_id) do
true ->
WandererApp.Api.MapSystemSignature.by_system_id!(system.id)
|> Enum.filter(fn s -> s.eve_id == signature_eve_id end)
|> Enum.each(fn s ->
s
|> WandererApp.Api.MapSystemSignature.update_linked_system(%{
linked_system_id: nil
})
end)
Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{
event: :signatures_updated,
payload: solar_system_source
})
{:noreply, socket}
_ ->
{:noreply,
socket
|> put_flash(
:error,
"You should enable tracking for at least one character to work with signatures."
)}
end
_ ->
{:noreply, socket}
end
end
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
defp get_system_signatures(system_id),
do:
system_id
|> WandererApp.Api.MapSystemSignature.by_system_id!()
|> Enum.map(fn %{updated_at: updated_at, linked_system_id: linked_system_id} = s ->
s
|> Map.take([
:eve_id,
:name,
:description,
:kind,
:group,
:type,
:updated_at
])
|> Map.put(:linked_system, MapEventHandler.get_system_static_info(linked_system_id))
|> Map.put(:updated_at, updated_at |> Calendar.strftime("%Y/%m/%d %H:%M:%S"))
end)
defp parse_signatures(signatures, character_eve_id, system_id),
do:
signatures
|> Enum.map(fn %{
"eve_id" => eve_id,
"name" => name,
"kind" => kind,
"group" => group
} = signature ->
%{
system_id: system_id,
eve_id: eve_id,
name: name,
description: Map.get(signature, "description"),
kind: kind,
group: group,
type: Map.get(signature, "type"),
character_eve_id: character_eve_id
}
end)
end

View File

@@ -0,0 +1,326 @@
defmodule WandererAppWeb.MapSystemsEventHandler do
use WandererAppWeb, :live_component
use Phoenix.Component
require Logger
alias WandererAppWeb.{MapEventHandler, MapCoreEventHandler}
def handle_server_event(%{event: :add_system, payload: system}, socket),
do:
socket
|> MapEventHandler.push_map_event("add_systems", [MapEventHandler.map_ui_system(system)])
def handle_server_event(%{event: :update_system, payload: system}, socket),
do:
socket
|> MapEventHandler.push_map_event("update_systems", [MapEventHandler.map_ui_system(system)])
def handle_server_event(%{event: :systems_removed, payload: solar_system_ids}, socket),
do:
socket
|> MapEventHandler.push_map_event("remove_systems", solar_system_ids)
def handle_server_event(
%{
event: :maybe_select_system,
payload: %{
character_id: character_id,
solar_system_id: solar_system_id
}
},
%{assigns: %{current_user: current_user, map_user_settings: map_user_settings}} = socket
) do
is_user_character =
current_user.characters |> Enum.map(& &1.id) |> Enum.member?(character_id)
is_select_on_spash =
map_user_settings
|> WandererApp.MapUserSettingsRepo.to_form_data!()
|> WandererApp.MapUserSettingsRepo.get_boolean_setting("select_on_spash")
(is_user_character && is_select_on_spash)
|> case do
true ->
socket
|> MapEventHandler.push_map_event("select_system", solar_system_id)
false ->
socket
end
end
def handle_server_event(%{event: :kills_updated, payload: kills}, socket) do
kills =
kills
|> Enum.map(&MapEventHandler.map_ui_kill/1)
socket
|> MapEventHandler.push_map_event(
"kills_updated",
kills
)
end
def handle_server_event(event, socket),
do: MapCoreEventHandler.handle_server_event(event, socket)
def handle_ui_event(
"add_system",
%{"system_id" => solar_system_id} = _event,
%{
assigns:
%{
map_id: map_id,
map_slug: map_slug,
current_user: current_user,
tracked_character_ids: tracked_character_ids,
user_permissions: %{add_system: true}
} = assigns
} = socket
)
when is_binary(solar_system_id) and solar_system_id != "" do
coordinates = Map.get(assigns, :coordinates)
WandererApp.Map.Server.add_system(
map_id,
%{
solar_system_id: solar_system_id |> String.to_integer(),
coordinates: coordinates
},
current_user.id,
tracked_character_ids |> List.first()
)
{:noreply,
socket
|> push_patch(to: ~p"/#{map_slug}")}
end
def handle_ui_event(
"manual_add_system",
%{"coordinates" => coordinates} = _event,
%{
assigns: %{
has_tracked_characters?: true,
map_slug: map_slug,
user_permissions: %{add_system: true}
}
} =
socket
),
do:
{:noreply,
socket
|> assign(coordinates: coordinates)
|> push_patch(to: ~p"/#{map_slug}/add-system")}
def handle_ui_event(
"add_hub",
%{"system_id" => solar_system_id} = _event,
%{
assigns: %{
map_id: map_id,
current_user: current_user,
tracked_character_ids: tracked_character_ids,
has_tracked_characters?: true,
user_permissions: %{update_system: true}
}
} =
socket
) do
map_id
|> WandererApp.Map.Server.add_hub(%{
solar_system_id: solar_system_id
})
{:ok, _} =
WandererApp.User.ActivityTracker.track_map_event(:hub_added, %{
character_id: tracked_character_ids |> List.first(),
user_id: current_user.id,
map_id: map_id,
solar_system_id: solar_system_id
})
{:noreply, socket}
end
def handle_ui_event(
"delete_hub",
%{"system_id" => solar_system_id} = _event,
%{
assigns: %{
map_id: map_id,
current_user: current_user,
tracked_character_ids: tracked_character_ids,
has_tracked_characters?: true,
user_permissions: %{update_system: true}
}
} =
socket
) do
map_id
|> WandererApp.Map.Server.remove_hub(%{
solar_system_id: solar_system_id
})
{:ok, _} =
WandererApp.User.ActivityTracker.track_map_event(:hub_removed, %{
character_id: tracked_character_ids |> List.first(),
user_id: current_user.id,
map_id: map_id,
solar_system_id: solar_system_id
})
{:noreply, socket}
end
def handle_ui_event(
"update_system_position",
position,
%{
assigns: %{
map_id: map_id,
has_tracked_characters?: true,
user_permissions: %{update_system: true}
}
} = socket
) do
map_id
|> update_system_position(position)
{:noreply, socket}
end
def handle_ui_event(
"update_system_positions",
positions,
%{
assigns: %{
map_id: map_id,
has_tracked_characters?: true,
user_permissions: %{update_system: true}
}
} = socket
) do
map_id
|> update_system_positions(positions)
{:noreply, socket}
end
def handle_ui_event(
"update_system_" <> param,
%{"system_id" => solar_system_id, "value" => value} = _event,
%{
assigns: %{
map_id: map_id,
current_user: current_user,
tracked_character_ids: tracked_character_ids,
has_tracked_characters?: true,
user_permissions: %{update_system: true}
}
} =
socket
) do
method_atom =
case param do
"name" -> :update_system_name
"description" -> :update_system_description
"labels" -> :update_system_labels
"locked" -> :update_system_locked
"tag" -> :update_system_tag
"status" -> :update_system_status
_ -> nil
end
key_atom =
case param do
"name" -> :name
"description" -> :description
"labels" -> :labels
"locked" -> :locked
"tag" -> :tag
"status" -> :status
_ -> :none
end
apply(WandererApp.Map.Server, method_atom, [
map_id,
%{
solar_system_id: "#{solar_system_id}" |> String.to_integer()
}
|> Map.put_new(key_atom, value)
])
{:ok, _} =
WandererApp.User.ActivityTracker.track_map_event(:system_updated, %{
character_id: tracked_character_ids |> List.first(),
user_id: current_user.id,
map_id: map_id,
solar_system_id: "#{solar_system_id}" |> String.to_integer(),
key: key_atom,
value: value
})
{:noreply, socket}
end
def handle_ui_event(
"get_system_static_infos",
%{"solar_system_ids" => solar_system_ids} = _event,
socket
) do
system_static_infos =
solar_system_ids
|> Enum.map(&WandererApp.CachedInfo.get_system_static_info!/1)
|> Enum.map(&MapEventHandler.map_ui_system_static_info/1)
{:reply, %{system_static_infos: system_static_infos}, socket}
end
def handle_ui_event(
"delete_systems",
solar_system_ids,
%{
assigns: %{
map_id: map_id,
current_user: current_user,
tracked_character_ids: tracked_character_ids,
has_tracked_characters?: true,
user_permissions: %{delete_system: true}
}
} =
socket
) do
map_id
|> WandererApp.Map.Server.delete_systems(
solar_system_ids |> Enum.map(&String.to_integer/1),
current_user.id,
tracked_character_ids |> List.first()
)
{:noreply, socket}
end
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
defp update_system_positions(_map_id, []), do: :ok
defp update_system_positions(map_id, [position | rest]) do
update_system_position(map_id, position)
update_system_positions(map_id, rest)
end
defp update_system_position(map_id, %{
"position" => %{"x" => x, "y" => y},
"solar_system_id" => solar_system_id
}),
do:
map_id
|> WandererApp.Map.Server.update_system_position(%{
solar_system_id: solar_system_id |> String.to_integer(),
position_x: x,
position_y: y
})
end

View File

@@ -0,0 +1,293 @@
defmodule WandererAppWeb.MapEventHandler do
use WandererAppWeb, :live_component
use Phoenix.Component
require Logger
alias WandererAppWeb.{
MapActivityEventHandler,
MapCharactersEventHandler,
MapConnectionsEventHandler,
MapCoreEventHandler,
MapRoutesEventHandler,
MapSignaturesEventHandler,
MapSystemsEventHandler
}
@map_characters_events [
:character_added,
:character_removed,
:character_updated,
:characters_updated,
:present_characters_updated
]
@map_characters_ui_events [
"add_character",
"toggle_track",
"hide_tracking"
]
@map_system_events [
:add_system,
:update_system,
:systems_removed,
:maybe_select_system,
:kills_updated
]
@map_system_ui_events [
"add_hub",
"delete_hub",
"add_system",
"delete_systems",
"manual_add_system",
"get_system_static_infos",
"update_system_position",
"update_system_positions",
"update_system_name",
"update_system_description",
"update_system_labels",
"update_system_locked",
"update_system_tag",
"update_system_status"
]
@map_connection_events [
:add_connection,
:remove_connections,
:update_connection
]
@map_connection_ui_events [
"manual_add_connection",
"manual_delete_connection",
"get_connection_info",
"get_passages",
"update_connection_time_status",
"update_connection_mass_status",
"update_connection_ship_size_type",
"update_connection_locked",
"update_connection_custom_info"
]
@map_activity_events [
:character_activity
]
@map_activity_ui_events [
"show_activity",
"hide_activity"
]
@map_routes_events [
:routes
]
@map_routes_ui_events [
"get_routes",
"set_autopilot_waypoint"
]
@map_signatures_events [
:maybe_link_signature,
:signatures_updated
]
@map_signatures_ui_events [
"update_signatures",
"get_signatures",
"link_signature_to_system",
"unlink_signature"
]
def handle_event(socket, %{event: event_name} = event)
when event_name in @map_characters_events,
do: MapCharactersEventHandler.handle_server_event(event, socket)
def handle_event(socket, %{event: event_name} = event)
when event_name in @map_system_events,
do: MapSystemsEventHandler.handle_server_event(event, socket)
def handle_event(socket, %{event: event_name} = event)
when event_name in @map_connection_events,
do: MapConnectionsEventHandler.handle_server_event(event, socket)
def handle_event(socket, %{event: event_name} = event)
when event_name in @map_activity_events,
do: MapActivityEventHandler.handle_server_event(event, socket)
def handle_event(socket, %{event: event_name} = event)
when event_name in @map_routes_events,
do: MapRoutesEventHandler.handle_server_event(event, socket)
def handle_event(socket, %{event: event_name} = event)
when event_name in @map_signatures_events,
do: MapSignaturesEventHandler.handle_server_event(event, socket)
def handle_event(socket, {ref, result}) when is_reference(ref) do
Process.demonitor(ref, [:flush])
case result do
{:map_error, map_error} ->
Process.send_after(self(), map_error, 100)
socket
{event, payload} ->
Process.send_after(
self(),
%{
event: event,
payload: payload
},
10
)
socket
_ ->
socket
end
end
def handle_event(socket, event),
do: MapCoreEventHandler.handle_server_event(event, socket)
def handle_ui_event(event, body, socket)
when event in @map_characters_ui_events,
do: MapCharactersEventHandler.handle_ui_event(event, body, socket)
def handle_ui_event(event, body, socket)
when event in @map_system_ui_events,
do: MapSystemsEventHandler.handle_ui_event(event, body, socket)
def handle_ui_event(event, body, socket)
when event in @map_connection_ui_events,
do: MapConnectionsEventHandler.handle_ui_event(event, body, socket)
def handle_ui_event(event, body, socket)
when event in @map_routes_ui_events,
do: MapRoutesEventHandler.handle_ui_event(event, body, socket)
def handle_ui_event(event, body, socket)
when event in @map_signatures_ui_events,
do: MapSignaturesEventHandler.handle_ui_event(event, body, socket)
def handle_ui_event(event, body, socket)
when event in @map_activity_ui_events,
do: MapActivityEventHandler.handle_ui_event(event, body, socket)
def handle_ui_event(event, body, socket),
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
def get_system_static_info(nil), do: nil
def get_system_static_info(solar_system_id) do
case WandererApp.CachedInfo.get_system_static_info(solar_system_id) do
{:ok, system_static_info} ->
map_ui_system_static_info(system_static_info)
_ ->
%{}
end
end
def push_map_event(socket, type, body),
do:
socket
|> Phoenix.LiveView.Utils.push_event("map_event", %{
type: type,
body: body
})
def map_ui_character_stat(character),
do:
character
|> Map.take([
:eve_id,
:name,
:corporation_ticker,
:alliance_ticker
])
def map_ui_connection(
%{
solar_system_source: solar_system_source,
solar_system_target: solar_system_target,
mass_status: mass_status,
time_status: time_status,
ship_size_type: ship_size_type,
locked: locked
} = _connection
),
do: %{
id: "#{solar_system_source}_#{solar_system_target}",
mass_status: mass_status,
time_status: time_status,
ship_size_type: ship_size_type,
locked: locked,
source: "#{solar_system_source}",
target: "#{solar_system_target}"
}
def map_ui_system(
%{
solar_system_id: solar_system_id,
name: name,
description: description,
position_x: position_x,
position_y: position_y,
locked: locked,
tag: tag,
labels: labels,
status: status,
visible: visible
} = _system,
_include_static_data? \\ true
) do
system_static_info = get_system_static_info(solar_system_id)
%{
id: "#{solar_system_id}",
position: %{x: position_x, y: position_y},
description: description,
name: name,
system_static_info: system_static_info,
labels: labels,
locked: locked,
status: status,
tag: tag,
visible: visible
}
end
def map_ui_system_static_info(nil), do: %{}
def map_ui_system_static_info(system_static_info),
do:
system_static_info
|> Map.take([
:region_id,
:constellation_id,
:solar_system_id,
:solar_system_name,
:solar_system_name_lc,
:constellation_name,
:region_name,
:system_class,
:security,
:type_description,
:class_title,
:is_shattered,
:effect_name,
:effect_power,
:statics,
:wandering,
:triglavian_invasion_status,
:sun_type_id
])
def map_ui_kill({solar_system_id, kills}),
do: %{solar_system_id: solar_system_id, kills: kills}
def map_ui_kill(_kill), do: %{}
end

File diff suppressed because it is too large Load Diff

View File

@@ -104,20 +104,18 @@
id="characters-tracking-table"
class="h-[400px] !overflow-y-auto"
rows={characters}
row_click={fn character -> send(self(), "toggle_track_#{character.id}") end}
>
<:col :let={character} label="Tracked">
<div class="flex items-center gap-3">
<label>
<input
type="checkbox"
class="checkbox"
phx-click="toggle_track"
phx-value-character-id={character.id}
id={"character-track-#{character.id}"}
checked={character.tracked}
/>
</label>
<label class="flex items-center gap-3">
<input
type="checkbox"
class="checkbox"
phx-click="toggle_track"
phx-value-character-id={character.id}
id={"character-track-#{character.id}"}
checked={character.tracked}
/>
<div class="flex items-center gap-3">
<.avatar url={member_icon_url(character.eve_id)} label={character.name} />
<div>
@@ -127,7 +125,7 @@
<div class="text-sm opacity-50"></div>
</div>
</div>
</div>
</label>
</:col>
</.table>
</.async_result>

View File

@@ -8,11 +8,14 @@ defmodule WandererAppWeb.MapsLive do
@pubsub_client Application.compile_env(:wanderer_app, :pubsub_client)
@impl true
def mount(_params, %{"user_id" => user_id} = _session, socket) when not is_nil(user_id) do
def mount(
_params,
%{"user_id" => user_id} = _session,
%{assigns: %{current_user: current_user}} = socket
)
when not is_nil(user_id) do
{:ok, active_characters} = WandererApp.Api.Character.active_by_user(%{user_id: user_id})
current_user = socket.assigns.current_user
user_characters =
active_characters
|> Enum.map(&map_character/1)
@@ -601,16 +604,6 @@ defmodule WandererAppWeb.MapsLive do
{added_acls, removed_acls} = map.acls |> Enum.map(& &1.id) |> _get_acls_diff(form["acls"])
added_acls
|> Enum.each(fn acl_id ->
:telemetry.execute([:wanderer_app, :map, :acl, :add], %{count: 1})
end)
removed_acls
|> Enum.each(fn acl_id ->
:telemetry.execute([:wanderer_app, :map, :acl, :remove], %{count: 1})
end)
Phoenix.PubSub.broadcast(
WandererApp.PubSub,
"maps:#{map.id}",

View File

@@ -2,7 +2,7 @@ defmodule WandererApp.MixProject do
use Mix.Project
@source_url "https://github.com/wanderer-industries/wanderer"
@version "1.12.9"
@version "1.15.3"
def project do
[

View File

@@ -1,54 +0,0 @@
defmodule WandererApp.Repo.Migrations.InstallAshFunctionsExtension420240922090427 do
@moduledoc """
Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
execute("""
CREATE OR REPLACE FUNCTION uuid_generate_v7()
RETURNS UUID
AS $$
DECLARE
timestamp TIMESTAMPTZ;
microseconds INT;
BEGIN
timestamp = clock_timestamp();
microseconds = (cast(extract(microseconds FROM timestamp)::INT - (floor(extract(milliseconds FROM timestamp))::INT * 1000) AS DOUBLE PRECISION) * 4.096)::INT;
RETURN encode(
set_byte(
set_byte(
overlay(uuid_send(gen_random_uuid()) placing substring(int8send(floor(extract(epoch FROM timestamp) * 1000)::BIGINT) FROM 3) FROM 1 FOR 6
),
6, (b'0111' || (microseconds >> 8)::bit(4))::bit(8)::int
),
7, microseconds::bit(8)::int
),
'hex')::UUID;
END
$$
LANGUAGE PLPGSQL
VOLATILE;
""")
execute("""
CREATE OR REPLACE FUNCTION timestamp_from_uuid_v7(_uuid uuid)
RETURNS TIMESTAMP WITHOUT TIME ZONE
AS $$
SELECT to_timestamp(('x0000' || substr(_uuid::TEXT, 1, 8) || substr(_uuid::TEXT, 10, 4))::BIT(64)::BIGINT::NUMERIC / 1000);
$$
LANGUAGE SQL
IMMUTABLE PARALLEL SAFE STRICT;
""")
end
def down do
# Uncomment this if you actually want to uninstall the extensions
# when this migration is rolled back:
execute("DROP FUNCTION IF EXISTS uuid_generate_v7(), timestamp_from_uuid_v7(uuid)")
end
end