const { Pool } = require('pg'); const BaseStorage = require('@gofynd/fdk-extension-javascript/express/storage/base_storage'); const DEFAULT_TABLE_NAME = 'fdk session storage'; function normalizeText(value) { return typeof value === 'string' ? value.trim() : ''; } function quoteIdentifier(value) { return `"${String(value).replace(/"/g, '""')}"`; } function coerceJsonValue(value) { if (!value) return null; if (typeof value === 'object') return value; try { return JSON.parse(value); } catch { return null; } } class PostgresFdkStorage extends BaseStorage { constructor(connectionString, prefixKey = 'sms_extension_', tableName = DEFAULT_TABLE_NAME) { super(prefixKey); this.pool = new Pool({ connectionString }); this.tableName = quoteIdentifier(tableName); } getFullKey(key) { return `${this.prefixKey}${key}`; } async get(key) { const fullKey = this.getFullKey(key); const result = await this.pool.query( `SELECT value, expires_at FROM ${this.tableName} WHERE storage_key = $1 ORDER BY updated_at DESC NULLS LAST, id DESC LIMIT 1`, [fullKey] ); if (!result.rows.length) return null; const row = result.rows[0]; const expiresAt = row.expires_at ? new Date(row.expires_at) : null; if (expiresAt && expiresAt.getTime() <= Date.now()) { await this.del(key); return null; } return coerceJsonValue(row.value); } async set(key, value) { return this.save(key, value, null); } async setex(key, value, ttl) { const ttlSeconds = Number(ttl); const expiresAt = Number.isFinite(ttlSeconds) && ttlSeconds > 0 ? new Date(Date.now() + ttlSeconds * 1000) : null; return this.save(key, value, expiresAt); } async save(key, value, expiresAt) { const fullKey = this.getFullKey(key); const serializedValue = JSON.stringify(value || {}); const client = await this.pool.connect(); try { await client.query('BEGIN'); const updateResult = await client.query( `UPDATE ${this.tableName} SET value = $2, expires_at = $3, updated_at = NOW() WHERE storage_key = $1`, [fullKey, serializedValue, expiresAt] ); if (updateResult.rowCount === 0) { await client.query( `INSERT INTO ${this.tableName} (storage_key, value, expires_at) VALUES ($1, $2, $3)`, [fullKey, serializedValue, expiresAt] ); } await client.query('COMMIT'); return 'OK'; } catch (error) { await client.query('ROLLBACK'); throw error; } finally { client.release(); } } async del(key) { const fullKey = this.getFullKey(key); await this.pool.query(`DELETE FROM ${this.tableName} WHERE storage_key = $1`, [fullKey]); } } function createFdkStorage(connectionString) { const normalizedConnectionString = normalizeText(connectionString); if (!normalizedConnectionString) return null; return new PostgresFdkStorage(normalizedConnectionString); } module.exports = { createFdkStorage, PostgresFdkStorage, };