Skip to content

Commit 95230b5

Browse files
authored
Merge pull request #650 from codex-team/master
Update prod
2 parents 57a82a9 + 53f46d4 commit 95230b5

13 files changed

Lines changed: 1179 additions & 11 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hawk.api",
3-
"version": "1.4.13",
3+
"version": "1.5.0",
44
"main": "index.ts",
55
"license": "BUSL-1.1",
66
"scripts": {

src/models/eventsFactory.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,27 @@ class EventsFactory extends Factory {
882882
return result;
883883
}
884884

885+
/**
886+
* Mark many original events as visited for passed user
887+
*
888+
* @param {string[]} eventIds - original event ids
889+
* @param {string|ObjectId} userId - id of the user who is visiting events
890+
* @returns {Promise<UpdateWriteOpResult>}
891+
*/
892+
async bulkVisitEvents(eventIds, userId) {
893+
const uniqueEventIds = [ ...new Set((eventIds || []).map(id => String(id))) ];
894+
const collection = this.getCollection(this.TYPES.EVENTS);
895+
const userObjectId = new ObjectId(userId);
896+
897+
return collection.updateMany(
898+
{
899+
_id: { $in: uniqueEventIds.map(id => new ObjectId(id)) },
900+
visitedBy: { $ne: userObjectId },
901+
},
902+
{ $addToSet: { visitedBy: userObjectId } }
903+
);
904+
}
905+
885906
/**
886907
* Mark or unmark event as Resolved, Ignored or Starred
887908
*
@@ -918,6 +939,61 @@ class EventsFactory extends Factory {
918939
return collection.updateOne(query, update);
919940
}
920941

942+
/**
943+
* Bulk set or clear mark for original events.
944+
*
945+
* @param {string[]} eventIds - original event ids
946+
* @param {string} mark - 'resolved' | 'ignored' | 'starred'
947+
* @param {boolean} enabled - true to set mark, false to clear
948+
* @returns {Promise<UpdateWriteOpResult>}
949+
*/
950+
async bulkSetEventMarks(eventIds, mark, enabled) {
951+
const uniqueEventIds = [ ...new Set((eventIds || []).map(id => String(id))) ];
952+
const objectIds = uniqueEventIds.map(id => new ObjectId(id));
953+
const collection = this.getCollection(this.TYPES.EVENTS);
954+
const nowSec = Math.floor(Date.now() / 1000);
955+
const markKey = `marks.${mark}`;
956+
957+
if (!enabled) {
958+
return collection.updateMany(
959+
{
960+
_id: { $in: objectIds },
961+
[markKey]: { $exists: true },
962+
},
963+
{ $unset: { [markKey]: '' } }
964+
);
965+
}
966+
967+
return collection.updateMany(
968+
{
969+
_id: { $in: objectIds },
970+
[markKey]: { $exists: false },
971+
},
972+
{ $set: { [markKey]: nowSec } }
973+
);
974+
}
975+
976+
/**
977+
* Bulk set/clear assignee for many original events.
978+
*
979+
* @param {string[]} eventIds - original event ids
980+
* @param {string|null|undefined} assignee - target assignee id, null/undefined to clear
981+
* @returns {Promise<UpdateWriteOpResult>}
982+
*/
983+
async bulkUpdateAssignee(eventIds, assignee) {
984+
const uniqueEventIds = [ ...new Set((eventIds || []).map(id => String(id))) ];
985+
const collection = this.getCollection(this.TYPES.EVENTS);
986+
const normalizedAssignee = assignee ? String(assignee) : '';
987+
988+
return collection.updateMany(
989+
{
990+
_id: { $in: uniqueEventIds.map(id => new ObjectId(id)) },
991+
assignee: { $ne: normalizedAssignee },
992+
},
993+
{ $set: { assignee: normalizedAssignee } }
994+
);
995+
}
996+
921997
/**
922998
* Remove a single event and all related data (repetitions, daily events)
923999
*

src/resolvers/event.js

Lines changed: 112 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
const getEventsFactory = require('./helpers/eventsFactory').default;
2-
const sendPersonalNotification = require('../utils/personalNotifications').default;
2+
const {
3+
parseBulkEventIds,
4+
enqueueAssigneeNotification,
5+
} = require('./helpers/bulkEventUtils');
36
const { aiService } = require('../services/ai');
7+
const { UserInputError } = require('apollo-server-express');
8+
const { ObjectId } = require('mongodb');
49

510
/**
611
* See all types and fields here {@see ../typeDefs/event.graphql}
@@ -135,6 +140,26 @@ module.exports = {
135140

136141
return !!result.acknowledged;
137142
},
143+
/**
144+
* Mark many original events as visited for current user
145+
*
146+
* @param {ResolverObj} _obj - resolver context
147+
* @param {string} projectId - project id
148+
* @param {string[]} eventIds - original event ids
149+
* @param {UserInContext} user - user context
150+
* @returns {Promise<{ success: boolean, modifiedCount: number }>}
151+
*/
152+
async bulkVisitEvents(_obj, { projectId, eventIds }, { user, ...context }) {
153+
const validEventIds = parseBulkEventIds(eventIds);
154+
155+
const factory = getEventsFactory(context, projectId);
156+
const result = await factory.bulkVisitEvents(validEventIds, user.id);
157+
158+
return {
159+
success: !!result.acknowledged,
160+
modifiedCount: result.modifiedCount || 0,
161+
};
162+
},
138163

139164
/**
140165
* Mark event with one of the event marks
@@ -153,6 +178,29 @@ module.exports = {
153178
return !!result.acknowledged;
154179
},
155180

181+
/**
182+
* Bulk set or clear mark for original events.
183+
*
184+
* @param {ResolverObj} _obj - resolver context
185+
* @param {string} projectId - project id
186+
* @param {string[]} eventIds - original event ids
187+
* @param {string} mark - EventMark enum value
188+
* @param {boolean} enabled - true to set mark, false to remove
189+
* @param {object} context - gql context
190+
* @return {Promise<{ success: boolean, modifiedCount: number }>}
191+
*/
192+
async bulkSetEventMarks(_obj, { projectId, eventIds, mark, enabled }, context) {
193+
const validEventIds = parseBulkEventIds(eventIds);
194+
195+
const factory = getEventsFactory(context, projectId);
196+
const result = await factory.bulkSetEventMarks(validEventIds, mark, enabled);
197+
198+
return {
199+
success: !!result.acknowledged,
200+
modifiedCount: result.modifiedCount || 0,
201+
};
202+
},
203+
156204
/**
157205
* Remove event and all related data (repetitions, daily events)
158206
*
@@ -212,14 +260,12 @@ module.exports = {
212260

213261
const assigneeData = await factories.usersFactory.dataLoaders.userById.load(assignee);
214262

215-
await sendPersonalNotification(assigneeData, {
216-
type: 'assignee',
217-
payload: {
218-
assigneeId: assignee,
219-
projectId,
220-
whoAssignedId: user.id,
221-
eventId,
222-
},
263+
enqueueAssigneeNotification({
264+
assigneeData,
265+
assigneeId: assignee,
266+
projectId,
267+
whoAssignedId: user.id,
268+
eventId,
223269
});
224270

225271
return {
@@ -246,5 +292,62 @@ module.exports = {
246292
success: !!result.acknowledged,
247293
};
248294
},
295+
296+
/**
297+
* Bulk set/clear assignee for selected original events
298+
*
299+
* @param {ResolverObj} _obj - resolver context
300+
* @param {BulkUpdateAssigneeInput} input - object of arguments
301+
* @param factories - factories for working with models
302+
* @return {Promise<{ success: boolean, modifiedCount: number }>}
303+
*/
304+
async bulkUpdateAssignee(_obj, { input }, { factories, user, ...context }) {
305+
const { projectId, eventIds, assignee } = input;
306+
const validEventIds = parseBulkEventIds(eventIds);
307+
let assigneeData = null;
308+
309+
const factory = getEventsFactory(context, projectId);
310+
311+
if (assignee) {
312+
if (!ObjectId.isValid(String(assignee))) {
313+
throw new UserInputError('assignee must be a valid id or null');
314+
}
315+
316+
const userExists = await factories.usersFactory.findById(assignee);
317+
318+
if (!userExists) {
319+
throw new UserInputError('assignee not found');
320+
}
321+
322+
assigneeData = userExists;
323+
324+
const project = await factories.projectsFactory.findById(projectId);
325+
const workspace = await factories.workspacesFactory.findById(project.workspaceId);
326+
const assigneeExistsInWorkspace = await workspace.getMemberInfo(assignee);
327+
328+
if (!assigneeExistsInWorkspace) {
329+
throw new UserInputError('assignee is not a workspace member');
330+
}
331+
}
332+
333+
const result = await factory.bulkUpdateAssignee(validEventIds, assignee);
334+
335+
if (assignee && result.modifiedCount > 0) {
336+
validEventIds.forEach((eventId) => {
337+
enqueueAssigneeNotification({
338+
assigneeData,
339+
assigneeId: assignee,
340+
projectId,
341+
whoAssignedId: user.id,
342+
eventId,
343+
});
344+
});
345+
}
346+
347+
return {
348+
success: !!result.acknowledged,
349+
modifiedCount: result.modifiedCount || 0,
350+
};
351+
},
249352
},
250353
};
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { UserInputError } from 'apollo-server-express';
2+
import { ObjectId } from 'mongodb';
3+
import sendPersonalNotification from '../../utils/personalNotifications';
4+
import { SenderWorkerTaskType } from '../../types/userNotifications/task-type';
5+
import type { EnqueueAssigneeNotificationParams } from '../../types/userNotifications/assignee';
6+
7+
/**
8+
* Validate and normalize bulk event ids from resolver input.
9+
*
10+
* @param {string[]} eventIds - raw event ids from GraphQL input
11+
* @returns {string[]} unique valid event ids
12+
*/
13+
export function parseBulkEventIds(eventIds: string[]): string[] {
14+
if (!eventIds || !eventIds.length) {
15+
throw new UserInputError('eventIds must contain at least one id');
16+
}
17+
18+
const uniqueEventIds = [ ...new Set(eventIds.map(id => String(id))) ];
19+
20+
if (!uniqueEventIds.every((id) => ObjectId.isValid(id))) {
21+
throw new UserInputError('eventIds must contain only valid ids');
22+
}
23+
24+
return uniqueEventIds;
25+
}
26+
27+
/**
28+
* Enqueue one assignee notification without blocking resolver response.
29+
*/
30+
export function enqueueAssigneeNotification({
31+
assigneeData,
32+
assigneeId,
33+
projectId,
34+
whoAssignedId,
35+
eventId,
36+
}: EnqueueAssigneeNotificationParams): void {
37+
if (!assigneeData) {
38+
return;
39+
}
40+
41+
sendPersonalNotification(assigneeData, {
42+
type: SenderWorkerTaskType.Assignee,
43+
payload: {
44+
assigneeId,
45+
projectId,
46+
whoAssignedId,
47+
eventId,
48+
},
49+
}).catch((error: unknown) => {
50+
console.error('Failed to enqueue assignee notification', error);
51+
});
52+
}

0 commit comments

Comments
 (0)