Skip to content

Commit 2d7ef7e

Browse files
committed
feat: map of requests
1 parent c2caee2 commit 2d7ef7e

File tree

9 files changed

+448
-17
lines changed

9 files changed

+448
-17
lines changed

app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ app.get('/urls/edit/:id', urlsController.editUrl);
159159
// app.put('/urls/:id', urlsController.updateUrl);
160160
app.delete('/urls/deleteUrl', urlsController.deleteUrl);
161161
app.get('/admin', adminController.index);
162+
app.get('/admin/country', adminController.country);
162163
app.get('/account/verify', passportConfig.isAuthenticated, userController.getVerifyEmail);
163164
app.get('/account/verify/:token', passportConfig.isAuthenticated, userController.getVerifyEmailToken);
164165
app.get('/account', passportConfig.isAuthenticated, userController.getAccount);
@@ -169,6 +170,7 @@ app.get('/account/unlink/:provider', passportConfig.isAuthenticated, userControl
169170
app.post('/shorten', shortUrlController.postShortUrl);
170171
app.get('/shortener', shortenerController.index);
171172

173+
172174
// Must be last due to wildcard matching
173175
app.get('/:slug', shortUrlController.getShortUrl);
174176

controllers/admin.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,30 @@
22
* GET /
33
* Admin page.
44
*/
5+
6+
const Url = require('../models/Url')
7+
58
exports.index = (req, res) => {
69
res.render('admin', {
710
title: 'Admin'
811
});
912
};
10-
13+
14+
exports.country = async (req, res) => {
15+
const countryMap = {};
16+
17+
Url.find({}, function(err, urls) {
18+
if (err) {
19+
console.error(err);
20+
}
21+
for (const url of urls) {
22+
const { country, countryCode } = url;
23+
if (countryMap[country]) {
24+
countryMap[country] += 1;
25+
} else {
26+
countryMap[country] = 1;
27+
}
28+
}
29+
res.json(countryMap);
30+
});
31+
};

controllers/shortUrl.js

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
const Url = require('../models/Url')
22
const { validateUrl } = require('../utils/utils')
3-
const compareUrls = require('compare-urls');
3+
const dns = require('dns');
4+
const fetch = require('node-fetch');
5+
46
// const dotenv = require('dotenv')
57
// dotenv.config({ path: '.env.example' })
68

9+
async function lookupPromise(domain) {
10+
return new Promise((resolve, reject) => {
11+
dns.lookup(domain, (err, address, family) => {
12+
if (err) reject(err);
13+
resolve(address);
14+
});
15+
});
16+
};
17+
718

819
const urlNotDenylisted = (url) => {
920
const denylist = [
@@ -18,11 +29,9 @@ const urlNotDenylisted = (url) => {
1829
} catch (err) {
1930
return 'Invalid URL';
2031
}
21-
22-
for (const deniedUrl of denylist) {
23-
if (denylist.includes(urlObj.hostname) || denylist.includes(urlObj.host)) {
24-
return "That URL domain is banned";
25-
}
32+
33+
if (denylist.includes(urlObj.hostname) || denylist.includes(urlObj.host)) {
34+
return "That URL domain is banned";
2635
}
2736

2837
return true;
@@ -36,7 +45,7 @@ exports.postShortUrl = async (req, res) => {
3645
const { longUrl, user } = req.body
3746
const base = process.env.BASE_URL
3847

39-
const { nanoid } = await import ('nanoid');
48+
const { nanoid } = await import('nanoid');
4049

4150
const slug = nanoid(5)
4251
if (!validateUrl(longUrl)) {
@@ -56,6 +65,12 @@ exports.postShortUrl = async (req, res) => {
5665
}
5766
}
5867

68+
const ipAddress = await lookupPromise(new URL(longUrl).hostname);
69+
70+
const response = await fetch(`http://ip-api.com/json/${ipAddress}`);
71+
72+
const { country, countryCode } = await response.json();
73+
5974
try {
6075
let url = await Url.findOne({ longUrl, user })
6176
if (url) {
@@ -69,6 +84,9 @@ exports.postShortUrl = async (req, res) => {
6984
slug,
7085
date: new Date(),
7186
user,
87+
ipAddress,
88+
country,
89+
countryCode
7290
})
7391

7492
await url.save()
@@ -94,7 +112,7 @@ exports.getShortUrl = async (req, res) => {
94112
return res.redirect(url.longUrl)
95113
} else {
96114
res.status(404).json('No url found')
97-
}
115+
}
98116
} catch (err) {
99117
console.error(err)
100118
res.status(500).json('Server Error')

models/Url.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ const UrlSchema = new mongoose.Schema({
2525
user: {
2626
type: String,
2727
required: true,
28+
},
29+
ipAddess: {
30+
type: String,
31+
},
32+
country: {
33+
type: String,
34+
},
35+
countryCode: {
36+
type: String,
2837
}
2938
})
3039

package-lock.json

Lines changed: 89 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"mongoose": "^6.2.8",
4545
"morgan": "^1.10.0",
4646
"nanoid": "^4.0.0",
47+
"node-fetch": "2.6.0",
4748
"node-quickbooks": "^2.0.39",
4849
"node-sass": "^7.0.1",
4950
"node-sass-middleware": "^1.0.1",

public/js/countries.geo.js

Lines changed: 182 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/js/map.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
var map = L.map('map').setView([37.8, -96], 2);
2+
var geojson;
3+
var info = L.control();
4+
5+
info.onAdd = function (map) {
6+
this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info"
7+
this.update();
8+
return this._div;
9+
};
10+
11+
// method that we will use to update the control based on feature properties passed
12+
info.update = function (props) {
13+
this._div.innerHTML = '<h4>Countries</h4>' + (props ?
14+
'<b>' + props.name + '</b><br />' + (props.density || '0') + ' redirections'
15+
: 'Hover over a country');
16+
};
17+
18+
info.addTo(map);
19+
20+
function getColor(d) {
21+
return d > 7 ? '#800026' :
22+
d > 6 ? '#BD0026' :
23+
d > 5 ? '#E31A1C' :
24+
d > 4 ? '#FC4E2A' :
25+
d > 3 ? '#FD8D3C' :
26+
d > 2 ? '#FEB24C' :
27+
d > 1 ? '#FED976' :
28+
'#FFEDA0';
29+
}
30+
31+
function highlightFeature(e) {
32+
var layer = e.target;
33+
34+
layer.setStyle({
35+
weight: 5,
36+
color: '#666',
37+
dashArray: '',
38+
fillOpacity: 0.7
39+
});
40+
41+
layer.bringToFront();
42+
info.update(layer.feature.properties);
43+
}
44+
45+
function resetHighlight(e) {
46+
geojson.resetStyle(e.target);
47+
info.update();
48+
}
49+
50+
function zoomToFeature(e) {
51+
map.fitBounds(e.target.getBounds());
52+
}
53+
54+
function onEachFeature(feature, layer) {
55+
layer.on({
56+
mouseover: highlightFeature,
57+
mouseout: resetHighlight,
58+
click: zoomToFeature
59+
});
60+
}
61+
62+
fetch("/admin/country")
63+
.then(response => response.json())
64+
.then(data => {
65+
// const newCountriesData = JSON.parse(JSON.stringify(countriesData));
66+
console.log(data);
67+
for (const country of countriesData.features) {
68+
for (const countryData of Object.keys(data)) {
69+
if (country.properties.name.includes(countryData)) {
70+
country.properties.density = data[countryData];
71+
}
72+
}
73+
}
74+
console.log(countriesData);
75+
geojson = L.geoJson(countriesData, {style, onEachFeature}).addTo(map);
76+
});
77+
78+
function style(feature) {
79+
return {
80+
fillColor: getColor(feature.properties.density),
81+
weight: 2,
82+
opacity: 1,
83+
color: 'white',
84+
dashArray: '3',
85+
fillOpacity: 0.7
86+
};
87+
}
88+
89+
var tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
90+
maxZoom: 19,
91+
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
92+
}).addTo(map);

0 commit comments

Comments
 (0)