From 4799e477becb28e31ef8ed4cae30cab100b26997 Mon Sep 17 00:00:00 2001 From: grimhilt Date: Fri, 14 Apr 2023 20:54:44 +0200 Subject: [PATCH] reply message from thread and room --- back/abl/Account-abl.ts | 4 +- back/abl/Room-abl.ts | 44 +++++++++++++++-- back/db/Room-db.ts | 49 +++++++++++++++++++ back/db/utils/mail.ts | 10 +++- back/mails/message/saveMessage.ts | 6 +-- back/mails/message/storeMessage.ts | 4 +- back/mails/utils/mailBuilder.ts | 10 +++- back/test/mail/saveMessage-test.ts | 6 +-- back/test/test-utils/db/test-saveMessage.ts | 2 +- .../components/structure/message/Composer.vue | 2 + 10 files changed, 117 insertions(+), 20 deletions(-) diff --git a/back/abl/Account-abl.ts b/back/abl/Account-abl.ts index b9ad6d1..b47f6a7 100644 --- a/back/abl/Account-abl.ts +++ b/back/abl/Account-abl.ts @@ -1,6 +1,6 @@ import { Response } from "express"; import { getAccounts, registerAccount } from "../db/api-db"; -import { getAddresseId } from "../db/utils/mail"; +import { getAddressId } from "../db/utils/mail"; import statusCodes from "../utils/statusCodes"; export default class Account { @@ -12,7 +12,7 @@ export default class Account { static async register(body, res: Response) { const { email, pwd, xoauth, xoauth2, host, port, tls } = body; - getAddresseId(email).then((addressId) => { + getAddressId(email).then((addressId) => { registerAccount(addressId, pwd, xoauth, xoauth2, host, port, tls) .then((mailboxId) => { res.status(statusCodes.OK).json({ id: mailboxId }); diff --git a/back/abl/Room-abl.ts b/back/abl/Room-abl.ts index 00cfdb7..b139fe6 100644 --- a/back/abl/Room-abl.ts +++ b/back/abl/Room-abl.ts @@ -2,24 +2,58 @@ import statusCode from "../utils/statusCodes"; import { Response } from "express"; import { RoomType } from "../mails/message/saveMessage"; import { getRoomType } from "../db/message/saveMessage-db"; -import { getRoomOwner } from "../db/Room-db"; +import { getLastMsgData, getRoomOwner } from "../db/Room-db"; import emailManager from "../mails/EmailManager"; import MailBuilder from "../mails/utils/mailBuilder"; +import { getAddresses } from "../db/utils/mail"; +function rmUserFromAddrs(addresses: { email: string }[], user: string) { + let index = addresses.findIndex((a) => a.email == user); + if (index != -1) { + addresses.splice(index, 1); + } +} export default class Room { - // todo change name + // todo change name of reponse static async response(body, res: Response) { const { user, roomId, text, html } = body; - console.log(body) const roomType = (await getRoomType(roomId))[0].room_type; + if (roomType === RoomType.DM) { const ownerEmail = (await getRoomOwner(roomId))[0].email; + const mailBuilder = new MailBuilder(); mailBuilder.from(user).to(ownerEmail).text(text).html(html); + emailManager.getSmtp(user).sendMail(mailBuilder.message); - // send new msg to recipient of dm + res.status(statusCode.OK).send(); } else if (roomType === RoomType.GROUP || roomType === RoomType.THREAD) { - // get all cc and to from of previous message and add them + const lastMsgData = (await getLastMsgData(roomId))[0]; + console.log(lastMsgData); + + const mailBuilder = new MailBuilder(); + mailBuilder.inReplySubject(lastMsgData.subject).inReplyTo(lastMsgData.messageID).text(text).html(html); + + const from = await getAddresses(lastMsgData.fromA); + let to = lastMsgData.toA ? await getAddresses(lastMsgData.toA) : []; + let cc = lastMsgData.ccA ? await getAddresses(lastMsgData.ccA) : []; + + // remove us from recipients + rmUserFromAddrs(to, user); + rmUserFromAddrs(from, user); + + // add sender of previous as recipient if it is not us + if (from.findIndex((a) => a.email == user) == -1) { + to = to.concat(from); + } + + mailBuilder + .from(user) + .to(to.map((a) => a.email)) + .cc(cc.map((a) => a.email)); + + emailManager.getSmtp(user).sendMail(mailBuilder.message); + res.status(statusCode.OK).send(); } else { res.status(statusCode.FORBIDDEN).send({ error: "Cannot add a new message in a room or a channel." }); } diff --git a/back/db/Room-db.ts b/back/db/Room-db.ts index 0599e0b..b82bf93 100644 --- a/back/db/Room-db.ts +++ b/back/db/Room-db.ts @@ -1,4 +1,5 @@ import { execQueryAsync } from "./db"; +import { queryCcId, queryFromId, queryToId } from "./utils/addressQueries"; export async function getRoomOwner(roomId: number) { const query = ` @@ -10,3 +11,51 @@ export async function getRoomOwner(roomId: number) { const values = [roomId]; return await execQueryAsync(query, values); } + +export async function getLastMsgData(roomId: number) { + + const query = ` + SELECT + msg.message_id AS id, + GROUP_CONCAT(fromT.address_id) AS fromA, + GROUP_CONCAT(toT.address_id) AS toA, + GROUP_CONCAT(ccT.address_id) AS ccA, + subjectT.value AS subject, + content.text AS content, + message.idate AS date, + message.messageID AS messageID + FROM app_room_message msg + + ${queryFromId} fromT ON msg.message_id = fromT.message_id + ${queryToId} toT ON msg.message_id = toT.message_id + ${queryCcId} ccT ON msg.message_id = ccT.message_id + + LEFT JOIN ( + SELECT header_field.message_id, header_field.value + FROM header_field + INNER JOIN field_name + WHERE + field_name.field_id = header_field.field_id AND + field_name.field_name = 'subject' + ) subjectT ON msg.message_id = subjectT.message_id + + LEFT JOIN ( + SELECT bodypart.text, header_field.message_id FROM bodypart + INNER JOIN header_field + INNER JOIN field_name + WHERE + field_name.field_id = header_field.field_id AND + field_name.field_name = 'html' AND + bodypart.bodypart_id = header_field.bodypart_id + ) content ON msg.message_id = content.message_id + + INNER JOIN message ON message.message_id = msg.message_id + + WHERE msg.room_id = ? + GROUP BY msg.message_id + ORDER BY message.idate DESC + LIMIT 1 + `; + const values = [roomId]; + return await execQueryAsync(query, values); +} diff --git a/back/db/utils/mail.ts b/back/db/utils/mail.ts index 74aed4c..4feb221 100644 --- a/back/db/utils/mail.ts +++ b/back/db/utils/mail.ts @@ -1,6 +1,6 @@ import { execQueryAsync, execQueryAsyncWithId } from "../db"; -export async function getAddresseId(email: string, name?: string): Promise { +export async function getAddressId(email: string, name?: string): Promise { const localpart = email.split("@")[0]; const domain = email.split("@")[1]; const query = `INSERT INTO address @@ -10,6 +10,12 @@ export async function getAddresseId(email: string, name?: string): Promise { + const query = `SELECT address_id AS id, email FROM address WHERE address_id IN (?)`; + const values = [ids]; + return await execQueryAsync(query, values); +} + export async function getFieldId(field: string): Promise { const query = `INSERT INTO field_name (field_name) VALUES (?) ON DUPLICATE KEY UPDATE field_id=LAST_INSERT_ID(field_id)`; const values = [field]; @@ -22,7 +28,7 @@ export async function getFlagId(flag: string): Promise { return await execQueryAsyncWithId(query, values); } -export async function getMessageIdOnUid(uid: number): Promise<{message_id: number}[]> { +export async function getMessageIdOnUid(uid: number): Promise<{ message_id: number }[]> { const query = `SELECT message_id FROM mailbox_message WHERE uid = ?`; const values = [uid]; return await execQueryAsync(query, values); diff --git a/back/mails/message/saveMessage.ts b/back/mails/message/saveMessage.ts index d749914..ef2b305 100644 --- a/back/mails/message/saveMessage.ts +++ b/back/mails/message/saveMessage.ts @@ -11,7 +11,7 @@ import { getThreadInfoOnId, } from "../../db/message/saveMessage-db"; -import { findRoomByOwner, getAddresseId, getUserIdOfMailbox } from "../../db/utils/mail"; +import { findRoomByOwner, getAddressId, getUserIdOfMailbox } from "../../db/utils/mail"; import { nbMembers } from "../utils/envelopeUtils"; import logger from "../../system/Logger"; import { Attrs, Envelope, User } from "../../interfaces/mail/attrs.interface"; @@ -57,7 +57,7 @@ export default class RegisterMessageInApp { async init() { if (this.envelope.from) { - this.ownerId = await getAddresseId(createAddress(this.envelope.from[0])); // todo use sender or from ? + this.ownerId = await getAddressId(createAddress(this.envelope.from[0])); // todo use sender or from ? } else { throw new Error("Envelope must have a 'from' field"); } @@ -152,7 +152,7 @@ export default class RegisterMessageInApp { if (this.isDm()) { // create or add new message to DM if (!this.envelope.to) throw new Error("Who send a DM and put the recipient in cc ?"); - const userTo = await getAddresseId(createAddress(this.envelope.to[0])); + const userTo = await getAddressId(createAddress(this.envelope.to[0])); await this.createOrRegisterOnExistence(userTo, RoomType.DM); } else { // it is not a reply and not a dm diff --git a/back/mails/message/storeMessage.ts b/back/mails/message/storeMessage.ts index c8d5f76..700a91a 100644 --- a/back/mails/message/storeMessage.ts +++ b/back/mails/message/storeMessage.ts @@ -1,4 +1,4 @@ -import { getAddresseId, getFlagId } from "../../db/utils/mail"; +import { getAddressId, getFlagId } from "../../db/utils/mail"; import { EmailAddress, ParsedMail, simpleParser } from "mailparser"; import moment from "moment"; import Imap from "imap"; @@ -91,7 +91,7 @@ async function saveFromParsedData(parsed: ParsedMail, messageId: number) { // save address field getFieldId(key).then((fieldId) => { parsed[key].value.forEach((addr: EmailAddress, nb: number) => { - getAddresseId(addr.address, addr.name).then(async (addressId) => { + getAddressId(addr.address, addr.name).then(async (addressId) => { await saveAddress_fields(messageId, fieldId, addressId, nb); }); }); diff --git a/back/mails/utils/mailBuilder.ts b/back/mails/utils/mailBuilder.ts index 37dbe10..3458203 100644 --- a/back/mails/utils/mailBuilder.ts +++ b/back/mails/utils/mailBuilder.ts @@ -13,7 +13,7 @@ export default class MailBuilder { this.message.to = addresses; return this; } - + cc(addresses: string[] | string): MailBuilder { this.message.cc = addresses; return this; @@ -43,4 +43,10 @@ export default class MailBuilder { this.message.inReplyTo = messageID; return this; } -} \ No newline at end of file + + inReplySubject(originSubject: string): MailBuilder { + // todo concate if multiple ? + this.message.subject = "RE: " + originSubject; + return this; + } +} diff --git a/back/test/mail/saveMessage-test.ts b/back/test/mail/saveMessage-test.ts index 65b1e1b..40f7a8e 100644 --- a/back/test/mail/saveMessage-test.ts +++ b/back/test/mail/saveMessage-test.ts @@ -20,7 +20,7 @@ const boxId = 1; jest.mock("../../db/utils/mail", () => { return { findRoomByOwner: jest.fn(), - getAddresseId: jest.fn(), + getAddressId: jest.fn(), getUserIdOfMailbox: jest.fn(), }; }); @@ -38,7 +38,7 @@ jest.mock("../../db/message/saveMessage-db", () => { getThreadInfoOnId: jest.fn(), }; }); -import { getAddresseId, getUserIdOfMailbox, findRoomByOwner } from "../../db/utils/mail"; +import { getAddressId, getUserIdOfMailbox, findRoomByOwner } from "../../db/utils/mail"; import { createRoom, registerMessageInRoom, @@ -69,7 +69,7 @@ import { AttrsWithEnvelopeTest, createReplyWithSameMembers } from "../test-utils // if only me reply -> channel beforeAll(async () => { - mocked(getAddresseId).mockImplementation(db.getAddresseId); + mocked(getAddressId).mockImplementation(db.getAddressId); mocked(getUserIdOfMailbox).mockImplementation(db.getUserIdOfMailbox); mocked(findRoomByOwner).mockImplementation(db.findRoomByOwner); diff --git a/back/test/test-utils/db/test-saveMessage.ts b/back/test/test-utils/db/test-saveMessage.ts index 72ef0af..4d1dd9a 100644 --- a/back/test/test-utils/db/test-saveMessage.ts +++ b/back/test/test-utils/db/test-saveMessage.ts @@ -146,7 +146,7 @@ export default class saveMessageDatabase { }); }; - getAddresseId = (email: string, name?: string): Promise => { + getAddressId = (email: string, name?: string): Promise => { const match = this.users.find((user) => user.user.mailbox + "@" + user.user.host == email); return new Promise((resolve, reject) => resolve(match?.id)); }; diff --git a/front/src/components/structure/message/Composer.vue b/front/src/components/structure/message/Composer.vue index ef1c312..557d013 100644 --- a/front/src/components/structure/message/Composer.vue +++ b/front/src/components/structure/message/Composer.vue @@ -30,6 +30,8 @@ const send = () => { html: htmlContent, }); }; + +// todo subject input when dm of group...