UNPKG

@developers-joyride/shortify

Version:

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

161 lines (160 loc) 6.15 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MysqlAdapter = void 0; const promise_1 = __importDefault(require("mysql2/promise")); const events_1 = require("events"); class MysqlAdapter 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 = promise_1.default.createPool({ host: this.config.host, port: this.config.port, user: this.config.username, password: this.config.password, database: this.config.database, waitForConnections: true, connectionLimit: 10, queueLimit: 0, }); 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 INT AUTO_INCREMENT PRIMARY KEY, urlId VARCHAR(255) UNIQUE NOT NULL, originalUrl TEXT NOT NULL, shortUrl TEXT NOT NULL, clicks INT DEFAULT 0, createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, expiresAt DATETIME NULL ); `; // MySQL does not support CREATE INDEX IF NOT EXISTS, so we need to check manually const createIndex1 = `CREATE INDEX idx_${this.tableName}_urlId ON ${quotedTableName}(urlId)`; const createIndex2 = `CREATE INDEX idx_${this.tableName}_originalUrl ON ${quotedTableName}(originalUrl)`; await this.pool.query(createTableSQL); const [rows] = await this.pool.query(`SHOW INDEX FROM ${quotedTableName} WHERE Key_name = "idx_${this.tableName}_urlId"`); if (rows.length === 0) { await this.pool.query(createIndex1); } const [rows2] = await this.pool.query(`SHOW INDEX FROM ${quotedTableName} WHERE Key_name = "idx_${this.tableName}_originalUrl"`); if (rows2.length === 0) { await this.pool.query(createIndex2); } } 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 (?, ?, ?, ?, ?)`; const [result] = await this.pool.query(sql, [ url.urlId, url.originalUrl, url.shortUrl, url.clicks, url.expiresAt ? url.expiresAt.toISOString().slice(0, 19).replace("T", " ") : null, ]); // MySQL does not return the row, so fetch it return (await this.findUrlByUrlId(url.urlId)); } 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 = ? LIMIT 1`; const [rows] = await this.pool.query(sql, [urlId]); const row = 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, }; } 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 = ? LIMIT 1`; const [rows] = await this.pool.query(sql, [originalUrl]); const row = 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, }; } 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 = ? WHERE urlId = ?`; await this.pool.query(sql, [clicks, urlId]); } 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 = ?`; const [result] = await this.pool.query(sql, [urlId]); return result.affectedRows > 0; } } exports.MysqlAdapter = MysqlAdapter;