UNPKG

@developers-joyride/shortify

Version:

High performance URL shortener library with multi-database support (MongoDB, SQLite, PostgreSQL, MySQL)

201 lines (200 loc) 7.01 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PostgresAdapter = void 0; const pg_1 = require("pg"); const events_1 = require("events"); class PostgresAdapter extends events_1.EventEmitter { constructor(config) { super(); this.pool = null; this.isConnectedFlag = false; this.config = { maxRetries: 5, retryDelay: 5000, ...config, }; this.tableName = config.tableName || "urls"; } async connect() { try { if (this.isConnectedFlag && this.pool) { return true; } this.pool = new pg_1.Pool({ host: this.config.host, port: this.config.port, database: this.config.database, user: this.config.username, password: this.config.password, max: 20, // Maximum number of clients in the pool idleTimeoutMillis: 30000, // Close idle clients after 30 seconds connectionTimeoutMillis: 2000, // Return an error after 2 seconds if connection could not be established }); // Test the connection const client = await this.pool.connect(); client.release(); await this.createTables(); this.isConnectedFlag = true; this.emit("connected"); return true; } catch (error) { this.emit("error", error); return false; } } async createTables() { if (!this.pool) throw new Error("Database not connected"); // Quote table name to preserve case const quotedTableName = `"${this.tableName}"`; const createTableSQL = ` CREATE TABLE IF NOT EXISTS ${quotedTableName} ( id SERIAL PRIMARY KEY, urlId VARCHAR(255) UNIQUE NOT NULL, originalUrl TEXT NOT NULL, shortUrl TEXT NOT NULL, clicks INTEGER DEFAULT 0, createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expiresAt TIMESTAMP NULL ); `; const createIndexesSQL = ` CREATE INDEX IF NOT EXISTS idx_${this.tableName}_urlId ON ${quotedTableName}(urlId); CREATE INDEX IF NOT EXISTS idx_${this.tableName}_originalUrl ON ${quotedTableName}(originalUrl); CREATE INDEX IF NOT EXISTS idx_${this.tableName}_createdAt ON ${quotedTableName}(createdAt); `; const client = await this.pool.connect(); try { await client.query(createTableSQL); await client.query(createIndexesSQL); } finally { client.release(); } } async disconnect() { if (this.pool) { await this.pool.end(); this.pool = null; this.isConnectedFlag = false; this.emit("disconnected"); } } isConnected() { return this.isConnectedFlag && this.pool !== null; } async createUrl(url) { if (!this.pool) throw new Error("Database not connected"); // Quote table name to preserve case const quotedTableName = `"${this.tableName}"`; const sql = ` INSERT INTO ${quotedTableName} (urlId, originalUrl, shortUrl, clicks, expiresAt) VALUES ($1, $2, $3, $4, $5) RETURNING * `; const client = await this.pool.connect(); try { const result = await client.query(sql, [ url.urlId, url.originalUrl, url.shortUrl, url.clicks, url.expiresAt ? url.expiresAt.toISOString() : null, ]); const row = result.rows[0]; return { urlId: row.urlid, originalUrl: row.originalurl, shortUrl: row.shorturl, clicks: row.clicks, createdAt: row.createdat, expiresAt: row.expiresat ? new Date(row.expiresat) : undefined, }; } finally { client.release(); } } async findUrlByUrlId(urlId) { if (!this.pool) throw new Error("Database not connected"); // Quote table name to preserve case const quotedTableName = `"${this.tableName}"`; const sql = `SELECT * FROM ${quotedTableName} WHERE urlId = $1 LIMIT 1`; const client = await this.pool.connect(); try { const result = await client.query(sql, [urlId]); const row = result.rows[0]; if (!row) return null; return { urlId: row.urlid, originalUrl: row.originalurl, shortUrl: row.shorturl, clicks: row.clicks, createdAt: row.createdat, expiresAt: row.expiresat ? new Date(row.expiresat) : undefined, }; } finally { client.release(); } } async findUrlByOriginalUrl(originalUrl) { if (!this.pool) throw new Error("Database not connected"); // Quote table name to preserve case const quotedTableName = `"${this.tableName}"`; const sql = `SELECT * FROM ${quotedTableName} WHERE originalUrl = $1 LIMIT 1`; const client = await this.pool.connect(); try { const result = await client.query(sql, [originalUrl]); const row = result.rows[0]; if (!row) return null; return { urlId: row.urlid, originalUrl: row.originalurl, shortUrl: row.shorturl, clicks: row.clicks, createdAt: row.createdat, expiresAt: row.expiresat ? new Date(row.expiresat) : undefined, }; } finally { client.release(); } } async updateUrlClicks(urlId, clicks) { if (!this.pool) throw new Error("Database not connected"); // Quote table name to preserve case const quotedTableName = `"${this.tableName}"`; const sql = `UPDATE ${quotedTableName} SET clicks = $1 WHERE urlId = $2`; const client = await this.pool.connect(); try { await client.query(sql, [clicks, urlId]); } finally { client.release(); } } async deleteUrl(urlId) { if (!this.pool) throw new Error("Database not connected"); // Quote table name to preserve case const quotedTableName = `"${this.tableName}"`; const sql = `DELETE FROM ${quotedTableName} WHERE urlId = $1`; const client = await this.pool.connect(); try { const result = await client.query(sql, [urlId]); return (result.rowCount || 0) > 0; } finally { client.release(); } } } exports.PostgresAdapter = PostgresAdapter;