@ -14,19 +14,17 @@ const db = new Pool({
const app = express ( ) ;
const app = express ( ) ;
const port = 80 ;
const port = 80 ;
db . query ( "CREATE TABLE IF NOT EXISTS bin (id VARCHAR PRIMARY KEY );"
db . query ( "CREATE TABLE IF NOT EXISTS bin (id VARCHAR PRIMARY KEY , name VARCHAR );"
+ "CREATE TABLE IF NOT EXISTS note (id VARCHAR PRIMARY KEY, text TEXT, modified TIMESTAMPTZ);"
+ "CREATE TABLE IF NOT EXISTS note (id VARCHAR PRIMARY KEY, text TEXT, modified TIMESTAMPTZ);"
+ "CREATE TABLE IF NOT EXISTS bin_note (bin_id VARCHAR REFERENCES bin (id), note_id VARCHAR REFERENCES note (id), PRIMARY KEY (bin_id, note_id));"
+ "CREATE TABLE IF NOT EXISTS bin_note (bin_id VARCHAR REFERENCES bin (id), note_id VARCHAR REFERENCES note (id), PRIMARY KEY (bin_id, note_id));"
+ "CREATE TABLE IF NOT EXISTS user_ (id VARCHAR PRIMARY KEY, username TEXT, password TEXT);" // table 'user' is already taken
+ "CREATE TABLE IF NOT EXISTS user_ (id VARCHAR PRIMARY KEY, username TEXT, password TEXT);" // table 'user' is already taken
+ "CREATE TABLE IF NOT EXISTS bin_user (bin_id VARCHAR REFERENCES bin (id), user_id VARCHAR REFERENCES user_ (id), PRIMARY KEY (bin_id, user_id));"
+ "CREATE TABLE IF NOT EXISTS session (id VARCHAR PRIMARY KEY, user_id VARCHAR REFERENCES user_ (id));"
) ;
) ;
const router = express . Router ( ) ;
const router = express . Router ( ) ;
router . get ( '/' , ( req , res ) => {
res . send ( 'Feh' )
} ) ;
const load _notes _stmt =
const load _notes _stmt =
"SELECT n.id, n.text, n.modified FROM bin_note AS bn"
"SELECT n.id, n.text, n.modified FROM bin_note AS bn"
+ " JOIN note AS n"
+ " JOIN note AS n"
@ -37,7 +35,26 @@ router.post('/load-notes', (req, res)=>{
const bin _id = req . body . bin _id ;
const bin _id = req . body . bin _id ;
db . query ( load _notes _stmt , [ bin _id ] )
db . query ( load _notes _stmt , [ bin _id ] )
. then ( result => {
. then ( result => {
res . json ( { status : 'ok' , bin : { id : bin _id } , notes : result . rows } )
res . json ( { success : true , notes : result . rows } )
} ) ;
} ) ;
const load _bin _stmt =
"SELECT b.id, b.name FROM bin AS b"
+ " WHERE b.id = $1" ;
// {bin_id}
router . post ( '/load-bin' , ( req , res ) => {
const bin _id = req . body . bin _id ;
db . query ( load _bin _stmt , [ bin _id ] )
. then ( result => {
const bin = result . rows [ 0 ] ;
// if a bin with given id was found:
if ( result . rows . length > 0 ) {
res . json ( { success : true , bin : { id : bin . id , name : bin . name } } ) ;
}
else {
res . json ( { success : false , bin : { id : bin _id , name : bin _id } } ) ;
}
} ) ;
} ) ;
// {status: 'ok', bin:{id:bin.id}, notes: bin.notes}
// {status: 'ok', bin:{id:bin.id}, notes: bin.notes}
} ) ;
} ) ;
@ -61,24 +78,33 @@ const upsert_note_stmt =
+ " ON CONFLICT (id)"
+ " ON CONFLICT (id)"
+ " DO UPDATE SET text = EXCLUDED.text, modified = EXCLUDED.modified" ;
+ " DO UPDATE SET text = EXCLUDED.text, modified = EXCLUDED.modified" ;
const upsert _bin _stmt =
const upsert _bin _stmt =
"INSERT INTO bin (id ) VALUES ($1)"
"INSERT INTO bin (id , name ) VALUES ($1, $1)"
+ " ON CONFLICT (id)"
+ " ON CONFLICT (id)"
+ " DO NOTHING" ;
+ " DO NOTHING" ;
const upsert _bin _note _stmt =
const upsert _bin _note _stmt =
"INSERT INTO bin_note (bin_id, note_id) VALUES ($1, $2)"
"INSERT INTO bin_note (bin_id, note_id) VALUES ($1, $2)"
+ " ON CONFLICT (bin_id, note_id)"
+ " ON CONFLICT (bin_id, note_id)"
+ " DO NOTHING" ;
+ " DO NOTHING" ;
const upsert _bin _user _stmt =
"INSERT INTO bin_user (bin_id, user_id)"
+ " (SELECT $1, s.user_id FROM session AS s WHERE s.id = $2)"
+ " ON CONFLICT (bin_id, user_id)"
+ " DO NOTHING" ;
// {bin_id, note_id, text}
// {bin_id, note_id, text}
router . post ( '/save' , ( req , res ) => {
router . post ( '/save' , ( req , res ) => {
const { bin _id , note _id , text } = req . body ;
const { bin _id , note _id , text , session _id } = req . body ;
db . connect ( ) . then ( async ( client ) => {
db . connect ( ) . then ( async ( client ) => {
// TODO: make the following into a transaction:
await client . query ( upsert _note _stmt , [ note _id , text ] ) ;
await client . query ( upsert _note _stmt , [ note _id , text ] ) ;
await client . query ( upsert _bin _stmt , [ bin _id ] ) ;
await client . query ( upsert _bin _stmt , [ bin _id ] ) ;
await client . query ( upsert _bin _note _stmt , [ bin _id , note _id ] ) ;
await client . query ( upsert _bin _note _stmt , [ bin _id , note _id ] ) ;
// if the user is signed-in, ensure the bin and user are associated:
if ( session _id !== '' ) {
await client . query ( upsert _bin _user _stmt , [ bin _id , session _id ] ) ;
}
// don't forget to release back into the Pool:
// don't forget to release back into the Pool:
client . release ( ) ;
client . release ( ) ;
res . json ( { status : 'ok' } ) ;
res . json ( { s uccess: true } ) ;
} ) ;
} ) ;
// {status: 'ok'}
// {status: 'ok'}
} ) ;
} ) ;
@ -88,17 +114,32 @@ const login_check_stmt =
+ " WHERE u.username = $1" ;
+ " WHERE u.username = $1" ;
const register _stmt =
const register _stmt =
"INSERT INTO user_ (id, username, password) VALUES ($1, $2, $3)" ;
"INSERT INTO user_ (id, username, password) VALUES ($1, $2, $3)" ;
const session _create _stmt =
"INSERT INTO session (id, user_id) VALUES ($1, $2)" ;
const user _bin _list _stmt =
"SELECT b.id, b.name FROM bin_user AS bu"
+ " INNER JOIN bin AS b"
+ " ON bu.bin_id = b.id"
+ " WHERE bu.user_id = $1" ;
router . post ( '/login' , ( req , res ) => {
router . post ( '/login' , ( req , res ) => {
const { username , password _from _client } = req . body ;
const { username , password } = req . body ;
const password _from _client = password ;
db . query ( login _check _stmt , [ username ] )
db . query ( login _check _stmt , [ username ] )
. then ( result => {
. then ( result => {
// if there is such a username:
// if there is such a username:
if ( result . rows . length > 0 ) {
if ( result . rows . length > 0 ) {
const row = result . rows [ 0 ] ;
const row = result . rows [ 0 ] ;
const id= row . id , password _from _db = row . password ;
const user_ id= row . id , password _from _db = row . password ;
// if the passwords match:
// if the passwords match:
// TODO: replace `===` with a constant-time comparison function to prevent timing attacks; or better, store passwords as salted hashes and use `===` on that:
if ( password _from _client === password _from _db ) {
if ( password _from _client === password _from _db ) {
res . json ( { success : true , user : { id , username } , session _id : nanoid ( ) } ) ;
const session _id = nanoid ( ) ;
db . query ( session _create _stmt , [ session _id , user _id ] ) ;
db . query ( user _bin _list _stmt , [ user _id ] )
. then ( result => {
const bins = result . rows ;
res . json ( { success : true , user : { id : user _id , username } , session _id , bins } ) ;
} ) ;
}
}
else {
else {
res . json ( { success : false } ) ;
res . json ( { success : false } ) ;
@ -106,15 +147,41 @@ router.post('/login', (req, res)=>{
}
}
// if no such user exists:
// if no such user exists:
else {
else {
const id = nanoid ( ) ;
const user _id = nanoid ( ) ;
db . query ( register _stmt , [ id , username , password _from _client ] )
const session _id = nanoid ( ) ;
db . query ( register _stmt , [ user _id , username , password _from _client ] )
. then ( r => {
. then ( r => {
res . json ( { success : true , user : { id , username } , session _id : nanoid ( ) } ) ;
db . query ( session _create _stmt , [ session _id , user _id ] ) ;
res . json ( { success : true , user : { id : user _id , username } , session _id , bins : [ ] } ) ;
} ) ;
} ) ;
}
}
} ) ;
} ) ;
} ) ;
} ) ;
const delete _session _stmt =
"DELETE FROM session WHERE id = $1" ;
router . post ( '/logout' , ( req , res ) => {
db . query ( delete _session _stmt , [ req . body . session _id ] ) ;
res . json ( { success : true } ) ;
} ) ;
const rename _bin _stmt =
"INSERT INTO bin (id, name) VALUES ($1, $2)"
+ " ON CONFLICT (id)"
+ " DO UPDATE SET name = EXCLUDED.name;" ;
router . post ( '/bin-rename' , ( req , res ) => {
const { bin _id , name , session _id } = req . body ;
db . query ( rename _bin _stmt , [ bin _id , name ] )
. then ( x => {
// ensure user and bin are associated if necessary:
if ( session _id !== '' ) {
// `upsert_bin_user_stmt` is defined above:
db . query ( upsert _bin _user _stmt , [ bin _id , session _id ] ) ;
}
res . json ( { success : true } ) ;
} ) ;
} ) ;
app . use ( '/' , express . json ( ) , router ) ;
app . use ( '/' , express . json ( ) , router ) ;