mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-11 02:05:58 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
802e81b1cd | ||
|
|
41f0834c51 | ||
|
|
880de0b047 | ||
|
|
bbe7fda4e0 | ||
|
|
92a9274dce | ||
|
|
8765d83083 | ||
|
|
a298152bc8 | ||
|
|
2b7abe5774 | ||
|
|
3e9241892e | ||
|
|
6ea79a7960 | ||
|
|
2af562e313 | ||
|
|
40672f6a47 | ||
|
|
6d66ae3f50 | ||
|
|
94c89e0325 | ||
|
|
3670ef40a3 | ||
|
|
16d464fba5 | ||
|
|
0b7e0b9cd0 | ||
|
|
dd5fd114d2 | ||
|
|
6e53879344 | ||
|
|
af2bfd4d59 | ||
|
|
a4a34c8ba7 | ||
|
|
8c609f4fdf | ||
|
|
197f5b583f | ||
|
|
4eb4a03e59 | ||
|
|
3d4e66d438 | ||
|
|
ffbc9f169a | ||
|
|
99650187e9 | ||
|
|
92699317cd | ||
|
|
0e48315803 | ||
|
|
868ec246bd | ||
|
|
0030a688c6 | ||
|
|
3ba8f51a2f | ||
|
|
04576b335c | ||
|
|
ea29aa176f | ||
|
|
bf58d3ae93 | ||
|
|
d6c32e2d39 | ||
|
|
bdc4948afb | ||
|
|
331db10029 | ||
|
|
3338dce900 | ||
|
|
1364779f81 | ||
|
|
cb318aa6c6 | ||
|
|
1a27b21efe | ||
|
|
e57f565812 | ||
|
|
da2605ee03 |
42
.github/workflows/build.yml
vendored
42
.github/workflows/build.yml
vendored
@@ -18,49 +18,8 @@ permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy-test:
|
||||
name: 🚀 Deploy to test env (fly.io)
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.base_ref == 'main' || (github.ref == 'refs/heads/main' && github.event_name == 'push') }}
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
|
||||
- name: 👀 Read app name
|
||||
uses: SebRollen/toml-action@v1.0.0
|
||||
id: app_name
|
||||
with:
|
||||
file: "fly.toml"
|
||||
field: "app"
|
||||
|
||||
- name: 🚀 Deploy Test
|
||||
run: flyctl deploy --remote-only --wait-timeout=300 --ha=false
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
|
||||
manual-approval:
|
||||
name: Manual Approval
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-test
|
||||
if: success()
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Await Manual Approval
|
||||
uses: trstringer/manual-approval@v1
|
||||
with:
|
||||
secret: ${{ github.TOKEN }}
|
||||
approvers: DmitryPopov
|
||||
minimum-approvals: 1
|
||||
issue-title: "Manual Approval Required for Release"
|
||||
issue-body: "Please approve or deny the deployment."
|
||||
|
||||
build:
|
||||
name: 🛠 Build
|
||||
needs: manual-approval
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ (github.ref == 'refs/heads/main') && github.event_name == 'push' }}
|
||||
permissions:
|
||||
@@ -157,7 +116,6 @@ jobs:
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
|
||||
90
CHANGELOG.md
90
CHANGELOG.md
@@ -2,6 +2,96 @@
|
||||
|
||||
<!-- changelog -->
|
||||
|
||||
## [v1.65.7](https://github.com/wanderer-industries/wanderer/compare/v1.65.6...v1.65.7) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map character tracking issues
|
||||
|
||||
## [v1.65.6](https://github.com/wanderer-industries/wanderer/compare/v1.65.5...v1.65.6) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map character tracking issues
|
||||
|
||||
## [v1.65.5](https://github.com/wanderer-industries/wanderer/compare/v1.65.4...v1.65.5) (2025-05-26)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Fixed map character tracking issues
|
||||
|
||||
* Signature: Update restored signature character
|
||||
|
||||
## [v1.65.4](https://github.com/wanderer-industries/wanderer/compare/v1.65.3...v1.65.4) (2025-05-24)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signature: Force signature update even if there are no any changes
|
||||
|
||||
## [v1.65.3](https://github.com/wanderer-industries/wanderer/compare/v1.65.2...v1.65.3) (2025-05-23)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signature: Fixed signature clenup
|
||||
|
||||
## [v1.65.2](https://github.com/wanderer-industries/wanderer/compare/v1.65.1...v1.65.2) (2025-05-23)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Signature: Fixed signature updates
|
||||
|
||||
## [v1.65.1](https://github.com/wanderer-industries/wanderer/compare/v1.65.0...v1.65.1) (2025-05-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* Core: Added unsync map events timeout handling (force page refresh if outdated map events found)
|
||||
|
||||
## [v1.65.0](https://github.com/wanderer-industries/wanderer/compare/v1.64.8...v1.65.0) (2025-05-22)
|
||||
|
||||
|
||||
|
||||
|
||||
### Features:
|
||||
|
||||
* default connections from c1 holes to medium size
|
||||
|
||||
* support german and french signatures
|
||||
|
||||
* improve signature undo process
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
* remove required id field from character schema
|
||||
|
||||
* update openapi spec response types
|
||||
|
||||
* fix issue with connection generation between k-space
|
||||
|
||||
* Signature: Fixed signatures updates
|
||||
|
||||
* update openapi spec for other apis
|
||||
|
||||
## [v1.64.8](https://github.com/wanderer-industries/wanderer/compare/v1.64.7...v1.64.8) (2025-05-20)
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface SignatureViewProps {
|
||||
export const SignatureView = ({ signature, showCharacterPortrait = false }: SignatureViewProps) => {
|
||||
const isWormhole = signature?.group === SignatureGroup.Wormhole;
|
||||
const hasCharacterInfo = showCharacterPortrait && signature.character_eve_id;
|
||||
const groupDisplay = isWormhole ? SignatureGroup.Wormhole : (signature?.group ?? SignatureGroup.CosmicSignature);
|
||||
const groupDisplay = isWormhole ? SignatureGroup.Wormhole : signature?.group ?? SignatureGroup.CosmicSignature;
|
||||
const characterName = signature.character_name || 'Unknown character';
|
||||
|
||||
return (
|
||||
|
||||
@@ -19,7 +19,7 @@ export type HeaderProps = {
|
||||
lazyDeleteValue: boolean;
|
||||
onLazyDeleteChange: (checked: boolean) => void;
|
||||
pendingCount: number;
|
||||
pendingTimeRemaining?: number; // Time remaining in ms
|
||||
undoCountdown?: number;
|
||||
onUndoClick: () => void;
|
||||
onSettingsClick: () => void;
|
||||
};
|
||||
@@ -29,7 +29,7 @@ export const SystemSignaturesHeader = ({
|
||||
lazyDeleteValue,
|
||||
onLazyDeleteChange,
|
||||
pendingCount,
|
||||
pendingTimeRemaining,
|
||||
undoCountdown,
|
||||
onUndoClick,
|
||||
onSettingsClick,
|
||||
}: HeaderProps) => {
|
||||
@@ -43,13 +43,6 @@ export const SystemSignaturesHeader = ({
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const isCompact = useMaxWidth(containerRef, COMPACT_MAX_WIDTH);
|
||||
|
||||
// Format time remaining as seconds
|
||||
const formatTimeRemaining = () => {
|
||||
if (!pendingTimeRemaining) return '';
|
||||
const seconds = Math.ceil(pendingTimeRemaining / 1000);
|
||||
return ` (${seconds}s remaining)`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="w-full">
|
||||
<div className="flex justify-between items-center text-xs w-full h-full">
|
||||
@@ -78,7 +71,9 @@ export const SystemSignaturesHeader = ({
|
||||
<WdImgButton
|
||||
className={PrimeIcons.UNDO}
|
||||
style={{ color: 'red' }}
|
||||
tooltip={{ content: `Undo pending changes (${pendingCount})${formatTimeRemaining()}` }}
|
||||
tooltip={{
|
||||
content: `Undo pending deletions (${pendingCount})${undoCountdown && undoCountdown > 0 ? ` — ${undoCountdown}s left` : ''}`,
|
||||
}}
|
||||
onClick={onUndoClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,99 +1,156 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { Widget } from '@/hooks/Mapper/components/mapInterface/components';
|
||||
import { SystemSignaturesContent } from './SystemSignaturesContent';
|
||||
import { SystemSignatureSettingsDialog } from './SystemSignatureSettingsDialog';
|
||||
import { ExtendedSystemSignature, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks';
|
||||
import { SystemSignaturesHeader } from './SystemSignatureHeader';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import { useHotkey } from '@/hooks/Mapper/hooks/useHotkey';
|
||||
import {
|
||||
SETTINGS_KEYS,
|
||||
SETTINGS_VALUES,
|
||||
SIGNATURE_DELETION_TIMEOUTS,
|
||||
SIGNATURE_SETTING_STORE_KEY,
|
||||
SIGNATURE_WINDOW_ID,
|
||||
SIGNATURES_DELETION_TIMING,
|
||||
SignatureSettingsType,
|
||||
SIGNATURES_DELETION_TIMING,
|
||||
SIGNATURE_DELETION_TIMEOUTS,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { calculateTimeRemaining } from './helpers';
|
||||
import { OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers';
|
||||
|
||||
export const SystemSignatures = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [sigCount, setSigCount] = useState<number>(0);
|
||||
const [pendingSigs, setPendingSigs] = useState<SystemSignature[]>([]);
|
||||
const [pendingTimeRemaining, setPendingTimeRemaining] = useState<number | undefined>();
|
||||
const undoPendingFnRef = useRef<() => void>(() => {});
|
||||
/**
|
||||
* Custom hook for managing pending signature deletions and undo countdown.
|
||||
*/
|
||||
function useSignatureUndo(
|
||||
systemId: string | undefined,
|
||||
settings: SignatureSettingsType,
|
||||
outCommand: OutCommandHandler,
|
||||
) {
|
||||
const [countdown, setCountdown] = useState<number>(0);
|
||||
const [pendingIds, setPendingIds] = useState<Set<string>>(new Set());
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
} = useMapRootState();
|
||||
|
||||
const [currentSettings, setCurrentSettings] = useLocalStorageState(SIGNATURE_SETTING_STORE_KEY, {
|
||||
defaultValue: SETTINGS_VALUES,
|
||||
});
|
||||
|
||||
const handleSigCountChange = useCallback((count: number) => {
|
||||
setSigCount(count);
|
||||
const addDeleted = useCallback((ids: string[]) => {
|
||||
setPendingIds(prev => {
|
||||
const next = new Set(prev);
|
||||
ids.forEach(id => next.add(id));
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
const isNotSelectedSystem = selectedSystems.length !== 1;
|
||||
|
||||
const handleSettingsChange = useCallback((newSettings: SignatureSettingsType) => {
|
||||
setCurrentSettings(newSettings);
|
||||
setVisible(false);
|
||||
}, []);
|
||||
|
||||
const handleLazyDeleteChange = useCallback((value: boolean) => {
|
||||
setCurrentSettings(prev => ({ ...prev, [SETTINGS_KEYS.LAZY_DELETE_SIGNATURES]: value }));
|
||||
}, []);
|
||||
|
||||
useHotkey(true, ['z'], event => {
|
||||
if (pendingSigs.length > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
undoPendingFnRef.current();
|
||||
setPendingSigs([]);
|
||||
setPendingTimeRemaining(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const handleUndoClick = useCallback(() => {
|
||||
undoPendingFnRef.current();
|
||||
setPendingSigs([]);
|
||||
setPendingTimeRemaining(undefined);
|
||||
}, []);
|
||||
|
||||
const handleSettingsButtonClick = useCallback(() => {
|
||||
setVisible(true);
|
||||
}, []);
|
||||
|
||||
const handlePendingChange = useCallback(
|
||||
(pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>, newUndo: () => void) => {
|
||||
setPendingSigs(() => {
|
||||
return Object.values(pending.current).filter(sig => sig.pendingDeletion);
|
||||
});
|
||||
undoPendingFnRef.current = newUndo;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// Calculate the minimum time remaining for any pending signature
|
||||
// kick off or clear countdown whenever pendingIds changes
|
||||
useEffect(() => {
|
||||
if (pendingSigs.length === 0) {
|
||||
setPendingTimeRemaining(undefined);
|
||||
// clear any existing timer
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
|
||||
if (pendingIds.size === 0) {
|
||||
setCountdown(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const calculate = () => {
|
||||
setPendingTimeRemaining(() => calculateTimeRemaining(pendingSigs));
|
||||
};
|
||||
// determine timeout from settings
|
||||
const timingKey = Number(settings[SETTINGS_KEYS.DELETION_TIMING] ?? SIGNATURES_DELETION_TIMING.DEFAULT);
|
||||
const timeoutMs =
|
||||
Number(SIGNATURE_DELETION_TIMEOUTS[timingKey as keyof typeof SIGNATURE_DELETION_TIMEOUTS]) || 10000;
|
||||
setCountdown(Math.ceil(timeoutMs / 1000));
|
||||
|
||||
calculate();
|
||||
const interval = setInterval(calculate, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [pendingSigs]);
|
||||
// start new interval
|
||||
intervalRef.current = window.setInterval(() => {
|
||||
setCountdown(prev => {
|
||||
if (prev <= 1) {
|
||||
clearInterval(intervalRef.current!);
|
||||
intervalRef.current = null;
|
||||
setPendingIds(new Set());
|
||||
return 0;
|
||||
}
|
||||
return prev - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [pendingIds, settings[SETTINGS_KEYS.DELETION_TIMING]]);
|
||||
|
||||
// undo handler
|
||||
const handleUndo = useCallback(async () => {
|
||||
if (!systemId || pendingIds.size === 0) return;
|
||||
await outCommand({
|
||||
type: OutCommand.undoDeleteSignatures,
|
||||
data: { system_id: systemId, eve_ids: Array.from(pendingIds) },
|
||||
});
|
||||
setPendingIds(new Set());
|
||||
setCountdown(0);
|
||||
if (intervalRef.current != null) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}, [systemId, pendingIds, outCommand]);
|
||||
|
||||
return {
|
||||
pendingIds,
|
||||
countdown,
|
||||
addDeleted,
|
||||
handleUndo,
|
||||
};
|
||||
}
|
||||
|
||||
export const SystemSignatures = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [sigCount, setSigCount] = useState(0);
|
||||
|
||||
const {
|
||||
data: { selectedSystems },
|
||||
outCommand,
|
||||
} = useMapRootState();
|
||||
|
||||
const [currentSettings, setCurrentSettings] = useLocalStorageState<SignatureSettingsType>(
|
||||
SIGNATURE_SETTING_STORE_KEY,
|
||||
{
|
||||
defaultValue: SETTINGS_VALUES,
|
||||
},
|
||||
);
|
||||
|
||||
const [systemId] = selectedSystems;
|
||||
const isSystemSelected = useMemo(() => selectedSystems.length === 1, [selectedSystems.length]);
|
||||
const { pendingIds, countdown, addDeleted, handleUndo } = useSignatureUndo(systemId, currentSettings, outCommand);
|
||||
|
||||
useHotkey(true, ['z', 'Z'], (event: KeyboardEvent) => {
|
||||
if (pendingIds.size > 0 && countdown > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleUndo();
|
||||
}
|
||||
});
|
||||
|
||||
const handleCountChange = useCallback((count: number) => {
|
||||
setSigCount(count);
|
||||
}, []);
|
||||
|
||||
const handleSettingsSave = useCallback(
|
||||
(newSettings: SignatureSettingsType) => {
|
||||
setCurrentSettings(newSettings);
|
||||
setVisible(false);
|
||||
},
|
||||
[setCurrentSettings],
|
||||
);
|
||||
|
||||
const handleLazyDeleteToggle = useCallback(
|
||||
(value: boolean) => {
|
||||
setCurrentSettings(prev => ({
|
||||
...prev,
|
||||
[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES]: value,
|
||||
}));
|
||||
},
|
||||
[setCurrentSettings],
|
||||
);
|
||||
|
||||
const openSettings = useCallback(() => setVisible(true), []);
|
||||
|
||||
return (
|
||||
<Widget
|
||||
@@ -101,16 +158,16 @@ export const SystemSignatures = () => {
|
||||
<SystemSignaturesHeader
|
||||
sigCount={sigCount}
|
||||
lazyDeleteValue={currentSettings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean}
|
||||
pendingCount={pendingSigs.length}
|
||||
pendingTimeRemaining={pendingTimeRemaining}
|
||||
onLazyDeleteChange={handleLazyDeleteChange}
|
||||
onUndoClick={handleUndoClick}
|
||||
onSettingsClick={handleSettingsButtonClick}
|
||||
pendingCount={pendingIds.size}
|
||||
undoCountdown={countdown}
|
||||
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||
onUndoClick={handleUndo}
|
||||
onSettingsClick={openSettings}
|
||||
/>
|
||||
}
|
||||
windowId={SIGNATURE_WINDOW_ID}
|
||||
>
|
||||
{isNotSelectedSystem ? (
|
||||
{!isSystemSelected ? (
|
||||
<div className="w-full h-full flex justify-center items-center select-none text-center text-stone-400/80 text-sm">
|
||||
System is not selected
|
||||
</div>
|
||||
@@ -118,22 +175,17 @@ export const SystemSignatures = () => {
|
||||
<SystemSignaturesContent
|
||||
systemId={systemId}
|
||||
settings={currentSettings}
|
||||
deletionTiming={
|
||||
SIGNATURE_DELETION_TIMEOUTS[
|
||||
(currentSettings[SETTINGS_KEYS.DELETION_TIMING] as keyof typeof SIGNATURE_DELETION_TIMEOUTS) ||
|
||||
SIGNATURES_DELETION_TIMING.DEFAULT
|
||||
] as number
|
||||
}
|
||||
onLazyDeleteChange={handleLazyDeleteChange}
|
||||
onCountChange={handleSigCountChange}
|
||||
onPendingChange={handlePendingChange}
|
||||
onLazyDeleteChange={handleLazyDeleteToggle}
|
||||
onCountChange={handleCountChange}
|
||||
onSignatureDeleted={addDeleted}
|
||||
/>
|
||||
)}
|
||||
|
||||
{visible && (
|
||||
<SystemSignatureSettingsDialog
|
||||
settings={currentSettings}
|
||||
onCancel={() => setVisible(false)}
|
||||
onSave={handleSettingsChange}
|
||||
onSave={handleSettingsSave}
|
||||
/>
|
||||
)}
|
||||
</Widget>
|
||||
|
||||
@@ -57,12 +57,8 @@ interface SystemSignaturesContentProps {
|
||||
onSelect?: (signature: SystemSignature) => void;
|
||||
onLazyDeleteChange?: (value: boolean) => void;
|
||||
onCountChange?: (count: number) => void;
|
||||
onPendingChange?: (
|
||||
pending: React.MutableRefObject<Record<string, ExtendedSystemSignature>>,
|
||||
undo: () => void,
|
||||
) => void;
|
||||
deletionTiming?: number;
|
||||
filterSignature?: (signature: SystemSignature) => boolean;
|
||||
onSignatureDeleted?: (deletedIds: string[]) => void;
|
||||
}
|
||||
|
||||
export const SystemSignaturesContent = ({
|
||||
@@ -73,9 +69,8 @@ export const SystemSignaturesContent = ({
|
||||
onSelect,
|
||||
onLazyDeleteChange,
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
deletionTiming,
|
||||
filterSignature,
|
||||
onSignatureDeleted,
|
||||
}: SystemSignaturesContentProps) => {
|
||||
const [selectedSignatureForDialog, setSelectedSignatureForDialog] = useState<SystemSignature | null>(null);
|
||||
const [showSignatureSettings, setShowSignatureSettings] = useState(false);
|
||||
@@ -95,15 +90,21 @@ export const SystemSignaturesContent = ({
|
||||
{ defaultValue: SORT_DEFAULT_VALUES },
|
||||
);
|
||||
|
||||
const { signatures, selectedSignatures, setSelectedSignatures, handleDeleteSelected, handleSelectAll, handlePaste } =
|
||||
useSystemSignaturesData({
|
||||
systemId,
|
||||
settings,
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
onLazyDeleteChange,
|
||||
deletionTiming,
|
||||
});
|
||||
const {
|
||||
signatures,
|
||||
selectedSignatures,
|
||||
setSelectedSignatures,
|
||||
handleDeleteSelected,
|
||||
handleSelectAll,
|
||||
handlePaste,
|
||||
hasUnsupportedLanguage,
|
||||
} = useSystemSignaturesData({
|
||||
systemId,
|
||||
settings,
|
||||
onCountChange,
|
||||
onLazyDeleteChange,
|
||||
onSignatureDeleted,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (selectable) return;
|
||||
@@ -125,6 +126,10 @@ export const SystemSignaturesContent = ({
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (onSignatureDeleted && selectedSignatures.length > 0) {
|
||||
const deletedIds = selectedSignatures.map(s => s.eve_id);
|
||||
onSignatureDeleted(deletedIds);
|
||||
}
|
||||
handleDeleteSelected();
|
||||
});
|
||||
|
||||
@@ -155,7 +160,7 @@ export const SystemSignaturesContent = ({
|
||||
(e: { value: SystemSignature[] }) => {
|
||||
selectable ? onSelect?.(e.value[0]) : setSelectedSignatures(e.value as ExtendedSystemSignature[]);
|
||||
},
|
||||
[selectable],
|
||||
[onSelect, selectable, setSelectedSignatures],
|
||||
);
|
||||
|
||||
const { showDescriptionColumn, showUpdatedColumn, showCharacterColumn, showCharacterPortrait } = useMemo(
|
||||
@@ -188,7 +193,11 @@ export const SystemSignaturesContent = ({
|
||||
x => GROUPS_LIST.includes(x as SignatureGroup) && settings[x as SETTINGS_KEYS],
|
||||
);
|
||||
|
||||
return enabledGroups.includes(getGroupIdByRawGroup(sig.group));
|
||||
const mappedGroup = getGroupIdByRawGroup(sig.group);
|
||||
if (!mappedGroup) {
|
||||
return true; // If we can't determine the group, still show it
|
||||
}
|
||||
return enabledGroups.includes(mappedGroup);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -236,113 +245,121 @@ export const SystemSignaturesContent = ({
|
||||
No signatures
|
||||
</div>
|
||||
) : (
|
||||
<DataTable
|
||||
value={filteredSignatures}
|
||||
size="small"
|
||||
selectionMode="multiple"
|
||||
selection={selectedSignatures}
|
||||
metaKeySelection
|
||||
onSelectionChange={handleSelectSignatures}
|
||||
dataKey="eve_id"
|
||||
className="w-full select-none"
|
||||
resizableColumns={false}
|
||||
rowHover
|
||||
selectAll
|
||||
onRowDoubleClick={handleRowClick}
|
||||
sortField={sortSettings.sortField}
|
||||
sortOrder={sortSettings.sortOrder}
|
||||
onSort={handleSortSettings}
|
||||
onRowMouseEnter={onRowMouseEnter}
|
||||
onRowMouseLeave={onRowMouseLeave}
|
||||
// @ts-ignore
|
||||
rowClassName={getRowClassName}
|
||||
>
|
||||
<Column
|
||||
field="icon"
|
||||
header=""
|
||||
body={renderColIcon}
|
||||
bodyClassName="p-0 px-1"
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||
/>
|
||||
<Column
|
||||
field="eve_id"
|
||||
header="Id"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
|
||||
sortable
|
||||
/>
|
||||
<Column
|
||||
field="group"
|
||||
header="Group"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
|
||||
body={sig => sig.group ?? ''}
|
||||
hidden={isCompact}
|
||||
sortable
|
||||
/>
|
||||
<Column
|
||||
field="info"
|
||||
header="Info"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
hidden={isCompact || isMedium}
|
||||
body={renderInfoColumn}
|
||||
/>
|
||||
{showDescriptionColumn && (
|
||||
<Column
|
||||
field="description"
|
||||
header="Description"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
hidden={isCompact}
|
||||
body={renderDescription}
|
||||
sortable
|
||||
/>
|
||||
<>
|
||||
{hasUnsupportedLanguage && (
|
||||
<div className="w-full flex justify-center items-center text-amber-500 text-xs p-1 bg-amber-950/20 border-b border-amber-800/30">
|
||||
<i className={PrimeIcons.EXCLAMATION_TRIANGLE + ' mr-1'} />
|
||||
Non-English signatures detected. Some signatures may not display correctly. Double-click to edit signature details.
|
||||
</div>
|
||||
)}
|
||||
<Column
|
||||
field="inserted_at"
|
||||
header="Added"
|
||||
dataType="date"
|
||||
body={renderAddedTimeLeft}
|
||||
style={{ minWidth: 70, maxWidth: 80 }}
|
||||
bodyClassName="ssc-header text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
/>
|
||||
{showUpdatedColumn && (
|
||||
<Column
|
||||
field="updated_at"
|
||||
header="Updated"
|
||||
dataType="date"
|
||||
body={renderUpdatedTimeLeft}
|
||||
style={{ minWidth: 70, maxWidth: 80 }}
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
/>
|
||||
)}
|
||||
|
||||
{showCharacterColumn && (
|
||||
<Column
|
||||
field="character_name"
|
||||
header="Character"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
></Column>
|
||||
)}
|
||||
|
||||
{!selectable && (
|
||||
<DataTable
|
||||
value={filteredSignatures}
|
||||
size="small"
|
||||
selectionMode="multiple"
|
||||
selection={selectedSignatures}
|
||||
metaKeySelection
|
||||
onSelectionChange={handleSelectSignatures}
|
||||
dataKey="eve_id"
|
||||
className="w-full select-none"
|
||||
resizableColumns={false}
|
||||
rowHover
|
||||
selectAll
|
||||
onRowDoubleClick={handleRowClick}
|
||||
sortField={sortSettings.sortField}
|
||||
sortOrder={sortSettings.sortOrder}
|
||||
onSort={handleSortSettings}
|
||||
onRowMouseEnter={onRowMouseEnter}
|
||||
onRowMouseLeave={onRowMouseLeave}
|
||||
// @ts-ignore
|
||||
rowClassName={getRowClassName}
|
||||
>
|
||||
<Column
|
||||
field="icon"
|
||||
header=""
|
||||
body={() => (
|
||||
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
||||
<WdTooltipWrapper content="Double-click a row to edit signature">
|
||||
<span className={PrimeIcons.PENCIL + ' text-[10px]'} />
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
)}
|
||||
body={renderColIcon}
|
||||
bodyClassName="p-0 px-1"
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||
bodyClassName="p-0 pl-1 pr-2"
|
||||
/>
|
||||
)}
|
||||
</DataTable>
|
||||
<Column
|
||||
field="eve_id"
|
||||
header="Id"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: 72, minWidth: 72, width: 72 }}
|
||||
sortable
|
||||
/>
|
||||
<Column
|
||||
field="group"
|
||||
header="Group"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: 110, minWidth: 110, width: 110 }}
|
||||
body={sig => sig.group ?? ''}
|
||||
hidden={isCompact}
|
||||
sortable
|
||||
/>
|
||||
<Column
|
||||
field="info"
|
||||
header="Info"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
style={{ maxWidth: nameColumnWidth }}
|
||||
hidden={isCompact || isMedium}
|
||||
body={renderInfoColumn}
|
||||
/>
|
||||
{showDescriptionColumn && (
|
||||
<Column
|
||||
field="description"
|
||||
header="Description"
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
hidden={isCompact}
|
||||
body={renderDescription}
|
||||
sortable
|
||||
/>
|
||||
)}
|
||||
<Column
|
||||
field="inserted_at"
|
||||
header="Added"
|
||||
dataType="date"
|
||||
body={renderAddedTimeLeft}
|
||||
style={{ minWidth: 70, maxWidth: 80 }}
|
||||
bodyClassName="ssc-header text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
/>
|
||||
{showUpdatedColumn && (
|
||||
<Column
|
||||
field="updated_at"
|
||||
header="Updated"
|
||||
dataType="date"
|
||||
body={renderUpdatedTimeLeft}
|
||||
style={{ minWidth: 70, maxWidth: 80 }}
|
||||
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
/>
|
||||
)}
|
||||
|
||||
{showCharacterColumn && (
|
||||
<Column
|
||||
field="character_name"
|
||||
header="Character"
|
||||
bodyClassName="w-[70px] text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
sortable
|
||||
></Column>
|
||||
)}
|
||||
|
||||
{!selectable && (
|
||||
<Column
|
||||
header=""
|
||||
body={() => (
|
||||
<div className="flex justify-end items-center gap-2 mr-[4px]">
|
||||
<WdTooltipWrapper content="Double-click a row to edit signature">
|
||||
<span className={PrimeIcons.PENCIL + ' text-[10px]'} />
|
||||
</WdTooltipWrapper>
|
||||
</div>
|
||||
)}
|
||||
style={{ maxWidth: 26, minWidth: 26, width: 26 }}
|
||||
bodyClassName="p-0 pl-1 pr-2"
|
||||
/>
|
||||
)}
|
||||
</DataTable>
|
||||
</>
|
||||
)}
|
||||
|
||||
<WdTooltip
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import {
|
||||
GroupType,
|
||||
SignatureGroup,
|
||||
SignatureGroupDE,
|
||||
SignatureGroupENG,
|
||||
SignatureGroupFR,
|
||||
SignatureGroupRU,
|
||||
SignatureKind,
|
||||
SignatureKindDE,
|
||||
SignatureKindENG,
|
||||
SignatureKindFR,
|
||||
SignatureKindRU,
|
||||
} from '@/hooks/Mapper/types';
|
||||
|
||||
@@ -40,46 +44,58 @@ export const GROUPS: Record<SignatureGroup, GroupType> = {
|
||||
[SignatureGroup.CosmicSignature]: { id: SignatureGroup.CosmicSignature, icon: '/icons/x_close14.png', w: 9, h: 9 },
|
||||
};
|
||||
|
||||
export const MAPPING_GROUP_TO_ENG = {
|
||||
// ENGLISH
|
||||
[SignatureGroupENG.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupENG.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupENG.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupENG.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupENG.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupENG.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupENG.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
|
||||
// RUSSIAN
|
||||
[SignatureGroupRU.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupRU.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupRU.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupRU.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupRU.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupRU.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupRU.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
export const LANGUAGE_GROUP_MAPPINGS = {
|
||||
EN: {
|
||||
[SignatureGroupENG.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupENG.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupENG.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupENG.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupENG.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupENG.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupENG.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
},
|
||||
RU: {
|
||||
[SignatureGroupRU.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupRU.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupRU.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupRU.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupRU.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupRU.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupRU.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
},
|
||||
FR: {
|
||||
[SignatureGroupFR.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupFR.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupFR.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupFR.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupFR.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupFR.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupFR.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
},
|
||||
DE: {
|
||||
[SignatureGroupDE.GasSite]: SignatureGroup.GasSite,
|
||||
[SignatureGroupDE.RelicSite]: SignatureGroup.RelicSite,
|
||||
[SignatureGroupDE.DataSite]: SignatureGroup.DataSite,
|
||||
[SignatureGroupDE.OreSite]: SignatureGroup.OreSite,
|
||||
[SignatureGroupDE.CombatSite]: SignatureGroup.CombatSite,
|
||||
[SignatureGroupDE.Wormhole]: SignatureGroup.Wormhole,
|
||||
[SignatureGroupDE.CosmicSignature]: SignatureGroup.CosmicSignature,
|
||||
},
|
||||
};
|
||||
|
||||
export const MAPPING_TYPE_TO_ENG = {
|
||||
// ENGLISH
|
||||
[SignatureKindENG.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindENG.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindENG.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindENG.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindENG.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindENG.Drone]: SignatureKind.Drone,
|
||||
// Flatten the structure for backward compatibility
|
||||
export const MAPPING_GROUP_TO_ENG: Record<string, SignatureGroup> = (() => {
|
||||
const flattened: Record<string, SignatureGroup> = {};
|
||||
for (const [, mappings] of Object.entries(LANGUAGE_GROUP_MAPPINGS)) {
|
||||
Object.assign(flattened, mappings);
|
||||
}
|
||||
return flattened;
|
||||
})();
|
||||
|
||||
// RUSSIAN
|
||||
[SignatureKindRU.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindRU.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindRU.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindRU.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindRU.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindRU.Drone]: SignatureKind.Drone,
|
||||
export const getGroupIdByRawGroup = (val: string): SignatureGroup | undefined => {
|
||||
return MAPPING_GROUP_TO_ENG[val] || undefined;
|
||||
};
|
||||
|
||||
export const getGroupIdByRawGroup = (val: string) => MAPPING_GROUP_TO_ENG[val as SignatureGroup];
|
||||
|
||||
export const SIGNATURE_WINDOW_ID = 'system_signatures_window';
|
||||
export const SIGNATURE_SETTING_STORE_KEY = 'wanderer_system_signature_settings_v6_5';
|
||||
|
||||
@@ -123,7 +139,7 @@ export type Setting = {
|
||||
name: string;
|
||||
type: SettingsTypes;
|
||||
isSeparator?: boolean;
|
||||
options?: { label: string; value: any }[];
|
||||
options?: { label: string; value: number | string | boolean }[];
|
||||
};
|
||||
|
||||
export enum SIGNATURES_DELETION_TIMING {
|
||||
@@ -208,3 +224,52 @@ export const SIGNATURE_DELETION_TIMEOUTS: SignatureDeletionTimingType = {
|
||||
[SIGNATURES_DELETION_TIMING.IMMEDIATE]: 0,
|
||||
[SIGNATURES_DELETION_TIMING.EXTENDED]: 30_000,
|
||||
};
|
||||
|
||||
// Replace the flat structure with a nested structure by language
|
||||
export const LANGUAGE_TYPE_MAPPINGS = {
|
||||
EN: {
|
||||
[SignatureKindENG.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindENG.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindENG.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindENG.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindENG.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindENG.Drone]: SignatureKind.Drone,
|
||||
[SignatureKindENG.Starbase]: SignatureKind.Starbase,
|
||||
},
|
||||
RU: {
|
||||
[SignatureKindRU.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindRU.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindRU.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindRU.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindRU.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindRU.Drone]: SignatureKind.Drone,
|
||||
[SignatureKindRU.Starbase]: SignatureKind.Starbase,
|
||||
},
|
||||
FR: {
|
||||
[SignatureKindFR.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindFR.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindFR.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindFR.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindFR.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindFR.Drone]: SignatureKind.Drone,
|
||||
[SignatureKindFR.Starbase]: SignatureKind.Starbase,
|
||||
},
|
||||
DE: {
|
||||
[SignatureKindDE.CosmicSignature]: SignatureKind.CosmicSignature,
|
||||
[SignatureKindDE.CosmicAnomaly]: SignatureKind.CosmicAnomaly,
|
||||
[SignatureKindDE.Structure]: SignatureKind.Structure,
|
||||
[SignatureKindDE.Ship]: SignatureKind.Ship,
|
||||
[SignatureKindDE.Deployable]: SignatureKind.Deployable,
|
||||
[SignatureKindDE.Drone]: SignatureKind.Drone,
|
||||
[SignatureKindDE.Starbase]: SignatureKind.Starbase,
|
||||
},
|
||||
};
|
||||
|
||||
// Flatten the structure for backward compatibility
|
||||
export const MAPPING_TYPE_TO_ENG: Record<string, SignatureKind> = (() => {
|
||||
const flattened: Record<string, SignatureKind> = {};
|
||||
for (const [, mappings] of Object.entries(LANGUAGE_TYPE_MAPPINGS)) {
|
||||
Object.assign(flattened, mappings);
|
||||
}
|
||||
return flattened;
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { GROUPS_LIST } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants';
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { getState } from './getState';
|
||||
|
||||
/**
|
||||
@@ -22,6 +22,7 @@ export const getActualSigs = (
|
||||
|
||||
oldSignatures.forEach(oldSig => {
|
||||
const newSig = newSignatures.find(s => s.eve_id === oldSig.eve_id);
|
||||
|
||||
if (newSig) {
|
||||
const needUpgrade = getState(GROUPS_LIST, newSig) > getState(GROUPS_LIST, oldSig);
|
||||
const mergedSig = { ...oldSig };
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { UNKNOWN_SIGNATURE_NAME } from '@/hooks/Mapper/helpers';
|
||||
import { SignatureGroup, SystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export const getState = (_: string[], newSig: SystemSignature) => {
|
||||
let state = -1;
|
||||
if (!newSig.group) {
|
||||
if (!newSig.group || newSig.group === SignatureGroup.CosmicSignature) {
|
||||
state = 0;
|
||||
} else if (!newSig.name || newSig.name === '') {
|
||||
} else if (!newSig.name || newSig.name === '' || newSig.name === UNKNOWN_SIGNATURE_NAME) {
|
||||
state = 1;
|
||||
} else if (newSig.name !== '') {
|
||||
state = 2;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
import { useCallback, useRef, useEffect } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers';
|
||||
import { prepareUpdatePayload, scheduleLazyTimers } from '../helpers';
|
||||
import { prepareUpdatePayload } from '../helpers';
|
||||
import { UsePendingDeletionParams } from './types';
|
||||
import { FINAL_DURATION_MS } from '../constants';
|
||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||
import { ExtendedSystemSignature } from '@/hooks/Mapper/types';
|
||||
|
||||
export function usePendingDeletions({
|
||||
systemId,
|
||||
setSignatures,
|
||||
deletionTiming,
|
||||
onPendingChange,
|
||||
}: UsePendingDeletionParams) {
|
||||
}: Omit<UsePendingDeletionParams, 'deletionTiming'>) {
|
||||
const { outCommand } = useMapRootState();
|
||||
const pendingDeletionMapRef = useRef<Record<string, ExtendedSystemSignature>>({});
|
||||
|
||||
// Use the provided deletion timing or fall back to the default
|
||||
const finalDuration = deletionTiming !== undefined ? deletionTiming : FINAL_DURATION_MS;
|
||||
|
||||
const processRemovedSignatures = useCallback(
|
||||
async (
|
||||
removed: ExtendedSystemSignature[],
|
||||
@@ -25,63 +20,15 @@ export function usePendingDeletions({
|
||||
updated: ExtendedSystemSignature[],
|
||||
) => {
|
||||
if (!removed.length) return;
|
||||
|
||||
// If deletion timing is 0, immediately delete without pending state
|
||||
if (finalDuration === 0) {
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, added, updated, removed),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const processedRemoved = removed.map(r => ({
|
||||
...r,
|
||||
pendingDeletion: true,
|
||||
pendingUntil: now + finalDuration,
|
||||
}));
|
||||
pendingDeletionMapRef.current = {
|
||||
...pendingDeletionMapRef.current,
|
||||
...processedRemoved.reduce((acc: any, sig) => {
|
||||
acc[sig.eve_id] = sig;
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
||||
|
||||
setSignatures(prev =>
|
||||
prev.map(sig => {
|
||||
if (processedRemoved.find(r => r.eve_id === sig.eve_id)) {
|
||||
return { ...sig, pendingDeletion: true, pendingUntil: now + finalDuration };
|
||||
}
|
||||
return sig;
|
||||
}),
|
||||
);
|
||||
|
||||
scheduleLazyTimers(
|
||||
processedRemoved,
|
||||
pendingDeletionMapRef,
|
||||
async sig => {
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, [], [], [sig]),
|
||||
});
|
||||
delete pendingDeletionMapRef.current[sig.eve_id];
|
||||
setSignatures(prev => prev.filter(x => x.eve_id !== sig.eve_id));
|
||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
||||
},
|
||||
finalDuration,
|
||||
);
|
||||
await outCommand({
|
||||
type: OutCommand.updateSignatures,
|
||||
data: prepareUpdatePayload(systemId, added, updated, removed),
|
||||
});
|
||||
},
|
||||
[systemId, outCommand, finalDuration],
|
||||
[systemId, outCommand],
|
||||
);
|
||||
|
||||
const clearPendingDeletions = useCallback(() => {
|
||||
Object.values(pendingDeletionMapRef.current).forEach(({ finalTimeoutId }) => {
|
||||
clearTimeout(finalTimeoutId);
|
||||
});
|
||||
pendingDeletionMapRef.current = {};
|
||||
setSignatures(prev => prev.map(x => (x.pendingDeletion ? { ...x, pendingDeletion: false } : x)));
|
||||
onPendingChange?.(pendingDeletionMapRef, clearPendingDeletions);
|
||||
|
||||
@@ -18,16 +18,18 @@ export const useSystemSignaturesData = ({
|
||||
onCountChange,
|
||||
onPendingChange,
|
||||
onLazyDeleteChange,
|
||||
deletionTiming,
|
||||
}: UseSystemSignaturesDataProps) => {
|
||||
onSignatureDeleted,
|
||||
}: Omit<UseSystemSignaturesDataProps, 'deletionTiming'> & {
|
||||
onSignatureDeleted?: (deletedIds: string[]) => void;
|
||||
}) => {
|
||||
const { outCommand } = useMapRootState();
|
||||
const [signatures, setSignatures, signaturesRef] = useRefState<ExtendedSystemSignature[]>([]);
|
||||
const [selectedSignatures, setSelectedSignatures] = useState<ExtendedSystemSignature[]>([]);
|
||||
const [hasUnsupportedLanguage, setHasUnsupportedLanguage] = useState<boolean>(false);
|
||||
|
||||
const { pendingDeletionMapRef, processRemovedSignatures, clearPendingDeletions } = usePendingDeletions({
|
||||
systemId,
|
||||
setSignatures,
|
||||
deletionTiming,
|
||||
onPendingChange,
|
||||
});
|
||||
|
||||
@@ -42,6 +44,7 @@ export const useSystemSignaturesData = ({
|
||||
async (clipboardString: string) => {
|
||||
const lazyDeleteValue = settings[SETTINGS_KEYS.LAZY_DELETE_SIGNATURES] as boolean;
|
||||
|
||||
// Parse the incoming signatures
|
||||
const incomingSignatures = parseSignatures(
|
||||
clipboardString,
|
||||
Object.keys(settings).filter(skey => skey in SignatureKind),
|
||||
@@ -51,14 +54,30 @@ export const useSystemSignaturesData = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if any signatures might be using unsupported languages
|
||||
// This is a basic heuristic: if we have signatures where the original group wasn't mapped
|
||||
const clipboardRows = clipboardString.split('\n').filter(row => row.trim() !== '');
|
||||
const detectedSignatureCount = clipboardRows.filter(row => row.match(/^[A-Z]{3}-\d{3}/)).length;
|
||||
|
||||
// If we detected valid IDs but got fewer parsed signatures, we might have language issues
|
||||
if (detectedSignatureCount > 0 && incomingSignatures.length < detectedSignatureCount) {
|
||||
setHasUnsupportedLanguage(true);
|
||||
} else {
|
||||
setHasUnsupportedLanguage(false);
|
||||
}
|
||||
|
||||
const currentNonPending = lazyDeleteValue
|
||||
? signaturesRef.current.filter(sig => !sig.pendingDeletion)
|
||||
: signaturesRef.current.filter(sig => !sig.pendingDeletion || !sig.pendingAddition);
|
||||
|
||||
const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, true);
|
||||
const { added, updated, removed } = getActualSigs(currentNonPending, incomingSignatures, !lazyDeleteValue, false);
|
||||
|
||||
if (removed.length > 0) {
|
||||
await processRemovedSignatures(removed, added, updated);
|
||||
if (onSignatureDeleted) {
|
||||
const deletedIds = removed.map(sig => sig.eve_id);
|
||||
onSignatureDeleted(deletedIds);
|
||||
}
|
||||
}
|
||||
|
||||
if (updated.length !== 0 || added.length !== 0) {
|
||||
@@ -78,17 +97,16 @@ export const useSystemSignaturesData = ({
|
||||
onLazyDeleteChange?.(false);
|
||||
}
|
||||
},
|
||||
[settings, signaturesRef, processRemovedSignatures, outCommand, systemId, onLazyDeleteChange],
|
||||
[settings, signaturesRef, processRemovedSignatures, outCommand, systemId, onLazyDeleteChange, onSignatureDeleted],
|
||||
);
|
||||
|
||||
const handleDeleteSelected = useCallback(async () => {
|
||||
if (!selectedSignatures.length) return;
|
||||
const selectedIds = selectedSignatures.map(s => s.eve_id);
|
||||
const finalList = signatures.filter(s => !selectedIds.includes(s.eve_id));
|
||||
|
||||
await handleUpdateSignatures(finalList, false, true);
|
||||
setSelectedSignatures([]);
|
||||
}, [selectedSignatures, signatures]);
|
||||
}, [handleUpdateSignatures, selectedSignatures, signatures]);
|
||||
|
||||
const handleSelectAll = useCallback(() => {
|
||||
setSelectedSignatures(signatures);
|
||||
@@ -119,11 +137,12 @@ export const useSystemSignaturesData = ({
|
||||
}, [signatures]);
|
||||
|
||||
return {
|
||||
signatures,
|
||||
signatures: signatures.filter(sig => !sig.deleted),
|
||||
selectedSignatures,
|
||||
setSelectedSignatures,
|
||||
handleDeleteSelected,
|
||||
handleSelectAll,
|
||||
handlePaste,
|
||||
hasUnsupportedLanguage,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import {
|
||||
MAPPING_GROUP_TO_ENG,
|
||||
MAPPING_TYPE_TO_ENG,
|
||||
} from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
import { SignatureGroup, SignatureKind, SystemSignature } from '@/hooks/Mapper/types';
|
||||
import { MAPPING_TYPE_TO_ENG } from '@/hooks/Mapper/components/mapInterface/widgets/SystemSignatures/constants.ts';
|
||||
|
||||
export const UNKNOWN_SIGNATURE_NAME = 'Unknown';
|
||||
|
||||
export const parseSignatures = (value: string, availableKeys: string[]): SystemSignature[] => {
|
||||
const outArr: SystemSignature[] = [];
|
||||
@@ -14,13 +19,39 @@ export const parseSignatures = (value: string, availableKeys: string[]): SystemS
|
||||
continue;
|
||||
}
|
||||
|
||||
const kind = MAPPING_TYPE_TO_ENG[sigArrInfo[1] as SignatureKind];
|
||||
// Extract the signature ID and check if it's valid (XXX-XXX format)
|
||||
const sigId = sigArrInfo[0];
|
||||
|
||||
if (!sigId || !sigId.match(/^[A-Z]{3}-\d{3}$/)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to map the kind, or fall back to CosmicSignature if unknown
|
||||
const typeString = sigArrInfo[1];
|
||||
let kind = SignatureKind.CosmicSignature;
|
||||
|
||||
// Try to map the kind using the flattened mapping
|
||||
const mappedKind = MAPPING_TYPE_TO_ENG[typeString];
|
||||
|
||||
if (mappedKind && availableKeys.includes(mappedKind)) {
|
||||
kind = mappedKind;
|
||||
}
|
||||
|
||||
// Try to map the group, or fall back to CosmicSignature if unknown
|
||||
const rawGroup = sigArrInfo[2];
|
||||
let group = SignatureGroup.CosmicSignature;
|
||||
|
||||
// Try to map the group using the flattened mapping
|
||||
const mappedGroup = MAPPING_GROUP_TO_ENG[rawGroup];
|
||||
if (mappedGroup) {
|
||||
group = mappedGroup;
|
||||
}
|
||||
|
||||
const signature: SystemSignature = {
|
||||
eve_id: sigArrInfo[0],
|
||||
kind: availableKeys.includes(kind) ? kind : SignatureKind.CosmicSignature,
|
||||
group: sigArrInfo[2] as SignatureGroup,
|
||||
name: sigArrInfo[3],
|
||||
eve_id: sigId,
|
||||
kind,
|
||||
group,
|
||||
name: sigArrInfo[3] || UNKNOWN_SIGNATURE_NAME,
|
||||
type: '',
|
||||
};
|
||||
|
||||
|
||||
@@ -242,15 +242,12 @@ export enum OutCommand {
|
||||
addSystemComment = 'addSystemComment',
|
||||
deleteSystemComment = 'deleteSystemComment',
|
||||
getSystemComments = 'getSystemComments',
|
||||
// toggleTrack = 'toggle_track',
|
||||
toggleFollow = 'toggle_follow',
|
||||
getCharacterInfo = 'getCharacterInfo',
|
||||
getCharactersTrackingInfo = 'getCharactersTrackingInfo',
|
||||
updateCharacterTracking = 'updateCharacterTracking',
|
||||
updateFollowingCharacter = 'updateFollowingCharacter',
|
||||
updateMainCharacter = 'updateMainCharacter',
|
||||
|
||||
// Only UI commands
|
||||
openSettings = 'open_settings',
|
||||
showActivity = 'show_activity',
|
||||
showTracking = 'show_tracking',
|
||||
@@ -258,7 +255,7 @@ export enum OutCommand {
|
||||
updateUserSettings = 'update_user_settings',
|
||||
unlinkSignature = 'unlink_signature',
|
||||
searchSystems = 'search_systems',
|
||||
undoDeleteSignatures = 'undo_delete_signatures',
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type OutCommandHandler = <T = any>(event: { type: OutCommand; data: any }) => Promise<T>;
|
||||
export type OutCommandHandler = <T = unknown>(event: { type: OutCommand; data: unknown }) => Promise<T>;
|
||||
|
||||
@@ -30,6 +30,7 @@ export type GroupType = {
|
||||
export type SignatureCustomInfo = {
|
||||
k162Type?: string;
|
||||
isEOL?: boolean;
|
||||
isCrit?: boolean;
|
||||
};
|
||||
|
||||
export type SystemSignature = {
|
||||
@@ -46,6 +47,7 @@ export type SystemSignature = {
|
||||
linked_system?: SolarSystemStaticInfoRaw;
|
||||
inserted_at?: string;
|
||||
updated_at?: string;
|
||||
deleted?: boolean;
|
||||
};
|
||||
|
||||
export interface ExtendedSystemSignature extends SystemSignature {
|
||||
@@ -53,6 +55,7 @@ export interface ExtendedSystemSignature extends SystemSignature {
|
||||
pendingAddition?: boolean;
|
||||
pendingUntil?: number;
|
||||
finalTimeoutId?: number;
|
||||
deleted?: boolean;
|
||||
}
|
||||
|
||||
export enum SignatureKindENG {
|
||||
@@ -75,6 +78,26 @@ export enum SignatureKindRU {
|
||||
Starbase = 'Starbase',
|
||||
}
|
||||
|
||||
export enum SignatureKindFR {
|
||||
CosmicSignature = 'Signature cosmique (type)',
|
||||
CosmicAnomaly = 'Anomalie cosmique',
|
||||
Structure = 'Structure',
|
||||
Ship = 'Vaisseau',
|
||||
Deployable = 'Déployable',
|
||||
Drone = 'Drone',
|
||||
Starbase = 'Base stellaire',
|
||||
}
|
||||
|
||||
export enum SignatureKindDE {
|
||||
CosmicSignature = 'Kosmische Signatur (typ)',
|
||||
CosmicAnomaly = 'Kosmische Anomalie',
|
||||
Structure = 'Struktur',
|
||||
Ship = 'Schiff',
|
||||
Deployable = 'Mobile Struktur',
|
||||
Drone = 'Drohne',
|
||||
Starbase = 'Sternenbasis',
|
||||
}
|
||||
|
||||
export enum SignatureGroupENG {
|
||||
CosmicSignature = 'Cosmic Signature',
|
||||
Wormhole = 'Wormhole',
|
||||
@@ -94,3 +117,23 @@ export enum SignatureGroupRU {
|
||||
OreSite = 'Астероидный район',
|
||||
CombatSite = 'Боевой район',
|
||||
}
|
||||
|
||||
export enum SignatureGroupFR {
|
||||
CosmicSignature = 'Signature cosmique (groupe)',
|
||||
Wormhole = 'Trou de ver',
|
||||
GasSite = 'Site de gaz',
|
||||
RelicSite = 'Site de reliques',
|
||||
DataSite = 'Site de données',
|
||||
OreSite = 'Site de minerai',
|
||||
CombatSite = 'Site de combat',
|
||||
}
|
||||
|
||||
export enum SignatureGroupDE {
|
||||
CosmicSignature = 'Kosmische Signatur (gruppe)',
|
||||
Wormhole = 'Wurmloch',
|
||||
GasSite = 'Gasgebiet',
|
||||
RelicSite = 'Reliktgebiet',
|
||||
DataSite = 'Datengebiet',
|
||||
OreSite = 'Mineraliengebiet',
|
||||
CombatSite = 'Kampfgebiet',
|
||||
}
|
||||
|
||||
BIN
assets/static/images/news/05-08-undo/undo.png
Executable file
BIN
assets/static/images/news/05-08-undo/undo.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -3,17 +3,19 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
|
||||
use Ash.Resource,
|
||||
domain: WandererApp.Api,
|
||||
data_layer: AshPostgres.DataLayer
|
||||
data_layer: AshPostgres.DataLayer,
|
||||
extensions: [AshCloak]
|
||||
|
||||
@derive {Jason.Encoder, only: [
|
||||
:id,
|
||||
:map_id,
|
||||
:character_id,
|
||||
:tracked,
|
||||
:followed,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
]}
|
||||
@derive {Jason.Encoder,
|
||||
only: [
|
||||
:id,
|
||||
:map_id,
|
||||
:character_id,
|
||||
:tracked,
|
||||
:followed,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
]}
|
||||
|
||||
postgres do
|
||||
repo(WandererApp.Repo)
|
||||
@@ -23,8 +25,10 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
code_interface do
|
||||
define(:create, action: :create)
|
||||
define(:destroy, action: :destroy)
|
||||
define(:update, action: :update)
|
||||
|
||||
define(:read_by_map, action: :read_by_map)
|
||||
define(:read_by_map_and_character, action: :read_by_map_and_character)
|
||||
define(:by_map_filtered, action: :by_map_filtered)
|
||||
define(:tracked_by_map_filtered, action: :tracked_by_map_filtered)
|
||||
define(:tracked_by_character, action: :tracked_by_character)
|
||||
@@ -44,7 +48,31 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
:tracked
|
||||
]
|
||||
|
||||
defaults [:create, :read, :update, :destroy]
|
||||
defaults [:read, :destroy]
|
||||
|
||||
create :create do
|
||||
primary? true
|
||||
upsert? true
|
||||
upsert_identity :uniq_map_character
|
||||
|
||||
upsert_fields [
|
||||
:map_id,
|
||||
:character_id
|
||||
]
|
||||
|
||||
accept [
|
||||
:map_id,
|
||||
:character_id,
|
||||
:tracked,
|
||||
:followed
|
||||
]
|
||||
|
||||
argument :map_id, :uuid, allow_nil?: false
|
||||
argument :character_id, :uuid, allow_nil?: false
|
||||
|
||||
change manage_relationship(:map_id, :map, on_lookup: :relate, on_no_match: nil)
|
||||
change manage_relationship(:character_id, :character, on_lookup: :relate, on_no_match: nil)
|
||||
end
|
||||
|
||||
read :by_map_filtered do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
@@ -67,6 +95,15 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
filter(expr(map_id == ^arg(:map_id)))
|
||||
end
|
||||
|
||||
read :read_by_map_and_character do
|
||||
get? true
|
||||
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
argument(:character_id, :uuid, allow_nil?: false)
|
||||
|
||||
filter(expr(map_id == ^arg(:map_id) and character_id == ^arg(:character_id)))
|
||||
end
|
||||
|
||||
read :tracked_by_map_all do
|
||||
argument(:map_id, :string, allow_nil?: false)
|
||||
filter(expr(map_id == ^arg(:map_id) and tracked == true))
|
||||
@@ -77,6 +114,20 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
filter(expr(character_id == ^arg(:character_id) and tracked == true))
|
||||
end
|
||||
|
||||
update :update do
|
||||
primary? true
|
||||
require_atomic? false
|
||||
|
||||
accept([
|
||||
:ship,
|
||||
:ship_name,
|
||||
:ship_item_id,
|
||||
:solar_system_id,
|
||||
:structure_id,
|
||||
:station_id
|
||||
])
|
||||
end
|
||||
|
||||
update :track do
|
||||
accept [:map_id, :character_id]
|
||||
argument :map_id, :string, allow_nil?: false
|
||||
@@ -134,6 +185,28 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
end
|
||||
end
|
||||
|
||||
cloak do
|
||||
vault(WandererApp.Vault)
|
||||
|
||||
attributes([
|
||||
:ship,
|
||||
:ship_name,
|
||||
:ship_item_id,
|
||||
:solar_system_id,
|
||||
:structure_id,
|
||||
:station_id
|
||||
])
|
||||
|
||||
decrypt_by_default([
|
||||
:ship,
|
||||
:ship_name,
|
||||
:ship_item_id,
|
||||
:solar_system_id,
|
||||
:structure_id,
|
||||
:station_id
|
||||
])
|
||||
end
|
||||
|
||||
attributes do
|
||||
uuid_primary_key :id
|
||||
|
||||
@@ -147,6 +220,13 @@ defmodule WandererApp.Api.MapCharacterSettings do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :solar_system_id, :integer
|
||||
attribute :structure_id, :integer
|
||||
attribute :station_id, :integer
|
||||
attribute :ship, :integer
|
||||
attribute :ship_name, :string
|
||||
attribute :ship_item_id, :integer
|
||||
|
||||
create_timestamp(:inserted_at)
|
||||
update_timestamp(:updated_at)
|
||||
end
|
||||
|
||||
@@ -38,7 +38,8 @@ defmodule WandererApp.Api.MapConnection do
|
||||
:map_id,
|
||||
:solar_system_source,
|
||||
:solar_system_target,
|
||||
:type
|
||||
:type,
|
||||
:ship_size_type
|
||||
]
|
||||
|
||||
defaults [:create, :read, :update, :destroy]
|
||||
|
||||
@@ -24,7 +24,19 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
)
|
||||
|
||||
define(:by_system_id, action: :by_system_id, args: [:system_id])
|
||||
define(:by_system_id_all, action: :by_system_id_all, args: [:system_id])
|
||||
|
||||
define(:by_system_id_and_eve_ids,
|
||||
action: :by_system_id_and_eve_ids,
|
||||
args: [:system_id, :eve_ids]
|
||||
)
|
||||
|
||||
define(:by_linked_system_id, action: :by_linked_system_id, args: [:linked_system_id])
|
||||
|
||||
define(:by_deleted_and_updated_before!,
|
||||
action: :by_deleted_and_updated_before,
|
||||
args: [:deleted, :updated_before]
|
||||
)
|
||||
end
|
||||
|
||||
actions do
|
||||
@@ -36,7 +48,8 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
:description,
|
||||
:kind,
|
||||
:group,
|
||||
:type
|
||||
:type,
|
||||
:deleted
|
||||
]
|
||||
|
||||
defaults [:read, :destroy]
|
||||
@@ -64,7 +77,8 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
:kind,
|
||||
:group,
|
||||
:type,
|
||||
:custom_info
|
||||
:custom_info,
|
||||
:deleted
|
||||
]
|
||||
|
||||
argument :system_id, :uuid, allow_nil?: false
|
||||
@@ -83,7 +97,8 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
:group,
|
||||
:type,
|
||||
:custom_info,
|
||||
:updated
|
||||
:deleted,
|
||||
:update_forced_at
|
||||
]
|
||||
|
||||
primary? true
|
||||
@@ -105,14 +120,32 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
read :by_system_id do
|
||||
argument(:system_id, :string, allow_nil?: false)
|
||||
|
||||
filter(expr(system_id == ^arg(:system_id) and deleted == false))
|
||||
end
|
||||
|
||||
read :by_system_id_all do
|
||||
argument(:system_id, :string, allow_nil?: false)
|
||||
filter(expr(system_id == ^arg(:system_id)))
|
||||
end
|
||||
|
||||
read :by_system_id_and_eve_ids do
|
||||
argument(:system_id, :string, allow_nil?: false)
|
||||
argument(:eve_ids, {:array, :string}, allow_nil?: false)
|
||||
filter(expr(system_id == ^arg(:system_id) and eve_id in ^arg(:eve_ids)))
|
||||
end
|
||||
|
||||
read :by_linked_system_id do
|
||||
argument(:linked_system_id, :integer, allow_nil?: false)
|
||||
|
||||
filter(expr(linked_system_id == ^arg(:linked_system_id)))
|
||||
end
|
||||
|
||||
read :by_deleted_and_updated_before do
|
||||
argument(:deleted, :boolean, allow_nil?: false)
|
||||
argument(:updated_before, :utc_datetime, allow_nil?: false)
|
||||
|
||||
filter(expr(deleted == ^arg(:deleted) and updated_at < ^arg(:updated_before)))
|
||||
end
|
||||
end
|
||||
|
||||
attributes do
|
||||
@@ -149,7 +182,14 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
attribute :updated, :integer
|
||||
attribute :deleted, :boolean do
|
||||
allow_nil? false
|
||||
default false
|
||||
end
|
||||
|
||||
attribute :update_forced_at, :utc_datetime do
|
||||
allow_nil? true
|
||||
end
|
||||
|
||||
create_timestamp(:inserted_at)
|
||||
update_timestamp(:updated_at)
|
||||
@@ -166,21 +206,20 @@ defmodule WandererApp.Api.MapSystemSignature do
|
||||
end
|
||||
|
||||
@derive {Jason.Encoder,
|
||||
only: [
|
||||
:id,
|
||||
:system_id,
|
||||
:eve_id,
|
||||
:character_eve_id,
|
||||
:name,
|
||||
:description,
|
||||
:type,
|
||||
:linked_system_id,
|
||||
:kind,
|
||||
:group,
|
||||
:custom_info,
|
||||
:updated,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
]
|
||||
}
|
||||
only: [
|
||||
:id,
|
||||
:system_id,
|
||||
:eve_id,
|
||||
:character_eve_id,
|
||||
:name,
|
||||
:description,
|
||||
:type,
|
||||
:linked_system_id,
|
||||
:kind,
|
||||
:group,
|
||||
:custom_info,
|
||||
:deleted,
|
||||
:inserted_at,
|
||||
:updated_at
|
||||
]}
|
||||
end
|
||||
|
||||
@@ -39,40 +39,49 @@ defmodule WandererApp.CachedInfo do
|
||||
def get_system_static_info(solar_system_id) do
|
||||
case Cachex.get(:system_static_info_cache, solar_system_id) do
|
||||
{:ok, nil} ->
|
||||
{:ok, systems} = WandererApp.Api.MapSolarSystem.read()
|
||||
case WandererApp.Api.MapSolarSystem.read() do
|
||||
{:ok, systems} ->
|
||||
systems
|
||||
|> Enum.each(fn system ->
|
||||
Cachex.put(
|
||||
:system_static_info_cache,
|
||||
system.solar_system_id,
|
||||
Map.take(system, [
|
||||
:solar_system_id,
|
||||
:region_id,
|
||||
:constellation_id,
|
||||
:solar_system_name,
|
||||
:solar_system_name_lc,
|
||||
:constellation_name,
|
||||
:region_name,
|
||||
:system_class,
|
||||
:security,
|
||||
:type_description,
|
||||
:class_title,
|
||||
:is_shattered,
|
||||
:effect_name,
|
||||
:effect_power,
|
||||
:statics,
|
||||
:wandering,
|
||||
:triglavian_invasion_status,
|
||||
:sun_type_id
|
||||
])
|
||||
)
|
||||
end)
|
||||
|
||||
systems
|
||||
|> Enum.each(fn system ->
|
||||
Cachex.put(
|
||||
:system_static_info_cache,
|
||||
system.solar_system_id,
|
||||
Map.take(system, [
|
||||
:solar_system_id,
|
||||
:region_id,
|
||||
:constellation_id,
|
||||
:solar_system_name,
|
||||
:solar_system_name_lc,
|
||||
:constellation_name,
|
||||
:region_name,
|
||||
:system_class,
|
||||
:security,
|
||||
:type_description,
|
||||
:class_title,
|
||||
:is_shattered,
|
||||
:effect_name,
|
||||
:effect_power,
|
||||
:statics,
|
||||
:wandering,
|
||||
:triglavian_invasion_status,
|
||||
:sun_type_id
|
||||
])
|
||||
)
|
||||
end)
|
||||
Cachex.get(:system_static_info_cache, solar_system_id)
|
||||
|
||||
Cachex.get(:system_static_info_cache, solar_system_id)
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to read solar systems from API: #{inspect(reason)}")
|
||||
{:error, :api_error}
|
||||
end
|
||||
|
||||
{:ok, system_static_info} ->
|
||||
{:ok, system_static_info}
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to get system static info from cache: #{inspect(reason)}")
|
||||
{:error, :cache_error}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -7,6 +7,15 @@ defmodule WandererApp.Character do
|
||||
@read_character_wallet_scope "esi-wallet.read_character_wallet.v1"
|
||||
@read_corp_wallet_scope "esi-wallet.read_corporation_wallets.v1"
|
||||
|
||||
@default_character_tracking_data %{
|
||||
solar_system_id: nil,
|
||||
structure_id: nil,
|
||||
station_id: nil,
|
||||
ship: nil,
|
||||
ship_name: nil,
|
||||
ship_item_id: nil
|
||||
}
|
||||
|
||||
@decorate cacheable(
|
||||
cache: WandererApp.Cache,
|
||||
key: "characters-#{character_eve_id}"
|
||||
@@ -45,6 +54,32 @@ defmodule WandererApp.Character do
|
||||
end
|
||||
end
|
||||
|
||||
def get_map_character(map_id, character_id) do
|
||||
case get_character(character_id) do
|
||||
{:ok, character} ->
|
||||
{:ok,
|
||||
character
|
||||
|> maybe_merge_map_character_settings(
|
||||
map_id,
|
||||
WandererApp.Character.TrackerManager.Impl.character_is_present(map_id, character_id)
|
||||
)}
|
||||
|
||||
_ ->
|
||||
{:ok, nil}
|
||||
end
|
||||
end
|
||||
|
||||
def get_map_character!(map_id, character_id) do
|
||||
case get_map_character(map_id, character_id) do
|
||||
{:ok, character} ->
|
||||
character
|
||||
|
||||
_ ->
|
||||
Logger.error("Failed to get map character #{map_id} #{character_id}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_character_eve_ids!(character_ids),
|
||||
do:
|
||||
character_ids
|
||||
@@ -146,7 +181,7 @@ defmodule WandererApp.Character do
|
||||
params: opts[:params]
|
||||
) do
|
||||
{:ok, result} ->
|
||||
{:ok, result |> _prepare_search_results()}
|
||||
{:ok, result |> prepare_search_results()}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.warning("#{__MODULE__} failed search: #{inspect(error)}")
|
||||
@@ -208,7 +243,28 @@ defmodule WandererApp.Character do
|
||||
end
|
||||
end
|
||||
|
||||
defp _prepare_search_results(result) do
|
||||
defp maybe_merge_map_character_settings(character, map_id, true), do: character
|
||||
|
||||
defp maybe_merge_map_character_settings(
|
||||
%{id: character_id} = character,
|
||||
map_id,
|
||||
_character_is_present
|
||||
) do
|
||||
WandererApp.MapCharacterSettingsRepo.get(map_id, character_id)
|
||||
|> case do
|
||||
{:ok, settings} when not is_nil(settings) ->
|
||||
character
|
||||
|> Map.put(:online, false)
|
||||
|> Map.merge(settings)
|
||||
|
||||
_ ->
|
||||
character
|
||||
|> Map.put(:online, false)
|
||||
|> Map.merge(@default_character_tracking_data)
|
||||
end
|
||||
end
|
||||
|
||||
defp prepare_search_results(result) do
|
||||
{:ok, characters} =
|
||||
_load_eve_info(Map.get(result, "character"), :get_character_info, &_map_character_info/1)
|
||||
|
||||
|
||||
@@ -319,7 +319,6 @@ defmodule WandererApp.Character.Tracker do
|
||||
duration = DateTime.diff(DateTime.utc_now(), error_time, :second)
|
||||
|
||||
if duration >= @online_error_timeout do
|
||||
{:ok, character_state} = WandererApp.Character.get_character_state(character_id)
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_forbidden")
|
||||
WandererApp.Cache.delete("character:#{character_id}:online_error_time")
|
||||
WandererApp.Character.update_character(character_id, %{online: false})
|
||||
|
||||
@@ -13,7 +13,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
}
|
||||
|
||||
@garbage_collection_interval :timer.minutes(15)
|
||||
@untrack_characters_interval :timer.minutes(5)
|
||||
@untrack_characters_interval :timer.minutes(1)
|
||||
@inactive_character_timeout :timer.minutes(5)
|
||||
|
||||
@logger Application.compile_env(:wanderer_app, :logger)
|
||||
@@ -220,6 +220,18 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
track: false
|
||||
})
|
||||
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
|
||||
{:ok, _updated} =
|
||||
WandererApp.MapCharacterSettingsRepo.update(map_id, character_id, %{
|
||||
ship: character.ship,
|
||||
ship_name: character.ship_name,
|
||||
ship_item_id: character.ship_item_id,
|
||||
solar_system_id: character.solar_system_id,
|
||||
structure_id: character.structure_id,
|
||||
station_id: character.station_id
|
||||
})
|
||||
|
||||
WandererApp.Character.update_character_state(character_id, character_state)
|
||||
end
|
||||
end,
|
||||
@@ -246,7 +258,7 @@ defmodule WandererApp.Character.TrackerManager.Impl do
|
||||
def handle_info(_event, state),
|
||||
do: state
|
||||
|
||||
defp character_is_present(map_id, character_id) do
|
||||
def character_is_present(map_id, character_id) do
|
||||
{:ok, presence_character_ids} =
|
||||
WandererApp.Cache.lookup("map_#{map_id}:presence_character_ids", [])
|
||||
|
||||
|
||||
@@ -121,7 +121,6 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
WandererApp.MapCharacterSettingsRepo.untrack(existing_settings)
|
||||
|
||||
:ok = untrack([character], map_id, caller_pid)
|
||||
:ok = remove_characters([character], map_id)
|
||||
{:ok, updated_settings}
|
||||
else
|
||||
{:ok, existing_settings}
|
||||
@@ -132,7 +131,6 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
if track do
|
||||
{:ok, updated_settings} = WandererApp.MapCharacterSettingsRepo.track(existing_settings)
|
||||
:ok = track([character], map_id, true, caller_pid)
|
||||
:ok = add_characters([character], map_id, true)
|
||||
{:ok, updated_settings}
|
||||
else
|
||||
{:ok, existing_settings}
|
||||
@@ -149,7 +147,6 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
})
|
||||
|
||||
:ok = track([character], map_id, true, caller_pid)
|
||||
:ok = add_characters([character], map_id, true)
|
||||
{:ok, settings}
|
||||
else
|
||||
{:error, "Character settings not found"}
|
||||
@@ -231,15 +228,15 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
with false <- is_nil(caller_pid) do
|
||||
character_ids = characters |> Enum.map(& &1.id)
|
||||
|
||||
characters
|
||||
|> Enum.each(fn character ->
|
||||
WandererAppWeb.Presence.update(caller_pid, map_id, character.id, %{
|
||||
character_ids
|
||||
|> Enum.each(fn character_id ->
|
||||
WandererAppWeb.Presence.update(caller_pid, map_id, character_id, %{
|
||||
tracked: false,
|
||||
from: DateTime.utc_now()
|
||||
})
|
||||
end)
|
||||
|
||||
WandererApp.Map.Server.untrack_characters(map_id, character_ids)
|
||||
# WandererApp.Map.Server.untrack_characters(map_id, character_ids)
|
||||
|
||||
:ok
|
||||
else
|
||||
@@ -249,19 +246,19 @@ defmodule WandererApp.Character.TrackingUtils do
|
||||
end
|
||||
end
|
||||
|
||||
def add_characters([], _map_id, _track_character), do: :ok
|
||||
# def add_characters([], _map_id, _track_character), do: :ok
|
||||
|
||||
def add_characters([character | characters], map_id, track_character) do
|
||||
:ok = WandererApp.Map.Server.add_character(map_id, character, track_character)
|
||||
add_characters(characters, map_id, track_character)
|
||||
end
|
||||
# def add_characters([character | characters], map_id, track_character) do
|
||||
# :ok = WandererApp.Map.Server.add_character(map_id, character, track_character)
|
||||
# add_characters(characters, map_id, track_character)
|
||||
# end
|
||||
|
||||
def remove_characters([], _map_id), do: :ok
|
||||
# def remove_characters([], _map_id), do: :ok
|
||||
|
||||
def remove_characters([character | characters], map_id) do
|
||||
:ok = WandererApp.Map.Server.remove_character(map_id, character.id)
|
||||
remove_characters(characters, map_id)
|
||||
end
|
||||
# def remove_characters([character | characters], map_id) do
|
||||
# :ok = WandererApp.Map.Server.remove_character(map_id, character.id)
|
||||
# remove_characters(characters, map_id)
|
||||
# end
|
||||
|
||||
def get_main_character(
|
||||
nil,
|
||||
|
||||
@@ -579,7 +579,7 @@ defmodule WandererApp.Esi.ApiClient do
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_cache_response(_path, _body, _headers), do: :ok
|
||||
defp maybe_cache_response(_path, _body, _headers, _opts), do: :ok
|
||||
|
||||
defp post(url, opts) do
|
||||
try do
|
||||
|
||||
@@ -96,7 +96,7 @@ defmodule WandererApp.Map do
|
||||
map_id
|
||||
|> get_map!()
|
||||
|> Map.get(:characters, [])
|
||||
|> Enum.map(&WandererApp.Character.get_character!(&1))
|
||||
|> Enum.map(fn character_id -> WandererApp.Character.get_map_character!(map_id, character_id) end)
|
||||
|
||||
def list_systems(map_id),
|
||||
do: {:ok, map_id |> get_map!() |> Map.get(:systems, Map.new()) |> Map.values()}
|
||||
|
||||
@@ -9,12 +9,15 @@ defmodule WandererApp.Map.Manager do
|
||||
|
||||
alias WandererApp.Map.Server
|
||||
alias WandererApp.Map.ServerSupervisor
|
||||
alias WandererApp.Api.MapSystemSignature
|
||||
|
||||
@maps_start_per_second 5
|
||||
@maps_start_interval 1000
|
||||
@maps_queue :maps_queue
|
||||
@garbage_collection_interval :timer.hours(1)
|
||||
@check_maps_queue_interval :timer.seconds(1)
|
||||
@signatures_cleanup_interval :timer.minutes(30)
|
||||
@delete_after_minutes 30
|
||||
|
||||
def start_map(map_id) when is_binary(map_id),
|
||||
do: WandererApp.Queue.push_uniq(@maps_queue, map_id)
|
||||
@@ -44,6 +47,9 @@ defmodule WandererApp.Map.Manager do
|
||||
{:ok, garbage_collector_timer} =
|
||||
:timer.send_interval(@garbage_collection_interval, :garbage_collect)
|
||||
|
||||
{:ok, signatures_cleanup_timer} =
|
||||
:timer.send_interval(@signatures_cleanup_interval, :cleanup_signatures)
|
||||
|
||||
try do
|
||||
Task.async(fn ->
|
||||
start_last_active_maps()
|
||||
@@ -56,7 +62,8 @@ defmodule WandererApp.Map.Manager do
|
||||
{:ok,
|
||||
%{
|
||||
garbage_collector_timer: garbage_collector_timer,
|
||||
check_maps_queue_timer: check_maps_queue_timer
|
||||
check_maps_queue_timer: check_maps_queue_timer,
|
||||
signatures_cleanup_timer: signatures_cleanup_timer
|
||||
}}
|
||||
end
|
||||
|
||||
@@ -118,6 +125,36 @@ defmodule WandererApp.Map.Manager do
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:cleanup_signatures, state) do
|
||||
try do
|
||||
cleanup_deleted_signatures()
|
||||
{:noreply, state}
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("Failed to cleanup signatures: #{inspect(e)}")
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup_deleted_signatures() do
|
||||
delete_after_date = DateTime.utc_now() |> DateTime.add(-1 * @delete_after_minutes, :minute)
|
||||
|
||||
case MapSystemSignature.by_deleted_and_updated_before!(true, delete_after_date) do
|
||||
{:ok, deleted_signatures} ->
|
||||
|
||||
Enum.each(deleted_signatures, fn sig ->
|
||||
Ash.destroy!(sig)
|
||||
end)
|
||||
|
||||
:ok
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("Failed to fetch deleted signatures: #{inspect(error)}")
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
defp start_last_active_maps() do
|
||||
{:ok, last_map_states} =
|
||||
WandererApp.Api.MapState.get_last_active(
|
||||
|
||||
@@ -11,7 +11,6 @@ defmodule WandererApp.Map.ZkbDataFetcher do
|
||||
@interval :timer.seconds(15)
|
||||
@store_map_kills_timeout :timer.hours(1)
|
||||
@logger Application.compile_env(:wanderer_app, :logger)
|
||||
@pubsub_client Application.compile_env(:wanderer_app, :pubsub_client)
|
||||
|
||||
# This means 120 “ticks” of 15s each → ~30 minutes
|
||||
@preload_cycle_ticks 120
|
||||
|
||||
@@ -1,12 +1,122 @@
|
||||
defmodule WandererApp.Map.Operations.Connections do
|
||||
@moduledoc """
|
||||
CRUD and batch upsert for map connections.
|
||||
Operations for managing map connections, including creation, updates, and deletions.
|
||||
Handles special cases like C1 wormhole sizing rules and unique constraint handling.
|
||||
"""
|
||||
|
||||
require Logger
|
||||
alias WandererApp.Map.Server.{ConnectionsImpl, Server}
|
||||
alias Ash.Error.Invalid
|
||||
alias WandererApp.MapConnectionRepo
|
||||
alias WandererApp.Map.Server
|
||||
require Logger
|
||||
|
||||
# Connection type constants
|
||||
@connection_type_wormhole 0
|
||||
@connection_type_stargate 1
|
||||
|
||||
# Ship size constants
|
||||
@small_ship_size 0
|
||||
@medium_ship_size 1
|
||||
@large_ship_size 2
|
||||
@xlarge_ship_size 3
|
||||
|
||||
# System class constants
|
||||
@c1_system_class "C1"
|
||||
|
||||
@doc """
|
||||
Creates a connection between two systems, applying special rules for C1 wormholes.
|
||||
Handles parsing of input parameters, validates system information, and manages
|
||||
unique constraint violations gracefully.
|
||||
"""
|
||||
def create(attrs, map_id, char_id) do
|
||||
do_create(attrs, map_id, char_id)
|
||||
end
|
||||
|
||||
defp do_create(attrs, map_id, char_id) do
|
||||
with {:ok, source} <- parse_int(attrs["solar_system_source"], "solar_system_source"),
|
||||
{:ok, target} <- parse_int(attrs["solar_system_target"], "solar_system_target"),
|
||||
{:ok, src_info} <- ConnectionsImpl.get_system_static_info(source),
|
||||
{:ok, tgt_info} <- ConnectionsImpl.get_system_static_info(target) do
|
||||
build_and_add_connection(attrs, map_id, char_id, src_info, tgt_info)
|
||||
else
|
||||
{:error, reason} -> handle_precondition_error(reason, attrs)
|
||||
{:ok, []} -> {:error, :inconsistent_state}
|
||||
other -> {:error, :unexpected_precondition_error, other}
|
||||
end
|
||||
end
|
||||
|
||||
defp build_and_add_connection(attrs, map_id, char_id, src_info, tgt_info) do
|
||||
info = %{
|
||||
solar_system_source_id: src_info.solar_system_id,
|
||||
solar_system_target_id: tgt_info.solar_system_id,
|
||||
character_id: char_id,
|
||||
type: parse_type(attrs["type"]),
|
||||
ship_size_type: resolve_ship_size(attrs, src_info, tgt_info)
|
||||
}
|
||||
|
||||
case Server.add_connection(map_id, info) do
|
||||
:ok -> {:ok, :created}
|
||||
{:ok, []} -> log_warn_and(:inconsistent_state, info)
|
||||
{:error, %Invalid{errors: errs}} = err ->
|
||||
if Enum.any?(errs, &is_unique_constraint_error?/1), do: {:skip, :exists}, else: err
|
||||
{:error, _} = err -> Logger.error("[add_connection] #{inspect(err)}"); {:error, :server_error}
|
||||
other -> Logger.error("[add_connection] unexpected: #{inspect(other)}"); {:error, :unexpected_error}
|
||||
end
|
||||
end
|
||||
|
||||
defp resolve_ship_size(attrs, src_info, tgt_info) do
|
||||
type = parse_type(attrs["type"])
|
||||
|
||||
if type == @connection_type_wormhole and
|
||||
(src_info.system_class == @c1_system_class or
|
||||
tgt_info.system_class == @c1_system_class) do
|
||||
@medium_ship_size
|
||||
else
|
||||
parse_ship_size(attrs["ship_size_type"], @large_ship_size)
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_ship_size(nil, default), do: default
|
||||
defp parse_ship_size(val, _default) when is_integer(val), do: val
|
||||
defp parse_ship_size(val, default) when is_binary(val) do
|
||||
case Integer.parse(val) do
|
||||
{i, _} -> i
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
defp parse_ship_size(_, default), do: default
|
||||
|
||||
defp parse_type(nil), do: @connection_type_wormhole
|
||||
defp parse_type(val) when is_integer(val), do: val
|
||||
defp parse_type(val) when is_binary(val) do
|
||||
case Integer.parse(val) do
|
||||
{i, _} -> i
|
||||
:error -> @connection_type_wormhole
|
||||
end
|
||||
end
|
||||
defp parse_type(_), do: @connection_type_wormhole
|
||||
|
||||
defp parse_int(nil, field), do: {:error, {:missing_field, field}}
|
||||
defp parse_int(val, _) when is_integer(val), do: {:ok, val}
|
||||
defp parse_int(val, _) when is_binary(val) do
|
||||
case Integer.parse(val) do
|
||||
{i, _} -> {:ok, i}
|
||||
:error -> {:error, :invalid_integer}
|
||||
end
|
||||
end
|
||||
defp parse_int(_, field), do: {:error, {:invalid_field, field}}
|
||||
|
||||
defp handle_precondition_error(reason, attrs) do
|
||||
Logger.warning("[add_connection] precondition failed: #{inspect(reason)} for #{inspect(attrs)}")
|
||||
{:error, :precondition_failed, reason}
|
||||
end
|
||||
|
||||
defp log_warn_and(return, info) do
|
||||
Logger.warning("[add_connection] inconsistent for #{inspect(info)}")
|
||||
{:error, return}
|
||||
end
|
||||
|
||||
defp is_unique_constraint_error?(%{code: :unique_constraint}), do: true
|
||||
defp is_unique_constraint_error?(_), do: false
|
||||
|
||||
@spec list_connections(String.t()) :: [map()] | {:error, atom()}
|
||||
def list_connections(map_id) do
|
||||
@@ -38,52 +148,6 @@ defmodule WandererApp.Map.Operations.Connections do
|
||||
end
|
||||
end
|
||||
|
||||
@spec create_connection(Plug.Conn.t(), map()) :: {:ok, map()} | {:skip, :exists} | {:error, atom()}
|
||||
def create_connection(%{assigns: %{map_id: map_id, owner_character_id: char_id}} = _conn, attrs) do
|
||||
do_create(attrs, map_id, char_id)
|
||||
end
|
||||
|
||||
def create_connection(map_id, attrs, char_id) do
|
||||
do_create(attrs, map_id, char_id)
|
||||
end
|
||||
|
||||
defp do_create(attrs, map_id, char_id) do
|
||||
with {:ok, source} <- parse_int(attrs["solar_system_source"], "solar_system_source"),
|
||||
{:ok, target} <- parse_int(attrs["solar_system_target"], "solar_system_target") do
|
||||
info = %{
|
||||
solar_system_source_id: source,
|
||||
solar_system_target_id: target,
|
||||
character_id: char_id,
|
||||
type: parse_type(attrs["type"])
|
||||
}
|
||||
add_result = Server.add_connection(map_id, info)
|
||||
case add_result do
|
||||
:ok -> {:ok, :created}
|
||||
{:ok, []} ->
|
||||
Logger.warning("[do_create] Server.add_connection returned :ok, [] for map_id=#{inspect(map_id)}, source=#{inspect(source)}, target=#{inspect(target)}")
|
||||
{:error, :inconsistent_state}
|
||||
{:error, %Invalid{errors: errors}} = err ->
|
||||
if Enum.any?(errors, &is_unique_constraint_error?/1), do: {:skip, :exists}, else: err
|
||||
{:error, _} = err ->
|
||||
Logger.error("[do_create] Server.add_connection error: #{inspect(err)}")
|
||||
{:error, :server_error}
|
||||
_ ->
|
||||
Logger.error("[do_create] Unexpected add_result: #{inspect(add_result)}")
|
||||
{:error, :unexpected_error}
|
||||
end
|
||||
else
|
||||
{:ok, []} ->
|
||||
Logger.warning("[do_create] Source or target system not found: attrs=#{inspect(attrs)}")
|
||||
{:error, :inconsistent_state}
|
||||
{:error, _} = err ->
|
||||
Logger.error("[do_create] parse_int error: #{inspect(err)}, attrs=#{inspect(attrs)}")
|
||||
{:error, :parse_error}
|
||||
_ ->
|
||||
Logger.error("[do_create] Unexpected error in preconditions: attrs=#{inspect(attrs)}")
|
||||
{:error, :unexpected_precondition_error}
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_connection(Plug.Conn.t(), String.t(), map()) :: {:ok, map()} | {:error, atom()}
|
||||
def update_connection(%{assigns: %{map_id: map_id, owner_character_id: char_id}} = _conn, conn_id, attrs) do
|
||||
with {:ok, conn_struct} <- MapConnectionRepo.get_by_id(map_id, conn_id),
|
||||
@@ -185,29 +249,6 @@ defmodule WandererApp.Map.Operations.Connections do
|
||||
|
||||
# -- Helpers ---------------------------------------------------------------
|
||||
|
||||
defp parse_int(val, _field) when is_integer(val), do: {:ok, val}
|
||||
defp parse_int(val, field) when is_binary(val) do
|
||||
case Integer.parse(val) do
|
||||
{i, _} -> {:ok, i}
|
||||
_ -> {:error, "Invalid #{field}: #{val}"}
|
||||
end
|
||||
end
|
||||
defp parse_int(nil, field), do: {:error, "Missing #{field}"}
|
||||
defp parse_int(val, field), do: {:error, "Invalid #{field} type: #{inspect(val)}"}
|
||||
|
||||
defp parse_type(val) when is_integer(val), do: val
|
||||
defp parse_type(val) when is_binary(val) do
|
||||
case Integer.parse(val) do
|
||||
{i, _} -> i
|
||||
_ -> 0
|
||||
end
|
||||
end
|
||||
defp parse_type(_), do: 0
|
||||
|
||||
defp is_unique_constraint_error?(%{constraint: :unique}), do: true
|
||||
defp is_unique_constraint_error?(%{constraint: :unique_constraint}), do: true
|
||||
defp is_unique_constraint_error?(_), do: false
|
||||
|
||||
defp apply_connection_updates(map_id, conn, attrs, _char_id) do
|
||||
Enum.reduce_while(attrs, :ok, fn {key, val}, _acc ->
|
||||
result =
|
||||
@@ -256,4 +297,16 @@ defmodule WandererApp.Map.Operations.Connections do
|
||||
})
|
||||
end
|
||||
|
||||
@doc "Creates a connection between two systems"
|
||||
@spec create_connection(String.t(), map(), String.t()) :: {:ok, :created} | {:skip, :exists} | {:error, atom()}
|
||||
def create_connection(map_id, attrs, char_id) do
|
||||
do_create(attrs, map_id, char_id)
|
||||
end
|
||||
|
||||
@doc "Creates a connection between two systems from a Plug.Conn"
|
||||
@spec create_connection(Plug.Conn.t(), map()) :: {:ok, :created} | {:skip, :exists} | {:error, atom()}
|
||||
def create_connection(%{assigns: %{map_id: map_id, owner_character_id: char_id}} = _conn, attrs) do
|
||||
do_create(attrs, map_id, char_id)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -3,8 +3,6 @@ defmodule WandererApp.Map.Server.AclsImpl do
|
||||
|
||||
require Logger
|
||||
|
||||
alias WandererApp.Map.Server.Impl
|
||||
|
||||
@pubsub_client Application.compile_env(:wanderer_app, :pubsub_client)
|
||||
|
||||
def handle_map_acl_updated(%{map_id: map_id, map: old_map} = state, added_acls, removed_acls) do
|
||||
@@ -86,7 +84,7 @@ defmodule WandererApp.Map.Server.AclsImpl do
|
||||
end
|
||||
end
|
||||
|
||||
def handle_acl_deleted(map_id, acl_id) do
|
||||
def handle_acl_deleted(map_id, _acl_id) do
|
||||
{:ok, map} =
|
||||
WandererApp.MapRepo.get(map_id,
|
||||
acls: [
|
||||
|
||||
@@ -5,25 +5,22 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
|
||||
alias WandererApp.Map.Server.{Impl, ConnectionsImpl, SystemsImpl}
|
||||
|
||||
def get_characters(%{map_id: map_id} = _state),
|
||||
do: {:ok, map_id |> WandererApp.Map.list_characters()}
|
||||
|
||||
def add_character(%{map_id: map_id} = state, %{id: character_id} = character, track_character) do
|
||||
Task.start_link(fn ->
|
||||
with :ok <- map_id |> WandererApp.Map.add_character(character),
|
||||
{:ok, _} <-
|
||||
{:ok, _settings} <-
|
||||
WandererApp.MapCharacterSettingsRepo.create(%{
|
||||
character_id: character_id,
|
||||
map_id: map_id,
|
||||
tracked: track_character
|
||||
}),
|
||||
{:ok, character} <- WandererApp.Character.get_character(character_id) do
|
||||
{:ok, character} <- WandererApp.Character.get_map_character(map_id, character_id) do
|
||||
Impl.broadcast!(map_id, :character_added, character)
|
||||
:telemetry.execute([:wanderer_app, :map, :character, :added], %{count: 1})
|
||||
:ok
|
||||
else
|
||||
_error ->
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
{:ok, character} = WandererApp.Character.get_map_character(map_id, character_id)
|
||||
Impl.broadcast!(map_id, :character_added, character)
|
||||
:ok
|
||||
end
|
||||
@@ -35,7 +32,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
def remove_character(map_id, character_id) do
|
||||
Task.start_link(fn ->
|
||||
with :ok <- WandererApp.Map.remove_character(map_id, character_id),
|
||||
{:ok, character} <- WandererApp.Character.get_character(character_id) do
|
||||
{:ok, character} <- WandererApp.Character.get_map_character(map_id, character_id) do
|
||||
Impl.broadcast!(map_id, :character_removed, character)
|
||||
|
||||
:telemetry.execute([:wanderer_app, :map, :character, :removed], %{count: 1})
|
||||
@@ -64,7 +61,9 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
map_tracked_character_ids
|
||||
|> Enum.filter(fn character -> character in tracked_characters end)
|
||||
|
||||
{:ok, old_map_tracked_characters} = WandererApp.Cache.lookup("maps:#{map_id}:tracked_characters", [])
|
||||
{:ok, old_map_tracked_characters} =
|
||||
WandererApp.Cache.lookup("maps:#{map_id}:tracked_characters", [])
|
||||
|
||||
characters_to_remove = old_map_tracked_characters -- map_active_tracked_characters
|
||||
|
||||
{:ok, invalidate_character_ids} =
|
||||
@@ -73,7 +72,11 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
[]
|
||||
)
|
||||
|
||||
WandererApp.Cache.insert("map_#{map_id}:invalidate_character_ids", (invalidate_character_ids ++ characters_to_remove) |> Enum.uniq())
|
||||
WandererApp.Cache.insert(
|
||||
"map_#{map_id}:invalidate_character_ids",
|
||||
(invalidate_character_ids ++ characters_to_remove) |> Enum.uniq()
|
||||
)
|
||||
|
||||
WandererApp.Cache.insert("maps:#{map_id}:tracked_characters", map_active_tracked_characters)
|
||||
|
||||
:ok
|
||||
@@ -84,12 +87,26 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
do:
|
||||
character_ids
|
||||
|> Enum.each(fn character_id ->
|
||||
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
|
||||
map_id: map_id,
|
||||
track: false
|
||||
})
|
||||
if is_character_map_active?(map_id, character_id) do
|
||||
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
|
||||
map_id: map_id,
|
||||
track: false
|
||||
})
|
||||
|
||||
Impl.broadcast!(map_id, :untrack_character, character_id)
|
||||
end
|
||||
end)
|
||||
|
||||
def is_character_map_active?(map_id, character_id) do
|
||||
case WandererApp.Character.get_character_state(character_id) do
|
||||
{:ok, %{active_maps: active_maps}} ->
|
||||
map_id in active_maps
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def cleanup_characters(map_id, owner_id) do
|
||||
{:ok, invalidate_character_ids} =
|
||||
WandererApp.Cache.lookup(
|
||||
@@ -265,7 +282,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
end
|
||||
|
||||
defp update_character(map_id, character_id) do
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
{:ok, character} = WandererApp.Character.get_map_character(map_id, character_id)
|
||||
Impl.broadcast!(map_id, :character_updated, character)
|
||||
end
|
||||
|
||||
@@ -315,15 +332,18 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
is_nil(structure_id) and is_nil(station_id)
|
||||
end
|
||||
|
||||
defp track_character(map_id, character_id),
|
||||
do:
|
||||
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
|
||||
map_id: map_id,
|
||||
track: true,
|
||||
track_online: true,
|
||||
track_location: true,
|
||||
track_ship: true
|
||||
})
|
||||
defp track_character(map_id, character_id) do
|
||||
{:ok, character} = WandererApp.Character.get_character(character_id)
|
||||
add_character(%{map_id: map_id}, character, true)
|
||||
|
||||
WandererApp.Character.TrackerManager.update_track_settings(character_id, %{
|
||||
map_id: map_id,
|
||||
track: true,
|
||||
track_online: true,
|
||||
track_location: true,
|
||||
track_ship: true
|
||||
})
|
||||
end
|
||||
|
||||
defp maybe_update_online(map_id, character_id) do
|
||||
with {:ok, old_online} <-
|
||||
@@ -394,8 +414,7 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
{:ok, old_structure_id} =
|
||||
WandererApp.Cache.lookup("map:#{map_id}:character:#{character_id}:structure_id")
|
||||
|
||||
{:ok,
|
||||
%{solar_system_id: solar_system_id, structure_id: structure_id, station_id: station_id}} =
|
||||
{:ok, %{solar_system_id: solar_system_id, structure_id: structure_id, station_id: station_id}} =
|
||||
WandererApp.Character.get_character(character_id)
|
||||
|
||||
WandererApp.Cache.insert(
|
||||
@@ -413,14 +432,15 @@ defmodule WandererApp.Map.Server.CharactersImpl do
|
||||
structure_id
|
||||
)
|
||||
|
||||
if solar_system_id != old_solar_system_id || structure_id != old_structure_id || station_id != old_station_id do
|
||||
if solar_system_id != old_solar_system_id || structure_id != old_structure_id ||
|
||||
station_id != old_station_id do
|
||||
[
|
||||
{:character_location,
|
||||
%{
|
||||
solar_system_id: solar_system_id,
|
||||
structure_id: structure_id,
|
||||
station_id: station_id
|
||||
}, %{solar_system_id: old_solar_system_id}}
|
||||
%{
|
||||
solar_system_id: solar_system_id,
|
||||
structure_id: structure_id,
|
||||
station_id: station_id
|
||||
}, %{solar_system_id: old_solar_system_id}}
|
||||
]
|
||||
else
|
||||
[:skip]
|
||||
|
||||
@@ -69,6 +69,7 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
|
||||
@connection_time_status_eol 1
|
||||
@connection_type_wormhole 0
|
||||
@connection_type_stargate 1
|
||||
@medium_ship_size 1
|
||||
|
||||
def get_connection_auto_expire_hours(), do: WandererApp.Env.map_connection_auto_expire_hours()
|
||||
|
||||
@@ -353,12 +354,26 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
|
||||
@connection_type_wormhole
|
||||
end
|
||||
|
||||
# Check if either system is C1 before creating the connection
|
||||
{:ok, source_system_info} = get_system_static_info(old_location.solar_system_id)
|
||||
{:ok, target_system_info} = get_system_static_info(location.solar_system_id)
|
||||
|
||||
# Set ship size type to medium only for wormhole connections involving C1 systems
|
||||
ship_size_type = if connection_type == @connection_type_wormhole and
|
||||
(source_system_info.system_class == @c1 or
|
||||
target_system_info.system_class == @c1) do
|
||||
@medium_ship_size
|
||||
else
|
||||
2 # Default to large for non-wormhole or non-C1 connections
|
||||
end
|
||||
|
||||
{:ok, connection} =
|
||||
WandererApp.MapConnectionRepo.create(%{
|
||||
map_id: map_id,
|
||||
solar_system_source: old_location.solar_system_id,
|
||||
solar_system_target: location.solar_system_id,
|
||||
type: connection_type
|
||||
type: connection_type,
|
||||
ship_size_type: ship_size_type
|
||||
})
|
||||
|
||||
if connection_type == @connection_type_wormhole do
|
||||
@@ -497,7 +512,13 @@ defmodule WandererApp.Map.Server.ConnectionsImpl do
|
||||
known_jumps |> Enum.empty?()
|
||||
|
||||
:stargates ->
|
||||
not is_prohibited_system_class?(from_system_static_info.system_class) and
|
||||
# For stargates, we need to check:
|
||||
# 1. Both systems are in known space (HS, LS, NS)
|
||||
# 2. There is a known jump between them
|
||||
# 3. Neither system is prohibited
|
||||
from_system_static_info.system_class in @known_space and
|
||||
to_system_static_info.system_class in @known_space and
|
||||
not is_prohibited_system_class?(from_system_static_info.system_class) and
|
||||
not is_prohibited_system_class?(to_system_static_info.system_class) and
|
||||
not (known_jumps |> Enum.empty?())
|
||||
end
|
||||
|
||||
@@ -3,147 +3,212 @@ defmodule WandererApp.Map.Server.SignaturesImpl do
|
||||
|
||||
require Logger
|
||||
|
||||
alias WandererApp.Api.{MapSystem, MapSystemSignature}
|
||||
alias WandererApp.Character
|
||||
alias WandererApp.User.ActivityTracker
|
||||
alias WandererApp.Map.Server.{Impl, ConnectionsImpl, SystemsImpl}
|
||||
|
||||
@doc """
|
||||
Public entrypoint for updating signatures on a map system.
|
||||
"""
|
||||
def update_signatures(
|
||||
%{map_id: map_id} = state,
|
||||
%{
|
||||
solar_system_id: solar_system_id,
|
||||
character_id: character_id,
|
||||
solar_system_id: system_solar_id,
|
||||
character_id: char_id,
|
||||
user_id: user_id,
|
||||
delete_connection_with_sigs: delete_connection_with_sigs,
|
||||
added_signatures: added_signatures,
|
||||
updated_signatures: updated_signatures,
|
||||
removed_signatures: removed_signatures
|
||||
} =
|
||||
_signatures_update
|
||||
delete_connection_with_sigs: delete_conn?,
|
||||
added_signatures: added_params,
|
||||
updated_signatures: updated_params,
|
||||
removed_signatures: removed_params
|
||||
}
|
||||
)
|
||||
when not is_nil(character_id) do
|
||||
WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_id
|
||||
})
|
||||
|> case do
|
||||
{:ok, system} ->
|
||||
{:ok, %{eve_id: character_eve_id}} = WandererApp.Character.get_character(character_id)
|
||||
|
||||
added_signatures =
|
||||
added_signatures
|
||||
|> parse_signatures(character_eve_id, system.id)
|
||||
|
||||
updated_signatures =
|
||||
updated_signatures
|
||||
|> parse_signatures(character_eve_id, system.id)
|
||||
|
||||
updated_signatures_eve_ids =
|
||||
updated_signatures
|
||||
|> Enum.map(fn s -> s.eve_id end)
|
||||
|
||||
removed_signatures_eve_ids =
|
||||
removed_signatures
|
||||
|> parse_signatures(character_eve_id, system.id)
|
||||
|> Enum.map(fn s -> s.eve_id end)
|
||||
|
||||
WandererApp.Api.MapSystemSignature.by_system_id!(system.id)
|
||||
|> Enum.filter(fn s -> s.eve_id in removed_signatures_eve_ids end)
|
||||
|> Enum.each(fn s ->
|
||||
if delete_connection_with_sigs && not is_nil(s.linked_system_id) do
|
||||
state
|
||||
|> ConnectionsImpl.delete_connection(%{
|
||||
solar_system_source_id: system.solar_system_id,
|
||||
solar_system_target_id: s.linked_system_id
|
||||
})
|
||||
end
|
||||
|
||||
if not is_nil(s.linked_system_id) do
|
||||
state
|
||||
|> SystemsImpl.update_system_linked_sig_eve_id(%{
|
||||
solar_system_id: s.linked_system_id,
|
||||
linked_sig_eve_id: nil
|
||||
})
|
||||
end
|
||||
|
||||
s
|
||||
|> Ash.destroy!()
|
||||
end)
|
||||
|
||||
WandererApp.Api.MapSystemSignature.by_system_id!(system.id)
|
||||
|> Enum.filter(fn s -> s.eve_id in updated_signatures_eve_ids end)
|
||||
|> Enum.each(fn s ->
|
||||
updated = updated_signatures |> Enum.find(fn u -> u.eve_id == s.eve_id end)
|
||||
|
||||
if not is_nil(updated) do
|
||||
s
|
||||
|> WandererApp.Api.MapSystemSignature.update(
|
||||
updated
|
||||
|> Map.put(:updated, System.os_time())
|
||||
)
|
||||
end
|
||||
end)
|
||||
|
||||
added_signatures
|
||||
|> Enum.each(fn s ->
|
||||
s |> WandererApp.Api.MapSystemSignature.create!()
|
||||
end)
|
||||
|
||||
added_signatures_eve_ids =
|
||||
added_signatures
|
||||
|> Enum.map(fn s -> s.eve_id end)
|
||||
|
||||
if not (added_signatures_eve_ids |> Enum.empty?()) do
|
||||
WandererApp.User.ActivityTracker.track_map_event(:signatures_added, %{
|
||||
character_id: character_id,
|
||||
user_id: user_id,
|
||||
map_id: map_id,
|
||||
solar_system_id: system.solar_system_id,
|
||||
signatures: added_signatures_eve_ids
|
||||
})
|
||||
end
|
||||
|
||||
if not (removed_signatures_eve_ids |> Enum.empty?()) do
|
||||
WandererApp.User.ActivityTracker.track_map_event(:signatures_removed, %{
|
||||
character_id: character_id,
|
||||
user_id: user_id,
|
||||
map_id: map_id,
|
||||
solar_system_id: system.solar_system_id,
|
||||
signatures: removed_signatures_eve_ids
|
||||
})
|
||||
end
|
||||
|
||||
Impl.broadcast!(map_id, :signatures_updated, system.solar_system_id)
|
||||
|
||||
state
|
||||
|
||||
_ ->
|
||||
when not is_nil(char_id) do
|
||||
with {:ok, system} <-
|
||||
MapSystem.read_by_map_and_solar_system(%{
|
||||
map_id: map_id,
|
||||
solar_system_id: system_solar_id
|
||||
}),
|
||||
{:ok, %{eve_id: char_eve_id}} <- Character.get_character(char_id) do
|
||||
do_update_signatures(
|
||||
state,
|
||||
system,
|
||||
char_eve_id,
|
||||
user_id,
|
||||
delete_conn?,
|
||||
added_params,
|
||||
updated_params,
|
||||
removed_params
|
||||
)
|
||||
else
|
||||
error ->
|
||||
Logger.warning("Skipping signature update: #{inspect(error)}")
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
def update_signatures(
|
||||
state,
|
||||
_signatures_update
|
||||
),
|
||||
do: state
|
||||
def update_signatures(state, _), do: state
|
||||
|
||||
defp parse_signatures(signatures, character_eve_id, system_id),
|
||||
do:
|
||||
signatures
|
||||
|> Enum.map(fn %{
|
||||
"eve_id" => eve_id,
|
||||
"name" => name,
|
||||
"kind" => kind,
|
||||
"group" => group
|
||||
} = signature ->
|
||||
%{
|
||||
system_id: system_id,
|
||||
eve_id: eve_id,
|
||||
name: name,
|
||||
description: Map.get(signature, "description"),
|
||||
kind: kind,
|
||||
group: group,
|
||||
type: Map.get(signature, "type"),
|
||||
custom_info: Map.get(signature, "custom_info"),
|
||||
character_eve_id: character_eve_id
|
||||
}
|
||||
end)
|
||||
defp do_update_signatures(
|
||||
state,
|
||||
system,
|
||||
character_eve_id,
|
||||
user_id,
|
||||
delete_conn?,
|
||||
added_params,
|
||||
updated_params,
|
||||
removed_params
|
||||
) do
|
||||
# parse incoming DTOs
|
||||
added_sigs = parse_signatures(added_params, character_eve_id, system.id)
|
||||
updated_sigs = parse_signatures(updated_params, character_eve_id, system.id)
|
||||
removed_sigs = parse_signatures(removed_params, character_eve_id, system.id)
|
||||
|
||||
# fetch both current & all (including deleted) signatures once
|
||||
existing_current = MapSystemSignature.by_system_id!(system.id)
|
||||
existing_all = MapSystemSignature.by_system_id_all!(system.id)
|
||||
|
||||
removed_ids = Enum.map(removed_sigs, & &1.eve_id)
|
||||
updated_ids = Enum.map(updated_sigs, & &1.eve_id)
|
||||
added_ids = Enum.map(added_sigs, & &1.eve_id)
|
||||
|
||||
# 1. Removals
|
||||
existing_current
|
||||
|> Enum.filter(&(&1.eve_id in removed_ids))
|
||||
|> Enum.each(&remove_signature(&1, state, system, delete_conn?))
|
||||
|
||||
# 2. Updates
|
||||
existing_current
|
||||
|> Enum.filter(&(&1.eve_id in updated_ids))
|
||||
|> Enum.each(fn existing ->
|
||||
update = Enum.find(updated_sigs, &(&1.eve_id == existing.eve_id))
|
||||
apply_update_signature(existing, update)
|
||||
end)
|
||||
|
||||
# 3. Additions & restorations
|
||||
added_eve_ids = Enum.map(added_sigs, & &1.eve_id)
|
||||
|
||||
existing_index =
|
||||
MapSystemSignature.by_system_id_all!(system.id)
|
||||
|> Enum.filter(&(&1.eve_id in added_eve_ids))
|
||||
|> Map.new(&{&1.eve_id, &1})
|
||||
|
||||
added_sigs
|
||||
|> Enum.each(fn sig ->
|
||||
case existing_index[sig.eve_id] do
|
||||
nil ->
|
||||
MapSystemSignature.create!(sig)
|
||||
|
||||
%MapSystemSignature{deleted: true} = deleted_sig ->
|
||||
MapSystemSignature.update!(
|
||||
deleted_sig,
|
||||
Map.take(sig, [
|
||||
:name,
|
||||
:description,
|
||||
:kind,
|
||||
:group,
|
||||
:type,
|
||||
:character_eve_id,
|
||||
:custom_info,
|
||||
:deleted,
|
||||
:update_forced_at
|
||||
])
|
||||
)
|
||||
|
||||
_ ->
|
||||
:noop
|
||||
end
|
||||
end)
|
||||
|
||||
# 4. Activity tracking
|
||||
if added_ids != [] do
|
||||
track_activity(
|
||||
:signatures_added,
|
||||
state.map_id,
|
||||
system.solar_system_id,
|
||||
user_id,
|
||||
character_eve_id,
|
||||
added_ids
|
||||
)
|
||||
end
|
||||
|
||||
if removed_ids != [] do
|
||||
track_activity(
|
||||
:signatures_removed,
|
||||
state.map_id,
|
||||
system.solar_system_id,
|
||||
user_id,
|
||||
character_eve_id,
|
||||
removed_ids
|
||||
)
|
||||
end
|
||||
|
||||
# 5. Broadcast to any live subscribers
|
||||
Impl.broadcast!(state.map_id, :signatures_updated, system.solar_system_id)
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
defp remove_signature(sig, state, system, delete_conn?) do
|
||||
# optionally remove the linked connection
|
||||
if delete_conn? && sig.linked_system_id do
|
||||
ConnectionsImpl.delete_connection(state, %{
|
||||
solar_system_source_id: system.solar_system_id,
|
||||
solar_system_target_id: sig.linked_system_id
|
||||
})
|
||||
end
|
||||
|
||||
# clear any linked_sig_eve_id on the target system
|
||||
if sig.linked_system_id do
|
||||
SystemsImpl.update_system_linked_sig_eve_id(state, %{
|
||||
solar_system_id: sig.linked_system_id,
|
||||
linked_sig_eve_id: nil
|
||||
})
|
||||
end
|
||||
|
||||
# mark as deleted
|
||||
MapSystemSignature.update!(sig, %{deleted: true})
|
||||
end
|
||||
|
||||
defp apply_update_signature(%MapSystemSignature{} = existing, update_params)
|
||||
when not is_nil(update_params) do
|
||||
case MapSystemSignature.update(
|
||||
existing,
|
||||
update_params |> Map.put(:update_forced_at, DateTime.utc_now())
|
||||
) do
|
||||
{:ok, _updated} ->
|
||||
:ok
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to update signature #{existing.id}: #{inspect(reason)}")
|
||||
end
|
||||
end
|
||||
|
||||
defp track_activity(event, map_id, solar_system_id, user_id, character_id, signatures) do
|
||||
ActivityTracker.track_map_event(event, %{
|
||||
map_id: map_id,
|
||||
solar_system_id: solar_system_id,
|
||||
user_id: user_id,
|
||||
character_id: character_id,
|
||||
signatures: signatures
|
||||
})
|
||||
end
|
||||
|
||||
@doc false
|
||||
defp parse_signatures(signatures, character_eve_id, system_id) do
|
||||
Enum.map(signatures, fn sig ->
|
||||
%{
|
||||
system_id: system_id,
|
||||
eve_id: sig["eve_id"],
|
||||
name: sig["name"],
|
||||
description: Map.get(sig, "description"),
|
||||
kind: sig["kind"],
|
||||
group: sig["group"],
|
||||
type: Map.get(sig, "type"),
|
||||
custom_info: Map.get(sig, "custom_info"),
|
||||
character_eve_id: character_eve_id,
|
||||
deleted: false
|
||||
}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,8 +1,37 @@
|
||||
defmodule WandererApp.MapCharacterSettingsRepo do
|
||||
use WandererApp, :repository
|
||||
|
||||
def create(settings),
|
||||
do: WandererApp.Api.MapCharacterSettings.create(settings)
|
||||
def get(map_id, character_id) do
|
||||
case WandererApp.Api.MapCharacterSettings.read_by_map_and_character(%{
|
||||
map_id: map_id,
|
||||
character_id: character_id
|
||||
}) do
|
||||
{:ok, settings} when not is_nil(settings) ->
|
||||
{:ok, settings}
|
||||
|
||||
_ ->
|
||||
WandererApp.Api.MapCharacterSettings.create(%{
|
||||
character_id: character_id,
|
||||
map_id: map_id,
|
||||
tracked: false
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def create(settings) do
|
||||
WandererApp.Api.MapCharacterSettings.create(settings)
|
||||
end
|
||||
|
||||
def update(map_id, character_id, updated_settings) do
|
||||
case get(map_id, character_id) do
|
||||
{:ok, settings} when not is_nil(settings) ->
|
||||
settings
|
||||
|> WandererApp.Api.MapCharacterSettings.update(updated_settings)
|
||||
|
||||
_ ->
|
||||
{:ok, nil}
|
||||
end
|
||||
end
|
||||
|
||||
def get_tracked_by_map_filtered(map_id, character_ids),
|
||||
do:
|
||||
|
||||
@@ -200,48 +200,28 @@ defmodule WandererAppWeb.MapAccessListAPIController do
|
||||
@spec index(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
||||
operation :index,
|
||||
summary: "List ACLs for a Map",
|
||||
description: "Lists the ACLs for a given map. Requires either 'map_id' or 'slug' as a query parameter to identify the map.",
|
||||
description: "Lists the ACLs for a given map. Provide only one of map_id or slug as a query parameter. If both are provided, the request will fail.",
|
||||
parameters: [
|
||||
map_id: [
|
||||
in: :query,
|
||||
description: "Map identifier (UUID) - Either map_id or slug must be provided",
|
||||
description: "Map identifier (UUID) - Provide only one of map_id or slug.",
|
||||
type: :string,
|
||||
required: false,
|
||||
example: "00000000-0000-0000-0000-000000000000"
|
||||
required: false
|
||||
],
|
||||
slug: [
|
||||
in: :query,
|
||||
description: "Map slug - Either map_id or slug must be provided",
|
||||
description: "Map slug - Provide only one of map_id or slug.",
|
||||
type: :string,
|
||||
required: false,
|
||||
example: "map-name"
|
||||
required: false
|
||||
]
|
||||
],
|
||||
responses: [
|
||||
ok: {
|
||||
"List of ACLs",
|
||||
"application/json",
|
||||
@acl_index_response_schema
|
||||
},
|
||||
ok: {"List of ACLs", "application/json", @acl_index_response_schema},
|
||||
bad_request: {"Error", "application/json", %OpenApiSpex.Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
error: %OpenApiSpex.Schema{type: :string}
|
||||
},
|
||||
properties: %{error: %OpenApiSpex.Schema{type: :string}},
|
||||
required: ["error"],
|
||||
example: %{
|
||||
"error" => "Must provide either ?map_id=UUID or ?slug=SLUG as a query parameter"
|
||||
}
|
||||
}},
|
||||
not_found: {"Error", "application/json", %OpenApiSpex.Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
error: %OpenApiSpex.Schema{type: :string}
|
||||
},
|
||||
required: ["error"],
|
||||
example: %{
|
||||
"error" => "Map not found. Please provide a valid map_id or slug as a query parameter."
|
||||
}
|
||||
example: %{"error" => "Must provide only one of map_id or slug as a query parameter"}
|
||||
}}
|
||||
]
|
||||
def index(conn, params) do
|
||||
@@ -277,46 +257,30 @@ defmodule WandererAppWeb.MapAccessListAPIController do
|
||||
"""
|
||||
@spec create(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
||||
operation :create,
|
||||
summary: "Create a new ACL",
|
||||
description: "Creates a new ACL for a map. Requires either 'map_id' or 'slug' as a query parameter to identify the map.",
|
||||
summary: "Create ACL for a Map",
|
||||
description: "Creates a new ACL for a given map. Provide only one of map_id or slug as a query parameter. If both are provided, the request will fail.",
|
||||
parameters: [
|
||||
map_id: [
|
||||
in: :query,
|
||||
description: "Map identifier (UUID) - Either map_id or slug must be provided",
|
||||
description: "Map identifier (UUID) - Provide only one of map_id or slug.",
|
||||
type: :string,
|
||||
required: false,
|
||||
example: "00000000-0000-0000-0000-000000000000"
|
||||
required: false
|
||||
],
|
||||
slug: [
|
||||
in: :query,
|
||||
description: "Map slug - Either map_id or slug must be provided",
|
||||
description: "Map slug - Provide only one of map_id or slug.",
|
||||
type: :string,
|
||||
required: false,
|
||||
example: "map-name"
|
||||
required: false
|
||||
]
|
||||
],
|
||||
request_body: {"Access List parameters", "application/json", @acl_create_request_schema},
|
||||
request_body: {"ACL parameters", "application/json", @acl_create_request_schema},
|
||||
responses: [
|
||||
ok: {"Access List", "application/json", @acl_create_response_schema},
|
||||
created: {"Created ACL", "application/json", @acl_create_response_schema},
|
||||
bad_request: {"Error", "application/json", %OpenApiSpex.Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
error: %OpenApiSpex.Schema{type: :string}
|
||||
},
|
||||
properties: %{error: %OpenApiSpex.Schema{type: :string}},
|
||||
required: ["error"],
|
||||
example: %{
|
||||
"error" => "Must provide either ?map_id=UUID or ?slug=SLUG as a query parameter"
|
||||
}
|
||||
}},
|
||||
not_found: {"Error", "application/json", %OpenApiSpex.Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
error: %OpenApiSpex.Schema{type: :string}
|
||||
},
|
||||
required: ["error"],
|
||||
example: %{
|
||||
"error" => "Map not found. Please provide a valid map_id or slug as a query parameter."
|
||||
}
|
||||
example: %{"error" => "Must provide only one of map_id or slug as a query parameter"}
|
||||
}}
|
||||
]
|
||||
def create(conn, params) do
|
||||
|
||||
@@ -246,7 +246,6 @@ defmodule WandererAppWeb.MapAPIController do
|
||||
in: :query,
|
||||
description: "Map slug",
|
||||
type: :string,
|
||||
example: "my-map",
|
||||
required: false
|
||||
],
|
||||
map_id: [
|
||||
@@ -319,7 +318,7 @@ defmodule WandererAppWeb.MapAPIController do
|
||||
end
|
||||
|
||||
@doc """
|
||||
GET /api/map/structure_timers
|
||||
GET /api/map/structure-timers
|
||||
|
||||
Returns structure timers for visible systems on the map or for a specific system.
|
||||
"""
|
||||
@@ -327,6 +326,7 @@ defmodule WandererAppWeb.MapAPIController do
|
||||
operation :show_structure_timers,
|
||||
summary: "Show Structure Timers",
|
||||
description: "Retrieves structure timers for a map.",
|
||||
deprecated: true,
|
||||
parameters: [
|
||||
map_id: [
|
||||
in: :query,
|
||||
@@ -342,7 +342,7 @@ defmodule WandererAppWeb.MapAPIController do
|
||||
],
|
||||
system_id: [
|
||||
in: :query,
|
||||
description: "System ID",
|
||||
description: "Optional: System ID to filter timers for a specific system",
|
||||
type: :string,
|
||||
required: false
|
||||
]
|
||||
@@ -790,15 +790,13 @@ defmodule WandererAppWeb.MapAPIController do
|
||||
in: :query,
|
||||
description: "Map identifier (UUID) - Either map_id or slug must be provided",
|
||||
type: :string,
|
||||
required: false,
|
||||
example: ""
|
||||
required: false
|
||||
],
|
||||
slug: [
|
||||
in: :query,
|
||||
description: "Map slug - Either map_id or slug must be provided",
|
||||
type: :string,
|
||||
required: false,
|
||||
example: "map-name"
|
||||
required: false
|
||||
]
|
||||
],
|
||||
responses: [
|
||||
|
||||
@@ -2,15 +2,9 @@ defmodule WandererAppWeb.MapAuditAPIController do
|
||||
use WandererAppWeb, :controller
|
||||
use OpenApiSpex.ControllerSpecs
|
||||
|
||||
import Ash.Query, only: [filter: 2]
|
||||
require Logger
|
||||
|
||||
alias WandererApp.Api
|
||||
alias WandererApp.Api.Character
|
||||
alias WandererApp.MapSystemRepo
|
||||
alias WandererApp.MapCharacterSettingsRepo
|
||||
|
||||
alias WandererApp.Zkb.KillsProvider.KillsCache
|
||||
|
||||
alias WandererAppWeb.Helpers.APIUtils
|
||||
|
||||
@@ -158,15 +152,4 @@ defmodule WandererAppWeb.MapAuditAPIController do
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
defp get_original_system_name(solar_system_id) do
|
||||
# Fetch the original system name from the MapSolarSystem resource
|
||||
case WandererApp.Api.MapSolarSystem.by_solar_system_id(solar_system_id) do
|
||||
{:ok, system} ->
|
||||
system.solar_system_name
|
||||
|
||||
_error ->
|
||||
"Unknown System"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -129,23 +129,46 @@ defmodule WandererAppWeb.MapConnectionAPIController do
|
||||
|
||||
operation :index,
|
||||
summary: "List Map Connections",
|
||||
description: "Lists all connections for a map.",
|
||||
parameters: [
|
||||
map_identifier: [
|
||||
in: :path,
|
||||
description: "Map identifier (UUID or slug). Provide either a UUID or a slug.",
|
||||
description: "Map identifier (UUID or slug)",
|
||||
type: :string,
|
||||
required: true,
|
||||
example: "map-slug or map UUID"
|
||||
],
|
||||
solar_system_source: [in: :query, type: :integer, required: false],
|
||||
solar_system_target: [in: :query, type: :integer, required: false]
|
||||
solar_system_source: [
|
||||
in: :query,
|
||||
description: "Filter connections by source system ID",
|
||||
type: :integer,
|
||||
required: false,
|
||||
example: 30000142
|
||||
],
|
||||
solar_system_target: [
|
||||
in: :query,
|
||||
description: "Filter connections by target system ID",
|
||||
type: :integer,
|
||||
required: false,
|
||||
example: 30000144
|
||||
]
|
||||
],
|
||||
responses: [
|
||||
ok: {
|
||||
"List Map Connections",
|
||||
"List of Map Connections",
|
||||
"application/json",
|
||||
@list_response_schema
|
||||
}
|
||||
},
|
||||
not_found: {"Error", "application/json", %OpenApiSpex.Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
error: %OpenApiSpex.Schema{type: :string}
|
||||
},
|
||||
required: ["error"],
|
||||
example: %{
|
||||
"error" => "Map not found"
|
||||
}
|
||||
}}
|
||||
]
|
||||
def index(%{assigns: %{map_id: map_id}} = conn, params) do
|
||||
with {:ok, src_filter} <- parse_optional(params, "solar_system_source"),
|
||||
@@ -187,7 +210,7 @@ defmodule WandererAppWeb.MapConnectionAPIController do
|
||||
parameters: [
|
||||
map_identifier: [
|
||||
in: :path,
|
||||
description: "Map identifier (UUID or slug). Provide either a UUID or a slug.",
|
||||
description: "Map identifier (UUID or slug)",
|
||||
type: :string,
|
||||
required: true,
|
||||
example: "map-slug or map UUID"
|
||||
@@ -218,12 +241,11 @@ defmodule WandererAppWeb.MapConnectionAPIController do
|
||||
parameters: [
|
||||
map_identifier: [
|
||||
in: :path,
|
||||
description: "Map identifier (UUID or slug). Provide either a UUID or a slug.",
|
||||
description: "Map identifier (UUID or slug)",
|
||||
type: :string,
|
||||
required: true,
|
||||
example: "map-slug or map UUID"
|
||||
],
|
||||
system_id: [in: :path, type: :string, required: false]
|
||||
]
|
||||
],
|
||||
request_body: {"Connection create", "application/json", @connection_request_schema},
|
||||
responses: ResponseSchemas.create_responses(@detail_response_schema)
|
||||
@@ -256,7 +278,7 @@ defmodule WandererAppWeb.MapConnectionAPIController do
|
||||
parameters: [
|
||||
map_identifier: [
|
||||
in: :path,
|
||||
description: "Map identifier (UUID or slug). Provide either a UUID or a slug.",
|
||||
description: "Map identifier (UUID or slug)",
|
||||
type: :string,
|
||||
required: true,
|
||||
example: "map-slug or map UUID"
|
||||
@@ -344,7 +366,7 @@ defmodule WandererAppWeb.MapConnectionAPIController do
|
||||
parameters: [
|
||||
map_identifier: [
|
||||
in: :path,
|
||||
description: "Map identifier (UUID or slug). Provide either a UUID or a slug.",
|
||||
description: "Map identifier (UUID or slug)",
|
||||
type: :string,
|
||||
required: true,
|
||||
example: "map-slug or map UUID"
|
||||
@@ -442,9 +464,49 @@ defmodule WandererAppWeb.MapConnectionAPIController do
|
||||
@deprecated "Use GET /api/maps/:map_identifier/systems instead"
|
||||
operation :list_all_connections,
|
||||
summary: "List All Connections (Legacy)",
|
||||
description: "Legacy endpoint for listing connections. Use GET /api/maps/:map_identifier/connections instead. Requires exactly one of map_id or slug as a query parameter. If both are provided, a 400 Bad Request will be returned.",
|
||||
deprecated: true,
|
||||
parameters: [map_id: [in: :query]],
|
||||
responses: ResponseSchemas.standard_responses(@list_response_schema)
|
||||
parameters: [
|
||||
map_id: [
|
||||
in: :query,
|
||||
description: "Map identifier (UUID) - Exactly one of map_id or slug must be provided",
|
||||
type: :string,
|
||||
required: false
|
||||
],
|
||||
slug: [
|
||||
in: :query,
|
||||
description: "Map slug - Exactly one of map_id or slug must be provided",
|
||||
type: :string,
|
||||
required: false
|
||||
]
|
||||
],
|
||||
responses: [
|
||||
ok: {
|
||||
"List of Map Connections",
|
||||
"application/json",
|
||||
@list_response_schema
|
||||
},
|
||||
bad_request: {"Error", "application/json", %OpenApiSpex.Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
error: %OpenApiSpex.Schema{type: :string}
|
||||
},
|
||||
required: ["error"],
|
||||
example: %{
|
||||
"error" => "Must provide exactly one of map_id or slug as a query parameter"
|
||||
}
|
||||
}},
|
||||
not_found: {"Error", "application/json", %OpenApiSpex.Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
error: %OpenApiSpex.Schema{type: :string}
|
||||
},
|
||||
required: ["error"],
|
||||
example: %{
|
||||
"error" => "Map not found. Please provide a valid map_id or slug as a query parameter."
|
||||
}
|
||||
}}
|
||||
]
|
||||
def list_all_connections(%{assigns: %{map_id: map_id}} = conn, _params) do
|
||||
connections = Operations.list_connections(map_id)
|
||||
data = Enum.map(connections, &APIUtils.connection_to_json/1)
|
||||
|
||||
@@ -24,15 +24,18 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
solar_system_id: %Schema{type: :integer, description: "EVE solar system ID"},
|
||||
solar_system_name: %Schema{type: :string, description: "EVE solar system name"},
|
||||
region_name: %Schema{type: :string, description: "EVE region name"},
|
||||
position_x: %Schema{type: :number, format: :float, description: "X coordinate"},
|
||||
position_y: %Schema{type: :number, format: :float, description: "Y coordinate"},
|
||||
status: %Schema{type: :string, description: "System status"},
|
||||
position_x: %Schema{type: :integer, description: "X coordinate"},
|
||||
position_y: %Schema{type: :integer, description: "Y coordinate"},
|
||||
status: %Schema{
|
||||
type: :integer,
|
||||
description: "System status (0: unknown, 1: friendly, 2: warning, 3: targetPrimary, 4: targetSecondary, 5: dangerousPrimary, 6: dangerousSecondary, 7: lookingFor, 8: home)"
|
||||
},
|
||||
visible: %Schema{type: :boolean, description: "Visibility flag"},
|
||||
description: %Schema{type: :string, nullable: true, description: "Custom description"},
|
||||
tag: %Schema{type: :string, nullable: true, description: "Custom tag"},
|
||||
locked: %Schema{type: :boolean, description: "Lock flag"},
|
||||
temporary_name: %Schema{type: :string, nullable: true, description: "Temporary name"},
|
||||
labels: %Schema{type: :array, items: %Schema{type: :string}, nullable: true, description: "Labels"}
|
||||
labels: %Schema{type: :string, description: "Comma-separated list of labels"}
|
||||
},
|
||||
required: ~w(id map_id solar_system_id)a
|
||||
}
|
||||
@@ -42,23 +45,27 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
properties: %{
|
||||
solar_system_id: %Schema{type: :integer, description: "EVE solar system ID"},
|
||||
solar_system_name: %Schema{type: :string, description: "EVE solar system name"},
|
||||
position_x: %Schema{type: :number, format: :float, description: "X coordinate"},
|
||||
position_y: %Schema{type: :number, format: :float, description: "Y coordinate"},
|
||||
status: %Schema{type: :string, description: "System status"},
|
||||
position_x: %Schema{type: :integer, description: "X coordinate"},
|
||||
position_y: %Schema{type: :integer, description: "Y coordinate"},
|
||||
status: %Schema{
|
||||
type: :integer,
|
||||
description: "System status (0: unknown, 1: friendly, 2: warning, 3: targetPrimary, 4: targetSecondary, 5: dangerousPrimary, 6: dangerousSecondary, 7: lookingFor, 8: home)"
|
||||
},
|
||||
visible: %Schema{type: :boolean, description: "Visibility flag"},
|
||||
description: %Schema{type: :string, nullable: true, description: "Custom description"},
|
||||
tag: %Schema{type: :string, nullable: true, description: "Custom tag"},
|
||||
locked: %Schema{type: :boolean, description: "Lock flag"},
|
||||
temporary_name: %Schema{type: :string, nullable: true, description: "Temporary name"},
|
||||
labels: %Schema{type: :array, items: %Schema{type: :string}, nullable: true, description: "Labels"}
|
||||
labels: %Schema{type: :string, description: "Comma-separated list of labels"}
|
||||
},
|
||||
required: ~w(solar_system_id)a,
|
||||
example: %{
|
||||
solar_system_id: 30_000_142,
|
||||
solar_system_name: "Jita",
|
||||
position_x: 100.5,
|
||||
position_y: 200.3,
|
||||
visible: true
|
||||
position_x: 100,
|
||||
position_y: 200,
|
||||
visible: true,
|
||||
labels: "market,hub"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,24 +73,29 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
type: :object,
|
||||
properties: %{
|
||||
solar_system_name: %Schema{type: :string, description: "EVE solar system name", nullable: true},
|
||||
position_x: %Schema{type: :number, format: :float, description: "X coordinate", nullable: true},
|
||||
position_y: %Schema{type: :number, format: :float, description: "Y coordinate", nullable: true},
|
||||
status: %Schema{type: :string, description: "System status", nullable: true},
|
||||
position_x: %Schema{type: :integer, description: "X coordinate", nullable: true},
|
||||
position_y: %Schema{type: :integer, description: "Y coordinate", nullable: true},
|
||||
status: %Schema{
|
||||
type: :integer,
|
||||
description: "System status (0: unknown, 1: friendly, 2: warning, 3: targetPrimary, 4: targetSecondary, 5: dangerousPrimary, 6: dangerousSecondary, 7: lookingFor, 8: home)",
|
||||
nullable: true
|
||||
},
|
||||
visible: %Schema{type: :boolean, description: "Visibility flag", nullable: true},
|
||||
description: %Schema{type: :string, nullable: true, description: "Custom description"},
|
||||
tag: %Schema{type: :string, nullable: true, description: "Custom tag"},
|
||||
locked: %Schema{type: :boolean, description: "Lock flag", nullable: true},
|
||||
temporary_name: %Schema{type: :string, nullable: true, description: "Temporary name"},
|
||||
labels: %Schema{type: :array, items: %Schema{type: :string}, nullable: true, description: "Labels"}
|
||||
labels: %Schema{type: :string, description: "Comma-separated list of labels"}
|
||||
},
|
||||
example: %{
|
||||
solar_system_name: "Jita",
|
||||
position_x: 101.0,
|
||||
position_y: 202.0,
|
||||
position_x: 101,
|
||||
position_y: 202,
|
||||
visible: false,
|
||||
status: "active",
|
||||
status: 0,
|
||||
tag: "HQ",
|
||||
locked: true
|
||||
locked: true,
|
||||
labels: "market,hub"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,10 +302,10 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
parameters: [
|
||||
map_identifier: [
|
||||
in: :path,
|
||||
description: "Map identifier (UUID or slug). Provide either a UUID or a slug.",
|
||||
description: "Map identifier (UUID or slug)",
|
||||
type: :string,
|
||||
required: true,
|
||||
example: "my-map-slug or map UUID"
|
||||
example: "map-slug or map UUID"
|
||||
]
|
||||
],
|
||||
responses: [
|
||||
@@ -314,12 +326,17 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
parameters: [
|
||||
map_identifier: [
|
||||
in: :path,
|
||||
description: "Map identifier (UUID or slug). Provide either a UUID or a slug.",
|
||||
description: "Map identifier (UUID or slug)",
|
||||
type: :string,
|
||||
required: true,
|
||||
example: "my-map-slug or map UUID"
|
||||
example: "map-slug or map UUID"
|
||||
],
|
||||
id: [in: :path, type: :string, required: true]
|
||||
id: [
|
||||
in: :path,
|
||||
description: "System ID",
|
||||
type: :string,
|
||||
required: true
|
||||
]
|
||||
],
|
||||
responses: ResponseSchemas.standard_responses(@detail_response_schema)
|
||||
def show(%{assigns: %{map_id: map_id}} = conn, %{"id" => id}) do
|
||||
@@ -334,10 +351,10 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
parameters: [
|
||||
map_identifier: [
|
||||
in: :path,
|
||||
description: "Map identifier (UUID or slug). Provide either a UUID or a slug.",
|
||||
description: "Map identifier (UUID or slug)",
|
||||
type: :string,
|
||||
required: true,
|
||||
example: "my-map-slug or map UUID"
|
||||
example: "map-slug or map UUID"
|
||||
]
|
||||
],
|
||||
request_body: {"Systems+Connections upsert", "application/json", @batch_request_schema},
|
||||
@@ -358,12 +375,17 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
parameters: [
|
||||
map_identifier: [
|
||||
in: :path,
|
||||
description: "Map identifier (UUID or slug). Provide either a UUID or a slug.",
|
||||
description: "Map identifier (UUID or slug)",
|
||||
type: :string,
|
||||
required: true,
|
||||
example: "my-map-slug or map UUID"
|
||||
example: "map-slug or map UUID"
|
||||
],
|
||||
id: [in: :path, type: :string, required: true]
|
||||
id: [
|
||||
in: :path,
|
||||
description: "System ID",
|
||||
type: :string,
|
||||
required: true
|
||||
]
|
||||
],
|
||||
request_body: {"System update request", "application/json", @system_update_schema},
|
||||
responses: ResponseSchemas.update_responses(@detail_response_schema)
|
||||
@@ -381,10 +403,10 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
parameters: [
|
||||
map_identifier: [
|
||||
in: :path,
|
||||
description: "Map identifier (UUID or slug). Provide either a UUID or a slug.",
|
||||
description: "Map identifier (UUID or slug)",
|
||||
type: :string,
|
||||
required: true,
|
||||
example: "my-map-slug or map UUID"
|
||||
example: "map-slug or map UUID"
|
||||
]
|
||||
],
|
||||
request_body: {"Batch delete", "application/json", @batch_delete_schema},
|
||||
@@ -428,12 +450,17 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
parameters: [
|
||||
map_identifier: [
|
||||
in: :path,
|
||||
description: "Map identifier (UUID or slug). Provide either a UUID or a slug.",
|
||||
description: "Map identifier (UUID or slug)",
|
||||
type: :string,
|
||||
required: true,
|
||||
example: "my-map-slug or map UUID"
|
||||
example: "map-slug or map UUID"
|
||||
],
|
||||
id: [in: :path, type: :string, required: true]
|
||||
id: [
|
||||
in: :path,
|
||||
description: "System ID",
|
||||
type: :string,
|
||||
required: true
|
||||
]
|
||||
],
|
||||
responses: ResponseSchemas.standard_responses(@delete_response_schema)
|
||||
def delete_single(conn, %{"id" => id}) do
|
||||
@@ -462,7 +489,20 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
summary: "List Map Systems (Legacy)",
|
||||
deprecated: true,
|
||||
description: "Deprecated, use GET /api/maps/:map_identifier/systems instead",
|
||||
parameters: [map_id: [in: :query]],
|
||||
parameters: [
|
||||
map_id: [
|
||||
in: :query,
|
||||
description: "Map identifier (UUID) - Either map_id or slug must be provided, but not both",
|
||||
type: :string,
|
||||
required: false,
|
||||
],
|
||||
slug: [
|
||||
in: :query,
|
||||
description: "Map slug - Either map_id or slug must be provided, but not both",
|
||||
type: :string,
|
||||
required: false,
|
||||
]
|
||||
],
|
||||
responses: ResponseSchemas.standard_responses(@list_response_schema)
|
||||
defdelegate list_systems(conn, params), to: __MODULE__, as: :index
|
||||
|
||||
@@ -470,7 +510,26 @@ defmodule WandererAppWeb.MapSystemAPIController do
|
||||
summary: "Show Map System (Legacy)",
|
||||
deprecated: true,
|
||||
description: "Deprecated, use GET /api/maps/:map_identifier/systems/:id instead",
|
||||
parameters: [map_id: [in: :query], id: [in: :query]],
|
||||
parameters: [
|
||||
map_id: [
|
||||
in: :query,
|
||||
description: "Map identifier (UUID) - Either map_id or slug must be provided, but not both",
|
||||
type: :string,
|
||||
required: false,
|
||||
],
|
||||
slug: [
|
||||
in: :query,
|
||||
description: "Map slug - Either map_id or slug must be provided, but not both",
|
||||
type: :string,
|
||||
required: false,
|
||||
],
|
||||
id: [
|
||||
in: :query,
|
||||
description: "System ID",
|
||||
type: :string,
|
||||
required: true
|
||||
]
|
||||
],
|
||||
responses: ResponseSchemas.standard_responses(@detail_response_schema)
|
||||
defdelegate show_system(conn, params), to: __MODULE__, as: :show
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ defmodule WandererAppWeb.MapSystemStructureAPIController do
|
||||
|
||||
@moduledoc """
|
||||
API controller for managing map system structures.
|
||||
Includes legacy structure-timers endpoint (deprecated).
|
||||
"""
|
||||
|
||||
# Inlined OpenAPI schema for a map system structure
|
||||
@@ -174,16 +173,25 @@ defmodule WandererAppWeb.MapSystemStructureAPIController do
|
||||
end
|
||||
|
||||
@doc """
|
||||
@deprecated "Use /structures instead. This endpoint will be removed in a future release."
|
||||
Legacy: Get structure timers for a map.
|
||||
Get structure timers for a map.
|
||||
"""
|
||||
operation :structure_timers,
|
||||
summary: "Get structure timers for a map (Legacy)",
|
||||
deprecated: true,
|
||||
summary: "Get structure timers for a map",
|
||||
parameters: [
|
||||
map_identifier: [in: :path, description: "Map identifier (UUID or slug)", type: :string, required: true]
|
||||
],
|
||||
responses: [ok: {"Structure timers", "application/json", %Schema{type: :array, items: %Schema{type: :object}}}]
|
||||
responses: [ok: {"Structure timers", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
data: %Schema{
|
||||
type: :array,
|
||||
items: @structure_schema
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
data: [@structure_schema.example]
|
||||
}
|
||||
}}]
|
||||
def structure_timers(conn, _params) do
|
||||
map_id = conn.assigns.map_id
|
||||
structures = MapOperations.list_structures(map_id)
|
||||
|
||||
@@ -91,6 +91,8 @@ defmodule WandererAppWeb.CharactersTrackingLive do
|
||||
character_setting
|
||||
|> WandererApp.MapCharacterSettingsRepo.untrack!()
|
||||
|
||||
WandererApp.Map.Server.untrack_characters(selected_map.id, [character_setting.character_id])
|
||||
|
||||
_ ->
|
||||
character_setting
|
||||
|> WandererApp.MapCharacterSettingsRepo.track!()
|
||||
|
||||
@@ -32,6 +32,16 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
||||
)
|
||||
end
|
||||
|
||||
def handle_server_event(%{event: :untrack_character, payload: character_id}, %{
|
||||
assigns: %{
|
||||
map_id: map_id
|
||||
}
|
||||
} = socket) do
|
||||
:ok = WandererApp.Character.TrackingUtils.untrack([%{id: character_id}], map_id, self())
|
||||
socket
|
||||
end
|
||||
|
||||
|
||||
def handle_server_event(
|
||||
%{event: :characters_updated},
|
||||
%{
|
||||
@@ -342,9 +352,6 @@ defmodule WandererAppWeb.MapCharactersEventHandler do
|
||||
self()
|
||||
)
|
||||
|
||||
:ok =
|
||||
WandererApp.Character.TrackingUtils.add_characters(map_characters, map_id, track_character)
|
||||
|
||||
socket
|
||||
end
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
||||
case track_character do
|
||||
false ->
|
||||
:ok = WandererApp.Character.TrackingUtils.untrack(map_characters, map_id, self())
|
||||
:ok = WandererApp.Character.TrackingUtils.remove_characters(map_characters, map_id)
|
||||
|
||||
_ ->
|
||||
:ok =
|
||||
@@ -67,13 +66,6 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
||||
true,
|
||||
self()
|
||||
)
|
||||
|
||||
:ok =
|
||||
WandererApp.Character.TrackingUtils.add_characters(
|
||||
map_characters,
|
||||
map_id,
|
||||
track_character
|
||||
)
|
||||
end
|
||||
|
||||
socket
|
||||
|
||||
@@ -267,6 +267,39 @@ defmodule WandererAppWeb.MapSignaturesEventHandler do
|
||||
end
|
||||
end
|
||||
|
||||
def handle_ui_event(
|
||||
"undo_delete_signatures",
|
||||
%{"system_id" => solar_system_id, "eve_ids" => eve_ids} = payload,
|
||||
%{
|
||||
assigns: %{
|
||||
map_id: map_id,
|
||||
main_character_id: main_character_id,
|
||||
user_permissions: %{update_system: true}
|
||||
}
|
||||
} = socket
|
||||
)
|
||||
when not is_nil(main_character_id) do
|
||||
case WandererApp.Api.MapSystem.read_by_map_and_solar_system(%{
|
||||
map_id: map_id,
|
||||
solar_system_id: get_integer(solar_system_id)
|
||||
}) do
|
||||
{:ok, system} ->
|
||||
restored =
|
||||
WandererApp.Api.MapSystemSignature.by_system_id_all!(system.id)
|
||||
|> Enum.filter(fn s -> s.eve_id in eve_ids end)
|
||||
|> Enum.map(fn s ->
|
||||
s |> WandererApp.Api.MapSystemSignature.update!(%{deleted: false})
|
||||
end)
|
||||
Phoenix.PubSub.broadcast!(WandererApp.PubSub, map_id, %{
|
||||
event: :signatures_updated,
|
||||
payload: system.solar_system_id
|
||||
})
|
||||
{:noreply, socket}
|
||||
_ ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_ui_event(event, body, socket),
|
||||
do: MapCoreEventHandler.handle_ui_event(event, body, socket)
|
||||
|
||||
|
||||
@@ -84,11 +84,7 @@ defmodule WandererAppWeb.MapCharactersLive do
|
||||
character_setting ->
|
||||
case character_setting.tracked do
|
||||
true ->
|
||||
{:ok, map_character_settings} =
|
||||
character_setting
|
||||
|> WandererApp.MapCharacterSettingsRepo.untrack()
|
||||
|
||||
WandererApp.Map.Server.remove_character(map_id, map_character_settings.character_id)
|
||||
WandererApp.Map.Server.untrack_characters(map_id, [character_setting.character_id])
|
||||
|
||||
socket |> put_flash(:info, "Character untracked!") |> load_characters()
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ defmodule WandererAppWeb.MapEventHandler do
|
||||
:characters_updated,
|
||||
:present_characters_updated,
|
||||
:refresh_user_characters,
|
||||
:show_tracking
|
||||
:show_tracking,
|
||||
:untrack_character
|
||||
]
|
||||
|
||||
@map_characters_ui_events [
|
||||
@@ -121,7 +122,8 @@ defmodule WandererAppWeb.MapEventHandler do
|
||||
"update_signatures",
|
||||
"get_signatures",
|
||||
"link_signature_to_system",
|
||||
"unlink_signature"
|
||||
"unlink_signature",
|
||||
"undo_delete_signatures"
|
||||
]
|
||||
|
||||
@map_structures_events [
|
||||
|
||||
@@ -4,7 +4,7 @@ defmodule WandererAppWeb.MapLive do
|
||||
|
||||
require Logger
|
||||
|
||||
@server_event_unsync_timeout :timer.minutes(5)
|
||||
@server_event_unsync_timeout :timer.minutes(2)
|
||||
|
||||
@impl true
|
||||
def mount(%{"slug" => map_slug} = _params, _session, socket) when is_connected?(socket) do
|
||||
|
||||
@@ -212,7 +212,7 @@ defmodule WandererAppWeb.Router do
|
||||
get "/system", MapSystemAPIController, :show_system
|
||||
get "/connections", MapConnectionAPIController, :list_all_connections
|
||||
get "/characters", MapAPIController, :list_tracked_characters
|
||||
get "/structure-timers", MapSystemStructureAPIController, :structure_timers
|
||||
get "/structure-timers", MapAPIController, :show_structure_timers
|
||||
get "/character-activity", MapAPIController, :character_activity
|
||||
get "/user_characters", MapAPIController, :user_characters
|
||||
|
||||
|
||||
@@ -38,14 +38,23 @@ defmodule WandererAppWeb.Schemas.ApiSchemas do
|
||||
%Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
eve_id: %Schema{type: :string},
|
||||
name: %Schema{type: :string},
|
||||
corporation_id: %Schema{type: :string},
|
||||
corporation_ticker: %Schema{type: :string},
|
||||
alliance_id: %Schema{type: :string},
|
||||
alliance_ticker: %Schema{type: :string}
|
||||
id: %Schema{type: :string, description: "Character UUID"},
|
||||
eve_id: %Schema{type: :string, description: "EVE Online character ID"},
|
||||
name: %Schema{type: :string, description: "Character name"},
|
||||
online: %Schema{type: :boolean, description: "Online status"},
|
||||
corporation_id: %Schema{type: :integer, description: "Corporation ID"},
|
||||
corporation_name: %Schema{type: :string, description: "Corporation name"},
|
||||
corporation_ticker: %Schema{type: :string, description: "Corporation ticker"},
|
||||
alliance_id: %Schema{type: :integer, description: "Alliance ID"},
|
||||
alliance_name: %Schema{type: :string, description: "Alliance name"},
|
||||
alliance_ticker: %Schema{type: :string, description: "Alliance ticker"},
|
||||
solar_system_id: %Schema{type: :integer, description: "Current solar system ID"},
|
||||
ship: %Schema{type: :integer, description: "Current ship type ID"},
|
||||
ship_name: %Schema{type: :string, description: "Current ship name"},
|
||||
inserted_at: %Schema{type: :string, format: :date_time, description: "Creation timestamp"},
|
||||
updated_at: %Schema{type: :string, format: :date_time, description: "Last update timestamp"}
|
||||
},
|
||||
required: ["eve_id", "name"]
|
||||
required: ~w(eve_id name)a
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
2
mix.exs
2
mix.exs
@@ -3,7 +3,7 @@ defmodule WandererApp.MixProject do
|
||||
|
||||
@source_url "https://github.com/wanderer-industries/wanderer"
|
||||
|
||||
@version "1.64.8"
|
||||
@version "1.65.7"
|
||||
|
||||
def project do
|
||||
[
|
||||
|
||||
70
priv/posts/2025/05-08-signature-deletion-flow.md
Normal file
70
priv/posts/2025/05-08-signature-deletion-flow.md
Normal file
@@ -0,0 +1,70 @@
|
||||
%{
|
||||
title: "Instant Signature Deletion & Undo: A New Flow for Map Signatures",
|
||||
author: "Wanderer Team",
|
||||
cover_image_uri: "/images/news/05-08-undo/undo.png",
|
||||
tags: ~w(signatures deletion undo map realtime guide),
|
||||
description: "Learn about the new instant signature deletion flow, real-time updates, and the ability to undo removals in Wanderer maps."
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
### Introduction
|
||||
|
||||
Managing cosmic signatures is a core part of mapping and navigation in EVE Online. With our latest update, signature deletion is now **instant, real-time, and reversible**—making it easier than ever to keep your map up to date and error-free.
|
||||
|
||||
This guide covers the new signature deletion flow, how to use the undo feature, and what happens behind the scenes to keep your map clean and synchronized for all users.
|
||||
|
||||
---
|
||||
|
||||
### 1. The New User Flow: Instant, Real-Time, Reversible
|
||||
|
||||
- **Delete a signature:** When you remove a signature, it disappears from your map (and all other users' maps) instantly after a server roundtrip.
|
||||
- **Undo:** If you make a mistake, you have a window of up to 30s to undo the deletion
|
||||
|
||||
---
|
||||
|
||||
### 2. How to Use the New Signature Deletion Flow
|
||||
|
||||
1. **Select and Delete:**
|
||||
- Open the system signatures widget.
|
||||
- Select one or more signatures and click delete (or paste and use lazy delete).
|
||||
- The signatures will disappear for all users viewing the same system.
|
||||
|
||||
2. **Undo a Deletion:**
|
||||
- After deleting, an **Undo** button appears for you (the user who deleted the signature) and remains visible based on your timeout settings.
|
||||
- Click **Undo** to restore the removed signatures instantly for all users.
|
||||
- If you don't click Undo in time, the deletion becomes permanent
|
||||
|
||||
3. **Real-Time Updates:**
|
||||
- All users see signature changes (add, update, remove, undo) in real time—no need to refresh.
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 4. FAQ & Troubleshooting
|
||||
|
||||
|
||||
**Q: Who sees the Undo button?**
|
||||
- Only the user who deleted the signature sees the Undo button
|
||||
|
||||
**Q: Do all users see the same signature list in real time?**
|
||||
- Yes! All changes are broadcast instantly to everyone viewing the same map.
|
||||
|
||||
**Q: Can I configure the undo timeout?**
|
||||
- Yes, in the user inteface settings for the signatures widget
|
||||
|
||||
**Q: What about performance?**
|
||||
- The new flow is optimized for real-time collaboration and efficient cleanup, ensuring your map stays fast and accurate.
|
||||
|
||||
---
|
||||
|
||||
### 5. Summary
|
||||
|
||||
The new signature deletion flow brings instant, real-time updates and a safety net for accidental removals. Enjoy a more collaborative, error-resistant mapping experience—now live for all Wanderer users!
|
||||
|
||||
---
|
||||
|
||||
Fly safe,
|
||||
**The Wanderer Team**
|
||||
|
||||
---
|
||||
@@ -0,0 +1,23 @@
|
||||
defmodule WandererApp.Repo.Migrations.AddDeletedSignature do
|
||||
@moduledoc """
|
||||
Updates resources based on their most recent snapshots.
|
||||
|
||||
This file was autogenerated with `mix ash_postgres.generate_migrations`
|
||||
"""
|
||||
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
alter table(:map_system_signatures_v1) do
|
||||
remove :updated
|
||||
add :deleted, :boolean, null: false, default: false
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
alter table(:map_system_signatures_v1) do
|
||||
remove :deleted
|
||||
add :updated, :bigint
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
defmodule WandererApp.Repo.Migrations.AddSigUpdateForcedAt do
|
||||
@moduledoc """
|
||||
Updates resources based on their most recent snapshots.
|
||||
|
||||
This file was autogenerated with `mix ash_postgres.generate_migrations`
|
||||
"""
|
||||
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
alter table(:map_system_signatures_v1) do
|
||||
add :update_forced_at, :utc_datetime
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
alter table(:map_system_signatures_v1) do
|
||||
remove :update_forced_at
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
defmodule WandererApp.Repo.Migrations.AddMapCharacterTrackingInfo do
|
||||
@moduledoc """
|
||||
Updates resources based on their most recent snapshots.
|
||||
|
||||
This file was autogenerated with `mix ash_postgres.generate_migrations`
|
||||
"""
|
||||
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
alter table(:map_character_settings_v1) do
|
||||
add :encrypted_ship, :binary
|
||||
add :encrypted_ship_name, :binary
|
||||
add :encrypted_ship_item_id, :binary
|
||||
add :encrypted_solar_system_id, :binary
|
||||
add :encrypted_structure_id, :binary
|
||||
add :encrypted_station_id, :binary
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
alter table(:map_character_settings_v1) do
|
||||
remove :encrypted_station_id
|
||||
remove :encrypted_structure_id
|
||||
remove :encrypted_solar_system_id
|
||||
remove :encrypted_ship_item_id
|
||||
remove :encrypted_ship_name
|
||||
remove :encrypted_ship
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,206 @@
|
||||
{
|
||||
"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": "false",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "tracked",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "false",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "followed",
|
||||
"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?": false,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": true,
|
||||
"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_character_settings_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,
|
||||
"index?": false,
|
||||
"match_type": null,
|
||||
"match_with": null,
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"name": "map_character_settings_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"
|
||||
},
|
||||
{
|
||||
"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_ship_name",
|
||||
"type": "binary"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "encrypted_ship_item_id",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"base_filter": null,
|
||||
"check_constraints": [],
|
||||
"custom_indexes": [],
|
||||
"custom_statements": [],
|
||||
"has_create_action": true,
|
||||
"hash": "3F585D1C545A5264AEFA05502C0F625F9B27B15CA36699DCF37E4F834E6339AE",
|
||||
"identities": [
|
||||
{
|
||||
"all_tenants?": false,
|
||||
"base_filter": null,
|
||||
"index_name": "map_character_settings_v1_uniq_map_character_index",
|
||||
"keys": [
|
||||
{
|
||||
"type": "atom",
|
||||
"value": "map_id"
|
||||
},
|
||||
{
|
||||
"type": "atom",
|
||||
"value": "character_id"
|
||||
}
|
||||
],
|
||||
"name": "uniq_map_character",
|
||||
"nils_distinct?": true,
|
||||
"where": null
|
||||
}
|
||||
],
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"repo": "Elixir.WandererApp.Repo",
|
||||
"schema": null,
|
||||
"table": "map_character_settings_v1"
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
{
|
||||
"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": "character_eve_id",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"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?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "type",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "linked_system_id",
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "kind",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "group",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "custom_info",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "false",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "deleted",
|
||||
"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_system_signatures_v1_system_id_fkey",
|
||||
"on_delete": null,
|
||||
"on_update": null,
|
||||
"primary_key?": true,
|
||||
"schema": "public",
|
||||
"table": "map_system_v1"
|
||||
},
|
||||
"size": null,
|
||||
"source": "system_id",
|
||||
"type": "uuid"
|
||||
}
|
||||
],
|
||||
"base_filter": null,
|
||||
"check_constraints": [],
|
||||
"custom_indexes": [],
|
||||
"custom_statements": [],
|
||||
"has_create_action": true,
|
||||
"hash": "63D26445C9E67459C4D41CF31D61C3EE2356BE664F0D44AB5BC04C2100B701F3",
|
||||
"identities": [
|
||||
{
|
||||
"all_tenants?": false,
|
||||
"base_filter": null,
|
||||
"index_name": "map_system_signatures_v1_uniq_system_eve_id_index",
|
||||
"keys": [
|
||||
{
|
||||
"type": "atom",
|
||||
"value": "system_id"
|
||||
},
|
||||
{
|
||||
"type": "atom",
|
||||
"value": "eve_id"
|
||||
}
|
||||
],
|
||||
"name": "uniq_system_eve_id",
|
||||
"nils_distinct?": true,
|
||||
"where": null
|
||||
}
|
||||
],
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"repo": "Elixir.WandererApp.Repo",
|
||||
"schema": null,
|
||||
"table": "map_system_signatures_v1"
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"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": "character_eve_id",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"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?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "type",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "linked_system_id",
|
||||
"type": "bigint"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "kind",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "group",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "custom_info",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"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": "update_forced_at",
|
||||
"type": "utc_datetime"
|
||||
},
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "inserted_at",
|
||||
"type": "utc_datetime_usec"
|
||||
},
|
||||
{
|
||||
"allow_nil?": false,
|
||||
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": null,
|
||||
"size": null,
|
||||
"source": "updated_at",
|
||||
"type": "utc_datetime_usec"
|
||||
},
|
||||
{
|
||||
"allow_nil?": true,
|
||||
"default": "nil",
|
||||
"generated?": false,
|
||||
"primary_key?": false,
|
||||
"references": {
|
||||
"deferrable": false,
|
||||
"destination_attribute": "id",
|
||||
"destination_attribute_default": null,
|
||||
"destination_attribute_generated": null,
|
||||
"index?": false,
|
||||
"match_type": null,
|
||||
"match_with": null,
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"name": "map_system_signatures_v1_system_id_fkey",
|
||||
"on_delete": null,
|
||||
"on_update": null,
|
||||
"primary_key?": true,
|
||||
"schema": "public",
|
||||
"table": "map_system_v1"
|
||||
},
|
||||
"size": null,
|
||||
"source": "system_id",
|
||||
"type": "uuid"
|
||||
}
|
||||
],
|
||||
"base_filter": null,
|
||||
"check_constraints": [],
|
||||
"custom_indexes": [],
|
||||
"custom_statements": [],
|
||||
"has_create_action": true,
|
||||
"hash": "915C0896211ECCB6C38871664117E7D470C794825536E7F0887DC5B92681F17B",
|
||||
"identities": [
|
||||
{
|
||||
"all_tenants?": false,
|
||||
"base_filter": null,
|
||||
"index_name": "map_system_signatures_v1_uniq_system_eve_id_index",
|
||||
"keys": [
|
||||
{
|
||||
"type": "atom",
|
||||
"value": "system_id"
|
||||
},
|
||||
{
|
||||
"type": "atom",
|
||||
"value": "eve_id"
|
||||
}
|
||||
],
|
||||
"name": "uniq_system_eve_id",
|
||||
"nils_distinct?": true,
|
||||
"where": null
|
||||
}
|
||||
],
|
||||
"multitenancy": {
|
||||
"attribute": null,
|
||||
"global": null,
|
||||
"strategy": null
|
||||
},
|
||||
"repo": "Elixir.WandererApp.Repo",
|
||||
"schema": null,
|
||||
"table": "map_system_signatures_v1"
|
||||
}
|
||||
Reference in New Issue
Block a user