1
0

Compare commits

...

2 Commits

Author SHA1 Message Date
James Ketrenos
3ba43bc8b8 Plumbing events
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
2022-04-09 23:19:47 -07:00
James Ketrenos
7aaa2cd1bf Added base data for events
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
2022-04-09 22:36:25 -07:00
12 changed files with 329 additions and 409 deletions

View File

@ -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 />}/>
}

View File

@ -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
View File

@ -0,0 +1,4 @@
.Event {
text-align: left;
}

109
client/src/Event.js Normal file
View 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 };

View File

@ -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>
);
}

View File

@ -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
View 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
View 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;

View File

@ -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;

View File

@ -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;

View File

@ -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) => {

View File

@ -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;