Compare commits

...

36 Commits

Author SHA1 Message Date
CI
1523b625bc chore: release version v1.75.11 2025-08-11 11:21:27 +00:00
CI
fb91eeb692 chore: release version v1.75.10 2025-08-11 11:20:57 +00:00
CI
601d2e02cb chore: release version v1.75.9 2025-08-11 11:20:24 +00:00
Dmitry Popov
0a662d34eb Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 13:19:52 +02:00
Dmitry Popov
5cd4693e9d Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 13:19:49 +02:00
CI
f3f0f860e3 chore: release version v1.75.8 2025-08-11 11:04:18 +00:00
Dmitry Popov
93a5cf8a79 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 13:03:42 +02:00
Dmitry Popov
7cf15cbc21 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 13:03:39 +02:00
CI
30bc6d20b2 chore: release version v1.75.7 2025-08-11 10:55:27 +00:00
Dmitry Popov
b39f99fde4 Merge branch 'main' of github.com:wanderer-industries/wanderer 2025-08-11 12:55:02 +02:00
Dmitry Popov
0e8aa9efa4 chore: release version v1.75.5 2025-08-11 12:54:58 +02:00
CI
e1fcde36e3 chore: release version v1.75.6 2025-08-11 10:51:27 +00:00
Dmitry Popov
7aafe077d3 chore: release version v1.75.5 2025-08-11 12:50:59 +02:00
CI
5b8cab5e76 chore: release version v1.75.5 2025-08-11 10:48:40 +00:00
Dmitry Popov
4ab56af40a chore: release version v1.75.4 2025-08-11 12:48:09 +02:00
CI
e8cea86a76 chore: release version v1.75.4 2025-08-11 07:52:21 +00:00
Dmitry Popov
d0a6e0b358 Merge pull request #496 from guarzo/guarzo/secaudit 2025-08-11 11:51:26 +04:00
guarzo
8831b3e970 fix: restore security audit 2025-08-11 03:37:33 +00:00
CI
f6db6f0914 chore: release version v1.75.3
Some checks failed
Flaky Test Detection / 🔍 Detect Flaky Tests (push) Has been cancelled
Flaky Test Detection / 📊 Analyze Test History (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
🧪 Test Suite / Test Suite (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
2025-08-10 22:17:25 +00:00
Dmitry Popov
ab8baeedd1 fix(core): Fixed character tracking issues 2025-08-11 00:16:52 +02:00
CI
eccee5e72e chore: release version v1.75.2 2025-08-10 16:00:40 +00:00
Dmitry Popov
4d93055bda Merge pull request #480 from wanderer-industries/default-settings
Default settings
2025-08-10 19:56:58 +04:00
Dmitry Popov
c60c16e56a chore: Fix issues with ash resources 2025-08-10 17:45:29 +02:00
Dmitry Popov
99b1de5647 chore: Fix issues with ash resources 2025-08-10 17:44:56 +02:00
Dmitry Popov
7efe11a421 chore: Fix issues with ash resources 2025-08-10 17:21:36 +02:00
Dmitry Popov
954108856a chore: Fix issues with ash resources 2025-08-10 16:55:45 +02:00
Dmitry Popov
cbca745ec4 Merge branch 'main' into default-settings 2025-08-10 16:19:59 +02:00
DanSylvest
e15e7c8f8d fix(Map): Fix indents for ally logos in list "On the map" 2025-08-10 12:51:15 +03:00
DanSylvest
65e8a520e5 fix(Map): Fix cancelling ping from system context menu 2025-08-10 12:00:05 +03:00
DanSylvest
3926af5a6d fix(Map): Hide admin settings tab 2025-08-10 10:02:29 +03:00
DanSylvest
556fb33223 fix(Map): Remote map setting refactoring 2025-08-10 09:57:50 +03:00
Dmitry Popov
82295adeab Merge pull request #477 from guarzo/guarzo/settings
feature: provide default settings interface
2025-07-31 12:21:06 +04:00
CI
efabf060c7 chore: release version v1.75.1
Some checks failed
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
🧪 Test Suite / Test Suite (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled
Flaky Test Detection / 🔍 Detect Flaky Tests (push) Has been cancelled
Flaky Test Detection / 📊 Analyze Test History (push) Has been cancelled
2025-07-30 07:11:08 +00:00
Dmitry Popov
96e434ebf5 Merge pull request #479 from guarzo/guarzo/rally 2025-07-30 11:10:42 +04:00
guarzo
d81e2567cc fix: unable to cancel ping from right click context menu 2025-07-30 03:12:27 +00:00
guarzo
9d7d4fad2e feature: provide default settings interface 2025-07-27 14:27:45 -04:00
140 changed files with 4446 additions and 11743 deletions

View File

@@ -5,7 +5,7 @@ on:
branches:
- main
- develop
- "releases/*"
env:
MIX_ENV: prod
GH_TOKEN: ${{ github.token }}
@@ -53,6 +53,7 @@ jobs:
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
ssh-key: "${{ secrets.COMMIT_KEY }}"
fetch-depth: 0
- name: 😅 Cache deps
id: cache-deps
@@ -97,7 +98,7 @@ jobs:
mix git_ops.release --force-patch --yes
git push --follow-tags
echo "commit_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
- name: Set commit hash for develop
id: set-commit-develop
if: github.ref == 'refs/heads/develop'
@@ -106,11 +107,9 @@ jobs:
docker:
name: 🛠 Build Docker Images
if: github.ref == 'refs/heads/develop'
needs: build
runs-on: ubuntu-22.04
outputs:
release-tag: ${{ steps.get-latest-tag.outputs.tag }}
release-notes: ${{ steps.get-content.outputs.string }}
permissions:
checks: write
contents: write
@@ -137,19 +136,6 @@ jobs:
ref: ${{ needs.build.outputs.commit_hash }}
fetch-depth: 0
- name: Prepare Changelog
if: github.ref == 'refs/heads/main'
run: |
yes | cp -rf CHANGELOG.md priv/changelog/CHANGELOG.md
sed -i '1i%{title: "Change Log"}\n\n---\n' priv/changelog/CHANGELOG.md
- name: Get Release Tag
id: get-latest-tag
if: github.ref == 'refs/heads/main'
uses: "WyriHaximus/github-action-get-previous-tag@v1"
with:
fallback: 1.0.0
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
@@ -198,26 +184,6 @@ jobs:
if-no-files-found: error
retention-days: 1
- uses: markpatterson27/markdown-to-output@v1
id: extract-changelog
if: github.ref == 'refs/heads/main'
with:
filepath: CHANGELOG.md
- name: Get content
uses: 2428392/gh-truncate-string-action@v1.3.0
id: get-content
if: github.ref == 'refs/heads/main'
with:
stringToTruncate: |
📣 Wanderer new release available 🎉
**Version**: ${{ steps.get-latest-tag.outputs.tag }}
${{ steps.extract-changelog.outputs.body }}
maxLength: 500
truncationSymbol: "…"
merge:
runs-on: ubuntu-latest
needs:
@@ -248,9 +214,6 @@ jobs:
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}},enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern={{major}}.{{minor}},enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern={{version}},value=${{ needs.docker.outputs.release-tag }},enable=${{ github.ref == 'refs/heads/main' }}
type=raw,value=develop,enable=${{ github.ref == 'refs/heads/develop' }}
type=raw,value=develop-{{sha}},enable=${{ github.ref == 'refs/heads/develop' }}
@@ -267,19 +230,25 @@ jobs:
create-release:
name: 🏷 Create Release
runs-on: ubuntu-22.04
needs: [docker, merge]
if: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }}
needs: build
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get Release Tag
id: get-latest-tag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
with:
fallback: 1.0.0
- name: 🏷 Create Draft Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.docker.outputs.release-tag }}
name: Release ${{ needs.docker.outputs.release-tag }}
tag_name: ${{ steps.get-latest-tag.outputs.tag }}
name: Release ${{ steps.get-latest-tag.outputs.tag }}
body: |
## Info
Commit ${{ github.sha }} was deployed to `staging`. [See code diff](${{ github.event.compare }}).
@@ -289,10 +258,3 @@ jobs:
## How to Promote?
In order to promote this to prod, edit the draft and press **"Publish release"**.
draft: true
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v5.3.0
if: github.ref == 'refs/heads/main'
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: ${{ needs.docker.outputs.release-notes }}

184
.github/workflows/docker-arm.yml vendored Normal file
View File

@@ -0,0 +1,184 @@
name: Build Docker ARM Image
on:
push:
tags:
- '**'
env:
MIX_ENV: prod
GH_TOKEN: ${{ github.token }}
REGISTRY_IMAGE: wandererltd/community-edition-arm
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
jobs:
docker:
name: 🛠 Build Docker Images
runs-on: ubuntu-22.04
outputs:
release-tag: ${{ steps.get-latest-tag.outputs.tag }}
release-notes: ${{ steps.get-content.outputs.string }}
permissions:
checks: write
contents: write
packages: write
attestations: write
id-token: write
pull-requests: write
repository-projects: write
strategy:
fail-fast: false
matrix:
platform:
- linux/arm64
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Get Release Tag
id: get-latest-tag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
with:
fallback: 1.0.0
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
ref: ${{ steps.get-latest-tag.outputs.tag }}
fetch-depth: 0
- name: Prepare Changelog
run: |
yes | cp -rf CHANGELOG.md priv/changelog/CHANGELOG.md
sed -i '1i%{title: "Change Log"}\n\n---\n' priv/changelog/CHANGELOG.md
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_IMAGE }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.WANDERER_DOCKER_USER }}
password: ${{ secrets.WANDERER_DOCKER_PASSWORD }}
- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
push: true
context: .
file: ./Dockerfile
cache-from: type=gha
cache-to: type=gha,mode=max
labels: ${{ steps.meta.outputs.labels }}
platforms: ${{ matrix.platform }}
outputs: type=image,"name=${{ env.REGISTRY_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
build-args: |
MIX_ENV=prod
BUILD_METADATA=${{ steps.meta.outputs.json }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
- 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 **ARM** release available 🎉
[wandererltd/community-edition-arm:${{ steps.get-latest-tag.outputs.tag }}](https://hub.docker.com/r/wandererltd/community-edition-arm/tags)
**Version**: ${{ steps.get-latest-tag.outputs.tag }}
${{ steps.extract-changelog.outputs.body }}
maxLength: 500
truncationSymbol: "…"
merge:
runs-on: ubuntu-latest
needs:
- docker
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.WANDERER_DOCKER_USER }}
password: ${{ secrets.WANDERER_DOCKER_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY_IMAGE }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}},value=${{ needs.docker.outputs.release-tag }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
notify:
name: 🏷 Notify about release
runs-on: ubuntu-22.04
needs: [docker, merge]
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: ${{ needs.docker.outputs.release-notes }}

184
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,184 @@
name: Build Docker Image
on:
push:
tags:
- '**'
env:
MIX_ENV: prod
GH_TOKEN: ${{ github.token }}
REGISTRY_IMAGE: wandererltd/community-edition
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
jobs:
docker:
name: 🛠 Build Docker Images
runs-on: ubuntu-22.04
outputs:
release-tag: ${{ steps.get-latest-tag.outputs.tag }}
release-notes: ${{ steps.get-content.outputs.string }}
permissions:
checks: write
contents: write
packages: write
attestations: write
id-token: write
pull-requests: write
repository-projects: write
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Get Release Tag
id: get-latest-tag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
with:
fallback: 1.0.0
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
ref: ${{ steps.get-latest-tag.outputs.tag }}
fetch-depth: 0
- name: Prepare Changelog
run: |
yes | cp -rf CHANGELOG.md priv/changelog/CHANGELOG.md
sed -i '1i%{title: "Change Log"}\n\n---\n' priv/changelog/CHANGELOG.md
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_IMAGE }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.WANDERER_DOCKER_USER }}
password: ${{ secrets.WANDERER_DOCKER_PASSWORD }}
- name: Build and push
id: build
uses: docker/build-push-action@v6
with:
push: true
context: .
file: ./Dockerfile
cache-from: type=gha
cache-to: type=gha,mode=max
labels: ${{ steps.meta.outputs.labels }}
platforms: ${{ matrix.platform }}
outputs: type=image,"name=${{ env.REGISTRY_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
build-args: |
MIX_ENV=prod
BUILD_METADATA=${{ steps.meta.outputs.json }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
- 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 🎉
[wandererltd/community-edition:${{ steps.get-latest-tag.outputs.tag }}](https://hub.docker.com/r/wandererltd/community-edition/tags)
**Version**: ${{ steps.get-latest-tag.outputs.tag }}
${{ steps.extract-changelog.outputs.body }}
maxLength: 500
truncationSymbol: "…"
merge:
runs-on: ubuntu-latest
needs:
- docker
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.WANDERER_DOCKER_USER }}
password: ${{ secrets.WANDERER_DOCKER_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY_IMAGE }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}},value=${{ needs.docker.outputs.release-tag }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
notify:
name: 🏷 Notify about release
runs-on: ubuntu-22.04
needs: [docker, merge]
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@v5.3.0
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
content: ${{ needs.docker.outputs.release-notes }}

View File

@@ -2,6 +2,83 @@
<!-- changelog -->
## [v1.75.11](https://github.com/wanderer-industries/wanderer/compare/v1.75.10...v1.75.11) (2025-08-11)
## [v1.75.10](https://github.com/wanderer-industries/wanderer/compare/v1.75.9...v1.75.10) (2025-08-11)
## [v1.75.9](https://github.com/wanderer-industries/wanderer/compare/v1.75.8...v1.75.9) (2025-08-11)
## [v1.75.8](https://github.com/wanderer-industries/wanderer/compare/v1.75.7...v1.75.8) (2025-08-11)
## [v1.75.7](https://github.com/wanderer-industries/wanderer/compare/v1.75.6...v1.75.7) (2025-08-11)
## [v1.75.6](https://github.com/wanderer-industries/wanderer/compare/v1.75.5...v1.75.6) (2025-08-11)
## [v1.75.5](https://github.com/wanderer-industries/wanderer/compare/v1.75.4...v1.75.5) (2025-08-11)
## [v1.75.4](https://github.com/wanderer-industries/wanderer/compare/v1.75.3...v1.75.4) (2025-08-11)
### Bug Fixes:
* restore security audit
## [v1.75.3](https://github.com/wanderer-industries/wanderer/compare/v1.75.2...v1.75.3) (2025-08-10)
### Bug Fixes:
* core: Fixed character tracking issues
## [v1.75.2](https://github.com/wanderer-industries/wanderer/compare/v1.75.1...v1.75.2) (2025-08-10)
### Bug Fixes:
* Map: Fix indents for ally logos in list "On the map"
* Map: Fix cancelling ping from system context menu
* Map: Hide admin settings tab
* Map: Remote map setting refactoring
## [v1.75.1](https://github.com/wanderer-industries/wanderer/compare/v1.75.0...v1.75.1) (2025-07-30)
### Bug Fixes:
* unable to cancel ping from right click context menu
## [v1.75.0](https://github.com/wanderer-industries/wanderer/compare/v1.74.13...v1.75.0) (2025-07-29)

View File

@@ -21,21 +21,17 @@ RUN mkdir config
# to ensure any relevant config change will trigger the dependencies
# to be re-compiled.
COPY config/config.exs config/${MIX_ENV}.exs config/
COPY priv priv
COPY lib lib
COPY assets assets
RUN mix compile
RUN mix assets.deploy
RUN mix compile
# Changes to config/runtime.exs don't require recompiling the code
COPY config/runtime.exs config/
COPY rel rel
RUN mix release
# start a new build stage so that the final image will only contain

View File

@@ -1,7 +1,7 @@
.vertical-tabs-container {
display: flex;
width: 100%;
min-height: 300px;
min-height: 400px;
.p-tabview {
width: 100%;
@@ -68,6 +68,28 @@
}
}
&.color-warn {
@apply bg-yellow-600/5 border-r-yellow-600/20;
&:hover {
@apply bg-yellow-600/10 border-r-yellow-600/40;
}
&.p-tabview-selected {
@apply bg-yellow-600/10 border-r-yellow-600;
.p-tabview-nav-link {
@apply text-yellow-600;
}
&:hover {
@apply bg-yellow-600/10 border-r-yellow-600;
}
}
}
}
}

View File

@@ -19,7 +19,7 @@ export interface ContextMenuSystemProps {
onSystemStatus(val: number): void;
onSystemLabels(val: string): void;
onCustomLabelDialog(): void;
onTogglePing(type: PingType, solar_system_id: string, hasPing: boolean): void;
onTogglePing(type: PingType, solar_system_id: string, ping_id: string | undefined, hasPing: boolean): void;
onWaypointSet: WaypointSetContextHandler;
}

View File

@@ -109,7 +109,7 @@ export const useContextMenuSystemItems = ({
{ separator: true },
{
command: () => onTogglePing(PingType.Rally, systemId, hasPing),
command: () => onTogglePing(PingType.Rally, systemId, ping?.id, hasPing),
disabled: !isShowPingBtn,
template: () => {
const iconClasses = clsx({

View File

@@ -1,17 +1,24 @@
import { Node } from 'reactflow';
import { useCallback, useRef, useState } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import { SolarSystemRawType } from '@/hooks/Mapper/types';
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
export const useContextMenuSystemMultipleHandlers = () => {
const {
data: { pings },
} = useMapRootState();
const contextMenuRef = useRef<ContextMenu | null>(null);
const [systems, setSystems] = useState<Node<SolarSystemRawType>[]>();
const { deleteSystems } = useDeleteSystems();
const ping = useMemo(() => (pings.length === 1 ? pings[0] : undefined), [pings]);
const handleSystemMultipleContext: NodeSelectionMouseHandler = (ev, systems_) => {
setSystems(systems_);
ev.preventDefault();
@@ -24,13 +31,17 @@ export const useContextMenuSystemMultipleHandlers = () => {
return;
}
const sysToDel = systems.filter(x => !x.data.locked).map(x => x.id);
const sysToDel = systems
.filter(x => !x.data.locked)
.filter(x => x.id !== ping?.solar_system_id)
.map(x => x.id);
if (sysToDel.length === 0) {
return;
}
deleteSystems(sysToDel);
}, [deleteSystems, systems]);
}, [deleteSystems, systems, ping]);
return {
handleSystemMultipleContext,

View File

@@ -1,6 +1,6 @@
import { MapUserSettings, SettingsWithVersion } from '@/hooks/Mapper/mapRootProvider/types.ts';
const REQUIRED_KEYS = [
export const REQUIRED_KEYS = [
'widgets',
'interface',
'onTheMap',

View File

@@ -1,3 +1,4 @@
export * from './useSystemInfo';
export * from './useGetOwnOnlineCharacters';
export * from './useElementWidth';
export * from './useDetectSettingsChanged';

View File

@@ -0,0 +1,23 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useEffect, useState } from 'react';
export const useDetectSettingsChanged = () => {
const {
storedSettings: {
interfaceSettings,
settingsRoutes,
settingsLocal,
settingsSignatures,
settingsOnTheMap,
settingsKills,
},
} = useMapRootState();
const [counter, setCounter] = useState(0);
useEffect(
() => setCounter(x => x + 1),
[interfaceSettings, settingsRoutes, settingsLocal, settingsSignatures, settingsOnTheMap, settingsKills],
);
return counter;
};

View File

@@ -21,7 +21,9 @@ import { KillsCounter } from '@/hooks/Mapper/components/map/components/KillsCoun
export const SolarSystemNodeTheme = memo((props: NodeProps<MapSolarSystemType>) => {
const nodeVars = useSolarSystemNode(props);
const { localCounterCharacters } = useLocalCounter(nodeVars);
const { killsCount: localKillsCount, killsActivityType: localKillsActivityType } = useNodeKillsCount(nodeVars.solarSystemId);
const { killsCount: localKillsCount, killsActivityType: localKillsActivityType } = useNodeKillsCount(
nodeVars.solarSystemId,
);
// console.log('JOipP', `render ${nodeVars.id}`, render++);

View File

@@ -14,6 +14,7 @@ import { PrimeIcons } from 'primereact/api';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand } from '@/hooks/Mapper/types';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
const TOOLTIP_PROPS = { content: 'Remove comment', position: TooltipPosition.top };
@@ -28,8 +29,7 @@ export const MarkdownComment = ({ text, time, characterEveId, id }: MarkdownComm
const char = useGetCacheCharacter(characterEveId);
const [hovered, setHovered] = useState(false);
const cpRemoveBtnRef = useRef<HTMLElement>();
const [cpRemoveVisible, setCpRemoveVisible] = useState(false);
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
const { outCommand } = useMapRootState();
const ref = useRef({ outCommand, id });
@@ -45,9 +45,6 @@ export const MarkdownComment = ({ text, time, characterEveId, id }: MarkdownComm
const handleMouseEnter = useCallback(() => setHovered(true), []);
const handleMouseLeave = useCallback(() => setHovered(false), []);
const handleShowCP = useCallback(() => setCpRemoveVisible(true), []);
const handleHideCP = useCallback(() => setCpRemoveVisible(false), []);
return (
<>
<InfoDrawer
@@ -68,11 +65,11 @@ export const MarkdownComment = ({ text, time, characterEveId, id }: MarkdownComm
{!hovered && <TimeAgo timestamp={time} />}
{hovered && (
// @ts-ignore
<div ref={cpRemoveBtnRef}>
<div ref={cfRef}>
<WdImgButton
className={clsx(PrimeIcons.TRASH, 'hover:text-red-400')}
tooltip={TOOLTIP_PROPS}
onClick={handleShowCP}
onClick={cfShow}
/>
</div>
)}
@@ -85,9 +82,9 @@ export const MarkdownComment = ({ text, time, characterEveId, id }: MarkdownComm
</InfoDrawer>
<ConfirmPopup
target={cpRemoveBtnRef.current}
visible={cpRemoveVisible}
onHide={handleHideCP}
target={cfRef.current}
visible={cfVisible}
onHide={cfHide}
message="Are you sure you want to delete?"
icon="pi pi-exclamation-triangle"
accept={handleDelete}

View File

@@ -16,8 +16,9 @@ import { PrimeIcons } from 'primereact/api';
import { Button } from 'primereact/button';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { Toast } from 'primereact/toast';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import useRefState from 'react-usestateref';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
const PING_PLACEMENT_MAP = {
[PingsPlacement.rightTop]: 'top-right',
@@ -78,9 +79,7 @@ export interface PingsInterfaceProps {
export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
const toast = useRef<Toast>(null);
const [isShow, setIsShow, isShowRef] = useRefState(false);
const cpRemoveBtnRef = useRef<HTMLElement>();
const [cpRemoveVisible, setCpRemoveVisible] = useState(false);
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
const {
storedSettings: { interfaceSettings },
@@ -98,9 +97,6 @@ export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
const ping = useMemo(() => (pings.length === 1 ? pings[0] : null), [pings]);
const handleShowCP = useCallback(() => setCpRemoveVisible(true), []);
const handleHideCP = useCallback(() => setCpRemoveVisible(false), []);
const navigateTo = useCallback(() => {
if (!ping) {
return;
@@ -242,11 +238,11 @@ export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
/>
{/*@ts-ignore*/}
<div ref={cpRemoveBtnRef}>
<div ref={cfRef}>
<WdImgButton
className={clsx('pi-trash', 'text-red-400 hover:text-red-300')}
tooltip={DELETE_TOOLTIP_PROPS}
onClick={handleShowCP}
onClick={cfShow}
/>
</div>
{/* TODO ADD solar system menu*/}
@@ -272,9 +268,9 @@ export const PingsInterface = ({ hasLeftOffset }: PingsInterfaceProps) => {
/>
<ConfirmPopup
target={cpRemoveBtnRef.current}
visible={cpRemoveVisible}
onHide={handleHideCP}
target={cfRef.current}
visible={cfVisible}
onHide={cfHide}
message="Are you sure you want to delete ping?"
icon="pi pi-exclamation-triangle text-orange-400"
accept={removePing}

View File

@@ -3,7 +3,7 @@ import { Dialog } from 'primereact/dialog';
import { useCallback, useRef, useState } from 'react';
import { TabPanel, TabView } from 'primereact/tabview';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand } from '@/hooks/Mapper/types';
import { OutCommand, UserPermission } from '@/hooks/Mapper/types';
import { CONNECTIONS_CHECKBOXES_PROPS, SIGNATURES_CHECKBOXES_PROPS, SYSTEMS_CHECKBOXES_PROPS } from './constants.ts';
import {
MapSettingsProvider,
@@ -12,7 +12,10 @@ import {
import { WidgetsSettings } from './components/WidgetsSettings';
import { CommonSettings } from './components/CommonSettings';
import { SettingsListItem } from './types.ts';
import { ImportExport } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/components/ImportExport.tsx';
import { ImportExport } from './components/ImportExport.tsx';
import { ServerSettings } from './components/ServerSettings.tsx';
import { AdminSettings } from './components/AdminSettings.tsx';
import { useMapCheckPermissions } from '@/hooks/Mapper/mapRootProvider/hooks/api';
export interface MapSettingsProps {
visible: boolean;
@@ -24,6 +27,7 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
const { outCommand } = useMapRootState();
const { renderSettingItem, setUserRemoteSettings } = useMapSettings();
const isAdmin = useMapCheckPermissions([UserPermission.ADMIN_MAP]);
const refVars = useRef({ outCommand, onHide, visible });
refVars.current = { outCommand, onHide, visible };
@@ -58,7 +62,7 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
header="Map user settings"
visible
draggable={false}
style={{ width: '550px' }}
style={{ width: '600px' }}
onShow={handleShow}
onHide={handleHide}
>
@@ -92,6 +96,16 @@ export const MapSettingsComp = ({ visible, onHide }: MapSettingsProps) => {
<TabPanel header="Import/Export" className="h-full" headerClassName={styles.verticalTabHeader}>
<ImportExport />
</TabPanel>
<TabPanel header="Server Settings" className="h-full" headerClassName="color-warn">
<ServerSettings />
</TabPanel>
{isAdmin && (
<TabPanel header="Admin Settings" className="h-full" headerClassName="color-warn">
<AdminSettings />
</TabPanel>
)}
</TabView>
</div>
</div>

View File

@@ -0,0 +1,128 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { Button } from 'primereact/button';
import { callToastError, callToastSuccess, callToastWarn } from '@/hooks/Mapper/helpers';
import { OutCommand } from '@/hooks/Mapper/types';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
import { MapUserSettings, RemoteAdminSettingsResponse } from '@/hooks/Mapper/mapRootProvider/types.ts';
import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
import fastDeepEqual from 'fast-deep-equal';
import { useDetectSettingsChanged } from '@/hooks/Mapper/components/hooks';
export const AdminSettings = () => {
const {
storedSettings: { getSettingsForExport },
outCommand,
} = useMapRootState();
const settingsChanged = useDetectSettingsChanged();
const [currentRemoteSettings, setCurrentRemoteSettings] = useState<MapUserSettings | null>(null);
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
const toast = useRef<Toast | null>(null);
const hasSettingsForExport = useMemo(() => !!getSettingsForExport(), [getSettingsForExport]);
const refVars = useRef({ currentRemoteSettings, getSettingsForExport });
refVars.current = { currentRemoteSettings, getSettingsForExport };
useEffect(() => {
const load = async () => {
let res: RemoteAdminSettingsResponse | undefined;
try {
res = await outCommand({ type: OutCommand.getDefaultSettings, data: null });
} catch (error) {
// do nothing
}
if (!res || res.default_settings == null) {
return;
}
setCurrentRemoteSettings(parseMapUserSettings(res.default_settings));
};
load();
}, [outCommand]);
const isDirty = useMemo(() => {
const { currentRemoteSettings, getSettingsForExport } = refVars.current;
const localCurrent = parseMapUserSettings(getSettingsForExport());
return !fastDeepEqual(currentRemoteSettings, localCurrent);
// eslint-disable-next-line
}, [settingsChanged, currentRemoteSettings]);
const handleSync = useCallback(async () => {
const settings = getSettingsForExport();
if (!settings) {
callToastWarn(toast.current, 'No settings to save');
return;
}
let response: { success: boolean } | undefined;
try {
response = await outCommand({
type: OutCommand.saveDefaultSettings,
data: { settings },
});
} catch (err) {
callToastError(toast.current, 'Something went wrong while saving settings');
console.error('ERROR: ', err);
return;
}
if (!response || !response.success) {
callToastError(toast.current, 'Settings not saved - dont not why it');
return;
}
setCurrentRemoteSettings(parseMapUserSettings(settings));
callToastSuccess(toast.current, 'Settings saved successfully');
}, [getSettingsForExport, outCommand]);
return (
<div className="w-full h-full flex flex-col gap-5">
<div className="flex flex-col gap-1">
<div>
<Button
// @ts-ignore
ref={cfRef}
onClick={cfShow}
icon="pi pi-save"
size="small"
severity="danger"
label="Save as Map Default"
className="py-[4px]"
disabled={!hasSettingsForExport || !isDirty}
/>
</div>
{!isDirty && <span className="text-red-500/70 text-[12px]">*Local and remote are identical.</span>}
<span className="text-stone-500 text-[12px]">
*Will save your current settings as the default for all new users of this map. This action will overwrite any
existing default settings.
</span>
</div>
<Toast ref={toast} />
<ConfirmPopup
target={cfRef.current}
visible={cfVisible}
onHide={cfHide}
message="Your settings will overwrite default. Sure?."
icon="pi pi-exclamation-triangle"
accept={handleSync}
/>
</div>
);
};

View File

@@ -7,9 +7,14 @@ import {
import { useMapSettings } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/MapSettingsProvider.tsx';
import { SettingsListItem } from '@/hooks/Mapper/components/mapRootContent/components/MapSettings/types.ts';
import { useCallback } from 'react';
import { Button } from 'primereact/button';
import { TooltipPosition, WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
export const CommonSettings = () => {
const { renderSettingItem } = useMapSettings();
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
const renderSettingsList = useCallback(
(list: SettingsListItem[]) => {
@@ -18,6 +23,8 @@ export const CommonSettings = () => {
[renderSettingItem],
);
const handleResetSettings = () => {};
return (
<div className="flex flex-col h-full gap-1">
<div>
@@ -29,6 +36,33 @@ export const CommonSettings = () => {
<div className="grid grid-cols-[1fr_auto]">{renderSettingItem(MINI_MAP_PLACEMENT)}</div>
<div className="grid grid-cols-[1fr_auto]">{renderSettingItem(PINGS_PLACEMENT)}</div>
<div className="grid grid-cols-[1fr_auto]">{renderSettingItem(THEME_SETTING)}</div>
<div className="border-b-2 border-dotted border-stone-700/50 h-px my-3" />
<div className="grid grid-cols-[1fr_auto]">
<div />
<WdTooltipWrapper content="This dangerous action. And can not be undone" position={TooltipPosition.top}>
<Button
// @ts-ignore
ref={cfRef}
className="py-[4px]"
onClick={cfShow}
outlined
size="small"
severity="danger"
label="Reset Settings"
/>
</WdTooltipWrapper>
</div>
<ConfirmPopup
target={cfRef.current}
visible={cfVisible}
onHide={cfHide}
message="All settings for this map will be reset to default."
icon="pi pi-exclamation-triangle"
accept={handleResetSettings}
/>
</div>
);
};

View File

@@ -0,0 +1,90 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { Divider } from 'primereact/divider';
import { callToastError, callToastSuccess, callToastWarn } from '@/hooks/Mapper/helpers';
type SaveDefaultSettingsReturn = { success: boolean; error: string };
export const DefaultSettings = () => {
const {
outCommand,
storedSettings: { getSettingsForExport },
data: { userPermissions },
} = useMapRootState();
const [loading, setLoading] = useState(false);
const toast = useRef<Toast | null>(null);
const refVars = useRef({ getSettingsForExport, outCommand });
refVars.current = { getSettingsForExport, outCommand };
const handleSaveAsDefault = useCallback(async () => {
const settings = refVars.current.getSettingsForExport();
if (!settings) {
callToastWarn(toast.current, 'No settings to save');
return;
}
setLoading(true);
let response: SaveDefaultSettingsReturn;
try {
response = await refVars.current.outCommand({
type: OutCommand.saveDefaultSettings,
data: { settings },
});
} catch (error) {
console.error('Save default settings error:', error);
callToastError(toast.current, 'Failed to save default settings');
setLoading(false);
return;
}
if (response.success) {
callToastSuccess(toast.current, 'Default settings saved successfully');
setLoading(false);
return;
}
callToastError(toast.current, response.error || 'Failed to save default settings');
setLoading(false);
}, []);
if (!userPermissions?.admin_map) {
return null;
}
return (
<>
<Divider />
<div className="w-full h-full flex flex-col gap-5">
<h3 className="text-lg font-semibold">Default Settings (Admin Only)</h3>
<div className="flex flex-col gap-1">
<div>
<Button
onClick={handleSaveAsDefault}
icon="pi pi-save"
size="small"
severity="danger"
label="Save as Map Default"
className="py-[4px]"
loading={loading}
disabled={loading}
/>
</div>
<span className="text-stone-500 text-[12px]">
*Will save your current settings as the default for all new users of this map. This action will overwrite
any existing default settings.
</span>
</div>
<Toast ref={toast} />
</div>
</>
);
};

View File

@@ -0,0 +1,97 @@
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Toast } from 'primereact/toast';
import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
import { Button } from 'primereact/button';
import { OutCommand } from '@/hooks/Mapper/types';
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
import { callToastSuccess } from '@/hooks/Mapper/helpers';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
import { RemoteAdminSettingsResponse } from '@/hooks/Mapper/mapRootProvider/types.ts';
export const ServerSettings = () => {
const {
storedSettings: { applySettings },
outCommand,
} = useMapRootState();
const [hasSettings, setHasSettings] = useState(false);
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
const toast = useRef<Toast | null>(null);
const handleSync = useCallback(async () => {
let res: RemoteAdminSettingsResponse | undefined;
try {
res = await outCommand({ type: OutCommand.getDefaultSettings, data: null });
} catch (error) {
// do nothing
}
if (res?.default_settings == null) {
applySettings(createDefaultWidgetSettings());
return;
}
try {
applySettings(parseMapUserSettings(res.default_settings));
callToastSuccess(toast.current, 'Settings synchronized successfully');
} catch (error) {
applySettings(createDefaultWidgetSettings());
}
}, [applySettings, outCommand]);
useEffect(() => {
const load = async () => {
let res: RemoteAdminSettingsResponse | undefined;
try {
res = await outCommand({ type: OutCommand.getDefaultSettings, data: null });
} catch (error) {
// do nothing
}
if (res?.default_settings == null) {
return;
}
setHasSettings(true);
};
load();
}, [outCommand]);
return (
<div className="w-full h-full flex flex-col gap-5">
<div className="flex flex-col gap-1">
<div>
<Button
// @ts-ignore
ref={cfRef}
onClick={cfShow}
icon="pi pi-file-import"
size="small"
severity="warning"
label="Sync with Default Settings"
className="py-[4px]"
disabled={!hasSettings}
/>
</div>
{!hasSettings && (
<span className="text-red-500/70 text-[12px]">*Default settings was not set by map administrator.</span>
)}
<span className="text-stone-500 text-[12px]">*Will apply admin settings which set as Default for map.</span>
</div>
<Toast ref={toast} />
<ConfirmPopup
target={cfRef.current}
visible={cfVisible}
onHide={cfHide}
message="You lost your current settings. Sure?."
icon="pi pi-exclamation-triangle"
accept={handleSync}
/>
</div>
);
};

View File

@@ -28,6 +28,9 @@ export const WidgetsSettings = ({}: WidgetsSettingsProps) => {
/>
))}
</div>
<div className="border-b-2 border-dotted border-stone-700/50 h-px my-3" />
<div className="grid grid-cols-[1fr_auto]">
<div />
<Button className="py-[4px]" onClick={resetWidgets} outlined size="small" label="Reset Widgets"></Button>

View File

@@ -1,7 +1,7 @@
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import { ConfirmPopup } from 'primereact/confirmpopup';
import { useCallback, useRef, useState } from 'react';
import { useCallback, useRef } from 'react';
import { MapUserSettings } from '@/hooks/Mapper/mapRootProvider/types.ts';
import {
DEFAULT_KILLS_WIDGET_SETTINGS,
@@ -15,6 +15,7 @@ import { DEFAULT_SIGNATURE_SETTINGS } from '@/hooks/Mapper/constants/signatures.
import { Toast } from 'primereact/toast';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { saveTextFile } from '@/hooks/Mapper/utils';
import { useConfirmPopup } from '@/hooks/Mapper/hooks';
const createSettings = function <T>(lsSettings: string | null, defaultValues: T) {
return {
@@ -24,10 +25,7 @@ const createSettings = function <T>(lsSettings: string | null, defaultValues: T)
};
export const OldSettingsDialog = () => {
const cpRemoveBtnRef = useRef<HTMLElement>();
const [cpRemoveVisible, setCpRemoveVisible] = useState(false);
const handleShowCP = useCallback(() => setCpRemoveVisible(true), []);
const handleHideCP = useCallback(() => setCpRemoveVisible(false), []);
const { cfShow, cfHide, cfVisible, cfRef } = useConfirmPopup();
const toast = useRef<Toast | null>(null);
const {
@@ -143,8 +141,8 @@ export const OldSettingsDialog = () => {
<div className="flex items-center justify-end">
<Button
// @ts-ignore
ref={cpRemoveBtnRef}
onClick={handleShowCP}
ref={cfRef}
onClick={cfShow}
icon="pi pi-exclamation-triangle"
size="small"
severity="warning"
@@ -192,9 +190,9 @@ export const OldSettingsDialog = () => {
</Dialog>
<ConfirmPopup
target={cpRemoveBtnRef.current}
visible={cpRemoveVisible}
onHide={handleHideCP}
target={cfRef.current}
visible={cfVisible}
onHide={cfHide}
message="After click dialog will disappear. Ready?"
icon="pi pi-exclamation-triangle"
accept={handleProceed}

View File

@@ -13,6 +13,8 @@ import { InputText } from 'primereact/inputtext';
import { IconField } from 'primereact/iconfield';
const itemTemplate = (item: CharacterTypeRaw & WithIsOwnCharacter, options: VirtualScrollerTemplateOptions) => {
const showAllyLogoPlaceholder = options.props.items?.some(x => x.alliance_id != null);
return (
<div
className={clsx(classes.CharacterRow, 'w-full box-border px-2 py-1', {
@@ -22,7 +24,15 @@ const itemTemplate = (item: CharacterTypeRaw & WithIsOwnCharacter, options: Virt
})}
style={{ height: options.props.itemSize + 'px' }}
>
<CharacterCard showCorporationLogo showAllyLogo showSystem showTicker showShip {...item} />
<CharacterCard
showCorporationLogo
showAllyLogo
showAllyLogoPlaceholder={showAllyLogoPlaceholder}
showSystem
showTicker
showShip
{...item}
/>
</div>
);
};

View File

@@ -181,17 +181,20 @@ export const MapWrapper = () => {
ref.current.systemContextProps.systemId && setOpenSettings(ref.current.systemContextProps.systemId);
}, []);
const handleTogglePing = useCallback(async (type: PingType, solar_system_id: string, hasPing: boolean) => {
if (hasPing) {
await outCommand({
type: OutCommand.cancelPing,
data: { type, solar_system_id: solar_system_id },
});
return;
}
const handleTogglePing = useCallback(
async (type: PingType, solar_system_id: string, ping_id: string | undefined, hasPing: boolean) => {
if (hasPing) {
await outCommand({
type: OutCommand.cancelPing,
data: { type, id: ping_id },
});
return;
}
setOpenPing({ type, solar_system_id });
}, []);
setOpenPing({ type, solar_system_id });
},
[],
);
const handleCustomLabelDialog = useCallback(() => {
const { systemContextProps } = ref.current;

View File

@@ -24,6 +24,7 @@ export type CharacterCardProps = {
useSystemsCache?: boolean;
showCorporationLogo?: boolean;
showAllyLogo?: boolean;
showAllyLogoPlaceholder?: boolean;
simpleMode?: boolean;
} & WithIsOwnCharacter &
WithClassName;
@@ -47,6 +48,7 @@ export const CharacterCard = ({
showShipName,
showCorporationLogo,
showAllyLogo,
showAllyLogoPlaceholder,
showTicker,
useSystemsCache,
className,
@@ -217,6 +219,18 @@ export const CharacterCard = ({
/>
</WdTooltipWrapper>
)}
{showAllyLogo && showAllyLogoPlaceholder && !char.alliance_id && (
<WdTooltipWrapper position={TooltipPosition.top} content="No alliance">
<span
className={clsx(
'min-w-[33px] min-h-[33px] w-[33px] h-[33px]',
'flex transition-[border-color,opacity] duration-250 rounded-none',
'wd-bg-default',
)}
/>
</WdTooltipWrapper>
)}
</div>
<div className="flex flex-col flex-grow overflow-hidden w-[50px]">

View File

@@ -2,3 +2,4 @@ export * from './sortWHClasses';
export * from './parseSignatures';
export * from './getSystemById';
export * from './getEveImageUrl';
export * from './toastHelpers';

View File

@@ -0,0 +1,28 @@
import { Toast } from 'primereact/toast';
export const callToastWarn = (toast: Toast | null, msg: string, life = 3000) => {
toast?.show({
severity: 'warn',
summary: 'Warning',
detail: msg,
life,
});
};
export const callToastError = (toast: Toast | null, msg: string, life = 3000) => {
toast?.show({
severity: 'error',
summary: 'Error',
detail: msg,
life,
});
};
export const callToastSuccess = (toast: Toast | null, msg: string, life = 3000) => {
toast?.show({
severity: 'success',
summary: 'Success',
detail: msg,
life,
});
};

View File

@@ -3,3 +3,4 @@ export * from './useHotkey';
export * from './usePageVisibility';
export * from './useSkipContextMenu';
export * from './useThrottle';
export * from './useConfirmPopup';

View File

@@ -0,0 +1,10 @@
import { useCallback, useRef, useState } from 'react';
export const useConfirmPopup = () => {
const cfRef = useRef<HTMLElement>();
const [cfVisible, setCfVisible] = useState(false);
const cfShow = useCallback(() => setCfVisible(true), []);
const cfHide = useCallback(() => setCfVisible(false), []);
return { cfRef, cfVisible, cfShow, cfHide };
};

View File

@@ -131,6 +131,7 @@ export interface MapRootContextProps {
hasOldSettings: boolean;
getSettingsForExport(): string | undefined;
applySettings(settings: MapUserSettings): boolean;
resetSettings(settings: MapUserSettings): void;
checkOldSettings(): void;
};
}
@@ -175,6 +176,7 @@ const MapRootContext = createContext<MapRootContextProps>({
hasOldSettings: false,
getSettingsForExport: () => '',
applySettings: () => false,
resetSettings: () => null,
checkOldSettings: () => null,
},
});
@@ -196,7 +198,7 @@ const MapRootHandlers = forwardRef(({ children }: WithChildren, fwdRef: Forwarde
export const MapRootProvider = ({ children, fwdRef, outCommand }: MapRootProviderProps) => {
const { update, ref } = useContextStore<MapRootData>({ ...INITIAL_DATA });
const storedSettings = useMapUserSettings(ref);
const storedSettings = useMapUserSettings(ref, outCommand);
const { windowsSettings, toggleWidgetVisibility, updateWidgetSettings, resetWidgets } =
useStoreWidgets(storedSettings);

View File

@@ -0,0 +1,30 @@
import { MapUserSettings } from '@/hooks/Mapper/mapRootProvider/types.ts';
import {
DEFAULT_KILLS_WIDGET_SETTINGS,
DEFAULT_ON_THE_MAP_SETTINGS,
DEFAULT_ROUTES_SETTINGS,
DEFAULT_WIDGET_LOCAL_SETTINGS,
getDefaultWidgetProps,
STORED_INTERFACE_DEFAULT_VALUES,
} from '@/hooks/Mapper/mapRootProvider/constants.ts';
import { DEFAULT_SIGNATURE_SETTINGS } from '@/hooks/Mapper/constants/signatures.ts';
// TODO - we need provide and compare version
const createWidgetSettingsWithVersion = <T>(settings: T) => {
return {
version: 0,
settings,
};
};
export const createDefaultWidgetSettings = (): MapUserSettings => {
return {
killsWidget: createWidgetSettingsWithVersion(DEFAULT_KILLS_WIDGET_SETTINGS),
localWidget: createWidgetSettingsWithVersion(DEFAULT_WIDGET_LOCAL_SETTINGS),
widgets: createWidgetSettingsWithVersion(getDefaultWidgetProps()),
routes: createWidgetSettingsWithVersion(DEFAULT_ROUTES_SETTINGS),
onTheMap: createWidgetSettingsWithVersion(DEFAULT_ON_THE_MAP_SETTINGS),
signaturesWidget: createWidgetSettingsWithVersion(DEFAULT_SIGNATURE_SETTINGS),
interface: createWidgetSettingsWithVersion(STORED_INTERFACE_DEFAULT_VALUES),
};
};

View File

@@ -0,0 +1,66 @@
import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types';
import { Dispatch, SetStateAction, useCallback, useEffect, useRef } from 'react';
import {
MapUserSettings,
MapUserSettingsStructure,
RemoteAdminSettingsResponse,
} from '@/hooks/Mapper/mapRootProvider/types.ts';
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
import { parseMapUserSettings } from '@/hooks/Mapper/components/helpers';
interface UseActualizeRemoteMapSettingsProps {
outCommand: OutCommandHandler;
mapUserSettings: MapUserSettingsStructure;
applySettings: (val: MapUserSettings) => void;
setMapUserSettings: Dispatch<SetStateAction<MapUserSettingsStructure>>;
map_slug: string | null;
}
export const useActualizeRemoteMapSettings = ({
outCommand,
mapUserSettings,
setMapUserSettings,
applySettings,
map_slug,
}: UseActualizeRemoteMapSettingsProps) => {
const refVars = useRef({ applySettings, mapUserSettings, setMapUserSettings, map_slug });
refVars.current = { applySettings, mapUserSettings, setMapUserSettings, map_slug };
const actualizeRemoteMapSettings = useCallback(async () => {
const { applySettings } = refVars.current;
let res: RemoteAdminSettingsResponse | undefined;
try {
res = await outCommand({ type: OutCommand.getDefaultSettings, data: null });
} catch (error) {
// do nothing
}
if (res?.default_settings == null) {
applySettings(createDefaultWidgetSettings());
return;
}
try {
applySettings(parseMapUserSettings(res.default_settings));
} catch (error) {
applySettings(createDefaultWidgetSettings());
}
}, [outCommand]);
useEffect(() => {
const { mapUserSettings } = refVars.current;
// INFO: Do nothing if slug is not set
if (map_slug == null) {
return;
}
// INFO: Do nothing if user have already data
if (map_slug in mapUserSettings) {
return;
}
actualizeRemoteMapSettings();
}, [actualizeRemoteMapSettings, map_slug]);
};

View File

@@ -1,44 +1,16 @@
import useLocalStorageState from 'use-local-storage-state';
import { MapUserSettings, MapUserSettingsStructure } from '@/hooks/Mapper/mapRootProvider/types.ts';
import {
DEFAULT_KILLS_WIDGET_SETTINGS,
DEFAULT_ON_THE_MAP_SETTINGS,
DEFAULT_ROUTES_SETTINGS,
DEFAULT_WIDGET_LOCAL_SETTINGS,
getDefaultWidgetProps,
STORED_INTERFACE_DEFAULT_VALUES,
} from '@/hooks/Mapper/mapRootProvider/constants.ts';
import { useCallback, useEffect, useRef, useState } from 'react';
import { DEFAULT_SIGNATURE_SETTINGS } from '@/hooks/Mapper/constants/signatures';
import { MapRootData } from '@/hooks/Mapper/mapRootProvider';
import { useSettingsValueAndSetter } from '@/hooks/Mapper/mapRootProvider/hooks/useSettingsValueAndSetter.ts';
import fastDeepEqual from 'fast-deep-equal';
// import { actualizeSettings } from '@/hooks/Mapper/mapRootProvider/helpers';
// TODO - we need provide and compare version
const createWidgetSettingsWithVersion = <T>(settings: T) => {
return {
version: 0,
settings,
};
};
const createDefaultWidgetSettings = (): MapUserSettings => {
return {
killsWidget: createWidgetSettingsWithVersion(DEFAULT_KILLS_WIDGET_SETTINGS),
localWidget: createWidgetSettingsWithVersion(DEFAULT_WIDGET_LOCAL_SETTINGS),
widgets: createWidgetSettingsWithVersion(getDefaultWidgetProps()),
routes: createWidgetSettingsWithVersion(DEFAULT_ROUTES_SETTINGS),
onTheMap: createWidgetSettingsWithVersion(DEFAULT_ON_THE_MAP_SETTINGS),
signaturesWidget: createWidgetSettingsWithVersion(DEFAULT_SIGNATURE_SETTINGS),
interface: createWidgetSettingsWithVersion(STORED_INTERFACE_DEFAULT_VALUES),
};
};
import { OutCommandHandler } from '@/hooks/Mapper/types';
import { useActualizeRemoteMapSettings } from '@/hooks/Mapper/mapRootProvider/hooks/useActualizeRemoteMapSettings.ts';
import { createDefaultWidgetSettings } from '@/hooks/Mapper/mapRootProvider/helpers/createDefaultWidgetSettings.ts';
const EMPTY_OBJ = {};
export const useMapUserSettings = ({ map_slug }: MapRootData) => {
export const useMapUserSettings = ({ map_slug }: MapRootData, outCommand: OutCommandHandler) => {
const [isReady, setIsReady] = useState(false);
const [hasOldSettings, setHasOldSettings] = useState(false);
@@ -49,19 +21,25 @@ export const useMapUserSettings = ({ map_slug }: MapRootData) => {
const ref = useRef({ mapUserSettings, setMapUserSettings, map_slug });
ref.current = { mapUserSettings, setMapUserSettings, map_slug };
useEffect(() => {
const { mapUserSettings, setMapUserSettings } = ref.current;
if (map_slug === null) {
return;
const applySettings = useCallback((settings: MapUserSettings) => {
const { map_slug, mapUserSettings, setMapUserSettings } = ref.current;
if (map_slug == null) {
return false;
}
if (!(map_slug in mapUserSettings)) {
setMapUserSettings({
...mapUserSettings,
[map_slug]: createDefaultWidgetSettings(),
});
if (fastDeepEqual(settings, mapUserSettings[map_slug])) {
return false;
}
}, [map_slug]);
setMapUserSettings(old => ({
...old,
[map_slug]: settings,
}));
return true;
}, []);
useActualizeRemoteMapSettings({ outCommand, applySettings, mapUserSettings, setMapUserSettings, map_slug });
const [interfaceSettings, setInterfaceSettings] = useSettingsValueAndSetter(
mapUserSettings,
@@ -178,23 +156,9 @@ export const useMapUserSettings = ({ map_slug }: MapRootData) => {
return JSON.stringify(ref.current.mapUserSettings[map_slug]);
}, []);
const applySettings = useCallback((settings: MapUserSettings) => {
const { map_slug, mapUserSettings, setMapUserSettings } = ref.current;
if (map_slug == null) {
return false;
}
if (fastDeepEqual(settings, mapUserSettings[map_slug])) {
return false;
}
setMapUserSettings(old => ({
...old,
[map_slug]: settings,
}));
return true;
}, []);
const resetSettings = useCallback(() => {
applySettings(createDefaultWidgetSettings());
}, [applySettings]);
return {
isReady,
@@ -217,6 +181,7 @@ export const useMapUserSettings = ({ map_slug }: MapRootData) => {
getSettingsForExport,
applySettings,
resetSettings,
checkOldSettings,
};
};

View File

@@ -85,3 +85,7 @@ export type MapUserSettings = {
export type MapUserSettingsStructure = {
[mapId: string]: MapUserSettings;
};
export type WdResponse<T> = T;
export type RemoteAdminSettingsResponse = { default_settings?: string };

View File

@@ -269,6 +269,8 @@ export enum OutCommand {
showTracking = 'show_tracking',
getUserSettings = 'get_user_settings',
updateUserSettings = 'update_user_settings',
saveDefaultSettings = 'save_default_settings',
getDefaultSettings = 'get_default_settings',
unlinkSignature = 'unlink_signature',
searchSystems = 'search_systems',
undoDeleteSignatures = 'undo_delete_signatures',

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@@ -102,6 +102,23 @@ config :error_tracker,
repo: WandererApp.Repo,
otp_app: :wanderer_app
# Security Audit Configuration
config :wanderer_app, WandererApp.SecurityAudit,
enabled: true,
# Set to true in production for better performance
async: false,
batch_size: 100,
flush_interval: 5000,
log_level: :info,
threat_detection: %{
enabled: true,
max_failed_attempts: 5,
max_permission_denials: 10,
window_seconds: 300,
bulk_operation_threshold: 10000
},
retention_days: 90
config :git_ops,
mix_project: Mix.Project.get!(),
changelog_file: "CHANGELOG.md",

View File

@@ -27,5 +27,8 @@ config :swoosh, local: false
config :logger,
level: :info
# Enable async security audit processing in production
config :wanderer_app, WandererApp.SecurityAudit, async: true
# Runtime production configuration, including reading
# of environment variables, is done on config/runtime.exs.

View File

@@ -28,6 +28,7 @@ defmodule WandererApp.Api do
resource WandererApp.Api.MapSubscription
resource WandererApp.Api.MapTransaction
resource WandererApp.Api.MapUserSettings
resource WandererApp.Api.MapDefaultSettings
resource WandererApp.Api.User
resource WandererApp.Api.ShipTypeInfo
resource WandererApp.Api.UserActivity

View File

@@ -0,0 +1,145 @@
defmodule WandererApp.Api.MapDefaultSettings do
@moduledoc """
Resource for storing default map settings that admins can configure.
These settings will be applied to new users when they first access the map.
"""
use Ash.Resource,
domain: WandererApp.Api,
data_layer: AshPostgres.DataLayer,
extensions: [AshJsonApi.Resource]
postgres do
repo(WandererApp.Repo)
table("map_default_settings")
end
json_api do
type "map_default_settings"
includes([
:map,
:created_by,
:updated_by
])
routes do
base("/map_default_settings")
get(:read)
index(:read)
post(:create)
patch(:update)
delete(:destroy)
end
end
code_interface do
define(:create, action: :create)
define(:update, action: :update)
define(:destroy, action: :destroy)
define(:get_by_map_id, action: :get_by_map_id)
end
actions do
default_accept [
:map_id,
:settings
]
defaults [:read, :destroy]
create :create do
primary?(true)
accept [:map_id, :settings]
change relate_actor(:created_by)
change relate_actor(:updated_by)
change fn changeset, _context ->
changeset
|> validate_json_settings()
end
end
update :update do
primary?(true)
accept [:settings]
# Required for managing relationships
require_atomic? false
change relate_actor(:updated_by)
change fn changeset, _context ->
changeset
|> validate_json_settings()
end
end
read :get_by_map_id do
argument :map_id, :uuid, allow_nil?: false
filter expr(map_id == ^arg(:map_id))
prepare fn query, _context ->
Ash.Query.limit(query, 1)
end
end
end
attributes do
uuid_primary_key :id
attribute :settings, :string do
allow_nil? false
constraints min_length: 2
description "JSON string containing the default map settings"
end
create_timestamp(:inserted_at)
update_timestamp(:updated_at)
end
relationships do
belongs_to :map, WandererApp.Api.Map do
primary_key? false
allow_nil? false
public? true
end
belongs_to :created_by, WandererApp.Api.Character do
allow_nil? true
public? true
end
belongs_to :updated_by, WandererApp.Api.Character do
allow_nil? true
public? true
end
end
identities do
identity :unique_map_settings, [:map_id]
end
defp validate_json_settings(changeset) do
case Ash.Changeset.get_attribute(changeset, :settings) do
nil ->
changeset
settings ->
case Jason.decode(settings) do
{:ok, _} ->
changeset
{:error, _} ->
Ash.Changeset.add_error(
changeset,
field: :settings,
message: "must be valid JSON"
)
end
end
end
end

View File

@@ -31,6 +31,8 @@ defmodule WandererApp.Api.MapSubscription do
end
code_interface do
define(:create, action: :create)
define(:by_id,
get_by: [:id],
action: :read
@@ -39,6 +41,15 @@ defmodule WandererApp.Api.MapSubscription do
define(:all_active, action: :all_active)
define(:all_by_map, action: :all_by_map)
define(:active_by_map, action: :active_by_map)
define(:destroy, action: :destroy)
define(:cancel, action: :cancel)
define(:expire, action: :expire)
define(:update_plan, action: :update_plan)
define(:update_characters_limit, action: :update_characters_limit)
define(:update_hubs_limit, action: :update_hubs_limit)
define(:update_active_till, action: :update_active_till)
define(:update_auto_renew, action: :update_auto_renew)
end
actions do
@@ -51,7 +62,7 @@ defmodule WandererApp.Api.MapSubscription do
:auto_renew?
]
defaults [:read]
defaults [:create, :read, :update, :destroy]
read :all_active do
prepare build(sort: [updated_at: :asc])

View File

@@ -31,6 +31,9 @@ defmodule WandererApp.Api.MapSystemComment do
end
code_interface do
define(:create, action: :create)
define(:destroy, action: :destroy)
define(:by_id,
get_by: [:id],
action: :read
@@ -46,7 +49,7 @@ defmodule WandererApp.Api.MapSystemComment do
:text
]
defaults [:read]
defaults [:read, :destroy]
create :create do
primary? true

View File

@@ -29,19 +29,7 @@ defmodule WandererApp.Api.MapTransaction do
:amount
]
defaults [:create]
read :read do
primary?(true)
pagination offset?: true,
default_limit: 25,
max_page_size: 100,
countable: true,
required?: false
prepare build(sort: [inserted_at: :desc])
end
defaults [:create, :read, :update, :destroy]
read :by_map do
argument(:map_id, :string, allow_nil?: false)

View File

@@ -40,6 +40,7 @@ defmodule WandererApp.Api.MapUserSettings do
action: :read
)
define(:update_hubs, action: :update_hubs)
define(:update_settings, action: :update_settings)
define(:update_following_character, action: :update_following_character)
define(:update_main_character, action: :update_main_character)
@@ -52,7 +53,7 @@ defmodule WandererApp.Api.MapUserSettings do
:settings
]
defaults [:create, :read]
defaults [:create, :read, :update, :destroy]
update :update_settings do
accept [:settings]

View File

@@ -145,7 +145,12 @@ defmodule WandererApp.Api.UserActivity do
:admin_action,
:config_change,
:bulk_operation,
:security_alert
:security_alert,
# Subscription events
:subscription_created,
:subscription_updated,
:subscription_deleted,
:subscription_unknown
]
)

View File

@@ -45,6 +45,9 @@ defmodule WandererApp.Application do
Supervisor.child_spec({Cachex, name: :tracked_characters},
id: :tracked_characters_cache_worker
),
Supervisor.child_spec({Cachex, name: :wanderer_app_cache},
id: :wanderer_app_cache_worker
),
{Registry, keys: :unique, name: WandererApp.MapRegistry},
{Registry, keys: :unique, name: WandererApp.Character.TrackerRegistry},
{PartitionSupervisor,
@@ -60,6 +63,14 @@ defmodule WandererApp.Application do
if Application.get_env(:wanderer_app, :environment) == :test do
[]
else
security_audit_children =
if Application.get_env(:wanderer_app, WandererApp.SecurityAudit, [])
|> Keyword.get(:async, false) do
[WandererApp.SecurityAudit.AsyncProcessor]
else
[]
end
[
WandererApp.Esi.InitClientsTask,
WandererApp.Scheduler,
@@ -68,7 +79,7 @@ defmodule WandererApp.Application do
{WandererApp.Character.TrackerPoolSupervisor, []},
WandererApp.Character.TrackerManager,
WandererApp.Map.Manager
]
] ++ security_audit_children
end
children =

View File

@@ -0,0 +1,150 @@
defmodule WandererApp.Audit.RequestContext do
@moduledoc """
Provides utilities for extracting request context information
for audit logging purposes.
"""
require Logger
@doc """
Extract the client's IP address from the connection.
Simply returns the remote_ip from the connection.
"""
def get_ip_address(conn) do
conn.remote_ip
|> :inet.ntoa()
|> to_string()
rescue
error ->
Logger.warning("Failed to get IP address: #{inspect(error)}",
error: error,
stacktrace: __STACKTRACE__
)
"unknown"
end
@doc """
Extract the user agent from the request headers.
"""
def get_user_agent(conn) do
get_header(conn, "user-agent") || "unknown"
end
@doc """
Extract or generate a session ID for the request.
"""
def get_session_id(conn) do
# Try to get from session
session_id = get_session(conn, :session_id)
# Fall back to request ID
session_id || get_request_id(conn)
end
@doc """
Extract or generate a request ID for correlation.
"""
def get_request_id(conn) do
# Try standard request ID headers
get_header(conn, "x-request-id") ||
get_header(conn, "x-correlation-id") ||
Logger.metadata()[:request_id] ||
generate_request_id()
end
@doc """
Build a complete request metadata map for audit logging.
"""
def build_request_metadata(conn) do
%{
ip_address: get_ip_address(conn),
user_agent: get_user_agent(conn),
session_id: get_session_id(conn),
request_id: get_request_id(conn),
request_path: conn.request_path,
method: conn.method |> to_string() |> String.upcase(),
host: conn.host,
port: conn.port,
scheme: conn.scheme |> to_string()
}
end
@doc """
Extract user information from the connection.
Returns a map with user_id and any additional user context.
"""
def get_user_info(conn) do
case conn.assigns[:current_user] do
%{id: user_id} = user ->
%{
user_id: user_id,
username: Map.get(user, :username),
email: Map.get(user, :email)
}
nil ->
%{user_id: nil}
end
end
@doc """
Build a minimal request details map for audit events.
This is used by existing audit calls that expect specific fields.
"""
def build_request_details(conn) do
metadata = build_request_metadata(conn)
%{
ip_address: metadata.ip_address,
user_agent: metadata.user_agent,
session_id: metadata.session_id,
request_path: metadata.request_path,
method: metadata.method
}
end
@doc """
Set request context in the process dictionary for async logging.
"""
def set_request_context(conn) do
context = %{
metadata: build_request_metadata(conn),
user_info: get_user_info(conn),
timestamp: DateTime.utc_now()
}
Process.put(:audit_request_context, context)
conn
end
@doc """
Get request context from the process dictionary.
"""
def get_request_context do
Process.get(:audit_request_context)
end
# Private functions
defp get_header(conn, header) do
case Plug.Conn.get_req_header(conn, header) do
[value | _] -> value
[] -> nil
end
end
defp get_session(conn, key) do
conn
|> Plug.Conn.get_session(key)
rescue
_ -> nil
end
defp generate_request_id do
"req_#{:crypto.strong_rand_bytes(16) |> Base.url_encode64(padding: false)}"
end
end

View File

@@ -28,7 +28,7 @@ defmodule WandererApp.Character do
Cachex.put(:character_cache, character_id, character)
{:ok, character}
_ ->
error ->
{:error, :not_found}
end
@@ -283,39 +283,44 @@ defmodule WandererApp.Character do
|> case do
{:ok, settings} when not is_nil(settings) ->
character
|> Map.put(:online, false)
|> Map.merge(settings)
|> Map.merge(%{
solar_system_id: settings.solar_system_id,
structure_id: settings.structure_id,
station_id: settings.station_id,
ship: settings.ship,
ship_name: settings.ship_name,
ship_item_id: settings.ship_item_id
})
_ ->
character
|> Map.put(:online, false)
|> Map.merge(@default_character_tracking_data)
end
|> Map.merge(%{tracking_paused: tracking_paused})
|> Map.merge(%{online: false, tracking_paused: tracking_paused})
end
defp prepare_search_results(result) do
{:ok, characters} =
_load_eve_info(Map.get(result, "character"), :get_character_info, &_map_character_info/1)
load_eve_info(Map.get(result, "character"), :get_character_info, &map_character_info/1)
{:ok, corporations} =
_load_eve_info(
load_eve_info(
Map.get(result, "corporation"),
:get_corporation_info,
&_map_corporation_info/1
&map_corporation_info/1
)
{:ok, alliances} =
_load_eve_info(Map.get(result, "alliance"), :get_alliance_info, &_map_alliance_info/1)
load_eve_info(Map.get(result, "alliance"), :get_alliance_info, &map_alliance_info/1)
[[characters | corporations] | alliances] |> List.flatten()
end
defp _load_eve_info(nil, _, _), do: {:ok, []}
defp load_eve_info(nil, _, _), do: {:ok, []}
defp _load_eve_info([], _, _), do: {:ok, []}
defp load_eve_info([], _, _), do: {:ok, []}
defp _load_eve_info(eve_ids, method, map_function),
defp load_eve_info(eve_ids, method, map_function),
do:
{:ok,
Enum.map(eve_ids, fn eve_id ->
@@ -331,7 +336,7 @@ defmodule WandererApp.Character do
end)
|> Enum.filter(fn result -> not is_nil(result) end)}
defp _map_alliance_info(info) do
defp map_alliance_info(info) do
%{
label: info["name"],
value: info["eve_id"] |> to_string(),
@@ -339,7 +344,7 @@ defmodule WandererApp.Character do
}
end
defp _map_character_info(info) do
defp map_character_info(info) do
%{
label: info["name"],
value: info["eve_id"] |> to_string(),
@@ -347,7 +352,7 @@ defmodule WandererApp.Character do
}
end
defp _map_corporation_info(info) do
defp map_corporation_info(info) do
%{
label: info["name"],
value: info["eve_id"] |> to_string(),

View File

@@ -34,7 +34,10 @@ defmodule WandererApp.ExternalEvents.EventFilter do
# ACL events
:acl_member_added,
:acl_member_removed,
:acl_member_updated
:acl_member_updated,
# Rally point events
:rally_point_added,
:rally_point_removed
]
@type event_type :: atom()

View File

@@ -1,6 +1,9 @@
defmodule WandererApp.Map.Audit do
@moduledoc """
Manager map subscription plans
This module now delegates to SecurityAudit for consistency.
It maintains backward compatibility while using the centralized audit system.
"""
require Ash.Query
@@ -13,19 +16,15 @@ defmodule WandererApp.Map.Audit do
@audit_expired_seconds @month_seconds * 3
def track_map_subscription_event(event_type, metadata) do
case event_type do
"subscription.created" ->
track_map_event(event_type, metadata)
mapped_type =
case event_type do
"subscription.created" -> :subscription_created
"subscription.updated" -> :subscription_updated
"subscription.deleted" -> :subscription_deleted
_ -> :subscription_unknown
end
"subscription.updated" ->
track_map_event(event_type, metadata)
"subscription.deleted" ->
track_map_event(event_type, metadata)
_ ->
{:ok, nil}
end
track_map_event(mapped_type, metadata)
end
def archive() do
@@ -40,191 +39,16 @@ defmodule WandererApp.Map.Audit do
end
def get_activity_query(map_id, period, activity) do
{from, to} = period |> get_period()
query =
WandererApp.Api.UserActivity
|> Ash.Query.filter(
and: [
[entity_id: map_id],
[inserted_at: [greater_than_or_equal: from]],
[inserted_at: [less_than_or_equal: to]]
]
)
query =
activity
|> case do
"all" ->
query
activity ->
query
|> Ash.Query.filter(event_type: activity)
end
query
|> Ash.Query.sort(inserted_at: :desc)
SecurityAudit.get_map_activity_query(map_id, period, activity)
end
@doc """
Get combined activity including security events for a map.
"""
def get_combined_activity_query(map_id, period, activity) do
{from, to} = period |> get_period()
# Get regular map activity
map_query = get_activity_query(map_id, period, activity)
# Get security events related to this map
security_query =
WandererApp.Api.UserActivity
|> Ash.Query.filter(entity_type: :security_event)
|> Ash.Query.filter(inserted_at: [greater_than_or_equal: from])
|> Ash.Query.filter(inserted_at: [less_than_or_equal: to])
|> Ash.Query.sort(inserted_at: :desc)
# Execute both queries and combine results
case {Ash.read(map_query), Ash.read(security_query)} do
{{:ok, map_activities}, {:ok, security_activities}} ->
# Combine and sort by timestamp
combined =
(map_activities ++ security_activities)
|> Enum.sort_by(& &1.inserted_at, {:desc, DateTime})
{:ok, combined}
{{:error, _} = error, _} ->
error
{_, {:error, _} = error} ->
error
end
def track_acl_event(event_type, metadata) do
SecurityAudit.track_acl_event(event_type, metadata)
end
@doc """
Get security events for a specific map.
"""
def get_security_events_for_map(map_id, period \\ "1D") do
{from, to} = period |> get_period()
# Get security events that might be related to this map
# This could include data access events, permission denied events, etc.
SecurityAudit.get_events_in_range(from, to)
|> Enum.filter(fn event ->
case Jason.decode(event.event_data || "{}") do
{:ok, data} ->
# Check if the event data contains references to this map
data["resource_id"] == map_id ||
data["entity_id"] == map_id ||
data["map_id"] == map_id
_ ->
false
end
end)
def track_map_event(event_type, metadata) do
SecurityAudit.track_map_event(event_type, metadata)
end
def track_acl_event(
event_type,
%{user_id: user_id, acl_id: acl_id} = metadata
)
when not is_nil(user_id) and not is_nil(acl_id),
do:
WandererApp.Api.UserActivity.new(%{
user_id: user_id,
entity_type: :access_list,
entity_id: acl_id,
event_type: event_type,
event_data: metadata |> Map.drop([:user_id, :acl_id]) |> Jason.encode!()
})
def track_acl_event(_event_type, _metadata), do: {:ok, nil}
def track_map_event(
event_type,
%{character_id: character_id, user_id: user_id, map_id: map_id} = metadata
)
when not is_nil(character_id) and not is_nil(user_id) and not is_nil(map_id) do
# Log regular map activity
result =
WandererApp.Api.UserActivity.new(%{
character_id: character_id,
user_id: user_id,
entity_type: :map,
entity_id: map_id,
event_type: event_type,
event_data: metadata |> Map.drop([:character_id, :user_id, :map_id]) |> Jason.encode!()
})
# Also log security-relevant map events
if security_relevant_event?(event_type) do
SecurityAudit.log_data_access(
"map",
map_id,
user_id,
event_type,
metadata
)
end
result
end
def track_map_event(_event_type, _metadata), do: {:ok, nil}
defp get_period("1H") do
now = DateTime.utc_now()
start_date = now |> DateTime.add(-1 * 3600, :second)
{start_date, now}
end
defp get_period("1D") do
now = DateTime.utc_now()
start_date = now |> DateTime.add(-24 * 3600, :second)
{start_date, now}
end
defp get_period("1W") do
now = DateTime.utc_now()
start_date = now |> DateTime.add(-24 * 3600 * 7, :second)
{start_date, now}
end
defp get_period("1M") do
now = DateTime.utc_now()
start_date = now |> DateTime.add(-24 * 3600 * 31, :second)
{start_date, now}
end
defp get_period("2M") do
now = DateTime.utc_now()
start_date = now |> DateTime.add(-24 * 3600 * 31 * 2, :second)
{start_date, now}
end
defp get_period("3M") do
now = DateTime.utc_now()
start_date = now |> DateTime.add(-24 * 3600 * 31 * 3, :second)
{start_date, now}
end
defp get_period(_), do: get_period("1H")
defp get_expired_at(), do: DateTime.utc_now() |> DateTime.add(-@audit_expired_seconds, :second)
defp security_relevant_event?(event_type) do
# Define which map events should also be logged as security events
event_type in [
:map_acl_added,
:map_acl_removed,
:map_acl_updated,
:map_acl_member_added,
:map_acl_member_removed,
:map_acl_member_updated,
:map_removed,
:character_added,
:character_removed
]
end
end

View File

@@ -0,0 +1,898 @@
defmodule WandererApp.SecurityAudit do
@moduledoc """
Comprehensive security audit logging system.
This module provides centralized logging for security-related events including:
- Authentication events (login, logout, failures)
- Authorization events (permission denied, privilege escalation)
- Data access events (sensitive queries, bulk exports)
- Configuration changes and admin actions
"""
require Logger
require Ash.Query
alias WandererApp.Api.UserActivity
@doc """
Log a security event with structured data.
## Examples
iex> WandererApp.SecurityAudit.log_event(:auth_success, user_id, %{
...> ip_address: "192.168.1.100",
...> user_agent: "Mozilla/5.0...",
...> auth_method: "session"
...> })
:ok
"""
def log_event(event_type, user_id, details \\ %{}) do
audit_entry = %{
event_type: event_type,
user_id: user_id,
timestamp: DateTime.utc_now(),
details: details,
severity: determine_severity(event_type),
session_id: details[:session_id],
ip_address: details[:ip_address],
user_agent: details[:user_agent]
}
# Store in database
store_audit_entry(audit_entry)
# Send to telemetry for monitoring
emit_telemetry_event(audit_entry)
# Log to application logs
log_to_application_log(audit_entry)
# Check for security alerts
check_security_alerts(audit_entry)
:ok
end
@doc """
Log authentication events.
"""
def log_auth_event(event_type, user_id, request_details) do
# Start with the basic required fields
details = %{
ip_address: request_details[:ip_address],
user_agent: request_details[:user_agent],
auth_method: request_details[:auth_method],
session_id: request_details[:session_id]
}
# Merge any additional fields from request_details
details = Map.merge(details, request_details)
log_event(event_type, user_id, details)
end
@doc """
Log data access events.
"""
def log_data_access(resource_type, resource_id, user_id, action, request_details \\ %{}) do
details = %{
resource_type: resource_type,
resource_id: resource_id,
action: action,
ip_address: request_details[:ip_address],
user_agent: request_details[:user_agent],
session_id: request_details[:session_id]
}
log_event(:data_access, user_id, details)
end
@doc """
Log permission denied events.
"""
def log_permission_denied(
resource_type,
resource_id,
user_id,
attempted_action,
request_details \\ %{}
) do
details = %{
resource_type: resource_type,
resource_id: resource_id,
attempted_action: attempted_action,
ip_address: request_details[:ip_address],
user_agent: request_details[:user_agent],
session_id: request_details[:session_id]
}
log_event(:permission_denied, user_id, details)
end
@doc """
Log admin actions.
"""
def log_admin_action(action, user_id, target_resource, request_details \\ %{}) do
details = %{
action: action,
target_resource: target_resource,
ip_address: request_details[:ip_address],
user_agent: request_details[:user_agent],
session_id: request_details[:session_id]
}
log_event(:admin_action, user_id, details)
end
@doc """
Log configuration changes.
"""
def log_config_change(config_key, old_value, new_value, user_id, request_details \\ %{}) do
details = %{
config_key: config_key,
old_value: sanitize_sensitive_data(old_value),
new_value: sanitize_sensitive_data(new_value),
ip_address: request_details[:ip_address],
user_agent: request_details[:user_agent],
session_id: request_details[:session_id]
}
log_event(:config_change, user_id, details)
end
@doc """
Log bulk data operations.
"""
def log_bulk_operation(operation_type, record_count, user_id, request_details \\ %{}) do
details = %{
operation_type: operation_type,
record_count: record_count,
ip_address: request_details[:ip_address],
user_agent: request_details[:user_agent],
session_id: request_details[:session_id]
}
log_event(:bulk_operation, user_id, details)
end
@doc """
Get audit events for a specific user.
"""
def get_user_audit_events(user_id, limit \\ 100) do
UserActivity
|> Ash.Query.filter(user_id: user_id)
|> Ash.Query.filter(entity_type: :security_event)
|> Ash.Query.sort(inserted_at: :desc)
|> Ash.Query.limit(limit)
|> Ash.read!()
end
@doc """
Get recent security events.
"""
def get_recent_events(limit \\ 50) do
UserActivity
|> Ash.Query.filter(entity_type: :security_event)
|> Ash.Query.sort(inserted_at: :desc)
|> Ash.Query.limit(limit)
|> Ash.read!()
end
@doc """
Get security events by type.
"""
def get_events_by_type(event_type, limit \\ 50) do
UserActivity
|> Ash.Query.filter(entity_type: :security_event)
|> Ash.Query.filter(event_type: event_type)
|> Ash.Query.sort(inserted_at: :desc)
|> Ash.Query.limit(limit)
|> Ash.read!()
end
@doc """
Get security events within a time range.
"""
def get_events_in_range(from_datetime, to_datetime, limit \\ 100) do
UserActivity
|> Ash.Query.filter(entity_type: :security_event)
|> Ash.Query.filter(inserted_at: [greater_than_or_equal: from_datetime])
|> Ash.Query.filter(inserted_at: [less_than_or_equal: to_datetime])
|> Ash.Query.sort(inserted_at: :desc)
|> Ash.Query.limit(limit)
|> Ash.read!()
end
@doc """
Track map-related events (compatibility with Map.Audit).
"""
def track_map_event(
event_type,
%{character_id: character_id, user_id: user_id, map_id: map_id} = metadata
)
when not is_nil(character_id) and not is_nil(user_id) and not is_nil(map_id) do
# Sanitize and prepare metadata
sanitized_metadata =
metadata
|> Map.drop([:character_id, :user_id, :map_id])
|> sanitize_metadata()
attrs = %{
character_id: character_id,
user_id: user_id,
entity_type: :map,
entity_id: map_id,
event_type: normalize_event_type(event_type),
event_data: Jason.encode!(sanitized_metadata)
}
case UserActivity.new(attrs) do
{:ok, activity} ->
{:ok, activity}
{:error, error} ->
Logger.error("Failed to track map event",
error: inspect(error),
event_type: event_type,
map_id: map_id
)
{:error, error}
end
end
def track_map_event(_event_type, _metadata), do: {:ok, nil}
@doc """
Track ACL-related events (compatibility with Map.Audit).
"""
def track_acl_event(
event_type,
%{user_id: user_id, acl_id: acl_id} = metadata
)
when not is_nil(user_id) and not is_nil(acl_id) do
# Sanitize and prepare metadata
sanitized_metadata =
metadata
|> Map.drop([:user_id, :acl_id])
|> sanitize_metadata()
attrs = %{
user_id: user_id,
entity_type: :access_list,
entity_id: acl_id,
event_type: normalize_event_type(event_type),
event_data: Jason.encode!(sanitized_metadata)
}
case UserActivity.new(attrs) do
{:ok, activity} ->
{:ok, activity}
{:error, error} ->
Logger.error("Failed to track ACL event",
error: inspect(error),
event_type: event_type,
acl_id: acl_id
)
{:error, error}
end
end
def track_acl_event(_event_type, _metadata), do: {:ok, nil}
@doc """
Get activity query for maps (compatibility with Map.Audit).
"""
def get_map_activity_query(map_id, period, activity \\ "all") do
{from, to} = get_period(period)
query =
UserActivity
|> Ash.Query.filter(
and: [
[entity_id: map_id],
[inserted_at: [greater_than_or_equal: from]],
[inserted_at: [less_than_or_equal: to]]
]
)
query =
case activity do
"all" ->
query
activity ->
query
|> Ash.Query.filter(event_type: normalize_event_type(activity))
end
query
|> Ash.Query.sort(inserted_at: :desc)
end
defp get_period("1H") do
now = DateTime.utc_now()
start_date = DateTime.add(now, -1 * 3600, :second)
{start_date, now}
end
defp get_period("1D") do
now = DateTime.utc_now()
start_date = DateTime.add(now, -24 * 3600, :second)
{start_date, now}
end
defp get_period("1W") do
now = DateTime.utc_now()
start_date = DateTime.add(now, -24 * 3600 * 7, :second)
{start_date, now}
end
defp get_period("1M") do
now = DateTime.utc_now()
start_date = DateTime.add(now, -24 * 3600 * 31, :second)
{start_date, now}
end
defp get_period("2M") do
now = DateTime.utc_now()
start_date = DateTime.add(now, -24 * 3600 * 31 * 2, :second)
{start_date, now}
end
defp get_period("3M") do
now = DateTime.utc_now()
start_date = DateTime.add(now, -24 * 3600 * 31 * 3, :second)
{start_date, now}
end
defp get_period(_), do: get_period("1H")
@doc """
Check for suspicious patterns in user activity.
"""
def analyze_user_behavior(user_id, time_window \\ 3600) do
now = DateTime.utc_now()
from_time = DateTime.add(now, -time_window, :second)
# Get recent activities
activities =
UserActivity
|> Ash.Query.filter(user_id: user_id)
|> Ash.Query.filter(entity_type: :security_event)
|> Ash.Query.filter(inserted_at: [greater_than_or_equal: from_time])
|> Ash.Query.sort(inserted_at: :desc)
|> Ash.read!()
# Analyze patterns
patterns = analyze_patterns(activities)
risk_score = calculate_risk_score(patterns)
recommendations = generate_recommendations(patterns, risk_score)
%{
risk_score: risk_score,
suspicious_patterns: patterns,
recommendations: recommendations,
activities_analyzed: length(activities),
time_window_seconds: time_window
}
end
defp analyze_patterns(activities) do
patterns = []
# Count by event type
event_counts = Enum.frequencies_by(activities, & &1.event_type)
# Check for multiple auth failures
auth_failures = Map.get(event_counts, :auth_failure, 0)
patterns =
if auth_failures >= 3 do
[{:multiple_auth_failures, auth_failures} | patterns]
else
patterns
end
# Check for permission denied spikes
permission_denied = Map.get(event_counts, :permission_denied, 0)
patterns =
if permission_denied >= 5 do
[{:excessive_permission_denials, permission_denied} | patterns]
else
patterns
end
# Check for rapid activity (more than 100 events in time window)
patterns =
if length(activities) > 100 do
[{:high_activity_volume, length(activities)} | patterns]
else
patterns
end
# Check for geographic anomalies by analyzing unique IPs
unique_ips =
activities
|> Enum.map(fn activity ->
case Jason.decode(activity.event_data || "{}") do
{:ok, data} -> data["ip_address"]
_ -> nil
end
end)
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
|> length()
patterns =
if unique_ips > 5 do
[{:multiple_ip_addresses, unique_ips} | patterns]
else
patterns
end
patterns
end
defp calculate_risk_score(patterns) do
score =
Enum.reduce(patterns, 0, fn
{:multiple_auth_failures, count}, acc -> acc + count * 2
{:excessive_permission_denials, count}, acc -> acc + count * 1.5
{:high_activity_volume, _}, acc -> acc + 5
{:multiple_ip_addresses, count}, acc -> acc + count * 3
_, acc -> acc
end)
cond do
score >= 20 -> :critical
score >= 10 -> :high
score >= 5 -> :medium
true -> :low
end
end
defp generate_recommendations(patterns, risk_score) do
base_recommendations =
case risk_score do
:critical -> ["Immediate review required", "Consider blocking user temporarily"]
:high -> ["Monitor user activity closely", "Review recent actions"]
:medium -> ["Keep user under observation"]
:low -> []
end
pattern_recommendations =
Enum.flat_map(patterns, fn
{:multiple_auth_failures, _} ->
["Reset user password", "Enable MFA"]
{:excessive_permission_denials, _} ->
["Review user permissions", "Check for compromised account"]
{:high_activity_volume, _} ->
["Check for automated activity", "Review API usage"]
{:multiple_ip_addresses, _} ->
["Verify user location changes", "Check for account sharing"]
_ ->
[]
end)
Enum.uniq(base_recommendations ++ pattern_recommendations)
end
# Private functions
defp store_audit_entry(audit_entry) do
# Handle async processing if enabled
if async_enabled?() do
WandererApp.SecurityAudit.AsyncProcessor.log_event(audit_entry)
else
do_store_audit_entry(audit_entry)
end
end
@doc false
def do_store_audit_entry(audit_entry) do
# Ensure event_type is properly formatted
event_type = normalize_event_type(audit_entry.event_type)
attrs = %{
user_id: audit_entry.user_id,
character_id: nil,
entity_id: hash_identifier(audit_entry.session_id),
entity_type: :security_event,
event_type: event_type,
event_data: encode_event_data(audit_entry)
}
case UserActivity.new(attrs) do
{:ok, _activity} ->
:ok
{:error, error} ->
Logger.error("Failed to store security audit entry",
error: inspect(error),
event_type: event_type,
user_id: audit_entry.user_id
)
# Emit telemetry for monitoring
:telemetry.execute(
[:wanderer_app, :security_audit, :storage_error],
%{count: 1},
%{event_type: event_type, error: error}
)
# Don't block the request, but track the failure
{:error, :storage_failed}
end
end
defp hash_identifier(identifier) when is_binary(identifier) do
secret_salt =
Application.get_env(:wanderer_app, :secret_key_base) ||
raise "SECRET_KEY_BASE not configured"
:crypto.hash(:sha256, secret_salt <> identifier)
|> Base.encode16(case: :lower)
end
defp hash_identifier(nil), do: generate_entity_id()
defp normalize_event_type(event_type) when is_atom(event_type), do: event_type
defp normalize_event_type(event_type) when is_binary(event_type) do
try do
String.to_existing_atom(event_type)
rescue
ArgumentError -> :security_alert
end
end
defp normalize_event_type(_), do: :security_alert
defp encode_event_data(audit_entry) do
sanitized_details = sanitize_for_json(audit_entry.details)
data =
Map.merge(sanitized_details, %{
timestamp: convert_datetime(audit_entry.timestamp),
severity: to_string(audit_entry.severity),
ip_address: audit_entry.ip_address,
user_agent: audit_entry.user_agent
})
case Jason.encode(data) do
{:ok, json} -> json
{:error, _} -> Jason.encode!(%{error: "Failed to encode audit data"})
end
end
defp sanitize_for_json(data) when is_map(data) do
data
|> Enum.reduce(%{}, fn {key, value}, acc ->
sanitized_key = to_string(key)
# Skip sensitive fields
if sanitized_key in ~w(password secret token private_key api_key) do
acc
else
Map.put(acc, sanitized_key, sanitize_value(value))
end
end)
end
defp sanitize_for_json(data), do: sanitize_value(data)
defp sanitize_metadata(metadata) do
# List of sensitive keys to remove from metadata
sensitive_keys = [:password, :token, :secret, :api_key, :private_key, :auth_token]
metadata
|> Map.drop(sensitive_keys)
|> Enum.map(fn {k, v} ->
# Ensure keys are strings or atoms
key = if is_binary(k), do: k, else: to_string(k)
{key, sanitize_value(v)}
end)
|> Enum.into(%{})
end
defp sanitize_value(%DateTime{} = dt), do: DateTime.to_iso8601(dt)
defp sanitize_value(%NaiveDateTime{} = dt), do: NaiveDateTime.to_iso8601(dt)
defp sanitize_value(%Date{} = date), do: Date.to_iso8601(date)
defp sanitize_value(%Time{} = time), do: Time.to_iso8601(time)
defp sanitize_value(atom) when is_atom(atom) and not is_nil(atom) and not is_boolean(atom),
do: to_string(atom)
defp sanitize_value(list) when is_list(list), do: Enum.map(list, &sanitize_value/1)
defp sanitize_value(map) when is_map(map), do: sanitize_for_json(map)
defp sanitize_value(value), do: value
defp convert_datetime(%DateTime{} = dt), do: DateTime.to_iso8601(dt)
defp convert_datetime(%NaiveDateTime{} = dt), do: NaiveDateTime.to_iso8601(dt)
defp convert_datetime(value), do: value
defp generate_entity_id do
"audit_#{DateTime.utc_now() |> DateTime.to_unix(:microsecond)}_#{System.unique_integer([:positive])}"
end
defp async_enabled? do
Application.get_env(:wanderer_app, __MODULE__, [])
|> Keyword.get(:async, false)
end
defp emit_telemetry_event(audit_entry) do
:telemetry.execute(
[:wanderer_app, :security_audit],
%{count: 1},
%{
event_type: audit_entry.event_type,
severity: audit_entry.severity,
user_id: audit_entry.user_id
}
)
end
defp log_to_application_log(audit_entry) do
log_level =
case audit_entry.severity do
:critical -> :error
:high -> :warning
:medium -> :info
:low -> :debug
end
Logger.log(log_level, "Security audit: #{audit_entry.event_type}",
user_id: audit_entry.user_id,
timestamp: audit_entry.timestamp,
details: audit_entry.details
)
end
defp check_security_alerts(audit_entry) do
case audit_entry.event_type do
:auth_failure ->
check_failed_login_attempts(audit_entry)
:permission_denied ->
check_privilege_escalation_attempts(audit_entry)
:bulk_operation ->
check_bulk_data_access(audit_entry)
:security_alert ->
# Already a security alert, don't double-check
:ok
_ ->
:ok
end
end
defp check_failed_login_attempts(audit_entry) do
config = threat_detection_config()
if config[:enabled] do
ip_address = audit_entry.ip_address || "unknown"
cache_key = "auth_failures:#{ip_address}"
window = config[:window_seconds] || 300
max_attempts = config[:max_failed_attempts] || 5
# Increment counter in Cachex with TTL
count =
case Cachex.incr(:wanderer_app_cache, cache_key) do
{:ok, count} ->
# Set TTL on first increment
if count == 1 do
Cachex.expire(:wanderer_app_cache, cache_key, :timer.seconds(window))
end
count
{:error, :no_key} ->
# Key doesn't exist, initialize it with TTL
case Cachex.put(:wanderer_app_cache, cache_key, 1, ttl: :timer.seconds(window)) do
{:ok, _} ->
1
{:error, error} ->
Logger.error("Failed to initialize auth failure counter",
error: inspect(error),
cache_key: cache_key
)
1
end
{:error, error} ->
# Other errors - log and return safe default
Logger.error("Failed to increment auth failure counter",
error: inspect(error),
cache_key: cache_key
)
1
end
if count >= max_attempts do
Logger.warning("Potential brute force attack detected",
ip_address: ip_address,
attempts: count,
user_id: audit_entry.user_id
)
# Emit security alert
:telemetry.execute(
[:wanderer_app, :security_audit, :threat_detected],
%{count: 1},
%{threat_type: :brute_force, ip_address: ip_address}
)
# Log a security alert event
log_event(:security_alert, audit_entry.user_id, %{
threat_type: "brute_force",
ip_address: ip_address,
failed_attempts: count,
window_seconds: window
})
end
end
:ok
end
defp check_privilege_escalation_attempts(audit_entry) do
config = threat_detection_config()
if config[:enabled] && audit_entry.user_id do
cache_key = "privilege_escalation:#{audit_entry.user_id}"
window = config[:window_seconds] || 300
max_denials = config[:max_permission_denials] || 10
count =
case Cachex.incr(:wanderer_app_cache, cache_key) do
{:ok, count} ->
if count == 1 do
Cachex.expire(:wanderer_app_cache, cache_key, :timer.seconds(window))
end
count
{:error, :no_key} ->
# Key doesn't exist, initialize it with TTL
case Cachex.put(:wanderer_app_cache, cache_key, 1, ttl: :timer.seconds(window)) do
{:ok, _} ->
1
{:error, error} ->
Logger.error("Failed to initialize privilege escalation counter",
error: inspect(error),
cache_key: cache_key
)
1
end
{:error, error} ->
# Other errors - log and return safe default
Logger.error("Failed to increment privilege escalation counter",
error: inspect(error),
cache_key: cache_key
)
1
end
if count >= max_denials do
Logger.warning("Potential privilege escalation attempt detected",
user_id: audit_entry.user_id,
denials: count,
resource_type: audit_entry.details[:resource_type]
)
:telemetry.execute(
[:wanderer_app, :security_audit, :threat_detected],
%{count: 1},
%{threat_type: :privilege_escalation, user_id: audit_entry.user_id}
)
end
end
:ok
end
defp check_bulk_data_access(audit_entry) do
config = threat_detection_config()
if config[:enabled] && audit_entry.user_id do
record_count = audit_entry.details[:record_count] || 0
threshold = config[:bulk_operation_threshold] || 10000
if record_count > threshold do
Logger.warning("Large bulk operation detected",
user_id: audit_entry.user_id,
operation_type: audit_entry.details[:operation_type],
record_count: record_count
)
:telemetry.execute(
[:wanderer_app, :security_audit, :bulk_operation],
%{record_count: record_count},
%{user_id: audit_entry.user_id, operation_type: audit_entry.details[:operation_type]}
)
end
end
:ok
end
defp threat_detection_config do
Application.get_env(:wanderer_app, __MODULE__, [])
|> Keyword.get(:threat_detection, %{})
end
defp determine_severity(event_type) do
case event_type do
:auth_failure -> :medium
:permission_denied -> :high
:privilege_escalation -> :critical
:config_change -> :high
:admin_action -> :medium
:bulk_operation -> :medium
:data_access -> :low
:auth_success -> :low
_ -> :medium
end
end
defp sanitize_sensitive_data(value) when is_binary(value) do
# Patterns to detect sensitive data
sensitive_patterns = [
~r/password/i,
~r/token/i,
~r/secret/i,
~r/api[_-]?key/i,
~r/private[_-]?key/i,
~r/access[_-]?key/i,
~r/auth/i,
~r/bearer\s+[a-zA-Z0-9\-_]+/i,
# Long hex strings (potential tokens)
~r/[a-f0-9]{32,}/i
]
# Check if value contains sensitive patterns
is_sensitive = Enum.any?(sensitive_patterns, &Regex.match?(&1, value))
cond do
is_sensitive -> "[REDACTED]"
String.length(value) > 200 -> String.slice(value, 0, 200) <> "..."
true -> value
end
end
defp sanitize_sensitive_data(value) when is_map(value) do
# Recursively sanitize map values
Map.new(value, fn {k, v} ->
key_str = to_string(k)
if Regex.match?(~r/password|token|secret|key|auth/i, key_str) do
{k, "[REDACTED]"}
else
{k, sanitize_sensitive_data(v)}
end
end)
end
defp sanitize_sensitive_data(value) when is_list(value) do
Enum.map(value, &sanitize_sensitive_data/1)
end
defp sanitize_sensitive_data(value), do: value
end

View File

@@ -0,0 +1,246 @@
defmodule WandererApp.SecurityAudit.AsyncProcessor do
@moduledoc """
GenServer for asynchronous batch processing of security audit events.
This server buffers audit events in memory and periodically flushes them
to the database in batches for improved performance.
"""
use GenServer
require Logger
alias WandererApp.SecurityAudit
@default_batch_size 100
# 5 seconds
@default_flush_interval 5_000
@max_buffer_size 1_000
defstruct [
:batch_size,
:flush_interval,
:buffer,
:timer_ref,
:stats
]
# Client API
@doc """
Start the async processor.
"""
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc """
Log an event asynchronously.
"""
def log_event(audit_entry) do
GenServer.cast(__MODULE__, {:log_event, audit_entry})
end
@doc """
Force a flush of the buffer.
"""
def flush do
GenServer.call(__MODULE__, :flush)
end
@doc """
Get current processor statistics.
"""
def get_stats do
GenServer.call(__MODULE__, :get_stats)
end
# Server callbacks
@impl true
def init(opts) do
config = Application.get_env(:wanderer_app, WandererApp.SecurityAudit, [])
batch_size = Keyword.get(opts, :batch_size, config[:batch_size] || @default_batch_size)
flush_interval =
Keyword.get(opts, :flush_interval, config[:flush_interval] || @default_flush_interval)
state = %__MODULE__{
batch_size: batch_size,
flush_interval: flush_interval,
buffer: [],
timer_ref: nil,
stats: %{
events_processed: 0,
batches_flushed: 0,
errors: 0,
last_flush: nil
}
}
# Schedule first flush
state = schedule_flush(state)
{:ok, state}
end
@impl true
def handle_cast({:log_event, audit_entry}, state) do
# Add to buffer
buffer = [audit_entry | state.buffer]
# Update stats
stats = Map.update!(state.stats, :events_processed, &(&1 + 1))
# Check if we need to flush
cond do
length(buffer) >= state.batch_size ->
# Flush immediately if batch size reached
{:noreply, do_flush(%{state | buffer: buffer, stats: stats})}
length(buffer) >= @max_buffer_size ->
# Force flush if max buffer size reached
Logger.warning("Security audit buffer overflow, forcing flush",
buffer_size: length(buffer),
max_size: @max_buffer_size
)
{:noreply, do_flush(%{state | buffer: buffer, stats: stats})}
true ->
# Just add to buffer
{:noreply, %{state | buffer: buffer, stats: stats}}
end
end
@impl true
def handle_call(:flush, _from, state) do
new_state = do_flush(state)
{:reply, :ok, new_state}
end
@impl true
def handle_call(:get_stats, _from, state) do
stats = Map.put(state.stats, :current_buffer_size, length(state.buffer))
{:reply, stats, state}
end
@impl true
def handle_info(:flush_timer, state) do
state =
if length(state.buffer) > 0 do
do_flush(state)
else
state
end
# Schedule next flush
state = schedule_flush(state)
{:noreply, state}
end
@impl true
def terminate(_reason, state) do
# Flush any remaining events on shutdown
if length(state.buffer) > 0 do
do_flush(state)
end
:ok
end
# Private functions
defp schedule_flush(state) do
# Cancel existing timer if any
if state.timer_ref do
Process.cancel_timer(state.timer_ref)
end
# Schedule new timer
timer_ref = Process.send_after(self(), :flush_timer, state.flush_interval)
%{state | timer_ref: timer_ref}
end
defp do_flush(state) when length(state.buffer) == 0 do
state
end
defp do_flush(state) do
# Take events to flush (reverse to maintain order)
events = Enum.reverse(state.buffer)
# Attempt to store events
case bulk_store_events(events) do
{:ok, count} ->
Logger.debug("Flushed #{count} security audit events")
# Update stats
stats =
state.stats
|> Map.update!(:batches_flushed, &(&1 + 1))
|> Map.put(:last_flush, DateTime.utc_now())
# Clear buffer
%{state | buffer: [], stats: stats}
{:error, reason} ->
Logger.error("Failed to flush security audit events",
reason: inspect(reason),
event_count: length(events)
)
# Update error stats
stats = Map.update!(state.stats, :errors, &(&1 + 1))
# Implement backoff - keep events in buffer but don't grow indefinitely
buffer =
if length(state.buffer) > @max_buffer_size do
Logger.warning("Dropping oldest audit events due to repeated flush failures")
Enum.take(state.buffer, @max_buffer_size)
else
state.buffer
end
%{state | buffer: buffer, stats: stats}
end
end
defp bulk_store_events(events) do
# Process events in smaller chunks if necessary
events
# Ash bulk operations work better with smaller chunks
|> Enum.chunk_every(50)
|> Enum.reduce_while({:ok, 0}, fn chunk, {:ok, count} ->
case store_event_chunk(chunk) do
{:ok, chunk_count} ->
{:cont, {:ok, count + chunk_count}}
{:error, _} = error ->
{:halt, error}
end
end)
end
defp store_event_chunk(events) do
# Transform events to Ash attributes
records =
Enum.map(events, fn event ->
SecurityAudit.do_store_audit_entry(event)
end)
# Count successful stores
successful =
Enum.count(records, fn
:ok -> true
_ -> false
end)
{:ok, successful}
rescue
error ->
{:error, error}
end
end

View File

@@ -65,8 +65,7 @@ defmodule WandererAppWeb.CoreComponents do
phx-mounted={@show && show_modal(@id)}
phx-remove={hide_modal(@id)}
data-cancel={JS.exec(@on_cancel, "phx-remove")}
class="relative z-50 hidden overflow-visible"
class=""
class="relative z-[1000] hidden overflow-visible"
>
<div id={"#{@id}-bg"} class="overflow-visible p-dialog-resizable" aria-hidden="true" />
<div

View File

@@ -2,14 +2,20 @@ defmodule WandererAppWeb.Plugs.CheckJsonApiAuth do
@moduledoc """
Plug for authenticating JSON:API v1 endpoints.
Supports both session-based authentication (for web clients) and
Supports both session-based authentication (for web clients) and
Bearer token authentication (for API clients).
Currently, Bearer token authentication only supports map API keys.
When a valid map API key is provided, the map owner is set as the
authenticated user and the map is made available in conn.assigns.
"""
import Plug.Conn
alias WandererApp.Api.User
alias WandererApp.SecurityAudit
alias WandererApp.Audit.RequestContext
def init(opts), do: opts
@@ -57,7 +63,8 @@ defmodule WandererAppWeb.Plugs.CheckJsonApiAuth do
|> assign(:current_user, user)
|> assign(:current_user_role, get_user_role(user))
{:error, reason} ->
{:error, reason} when is_binary(reason) ->
# Legacy error handling for simple string errors
end_time = System.monotonic_time(:millisecond)
duration = end_time - start_time
@@ -82,6 +89,36 @@ defmodule WandererAppWeb.Plugs.CheckJsonApiAuth do
|> put_resp_content_type("application/json")
|> send_resp(401, Jason.encode!(%{error: reason}))
|> halt()
{:error, external_message, internal_reason} ->
# New error handling with separate internal and external messages
end_time = System.monotonic_time(:millisecond)
duration = end_time - start_time
# Log failed authentication with detailed internal reason
request_details = extract_request_details(conn)
SecurityAudit.log_auth_event(
:auth_failure,
nil,
Map.merge(request_details, %{
failure_reason: internal_reason,
external_message: external_message
})
)
# Emit failed authentication event
:telemetry.execute(
[:wanderer_app, :json_api, :auth],
%{count: 1, duration: duration},
%{auth_type: get_auth_type(conn), result: "failure"}
)
conn
|> put_status(:unauthorized)
|> put_resp_content_type("application/json")
|> send_resp(401, Jason.encode!(%{error: external_message}))
|> halt()
end
end
@@ -103,8 +140,6 @@ defmodule WandererAppWeb.Plugs.CheckJsonApiAuth do
defp authenticate_bearer_token(conn) do
case get_req_header(conn, "authorization") do
["Bearer " <> token] ->
# For now, use a simple approach - validate token format
# In the future, this could be extended to support JWT or other token types
validate_api_token(token)
_ ->
@@ -113,48 +148,23 @@ defmodule WandererAppWeb.Plugs.CheckJsonApiAuth do
end
defp validate_api_token(token) do
# For test environment, accept test API keys
if Application.get_env(:wanderer_app, :env) == :test and
(String.starts_with?(token, "test_") or String.starts_with?(token, "test_api_key_")) do
# For test tokens, look up the actual map by API key
case find_map_by_api_key(token) do
{:ok, map} when not is_nil(map) ->
# Use the actual map owner as the user
user = %User{
id: map.owner_id || Ecto.UUID.generate(),
name: "Test User",
hash: "test_hash_#{System.unique_integer([:positive])}"
}
# Look up the map by its public API key
case find_map_by_api_key(token) do
{:ok, map} when not is_nil(map) ->
# Get the actual owner of the map
case User.by_id(map.owner_id, load: :characters) do
{:ok, user} ->
# Return the map owner as the authenticated user
{:ok, user, map}
{:ok, user, map}
{:error, _} ->
# Return generic error with specific reason for internal logging
{:error, "Authentication failed", :map_owner_not_found}
end
_ ->
# If no map found with this test token, create a test user without a map
user = %User{
id: Ecto.UUID.generate(),
name: "Test User",
hash: "test_hash_#{System.unique_integer([:positive])}"
}
{:ok, user}
end
else
# Look up the map by its public API key
case find_map_by_api_key(token) do
{:ok, map} when not is_nil(map) ->
# Create a user representing API access for this map
# In a real implementation, you might want to track the actual user who created the API key
user = %User{
id: map.owner_id || Ecto.UUID.generate(),
name: "API User for #{map.name}",
hash: "api_hash_#{map.id}"
}
{:ok, user, map}
_ ->
{:error, "Invalid API key"}
end
_ ->
# Return generic error with specific reason for internal logging
{:error, "Authentication failed", :invalid_api_key}
end
end
@@ -192,50 +202,8 @@ defmodule WandererAppWeb.Plugs.CheckJsonApiAuth do
end
defp extract_request_details(conn) do
%{
ip_address: get_peer_ip(conn),
user_agent: get_user_agent(conn),
auth_method: get_auth_type(conn),
session_id: get_session_id(conn),
request_path: conn.request_path,
method: conn.method
}
end
defp get_peer_ip(conn) do
case get_req_header(conn, "x-forwarded-for") do
[forwarded_for] ->
forwarded_for
|> String.split(",")
|> List.first()
|> String.trim()
[] ->
case get_req_header(conn, "x-real-ip") do
[real_ip] ->
real_ip
[] ->
case conn.remote_ip do
{a, b, c, d} -> "#{a}.#{b}.#{c}.#{d}"
_ -> "unknown"
end
end
end
end
defp get_user_agent(conn) do
case get_req_header(conn, "user-agent") do
[user_agent] -> user_agent
[] -> "unknown"
end
end
defp get_session_id(conn) do
case get_session(conn, :session_id) do
nil -> conn.assigns[:request_id] || "unknown"
session_id -> session_id
end
RequestContext.build_request_details(conn)
|> Map.put(:auth_method, get_auth_type(conn))
end
defp maybe_assign_map(conn, nil), do: conn

View File

@@ -227,6 +227,61 @@ defmodule WandererAppWeb.MapCoreEventHandler do
})}
end
def handle_ui_event(
"save_default_settings",
%{"settings" => settings},
%{
assigns: %{
map_id: map_id,
current_user: current_user,
user_permissions: user_permissions
}
} = socket
) do
# Check if user is map admin
if user_permissions.admin_map do
case save_default_settings(map_id, settings, current_user) do
{:ok, _default_settings} ->
{:reply, %{success: true}, socket}
{:error, reason} ->
Logger.error("Failed to save default settings: #{inspect(reason)}")
error_message =
case reason do
%Ash.Error.Invalid{} = error ->
errors = Ash.Error.to_error_class(error)
"Validation error: #{inspect(errors)}"
:no_character ->
"No character found for user"
_ ->
"Failed to save default settings: #{inspect(reason)}"
end
{:reply, %{success: false, error: error_message},
socket |> put_flash(:error, error_message)}
end
else
{:reply, %{success: false, error: "unauthorized"}, socket}
end
end
def handle_ui_event(
"get_default_settings",
_,
%{assigns: %{map_id: map_id}} = socket
) do
case WandererApp.Api.MapDefaultSettings.get_by_map_id(%{map_id: map_id}) do
{:ok, [default_settings | _]} ->
{:reply, %{default_settings: default_settings.settings}, socket}
_ ->
{:reply, %{default_settings: nil}, socket}
end
end
def handle_ui_event("noop", _, socket), do: {:noreply, socket}
def handle_ui_event(
@@ -262,6 +317,38 @@ defmodule WandererAppWeb.MapCoreEventHandler do
{:noreply, socket}
end
defp save_default_settings(map_id, settings, current_user) do
# Find the character to use as actor
actor =
case current_user.characters do
[character | _] -> character
_ -> nil
end
if actor do
case WandererApp.Api.MapDefaultSettings.get_by_map_id(%{map_id: map_id}) do
{:ok, [existing | _]} ->
result =
WandererApp.Api.MapDefaultSettings.update(existing, %{settings: settings},
actor: actor
)
result
error ->
result =
WandererApp.Api.MapDefaultSettings.create(%{map_id: map_id, settings: settings},
actor: actor
)
result
end
else
Logger.error("No character found for user #{current_user.id}")
{:error, :no_character}
end
end
defp maybe_start_map(map_id) do
{:ok, map_server_started} = WandererApp.Cache.lookup("map_#{map_id}:started", false)

View File

@@ -51,146 +51,148 @@
<.icon name="hero-user-group-solid" class="w-6 h-6" />
</button>
</.link>
</div>
<.modal
:if={@show_topup}
title="Map Subscription Info"
class="!min-w-[700px]"
id="map-topup-modal"
show
on_cancel={JS.navigate(~p"/#{@map_slug}")}
>
<div class="flex flex-col gap-3">
<div class="flex flex-col gap-2">
<div class="verticalTabsContainer">
<div class="p-tabview p-component" data-pc-name="tabview" data-pc-section="root">
<div class="p-tabview-nav-container" data-pc-section="navcontainer">
<div class="p-tabview-nav-content" data-pc-section="navcontent">
<ul class="p-tabview-nav" role="tablist" data-pc-section="nav">
<li
class={[
"p-unselectable-text",
classes(
"p-tabview-selected p-highlight": @active_subscription_tab == "balance"
)
]}
role="presentation"
data-pc-name=""
data-pc-section="header"
>
<a
role="tab"
class="p-tabview-nav-link flex p-[10px]"
tabindex="-1"
aria-controls="pr_id_332_content"
aria-selected="false"
aria-disabled="false"
data-pc-section="headeraction"
phx-click="change_subscription_tab"
phx-value-tab="balance"
<.modal
:if={@show_topup}
title="Map Subscription Info"
class="!min-w-[700px] !z-[10000]"
id="map-topup-modal"
show
on_cancel={JS.navigate(~p"/#{@map_slug}")}
>
<div class="flex flex-col gap-3">
<div class="flex flex-col gap-2">
<div class="verticalTabsContainer">
<div class="p-tabview p-component" data-pc-name="tabview" data-pc-section="root">
<div class="p-tabview-nav-container" data-pc-section="navcontainer">
<div class="p-tabview-nav-content" data-pc-section="navcontent">
<ul class="p-tabview-nav" role="tablist" data-pc-section="nav">
<li
class={[
"p-unselectable-text",
classes(
"p-tabview-selected p-highlight": @active_subscription_tab == "balance"
)
]}
role="presentation"
data-pc-name=""
data-pc-section="header"
>
<span class="p-tabview-title" data-pc-section="headertitle">
<.icon name="hero-banknotes-solid" class="w-4 h-4" />&nbsp;Balance
</span>
</a>
</li>
<li
:if={@map_subscriptions_enabled?}
class={[
"p-unselectable-text",
classes(
"p-tabview-selected p-highlight": @active_subscription_tab == "subscription"
)
]}
role="presentation"
data-pc-name=""
data-pc-section="header"
>
<a
role="tab"
class="p-tabview-nav-link flex p-[10px]"
tabindex="-1"
aria-controls="pr_id_334_content"
aria-selected="false"
aria-disabled="false"
data-pc-section="headeraction"
phx-click="change_subscription_tab"
phx-value-tab="subscription"
<a
role="tab"
class="p-tabview-nav-link flex p-[10px]"
tabindex="-1"
aria-controls="pr_id_332_content"
aria-selected="false"
aria-disabled="false"
data-pc-section="headeraction"
phx-click="change_subscription_tab"
phx-value-tab="balance"
>
<span class="p-tabview-title" data-pc-section="headertitle">
<.icon name="hero-banknotes-solid" class="w-4 h-4" />&nbsp;Balance
</span>
</a>
</li>
<li
:if={@map_subscriptions_enabled?}
class={[
"p-unselectable-text",
classes(
"p-tabview-selected p-highlight":
@active_subscription_tab == "subscription"
)
]}
role="presentation"
data-pc-name=""
data-pc-section="header"
>
<span class="p-tabview-title" data-pc-section="headertitle">
<.icon name="hero-check-badge-solid" class="w-4 h-4" />&nbsp;Subscription
</span>
</a>
</li>
<li
class={[
"p-unselectable-text",
classes("p-tabview-selected p-highlight": false)
]}
role="presentation"
data-pc-name=""
data-pc-section="header"
>
<a
role="tab"
class="p-tabview-nav-link flex p-[10px]"
tabindex="-1"
aria-controls="pr_id_332_content"
aria-selected="false"
aria-disabled="false"
data-pc-section="headeraction"
phx-click="change_settings_tab"
phx-value-tab="balance"
<a
role="tab"
class="p-tabview-nav-link flex p-[10px]"
tabindex="-1"
aria-controls="pr_id_334_content"
aria-selected="false"
aria-disabled="false"
data-pc-section="headeraction"
phx-click="change_subscription_tab"
phx-value-tab="subscription"
>
<span class="p-tabview-title" data-pc-section="headertitle">
<.icon name="hero-check-badge-solid" class="w-4 h-4" />&nbsp;Subscription
</span>
</a>
</li>
<li
class={[
"p-unselectable-text",
classes("p-tabview-selected p-highlight": false)
]}
role="presentation"
data-pc-name=""
data-pc-section="header"
>
<span class="p-tabview-title" data-pc-section="headertitle">
<.icon name="hero-arrow-up-solid" class="w-4 h-4" />&nbsp;Top Donators
<span class="badge">coming soon</span>
</span>
</a>
</li>
</ul>
<a
role="tab"
class="p-tabview-nav-link flex p-[10px]"
tabindex="-1"
aria-controls="pr_id_332_content"
aria-selected="false"
aria-disabled="false"
data-pc-section="headeraction"
phx-click="change_settings_tab"
phx-value-tab="balance"
>
<span class="p-tabview-title" data-pc-section="headertitle">
<.icon name="hero-arrow-up-solid" class="w-4 h-4" />&nbsp;Top Donators
<span class="badge">coming soon</span>
</span>
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="p-tabview-panels" data-pc-section="panelcontainer">
<div
id="pr_id_330_content"
class="p-tabview-panel"
role="tabpanel"
aria-labelledby="pr_id_33_header_0"
data-pc-name=""
data-pc-section="content"
>
<.live_component
:if={
@active_subscription_tab == "balance" && not is_nil(assigns |> Map.get(:map_id))
}
module={WandererAppWeb.Maps.MapBalanceComponent}
id="map-balance-component"
map_id={@map_id}
notify_to={self()}
event_name="balance_event"
current_user={@current_user}
/>
<div class="p-tabview-panels" data-pc-section="panelcontainer">
<div
id="pr_id_330_content"
class="p-tabview-panel"
role="tabpanel"
aria-labelledby="pr_id_33_header_0"
data-pc-name=""
data-pc-section="content"
>
<.live_component
:if={
@active_subscription_tab == "balance" &&
not is_nil(assigns |> Map.get(:map_id))
}
module={WandererAppWeb.Maps.MapBalanceComponent}
id="map-balance-component"
map_id={@map_id}
notify_to={self()}
event_name="balance_event"
current_user={@current_user}
/>
<.live_component
:if={@active_subscription_tab == "subscription"}
module={WandererAppWeb.Maps.MapSubscriptionsComponent}
id="map-subscriptions-component"
map_id={@map_id}
notify_to={self()}
event_name="subscriptions_event"
current_user={@current_user}
readonly={
(@user_permissions || %{}) |> Map.get(:delete_map, false) |> Kernel.not()
}
/>
<.live_component
:if={@active_subscription_tab == "subscription"}
module={WandererAppWeb.Maps.MapSubscriptionsComponent}
id="map-subscriptions-component"
map_id={@map_id}
notify_to={self()}
event_name="subscriptions_event"
current_user={@current_user}
readonly={
(@user_permissions || %{}) |> Map.get(:delete_map, false) |> Kernel.not()
}
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-action"></div>
</.modal>
<div class="modal-action"></div>
</.modal>
</div>

View File

@@ -13,6 +13,7 @@ defmodule WandererAppWeb.Plugs.ApiVersioning do
import Plug.Conn
alias WandererApp.SecurityAudit
alias WandererApp.Audit.RequestContext
@supported_versions ["1"]
@default_version "1"
@@ -260,14 +261,13 @@ defmodule WandererAppWeb.Plugs.ApiVersioning do
defp log_deprecation_usage(conn, version) do
user_id = get_user_id(conn)
request_details = RequestContext.build_request_details(conn)
SecurityAudit.log_event(:deprecated_api_usage, user_id, %{
version: version,
path: conn.request_path,
method: conn.method,
user_agent: get_user_agent(conn),
ip_address: get_peer_ip(conn)
})
SecurityAudit.log_event(
:deprecated_api_usage,
user_id,
Map.put(request_details, :version, version)
)
conn
end
@@ -316,12 +316,15 @@ defmodule WandererAppWeb.Plugs.ApiVersioning do
# Error handling
defp handle_version_error(conn, reason, _opts) do
SecurityAudit.log_event(:api_version_error, get_user_id(conn), %{
reason: reason,
path: conn.request_path,
method: conn.method,
headers: get_version_headers(conn)
})
request_details = RequestContext.build_request_details(conn)
SecurityAudit.log_event(
:api_version_error,
get_user_id(conn),
request_details
|> Map.put(:reason, reason)
|> Map.put(:headers, get_version_headers(conn))
)
conn
|> send_version_error(400, "Invalid API version", %{
@@ -376,35 +379,6 @@ defmodule WandererAppWeb.Plugs.ApiVersioning do
end
end
defp get_user_agent(conn) do
case get_req_header(conn, "user-agent") do
[user_agent] -> user_agent
[] -> "unknown"
end
end
defp get_peer_ip(conn) do
case get_req_header(conn, "x-forwarded-for") do
[forwarded_for] ->
forwarded_for
|> String.split(",")
|> List.first()
|> String.trim()
[] ->
case get_req_header(conn, "x-real-ip") do
[real_ip] ->
real_ip
[] ->
case conn.remote_ip do
{a, b, c, d} -> "#{a}.#{b}.#{c}.#{d}"
_ -> "unknown"
end
end
end
end
defp get_version_headers(conn) do
%{
"api-version" => get_req_header(conn, "api-version"),
@@ -429,8 +403,6 @@ defmodule WandererAppWeb.Plugs.ApiVersioning do
end
defp get_breaking_changes(from_version, to_version) do
# Define breaking changes between versions
# Since we've consolidated to v1, most legacy versions are no longer supported
%{
{"1.0", "1"} => [
"All API endpoints now use /api/v1/ prefix",

View File

@@ -14,6 +14,7 @@ defmodule WandererAppWeb.Plugs.RequestValidator do
import Plug.Conn
alias WandererApp.SecurityAudit
alias WandererApp.Audit.RequestContext
# 10MB
@max_request_size 10 * 1024 * 1024
@@ -344,13 +345,13 @@ defmodule WandererAppWeb.Plugs.RequestValidator do
# Log security threat
user_id = get_user_id(conn)
SecurityAudit.log_event(:security_alert, user_id, %{
threats: threats,
ip_address: get_peer_ip(conn),
user_agent: get_user_agent(conn),
request_path: conn.request_path,
method: conn.method
})
request_details = RequestContext.build_request_details(conn)
SecurityAudit.log_event(
:security_alert,
user_id,
Map.put(request_details, :threats, threats)
)
conn
|> send_validation_error(400, "Malicious content detected", %{
@@ -457,35 +458,6 @@ defmodule WandererAppWeb.Plugs.RequestValidator do
end
end
defp get_peer_ip(conn) do
case get_req_header(conn, "x-forwarded-for") do
[forwarded_for] ->
forwarded_for
|> String.split(",")
|> List.first()
|> String.trim()
[] ->
case get_req_header(conn, "x-real-ip") do
[real_ip] ->
real_ip
[] ->
case conn.remote_ip do
{a, b, c, d} -> "#{a}.#{b}.#{c}.#{d}"
_ -> "unknown"
end
end
end
end
defp get_user_agent(conn) do
case get_req_header(conn, "user-agent") do
[user_agent] -> user_agent
[] -> "unknown"
end
end
defp send_validation_error(conn, status, message, details) do
error_response = %{
error: message,
@@ -504,14 +476,15 @@ defmodule WandererAppWeb.Plugs.RequestValidator do
# Log the validation error
user_id = get_user_id(conn)
SecurityAudit.log_event(:security_alert, user_id, %{
error: "validation_error",
message: Exception.message(error),
ip_address: get_peer_ip(conn),
user_agent: get_user_agent(conn),
request_path: conn.request_path,
method: conn.method
})
request_details = RequestContext.build_request_details(conn)
SecurityAudit.log_event(
:security_alert,
user_id,
request_details
|> Map.put(:error, "validation_error")
|> Map.put(:message, Exception.message(error))
)
conn
|> send_validation_error(500, "Request validation failed", %{

View File

@@ -31,8 +31,6 @@ defmodule WandererAppWeb.Presence do
character_id
end)
WandererApp.Cache.insert("map_#{map_id}:presence_updated", true)
WandererApp.Cache.insert(
"map_#{map_id}:presence_character_ids",
presence_tracked_character_ids
@@ -43,6 +41,8 @@ defmodule WandererAppWeb.Presence do
presence_data
)
WandererApp.Cache.insert("map_#{map_id}:presence_updated", true)
{:ok, state}
end
end

View File

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

View File

@@ -0,0 +1,136 @@
%{
title: "New Feature: Map Default Settings",
author: "Wanderer Team",
cover_image_uri: "/images/news/2025/07-27-settings/common_settings.png",
tags: ~w(feature settings maps customization admin),
description: "Map administrators can now configure default settings for new users, providing a customized initial experience for each map."
}
---
# Introducing Map Default Settings
## A Better First Experience for Your Map Users
We're excited to announce a new feature that gives map administrators more control over the user experience: **Map Default Settings**. This feature allows map admins to configure the default settings that new users will receive when they first access a map.
---
## The Challenge
Previously, all new users would start with the same hardcoded default settings, regardless of the map they were joining. This one-size-fits-all approach meant that:
- New users often had to spend time configuring settings to match the map's intended use
- Map administrators couldn't optimize the initial experience for their specific mapping needs
- Training new members required explaining which settings to change
Different mapping groups have different needs. A wormhole mapping corporation might want different default widget layouts than a nullsec alliance. A small gang PvP group might prioritize different information than an exploration-focused team.
---
## The Solution: Customizable Default Settings
With Map Default Settings, administrators can now:
1. **Configure their ideal settings** - Set up the map interface exactly as they want new users to experience it
2. **Save as defaults** - With a single click, save these settings as the default for all new users
3. **Provide consistency** - Ensure all new members start with the same optimized configuration
---
## How It Works
### For Map Administrators
Setting up default settings is simple:
1. Configure your map settings to your preferred state
2. Open the Settings dialog and navigate to the 'Admin Settings' tab
4. Click "Save as Map Default"
That's it! Your current settings are now saved as the default for any new user accessing your map.
![Admin Settings Interface](/images/news/2025/07-27-settings/admin_settings.png)
### For New Users
When a user accesses your map for the first time:
1. The system checks if they have existing settings for this map
2. If not, it automatically loads the admin-configured defaults
3. The user starts with an optimized configuration from day one
4. They can still customize settings to their personal preferences
---
## Sync with Default Settings
We've also added a complementary feature: **Sync with Default Settings**. Any user can now reset their settings back to the map's default configuration:
1. Open Settings → Server Settings tab
2. Click "Sync with Default Settings"
3. Confirm the action
4. Settings are restored to:
- Admin-configured defaults (if available)
This is perfect for users who want to start fresh or have accidentally misconfigured their settings.
![Sync with Default Settings Button](/images/news/2025/07-27-settings/server_settings.png)
---
## What Can Be Configured?
Map default settings include all user interface preferences:
- **Widget Settings** - Which widgets are visible and their default positions
- **Kill Tracking** - Default kill display options and filters
- **Local Characters** - How local pilots are displayed
- **Signatures** - Default signature widget configuration
- **Routes** - Route planning preferences
- **Map Display** - Visual preferences for the map itself
- **Interface Options** - General UI preferences
---
## Security and Permissions
- Only map owners and administrators can save default settings
- All users can read and apply default settings
- Settings are validated to ensure they contain only UI preferences
- No sensitive data is stored in default settings
---
## Coming From Other Mapping Tools?
If you're migrating from another mapping tool, you can now configure Wanderer to feel familiar to your members:
1. Set up the interface to match your previous tool's layout
2. Save as default settings
3. New members will find a familiar interface waiting for them
---
## Get Started Today
Map administrators can start using this feature immediately:
1. Configure your ideal settings
2. Save them as defaults
3. Share your map with confidence that new users will have a great first experience
We hope this feature helps you create more cohesive and efficient mapping teams. As always, we welcome your feedback and suggestions for future improvements.
---
## Thank You
Special thanks to our community members who suggested this feature and helped test it during development. Your feedback continues to shape Wanderer into the best mapping tool for EVE Online.
----
Fly safe,
**The Wanderer Team**
----

View File

@@ -254,7 +254,7 @@ groupID,categoryID,groupName,iconID,useBasePrice,anchored,anchorable,fittableNon
364,23,Mobile Storage,0,0,0,1,0,0
365,23,Control Tower,0,1,0,1,0,1
366,2,Warp Gate,0,0,1,0,0,0
367,7,Ballistic Control system,0,0,0,0,0,1
367,7,Ballistic Control System,0,0,0,0,0,1
368,2,Global Warp Disruptor,0,0,1,0,0,0
369,17,Ship Logs,0,1,0,0,0,1
370,17,Criminal Tags,0,1,0,0,0,1
@@ -1505,7 +1505,7 @@ groupID,categoryID,groupName,iconID,useBasePrice,anchored,anchorable,fittableNon
4757,25,Ueganite,15,0,1,0,0,1
4758,25,Hezorime,15,0,1,0,0,1
4759,25,Griemeer,15,0,1,0,0,1
4768,39,Sovereignty Hub Anomaly Detection Upgrades,None,1,0,0,0,1
4768,39,Sovereignty Hub Site Detection Upgrades,None,1,0,0,0,1
4769,7,Capital Mobility Modules,None,0,0,0,0,1
4771,11,Homefront Operations Noncombatant,None,0,0,0,0,0
4772,39,Sovereignty Hub Service Infrastructure Upgrade,None,1,0,0,0,1
@@ -1533,8 +1533,11 @@ groupID,categoryID,groupName,iconID,useBasePrice,anchored,anchorable,fittableNon
4825,2,Local Beacon,None,0,1,0,0,0
4827,17,EDENCOM Data,None,1,0,0,0,1
4828,2,Pirate Spawners,None,0,0,0,0,0
4838,39,Sovereignty Hub Colony Resources Management Upgrades,None,1,0,0,0,1
4839,39,Sovereignty Hub System Effect Generator Upgrades,None,1,0,0,0,1
4843,17,Limited Rarities,None,1,0,0,0,1
4857,25,Tyranite,15,0,1,0,0,1
4900,17,Stability Telemetry,None,1,0,0,0,1
350858,350001,Infantry Weapons,None,1,0,0,0,0
351064,350001,Infantry Dropsuits,None,1,0,0,0,0
351121,350001,Infantry Modules,None,1,0,0,0,0
1 groupID categoryID groupName iconID useBasePrice anchored anchorable fittableNonSingleton published
254 364 23 Mobile Storage 0 0 0 1 0 0
255 365 23 Control Tower 0 1 0 1 0 1
256 366 2 Warp Gate 0 0 1 0 0 0
257 367 7 Ballistic Control system Ballistic Control System 0 0 0 0 0 1
258 368 2 Global Warp Disruptor 0 0 1 0 0 0
259 369 17 Ship Logs 0 1 0 0 0 1
260 370 17 Criminal Tags 0 1 0 0 0 1
1505 4757 25 Ueganite 15 0 1 0 0 1
1506 4758 25 Hezorime 15 0 1 0 0 1
1507 4759 25 Griemeer 15 0 1 0 0 1
1508 4768 39 Sovereignty Hub Anomaly Detection Upgrades Sovereignty Hub Site Detection Upgrades None 1 0 0 0 1
1509 4769 7 Capital Mobility Modules None 0 0 0 0 1
1510 4771 11 Homefront Operations Noncombatant None 0 0 0 0 0
1511 4772 39 Sovereignty Hub Service Infrastructure Upgrade None 1 0 0 0 1
1533 4825 2 Local Beacon None 0 1 0 0 0
1534 4827 17 EDENCOM Data None 1 0 0 0 1
1535 4828 2 Pirate Spawners None 0 0 0 0 0
1536 4838 39 Sovereignty Hub Colony Resources Management Upgrades None 1 0 0 0 1
1537 4839 39 Sovereignty Hub System Effect Generator Upgrades None 1 0 0 0 1
1538 4843 17 Limited Rarities None 1 0 0 0 1
1539 4857 25 Tyranite 15 0 1 0 0 1
1540 4900 17 Stability Telemetry None 1 0 0 0 1
1541 350858 350001 Infantry Weapons None 1 0 0 0 0
1542 351064 350001 Infantry Dropsuits None 1 0 0 0 0
1543 351121 350001 Infantry Modules None 1 0 0 0 0

File diff suppressed because it is too large Load Diff

View File

@@ -1173,3 +1173,4 @@ regionID,constellationID,constellationName,x,y,z,xMin,xMax,yMin,yMax,zMin,zMax,f
14000005,24000023,VC-023,-3929303414294949888.0000000000,2052312219158850048.0000000000,-5876493745543360512.0000000000,-3969303414294949888.0000000000,-3889303414294949888.0000000000,2012312219158850048.0000000000,2092312219158850048.0000000000,-5916493745543360512.0000000000,-5836493745543360512.0000000000,None,4e+16
14000005,24000024,VC-024,-4000137332327759872.0000000000,2065749800750390016.0000000000,-5861975205782179840.0000000000,-4040137332327759872.0000000000,-3960137332327759872.0000000000,2025749800750390016.0000000000,2105749800750390016.0000000000,-5901975205782179840.0000000000,-5821975205782179840.0000000000,None,4e+16
14000005,24000025,VC-025,-3737391588602420224.0000000000,2280269739557479936.0000000000,-6106825722789959680.0000000000,-3777391588602420224.0000000000,-3697391588602420224.0000000000,2240269739557479936.0000000000,2320269739557479936.0000000000,-6146825722789959680.0000000000,-6066825722789959680.0000000000,None,4e+16
19000001,26000001,No Constellation name,1.0000000000,1.0000000000,1.0000000000,-300000.0000000000,300000.0000000000,-300000.0000000000,300000.0000000000,-300000.0000000000,300000.0000000000,None,4e+16
1 regionID constellationID constellationName x y z xMin xMax yMin yMax zMin zMax factionID radius
1173 14000005 24000023 VC-023 -3929303414294949888.0000000000 2052312219158850048.0000000000 -5876493745543360512.0000000000 -3969303414294949888.0000000000 -3889303414294949888.0000000000 2012312219158850048.0000000000 2092312219158850048.0000000000 -5916493745543360512.0000000000 -5836493745543360512.0000000000 None 4e+16
1174 14000005 24000024 VC-024 -4000137332327759872.0000000000 2065749800750390016.0000000000 -5861975205782179840.0000000000 -4040137332327759872.0000000000 -3960137332327759872.0000000000 2025749800750390016.0000000000 2105749800750390016.0000000000 -5901975205782179840.0000000000 -5821975205782179840.0000000000 None 4e+16
1175 14000005 24000025 VC-025 -3737391588602420224.0000000000 2280269739557479936.0000000000 -6106825722789959680.0000000000 -3777391588602420224.0000000000 -3697391588602420224.0000000000 2240269739557479936.0000000000 2320269739557479936.0000000000 -6146825722789959680.0000000000 -6066825722789959680.0000000000 None 4e+16
1176 19000001 26000001 No Constellation name 1.0000000000 1.0000000000 1.0000000000 -300000.0000000000 300000.0000000000 -300000.0000000000 300000.0000000000 -300000.0000000000 300000.0000000000 None 4e+16

View File

@@ -107,6 +107,7 @@ locationID,wormholeClassID
14000003,19
14000004,19
14000005,19
19000001,7
20000061,11
20000062,10
30000012,8
1 locationID wormholeClassID
107 14000003 19
108 14000004 19
109 14000005 19
110 19000001 7
111 20000061 11
112 20000062 10
113 30000012 8

View File

@@ -111,3 +111,4 @@ regionID,regionName,x,y,z,xMin,xMax,yMin,yMax,zMin,zMax,factionID,nebula,radius
14000003,VR-03,-5431841546101449728.0000000000,2985429153089509888.0000000000,-6018316379407049728.0000000000,-5581841546101449728.0000000000,-5281841546101449728.0000000000,2835429153089509888.0000000000,3135429153089509888.0000000000,-6168316379407049728.0000000000,-5868316379407049728.0000000000,None,11821,None
14000004,VR-04,-4545298976287320064.0000000000,2308091069617820160.0000000000,-6316706917218969600.0000000000,-4695298976287320064.0000000000,-4395298976287320064.0000000000,2158091069617819904.0000000000,2458091069617820160.0000000000,-6466706917218969600.0000000000,-6166706917218969600.0000000000,None,11821,None
14000005,VR-05,-3876324035229649920.0000000000,2174764391831830016.0000000000,-5975813282299729920.0000000000,-4026324035229649920.0000000000,-3726324035229649920.0000000000,2024764391831830016.0000000000,2324764391831830016.0000000000,-6125813282299729920.0000000000,-5825813282299729920.0000000000,None,11821,None
19000001,No Name,1.0000000000,1.0000000000,1.0000000000,-300000.0000000000,300000.0000000000,-300000.0000000000,300000.0000000000,-300000.0000000000,300000.0000000000,None,11806,None
1 regionID regionName x y z xMin xMax yMin yMax zMin zMax factionID nebula radius
111 14000003 VR-03 -5431841546101449728.0000000000 2985429153089509888.0000000000 -6018316379407049728.0000000000 -5581841546101449728.0000000000 -5281841546101449728.0000000000 2835429153089509888.0000000000 3135429153089509888.0000000000 -6168316379407049728.0000000000 -5868316379407049728.0000000000 None 11821 None
112 14000004 VR-04 -4545298976287320064.0000000000 2308091069617820160.0000000000 -6316706917218969600.0000000000 -4695298976287320064.0000000000 -4395298976287320064.0000000000 2158091069617819904.0000000000 2458091069617820160.0000000000 -6466706917218969600.0000000000 -6166706917218969600.0000000000 None 11821 None
113 14000005 VR-05 -3876324035229649920.0000000000 2174764391831830016.0000000000 -5975813282299729920.0000000000 -4026324035229649920.0000000000 -3726324035229649920.0000000000 2024764391831830016.0000000000 2324764391831830016.0000000000 -6125813282299729920.0000000000 -5825813282299729920.0000000000 None 11821 None
114 19000001 No Name 1.0000000000 1.0000000000 1.0000000000 -300000.0000000000 300000.0000000000 -300000.0000000000 300000.0000000000 -300000.0000000000 300000.0000000000 None 11806 None

View File

@@ -8435,3 +8435,4 @@ regionID,constellationID,solarSystemID,solarSystemName,x,y,z,xMin,xMax,yMin,yMax
14000005,24000025,34000198,V-198,-3726804805078839808.0000000000,2273820166108100096.0000000000,-6118384105031680000.0000000000,-3726819764865909760.0000000000,-3726789845291769856.0000000000,2273805206321029888.0000000000,2273835125895170048.0000000000,-6118399064818750464.0000000000,-6118369145244609536.0000000000,0E-10,0,0,0,0,0,0,None,-0.9900000000,None,14959787070000.0000000000,None,None
14000005,24000025,34000199,V-199,-3702467135073939968.0000000000,2271227427207830016.0000000000,-6075476754085990400.0000000000,-3702482094861009920.0000000000,-3702452175286870016.0000000000,2271212467420760064.0000000000,2271242386994899968.0000000000,-6075491713873059840.0000000000,-6075461794298919936.0000000000,0E-10,0,0,0,0,0,0,None,-0.9900000000,None,14959787070000.0000000000,None,None
14000005,24000025,34000200,V-200,-3726767846292810240.0000000000,2248086962890269952.0000000000,-6097487616715119616.0000000000,-3726782806079880192.0000000000,-3726752886505739776.0000000000,2248072003103200000.0000000000,2248101922677339904.0000000000,-6097502576502190080.0000000000,-6097472656928050176.0000000000,0E-10,0,0,0,0,0,0,None,-0.9900000000,None,14959787070000.0000000000,None,None
19000001,26000001,36000001,No System Name,1.0000000000,1.0000000000,1.0000000000,-300000.0000000000,300000.0000000000,-300000.0000000000,300000.0000000000,-300000.0000000000,300000.0000000000,0E-10,0,0,0,0,0,0,None,1.0000000000,None,14959787070000.0000000000,None,None
Can't render this file because it is too large.

View File

@@ -0,0 +1,60 @@
defmodule WandererApp.Repo.Migrations.CreateMapDefaultSettingsOnly do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
create table(:map_default_settings, primary_key: false) do
add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true
add :settings, :text, null: false
add :inserted_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
add :updated_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
add :map_id,
references(:maps_v1, column: :id, name: "map_default_settings_map_id_fkey", type: :uuid),
null: false
add :created_by_id,
references(:character_v1,
column: :id,
name: "map_default_settings_created_by_id_fkey",
type: :uuid
)
add :updated_by_id,
references(:character_v1,
column: :id,
name: "map_default_settings_updated_by_id_fkey",
type: :uuid
)
end
create unique_index(:map_default_settings, [:map_id],
name: "map_default_settings_unique_map_settings_index"
)
end
def down do
drop_if_exists unique_index(:map_default_settings, [:map_id],
name: "map_default_settings_unique_map_settings_index"
)
drop constraint(:map_default_settings, "map_default_settings_map_id_fkey")
drop constraint(:map_default_settings, "map_default_settings_created_by_id_fkey")
drop constraint(:map_default_settings, "map_default_settings_updated_by_id_fkey")
drop table(:map_default_settings)
end
end

View File

@@ -0,0 +1,41 @@
defmodule WandererApp.Repo.Migrations.AddSecurityAuditIndexes do
use Ecto.Migration
@disable_ddl_transaction true
@disable_migration_lock true
def up do
# Add indexes for security audit queries
create_if_not_exists index(:user_activity_v1, [:entity_type, :event_type, :inserted_at],
concurrently: true
)
create_if_not_exists index(:user_activity_v1, [:user_id, :inserted_at], concurrently: true)
create_if_not_exists index(:user_activity_v1, [:event_type], concurrently: true)
# Partial index for security events only - for better performance
create_if_not_exists index(:user_activity_v1, [:user_id, :inserted_at],
where: "entity_type = 'security_event'",
name: :user_activity_v1_security_events_idx,
concurrently: true
)
# Index for entity_id queries (used by Map.Audit)
create_if_not_exists index(:user_activity_v1, [:entity_id, :inserted_at], concurrently: true)
end
def down do
drop_if_exists index(:user_activity_v1, [:entity_id, :inserted_at], concurrently: true)
drop_if_exists index(:user_activity_v1, [:user_id, :inserted_at],
name: :user_activity_v1_security_events_idx,
concurrently: true
)
drop_if_exists index(:user_activity_v1, [:event_type], concurrently: true)
drop_if_exists index(:user_activity_v1, [:user_id, :inserted_at], concurrently: true)
drop_if_exists index(:user_activity_v1, [:entity_type, :event_type, :inserted_at],
concurrently: true
)
end
end

View File

@@ -1,127 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_character_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_corporation_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_alliance_id",
"type": "text"
},
{
"allow_nil?": false,
"default": "\"member\"",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "role",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "access_list_members_v1_access_list_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "access_lists_v1"
},
"size": null,
"source": "access_list_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "CB795E3E8D78F769E55971510831DA39A33F1D0CC065CFEE6969164B1F33916F",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "access_list_members_v1"
}

View File

@@ -1,127 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_character_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_corporation_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_alliance_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "role",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "access_list_members_v1_access_list_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "access_lists_v1"
},
"size": null,
"source": "access_list_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "098ED55EA6882AA6E7033650E918E40B7E8A2F97C205D3B15739F88DC4A1FF87",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "access_list_members_v1"
}

View File

@@ -1,127 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_character_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_corporation_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_alliance_id",
"type": "text"
},
{
"allow_nil?": false,
"default": "\"viewer\"",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "role",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "access_list_members_v1_access_list_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "access_lists_v1"
},
"size": null,
"source": "access_list_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "9DF32D06113D3923BA091BF65AE604418B8C33C01B421BDDE32032A4D2C31507",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "access_list_members_v1"
}

View File

@@ -1,127 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_character_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_corporation_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_alliance_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "\"viewer\"",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "role",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "access_list_members_v1_access_list_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "access_lists_v1"
},
"size": null,
"source": "access_list_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "B9FA697C24A6DEC9FF0E5B7588C07B17E2CE659EC27D1F05A30DBEAD72883FF7",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "access_list_members_v1"
}

View File

@@ -1,155 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_character_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_corporation_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_alliance_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "\"viewer\"",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "role",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "access_list_members_v1_access_list_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "access_lists_v1"
},
"size": null,
"source": "access_list_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "74A488E6C73B100674AB9031886BFD74C84234655A1D1BA4F1B649DBD1DE783F",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "access_list_members_v1_uniq_acl_member_id_index",
"keys": [
{
"type": "atom",
"value": "access_list_id"
},
{
"type": "atom",
"value": "eve_character_id"
},
{
"type": "atom",
"value": "eve_corporation_id"
},
{
"type": "atom",
"value": "eve_alliance_id"
}
],
"name": "uniq_acl_member_id",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "access_list_members_v1"
}

View File

@@ -1,183 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_character_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_corporation_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_alliance_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "\"viewer\"",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "role",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "access_list_members_v1_access_list_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "access_lists_v1"
},
"size": null,
"source": "access_list_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "6773A2F5243E2DCC21A2F460AD0053B28E38235784B3F33045200B4EB4349718",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "access_list_members_v1_uniq_acl_alliance_id_index",
"keys": [
{
"type": "atom",
"value": "access_list_id"
},
{
"type": "atom",
"value": "eve_alliance_id"
}
],
"name": "uniq_acl_alliance_id",
"nils_distinct?": true,
"where": null
},
{
"all_tenants?": false,
"base_filter": null,
"index_name": "access_list_members_v1_uniq_acl_character_id_index",
"keys": [
{
"type": "atom",
"value": "access_list_id"
},
{
"type": "atom",
"value": "eve_character_id"
}
],
"name": "uniq_acl_character_id",
"nils_distinct?": true,
"where": null
},
{
"all_tenants?": false,
"base_filter": null,
"index_name": "access_list_members_v1_uniq_acl_corporation_id_index",
"keys": [
{
"type": "atom",
"value": "access_list_id"
},
{
"type": "atom",
"value": "eve_corporation_id"
}
],
"name": "uniq_acl_corporation_id",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "access_list_members_v1"
}

View File

@@ -1,97 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "description",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "access_lists_v1_owner_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "character_v1"
},
"size": null,
"source": "owner_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "444F11C25C585114DAA27322D172918D59CEE6B49FE9F6AC88181566782B1DBA",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "access_lists_v1"
}

View File

@@ -1,197 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_id",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "online",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "deleted",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "scopes",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "character_owner_hash",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "access_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "refresh_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "token_type",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "expires_at",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "location",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "character_v1_user_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "user_v1"
},
"size": null,
"source": "user_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "10706AB4D288BDEE7AE1DB7FD2072989B40231FA7E416EB164022D33157F5F97",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "character_v1"
}

View File

@@ -1,257 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_id",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "online",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "deleted",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "scopes",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "character_owner_hash",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "access_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "refresh_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "token_type",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "expires_at",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "location",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_ticker",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_ticker",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "character_v1_user_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "user_v1"
},
"size": null,
"source": "user_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "C3F49D7B512DF7617A7CC9A9A7D4DF55AF090423B663EF24503F6159A98204E2",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "character_v1"
}

View File

@@ -1,277 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_id",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "online",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "deleted",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "scopes",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "character_owner_hash",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "access_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "refresh_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "token_type",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "expires_at",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "location",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "structure_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_ticker",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_ticker",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "character_v1_user_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "user_v1"
},
"size": null,
"source": "user_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "29EF3CA3C393C1D3E7A002E90E1D8D16BDA54E7A4C9163ED534791B63724B345",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "character_v1"
}

View File

@@ -1,287 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_id",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "online",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "deleted",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "scopes",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "character_owner_hash",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "access_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "refresh_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "token_type",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "expires_at",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "location",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "structure_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_ticker",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_ticker",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "character_v1_user_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "user_v1"
},
"size": null,
"source": "user_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "C71AABB1835923C905F328CADA4F6F7565B09B6F4BA0603AB15B4A0B791630D1",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "character_v1"
}

View File

@@ -1,297 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_id",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "online",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "deleted",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "scopes",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "character_owner_hash",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "access_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "refresh_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "token_type",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "expires_at",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "location",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "structure_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_ticker",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_ticker",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "wallet_ballance",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "character_v1_user_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "user_v1"
},
"size": null,
"source": "user_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "CEEDE046912657867296D1FC17AC44336CF52D80E801D39341C323FC1B26BDB9",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "character_v1"
}

View File

@@ -1,297 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_id",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "online",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "deleted",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "scopes",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "character_owner_hash",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "access_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "refresh_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "token_type",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "expires_at",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "location",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "structure_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_ticker",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_ticker",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "wallet_balance",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "character_v1_user_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "user_v1"
},
"size": null,
"source": "user_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "1AEB87AAF8CD6CF5A98A330558A3C0050427E164F2FB2FE7AA0D4AB8E61980D0",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "character_v1"
}

View File

@@ -1,308 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_id",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "name",
"type": "text"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "online",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "false",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "deleted",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "scopes",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "character_owner_hash",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "access_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "refresh_token",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "token_type",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "expires_at",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "location",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "structure_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "corporation_ticker",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_name",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "alliance_ticker",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "wallet_balance",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "character_v1_user_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "user_v1"
},
"size": null,
"source": "user_id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_eve_wallet_balance",
"type": "binary"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "C919A2551EAC04804C90FCA04ECF3C719FE768608D429AFD9706A7684D88EF8B",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "character_v1"
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,139 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_transaction_id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "amount",
"type": "float"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "balance",
"type": "float"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "first_party_id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "second_party_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "date",
"type": "utc_datetime"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "description",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "reason",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ref_type",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "E45BA247AD12230CAD90896DA3CA71B9940E1D00BD51215106256AE7C75AD6B6",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "corp_wallet_transactions_v1"
}

View File

@@ -1,154 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_transaction_id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "amount",
"type": "float"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "balance",
"type": "float"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "first_party_id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "second_party_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "date",
"type": "utc_datetime"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "description",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "reason",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ref_type",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "0008A3278A23F8B93B1322F98D47124DE720818DE188C6DA203B92C6B948F219",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "corp_wallet_transactions_v1_eve_transaction_id_index",
"keys": [
{
"type": "atom",
"value": "eve_transaction_id"
}
],
"name": "eve_transaction_id",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "corp_wallet_transactions_v1"
}

View File

@@ -1,204 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_transaction_id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "amount",
"type": "float"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "balance",
"type": "float"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "first_party_id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "second_party_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "date",
"type": "utc_datetime"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "description",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "reason",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ref_type",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_amount_encoded",
"type": "binary"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_balance_encoded",
"type": "binary"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_first_party_id_encoded",
"type": "binary"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_second_party_id_encoded",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_reason_encoded",
"type": "binary"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "B009AFDEC92308BD2C7129BE50BC21B042B61085A955E8FCB2347FADF64F015F",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "corp_wallet_transactions_v1_eve_transaction_id_index",
"keys": [
{
"type": "atom",
"value": "eve_transaction_id"
}
],
"name": "eve_transaction_id",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "corp_wallet_transactions_v1"
}

View File

@@ -1,154 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_transaction_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "date",
"type": "utc_datetime"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "description",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ref_type",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_amount_encoded",
"type": "binary"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_balance_encoded",
"type": "binary"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_first_party_id_encoded",
"type": "binary"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_second_party_id_encoded",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_reason_encoded",
"type": "binary"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "C66789F82FF4587652C69760817344F16566775EABFC962B6DF550D86D10A2FF",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "corp_wallet_transactions_v1_eve_transaction_id_index",
"keys": [
{
"type": "atom",
"value": "eve_transaction_id"
}
],
"name": "eve_transaction_id",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "corp_wallet_transactions_v1"
}

View File

@@ -1,174 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "eve_transaction_id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "first_party_id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "second_party_id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "first_party_id_encoded",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "second_party_id_encoded",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "date",
"type": "utc_datetime"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "description",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ref_type",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_amount_encoded",
"type": "binary"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_balance_encoded",
"type": "binary"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "encrypted_reason_encoded",
"type": "binary"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "E31376D56403DCB616F4579B0800E07217E86C99431FF50650294724537010B3",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "corp_wallet_transactions_v1_eve_transaction_id_index",
"keys": [
{
"type": "atom",
"value": "eve_transaction_id"
}
],
"name": "eve_transaction_id",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "corp_wallet_transactions_v1"
}

View File

@@ -1,105 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": true,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "map_access_lists_v1_map_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "maps_v1"
},
"size": null,
"source": "map_id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": true,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "map_access_lists_v1_access_list_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "access_lists_v1"
},
"size": null,
"source": "access_list_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "D969A70A438F04CFA681D0B3A9A179D7449954D598E55FE1F9B3DAC8EC3B3062",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "map_access_lists_v1"
}

View File

@@ -1,126 +0,0 @@
{
"attributes": [
{
"default": "fragment(\"gen_random_uuid()\")",
"size": null,
"type": "uuid",
"source": "id",
"references": null,
"allow_nil?": false,
"primary_key?": true,
"generated?": false
},
{
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"size": null,
"type": "utc_datetime_usec",
"source": "inserted_at",
"references": null,
"allow_nil?": false,
"primary_key?": false,
"generated?": false
},
{
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"size": null,
"type": "utc_datetime_usec",
"source": "updated_at",
"references": null,
"allow_nil?": false,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "uuid",
"source": "map_id",
"references": {
"name": "map_access_lists_v1_map_id_fkey",
"table": "maps_v1",
"primary_key?": true,
"schema": "public",
"multitenancy": {
"global": null,
"attribute": null,
"strategy": null
},
"destination_attribute": "id",
"on_delete": null,
"on_update": null,
"deferrable": false,
"match_with": null,
"match_type": null,
"index?": false,
"destination_attribute_default": null,
"destination_attribute_generated": null
},
"allow_nil?": false,
"primary_key?": true,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "uuid",
"source": "access_list_id",
"references": {
"name": "map_access_lists_v1_access_list_id_fkey",
"table": "access_lists_v1",
"primary_key?": true,
"schema": "public",
"multitenancy": {
"global": null,
"attribute": null,
"strategy": null
},
"destination_attribute": "id",
"on_delete": null,
"on_update": null,
"deferrable": false,
"match_with": null,
"match_type": null,
"index?": false,
"destination_attribute_default": null,
"destination_attribute_generated": null
},
"allow_nil?": false,
"primary_key?": true,
"generated?": false
}
],
"table": "map_access_lists_v1",
"hash": "72DB5BB5D63D90C44A75097741E9132A50EAFE974F30BE84F88A2A2CA6415D46",
"repo": "Elixir.WandererApp.Repo",
"identities": [
{
"name": "unique_map_acl",
"keys": [
{
"type": "atom",
"value": "map_id"
},
{
"type": "atom",
"value": "access_list_id"
}
],
"where": null,
"base_filter": null,
"all_tenants?": false,
"nils_distinct?": true,
"index_name": "map_access_lists_v1_unique_map_acl_index"
}
],
"schema": null,
"check_constraints": [],
"custom_indexes": [],
"multitenancy": {
"global": null,
"attribute": null,
"strategy": null
},
"base_filter": null,
"custom_statements": [],
"has_create_action": true
}

View File

@@ -1,135 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship_type_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_source_id",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_target_id",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": true,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "map_chain_passages_v1_map_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "maps_v1"
},
"size": null,
"source": "map_id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": true,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "map_chain_passages_v1_character_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "character_v1"
},
"size": null,
"source": "character_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "54E64D30DCFB80B93C57FD251790C00F69F8050F78B8BDE74302361815DBC5F2",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "map_chain_passages_v1"
}

View File

@@ -1,147 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_source",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_target",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "0",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "mass_status",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "0",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "time_status",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "1",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship_size_type",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "wormhole_type",
"type": "text"
},
{
"allow_nil?": true,
"default": "0",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "count_of_passage",
"type": "bigint"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "map_chain_v1_map_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "maps_v1"
},
"size": null,
"source": "map_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "B72F0AB8CAFC37FDDC5F54EBE5FDE3D7E9A4E066A6A2400E507E8F43509B2446",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "map_chain_v1"
}

View File

@@ -1,158 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_source",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_target",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "0",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "mass_status",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "0",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "time_status",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "1",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship_size_type",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "wormhole_type",
"type": "text"
},
{
"allow_nil?": true,
"default": "0",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "count_of_passage",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "locked",
"type": "boolean"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "map_chain_v1_map_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "maps_v1"
},
"size": null,
"source": "map_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "A56C58B107AAB08706CA42BA017DCC7972DE1ADD0B9C6D45A3C5C4CDFA4A0C11",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "map_chain_v1"
}

View File

@@ -1,168 +0,0 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_source",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "solar_system_target",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "0",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "mass_status",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "0",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "time_status",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "1",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "ship_size_type",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "wormhole_type",
"type": "text"
},
{
"allow_nil?": true,
"default": "0",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "count_of_passage",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "locked",
"type": "boolean"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "custom_info",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "map_chain_v1_map_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "maps_v1"
},
"size": null,
"source": "map_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "D2213536C4FA24865B2977B1544847E583441789FF1F1F07E7ADAEBD238764CF",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.WandererApp.Repo",
"schema": null,
"table": "map_chain_v1"
}

Some files were not shown because too many files have changed in this diff Show More