1
0
Fork 0
mirror of https://github.com/Alamantus/Lexiconga.git synced 2025-06-18 15:16:40 +02:00

Enable two-way intelligent word deletion by storing deleted ids

This commit is contained in:
Robbie Antenesse 2018-06-23 17:24:48 -06:00
parent 744506bdc3
commit 9363dc9274
8 changed files with 171 additions and 41 deletions

View file

@ -189,11 +189,30 @@ WHERE dictionary=$dictionary";
return array(); return array();
} }
public function getDeletedWords ($user, $dictionary) {
$query = "SELECT deleted_words.* FROM deleted_words JOIN dictionaries ON id = dictionary WHERE dictionary=$dictionary AND user=$user";
$results = $this->db->query($query)->fetchAll();
if ($results) {
return array_map(function ($row) {
return array(
'id' => intval($row['word_id']),
'deletedOn' => intval($row['deleted_on']),
);
}, $results);
}
return array();
}
public function setWords ($user, $dictionary, $words = array()) { public function setWords ($user, $dictionary, $words = array()) {
$query = 'INSERT INTO words (dictionary, word_id, name, pronunciation, part_of_speech, definition, details, last_updated, created_on) VALUES '; $query = 'INSERT INTO words (dictionary, word_id, name, pronunciation, part_of_speech, definition, details, last_updated, created_on) VALUES ';
$params = array(); $params = array();
$word_ids = array(); $word_ids = array();
$most_recent_word_update = 0;
foreach($words as $word) { foreach($words as $word) {
$last_updated = is_null($word['lastUpdated']) ? $word['createdOn'] : $word['lastUpdated'];
if ($most_recent_word_update < $last_updated) {
$most_recent_word_update = $last_updated;
}
$word_ids[] = $word['id']; $word_ids[] = $word['id'];
$query .= "(?, ?, ?, ?, ?, ?, ?, ?, ?), "; $query .= "(?, ?, ?, ?, ?, ?, ?, ?, ?), ";
$params[] = $dictionary; $params[] = $dictionary;
@ -203,7 +222,7 @@ WHERE dictionary=$dictionary";
$params[] = $word['partOfSpeech']; $params[] = $word['partOfSpeech'];
$params[] = $word['definition']; $params[] = $word['definition'];
$params[] = $word['details']; $params[] = $word['details'];
$params[] = is_null($word['lastUpdated']) ? $word['createdOn'] : $word['lastUpdated']; $params[] = $last_updated;
$params[] = $word['createdOn']; $params[] = $word['createdOn'];
} }
$query = trim($query, ', ') . ' ON DUPLICATE KEY UPDATE $query = trim($query, ', ') . ' ON DUPLICATE KEY UPDATE
@ -216,28 +235,42 @@ last_updated=VALUES(last_updated)';
$results = $this->db->execute($query, $params); $results = $this->db->execute($query, $params);
if ($results) { // if ($results) {
$database_words = $this->getWords($user, $dictionary); // $database_words = $this->getWords($user, $dictionary);
$database_ids = array_map(function($database_word) { return $database_word['id']; }, $database_words); // $database_ids = array_map(function($database_word) { return $database_word['id']; }, $database_words);
$words_to_delete = array_filter($database_ids, function($database_id) use($word_ids) { return !in_array($database_id, $word_ids); }); // $words_to_delete = array_filter($database_ids, function($database_id) use($word_ids) { return !in_array($database_id, $word_ids); });
if ($words_to_delete) { // if ($words_to_delete) {
$delete_results = $this->deleteWords($dictionary, $words_to_delete); // $delete_results = $this->deleteWords($dictionary, $words_to_delete);
return $delete_results; // return $delete_results;
} // }
} // }
return $results; return $results;
} }
public function deleteWords ($dictionary, $word_ids) { public function deleteWords ($dictionary, $word_ids) {
$query = 'DELETE FROM words WHERE dictionary=? AND word_id IN ('; $insert_query = 'INSERT INTO deleted_words (dictionary, word_id, deleted_on) VALUES ';
$params = array($dictionary); $insert_params = array();
$delete_query = 'DELETE FROM words WHERE dictionary=? AND word_id IN (';
$delete_params = array($dictionary);
foreach($word_ids as $word_id) { foreach($word_ids as $word_id) {
$query .= '?, '; $insert_query .= "(?, ?, ?), ";
$params[] = $word_id; $insert_params[] = $dictionary;
$insert_params[] = $word_id;
$insert_params[] = time();
$delete_query .= '?, ';
$delete_params[] = $word_id;
} }
$query = trim($query, ', ') . ')';
$results = $this->db->execute($query, $params); $insert_query = trim($insert_query, ', ') . ' ON DUPLICATE KEY UPDATE deleted_on=VALUES(deleted_on)';
return $results; $delete_query = trim($delete_query, ', ') . ')';
$insert_results = $this->db->execute($insert_query, $insert_params);
if ($insert_results) {
$delete_results = $this->db->execute($delete_query, $delete_params);
return $delete_results;
}
return $insert_results;
} }
} }

View file

@ -171,6 +171,7 @@ VALUES (?, ?, ?, ?, ?, ?)';
return array( return array(
'details' => $this->dictionary->getDetails($user, $dictionary), 'details' => $this->dictionary->getDetails($user, $dictionary),
'words' => $this->dictionary->getWords($user, $dictionary), 'words' => $this->dictionary->getWords($user, $dictionary),
'deletedWords' => $this->dictionary->getDeletedWords($user, $dictionary),
); );
} }
return false; return false;
@ -212,13 +213,13 @@ VALUES (?, ?, ?, ?, ?, ?)';
return false; return false;
} }
public function deleteWordFromCurrentDictionary ($token, $word_id) { public function deleteWordsFromCurrentDictionary ($token, $word_ids) {
// Useful even for just one word // Useful even for just one word
$user_data = $this->token->decode($token); $user_data = $this->token->decode($token);
if ($user_data !== false) { if ($user_data !== false) {
$dictionary = $user_data->dictionary; $dictionary = $user_data->dictionary;
$user = $user_data->id; $user = $user_data->id;
$deleted_word = $this->dictionary->deleteWords($dictionary, array($word_id)); $deleted_word = $this->dictionary->deleteWords($dictionary, $word_ids);
if ($deleted_word) { if ($deleted_word) {
return true; return true;
} }

View file

@ -247,7 +247,7 @@ switch ($action) {
case 'delete-word': { case 'delete-word': {
if ($token !== false && isset($request['word'])) { if ($token !== false && isset($request['word'])) {
$user = new User(); $user = new User();
$delete_word_success = $user->deleteWordFromCurrentDictionary($token, $request['word']); $delete_word_success = $user->deleteWordsFromCurrentDictionary($token, array($request['word']));
if ($delete_word_success !== false) { if ($delete_word_success !== false) {
return Response::json(array( return Response::json(array(
'data' => 'Deleted successfully', 'data' => 'Deleted successfully',
@ -264,6 +264,26 @@ switch ($action) {
'error' => true, 'error' => true,
), 400); ), 400);
} }
case 'delete-words': {
if ($token !== false && isset($request['words'])) {
$user = new User();
$delete_word_success = $user->deleteWordsFromCurrentDictionary($token, $request['words']);
if ($delete_word_success !== false) {
return Response::json(array(
'data' => 'Deleted successfully',
'error' => false,
), 200);
}
return Response::json(array(
'data' => 'Could not delete words: invalid token',
'error' => true,
), 401);
}
return Response::json(array(
'data' => 'Could not delete words: required data missing',
'error' => true,
), 400);
}
default: { default: {
return Response::html('Hi!'); return Response::html('Hi!');

View file

@ -7,6 +7,12 @@ SET time_zone = "+00:00";
/*!40101 SET NAMES utf8mb4 */; /*!40101 SET NAMES utf8mb4 */;
CREATE TABLE IF NOT EXISTS `deleted_words` (
`dictionary` int(11) NOT NULL,
`word_id` int(11) NOT NULL,
`deleted_on` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `dictionaries` ( CREATE TABLE IF NOT EXISTS `dictionaries` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(11) NOT NULL, `user` int(11) NOT NULL,

View file

@ -312,6 +312,10 @@ class DictionaryData {
return wordDb.words.orderBy('name').toArray(); return wordDb.words.orderBy('name').toArray();
} }
get deletedWordsPromise () {
return wordDb.deletedWords.toArray();
}
wordsWithPartOfSpeech (partOfSpeech) { wordsWithPartOfSpeech (partOfSpeech) {
let words = wordDb.words.where('partOfSpeech'); let words = wordDb.words.where('partOfSpeech');

View file

@ -101,6 +101,13 @@ export class Updater {
}, response => console.log(response)); }, response => console.log(response));
} }
sendDeletedWords (words) {
return request('delete-words', {
token: store.get('LexicongaToken'),
words,
}, response => console.log(response));
}
sync () { sync () {
return request('get-current-dictionary', { return request('get-current-dictionary', {
token: store.get('LexicongaToken'), token: store.get('LexicongaToken'),
@ -110,7 +117,7 @@ export class Updater {
console.error(data); console.error(data);
} else { } else {
this.compareDetails(data.details); this.compareDetails(data.details);
this.compareWords(data.words); this.compareWords(data.words, data.deletedWords);
} }
}); });
} }
@ -129,10 +136,11 @@ export class Updater {
} }
} }
compareWords (externalWords) { compareWords (externalWords, deletedWords) {
const wordsToSend = []; const wordsToSend = [];
const wordsToAdd = []; const wordsToAdd = [];
const wordsToUpdate = []; const wordsToUpdate = [];
const wordsToDelete = [];
const localWordsPromise = this.dictionary.wordsPromise.then(localWords => { const localWordsPromise = this.dictionary.wordsPromise.then(localWords => {
externalWords.forEach(externalWord => { externalWords.forEach(externalWord => {
if (externalWord.lastUpdated) { if (externalWord.lastUpdated) {
@ -151,24 +159,61 @@ export class Updater {
// Find words not in external database and add them to send. // Find words not in external database and add them to send.
localWords.forEach(localWord => { localWords.forEach(localWord => {
if (localWord.lastUpdated) { if (localWord.lastUpdated) {
const wordDeleted = deletedWords.some(word => word.id === localWord.id);
if (wordDeleted) {
wordsToDelete.push(localWord);
} else {
const wordAlreadyChecked = externalWords.some(word => word.id === localWord.id); const wordAlreadyChecked = externalWords.some(word => word.id === localWord.id);
if (!wordAlreadyChecked) { if (!wordAlreadyChecked) {
wordsToSend.push(localWord); wordsToSend.push(localWord);
} }
} }
}
}); });
return {
wordsToAdd,
wordsToUpdate,
wordsToDelete,
wordsToSend,
};
}).then(processedWords => {
let {
wordsToAdd,
wordsToUpdate,
wordsToDelete,
wordsToSend,
} = processedWords;
return this.dictionary.deletedWordsPromise.then(localDeletedWords => {
wordsToAdd = wordsToAdd.filter(word => !localDeletedWords.some(deleted => deleted.id === word.id));
wordsToUpdate = wordsToUpdate.filter(word => !localDeletedWords.some(deleted => deleted.id === word.id));
wordsToSend = wordsToSend.filter(word => !localDeletedWords.some(deleted => deleted.id === word.id));
const deletedWordsToSend = localDeletedWords.filter(local => !deletedWords.some(remote => remote.id === local.id));
wordsToAdd.forEach(newWord => { wordsToAdd.forEach(newWord => {
new Word(newWord).create(); new Word(newWord).create();
}); });
wordsToUpdate.forEach(updatedWord => { wordsToUpdate.forEach(updatedWord => {
new Word(updatedWord).update(); new Word(updatedWord).update();
}); });
wordsToDelete.forEach(deletedWord => {
// Remove words deleted on server from local dictionary
new Word(deletedWord).delete(deletedWord.id, true);
});
if (wordsToSend.length > 0) { if (wordsToSend.length > 0) {
this.sendWords(wordsToSend); this.sendWords(wordsToSend);
} }
if (deletedWordsToSend.length > 0) {
this.sendDeletedWords(deletedWordsToSend.map(deletedWord => deletedWord.id));
}
}).catch(error => {
console.error(error);
});
}).then(() => { }).then(() => {
this.app.updateDisplayedWords(() => console.log('synced words')); this.app.updateDisplayedWords(() => console.log('synced words'));
}).catch(error => {
console.error(error);
}); });
} }
} }

View file

@ -42,10 +42,15 @@ export class Word {
create () { create () {
this.createdOn = this.createdOn ? this.createdOn : timestampInSeconds(); this.createdOn = this.createdOn ? this.createdOn : timestampInSeconds();
let addPromise;
// Delete id if it exists to allow creation of new word. // Delete id if it exists to allow creation of new word.
if (this.hasOwnProperty('id')) delete this.id; if (this.hasOwnProperty('id')) {
addPromise = wordDb.words.put(this);
} else {
addPromise = wordDb.words.add(this);
}
return wordDb.words.add(this) return addPromise
.then((id) => { .then((id) => {
this.id = id; this.id = id;
console.log('Word added successfully'); console.log('Word added successfully');
@ -69,14 +74,26 @@ export class Word {
}); });
} }
delete (wordId) { delete (wordId, skipSend = false) {
return wordDb.words.delete(wordId) return wordDb.words.delete(wordId)
.then(() => { .then(() => {
console.log('Word deleted successfully'); console.log('Word deleted successfully');
if (!skipSend) {
request('delete-word', { request('delete-word', {
token: store.get('LexicongaToken'), token: store.get('LexicongaToken'),
word: wordId, word: wordId,
}, response => console.log(response)); }, response => console.log(response));
}
wordDb.deletedWords.add({
id: wordId,
deletedOn: timestampInSeconds(),
})
.then(() => {
console.log('Word added to deleted list');
})
.catch(error => {
console.error(error);
});
}) })
.catch(error => { .catch(error => {
console.error(error); console.error(error);

View file

@ -6,6 +6,10 @@ const db = new Dexie('Lexiconga');
db.version(1).stores({ db.version(1).stores({
words: '++id, name, partOfSpeech, createdOn, lastUpdated', words: '++id, name, partOfSpeech, createdOn, lastUpdated',
}); });
db.version(2).stores({
words: '++id, name, partOfSpeech, createdOn, lastUpdated',
deletedWords: 'id',
});
if (['emptydb', 'donotsave'].includes(process.env.NODE_ENV)) { if (['emptydb', 'donotsave'].includes(process.env.NODE_ENV)) {
db.words.clear(); db.words.clear();