refuses to load private bins; fixed NodeJS startup error connectiong to Postgres

dev
brian 4 years ago
parent 8432255890
commit 555e33040b

1
.gitignore vendored

@ -107,3 +107,4 @@ dist
# Stores VSCode versions used for testing VSCode extensions # Stores VSCode versions used for testing VSCode extensions
.vscode-test .vscode-test
pgdata

@ -37,7 +37,7 @@ services:
environment: environment:
- POSTGRES_USER=pastebin - POSTGRES_USER=pastebin
- POSTGRES_PASSWORD=buginoo - POSTGRES_PASSWORD=buginoo
#volumes: volumes:
# - ./postgres/pgdata:/var/lib/postgresql/data - ./postgres/pgdata:/var/lib/postgresql/data
expose: expose:
- "5432" - "5432"

@ -27,6 +27,7 @@ function App(vnode_init){
const o = function(handler){ return handler.bind(null, s, dispatch); } const o = function(handler){ return handler.bind(null, s, dispatch); }
const o1 = function(handler, p1){ return handler.bind(null, s, dispatch, p1); } const o1 = function(handler, p1){ return handler.bind(null, s, dispatch, p1); }
return m('.app', {key: 'app'}, [ return m('.app', {key: 'app'}, [
m('.error-message', {key:'error-message'}, s.error_message),
m('.top', {key: 'top'}, [ m('.top', {key: 'top'}, [
m('.top-left', {key: 'top-left'}, [ m('.top-left', {key: 'top-left'}, [
m('button', {key: 'button', onclick: o(new_note_handler)}, 'New Note...'), m('button', {key: 'button', onclick: o(new_note_handler)}, 'New Note...'),

@ -10,7 +10,12 @@ const preventDblClickSelection = function(e){
const load_notes = function(state, dispatch){ const load_notes = function(state, dispatch){
api.post('/load-notes', {bin_id: state.bin_id}) api.post('/load-notes', {bin_id: state.bin_id})
.then(res=>{ .then(res=>{
dispatch('notes-loaded', res.notes); if(res.success===true){
dispatch('notes-loaded', res.notes);
}
else if(res.authorized===false){
dispatch('notes-unauthorized');
}
}); });
}; };
const runSearch = function(state, dispatch){ const runSearch = function(state, dispatch){
@ -86,11 +91,21 @@ const choose_bin_handler = function(state, dispatch, bin_id){
dispatch('bin-requested', bin_id); dispatch('bin-requested', bin_id);
api.post('/load-bin', {bin_id}) api.post('/load-bin', {bin_id})
.then(res=>{ .then(res=>{
dispatch('bin-loaded', res.bin); if(res.success===true){
}); dispatch('bin-loaded', res.bin);
api.post('/load-notes', {bin_id}) api.post('/load-notes', {bin_id})
.then(res=>{ .then(res=>{
dispatch('notes-loaded', res.notes); if(res.success===true && res.authorized===true){
dispatch('notes-loaded', res.notes);
}
else if(res.success===false && res.authorized===false){
dispatch('notes-unauthorized');
}
});
}
else if(res.success===false && res.authorized==false){
dispatch('bin-unauthorized');
}
}); });
}; };

@ -3,11 +3,18 @@ import api from '../api.js';
const initial_load_bin_handler = function(state, dispatch, bin_id){ const initial_load_bin_handler = function(state, dispatch, bin_id){
api.post('/load-bin', {bin_id}) api.post('/load-bin', {bin_id})
.then(res => { .then(res => {
dispatch('bin-loaded', res.bin); if(res.success===false && res.authorized===false){
dispatch('bin-unauthorized');
}
else{
dispatch('bin-loaded', res.bin);
}
}); });
api.post('/load-notes', {bin_id}) api.post('/load-notes', {bin_id})
.then(res => { .then(res => {
dispatch('notes-loaded', res.notes); if(res.success===true){
dispatch('notes-loaded', res.notes);
}
}); });
}; };
@ -22,11 +29,18 @@ const hash_change_handler = function(state, dispatch, e){
dispatch('bin-requested', bin_id); dispatch('bin-requested', bin_id);
api.post('/load-bin', {bin_id}) api.post('/load-bin', {bin_id})
.then(res=>{ .then(res=>{
dispatch('bin-loaded', res.bin); if(res.success==true){
dispatch('bin-loaded', res.bin);
}
else if(res.authorized===false){
dispatch('bin-unauthorized');
}
}); });
api.post('/load-notes', {bin_id}) api.post('/load-notes', {bin_id})
.then(res => { .then(res => {
dispatch('notes-loaded', res.notes); if(res.success==true){
dispatch('notes-loaded', res.notes);
}
}); });
} }
}; };

@ -14,6 +14,16 @@ body{
} }
/* end full-page app */ /* end full-page app */
.error-message{
position: fixed;
top: 0px;
margin-top: 1rem;
width: 40%;
margin-right: auto;
margin-left: auto;
background-color: #ee8888;
}
.app{ .app{
height: 100%; height: 100%;
width: 100%; width: 100%;

@ -99,12 +99,14 @@ function addToBinListIfLoggedIn(state){
const reducer = handleActions({ const reducer = handleActions({
'new-bin': (s, bin) => { i(s,bin); ref(s,s,'bin_id',bin.id); s.notes=[]; s.temp_bin_name=bin.id; }, 'new-bin': (s, bin) => { i(s,bin); ref(s,s,'bin_id',bin.id); s.notes=[]; s.temp_bin_name=bin.id; },
'bin-requested': (s, bin_id) => { i(s,{id:bin_id}); ref(s,s,'bin_id',bin_id); s.notes = []; }, 'bin-requested': (s, bin_id) => { i(s,{id:bin_id}); ref(s,s,'bin_id',bin_id); s.notes = []; s.error_message=''; },
'bin-loaded': (s, bin) => { i(s,bin); ref(s,s,'bin_id',bin.id); s.temp_bin_name=bin.name; }, 'bin-loaded': (s, bin) => { i(s,bin); ref(s,s,'bin_id',bin.id); s.temp_bin_name=bin.name; },
'bin-unauthorized': (s) => { s.error_message = 'Not authorized to load bin. Please sign-in.'; },
'update-search-term': (s, search_term) => { s.search_term=search_term; }, 'update-search-term': (s, search_term) => { s.search_term=search_term; },
'update-search-results': (s, _notes) => { integrateAll(s,s.notes,'note_id',_notes); s.notes =_notes.map(n=>({is_editing: false, temp_text: '', bin_id: s.bin_id, note_id:n.id})); }, 'update-search-results': (s, _notes) => { integrateAll(s,s.notes,'note_id',_notes); s.notes =_notes.map(n=>({is_editing: false, temp_text: '', bin_id: s.bin_id, note_id:n.id})); },
'add-note': (s, {id, date}) => { i(s, {id: id, text: '', modified: date}); s.notes.unshift({is_editing: true, temp_text: '', bin_id: s.bin_id, is_focused:true, is_new: true, note_id: id}); }, 'add-note': (s, {id, date}) => { i(s, {id: id, text: '', modified: date}); s.notes.unshift({is_editing: true, temp_text: '', bin_id: s.bin_id, is_focused:true, is_new: true, note_id: id}); },
'notes-loaded': (s, _notes) => { integrateAll(s,_notes); s.notes = _notes.map(n=>({is_editing: false, temp_text: n.text, bin_id: s.bin_id, note_id:n.id})); }, 'notes-loaded': (s, _notes) => { integrateAll(s,_notes); s.notes = _notes.map(n=>({is_editing: false, temp_text: n.text, bin_id: s.bin_id, note_id:n.id})); },
'notes-unauthorized': (s) => { s.error_message = 'Not authorized to load notes in bin. Please sign-in.'; },
'immediately-cancel-note': (s, note_id) => { disintegrate(s,note_id); s.notes.splice(s.notes.findIndex(n=>n.note_id===note_id), 1); }, 'immediately-cancel-note': (s, note_id) => { disintegrate(s,note_id); s.notes.splice(s.notes.findIndex(n=>n.note_id===note_id), 1); },
'update-note-text': (s, {id, text}) => { const note_s=s.notes.find(n=>n.note_id===id); note_s.is_new=false; s.db[note_s.note_id].model.text=text; }, // updates underlying note text (i.e. the "model", not the app note_state) "in the background" (e.g. from a server-pushed update), regardless of whether it's being edited; "save" is a separate action, below 'update-note-text': (s, {id, text}) => { const note_s=s.notes.find(n=>n.note_id===id); note_s.is_new=false; s.db[note_s.note_id].model.text=text; }, // updates underlying note text (i.e. the "model", not the app note_state) "in the background" (e.g. from a server-pushed update), regardless of whether it's being edited; "save" is a separate action, below
'update-note-editing': (s, {id, is_editing}) => { const note_s=s.notes.find(n=>n.note_id===id); note_s.is_editing=is_editing; note_s.temp_text=s.db[note_s.note_id].model.text; }, 'update-note-editing': (s, {id, is_editing}) => { const note_s=s.notes.find(n=>n.note_id===id); note_s.is_editing=is_editing; note_s.temp_text=s.db[note_s.note_id].model.text; },
@ -146,7 +148,8 @@ const reducer = handleActions({
ref_count: 1, ref_count: 1,
model: {id: bin_id, name: bin_id, user_id: ''} model: {id: bin_id, name: bin_id, user_id: ''}
} }
} },
error_message: ''
//search_result_notes: [] //search_result_notes: []
}); });

@ -30,22 +30,86 @@ const load_notes_stmt =
+" JOIN note AS n" +" JOIN note AS n"
+" ON n.id = bn.note_id" +" ON n.id = bn.note_id"
+" WHERE bn.bin_id = $1"; +" WHERE bn.bin_id = $1";
const load_bin_stmt =
"SELECT b.id, b.name, bu.user_id as owner_user_id, s.user_id as session_user_id FROM bin AS b"
+" FULL JOIN bin_user AS bu" // we want the bin regardless of whether it has an associated user, hence LEFT JOIN
+" ON bu.bin_id = b.id"
+" FULL JOIN session AS s"
+" ON s.id = $2"
+" WHERE b.id = $1";
const search_stmt =
"SELECT n.id, n.text, n.modified FROM note AS n"
+" INNER JOIN bin_note AS bn"
+" ON bn.note_id = n.id AND bn.bin_id = $2"
+" WHERE n.text LIKE '%' || $1 || '%'"; // need to use string concat otherwise the `$1` is viewed as part of the string instead of a placeholder
const order_desc_stmt = " ORDER BY n.modified DESC";
const order_asc_stmt = " ORDER BY n.modified ASC";
const upsert_note_stmt =
"INSERT INTO note (id, text, modified) VALUES ($1, $2, NOW())"
+" ON CONFLICT (id)"
+" DO UPDATE SET text = EXCLUDED.text, modified = EXCLUDED.modified";
const upsert_bin_stmt =
"INSERT INTO bin (id, name) VALUES ($1, $1)"
+" ON CONFLICT (id)"
+" DO NOTHING";
const upsert_bin_note_stmt =
"INSERT INTO bin_note (bin_id, note_id) VALUES ($1, $2)"
+" ON CONFLICT (bin_id, note_id)"
+" 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";
const login_check_stmt =
"SELECT id, password FROM user_ AS u"
+" WHERE u.username = $1";
const register_stmt =
"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";
const delete_session_stmt =
"DELETE FROM session WHERE id = $1";
const rename_bin_stmt =
"INSERT INTO bin (id, name) VALUES ($1, $2)"
+" ON CONFLICT (id)"
+" DO UPDATE SET name = EXCLUDED.name;";
// {bin_id} // {bin_id}
router.post('/load-notes', (req, res)=>{ router.post('/load-notes', (req, res)=>{
const bin_id = req.body.bin_id; const {bin_id, session_id} = req.body;
db.query(load_notes_stmt, [bin_id]) db.query(load_bin_stmt, [bin_id, session_id])
.then(result => { .then(bin_result =>{
res.json({success:true, notes:result.rows}) if(bin_result.rows.length > 0){
const bin = bin_result.rows[0];
// if it's a public bin:
if(bin.owner_user_id===null){
db.query(load_notes_stmt, [bin_id])
.then(result => {
res.json({success:true, authorized:false, notes:result.rows})
});
}
// if it's a private bin, and the owner is signed-in:
else if(bin.owner_user_id===bin.session_user_id){
db.query(load_notes_stmt, [bin_id])
.then(result => {
res.json({success:true, authorized:true, notes:result.rows})
});
}
else{
res.json({success:false, authorized:false});
}
}
}); });
}); });
const load_bin_stmt =
"SELECT b.id, b.name FROM bin AS b"
+" FULL JOIN bin_user AS bu" // we want the bin regardless of whether it has an associated user, hence LEFT JOIN
+" ON bu.bin_id = b.id"
+" INNER JOIN session AS s"
+" ON (bu.bin_id IS NULL) OR (s.id = $2 AND s.user_id = bu.user_id)"
+" WHERE b.id = $1";
router.post('/load-bin', (req, res)=>{ router.post('/load-bin', (req, res)=>{
const {bin_id, session_id} = req.body; const {bin_id, session_id} = req.body;
// if a bin has no associated user, it's considered public and can be accessed even when not logged-in. // if a bin has no associated user, it's considered public and can be accessed even when not logged-in.
@ -55,22 +119,26 @@ router.post('/load-bin', (req, res)=>{
const bin = result.rows[0]; const bin = result.rows[0];
// if a bin with given id was found: // if a bin with given id was found:
if(result.rows.length>0){ if(result.rows.length>0){
res.json({success:true, bin:{id:bin.id, name:bin.name}}); const bin = result.rows[0];
// if it's a public bin:
if(bin.owner_user_id===null){
res.json({success:true, authorized:false, bin:{id:bin.id, name:bin.name}});
}
// if it's a private bin, and the owner is signed-in:
else if(bin.owner_user_id===bin.session_user_id){
res.json({success:true, authorized:true, bin:{id:bin.id, name:bin.name}});
}
else{
res.json({success:false, authorized:false});
}
} }
else{ else{
res.json({success:true, bin:{id:bin_id, name:bin_id}}); res.json({success:true, authorized:true, 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}
}); });
const search_stmt =
"SELECT n.id, n.text, n.modified FROM note AS n"
+" INNER JOIN bin_note AS bn"
+" ON bn.note_id = n.id AND bn.bin_id = $2"
+" WHERE n.text LIKE '%' || $1 || '%'"; // need to use string concat otherwise the `$1` is viewed as part of the string instead of a placeholder
const order_desc_stmt = " ORDER BY n.modified DESC";
const order_asc_stmt = " ORDER BY n.modified ASC";
// {search_term, sorting, bin_id} // {search_term, sorting, bin_id}
router.post('/search', (req, res)=>{ router.post('/search', (req, res)=>{
const {search_term, bin_id, sorting} = req.body; const {search_term, bin_id, sorting} = req.body;
@ -80,23 +148,6 @@ router.post('/search', (req, res)=>{
}); });
}); });
const upsert_note_stmt =
"INSERT INTO note (id, text, modified) VALUES ($1, $2, NOW())"
+" ON CONFLICT (id)"
+" DO UPDATE SET text = EXCLUDED.text, modified = EXCLUDED.modified";
const upsert_bin_stmt =
"INSERT INTO bin (id, name) VALUES ($1, $1)"
+" ON CONFLICT (id)"
+" DO NOTHING";
const upsert_bin_note_stmt =
"INSERT INTO bin_note (bin_id, note_id) VALUES ($1, $2)"
+" ON CONFLICT (bin_id, note_id)"
+" 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, session_id} = req.body; const {bin_id, note_id, text, session_id} = req.body;
@ -116,18 +167,6 @@ router.post('/save', (req, res)=>{
// {status: 'ok'} // {status: 'ok'}
}); });
const login_check_stmt =
"SELECT id, password FROM user_ AS u"
+" WHERE u.username = $1";
const register_stmt =
"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} = req.body; const {username, password} = req.body;
const password_from_client=password; const password_from_client=password;
@ -165,17 +204,13 @@ router.post('/login', (req, res)=>{
}); });
}); });
const delete_session_stmt =
"DELETE FROM session WHERE id = $1";
router.post('/logout', (req, res)=>{ router.post('/logout', (req, res)=>{
db.query(delete_session_stmt, [req.body.session_id]); db.query(delete_session_stmt, [req.body.session_id]);
res.json({success:true}); 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)=>{ router.post('/bin-rename', (req,res)=>{
const {bin_id, name, session_id} = req.body; const {bin_id, name, session_id} = req.body;
db.query(rename_bin_stmt, [bin_id, name]) db.query(rename_bin_stmt, [bin_id, name])

Loading…
Cancel
Save