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();
}
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()) {
$query = 'INSERT INTO words (dictionary, word_id, name, pronunciation, part_of_speech, definition, details, last_updated, created_on) VALUES ';
$params = array();
$word_ids = array();
$most_recent_word_update = 0;
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'];
$query .= "(?, ?, ?, ?, ?, ?, ?, ?, ?), ";
$params[] = $dictionary;
@ -203,7 +222,7 @@ WHERE dictionary=$dictionary";
$params[] = $word['partOfSpeech'];
$params[] = $word['definition'];
$params[] = $word['details'];
$params[] = is_null($word['lastUpdated']) ? $word['createdOn'] : $word['lastUpdated'];
$params[] = $last_updated;
$params[] = $word['createdOn'];
}
$query = trim($query, ', ') . ' ON DUPLICATE KEY UPDATE
@ -216,28 +235,42 @@ last_updated=VALUES(last_updated)';
$results = $this->db->execute($query, $params);
if ($results) {
$database_words = $this->getWords($user, $dictionary);
$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); });
if ($words_to_delete) {
$delete_results = $this->deleteWords($dictionary, $words_to_delete);
return $delete_results;
}
}
// if ($results) {
// $database_words = $this->getWords($user, $dictionary);
// $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); });
// if ($words_to_delete) {
// $delete_results = $this->deleteWords($dictionary, $words_to_delete);
// return $delete_results;
// }
// }
return $results;
}
public function deleteWords ($dictionary, $word_ids) {
$query = 'DELETE FROM words WHERE dictionary=? AND word_id IN (';
$params = array($dictionary);
$insert_query = 'INSERT INTO deleted_words (dictionary, word_id, deleted_on) VALUES ';
$insert_params = array();
$delete_query = 'DELETE FROM words WHERE dictionary=? AND word_id IN (';
$delete_params = array($dictionary);
foreach($word_ids as $word_id) {
$query .= '?, ';
$params[] = $word_id;
$insert_query .= "(?, ?, ?), ";
$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);
return $results;
$insert_query = trim($insert_query, ', ') . ' ON DUPLICATE KEY UPDATE deleted_on=VALUES(deleted_on)';
$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(
'details' => $this->dictionary->getDetails($user, $dictionary),
'words' => $this->dictionary->getWords($user, $dictionary),
'deletedWords' => $this->dictionary->getDeletedWords($user, $dictionary),
);
}
return false;
@ -212,13 +213,13 @@ VALUES (?, ?, ?, ?, ?, ?)';
return false;
}
public function deleteWordFromCurrentDictionary ($token, $word_id) {
public function deleteWordsFromCurrentDictionary ($token, $word_ids) {
// Useful even for just one word
$user_data = $this->token->decode($token);
if ($user_data !== false) {
$dictionary = $user_data->dictionary;
$user = $user_data->id;
$deleted_word = $this->dictionary->deleteWords($dictionary, array($word_id));
$deleted_word = $this->dictionary->deleteWords($dictionary, $word_ids);
if ($deleted_word) {
return true;
}

View File

@ -247,7 +247,7 @@ switch ($action) {
case 'delete-word': {
if ($token !== false && isset($request['word'])) {
$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) {
return Response::json(array(
'data' => 'Deleted successfully',
@ -264,6 +264,26 @@ switch ($action) {
'error' => true,
), 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: {
return Response::html('Hi!');

View File

@ -7,6 +7,12 @@ SET time_zone = "+00:00";
/*!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` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(11) NOT NULL,

View File

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

View File

@ -101,6 +101,13 @@ export class Updater {
}, response => console.log(response));
}
sendDeletedWords (words) {
return request('delete-words', {
token: store.get('LexicongaToken'),
words,
}, response => console.log(response));
}
sync () {
return request('get-current-dictionary', {
token: store.get('LexicongaToken'),
@ -110,7 +117,7 @@ export class Updater {
console.error(data);
} else {
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 wordsToAdd = [];
const wordsToUpdate = [];
const wordsToDelete = [];
const localWordsPromise = this.dictionary.wordsPromise.then(localWords => {
externalWords.forEach(externalWord => {
if (externalWord.lastUpdated) {
@ -151,24 +159,61 @@ export class Updater {
// Find words not in external database and add them to send.
localWords.forEach(localWord => {
if (localWord.lastUpdated) {
const wordAlreadyChecked = externalWords.some(word => word.id === localWord.id);
if (!wordAlreadyChecked) {
wordsToSend.push(localWord);
const wordDeleted = deletedWords.some(word => word.id === localWord.id);
if (wordDeleted) {
wordsToDelete.push(localWord);
} else {
const wordAlreadyChecked = externalWords.some(word => word.id === localWord.id);
if (!wordAlreadyChecked) {
wordsToSend.push(localWord);
}
}
}
});
wordsToAdd.forEach(newWord => {
new Word(newWord).create();
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 => {
new Word(newWord).create();
});
wordsToUpdate.forEach(updatedWord => {
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) {
this.sendWords(wordsToSend);
}
if (deletedWordsToSend.length > 0) {
this.sendDeletedWords(deletedWordsToSend.map(deletedWord => deletedWord.id));
}
}).catch(error => {
console.error(error);
});
wordsToUpdate.forEach(updatedWord => {
new Word(updatedWord).update();
});
if (wordsToSend.length > 0) {
this.sendWords(wordsToSend);
}
}).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 () {
this.createdOn = this.createdOn ? this.createdOn : timestampInSeconds();
let addPromise;
// 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) => {
this.id = id;
console.log('Word added successfully');
@ -69,14 +74,26 @@ export class Word {
});
}
delete (wordId) {
delete (wordId, skipSend = false) {
return wordDb.words.delete(wordId)
.then(() => {
console.log('Word deleted successfully');
request('delete-word', {
token: store.get('LexicongaToken'),
word: wordId,
}, response => console.log(response));
if (!skipSend) {
request('delete-word', {
token: store.get('LexicongaToken'),
word: wordId,
}, 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 => {
console.error(error);

View File

@ -6,6 +6,10 @@ const db = new Dexie('Lexiconga');
db.version(1).stores({
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)) {
db.words.clear();