Enable two-way intelligent word deletion by storing deleted ids
This commit is contained in:
parent
744506bdc3
commit
9363dc9274
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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!');
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue