@@ -918,6 +918,100 @@ class EventsFactory extends Factory {
918918 return collection . updateOne ( query , update ) ;
919919 }
920920
921+ /**
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 (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' and 'ignored' are allowed for bulk.
934+ *
935+ * @param {string[] } eventIds - original event ids
936+ * @param {string } mark - 'resolved' | 'ignored'
937+ * @returns {Promise<{ updatedCount: number, failedEventIds: string[] }> }
938+ */
939+ async bulkToggleEventMark ( eventIds , mark ) {
940+ if ( mark !== 'resolved' && mark !== 'ignored' ) {
941+ throw new Error ( `bulkToggleEventMark: mark must be resolved or ignored, 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 { updatedCount : 0 , failedEventIds } ;
964+ }
965+
966+ const collection = this . getCollection ( this . TYPES . EVENTS ) ;
967+ const found = await collection . find ( { _id : { $in : validObjectIds } } ) . toArray ( ) ;
968+ const foundByIdStr = new Map ( found . map ( doc => [ doc . _id . toString ( ) , doc ] ) ) ;
969+
970+ for ( const oid of validObjectIds ) {
971+ const idStr = oid . toString ( ) ;
972+
973+ if ( ! foundByIdStr . has ( idStr ) ) {
974+ failedEventIds . push ( idStr ) ;
975+ }
976+ }
977+
978+ const nowSec = Math . floor ( Date . now ( ) / 1000 ) ;
979+ const markKey = `marks.${ mark } ` ;
980+ const allHaveMark = found . length > 0 && found . every ( doc => doc . marks && doc . marks [ mark ] ) ;
981+ const ops = [ ] ;
982+
983+ for ( const doc of found ) {
984+ const hasMark = doc . marks && doc . marks [ mark ] ;
985+ let update ;
986+
987+ if ( allHaveMark ) {
988+ update = { $unset : { [ markKey ] : '' } } ;
989+ } else if ( ! hasMark ) {
990+ update = { $set : { [ markKey ] : nowSec } } ;
991+ } else {
992+ continue ;
993+ }
994+
995+ ops . push ( {
996+ updateOne : {
997+ filter : { _id : doc . _id } ,
998+ update,
999+ } ,
1000+ } ) ;
1001+ }
1002+
1003+ if ( ops . length === 0 ) {
1004+ return { updatedCount : 0 , failedEventIds } ;
1005+ }
1006+
1007+ const bulkResult = await collection . bulkWrite ( ops , { ordered : false } ) ;
1008+
1009+ return {
1010+ updatedCount : bulkResult . modifiedCount + bulkResult . upsertedCount ,
1011+ failedEventIds,
1012+ } ;
1013+ }
1014+
9211015 /**
9221016 * Remove all project events
9231017 *
0 commit comments