diff --git a/client/src/App.js b/client/src/App.js
index c4e7ad2..24583d8 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -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 (
-
+
+ { error && {error} }
}/>
}/>
diff --git a/client/src/Dashboard.js b/client/src/Dashboard.js
index c32c34e..57a3608 100644
--- a/client/src/Dashboard.js
+++ b/client/src/Dashboard.js
@@ -47,36 +47,81 @@ function Dashboard() {
effect();
}, [user, setGroups, csrfToken ]);
+ const upcomingEvents = groups
+ .filter(group => group.nextEvent > Date.now());
+
return (
-
+
-
+ {upcomingEvents && upcomingEvents.length &&
+
+
+ Upcoming events
+
- { groups.map((group) => {
- return ;
- }) }
-
+ { upcomingEvents
+ .map((group) => {
+ return ;
+ })
+ }
+
+ }
+ { groups && groups.length &&
+
+
+ Groups
+
+ { groups
+ .map((group) => {
+ return ;
+ })
+ }
+
+ }
-
+
);
}
diff --git a/client/src/Group.js b/client/src/Group.js
index 6184c9e..5a9e97e 100644
--- a/client/src/Group.js
+++ b/client/src/Group.js
@@ -11,11 +11,10 @@ import { base } from "./Common.js";
import { Location } from "./Location.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 +44,7 @@ function Group() {
setLocations(data);
};
effect();
- }, [user, setGroup, groupId, csrfToken]);
+ }, [user, setGroup, groupId, csrfToken, setError]);
useEffect(() => {
if (!user || !groupId || !csrfToken) {
@@ -74,7 +73,7 @@ function Group() {
setEvents(data);
};
effect();
- }, [user, setEvents, groupId, csrfToken]);
+ }, [user, setEvents, groupId, csrfToken, setError]);
useEffect(() => {
if (!user || !groupId || !csrfToken) {
@@ -103,7 +102,7 @@ function Group() {
setGroup(data[0]);
};
effect();
- }, [user, setGroup, groupId, csrfToken]);
+ }, [user, setGroup, groupId, csrfToken, setError]);
return (
- { error && {error}
}
- { !error && group && <>
+ { group && <>
{group.name}
Locations
{ locations.map(location =>
diff --git a/client/src/Location.js b/client/src/Location.js
index 3664307..e62b630 100644
--- a/client/src/Location.js
+++ b/client/src/Location.js
@@ -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;
@@ -139,10 +109,7 @@ function Location(props) {
marginTop: '0.25rem',
marginBottom: '0.25rem'
}}>
- { error && {error}
}
- { !error && <>
- { createLocation(location) }
- > }
+ { createLocation(location) }
);
}
diff --git a/server/event-data.js b/server/event-data.js
new file mode 100644
index 0000000..7cb547f
--- /dev/null
+++ b/server/event-data.js
@@ -0,0 +1,8 @@
+const originalEvents = [ {
+ id: 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;
\ No newline at end of file
diff --git a/server/group-data.js b/server/group-data.js
new file mode 100644
index 0000000..c8f6ba7
--- /dev/null
+++ b/server/group-data.js
@@ -0,0 +1,10 @@
+const originalGroups = [ {
+ ownerId: 1,
+ name: 'Beer Tuesday',
+ group: 'beer-tuesday',
+ nextEvent: Date.now() + 86400 * 14 * 1000, /* 2 weeks from now */
+ nextEventId: 1
+} ];
+originalGroups.forEach((item, index) => item.id = index + 1);
+
+module.exports = originalGroups;
\ No newline at end of file
diff --git a/server/location-data.js b/server/location-data.js
index ad4cedb..b97333c 100644
--- a/server/location-data.js
+++ b/server/location-data.js
@@ -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;
\ No newline at end of file
diff --git a/server/routes/events.js b/server/routes/events.js
index e5e47a0..6248e37 100644
--- a/server/routes/events.js
+++ b/server/routes/events.js
@@ -1,337 +1,64 @@
'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');
- 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.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 (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;
- }
- return user;
- }).then(function(user) {
- if (!user) {
- console.log('Invalid password');
- /* Invalid password */
- res.status(401).send('Invalid password');
- return null;
- }
-
- 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);
- });
- });
+router.get('/google-api-key', (req, res) => {
+ return res.status(200).send({ key: config.get('googleApi') });
});
-router.get('/csrf', (req, res) => {
- const token = req.csrfToken();
- console.log('/users/csrf', token);
- res.json({ csrfToken: token });
+router.get('/:eventId?', (req, res) => {
+ const { eventId } = req.params;
+ if (eventId) {
+ const event = originalEvents.find(
+ item => item.id === eventId
+ );
+ if (!event) {
+ return res.status(404).send({ message: `Event ${eventId} not found.` });
+ }
+ return res.status(200).send([event]);
+ }
+
+ return res.status(200).send(originalEvents);
});
-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.'
- });
+router.post('/:eventId', (req, res) => {
+ const { eventId } = req.params;
+ if (!eventId) {
+ return res.status(400).send({ message: `Invalid 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);
- }
- });
-});
-
-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.';
- }
-
- 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;
diff --git a/server/routes/groups.js b/server/routes/groups.js
index 684f0cd..dff9f92 100755
--- a/server/routes/groups.js
+++ b/server/routes/groups.js
@@ -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) => {
diff --git a/server/routes/locations.js b/server/routes/locations.js
index ec1841b..f2c438b 100644
--- a/server/routes/locations.js
+++ b/server/routes/locations.js
@@ -5,7 +5,6 @@ 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;