This commit is contained in:
Martin Donnelly 2017-08-05 21:14:17 +01:00
parent f212a85a41
commit 1209271cd4
11 changed files with 8194 additions and 64 deletions

24
.eslintrc.json Normal file
View File

@ -0,0 +1,24 @@
{
"plugins": [
"react"
],
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"env": {
"es6": true,
"browser": true,
"node": true,
"mocha": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"rules": {
}
}

166
.gitignore vendored
View File

@ -1,3 +1,147 @@
### Archives template
# It's better to unpack these files and commit the raw source because
# git has its own built in compression methods.
*.7z
*.jar
*.rar
*.zip
*.gz
*.bzip
*.bz2
*.xz
*.lzma
*.cab
#packing-only formats
*.iso
*.tar
#package management formats
*.dmg
*.xpi
*.gem
*.egg
*.deb
*.rpm
*.msi
*.msm
*.msp
### Windows template
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Xcode template
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
## Other
*.xccheckout
*.moved-aside
*.xcuserstate
### OSX template
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Node template
# Logs
logs
*.log
@ -24,10 +168,24 @@ coverage
build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
# Optional npm cache directory
.npm
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Optional REPL history
.node_repl_history
# compiled output
/dist
/tmp
# dependencies
/node_modules
/bower_components
# misc
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
/lib/newdata.json

64
app.js
View File

@ -1,13 +1,16 @@
var express = require('express');
var app = express();
var path = require('path');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var config = require('./config');
var base58 = require('./base58.js');
const express = require('express');
const app = express();
const path = require('path');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const config = require('./config');
const base58 = require('./base58.js');
const log4js = require('log4js');
const logger = log4js.getLogger();
logger.level = 'debug';
// grab the url model
var Url = require('./models/url');
const Url = require('./models/url');
mongoose.connect('mongodb://' + config.db.host + '/' + config.db.name);
@ -16,16 +19,30 @@ app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
app.all('/*', function(req, res, next) {
// CORS headers
res.header("Access-Control-Allow-Origin", "*"); // restrict it to the required domain
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
// Set custom headers for CORS
res.header('Access-Control-Allow-Headers', 'Content-type,Accept,X-Access-Token,X-Key');
if (req.method == 'OPTIONS') {
res.status(200).end();
} else {
next();
}
});
app.get('/', function(req, res){
res.sendFile(path.join(__dirname, 'views/index.html'));
});
app.post('/api/shorten', function(req, res){
var longUrl = req.body.url;
var shortUrl = '';
app.post('/api/v1/shorten', function(req, res){
const longUrl = req.body.url;
let shortUrl = '';
// check if url already exists in database
// check if url already exists in database
Url.findOne({long_url: longUrl}, function (err, doc){
if (doc){
shortUrl = config.webhost + base58.encode(doc._id);
@ -33,14 +50,14 @@ app.post('/api/shorten', function(req, res){
res.send({'shortUrl': shortUrl});
} else {
// since it doesn't exist, let's go ahead and create it:
var newUrl = Url({
long_url: longUrl
});
const newUrl = Url({
long_url: longUrl
});
// save the new link
// save the new link
newUrl.save(function(err) {
if (err){
console.log(err);
logger.error(err);
}
shortUrl = config.webhost + base58.encode(newUrl._id);
@ -55,13 +72,14 @@ app.post('/api/shorten', function(req, res){
app.get('/:encoded_id', function(req, res){
var base58Id = req.params.encoded_id;
const base58Id = req.params.encoded_id;
var id = base58.decode(base58Id);
const id = base58.decode(base58Id);
// check if url already exists in database
Url.findOne({_id: id}, function (err, doc){
// check if url already exists in database
Url.findOneAndUpdate({_id: id}, {$inc:{visits:1}}, function (err, doc){
if (doc) {
logger.debug('doc', doc);
res.redirect(doc.long_url);
} else {
res.redirect(config.webhost);
@ -70,6 +88,6 @@ app.get('/:encoded_id', function(req, res){
});
var server = app.listen(3000, function(){
console.log('Server listening on port 3000');
const server = app.listen(3000, function () {
logger.info('Server listening on port 3000');
});

View File

@ -1,22 +1,24 @@
var alphabet = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
var base = alphabet.length;
// var alphabet = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
const alphabet = 'bMJZSrnxEyq8kN3UYQL5oXwV7BCFRtvpmDf1shAuHzKicTjeG29Pg4adW6';
const base = alphabet.length;
function encode(num){
var encoded = '';
while (num){
var remainder = num % base;
num = Math.floor(num / base);
let encoded = '';
while (num){
const remainder = num % base;
num = Math.floor(num / base);
encoded = alphabet[remainder].toString() + encoded;
}
return encoded;
}
function decode(str){
var decoded = 0;
while (str){
var index = alphabet.indexOf(str[0]);
var power = str.length - 1;
decoded += index * (Math.pow(base, power));
let decoded = 0;
while (str){
const index = alphabet.indexOf(str[0]);
const power = str.length - 1;
decoded += index * (Math.pow(base, power));
str = str.substring(1);
}
return decoded;

View File

@ -1,4 +1,4 @@
var config = {};
const config = {};
config.db = {};
config.webhost = 'http://localhost:3000/';

View File

@ -1,23 +1,24 @@
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
var CounterSchema = Schema({
const CounterSchema = Schema({
_id: {type: String, required: true},
seq: { type: Number, default: 0 }
seq: {type: Number, default: 1000}
});
var counter = mongoose.model('counter', CounterSchema);
const counter = mongoose.model('counter', CounterSchema);
// create a schema for our links
var urlSchema = new Schema({
_id: {type: Number, index: true},
long_url: String,
created_at: Date
const urlSchema = new Schema({
_id: {type: Number, index: true},
long_url: String,
created_at: Date,
visits: {type: Number, default: 0}
});
urlSchema.pre('save', function(next){
var doc = this;
counter.findByIdAndUpdate({_id: 'url_count'}, {$inc: {seq: 1} }, function(error, counter) {
const doc = this;
counter.findByIdAndUpdate({_id: 'url_count'}, {$inc: {seq: 1} }, function(error, counter) {
if (error)
return next(error);
doc.created_at = new Date();
@ -26,6 +27,6 @@ urlSchema.pre('save', function(next){
});
});
var Url = mongoose.model('Url', urlSchema);
const Url = mongoose.model('Url', urlSchema);
module.exports = Url;

7883
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -12,5 +12,31 @@
"body-parser": "^1.14.1",
"express": "^4.13.3",
"mongoose": "4.2.9"
},
"devDependencies": {
"eslint": "^3.18.0",
"eslint-config-defaults": "^9.0.0",
"eslint-config-standard": "^7.1.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-node": "^4.2.2",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-react": "^6.10.3",
"eslint-plugin-standard": "^2.1.1",
"eslint-watch": "^3.0.1",
"gulp-autoprefixer": "^3.1.1",
"gulp-babel": "^6.1.2",
"gulp-cache": "^0.4.6",
"gulp-concat": "^2.6.1",
"gulp-cssnano": "^2.1.2",
"gulp-google-webfonts": "0.0.14",
"gulp-html-replace": "^1.6.2",
"gulp-htmlmin": "^3.0.0",
"gulp-jshint": "^2.0.4",
"gulp-rename": "^1.2.2",
"gulp-sass": "^3.1.0",
"gulp-scss": "^1.4.0",
"gulp-strip-debug": "^1.1.0",
"gulp-uglify": "^2.1.2",
"log4js": "^2.3.3"
}
}

View File

@ -5,13 +5,13 @@
html,
body {
height: 100%;
background-color: #4791D2;
background-color: #FFC107;
}
body {
color: #fff;
color: #4A148C;
text-align: center;
font-family: 'Raleway', sans-serif;
font-family: 'Roboto Condensed', sans-serif;
}
.btn-shorten {
@ -69,7 +69,7 @@ body {
}
#link a{
color: #F89406;
color: #FF4081;
font-size: 1.5em;
margin-right: 20px;
}

View File

@ -1,7 +1,8 @@
$('.btn-shorten').on('click', function(){
$('#btn-shorten').on('click', function(){
console.log('Click');
$.ajax({
url: '/api/shorten',
url: '/api/v1/shorten',
type: 'POST',
dataType: 'JSON',
data: {url: $('#url-field').val()},

View File

@ -5,14 +5,31 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>URL Shortener - coligo.io</title>
<link href='https://fonts.googleapis.com/css?family=Raleway' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<title>nURL</title>
<link href="https://fonts.googleapis.com/css?family=Roboto+Condensed" rel="stylesheet">
<!--<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">-->
<link rel="stylesheet" href="//cdn.muicss.com/mui-0.9.20/css/mui.min.css">
<link href="css/styles.css" rel="stylesheet">
</head>
<body>
<div class="site-wrapper">
<div class="mui-container-fluid">
<div class="mui--text-display3">nURL</div>
<div class="mui--text-subhead">nurl.co</div>
<div class="mui-panel">
<form class="mui-form--inline">
<div class="mui-textfield">
<input type="text" id="url-field" placeholder="Paste a link...">
</div>
<button class="mui-btn mui-btn--raised mui-btn--accent" id="btn-shorten" type="button">SHORTEN</button>
</form>
<div class="mui-row">
<div class="mui-col-lg-12" id="link"></div>
</div>
</div>
</div>
<!--<div class="site-wrapper">
<div class="site-wrapper-inner">
@ -20,8 +37,8 @@
<div class="inner cover">
<span class="glyphicon glyphicon-link"></span>
<h1>URL Shortener</h1>
<h4>coligo.io</h4>
<h1>nURL</h1>
<h4>nurl.co</h4>
<div class="row">
@ -46,7 +63,7 @@
</div>
</div>
</div>-->
<script src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>