Compare commits
4 Commits
8f980748b5
...
b137263bef
Author | SHA1 | Date | |
---|---|---|---|
|
b137263bef | ||
|
2c7b4f1c78 | ||
|
3dab9c8db1 | ||
|
5aef5ab7b0 |
@ -2,6 +2,7 @@ import statusCode from "../utils/statusCodes";
|
||||
import { Response } from "express";
|
||||
import { getMessageUid, getUserOfMailbox } from "../db/utils/mail";
|
||||
import emailManager from "../mails/EmailManager";
|
||||
import { deleteMessage } from "../db/message/updateMessage-db";
|
||||
|
||||
export default class Message {
|
||||
static async addFlag(body, res: Response) {
|
||||
@ -51,4 +52,41 @@ export default class Message {
|
||||
res.status(statusCode.METHOD_FAILURE).send({ error: err });
|
||||
});
|
||||
}
|
||||
|
||||
static deleteRemoteOnly = async (body, res: Response) => {
|
||||
body.flag = "\\Deleted";
|
||||
await this.addFlag(body, res);
|
||||
};
|
||||
|
||||
static async deleteEverywhere(body, res: Response) {
|
||||
const { mailboxId, messageId } = body;
|
||||
const uid = (await getMessageUid(messageId))[0]?.uid;
|
||||
if (!uid) {
|
||||
res.status(statusCode.NOT_FOUND).send({ error: "Message uid not found." });
|
||||
}
|
||||
|
||||
const user = (await getUserOfMailbox(mailboxId))[0]?.user;
|
||||
if (!user) {
|
||||
res.status(statusCode.NOT_FOUND).send({ error: "Not account for this mailbox." });
|
||||
}
|
||||
emailManager
|
||||
.getImap(user)
|
||||
.getMailbox(mailboxId)
|
||||
.removeFlag(uid.toString(), ["\\Deleted"])
|
||||
.then(() => {
|
||||
deleteMessage(messageId)
|
||||
.then((result) => {
|
||||
// todo check if room is empty
|
||||
res.status(statusCode.OK).send();
|
||||
})
|
||||
.catch((err) => {
|
||||
res.status(statusCode.METHOD_FAILURE).send({ error: err });
|
||||
console.log(err);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
res.status(statusCode.METHOD_FAILURE).send({ error: err });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ CREATE TABLE app_room (
|
||||
PRIMARY KEY (room_id),
|
||||
UNIQUE KEY (owner_id, message_id, room_type),
|
||||
FOREIGN KEY (owner_id) REFERENCES address(address_id),
|
||||
FOREIGN KEY (message_id) REFERENCES message(message_id)
|
||||
FOREIGN KEY (message_id) REFERENCES message(message_id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- 12
|
||||
@ -175,6 +175,5 @@ create table flag (
|
||||
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
|
||||
);
|
@ -35,6 +35,7 @@ export async function getMailbox(mailboxId: number) {
|
||||
}
|
||||
|
||||
export function updateMailbox(mailboxId: number, uidnext: number) {
|
||||
console.log("updateMailbox", mailboxId, uidnext);
|
||||
const query = `UPDATE mailbox SET uidnext = ? WHERE mailbox_id = ?`;
|
||||
const values = [uidnext, mailboxId];
|
||||
execQuery(query, values);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { execQuery, execQueryAsync, execQueryAsyncWithId } from "../db";
|
||||
|
||||
export async function getFlags(uid: number): Promise<{flag_id: number, flag_name: string}[]> {
|
||||
export async function getFlags(uid: number): Promise<{ flag_id: number; flag_name: string }[]> {
|
||||
const query = `
|
||||
SELECT * FROM flag_name
|
||||
INNER JOIN flag ON flag.flag_id = flag_name.flag_id
|
||||
@ -28,3 +28,9 @@ export async function updateMailboxDeleted(messageId: number, isDeleted: boolean
|
||||
const values = [messageId, isDeleted];
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
|
||||
export async function deleteMessage(messageId: number) {
|
||||
const query = `DELETE FROM message WHERE message_id = ?`;
|
||||
const values = [messageId];
|
||||
return await execQueryAsync(query, values);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Imap, { Box } from "imap";
|
||||
import { resolve } from "path";
|
||||
import { getMailbox, getMailboxModseq, updateMailbox, updateMailboxModseq } from "../../db/imap/imap-db";
|
||||
import { Attrs, AttrsWithEnvelope } from "../../interfaces/mail/attrs.interface";
|
||||
import logger from "../../system/Logger";
|
||||
@ -44,7 +45,7 @@ export default class Mailbox {
|
||||
this.imap.on("mail", (numNewMsgs: number) => {
|
||||
if (!this.syncing) {
|
||||
// if not syncing restart a sync
|
||||
this.syncMail(this.box.uidnext, this.box.uidnext + numNewMsgs);
|
||||
this.syncManager(this.box.uidnext - 1, this.box.uidnext + numNewMsgs - 1);
|
||||
} else {
|
||||
// else save number of message to sync latter
|
||||
this.msgToSync += numNewMsgs;
|
||||
@ -57,6 +58,11 @@ export default class Mailbox {
|
||||
const updateMsg = new updateMessage(info.uid, info.flags);
|
||||
updateMsg.updateFlags();
|
||||
});
|
||||
|
||||
// wait for deletion
|
||||
this.imap.on("expunge", (seqno: number) => {
|
||||
console.log("Message with sequence number " + seqno + " has been deleted from the server.");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -69,7 +75,7 @@ export default class Mailbox {
|
||||
async initSync(box: Box) {
|
||||
// sync mail only if has new messages
|
||||
if (this.box.uidnext < box.uidnext) {
|
||||
this.syncMail(this.box.uidnext, box.uidnext);
|
||||
this.syncManager(this.box.uidnext, box.uidnext);
|
||||
} else {
|
||||
logger.log("Mail already up to date");
|
||||
}
|
||||
@ -91,53 +97,44 @@ export default class Mailbox {
|
||||
logger.log("Done fetching new flags");
|
||||
});
|
||||
} else {
|
||||
logger.log("Flags already up to date")
|
||||
logger.log("Flags already up to date");
|
||||
}
|
||||
this.updateModseq(parseInt(box.highestmodseq));
|
||||
}
|
||||
|
||||
async syncMail(savedUid: number, currentUid: number) {
|
||||
syncManager = async (savedUid: number, currentUid: number) => {
|
||||
this.syncing = true;
|
||||
const promises: Promise<unknown>[] = [];
|
||||
const mails: Attrs[] = [];
|
||||
logger.log(`Syncing from ${savedUid} to ${currentUid} uid`);
|
||||
const f = this.imap.seq.fetch(`${savedUid}:${currentUid}`, {
|
||||
size: true,
|
||||
envelope: true,
|
||||
});
|
||||
logger.log(`Fetching from ${savedUid} to ${currentUid} uid`);
|
||||
const nbMessageToSync = currentUid - savedUid;
|
||||
let STEP = nbMessageToSync > 300 ? Math.floor(nbMessageToSync / 7) : nbMessageToSync;
|
||||
let mails: AttrsWithEnvelope[] = [];
|
||||
|
||||
f.on("message", (msg, seqno) => {
|
||||
msg.once("attributes", (attrs: AttrsWithEnvelope) => {
|
||||
mails.push(attrs);
|
||||
promises.push(saveMessage(attrs, this.id, this.imap));
|
||||
});
|
||||
});
|
||||
|
||||
f.once("error", (err) => {
|
||||
logger.err("Fetch error: " + err);
|
||||
});
|
||||
|
||||
f.once("end", async () => {
|
||||
let step = 20;
|
||||
for (let i = 0; i < promises.length; i += step) {
|
||||
for (let j = i; j < (i + step && promises.length); j++) {
|
||||
await new Promise((resolve, reject) => {
|
||||
promises[j]
|
||||
.then(async (res: number) => {
|
||||
const register = new RegisterMessageInApp(res, mails[j], this.id);
|
||||
for (let i = 0; i < nbMessageToSync; i += STEP) {
|
||||
mails = [];
|
||||
try {
|
||||
// fetch mails
|
||||
let secondUid = savedUid + STEP < currentUid ? savedUid + STEP : currentUid;
|
||||
await this.mailFetcher(savedUid, secondUid, mails);
|
||||
logger.log(`Fetched ${STEP} uids (${mails.length} messages)`);
|
||||
// save same in the database
|
||||
for (let k = 0; k < mails.length; k++) {
|
||||
try {
|
||||
const messageId = await saveMessage(mails[k], this.id, this.imap);
|
||||
const register = new RegisterMessageInApp(messageId, mails[k], this.id);
|
||||
await register.save();
|
||||
resolve("");
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
logger.err("Failed to save a message: " + error);
|
||||
}
|
||||
logger.log(`Saved messages ${i + step > promises.length ? promises.length : i + step}/${mails.length}`);
|
||||
updateMailbox(this.id, mails[i].uid);
|
||||
}
|
||||
updateMailbox(this.id, currentUid);
|
||||
this.syncing = false;
|
||||
savedUid = secondUid;
|
||||
this.box.uidnext += savedUid;
|
||||
|
||||
updateMailbox(this.id, savedUid);
|
||||
} catch (error) {
|
||||
logger.err("Failed to sync message " + error);
|
||||
}
|
||||
logger.log(`Saved messages ${i + STEP > nbMessageToSync ? nbMessageToSync : i + STEP}/${nbMessageToSync}`);
|
||||
}
|
||||
|
||||
// if has receive new msg during last sync then start a new sync
|
||||
if (this.msgToSync > 0) {
|
||||
@ -145,8 +142,33 @@ export default class Mailbox {
|
||||
this.box.uidnext += this.msgToSync;
|
||||
// reset value to allow to detect new incoming message while syncing
|
||||
this.msgToSync = 0;
|
||||
this.syncMail(currentUid, this.box.uidnext);
|
||||
await this.syncManager(currentUid, this.box.uidnext);
|
||||
}
|
||||
this.syncing = false;
|
||||
logger.log(`Finished syncing messages`);
|
||||
};
|
||||
|
||||
async mailFetcher(startUid: number, endUid: number, mails: Attrs[]): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const f = this.imap.fetch(`${startUid}:${endUid}`, {
|
||||
size: true,
|
||||
envelope: true,
|
||||
});
|
||||
|
||||
f.on("message", (msg, seqno) => {
|
||||
msg.once("attributes", (attrs: AttrsWithEnvelope) => {
|
||||
mails.push(attrs);
|
||||
});
|
||||
});
|
||||
|
||||
f.once("error", (err) => {
|
||||
logger.err("Fetch error: " + err);
|
||||
reject(1);
|
||||
});
|
||||
|
||||
f.once("end", async () => {
|
||||
resolve(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -173,4 +195,8 @@ export default class Mailbox {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
move(source: string, mailboxName: string, callback: (error: Error) => void) {
|
||||
this.imap.move(source, mailboxName, callback);
|
||||
}
|
||||
}
|
||||
|
@ -11,5 +11,13 @@ router.post("/removeFlag", async (req, res) => {
|
||||
await validator.validate("removeFlag", req.body, res, Message.removeFlag);
|
||||
});
|
||||
|
||||
router.post("/deleteRemote", async(req, res) => {
|
||||
await validator.validate("delete", req.body, res, Message.deleteRemoteOnly);
|
||||
});
|
||||
|
||||
router.post("/delete", async(req, res) => {
|
||||
await validator.validate("delete", req.body, res, Message.deleteEverywhere);
|
||||
});
|
||||
|
||||
|
||||
export default router;
|
||||
|
16
back/validator/schemas/delete-schema.json
Normal file
16
back/validator/schemas/delete-schema.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mailboxId": {
|
||||
"type": "number"
|
||||
},
|
||||
"messageId": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"mailboxId",
|
||||
"messageId"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
@ -10,6 +10,7 @@ import getMessagesSchema from "./schemas/getMessages-schema.json";
|
||||
import getMembersSchema from "./schemas/getMembers-schema.json";
|
||||
import setFlagSchema from "./schemas/setFlag-schema.json";
|
||||
import responseSchema from "./schemas/response-schema.json";
|
||||
import deleteSchema from "./schemas/delete-schema.json";
|
||||
import { Request, Response } from "express";
|
||||
import statusCodes from "../utils/statusCodes";
|
||||
import logger from "../system/Logger";
|
||||
@ -22,6 +23,7 @@ class Validator {
|
||||
validateGetMembers: any;
|
||||
validateSetFlag: any;
|
||||
validateResponse: any;
|
||||
delete: any;
|
||||
|
||||
constructor() {
|
||||
this.validateCreateAccount = ajv.compile(createAccountSchema);
|
||||
@ -31,6 +33,7 @@ class Validator {
|
||||
this.validateGetMembers = ajv.compile(getMembersSchema);
|
||||
this.validateSetFlag = ajv.compile(setFlagSchema);
|
||||
this.validateResponse = ajv.compile(responseSchema);
|
||||
this.delete = ajv.compile(deleteSchema);
|
||||
}
|
||||
|
||||
_getSchema(name: string): any {
|
||||
@ -50,6 +53,8 @@ class Validator {
|
||||
return this.validateSetFlag;
|
||||
case "response":
|
||||
return this.validateResponse;
|
||||
case "delete":
|
||||
return this.delete;
|
||||
default:
|
||||
logger.err(`Schema ${name} not found`);
|
||||
break;
|
||||
|
@ -296,6 +296,7 @@ function sendMessage() {
|
||||
border-radius: 10px;
|
||||
padding: 0 10px;
|
||||
overflow: auto;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.bubble-menu,
|
||||
|
@ -42,6 +42,30 @@ const setFlag = (flag: string, loadingState: Ref<boolean>) => {
|
||||
loadingState.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const deleteEverywhere = () => {
|
||||
if (!room?.value || !props.msg) return;
|
||||
API.deleteEverywhere({ mailboxId: room.value?.mailboxId, messageId: props.msg?.id })
|
||||
.then((res) => {
|
||||
store.commit("removeMsg", { roomId: room.value?.id, messageId: props.msg?.id });
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
const deleteRemoteOnly = () => {
|
||||
if (!room?.value || !props.msg) return;
|
||||
API.deleteRemoteOnly({ mailboxId: room.value?.mailboxId, messageId: props.msg?.id })
|
||||
.then((res) => {
|
||||
if (!hasFlag(props.msg?.flags, "\\Deleted")) {
|
||||
store.commit("addFlag", { roomId: room.value?.id, messageId: props.msg?.id, flag: "\\Deleted" });
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -71,25 +95,14 @@ const setFlag = (flag: string, loadingState: Ref<boolean>) => {
|
||||
:classes="hasFlag(props.msg?.flags, '\\Flagged') ? 'warn' : ''"
|
||||
v-tooltip="hasFlag(props.msg?.flags, '\\Flagged') ? 'Unflag' : 'Flag'"
|
||||
/>
|
||||
<!--
|
||||
<SvgLoader
|
||||
v-if="isSeenFc(props.msg?.flags)"
|
||||
svg="mail-check-line"
|
||||
class="option"
|
||||
v-tooltip="'Mark unread'"
|
||||
:loading="seenLoading"
|
||||
/>
|
||||
<SvgLoader
|
||||
v-if="!isSeenFc(props.msg?.flags)"
|
||||
svg="mail-unread-line"
|
||||
class="option"
|
||||
v-tooltip="'Mark as read'"
|
||||
:loading="seenLoading"
|
||||
/> -->
|
||||
</span>
|
||||
<SvgLoader svg="reply-line" class="option" />
|
||||
<SvgLoader svg="delete-bin-4-line" class="option" classes="danger" v-tooltip="'Delete from server'" />
|
||||
<span @click="deleteRemoteOnly()">
|
||||
<SvgLoader svg="delete-bin-4-line" class="option" classes="danger" v-tooltip="'Delete from remote'" />
|
||||
</span>
|
||||
<span @click="deleteEverywhere()">
|
||||
<SvgLoader svg="delete-bin-6-line" class="option" classes="danger" v-tooltip="'Delete everywhere'" />
|
||||
</span>
|
||||
<SvgLoader svg="share-forward-line" class="option" />
|
||||
<SvgLoader svg="reply-all-line" class="option" />
|
||||
</div>
|
||||
|
@ -25,4 +25,10 @@ export default {
|
||||
reponseEmail(data: { user: string; roomId: number; text: string; html: string }) {
|
||||
return API().post(`/room/response`, data);
|
||||
},
|
||||
deleteRemoteOnly(data: { mailboxId: number; messageId: number }) {
|
||||
return API().post(`/message/deleteRemote`, data);
|
||||
},
|
||||
deleteEverywhere(data: { mailboxId: number; messageId: number }) {
|
||||
return API().post(`/message/delete`, data);
|
||||
},
|
||||
};
|
||||
|
@ -170,6 +170,13 @@ const store = createStore<State>({
|
||||
updateSeen(state, payload.roomId, payload.flag, false);
|
||||
}
|
||||
},
|
||||
removeMsg(state, payload) {
|
||||
const msgs = msgOnRoomId(state, payload.roomId);
|
||||
const msgIndex = msgs?.messages.findIndex((msg) => msg.id == payload.messageId) ?? -1;
|
||||
if (msgs && msgIndex != -1) {
|
||||
msgs.messages.splice(msgIndex, 1);
|
||||
}
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
rooms: (state) => (): Room[] => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
export function removeDuplicates(array: number[]) {
|
||||
const unique: number[] = [];
|
||||
export function removeDuplicates(array: any[]) {
|
||||
const unique: any[] = [];
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (!unique.includes(array[i])) {
|
||||
unique.push(array[i]);
|
||||
|
Loading…
Reference in New Issue
Block a user