150 lines
3.8 KiB
JavaScript
150 lines
3.8 KiB
JavaScript
const { Pool } = require('pg');
|
|
const BaseStorage = require('@gofynd/fdk-extension-javascript/express/storage/base_storage');
|
|
|
|
const DEFAULT_TABLE_NAME = 'fdk session storage';
|
|
|
|
function normalizeEnvText(value) {
|
|
return typeof value === 'string' ? value.trim() : '';
|
|
}
|
|
|
|
function quoteIdentifier(identifier) {
|
|
return `"${String(identifier).replace(/"/g, '""')}"`;
|
|
}
|
|
|
|
function shouldUseSsl(connectionString) {
|
|
const normalized = normalizeEnvText(connectionString).toLowerCase();
|
|
if (!normalized) return false;
|
|
if (normalized.includes('sslmode=disable')) return false;
|
|
if (normalized.includes('sslmode=require')) return true;
|
|
return !normalized.includes('localhost') && !normalized.includes('127.0.0.1');
|
|
}
|
|
|
|
function getPool(connectionString) {
|
|
if (!globalThis.__smsExtensionFdkStoragePool) {
|
|
globalThis.__smsExtensionFdkStoragePool = new Pool({
|
|
connectionString,
|
|
max: 3,
|
|
idleTimeoutMillis: 30000,
|
|
ssl: shouldUseSsl(connectionString) ? { rejectUnauthorized: false } : undefined,
|
|
});
|
|
}
|
|
|
|
return globalThis.__smsExtensionFdkStoragePool;
|
|
}
|
|
|
|
function serializeValue(value) {
|
|
return JSON.stringify(value);
|
|
}
|
|
|
|
function parseValue(rawValue) {
|
|
if (!rawValue) return null;
|
|
if (typeof rawValue === 'object') return rawValue;
|
|
|
|
try {
|
|
return JSON.parse(rawValue);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class PostgresFdkStorage extends BaseStorage {
|
|
constructor({ prefixKey, connectionString, tableName = DEFAULT_TABLE_NAME }) {
|
|
super(prefixKey);
|
|
|
|
if (!normalizeEnvText(connectionString)) {
|
|
throw new Error('FDK_STORAGE_CONNECTION_STRING is required for Postgres FDK storage');
|
|
}
|
|
|
|
this.pool = getPool(connectionString);
|
|
this.tableIdentifier = quoteIdentifier(tableName);
|
|
}
|
|
|
|
buildKey(key) {
|
|
return `${this.prefixKey}${key}`;
|
|
}
|
|
|
|
async get(key) {
|
|
const storageKey = this.buildKey(key);
|
|
const { rows } = await this.pool.query(
|
|
`SELECT value, expires_at
|
|
FROM ${this.tableIdentifier}
|
|
WHERE storage_key = $1
|
|
ORDER BY updated_at DESC NULLS LAST, created_at DESC NULLS LAST, id DESC
|
|
LIMIT 1`,
|
|
[storageKey]
|
|
);
|
|
|
|
const row = rows[0];
|
|
if (!row) return null;
|
|
|
|
const expiresAt = row.expires_at ? new Date(row.expires_at) : null;
|
|
if (expiresAt && expiresAt.getTime() <= Date.now()) {
|
|
await this.del(key);
|
|
return null;
|
|
}
|
|
|
|
return parseValue(row.value);
|
|
}
|
|
|
|
async set(key, value) {
|
|
await this.writeRecord(key, value, null);
|
|
return 'OK';
|
|
}
|
|
|
|
async setex(key, value, ttl) {
|
|
const expiresAt = Number.isFinite(ttl)
|
|
? new Date(Date.now() + ttl * 1000)
|
|
: null;
|
|
|
|
await this.writeRecord(key, value, expiresAt);
|
|
return 'OK';
|
|
}
|
|
|
|
async del(key) {
|
|
const storageKey = this.buildKey(key);
|
|
await this.pool.query(
|
|
`DELETE FROM ${this.tableIdentifier}
|
|
WHERE storage_key = $1`,
|
|
[storageKey]
|
|
);
|
|
}
|
|
|
|
async writeRecord(key, value, expiresAt) {
|
|
const storageKey = this.buildKey(key);
|
|
const serializedValue = serializeValue(value);
|
|
|
|
const updateResult = await this.pool.query(
|
|
`UPDATE ${this.tableIdentifier}
|
|
SET value = $2,
|
|
expires_at = $3,
|
|
updated_at = NOW()
|
|
WHERE storage_key = $1`,
|
|
[storageKey, serializedValue, expiresAt]
|
|
);
|
|
|
|
if (updateResult.rowCount > 0) return;
|
|
|
|
await this.pool.query(
|
|
`INSERT INTO ${this.tableIdentifier} (storage_key, value, expires_at)
|
|
VALUES ($1, $2, $3)`,
|
|
[storageKey, serializedValue, expiresAt]
|
|
);
|
|
}
|
|
}
|
|
|
|
function createFdkStorage({ prefixKey, connectionString }) {
|
|
if (!normalizeEnvText(connectionString)) return null;
|
|
|
|
return new PostgresFdkStorage({
|
|
prefixKey,
|
|
connectionString,
|
|
tableName: DEFAULT_TABLE_NAME,
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
DEFAULT_TABLE_NAME,
|
|
PostgresFdkStorage,
|
|
createFdkStorage,
|
|
};
|