implement save of thread and members

This commit is contained in:
grimhilt 2023-03-31 16:07:02 +02:00
parent e2bd0bafea
commit 7f28823758
10 changed files with 225 additions and 142 deletions

View File

@ -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
};

View File

@ -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)
);

View File

@ -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,
};

View File

@ -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);
}
}
}

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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);
});
});
});

View File

@ -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);
});
});

View File

@ -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("", () => {
});
});

View File

@ -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,
};