@@ -882,6 +882,48 @@ 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<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }> }
891+ */
892+ async bulkVisitEvent ( eventIds , userId ) {
893+ const {
894+ collection,
895+ found,
896+ failedEventIds,
897+ } = await this . _resolveBulkEventsByIds ( eventIds ) ;
898+ const userIdStr = String ( userId ) ;
899+
900+ const docsToUpdate = found . filter ( ( doc ) => {
901+ const visitedBy = Array . isArray ( doc . visitedBy ) ? doc . visitedBy : [ ] ;
902+
903+ return ! visitedBy . some ( ( visitedUserId ) => String ( visitedUserId ) === userIdStr ) ;
904+ } ) ;
905+ const updatedEventIds = docsToUpdate . map ( doc => doc . _id . toString ( ) ) ;
906+
907+ if ( docsToUpdate . length === 0 ) {
908+ return {
909+ updatedCount : 0 ,
910+ updatedEventIds : [ ] ,
911+ failedEventIds,
912+ } ;
913+ }
914+
915+ const updateManyResult = await collection . updateMany (
916+ { _id : { $in : docsToUpdate . map ( doc => doc . _id ) } } ,
917+ { $addToSet : { visitedBy : new ObjectId ( userId ) } }
918+ ) ;
919+
920+ return {
921+ updatedCount : updateManyResult . modifiedCount ,
922+ updatedEventIds,
923+ failedEventIds,
924+ } ;
925+ }
926+
885927 /**
886928 * Mark or unmark event as Resolved, Ignored or Starred
887929 *
@@ -919,65 +961,18 @@ class EventsFactory extends Factory {
919961 }
920962
921963 /**
922- * Max original event ids per bulkToggleEventMark request
923- */
924- static get BULK_TOGGLE_EVENT_MARK_MAX ( ) {
925- return 100 ;
926- }
927-
928- /**
929- * Bulk mark for resolved / ignored / starred (not the same as per-event toggleEventMark).
930- * - If every found event already has the mark: remove it from all (bulk "undo").
931- * - Otherwise: set the mark on every found event that does not have it yet (never remove
932- * from a subset when the selection is mixed).
933- * Only 'resolved', 'ignored' and 'starred' are allowed for bulk.
964+ * Bulk toggle mark for original events.
934965 *
935966 * @param {string[] } eventIds - original event ids
936967 * @param {string } mark - 'resolved' | 'ignored' | 'starred'
937968 * @returns {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }> }
938969 */
939970 async bulkToggleEventMark ( eventIds , mark ) {
940- if ( mark !== 'resolved' && mark !== 'ignored' && mark !== 'starred' ) {
941- throw new Error ( `bulkToggleEventMark: mark must be resolved, ignored or starred, got ${ mark } ` ) ;
942- }
943-
944- const max = EventsFactory . BULK_TOGGLE_EVENT_MARK_MAX ;
945- const unique = [ ...new Set ( ( eventIds || [ ] ) . map ( id => String ( id ) ) ) ] ;
946-
947- if ( unique . length > max ) {
948- throw new Error ( `bulkToggleEventMark: at most ${ max } event ids allowed` ) ;
949- }
950-
951- const failedEventIds = [ ] ;
952- const validObjectIds = [ ] ;
953-
954- for ( const id of unique ) {
955- if ( ! ObjectId . isValid ( id ) ) {
956- failedEventIds . push ( id ) ;
957- } else {
958- validObjectIds . push ( new ObjectId ( id ) ) ;
959- }
960- }
961-
962- if ( validObjectIds . length === 0 ) {
963- return {
964- updatedCount : 0 ,
965- updatedEventIds : [ ] ,
966- failedEventIds,
967- } ;
968- }
969-
970- const collection = this . getCollection ( this . TYPES . EVENTS ) ;
971- const found = await collection . find ( { _id : { $in : validObjectIds } } ) . toArray ( ) ;
972- const foundByIdStr = new Map ( found . map ( doc => [ doc . _id . toString ( ) , doc ] ) ) ;
973-
974- for ( const oid of validObjectIds ) {
975- const idStr = oid . toString ( ) ;
976-
977- if ( ! foundByIdStr . has ( idStr ) ) {
978- failedEventIds . push ( idStr ) ;
979- }
980- }
971+ const {
972+ collection,
973+ found,
974+ failedEventIds,
975+ } = await this . _resolveBulkEventsByIds ( eventIds ) ;
981976
982977 const nowSec = Math . floor ( Date . now ( ) / 1000 ) ;
983978 const markKey = `marks.${ mark } ` ;
@@ -1023,6 +1018,44 @@ class EventsFactory extends Factory {
10231018 } ;
10241019 }
10251020
1021+ /**
1022+ * Bulk set/clear assignee for many original events.
1023+ *
1024+ * @param {string[] } eventIds - original event ids
1025+ * @param {string|null|undefined } assignee - target assignee id, null/undefined to clear
1026+ * @returns {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }> }
1027+ */
1028+ async bulkUpdateAssignee ( eventIds , assignee ) {
1029+ const {
1030+ collection,
1031+ found,
1032+ failedEventIds,
1033+ } = await this . _resolveBulkEventsByIds ( eventIds ) ;
1034+
1035+ const normalizedAssignee = assignee ? String ( assignee ) : '' ;
1036+ const docsToUpdate = found . filter ( doc => String ( doc . assignee || '' ) !== normalizedAssignee ) ;
1037+ const updatedEventIds = docsToUpdate . map ( doc => doc . _id . toString ( ) ) ;
1038+
1039+ if ( docsToUpdate . length === 0 ) {
1040+ return {
1041+ updatedCount : 0 ,
1042+ updatedEventIds : [ ] ,
1043+ failedEventIds,
1044+ } ;
1045+ }
1046+
1047+ const updateManyResult = await collection . updateMany (
1048+ { _id : { $in : docsToUpdate . map ( doc => doc . _id ) } } ,
1049+ { $set : { assignee : normalizedAssignee } }
1050+ ) ;
1051+
1052+ return {
1053+ updatedCount : updateManyResult . modifiedCount ,
1054+ updatedEventIds,
1055+ failedEventIds,
1056+ } ;
1057+ }
1058+
10261059 /**
10271060 * Remove all project events
10281061 *
@@ -1071,6 +1104,29 @@ class EventsFactory extends Factory {
10711104 return result ;
10721105 }
10731106
1107+ /**
1108+ * Resolve original events for bulk operations and collect not found ids.
1109+ *
1110+ * @param {string[] } eventIds - original event ids
1111+ * @returns {Promise<{ collection: any, found: any[], failedEventIds: string[] }> }
1112+ */
1113+ async _resolveBulkEventsByIds ( eventIds ) {
1114+ const unique = [ ...new Set ( ( eventIds || [ ] ) . map ( id => String ( id ) ) ) ] ;
1115+ const objectIds = unique . map ( id => new ObjectId ( id ) ) ;
1116+ const collection = this . getCollection ( this . TYPES . EVENTS ) ;
1117+ const found = await collection . find ( { _id : { $in : objectIds } } ) . toArray ( ) ;
1118+ const foundByIdStr = new Set ( found . map ( doc => doc . _id . toString ( ) ) ) ;
1119+ const failedEventIds = objectIds
1120+ . map ( id => id . toString ( ) )
1121+ . filter ( id => ! foundByIdStr . has ( id ) ) ;
1122+
1123+ return {
1124+ collection,
1125+ found,
1126+ failedEventIds,
1127+ } ;
1128+ }
1129+
10741130 /**
10751131 * Compose event with repetition
10761132 *
0 commit comments