sms-extension-1777538290/server/postgresFdkStorage.js

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,
};