UNPKG

ucg

Version:

Universal CRUD Generator - Express.js plugin and CLI tool for generating complete Node.js REST APIs with database models, controllers, routes, validators, and admin interface. Supports PostgreSQL, MySQL, SQLite with Sequelize, TypeORM, and Knex.js. Develo

160 lines (139 loc) 4.31 kB
const session = require('express-session'); const fs = require('fs-extra'); const path = require('path'); /** * File-based session store for UCG Plugin * Persists sessions across server restarts */ class FileSessionStore extends session.Store { constructor(configDir) { super(); this.sessionDir = path.join(configDir, 'sessions'); this.ready = false; this.initPromise = this.init(); } async init() { try { await fs.ensureDir(this.sessionDir); this.ready = true; // Clean up expired sessions on startup this.cleanup(); } catch (error) { console.warn('Session store initialization error:', error.message); this.ready = true; // Still mark as ready to prevent hanging } } get(sessionId, callback) { // Ensure store is ready before processing if (!this.ready) { this.initPromise.then(() => this._get(sessionId, callback)); return; } this._get(sessionId, callback); } _get(sessionId, callback) { const sessionFile = path.join(this.sessionDir, `${sessionId}.json`); fs.readFile(sessionFile, 'utf8') .then(data => { let session; try { session = JSON.parse(data); } catch (parseError) { // Handle corrupted JSON files console.warn(`Corrupted session file: ${sessionFile}. Removing and creating new session...`); fs.remove(sessionFile).catch(() => { }); return callback(null, null); } // Check if session is expired if (session.cookie && session.cookie.expires) { const expiry = new Date(session.cookie.expires); if (expiry < new Date()) { return this.destroy(sessionId, callback); } } callback(null, session); }) .catch(err => { if (err.code === 'ENOENT') { callback(null, null); // Session not found } else { // Handle any other file system errors console.warn(`Session file error: ${sessionFile}. Error: ${err.message}`); callback(null, null); } }); } set(sessionId, session, callback) { // Ensure store is ready before processing if (!this.ready) { this.initPromise.then(() => this._set(sessionId, session, callback)); return; } this._set(sessionId, session, callback); } _set(sessionId, session, callback) { const sessionFile = path.join(this.sessionDir, `${sessionId}.json`); fs.writeJson(sessionFile, session, { spaces: 2 }) .then(() => callback()) .catch(callback); } destroy(sessionId, callback) { const sessionFile = path.join(this.sessionDir, `${sessionId}.json`); fs.remove(sessionFile) .then(() => callback()) .catch(err => { if (err.code === 'ENOENT') { callback(); // Session doesn't exist, that's fine } else { callback(err); } }); } length(callback) { fs.readdir(this.sessionDir) .then(files => { const sessionFiles = files.filter(file => file.endsWith('.json')); callback(null, sessionFiles.length); }) .catch(callback); } clear(callback) { fs.emptyDir(this.sessionDir) .then(() => callback()) .catch(callback); } touch(sessionId, session, callback) { // Update the session without changing the data this.set(sessionId, session, callback); } async cleanup() { try { const files = await fs.readdir(this.sessionDir); const now = new Date(); for (const file of files) { if (!file.endsWith('.json')) continue; const filePath = path.join(this.sessionDir, file); try { const session = await fs.readJson(filePath); if (session.cookie && session.cookie.expires) { const expiry = new Date(session.cookie.expires); if (expiry < now) { await fs.remove(filePath); } } } catch (err) { // Remove corrupted session files await fs.remove(filePath); } } } catch (err) { console.warn('Session cleanup error:', err.message); } } } /** * Export factory function */ module.exports = (configDir) => { return new FileSessionStore(configDir); };