SHowing locations
Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
This commit is contained in:
parent
641fb29e16
commit
d8b15e50af
@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useEffect, useContext } from "react";
|
import React, { useState, useEffect, useContext } from "react";
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from '@mui/material/Paper';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
import Moment from 'react-moment';
|
import Moment from 'react-moment';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -12,6 +13,7 @@ import { GlobalContext } from "./GlobalContext.js";
|
|||||||
import { base } from "./Common.js";
|
import { base } from "./Common.js";
|
||||||
|
|
||||||
function Dashboard() {
|
function Dashboard() {
|
||||||
|
const navigate = useNavigate();
|
||||||
const { csrfToken, user, setUser } = useContext(GlobalContext);
|
const { csrfToken, user, setUser } = useContext(GlobalContext);
|
||||||
const [ groups, setGroups ] = useState([]);
|
const [ groups, setGroups ] = useState([]);
|
||||||
const [ error, setError ] = useState(null);
|
const [ error, setError ] = useState(null);
|
||||||
@ -55,19 +57,22 @@ function Dashboard() {
|
|||||||
}}>
|
}}>
|
||||||
|
|
||||||
{ groups.map((group) => {
|
{ groups.map((group) => {
|
||||||
return <div key={group.id} style={{
|
return <Button key={group.id}
|
||||||
flexDirection: 'column',
|
onClick={() => navigate(`/${group.group}`)}
|
||||||
display: 'flex',
|
style={{
|
||||||
alignItems: 'flex-start',
|
flexDirection: 'column',
|
||||||
padding: '0.5rem'
|
display: 'flex',
|
||||||
}}>
|
alignItems: 'flex-start',
|
||||||
|
padding: '0.5rem'
|
||||||
|
}
|
||||||
|
}>
|
||||||
<div key={group.id} style={{
|
<div key={group.id} style={{
|
||||||
fontWeight: 'bold'
|
fontWeight: 'bold'
|
||||||
}}>{group.name}</div>
|
}}>{group.name}</div>
|
||||||
{ group.nextEvent &&
|
{ 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>Next event <Moment fromNow date={group.nextEvent} /> on <Moment format={'MMMM Do YYYY, h: mm: ss a'} date={group.nextEvent}/>.</div>
|
||||||
}
|
}
|
||||||
</div>;
|
</Button>;
|
||||||
}) }
|
}) }
|
||||||
</Paper>
|
</Paper>
|
||||||
</GlobalContext.Provider>
|
</GlobalContext.Provider>
|
||||||
|
@ -42,11 +42,33 @@ function Group() {
|
|||||||
}
|
}
|
||||||
setLocations(data.map(location => {
|
setLocations(data.map(location => {
|
||||||
const fields = Object.getOwnPropertyNames(location)
|
const fields = Object.getOwnPropertyNames(location)
|
||||||
.map(field => <div key={field}>
|
.map(field => <div key={field}
|
||||||
<div>{field}</div>
|
style={{
|
||||||
<div>{location[field]}</div>
|
display: 'flex',
|
||||||
</div>);
|
flexDirection: 'row',
|
||||||
return <div key={location.id}>{ fields }</div>;
|
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();
|
effect();
|
||||||
@ -61,7 +83,7 @@ function Group() {
|
|||||||
const effect = async () => {
|
const effect = async () => {
|
||||||
const res = await window.fetch(
|
const res = await window.fetch(
|
||||||
`${base}/api/v1/groups/${groupId}/events`, {
|
`${base}/api/v1/groups/${groupId}/events`, {
|
||||||
method: 'POST',
|
method: 'GET',
|
||||||
cache: 'no-cache',
|
cache: 'no-cache',
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
headers: {
|
headers: {
|
||||||
@ -83,11 +105,17 @@ function Group() {
|
|||||||
}, [user, setGroup, groupId, csrfToken]);
|
}, [user, setGroup, groupId, csrfToken]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="Group">
|
<Paper className="Group" style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column'
|
||||||
|
}}>
|
||||||
<GlobalContext.Provider value={{user, setUser}}>
|
<GlobalContext.Provider value={{user, setUser}}>
|
||||||
Group: {groupId}
|
{ error && <div>{error}</div>}
|
||||||
<div>Locations</div>
|
{ !error && <>
|
||||||
{ locations }
|
<div>Group: {groupId}</div>
|
||||||
|
<div>Locations</div>
|
||||||
|
{ locations }
|
||||||
|
</> }
|
||||||
</GlobalContext.Provider>
|
</GlobalContext.Provider>
|
||||||
</Paper>
|
</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\".")
|
||||||
|
}
|
||||||
|
}
|
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;
|
@ -7,19 +7,31 @@ router.get('/', async (req, res/*, next*/) => {
|
|||||||
id: 1,
|
id: 1,
|
||||||
ownerId: 1,
|
ownerId: 1,
|
||||||
name: 'Beer Tuesday',
|
name: 'Beer Tuesday',
|
||||||
|
group: 'beer-tuesday',
|
||||||
nextEvent: Date.now() + 86400 * 14 * 1000 /* 2 weeks from now */
|
nextEvent: Date.now() + 86400 * 14 * 1000 /* 2 weeks from now */
|
||||||
} ]
|
} ]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/:id', async (req, res) => {
|
|
||||||
const { id } = req.params;
|
router.get('/:groupId/events', async (req, res/*, next*/) => {
|
||||||
if (!id) {
|
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.`});
|
return res.status(400).send({ message: `Invalid group.`});
|
||||||
}
|
}
|
||||||
|
|
||||||
const group = {
|
const group = {
|
||||||
id
|
id: groupId
|
||||||
};
|
};
|
||||||
|
|
||||||
return res.status(200).send({ id: group.id });
|
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;
|
Loading…
x
Reference in New Issue
Block a user