Merge remote-tracking branch 'origin/release-0.6.0' into develop
commit
ad86a22874
|
@ -2,8 +2,8 @@
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
# Secret stuff
|
# Secret stuff
|
||||||
config/env*
|
config/env/*
|
||||||
!config/env-sample.js
|
!config/env/sample.js
|
||||||
|
|
||||||
# Minified static files (can be built with `npm run minify`)
|
# Minified static files (can be built with `npm run minify`)
|
||||||
static/**/*.min.*
|
static/**/*.min.*
|
||||||
|
|
33
README.md
33
README.md
|
@ -1,7 +1,7 @@
|
||||||
# Tracman
|
# <img align="left" src="/static/img/icon/by/48.png" alt="[]" title="The Tracman Logo">Tracman
|
||||||
###### v 0.6.0
|
###### v 0.6.0
|
||||||
|
|
||||||
node.js application to display a map with user's location.
|
node.js application to display a sharable map with user's location.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -9,31 +9,48 @@ node.js application to display a map with user's location.
|
||||||
$ git clone https://github.com/Tracman-org/Server.git && (cd Server && exec npm install)
|
$ git clone https://github.com/Tracman-org/Server.git && (cd Server && exec npm install)
|
||||||
```
|
```
|
||||||
|
|
||||||
You will need to set up a configuration file at `config/env.js`. Use `config/env-sample.js` for an example. You can get API keys at the [google developer's console](https://console.developers.google.com/apis/credentials). You will need to set up approved hosts and auth callbacks. There is more information in [their documentation](https://support.google.com/googleapi/answer/6158857?hl=en).
|
You will need to set up a configuration file at `config/env/env.js`. Use `config/env/sample.js` for an example. You can get API keys at the [google developer's console](https://console.developers.google.com/apis/credentials). You will need to set up approved hosts and auth callbacks. There is more information in [their documentation](https://support.google.com/googleapi/answer/6158857?hl=en).
|
||||||
|
|
||||||
## Running
|
A good method is to simply copy the sample configuration and point `config/env/env.js` to the new version:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cp config/env/sample.js config/env/my-config.js
|
||||||
|
$ echo "module.exports = require('./my-config.js');" > config/env/env.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Then edit `config/env/my-config.js` to match your local environment.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Run Tracman with npm:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ npm run minify && npm start
|
$ npm run minify && npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
or, using [nodemon](https://nodemon.io/):
|
...or with [nodemon](https://nodemon.io/):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ npm run nodemon
|
$ npm run nodemon
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Nodemon will automatically minify files and restart the app when you make changes. Check out the `nodemon.json` configuration.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Tracman will be updated according to [this branching model](http://nvie.com/posts/a-successful-git-branching-model).
|
Tracman will be updated according to [this branching model](http://nvie.com/posts/a-successful-git-branching-model)... more or less. If you know anything about programming Android, [the Tracman android app](https://github.com/Tracman-org/Android) is more desperate for help.
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
#### v0.6.0
|
#### v0.6.0
|
||||||
|
|
||||||
* Added more login options
|
* [#32](https://github.com/Tracman-org/Server/issues/32), [#57](https://github.com/Tracman-org/Server/issues/57), [#58](https://github.com/Tracman-org/Server/issues/58), [#60](https://github.com/Tracman-org/Server/issues/60) Added more login options
|
||||||
* Replaced some callbacks with promises
|
* [#50](https://github.com/Tracman-org/Server/issues/50) Replaced some callbacks with promises
|
||||||
* Minified static files
|
* Minified static files
|
||||||
|
* [#51](https://github.com/Tracman-org/Server/issues/51), [#52](https://github.com/Tracman-org/Server/issues/52) Added settings validations
|
||||||
|
* [#54](https://github.com/Tracman-org/Server/issues/54), [#55](https://github.com/Tracman-org/Server/issues/55) Made map work better
|
||||||
|
* [#61](https://github.com/Tracman-org/Server/issues/61) New MongoDB security
|
||||||
|
* [#62](https://github.com/Tracman-org/Server/issues/62) Fixed error handling
|
||||||
|
|
||||||
#### v0.5.1
|
#### v0.5.1
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const nodemailer = require('nodemailer'),
|
const nodemailer = require('nodemailer'),
|
||||||
env = require('./env.js');
|
env = require('./env/env.js');
|
||||||
|
|
||||||
let transporter = nodemailer.createTransport({
|
let transporter = nodemailer.createTransport({
|
||||||
host: 'keithirwin.us',
|
host: 'keithirwin.us',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const env = require('./env.js');
|
const env = require('./env/env.js');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
|
|
|
@ -47,58 +47,80 @@ const userSchema = new mongoose.Schema({
|
||||||
/* User methods */ {
|
/* User methods */ {
|
||||||
|
|
||||||
//TODO: Return promises instead of taking callbacks
|
//TODO: Return promises instead of taking callbacks
|
||||||
|
// See https://gist.github.com/7h1b0/5154fda207e68ad1cefc#file-random-js
|
||||||
|
// For an example
|
||||||
|
|
||||||
// Create email confirmation token
|
// Create email confirmation token
|
||||||
userSchema.methods.createEmailToken = function(next){
|
userSchema.methods.createEmailToken = function(next){ // next(err,token)
|
||||||
|
//console.log('user.createEmailToken() called');
|
||||||
var user = this;
|
var user = this;
|
||||||
|
|
||||||
crypto.randomBytes(16)
|
crypto.randomBytes(16, (err,buf)=>{
|
||||||
.then( (buf)=>{
|
if (err){ next(err,null); }
|
||||||
user.emailToken = buf.toString('hex');
|
if (buf){
|
||||||
user.save();
|
//console.log(`Buffer ${buf.toString('hex')} created`);
|
||||||
})
|
user.emailToken = buf.toString('hex');
|
||||||
.catch( (err)=>{ next(err,null); });
|
user.save()
|
||||||
|
.then( ()=>{
|
||||||
|
return next(null,user.emailToken);
|
||||||
|
})
|
||||||
|
.catch( (err)=>{
|
||||||
|
return next(err,null);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate hash for new password
|
|
||||||
userSchema.methods.generateHash = function(password,next){
|
|
||||||
bcrypt.genSalt(8)
|
|
||||||
.then( (salt)=>{
|
|
||||||
bcrypt.hash(password, salt, null, next);
|
|
||||||
})
|
|
||||||
.catch( (err)=>{ return next(err); });
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create password reset token
|
// Create password reset token
|
||||||
userSchema.methods.createPassToken = function(next){
|
userSchema.methods.createPassToken = function(next){ // next(err,token,expires)
|
||||||
var user = this;
|
var user = this;
|
||||||
|
|
||||||
// Reuse old token, resetting clock
|
// Reuse old token, resetting clock
|
||||||
if ( user.auth.passTokenExpires <= Date.now() ){
|
if ( user.auth.passTokenExpires >= Date.now() ){
|
||||||
|
console.log(`Reusing old password token...`);
|
||||||
user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour
|
user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour
|
||||||
user.save()
|
user.save()
|
||||||
.then( ()=>{
|
.then( ()=>{
|
||||||
return next(null,user.auth.passToken);
|
return next(null,user.auth.passToken,user.auth.passTokenExpires);
|
||||||
})
|
})
|
||||||
.catch( (err)=>{
|
.catch( (err)=>{
|
||||||
return next(err,user.auth.passToken);
|
return next(err,null,null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new token
|
// Create new token
|
||||||
else {
|
else {
|
||||||
crypto.randomBytes(16)
|
console.log(`Creating new password token...`);
|
||||||
.then( (buf)=>{
|
crypto.randomBytes(16, (err,buf)=>{
|
||||||
user.auth.passToken = buf.toString('hex');
|
if (err){ return next(err,null,null); }
|
||||||
user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour
|
if (buf) {
|
||||||
user.save();
|
user.auth.passToken = buf.toString('hex');
|
||||||
})
|
user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour
|
||||||
.catch( (err)=>{ return next(err,null); });
|
user.save()
|
||||||
|
.then( ()=>{
|
||||||
|
return next(null,user.auth.passToken,user.auth.passTokenExpires);
|
||||||
|
})
|
||||||
|
.catch( (err)=>{
|
||||||
|
return next(err,null,null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Generate hash for new password
|
||||||
|
userSchema.methods.generateHash = function(password,next){
|
||||||
|
// next(err,hash);
|
||||||
|
bcrypt.genSalt(8)
|
||||||
|
.then( (salt)=>{
|
||||||
|
bcrypt.hash(password, salt, null, next);
|
||||||
|
})
|
||||||
|
.catch( (err)=>{ return next(err,null); });
|
||||||
|
};
|
||||||
|
|
||||||
// Check for valid password
|
// Check for valid password
|
||||||
userSchema.methods.validPassword = function(password,next){
|
userSchema.methods.validPassword = function(password,next){
|
||||||
bcrypt.compare(password, this.auth.password, next);
|
bcrypt.compare(password, this.auth.password, next);
|
||||||
|
|
|
@ -8,7 +8,7 @@ const
|
||||||
GoogleTokenStrategy = require('passport-google-id-token'),
|
GoogleTokenStrategy = require('passport-google-id-token'),
|
||||||
FacebookTokenStrategy = require('passport-facebook-token'),
|
FacebookTokenStrategy = require('passport-facebook-token'),
|
||||||
TwitterTokenStrategy = require('passport-twitter-token'),
|
TwitterTokenStrategy = require('passport-twitter-token'),
|
||||||
env = require('./env.js'),
|
env = require('./env/env.js'),
|
||||||
mw = require('./middleware.js'),
|
mw = require('./middleware.js'),
|
||||||
User = require('./models.js').user;
|
User = require('./models.js').user;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ const
|
||||||
mail = require('../mail.js'),
|
mail = require('../mail.js'),
|
||||||
User = require('../models.js').user,
|
User = require('../models.js').user,
|
||||||
crypto = require('crypto'),
|
crypto = require('crypto'),
|
||||||
env = require('../env.js');
|
env = require('../env/env.js');
|
||||||
|
|
||||||
module.exports = (app, passport) => {
|
module.exports = (app, passport) => {
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ module.exports = (app, passport) => {
|
||||||
user.slug = slug(user.email.substring(0, user.email.indexOf('@')));
|
user.slug = slug(user.email.substring(0, user.email.indexOf('@')));
|
||||||
|
|
||||||
// Generate unique slug
|
// Generate unique slug
|
||||||
let slug = new Promise((resolve,reject) => {
|
const slug = new Promise((resolve,reject) => {
|
||||||
(function checkSlug(s,cb){
|
(function checkSlug(s,cb){
|
||||||
|
|
||||||
User.findOne({slug:s})
|
User.findOne({slug:s})
|
||||||
|
@ -150,7 +150,7 @@ module.exports = (app, passport) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate sk32
|
// Generate sk32
|
||||||
let sk32 = new Promise((resolve,reject) => {
|
const sk32 = new Promise((resolve,reject) => {
|
||||||
crypto.randomBytes(32)
|
crypto.randomBytes(32)
|
||||||
.then( (buf)=>{
|
.then( (buf)=>{
|
||||||
user.sk32 = buf.toString('hex');
|
user.sk32 = buf.toString('hex');
|
||||||
|
|
|
@ -14,7 +14,7 @@ module.exports = router
|
||||||
})
|
})
|
||||||
|
|
||||||
// Help
|
// Help
|
||||||
.get('/help', mw.ensureAuth, (req,res)=>{
|
.get('/help', (req,res)=>{
|
||||||
res.render('help');
|
res.render('help');
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ module.exports = router
|
||||||
})
|
})
|
||||||
|
|
||||||
// Endpoint to validate forms
|
// Endpoint to validate forms
|
||||||
.get('/validate', (req,res)=>{
|
.get('/validate', (req,res,next)=>{
|
||||||
|
|
||||||
// Validate unique slug
|
// Validate unique slug
|
||||||
if (req.query.slug) {
|
if (req.query.slug) {
|
||||||
|
@ -51,7 +51,10 @@ module.exports = router
|
||||||
}
|
}
|
||||||
else { res.sendStatus(200); }
|
else { res.sendStatus(200); }
|
||||||
})
|
})
|
||||||
.catch( (err)=>{ mw.throwErr(err,req); });
|
.catch( (err)=>{
|
||||||
|
console.error(err);
|
||||||
|
res.sendStatus(500);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate unique email
|
// Validate unique email
|
||||||
|
@ -63,7 +66,10 @@ module.exports = router
|
||||||
}
|
}
|
||||||
else { res.sendStatus(200); }
|
else { res.sendStatus(200); }
|
||||||
})
|
})
|
||||||
.catch( (err)=>{ mw.throwErr(err,req); });
|
.catch( (err)=>{
|
||||||
|
console.error(err);
|
||||||
|
res.sendStatus(500);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create slug
|
// Create slug
|
||||||
|
@ -71,10 +77,14 @@ module.exports = router
|
||||||
res.send(slug(xss(req.query.slugify)));
|
res.send(slug(xss(req.query.slugify)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sanitize for XSS
|
||||||
else if (req.query.xss) {
|
else if (req.query.xss) {
|
||||||
res.send(xss(req.query.xss));
|
res.send(xss(req.query.xss));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 404
|
||||||
|
else { next(); }
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Link to androidapp in play store
|
// Link to androidapp in play store
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
//TODO: Use promises
|
|
||||||
|
|
||||||
const router = require('express').Router(),
|
const router = require('express').Router(),
|
||||||
mw = require('../middleware.js'),
|
mw = require('../middleware.js'),
|
||||||
env = require('../env.js'),
|
env = require('../env/env.js'),
|
||||||
User = require('../models.js').user;
|
User = require('../models.js').user;
|
||||||
|
|
||||||
// Redirect to real slug
|
// Redirect to real slug
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
const slug = require('slug'),
|
const slug = require('slug'),
|
||||||
xss = require('xss'),
|
xss = require('xss'),
|
||||||
mellt = require('mellt'),
|
mellt = require('mellt'),
|
||||||
|
moment = require('moment'),
|
||||||
mw = require('../middleware.js'),
|
mw = require('../middleware.js'),
|
||||||
User = require('../models.js').user,
|
User = require('../models.js').user,
|
||||||
mail = require('../mail.js'),
|
mail = require('../mail.js'),
|
||||||
env = require('../env.js'),
|
env = require('../env/env.js'),
|
||||||
router = require('express').Router();
|
router = require('express').Router();
|
||||||
|
|
||||||
// Validate email addresses
|
// Validate email addresses
|
||||||
|
@ -29,11 +30,112 @@ router.route('/')
|
||||||
// Set new settings
|
// Set new settings
|
||||||
.post( (req,res,next)=>{
|
.post( (req,res,next)=>{
|
||||||
|
|
||||||
function setSettings(){
|
// Validate email
|
||||||
|
const checkEmail = new Promise( (resolve,reject)=>{
|
||||||
|
|
||||||
|
// Check validity
|
||||||
|
if (!validateEmail(req.body.email)) {
|
||||||
|
req.flash('warning', `<u>${req.body.email}</u> is not a valid email address. `);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if unchanged
|
||||||
|
else if (req.user.email===req.body.email) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check uniqueness
|
||||||
|
else {
|
||||||
|
User.findOne({ email: req.body.email })
|
||||||
|
.then( (existingUser)=>{
|
||||||
|
|
||||||
|
// Not unique!
|
||||||
|
if (existingUser && existingUser.id!==req.user.id) {
|
||||||
|
//console.log("Email not unique!");
|
||||||
|
req.flash('warning', `That email, <u>${req.body.email}</u>, is already in use by another user! `);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's unique
|
||||||
|
else {
|
||||||
|
//console.log("Email is unique");
|
||||||
|
req.user.newEmail = req.body.email;
|
||||||
|
|
||||||
|
// Create token
|
||||||
|
//console.log(`Creating email token...`);
|
||||||
|
req.user.createEmailToken((err,token)=>{
|
||||||
|
if (err){ reject(err); }
|
||||||
|
|
||||||
|
// Send token to user by email
|
||||||
|
//console.log(`Mailing new email token to ${req.body.email}...`);
|
||||||
|
mail.send({
|
||||||
|
to: `"${req.user.name}" <${req.body.email}>`,
|
||||||
|
from: mail.from,
|
||||||
|
subject: 'Confirm your new email address for Tracman',
|
||||||
|
text: mail.text(`A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it. \n\nTo confirm your email, follow this link:\n${env.url}/settings/email/${token}. `),
|
||||||
|
html: mail.html(`<p>A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it. </p><p>To confirm your email, follow this link:<br><a href="${env.url}/settings/email/${token}">${env.url}/settings/email/${token}</a>. </p>`)
|
||||||
|
})
|
||||||
|
.then( ()=>{
|
||||||
|
req.flash('warning',`An email has been sent to <u>${req.body.email}</u>. Check your inbox to confirm your new email address. `);
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validate slug
|
||||||
|
const checkSlug = new Promise( (resolve,reject)=>{
|
||||||
|
|
||||||
|
// Check existence
|
||||||
|
if (req.body.slug==='') {
|
||||||
|
req.flash('warning', `You must supply a slug. `);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if unchanged
|
||||||
|
else if (req.user.slug===slug(xss(req.body.slug))) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check uniqueness
|
||||||
|
else {
|
||||||
|
|
||||||
|
User.findOne({ slug: req.body.slug })
|
||||||
|
.then( (existingUser)=>{
|
||||||
|
|
||||||
|
// Not unique!
|
||||||
|
if (existingUser && existingUser.id!==req.user.id) {
|
||||||
|
req.flash('warning', `That slug, <u>${req.body.slug}</u>, is already in use by another user! `);
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's unique
|
||||||
|
else {
|
||||||
|
req.user.slug = slug(xss(req.body.slug));
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set settings when done
|
||||||
|
Promise.all([checkEmail, checkSlug])
|
||||||
|
.then( ()=>{
|
||||||
|
//console.log('Setting settings... ');
|
||||||
|
|
||||||
// Set values
|
// Set values
|
||||||
req.user.name = xss(req.body.name);
|
req.user.name = xss(req.body.name);
|
||||||
req.user.slug = slug(xss(req.body.slug));
|
|
||||||
req.user.settings = {
|
req.user.settings = {
|
||||||
units: req.body.units,
|
units: req.body.units,
|
||||||
defaultMap: req.body.map,
|
defaultMap: req.body.map,
|
||||||
|
@ -43,10 +145,12 @@ router.route('/')
|
||||||
showAlt: (req.body.showAlt)?true:false,
|
showAlt: (req.body.showAlt)?true:false,
|
||||||
showStreetview: (req.body.showStreet)?true:false
|
showStreetview: (req.body.showStreet)?true:false
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save user and send response
|
// Save user and send response
|
||||||
|
//console.log(`Saving new settings for user ${req.user.name}...`);
|
||||||
req.user.save()
|
req.user.save()
|
||||||
.then( ()=>{
|
.then( ()=>{
|
||||||
|
//console.log(`DONE! Redirecting user...`);
|
||||||
req.flash('success', 'Settings updated. ');
|
req.flash('success', 'Settings updated. ');
|
||||||
res.redirect('/settings');
|
res.redirect('/settings');
|
||||||
})
|
})
|
||||||
|
@ -55,62 +159,17 @@ router.route('/')
|
||||||
res.redirect('/settings');
|
res.redirect('/settings');
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
})
|
||||||
|
.catch( (err)=>{
|
||||||
// Validations
|
mw.throwErr(err,req);
|
||||||
if (req.body.slug==='') {
|
|
||||||
req.flash('warning', `You must supply a slug. `);
|
|
||||||
res.redirect('/settings');
|
res.redirect('/settings');
|
||||||
}
|
});
|
||||||
else if (!validateEmail(req.body.email)) {
|
|
||||||
req.flash('warning', `<u>${req.body.email}</u> is not a valid email address. `);
|
|
||||||
res.redirect('/settings');
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
|
|
||||||
// Email changed
|
|
||||||
if (req.user.email!==req.body.email) {
|
|
||||||
req.user.newEmail = req.body.email;
|
|
||||||
|
|
||||||
// Create token
|
|
||||||
req.user.createEmailToken((err,token)=>{
|
|
||||||
if (err){
|
|
||||||
mw.throwErr(err,req);
|
|
||||||
res.redirect(req.session.next||'/settings');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send token to user by email
|
|
||||||
mail.send({
|
|
||||||
to: `"${req.user.name}" <${req.body.email}>`,
|
|
||||||
from: mail.from,
|
|
||||||
subject: 'Confirm your new email address for Tracman',
|
|
||||||
text: mail.text(`A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it. \n\nTo confirm your email, follow this link:\n${env.url}/settings/email/${token}. `),
|
|
||||||
html: mail.html(`<p>A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it. </p><p>To confirm your email, follow this link:<br><a href="${env.url}/settings/email/${token}">${env.url}/settings/email/${token}</a>. </p>`)
|
|
||||||
})
|
|
||||||
.then( ()=>{
|
|
||||||
req.flash('warning',`An email has been sent to <u>${req.body.email}</u>. Check your inbox to confirm your new email address. `);
|
|
||||||
setSettings();
|
|
||||||
})
|
|
||||||
.catch( (err)=>{
|
|
||||||
mw.throwErr(err,req);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Email not changed
|
|
||||||
else { setSettings(); }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} )
|
} )
|
||||||
|
|
||||||
// Delete user account
|
// Delete user account
|
||||||
.delete( (req,res,next)=>{
|
.delete( (req,res,next)=>{
|
||||||
|
|
||||||
//TODO: Reenter password?
|
|
||||||
|
|
||||||
User.findByIdAndRemove(req.user)
|
User.findByIdAndRemove(req.user)
|
||||||
.then( ()=>{
|
.then( ()=>{
|
||||||
req.flash('success', 'Your account has been deleted. ');
|
req.flash('success', 'Your account has been deleted. ');
|
||||||
|
@ -168,28 +227,33 @@ router.route('/password')
|
||||||
.get( (req,res,next)=>{
|
.get( (req,res,next)=>{
|
||||||
|
|
||||||
// Create token for password change
|
// Create token for password change
|
||||||
req.user.createPassToken( (err,token)=>{
|
req.user.createPassToken( (err,token,expires)=>{
|
||||||
if (err){
|
if (err){
|
||||||
mw.throwErr(err,req);
|
mw.throwErr(err,req);
|
||||||
res.redirect(req.session.next||'/settings');
|
res.redirect((req.user)?'/settings':'/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Figure out expiration time
|
||||||
|
let expirationTimeString = (req.query.tz)?
|
||||||
|
moment(expires).utcOffset(req.query.tz).toDate().toLocaleTimeString(req.acceptsLanguages[0]):
|
||||||
|
moment(expires).toDate().toLocaleTimeString(req.acceptsLanguages[0])+" UTC";
|
||||||
|
|
||||||
// Confirm password change request by email.
|
// Confirm password change request by email.
|
||||||
mail.send({
|
mail.send({
|
||||||
to: mail.to(req.user),
|
to: mail.to(req.user),
|
||||||
from: mail.from,
|
from: mail.from,
|
||||||
subject: 'Request to change your Tracman password',
|
subject: 'Request to change your Tracman password',
|
||||||
text: mail.text(`A request has been made to change your tracman password. If you did not initiate this request, please contact support at keith@tracman.org. \n\nTo change your password, follow this link:\n${env.url}/settings/password/${token}. \n\nThis request will expire in 1 hour. `),
|
text: mail.text(`A request has been made to change your tracman password. If you did not initiate this request, please contact support at keith@tracman.org. \n\nTo change your password, follow this link:\n${env.url}/settings/password/${token}. \n\nThis request will expire at ${expirationTimeString}. `),
|
||||||
html: mail.html(`<p>A request has been made to change your tracman password. If you did not initiate this request, please contact support at <a href="mailto:keith@tracman.org">keith@tracman.org</a>. </p><p>To change your password, follow this link:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a>. </p><p>This request will expire in 1 hour. </p>`)
|
html: mail.html(`<p>A request has been made to change your tracman password. If you did not initiate this request, please contact support at <a href="mailto:keith@tracman.org">keith@tracman.org</a>. </p><p>To change your password, follow this link:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a>. </p><p>This request will expire at ${expirationTimeString}. </p>`)
|
||||||
})
|
})
|
||||||
.then( ()=>{
|
.then( ()=>{
|
||||||
// Alert user to check email.
|
// Alert user to check email.
|
||||||
req.flash('success',`An email has been sent to <u>${req.user.email}</u>. Check your inbox to complete your password change. `);
|
req.flash('success',`An link has been sent to <u>${req.user.email}</u>. Click on the link to complete your password change. This link will expire in one hour (${expirationTimeString}). `);
|
||||||
res.redirect('/login#login');
|
res.redirect((req.user)?'/settings':'/login');
|
||||||
})
|
})
|
||||||
.catch( (err)=>{
|
.catch( (err)=>{
|
||||||
mw.throwErr(err,req);
|
mw.throwErr(err,req);
|
||||||
res.redirect('/login#login');
|
res.redirect((req.user)?'/settings':'/login');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// Imports
|
// Imports
|
||||||
const mw = require('./middleware.js'),
|
const User = require('./models.js').user;
|
||||||
User = require('./models.js').user;
|
|
||||||
|
|
||||||
// Check for tracking clients
|
// Check for tracking clients
|
||||||
function checkForUsers(io, user) {
|
function checkForUsers(io, user) {
|
||||||
|
@ -10,10 +9,14 @@ function checkForUsers(io, user) {
|
||||||
|
|
||||||
// Checks if any sockets are getting updates for this user
|
// Checks if any sockets are getting updates for this user
|
||||||
//TODO: Use Object.values() after upgrading to node v7
|
//TODO: Use Object.values() after upgrading to node v7
|
||||||
if (Object.keys(io.sockets.connected).map( (id)=>{
|
/* if (Object.values(io.sockets.connected).some( (socket)=>{
|
||||||
return io.sockets.connected[id];
|
* return socket.gets==user;
|
||||||
|
* })) {
|
||||||
|
*/
|
||||||
|
if (Object.keys(io.sockets.connected).map( (key)=>{
|
||||||
|
return io.sockets.connected[key];
|
||||||
}).some( (socket)=>{
|
}).some( (socket)=>{
|
||||||
return socket.gets==user;
|
return socket.gets===user;
|
||||||
})) {
|
})) {
|
||||||
//console.log(`Activating updates for ${user}.`);
|
//console.log(`Activating updates for ${user}.`);
|
||||||
io.to(user).emit('activate','true');
|
io.to(user).emit('activate','true');
|
||||||
|
@ -29,11 +32,11 @@ module.exports = {
|
||||||
|
|
||||||
init: (io)=>{
|
init: (io)=>{
|
||||||
io.on('connection', (socket)=>{
|
io.on('connection', (socket)=>{
|
||||||
// console.log(`${socket.id} connected.`);
|
//console.log(`${socket.id} connected.`);
|
||||||
|
|
||||||
// Set a few variables
|
// Set a few variables
|
||||||
socket.ip = socket.client.request.headers['x-real-ip'];
|
//socket.ip = socket.client.request.headers['x-real-ip'];
|
||||||
socket.ua = socket.client.request.headers['user-agent'];
|
//socket.ua = socket.client.request.headers['user-agent'];
|
||||||
|
|
||||||
/* Log */
|
/* Log */
|
||||||
//socket.on('log', (text)=>{
|
//socket.on('log', (text)=>{
|
||||||
|
@ -61,7 +64,7 @@ module.exports = {
|
||||||
|
|
||||||
// Set location
|
// Set location
|
||||||
socket.on('set', (loc)=>{
|
socket.on('set', (loc)=>{
|
||||||
// console.log(`${socket.id} set location for ${loc.usr}`);
|
//console.log(`${socket.id} set location for ${loc.usr}`);
|
||||||
loc.time = Date.now();
|
loc.time = Date.now();
|
||||||
|
|
||||||
// Check for user and sk32 token
|
// Check for user and sk32 token
|
||||||
|
|
|
@ -11,7 +11,7 @@ const
|
||||||
nunjucks = require('nunjucks'),
|
nunjucks = require('nunjucks'),
|
||||||
passport = require('passport'),
|
passport = require('passport'),
|
||||||
flash = require('connect-flash-plus'),
|
flash = require('connect-flash-plus'),
|
||||||
env = require('./config/env.js'),
|
env = require('./config/env/env.js'),
|
||||||
User = require('./config/models.js').user,
|
User = require('./config/models.js').user,
|
||||||
app = express(),
|
app = express(),
|
||||||
http = require('http').Server(app),
|
http = require('http').Server(app),
|
||||||
|
@ -79,8 +79,8 @@ const
|
||||||
// Path for redirects
|
// Path for redirects
|
||||||
let nextPath = ( req.path.substring(0, req.path.indexOf('#')) || req.path );
|
let nextPath = ( req.path.substring(0, req.path.indexOf('#')) || req.path );
|
||||||
if ( nextPath.substring(0,6)!=='/login' && nextPath.substring(0,7)!=='/logout' ){
|
if ( nextPath.substring(0,6)!=='/login' && nextPath.substring(0,7)!=='/logout' ){
|
||||||
// console.log(`Setting redirect path to ${nextPath}#`);
|
|
||||||
req.session.next = nextPath+'#';
|
req.session.next = nextPath+'#';
|
||||||
|
//console.log(`Set redirect path to ${nextPath}#`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// User account
|
// User account
|
||||||
|
|
|
@ -31,60 +31,6 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Elements */
|
/* Elements */
|
||||||
h1, h2, h3 {
|
|
||||||
margin: 0 0 5% 0;
|
|
||||||
position: relative;
|
|
||||||
z-index: 6;
|
|
||||||
}
|
|
||||||
h1, h2, h3, h4 { font-weight: 600; }
|
|
||||||
h1 {
|
|
||||||
font-size: 48px;
|
|
||||||
line-height: 46px; }
|
|
||||||
h2 {
|
|
||||||
font-size: 40px;
|
|
||||||
line-height: 36px; }
|
|
||||||
h3 { font-size: 28px; }
|
|
||||||
h4 { font-size: 20px; }
|
|
||||||
p {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 10vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #fbc93d;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
main a:hover:not(.btn) {
|
|
||||||
color: #fbc93d;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
a.underline {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
a.underline:hover:not(.btn) {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
width: 90%;
|
|
||||||
margin: 10% auto;
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
p img {
|
|
||||||
display: block;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
white-space: -moz-pre-wrap;
|
|
||||||
white-space: -pre-wrap;
|
|
||||||
white-space: -o-pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
main {
|
main {
|
||||||
top: 59px;
|
top: 59px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -108,6 +54,60 @@ section {
|
||||||
padding: 10vh 0 5vh;
|
padding: 10vh 0 5vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
margin: 0 0 5% 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 6;
|
||||||
|
}
|
||||||
|
/* Font sizes */
|
||||||
|
h1, h2, h3, h4 { font-weight: 600; }
|
||||||
|
h1 {
|
||||||
|
font-size: 48px;
|
||||||
|
line-height: 46px; }
|
||||||
|
h2 {
|
||||||
|
font-size: 40px;
|
||||||
|
line-height: 36px; }
|
||||||
|
h3 { font-size: 28px; }
|
||||||
|
h4 { font-size: 20px; }
|
||||||
|
|
||||||
|
p, main ul {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 10vh;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
width: 90%;
|
||||||
|
margin: 10% auto;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
p img {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
white-space: -moz-pre-wrap;
|
||||||
|
white-space: -pre-wrap;
|
||||||
|
white-space: -o-pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #fbc93d;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
main a:hover:not(.btn) {
|
||||||
|
color: #fbc93d;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
a.underline {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
a.underline:hover:not(.btn) {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Modifiers */
|
/* Modifiers */
|
||||||
.hide { display: none !important; }
|
.hide { display: none !important; }
|
||||||
.red, .red:hover { color: #fb6e3d !important; }
|
.red, .red:hover { color: #fb6e3d !important; }
|
||||||
|
|
|
@ -61,15 +61,56 @@ main {
|
||||||
padding: 2%;
|
padding: 2%;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
margin: 3%;
|
margin: 3%;
|
||||||
} #spd-sign {
|
}
|
||||||
|
#spd-sign {
|
||||||
color: #000;
|
color: #000;
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
border: 2px solid #000;
|
border: 2px solid #000;
|
||||||
} #alt-sign {
|
}
|
||||||
|
#alt-sign {
|
||||||
color: #FFF;
|
color: #FFF;
|
||||||
background-color: #009800;
|
background-color: #009800;
|
||||||
border: 2px solid #FFF;
|
border: 2px solid #FFF;
|
||||||
}
|
}
|
||||||
|
@media (max-width:300px) {
|
||||||
|
#spd, #alt {
|
||||||
|
height: 20px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
#alt-unit, #spd-unit {
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
#alt-label, #spd-label {
|
||||||
|
font-size: 9px;
|
||||||
|
height: 9px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width:300px) and (max-width:350px) {
|
||||||
|
#spd, #alt {
|
||||||
|
height: 22px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
#alt-unit, #spd-unit {
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
#alt-label, #spd-label {
|
||||||
|
font-size: 11px;
|
||||||
|
height: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width:350px) and (max-width:400px) {
|
||||||
|
#spd, #alt {
|
||||||
|
height: 30px;
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
#alt-unit, #spd-unit {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
#alt-label, #spd-label {
|
||||||
|
font-size: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@media (min-width:400px) {
|
@media (min-width:400px) {
|
||||||
#spd, #alt {
|
#spd, #alt {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
@ -82,44 +123,9 @@ main {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
} @media (min-width:350px) and (max-width:400px) {
|
|
||||||
#spd, #alt {
|
|
||||||
height: 30px;
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
#alt-unit, #spd-unit {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
#alt-label, #spd-label {
|
|
||||||
font-size: 14px;
|
|
||||||
height: 14px;
|
|
||||||
}
|
|
||||||
} @media (min-width:300px) and (max-width:350px) {
|
|
||||||
#spd, #alt {
|
|
||||||
height: 22px;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
#alt-unit, #spd-unit {
|
|
||||||
font-size: 9px;
|
|
||||||
}
|
|
||||||
#alt-label, #spd-label {
|
|
||||||
font-size: 11px;
|
|
||||||
height: 11px;
|
|
||||||
}
|
|
||||||
} @media (min-width:0px) and (max-width:300px) {
|
|
||||||
#spd, #alt {
|
|
||||||
height: 20px;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
#alt-unit, #spd-unit {
|
|
||||||
font-size: 8px;
|
|
||||||
}
|
|
||||||
#alt-label, #spd-label {
|
|
||||||
font-size: 9px;
|
|
||||||
height: 9px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Control buttons */
|
/* Control buttons */
|
||||||
#controls {
|
#controls {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
@ -127,16 +133,34 @@ main {
|
||||||
bottom: 50px;
|
bottom: 50px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
} #controls .btn {
|
}
|
||||||
|
#controls .btn {
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
background: #222;
|
background: #222;
|
||||||
height: 10vh;
|
height: 10vh;
|
||||||
padding: 2vh 0;
|
padding: 2vh 0;
|
||||||
} #controls .btn:hover {
|
}
|
||||||
|
#controls .btn .fa {
|
||||||
|
margin: 0 2vw;
|
||||||
|
}
|
||||||
|
#controls .btn:hover {
|
||||||
background: #333;
|
background: #333;
|
||||||
}
|
}
|
||||||
#controls .btn.set, #controls .btn.clear {
|
#controls .btn.set, #controls .btn.clear {
|
||||||
width: 30vw;
|
width: 30vw;
|
||||||
} #controls .btn.track {
|
}
|
||||||
|
#controls .btn.track {
|
||||||
width: 35vw;
|
width: 35vw;
|
||||||
|
}
|
||||||
|
@media (max-width:250px) {
|
||||||
|
#controls .btn { font-size:.8em; }
|
||||||
|
}
|
||||||
|
@media (min-width:250px) and (max-width:350px) {
|
||||||
|
#controls .btn { font-size:1em; }
|
||||||
|
}
|
||||||
|
@media (min-width:350px) and (max-width:450px) {
|
||||||
|
#controls .btn { font-size:1.15em; }
|
||||||
|
}
|
||||||
|
@media (min-width:450px) {
|
||||||
|
#controls .btn { font-size:1.3em; }
|
||||||
}
|
}
|
|
@ -54,7 +54,6 @@
|
||||||
|
|
||||||
/* Submit buttons */
|
/* Submit buttons */
|
||||||
#submit-group {
|
#submit-group {
|
||||||
padding: 0 0 60px;
|
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
}
|
}
|
||||||
#submit-group .main {
|
#submit-group .main {
|
||||||
|
|
|
@ -48,7 +48,7 @@ $(function(){
|
||||||
if (!wpid) {
|
if (!wpid) {
|
||||||
if (!navigator.geolocation) { alert('Unable to track location. '); }
|
if (!navigator.geolocation) { alert('Unable to track location. '); }
|
||||||
else {
|
else {
|
||||||
$('#track-loc').html('<i class="fa fa-crosshairs fa-spin"></i> Stop').prop('title',"Click here to stop tracking your location. ");
|
$('#track-loc').html('<i class="fa fa-crosshairs fa-spin"></i>Stop').prop('title',"Click here to stop tracking your location. ");
|
||||||
wpid = navigator.geolocation.watchPosition(
|
wpid = navigator.geolocation.watchPosition(
|
||||||
|
|
||||||
// Success callback
|
// Success callback
|
||||||
|
@ -80,7 +80,7 @@ $(function(){
|
||||||
|
|
||||||
// Stop tracking
|
// Stop tracking
|
||||||
else {
|
else {
|
||||||
$('#track-loc').html('<i class="fa fa-crosshairs"></i> Track').prop('title',"Click here to track your location. ");
|
$('#track-loc').html('<i class="fa fa-crosshairs"></i>Track').prop('title',"Click here to track your location. ");
|
||||||
navigator.geolocation.clearWatch(wpid);
|
navigator.geolocation.clearWatch(wpid);
|
||||||
wpid = undefined;
|
wpid = undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ function onConnect(socket,userid,mapuserid) {
|
||||||
console.log("🚹 Receiving updates for",mapuserid);
|
console.log("🚹 Receiving updates for",mapuserid);
|
||||||
|
|
||||||
// Can set location too
|
// Can set location too
|
||||||
if (mapuserid==userid) {
|
if (mapuserid===userid) {
|
||||||
socket.emit('can-set', userid );
|
socket.emit('can-set', userid );
|
||||||
console.log("🚹 Sending updates for",userid);
|
console.log("🚹 Sending updates for",userid);
|
||||||
}
|
}
|
||||||
|
@ -75,9 +75,10 @@ $(function() {
|
||||||
|
|
||||||
// Google maps API callback
|
// Google maps API callback
|
||||||
window.gmapsCb = function() {
|
window.gmapsCb = function() {
|
||||||
|
//console.log("gmapsCb() called");
|
||||||
|
|
||||||
// Make sure everything's ready...
|
// Make sure everything's ready...
|
||||||
waitForElements([mapuser,disp,noHeader], function() {
|
waitForElements([mapuser,disp,noHeader], function() {
|
||||||
//console.log("gmapsCb() called");
|
|
||||||
|
|
||||||
// Create map
|
// Create map
|
||||||
if (disp!=='1') {
|
if (disp!=='1') {
|
||||||
|
@ -145,8 +146,8 @@ window.gmapsCb = function() {
|
||||||
// Create altitude block
|
// Create altitude block
|
||||||
if (mapuser.settings.showAlt) {
|
if (mapuser.settings.showAlt) {
|
||||||
//console.log("Creating altitude sign...");
|
//console.log("Creating altitude sign...");
|
||||||
var elevator = new google.maps.ElevationService;
|
const elevator = new google.maps.ElevationService,
|
||||||
const altitudeSign = document.createElement('div'),
|
altitudeSign = document.createElement('div'),
|
||||||
altitudeLabel = document.createElement('div'),
|
altitudeLabel = document.createElement('div'),
|
||||||
altitudeText = document.createElement('div'),
|
altitudeText = document.createElement('div'),
|
||||||
altitudeUnit = document.createElement('div');
|
altitudeUnit = document.createElement('div');
|
||||||
|
@ -175,29 +176,53 @@ window.gmapsCb = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get location
|
// Got location
|
||||||
socket.on('get', function(loc) {
|
socket.on('get', function(loc) {
|
||||||
|
console.log("🌐️ Got location:",loc.lat+", "+loc.lon);
|
||||||
|
|
||||||
|
// Parse location
|
||||||
loc = parseLoc(loc);
|
loc = parseLoc(loc);
|
||||||
|
|
||||||
// Update street view
|
// Update map
|
||||||
if (disp!=='0' && mapuser.settings.showStreetview) {
|
if (disp!=='1') {
|
||||||
$('.tim').text('location updated '+loc.time);
|
|
||||||
if (mapuser.settings.showSpeed) { $('.spd').text(loc.spd.toFixed()); }
|
// Update time
|
||||||
|
$('#timestamp').text('location updated '+loc.time);
|
||||||
|
|
||||||
|
// Show or hide map
|
||||||
|
toggleMaps(loc);
|
||||||
|
|
||||||
|
// Update marker and map center
|
||||||
|
map.setCenter({ lat:loc.lat, lng:loc.lon });
|
||||||
|
marker.setPosition({ lat:loc.lat, lng:loc.lon });
|
||||||
|
|
||||||
|
// Update speed
|
||||||
|
if (mapuser.settings.showSpeed) {
|
||||||
|
$('#spd').text( loc.spd.toFixed() );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update altitude
|
||||||
if (mapuser.settings.showAlt) {
|
if (mapuser.settings.showAlt) {
|
||||||
getAltitude({lat:loc.lat,lng:loc.lon}, elevator, function(alt) {
|
getAltitude({
|
||||||
if (alt) { $('.alt').text((mapuser.settings.units=='standard')?(alt*3.28084).toFixed():alt.toFixed()); }
|
lat:loc.lat,
|
||||||
|
lng:loc.lon
|
||||||
|
}, elevator, function(alt) {
|
||||||
|
if (alt) {
|
||||||
|
$('#alt').text( (mapuser.settings.units=='standard')?(alt*3.28084).toFixed():alt.toFixed() );
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
toggleMaps(loc);
|
|
||||||
map.setCenter({lat:loc.lat,lng:loc.lon});
|
}
|
||||||
marker.setPosition({lat:loc.lat,lng:loc.lon});
|
|
||||||
|
// Update street view
|
||||||
|
if (disp!=='0' && mapuser.settings.showStreetview) {
|
||||||
updateStreetView(loc,10);
|
updateStreetView(loc,10);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("🌐️ Got location:",loc.lat+", "+loc.lon);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check altitude
|
// Check altitude
|
||||||
|
|
|
@ -8,7 +8,7 @@ function validateEmail(email) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace inputed value with response
|
// Replace inputed value with response
|
||||||
function validateFromEndpoint(type, selector, cb) {
|
function replaceFromEndpoint(type, selector, cb) {
|
||||||
$.get('/validate?'+type+'='+$(selector).val())
|
$.get('/validate?'+type+'='+$(selector).val())
|
||||||
.done(function(data){
|
.done(function(data){
|
||||||
$(selector).val(data);
|
$(selector).val(data);
|
||||||
|
@ -18,98 +18,17 @@ function validateFromEndpoint(type, selector, cb) {
|
||||||
|
|
||||||
// On page load
|
// On page load
|
||||||
$(function(){
|
$(function(){
|
||||||
|
var slugNotUnique, emailNotUnique;
|
||||||
function validateForm(input) {
|
|
||||||
|
|
||||||
// Everything passed - make sure no help texts are visible
|
|
||||||
function recheckInputs() {
|
|
||||||
if ($('#email-help').is(":visible")) {
|
|
||||||
$('#submit-group .main').prop('disabled',true).prop('title',"You need to supply a different email address. ");
|
|
||||||
}
|
|
||||||
else if ($('#slug-help').is(":visible")) {
|
|
||||||
$('#submit-group .main').prop('disabled',true).prop('title',"You need to supply a different slug. ");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$('#submit-group .main').prop('disabled',false).prop('title',"Click here to save your changes. ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty fields
|
|
||||||
if ($('#slug-input').val()===''){
|
|
||||||
$('#slug-help').show().text("A slug is required. ");
|
|
||||||
$('#submit-group .main').prop('disabled',true).prop('title',"You need to enter a slug. ");
|
|
||||||
}
|
|
||||||
else if ($('#email-input').val()===''){
|
|
||||||
$('#email-help').show().text("An email is required. ");
|
|
||||||
$('#submit-group .main').prop('disabled',true).prop('title',"You need to enter an email address. ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is email
|
|
||||||
else if (!validateEmail($('#email-input').val())) {
|
|
||||||
$('#email-help').show().text("You must enter a valid email address. ");
|
|
||||||
$('#submit-group .main').prop('disabled',true).prop('title',"You need to enter a valid email address. ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate unique fields with server
|
|
||||||
else if (input) {
|
|
||||||
|
|
||||||
// Make AJAX request
|
|
||||||
$.get('/validate?'+input+'='+$('#'+input+'-input').val())
|
|
||||||
.fail(function(data,status){
|
|
||||||
|
|
||||||
console.log(typeof status);
|
|
||||||
console.log(status);
|
|
||||||
|
|
||||||
// Input is not unique
|
|
||||||
if (status===400) {
|
|
||||||
$('#'+input+'-help').show().text("That "+input+" is already in use by another user. ");
|
|
||||||
$('#submit-group .main').prop('disabled',true).prop('title',"You need to supply a different "+input+". ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server error
|
|
||||||
else {
|
|
||||||
$('#'+input+'-help').show().text("Unable to confirm unique "+input+" with the server. This might not work... ");
|
|
||||||
recheckInputs();
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// Input is unique
|
|
||||||
.done(function(){
|
|
||||||
$('#'+input+'-help').hide();
|
|
||||||
recheckInputs();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// All passed
|
|
||||||
else { recheckInputs(); }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate slug
|
|
||||||
$('#slug-input').change(function(){
|
|
||||||
validateFromEndpoint('slugify','#slug-input',function(){
|
|
||||||
validateForm(slug);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Validate email
|
|
||||||
$('#email-input').change(function(){
|
|
||||||
validateForm('email');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Validate name
|
|
||||||
$('#name-input').change(function(){
|
|
||||||
validateFromEndpoint('xss','#name-input',validateForm);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Set timezone in password change link
|
||||||
|
$('#password').attr('href',"/settings/password?tz="+new Date().getTimezoneOffset());
|
||||||
|
|
||||||
// Delete account
|
// Delete account
|
||||||
$('#delete').click(function(){
|
$('#delete').click(function(){
|
||||||
if (confirm("Are you sure you want to delete your account? This CANNOT be undone! ")) {
|
if (confirm("Are you sure you want to delete your account? This CANNOT be undone! ")) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "/settings",
|
url: '/settings',
|
||||||
type: "DELETE",
|
type: 'DELETE',
|
||||||
success: function(){
|
success: function(){
|
||||||
location.reload();
|
location.reload();
|
||||||
},
|
},
|
||||||
|
@ -120,4 +39,126 @@ $(function(){
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function validateForm(input) {
|
||||||
|
|
||||||
|
// Perform basic check, then validate uniqueness
|
||||||
|
basicCheck(function(){ validateUniqueness(input); });
|
||||||
|
|
||||||
|
function basicCheck(cb){
|
||||||
|
var checkedCount = 0;
|
||||||
|
|
||||||
|
// Check slug
|
||||||
|
if (!$('#slug-input').val()){
|
||||||
|
$('#slug-help').show().text("A slug is required. ");
|
||||||
|
$('#submit-group .main').prop('disabled',true).prop('title',"You need to enter a slug. ");
|
||||||
|
if (checkedCount>0) {cb();} else {checkedCount++;}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!slugNotUnique){ $('#slug-help').hide(); }
|
||||||
|
if (checkedCount>0) {cb();} else {checkedCount++;}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check email
|
||||||
|
if (!$('#email-input').val()){
|
||||||
|
$('#email-help').show().text("An email is required. ");
|
||||||
|
$('#submit-group .main').prop('disabled',true).prop('title',"You need to enter an email address. ");
|
||||||
|
if (checkedCount>0) {cb();} else {checkedCount++;}
|
||||||
|
}
|
||||||
|
else if (!validateEmail($('#email-input').val())) {
|
||||||
|
$('#email-help').show().text("You must enter a valid email address. ");
|
||||||
|
$('#submit-group .main').prop('disabled',true).prop('title',"You need to enter a valid email address. ");
|
||||||
|
if (checkedCount>0) {cb();} else {checkedCount++;}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!emailNotUnique){ $('#email-help').hide(); }
|
||||||
|
if (checkedCount>0) {cb();} else {checkedCount++;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateUniqueness(input){
|
||||||
|
|
||||||
|
function recheckBasic(){
|
||||||
|
if ($('#email-help').is(":visible") && $('#email-help').text().substring(0,25)!=="Unable to confirm unique ") {
|
||||||
|
$('#submit-group .main').prop('disabled',true).prop('title',"You need to supply a different email address. ");
|
||||||
|
}
|
||||||
|
else if ($('#slug-help').is(":visible") && $('#slug-help').text().substring(0,25)!=="Unable to confirm unique ") {
|
||||||
|
$('#submit-group .main').prop('disabled',true).prop('title',"You need to supply a different slug. ");
|
||||||
|
}
|
||||||
|
else if ( $('#slug-help').text().substring(0,25)==="Unable to confirm unique " ) {
|
||||||
|
$('#submit-group .main').prop('title',"Unable to confirm unique slug with the server. This might not work... ");
|
||||||
|
}
|
||||||
|
else if ( $('#email-help').text().substring(0,25)==="Unable to confirm unique " ) {
|
||||||
|
$('#submit-group .main').prop('title',"Unable to confirm unique email with the server. This might not work... ");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#submit-group .main').prop('disabled',false).prop('title',"Click here to save your changes. ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should server be queried for unique values?
|
||||||
|
if (input && $('#'+input+'-input').val()) {
|
||||||
|
if (input==='email' && !validateEmail($('#email-input').val())) {}
|
||||||
|
|
||||||
|
// Query server for unique values
|
||||||
|
else {
|
||||||
|
$.ajax({
|
||||||
|
url: '/validate?'+input+'='+$('#'+input+'-input').val(),
|
||||||
|
type: 'GET',
|
||||||
|
statusCode: {
|
||||||
|
|
||||||
|
// Is unique
|
||||||
|
200: function(){
|
||||||
|
$('#'+input+'-help').hide();
|
||||||
|
if (input==='slug'){ slugNotUnique=false; }
|
||||||
|
else if (input==='email'){ emailNotUnique=false; }
|
||||||
|
recheckBasic();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Isn't unique
|
||||||
|
400: function(){
|
||||||
|
if (input==='slug'){ slugNotUnique=true; }
|
||||||
|
else if (input==='email'){ emailNotUnique=true; }
|
||||||
|
$('#'+input+'-help').show().text("That "+input+" is already in use by another user. ");
|
||||||
|
$('#submit-group .main').prop('disabled',true).prop('title',"You need to supply a different "+input+". ");
|
||||||
|
}
|
||||||
|
|
||||||
|
} })
|
||||||
|
|
||||||
|
// Server error
|
||||||
|
.error( function(){
|
||||||
|
if (input==='slug'){ slugNotUnique=undefined; }
|
||||||
|
else if (input==='email'){ emailNotUnique=undefined; }
|
||||||
|
$('#'+input+'-help').show().text("Unable to confirm unique "+input+". This might not work... ");
|
||||||
|
recheckBasic();
|
||||||
|
});
|
||||||
|
|
||||||
|
} }
|
||||||
|
|
||||||
|
// Nothing changed. Recheck basic validations
|
||||||
|
else { recheckBasic(); }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input change listeners
|
||||||
|
$('#slug-input').change(function(){
|
||||||
|
if (!$('#slug-input').val()){
|
||||||
|
$('#slug-help').show().text("A slug is required. ");
|
||||||
|
$('#submit-group .main').prop('disabled',true).prop('title',"You need to enter a slug. ");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#slug-help').hide();
|
||||||
|
replaceFromEndpoint('slugify','#slug-input',function(){
|
||||||
|
validateForm('slug');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#email-input').change(function(){
|
||||||
|
validateForm('email');
|
||||||
|
});
|
||||||
|
$('#name-input').change(function(){
|
||||||
|
replaceFromEndpoint('xss','#name-input',validateForm);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<section class='container'>
|
<section class='container'>
|
||||||
<h2>❌️ {% if code %}{{code}} {% endif %}{% if message %}{{message}}{% endif %}</h2>
|
{% if message %}<h2>❌️ {{message}}</h2>{% endif %}
|
||||||
{% if stack %}<p>{{stack}}</p>{% else %}
|
{% if stack %}<p>{{stack}}</p>{% else %}
|
||||||
{% if code == '404' %}<p>This page does not exist. Maybe you followed a dead link here. </p>
|
{% if code == '404' %}<p>This page does not exist. Maybe you followed a dead link here. </p>
|
||||||
{% else %}<p>Would you please <a href="https://github.com/Tracman-org/Server/issues/new">report this error</a>? </p>{% endif %}
|
{% else %}<p>Would you please <a href="https://github.com/Tracman-org/Server/issues/new">report this error</a>? </p>{% endif %}
|
||||||
|
|
|
@ -96,7 +96,5 @@
|
||||||
<li>iOS apps can only be built using a mac. </li>
|
<li>iOS apps can only be built using a mac. </li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<a href="/map" class='btn' style="width:60%; position:relative; left:20%; background:#333">Go to map <i class='fa fa-angle-right'></i></a>
|
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -66,7 +66,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<button id='set-loc' class='btn set' title="Click here to set your location">Set</button>
|
<button id='set-loc' class='btn set' title="Click here to set your location">Set</button>
|
||||||
<button id='track-loc' class='btn track' title="Click here to track your location"><i class='fa fa-crosshairs'></i> Track</button>
|
<button id='track-loc' class='btn track' title="Click here to track your location"><i class='fa fa-crosshairs'></i>Track</button>
|
||||||
<button id='clear-loc' class='btn clear' title="Click here to clear your location">Clear</button>
|
<button id='clear-loc' class='btn clear' title="Click here to clear your location">Clear</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,14 +4,12 @@
|
||||||
<br>Design by <a href="http://boag.online/blog/maglev-free-responsive-website-template">Fraser Boag</a>. </p>
|
<br>Design by <a href="http://boag.online/blog/maglev-free-responsive-website-template">Fraser Boag</a>. </p>
|
||||||
</div>
|
</div>
|
||||||
<div class='right'>
|
<div class='right'>
|
||||||
Share:
|
Contribute:
|
||||||
<a href="https://www.facebook.com/sharer/sharer.php?u=https://tracman.org/" target="_blank"><i class="fa fa-facebook"></i></a>
|
|
||||||
<a href="https://twitter.com/home?status=Show%20your%20location%20to%20friends%20in%20realtime%20at%20https://tracman.org/" target="_blank"><i class="fa fa-twitter"></i></a>
|
|
||||||
<a href="https://www.reddit.com/submit?title=Show%20your%20location%20to%20friends%20in%20realtime%20at%20&url=https://tracman.org/" target="_blank"><i class="fa fa-reddit-alien"></i></a>
|
|
||||||
<br>Contribute:
|
|
||||||
<a href="https://github.com/Tracman-org/Server"><i class="fa fa-github"></i></a>
|
<a href="https://github.com/Tracman-org/Server"><i class="fa fa-github"></i></a>
|
||||||
<a href="https://cash.me/$KeithIrwin"><i class="fa fa-dollar"></i></a>
|
<a href="https://cash.me/$KeithIrwin"><i class="fa fa-dollar"></i></a>
|
||||||
<a href="bitcoin:14VN8GzWQPssWQherCE5XNGBWzy3eCDn74?label=tracman"><i class="fa fa-btc"></i></a>
|
<a href="bitcoin:14VN8GzWQPssWQherCE5XNGBWzy3eCDn74?label=tracman"><i class="fa fa-btc"></i></a>
|
||||||
|
<br>
|
||||||
|
<a href="/privacy">Privacy Policy</a> ▪️ <a href="/terms">Terms of Service</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,12 @@
|
||||||
<li><a href="/map">Map</a></li>
|
<li><a href="/map">Map</a></li>
|
||||||
<li><a href="/settings">Settings</a></li>
|
<li><a href="/settings">Settings</a></li>
|
||||||
{% if user.isAdmin %}<li><a href="/admin">Admin</a></li>{% endif %}
|
{% if user.isAdmin %}<li><a href="/admin">Admin</a></li>{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<li><a href="/">About</a></li>
|
||||||
<li><a href="/help">Help</a></li>
|
<li><a href="/help">Help</a></li>
|
||||||
|
{% if user %}
|
||||||
<li><a href="/logout">Logout</a></li>
|
<li><a href="/logout">Logout</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="/">About</a></li>
|
|
||||||
<li><a href="/map/keith">Demo</a></li>
|
<li><a href="/map/keith">Demo</a></li>
|
||||||
<li><a href="/login#login">Login</a></li>
|
<li><a href="/login#login">Login</a></li>
|
||||||
<li><a href="/login#signup">Join</a></li>
|
<li><a href="/login#signup">Join</a></li>
|
||||||
|
|
Loading…
Reference in New Issue