@developers-joyride/shortify
Version:
High performance URL shortener library with multi-database support (MongoDB, SQLite, PostgreSQL, MySQL)
123 lines (122 loc) • 3.81 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ShortenerService = void 0;
const url_1 = require("url");
const id_generator_1 = require("../utils/id-generator");
class ShortenerService {
constructor(baseUrl, dbAdapter) {
this.defaultUrlLength = 8;
// Ensure the base URL ends with a slash
this.baseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
this.dbAdapter = dbAdapter;
}
/**
* Validate if a URL is properly formatted
*/
isValidUrl(urlString) {
try {
const url = new url_1.URL(urlString);
return url.protocol === "http:" || url.protocol === "https:";
}
catch (error) {
return false;
}
}
/**
* Generate a short URL ID
*/
generateUrlId(length = this.defaultUrlLength) {
return (0, id_generator_1.generateId)(length);
}
/**
* Check if a URL ID already exists in the database
* For performance, we use this to avoid duplicate URL IDs
*/
async findExistingUrlId(urlId) {
return this.dbAdapter.findUrlByUrlId(urlId);
}
/**
* Shorten a URL
*/
async shorten(originalUrl, options = {}) {
// Validate the original URL
if (!this.isValidUrl(originalUrl)) {
throw new Error("Invalid URL format");
}
// Apply options with defaults
const urlLength = options.urlLength || this.defaultUrlLength;
const baseUrl = options.baseUrl || this.baseUrl;
// Generate URL ID (either custom or generated)
let urlId = options.customUrlId || this.generateUrlId(urlLength);
// Check if the URL ID already exists and generate a new one if needed
let existingUrl = await this.findExistingUrlId(urlId);
while (existingUrl) {
urlId = this.generateUrlId(urlLength);
existingUrl = await this.findExistingUrlId(urlId);
}
// Calculate expiration date if provided
let expiresAt = undefined;
if (options.expiresInDays) {
expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + options.expiresInDays);
}
// Create the short URL
const shortUrl = `${baseUrl}${urlId}`;
// Save the URL to database
await this.dbAdapter.createUrl({
urlId,
originalUrl,
shortUrl,
clicks: 0,
expiresAt,
});
return {
originalUrl,
shortUrl,
urlId,
expiresAt,
};
}
/**
* Resolve a short URL to its original URL
*/
async resolve(urlId) {
const url = await this.dbAdapter.findUrlByUrlId(urlId);
if (!url) {
return null;
}
// Check if URL has expired
if (url.expiresAt && url.expiresAt < new Date()) {
// URL has expired, delete it
await this.dbAdapter.deleteUrl(urlId);
return null;
}
// Increment click count
await this.dbAdapter.updateUrlClicks(urlId, url.clicks + 1);
return url.originalUrl;
}
/**
* Get URL stats
*/
async getUrlStats(urlId) {
const url = await this.dbAdapter.findUrlByUrlId(urlId);
if (!url) {
return null;
}
return {
urlId: url.urlId,
originalUrl: url.originalUrl,
shortUrl: url.shortUrl,
clicks: url.clicks,
createdAt: url.createdAt,
expiresAt: url.expiresAt,
};
}
/**
* Delete a shortened URL
*/
async deleteUrl(urlId) {
return this.dbAdapter.deleteUrl(urlId);
}
}
exports.ShortenerService = ShortenerService;