Compare commits
3 Commits
4c6040e3bc
...
d8b15e50af
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d8b15e50af | ||
![]() |
641fb29e16 | ||
![]() |
d06a3e6846 |
@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect, useContext } from "react";
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Button from '@mui/material/Button';
|
||||
import Moment from 'react-moment';
|
||||
|
||||
import {
|
||||
@ -12,6 +13,7 @@ import { GlobalContext } from "./GlobalContext.js";
|
||||
import { base } from "./Common.js";
|
||||
|
||||
function Dashboard() {
|
||||
const navigate = useNavigate();
|
||||
const { csrfToken, user, setUser } = useContext(GlobalContext);
|
||||
const [ groups, setGroups ] = useState([]);
|
||||
const [ error, setError ] = useState(null);
|
||||
@ -55,19 +57,22 @@ function Dashboard() {
|
||||
}}>
|
||||
|
||||
{ groups.map((group) => {
|
||||
return <div key={group.id} style={{
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
padding: '0.5rem'
|
||||
}}>
|
||||
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>
|
||||
}
|
||||
</div>;
|
||||
</Button>;
|
||||
}) }
|
||||
</Paper>
|
||||
</GlobalContext.Provider>
|
||||
|
@ -14,16 +14,17 @@ function Group() {
|
||||
const groupId = useParams().group;
|
||||
const [ group, setGroup ] = useState(null);
|
||||
const [ error, setError ] = useState(null);
|
||||
const [ locations, setLocations ] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user || !groupId || !csrfToken) {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
const effect = async () => {
|
||||
const res = await window.fetch(
|
||||
`${base}/api/v1/group/${groupId}/events`, {
|
||||
method: 'POST',
|
||||
`${base}/api/v1/locations/`, {
|
||||
method: 'GET',
|
||||
cache: 'no-cache',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
@ -35,7 +36,66 @@ function Group() {
|
||||
if (res.status >= 400) {
|
||||
setError(data.message ? data.message : res.statusText);
|
||||
return;
|
||||
}
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
setLocations(data.map(location => {
|
||||
const fields = Object.getOwnPropertyNames(location)
|
||||
.map(field => <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'
|
||||
}}>
|
||||
{location[field]}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <div key={location.id} style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
}}>{ fields }</div>;
|
||||
}));
|
||||
};
|
||||
effect();
|
||||
}, [user, setGroup, groupId, csrfToken]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!user || !groupId || !csrfToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
const effect = async () => {
|
||||
const res = await window.fetch(
|
||||
`${base}/api/v1/groups/${groupId}/events`, {
|
||||
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;
|
||||
}
|
||||
@ -45,9 +105,17 @@ function Group() {
|
||||
}, [user, setGroup, groupId, csrfToken]);
|
||||
|
||||
return (
|
||||
<Paper className="Group">
|
||||
<Paper className="Group" style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}>
|
||||
<GlobalContext.Provider value={{user, setUser}}>
|
||||
Group: {groupId}
|
||||
{ error && <div>{error}</div>}
|
||||
{ !error && <>
|
||||
<div>Group: {groupId}</div>
|
||||
<div>Locations</div>
|
||||
{ locations }
|
||||
</> }
|
||||
</GlobalContext.Provider>
|
||||
</Paper>
|
||||
);
|
||||
|
289
conv
Executable file
289
conv
Executable file
@ -0,0 +1,289 @@
|
||||
#!/usr/bin/awk -f
|
||||
|
||||
# Authors: @esperlu, @artemyk, @gkuenning, @dumblob
|
||||
|
||||
# FIXME detect empty input file and issue a warning
|
||||
|
||||
function printerr( s ){ print s | "cat >&2" }
|
||||
|
||||
BEGIN {
|
||||
if( ARGC != 2 ){
|
||||
printerr( \
|
||||
"USAGE:\n"\
|
||||
" mysql2sqlite dump_mysql.sql > dump_sqlite3.sql\n" \
|
||||
" OR\n" \
|
||||
" mysql2sqlite dump_mysql.sql | sqlite3 sqlite.db\n" \
|
||||
"\n" \
|
||||
"NOTES:\n" \
|
||||
" Dash in filename is not supported, because dash (-) means stdin." )
|
||||
no_END = 1
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Find INT_MAX supported by both this AWK (usually an ISO C signed int)
|
||||
# and SQlite.
|
||||
# On non-8bit-based architectures, the additional bits are safely ignored.
|
||||
|
||||
# 8bit (lower precision should not exist)
|
||||
s="127"
|
||||
# "63" + 0 avoids potential parser misbehavior
|
||||
if( (s + 0) "" == s ){ INT_MAX_HALF = "63" + 0 }
|
||||
# 16bit
|
||||
s="32767"
|
||||
if( (s + 0) "" == s ){ INT_MAX_HALF = "16383" + 0 }
|
||||
# 32bit
|
||||
s="2147483647"
|
||||
if( (s + 0) "" == s ){ INT_MAX_HALF = "1073741823" + 0 }
|
||||
# 64bit (as INTEGER in SQlite3)
|
||||
s="9223372036854775807"
|
||||
if( (s + 0) "" == s ){ INT_MAX_HALF = "4611686018427387904" + 0 }
|
||||
# # 128bit
|
||||
# s="170141183460469231731687303715884105728"
|
||||
# if( (s + 0) "" == s ){ INT_MAX_HALF = "85070591730234615865843651857942052864" + 0 }
|
||||
# # 256bit
|
||||
# s="57896044618658097711785492504343953926634992332820282019728792003956564819968"
|
||||
# if( (s + 0) "" == s ){ INT_MAX_HALF = "28948022309329048855892746252171976963317496166410141009864396001978282409984" + 0 }
|
||||
# # 512bit
|
||||
# s="6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048"
|
||||
# if( (s + 0) "" == s ){ INT_MAX_HALF = "3351951982485649274893506249551461531869841455148098344430890360930441007518386744200468574541725856922507964546621512713438470702986642486608412251521024" + 0 }
|
||||
# # 1024bit
|
||||
# s="89884656743115795386465259539451236680898848947115328636715040578866337902750481566354238661203768010560056939935696678829394884407208311246423715319737062188883946712432742638151109800623047059726541476042502884419075341171231440736956555270413618581675255342293149119973622969239858152417678164812112068608"
|
||||
# if( (s + 0) "" == s ){ INT_MAX_HALF = "44942328371557897693232629769725618340449424473557664318357520289433168951375240783177119330601884005280028469967848339414697442203604155623211857659868531094441973356216371319075554900311523529863270738021251442209537670585615720368478277635206809290837627671146574559986811484619929076208839082406056034304" + 0 }
|
||||
# # higher precision probably not needed
|
||||
|
||||
FS=",$"
|
||||
print "PRAGMA synchronous = OFF;"
|
||||
print "PRAGMA journal_mode = MEMORY;"
|
||||
print "BEGIN TRANSACTION;"
|
||||
}
|
||||
|
||||
# historically 3 spaces separate non-argument local variables
|
||||
function bit_to_int( str_bit, powtwo, i, res, bit, overflow ){
|
||||
powtwo = 1
|
||||
overflow = 0
|
||||
# 011101 = 1*2^0 + 0*2^1 + 1*2^2 ...
|
||||
for( i = length( str_bit ); i > 0; --i ){
|
||||
bit = substr( str_bit, i, 1 )
|
||||
if( overflow || ( bit == 1 && res > INT_MAX_HALF ) ){
|
||||
printerr( \
|
||||
NR ": WARN Bit field overflow, number truncated (LSBs saved, MSBs ignored)." )
|
||||
break
|
||||
}
|
||||
res = res + bit * powtwo
|
||||
# no warning here as it might be the last iteration
|
||||
if( powtwo > INT_MAX_HALF ){ overflow = 1; continue }
|
||||
powtwo = powtwo * 2
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
# CREATE TRIGGER statements have funny commenting. Remember we are in trigger.
|
||||
/^\/\*.*(CREATE.*TRIGGER|create.*trigger)/ {
|
||||
gsub( /^.*(TRIGGER|trigger)/, "CREATE TRIGGER" )
|
||||
print
|
||||
inTrigger = 1
|
||||
next
|
||||
}
|
||||
# The end of CREATE TRIGGER has a stray comment terminator
|
||||
/(END|end) \*\/;;/ { gsub( /\*\//, "" ); print; inTrigger = 0; next }
|
||||
# The rest of triggers just get passed through
|
||||
inTrigger != 0 { print; next }
|
||||
|
||||
# CREATE VIEW looks like a TABLE in comments
|
||||
/^\/\*.*(CREATE.*TABLE|create.*table)/ {
|
||||
inView = 1
|
||||
next
|
||||
}
|
||||
# end of CREATE VIEW
|
||||
/^(\).*(ENGINE|engine).*\*\/;)/ {
|
||||
inView = 0
|
||||
next
|
||||
}
|
||||
# content of CREATE VIEW
|
||||
inView != 0 { next }
|
||||
|
||||
# skip comments
|
||||
/^\/\*/ { next }
|
||||
|
||||
# skip PARTITION statements
|
||||
/^ *[(]?(PARTITION|partition) +[^ ]+/ { next }
|
||||
|
||||
# print all INSERT lines
|
||||
( /^ *\(/ && /\) *[,;] *$/ ) || /^(INSERT|insert|REPLACE|replace)/ {
|
||||
prev = ""
|
||||
|
||||
# first replace \\ by \_ that mysqldump never generates to deal with
|
||||
# sequnces like \\n that should be translated into \n, not \<LF>.
|
||||
# After we convert all escapes we replace \_ by backslashes.
|
||||
gsub( /\\\\/, "\\_" )
|
||||
|
||||
# single quotes are escaped by another single quote
|
||||
gsub( /\\'/, "''" )
|
||||
gsub( /\\n/, "\n" )
|
||||
gsub( /\\r/, "\r" )
|
||||
gsub( /\\"/, "\"" )
|
||||
gsub( /\\\032/, "\032" ) # substitute char
|
||||
|
||||
gsub( /\\_/, "\\" )
|
||||
|
||||
# sqlite3 is limited to 16 significant digits of precision
|
||||
while( match( $0, /0x[0-9a-fA-F]{17}/ ) ){
|
||||
hexIssue = 1
|
||||
sub( /0x[0-9a-fA-F]+/, substr( $0, RSTART, RLENGTH-1 ), $0 )
|
||||
}
|
||||
if( hexIssue ){
|
||||
printerr( \
|
||||
NR ": WARN Hex number trimmed (length longer than 16 chars)." )
|
||||
hexIssue = 0
|
||||
}
|
||||
print
|
||||
next
|
||||
}
|
||||
|
||||
# CREATE DATABASE is not supported
|
||||
/^(CREATE DATABASE|create database)/ { next }
|
||||
|
||||
# print the CREATE line as is and capture the table name
|
||||
/^(CREATE|create)/ {
|
||||
if( $0 ~ /IF NOT EXISTS|if not exists/ || $0 ~ /TEMPORARY|temporary/ ){
|
||||
caseIssue = 1
|
||||
printerr( \
|
||||
NR ": WARN Potential case sensitivity issues with table/column naming\n" \
|
||||
" (see INFO at the end)." )
|
||||
}
|
||||
if( match( $0, /`[^`]+/ ) ){
|
||||
tableName = substr( $0, RSTART+1, RLENGTH-1 )
|
||||
}
|
||||
aInc = 0
|
||||
prev = ""
|
||||
firstInTable = 1
|
||||
print
|
||||
next
|
||||
}
|
||||
|
||||
# Replace `FULLTEXT KEY` (probably other `XXXXX KEY`)
|
||||
/^ (FULLTEXT KEY|fulltext key)/ { gsub( /[A-Za-z ]+(KEY|key)/, " KEY" ) }
|
||||
|
||||
# Get rid of field lengths in KEY lines
|
||||
/ (PRIMARY |primary )?(KEY|key)/ { gsub( /\([0-9]+\)/, "" ) }
|
||||
|
||||
aInc == 1 && /PRIMARY KEY|primary key/ { next }
|
||||
|
||||
# Replace COLLATE xxx_xxxx_xx statements with COLLATE BINARY
|
||||
/ (COLLATE|collate) [a-z0-9_]*/ { gsub( /(COLLATE|collate) [a-z0-9_]*/, "COLLATE BINARY" ) }
|
||||
|
||||
# Print all fields definition lines except the `KEY` lines.
|
||||
/^ / && !/^( (KEY|key)|\);)/ {
|
||||
if( match( $0, /[^"`]AUTO_INCREMENT|auto_increment[^"`]/) ){
|
||||
aInc = 1
|
||||
gsub( /AUTO_INCREMENT|auto_increment/, "PRIMARY KEY AUTOINCREMENT" )
|
||||
}
|
||||
gsub( /(UNIQUE KEY|unique key) (`.*`|".*") /, "UNIQUE " )
|
||||
gsub( /(CHARACTER SET|character set) [^ ]+[ ,]/, "" )
|
||||
# FIXME
|
||||
# CREATE TRIGGER [UpdateLastTime]
|
||||
# AFTER UPDATE
|
||||
# ON Package
|
||||
# FOR EACH ROW
|
||||
# BEGIN
|
||||
# UPDATE Package SET LastUpdate = CURRENT_TIMESTAMP WHERE ActionId = old.ActionId;
|
||||
# END
|
||||
gsub( /(ON|on) (UPDATE|update) (CURRENT_TIMESTAMP|current_timestamp)(\(\))?/, "" )
|
||||
gsub( /(DEFAULT|default) (CURRENT_TIMESTAMP|current_timestamp)(\(\))?/, "DEFAULT current_timestamp")
|
||||
gsub( /(COLLATE|collate) [^ ]+ /, "" )
|
||||
gsub( /(ENUM|enum)[^)]+\)/, "text " )
|
||||
gsub( /(SET|set)\([^)]+\)/, "text " )
|
||||
gsub( /UNSIGNED|unsigned/, "" )
|
||||
gsub( /_utf8mb3/, "" )
|
||||
gsub( /` [^ ]*(INT|int|BIT|bit)[^ ]*/, "` integer" )
|
||||
gsub( /" [^ ]*(INT|int|BIT|bit)[^ ]*/, "\" integer" )
|
||||
ere_bit_field = "[bB]'[10]+'"
|
||||
if( match($0, ere_bit_field) ){
|
||||
sub( ere_bit_field, bit_to_int( substr( $0, RSTART +2, RLENGTH -2 -1 ) ) )
|
||||
}
|
||||
|
||||
# remove USING BTREE and other suffixes for USING, for example: "UNIQUE KEY
|
||||
# `hostname_domain` (`hostname`,`domain`) USING BTREE,"
|
||||
gsub( / USING [^, ]+/, "" )
|
||||
|
||||
# field comments are not supported
|
||||
gsub( / (COMMENT|comment).+$/, "" )
|
||||
# Get commas off end of line
|
||||
gsub( /,.?$/, "" )
|
||||
if( prev ){
|
||||
if( firstInTable ){
|
||||
print prev
|
||||
firstInTable = 0
|
||||
}
|
||||
else {
|
||||
print "," prev
|
||||
}
|
||||
}
|
||||
else {
|
||||
# FIXME check if this is correct in all cases
|
||||
if( match( $1,
|
||||
/(CONSTRAINT|constraint) ["].*["] (FOREIGN KEY|foreign key)/ ) ){
|
||||
print ","
|
||||
}
|
||||
}
|
||||
prev = $1
|
||||
}
|
||||
|
||||
/ ENGINE| engine/ {
|
||||
if( prev ){
|
||||
if( firstInTable ){
|
||||
print prev
|
||||
firstInTable = 0
|
||||
}
|
||||
else {
|
||||
print "," prev
|
||||
}
|
||||
}
|
||||
prev=""
|
||||
print ");"
|
||||
next
|
||||
}
|
||||
# `KEY` lines are extracted from the `CREATE` block and stored in array for later print
|
||||
# in a separate `CREATE KEY` command. The index name is prefixed by the table name to
|
||||
# avoid a sqlite error for duplicate index name.
|
||||
/^( (KEY|key)|\);)/ {
|
||||
if( prev ){
|
||||
if( firstInTable ){
|
||||
print prev
|
||||
firstInTable = 0
|
||||
}
|
||||
else {
|
||||
print "," prev
|
||||
}
|
||||
}
|
||||
prev = ""
|
||||
if( $0 == ");" ){
|
||||
print
|
||||
}
|
||||
else {
|
||||
if( match( $0, /`[^`]+/ ) ){
|
||||
indexName = substr( $0, RSTART+1, RLENGTH-1 )
|
||||
}
|
||||
if( match( $0, /\([^()]+/ ) ){
|
||||
indexKey = substr( $0, RSTART+1, RLENGTH-1 )
|
||||
}
|
||||
# idx_ prefix to avoid name clashes (they really happen!)
|
||||
key[tableName] = key[tableName] "CREATE INDEX \"idx_" \
|
||||
tableName "_" indexName "\" ON \"" tableName "\" (" indexKey ");\n"
|
||||
}
|
||||
}
|
||||
|
||||
END {
|
||||
if( no_END ){ exit 1}
|
||||
# print all KEY creation lines.
|
||||
for( table in key ){ printf key[table] }
|
||||
|
||||
print "END TRANSACTION;"
|
||||
|
||||
if( caseIssue ){
|
||||
printerr( \
|
||||
"INFO Pure sqlite identifiers are case insensitive (even if quoted\n" \
|
||||
" or if ASCII) and doesnt cross-check TABLE and TEMPORARY TABLE\n" \
|
||||
" identifiers. Thus expect errors like \"table T has no column named F\".")
|
||||
}
|
||||
}
|
@ -61,12 +61,29 @@ goodTimesDB.init().then((db) => {
|
||||
app.locals.db = db;
|
||||
app.set('basePath', basePath);
|
||||
|
||||
app.use(`${basePath}api/v1/groups`, require('./routes/groups'));
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
app.use((req, _res, next) => {
|
||||
console.log(
|
||||
`${req.method} ${req.path} - ` +
|
||||
`${req.session.userId ? req.session.userId : 'Not logged in'}`
|
||||
);
|
||||
next();
|
||||
});
|
||||
app.use(`${basePath}api/v1/users`, require('./routes/users'));
|
||||
app.use((req, res, next) => {
|
||||
if (!req.session.userId) {
|
||||
return res.status(401).send({ message: `You must be logged in.` });
|
||||
}
|
||||
next();
|
||||
});
|
||||
app.use(`${basePath}api/v1/groups`, require('./routes/groups'));
|
||||
app.use(`${basePath}api/v1/events`, require('./routes/events'));
|
||||
app.use(`${basePath}api/v1/locations`, require('./routes/locations'));
|
||||
|
||||
/* Error handler and catch for 404 */
|
||||
app.use(methodOverride());
|
||||
app.use((err, req, res, next) => {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
app.use((err, req, res, _next) => {
|
||||
console.log('CSRF debug: ', {
|
||||
token: req.header('CSRF-Token'),
|
||||
csrf: req.csrfToken(),
|
||||
|
115
server/db.js
115
server/db.js
@ -17,6 +17,12 @@ function init() {
|
||||
autoIncrement: true
|
||||
},
|
||||
name: Sequelize.STRING,
|
||||
description: Sequelize.STRING,
|
||||
public: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true
|
||||
},
|
||||
ownerId: Sequelize.INTEGER
|
||||
}, {
|
||||
timestamps: false,
|
||||
@ -47,6 +53,33 @@ function init() {
|
||||
timestamps: false
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const Location = db.sequelize.define('locations', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
groupId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Group,
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
name: Sequelize.STRING,
|
||||
location: Sequelize.STRING,
|
||||
map: Sequelize.STRING,
|
||||
url: Sequelize.STRING,
|
||||
note: Sequelize.STRING,
|
||||
disabled: Sequelize.BOOLEAN,
|
||||
beerlist: Sequelize.STRING,
|
||||
added: Sequelize.DATE
|
||||
}, {
|
||||
timestamps: false
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const Authentication = db.sequelize.define('authentication', {
|
||||
key: {
|
||||
@ -72,7 +105,7 @@ function init() {
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const GroupUsers = db.sequelize.define('groupuser', {
|
||||
const GroupUsers = db.sequelize.define('groupusers', {
|
||||
groupId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
@ -93,6 +126,86 @@ function init() {
|
||||
timestamps: false
|
||||
});
|
||||
|
||||
const Event = db.sequelize.define('events', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
groupId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Group,
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
date: Sequelize.DATE
|
||||
}, {
|
||||
timestamps: false
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const EventVote = db.sequelize.define('eventvotes', {
|
||||
eventId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Event,
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
userId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: User,
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
locationId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Location,
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
rating: Sequelize.INTEGER,
|
||||
submitted: Sequelize.DATE
|
||||
}, {
|
||||
timestamps: false
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const Attendance = db.sequelize.define('attendees', {
|
||||
eventId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Event,
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
userId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: User,
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
state: {
|
||||
type: {
|
||||
type: Sequelize.ENUM,
|
||||
values: ['unknown', 'in', 'out']
|
||||
}
|
||||
},
|
||||
note: Sequelize.STRING
|
||||
}, {
|
||||
timestamps: false
|
||||
});
|
||||
|
||||
return db.sequelize.sync({
|
||||
force: false
|
||||
}).then(function () {
|
||||
|
32
server/location-data.js
Normal file
32
server/location-data.js
Normal file
@ -0,0 +1,32 @@
|
||||
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':''},
|
||||
{'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'},
|
||||
{'id':5,'name':'OHBP','location':'Cornell Rd just north of Hwy 26','map':'http://goo.gl/maps/mGwul','url':'','note':'Conveniently located for most of America, the Oak Hills Brew Pub is a regional favorite. This is due primarily to the number of people that either live near it, or drive by it on their way home.\n\nA small McMenamin pub, OHBP offers the same food and beverage listings as the others from the local pub chain.','disabled':0,'beerlist':''},
|
||||
{'id':6,'name':'Golden Valley Brewing','location':'Just south of 26 on Bethany.','map':'','url':'http://www.goldenvalleybrewery.com','note':'','disabled':0,'beerlist':'http://www.goldenvalleybrewery.com/beer/'},
|
||||
{'id':7,'name':'Monteaux\'s Public House','location':'A quarter mile west of the 158th and Walker Rd intersection in Beaverton (across Walker from Fred Meyer.)','map':'http://goo.gl/maps/Oubb0','url':'http://www.monteauxs.com','note':'With good food, and happy hour running from 3-6, Monteaux\'s is a good place to kick back and have a pint with some friends (or co-workers; whichever you have available.)','disabled':1,'beerlist':'http://www.taplister.com/bars/410c3280f964a520ad0b1fe3'},
|
||||
{'id':8,'name':'Coyote\'s','location':'','map':'','url':'','note':'','disabled':0,'beerlist':''},
|
||||
{'id':9,'name':'Bugatti\'s','location':'','map':'','url':'','note':'','disabled':0,'beerlist':''},
|
||||
{'id':10,'name':'BJ\'s','location':'Cornell Rd near the intersection with Cornelius Pass Rd.','map':'http://goo.gl/maps/o5wJc','url':'','note':'A few really good beers (Tatonka Stout) and some tasty appetizers. Plus Tuesday gets you half off wine. What\'s not to like?\n\nReasonably located for most of the world, BJ\'s has climbed the ladder as a frequent Beer Tuesday target.','disabled':0,'beerlist':''},
|
||||
{'id':11,'name':'Oregon Brewer\'s Festival','location':'Downtown Portland (waterfront)','map':'http://goo.gl/maps/jb622','url':'http://oregonbrewfest.com','note':'Instead of the July 23rd (or in addition to), add a Beer Thursday for the 25th.\n\nWe\'ll head down *early* afternoon to beat the crowds.','disabled':1,'beerlist':''},
|
||||
{'id':12,'name':'Tug Boat','location':'Downtown, a block from Burnside on Ankeny (just off of Broadway)','map':'http://goo.gl/maps/I83Oj','url':'http://www.d2m.com/tugwebsite/','note':'A "quaint" location, Tug Boat offers a reasonable set of beers in a unique setting. Its a great spot to finish a downtown hack session at the Collective Agency.','disabled':0,'beerlist':'http://www.taplister.com/bars/40b13b00f964a5200df61ee3'},
|
||||
{'id':13,'name':'Deschutes Brewery','location':'Downtown Bend. And more conveniently located on 11th a block off Burnside next to Powell\'s Books.','map':'http://goo.gl/maps/2r2Eh','url':'http://www.deschutesbrewery.com/locations/portland','note':'Beer. Good, wholesome, beer. Obsidian Stout on Nitro. Can\'t go wrong there, unless you don\'t like stout... if that be the case, mayhaps you\'d prefer the Chainbreaker or Mirror Pond. Or even one of the seasonals on tap. A good location after a day at the Collective Agency, or any other reason to be downtown.','disabled':0,'beerlist':'http://www.deschutesbrewery.com/on-tap/portland'},
|
||||
{'id':14,'name':'Ridge Pub','location':'Progress Ridge, between Ava Roasteria and Cinetopia.','map':'http://goo.gl/maps/Uckva','url':'http://www.theridgepub.com/','note':'A good location if you\'ll be catching an evening show at Cinetopia, or if you happen to be heading toward Tigard for any reason.','disabled':0,'beerlist':''},
|
||||
{'id':15,'name':'Stanford\'s','location':'Off of 185th and Cornell, walking distance from the Evergreen theater.','map':'http://goo.gl/maps/NKLOI','url':'http://www.stanfords.com','note':'Tasty appetizers with happy hour running to 6pm. The Wood-Fire Grilled Portobello\nMushroom is awesome.','disabled':0,'beerlist':'http://www.stanfords.com/menu.php?c=tanasbourne&id=Bar%20Menu'},
|
||||
{'id':16,'name':'Old Chicago','location':'Along Evergreen Parkway, between 185th and Cornell.','map':'http://goo.gl/maps/Blj9U','url':'http://oldchicago.com/locations/beaverton-tanasbourne?action=view','note':'A reasonable establishment to share a pint or two with your fellow coworker. Old Chicago has some tasty food, and a wide variety of beers, some ciders, and other beverages.','disabled':0,'beerlist':'http://oldchicago.com/beverages/hand-crafted-beers'},
|
||||
{'id':17,'name':'Rock Creek','location':'A couple minutes north of highway 26 on Cornelius Pass Rd','map':'http://goo.gl/maps/NwYhC','url':'http://www.mcmenamins.com/RockCreek','note':'McMenamin Ales. Good friends. Nice building. A win all around, especially if you hang round until the Blue Grass kicks in at 7PM on Tuesdays.','disabled':0,'beerlist':'http://www.mcmenamins.com/532-rock-creek-tavern-ales'},
|
||||
{'id':18,'name':'Thirsty Lion - Tanasbourne','location':'In the Streets of Tanasbourne, just minutes from shopping, beer, and food.','map':'https://maps.google.com/maps?q=2290+NW+Allie+Avenue,+Hillsboro,+Oregon+97124&hl=en&sll=37.6,-95.665&sspn=60.181414,50.185547&hnear=2290+NW+Allie+Ave,+Hillsboro,+Oregon+97124&t=m&z=16','url':'https://www.facebook.com/thirstyliontanasbourne','note':'A gastropub. Reasonable food. Good beers. Fun times.','disabled':0,'beerlist':'http://thirstylionpub.com/tanasbourne/menu'},
|
||||
{'id':19,'name':'Metropolitan Bistro & Bar','location':'In the small commercial complex where Baseline turns into Jenkins, just east of 170th you will find the Metropolitan Bistro & Bar, previously known as Mireille\'s Bistro. It\'s quaint, quiet, and the service is friendly. Worth hanging out on an afternoon.','map':'https://maps.google.com/maps?q=16755+SW+Baseline+Rd.,+Beaverton,+Oregon+97006&hl=en&ll=45.512106,-122.849207&spn=0.0103,0.022123&sll=45.512001,-122.849596&sspn=0.001295,0.002765&hnear=16755+SW+Baseline+Rd,+Beaverton,+Oregon+97006&t=m&z=16','url':'https://www.facebook.com/mireillesbistro','note':'It isn\'t a brewery or pub, and doesn\'t have the largest beer selection. However it is nice and quiet, and the staff is very friendly.\n\nThe food is pretty good too.\n\n*AND* Tuesday night is Trivia night (6:30-8:00)!','disabled':1,'beerlist':'http://www.beermenus.com/places/10862-mireille-s-bistro'},
|
||||
{'id':20,'name':'ABV Public House','location':'Conveniently located just a rock\'s throw from the corner of Brookwood and Hwy 26. ','map':'http://goo.gl/maps/WwjDT','url':'http://www.abvpub.com/','note':'Fantabulous! Recently opened, this brew pub / tap house is sure to become the next big thing for Beer Tuesday. Check out the chicken fried portobello mushroom sandwich. ','disabled':0,'beerlist':'http://www.abvpub.com/taps/'},
|
||||
{'id':21,'name':'Three Mugs Brewing Company','location':'Right behind Brew Brothers just off Cornelius Pass Road, south of Hwy 26 and kitty corner from Kohls.','map':'https://maps.google.com/maps?q=2020+NW+Aloclek+Dr+Ste+108,+Hillsboro,+Oregon&hl=en&sll=45.532072,-122.935317&sspn=0.165705,0.306931&hnear=2020+NW+Aloclek+Dr+%23108,+Hillsboro,+Oregon+97124&t=m&z=16','url':'http://www.threemugsbrewing.com/','note':'Beers, Ciders, however no food...','disabled':0,'beerlist':'http://www.threemugsbrewingco.com/#!about2/c166u'},
|
||||
{'id':22,'name':'APEX','location':'Just 30 minutes from the farm (without traffic) and you\'re there! -- 1216 SE Division St.','map':'http://tinyurl.com/koldwj5','url':'http://www.apexbar.com/','note':'If it\'s beer you want, it\'s beer they have! A bit of a jug from the farm, however they have the juice that will cure what ales you once you land at their bar.\n\nSee what I did there? Punny, eh?','disabled':0,'beerlist':'http://www.apexbar.com/menu'},
|
||||
{'id':23,'name':'Vagabond Brewing','location':'It may be a bit of a drive, however the beer is good, and the company is fantastic!\n\nLocated in north Salem, they are a short 50 minute drive from Farm to Beer (without traffic)','map':'http://tinyurl.com/vegabondbrewing','url':'http://www.vagabondbrewing.com/','note':'If you hit up Vagabond, make sure you say hi to the brewmaster -- James Cardwell. In addition to sharing the same first name as James Ketrenos, the two are also cousins.','disabled':0,'beerlist':'http://www.vagabondbrewing.com/#!beers/clg4'},
|
||||
{'id':24,'name':'McNally\'s Taproom','location':'If it\'s west you want to go, then McNally\'s has the location for you -- on the corner of 4th and East Main St in downtown Hillsboro.','map':'http://tinyurl.com/mcnallystaproom','url':'http://mcnallystaproom.com/','note':'','disabled':0,'beerlist':'http://mcnallystaproom.com/taplist/'},
|
||||
{'id':25,'name':'Brannons Pub and Brewery','location':'A couple miles south of 26 on Cedar Hills Blvd near where the old Westgate theater used to be.','map':'http://goo.gl/yq7gLl','url':'http://www.brannonsbrewery.com','note':'','disabled':1,'beerlist':'http://www.brannonsbrewery.com/brews'},
|
||||
{'id':26,'name':'Ambacht Brewing','location':'Ambacht brewing on 25th, about 10 minutes walk from JF Campus.','map':'http://goo.gl/f4tUUn','url':'http://www.ambacht.us','note':'','disabled':0,'beerlist':'http://www.ambacht.us/ambacht_ales.html'},
|
||||
{'id':27,'name':'Copper River Grill','location':'Next door to BJ\'s where \'On the Border\' used to be.','map':'https://www.google.com/maps/dir/2500+NE+25th+Ave,+Hillsboro,+OR+97124/45.5388207,-122.9029725/@45.5417113,-122.951877,14z/data=!3m1!4b1!4m8!4m7!1m5!1m1!1s0x5495053f6bb8f389:0xa39991f76a1278a2!2m2!1d-122.957124!2d45.5431728!1m0','url':'http://www.copperriverrestaurant.com/','note':'','disabled':0,'beerlist':'http://www.copperriverrestaurant.com/beer--wine.html'},
|
||||
{'id':28,'name':'Growlerie','location':'Progress Ridge - just a quick 14 miles away!','map':'https://goo.gl/umqffZ','url':'http://thegrowlerie.com','note':'Just down the stairs from Cinetopia. Have a pint, or a pitcher, then go watch a movie! It\'s a perfect Beer Tuesday!','disabled':0,'beerlist':'http://www.thegrowlerie.com/#new-page-section'},
|
||||
{'id':30,'name':'Imperial Bottle Shop and Tap Room','location':'Want some good food and some beer? Go to Pok Pok and then wander over to the Imperial Bottle Shop and Taproom. It may be a drive to get to the other side of the known universe, however it could be worth your while.\n\nAnd if they do not have the beer you want, you are not that far from APEX where Boulder Shake is ALWAYS on Nitro.','map':'https://goo.gl/l6ek5w','url':'http://imperialbottleshop.com/','note':'','disabled':0,'beerlist':'http://imperialbottleshop.com/'},
|
||||
{'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/'}];
|
337
server/routes/events.js
Normal file
337
server/routes/events.js
Normal file
@ -0,0 +1,337 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express'),
|
||||
{ sendVerifyMail, sendPasswordChangedMail, sendVerifiedMail } =
|
||||
require('../lib/mail'),
|
||||
crypto = require('crypto');
|
||||
|
||||
const router = express.Router();
|
||||
const autoAuthenticate = 1;
|
||||
|
||||
router.get('/', (req, res/*, next*/) => {
|
||||
console.log('GET /users/');
|
||||
|
||||
return getSessionUser(req).then((user) => {
|
||||
if (typeof user === 'string') {
|
||||
return res.status(403).send({ message: user });
|
||||
}
|
||||
return res.status(200).send(user);
|
||||
});
|
||||
});
|
||||
|
||||
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('/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.'
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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'
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
res.status(200).send({});
|
||||
});
|
||||
|
||||
module.exports = router;
|
@ -1,41 +1,39 @@
|
||||
const express = require('express'),
|
||||
router = express.Router(),
|
||||
crypto = require('crypto');
|
||||
router = express.Router();
|
||||
|
||||
router.get('/', async (req, res/*, next*/) => {
|
||||
console.log('GET /groups/', req.session.userId);
|
||||
return res.status(200).send(
|
||||
[ {
|
||||
id: 1,
|
||||
ownerId: 1,
|
||||
name: 'Beer Tuesday',
|
||||
group: 'beer-tuesday',
|
||||
nextEvent: Date.now() + 86400 * 14 * 1000 /* 2 weeks from now */
|
||||
} ]
|
||||
);
|
||||
});
|
||||
|
||||
router.post('/:id', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
let userId;
|
||||
if (!req.cookies.user) {
|
||||
userId = crypto.randomBytes(16).toString('hex');
|
||||
res.cookie('user', userId);
|
||||
} else {
|
||||
userId = req.cookies.user;
|
||||
router.get('/:groupId/events', async (req, res/*, next*/) => {
|
||||
return res.status(200).send(
|
||||
[{
|
||||
id: 1,
|
||||
name: 'Tuesday',
|
||||
date: Date.now() + 86400 * 14 * 1000 /* 2 weeks from now */
|
||||
}]
|
||||
);
|
||||
});
|
||||
|
||||
router.post('/:groupId', async (req, res) => {
|
||||
const { groupId } = req.params;
|
||||
if (!groupId) {
|
||||
return res.status(400).send({ message: `Invalid group.`});
|
||||
}
|
||||
|
||||
|
||||
const group = {
|
||||
id: 1
|
||||
id: groupId
|
||||
};
|
||||
|
||||
if (id) {
|
||||
console.log(`[${userId.substring(0,8)}]: Attempting load of ${id}`);
|
||||
} else {
|
||||
console.log(`[${userId.substring(0,8)}]: Creating new group.`);
|
||||
}
|
||||
console.log(`[${userId.substring(0,8)}]: ${group.id} loaded.`);
|
||||
|
||||
return res.status(200).send({ id: group.id });
|
||||
});
|
||||
|
||||
|
60
server/routes/locations.js
Normal file
60
server/routes/locations.js
Normal file
@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const originalLocations = require('../location-data.js');
|
||||
|
||||
|
||||
router.put('/', (req, res) => {
|
||||
const location = req.body;
|
||||
location.id = originalLocations.length;
|
||||
originalLocations.push(location);
|
||||
res.status(200).send([ location ]);
|
||||
});
|
||||
|
||||
router.get('/:locationId?', (req, res) => {
|
||||
const { locationId } = req.params;
|
||||
if (locationId) {
|
||||
const location = originalLocations.find(
|
||||
item => item.id === locationId
|
||||
);
|
||||
if (!location) {
|
||||
res.status(404).send({ message: `Location ${locationId} not found.`});
|
||||
}
|
||||
res.status(200).send([ location ]);
|
||||
}
|
||||
|
||||
return res.status(200).send(originalLocations);
|
||||
});
|
||||
|
||||
router.post('/:locationId', (req, res) => {
|
||||
const { locationId } = req.params;
|
||||
if (!locationId) {
|
||||
return res.status(400).send({ message: `Invalid location.`});
|
||||
}
|
||||
const location = originalLocations.find(
|
||||
item => item.id === locationId
|
||||
);
|
||||
if (!location) {
|
||||
res.status(404).send({ message: `Location ${locationId} not found.` });
|
||||
}
|
||||
res.status(200).send([location]);
|
||||
});
|
||||
|
||||
router.delete('/:locationId', (req, res) => {
|
||||
const { locationId } = req.params;
|
||||
if (!locationId) {
|
||||
return res.status(400).send({ message: `Invalid location.` });
|
||||
}
|
||||
const locationIndex = originalLocations.findIndex(
|
||||
item => item.id === locationId
|
||||
);
|
||||
if (locationIndex === -1) {
|
||||
res.status(404).send({ message: `Location ${locationId} not found.` });
|
||||
}
|
||||
originalLocations.splice(locationIndex, 1);
|
||||
res.status(200).send({ message: `Location ${locationId} deleted.`});
|
||||
});
|
||||
|
||||
module.exports = router;
|
@ -9,8 +9,6 @@ const router = express.Router();
|
||||
const autoAuthenticate = 1;
|
||||
|
||||
router.get('/', (req, res/*, next*/) => {
|
||||
console.log('GET /users/');
|
||||
|
||||
return getSessionUser(req).then((user) => {
|
||||
if (typeof user === 'string') {
|
||||
return res.status(403).send({ message: user });
|
||||
@ -20,7 +18,6 @@ router.get('/', (req, res/*, next*/) => {
|
||||
});
|
||||
|
||||
router.put('/password', async (req, res) => {
|
||||
console.log('/users/password');
|
||||
const db = req.app.locals.db;
|
||||
|
||||
const changes = {
|
||||
@ -80,12 +77,10 @@ router.put('/password', async (req, res) => {
|
||||
|
||||
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 = {
|
||||
@ -163,7 +158,6 @@ router.post('/signup', function(req, res) {
|
||||
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.';
|
||||
}
|
||||
@ -205,7 +199,6 @@ const getSessionUser = async (req) => {
|
||||
};
|
||||
|
||||
router.post('/verify-email', async (req, res) => {
|
||||
console.log('/users/verify-email');
|
||||
const key = req.body.token;
|
||||
const db = req.app.locals.db;
|
||||
|
||||
@ -268,7 +261,6 @@ router.post('/verify-email', async (req, res) => {
|
||||
});
|
||||
|
||||
router.post('/signin', (req, res) => {
|
||||
console.log('/users/signin');
|
||||
const db = req.app.locals.db;
|
||||
|
||||
let { email, password } = req.body;
|
||||
@ -327,7 +319,6 @@ router.post('/signin', (req, res) => {
|
||||
});
|
||||
|
||||
router.post('/signout', (req, res) => {
|
||||
console.log('/users/signout');
|
||||
if (req.session && req.session.userId) {
|
||||
req.session.userId = null;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user