* Removed arrows from notification

* Added trash can function
* Tweaked status text to always be the same
* Removed some open second note code
* Edior always focuses on text now
* Added some extra loading note messages
* Notes are now removed from search index when deleted
* Lots more things happen and update in real time on multiple machines
* Shared notes can be reverted
* WAY more tests
* Note Categories are much more reliable
* Lots of code is much cleaner
This commit is contained in:
Max G 2020-05-18 07:45:35 +00:00
parent b0eee636b5
commit 9c4fff7913
18 changed files with 644 additions and 368 deletions

View File

@ -70,9 +70,7 @@
<template> <template>
<div class="popup-body slide-in-bottom" v-on:click="dismiss" v-if="notifications.length > 0"> <div class="popup-body slide-in-bottom" v-on:click="dismiss" v-if="notifications.length > 0">
<div class="popup-row color-fade" v-for="item in notifications"> <div class="popup-row color-fade" v-for="item in notifications">
<i class="disabled angle left icon"></i>
<span>{{ item }}</span> <span>{{ item }}</span>
<i class="disabled angle right icon"></i>
</div> </div>
</div> </div>
</template> </template>

View File

@ -257,7 +257,7 @@
}, },
data: function(){ data: function(){
return { return {
version: '2.1.2', version: '2.2.2',
username: '', username: '',
collapsed: false, collapsed: false,
mobile: false, mobile: false,
@ -329,7 +329,7 @@
.then(response => { .then(response => {
if(response.data && response.data.id){ if(response.data && response.data.id){
// this.$router.push('/notes/open/'+response.data.id) //Redirect to note page if user is not on it
this.$bus.$emit('open_note', response.data.id) this.$bus.$emit('open_note', response.data.id)
this.disableNewNote = false this.disableNewNote = false
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<span> <span>
<span class="clickable" @click="confirmDelete()" v-if="click == 0" data-tooltip="Delete" data-inverted="" data-position="top right"> <span class="clickable" @click="confirmDelete()" v-if="click == 0" data-tooltip="Delete Forever" data-inverted="" data-position="top right">
<i class="trash alternate icon"></i> <i class="trash alternate icon"></i>
</span> </span>
<span class="clickable" @click="actuallyDelete()" @mouseleave="reset" v-if="click == 1" data-tooltip="Click again to delete." data-position="top right" data-inverted=""> <span class="clickable" @click="actuallyDelete()" @mouseleave="reset" v-if="click == 1" data-tooltip="Click again to delete." data-position="top right" data-inverted="">

View File

@ -265,7 +265,7 @@
updated: '', updated: '',
shareUsername: null, shareUsername: null,
diffNoteText: '', diffNoteText: '',
statusText: 'Saved', statusText: 'Saved.',
lastNoteHash: null, lastNoteHash: null,
saveDebounce: null, //Prevent save from being called numerous times quickly saveDebounce: null, //Prevent save from being called numerous times quickly
updated: 'Never', updated: 'Never',
@ -306,16 +306,16 @@
watch: { watch: {
noteid:function(newVal, oldVal){ noteid:function(newVal, oldVal){
if(newVal == this.currentNoteId){ // if(newVal == this.currentNoteId){
return // return
} // }
if(newVal == oldVal){ // if(newVal == oldVal){
return // return
} // }
this.currentNoteId = newVal // this.currentNoteId = newVal
this.loadNote(this.currentNoteId) // this.loadNote(this.currentNoteId)
}, },
urlData(newVal, oldVal){ urlData(newVal, oldVal){
@ -388,17 +388,10 @@
this.lastNoteHash = this.hashString(this.getText()) this.lastNoteHash = this.hashString(this.getText())
// console.log('hash on load', this.lastNoteHash) // console.log('hash on load', this.lastNoteHash)
//focus on open, not on mobile, thats annoying //focus on open, not on mobile, it causes the keyboard to pop up, thats annoying
if(!this.$store.getters.getIsUserOnMobile){ if(!this.$store.getters.getIsUserOnMobile){
// this.editor.focus() this.editor.focus()
this.editor.moveCursorToEnd()
if(this.noteTitle.length == 0){
this.$refs.titleTextarea.focus()
} else {
this.editor.focus()
this.editor.moveCursorToEnd()
}
} }
//Change button states on editor when element is active //Change button states on editor when element is active
@ -812,11 +805,14 @@
loadNote(noteId){ loadNote(noteId){
//Generate a random loading message //Generate a random loading message
let mod = ['Gently','Calmly','Lovingly','Quickly','','','','','','','','','','','','','']
let doing = ['Loading','Loading','Getting','Fetching','Grabbing','Sequencing','Organizing','Untangling','Processing','Refining','Extracting','Fusing','Pruning','Expanding','Enlarging','Transfiguring','Quantizing','Ingratiating','Lumping'] let doing = ['Loading','Loading','Getting','Fetching','Grabbing','Sequencing','Organizing','Untangling','Processing','Refining','Extracting','Fusing','Pruning','Expanding','Enlarging','Transfiguring','Quantizing','Ingratiating','Lumping']
let thing = ['Note','Note','Note','Note','Data','Text','Document','Algorithm','Buffer','Client','Download','File','Frame','Graphics','Hardware','HTML','Interface','Logic','Mainframe','Memory','Media','Nodes','Network','Chaos'] let thing = ['Note','Note','Note','Note','Data','Text','Document','Algorithm','Buffer','Client','Download','File','Frame','Graphics','Hardware','HTML','Interface','Logic','Mainframe','Memory','Media','Nodes','Network','Chaos']
let p1 = doing[Math.floor(Math.random() * doing.length)]
let p2 = thing[Math.floor(Math.random() * thing.length)] let p1 = mod[Math.floor(Math.random() * mod.length)]
this.loadingMessage = p1 + ' ' + p2 let p2 = doing[Math.floor(Math.random() * doing.length)]
let p3 = thing[Math.floor(Math.random() * thing.length)]
this.loadingMessage = `${p1} ${p2} ${p3}`
//Component is activated with NoteId in place, lookup text with associated ID //Component is activated with NoteId in place, lookup text with associated ID
if(this.$store.getters.getLoggedIn){ if(this.$store.getters.getLoggedIn){
@ -1039,7 +1035,7 @@
}, },
onKeyup(){ onKeyup(){
this.statusText = '' this.statusText = 'Modded'
// this.diffText() // this.diffText()
@ -1067,7 +1063,7 @@
const currentNoteText = this.getText() const currentNoteText = this.getText()
const currentHash = this.hashString( currentNoteText ) const currentHash = this.hashString( currentNoteText )
if( this.lastNoteHash == currentHash){ if( this.lastNoteHash == currentHash){
this.statusText = 'Saved' this.statusText = 'Saved.'
return resolve(true) return resolve(true)
} }
@ -1090,7 +1086,7 @@
this.statusText = 'Saving' this.statusText = 'Saving'
axios.post('/api/note/update', postData).then( response => { axios.post('/api/note/update', postData).then( response => {
this.statusText = 'Saved' this.statusText = 'Saved.'
this.updated = Math.round((+new Date)/1000) this.updated = Math.round((+new Date)/1000)
this.modified = true this.modified = true

View File

@ -29,12 +29,10 @@
<!-- Title display --> <!-- Title display -->
<span v-if="note.title.length > 0" <span v-if="note.title.length > 0"
data-test-id="title"
class="big-text"><p>{{ note.title }}</p></span> class="big-text"><p>{{ note.title }}</p></span>
<!-- Sub text display --> <!-- Sub text display -->
<span v-if="note.subtext.length > 0 && !isShowingSearchResults()" <span v-if="note.subtext.length > 0 && !isShowingSearchResults()"
data-test-id="subtext"
class="small-text" class="small-text"
v-html="note.subtext"></span> v-html="note.subtext"></span>
@ -72,13 +70,6 @@
<div class="tool-bar" @click.self="cardClicked" v-if="!titleView"> <div class="tool-bar" @click.self="cardClicked" v-if="!titleView">
<div class="icon-bar"> <div class="icon-bar">
<!-- <span v-if="note.pinned == 1" data-position="top left" data-tooltip="Pinned" data-inverted>
<i class="green pin icon"></i>
</span>
<span v-if="note.archived == 1" data-position="top left" data-tooltip="Archived" data-inverted>
<i class="green archive icon"></i>
</span> -->
<span class="tags" v-if="note.tags"> <span class="tags" v-if="note.tags">
<span v-for="tag in (note.tags.split(','))" class="little-tag">{{ tag }}</span> <span v-for="tag in (note.tags.split(','))" class="little-tag">{{ tag }}</span>
<br> <br>
@ -90,24 +81,43 @@
<span class="teeny-buttons" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }"> <span class="teeny-buttons" :class="{ 'hover-hide':(!$store.getters.getIsUserOnMobile) }">
<i class="teeny-button" data-tooltip="Tags" data-inverted v-on:click="toggleTags(true)"> <span v-if="!note.trashed">
<i class="tags icon"></i>
</i>
<i class="teeny-button" <i class="teeny-button" data-tooltip="Tags" data-inverted v-on:click="toggleTags(true)">
data-tooltip="Archive" <i class="tags icon"></i>
:data-tooltip="note.archived ? 'Un-Archive':'Archive' " </i>
data-inverted v-on:click="archiveNote">
<i class="archive icon" :class="{'green':note.archived}"></i> <i class="teeny-button"
</i> data-tooltip="Archive"
:data-tooltip="note.archived ? 'Un-Archive':'Archive' "
data-inverted v-on:click="archiveNote">
<i class="archive icon" :class="{'green':note.archived}"></i>
</i>
<i class="teeny-button"
:data-tooltip="note.pinned ? 'Un-Pin':'Pin' "
data-inverted v-on:click="pinNote">
<i class="pin icon" :class="{'green':note.pinned}"></i>
</i>
<i class="teeny-button"
data-tooltip="Move to Trash"
data-inverted v-on:click="trashNote()">
<i class="trash icon"></i>
</i>
</span>
<!-- Trash note options -->
<span v-if="note.trashed">
<i class="teeny-button"
data-tooltip="Un-Trash"
data-inverted v-on:click="trashNote()">
<i class="reply icon"></i>
</i>
<delete-button class="teeny-button" :note-id="note.id" />
</span>
<i class="teeny-button"
:data-tooltip="note.pinned ? 'Un-Pin':'Pin' "
data-inverted v-on:click="pinNote">
<i class="pin icon" :class="{'green':note.pinned}"></i>
</i>
<delete-button class="teeny-button" :note-id="note.id" />
</span> </span>
</div> </div>
@ -185,7 +195,7 @@
let postData = {'pinned': !this.note.pinned, 'noteId':this.note.id} let postData = {'pinned': !this.note.pinned, 'noteId':this.note.id}
axios.post('/api/note/setpinned', postData) axios.post('/api/note/setpinned', postData)
.then(data => { .then(data => {
this.$bus.$emit('update_single_note', this.note.id) // this.$bus.$emit('update_single_note', this.note.id)
}) })
.catch(error => { this.$bus.$emit('notification', 'Failed to Pin Note') }) .catch(error => { this.$bus.$emit('notification', 'Failed to Pin Note') })
}, },
@ -197,20 +207,35 @@
//Show message so no one worries where note went //Show message so no one worries where note went
let message = 'Moved to Archive' let message = 'Moved to Archive'
if(postData.archived != 1){ if(postData.archived != 1){
message = 'Move to main list' message = 'Moved to main list'
} }
this.$bus.$emit('notification', message) this.$bus.$emit('notification', message)
this.$bus.$emit('update_single_note', this.note.id) // this.$bus.$emit('update_single_note', this.note.id)
}) })
.catch(error => { this.$bus.$emit('notification', 'Failed to Archive Note') }) .catch(error => { this.$bus.$emit('notification', 'Failed to Archive Note') })
}, },
trashNote(){ //toggleArchived() <- old name
let postData = {'trashed': !this.note.trashed, 'noteId':this.note.id}
axios.post('/api/note/settrashed', postData)
.then(data => {
//Show message so no one worries where note went
let message = 'Moved to Trash'
if(postData.trashed == 0){
message = 'Moved to main list'
}
this.$bus.$emit('notification', message)
})
.catch(error => { this.$bus.$emit('notification', 'Failed to Trash Note') })
},
toggleTags(state){ toggleTags(state){
this.showTagSlideMenu = state this.showTagSlideMenu = state
if(state == false){ if(state == false){
this.$bus.$emit('update_single_note', this.note.id) // this.$bus.$emit('update_single_note', this.note.id)
} }
}, },
@ -330,6 +355,10 @@
color: var(--text_color); color: var(--text_color);
background-color: var(--background_color); background-color: var(--background_color);
} }
.subtext {
display: inline-block;
width: 100%;
}
/*Strict font sizes for card display*/ /*Strict font sizes for card display*/
.small-text { .small-text {

View File

@ -80,8 +80,14 @@
}) })
.catch(error => { this.$bus.$emit('notification', 'Failed to Load Shared') }) .catch(error => { this.$bus.$emit('notification', 'Failed to Load Shared') })
}, },
onRevokeAccess(noteId){ onRevokeAccess(sharedNoteId){
axios.post('/api/note/shareremoveuser', {'noteId':noteId})
const postData = {
'noteId': this.noteId,
'shareUserNoteId': sharedNoteId
}
axios.post('/api/note/shareremoveuser', postData)
.then( ({data}) => { .then( ({data}) => {
console.log(data) console.log(data)
if(data == true){ if(data == true){

View File

@ -25,6 +25,9 @@
<div class="two wide middle aligned center aligned column" v-on:click="menuOpen = false"> <div class="two wide middle aligned center aligned column" v-on:click="menuOpen = false">
<i class="grey close icon"></i> <i class="grey close icon"></i>
</div> </div>
<div class="sixteen wide middle aligned column" v-if="loadedTags.length == 0">
Tags added to Notes will appear here.
</div>
<div class="row hover-row" v-for="tag in loadedTags" v-on:click="onClick(tag.id)" :class="{'green':(activeTags[0] == tag.id)}"> <div class="row hover-row" v-for="tag in loadedTags" v-on:click="onClick(tag.id)" :class="{'green':(activeTags[0] == tag.id)}">
<div class="two wide center aligned column"> <div class="two wide center aligned column">
<i class="grey tag icon"></i> <i class="grey tag icon"></i>

View File

@ -82,10 +82,13 @@
const username = response.data.username const username = response.data.username
const masterKey = response.data.masterKey const masterKey = response.data.masterKey
vm.$store.commit('setLoginToken', {token, username, masterKey}) this.$store.commit('setLoginToken', {token, username, masterKey})
//Setup socket io after user logs in
this.$io.emit('user_connect', token)
//Redirect user to notes section after login //Redirect user to notes section after login
vm.$router.push('/notes') this.$router.push('/notes')
} else { } else {
// this.password = '' // this.password = ''
this.$bus.$emit('notification', 'Incorrect Username or Password') this.$bus.$emit('notification', 'Incorrect Username or Password')

View File

@ -26,6 +26,10 @@
<!-- <span>{{ $store.getters.totals['archivedNotes'] }}</span> --> <!-- <span>{{ $store.getters.totals['archivedNotes'] }}</span> -->
</div> </div>
<div class="ui basic icon button shrinking" v-on:click="updateFastFilters(4)" v-if="$store.getters.totals && $store.getters.totals['trashedNotes'] > 0">
<i class="trash alternate outline icon"></i>
</div>
<tag-display <tag-display
:active-tags="searchTags" :active-tags="searchTags"
v-on:tagClick="tagId => toggleTagFilter(tagId)" v-on:tagClick="tagId => toggleTagFilter(tagId)"
@ -66,11 +70,23 @@
</h2> </h2>
</div> </div>
<h2 v-if="fastFilters['withLinks'] == 1">Notes with Links</h2> <div v-if="fastFilters['onlyArchived'] == 1" class="sixteen wide column">
<h2 v-if="fastFilters['withTags'] == 1">Notes with Tags</h2> <h2>Archived Notes</h2>
<h2 v-if="fastFilters['onlyArchived'] == 1">Archived Notes</h2> </div>
<h2 v-if="fastFilters['onlyShowSharedNotes'] == 1">Shared Notes</h2>
<h2 v-if="fastFilters['onlyShowEncrypted'] == 1">Password Protected - No longer supported</h2> <div class="sixteen wide column" v-if="fastFilters['onlyShowTrashed'] == 1">
<h2 >Trash
<span>({{ $store.getters.totals['trashedNotes'] }})</span>
<div class="ui right floated basic button" data-tooltip="This doesn't work yet">
<i class="poo storm icon"></i>
Empty Trash
</div>
</h2>
</div>
<div class="sixteen wide column" v-if="fastFilters['onlyShowSharedNotes'] == 1">
<h2>Shared Notes</h2>
</div>
<!-- Note title card display --> <!-- Note title card display -->
<div class="sixteen wide column"> <div class="sixteen wide column">
@ -124,15 +140,11 @@
<input-notes <input-notes
v-if="activeNoteId1 != null" v-if="activeNoteId1 != null"
:key="'active_note_'+activeNoteId1"
:noteid="activeNoteId1" :noteid="activeNoteId1"
:position="activeNote1Position" :position="activeNote1Position"
:url-data="$route.params" :url-data="$route.params"
ref="note1" /> ref="note1" />
<input-notes
v-if="activeNoteId2 != null"
:noteid="activeNoteId2"
:position="activeNote2Position"
ref="note2" />
</div> </div>
</template> </template>
@ -148,7 +160,7 @@
'input-notes': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'), 'input-notes': () => import(/* webpackChunkName: "NoteInputPanel" */ '@/components/NoteInputPanel.vue'),
'note-title-display-card': require('@/components/NoteTitleDisplayCard.vue').default, 'note-title-display-card': require('@/components/NoteTitleDisplayCard.vue').default,
'fast-filters': require('@/components/FastFilters.vue').default, // 'fast-filters': require('@/components/FastFilters.vue').default,
'search-input': require('@/components/SearchInput.vue').default, 'search-input': require('@/components/SearchInput.vue').default,
'attachment-display': require('@/components/AttachmentDisplayCard').default, 'attachment-display': require('@/components/AttachmentDisplayCard').default,
'counter':require('@/components/AnimatedCounterComponent.vue').default, 'counter':require('@/components/AnimatedCounterComponent.vue').default,
@ -205,7 +217,8 @@
'shared': ['envelope outline', 'Received Notes'], 'shared': ['envelope outline', 'Received Notes'],
'sent': ['paper plane outline', 'Shared Notes'], 'sent': ['paper plane outline', 'Shared Notes'],
'notes': ['file','Notes'], 'notes': ['file','Notes'],
'highlights': ['paragraph', 'Found In Text'] 'highlights': ['paragraph', 'Found In Text'],
'trashed': ['poop', 'Trashed Notes']
}, },
noteSections: { noteSections: {
pinned: [], pinned: [],
@ -213,7 +226,8 @@
shared:[], shared:[],
sent:[], sent:[],
notes: [], notes: [],
highlights: [] highlights: [],
trashed: []
}, },
} }
@ -223,35 +237,47 @@
this.$parent.loginGateway() this.$parent.loginGateway()
this.$io.on('new_note_created', noteId => { this.$io.on('new_note_created', noteId => {
//Do not update note if its open //Do not update note if its open
if(this.activeNoteId1 != noteId){ if(this.activeNoteId1 != noteId){
this.updateSingleNote(parseInt(noteId)) this.$store.dispatch('fetchAndUpdateUserTotals')
this.updateSingleNote(noteId)
}
})
this.$io.on('note_attribute_modified', noteId => {
//Do not update note if its open
if(this.activeNoteId1 != noteId){
this.$store.dispatch('fetchAndUpdateUserTotals')
this.updateSingleNote(noteId)
} }
}) })
//Update title cards when new note text is saved //Update title cards when new note text is saved
this.$io.on('new_note_text_saved', ({noteId, hash}) => { this.$io.on('new_note_text_saved', ({noteId, hash}) => {
//Do not update note if its open //Do not update note if its open
if(this.activeNoteId1 != noteId){ if(this.activeNoteId1 != noteId){
this.updateSingleNote(parseInt(noteId)) this.updateSingleNote(noteId)
} }
}) })
//Update totals for app //Update totals for app
this.$store.dispatch('fetchAndUpdateUserTotals') this.$store.dispatch('fetchAndUpdateUserTotals')
//Close note event
this.$bus.$on('close_active_note', ({position, noteId, modified}) => { this.$bus.$on('close_active_note', ({position, noteId, modified}) => {
this.closeNote(position) this.closeNote()
this.$store.dispatch('fetchAndUpdateUserTotals')
if(modified){ if(modified){
this.$store.dispatch('fetchAndUpdateUserTotals')
this.updateSingleNote(parseInt(noteId)) this.updateSingleNote(parseInt(noteId))
} }
}) })
this.$bus.$on('update_single_note', (noteId) => { // this.$bus.$on('update_single_note', (noteId) => {
this.updateSingleNote(noteId) // this.updateSingleNote(noteId)
}) // })
this.$bus.$on('note_deleted', (noteId) => { this.$bus.$on('note_deleted', (noteId) => {
//Remove deleted note from set, its deleted //Remove deleted note from set, its deleted
@ -320,7 +346,7 @@
this.$bus.$off('note_reload') this.$bus.$off('note_reload')
this.$bus.$off('close_active_note') this.$bus.$off('close_active_note')
this.$bus.$off('update_single_note') // this.$bus.$off('update_single_note')
this.$bus.$off('note_deleted') this.$bus.$off('note_deleted')
this.$bus.$off('update_fast_filters') this.$bus.$off('update_fast_filters')
this.$bus.$off('update_search_term') this.$bus.$off('update_search_term')
@ -364,30 +390,8 @@
} }
}, },
closeNote(position){ closeNote(position){
//One note open, close that note this.activeNoteId1 = null
if(position == 0){ this.$router.push('/notes')
this.activeNoteId1 = null
this.activeNoteId2 = null
}
//Right note closed, thats 1
if(position == 1){
this.activeNoteId1 = null
}
if(position == 2){
this.activeNoteId2 = null
}
//IF two notes get opened, update ID of open note
if(this.activeNoteId1 || this.activeNoteId2){
this.$router.push('/notes/open/'+Math.max(this.activeNoteId1, this.activeNoteId2))
} else {
//No notes are open, just show notes page
this.$router.push('/notes')
}
this.activeNote1Position = 0
this.activeNote2Position = 0
}, },
toggleTagFilter(tagId){ toggleTagFilter(tagId){
@ -479,6 +483,8 @@
// @TODO Don't even trigger this if the note wasn't changed // @TODO Don't even trigger this if the note wasn't changed
updateSingleNote(noteId){ updateSingleNote(noteId){
noteId = parseInt(noteId)
//Find local note, if it exists; continue //Find local note, if it exists; continue
@ -656,33 +662,58 @@
//Sort notes into defined sections //Sort notes into defined sections
notes.forEach(note => { notes.forEach(note => {
//Only show trashed notes when trashed
if(this.fastFilters.onlyShowTrashed == 1){
if(note.trashed == 1){
this.noteSections.trashed.push(note)
}
return
}
if(note.trashed == 1){
return
}
//Show archived notes //Show archived notes
if(note.archived == 1 && this.fastFilters.onlyArchived == 1){ if(this.fastFilters.onlyArchived == 1){
this.noteSections.archived.push(note)
if(note.pinned == 1 && note.archived == 1){
this.noteSections.pinned.push(note)
return
}
if(note.archived == 1){
this.noteSections.archived.push(note)
}
return return
} }
if(note.shareUsername != null){ if(note.archived == 1){
this.noteSections.shared.push(note)
return return
} }
//Only show sent notes section if shared is selected //Only show sent notes section if shared is selected
if(note.shared == 2 && this.fastFilters.onlyShowSharedNotes == 1){ if(this.fastFilters.onlyShowSharedNotes == 1){
this.noteSections.sent.push(note)
return if(note.shared == 2){
} this.noteSections.sent.push(note)
if(note.note_highlights.length > 0){ }
this.noteSections.highlights.push(note) if(note.shareUsername != null){
this.noteSections.shared.push(note)
}
return return
} }
//Show shared notes on main list but not notes shared with you
if(note.shareUsername != null){ return }
// Pinned notes are always first, they can appear in the archive // Pinned notes are always first, they can appear in the archive
if(note.pinned == 1){ if(note.pinned == 1){
this.noteSections.pinned.push(note) this.noteSections.pinned.push(note)
return return
} }
//If the note is not archived, push it.
if(note.archived != 1 && this.fastFilters.onlyArchived != 1){ //Push to default note section
this.noteSections.notes.push(note) this.noteSections.notes.push(note)
}
return
}) })
}, },
@ -692,14 +723,13 @@
this.searchTerm = '' this.searchTerm = ''
this.searchTags = [] this.searchTags = []
this.fastFilters = {} this.fastFilters = {}
this.updateFastFilters(5)
this.foundAttachments = [] //Remove all attachments this.foundAttachments = [] //Remove all attachments
this.$bus.$emit('reset_fast_filters') // this.$bus.$emit('reset_fast_filters')
//Load initial batch, then tags, then other batch //Load initial batch, then tags, then other batch
this.search(true, this.firstLoadBatchSize) this.search(true, this.firstLoadBatchSize)
.then( () => { .then( () => {
//Load a larger batch once first batch has loaded //Load a larger batch once first batch has loaded
return this.search(false, this.batchSize, true) return this.search(false, this.batchSize, true)
}) })
@ -711,6 +741,7 @@
//clear out tags //clear out tags
this.searchTags = [] this.searchTags = []
this.loadingInProgress = false
//A little hacky, brings user to notes page then filters on click //A little hacky, brings user to notes page then filters on click
if(this.$route.name != 'Note Page'){ if(this.$route.name != 'Note Page'){
@ -725,7 +756,8 @@
'withTags', // 'Only Show Notes with Tags' 'withTags', // 'Only Show Notes with Tags'
'onlyArchived', //'Only Show Archived Notes' 'onlyArchived', //'Only Show Archived Notes'
'onlyShowSharedNotes', //Only show shared notes 'onlyShowSharedNotes', //Only show shared notes
'onlyShowEncrypted', 'onlyShowTrashed',
'notesHome',
] ]
let filter = {} let filter = {}

View File

@ -123,6 +123,7 @@ export default new Vuex.Store({
//Save all the totals for the user //Save all the totals for the user
state.userTotals = totalsObject state.userTotals = totalsObject
// console.log('-------------')
// Object.keys(totalsObject).forEach( key => { // Object.keys(totalsObject).forEach( key => {
// console.log(key + ' -- ' + totalsObject[key]) // console.log(key + ' -- ' + totalsObject[key])
// }) // })

View File

@ -37,11 +37,9 @@ var io = require('socket.io')(http, {
path:'/socket' path:'/socket'
}); });
// Make io accessible to our router //Set socket IO as a global in the app
app.use(function(req,res,next){ global.SocketIo = io
req.io = io;
next();
});
io.on('connection', function(socket){ io.on('connection', function(socket){
@ -49,7 +47,7 @@ io.on('connection', function(socket){
//When a user connects, add them to their own room //When a user connects, add them to their own room
// This allows the server to emit events to that specific user // This allows the server to emit events to that specific user
// access socket.io in the controller with req.io // access socket.io in the controller with SocketIo global
socket.on('user_connect', token => { socket.on('user_connect', token => {
Auth.decodeToken(token) Auth.decodeToken(token)
.then(userData => { .then(userData => {
@ -138,12 +136,14 @@ app.use(function(req, res, next){
// Test Area // Test Area
// -> right here const printResults = true
// let UserTest = require('@models/User') let UserTest = require('@models/User')
// let NoteTest = require('@models/Note') let NoteTest = require('@models/Note')
// UserTest.keyPairTest() UserTest.keyPairTest('genMan2', '1', printResults)
// .then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey)) .then( ({testUserId, masterKey}) => NoteTest.test(testUserId, masterKey, printResults))
// .then( message => { console.log(message) }) .then( message => {
if(printResults) console.log(message)
})
// Test Area // Test Area

View File

@ -117,6 +117,9 @@ Attachment.update = (userId, attachmentId, updatedText, noteId) => {
Attachment.delete = (userId, attachmentId, urlDelete = false) => { Attachment.delete = (userId, attachmentId, urlDelete = false) => {
let attachment = null
let noteExists = true
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
db.promise() db.promise()
.query('SELECT * FROM attachment WHERE id = ? AND user_id = ? LIMIT 1', [attachmentId, userId]) .query('SELECT * FROM attachment WHERE id = ? AND user_id = ? LIMIT 1', [attachmentId, userId])
@ -127,21 +130,32 @@ Attachment.delete = (userId, attachmentId, urlDelete = false) => {
return resolve(true) return resolve(true)
} }
//Pull data we want out of attachment = rows[0][0]
let row = rows[0][0]
let url = row.url return db.promise().query('SELECT count(id) as `exists` FROM note WHERE id = ?', [attachment.note_id])
const noteId = row.note_id
})
.then((rows, fields) => {
noteExists = (rows[0]['exists'] > 0)
let url = attachment.url
const noteId = attachment.note_id
//Try to delete file and thumbnail //Try to delete file and thumbnail
try { try {
fs.unlinkSync(filePath+row.file_location) fs.unlinkSync(filePath+attachment.file_location)
} catch(err) { console.error('File Does not exist') } } catch(err) { console.error('File Does not exist') }
try { try {
fs.unlinkSync(filePath+'thumb_'+row.file_location) fs.unlinkSync(filePath+'thumb_'+attachment.file_location)
} catch(err) { console.error('Thumbnail Does not exist') } } catch(err) { console.error('Thumbnail Does not exist') }
//Do not delete link attachments, just hide them. They will be deleted if removed from note //Do not delete link attachments, just hide them. They will be deleted if removed from note or if note is deleted
if(row.attachment_type == 1 && !urlDelete){ if(attachment.attachment_type == 1 && !urlDelete && noteExists){
db.promise() db.promise()
.query(`UPDATE attachment SET visible = 0 WHERE id = ?`, [attachmentId]) .query(`UPDATE attachment SET visible = 0 WHERE id = ?`, [attachmentId])
.then((rows, fields) => { }) .then((rows, fields) => { })

View File

@ -14,63 +14,54 @@ const crypto = require('crypto')
const cs = require('@helpers/CryptoString') const cs = require('@helpers/CryptoString')
const rp = require('request-promise'); const rp = require('request-promise');
const fs = require('fs') const fs = require('fs')
const gm = require('gm') const gm = require('gm')
Note.test = (userId, masterKey) => { Note.test = (userId, masterKey, printResults) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let testNoteId = 0
Note.create(null, userId, '','', masterKey) let testNoteId = 0
let testNoteId2 = 0
let sharedNoteId = 0 //ID of note shared with user
const shareUserId = 61
Note.create(userId, 'Random Note','With Random Text dogs', masterKey)
.then( newNoteId => {
if(printResults) console.log('Test: Created Note -> ', newNoteId)
return Note.create(userId, 'Yo, note','a second test note cheese mate', masterKey)
})
.then( newNoteId => {
if(printResults) console.log('Test: Created Note -> ', newNoteId)
testNoteId2 = newNoteId
//Create a blank note to test updating note and reindexing it
return Note.create(userId, '','', masterKey)
})
.then(newNoteId => { .then(newNoteId => {
console.log('Test: Create Note - Pass') if(printResults) console.log('Test: Created Note -> ', newNoteId)
testNoteId = newNoteId testNoteId = newNoteId
return Note.update return Note.update
(null, userId, testNoteId, 'Note text', 'Test Note beans Title', 0, 0, 0, 'hash', masterKey) (userId, testNoteId, 'Note text', 'Test Note beans Title', 0, 0, 0, 'hash', masterKey)
}) })
.then(() => { .then(() => {
console.log('Test: Update Note - Pass') if(printResults) console.log('Test: Update Note '+testNoteId+' - Pass')
return Note.get(userId, testNoteId, masterKey)
})
.then(updatedText => {
console.log('Test: Open Updated Note - Pass')
const shareUserId = 61
return ShareNote.migrateNoteToShared(userId, testNoteId, shareUserId, masterKey)
})
.then(shareResults => {
console.log('Test: Set Note To Shared - Pass')
return Note.get(userId, testNoteId, masterKey) return Note.get(userId, testNoteId, masterKey)
}) })
.then(() => { .then(() => {
console.log('Test: Open Shared Note - Pass') if(printResults) console.log('Test: Open Updated Note - Pass')
return Note.update
(null, userId, testNoteId, 'Shared Update', 'Test Note beans Title', 0, 0, 0, 'hash', masterKey)
})
.then(() => {
console.log('Test: Update Shared Note - Pass')
return Note.reindex(userId, masterKey) return Note.reindex(userId, masterKey)
})
.then( reindexResults => {
console.log(`Test: Reindex Notes - ${reindexResults?'Pass':'Fail'}`) })
.then(() => {
if(printResults) console.log('Test: Reindex normal note - Pass')
return Note.encryptedIndexSearch(userId, 'beans', null, masterKey) return Note.encryptedIndexSearch(userId, 'beans', null, masterKey)
@ -78,17 +69,97 @@ Note.test = (userId, masterKey) => {
.then(textSearchResults => { .then(textSearchResults => {
if(textSearchResults['ids'] && textSearchResults['ids'].length >= 1){ if(textSearchResults['ids'] && textSearchResults['ids'].length >= 1){
console.log('Test: Search Index - Pass') if(printResults) console.log('Test: Normal Note Search Index - Pass')
} else { console.log('Test: Search Index - Fail') } } else { console.log('Test: Search Index - Fail') }
return Note.delete(userId, testNoteId) return ShareNote.migrateNoteToShared(userId, testNoteId, shareUserId, masterKey)
})
.then(({success, shareUserId, sharedUserNoteId}) => {
if(printResults) console.log('Test: Set Note To Shared - Pass')
sharedNoteId = sharedUserNoteId
return Note.get(userId, testNoteId, masterKey)
})
.then(() => {
if(printResults) console.log('Test: Open Shared Note - Pass')
return Note.update
(userId, testNoteId, 'Shared Update', 'Test Note yarnsmarf Title', 0, 0, 0, 'hash', masterKey)
})
.then(() => {
if(printResults) console.log('Test: Update Shared Note - Pass')
return Note.reindex(userId, masterKey)
})
.then( reindexResults => {
if(printResults) console.log(`Test: Reindex Notes - ${reindexResults?'Pass':'Fail'}`)
return Note.encryptedIndexSearch(userId, 'yarnsmarf', null, masterKey)
})
.then(textSearchResults => {
if(textSearchResults['ids'] && textSearchResults['ids'].length >= 1){
if(printResults) console.log('Test: Search Index - Pass')
} else { console.log('Test: Search Index - Fail') }
return Note.delete(userId, testNoteId, masterKey)
}) })
.then(results => { .then(results => {
console.log('Test: Delete Note - Pass') if(printResults) console.log('Test: Delete Note - Pass')
return resolve('Test: Complete') return Note.encryptedIndexSearch(userId, 'yarnsmarf', null, masterKey)
})
.then(textSearchResults => {
if(textSearchResults['ids'] && textSearchResults['ids'].length == 0){
if(printResults) console.log('Test: Search Deleted Note Text - Pass')
} else { console.log('Test: Search Deleted Note Text - Fail') }
return Note.delete(shareUserId, sharedNoteId)
})
.then(() => {
if(printResults) console.log('Test: Delete Shared Note - Pass')
return ShareNote.migrateNoteToShared(userId, testNoteId2, shareUserId, masterKey)
})
.then(({success, shareUserId, sharedUserNoteId}) => {
if(printResults) console.log('Test: Created Another New Shared Note - pass')
return ShareNote.removeUserFromShared(userId, testNoteId2, sharedUserNoteId, masterKey)
})
.then(() => {
if(printResults) console.log('Test: Unshared New Shared Note -> convert back to normal note - pass')
return Note.get(userId, testNoteId2, masterKey)
})
.then(() => {
if(printResults) console.log('Test: Decrypt unshared note - pass')
let User = require('@models/User')
return User.deleteUser(userId, '1')
})
.then(results => {
if(printResults) console.log('Test: Delete User Account - Pass')
return resolve('Test: Complete ---')
}) })
}) })
@ -103,7 +174,7 @@ Note.encryptEveryNote = (userId, masterKey) => {
db.promise().query(` db.promise().query(`
SELECT * FROM note SELECT * FROM note
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id) JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
WHERE salt IS NULL AND user_id = ? AND encrypted = 0 AND shared = 0`, [userId]) WHERE salt IS NULL AND user_id = ? AND shared = 0`, [userId])
.then((rows, fields) => { .then((rows, fields) => {
let foundNotes = rows[0] let foundNotes = rows[0]
@ -164,7 +235,7 @@ Note.encryptEveryNote = (userId, masterKey) => {
} }
//Returns insertedId of new note //Returns insertedId of new note
Note.create = (io, userId, noteTitle = '', noteText = '', masterKey) => { Note.create = (userId, noteTitle = '', noteText = '', masterKey) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if(userId == null || userId < 10){ reject('User Id required to create note') } if(userId == null || userId < 10){ reject('User Id required to create note') }
@ -186,13 +257,13 @@ Note.create = (io, userId, noteTitle = '', noteText = '', masterKey) => {
const rawTextId = rows[0].insertId const rawTextId = rows[0].insertId
return db.promise() return db.promise()
.query('INSERT INTO note (user_id, note_raw_text_id, created, quick_note, snippet, snippet_salt) VALUES (?,?,?,?,?,?)', .query('INSERT INTO note (user_id, note_raw_text_id, created, quick_note, snippet, snippet_salt, indexed) VALUES (?,?,?,?,?,?,0)',
[userId, rawTextId, created, 0, snippet, snippetSalt]) [userId, rawTextId, created, 0, snippet, snippetSalt])
}) })
.then((rows, fields) => { .then((rows, fields) => {
if(io){ if(SocketIo){
io.to(userId).emit('new_note_created', rows[0].insertId) SocketIo.to(userId).emit('new_note_created', rows[0].insertId)
} }
// Indexing is done on save // Indexing is done on save
@ -205,7 +276,8 @@ Note.create = (io, userId, noteTitle = '', noteText = '', masterKey) => {
// Called when a note is close // Called when a note is close
// Will attempt to reindex all notes that are flagged in database as not indexed // Will attempt to reindex all notes that are flagged in database as not indexed
// Limit to 100 notes per batch // Limit to 100 notes per batch
Note.reindex = (userId, masterKey) => { // removeId = array of Ids to remove, [122,223]
Note.reindex = (userId, masterKey, removeId = null) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if(!masterKey || masterKey.length == 0){ if(!masterKey || masterKey.length == 0){
@ -216,17 +288,37 @@ Note.reindex = (userId, masterKey) => {
let searchIndex = null let searchIndex = null
let searchIndexSalt = null let searchIndexSalt = null
let foundNotes = null let foundNotes = null
let userPrivateKey = null
//First check if we have any notes to index let User = require('@models/User')
db.promise().query(` User.generateKeypair(userId, masterKey)
SELECT note.id, text, salt FROM note .then(({publicKey, privateKey}) => {
JOIN note_raw_text ON note.note_raw_text_id = note_raw_text.id
WHERE indexed = 0 AND encrypted = 0 AND salt IS NOT NULL userPrivateKey = privateKey
AND user_id = ? LIMIT 100`, [userId]) //First check if we have any notes to index
return db.promise().query(`
SELECT note.id, text, salt, encrypted_share_password_key FROM note
JOIN note_raw_text ON note.note_raw_text_id = note_raw_text.id
WHERE indexed = 0 AND salt IS NOT NULL
AND user_id = ? LIMIT 100`, [userId])
})
.then((rows, fields) => { .then((rows, fields) => {
//Halt execution if there are no new notes //Halt execution if there are no new notes
foundNotes = rows[0] foundNotes = rows[0]
//Remove ID from index but don't reindex text
if(removeId != null){
removeId.forEach(removeId => {
foundNotes.push({
id:removeId,
text:'',
salt: null,
})
})
}
if(foundNotes.length == 0){ if(foundNotes.length == 0){
throw new Error('No new notes to index') throw new Error('No new notes to index')
} }
@ -244,9 +336,9 @@ Note.reindex = (userId, masterKey) => {
//Select all user notes to recreate index //Select all user notes to recreate index
return db.promise().query(` return db.promise().query(`
SELECT note.id, text, salt FROM note SELECT note.id, text, salt, encrypted_share_password_key FROM note
JOIN note_raw_text ON note.note_raw_text_id = note_raw_text.id JOIN note_raw_text ON note.note_raw_text_id = note_raw_text.id
WHERE encrypted = 0 AND user_id = ?`, [userId]) WHERE user_id = ?`, [userId])
.then((rows, fields) => { .then((rows, fields) => {
foundNotes = rows[0] foundNotes = rows[0]
@ -276,6 +368,10 @@ Note.reindex = (userId, masterKey) => {
}) })
.then(rawSearchIndex => { .then(rawSearchIndex => {
if(rawSearchIndex == null){
throw new Error('Search Index Not Found/Decrypted')
}
searchIndex = rawSearchIndex searchIndex = rawSearchIndex
//Remove all instances of IDs from text //Remove all instances of IDs from text
@ -288,7 +384,6 @@ Note.reindex = (userId, masterKey) => {
const removeDoubles = new RegExp(',,',"g") const removeDoubles = new RegExp(',,',"g")
// const removeTrail = new RegExp(',]',"g") // const removeTrail = new RegExp(',]',"g")
searchIndex = searchIndex searchIndex = searchIndex
.replace(removeId, '') .replace(removeId, '')
.replace(removeDoubles, ',') .replace(removeDoubles, ',')
@ -319,7 +414,17 @@ Note.reindex = (userId, masterKey) => {
return resolve(true) return resolve(true)
} }
const noteHtml = cs.decrypt(masterKey, note.salt, note.text) let currentNoteKey = masterKey
//Decrypt shared key if it exists
const encryptedShareKey = note.encrypted_share_password_key
if(encryptedShareKey != null){
currentNoteKey = crypto.privateDecrypt(userPrivateKey,
Buffer.from(encryptedShareKey, 'base64') )
}
//Decrypt text with proper key
const noteHtml = cs.decrypt(currentNoteKey, note.salt, note.text)
const rawText = const rawText =
ProcessText.removeHtml(noteHtml) //Remove HTML ProcessText.removeHtml(noteHtml) //Remove HTML
@ -356,8 +461,6 @@ Note.reindex = (userId, masterKey) => {
}) })
.then(rawSearchIndex => { .then(rawSearchIndex => {
// console.log('All notes indexed')
const created = Math.round((+new Date)/1000) const created = Math.round((+new Date)/1000)
const jsonSearchIndex = JSON.stringify(searchIndex) const jsonSearchIndex = JSON.stringify(searchIndex)
const encryptedJsonIndex = cs.encrypt(masterKey, searchIndexSalt, jsonSearchIndex) const encryptedJsonIndex = cs.encrypt(masterKey, searchIndexSalt, jsonSearchIndex)
@ -377,58 +480,14 @@ Note.reindex = (userId, masterKey) => {
}) })
}).catch(error => { }).catch(error => {
console.log('Reindex Error') console.log('Reindex Error:')
console.log(error) console.log(error)
}) })
//Find all note Ids that need to be reindexed
// return resolve(true)
return
Note.get(userId, noteId)
.then(note => {
let noteText = note.text
if(note.encrypted == 1){
noteText = '' //Don't put note text in encrypted notes
}
//
// Update Solr index
//
Tags.string(userId, noteId)
.then(tagString => {
const fullText = note.title + ' ' + ProcessText.removeHtml(noteText) +' '+ tagString
db.promise()
.query(`
INSERT INTO note_text_index (note_id, user_id, text)
VALUES (?,?,?)
ON DUPLICATE KEY UPDATE text = ?
`, [noteId, userId, fullText, fullText])
.then((rows, fields) => {
resolve(true)
})
.catch(console.log)
})
})
}) })
} }
// Returns updated note text // Returns updated note text
Note.update = (io, userId, noteId, noteText, noteTitle, color, pinned, archived, hash, masterKey) => { Note.update = (userId, noteId, noteText, noteTitle, color, pinned, archived, hash, masterKey) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const now = Math.round((+new Date)/1000) const now = Math.round((+new Date)/1000)
@ -483,6 +542,8 @@ Note.update = (io, userId, noteId, noteText, noteTitle, color, pinned, archived,
//Re-encrypt for other user //Re-encrypt for other user
const updatedSnippet = cs.encrypt(masterKey, otherNote.snippet_salt, snippet) const updatedSnippet = cs.encrypt(masterKey, otherNote.snippet_salt, snippet)
db.promise().query('UPDATE note SET snippet = ? WHERE id = ?', [updatedSnippet, otherNote.id]) db.promise().query('UPDATE note SET snippet = ? WHERE id = ?', [updatedSnippet, otherNote.id])
SocketIo.to(otherNote['user_id']).emit('new_note_text_saved', {'noteId':otherNote.id, hash})
} }
}) })
@ -501,12 +562,12 @@ Note.update = (io, userId, noteId, noteText, noteTitle, color, pinned, archived,
}) })
.then((rows, fields) => { .then((rows, fields) => {
if(io){ if(SocketIo){
io.to(userId).emit('new_note_text_saved', {noteId, hash}) SocketIo.to(userId).emit('new_note_text_saved', {noteId, hash})
} }
//Async attachment reindex //Async attachment reindex
Attachment.scanTextForWebsites(io, userId, noteId, noteText) Attachment.scanTextForWebsites(SocketIo, userId, noteId, noteText)
//Send back updated response //Send back updated response
resolve(rows[0]) resolve(rows[0])
@ -519,12 +580,14 @@ Note.setPinned = (userId, noteId, pinnedBoolean) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const pinned = pinnedBoolean ? 1:0 const pinned = pinnedBoolean ? 1:0
const now = Math.round((+new Date)/1000)
//Update other note attributes //Update other note attributes
return db.promise() return db.promise()
.query('UPDATE note SET pinned = ? WHERE id = ? AND user_id = ? LIMIT 1', .query('UPDATE note JOIN note_raw_text ON note_raw_text_id = note_raw_text.id SET pinned = ?, updated = ? WHERE note.id = ? AND user_id = ?',
[pinned, noteId, userId]) [pinned, now, noteId, userId])
.then((rows, fields) => { .then((rows, fields) => {
SocketIo.to(userId).emit('note_attribute_modified', noteId)
resolve(true) resolve(true)
}) })
}) })
@ -534,12 +597,31 @@ Note.setArchived = (userId, noteId, archivedBoolead) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const archived = archivedBoolead ? 1:0 const archived = archivedBoolead ? 1:0
const now = Math.round((+new Date)/1000)
//Update other note attributes //Update other note attributes
return db.promise() return db.promise()
.query('UPDATE note SET archived = ? WHERE id = ? AND user_id = ? LIMIT 1', .query('UPDATE note JOIN note_raw_text ON note_raw_text_id = note_raw_text.id SET archived = ?, updated = ? WHERE note.id = ? AND user_id = ?',
[archived, noteId, userId]) [archived, now, noteId, userId])
.then((rows, fields) => { .then((rows, fields) => {
SocketIo.to(userId).emit('note_attribute_modified', noteId)
resolve(true)
})
})
}
Note.setTrashed = (userId, noteId, trashedBoolean) => {
return new Promise((resolve, reject) => {
const trashed = trashedBoolean ? 1:0
const now = Math.round((+new Date)/1000)
//Update other note attributes
return db.promise()
.query('UPDATE note JOIN note_raw_text ON note_raw_text_id = note_raw_text.id SET trashed = ?, updated = ? WHERE note.id = ? AND user_id = ?',
[trashed, now, noteId, userId])
.then((rows, fields) => {
SocketIo.to(userId).emit('note_attribute_modified', noteId)
resolve(true) resolve(true)
}) })
}) })
@ -548,7 +630,7 @@ Note.setArchived = (userId, noteId, archivedBoolead) => {
// //
// Delete a note and all its remaining parts // Delete a note and all its remaining parts
// //
Note.delete = (userId, noteId) => { Note.delete = (userId, noteId, masterKey = null) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// //
@ -591,10 +673,10 @@ Note.delete = (userId, noteId) => {
return db.promise() return db.promise()
.query('DELETE FROM note WHERE note.id = ? AND note.user_id = ?', [noteId, userId]) .query('DELETE FROM note WHERE note.id = ? AND note.user_id = ?', [noteId, userId])
}) })
.then((rows, fields) => { .then( results => {
// Delete search index // Delete hidden attachments for this note (files for attachment are already gone)
return db.promise() return db.promise()
.query('DELETE FROM note_text_index WHERE note_text_index.note_id = ? AND note_text_index.user_id = ?', [noteId,userId]) .query('DELETE FROM attachment WHERE visible = 0 AND note_id = ? AND user_id = ?', [noteId, userId])
}) })
.then((rows, fields) => { .then((rows, fields) => {
// delete tags // delete tags
@ -603,7 +685,11 @@ Note.delete = (userId, noteId) => {
}) })
.then((rows, fields) => { .then((rows, fields) => {
//IF there are nots with a matching raw text id, we want to under their share status
})
.then((rows, fields) => {
//IF there are notes with a matching raw text id, we want to update their share status
db.promise().query('SELECT id FROM note WHERE note_raw_text_id = ?',[rawTextId]) db.promise().query('SELECT id FROM note WHERE note_raw_text_id = ?',[rawTextId])
.then((rows, fields) => { .then((rows, fields) => {
if(rows[0].length == 1){ if(rows[0].length == 1){
@ -611,7 +697,15 @@ Note.delete = (userId, noteId) => {
} }
}) })
resolve(true) if(masterKey){
//Remove note ID from index
Note.reindex(userId, masterKey, [noteId])
.then(results => {
return resolve(true)
})
} else {
return resolve(true)
}
}) })
}) })
} }
@ -698,6 +792,7 @@ Note.get = (userId, noteId, masterKey) => {
note.created, note.created,
note.pinned, note.pinned,
note.archived, note.archived,
note.trashed,
note.color, note.color,
note.encrypted_share_password_key, note.encrypted_share_password_key,
count(distinct attachment.id) as attachment_count, count(distinct attachment.id) as attachment_count,
@ -889,7 +984,7 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
count(distinct attachment.id) as attachment_count, count(distinct attachment.id) as attachment_count,
note.pinned, note.pinned,
note.archived, note.archived,
note.encrypted, note.trashed,
GROUP_CONCAT(DISTINCT tag.text) as tags, GROUP_CONCAT(DISTINCT tag.text) as tags,
GROUP_CONCAT(DISTINCT attachment.file_location) as thumbs, GROUP_CONCAT(DISTINCT attachment.file_location) as thumbs,
shareUser.username as shareUsername, shareUser.username as shareUsername,
@ -904,16 +999,6 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
WHERE note.user_id = ? WHERE note.user_id = ?
` `
//Show shared notes
if(fastFilters.onlyShowSharedNotes == 1){
//share_user_id means your shared them, a note with a shared user id filled in means it was shared
noteSearchQuery += ` AND share_user_id IS NOT NULL OR (note.shared = 2 AND note.user_id = ?)`
searchParams.push(userId)
//Show notes shared with you
} else {
noteSearchQuery += ' AND note.share_user_id IS NULL'
}
//If text search returned results, limit search to those ids //If text search returned results, limit search to those ids
if(textSearchIds.length > 0){ if(textSearchIds.length > 0){
searchParams.push(textSearchIds) searchParams.push(textSearchIds)
@ -921,18 +1006,13 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
searchAllNotes = true searchAllNotes = true
} }
//If Specific ID's are being searched, search ALL notes
if(fastFilters.noteIdSet && fastFilters.noteIdSet.length > 0){ if(fastFilters.noteIdSet && fastFilters.noteIdSet.length > 0){
searchParams.push(fastFilters.noteIdSet) searchParams.push(fastFilters.noteIdSet)
noteSearchQuery += ' AND note.id IN (?)' noteSearchQuery += ' AND note.id IN (?)'
searchAllNotes = true searchAllNotes = true
} }
//Encrypted Note
if(fastFilters.onlyShowEncrypted == 1){
noteSearchQuery += ' AND encrypted = 1'
searchAllNotes = true
}
//If tags are passed, use those tags in search //If tags are passed, use those tags in search
if(searchTags.length > 0){ if(searchTags.length > 0){
searchParams.push(searchTags) searchParams.push(searchTags)
@ -941,31 +1021,48 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
//Show archived notes, only if fast filter is set, default to not archived //Show archived notes, only if fast filter is set, default to not archived
if(searchAllNotes == false){ if(searchAllNotes == false){
if(fastFilters.onlyArchived == 1){
noteSearchQuery += ' AND note.archived = 1' //Show Archived if(fastFilters.notesHome == 1){
} else { noteSearchQuery += ' AND note.archived = 0 AND note.trashed = 0 AND note.share_user_id IS NULL'
noteSearchQuery += ' AND note.archived = 0' //Exclude archived
} }
if(fastFilters.onlyShowSharedNotes == 1){
//share_user_id means your shared them, a note with a shared user id filled in means it was shared
noteSearchQuery += ` AND share_user_id IS NOT NULL OR (note.shared = 2 AND note.user_id = ?) AND note.trashed = 0`
searchParams.push(userId)
//Show notes shared with you
}
if(fastFilters.onlyArchived == 1){
noteSearchQuery += ' AND note.archived = 1 AND note.trashed = 0' //Show Archived
}
if(fastFilters.onlyShowTrashed == 1){
noteSearchQuery += ' AND note.trashed = 1' //Show Exclude
}
} }
//Finish up note query //Finish up note query
noteSearchQuery += ' GROUP BY note.id' noteSearchQuery += ' GROUP BY note.id'
//Only show notes with Tags //Only show notes with Tags
if(fastFilters.withTags == 1){ if(fastFilters.withTags == 1){
returnTagResults = true
noteSearchQuery += ' HAVING tag_count > 0' noteSearchQuery += ' HAVING tag_count > 0'
} }
//Only show notes with links //Only show notes with links
if(fastFilters.withLinks == 1){ if(fastFilters.withLinks == 1){
returnTagResults = true
noteSearchQuery += ' HAVING attachment_count > 0' noteSearchQuery += ' HAVING attachment_count > 0'
} }
//Only show archived notes //Only show archived notes
if(fastFilters.onlyArchived == 1){ // if(fastFilters.onlyArchived == 1){
returnTagResults = true // noteSearchQuery += ' HAVING note.archived = 1'
noteSearchQuery += ' HAVING note.archived = 1' // }
} // //Only show trashed notes
// if(fastFilters.onlyShowTrashed == 1){
// noteSearchQuery += ' HAVING note.trashed = 1'
// }
// //
// Always prioritize pinned notes in searches. // Always prioritize pinned notes in searches.
@ -992,8 +1089,6 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
const limitSize = parseInt(fastFilters.limitSize, 10) || 10 //Use int or default to 10 const limitSize = parseInt(fastFilters.limitSize, 10) || 10 //Use int or default to 10
const limitOffset = parseInt(fastFilters.limitOffset, 10) || 0 //Either parse int, or use zero const limitOffset = parseInt(fastFilters.limitOffset, 10) || 0 //Either parse int, or use zero
// console.log(` LIMIT ${limitOffset}, ${limitSize}`)
noteSearchQuery += ` LIMIT ${limitOffset}, ${limitSize}` noteSearchQuery += ` LIMIT ${limitOffset}, ${limitSize}`
} }
@ -1005,19 +1100,14 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
.query(noteSearchQuery, searchParams) .query(noteSearchQuery, searchParams)
.then((noteRows, noteFields) => { .then((noteRows, noteFields) => {
//Current note key may change, default to master key
let currentNoteKey = masterKey
//Push all notes //Push all notes
returnData['notes'] = noteRows[0] returnData['notes'] = noteRows[0]
//pull out all note ids so we can fetch all tags for those notes //pull out all note ids so we can fetch all tags for those notes
let noteIds = []
returnData['notes'].forEach(note => { returnData['notes'].forEach(note => {
//Grab note ID for finding tags //Current note key may change, default to master key
noteIds.push(note.id) let currentNoteKey = masterKey
//Shared notes use encrypted key - decrypt key then decrypt note //Shared notes use encrypted key - decrypt key then decrypt note
const encryptedShareKey = note.encrypted_share_password_key const encryptedShareKey = note.encrypted_share_password_key
@ -1060,6 +1150,7 @@ Note.search = (userId, searchQuery, searchTags, fastFilters, masterKey) => {
//Clear out note.text before sending it to front end, its being used in title and subtext //Clear out note.text before sending it to front end, its being used in title and subtext
delete note.snippet delete note.snippet
delete note.salt delete note.salt
delete note.encrypted_share_password_key
}) })

View File

@ -58,7 +58,7 @@ QuickNote.makeUrlLink = (inputText) => {
return replacedText; return replacedText;
} }
QuickNote.update = (io, userId, pushText, masterKey) => { QuickNote.update = (userId, pushText, masterKey) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let finalId = null let finalId = null
@ -91,7 +91,7 @@ QuickNote.update = (io, userId, pushText, masterKey) => {
finalText += broken finalText += broken
return Note.create(io, userId, 'Quick Note', finalText, masterKey) return Note.create(userId, 'Quick Note', finalText, masterKey)
.then(insertedId => { .then(insertedId => {
finalId = insertedId finalId = insertedId
return db.promise().query('UPDATE note SET quick_note = 1 WHERE id = ? AND user_id = ?',[insertedId, userId]) return db.promise().query('UPDATE note SET quick_note = 1 WHERE id = ? AND user_id = ?',[insertedId, userId])
@ -101,7 +101,7 @@ QuickNote.update = (io, userId, pushText, masterKey) => {
finalText += (broken + noteObject.text) finalText += (broken + noteObject.text)
finalId = noteObject.id finalId = noteObject.id
return Note.update(io, userId, noteObject.id, finalText, noteObject.title, noteObject.color, noteObject.pinned, noteObject.archived, null, masterKey) return Note.update(userId, noteObject.id, finalText, noteObject.title, noteObject.color, noteObject.pinned, noteObject.archived, null, masterKey)
} }
}) })
.then( saveResults => { .then( saveResults => {

View File

@ -89,7 +89,7 @@ ShareNote.migrateNoteToShared = (userId, noteId, shareUserId, masterKey) => {
const encryptedSharedKey = crypto.publicEncrypt(publicKey, Buffer.from(sharedNoteMasterKey, 'utf8')).toString('base64') const encryptedSharedKey = crypto.publicEncrypt(publicKey, Buffer.from(sharedNoteMasterKey, 'utf8')).toString('base64')
//Update note snippet for current user with public key encoded snippet //Update note snippet for current user with public key encoded snippet
return db.promise().query('UPDATE note SET snippet = ?, snippet_salt = ?, encrypted_share_password_key = ? WHERE id = ? AND user_id = ?', return db.promise().query('UPDATE note SET snippet = ?, snippet_salt = ?, encrypted_share_password_key = ?, shared = 2 WHERE id = ? AND user_id = ?',
[encryptedSnippet, sharedNoteSnippetSalt, encryptedSharedKey, noteId, userId]) [encryptedSnippet, sharedNoteSnippetSalt, encryptedSharedKey, noteId, userId])
}) })
@ -116,8 +116,13 @@ ShareNote.migrateNoteToShared = (userId, noteId, shareUserId, masterKey) => {
}) })
.then((rows, fields) => { .then((rows, fields) => {
const sharedUserNoteId = rows[0]['insertId']
//Emit update count event to user shared with - so they see the note in real time
SocketIo.to(sharedUserNoteId).emit('update_counts')
let success = true let success = true
return resolve({success, shareUserId}) return resolve({success, shareUserId, sharedUserNoteId})
}) })
.catch(error => { .catch(error => {
@ -128,6 +133,69 @@ ShareNote.migrateNoteToShared = (userId, noteId, shareUserId, masterKey) => {
}) })
} }
ShareNote.removeUserFromShared = (userId, noteId, shareNoteUserId, masterKey) => {
return new Promise((resolve, reject) => {
let rawTextId = null
let removeUserId = null
db.promise()
.query('SELECT note_raw_text_id, user_id FROM note WHERE id = ? AND share_user_id = ?', [shareNoteUserId, userId])
.then( (rows, fields) => {
rawTextId = rows[0][0]['note_raw_text_id']
removeUserId = rows[0][0]['user_id']
//Delete note entry for other user - remove users access
//@TODO - this won't remove the note from their search index, it needs to
return Note.delete(removeUserId, shareNoteUserId)
})
.then(results => {
return db.promise().query('SELECT count(*) as count FROM note WHERE note_raw_text_id = ?', [rawTextId])
})
.then((rows, fields) => {
//Convert back to normal note if there is only one person with this note
if(rows[0][0]['count'] == 1){
Note.get(userId, noteId, masterKey)
.then(noteObject => {
const salt = cs.createSmallSalt()
const snippetSalt = cs.createSmallSalt()
const snippetObj = JSON.stringify([noteObject.title, noteObject.text.substring(0, 500)])
const snippet = cs.encrypt(masterKey, snippetSalt, snippetObj)
const textObject = JSON.stringify([noteObject.title, noteObject.text])
const encryptedText = cs.encrypt(masterKey, salt, textObject)
db.promise()
.query(`UPDATE note SET snippet = ?, snippet_salt = ?, encrypted_share_password_key = ?, shared = 0 WHERE id = ? AND user_id = ?`,
[snippet, snippetSalt, null, noteId, userId])
.then((r,f) => {
db.promise()
.query('UPDATE note_raw_text SET text = ?, salt = ? WHERE id = ?',
[encryptedText, salt, noteObject.rawTextId])
.then(() => {
return resolve(true)
})
})
})
} else {
//Keep note shared
return resolve(true)
}
})
})
}
// Get users who see a shared note // Get users who see a shared note
ShareNote.getUsers = (userId, rawTextId) => { ShareNote.getUsers = (userId, rawTextId) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -168,7 +236,7 @@ ShareNote.removeUser = (userId, noteId) => {
//Delete note entry for other user - remove users access //Delete note entry for other user - remove users access
if(removeUserId && Number.isInteger(removeUserId)){ if(removeUserId && Number.isInteger(removeUserId)){
//Delete this users access to the note //Delete this users access to the note
return Note.delete(removeUserId, noteId) return Note.delete(removeUserId, noteId, masterKey)
} else { } else {

View File

@ -19,10 +19,8 @@ User.login = (username, password) => {
.query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName]) .query('SELECT * FROM user WHERE username = ? LIMIT 1', [lowerName])
.then((rows, fields) => { .then((rows, fields) => {
//Pull out user data from database results // Create New Account
const lookedUpUser = rows[0][0]; //
//User not found, create a new account with set data
if(rows[0].length == 0){ if(rows[0].length == 0){
User.create(lowerName, password) User.create(lowerName, password)
.then( ({token, userId}) => { .then( ({token, userId}) => {
@ -30,7 +28,13 @@ User.login = (username, password) => {
}) })
} }
// Login User
//
if(rows[0].length == 1){ if(rows[0].length == 1){
//Pull out user data from database results
const lookedUpUser = rows[0][0]
//hash the password and check for a match //hash the password and check for a match
// const salt = new Buffer(lookedUpUser.salt, 'binary') // const salt = new Buffer(lookedUpUser.salt, 'binary')
const salt = Buffer.from(lookedUpUser.salt, 'binary') const salt = Buffer.from(lookedUpUser.salt, 'binary')
@ -102,37 +106,33 @@ User.create = (username, password) => {
created: currentDate created: currentDate
}; };
let userId = null
let newMasterKey = null
db.promise() db.promise()
.query('INSERT INTO user SET ?', new_user) .query('INSERT INTO user SET ?', new_user)
.then((rows, fields) => { .then((rows, fields) => {
if(rows[0].affectedRows == 1){ userId = rows[0].insertId
return User.generateMasterKey(userId, password)
})
.then( result => {
const userId = rows[0].insertId return User.getMasterKey(userId, password)
})
.then(masterKey => {
newMasterKey = masterKey
return User.generateKeypair(userId, newMasterKey)
})
.then(({publicKey, privateKey}) => {
User.generateMasterKey(userId, password) const token = Auth.createToken(userId, newMasterKey)
.then( result => User.getMasterKey(userId, password)) return resolve({token, userId})
.then(masterKey => {
User.generateKeypair(userId, masterKey)
.then(({publicKey, privateKey}) => {
const token = Auth.createToken(userId, masterKey)
return resolve({token, userId})
})
})
} else {
//Emit Error to user
reject('New user could not be created')
}
}) })
.catch(console.log) .catch(console.log)
}) })
} else { } else {
reject('Username already in use.') return reject('Username already in use.')
}//END user create }//END user create
}) })
.catch(console.log) .catch(console.log)
@ -149,22 +149,24 @@ User.getCounts = (userId) => {
db.promise().query( db.promise().query(
`SELECT `SELECT
SUM(pinned = 1 && archived = 0 && share_user_id IS NULL) AS pinnedNotes, SUM(archived = 1 && share_user_id IS NULL && trashed = 0) AS archivedNotes,
SUM(archived = 1 && share_user_id IS NULL) AS archivedNotes, SUM(trashed = 1) AS trashedNotes,
SUM(encrypted = 1) AS encryptedNotes, SUM(share_user_id IS NULL && trashed = 0) AS totalNotes,
SUM(share_user_id IS NULL) AS totalNotes, SUM(share_user_id != ? && trashed = 0) AS sharedToNotes,
SUM(share_user_id != ?) AS sharedToNotes, SUM( (share_user_id != ? && opened IS null && trashed = 0) || (share_user_id != ? && note_raw_text.updated > opened && trashed = 0) ) AS unreadNotes
SUM( (share_user_id != ? && opened IS null) || (share_user_id != ? && note_raw_text.updated > opened) ) AS unreadNotes
FROM note FROM note
LEFT JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id) LEFT JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
WHERE user_id = ?`, [userId, userId, userId, userId]) WHERE user_id = ?`, [userId, userId, userId, userId])
.then( (rows, fields) => { .then( (rows, fields) => {
Object.assign(countTotals, rows[0][0]) //combine results Object.assign(countTotals, rows[0][0]) //combine results
//
// @TODO - Figured out if this is useful
// We want, notes shared with user and note user has shared
//
return db.promise().query( return db.promise().query(
`SELECT count(id) AS sharedFromNotes `SELECT count(id) AS sharedFromNotes
FROM note WHERE share_user_id = ?`, [userId] FROM note WHERE shared = 2 AND user_id = ? AND trashed = 0`, [userId]
) )
}) })
.then( (rows, fields) => { .then( (rows, fields) => {
@ -228,18 +230,12 @@ User.generateMasterKey = (userId, password) => {
'INSERT INTO user_key (`user_id`, `salt`, `key`, `created`) VALUES (?, ?, ?, ?);', 'INSERT INTO user_key (`user_id`, `salt`, `key`, `created`) VALUES (?, ?, ?, ?);',
[userId, salt, encryptedMasterPassword, created] [userId, salt, encryptedMasterPassword, created]
) )
.then((rows, fields)=>{
return Note.encryptEveryNote(userId, masterPassword)
})
.then(results => { .then(results => {
return new Promise((resolve, reject) => { resolve(true) }) return resolve(true)
}) })
} }
}) })
.then((rows, fields) => {
return resolve(true)
})
.catch(error => { .catch(error => {
console.log('Create Master Password Error') console.log('Create Master Password Error')
console.log(error) console.log(error)
@ -261,6 +257,10 @@ User.getMasterKey = (userId, password) => {
const row = rows[0][0] const row = rows[0][0]
if(!rows[0] || rows[0].length == 0 || rows[0][0] == undefined){
return reject('Row or salt or something not set')
}
const masterKey = cs.decrypt(password, row['salt'], row['key']) const masterKey = cs.decrypt(password, row['salt'], row['key'])
if(masterKey == null){ if(masterKey == null){
@ -371,24 +371,55 @@ User.deleteUser = (userId, password) => {
//Verify user is correct by decryptig master key with password //Verify user is correct by decryptig master key with password
//Delete user, all notes, all keys let deletePromises = []
let noteDelete = db.promise().query(`
DELETE note, note_raw_text
FROM note
JOIN note_raw_text ON (note_raw_text.id = note.note_raw_text_id)
WHERE note.user_id = ?
`,[userId])
deletePromises.push(noteDelete)
let userDelete = db.promise().query(`
DELETE FROM user WHERE id = ?
`,[userId])
deletePromises.push(userDelete)
let tables = ['user_key', 'user_encrypted_search_index', 'attachment']
tables.forEach(tableName => {
const query = `DELETE FROM ${tableName} WHERE user_id = ?`
const deleteQuery = db.promise().query(query, [userId])
deletePromises.push(deleteQuery)
})
return Promise.all(deletePromises)
} }
User.keyPairTest = (testUserName = 'genMan', password = '1') => { User.keyPairTest = (testUserName = 'genMan', password = '1', printResults) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let masterKey = null let masterKey = null
let testUserId = null let testUserId = null
const randomUsername = Math.random().toString(36).substring(2, 15);
const randomPassword = '1'
User.login(testUserName, password) User.login(testUserName, password)
.then( ({ token, userId }) => { .then( ({ token, userId }) => {
testUserId = userId testUserId = userId
console.log('Test: Create/Login User - Pass')
if(printResults) console.log('Test: Create/Login User '+testUserName+' - Pass')
return User.getMasterKey(testUserId, password) return User.getMasterKey(testUserId, password)
}) })
.then(newMasterKey => { .then(newMasterKey => {
masterKey = newMasterKey masterKey = newMasterKey
console.log('Test: Generate/Decrypt Master Key - Pass')
if(printResults) console.log('Test: Generate/Decrypt Master Key - Pass')
return User.generateKeypair(testUserId, masterKey) return User.generateKeypair(testUserId, masterKey)
}) })
.then(({publicKey, privateKey}) => { .then(({publicKey, privateKey}) => {
@ -400,13 +431,13 @@ User.keyPairTest = (testUserName = 'genMan', password = '1') => {
const privateKeyEncrypted = crypto.privateEncrypt(privateKey, Buffer.from(privateKeyMessage, 'utf8')).toString('base64') const privateKeyEncrypted = crypto.privateEncrypt(privateKey, Buffer.from(privateKeyMessage, 'utf8')).toString('base64')
const decryptedPrivate = crypto.publicDecrypt(publicKey, Buffer.from(privateKeyEncrypted, 'base64')) const decryptedPrivate = crypto.publicDecrypt(publicKey, Buffer.from(privateKeyEncrypted, 'base64'))
//Conver back to a string //Conver back to a string
console.log(decryptedPrivate.toString('utf8')) if(printResults) console.log(decryptedPrivate.toString('utf8'))
//Encrypt with public key //Encrypt with public key
const pubEncrMsc = crypto.publicEncrypt(publicKey, Buffer.from(publicKeyMessage, 'utf8')).toString('base64') const pubEncrMsc = crypto.publicEncrypt(publicKey, Buffer.from(publicKeyMessage, 'utf8')).toString('base64')
const publicDeccryptMessage = crypto.privateDecrypt(privateKey, Buffer.from(pubEncrMsc, 'base64') ) const publicDeccryptMessage = crypto.privateDecrypt(privateKey, Buffer.from(pubEncrMsc, 'base64') )
//Convert it back to string //Convert it back to string
console.log(publicDeccryptMessage.toString('utf8')) if(printResults) console.log(publicDeccryptMessage.toString('utf8'))
resolve({testUserId, masterKey}) resolve({testUserId, masterKey})
}) })

View File

@ -1,7 +1,7 @@
var express = require('express') var express = require('express')
var router = express.Router() var router = express.Router()
let Notes = require('@models/Note') let Note = require('@models/Note')
let User = require('@models/User') let User = require('@models/User')
let ShareNote = require('@models/ShareNote') let ShareNote = require('@models/ShareNote')
@ -22,36 +22,36 @@ router.use(function setUserId (req, res, next) {
// Note actions // Note actions
// //
router.post('/get', function (req, res) { router.post('/get', function (req, res) {
Notes.get(userId, req.body.noteId, masterKey) Note.get(userId, req.body.noteId, masterKey)
.then( data => { .then( data => {
res.send(data) res.send(data)
}) })
}) })
router.post('/delete', function (req, res) { router.post('/delete', function (req, res) {
Notes.delete(userId, req.body.noteId) Note.delete(userId, req.body.noteId)
.then( data => res.send(data) ) .then( data => res.send(data) )
}) })
router.post('/create', function (req, res) { router.post('/create', function (req, res) {
Notes.create(req.io, userId, req.body.title, req.body.text, masterKey) Note.create(userId, req.body.title, req.body.text, masterKey)
.then( id => res.send({id}) ) .then( id => res.send({id}) )
}) })
router.post('/update', function (req, res) { router.post('/update', function (req, res) {
Notes.update(req.io, userId, req.body.noteId, req.body.text, req.body.title, req.body.color, req.body.pinned, req.body.archived, req.body.hash, masterKey) Note.update(userId, req.body.noteId, req.body.text, req.body.title, req.body.color, req.body.pinned, req.body.archived, req.body.hash, masterKey)
.then( id => res.send({id}) ) .then( id => res.send({id}) )
}) })
router.post('/search', function (req, res) { router.post('/search', function (req, res) {
Notes.search(userId, req.body.searchQuery, req.body.searchTags, req.body.fastFilters, masterKey) Note.search(userId, req.body.searchQuery, req.body.searchTags, req.body.fastFilters, masterKey)
.then( notesAndTags => { .then( NoteAndTags => {
res.send(notesAndTags) res.send(NoteAndTags)
}) })
}) })
router.post('/difftext', function (req, res) { router.post('/difftext', function (req, res) {
Notes.getDiffText(userId, req.body.noteId, req.body.text, req.body.updated) Note.getDiffText(userId, req.body.noteId, req.body.text, req.body.updated)
.then( fullDiffText => { .then( fullDiffText => {
//Response should be full diff text //Response should be full diff text
res.send(fullDiffText) res.send(fullDiffText)
@ -59,7 +59,7 @@ router.post('/difftext', function (req, res) {
}) })
router.post('/reindex', function (req, res) { router.post('/reindex', function (req, res) {
Notes.reindex(userId, masterKey) Note.reindex(userId, masterKey)
.then( data => { .then( data => {
res.send(data) res.send(data)
}) })
@ -70,13 +70,19 @@ router.post('/reindex', function (req, res) {
// Update single note attributes // Update single note attributes
// //
router.post('/setpinned', function (req, res) { router.post('/setpinned', function (req, res) {
Notes.setPinned(userId, req.body.noteId, req.body.pinned) Note.setPinned(userId, req.body.noteId, req.body.pinned)
.then( results => { .then( results => {
res.send(results) res.send(results)
}) })
}) })
router.post('/setarchived', function (req, res) { router.post('/setarchived', function (req, res) {
Notes.setArchived(userId, req.body.noteId, req.body.archived) Note.setArchived(userId, req.body.noteId, req.body.archived)
.then( results => {
res.send(results)
})
})
router.post('/settrashed', function (req, res) {
Note.setTrashed(userId, req.body.noteId, req.body.trashed)
.then( results => { .then( results => {
res.send(results) res.send(results)
}) })
@ -98,15 +104,13 @@ router.post('/shareadduser', function (req, res) {
}) })
.then( ({success, shareUserId}) => { .then( ({success, shareUserId}) => {
//Emit update count event to user shared with - so they see the note in real time
req.io.to(shareUserId).emit('update_counts')
res.send(success) res.send(success)
}) })
}) })
router.post('/shareremoveuser', function (req, res) { router.post('/shareremoveuser', function (req, res) {
ShareNote.removeUser(userId, req.body.noteId) // (userId, noteId, shareNoteUserId, shareUserId, masterKey)
ShareNote.removeUserFromShared(userId, req.body.noteId, req.body.shareUserNoteId, masterKey)
.then(results => res.send(results)) .then(results => res.send(results))
}) })
@ -114,10 +118,10 @@ router.post('/shareremoveuser', function (req, res) {
// //
// Testing Action // Testing Action
// //
//Reindex all notes. Not a very good function, not public //Reindex all Note. Not a very good function, not public
router.get('/reindex5yu43prchuj903mrc', function (req, res) { router.get('/reindex5yu43prchuj903mrc', function (req, res) {
Notes.migrateNoteTextToNewTable().then(status => { Note.migrateNoteTextToNewTable().then(status => {
return res.send(status) return res.send(status)
}) })

View File

@ -24,7 +24,7 @@ router.post('/get', function (req, res) {
//Push text to quick note //Push text to quick note
router.post('/update', function (req, res) { router.post('/update', function (req, res) {
QuickNote.update(req.io, userId, req.body.pushText, masterKey) QuickNote.update(userId, req.body.pushText, masterKey)
.then( data => res.send(data) ) .then( data => res.send(data) )
}) })