Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| edc7830a3d |
@@ -107,4 +107,3 @@ dist
|
|||||||
# Stores VSCode versions used for testing VSCode extensions
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
.vscode-test
|
.vscode-test
|
||||||
|
|
||||||
pgdata
|
|
||||||
@@ -35,9 +35,7 @@ The user may type-in his username/password to sign-in; in which case a list of h
|
|||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- remove 'unauthorized bin' error upon proper login, and when current bin is changed (by hash location, or by 'new bin' button)
|
- rename current bin
|
||||||
- remove immer comment which attempts to load sourcemap
|
- optional user sign-in (for listing bins)
|
||||||
- add favicon.ico to nginx/public_html
|
|
||||||
- implement Postgres DB migration system ('up'/'down' scripts)
|
|
||||||
- add pagination
|
- add pagination
|
||||||
- search term highlighting in result note text
|
- search term highlighting in result note text
|
||||||
+6
-6
@@ -17,10 +17,10 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
# production: no reason to use pm2 if docker offers auto-restart
|
# production: no reason to use pm2 if docker offers auto-restart
|
||||||
# development: don't use docker auto-restart; rather pm2, which can watch for file changes
|
# development: don't use docker auto-restart; rather pm2, which can watch for file changes
|
||||||
#restart: always
|
restart: always
|
||||||
user: "node"
|
user: "node"
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=production
|
||||||
volumes:
|
volumes:
|
||||||
- ./node/:/home/node/app
|
- ./node/:/home/node/app
|
||||||
expose:
|
expose:
|
||||||
@@ -29,15 +29,15 @@ services:
|
|||||||
- postgres
|
- postgres
|
||||||
working_dir: /home/node/app
|
working_dir: /home/node/app
|
||||||
# for production (no file watching):
|
# for production (no file watching):
|
||||||
# command: sh -c "yarn install && node server.js"
|
command: sh -c "yarn install && node server.js"
|
||||||
# for development (file watching/reloading):
|
# for development (file watching/reloading):
|
||||||
command: sh -c "yarn install && yarn pm2-dev server.js"
|
# command: sh -c "yarn install && yarn pm2-dev server.js"
|
||||||
postgres:
|
postgres:
|
||||||
image: "postgres:13.1-alpine"
|
image: "postgres:13.1-alpine"
|
||||||
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"
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import Note from './Note.js';
|
import Note from './Note.js';
|
||||||
import {
|
import {
|
||||||
preventDblClickSelection,
|
|
||||||
new_note_handler,
|
new_note_handler,
|
||||||
new_note_by_dblclick_handler,
|
new_note_by_dblclick_handler,
|
||||||
search_term_change_handler,
|
search_term_change_handler,
|
||||||
@@ -27,7 +26,6 @@ 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...'),
|
||||||
@@ -69,7 +67,7 @@ function App(vnode_init){
|
|||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
m('.main', {key: 'main'}, [
|
m('.main', {key: 'main'}, [
|
||||||
m('.notes', {ondblclick: o(new_note_by_dblclick_handler), onmousedown:preventDblClickSelection}, s.notes.map(note_state =>
|
m('.notes', {ondblclick: o(new_note_by_dblclick_handler)}, s.notes.map(note_state =>
|
||||||
m(Note, {state:s, note_state, dispatch, key: note_state.note_id})
|
m(Note, {state:s, note_state, dispatch, key: note_state.note_id})
|
||||||
))
|
))
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ function Note(vnode_init){
|
|||||||
return m('.note', [
|
return m('.note', [
|
||||||
m('textarea', {key: 'textarea', onchange: text_change_handler.bind(null, note_state, dispatch), oncreate: is_focused?({dom})=>{ dom.focus(); delete note_state.is_focused; }:null }, note_state.temp_text),
|
m('textarea', {key: 'textarea', onchange: text_change_handler.bind(null, note_state, dispatch), oncreate: is_focused?({dom})=>{ dom.focus(); delete note_state.is_focused; }:null }, note_state.temp_text),
|
||||||
m('.buttons', {key: 'editing-buttons'}, [
|
m('.buttons', {key: 'editing-buttons'}, [
|
||||||
m('button', {key: 'cancel-button', onclick: cancel_handler.bind(null, state, note_state, dispatch) }, 'Cancel'),
|
m('button', {key: 'cancel-button', onclick: cancel_handler.bind(null, note_state, dispatch) }, 'Cancel'),
|
||||||
m('button', {key: 'save-button', onclick: save_handler.bind(null, note_state, dispatch) }, 'Save')
|
m('button', {key: 'save-button', onclick: save_handler.bind(null, note_state, dispatch) }, 'Save')
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1,21 +1,10 @@
|
|||||||
import nanoid from '../nanoid.min.js';
|
import nanoid from '../nanoid.min.js';
|
||||||
import api from '../api.js';
|
import api from '../api.js';
|
||||||
|
|
||||||
const preventDblClickSelection = function(e){
|
|
||||||
// as per [https://stackoverflow.com/a/43321596]
|
|
||||||
if(e.detail>1){
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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=>{
|
||||||
if(res.success===true){
|
|
||||||
dispatch('notes-loaded', res.notes);
|
dispatch('notes-loaded', res.notes);
|
||||||
}
|
|
||||||
else if(res.authorized===false){
|
|
||||||
dispatch('notes-unauthorized');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const runSearch = function(state, dispatch){
|
const runSearch = function(state, dispatch){
|
||||||
@@ -91,26 +80,15 @@ 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=>{
|
||||||
if(res.success===true){
|
|
||||||
dispatch('bin-loaded', res.bin);
|
dispatch('bin-loaded', res.bin);
|
||||||
|
});
|
||||||
api.post('/load-notes', {bin_id})
|
api.post('/load-notes', {bin_id})
|
||||||
.then(res=>{
|
.then(res=>{
|
||||||
if(res.success===true && res.authorized===true){
|
|
||||||
dispatch('notes-loaded', res.notes);
|
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');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export {preventDblClickSelection,
|
export {new_note_handler,
|
||||||
new_note_handler,
|
|
||||||
new_note_by_dblclick_handler,
|
new_note_by_dblclick_handler,
|
||||||
search_term_change_handler,
|
search_term_change_handler,
|
||||||
sorting_change_handler,
|
sorting_change_handler,
|
||||||
|
|||||||
@@ -3,14 +3,8 @@ import api from '../api.js';
|
|||||||
const edit_handler = function(note_state, dispatch){
|
const edit_handler = function(note_state, dispatch){
|
||||||
dispatch('update-note-editing', {id: note_state.note_id, is_editing: true});
|
dispatch('update-note-editing', {id: note_state.note_id, is_editing: true});
|
||||||
};
|
};
|
||||||
const cancel_handler = function(state, note_state, dispatch){
|
const cancel_handler = function(note_state, dispatch){
|
||||||
// TODO: this `if` may cause glitches; keep in mind
|
|
||||||
if(note_state.is_new===true){
|
|
||||||
dispatch('immediately-cancel-note', note_state.note_id);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
dispatch('update-note-editing', {id: note_state.note_id, is_editing: false});
|
dispatch('update-note-editing', {id: note_state.note_id, is_editing: false});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const text_change_handler = function(note_state, dispatch, e){
|
const text_change_handler = function(note_state, dispatch, e){
|
||||||
note_state.temp_text = e.target.value;
|
note_state.temp_text = e.target.value;
|
||||||
|
|||||||
@@ -3,18 +3,11 @@ 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 => {
|
||||||
if(res.success===false && res.authorized===false){
|
|
||||||
dispatch('bin-unauthorized');
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
dispatch('bin-loaded', res.bin);
|
dispatch('bin-loaded', res.bin);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
api.post('/load-notes', {bin_id})
|
api.post('/load-notes', {bin_id})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if(res.success===true){
|
|
||||||
dispatch('notes-loaded', res.notes);
|
dispatch('notes-loaded', res.notes);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,18 +22,11 @@ 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=>{
|
||||||
if(res.success==true){
|
|
||||||
dispatch('bin-loaded', res.bin);
|
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 => {
|
||||||
if(res.success==true){
|
|
||||||
dispatch('notes-loaded', res.notes);
|
dispatch('notes-loaded', res.notes);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,16 +14,6 @@ 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%;
|
||||||
@@ -46,93 +36,19 @@ body{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app > .main{
|
.app > .main{
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
flex: 1;
|
|
||||||
}
|
}
|
||||||
.app > .main > .notes{
|
.app > .main > .notes{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
flex: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.note{
|
.note{
|
||||||
width: 17rem;
|
width: 17rem;
|
||||||
height: 17rem;
|
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
background-color: #aaccaa;
|
background-color: #aaccaa;
|
||||||
padding: 0.6rem;
|
padding: 0.6rem;
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
animation: bounce-in-fwd 0.8s ease-in-out both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note > textarea{
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
resize: none;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #aaccaa;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------
|
|
||||||
* Generated by Animista on 2021-4-12 15:12:16
|
|
||||||
* Licensed under FreeBSD License.
|
|
||||||
* See http://animista.net/license for more info.
|
|
||||||
* w: http://animista.net, t: @cssanimista
|
|
||||||
* ---------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ----------------------------------------
|
|
||||||
* animation bounce-in-fwd
|
|
||||||
* ----------------------------------------
|
|
||||||
*/
|
|
||||||
@keyframes bounce-in-fwd {
|
|
||||||
0% {
|
|
||||||
transform: scale(0);
|
|
||||||
animation-timing-function: ease-in;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
38% {
|
|
||||||
transform: scale(1);
|
|
||||||
animation-timing-function: ease-out;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
55% {
|
|
||||||
transform: scale(0.7);
|
|
||||||
animation-timing-function: ease-in;
|
|
||||||
}
|
|
||||||
72% {
|
|
||||||
transform: scale(1);
|
|
||||||
animation-timing-function: ease-out;
|
|
||||||
}
|
|
||||||
81% {
|
|
||||||
transform: scale(0.84);
|
|
||||||
animation-timing-function: ease-in;
|
|
||||||
}
|
|
||||||
89% {
|
|
||||||
transform: scale(1);
|
|
||||||
animation-timing-function: ease-out;
|
|
||||||
}
|
|
||||||
95% {
|
|
||||||
transform: scale(0.95);
|
|
||||||
animation-timing-function: ease-in;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(1);
|
|
||||||
animation-timing-function: ease-out;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,10 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Tabler</title>
|
<title>Tabler</title>
|
||||||
|
<!--<link rel="stylesheet" href="tachyons.min.css"></link>-->
|
||||||
<link rel="stylesheet" href="index.css"></link>
|
<link rel="stylesheet" href="index.css"></link>
|
||||||
|
<!--<link rel="stylesheet" href="mini-default.min.css"></link>-->
|
||||||
|
<!--<link rel="stylesheet" href="animate.min.css"></link>-->
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<script src="./mithril.min.js"></script>
|
<script src="./mithril.min.js"></script>
|
||||||
|
|||||||
@@ -78,11 +78,6 @@ const refAll = function(s, o, key, new_model_ids){
|
|||||||
This routine would need to be called for state.notes, which contains refs of the form: `state.notes[n].note_id`
|
This routine would need to be called for state.notes, which contains refs of the form: `state.notes[n].note_id`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: doesn't update anything that references the deleted record; results in dangling pointers:
|
|
||||||
const disintegrate = function(s, id){
|
|
||||||
delete s.db[id];
|
|
||||||
};
|
|
||||||
|
|
||||||
function addToBinListIfLoggedIn(state){
|
function addToBinListIfLoggedIn(state){
|
||||||
const s = state;
|
const s = state;
|
||||||
// if user is logged in:
|
// if user is logged in:
|
||||||
@@ -99,16 +94,13 @@ 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 = []; s.error_message=''; },
|
'bin-requested': (s, bin_id) => { i(s,{id:bin_id}); ref(s,s,'bin_id',bin_id); s.notes = []; },
|
||||||
'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, 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.'; },
|
'update-note-text': (s, {id, text}) => { const note_s=s.notes.find(n=>n.note_id===id); 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
|
||||||
'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-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; },
|
||||||
'save-note-edit': (s, {id, text}) => { const note_s=s.notes.find(n=>n.note_id===id); s.db[note_s.note_id].model.text=text; note_s.temp_text=text; note_s.is_editing=false; },
|
'save-note-edit': (s, {id, text}) => { const note_s=s.notes.find(n=>n.note_id===id); s.db[note_s.note_id].model.text=text; note_s.temp_text=text; note_s.is_editing=false; },
|
||||||
'update-sorting': (s, sorting) => { s.sorting=sorting; },
|
'update-sorting': (s, sorting) => { s.sorting=sorting; },
|
||||||
@@ -148,8 +140,7 @@ 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: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+57
-92
@@ -30,13 +30,40 @@ 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";
|
||||||
|
// {bin_id}
|
||||||
|
router.post('/load-notes', (req, res)=>{
|
||||||
|
const bin_id = req.body.bin_id;
|
||||||
|
db.query(load_notes_stmt, [bin_id])
|
||||||
|
.then(result => {
|
||||||
|
res.json({success:true, notes:result.rows})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const load_bin_stmt =
|
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"
|
"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
|
+" 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"
|
+" ON bu.bin_id = b.id"
|
||||||
+" FULL JOIN session AS s"
|
+" INNER JOIN session AS s"
|
||||||
+" ON s.id = $2"
|
+" ON (bu.bin_id IS NULL) OR (s.id = $2 AND s.user_id = bu.user_id)"
|
||||||
+" WHERE b.id = $1";
|
+" WHERE b.id = $1";
|
||||||
|
router.post('/load-bin', (req, res)=>{
|
||||||
|
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 an associated user, it can only be accessed by that user
|
||||||
|
db.query(load_bin_stmt, [bin_id, session_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:true, bin:{id:bin_id, name:bin_id}});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// {status: 'ok', bin:{id:bin.id}, notes: bin.notes}
|
||||||
|
});
|
||||||
|
|
||||||
const search_stmt =
|
const search_stmt =
|
||||||
"SELECT n.id, n.text, n.modified FROM note AS n"
|
"SELECT n.id, n.text, n.modified FROM note AS n"
|
||||||
+" INNER JOIN bin_note AS bn"
|
+" INNER JOIN bin_note AS bn"
|
||||||
@@ -44,6 +71,15 @@ const search_stmt =
|
|||||||
+" WHERE n.text LIKE '%' || $1 || '%'"; // need to use string concat otherwise the `$1` is viewed as part of the string instead of a placeholder
|
+" 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_desc_stmt = " ORDER BY n.modified DESC";
|
||||||
const order_asc_stmt = " ORDER BY n.modified ASC";
|
const order_asc_stmt = " ORDER BY n.modified ASC";
|
||||||
|
// {search_term, sorting, bin_id}
|
||||||
|
router.post('/search', (req, res)=>{
|
||||||
|
const {search_term, bin_id, sorting} = req.body;
|
||||||
|
db.query(search_stmt+(sorting==='old->new'?order_asc_stmt:order_desc_stmt), [search_term, bin_id])
|
||||||
|
.then(result => {
|
||||||
|
res.json({success:true, notes:result.rows});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const upsert_note_stmt =
|
const upsert_note_stmt =
|
||||||
"INSERT INTO note (id, text, modified) VALUES ($1, $2, NOW())"
|
"INSERT INTO note (id, text, modified) VALUES ($1, $2, NOW())"
|
||||||
+" ON CONFLICT (id)"
|
+" ON CONFLICT (id)"
|
||||||
@@ -61,93 +97,6 @@ const upsert_bin_user_stmt =
|
|||||||
+" (SELECT $1, s.user_id FROM session AS s WHERE s.id = $2)"
|
+" (SELECT $1, s.user_id FROM session AS s WHERE s.id = $2)"
|
||||||
+" ON CONFLICT (bin_id, user_id)"
|
+" ON CONFLICT (bin_id, user_id)"
|
||||||
+" DO NOTHING";
|
+" 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}
|
|
||||||
router.post('/load-notes', (req, res)=>{
|
|
||||||
const {bin_id, session_id} = req.body;
|
|
||||||
db.query(load_bin_stmt, [bin_id, session_id])
|
|
||||||
.then(bin_result =>{
|
|
||||||
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});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/load-bin', (req, res)=>{
|
|
||||||
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 an associated user, it can only be accessed by that user
|
|
||||||
db.query(load_bin_stmt, [bin_id, session_id])
|
|
||||||
.then(result => {
|
|
||||||
const bin = result.rows[0];
|
|
||||||
// if a bin with given id was found:
|
|
||||||
if(result.rows.length>0){
|
|
||||||
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{
|
|
||||||
res.json({success:true, authorized:true, bin:{id:bin_id, name:bin_id}});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// {status: 'ok', bin:{id:bin.id}, notes: bin.notes}
|
|
||||||
});
|
|
||||||
|
|
||||||
// {search_term, sorting, bin_id}
|
|
||||||
router.post('/search', (req, res)=>{
|
|
||||||
const {search_term, bin_id, sorting} = req.body;
|
|
||||||
db.query(search_stmt+(sorting==='old->new'?order_asc_stmt:order_desc_stmt), [search_term, bin_id])
|
|
||||||
.then(result => {
|
|
||||||
res.json({success:true, notes:result.rows});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// {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;
|
||||||
@@ -167,6 +116,18 @@ 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;
|
||||||
@@ -204,13 +165,17 @@ 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])
|
||||||
|
|||||||
Reference in New Issue
Block a user