diff --git a/back/db/saveMessageApp.js b/back/db/saveMessageApp.js index c50748c..d5a5f18 100644 --- a/back/db/saveMessageApp.js +++ b/back/db/saveMessageApp.js @@ -2,13 +2,31 @@ const { transformEmojis } = require("../utils/string.js"); const { db, execQueryAsync, execQueryAsyncWithId, execQuery } = require("./db.js"); const { queryFromId, queryToId, queryCcId } = require("./utils/addressQueries.js"); -async function createRoom(roomName, ownerId, messageId) { +async function getAllMembers(messageId) { + const query = ` + SELECT GROUP_CONCAT(address.address_id) AS is + FROM address + INNER JOIN address_field ON + address_field.address_id = address.address_id AND + address_field.message_id = ? + GROUP BY address_field.message_id + `; + const values = [messageId]; + return await execQueryAsync(query, values); +} + +async function registerMember(roomId, memberId) { + const query = `INSERT IGNORE INTO app_room_member (room_id, member_id) VALUES (?, ?)`; + const values = [roomId, memberId]; + return await execQueryAsync(query, values); +} + +async function createRoom(roomName, ownerId, messageId, roomType) { if (!roomName) roomName = "No room name"; roomName = transformEmojis(roomName); - const query = `INSERT IGNORE INTO app_room (room_name, owner_id, message_id) VALUES (?, ?, ?)`; - const values = [roomName.substring(0, 255), ownerId, messageId]; + const query = `INSERT IGNORE INTO app_room (room_name, owner_id, message_id, room_type) VALUES (?, ?, ?, ?)`; + const values = [roomName.substring(0, 255), ownerId, messageId, roomType]; return await execQueryAsyncWithId(query, values); - // todo add members } async function registerMessageInRoom(messageId, roomId, isSeen, idate) { @@ -17,10 +35,9 @@ async function registerMessageInRoom(messageId, roomId, isSeen, idate) { await execQueryAsync(query, values); updateLastUpdateRoom(roomId, idate); - - if (!isSeen) { - incrementNotSeenRoom(roomId); - } + // if (!isSeen) { + // incrementNotSeenRoom(roomId); + // } } function updateLastUpdateRoom(roomId, idate) { @@ -33,29 +50,25 @@ function incrementNotSeenRoom(roomId) { // todo } -async function createThread(threadName, ownerId, messageId, parentRoomId, isDm) { - const rootRoomId = -1; // todo - const threadId = await createRoom(threadName, ownerId, messageId); - const query = `INSERT INTO app_thread (room_id, parent_room_id, root_room_id, isDm) VALUES (?, ?, ?, ?)`; - const values = [threadId, parentRoomId, rootRoomId, isDm]; +async function getRoomInfo(messageID) { + const query = ` + SELECT + app_room.room_id + app_thread.root_id + FROM app_room + LEFT JOIN app_thread ON app_thread.room_id = app_room.room_id + INNER JOIN app_room_message ON app_room_message.room_id = app_room.room_id + INNER JOIN message ON message.message_id = app_room_message.message_id + WHERE message.messageID = ? + `; + const values = [messageID]; return await execQueryAsync(query, values); - // todo add members } -async function registerMessageInThread(messageId, threadId, isSeen) { - // todo check if it is still a thread or should be a room - // todo isdm - console.log("register message in thread") -} - -function updateLastUpdateThread(threadId) { - // todo - // check for parent -} - -function incrementNotSeenThread(threadId) { - // todo - // also increment root room +async function registerThread(roomId, parentId, rootId) { + const query = `INSERT IGNORE INTO app_thread (room_id, parent_id, root_id) VALUES (?, ?, ?)`; + const values = [roomId, parentId, rootId]; + return await execQueryAsync(query, values); } async function isRoomGroup(roomId) { @@ -119,11 +132,13 @@ async function hasSameMembersAsParent(messageId, messageID) { } module.exports = { + getAllMembers, + registerMember, createRoom, registerMessageInRoom, - createThread, - registerMessageInThread, + registerThread, isRoomGroup, findRoomsFromMessage, hasSameMembersAsParent, + getRoomInfo }; diff --git a/back/db/structureV2.sql b/back/db/structureV2.sql index 92adf9d..ce49f64 100644 --- a/back/db/structureV2.sql +++ b/back/db/structureV2.sql @@ -122,11 +122,11 @@ CREATE TABLE app_room ( room_name VARCHAR(255) NOT NULL, owner_id INT NOT NULL, message_id INT NOT NULL, - isGroup BOOLEAN NOT NULL DEFAULT false, + room_type INT NOT NULL DEFAULT 0, notSeen INT NOT NULL DEFAULT 0, lastUpdate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(), PRIMARY KEY (room_id), - UNIQUE KEY (owner_id, message_id, isGroup), + UNIQUE KEY (owner_id, message_id, room_type), FOREIGN KEY (owner_id) REFERENCES address(address_id), FOREIGN KEY (message_id) REFERENCES message(message_id) ); @@ -134,14 +134,13 @@ CREATE TABLE app_room ( -- 12 CREATE TABLE app_thread ( room_id INT NOT NULL, - parent_room_id INT, - root_room_id INT, - isDm BOOLEAN NOT NULL DEFAULT false, + parent_id INT, + root_id INT, PRIMARY KEY (room_id), - UNIQUE KEY (room_id, parent_room_id, root_room_id), + UNIQUE KEY (room_id, parent_id, root_id), FOREIGN KEY (room_id) REFERENCES app_room(room_id) ON DELETE CASCADE, - FOREIGN KEY (parent_room_id) REFERENCES app_room(room_id) ON DELETE SET NULL, - FOREIGN KEY (root_room_id) REFERENCES app_room(room_id) ON DELETE SET NULL + FOREIGN KEY (parent_id) REFERENCES app_room(room_id) ON DELETE SET NULL, + FOREIGN KEY (root_id) REFERENCES app_room(room_id) ON DELETE SET NULL ); -- 13 @@ -157,6 +156,7 @@ CREATE TABLE app_room_message ( CREATE TABLE app_room_member ( room_id INT NOT NULL, member_id INT NOT NULL, + UNIQUE KEY (room_id, member_id), FOREIGN KEY (room_id) REFERENCES app_room(room_id) ON DELETE CASCADE, FOREIGN KEY (member_id) REFERENCES address(address_id) ); diff --git a/back/jest-mysql-config.js b/back/jest-mysql-config.js deleted file mode 100644 index 5e7c276..0000000 --- a/back/jest-mysql-config.js +++ /dev/null @@ -1,14 +0,0 @@ -const MYSQL = require("./sql/config.json").mysql; - -module.exports = { - databaseOptions: { - host: "localhost", - port: 3306, - user: MYSQL.user, - password: MYSQL.pwd, - database: "mail_test", - }, - createDatabase: true, - dbSchema: "./sql/structureV2.sql", - truncateDatabase: true, -}; diff --git a/back/mails/saveMessage.js b/back/mails/saveMessage.js index 789f46b..41fb2c5 100644 --- a/back/mails/saveMessage.js +++ b/back/mails/saveMessage.js @@ -6,10 +6,14 @@ const { isRoomGroup, findRoomsFromMessage, hasSameMembersAsParent, + registerThread, + registerMember, + getAllMembers, } = require("../db/saveMessageApp"); const { findRoomByOwner, getAddresseId, getUserIdOfMailbox } = require("../db/mail"); -const { nbMembers } = require("./utils/statusUtils"); +const { nbMembers } = require("./utils/envelopeUtils"); +const { logger } = require("../system/Logger"); /** * take object address and join mailbox and host to return mailbox@host @@ -26,11 +30,11 @@ async function initiateRoom(envelope, ownerId, messageId, isSeen) { } const roomType = { - ROOM: 'room', - CHANNEL: 'channel', - GROUP: 'group', - DM: 'dm', - THREAD: 'thread' + ROOM: 0, + CHANNEL: 1, + GROUP: 2, + DM: 3, + THREAD: 4 } class registerMessageInApp { @@ -56,19 +60,30 @@ class registerMessageInApp { return this.ownerId == this.userId; } - async initiateRoom(owner, roomType) { - // todo roomType - await createRoom(this.envelope.subject, owner, this.messageId).then(async (roomId) => { - // todo register members - await registerMessageInRoom(this.messageId, roomId, this.isSeen, this.envelope.date); + async registerMembers(roomId) { + getAllMembers(this.messageId).then((res) => { + res[0].id.split(',').foreach(async (memberId) => { + await registerMember(roomId, memberId); + }); }); } + async initiateRoom(owner, type) { + try { + const roomId = await createRoom(this.envelope.subject, owner, this.messageId, type); + await registerMessageInRoom(this.messageId, roomId, this.isSeen, this.envelope.date); + this.registerMembers(roomId); + return roomId; + } catch (err) { + logger.error(err); + } + } + async createOrRegisterOnExistence(owner, roomType) { await findRoomByOwner(owner).then(async (res) => { if (res.length == 0) { // first message with this sender - await initiateRoom(owner, roomType); + await this.initiateRoom(owner, roomType); } else { // not a reply, add to the list of message if this sender await registerMessageInRoom(this.messageId, res[0].room_id, this.isSeen, this.envelope.date); @@ -76,11 +91,28 @@ class registerMessageInApp { }); } + async initiateThread() { + await createRoom(this.envelope.subject, owner, this.messageId, roomType.THREAD).then(async (roomId) => { + // find parent room infos + await getRoomInfo(this.envelope.inReplyTo).then(async (room) => { + // todo room not lenght, reply to transfer ? + let root_id = room[0].root_id + if (!root_id) root_id = room[0].room_id + await registerThread(roomId, room[0].room_id, root_id); + }); + // impl register previous message ? + await registerMessageInRoom(this.messageId, roomId, this.isSeen, this.envelope.date); + await this.registerMembers(roomId); + }); + + } + async createOrRegisterOnMembers(roomId) { const hasSameMembers = await hasSameMembersAsParent(this.messageID, this.envelope.inReplyTo); if (hasSameMembers) { await registerMessageInRoom(this.messageId, roomId, this.isSeen, this.envelope.date); } else { + await this.initiateThread(); await createThread(this.envelope.subject, this.ownerId, this.messageId, roomId, this.isDm()).then( async (threadId) => { await registerMessageInThread(this.messageId, threadId, this.isSeen); @@ -105,7 +137,7 @@ class registerMessageInApp { initiateRoom(this.envelope, this.ownerId, this.messageId, this.isSeen); } } else { - await this.createOrRegisterOnExistence(this.ownerId, roomType.ROOM); + await this.createOrRegisterOnExistence(this.ownerId, roomType.CHANNEL); } } } diff --git a/back/mails/utils/envelopeUtils.js b/back/mails/utils/envelopeUtils.js new file mode 100644 index 0000000..eaa614b --- /dev/null +++ b/back/mails/utils/envelopeUtils.js @@ -0,0 +1,21 @@ +function nbMembers(envelope) { + return getMembers(envelope).length; +} + +function getMembers(envelope) { + const members = []; + const fields = ["from", "to", "sender", "replyTo", "cc", "bcc"]; + fields.forEach((field) => { + if (!envelope[field]) return; + envelope[field].forEach((member) => { + if (members.find((m) => m.mailbox === member.mailbox && m.host === member.host)) return; + members.push(member); + }); + }); + return members; +} + +module.exports = { + nbMembers, + getMembers, +}; diff --git a/back/mails/utils/statusUtils.js b/back/mails/utils/statusUtils.js deleted file mode 100644 index 08b7c13..0000000 --- a/back/mails/utils/statusUtils.js +++ /dev/null @@ -1,18 +0,0 @@ -function nbMembers(envelope) { - let nbMembers = - (envelope.bcc?.length ?? 0) + - (envelope.cc?.length ?? 0) + - (envelope.to?.length ?? 0) + - (envelope.from?.length ?? 0); - if ( - envelope.sender?.length > 0 && - !(envelope.sender[0].mailbox == envelope.from[0].mailbox && envelope.sender[0].host == envelope.from[0].host) - ) { - nbMembers += envelope.sender?.length ?? 0; - } - return nbMembers; -} - -module.exports = { - nbMembers, -}; diff --git a/back/test/mail/utils/envelopeUtils-test.js b/back/test/mail/utils/envelopeUtils-test.js new file mode 100644 index 0000000..f3e1a3f --- /dev/null +++ b/back/test/mail/utils/envelopeUtils-test.js @@ -0,0 +1,45 @@ +const { nbMembers } = require("../../../mails/utils/envelopeUtils"); +const { generateUsers } = require("../../test-utils/test-attrsUtils"); + +describe("envelopeUtils", () => { + const names = generateUsers(6); + describe("nbMembers", () => { + it("sender and from shouldn't be counted twice if there are the same", () => { + const envelope = { + from: [names[0].user], + sender: [names[0].user], + replyTo: null, + to: null, + cc: null, + bcc: null, + inReplyTo: null, + }; + expect(nbMembers(envelope)).toBe(1); + }); + it("sender and from shoud be counted twice if there are the same", () => { + const envelope = { + from: [names[0].user], + sender: [names[1].user], + replyTo: null, + to: null, + cc: null, + bcc: null, + inReplyTo: null, + }; + expect(nbMembers(envelope)).toBe(2); + }); + it("should count every members", () => { + // todo should merge identic members + const envelope = { + from: [names[0].user], + sender: [names[1].user], + replyTo: [names[2].user], + to: [names[3].user], + cc: [names[4].user], + bcc: [names[5].user], + inReplyTo: null, + }; + expect(nbMembers(envelope)).toBe(6); + }); + }); +}); diff --git a/back/test/mail/utils/statusUtils-test.js b/back/test/mail/utils/statusUtils-test.js deleted file mode 100644 index f26a595..0000000 --- a/back/test/mail/utils/statusUtils-test.js +++ /dev/null @@ -1,40 +0,0 @@ -const { nbMembers } = require("../../../mails/utils/statusUtils"); - -describe("statusUtils", () => { - it("sender and from shouldn't be counted twice if there are the same", () => { - const envelope = { - from: [{ name: "", mailbox: "user_1", host: "provider.com" }], - sender: [{ name: "", mailbox: "user_1", host: "provider.com" }], - replyTo: [{ name: "", mailbox: "user_1", host: "provider.com" }], - to: null, - cc: null, - bcc: null, - inReplyTo: null, - }; - expect(nbMembers(envelope)).toBe(1); - }); - it("sender and from shoud be counted twice if there are the same", () => { - const envelope = { - from: [{ name: "", mailbox: "user_1", host: "provider.com" }], - sender: [{ name: "", mailbox: "user_2", host: "provider.com" }], - replyTo: [{ name: "", mailbox: "user_1", host: "provider.com" }], - to: null, - cc: null, - bcc: null, - inReplyTo: null, - }; - expect(nbMembers(envelope)).toBe(2); - }); - it("should count every members", () => { - const envelope = { - from: [{ name: "", mailbox: "user_1", host: "provider.com" }], - sender: [{ name: "", mailbox: "user_2", host: "provider.com" }], - replyTo: [{ name: "", mailbox: "user_1", host: "provider.com" }], - to: [{ name: "", mailbox: "user_1", host: "provider.com" }], - cc: [{ name: "", mailbox: "user_1", host: "provider.com" }], - bcc: [{ name: "", mailbox: "user_1", host: "provider.com" }], - inReplyTo: null, - }; - expect(nbMembers(envelope)).toBe(5); - }); -}); diff --git a/back/test/sql/saveMessageApp-test.js b/back/test/sql/saveMessageApp-test.js deleted file mode 100644 index 6f9d12c..0000000 --- a/back/test/sql/saveMessageApp-test.js +++ /dev/null @@ -1,19 +0,0 @@ -beforeAll(async () => { - // const schema = fs.readFileSync('../../sql/structureV2.sql', 'utf8'); - // await setupDB(mysqlConfig, schema); -}); - -afterAll(async () => { - global.db.query("DROP database mail_test"); -}); - -describe('saveMessageApp', async () => { - - beforeEach(() => { - - }); - - it("", () => { - - }); -}); \ No newline at end of file diff --git a/back/test/test-utils/test-attrsUtils.js b/back/test/test-utils/test-attrsUtils.js new file mode 100644 index 0000000..110d680 --- /dev/null +++ b/back/test/test-utils/test-attrsUtils.js @@ -0,0 +1,61 @@ +const { names } = require("./names"); + +function generateAttrs(options) { + const attrs = { + "size": 42, + "envelope": { + date: "2023-03-21T15:25:42.000Z", + subject: options.subject ?? "subject" + randomString(10), + from: options.from ?? null, + sender: options.sender ?? null, + replyTo: options.replyTo ?? null, + to: options.to ?? null, + cc: options.cc ?? null, + bcc: options.bcc ?? null, + inReplyTo: options.inReplyTo ?? null, + messageId: options.messageId ?? randomString(10), + }, + "date": options.date ?? new Date(), + "flags": options.flags ?? [], + "uid": options.uid ?? randomInt(3), + "modseq": options.modseq ?? randomInt(7), + "x-gm-labels": ["\\Inbox"], + "x-gm-msgid": "1760991478422670209", + "x-gm-thrid": "1760991478422670209", + }; + return attrs; +} + +function generateUsers(nb) { + const users = []; + for (let i = 0; i < nb; i++) { + users.push({ + user: { + name: "", + mailbox: names[i], + host: "provider.com", + }, + id: i, + }); + } + return users; +} + +function randomString(length) { + let result = ""; + const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + +function randomInt(length) { + return (Math.random() * Math.pow(10, length)).toFixed(); +} + +module.exports = { + generateAttrs, + generateUsers, +};