Compare commits

..

No commits in common. "8cc738c9b203acb26b807e83699fe719d4107dee" and "3357009d6a488131609431eb5e8de4929918cf3b" have entirely different histories.

18 changed files with 180 additions and 752 deletions

View File

@ -1,5 +1,5 @@
import { Response } from "express"; import { Response } from "express";
import { getAccounts, registerAccount } from "../db/api-db"; import { getAccounts, registerAccount } from "../db/api";
import { getAddresseId } from "../db/utils/mail"; import { getAddresseId } from "../db/utils/mail";
import statusCodes from "../utils/statusCodes"; import statusCodes from "../utils/statusCodes";

View File

@ -1,5 +1,5 @@
import statusCode from "../utils/statusCodes"; import statusCode from "../utils/statusCodes";
import { getMembers } from "../db/api-db"; import { getMembers } from "../db/api";
import logger from "../system/Logger"; import logger from "../system/Logger";
export async function members(body, res) { export async function members(body, res) {

View File

@ -1,5 +1,5 @@
import statusCode from "../utils/statusCodes"; import statusCode from "../utils/statusCodes";
import { getMessages } from "../db/api-db"; import { getMessages } from "../db/api";
import logger from "../system/Logger"; import logger from "../system/Logger";
import { Response } from "express"; import { Response } from "express";

View File

@ -1,5 +1,5 @@
import statusCode from "../utils/statusCodes"; import statusCode from "../utils/statusCodes";
import { getRooms } from "../db/api-db"; import { getRooms } from "../db/api";
import logger from "../system/Logger"; import logger from "../system/Logger";
import { Response } from "express"; import { Response } from "express";

View File

@ -34,7 +34,7 @@ export async function getRooms(mailboxId) {
room.room_name AS roomName, room.room_name AS roomName,
address.email AS user, address.email AS user,
room.owner_id AS userId, room.owner_id AS userId,
(COUNT(notSeenThreads.message_id) + COUNT(notSeenRoom.message_id)) AS notSeen, COUNT(notSeen.message_id) AS notSeen,
room.room_type AS roomType, room.room_type AS roomType,
mailbox_message.mailbox_id AS mailboxId, mailbox_message.mailbox_id AS mailboxId,
app_thread.parent_id app_thread.parent_id
@ -43,37 +43,35 @@ export async function getRooms(mailboxId) {
INNER JOIN mailbox_message ON mailbox_message.message_id = message.message_id INNER JOIN mailbox_message ON mailbox_message.message_id = message.message_id
INNER JOIN address ON address.address_id = room.owner_id INNER JOIN address ON address.address_id = room.owner_id
LEFT JOIN app_thread ON room.room_id = app_thread.room_id LEFT JOIN app_thread ON room.room_id = app_thread.room_id
LEFT JOIN ( LEFT JOIN (
SELECT app_room_message.room_id, app_room_message.message_id SELECT app_room_message.room_id, app_room_message.message_id
FROM app_room_message FROM app_room_message
WHERE INNER JOIN flag ON flag.message_id = app_room_message.message_id
"\\\\Seen" NOT IN ( INNER JOIN flag_name ON flag.flag_id = flag_name.flag_id
SELECT flag_name FROM flag_name WHERE flag_name.flag_name = "\\\\Seen"
INNER JOIN flag ON flag.flag_id = flag_name.flag_id AND flag.message_id = app_room_message.message_id ) notSeen ON notSeen.room_id = room.room_id
WHERE flag_name.flag_id = flag.flag_id
)
) notSeenRoom ON notSeenThreads.room_id = room.room_id
LEFT JOIN (
SELECT app_room_message.message_id, app_thread.parent_id
FROM app_room
INNER 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
WHERE
"\\\\Seen" NOT IN (
SELECT flag_name FROM flag_name
INNER JOIN flag ON flag.flag_id = flag_name.flag_id AND flag.message_id = app_room_message.message_id
WHERE flag_name.flag_id = flag.flag_id
)
) notSeenThreads ON notSeenThreads.parent_id = room.room_id
WHERE WHERE
mailbox_message.mailbox_id = ? mailbox_message.mailbox_id = ?
GROUP BY room.room_id GROUP BY room.room_id
ORDER BY room.lastUpdate DESC ORDER BY room.lastUpdate DESC
`; `;
// todo parent_id replace to root_id const query2 = `
SELECT
room.room_id AS id,
COUNT(t.message_id) AS notSeen
FROM app_room room
INNER JOIN message ON message.message_id = room.message_id
INNER JOIN mailbox_message ON mailbox_message.message_id = message.message_id
INNER JOIN address ON address.address_id = room.owner_id
LEFT JOIN (
SELECT app_room_message.room_id, app_room_message.message_id
FROM app_room_message
INNER JOIN flag ON flag.message_id = app_room_message.message_id
INNER JOIN flag_name ON flag.flag_id = flag_name.flag_id
WHERE flag_name.flag_name = "\\\\Seen"
) t ON t.room_id = room.room_id
GROUP BY room.room_id
`;
const values = [mailboxId]; const values = [mailboxId];
return await execQueryAsync(query, values); return await execQueryAsync(query, values);
} }

View File

@ -51,6 +51,12 @@ export function updateLastUpdateRoom(roomId: number, idate: string) {
execQuery(query, values); execQuery(query, values);
} }
export async function incrementNotSeenRoom(roomId: number) {
const query = `UPDATE app_room SET notSeen = notSeen + 1 WHERE room_id = ?`;
const values = [roomId];
execQuery(query, values);
}
export async function getThreadInfo(messageID: string): Promise<{ room_id: number; root_id: number }[]> { export async function getThreadInfo(messageID: string): Promise<{ room_id: number; root_id: number }[]> {
const query = ` const query = `
SELECT SELECT

View File

@ -27,10 +27,10 @@ export function registerMailbox_message(
execQuery(query, values); execQuery(query, values);
} }
export async function registerFlag(messageId: number, flagId: number) { export function registerFlag(messageId: number, flagId: number) {
const query = `INSERT IGNORE INTO flag (message_id, flag_id) VALUES (?, ?)`; const query = `INSERT IGNORE INTO flag_name (message_id, flag_id) VALUES (?, ?)`;
const values = [messageId, flagId]; const values = [messageId, flagId];
return await execQueryAsync(query, values); execQuery(query, values);
} }
export function registerBodypart(messageId: number, part: string, bodypartId: number, bytes: number, nbLines: null) { export function registerBodypart(messageId: number, part: string, bodypartId: number, bytes: number, nbLines: null) {

View File

@ -123,6 +123,7 @@ CREATE TABLE app_room (
owner_id INT NOT NULL, owner_id INT NOT NULL,
message_id INT NOT NULL, message_id INT NOT NULL,
room_type INT NOT NULL DEFAULT 0, room_type INT NOT NULL DEFAULT 0,
notSeen INT NOT NULL DEFAULT 0,
lastUpdate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(), lastUpdate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(),
PRIMARY KEY (room_id), PRIMARY KEY (room_id),
UNIQUE KEY (owner_id, message_id, room_type), UNIQUE KEY (owner_id, message_id, room_type),

View File

@ -8,6 +8,7 @@ import {
registerMember, registerMember,
getAllMembers, getAllMembers,
getThreadInfo, getThreadInfo,
incrementNotSeenRoom,
getThreadInfoOnId, getThreadInfoOnId,
} from "../../db/message/saveMessage-db"; } from "../../db/message/saveMessage-db";
@ -74,6 +75,14 @@ export default class RegisterMessageInApp {
return this.ownerId == this.userId; return this.ownerId == this.userId;
} }
async incrementNotSeen(roomId: number) {
// todo it appears there is an error with notifications
console.log("incrementRead", roomId)
if (!this.isSeen) {
await incrementNotSeenRoom(roomId);
}
}
async registerMembers(roomId: number) { async registerMembers(roomId: number) {
getAllMembers(this.messageId).then((res) => { getAllMembers(this.messageId).then((res) => {
if (res.lenght == 0) return; if (res.lenght == 0) return;
@ -88,6 +97,7 @@ export default class RegisterMessageInApp {
try { try {
const roomId = await createRoom(this.envelope.subject, owner, this.messageId, roomType); const roomId = await createRoom(this.envelope.subject, owner, this.messageId, roomType);
await registerMessageInRoom(this.messageId, roomId, this.envelope.date); await registerMessageInRoom(this.messageId, roomId, this.envelope.date);
await this.incrementNotSeen(roomId);
await this.registerMembers(roomId); await this.registerMembers(roomId);
return roomId; return roomId;
} catch (err) { } catch (err) {
@ -103,6 +113,7 @@ export default class RegisterMessageInApp {
} else { } else {
// not a reply, add to the list of message if this sender // not a reply, add to the list of message if this sender
await registerMessageInRoom(this.messageId, res[0].room_id, this.envelope.date); await registerMessageInRoom(this.messageId, res[0].room_id, this.envelope.date);
await this.incrementNotSeen(res[0].room_id);
} }
}); });
} }
@ -122,6 +133,8 @@ export default class RegisterMessageInApp {
}); });
// impl register previous message or go back // impl register previous message or go back
await registerMessageInRoom(this.messageId, threadId, this.envelope.date); await registerMessageInRoom(this.messageId, threadId, this.envelope.date);
await this.incrementNotSeen(root_id);
await this.incrementNotSeen(threadId);
await this.registerMembers(threadId); await this.registerMembers(threadId);
}, },
); );
@ -131,10 +144,12 @@ export default class RegisterMessageInApp {
const hasSameMembers = await hasSameMembersAsParent(this.messageId, this.inReplyTo); const hasSameMembers = await hasSameMembersAsParent(this.messageId, this.inReplyTo);
if (hasSameMembers) { if (hasSameMembers) {
await registerMessageInRoom(this.messageId, roomId, this.envelope.date); await registerMessageInRoom(this.messageId, roomId, this.envelope.date);
await this.incrementNotSeen(roomId);
if (isThread) { if (isThread) {
await getThreadInfoOnId(roomId).then(async (res) => { await getThreadInfoOnId(roomId).then(async (res) => {
let root_id = res[0].root_id; let root_id = res[0].root_id;
if (root_id == undefined) root_id = res[0].room_id; if (root_id == undefined) root_id = res[0].room_id;
await this.incrementNotSeen(res[0].root_id);
}); });
} }
} else { } else {
@ -157,12 +172,9 @@ export default class RegisterMessageInApp {
} else { } else {
// it is not a reply and not a dm // it is not a reply and not a dm
// so it is a channel, which can be possibly a group // so it is a channel, which can be possibly a group
// this version is considered to be for personnal use await this.initiateRoom(this.ownerId, RoomType.ROOM);
// so by default it will be a group
await this.initiateRoom(this.ownerId, RoomType.GROUP);
} }
} else { } else {
// todo if contains reply in recipent then is channel
await this.createOrRegisterOnExistence(this.ownerId, RoomType.ROOM); await this.createOrRegisterOnExistence(this.ownerId, RoomType.ROOM);
} }
} }

View File

@ -27,6 +27,7 @@ export default class updateMessage {
deleteFlag(messageId, flag.flag_id); deleteFlag(messageId, flag.flag_id);
}); });
// todo update seen counter rooms
if (flagsToAdd.includes("\\Seen")) { if (flagsToAdd.includes("\\Seen")) {
updateMailboxSeen(messageId, true); updateMailboxSeen(messageId, true);
} else if (flagToRm.find((f) => f.flag_name == "\\Seen")) { } else if (flagToRm.find((f) => f.flag_name == "\\Seen")) {

718
back/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,6 @@
"clean": "rm -rf build" "clean": "rm -rf build"
}, },
"dependencies": { "dependencies": {
"@databases/mysql-test": "^4.0.2",
"ajv": "^8.12.0", "ajv": "^8.12.0",
"ajv-formats": "^2.1.1", "ajv-formats": "^2.1.1",
"colors": "^1.4.0", "colors": "^1.4.0",

View File

@ -1,48 +0,0 @@
process.env.NODE_ENV = "test";
import { jest, describe, it, expect } from "@jest/globals";
import { execQueryAsync, execQuery } from "../../db/db";
import { createRoom, registerMessageInRoom } from "../../db/message/saveMessage-db";
import { registerFlag, registerMessage } from "../../db/message/storeMessage-db";
import { getFlagId } from "../../db/utils/mail";
import { RoomType } from "../../mails/message/saveMessage";
beforeAll(async () => {
console.log(await execQueryAsync(`SHOW TABLES`, []));
// mocked(incrementNotSeenRoom).mockImplementation(db.incrementNotSeenRoom);
});
beforeEach(async () => {
const query = "SELECT table_name FROM INFORMATION_SCHEMA.tables WHERE table_schema = 'mail_test'";
execQueryAsync(query, []).then((results) => {
execQuery("SET FOREIGN_KEY_CHECKS=0", []);
results.map((table) => {
execQuery("DELETE FROM " + table.table_name, []);
// execQuery("DROP TABLE " + table.table_name);
});
});
});
const insertMessageWithFlag = async (flags: string[]): Promise<number> => {
const messageId = await registerMessage("", 0, "");
flags.forEach(async (flag) => {
const flagId = await getFlagId(flag);
await registerFlag(messageId, flagId);
});
return messageId;
}
describe("api-db", () => {
it.only("should count the number of unseen message in a room", async () => {
const msgIdSeen = await insertMessageWithFlag(["\\\\Seen"]);
const msgIdNotSeen = await insertMessageWithFlag([]);
const msgIdNotSeen2 = await insertMessageWithFlag([]);
const roomId = await createRoom("roomName", 0, msgIdSeen, RoomType.ROOM);
await registerMessageInRoom(msgIdSeen, roomId, "");
await registerMessageInRoom(msgIdNotSeen, roomId, "");
await registerMessageInRoom(msgIdNotSeen2, roomId, "");
const res =
expect()
})
});

View File

@ -24,7 +24,7 @@ jest.mock("../../db/utils/mail", () => {
getUserIdOfMailbox: jest.fn(), getUserIdOfMailbox: jest.fn(),
}; };
}); });
jest.mock("../../db/message/saveMessage-db", () => { jest.mock("../../db/saveMessage-db", () => {
return { return {
createRoom: jest.fn(), createRoom: jest.fn(),
registerMessageInRoom: jest.fn(), registerMessageInRoom: jest.fn(),
@ -36,6 +36,7 @@ jest.mock("../../db/message/saveMessage-db", () => {
getAllMembers: jest.fn(), getAllMembers: jest.fn(),
getThreadInfo: jest.fn(), getThreadInfo: jest.fn(),
getThreadInfoOnId: jest.fn(), getThreadInfoOnId: jest.fn(),
incrementNotSeenRoom: jest.fn(),
}; };
}); });
import { getAddresseId, getUserIdOfMailbox, findRoomByOwner } from "../../db/utils/mail"; import { getAddresseId, getUserIdOfMailbox, findRoomByOwner } from "../../db/utils/mail";
@ -50,6 +51,7 @@ import {
getAllMembers, getAllMembers,
getThreadInfo, getThreadInfo,
getThreadInfoOnId, getThreadInfoOnId,
incrementNotSeenRoom,
} from "../../db/message/saveMessage-db"; } from "../../db/message/saveMessage-db";
import { AttrsWithEnvelopeTest, createReplyWithSameMembers } from "../test-utils/test-messageUtils"; import { AttrsWithEnvelopeTest, createReplyWithSameMembers } from "../test-utils/test-messageUtils";
// todo esbuild // todo esbuild
@ -83,6 +85,7 @@ beforeAll(async () => {
mocked(getAllMembers).mockImplementation(db.getAllMembers); mocked(getAllMembers).mockImplementation(db.getAllMembers);
mocked(getThreadInfo).mockImplementation(db.getThreadInfo); mocked(getThreadInfo).mockImplementation(db.getThreadInfo);
mocked(getThreadInfoOnId).mockImplementation(db.getThreadInfoOnId); mocked(getThreadInfoOnId).mockImplementation(db.getThreadInfoOnId);
mocked(incrementNotSeenRoom).mockImplementation(db.incrementNotSeenRoom);
}); });
let msgFromUs_1: AttrsWithEnvelopeTest; let msgFromUs_1: AttrsWithEnvelopeTest;
@ -148,7 +151,7 @@ describe("saveMessage", () => {
expect(createOrRegisterOnExistence).toHaveBeenCalledWith(db.users[1].id, RoomType.DM); expect(createOrRegisterOnExistence).toHaveBeenCalledWith(db.users[1].id, RoomType.DM);
}); });
it("should create a GROUP when there is a new first message from us to multiple recipients", async () => { it("should create a ROOM when there is a new first message from us to multiple recipients", async () => {
const attrs = generateAttrs({ from: [ownUser.user], to: [db.users[1].user, db.users[2].user] }); const attrs = generateAttrs({ from: [ownUser.user], to: [db.users[1].user, db.users[2].user] });
const register = new registerMessageInApp(messageId, attrs, boxId); const register = new registerMessageInApp(messageId, attrs, boxId);
@ -159,7 +162,7 @@ describe("saveMessage", () => {
await register.save(); await register.save();
expect(initiateRoom).toHaveBeenCalledWith(ownUser.id, RoomType.GROUP); expect(initiateRoom).toHaveBeenCalledWith(ownUser.id, RoomType.ROOM);
}); });
// it("response to new first message to multiple recipients with same members should change room type to GROUP", () => { // it("response to new first message to multiple recipients with same members should change room type to GROUP", () => {
@ -217,6 +220,7 @@ describe("saveMessage", () => {
await register.save(); await register.save();
register = new registerMessageInApp(replyTo1_2.message_id, replyTo1_2.attrs, boxId); register = new registerMessageInApp(replyTo1_2.message_id, replyTo1_2.attrs, boxId);
await register.save(); await register.save();
let newReplyInThread = createReplyWithSameMembers(replyTo1_2, db); let newReplyInThread = createReplyWithSameMembers(replyTo1_2, db);
register = new registerMessageInApp(newReplyInThread.message_id, newReplyInThread.attrs, boxId); register = new registerMessageInApp(newReplyInThread.message_id, newReplyInThread.attrs, boxId);
await register.save(); await register.save();
@ -225,4 +229,48 @@ describe("saveMessage", () => {
expect(db.room_message.filter((message) => message.room_id === db.rooms[1].room_id)).toHaveLength(2); expect(db.room_message.filter((message) => message.room_id === db.rooms[1].room_id)).toHaveLength(2);
}); });
}); });
describe("unseen behavior", () => {
it("should add unseen in room when a message creates a room", async () => {
let register = new registerMessageInApp(msgFromUs_1.message_id, msgFromUs_1.attrs, boxId);
await register.save();
expect(db.rooms[0].notSeen).toBe(1);
});
it("should add unseen in room when a message joins a room", async () => {
let register = new registerMessageInApp(msgFromUs_1.message_id, msgFromUs_1.attrs, boxId);
await register.save();
register = new registerMessageInApp(msgFromUs_1.message_id, msgFromUs_1.attrs, boxId);
await register.save();
expect(db.rooms).toHaveLength(1);
expect(db.rooms[0].notSeen).toBe(2);
});
it("should add unseen in root room and THREAD when new message creates a THREAD", async () => {
let register = new registerMessageInApp(msgFromUs_1.message_id, msgFromUs_1.attrs, boxId);
await register.save();
register = new registerMessageInApp(replyTo1_2.message_id, replyTo1_2.attrs, boxId);
await register.save();
register = new registerMessageInApp(replyTo2_3.message_id, replyTo2_3.attrs, boxId);
await register.save();
expect(db.rooms[0].notSeen).toBe(3);
expect(db.rooms[1].notSeen).toBe(1);
expect(db.rooms[2].notSeen).toBe(1);
});
it("should add unseen in root room and THREAD when new message joins in THREAD", async () => {
let register = new registerMessageInApp(msgFromUs_1.message_id, msgFromUs_1.attrs, boxId);
await register.save();
register = new registerMessageInApp(replyTo1_2.message_id, replyTo1_2.attrs, boxId);
await register.save();
let newReplyInThread = createReplyWithSameMembers(replyTo1_2, db);
register = new registerMessageInApp(newReplyInThread.message_id, newReplyInThread.attrs, boxId);
await register.save();
expect(db.rooms).toHaveLength(2);
expect(db.rooms[0].notSeen).toBe(3);
expect(db.rooms[1].notSeen).toBe(2);
});
});
}); });

View File

@ -135,6 +135,12 @@ export default class saveMessageDatabase {
return Promise.resolve([{ room_id: room.root_id, root_id: room.root_id }]); return Promise.resolve([{ room_id: room.root_id, root_id: room.root_id }]);
}; };
incrementNotSeenRoom = (roomId: number): Promise<void> => {
const room = this._findRoomById(roomId);
if (room) room.notSeen++; // todo
return Promise.resolve();
};
findRoomByOwner = (ownerId: number): Promise<{ room_id: number }[]> => { findRoomByOwner = (ownerId: number): Promise<{ room_id: number }[]> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const rooms = this.rooms.filter((room) => room.owner_id === ownerId); const rooms = this.rooms.filter((room) => room.owner_id === ownerId);

View File

@ -107,7 +107,7 @@ const classes = (): string => {
</div> </div>
<div class="content" :class="[classes()]"> <div class="content" :class="[classes()]">
<iframe ref="iframe"></iframe> <iframe ref="iframe"></iframe>
<div class="options">options {{ props?.msg?.flags }}</div> <div class="options">options</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -74,9 +74,11 @@ const router = useRouter();
.object { .object {
color: var(--secondary-text); color: var(--secondary-text);
line-height: 1.8rem;
font-size: 1.3rem; font-size: 1.3rem;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
width: 100%;
} }
</style> </style>

View File

@ -17,14 +17,13 @@ const router = useRouter();
v-bind:class="store.state.activeRoom == props.threadId ? 'selected' : ''" v-bind:class="store.state.activeRoom == props.threadId ? 'selected' : ''"
class="room" class="room"
> >
<div class="roomName">{{ room.roomName }}</div> {{ room.roomName }}
<Badge class="badge" v-if="room.notSeen > 0" :value="room.notSeen" type="badge-number" /> <Badge class="badge" v-if="room.notSeen > 0" :value="room.notSeen" type="badge-number" />
</div> </div>
</template> </template>
<style scoped> <style scoped>
.room { .room {
display: flex;
box-sizing: border-box; box-sizing: border-box;
contain: content; contain: content;
display: flex; display: flex;
@ -34,16 +33,6 @@ const router = useRouter();
color: var(--secondary-text); color: var(--secondary-text);
} }
.badge {
margin-right: 7px;
}
.roomName {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.room:hover, .room:hover,
.selected { .selected {
background-color: var(--selected); background-color: var(--selected);