@developers-joyride/shortify
Version:
High performance URL shortener library with multi-database support (MongoDB, SQLite, PostgreSQL, MySQL)
201 lines (200 loc) • 7.01 kB
JavaScript
;
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;