diff --git a/scripts/API.js b/scripts/API.js new file mode 100755 index 0000000..4922142 --- /dev/null +++ b/scripts/API.js @@ -0,0 +1,95 @@ +// Import modules +var URLManage = require(`./database/URLs`).URLManage; +var Messaging = require(`./utilities/messaging`).Messaging; +const bodyParser = require('body-parser'); + +class ShortenAPI { + /* + Paths used by this API. + */ + #paths = { + "create": ["/api/shorturl"], + "access": ["/:ID", "/api/shorturl/:ID"] + } + + /* + Create the URL shortener parser API. + + @param {Express} INSTANCE - The Express instance + */ + constructor (INSTANCE) { + this[`instance`] = INSTANCE; + this[`instance`].use(bodyParser.json()); + this[`instance`].use(bodyParser.urlencoded({extended: true})); + + this[`management`] = new URLManage; + + this.prepareRedirect(); + this.startCreation(); + } + + /* + Redirect from the short link. + */ + prepareRedirect() { + const findRedirect = (REQUEST, RESPONSE) => { + const redirectBrowser = (URL) => { + return((URL) ? RESPONSE.redirect(URL) : null); + } + + try { + ((Object.keys(REQUEST.params).length) + ? REQUEST.params.ID + : false) ? + this[`management`].open(REQUEST.params.ID, redirectBrowser) + : false; + } catch(ERR) { + Messaging.exception(RESPONSE, ERR); + } + }; + + + this.#paths[`access`].forEach((PATH) => { + this[`instance`].get(PATH, findRedirect) + console.log(`Redirection ready on ${PATH}.`); + }); + } + + /* + Enable receiving requests. + */ + startCreation() { + const redirect = (REQUEST, RESPONSE) => { + try { + let QUERY = {}; + if ((REQUEST.body) ? Object.keys(REQUEST.body).length : false) { + QUERY = REQUEST.body; + } else if ((REQUEST.query) ? Object.keys(REQUEST.query).length : false) { + QUERY = REQUEST.query; + } + + let RESULT = {}; + RESULT[`original_url`] = QUERY[`url`]; + + + const sendRedirect = (HASH) => { + RESULT[`short_url`] = HASH; + RESPONSE.json(RESULT); + } + + this[`management`].create(RESULT[`original_url`], sendRedirect); + } catch(ERR) { + Messaging.exception(RESPONSE, ERR); + }; + }; + + [`get`, `post`].forEach((METHOD) => { + this.#paths[`create`].forEach((PATH) => { + this[`instance`][METHOD](PATH, redirect); + console.log(`Creation ready on ${PATH} for method ${METHOD}.`) + }); + }) + } +} + +module.exports = {ShortenAPI}; \ No newline at end of file diff --git a/scripts/database/URLs.js b/scripts/database/URLs.js new file mode 100755 index 0000000..f99ca3a --- /dev/null +++ b/scripts/database/URLs.js @@ -0,0 +1,97 @@ +const Mongoose = require(`mongoose`); +const DBManagement = require(`./management`).DBManagement; +const URLs = require(`../utilities/URLs`).URLs; +const CustomErrors = require(`../utilities/errors`).CustomErrors; +const HashTools = require(`../utilities/hash`).HashTools; + +class URLManage extends DBManagement { + /* + Begin the URL management and connection. + */ + constructor () { + super(`localhost:27017`); + + this.schema = new Mongoose.Schema({ + "original": {"type": String, "required": true}, + "shortened": {"type": String, "required": true} + }); + + this[`state`].then(() => {this.model = this[`connection`].model(`URL`, this[`schema`]);}) + }; + + /* + Open the original URL given an identifier. + + @param {string} ID the ID + @param {function} done the callback function when successful + @return {string} the original URL + */ + open (ID, done) { + this.state.then(() => { + this.model.findOne({"shortened": ID}).then((DATA) => { + console.log((DATA) ? `Lengthened shortened URL with ID ${ID} to ${DATA[`original`]}.` : `No shortened URL with ID ${ID}.`); + return done((DATA) ? DATA[`original`] : null); + }).catch((ERR) => { + throw ERR; + }) + }).catch((ERROR) => {throw new CustomErrors.DBProblem(ERROR.message)}); + }; + + /* + Search for a short link given a URL. + + @param {string} URL the URL to shorten + @param {function} done the callback function when successful + @return {string} ID the ID + */ + search(URL, done) { + // Throw an error for an incorrect URL. + if (!URLs.test(URL)) { + throw new CustomErrors.URL(null, URL); + }; + + this.state.then(() => { + this.model.findOne({"original": URL}).then((DATA) => { + console.log((DATA) ? `The ID ${DATA[`shortened`]} refers to ${URL}.` : `No ID pertains to ${URL}.`); + return done((DATA) ? DATA[`shortened`] : null); + }).catch((ERR) => { + throw ERR; + }); + }).catch((ERROR) => {throw new CustomErrors.DBProblem(ERROR.message)}) + }; + + /* + Shorten the URL. + + @param {string} URL the URL to shorten + @param {function} done the callback function + @return {string} ID the ID + */ + create(URL, done) { + // Throw an error for an incorrect URL. + if (!URLs.test(URL)) { + throw new CustomErrors.URL(null, URL); + }; + + let ENTRY = {"original": URL}; + const save = (ID) => { + (ID) + ? done(ID) + : HashTools.digest(URL, {"output": "Number"}).then((HASH) => { + ENTRY[`shortened`] = HASH; + + let DOCUMENT = new this.model(ENTRY); + + this.state.then(() => { + DOCUMENT.save().then((DATA) => {done(HASH)}).catch((ERR) => {throw ERR;}) + }).catch((ERROR) => {throw new CustomErrors.DBProblem(ERROR.message)}); + }); + } + + this.search((ENTRY[`original`]), save); + + } + +} + +module.exports = {URLManage}; \ No newline at end of file diff --git a/scripts/database/management.js b/scripts/database/management.js new file mode 100644 index 0000000..a4d44b7 --- /dev/null +++ b/scripts/database/management.js @@ -0,0 +1,21 @@ +const Mongoose = require(`mongoose`); +const CustomErrors = require(`../utilities/errors`).CustomErrors; + +class DBManagement { + /* + Begin the connection. + + @param {string} DOMAIN the domain + @param {string} DBNAME the database name + */ + constructor (DOMAIN, DBNAME) { + const throwError = (ERROR) => {throw ERROR;}; + + this[`state`] = Mongoose.connect(`mongodb://${DOMAIN}/${(DBNAME) ? DBNAME : ""}`, { useNewUrlParser: true, useUnifiedTopology: true }).then((CONNECTION) => { + this[`connection`] = CONNECTION.connection; + console.log(`Connection successful.`); + }).catch(throwError); + }; +} + +module.exports = {DBManagement}; \ No newline at end of file