First commit
commit
fe6dff0220
@ -0,0 +1,2 @@
|
||||
MONGO_URI=YOUR_MONGO_COLLECTION_URI
|
||||
BASE=YOUR_BASE_URL
|
@ -0,0 +1,5 @@
|
||||
.vscode
|
||||
node_modules
|
||||
package-lock.json
|
||||
log_rotate.sh
|
||||
.env
|
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Mr¤KayJayDee
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1,118 @@
|
||||
|
||||
# MrKayJayDee Shortener
|
||||
|
||||
### A simple url shortener frontend and api using mongodb
|
||||
|
||||
<br>
|
||||
|
||||
# Setup
|
||||
|
||||
Clone the repo
|
||||
```shell
|
||||
git clone https://github.com/Mr-KayJayDee/simple-url-shortener
|
||||
```
|
||||
|
||||
CD Into the directory
|
||||
```
|
||||
cd simple-url-shortener
|
||||
```
|
||||
|
||||
Install the modules
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
Change the configuration
|
||||
|
||||
- Rename the .env.example to .env
|
||||
- Change MONGO_URI to your mongodb local or cluster url
|
||||
- Change BASE to your base url (localhost:8080 for local testing)
|
||||
|
||||
Change some others things
|
||||
|
||||
- Go to the index.html file and change the line 50 column 35 to be https://yourdomain.com/api/short
|
||||
|
||||
Start the server
|
||||
```
|
||||
node index.js
|
||||
```
|
||||
|
||||
# API Reference
|
||||
|
||||
### Shorten a link
|
||||
|
||||
```http
|
||||
POST https://mrkayjaydee.xyz/api/short
|
||||
```
|
||||
|
||||
| Body | Type | Description |
|
||||
| :-------- | :------- | :------------------------- |
|
||||
| `origUrl` | `string` | **Required**. The url you want to shorten |
|
||||
|
||||
Returns
|
||||
|
||||
| Key | Type | Description |
|
||||
| :-------- | :------- | :------------------------- |
|
||||
| `urlId` | `String` | The URL ID |
|
||||
| `origUrl` | `String` | The original URL |
|
||||
| `shortUrl` | `String` | The shortened URL |
|
||||
| `clicks` | `Number` | The amount of time this link has been clicked |
|
||||
| `date` | `Date` | The creation date of this link |
|
||||
| `_id` | `String` | The databse id of this link |
|
||||
| `__v` | `Number` | The version number |
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"urlId": String,
|
||||
"origUrl": String,
|
||||
"shortUrl": String,
|
||||
"clicks": Number,
|
||||
"date": Date,
|
||||
"_id": String,
|
||||
"__v": Number
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```http
|
||||
POST https://mrkayjaydee.xyz/api/short
|
||||
```
|
||||
|
||||
Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"origUrl": "http://google.com"
|
||||
}
|
||||
```
|
||||
Returns:
|
||||
|
||||
```json
|
||||
{
|
||||
"urlId": "9bcF-giN0",
|
||||
"origUrl": "http://google.com",
|
||||
"shortUrl": "https://mrkayjaydee.xyz/9bcF-giN0",
|
||||
"clicks": 0,
|
||||
"date": "2022-01-26T15:37:26.977Z",
|
||||
"_id": "61f16ab602262b5f179d3336",
|
||||
"__v": 0
|
||||
}
|
||||
```
|
||||
|
||||
# Contact Me:
|
||||
|
||||
- Discord: Mr¤KayJayDee#8961
|
||||
- Support Server: https://discord.gg/5ZSGFYtnqw
|
||||
- Shop Server: https://discord.gg/X2CHqpFPjw
|
||||
- Instagram: [@killian.dalcin](https://www.instagram.com/killian.dalcin)
|
||||
- Snapchat: [@killian.dalcin](https://www.snapchat.com/add/killian.dalcin)
|
||||
- Twitter: [@killiandalcin](https://twitter.com/killiandalcin)
|
||||
- Github: https://github.com/Mr-KayJayDee/
|
||||
- Portfolio: https://portfolio.mrkayjaydee.xyz
|
||||
- Fiverr: https://www.fiverr.com/mrkayjaydee
|
||||
- Shoppy: https://shoppy.gg/@MrKayJayDee
|
||||
- Email: [killian.dalcin@gmail.com](mailto:killian.dalcin@gmail.com)
|
||||
- Email pro: [mrkayjaydee@gmail.com](mailto:mrkayjaydee@gmail.com)
|
||||
- Email business: [contact@mrkayjaydee.xyz](mailto:contact@mrkayjaydee.xyz)
|
@ -0,0 +1,16 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const connectDB = async () => {
|
||||
try {
|
||||
await mongoose.connect(process.env.MONGO_URI, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
});
|
||||
console.log('Database Connected');
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = connectDB;
|
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="An Easy, Fast and Secured URL shortener">
|
||||
<title>Documentation - MrKayJayDee URL Shortener</title>
|
||||
<link rel="icon" href="https://i.imgur.com/Dnmmfqd.png">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flex h-screen justify-center items-center">
|
||||
<div class="bg-white rounded m-2 sm:m-5 p-3 sm:p-80 shadow-md">
|
||||
<p class="text-center font-bold text-4xl">Documentation</p>
|
||||
<div class="pt-5" id="shortenedLink">
|
||||
<p class="font-bold">Shortening a link</p>
|
||||
<p class="font-medium">POST</p>
|
||||
<pre class="border-black bg-gray-300 border-2 rounded-lg text-center"><code class="language-plaintext">/api/short</code></pre>
|
||||
<p class="font-medium">Body</p>
|
||||
<pre class="border-black bg-gray-300 border-2 rounded-lg"><code class="language-json">{
|
||||
"origUrl": "http://google.com"
|
||||
}</code></pre>
|
||||
<p class="font-medium">Return</p>
|
||||
<pre class="border-black bg-gray-300 border-2 rounded-lg"><code class="language-json">{
|
||||
"urlId": "9bcF-giN0",
|
||||
"origUrl": "http://google.com",
|
||||
"shortUrl": "https://mrkayjaydee.xyz/9bcF-giN0",
|
||||
"clicks": 0,
|
||||
"date": "2022-01-26T15:37:26.977Z",
|
||||
"_id": "61f16ab602262b5f179d3336",
|
||||
"__v": 0
|
||||
}</code></pre>
|
||||
</div>
|
||||
<div class="pt-5" id="state"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="An Easy, Fast and Secured URL shortener">
|
||||
<title>MrKayJayDee URL Shortener</title>
|
||||
<link rel="icon" href="https://i.imgur.com/Dnmmfqd.png">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="flex h-screen justify-center items-center">
|
||||
<div class="bg-white rounded m-2 sm:m-5 p-3 sm:p-10 shadow-md">
|
||||
<p class="text-center">
|
||||
<input id="link" class="border py-2 px-3 text-grey-darkest content-auto rounded-lg border-black" type="text" placeholder="Your URL">
|
||||
<button id="shorten" class="text-center mt-5 rounded-2xl text-white py-1 px-5 text-base shadow overflow-ellipsis cursor-pointer bg-blue-600" type="submit" onclick="shortenLink()">Get Shortened url</button>
|
||||
</p>
|
||||
<div class="pt-5" id="shortenedLink">
|
||||
<p class="font-bold text-center">Shortened Link:</p>
|
||||
<p class="text-center">
|
||||
<a target="_blank" href="" id="shortenedLinkText">None</a>
|
||||
</p>
|
||||
<p class="font-bold text-center">Clicks Count</p>
|
||||
<p class="text-center" id="clicksCount">None</p>
|
||||
<p class="font-bold text-center">Creation Date</p>
|
||||
<p class="text-center" id="creationDate">None</p>
|
||||
<p class="pt-8 text-center">
|
||||
<a class="text-center mt-5 rounded-2xl text-white py-1 px-5 text-base shadow overflow-ellipsis cursor-pointer bg-blue-600" href="docs">Documentation</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="pt-5" id="state"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
function shortenLink() {
|
||||
let url = document.getElementById("link").value;
|
||||
if (validateUrl(url)) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "http://localhost:8080/api/short", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.responseType = 'json';
|
||||
xhr.send(JSON.stringify({
|
||||
origUrl: url
|
||||
}));
|
||||
xhr.onreadystatechange = function (evt) {
|
||||
if (xhr.readyState !== 4) {
|
||||
return;
|
||||
}
|
||||
document.getElementById("shortenedLinkText").innerHTML = xhr.response.shortUrl;
|
||||
document.getElementById("shortenedLinkText").href = xhr.response.shortUrl;
|
||||
document.getElementById("clicksCount").innerHTML = xhr.response.clicks;
|
||||
document.getElementById("creationDate").innerHTML = new Date(xhr.response.date).toLocaleString();
|
||||
document.getElementById("state").innerHTML = "<p class='text-center text-green-600'>URL successfully retrieved</p>";
|
||||
};
|
||||
} else {
|
||||
document.getElementById("state").innerHTML = "<p class='text-center text-red-600'>Invalid URL</p>";
|
||||
}
|
||||
}
|
||||
|
||||
function validateUrl(value) {
|
||||
return /^(http|https|ftp)\:\/\/([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.([a-zA-Z]{1,}))(\:[0-9]+)*(\/($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+))*$/gm.test(
|
||||
value
|
||||
);
|
||||
}
|
||||
document.querySelector("#link").addEventListener("keyup", event => {
|
||||
if(event.key !== "Enter") return;
|
||||
document.querySelector("#shorten").click();
|
||||
event.preventDefault();
|
||||
});
|
||||
</script>
|
||||
</html>
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Install mogodb locally
|
||||
* https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const connectDB = require('./config/database');
|
||||
require('dotenv').config();
|
||||
|
||||
connectDB();
|
||||
|
||||
// Body Parser
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(express.json());
|
||||
app.use(express.static(__dirname + '/'));
|
||||
|
||||
app.use('/', require('./routes/index'));
|
||||
app.use('/api', require('./routes/urls'));
|
||||
|
||||
// Server Setup
|
||||
const PORT = 8080;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running at PORT ${PORT}`);
|
||||
});
|
@ -0,0 +1,27 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const UrlSchema = new mongoose.Schema({
|
||||
urlId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
origUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
shortUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
clicks: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0,
|
||||
},
|
||||
date: {
|
||||
type: String,
|
||||
default: Date.now,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('Url', UrlSchema);
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "link-shortener",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Killian' DAL-CIN contact@mrkayjaydee.xyz",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dotenv": "^14.3.2",
|
||||
"express": "^4.17.2",
|
||||
"mongoose": "^6.1.8",
|
||||
"shortid": "^2.2.16"
|
||||
},
|
||||
"description": "A simple link shortener API"
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
const express = require('express');
|
||||
const index = express.Router();
|
||||
const Url = require('../models/Url');
|
||||
|
||||
index.get('/', async (req, res) => {
|
||||
res.sendFile('index.html', { root: './' })
|
||||
});
|
||||
|
||||
index.get('/docs', async (req, res) => {
|
||||
res.sendFile('docs.html', { root: './' })
|
||||
});
|
||||
|
||||
index.get('/:urlId', async (req, res) => {
|
||||
try {
|
||||
const url = await Url.findOne({ urlId: req.params.urlId });
|
||||
if (url) {
|
||||
url.clicks++;
|
||||
url.save();
|
||||
return res.redirect(url.origUrl);
|
||||
} else res.status(404).json('Not found');
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).json('Server Error 2');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = index;
|
@ -0,0 +1,39 @@
|
||||
const express = require('express');
|
||||
const urls = express.Router();
|
||||
const shortid = require('shortid');
|
||||
const Url = require('../models/Url');
|
||||
const utils = require('../utils/utils');
|
||||
|
||||
urls.post('/short', async (req, res) => {
|
||||
const { origUrl } = req.body;
|
||||
const base = process.env.BASE;
|
||||
|
||||
const urlId = shortid.generate();
|
||||
if (utils.validateUrl(origUrl)) {
|
||||
try {
|
||||
let url = await Url.findOne({ origUrl });
|
||||
if (url) {
|
||||
res.json(url);
|
||||
} else {
|
||||
const shortUrl = `${base}/${urlId}`;
|
||||
|
||||
url = new Url({
|
||||
origUrl,
|
||||
shortUrl,
|
||||
urlId,
|
||||
date: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await url.save();
|
||||
res.json(url);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).json('Server Error');
|
||||
}
|
||||
} else {
|
||||
res.status(400).json('Invalid Provided Url');
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = urls;
|
@ -0,0 +1,7 @@
|
||||
function validateUrl(value) {
|
||||
return /^(http|https|ftp)\:\/\/([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.([a-zA-Z]{1,}))(\:[0-9]+)*(\/($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+))*$/gm.test(
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { validateUrl };
|
Loading…
Reference in New Issue