@@ -3,6 +3,37 @@ const sendPersonalNotification = require('../utils/personalNotifications').defau
33const { aiService } = require ( '../services/ai' ) ;
44const { UserInputError } = require ( 'apollo-server-express' ) ;
55
6+ /**
7+ * Enqueue assignee notifications in background (do not block resolver response)
8+ *
9+ * @param {object } args - notification args
10+ * @param {object } args.assigneeData - assigned user data
11+ * @param {string[] } args.eventIds - original event ids
12+ * @param {string } args.projectId - project id
13+ * @param {string } args.assigneeId - assignee id
14+ * @param {string } args.whoAssignedId - user id who performed assignment
15+ * @returns {void }
16+ */
17+ function fireAndForgetAssigneeNotifications ( {
18+ assigneeData,
19+ eventIds,
20+ projectId,
21+ assigneeId,
22+ whoAssignedId,
23+ } ) {
24+ void Promise . allSettled ( eventIds . map ( eventId => sendPersonalNotification ( assigneeData , {
25+ type : 'assignee' ,
26+ payload : {
27+ assigneeId,
28+ projectId,
29+ whoAssignedId,
30+ eventId,
31+ } ,
32+ } ) ) ) . catch ( ( error ) => {
33+ console . error ( 'Failed to enqueue assignee notifications' , error ) ;
34+ } ) ;
35+ }
36+
637/**
738 * See all types and fields here {@see ../typeDefs/event.graphql}
839 */
@@ -163,7 +194,7 @@ module.exports = {
163194 * @param {string[] } eventIds - original event ids
164195 * @param {string } mark - EventMark enum value
165196 * @param {object } context - gql context
166- * @return {Promise<{ updatedCount: number, failedEventIds: string[] }> }
197+ * @return {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }> }
167198 */
168199 async bulkToggleEventMarks ( _obj , { projectId, eventIds, mark } , context ) {
169200 if ( mark !== 'resolved' && mark !== 'ignored' && mark !== 'starred' ) {
@@ -176,15 +207,7 @@ module.exports = {
176207
177208 const factory = getEventsFactory ( context , projectId ) ;
178209
179- try {
180- return await factory . bulkToggleEventMark ( eventIds , mark ) ;
181- } catch ( err ) {
182- if ( err . message && err . message . includes ( 'bulkToggleEventMark: at most' ) ) {
183- throw new UserInputError ( err . message ) ;
184- }
185-
186- throw err ;
187- }
210+ return factory . bulkToggleEventMark ( eventIds , mark ) ;
188211 } ,
189212
190213 /**
@@ -230,14 +253,12 @@ module.exports = {
230253
231254 const assigneeData = await factories . usersFactory . dataLoaders . userById . load ( assignee ) ;
232255
233- await sendPersonalNotification ( assigneeData , {
234- type : 'assignee' ,
235- payload : {
236- assigneeId : assignee ,
237- projectId,
238- whoAssignedId : user . id ,
239- eventId,
240- } ,
256+ fireAndForgetAssigneeNotifications ( {
257+ assigneeData,
258+ eventIds : [ eventId ] ,
259+ projectId,
260+ assigneeId : assignee ,
261+ whoAssignedId : user . id ,
241262 } ) ;
242263
243264 return {
@@ -264,5 +285,58 @@ module.exports = {
264285 success : ! ! result . acknowledged ,
265286 } ;
266287 } ,
288+
289+ /**
290+ * Bulk set/clear assignee for selected original events
291+ *
292+ * @param {ResolverObj } _obj - resolver context
293+ * @param {BulkUpdateAssigneeInput } input - object of arguments
294+ * @param factories - factories for working with models
295+ * @return {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }> }
296+ */
297+ async bulkUpdateAssignee ( _obj , { input } , { factories, user, ...context } ) {
298+ const { projectId, eventIds, assignee } = input ;
299+ const factory = getEventsFactory ( context , projectId ) ;
300+
301+ if ( ! eventIds || ! eventIds . length ) {
302+ throw new UserInputError ( 'eventIds must contain at least one id' ) ;
303+ }
304+
305+ if ( assignee ) {
306+ const userExists = await factories . usersFactory . findById ( assignee ) ;
307+
308+ if ( ! userExists ) {
309+ throw new UserInputError ( 'assignee not found' ) ;
310+ }
311+
312+ const project = await factories . projectsFactory . findById ( projectId ) ;
313+ const workspace = await factories . workspacesFactory . findById ( project . workspaceId ) ;
314+ const assigneeExistsInWorkspace = await workspace . getMemberInfo ( assignee ) ;
315+
316+ if ( ! assigneeExistsInWorkspace ) {
317+ throw new UserInputError ( 'assignee is not a workspace member' ) ;
318+ }
319+ }
320+
321+ const result = await factory . bulkUpdateAssignee ( eventIds , assignee ) ;
322+
323+ if ( assignee && result . updatedEventIds . length > 0 ) {
324+ void factories . usersFactory . dataLoaders . userById . load ( assignee )
325+ . then ( ( assigneeData ) => {
326+ fireAndForgetAssigneeNotifications ( {
327+ assigneeData,
328+ eventIds : result . updatedEventIds ,
329+ projectId,
330+ assigneeId : assignee ,
331+ whoAssignedId : user . id ,
332+ } ) ;
333+ } )
334+ . catch ( ( error ) => {
335+ console . error ( 'Failed to load assignee data for bulk notifications' , error ) ;
336+ } ) ;
337+ }
338+
339+ return result ;
340+ } ,
267341 } ,
268342} ;
0 commit comments