6b8feb9075
- 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.
153 lines
4.4 KiB
TypeScript
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;
|
|
}
|