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
JavaScript
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);
};