From ee38e4b7ffcb86b79bf534741e7fc8e9def243a4 Mon Sep 17 00:00:00 2001 From: Natsumi Date: Tue, 21 Sep 2021 00:57:09 +1200 Subject: [PATCH] Notification table into SQLite --- html/src/app.js | 211 +++++++++++++++++++++----------- html/src/index.pug | 25 ++-- html/src/repository/database.js | 100 +++++++++++++++ 3 files changed, 256 insertions(+), 80 deletions(-) diff --git a/html/src/app.js b/html/src/app.js index eb71e7ba..1b24ee59 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -476,6 +476,13 @@ speechSynthesis.getVoices(); if (status === 404 && endpoint.substring(0, 6) === 'users/') { throw new Error("404: Can't find user!"); } + if ( + status === 404 && + endpoint.substring(0, 7) === 'invite/' && + init.inviteId + ) { + this.expireNotification(init.inviteId); + } if (data.error === Object(data.error)) { this.$throw( data.error.status_code || status, @@ -2218,11 +2225,9 @@ speechSynthesis.getVoices(); // API: Notification - API.cachedNotifications = new Map(); API.isNotificationsLoading = false; API.$on('LOGIN', function () { - this.cachedNotifications.clear(); this.isNotificationsLoading = false; }); @@ -2254,13 +2259,19 @@ speechSynthesis.getVoices(); }); API.$on('NOTIFICATION:ACCEPT', function (args) { - var ref = this.cachedNotifications.get(args.params.notificationId); - if (typeof ref === 'undefined' || ref.$isDeleted) { + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { + if (array[i].id === args.params.notificationId) { + var ref = array[i]; + break; + } + } + if (typeof ref === 'undefined') { return; } + ref.$isExpired = true; args.ref = ref; - ref.$isDeleted = true; - this.$emit('NOTIFICATION:@DELETE', { + this.$emit('NOTIFICATION:EXPIRE', { ref, params: { notificationId: ref.id @@ -2274,13 +2285,32 @@ speechSynthesis.getVoices(); }); API.$on('NOTIFICATION:HIDE', function (args) { - var ref = this.cachedNotifications.get(args.params.notificationId); - if (typeof ref === 'undefined' && ref.$isDeleted) { + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { + if (array[i].id === args.params.notificationId) { + var ref = array[i]; + break; + } + } + if (typeof ref === 'undefined') { return; } args.ref = ref; - ref.$isDeleted = true; - this.$emit('NOTIFICATION:@DELETE', { + if ( + ref.type === 'friendRequest' || + ref.type === 'hiddenFriendRequest' + ) { + for (var i = array.length - 1; i >= 0; i--) { + if (array[i].id === ref.id) { + array.splice(i, 1); + break; + } + } + } else { + ref.$isExpired = true; + database.updateNotificationExpired(ref); + } + this.$emit('NOTIFICATION:EXPIRE', { ref, params: { notificationId: ref.id @@ -2289,7 +2319,13 @@ speechSynthesis.getVoices(); }); API.applyNotification = function (json) { - var ref = this.cachedNotifications.get(json.id); + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { + if (array[i].id === json.id) { + var ref = array[i]; + break; + } + } if (typeof ref === 'undefined') { ref = { id: '', @@ -2301,12 +2337,10 @@ speechSynthesis.getVoices(); seen: false, created_at: '', // VRCX - $isDeleted: false, $isExpired: false, // ...json }; - this.cachedNotifications.set(ref.id, ref); } else { Object.assign(ref, json); ref.$isExpired = false; @@ -2326,36 +2360,42 @@ speechSynthesis.getVoices(); return ref; }; - API.expireNotifications = function () { - for (var ref of this.cachedNotifications.values()) { - ref.$isExpired = true; + API.expireFriendRequestNotifications = function () { + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { if ( - ref.type === 'friendRequest' || - ref.type === 'hiddenFriendRequest' + array[i].type === 'friendRequest' || + array[i].type === 'hiddenFriendRequest' ) { - this.cachedNotifications.delete(ref.id); + array.splice(i, 1); } } }; - API.deleteExpiredNotifcations = function () { - for (var ref of this.cachedNotifications.values()) { - if (ref.$isDeleted || ref.$isExpired === false) { - continue; + API.expireNotification = function (notificationId) { + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { + if (array[i].id === notificationId) { + var ref = array[i]; + break; } - ref.$isDeleted = true; - this.$emit('NOTIFICATION:@DELETE', { - ref, - params: { - notificationId: ref.id - } - }); } + if (typeof ref === 'undefined') { + return; + } + ref.$isExpired = true; + database.updateNotificationExpired(ref); + this.$emit('NOTIFICATION:EXPIRE', { + ref, + params: { + notificationId: ref.id + } + }); }; API.refreshNotifications = async function () { this.isNotificationsLoading = true; - this.expireNotifications(); + this.expireFriendRequestNotifications(); var params = { n: 100, offset: 0 @@ -2382,7 +2422,6 @@ speechSynthesis.getVoices(); break; } } - this.deleteExpiredNotifcations(); this.isNotificationsLoading = false; }; @@ -2512,31 +2551,33 @@ speechSynthesis.getVoices(); }); }; - API.sendInviteResponse = function (params, inviteID) { - return this.call(`invite/${inviteID}/response`, { + API.sendInviteResponse = function (params, inviteId) { + return this.call(`invite/${inviteId}/response`, { method: 'POST', - params + params, + inviteId }).then((json) => { var args = { json, params, - inviteID + inviteId }; this.$emit('INVITE:RESPONSE:SEND', args); return args; }); }; - API.sendInviteResponsePhoto = function (params, inviteID) { - return this.call(`invite/${inviteID}/response/photo`, { + API.sendInviteResponsePhoto = function (params, inviteId) { + return this.call(`invite/${inviteId}/response/photo`, { uploadImage: true, postData: JSON.stringify(params), - imageData: $app.uploadImage + imageData: $app.uploadImage, + inviteId }).then((json) => { var args = { json, params, - inviteID + inviteId }; this.$emit('INVITE:RESPONSE:PHOTO:SEND', args); return args; @@ -2586,13 +2627,13 @@ speechSynthesis.getVoices(); }; API.getFriendRequest = function (userId) { - for (var ref of this.cachedNotifications.values()) { + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { if ( - ref.$isDeleted === false && - ref.type === 'friendRequest' && - ref.senderUserId === userId + array[i].type === 'friendRequest' && + array[i].senderUserId === userId ) { - return ref.id; + return array[i].id; } } return ''; @@ -3506,6 +3547,11 @@ speechSynthesis.getVoices(); break; case 'hide-notification': + this.$emit('NOTIFICATION:HIDE', { + params: { + notificationId: content + } + }); this.$emit('NOTIFICATION:SEE', { params: { notificationId: content @@ -4508,9 +4554,6 @@ speechSynthesis.getVoices(); }; $app.methods.queueNotificationNoty = function (noty) { - if (noty.senderUserId === API.currentUser.id) { - return; - } noty.isFriend = this.friends.has(noty.senderUserId); noty.isFavorite = API.cachedFavoritesByObjectId.has(noty.senderUserId); var notyFilter = this.sharedFeedFilters.noty; @@ -6875,6 +6918,8 @@ speechSynthesis.getVoices(); await database.initUserTables(args.json.id); $app.feedTable.data = await database.getFeedDatabase(); $app.sweepFeed(); + // eslint-disable-next-line require-atomic-updates + $app.notificationTable.data = await database.getNotifications(); if (configRepository.getBool(`friendLogInit_${args.json.id}`)) { await $app.getFriendLog(); } else { @@ -8615,6 +8660,20 @@ speechSynthesis.getVoices(); this.friendLog.set(id, friendLogCurrent); database.setFriendLogCurrent(friendLogCurrent); this.notifyMenu('friendLog'); + this.deleteFriendRequest(id); + } + }; + + $app.methods.deleteFriendRequest = function (userId) { + var array = $app.notificationTable.data; + for (var i = array.length - 1; i >= 0; i--) { + if ( + array[i].type === 'friendRequest' && + array[i].senderUserId === userId + ) { + array.splice(i, 1); + return; + } } }; @@ -8859,22 +8918,22 @@ speechSynthesis.getVoices(); var {length} = array; for (var i = 0; i < length; ++i) { if (array[i].id === ref.id) { - if (ref.$isDeleted) { - array.splice(i, 1); - } else { - Vue.set(array, i, ref); - } + Vue.set(array, i, ref); return; } } - if (ref.$isDeleted === false) { - $app.notificationTable.data.push(ref); - if (ref.senderUserId !== this.currentUser.id) { - $app.notifyMenu('notification'); - $app.unseenNotifications.push(ref.id); + if (ref.senderUserId !== this.currentUser.id) { + if ( + ref.type !== 'friendRequest' && + ref.type !== 'hiddenFriendRequest' + ) { + database.addNotificationToDatabase(ref); } + $app.notifyMenu('notification'); + $app.unseenNotifications.push(ref.id); $app.queueNotificationNoty(ref); } + $app.notificationTable.data.push(ref); $app.updateSharedFeed(true); }); @@ -8886,18 +8945,6 @@ speechSynthesis.getVoices(); } }); - API.$on('NOTIFICATION:@DELETE', function (args) { - var {ref} = args; - var array = $app.notificationTable.data; - var {length} = array; - for (var i = 0; i < length; ++i) { - if (array[i].id === ref.id) { - array.splice(i, 1); - return; - } - } - }); - $app.methods.acceptNotification = function (row) { // FIXME: 메시지 수정 this.$confirm('Continue? Accept Friend Request', 'Confirm', { @@ -8915,8 +8962,7 @@ speechSynthesis.getVoices(); }; $app.methods.hideNotification = function (row) { - // FIXME: 메시지 수정 - this.$confirm('Continue? Delete Notification', 'Confirm', { + this.$confirm(`Continue? Decline ${row.type}`, 'Confirm', { confirmButtonText: 'Confirm', cancelButtonText: 'Cancel', type: 'info', @@ -8939,6 +8985,25 @@ speechSynthesis.getVoices(); }); }; + $app.methods.deleteNotificationLog = function (row) { + this.$confirm(`Continue? Delete ${row.type}`, 'Confirm', { + confirmButtonText: 'Confirm', + cancelButtonText: 'Cancel', + type: 'info', + callback: (action) => { + if (action === 'confirm') { + removeFromArray(this.notificationTable.data, row); + if ( + row.type !== 'friendRequest' && + row.type !== 'hiddenFriendRequest' + ) { + database.deleteNotification(row.id); + } + } + } + }); + }; + $app.methods.acceptRequestInvite = function (row) { this.$confirm('Continue? Send Invite', 'Confirm', { confirmButtonText: 'Confirm', @@ -10146,7 +10211,7 @@ speechSynthesis.getVoices(); D.isFriend = true; }); - API.$on('NOTIFICATION:@DELETE', function (args) { + API.$on('NOTIFICATION:EXPIRE', function (args) { var {ref} = args; var D = $app.userDialog; if ( diff --git a/html/src/index.pug b/html/src/index.pug index 3249d38b..af091f23 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -470,15 +470,26 @@ html span(v-else-if='scope.row.details && scope.row.details.inviteMessage' v-text="scope.row.details.inviteMessage") span(v-else-if='scope.row.details && scope.row.details.requestMessage' v-text="scope.row.details.requestMessage") span(v-else-if='scope.row.details && scope.row.details.responseMessage' v-text="scope.row.details.responseMessage") - el-table-column(label="Action" width="80" align="right") + el-table-column(label="Action" width="100" align="right") template(v-once #default="scope") - template(v-if="scope.row.senderUserId !== API.currentUser.id") - el-button(v-if="scope.row.type === 'friendRequest'" type="text" icon="el-icon-check" size="mini" @click="acceptNotification(scope.row)") - el-button(v-else-if="scope.row.type === 'invite'" type="text" icon="el-icon-chat-line-square" size="mini" @click="showSendInviteResponseDialog(scope.row)") + template(v-if="scope.row.senderUserId !== API.currentUser.id && !scope.row.$isExpired") + template(v-if="scope.row.type === 'friendRequest'") + el-tooltip(placement="top" content="Accept" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-check" size="mini" @click="acceptNotification(scope.row)") + template(v-else-if="scope.row.type === 'invite'") + el-tooltip(placement="top" content="Decline with message" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-chat-line-square" size="mini" @click="showSendInviteResponseDialog(scope.row)") template(v-else-if="scope.row.type === 'requestInvite'") - el-button(v-if="lastLocation.location && isGameRunning && !checkCanInvite(lastLocation.location)" type="text" icon="el-icon-check" size="mini" @click="acceptRequestInvite(scope.row)") - el-button(type="text" icon="el-icon-chat-line-square" size="mini" style="margin-left:5px" @click="showSendInviteRequestResponseDialog(scope.row)") - el-button(type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="hideNotification(scope.row)") + template(v-if="lastLocation.location && isGameRunning && !checkCanInvite(lastLocation.location)") + el-tooltip(placement="top" content="Invite" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-check" size="mini" @click="acceptRequestInvite(scope.row)") + el-tooltip(placement="top" content="Decline with message" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-chat-line-square" size="mini" style="margin-left:5px" @click="showSendInviteRequestResponseDialog(scope.row)") + template(v-if="scope.row.type !== 'requestInviteResponse' && scope.row.type !== 'inviteResponse' && scope.row.type !== 'message'") + el-tooltip(placement="top" content="Decline" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="hideNotification(scope.row)") + el-tooltip(placement="top" content="Delete log" :disabled="hideTooltips") + el-button(type="text" icon="el-icon-delete" size="mini" style="margin-left:5px" @click="deleteNotificationLog(scope.row)") //- profile .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'profile'") diff --git a/html/src/repository/database.js b/html/src/repository/database.js index 6ed5a500..b145567e 100644 --- a/html/src/repository/database.js +++ b/html/src/repository/database.js @@ -21,6 +21,9 @@ class Database { await sqliteService.executeNonQuery( `CREATE TABLE IF NOT EXISTS ${Database.userId}_friend_log_history (id INTEGER PRIMARY KEY, created_at TEXT, type TEXT, user_id TEXT, display_name TEXT, previous_display_name TEXT, trust_level TEXT, previous_trust_level TEXT)` ); + await sqliteService.executeNonQuery( + `CREATE TABLE IF NOT EXISTS ${Database.userId}_notifications (id TEXT PRIMARY KEY, created_at TEXT, type TEXT, sender_user_id TEXT, sender_username TEXT, receiver_user_id TEXT, message TEXT, world_id TEXT, world_name TEXT, image_url TEXT, invite_message TEXT, request_message TEXT, response_message TEXT, expired INTEGER)` + ); await sqliteService.executeNonQuery( `CREATE TABLE IF NOT EXISTS memos (user_id TEXT PRIMARY KEY, edited_at TEXT, memo TEXT)` ); @@ -523,6 +526,103 @@ class Database { } ); } + + async getNotifications() { + var notifications = []; + await sqliteService.execute((dbRow) => { + var row = { + id: dbRow[0], + created_at: dbRow[1], + type: dbRow[2], + senderUserId: dbRow[3], + senderUsername: dbRow[4], + receiverUserId: dbRow[5], + message: dbRow[6], + details: { + worldId: dbRow[7], + worldName: dbRow[8], + imageUrl: dbRow[9], + inviteMessage: dbRow[10], + requestMessage: dbRow[11], + responseMessage: dbRow[12] + } + }; + row.$isExpired = false; + if (dbRow[13] === 1) { + row.$isExpired = true; + } + notifications.unshift(row); + }, `SELECT * FROM ${Database.userId}_notifications LIMIT 5000`); + return notifications; + } + + addNotificationToDatabase(row) { + var entry = { + id: '', + created_at: '', + type: '', + senderUserId: '', + senderUsername: '', + receiverUserId: '', + message: '', + ...row, + details: { + worldId: '', + worldName: '', + imageUrl: '', + inviteMessage: '', + requestMessage: '', + responseMessage: '', + ...row.details + } + }; + var expired = 0; + if (row.$isExpired) { + expired = 1; + } + sqliteService.executeNonQuery( + `INSERT OR IGNORE INTO ${Database.userId}_notifications (id, created_at, type, sender_user_id, sender_username, receiver_user_id, message, world_id, world_name, image_url, invite_message, request_message, response_message, expired) VALUES (@id, @created_at, @type, @sender_user_id, @sender_username, @receiver_user_id, @message, @world_id, @world_name, @image_url, @invite_message, @request_message, @response_message, @expired)`, + { + '@id': entry.id, + '@created_at': entry.created_at, + '@type': entry.type, + '@sender_user_id': entry.senderUserId, + '@sender_username': entry.senderUsername, + '@receiver_user_id': entry.receiverUserId, + '@message': entry.message, + '@world_id': entry.details.worldId, + '@world_name': entry.details.worldName, + '@image_url': entry.details.imageUrl, + '@invite_message': entry.details.inviteMessage, + '@request_message': entry.details.requestMessage, + '@response_message': entry.details.responseMessage, + '@expired': expired + } + ); + } + + deleteNotification(rowId) { + sqliteService.executeNonQuery( + `DELETE FROM ${Database.userId}_notifications WHERE id = @row_id`, + { + '@row_id': rowId + } + ); + } + + updateNotificationExpired(entry) { + var expired = 0; + if (entry.$isExpired) { + expired = 1; + } + sqliteService.executeNonQuery( + `UPDATE ${Database.userId}_notifications SET expired = @expired WHERE id = @id`, + { + '@id': entry.id, + '@expired': expired + } + ); + } } var self = new Database();