register flags
This commit is contained in:
parent
46ed3a1f41
commit
af76e8f2f9
@ -27,6 +27,12 @@ export function registerMailbox_message(
|
|||||||
execQuery(query, values);
|
execQuery(query, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function registerFlag(messageId: number, flagId: number) {
|
||||||
|
const query = `INSERT IGNORE INTO flag_name (message_id, flag_id) VALUES (?, ?)`;
|
||||||
|
const values = [messageId, flagId];
|
||||||
|
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) {
|
||||||
const query = `
|
const query = `
|
||||||
INSERT IGNORE INTO part_number
|
INSERT IGNORE INTO part_number
|
||||||
|
@ -161,3 +161,21 @@ CREATE TABLE app_room_member (
|
|||||||
FOREIGN KEY (room_id) REFERENCES app_room(room_id) ON DELETE CASCADE,
|
FOREIGN KEY (room_id) REFERENCES app_room(room_id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (member_id) REFERENCES address(address_id)
|
FOREIGN KEY (member_id) REFERENCES address(address_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- 15
|
||||||
|
create table flag_name (
|
||||||
|
flag_id INT NOT NULL,
|
||||||
|
flag_name VARCHAR(255) NOT NULL,
|
||||||
|
PRIMARY KEY (flag_id),
|
||||||
|
UNIQUE KEY (flag_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 16
|
||||||
|
create table flag (
|
||||||
|
message_id INT NOT NULL,
|
||||||
|
flag_id INT NOT NULL,
|
||||||
|
UNIQUE KEY (message_id, flag_id),
|
||||||
|
FOREIGN KEY (message_id) REFERENCES message(message_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (message_id) REFERENCES message(message_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (flag_id) REFERENCES flag_name(flag_id) ON DELETE CASCADE
|
||||||
|
);
|
@ -16,6 +16,12 @@ export async function getFieldId(field: string): Promise<number> {
|
|||||||
return await execQueryAsyncWithId(query, values);
|
return await execQueryAsyncWithId(query, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getFlagId(flag: string): Promise<number> {
|
||||||
|
const query = `INSERT INTO flag_name (flag_name) VALUES (?) ON DUPLICATE KEY UPDATE flag_id=LAST_INSERT_ID(flag_id)`;
|
||||||
|
const values = [flag];
|
||||||
|
return await execQueryAsyncWithId(query, values);
|
||||||
|
}
|
||||||
|
|
||||||
export async function findRoomByOwner(ownerId: number): Promise<{ room_id: number }[]> {
|
export async function findRoomByOwner(ownerId: number): Promise<{ room_id: number }[]> {
|
||||||
const query = `SELECT room_id FROM app_room WHERE owner_id = ?`;
|
const query = `SELECT room_id FROM app_room WHERE owner_id = ?`;
|
||||||
const values = [ownerId];
|
const values = [ownerId];
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { getAddresseId } from "../../db/utils/mail";
|
import { getAddresseId, getFlagId } from "../../db/utils/mail";
|
||||||
import {simpleParser} from "mailparser";
|
import { EmailAddress, ParsedMail, simpleParser } from "mailparser";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import Imap from "imap";
|
import Imap from "imap";
|
||||||
import {
|
import {
|
||||||
@ -10,6 +10,7 @@ import {
|
|||||||
registerBodypart,
|
registerBodypart,
|
||||||
saveBodypart,
|
saveBodypart,
|
||||||
saveSource,
|
saveSource,
|
||||||
|
registerFlag,
|
||||||
} from "../../db/message/storeMessage-db";
|
} from "../../db/message/storeMessage-db";
|
||||||
|
|
||||||
import { getFieldId } from "../../db/utils/mail";
|
import { getFieldId } from "../../db/utils/mail";
|
||||||
@ -25,11 +26,13 @@ export function saveMessage(attrs: AttrsWithEnvelope, mailboxId: number, imap: I
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
registerMessage(ts, rfc822size, messageID)
|
registerMessage(ts, rfc822size, messageID)
|
||||||
.then((messageId) => {
|
.then((messageId) => {
|
||||||
|
|
||||||
const isSeen: boolean = attrs.flags.includes("\\Seen");
|
const isSeen: boolean = attrs.flags.includes("\\Seen");
|
||||||
const deleted: boolean = attrs.flags.includes("\\Deleted");
|
const deleted: boolean = attrs.flags.includes("\\Deleted");
|
||||||
|
|
||||||
registerMailbox_message(mailboxId, attrs.uid, messageId, attrs?.modseq || 0, isSeen, deleted);
|
registerMailbox_message(mailboxId, attrs.uid, messageId, attrs?.modseq || 0, isSeen, deleted);
|
||||||
|
registerFlags(messageId, attrs.flags);
|
||||||
|
|
||||||
|
// fetch message to save everything
|
||||||
const f = imap.fetch(attrs.uid, { bodies: "" });
|
const f = imap.fetch(attrs.uid, { bodies: "" });
|
||||||
let buffer = "";
|
let buffer = "";
|
||||||
|
|
||||||
@ -70,14 +73,24 @@ export function saveMessage(attrs: AttrsWithEnvelope, mailboxId: number, imap: I
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveFromParsedData(parsed, messageId) {
|
function registerFlags(messageId: number, flags: string[]) {
|
||||||
|
flags.forEach((flag) => {
|
||||||
|
getFlagId(flag).then((flagId) => {
|
||||||
|
registerFlag(messageId, flagId);
|
||||||
|
}).catch((err: Error) => {
|
||||||
|
logger.err(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveFromParsedData(parsed: ParsedMail, messageId: number) {
|
||||||
const promises: Promise<any>[] = [];
|
const promises: Promise<any>[] = [];
|
||||||
Object.keys(parsed).forEach((key) => {
|
Object.keys(parsed).forEach((key) => {
|
||||||
if (["from", "to", "cc", "bcc", "replyTo"].includes(key)) {
|
if (["from", "to", "cc", "bcc", "replyTo"].includes(key)) {
|
||||||
promises.push(
|
promises.push(
|
||||||
// save address field
|
// save address field
|
||||||
getFieldId(key).then((fieldId) => {
|
getFieldId(key).then((fieldId) => {
|
||||||
parsed[key].value.forEach((addr, nb) => {
|
parsed[key].value.forEach((addr: EmailAddress, nb: number) => {
|
||||||
getAddresseId(addr.address, addr.name).then(async (addressId) => {
|
getAddresseId(addr.address, addr.name).then(async (addressId) => {
|
||||||
await saveAddress_fields(messageId, fieldId, addressId, nb);
|
await saveAddress_fields(messageId, fieldId, addressId, nb);
|
||||||
});
|
});
|
||||||
@ -121,7 +134,6 @@ async function saveFromParsedData(parsed, messageId) {
|
|||||||
// todo when transfered
|
// todo when transfered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (process.env["NODE_DEV"] == "TEST") {
|
if (process.env["NODE_DEV"] == "TEST") {
|
||||||
module.exports = {
|
module.exports = {
|
||||||
saveFromParsedData,
|
saveFromParsedData,
|
||||||
|
@ -1,208 +0,0 @@
|
|||||||
import {
|
|
||||||
createRoom,
|
|
||||||
registerMessageInRoom,
|
|
||||||
getRoomType,
|
|
||||||
findRoomsFromMessage,
|
|
||||||
hasSameMembersAsParent,
|
|
||||||
registerThread,
|
|
||||||
registerMember,
|
|
||||||
getAllMembers,
|
|
||||||
getThreadInfo,
|
|
||||||
incrementNotSeenRoom,
|
|
||||||
getThreadInfoOnId,
|
|
||||||
} from "../db/saveMessage-db";
|
|
||||||
|
|
||||||
import { findRoomByOwner, getAddresseId, getUserIdOfMailbox } from "../db/utils/mail";
|
|
||||||
import { nbMembers } from "./utils/envelopeUtils";
|
|
||||||
import logger from "../system/Logger";
|
|
||||||
import { Attrs, Envelope, User } from "../interfaces/mail/attrs.interface";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* take object address and join mailbox and host to return mailbox@host
|
|
||||||
*/
|
|
||||||
function createAddress(elt: User): string {
|
|
||||||
return `${elt.mailbox}@${elt.host}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum RoomType {
|
|
||||||
ROOM = 0,
|
|
||||||
CHANNEL = 1,
|
|
||||||
GROUP = 2,
|
|
||||||
DM = 3,
|
|
||||||
THREAD = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class RegisterMessageInApp {
|
|
||||||
messageId: number;
|
|
||||||
attrs: Attrs;
|
|
||||||
envelope: Envelope;
|
|
||||||
messageID?: string;
|
|
||||||
boxId: number;
|
|
||||||
isSeen: boolean;
|
|
||||||
ownerId: number;
|
|
||||||
userId: number;
|
|
||||||
inReplyTo: string;
|
|
||||||
|
|
||||||
constructor(_messageId: number, _attrs: Attrs, _boxId: number) {
|
|
||||||
this.messageId = _messageId;
|
|
||||||
this.attrs = _attrs;
|
|
||||||
if (!this.attrs.envelope) throw new Error("Envelope must exist in attributes");
|
|
||||||
this.envelope = this.attrs.envelope;
|
|
||||||
this.messageID = this.envelope?.messageId;
|
|
||||||
this.boxId = _boxId;
|
|
||||||
this.isSeen = this.attrs.flags.includes("\\Seen") ? true : false;
|
|
||||||
this.ownerId = -1;
|
|
||||||
this.userId = -1;
|
|
||||||
this.inReplyTo = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
if (this.envelope.from) {
|
|
||||||
this.ownerId = await getAddresseId(createAddress(this.envelope.from[0])); // todo use sender or from ?
|
|
||||||
} else {
|
|
||||||
throw new Error("Envelope must have a 'from' field");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isDm = () => nbMembers(this.envelope) == 2;
|
|
||||||
|
|
||||||
async isFromUs() {
|
|
||||||
if (this.userId == -1) {
|
|
||||||
await getUserIdOfMailbox(this.boxId).then((res) => {
|
|
||||||
this.userId = res[0]?.user_id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.ownerId == this.userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
async incrementNotSeen(roomId: number) {
|
|
||||||
// todo it appears there is an error with notifications
|
|
||||||
if (!this.isSeen) {
|
|
||||||
await incrementNotSeenRoom(roomId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async registerMembers(roomId: number) {
|
|
||||||
getAllMembers(this.messageId).then((res) => {
|
|
||||||
if (res.lenght == 0) return;
|
|
||||||
const data = res[0].id.split(",");
|
|
||||||
data.forEach(async (memberId: number) => {
|
|
||||||
await registerMember(roomId, memberId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async initiateRoom(owner: number, roomType: RoomType) {
|
|
||||||
try {
|
|
||||||
const roomId = await createRoom(this.envelope.subject, owner, this.messageId, roomType);
|
|
||||||
await registerMessageInRoom(this.messageId, roomId, this.envelope.date);
|
|
||||||
await this.incrementNotSeen(roomId);
|
|
||||||
await this.registerMembers(roomId);
|
|
||||||
return roomId;
|
|
||||||
} catch (err) {
|
|
||||||
logger.err(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async createOrRegisterOnExistence(owner: number, roomType: RoomType) {
|
|
||||||
await findRoomByOwner(owner).then(async (res) => {
|
|
||||||
if (res.length == 0) {
|
|
||||||
// first message with this sender
|
|
||||||
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.envelope.date);
|
|
||||||
await this.incrementNotSeen(res[0].room_id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async initiateThread() {
|
|
||||||
await createRoom(this.envelope.subject, this.ownerId, this.messageId, RoomType.THREAD).then(
|
|
||||||
async (threadId: number) => {
|
|
||||||
// find parent room infos
|
|
||||||
let roomId: number;
|
|
||||||
let root_id: number;
|
|
||||||
await getThreadInfo(this.inReplyTo).then(async (room) => {
|
|
||||||
// todo room not lenght, reply to transfer ?
|
|
||||||
roomId = room[0].room_id;
|
|
||||||
root_id = room[0].root_id;
|
|
||||||
if (root_id === undefined) root_id = roomId;
|
|
||||||
await registerThread(threadId, roomId, root_id);
|
|
||||||
});
|
|
||||||
// impl register previous message or go back
|
|
||||||
await registerMessageInRoom(this.messageId, threadId, this.envelope.date);
|
|
||||||
await this.incrementNotSeen(root_id);
|
|
||||||
await this.incrementNotSeen(threadId);
|
|
||||||
await this.registerMembers(threadId);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async createOrRegisterOnMembers(roomId: number, isThread?: boolean) {
|
|
||||||
const hasSameMembers = await hasSameMembersAsParent(this.messageId, this.inReplyTo);
|
|
||||||
if (hasSameMembers) {
|
|
||||||
await registerMessageInRoom(this.messageId, roomId, this.envelope.date);
|
|
||||||
await this.incrementNotSeen(roomId);
|
|
||||||
if (isThread) {
|
|
||||||
await getThreadInfoOnId(roomId).then(async (res) => {
|
|
||||||
let root_id = res[0].root_id;
|
|
||||||
if (root_id == undefined) root_id = res[0].room_id;
|
|
||||||
await this.incrementNotSeen(res[0].root_id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.initiateThread();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async save() {
|
|
||||||
await this.init();
|
|
||||||
if (this.envelope.inReplyTo) {
|
|
||||||
this.inReplyTo = this.envelope.inReplyTo;
|
|
||||||
await this.saveReply();
|
|
||||||
} else {
|
|
||||||
if (await this.isFromUs()) {
|
|
||||||
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]));
|
|
||||||
await this.createOrRegisterOnExistence(userTo, RoomType.DM);
|
|
||||||
} else {
|
|
||||||
// it is not a reply and not a dm
|
|
||||||
// so it is a channel, which can be possibly a group
|
|
||||||
await this.initiateRoom(this.ownerId, RoomType.ROOM);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.createOrRegisterOnExistence(this.ownerId, RoomType.ROOM);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveReply() {
|
|
||||||
await findRoomsFromMessage(this.inReplyTo).then(async (rooms) => {
|
|
||||||
if (rooms.length < 1) {
|
|
||||||
// no rooms, so is a transfer
|
|
||||||
// todo test if members of transferred message are included
|
|
||||||
} else if (rooms.length === 1) {
|
|
||||||
// only one room so message is only in a room and not in a thread
|
|
||||||
// as a thread is associated to a room
|
|
||||||
const roomType = (await getRoomType(rooms[0].room_id))[0].room_type;
|
|
||||||
if (roomType == RoomType.GROUP || roomType == RoomType.THREAD) {
|
|
||||||
await this.createOrRegisterOnMembers(rooms[0].room_id, roomType == RoomType.THREAD);
|
|
||||||
} else {
|
|
||||||
// reply from CHANNEL or DM or ROOM
|
|
||||||
await this.initiateThread();
|
|
||||||
// todo
|
|
||||||
// if (sender == owner) { // correction from the original sender
|
|
||||||
// // leave in the same channel
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
} else if (rooms.length > 1) {
|
|
||||||
// get the lowest thread (order by room_id)
|
|
||||||
const roomId = rooms[rooms.length - 1].room_id;
|
|
||||||
await this.createOrRegisterOnMembers(roomId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,128 +0,0 @@
|
|||||||
import { getAddresseId } from "../db/utils/mail";
|
|
||||||
import {simpleParser} from "mailparser";
|
|
||||||
import moment from "moment";
|
|
||||||
import Imap from "imap";
|
|
||||||
import {
|
|
||||||
registerMessage,
|
|
||||||
registerMailbox_message,
|
|
||||||
saveHeader_fields,
|
|
||||||
saveAddress_fields,
|
|
||||||
registerBodypart,
|
|
||||||
saveBodypart,
|
|
||||||
saveSource,
|
|
||||||
} from "../db/imap/storeMessage-db";
|
|
||||||
|
|
||||||
import { getFieldId } from "../db/utils/mail";
|
|
||||||
import logger from "../system/Logger";
|
|
||||||
import { AttrsWithEnvelope } from "../interfaces/mail/attrs.interface";
|
|
||||||
|
|
||||||
export function saveMessage(attrs: AttrsWithEnvelope, mailboxId: number, imap: Imap): Promise<number> {
|
|
||||||
const envelope = attrs.envelope;
|
|
||||||
const ts = moment(new Date(envelope.date).getTime()).format("YYYY-MM-DD HH:mm:ss");
|
|
||||||
const rfc822size = attrs.size;
|
|
||||||
const messageID = envelope.messageId;
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
registerMessage(ts, rfc822size, messageID)
|
|
||||||
.then((messageId) => {
|
|
||||||
const isSeen = attrs.flags.includes("\\Seen") ? 1 : 0; // todo verify
|
|
||||||
const deleted = attrs.flags.includes("\\Deleted") ? 1 : 0; // todo verify
|
|
||||||
|
|
||||||
registerMailbox_message(mailboxId, attrs.uid, messageId, attrs?.modseq, isSeen, deleted);
|
|
||||||
const f = imap.fetch(attrs.uid, { bodies: "" });
|
|
||||||
let buffer = "";
|
|
||||||
|
|
||||||
f.on("message", function (msg, seqno) {
|
|
||||||
msg.on("body", function (stream, info) {
|
|
||||||
stream.on("data", function (chunk) {
|
|
||||||
buffer += chunk.toString("utf8");
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.once("end", () => {
|
|
||||||
// save raw data todo
|
|
||||||
// saveSource(messageId, buffer);
|
|
||||||
|
|
||||||
// parse data
|
|
||||||
simpleParser(buffer, async (err, parsed) => {
|
|
||||||
saveFromParsedData(parsed, messageId)
|
|
||||||
.then(() => {
|
|
||||||
resolve(messageId);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
f.once("error", function (err) {
|
|
||||||
logger.warn("Fetch error: " + err);
|
|
||||||
});
|
|
||||||
f.once("end", function () {
|
|
||||||
// logger.log("Done fetching data of " + messageID); // todo
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
logger.warn("Unable to register message: " + err);
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveFromParsedData(parsed, messageId) {
|
|
||||||
const promises: Promise<any>[] = [];
|
|
||||||
Object.keys(parsed).forEach((key) => {
|
|
||||||
if (["from", "to", "cc", "bcc", "replyTo"].includes(key)) {
|
|
||||||
promises.push(
|
|
||||||
// save address field
|
|
||||||
getFieldId(key).then((fieldId) => {
|
|
||||||
parsed[key].value.forEach((addr, nb) => {
|
|
||||||
getAddresseId(addr.address, addr.name).then(async (addressId) => {
|
|
||||||
await saveAddress_fields(messageId, fieldId, addressId, nb);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else if (["subject", "inReplyTo", "references"].includes(key)) {
|
|
||||||
// todo : "references" (array)
|
|
||||||
if (key == "references") return;
|
|
||||||
promises.push(
|
|
||||||
getFieldId(key).then(async (fieldId) => {
|
|
||||||
await saveHeader_fields(messageId, fieldId, undefined, undefined, parsed[key]);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else if (["html", "text", "textAsHtml"].includes(key)) {
|
|
||||||
const hash = "0";
|
|
||||||
const size = "0";
|
|
||||||
let partType = "text/plain";
|
|
||||||
if (key == "html") {
|
|
||||||
partType = "text/html";
|
|
||||||
} else if (key == "textAsHtml") {
|
|
||||||
partType = "text/TexAsHtml";
|
|
||||||
}
|
|
||||||
saveBodypart(size, hash, parsed[key], "").then((bodypartId) => {
|
|
||||||
getFieldId(key).then((fieldId) => {
|
|
||||||
saveHeader_fields(messageId, fieldId, bodypartId, partType, undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else if (key == "attachments") {
|
|
||||||
// todo
|
|
||||||
} else if (["date", "messageId", "headers", "headerLines"].includes(key)) {
|
|
||||||
// messageId and date are already saved
|
|
||||||
// other field are not important and can be retrieved in source
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
logger.warn("doesn't know key: " + key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Promise.all(promises);
|
|
||||||
// todo when transfered
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (process.env["NODE_DEV"] == "TEST") {
|
|
||||||
module.exports = {
|
|
||||||
saveFromParsedData,
|
|
||||||
};
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user