From 21f8dbf2deb0d766496c3503f036d07705da4238 Mon Sep 17 00:00:00 2001
From: Aya Morisawa <AyaMorisawa4869@gmail.com>
Date: Thu, 27 Dec 2018 01:24:57 +0900
Subject: [PATCH] Resolve #3248

Co-authored-by: syuilo <syuilotan@yahoo.co.jp>
---
 .../components/messaging-room.message.vue     | 15 ++++--
 .../views/components/messaging-room.vue       |  8 +++
 src/models/messaging-history.ts               | 13 -----
 src/models/messaging-message.ts               |  2 +
 src/server/api/endpoints/messaging/history.ts | 40 +++++++++-----
 .../endpoints/messaging/messages/create.ts    | 28 +---------
 .../endpoints/messaging/messages/delete.ts    | 54 +++++++++++++++++++
 7 files changed, 104 insertions(+), 56 deletions(-)
 delete mode 100644 src/models/messaging-history.ts
 create mode 100644 src/server/api/endpoints/messaging/messages/delete.ts

diff --git a/src/client/app/common/views/components/messaging-room.message.vue b/src/client/app/common/views/components/messaging-room.message.vue
index 872dc2d89e..16588da9a5 100644
--- a/src/client/app/common/views/components/messaging-room.message.vue
+++ b/src/client/app/common/views/components/messaging-room.message.vue
@@ -3,9 +3,9 @@
 	<mk-avatar class="avatar" :user="message.user" target="_blank"/>
 	<div class="content">
 		<div class="balloon" :data-no-text="message.text == null">
-			<!-- <button class="delete-button" v-if="isMe" :title="$t('@.delete')">
-				<img src="/assets/desktop/messaging/delete.png" alt="Delete"/>
-			</button> -->
+			<button class="delete-button" v-if="isMe" :title="$t('@.delete')" @click="del">
+				<img src="/assets/desktop/remove.png" alt="Delete"/>
+			</button>
 			<div class="content" v-if="!message.isDeleted">
 				<misskey-flavored-markdown class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/>
 				<div class="file" v-if="message.file">
@@ -16,7 +16,7 @@
 					</a>
 				</div>
 			</div>
-			<div class="content" v-if="message.isDeleted">
+			<div class="content" v-else>
 				<p class="is-deleted">{{ $t('deleted') }}</p>
 			</div>
 		</div>
@@ -58,6 +58,13 @@ export default Vue.extend({
 				return null;
 			}
 		}
+	},
+	methods: {
+		del() {
+			this.$root.api('messaging/messages/delete', {
+				messageId: this.message.id
+			});
+		}
 	}
 });
 </script>
diff --git a/src/client/app/common/views/components/messaging-room.vue b/src/client/app/common/views/components/messaging-room.vue
index 29aacd3bae..6f13d50c1e 100644
--- a/src/client/app/common/views/components/messaging-room.vue
+++ b/src/client/app/common/views/components/messaging-room.vue
@@ -79,6 +79,7 @@ export default Vue.extend({
 
 		this.connection.on('message', this.onMessage);
 		this.connection.on('read', this.onRead);
+		this.connection.on('deleted', this.onDeleted);
 
 		if (this.isNaked) {
 			window.addEventListener('scroll', this.onScroll, { passive: true });
@@ -204,6 +205,13 @@ export default Vue.extend({
 			}
 		},
 
+		onDeleted(id) {
+			const msg = this.messages.find(m => m.id === id);
+			if (msg) {
+				this.messages = this.messages.filter(m => m.id !== msg.id);
+			}
+		},
+
 		isBottom() {
 			const asobi = 64;
 			const current = this.isNaked
diff --git a/src/models/messaging-history.ts b/src/models/messaging-history.ts
deleted file mode 100644
index 6864e22d2f..0000000000
--- a/src/models/messaging-history.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as mongo from 'mongodb';
-import db from '../db/mongodb';
-
-const MessagingHistory = db.get<IMessagingHistory>('messagingHistories');
-export default MessagingHistory;
-
-export type IMessagingHistory = {
-	_id: mongo.ObjectID;
-	updatedAt: Date;
-	userId: mongo.ObjectID;
-	partnerId: mongo.ObjectID;
-	messageId: mongo.ObjectID;
-};
diff --git a/src/models/messaging-message.ts b/src/models/messaging-message.ts
index 4c52ae78ca..2c7cf37cd8 100644
--- a/src/models/messaging-message.ts
+++ b/src/models/messaging-message.ts
@@ -7,6 +7,8 @@ import isObjectId from '../misc/is-objectid';
 import { length } from 'stringz';
 
 const MessagingMessage = db.get<IMessagingMessage>('messagingMessages');
+MessagingMessage.createIndex('userId');
+MessagingMessage.createIndex('recipientId');
 export default MessagingMessage;
 
 export interface IMessagingMessage {
diff --git a/src/server/api/endpoints/messaging/history.ts b/src/server/api/endpoints/messaging/history.ts
index c026e5dd91..78abea269a 100644
--- a/src/server/api/endpoints/messaging/history.ts
+++ b/src/server/api/endpoints/messaging/history.ts
@@ -1,7 +1,6 @@
 import $ from 'cafy';
-import History from '../../../../models/messaging-history';
 import Mute from '../../../../models/mute';
-import { pack } from '../../../../models/messaging-message';
+import Message, { pack, IMessagingMessage } from '../../../../models/messaging-message';
 import define from '../../define';
 
 export const meta = {
@@ -28,19 +27,36 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
 		deletedAt: { $exists: false }
 	});
 
-	// Get history
-	const history = await History
-		.find({
-			userId: user._id,
-			partnerId: {
-				$nin: mute.map(m => m.muteeId)
-			}
+	const history: IMessagingMessage[] = [];
+
+	for (let i = 0; i < ps.limit; i++) {
+		const found = history.map(m => m.userId.equals(user._id) ? m.recipientId : m.userId);
+
+		const message = await Message.findOne({
+			$or: [{
+				userId: user._id
+			}, {
+				recipientId: user._id
+			}],
+			$and: [{
+				userId: { $nin: found },
+				recipientId: { $nin: found }
+			}, {
+				userId: { $nin: mute.map(m => m.muteeId) },
+				recipientId: { $nin: mute.map(m => m.muteeId) }
+			}]
 		}, {
-			limit: ps.limit,
 			sort: {
-				updatedAt: -1
+				createdAt: -1
 			}
 		});
 
-	res(await Promise.all(history.map(h => pack(h.messageId, user))));
+		if (message) {
+			history.push(message);
+		} else {
+			break;
+		}
+	}
+
+	res(await Promise.all(history.map(h => pack(h._id, user))));
 }));
diff --git a/src/server/api/endpoints/messaging/messages/create.ts b/src/server/api/endpoints/messaging/messages/create.ts
index f8901449fe..3630dc0d54 100644
--- a/src/server/api/endpoints/messaging/messages/create.ts
+++ b/src/server/api/endpoints/messaging/messages/create.ts
@@ -1,7 +1,6 @@
 import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id';
 import Message from '../../../../../models/messaging-message';
 import { isValidText } from '../../../../../models/messaging-message';
-import History from '../../../../../models/messaging-history';
 import User from '../../../../../models/user';
 import Mute from '../../../../../models/mute';
 import DriveFile from '../../../../../models/drive-file';
@@ -114,6 +113,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
 	// 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
 	setTimeout(async () => {
 		const freshMessage = await Message.findOne({ _id: message._id }, { isRead: true });
+		if (freshMessage == null) return; // メッセージが削除されている場合もある
 		if (!freshMessage.isRead) {
 			//#region ただしミュートされているなら発行しない
 			const mute = await Mute.find({
@@ -130,30 +130,4 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
 			pushSw(message.recipientId, 'unreadMessagingMessage', messageObj);
 		}
 	}, 2000);
-
-	// 履歴作成(自分)
-	History.update({
-		userId: user._id,
-		partnerId: recipient._id
-	}, {
-		updatedAt: new Date(),
-		userId: user._id,
-		partnerId: recipient._id,
-		messageId: message._id
-	}, {
-		upsert: true
-	});
-
-	// 履歴作成(相手)
-	History.update({
-		userId: recipient._id,
-		partnerId: user._id
-	}, {
-		updatedAt: new Date(),
-		userId: recipient._id,
-		partnerId: user._id,
-		messageId: message._id
-	}, {
-		upsert: true
-	});
 }));
diff --git a/src/server/api/endpoints/messaging/messages/delete.ts b/src/server/api/endpoints/messaging/messages/delete.ts
new file mode 100644
index 0000000000..dc9bb51b91
--- /dev/null
+++ b/src/server/api/endpoints/messaging/messages/delete.ts
@@ -0,0 +1,54 @@
+
+import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id';
+import Message from '../../../../../models/messaging-message';
+import define from '../../../define';
+import { publishMessagingStream } from '../../../../../stream';
+const ms = require('ms');
+
+export const meta = {
+	stability: 'stable',
+
+	desc: {
+		'ja-JP': '指定したメッセージを削除します。',
+		'en-US': 'Delete a message.'
+	},
+
+	requireCredential: true,
+
+	kind: 'messaging-write',
+
+	limit: {
+		duration: ms('1hour'),
+		max: 300,
+		minInterval: ms('1sec')
+	},
+
+	params: {
+		messageId: {
+			validator: $.type(ID),
+			transform: transform,
+			desc: {
+				'ja-JP': '対象のメッセージのID',
+				'en-US': 'Target message ID.'
+			}
+		}
+	}
+};
+
+export default define(meta, (ps, user) => new Promise(async (res, rej) => {
+	const message = await Message.findOne({
+		_id: ps.messageId,
+		userId: user._id
+	});
+
+	if (message === null) {
+		return rej('message not found');
+	}
+
+	await Message.remove({ _id: message._id });
+
+	publishMessagingStream(message.userId, message.recipientId, 'deleted', message._id);
+	publishMessagingStream(message.recipientId, message.userId, 'deleted', message._id);
+
+	res();
+}));