Skip to content

Commit 4c46def

Browse files
committed
Add flag counter
1 parent 3d38e98 commit 4c46def

8 files changed

Lines changed: 384 additions & 10 deletions

File tree

docs/api/openapidocs.json

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2735,6 +2735,57 @@
27352735
}
27362736
}
27372737
},
2738+
"/users/{user}/flagged-counter": {
2739+
"get": {
2740+
"tags": [
2741+
"Messages"
2742+
],
2743+
"summary": "Get flagged counter",
2744+
"description": "Returns total and unseen counters for flagged messages",
2745+
"operationId": "getFlaggedCounter",
2746+
"parameters": [
2747+
{
2748+
"name": "user",
2749+
"in": "path",
2750+
"description": "ID of the User",
2751+
"required": true,
2752+
"schema": {
2753+
"type": "string"
2754+
}
2755+
},
2756+
{
2757+
"name": "sess",
2758+
"in": "query",
2759+
"description": "Session identifier for the logs",
2760+
"required": false,
2761+
"schema": {
2762+
"type": "string"
2763+
}
2764+
},
2765+
{
2766+
"name": "ip",
2767+
"in": "query",
2768+
"description": "IP address for the logs ",
2769+
"required": false,
2770+
"schema": {
2771+
"type": "string"
2772+
}
2773+
}
2774+
],
2775+
"responses": {
2776+
"200": {
2777+
"description": "Success",
2778+
"content": {
2779+
"application/json": {
2780+
"schema": {
2781+
"$ref": "#/components/schemas/FlaggedCounterResponse"
2782+
}
2783+
}
2784+
}
2785+
}
2786+
}
2787+
}
2788+
},
27382789
"/users/{user}/mailboxes/{mailbox}/messages": {
27392790
"get": {
27402791
"tags": [
@@ -10750,6 +10801,27 @@
1075010801
"unseen"
1075110802
]
1075210803
},
10804+
"FlaggedCounterResponse": {
10805+
"type": "object",
10806+
"properties": {
10807+
"success": {
10808+
"type": "boolean"
10809+
},
10810+
"total": {
10811+
"type": "number",
10812+
"description": "Total number of flagged messages"
10813+
},
10814+
"unseen": {
10815+
"type": "number",
10816+
"description": "Unseen number of flagged messages"
10817+
}
10818+
},
10819+
"required": [
10820+
"success",
10821+
"total",
10822+
"unseen"
10823+
]
10824+
},
1075310825
"Address": {
1075410826
"type": "object",
1075510827
"properties": {

lib/api/messages.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti
6363

6464
const getMailboxCounter = tools.getMailboxCounter;
6565
const getKeywordCounter = tools.getKeywordCounter;
66+
const getFlaggedCounter = tools.getFlaggedCounter;
6667
const extractKeywords = tools.extractKeywords;
6768
const asyncForward = util.promisify(forward);
6869

@@ -223,6 +224,87 @@ module.exports = (db, server, messageHandler, userHandler, storageHandler, setti
223224
})
224225
);
225226

227+
server.get(
228+
{
229+
path: '/users/:user/flagged-counter',
230+
summary: 'Get flagged counter',
231+
name: 'getFlaggedCounter',
232+
description: 'Returns total and unseen counters for flagged messages',
233+
validationObjs: {
234+
requestBody: {},
235+
pathParams: {
236+
user: Joi.string().hex().lowercase().length(24).required().description('ID of the User')
237+
},
238+
queryParams: {
239+
sess: sessSchema,
240+
ip: sessIPSchema
241+
},
242+
response: {
243+
200: {
244+
description: 'Success',
245+
model: Joi.object({
246+
success: booleanSchema.required(),
247+
total: Joi.number().required().description('Total number of flagged messages'),
248+
unseen: Joi.number().required().description('Unseen number of flagged messages')
249+
}).$_setFlag('objectName', 'FlaggedCounterResponse')
250+
}
251+
}
252+
},
253+
tags: ['Messages']
254+
},
255+
tools.responseWrapper(async (req, res) => {
256+
res.charSet('utf-8');
257+
258+
const { requestBody, queryParams, pathParams } = req.route.spec.validationObjs;
259+
260+
const schema = Joi.object({
261+
...requestBody,
262+
...queryParams,
263+
...pathParams
264+
});
265+
266+
const result = schema.validate(req.params, {
267+
abortEarly: false,
268+
convert: true,
269+
allowUnknown: true
270+
});
271+
272+
if (result.error) {
273+
res.status(400);
274+
return res.json({
275+
error: result.error.message,
276+
code: 'InputValidationError',
277+
details: tools.validationErrors(result)
278+
});
279+
}
280+
281+
if (req.user && req.user === result.value.user) {
282+
req.validate(roles.can(req.role).readOwn('messages'));
283+
} else {
284+
req.validate(roles.can(req.role).readAny('messages'));
285+
}
286+
287+
const user = new ObjectId(result.value.user);
288+
289+
const userExists = await db.users.collection('users').countDocuments({ _id: user }, { limit: 1 });
290+
if (!userExists) {
291+
res.status(404);
292+
return res.json({
293+
error: 'This user does not exist',
294+
code: 'UserNotFound'
295+
});
296+
}
297+
298+
const [total, unseen] = await Promise.all([getFlaggedCounter(db, user), getFlaggedCounter(db, user, 'unseen')]);
299+
300+
return res.json({
301+
success: true,
302+
total,
303+
unseen
304+
});
305+
})
306+
);
307+
226308
const putMessageHandler = async (req, res) => {
227309
res.charSet('utf-8');
228310

lib/api/updates.js

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ function formatJournalData(e) {
228228
let data = {};
229229
Object.keys(e).forEach(key => {
230230
if (!['_id', 'ignore', 'user', 'modseq', 'unseenChange', 'created'].includes(key)) {
231-
if (!['COUNTERS', 'KEYWORD_COUNTERS'].includes(e.command) && key === 'unseen') {
231+
if (!['COUNTERS', 'KEYWORD_COUNTERS', 'FLAGGED_COUNTER'].includes(e.command) && key === 'unseen') {
232232
return;
233233
}
234234
data[key] = e[key];
@@ -252,6 +252,27 @@ function loadJournalStream(db, req, res, user, lastEventId, done) {
252252

253253
let mailboxes = new Set();
254254
let changedKeywords = new Set();
255+
let flaggedChanged = false;
256+
257+
let emitFlaggedCounter = next => {
258+
if (!flaggedChanged) {
259+
return next();
260+
}
261+
262+
Promise.all([tools.getFlaggedCounter(db, user), tools.getFlaggedCounter(db, user, 'unseen')])
263+
.then(([total, unseen]) => {
264+
res.write(
265+
formatJournalData({
266+
command: 'FLAGGED_COUNTER',
267+
_id: lastEventId,
268+
total,
269+
unseen
270+
})
271+
);
272+
next();
273+
})
274+
.catch(() => next());
275+
};
255276

256277
let emitKeywordCounters = next => {
257278
if (!changedKeywords.size) {
@@ -314,11 +335,13 @@ function loadJournalStream(db, req, res, user, lastEventId, done) {
314335
if (!e) {
315336
return cursor.close(() => {
316337
let finalize = () =>
317-
emitKeywordCounters(() =>
318-
done(null, {
319-
lastEventId,
320-
processed
321-
})
338+
emitFlaggedCounter(() =>
339+
emitKeywordCounters(() =>
340+
done(null, {
341+
lastEventId,
342+
processed
343+
})
344+
)
322345
);
323346

324347
if (!mailboxes.size) {
@@ -372,11 +395,22 @@ function loadJournalStream(db, req, res, user, lastEventId, done) {
372395
if (e.mailbox) {
373396
mailboxes.add(e.mailbox.toString());
374397
}
398+
if (e.flagged) {
399+
flaggedChanged = true;
400+
}
375401
break;
376402
case 'FETCH':
377403
if (e.mailbox && (e.unseen || e.unseenChange)) {
378404
mailboxes.add(e.mailbox.toString());
379405
}
406+
407+
if (e.flaggedChangedTo === true || e.flaggedChangedTo === false) {
408+
flaggedChanged = true;
409+
}
410+
411+
if (e.unseenChange && (e.flags ?? []).includes('\\Flagged')) {
412+
flaggedChanged = true;
413+
}
380414
break;
381415
}
382416

lib/handlers/on-copy.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ async function copyHandler(server, messageHandler, connection, mailbox, update,
347347
uid: messageData.uid,
348348
message: messageData._id,
349349
unseen: messageData.unseen,
350+
flagged: messageData.flagged,
350351
idate: messageData.idate,
351352
thread: messageData.thread
352353
};

lib/handlers/on-store.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ module.exports = server => (mailbox, update, session, callback) => {
153153

154154
let updated = false;
155155
const oldKeywords = new Set(extractKeywords(message.flags));
156+
const messageWasFlagged = message.flags.includes('\\Flagged');
156157
let existingFlags = message.flags.map(flag => flag.toLowerCase().trim());
157158
switch (update.action) {
158159
case 'set':
@@ -350,8 +351,9 @@ module.exports = server => (mailbox, update, session, callback) => {
350351
const currentKeywords = extractKeywords(message.flags);
351352
const addedKeywords = currentKeywords.filter(keyword => !oldKeywords.has(keyword));
352353
const removedKeywords = Array.from(oldKeywords).filter(keyword => !currentKeywords.includes(keyword));
354+
const messageIsFlagged = message.flags.includes('\\Flagged');
353355

354-
notifyEntries.push({
356+
const notifyEntry = {
355357
command: 'FETCH',
356358
ignore: session.id,
357359
uid: message.uid,
@@ -362,7 +364,13 @@ module.exports = server => (mailbox, update, session, callback) => {
362364
message: message._id,
363365
modseq,
364366
unseenChange
365-
});
367+
};
368+
369+
if (messageWasFlagged !== messageIsFlagged) {
370+
notifyEntry.flaggedChangedTo = messageIsFlagged;
371+
}
372+
373+
notifyEntries.push(notifyEntry);
366374

367375
if (updateEntries.length >= consts.BULK_BATCH_SIZE) {
368376
return db.database.collection('messages').bulkWrite(

0 commit comments

Comments
 (0)