addition of new dashboard
This commit is contained in:
115
backend/http/sessionStore.js
Normal file
115
backend/http/sessionStore.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import session from "express-session";
|
||||
import { getPool } from "../db/index.js";
|
||||
|
||||
const ONE_DAY_MS = 86_400_000;
|
||||
const DEFAULT_PRUNE_INTERVAL_MS = 15 * 60 * 1000; // 15 minutes
|
||||
// Memory is per-process: a session mutated on another worker (e.g. a logout)
|
||||
// can be served stale here for at most one TTL. Keep it short.
|
||||
const DEFAULT_MEMORY_TTL_MS = 60 * 1000; // 60 seconds
|
||||
|
||||
export const expiryOf = (sess) =>
|
||||
new Date(sess?.cookie?.expires ?? Date.now() + ONE_DAY_MS);
|
||||
|
||||
// In-memory cache in front of Postgres: sessions survive restarts and are
|
||||
// shared across workers, while the memory map serves the read-heavy hot path.
|
||||
export class PgSessionStore extends session.Store {
|
||||
constructor({ pruneIntervalMs = DEFAULT_PRUNE_INTERVAL_MS, memoryTtlMs = DEFAULT_MEMORY_TTL_MS } = {}) {
|
||||
super();
|
||||
// sid -> { sess, expire (ms epoch), freshUntil (ms epoch) }
|
||||
this._mem = new Map();
|
||||
this._memoryTtlMs = memoryTtlMs;
|
||||
// unref'd so the timer never keeps the process alive.
|
||||
if (pruneIntervalMs > 0) {
|
||||
this._pruneTimer = setInterval(() => this.prune(), pruneIntervalMs);
|
||||
this._pruneTimer.unref?.();
|
||||
}
|
||||
}
|
||||
|
||||
async _withPg(fn) {
|
||||
const client = await getPool();
|
||||
try {
|
||||
return await fn(client);
|
||||
} finally {
|
||||
await client.release();
|
||||
}
|
||||
}
|
||||
|
||||
get(sid, cb) {
|
||||
const now = Date.now();
|
||||
const hit = this._mem.get(sid);
|
||||
if (hit) {
|
||||
if (hit.expire <= now) {
|
||||
this._mem.delete(sid);
|
||||
return cb(null, null);
|
||||
}
|
||||
if (hit.freshUntil > now) return cb(null, hit.sess); // fast path: no DB query
|
||||
}
|
||||
this._withPg((c) =>
|
||||
c.query(`SELECT sess, expire FROM session WHERE sid = $1 AND expire > NOW()`, [sid]),
|
||||
)
|
||||
.then((res) => {
|
||||
const row = res.rows[0];
|
||||
if (!row) {
|
||||
this._mem.delete(sid);
|
||||
return cb(null, null);
|
||||
}
|
||||
this._mem.set(sid, {
|
||||
sess: row.sess,
|
||||
expire: new Date(row.expire).getTime(),
|
||||
freshUntil: now + this._memoryTtlMs,
|
||||
});
|
||||
cb(null, row.sess);
|
||||
})
|
||||
.catch(cb);
|
||||
}
|
||||
|
||||
set(sid, sess, cb = () => {}) {
|
||||
const expire = expiryOf(sess);
|
||||
this._mem.set(sid, { sess, expire: expire.getTime(), freshUntil: Date.now() + this._memoryTtlMs });
|
||||
this._withPg((c) =>
|
||||
c.query(
|
||||
`INSERT INTO session (sid, sess, expire) VALUES ($1, $2::jsonb, $3)
|
||||
ON CONFLICT (sid) DO UPDATE SET sess = EXCLUDED.sess, expire = EXCLUDED.expire, updated_ts = NOW()`,
|
||||
[sid, sess, expire],
|
||||
),
|
||||
)
|
||||
.then(() => cb(null))
|
||||
.catch(cb);
|
||||
}
|
||||
|
||||
destroy(sid, cb = () => {}) {
|
||||
this._mem.delete(sid);
|
||||
this._withPg((c) => c.query(`DELETE FROM session WHERE sid = $1`, [sid]))
|
||||
.then(() => cb(null))
|
||||
.catch(cb);
|
||||
}
|
||||
|
||||
touch(sid, sess, cb = () => {}) {
|
||||
const expire = expiryOf(sess);
|
||||
const hit = this._mem.get(sid);
|
||||
if (hit) {
|
||||
hit.expire = expire.getTime();
|
||||
hit.freshUntil = Date.now() + this._memoryTtlMs;
|
||||
}
|
||||
this._withPg((c) =>
|
||||
c.query(`UPDATE session SET expire = $2, updated_ts = NOW() WHERE sid = $1`, [sid, expire]),
|
||||
)
|
||||
.then(() => cb(null))
|
||||
.catch(cb);
|
||||
}
|
||||
|
||||
// Errors are swallowed: runs on a timer with no caller.
|
||||
async prune() {
|
||||
const now = Date.now();
|
||||
for (const [sid, v] of this._mem) if (v.expire <= now) this._mem.delete(sid);
|
||||
try {
|
||||
await this._withPg((c) => c.query(`DELETE FROM session WHERE expire <= NOW()`));
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
stopPruning() {
|
||||
if (this._pruneTimer) clearInterval(this._pruneTimer);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user