Files
ArchiTools/src/modules/parcel-sync/services/session-store.ts
T
AI Assistant 6b8feb9075 fix: workspace resolution via ArcGIS listLayer + seed UATs from uat.json
- resolveWorkspace: use listLayer() instead of listLayerByWhere() with
  hardcoded field names. Auto-discovers admin field (ADMIN_UNIT_ID/SIRUTA)
  from ArcGIS layer metadata via buildWhere().
- resolveWorkspace: persist WORKSPACE_ID to DB on first resolution for
  fast subsequent lookups.
- UATs POST: seed from uat.json (correct SIRUTA codes) instead of eTerra
  nomenclature API (nomenPk != SIRUTA, county nomenPk != WORKSPACE_ID).
- Remove eTerra nomenclature dependency from UATs endpoint.
- Fix activeJobs Set iteration error on container restart.
- Remove unused enrichedUatsFetched ref.
2026-03-06 21:24:51 +02:00

153 lines
4.4 KiB
TypeScript

/**
* Server-side eTerra session store (global singleton).
*
* Holds one shared session for the whole app. Multiple browser clients
* see the same connection state. The EterraClient already caches sessions
* by credential hash, so we just store which credentials are active +
* metadata (who connected, when, running jobs).
*/
import { getProgress } from "./progress-store";
/* ------------------------------------------------------------------ */
/* Types */
/* ------------------------------------------------------------------ */
export type EterraSession = {
/** Username used to connect */
username: string;
/** Password (kept in memory only, never sent to clients) */
password: string;
/** ISO timestamp of connection */
connectedAt: string;
/** Running job IDs — tracked so we can warn before disconnect */
activeJobs: Set<string>;
};
export type SessionStatus = {
connected: boolean;
username?: string;
connectedAt?: string;
/** How many jobs are currently running */
activeJobCount: number;
/** First running job's phase (for UI hint) */
activeJobPhase?: string;
};
/* ------------------------------------------------------------------ */
/* Global store */
/* ------------------------------------------------------------------ */
const g = globalThis as { __eterraSessionStore?: EterraSession | null };
function getSession(): EterraSession | null {
return g.__eterraSessionStore ?? null;
}
function setSession(session: EterraSession | null) {
g.__eterraSessionStore = session;
}
/* ------------------------------------------------------------------ */
/* Public API */
/* ------------------------------------------------------------------ */
export function createSession(username: string, password: string): void {
setSession({
username,
password,
connectedAt: new Date().toISOString(),
activeJobs: new Set(),
});
}
export function destroySession(): { destroyed: boolean; reason?: string } {
const session = getSession();
if (!session) return { destroyed: true };
// Check for running jobs
const running = getRunningJobs(session);
if (running.length > 0) {
return {
destroyed: false,
reason: `Există ${running.length} job(uri) active. Așteaptă finalizarea lor înainte de deconectare.`,
};
}
setSession(null);
return { destroyed: true };
}
export function forceDestroySession(): void {
setSession(null);
}
export function getSessionCredentials(): {
username: string;
password: string;
} | null {
const session = getSession();
if (!session) return null;
return { username: session.username, password: session.password };
}
export function registerJob(jobId: string): void {
const session = getSession();
if (session) session.activeJobs.add(jobId);
}
export function unregisterJob(jobId: string): void {
const session = getSession();
if (session) session.activeJobs.delete(jobId);
}
export function getSessionStatus(): SessionStatus {
const session = getSession();
if (!session) {
return { connected: false, activeJobCount: 0 };
}
const running = getRunningJobs(session);
// Find one running job's phase for a UI hint
let activeJobPhase: string | undefined;
for (const jid of running) {
const p = getProgress(jid);
if (p?.phase) {
activeJobPhase = p.phase;
break;
}
}
return {
connected: true,
username: session.username,
connectedAt: session.connectedAt,
activeJobCount: running.length,
activeJobPhase,
};
}
/* ------------------------------------------------------------------ */
/* Helpers */
/* ------------------------------------------------------------------ */
function getRunningJobs(session: EterraSession): string[] {
// Guard: ensure activeJobs is iterable (Set). Containers may restart
// with stale globalThis where the Set was serialized as plain object.
if (!(session.activeJobs instanceof Set)) {
session.activeJobs = new Set();
return [];
}
const running: string[] = [];
for (const jid of session.activeJobs) {
const p = getProgress(jid);
if (p && p.status === "running") {
running.push(jid);
} else {
session.activeJobs.delete(jid);
}
}
return running;
}