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