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:
parent
744506bdc3
commit
9363dc9274
8 changed files with 171 additions and 41 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!');
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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');
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Add table
Reference in a new issue