Compare commits
2 Commits
6238cdd6db
...
3ba43bc8b8
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3ba43bc8b8 | ||
![]() |
7aaa2cd1bf |
@ -25,6 +25,8 @@ const App = () => {
|
||||
const [ user, setUser ] = useState(null);
|
||||
const [ csrfToken, setCsrfToken ] = useState(undefined);
|
||||
const [ loading, setLoading ] = useState(true);
|
||||
const [ googleApiKey, setGoogleApiKey ] = useState(undefined);
|
||||
const [ error, setError] = useState(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const effect = async () => {
|
||||
@ -70,11 +72,44 @@ const App = () => {
|
||||
effect();
|
||||
}, [csrfToken, setUser]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!csrfToken || googleApiKey) {
|
||||
return;
|
||||
}
|
||||
const effect = async () => {
|
||||
const res = await window.fetch(
|
||||
`${base}/api/v1/locations/google-api-key`, {
|
||||
method: 'GET',
|
||||
cache: 'no-cache',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'CSRF-Token': csrfToken
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.status >= 400) {
|
||||
setError(data.message ? data.message : res.statusText);
|
||||
return;
|
||||
}
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
setGoogleApiKey(data.key);
|
||||
}
|
||||
effect();
|
||||
}, [csrfToken, googleApiKey, setGoogleApiKey]);
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<GlobalContext.Provider value={{user, setUser, csrfToken }}>
|
||||
<GlobalContext.Provider value={{
|
||||
user, setUser, csrfToken, googleApiKey,
|
||||
setError
|
||||
}}>
|
||||
<GoodTimesBar />
|
||||
<Container className="Content">
|
||||
{ error && <Paper style={{padding: "0.5rem"}}>{error}</Paper> }
|
||||
<Routes>
|
||||
<Route path="/signin" element={<SignIn />}/>
|
||||
<Route path="/signup" element={<SignUp />}/>
|
||||
@ -82,7 +117,7 @@ const App = () => {
|
||||
<Route path="/password" element={
|
||||
<Paper>Not implemented... yet.</Paper>
|
||||
}/>
|
||||
<Route path="/:group" element={<Group />}/>
|
||||
<Route path="/:group/*" element={<Group />}/>
|
||||
{ user && user.mailVerified &&
|
||||
<Route path="/" element={<Dashboard />}/>
|
||||
}
|
||||
|
@ -47,36 +47,81 @@ function Dashboard() {
|
||||
effect();
|
||||
}, [user, setGroups, csrfToken ]);
|
||||
|
||||
const upcomingEvents = groups
|
||||
.filter(group => group.nextEvent.date > Date.now());
|
||||
|
||||
return (
|
||||
<Paper className="Dashboard">
|
||||
<div className="Dashboard"
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
textAlign: 'left'
|
||||
}}>
|
||||
<GlobalContext.Provider value={{user, setUser}}>
|
||||
<Paper style={{
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
}}>
|
||||
{upcomingEvents && upcomingEvents.length &&
|
||||
<Paper style={{
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
marginTop: '0.5rem'
|
||||
}}>
|
||||
<div style={{ fontWeight: 'bold', padding: '0.5rem' }}>
|
||||
Upcoming events
|
||||
</div>
|
||||
|
||||
{ groups.map((group) => {
|
||||
return <Button key={group.id}
|
||||
onClick={() => navigate(`/${group.group}`)}
|
||||
style={{
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
padding: '0.5rem'
|
||||
}
|
||||
}>
|
||||
<div key={group.id} style={{
|
||||
fontWeight: 'bold'
|
||||
}}>{group.name}</div>
|
||||
{ group.nextEvent &&
|
||||
<div>Next event <Moment fromNow date={group.nextEvent} /> on <Moment format={'MMMM Do YYYY, h: mm: ss a'} date={group.nextEvent}/>.</div>
|
||||
}
|
||||
</Button>;
|
||||
}) }
|
||||
</Paper>
|
||||
{ upcomingEvents
|
||||
.map((group) => {
|
||||
return <Button key={group.id}
|
||||
onClick={() => navigate(`/${group.group}/${group.nextEvent.event}`)}
|
||||
style={{
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
padding: '0 0.5rem 0.5rem 0.5rem'
|
||||
}
|
||||
}>
|
||||
<div key={group.id} style={{
|
||||
fontWeight: 'bold'
|
||||
}}>{group.name}</div>
|
||||
{ group.nextEvent.date &&
|
||||
<div>Next event <Moment fromNow date={group.nextEvent.date} /> on <Moment format={'MMMM Do YYYY, h: mm: ss a'} date={group.nextEvent.date}/>.</div>
|
||||
}
|
||||
</Button>;
|
||||
})
|
||||
}
|
||||
</Paper>
|
||||
}
|
||||
{ groups && groups.length &&
|
||||
<Paper style={{
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
marginTop: '0.5rem'
|
||||
}}>
|
||||
<div style={{ fontWeight: 'bold', padding: '0.5rem' }}>
|
||||
Groups
|
||||
</div>
|
||||
{ groups
|
||||
.map((group) => {
|
||||
return <Button key={group.id}
|
||||
onClick={() => navigate(`/${group.group}`)}
|
||||
style={{
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
padding: '0 0.5rem 0.5rem 0.5rem'
|
||||
}
|
||||
}>
|
||||
<div key={group.id} style={{
|
||||
fontWeight: 'bold'
|
||||
}}>{group.name}</div>
|
||||
</Button>;
|
||||
})
|
||||
}
|
||||
</Paper>
|
||||
}
|
||||
</GlobalContext.Provider>
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
4
client/src/Event.css
Normal file
4
client/src/Event.css
Normal file
@ -0,0 +1,4 @@
|
||||
.Event {
|
||||
text-align: left;
|
||||
}
|
||||
|
109
client/src/Event.js
Normal file
109
client/src/Event.js
Normal file
@ -0,0 +1,109 @@
|
||||
import React, { useState, useEffect, useContext } from "react";
|
||||
import Paper from '@mui/material/Paper';
|
||||
import {
|
||||
useParams,
|
||||
useNavigate
|
||||
} from "react-router-dom";
|
||||
|
||||
import './Event.css';
|
||||
import { GlobalContext } from "./GlobalContext.js";
|
||||
import { base } from "./Common.js";
|
||||
|
||||
function Event({ groupId }) {
|
||||
const eventId = useParams().event;
|
||||
const { csrfToken, setError } = useContext(GlobalContext);
|
||||
const [ event, setEvent ] = useState(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (!csrfToken || event || !eventId || !groupId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const effect = async () => {
|
||||
const res = await window.fetch(
|
||||
`${base}/api/v1/events/${eventId}?groupId=${groupId}`, {
|
||||
method: 'GET',
|
||||
cache: 'no-cache',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'CSRF-Token': csrfToken
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.status >= 400) {
|
||||
setError(data.message ? data.message : res.statusText);
|
||||
return;
|
||||
}
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
setEvent(data[0]);
|
||||
}
|
||||
effect();
|
||||
}, [eventId, csrfToken, event, setError]);
|
||||
|
||||
const showEvent = (event) => {
|
||||
const fields = Object.getOwnPropertyNames(event)
|
||||
.filter(field => event[field])
|
||||
.map(field => {
|
||||
return <div key={field}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
width: '100%'
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignSelf: 'flex-start',
|
||||
fontWeight: 'bold',
|
||||
minWidth: '8rem'
|
||||
}}>{field}</div>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignSelf: 'flex-start',
|
||||
textAlign: 'left'
|
||||
}}>
|
||||
{ typeof event[field] === 'string'
|
||||
&& event[field].match(/^http.*/)
|
||||
&& <a href={event[field]}>{field.toUpperCase()}</a> }
|
||||
{ typeof event[field] === 'string'
|
||||
&& !event[field].match(/^http.*/)
|
||||
&& event[field] }
|
||||
{ typeof event[field] === 'number' && event[field]}
|
||||
</div>
|
||||
</div>
|
||||
} );
|
||||
return <div key={event.id} style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
}}>{fields}</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
if (!event) {
|
||||
return <>No event</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper className="Event" style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '0.5rem',
|
||||
marginTop: '0.25rem',
|
||||
marginBottom: '0.25rem'
|
||||
}}>
|
||||
{ showEvent(event) }
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
export { Event };
|
@ -2,20 +2,21 @@ import React, { useState, useEffect, useContext } from "react";
|
||||
import Paper from '@mui/material/Paper';
|
||||
import {
|
||||
useParams,
|
||||
useNavigate
|
||||
Routes,
|
||||
Route
|
||||
} from "react-router-dom";
|
||||
|
||||
import './Group.css';
|
||||
import { GlobalContext } from "./GlobalContext.js";
|
||||
import { base } from "./Common.js";
|
||||
import { Location } from "./Location.js";
|
||||
import { Event } from "./Event.js";
|
||||
|
||||
function Group() {
|
||||
const { csrfToken, user } = useContext(GlobalContext);
|
||||
const { csrfToken, user, setError } = useContext(GlobalContext);
|
||||
const groupId = useParams().group;
|
||||
const [ group, setGroup ] = useState(undefined);
|
||||
const [ events, setEvents ] = useState(null);
|
||||
const [ error, setError ] = useState(null);
|
||||
const [ locations, setLocations ] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -45,7 +46,7 @@ function Group() {
|
||||
setLocations(data);
|
||||
};
|
||||
effect();
|
||||
}, [user, setGroup, groupId, csrfToken]);
|
||||
}, [user, setGroup, groupId, csrfToken, setError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user || !groupId || !csrfToken) {
|
||||
@ -74,7 +75,7 @@ function Group() {
|
||||
setEvents(data);
|
||||
};
|
||||
effect();
|
||||
}, [user, setEvents, groupId, csrfToken]);
|
||||
}, [user, setEvents, groupId, csrfToken, setError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user || !groupId || !csrfToken) {
|
||||
@ -103,7 +104,7 @@ function Group() {
|
||||
setGroup(data[0]);
|
||||
};
|
||||
effect();
|
||||
}, [user, setGroup, groupId, csrfToken]);
|
||||
}, [user, setGroup, groupId, csrfToken, setError]);
|
||||
|
||||
return (
|
||||
<Paper className="Group" style={{
|
||||
@ -111,13 +112,15 @@ function Group() {
|
||||
flexDirection: 'column',
|
||||
textAlign: 'left',
|
||||
}}>
|
||||
{ error && <div>{error}</div>}
|
||||
{ !error && group && <>
|
||||
<div style={{fontWeight: 'bold'}}>{group.name}</div>
|
||||
<div>Locations</div>
|
||||
{ locations.map(location =>
|
||||
<Location location={location} key={location.id}/>) }
|
||||
</> }
|
||||
<Routes>
|
||||
<Route path="/:event" element={<Event groupId={groupId}/>} />
|
||||
<Route path="/" element={ group && <>
|
||||
<div style={{fontWeight: 'bold'}}>{group.name}</div>
|
||||
<div>Locations</div>
|
||||
{ locations.map(location =>
|
||||
<Location location={location} key={location.id}/>) }
|
||||
</> }/>
|
||||
</Routes>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
@ -11,42 +11,12 @@ import { base } from "./Common.js";
|
||||
|
||||
function Location(props) {
|
||||
const propLocation = props.location;
|
||||
const { csrfToken } = useContext(GlobalContext);
|
||||
const [googleApiKey, setGoogleApiKey] = useState(undefined);
|
||||
const { csrfToken, googleApiKey, setError } = useContext(GlobalContext);
|
||||
const [ location, setLocation ] = useState(
|
||||
typeof propLocation === 'object' ? propLocation : undefined);
|
||||
const [ error, setError ] = useState(null);
|
||||
const locationId =
|
||||
typeof propLocation === 'number' ? propLocation : undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (!csrfToken || googleApiKey) {
|
||||
return;
|
||||
}
|
||||
const effect = async () => {
|
||||
const res = await window.fetch(
|
||||
`${base}/api/v1/locations/google-api-key`, {
|
||||
method: 'GET',
|
||||
cache: 'no-cache',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'CSRF-Token': csrfToken
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.status >= 400) {
|
||||
setError(data.message ? data.message : res.statusText);
|
||||
return;
|
||||
}
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
setGoogleApiKey(data.key);
|
||||
}
|
||||
effect();
|
||||
}, [csrfToken, googleApiKey, setGoogleApiKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!csrfToken || location || !locationId) {
|
||||
return;
|
||||
@ -74,9 +44,9 @@ function Location(props) {
|
||||
setLocation(data);
|
||||
}
|
||||
effect();
|
||||
}, [locationId, csrfToken, location]);
|
||||
}, [locationId, csrfToken, location, setError]);
|
||||
|
||||
const createLocation = (location) => {
|
||||
const showLocation = (location) => {
|
||||
const fields = Object.getOwnPropertyNames(location)
|
||||
.filter(field => location[field])
|
||||
.map(field => {
|
||||
@ -139,10 +109,7 @@ function Location(props) {
|
||||
marginTop: '0.25rem',
|
||||
marginBottom: '0.25rem'
|
||||
}}>
|
||||
{ error && <div>{error}</div>}
|
||||
{ !error && <>
|
||||
{ createLocation(location) }
|
||||
</> }
|
||||
{ showLocation(location) }
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
8
server/event-data.js
Normal file
8
server/event-data.js
Normal file
@ -0,0 +1,8 @@
|
||||
const originalEvents = [ {
|
||||
groupId: 1,
|
||||
name: 'Tuesday',
|
||||
event: 'tuesday',
|
||||
date: Date.now() + 86400 * 14 * 1000 /* 2 weeks from now */
|
||||
} ];
|
||||
originalEvents.forEach((item, index) => item.id = index + 1);
|
||||
module.exports = originalEvents;
|
12
server/group-data.js
Normal file
12
server/group-data.js
Normal file
@ -0,0 +1,12 @@
|
||||
const originalEvents = require('./event-data.js');
|
||||
const originalGroups = [ {
|
||||
ownerId: 1,
|
||||
name: 'Beer Tuesday',
|
||||
group: 'beer-tuesday',
|
||||
} ];
|
||||
|
||||
originalGroups[0].nextEvent = originalEvents[0];
|
||||
originalEvents.forEach(item => item.groupId = 1);
|
||||
originalGroups.forEach((item, index) => item.id = index + 1);
|
||||
|
||||
module.exports = originalGroups;
|
@ -1,4 +1,4 @@
|
||||
module.exports = [{'id':1,'name':'CPR','location':'Cornelius Pass & Imbrie Dr ','map':'http://goo.gl/maps/ANsh2','url':'','note':'Cornelius Pass Roadhouse; standard McMenamin food and drinks. The Rubinator (Ruby + Terminator) is a common favorite, as are the rotating nitro offerings.\n\nHappy hour foods aren\'t that thrilling; tots, fries, etc.','disabled':0,'beerlist':''},
|
||||
const originalLocations = [{'id':1,'name':'CPR','location':'Cornelius Pass & Imbrie Dr ','map':'http://goo.gl/maps/ANsh2','url':'','note':'Cornelius Pass Roadhouse; standard McMenamin food and drinks. The Rubinator (Ruby + Terminator) is a common favorite, as are the rotating nitro offerings.\n\nHappy hour foods aren\'t that thrilling; tots, fries, etc.','disabled':0,'beerlist':''},
|
||||
{'id':2,'name':'Orenco Taphouse','location':'Couple blocks off Cornell in Orenco Station area.','map':'http://goo.gl/maps/qoOxl','url':'http://orencotaphouse.com','note':'A wide variety of frequently changing taps; a good place to have a beer or two. \n\nNo food or appetizers, or happy hour pricing. If its crowded, or the TVs are all on, it can be a little loud...','disabled':0,'beerlist':'http://orencotaphouse.com/?page_id=5'},
|
||||
{'id':3,'name':'Dugout','location':'Just down the street from Jones Farm, across Cornell in the strip mall near the Dollar Store','map':'https://goo.gl/HXc5oq','url':'https://www.facebook.com/TheDugoutGourmetDeliBeerBarInc','note':'','disabled':0,'beerlist':''},
|
||||
{'id':4,'name':'Raccoon Lodge','location':'Beaverton Hillsdale Hwy a couple miles east of 217.','map':'http://goo.gl/maps/fbFAa','url':'http://www.raclodge.com','note':'OMG! The fruit beers! This is the west-side spot to drink Cascade Brewing\'s various beers. In addition to the sours, they have a wide variety of other beer types.\n\nHappy hour prices aren\'t that great, and only apply to the "standard" Cascade Brewing beers--not the sours :(','disabled':0,'beerlist':'http://www.raclodge.com/PDFs/cascade_brewing_ales_new.do.do.do.doc_oct_20.3.1.docuse.doc.docx'},
|
||||
@ -30,3 +30,7 @@ module.exports = [{'id':1,'name':'CPR','location':'Cornelius Pass & Imbrie Dr ',
|
||||
{'id':31,'name':'Iron Tap Station','location':'Across Hall Blvd from Washington Square, in a strip mall just a few doors down from Kitchen Kaboodle and Lamps Plus.','map':'https://goo.gl/SDWgWB','url':'http://www.irontapstation.com/','note':'','disabled':0,'beerlist':'http://www.irontapstation.com/#menu-section'},
|
||||
{'id':32,'name':'Craft Pour House','location':'16055 SW Regatta Ln #700\nBeaverton, OR 97006\n\nNext door to where Monteaux\'s used to be.','map':'https://goo.gl/maps/VJpMpFS5RPC2','url':'http://craftpourhouse.com/','note':'They\'re new!','disabled':0,'beerlist':'http://craftpourhouse.com/'},
|
||||
{'id':33,'name':'Vertigo Brewing','location':'Located in the back of an industrial complex, this brewer is not to be missed!\n\n21420 NW Nicholas Ct.\nSuite D-6 & D-7\nHillsboro, OR 97124','map':'https://goo.gl/ABTbnD','url':'http://www.vertigobrew.com/','note':'','disabled':0,'beerlist':'http://www.vertigobrew.com/the-beer/'}];
|
||||
|
||||
originalLocations.forEach((item, index) => item.id = index + 1);
|
||||
|
||||
module.exports = originalLocations;
|
@ -1,337 +1,84 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express'),
|
||||
{ sendVerifyMail, sendPasswordChangedMail, sendVerifiedMail } =
|
||||
require('../lib/mail'),
|
||||
crypto = require('crypto');
|
||||
|
||||
const config = require('config');
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const autoAuthenticate = 1;
|
||||
|
||||
router.get('/', (req, res/*, next*/) => {
|
||||
console.log('GET /users/');
|
||||
const originalEvents = require('../event-data.js');
|
||||
const originalGroups = require('../group-data.js');
|
||||
|
||||
return getSessionUser(req).then((user) => {
|
||||
if (typeof user === 'string') {
|
||||
return res.status(403).send({ message: user });
|
||||
}
|
||||
return res.status(200).send(user);
|
||||
router.put('/', (req, res) => {
|
||||
const event = req.body;
|
||||
event.id = originalEvents.length;
|
||||
originalEvents.push(event);
|
||||
res.status(200).send([event]);
|
||||
});
|
||||
|
||||
router.get('/google-api-key', (req, res) => {
|
||||
return res.status(200).send({ key: config.get('googleApi') });
|
||||
});
|
||||
|
||||
router.get('/:event?', (req, res) => {
|
||||
const { event } = req.params;
|
||||
const { groupId } = req.query;
|
||||
|
||||
if (!event) {
|
||||
return res.status(200).send(originalEvents);
|
||||
}
|
||||
|
||||
const group = originalGroups.find(item => {
|
||||
return (item.groupId === groupId || item.group === groupId);
|
||||
});
|
||||
});
|
||||
|
||||
router.put('/password', async (req, res) => {
|
||||
console.log('/users/password');
|
||||
const db = req.app.locals.db;
|
||||
|
||||
const changes = {
|
||||
currentPassword: req.query.c || req.body.c,
|
||||
newPassword: req.query.n || req.body.n
|
||||
};
|
||||
|
||||
if (!changes.currentPassword || !changes.newPassword) {
|
||||
return res.status(400).send(
|
||||
'Missing current password and/or new password.');
|
||||
if (!group) {
|
||||
return res.status(500).send({ message: `Invalid group-event link.` });
|
||||
}
|
||||
|
||||
if (changes.currentPassword == changes.newPassword) {
|
||||
return res.status(400).send(
|
||||
'Attempt to set new password to current password.');
|
||||
}
|
||||
|
||||
const user = await getSessionUser(req);
|
||||
if (typeof user === 'string') {
|
||||
return res.status(403).send({ message: user });
|
||||
}
|
||||
return db.sequelize.query('SELECT id FROM users ' +
|
||||
'WHERE uid=:username AND password=:password', {
|
||||
replacements: {
|
||||
username: user.username,
|
||||
password: crypto.createHash('sha256').update(changes.currentPassword).digest('base64')
|
||||
},
|
||||
type: db.Sequelize.QueryTypes.SELECT,
|
||||
raw: true
|
||||
}).then(function(users) {
|
||||
if (users.length != 1) {
|
||||
return null;
|
||||
const found = originalEvents.find((item) => {
|
||||
if (item.groupId !== group.id) {
|
||||
return false;
|
||||
}
|
||||
return user;
|
||||
}).then(function(user) {
|
||||
if (!user) {
|
||||
console.log('Invalid password');
|
||||
/* Invalid password */
|
||||
res.status(401).send('Invalid password');
|
||||
return null;
|
||||
if (typeof event === 'number') {
|
||||
return item.id === event;
|
||||
}
|
||||
|
||||
return db.sequelize.query('UPDATE users SET password=:password WHERE uid=:username', {
|
||||
replacements: {
|
||||
username: user.username,
|
||||
password: crypto.createHash('sha256').update(changes.newPassword).digest('base64')
|
||||
}
|
||||
}).then(function() {
|
||||
console.log('Password changed for user ' + user.username + ' to \'' + changes.newPassword + '\'.');
|
||||
|
||||
res.status(200).send(user);
|
||||
user.id = req.session.userId;
|
||||
return sendPasswordChangedMail(db, req, user);
|
||||
});
|
||||
if (typeof event === 'string') {
|
||||
return item.event === event;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/csrf', (req, res) => {
|
||||
const token = req.csrfToken();
|
||||
console.log('/users/csrf', token);
|
||||
res.json({ csrfToken: token });
|
||||
});
|
||||
|
||||
router.post('/signup', function(req, res) {
|
||||
console.log('/users/signup');
|
||||
const db = req.app.locals.db;
|
||||
|
||||
const user = {
|
||||
uid: req.body.email,
|
||||
familyName: req.body.familyName,
|
||||
firstName: req.body.firstName,
|
||||
password: req.body.password,
|
||||
email: req.body.email,
|
||||
};
|
||||
|
||||
if (!user.uid
|
||||
|| !user.email
|
||||
|| !user.password
|
||||
|| !user.familyName
|
||||
|| !user.firstName) {
|
||||
return res.status(400).send({
|
||||
message: 'Missing email address, password, and/or name.'
|
||||
if (!found) {
|
||||
return res.status(404).send({
|
||||
message: `Unable to find ${groupId}:${event}.`
|
||||
});
|
||||
}
|
||||
|
||||
user.password = crypto.createHash('sha256')
|
||||
.update(user.password).digest('base64');
|
||||
user.md5 = crypto.createHash('md5')
|
||||
.update(user.email).digest('base64');
|
||||
|
||||
return db.sequelize.query('SELECT * FROM users WHERE uid=:uid', {
|
||||
replacements: user,
|
||||
type: db.Sequelize.QueryTypes.SELECT,
|
||||
raw: true
|
||||
}).then(async function(results) {
|
||||
if (results.length != 0 && results[0].mailVerified) {
|
||||
return res.status(400).send({
|
||||
message: 'Email address already used.'
|
||||
});
|
||||
}
|
||||
|
||||
let re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
if (!re.exec(user.email)) {
|
||||
const error = `Invalid email address: ${user.email}.`;
|
||||
console.log(error);
|
||||
return res.status(401).send({
|
||||
message: error
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
if (results.length != 0) {
|
||||
await db.sequelize.query('UPDATE users ' +
|
||||
'SET mailVerified=0');
|
||||
req.session.userId = results[0].id;
|
||||
} else {
|
||||
let [, metadata] = await db.sequelize.query('INSERT INTO users ' +
|
||||
'(uid,firstName,familyName,password,email,memberSince,' +
|
||||
'authenticated,md5) ' +
|
||||
'VALUES(:uid,:firstName,:familyName,:password,' +
|
||||
`:email,CURRENT_TIMESTAMP,${autoAuthenticate},:md5)`, {
|
||||
replacements: user
|
||||
});
|
||||
req.session.userId = metadata.lastID;
|
||||
}
|
||||
return getSessionUser(req).then(function(user) {
|
||||
if (typeof user === 'string') {
|
||||
return res.status(403).send({ message: user });
|
||||
}
|
||||
res.status(200).send(user);
|
||||
user.id = req.session.userId;
|
||||
return sendVerifyMail(db, req, user);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
return res.status(200).send([found]);
|
||||
});
|
||||
|
||||
const getSessionUser = async (req) => {
|
||||
const db = req.app.locals.db;
|
||||
|
||||
console.log(req.session);
|
||||
if (!req.session || !req.session.userId) {
|
||||
return 'Unauthorized. You must be logged in.';
|
||||
router.post('/:eventId', (req, res) => {
|
||||
const { eventId } = req.params;
|
||||
if (!eventId) {
|
||||
return res.status(400).send({ message: `Invalid event.` });
|
||||
}
|
||||
|
||||
let query = 'SELECT ' +
|
||||
'uid AS username,firstName,familyName,mailVerified,authenticated,memberSince,email,md5 ' +
|
||||
'FROM users WHERE id=:id';
|
||||
const results = await db.sequelize.query(query, {
|
||||
replacements: {
|
||||
id: req.session.userId
|
||||
},
|
||||
type: db.Sequelize.QueryTypes.SELECT,
|
||||
raw: true
|
||||
});
|
||||
|
||||
if (results.length != 1) {
|
||||
return 'Invalid account.';
|
||||
}
|
||||
|
||||
const user = results[0];
|
||||
|
||||
if (!user.mailVerified) {
|
||||
user.restriction = user.restriction || 'Email address not verified.';
|
||||
} else if (!user.authenticated) {
|
||||
user.restriction = user.restriction || 'Accout not authorized.';
|
||||
}
|
||||
|
||||
/* Strip out any fields that shouldn't be there.
|
||||
* The allowed fields are: */
|
||||
const allowed = [
|
||||
'maintainer', 'username', 'firstName', 'familyName', 'mailVerified', 'authenticated', 'name', 'email', 'restriction', 'md5'
|
||||
];
|
||||
for (let field in user) {
|
||||
if (allowed.indexOf(field) == -1) {
|
||||
delete user[field];
|
||||
}
|
||||
}
|
||||
return user;
|
||||
};
|
||||
|
||||
router.post('/verify-email', async (req, res) => {
|
||||
console.log('/users/verify-email');
|
||||
const key = req.body.token;
|
||||
const db = req.app.locals.db;
|
||||
|
||||
let results = await db.sequelize.query(
|
||||
'SELECT * FROM authentications WHERE key=:key', {
|
||||
replacements: { key },
|
||||
type: db.sequelize.QueryTypes.SELECT
|
||||
}
|
||||
const event = originalEvents.find(
|
||||
item => item.id === eventId
|
||||
);
|
||||
|
||||
let token;
|
||||
if (results.length == 0) {
|
||||
console.log('Invalid key. Ignoring.');
|
||||
return res.status(400).send({
|
||||
message: 'Invalid authentication token.'
|
||||
});
|
||||
}
|
||||
|
||||
token = results[0];
|
||||
console.log(token);
|
||||
console.log('Matched token: ' + JSON.stringify(token, null, 2));
|
||||
switch (token.type) {
|
||||
case 'account-setup':
|
||||
return db.sequelize.query(
|
||||
'UPDATE users SET mailVerified=1 WHERE id=:userId', {
|
||||
replacements: token
|
||||
}
|
||||
).then(function () {
|
||||
return db.sequelize.query(
|
||||
'DELETE FROM authentications WHERE key=:key', {
|
||||
replacements: { key }
|
||||
}
|
||||
);
|
||||
}).then(function () {
|
||||
return db.sequelize.query(
|
||||
'SELECT * FROM users WHERE id=:userId', {
|
||||
replacements: token,
|
||||
type: db.sequelize.QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
}).then(function (results) {
|
||||
if (results.length == 0) {
|
||||
return res.status(500).send({
|
||||
message: 'Internal authentication error.'
|
||||
});
|
||||
}
|
||||
return results[0];
|
||||
}).then((user) => {
|
||||
sendVerifiedMail(db, req, user);
|
||||
req.session.userId = user.id;
|
||||
}).then(() => {
|
||||
return getSessionUser(req).then((user) => {
|
||||
if (typeof user === 'string') {
|
||||
return res.status(403).send({ message: user });
|
||||
}
|
||||
return res.status(200).send(user);
|
||||
});
|
||||
});
|
||||
if (!event) {
|
||||
res.status(404).send({ message: `Event ${eventId} not found.` });
|
||||
}
|
||||
res.status(200).send([event]);
|
||||
});
|
||||
|
||||
router.post('/signin', (req, res) => {
|
||||
console.log('/users/signin');
|
||||
const db = req.app.locals.db;
|
||||
|
||||
let { email, password } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
return res.status(400).send({
|
||||
message: 'Missing email and/or password'
|
||||
});
|
||||
router.delete('/:eventId', (req, res) => {
|
||||
const { eventId } = req.params;
|
||||
if (!eventId) {
|
||||
return res.status(400).send({ message: `Invalid event.` });
|
||||
}
|
||||
|
||||
console.log('Looking up user in DB.');
|
||||
|
||||
let query = 'SELECT ' +
|
||||
'id,mailVerified,authenticated,' +
|
||||
'uid AS username,' +
|
||||
'familyName,firstName,email ' +
|
||||
'FROM users WHERE uid=:username AND password=:password';
|
||||
return db.sequelize.query(query, {
|
||||
replacements: {
|
||||
username: email,
|
||||
password: crypto.createHash('sha256').update(password).digest('base64')
|
||||
},
|
||||
type: db.Sequelize.QueryTypes.SELECT
|
||||
}).then(function(users) {
|
||||
if (users.length != 1) {
|
||||
return null;
|
||||
}
|
||||
let user = users[0];
|
||||
req.session.userId = user.id;
|
||||
return user;
|
||||
}).then(function(user) {
|
||||
if (!user) {
|
||||
console.log(email + ' not found (or invalid password.)');
|
||||
req.session.userId = null;
|
||||
return res.status(401).send({
|
||||
message: 'Invalid sign in credentials'
|
||||
});
|
||||
}
|
||||
|
||||
let message = 'Logged in as ' + user.email + ' (' + user.id + ')';
|
||||
if (!user.mailVerified) {
|
||||
console.log(message + ', who is not verified email.');
|
||||
} else if (!user.authenticated) {
|
||||
console.log(message + ', who is not authenticated.');
|
||||
} else {
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
return getSessionUser(req).then((user) => {
|
||||
if (typeof user === 'string') {
|
||||
return res.status(403).send({ message: user });
|
||||
}
|
||||
return res.status(200).send(user);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/signout', (req, res) => {
|
||||
console.log('/users/signout');
|
||||
if (req.session && req.session.userId) {
|
||||
req.session.userId = null;
|
||||
const eventIndex = originalEvents.findIndex(
|
||||
item => item.id === eventId
|
||||
);
|
||||
if (eventIndex === -1) {
|
||||
res.status(404).send({ message: `Event ${eventId} not found.` });
|
||||
}
|
||||
res.status(200).send({});
|
||||
originalEvents.splice(eventIndex, 1);
|
||||
res.status(200).send({ message: `Event ${eventId} deleted.` });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
@ -1,13 +1,8 @@
|
||||
const express = require('express'),
|
||||
router = express.Router();
|
||||
|
||||
const originalGroups = [ {
|
||||
id: 1,
|
||||
ownerId: 1,
|
||||
name: 'Beer Tuesday',
|
||||
group: 'beer-tuesday',
|
||||
nextEvent: Date.now() + 86400 * 14 * 1000 /* 2 weeks from now */
|
||||
} ];
|
||||
const originalGroups = require('../group-data.js');
|
||||
const originalEvents = require('../event-data.js');
|
||||
|
||||
router.get('/:group?', async (req, res/*, next*/) => {
|
||||
const { group } = req.params;
|
||||
@ -30,13 +25,7 @@ router.get('/:group?', async (req, res/*, next*/) => {
|
||||
});
|
||||
|
||||
router.get('/:group/events', async (req, res/*, next*/) => {
|
||||
return res.status(200).send(
|
||||
[{
|
||||
id: 1,
|
||||
name: 'Tuesday',
|
||||
date: Date.now() + 86400 * 14 * 1000 /* 2 weeks from now */
|
||||
}]
|
||||
);
|
||||
return res.status(200).send(originalEvents);
|
||||
});
|
||||
|
||||
router.post('/:group', async (req, res) => {
|
||||
|
@ -1,11 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const originalLocations = require('../location-data.js');
|
||||
originalLocations.forEach((item, index) => item.id = index + 1);
|
||||
|
||||
router.put('/', (req, res) => {
|
||||
const location = req.body;
|
||||
|
Loading…
x
Reference in New Issue
Block a user