@rapidmx/restapi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +382 -0
- package/README.md +50 -0
- package/dist/lib/blob/BlobStore.js +6 -0
- package/dist/lib/blob/BlobStore.js.map +1 -0
- package/dist/lib/blob/LocalFsBlobStore.js +101 -0
- package/dist/lib/blob/LocalFsBlobStore.js.map +1 -0
- package/dist/lib/blob/index.js +7 -0
- package/dist/lib/blob/index.js.map +1 -0
- package/dist/lib/index.js +12 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/jobs/AttachmentExtractionJob.js +124 -0
- package/dist/lib/jobs/AttachmentExtractionJob.js.map +1 -0
- package/dist/lib/jobs/CalendarReminderJob.js +132 -0
- package/dist/lib/jobs/CalendarReminderJob.js.map +1 -0
- package/dist/lib/jobs/EasDeviceStateCleanupJob.js +101 -0
- package/dist/lib/jobs/EasDeviceStateCleanupJob.js.map +1 -0
- package/dist/lib/jobs/ExternalShareExpirationJob.js +117 -0
- package/dist/lib/jobs/ExternalShareExpirationJob.js.map +1 -0
- package/dist/lib/jobs/MailboxQuotaRecalcJob.js +163 -0
- package/dist/lib/jobs/MailboxQuotaRecalcJob.js.map +1 -0
- package/dist/lib/jobs/MeetingSchedulingJob.js +106 -0
- package/dist/lib/jobs/MeetingSchedulingJob.js.map +1 -0
- package/dist/lib/jobs/QuarantineRetentionJob.js +97 -0
- package/dist/lib/jobs/QuarantineRetentionJob.js.map +1 -0
- package/dist/lib/jobs/ScanQueueJob.js +223 -0
- package/dist/lib/jobs/ScanQueueJob.js.map +1 -0
- package/dist/lib/jobs/SearchIndexJob.js +146 -0
- package/dist/lib/jobs/SearchIndexJob.js.map +1 -0
- package/dist/lib/jobs/index.js +10 -0
- package/dist/lib/jobs/index.js.map +1 -0
- package/dist/lib/jobs/mongo/AttachmentExtractionJobMongo.js +14 -0
- package/dist/lib/jobs/mongo/AttachmentExtractionJobMongo.js.map +1 -0
- package/dist/lib/jobs/mongo/CalendarReminderJobMongo.js +13 -0
- package/dist/lib/jobs/mongo/CalendarReminderJobMongo.js.map +1 -0
- package/dist/lib/jobs/mongo/EasDeviceStateCleanupJobMongo.js +13 -0
- package/dist/lib/jobs/mongo/EasDeviceStateCleanupJobMongo.js.map +1 -0
- package/dist/lib/jobs/mongo/ExternalShareExpirationJobMongo.js +13 -0
- package/dist/lib/jobs/mongo/ExternalShareExpirationJobMongo.js.map +1 -0
- package/dist/lib/jobs/mongo/MailboxQuotaRecalcJobMongo.js +15 -0
- package/dist/lib/jobs/mongo/MailboxQuotaRecalcJobMongo.js.map +1 -0
- package/dist/lib/jobs/mongo/MeetingSchedulingJobMongo.js +13 -0
- package/dist/lib/jobs/mongo/MeetingSchedulingJobMongo.js.map +1 -0
- package/dist/lib/jobs/mongo/QuarantineRetentionJobMongo.js +13 -0
- package/dist/lib/jobs/mongo/QuarantineRetentionJobMongo.js.map +1 -0
- package/dist/lib/jobs/mongo/ScanQueueJobMongo.js +18 -0
- package/dist/lib/jobs/mongo/ScanQueueJobMongo.js.map +1 -0
- package/dist/lib/jobs/mongo/SearchIndexJobMongo.js +14 -0
- package/dist/lib/jobs/mongo/SearchIndexJobMongo.js.map +1 -0
- package/dist/lib/jobs/mongo/index.js +10 -0
- package/dist/lib/jobs/mongo/index.js.map +1 -0
- package/dist/lib/jobs/mongo.js +2 -0
- package/dist/lib/jobs/mongo.js.map +1 -0
- package/dist/lib/jobs/sql/AttachmentExtractionJobSQL.js +14 -0
- package/dist/lib/jobs/sql/AttachmentExtractionJobSQL.js.map +1 -0
- package/dist/lib/jobs/sql/CalendarReminderJobSQL.js +13 -0
- package/dist/lib/jobs/sql/CalendarReminderJobSQL.js.map +1 -0
- package/dist/lib/jobs/sql/EasDeviceStateCleanupJobSQL.js +13 -0
- package/dist/lib/jobs/sql/EasDeviceStateCleanupJobSQL.js.map +1 -0
- package/dist/lib/jobs/sql/ExternalShareExpirationJobSQL.js +13 -0
- package/dist/lib/jobs/sql/ExternalShareExpirationJobSQL.js.map +1 -0
- package/dist/lib/jobs/sql/MailboxQuotaRecalcJobSQL.js +15 -0
- package/dist/lib/jobs/sql/MailboxQuotaRecalcJobSQL.js.map +1 -0
- package/dist/lib/jobs/sql/MeetingSchedulingJobSQL.js +13 -0
- package/dist/lib/jobs/sql/MeetingSchedulingJobSQL.js.map +1 -0
- package/dist/lib/jobs/sql/QuarantineRetentionJobSQL.js +13 -0
- package/dist/lib/jobs/sql/QuarantineRetentionJobSQL.js.map +1 -0
- package/dist/lib/jobs/sql/ScanQueueJobSQL.js +18 -0
- package/dist/lib/jobs/sql/ScanQueueJobSQL.js.map +1 -0
- package/dist/lib/jobs/sql/SearchIndexJobSQL.js +14 -0
- package/dist/lib/jobs/sql/SearchIndexJobSQL.js.map +1 -0
- package/dist/lib/jobs/sql/index.js +10 -0
- package/dist/lib/jobs/sql/index.js.map +1 -0
- package/dist/lib/jobs/sql.js +2 -0
- package/dist/lib/jobs/sql.js.map +1 -0
- package/dist/lib/models/index.js +2 -0
- package/dist/lib/models/index.js.map +1 -0
- package/dist/lib/models/mongo/AttachmentMongo.js +129 -0
- package/dist/lib/models/mongo/AttachmentMongo.js.map +1 -0
- package/dist/lib/models/mongo/CalendarEventMongo.js +172 -0
- package/dist/lib/models/mongo/CalendarEventMongo.js.map +1 -0
- package/dist/lib/models/mongo/CalendarShareLinkMongo.js +85 -0
- package/dist/lib/models/mongo/CalendarShareLinkMongo.js.map +1 -0
- package/dist/lib/models/mongo/ContactListMongo.js +60 -0
- package/dist/lib/models/mongo/ContactListMongo.js.map +1 -0
- package/dist/lib/models/mongo/ContactMongo.js +148 -0
- package/dist/lib/models/mongo/ContactMongo.js.map +1 -0
- package/dist/lib/models/mongo/DeviceSyncStateMongo.js +97 -0
- package/dist/lib/models/mongo/DeviceSyncStateMongo.js.map +1 -0
- package/dist/lib/models/mongo/FolderMongo.js +102 -0
- package/dist/lib/models/mongo/FolderMongo.js.map +1 -0
- package/dist/lib/models/mongo/IngestQueueEntryMongo.js +93 -0
- package/dist/lib/models/mongo/IngestQueueEntryMongo.js.map +1 -0
- package/dist/lib/models/mongo/MailboxMongo.js +105 -0
- package/dist/lib/models/mongo/MailboxMongo.js.map +1 -0
- package/dist/lib/models/mongo/MessageMongo.js +180 -0
- package/dist/lib/models/mongo/MessageMongo.js.map +1 -0
- package/dist/lib/models/mongo/NoteMongo.js +83 -0
- package/dist/lib/models/mongo/NoteMongo.js.map +1 -0
- package/dist/lib/models/mongo/QuarantineEntryMongo.js +99 -0
- package/dist/lib/models/mongo/QuarantineEntryMongo.js.map +1 -0
- package/dist/lib/models/mongo/ScanResultMongo.js +112 -0
- package/dist/lib/models/mongo/ScanResultMongo.js.map +1 -0
- package/dist/lib/models/mongo/SearchIndexStateMongo.js +83 -0
- package/dist/lib/models/mongo/SearchIndexStateMongo.js.map +1 -0
- package/dist/lib/models/mongo/TaskMongo.js +105 -0
- package/dist/lib/models/mongo/TaskMongo.js.map +1 -0
- package/dist/lib/models/mongo/index.js +16 -0
- package/dist/lib/models/mongo/index.js.map +1 -0
- package/dist/lib/models/mongo.js +2 -0
- package/dist/lib/models/mongo.js.map +1 -0
- package/dist/lib/models/sql/AttachmentSQL.js +129 -0
- package/dist/lib/models/sql/AttachmentSQL.js.map +1 -0
- package/dist/lib/models/sql/CalendarEventSQL.js +176 -0
- package/dist/lib/models/sql/CalendarEventSQL.js.map +1 -0
- package/dist/lib/models/sql/CalendarShareLinkSQL.js +85 -0
- package/dist/lib/models/sql/CalendarShareLinkSQL.js.map +1 -0
- package/dist/lib/models/sql/ContactListSQL.js +60 -0
- package/dist/lib/models/sql/ContactListSQL.js.map +1 -0
- package/dist/lib/models/sql/ContactSQL.js +148 -0
- package/dist/lib/models/sql/ContactSQL.js.map +1 -0
- package/dist/lib/models/sql/DeviceSyncStateSQL.js +97 -0
- package/dist/lib/models/sql/DeviceSyncStateSQL.js.map +1 -0
- package/dist/lib/models/sql/FolderSQL.js +106 -0
- package/dist/lib/models/sql/FolderSQL.js.map +1 -0
- package/dist/lib/models/sql/IngestQueueEntrySQL.js +97 -0
- package/dist/lib/models/sql/IngestQueueEntrySQL.js.map +1 -0
- package/dist/lib/models/sql/MailboxSQL.js +105 -0
- package/dist/lib/models/sql/MailboxSQL.js.map +1 -0
- package/dist/lib/models/sql/MessageSQL.js +184 -0
- package/dist/lib/models/sql/MessageSQL.js.map +1 -0
- package/dist/lib/models/sql/NoteSQL.js +83 -0
- package/dist/lib/models/sql/NoteSQL.js.map +1 -0
- package/dist/lib/models/sql/QuarantineEntrySQL.js +103 -0
- package/dist/lib/models/sql/QuarantineEntrySQL.js.map +1 -0
- package/dist/lib/models/sql/ScanResultSQL.js +116 -0
- package/dist/lib/models/sql/ScanResultSQL.js.map +1 -0
- package/dist/lib/models/sql/SearchIndexStateSQL.js +83 -0
- package/dist/lib/models/sql/SearchIndexStateSQL.js.map +1 -0
- package/dist/lib/models/sql/TaskSQL.js +109 -0
- package/dist/lib/models/sql/TaskSQL.js.map +1 -0
- package/dist/lib/models/sql/index.js +16 -0
- package/dist/lib/models/sql/index.js.map +1 -0
- package/dist/lib/models/sql.js +2 -0
- package/dist/lib/models/sql.js.map +1 -0
- package/dist/lib/models/types.js +108 -0
- package/dist/lib/models/types.js.map +1 -0
- package/dist/lib/mongo.js +8 -0
- package/dist/lib/mongo.js.map +1 -0
- package/dist/lib/push/MailPushRoute.js +42 -0
- package/dist/lib/push/MailPushRoute.js.map +1 -0
- package/dist/lib/push/index.js +6 -0
- package/dist/lib/push/index.js.map +1 -0
- package/dist/lib/routes/BaseAttachmentRoute.js +142 -0
- package/dist/lib/routes/BaseAttachmentRoute.js.map +1 -0
- package/dist/lib/routes/BaseCalendarShareLinkRoute.js +123 -0
- package/dist/lib/routes/BaseCalendarShareLinkRoute.js.map +1 -0
- package/dist/lib/routes/BaseFolderRoute.js +192 -0
- package/dist/lib/routes/BaseFolderRoute.js.map +1 -0
- package/dist/lib/routes/BaseMailIngestRoute.js +179 -0
- package/dist/lib/routes/BaseMailIngestRoute.js.map +1 -0
- package/dist/lib/routes/BaseMailboxRoute.js +187 -0
- package/dist/lib/routes/BaseMailboxRoute.js.map +1 -0
- package/dist/lib/routes/BaseMessageRoute.js +142 -0
- package/dist/lib/routes/BaseMessageRoute.js.map +1 -0
- package/dist/lib/routes/BaseScopedChildRoute.js +348 -0
- package/dist/lib/routes/BaseScopedChildRoute.js.map +1 -0
- package/dist/lib/routes/BaseSearchRoute.js +91 -0
- package/dist/lib/routes/BaseSearchRoute.js.map +1 -0
- package/dist/lib/routes/index.js +13 -0
- package/dist/lib/routes/index.js.map +1 -0
- package/dist/lib/routes/mongo/AttachmentRouteMongo.js +26 -0
- package/dist/lib/routes/mongo/AttachmentRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/CalendarEventRouteMongo.js +27 -0
- package/dist/lib/routes/mongo/CalendarEventRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/CalendarShareLinkRouteMongo.js +26 -0
- package/dist/lib/routes/mongo/CalendarShareLinkRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/ContactListRouteMongo.js +26 -0
- package/dist/lib/routes/mongo/ContactListRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/ContactRouteMongo.js +27 -0
- package/dist/lib/routes/mongo/ContactRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/FolderRouteMongo.js +26 -0
- package/dist/lib/routes/mongo/FolderRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/IngestQueueRouteMongo.js +33 -0
- package/dist/lib/routes/mongo/IngestQueueRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/MailIngestRouteMongo.js +14 -0
- package/dist/lib/routes/mongo/MailIngestRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/MailboxRouteMongo.js +39 -0
- package/dist/lib/routes/mongo/MailboxRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/MessageRouteMongo.js +27 -0
- package/dist/lib/routes/mongo/MessageRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/NoteRouteMongo.js +26 -0
- package/dist/lib/routes/mongo/NoteRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/QuarantineRouteMongo.js +35 -0
- package/dist/lib/routes/mongo/QuarantineRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/SearchRouteMongo.js +13 -0
- package/dist/lib/routes/mongo/SearchRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/TaskRouteMongo.js +27 -0
- package/dist/lib/routes/mongo/TaskRouteMongo.js.map +1 -0
- package/dist/lib/routes/mongo/index.js +15 -0
- package/dist/lib/routes/mongo/index.js.map +1 -0
- package/dist/lib/routes/mongo.js +2 -0
- package/dist/lib/routes/mongo.js.map +1 -0
- package/dist/lib/routes/sql/AttachmentRouteSQL.js +26 -0
- package/dist/lib/routes/sql/AttachmentRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/CalendarEventRouteSQL.js +27 -0
- package/dist/lib/routes/sql/CalendarEventRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/CalendarShareLinkRouteSQL.js +26 -0
- package/dist/lib/routes/sql/CalendarShareLinkRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/ContactListRouteSQL.js +26 -0
- package/dist/lib/routes/sql/ContactListRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/ContactRouteSQL.js +27 -0
- package/dist/lib/routes/sql/ContactRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/FolderRouteSQL.js +26 -0
- package/dist/lib/routes/sql/FolderRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/IngestQueueRouteSQL.js +29 -0
- package/dist/lib/routes/sql/IngestQueueRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/MailIngestRouteSQL.js +35 -0
- package/dist/lib/routes/sql/MailIngestRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/MailboxRouteSQL.js +64 -0
- package/dist/lib/routes/sql/MailboxRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/MessageRouteSQL.js +27 -0
- package/dist/lib/routes/sql/MessageRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/NoteRouteSQL.js +26 -0
- package/dist/lib/routes/sql/NoteRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/QuarantineRouteSQL.js +29 -0
- package/dist/lib/routes/sql/QuarantineRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/SearchRouteSQL.js +13 -0
- package/dist/lib/routes/sql/SearchRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/TaskRouteSQL.js +27 -0
- package/dist/lib/routes/sql/TaskRouteSQL.js.map +1 -0
- package/dist/lib/routes/sql/index.js +15 -0
- package/dist/lib/routes/sql/index.js.map +1 -0
- package/dist/lib/routes/sql.js +2 -0
- package/dist/lib/routes/sql.js.map +1 -0
- package/dist/lib/scan/AvScanProvider.js +2 -0
- package/dist/lib/scan/AvScanProvider.js.map +1 -0
- package/dist/lib/scan/ClamAvScanProvider.js +112 -0
- package/dist/lib/scan/ClamAvScanProvider.js.map +1 -0
- package/dist/lib/scan/RspamdSpamScanProvider.js +100 -0
- package/dist/lib/scan/RspamdSpamScanProvider.js.map +1 -0
- package/dist/lib/scan/ScanPipeline.js +118 -0
- package/dist/lib/scan/ScanPipeline.js.map +1 -0
- package/dist/lib/scan/SpamScanProvider.js +2 -0
- package/dist/lib/scan/SpamScanProvider.js.map +1 -0
- package/dist/lib/scan/index.js +10 -0
- package/dist/lib/scan/index.js.map +1 -0
- package/dist/lib/search/MongoTextSearchProvider.js +116 -0
- package/dist/lib/search/MongoTextSearchProvider.js.map +1 -0
- package/dist/lib/search/OpenSearchProvider.js +156 -0
- package/dist/lib/search/OpenSearchProvider.js.map +1 -0
- package/dist/lib/search/PostgresFullTextSearchProvider.js +139 -0
- package/dist/lib/search/PostgresFullTextSearchProvider.js.map +1 -0
- package/dist/lib/search/SearchProvider.js +6 -0
- package/dist/lib/search/SearchProvider.js.map +1 -0
- package/dist/lib/search/extraction/DocxTextExtractor.js +27 -0
- package/dist/lib/search/extraction/DocxTextExtractor.js.map +1 -0
- package/dist/lib/search/extraction/ExtractorRegistry.js +82 -0
- package/dist/lib/search/extraction/ExtractorRegistry.js.map +1 -0
- package/dist/lib/search/extraction/HtmlTextExtractor.js +20 -0
- package/dist/lib/search/extraction/HtmlTextExtractor.js.map +1 -0
- package/dist/lib/search/extraction/PdfTextExtractor.js +22 -0
- package/dist/lib/search/extraction/PdfTextExtractor.js.map +1 -0
- package/dist/lib/search/extraction/PlainTextExtractor.js +14 -0
- package/dist/lib/search/extraction/PlainTextExtractor.js.map +1 -0
- package/dist/lib/search/extraction/TextExtractor.js +6 -0
- package/dist/lib/search/extraction/TextExtractor.js.map +1 -0
- package/dist/lib/search/index.js +15 -0
- package/dist/lib/search/index.js.map +1 -0
- package/dist/lib/sql.js +8 -0
- package/dist/lib/sql.js.map +1 -0
- package/dist/lib/transport/MTAIngestAdapter.js +30 -0
- package/dist/lib/transport/MTAIngestAdapter.js.map +1 -0
- package/dist/lib/transport/MailTransport.js +6 -0
- package/dist/lib/transport/MailTransport.js.map +1 -0
- package/dist/lib/transport/PostfixSendmailTransport.js +61 -0
- package/dist/lib/transport/PostfixSendmailTransport.js.map +1 -0
- package/dist/lib/transport/index.js +8 -0
- package/dist/lib/transport/index.js.map +1 -0
- package/dist/lib/util/FolderUtils.js +50 -0
- package/dist/lib/util/FolderUtils.js.map +1 -0
- package/dist/lib/util/MailSendUtils.js +36 -0
- package/dist/lib/util/MailSendUtils.js.map +1 -0
- package/dist/lib/util/MailboxScopeUtils.js +23 -0
- package/dist/lib/util/MailboxScopeUtils.js.map +1 -0
- package/dist/lib/util/OptionalDeps.js +23 -0
- package/dist/lib/util/OptionalDeps.js.map +1 -0
- package/dist/lib/util/RecoverableRepoUtils.js +42 -0
- package/dist/lib/util/RecoverableRepoUtils.js.map +1 -0
- package/dist/lib/util/index.js +17 -0
- package/dist/lib/util/index.js.map +1 -0
- package/dist/types/blob/BlobStore.d.ts +54 -0
- package/dist/types/blob/LocalFsBlobStore.d.ts +23 -0
- package/dist/types/blob/index.d.ts +2 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/jobs/AttachmentExtractionJob.d.ts +34 -0
- package/dist/types/jobs/CalendarReminderJob.d.ts +37 -0
- package/dist/types/jobs/EasDeviceStateCleanupJob.d.ts +26 -0
- package/dist/types/jobs/ExternalShareExpirationJob.d.ts +34 -0
- package/dist/types/jobs/MailboxQuotaRecalcJob.d.ts +44 -0
- package/dist/types/jobs/MeetingSchedulingJob.d.ts +32 -0
- package/dist/types/jobs/QuarantineRetentionJob.d.ts +27 -0
- package/dist/types/jobs/ScanQueueJob.d.ts +41 -0
- package/dist/types/jobs/SearchIndexJob.d.ts +37 -0
- package/dist/types/jobs/index.d.ts +9 -0
- package/dist/types/jobs/mongo/AttachmentExtractionJobMongo.d.ts +6 -0
- package/dist/types/jobs/mongo/CalendarReminderJobMongo.d.ts +5 -0
- package/dist/types/jobs/mongo/EasDeviceStateCleanupJobMongo.d.ts +5 -0
- package/dist/types/jobs/mongo/ExternalShareExpirationJobMongo.d.ts +5 -0
- package/dist/types/jobs/mongo/MailboxQuotaRecalcJobMongo.d.ts +7 -0
- package/dist/types/jobs/mongo/MeetingSchedulingJobMongo.d.ts +5 -0
- package/dist/types/jobs/mongo/QuarantineRetentionJobMongo.d.ts +5 -0
- package/dist/types/jobs/mongo/ScanQueueJobMongo.d.ts +10 -0
- package/dist/types/jobs/mongo/SearchIndexJobMongo.d.ts +6 -0
- package/dist/types/jobs/mongo/index.d.ts +9 -0
- package/dist/types/jobs/mongo.d.ts +1 -0
- package/dist/types/jobs/sql/AttachmentExtractionJobSQL.d.ts +6 -0
- package/dist/types/jobs/sql/CalendarReminderJobSQL.d.ts +5 -0
- package/dist/types/jobs/sql/EasDeviceStateCleanupJobSQL.d.ts +5 -0
- package/dist/types/jobs/sql/ExternalShareExpirationJobSQL.d.ts +5 -0
- package/dist/types/jobs/sql/MailboxQuotaRecalcJobSQL.d.ts +7 -0
- package/dist/types/jobs/sql/MeetingSchedulingJobSQL.d.ts +5 -0
- package/dist/types/jobs/sql/QuarantineRetentionJobSQL.d.ts +5 -0
- package/dist/types/jobs/sql/ScanQueueJobSQL.d.ts +10 -0
- package/dist/types/jobs/sql/SearchIndexJobSQL.d.ts +6 -0
- package/dist/types/jobs/sql/index.d.ts +9 -0
- package/dist/types/jobs/sql.d.ts +1 -0
- package/dist/types/models/index.d.ts +1 -0
- package/dist/types/models/mongo/AttachmentMongo.d.ts +22 -0
- package/dist/types/models/mongo/CalendarEventMongo.d.ts +28 -0
- package/dist/types/models/mongo/CalendarShareLinkMongo.d.ts +16 -0
- package/dist/types/models/mongo/ContactListMongo.d.ts +13 -0
- package/dist/types/models/mongo/ContactMongo.d.ts +25 -0
- package/dist/types/models/mongo/DeviceSyncStateMongo.d.ts +18 -0
- package/dist/types/models/mongo/FolderMongo.d.ts +18 -0
- package/dist/types/models/mongo/IngestQueueEntryMongo.d.ts +17 -0
- package/dist/types/models/mongo/MailboxMongo.d.ts +18 -0
- package/dist/types/models/mongo/MessageMongo.d.ts +29 -0
- package/dist/types/models/mongo/NoteMongo.d.ts +16 -0
- package/dist/types/models/mongo/QuarantineEntryMongo.d.ts +18 -0
- package/dist/types/models/mongo/ScanResultMongo.d.ts +23 -0
- package/dist/types/models/mongo/SearchIndexStateMongo.d.ts +16 -0
- package/dist/types/models/mongo/TaskMongo.d.ts +19 -0
- package/dist/types/models/mongo/index.d.ts +15 -0
- package/dist/types/models/mongo.d.ts +1 -0
- package/dist/types/models/sql/AttachmentSQL.d.ts +22 -0
- package/dist/types/models/sql/CalendarEventSQL.d.ts +28 -0
- package/dist/types/models/sql/CalendarShareLinkSQL.d.ts +16 -0
- package/dist/types/models/sql/ContactListSQL.d.ts +13 -0
- package/dist/types/models/sql/ContactSQL.d.ts +25 -0
- package/dist/types/models/sql/DeviceSyncStateSQL.d.ts +18 -0
- package/dist/types/models/sql/FolderSQL.d.ts +18 -0
- package/dist/types/models/sql/IngestQueueEntrySQL.d.ts +17 -0
- package/dist/types/models/sql/MailboxSQL.d.ts +18 -0
- package/dist/types/models/sql/MessageSQL.d.ts +29 -0
- package/dist/types/models/sql/NoteSQL.d.ts +16 -0
- package/dist/types/models/sql/QuarantineEntrySQL.d.ts +18 -0
- package/dist/types/models/sql/ScanResultSQL.d.ts +23 -0
- package/dist/types/models/sql/SearchIndexStateSQL.d.ts +16 -0
- package/dist/types/models/sql/TaskSQL.d.ts +19 -0
- package/dist/types/models/sql/index.d.ts +15 -0
- package/dist/types/models/sql.d.ts +1 -0
- package/dist/types/models/types.d.ts +541 -0
- package/dist/types/mongo.d.ts +3 -0
- package/dist/types/push/MailPushRoute.d.ts +37 -0
- package/dist/types/push/index.d.ts +1 -0
- package/dist/types/routes/BaseAttachmentRoute.d.ts +24 -0
- package/dist/types/routes/BaseCalendarShareLinkRoute.d.ts +35 -0
- package/dist/types/routes/BaseFolderRoute.d.ts +58 -0
- package/dist/types/routes/BaseMailIngestRoute.d.ts +47 -0
- package/dist/types/routes/BaseMailboxRoute.d.ts +60 -0
- package/dist/types/routes/BaseMessageRoute.d.ts +27 -0
- package/dist/types/routes/BaseScopedChildRoute.d.ts +61 -0
- package/dist/types/routes/BaseSearchRoute.d.ts +23 -0
- package/dist/types/routes/index.d.ts +8 -0
- package/dist/types/routes/mongo/AttachmentRouteMongo.d.ts +6 -0
- package/dist/types/routes/mongo/CalendarEventRouteMongo.d.ts +6 -0
- package/dist/types/routes/mongo/CalendarShareLinkRouteMongo.d.ts +6 -0
- package/dist/types/routes/mongo/ContactListRouteMongo.d.ts +6 -0
- package/dist/types/routes/mongo/ContactRouteMongo.d.ts +6 -0
- package/dist/types/routes/mongo/FolderRouteMongo.d.ts +5 -0
- package/dist/types/routes/mongo/IngestQueueRouteMongo.d.ts +13 -0
- package/dist/types/routes/mongo/MailIngestRouteMongo.d.ts +6 -0
- package/dist/types/routes/mongo/MailboxRouteMongo.d.ts +8 -0
- package/dist/types/routes/mongo/MessageRouteMongo.d.ts +6 -0
- package/dist/types/routes/mongo/NoteRouteMongo.d.ts +6 -0
- package/dist/types/routes/mongo/QuarantineRouteMongo.d.ts +15 -0
- package/dist/types/routes/mongo/SearchRouteMongo.d.ts +5 -0
- package/dist/types/routes/mongo/TaskRouteMongo.d.ts +6 -0
- package/dist/types/routes/mongo/index.d.ts +14 -0
- package/dist/types/routes/mongo.d.ts +1 -0
- package/dist/types/routes/sql/AttachmentRouteSQL.d.ts +6 -0
- package/dist/types/routes/sql/CalendarEventRouteSQL.d.ts +6 -0
- package/dist/types/routes/sql/CalendarShareLinkRouteSQL.d.ts +6 -0
- package/dist/types/routes/sql/ContactListRouteSQL.d.ts +6 -0
- package/dist/types/routes/sql/ContactRouteSQL.d.ts +6 -0
- package/dist/types/routes/sql/FolderRouteSQL.d.ts +5 -0
- package/dist/types/routes/sql/IngestQueueRouteSQL.d.ts +9 -0
- package/dist/types/routes/sql/MailIngestRouteSQL.d.ts +23 -0
- package/dist/types/routes/sql/MailboxRouteSQL.d.ts +22 -0
- package/dist/types/routes/sql/MessageRouteSQL.d.ts +6 -0
- package/dist/types/routes/sql/NoteRouteSQL.d.ts +6 -0
- package/dist/types/routes/sql/QuarantineRouteSQL.d.ts +9 -0
- package/dist/types/routes/sql/SearchRouteSQL.d.ts +5 -0
- package/dist/types/routes/sql/TaskRouteSQL.d.ts +6 -0
- package/dist/types/routes/sql/index.d.ts +14 -0
- package/dist/types/routes/sql.d.ts +1 -0
- package/dist/types/scan/AvScanProvider.d.ts +22 -0
- package/dist/types/scan/ClamAvScanProvider.d.ts +17 -0
- package/dist/types/scan/RspamdSpamScanProvider.d.ts +16 -0
- package/dist/types/scan/ScanPipeline.d.ts +53 -0
- package/dist/types/scan/SpamScanProvider.d.ts +29 -0
- package/dist/types/scan/index.d.ts +5 -0
- package/dist/types/search/MongoTextSearchProvider.d.ts +22 -0
- package/dist/types/search/OpenSearchProvider.d.ts +24 -0
- package/dist/types/search/PostgresFullTextSearchProvider.d.ts +21 -0
- package/dist/types/search/SearchProvider.d.ts +62 -0
- package/dist/types/search/extraction/DocxTextExtractor.d.ts +14 -0
- package/dist/types/search/extraction/ExtractorRegistry.d.ts +19 -0
- package/dist/types/search/extraction/HtmlTextExtractor.d.ts +11 -0
- package/dist/types/search/extraction/PdfTextExtractor.d.ts +10 -0
- package/dist/types/search/extraction/PlainTextExtractor.d.ts +10 -0
- package/dist/types/search/extraction/TextExtractor.d.ts +17 -0
- package/dist/types/search/index.d.ts +10 -0
- package/dist/types/sql.d.ts +3 -0
- package/dist/types/transport/MTAIngestAdapter.d.ts +25 -0
- package/dist/types/transport/MailTransport.d.ts +29 -0
- package/dist/types/transport/PostfixSendmailTransport.d.ts +15 -0
- package/dist/types/transport/index.d.ts +3 -0
- package/dist/types/util/FolderUtils.d.ts +15 -0
- package/dist/types/util/MailSendUtils.d.ts +19 -0
- package/dist/types/util/MailboxScopeUtils.d.ts +19 -0
- package/dist/types/util/OptionalDeps.d.ts +9 -0
- package/dist/types/util/RecoverableRepoUtils.d.ts +24 -0
- package/dist/types/util/index.d.ts +12 -0
- package/package.json +122 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
11
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
12
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
13
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
14
|
+
import * as crypto from "crypto";
|
|
15
|
+
import { ObjectDecorators } from "@rapidrest/core";
|
|
16
|
+
import { BackgroundService, RepoUtils } from "@rapidrest/service-core";
|
|
17
|
+
import { ExtractorRegistry } from "../search/extraction/ExtractorRegistry.js";
|
|
18
|
+
import { RecoverableRepoUtils } from "../util/RecoverableRepoUtils.js";
|
|
19
|
+
const { Config, Init, Inject, Logger } = ObjectDecorators;
|
|
20
|
+
/**
|
|
21
|
+
* Runs `ExtractorRegistry` (PDF/DOCX/plain-text/HTML text extraction) over `Attachment` records that haven't
|
|
22
|
+
* been processed yet (`extractedTextBlobKey` unset), as a background job — never inline on ingestion, since
|
|
23
|
+
* extraction can be slow and must not block SMTP-ACK or webmail send/save-draft latency (see the architecture
|
|
24
|
+
* plan's rationale for this).
|
|
25
|
+
*
|
|
26
|
+
* Every eligible attachment gets `extractedTextBlobKey` set exactly once, even when nothing extractable was
|
|
27
|
+
* found (an empty-content blob) — this is what marks an attachment as "already attempted" so an unsupported
|
|
28
|
+
* MIME type (e.g. an image) isn't re-selected by this job forever. When extraction *does* produce non-empty
|
|
29
|
+
* text for an attachment whose parent `Message` was already search-indexed, this job clears that message's
|
|
30
|
+
* `searchIndexedAt` back to `undefined` so `SearchIndexJob` re-indexes it with the newly available text.
|
|
31
|
+
*
|
|
32
|
+
* @author Jean-Philippe Steinmetz
|
|
33
|
+
*/
|
|
34
|
+
export class AttachmentExtractionJob extends BackgroundService {
|
|
35
|
+
constructor() {
|
|
36
|
+
super(...arguments);
|
|
37
|
+
this.extractorRegistry = new ExtractorRegistry();
|
|
38
|
+
this.scheduleExpr = "*/20 * * * * *";
|
|
39
|
+
this.batchSize = 25;
|
|
40
|
+
}
|
|
41
|
+
get schedule() {
|
|
42
|
+
return this.scheduleExpr;
|
|
43
|
+
}
|
|
44
|
+
async init() {
|
|
45
|
+
this.attachmentRepo = await this._objectFactory.newInstance(RepoUtils, {
|
|
46
|
+
name: this.attachmentClass.name,
|
|
47
|
+
args: [this.attachmentClass],
|
|
48
|
+
});
|
|
49
|
+
this.messageRepo = await this._objectFactory.newInstance(RecoverableRepoUtils, {
|
|
50
|
+
name: this.messageClass.name,
|
|
51
|
+
args: [this.messageClass],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async start() {
|
|
55
|
+
// Nothing to do at startup beyond `init()` above; processing happens entirely in `run()`.
|
|
56
|
+
}
|
|
57
|
+
stop() {
|
|
58
|
+
// Do nothing
|
|
59
|
+
}
|
|
60
|
+
async run() {
|
|
61
|
+
if (!this.attachmentRepo || !this.blobStore) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// `limit` must be passed both via `options` (used by the Mongo backend) *and* baked into the query
|
|
65
|
+
// object itself (all `ModelUtils.buildSearchQuerySQL` reads - it ignores `options.limit` entirely and
|
|
66
|
+
// falls back to its own default of 100 otherwise). Confirmed by real-database testing: on the SQL
|
|
67
|
+
// backend, `options.limit` alone silently caps at 100 regardless of the configured batch size.
|
|
68
|
+
const pending = await this.attachmentRepo.find({ extractedTextBlobKey: null, limit: this.batchSize }, { ignoreACL: true, limit: this.batchSize });
|
|
69
|
+
for (const attachment of pending) {
|
|
70
|
+
try {
|
|
71
|
+
await this.processAttachment(attachment);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
this.logger?.warn(`AttachmentExtractionJob: failed to process attachment ${attachment.uid}: ${err.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async processAttachment(attachment) {
|
|
79
|
+
const content = await this.blobStore.get(attachment.blobKey);
|
|
80
|
+
const text = await this.extractorRegistry.extract(attachment.mimeType, content);
|
|
81
|
+
const extractedTextBlobKey = `attachment-text/${crypto.randomUUID()}`;
|
|
82
|
+
await this.blobStore.put(extractedTextBlobKey, Buffer.from(text ?? "", "utf-8"), {
|
|
83
|
+
contentType: "text/plain",
|
|
84
|
+
});
|
|
85
|
+
await this.attachmentRepo.update({ uid: attachment.uid, version: attachment.version, extractedTextBlobKey }, attachment, { ignoreACL: true });
|
|
86
|
+
if (text && text.length > 0) {
|
|
87
|
+
const message = await this.messageRepo.findOne(attachment.messageUid, {
|
|
88
|
+
ignoreACL: true,
|
|
89
|
+
});
|
|
90
|
+
if (message?.searchIndexedAt) {
|
|
91
|
+
// Explicit `null`, not `undefined`: TypeORM's `Repository.update()` silently drops any
|
|
92
|
+
// property whose value is `undefined` from its generated `SET` clause, so on the SQL backend
|
|
93
|
+
// an `undefined` here would leave the persisted `searchIndexedAt` completely untouched (still
|
|
94
|
+
// reporting "already indexed") - a real, confirmed cross-backend bug caught by real-database
|
|
95
|
+
// testing. MongoDB's own `updateOne($set: ...)` happens to coerce either value to `null`
|
|
96
|
+
// equivalently, so `null` is correct there too.
|
|
97
|
+
await this.messageRepo.update({ uid: message.uid, version: message.version, searchIndexedAt: null }, message, { ignoreACL: true, skipPush: true });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
__decorate([
|
|
103
|
+
Inject("BlobStore"),
|
|
104
|
+
__metadata("design:type", Object)
|
|
105
|
+
], AttachmentExtractionJob.prototype, "blobStore", void 0);
|
|
106
|
+
__decorate([
|
|
107
|
+
Config("mail:jobs:attachment_extraction:schedule", "*/20 * * * * *"),
|
|
108
|
+
__metadata("design:type", String)
|
|
109
|
+
], AttachmentExtractionJob.prototype, "scheduleExpr", void 0);
|
|
110
|
+
__decorate([
|
|
111
|
+
Config("mail:jobs:attachment_extraction:batch_size", 25),
|
|
112
|
+
__metadata("design:type", Number)
|
|
113
|
+
], AttachmentExtractionJob.prototype, "batchSize", void 0);
|
|
114
|
+
__decorate([
|
|
115
|
+
Logger,
|
|
116
|
+
__metadata("design:type", Object)
|
|
117
|
+
], AttachmentExtractionJob.prototype, "logger", void 0);
|
|
118
|
+
__decorate([
|
|
119
|
+
Init,
|
|
120
|
+
__metadata("design:type", Function),
|
|
121
|
+
__metadata("design:paramtypes", []),
|
|
122
|
+
__metadata("design:returntype", Promise)
|
|
123
|
+
], AttachmentExtractionJob.prototype, "init", null);
|
|
124
|
+
//# sourceMappingURL=AttachmentExtractionJob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AttachmentExtractionJob.js","sourceRoot":"","sources":["../../../src/jobs/AttachmentExtractionJob.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
11
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
12
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
13
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
14
|
+
import { ObjectDecorators } from "@rapidrest/core";
|
|
15
|
+
import { BackgroundService, NotificationUtils } from "@rapidrest/service-core";
|
|
16
|
+
import { RecoverableRepoUtils } from "../util/RecoverableRepoUtils.js";
|
|
17
|
+
const { Config, Init, Inject, Logger } = ObjectDecorators;
|
|
18
|
+
/**
|
|
19
|
+
* Polls upcoming `CalendarEvent` records and dispatches a `"reminder"` push notification once each event's
|
|
20
|
+
* configured `reminderMinutesBeforeStart` fire time falls within this job's own polling window.
|
|
21
|
+
*
|
|
22
|
+
* `CalendarEvent` has no persisted "reminder already sent" flag, and adding one is out of scope here, so this
|
|
23
|
+
* job takes the simplest correct-enough approach: the query only prunes past events (`startDate` lower-bounded
|
|
24
|
+
* at `now`, via `ModelUtils.buildSearchQuery`'s single-sided `gte(...)` operator, which - unlike its two-sided
|
|
25
|
+
* `range(...)` operator - has confirmed correct Date coercion on both backends), and everything else (the
|
|
26
|
+
* upper lookahead bound, and the actual reminder-fire-time arithmetic) is computed/filtered in-process.
|
|
27
|
+
*
|
|
28
|
+
* KNOWN LIMITATION: without a persisted "reminder already sent" flag (e.g. a future `CalendarEvent.
|
|
29
|
+
* reminderSentAt`), a reminder could in principle fire more than once if this job's own polling window ever
|
|
30
|
+
* overlaps across runs (e.g. after a restart, or a run that took longer than `windowSeconds`). This is a
|
|
31
|
+
* deliberate simplification, not an oversight - flagged here for a future follow-up.
|
|
32
|
+
*
|
|
33
|
+
* Concrete entity classes are supplied by the Mongo/SQL subclasses (`CalendarReminderJobMongo`/
|
|
34
|
+
* `CalendarReminderJobSQL`), following the same generic pattern `ScanQueueJob` uses.
|
|
35
|
+
*
|
|
36
|
+
* @author Jean-Philippe Steinmetz
|
|
37
|
+
*/
|
|
38
|
+
export class CalendarReminderJob extends BackgroundService {
|
|
39
|
+
constructor() {
|
|
40
|
+
super(...arguments);
|
|
41
|
+
this.scheduleExpr = "0 * * * * *";
|
|
42
|
+
this.batchSize = 200;
|
|
43
|
+
this.windowSeconds = 60;
|
|
44
|
+
}
|
|
45
|
+
get schedule() {
|
|
46
|
+
return this.scheduleExpr;
|
|
47
|
+
}
|
|
48
|
+
async init() {
|
|
49
|
+
this.calendarEventRepo = await this._objectFactory.newInstance(RecoverableRepoUtils, {
|
|
50
|
+
name: this.calendarEventClass.name,
|
|
51
|
+
args: [this.calendarEventClass],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async start() {
|
|
55
|
+
// Nothing to do at startup beyond `init()` above; processing happens entirely in `run()`.
|
|
56
|
+
}
|
|
57
|
+
stop() {
|
|
58
|
+
// Do nothing
|
|
59
|
+
}
|
|
60
|
+
async run() {
|
|
61
|
+
if (!this.calendarEventRepo) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const now = new Date();
|
|
65
|
+
const windowEnd = new Date(now.getTime() + this.windowSeconds * 1000);
|
|
66
|
+
// A generous upper bound (24 hours) on candidates fetched per run - actual eligibility is decided below
|
|
67
|
+
// by comparing each candidate's computed reminder fire time against [now, windowEnd].
|
|
68
|
+
const lookaheadEnd = new Date(now.getTime() + 24 * 60 * 60 * 1000);
|
|
69
|
+
// `limit` must be passed both via `options` (used by the Mongo backend) *and* baked into the query
|
|
70
|
+
// object itself (all `ModelUtils.buildSearchQuerySQL` reads - it ignores `options.limit` entirely and
|
|
71
|
+
// falls back to its own default of 100 otherwise). Confirmed by real-database testing: on the SQL
|
|
72
|
+
// backend, `options.limit` alone silently caps at 100 regardless of the configured batch size - and
|
|
73
|
+
// since this job's own default `batch_size` (200) already exceeds that fallback, this was a live bug
|
|
74
|
+
// on every SQL deployment out of the box, not just a high-`batch_size` edge case.
|
|
75
|
+
//
|
|
76
|
+
// `sort: "startDate"` (ascending) ensures that when the candidate set IS truncated at `batchSize`, the
|
|
77
|
+
// soonest-due events are the ones kept - without it, truncation could arbitrarily drop an event whose
|
|
78
|
+
// reminder is due imminently in favor of one due much later, silently skipping (and, per this job's own
|
|
79
|
+
// documented "no persisted already-sent flag" limitation, never retrying) that reminder.
|
|
80
|
+
const candidates = await this.calendarEventRepo.find({ startDate: `gte(${now.toISOString()})`, limit: this.batchSize, sort: "startDate" }, { ignoreACL: true, limit: this.batchSize });
|
|
81
|
+
for (const event of candidates) {
|
|
82
|
+
try {
|
|
83
|
+
if (event.reminderMinutesBeforeStart === undefined || event.reminderMinutesBeforeStart === null) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
// The query already lower-bounds `startDate` at `now`; only the upper lookahead bound needs
|
|
87
|
+
// checking here.
|
|
88
|
+
if (event.startDate > lookaheadEnd) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const fireAt = new Date(event.startDate.getTime() - event.reminderMinutesBeforeStart * 60 * 1000);
|
|
92
|
+
if (fireAt >= now && fireAt <= windowEnd) {
|
|
93
|
+
this.notificationUtils?.sendMessage([event.folderUid, event.mailboxUid], "CalendarEvent", "reminder", {
|
|
94
|
+
eventUid: event.uid,
|
|
95
|
+
title: event.title,
|
|
96
|
+
startDate: event.startDate,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
this.logger?.warn(`CalendarReminderJob: failed to process reminder for event ${event.uid}: ${err.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
__decorate([
|
|
107
|
+
Inject(NotificationUtils),
|
|
108
|
+
__metadata("design:type", NotificationUtils)
|
|
109
|
+
], CalendarReminderJob.prototype, "notificationUtils", void 0);
|
|
110
|
+
__decorate([
|
|
111
|
+
Config("mail:jobs:calendar_reminder:schedule", "0 * * * * *"),
|
|
112
|
+
__metadata("design:type", String)
|
|
113
|
+
], CalendarReminderJob.prototype, "scheduleExpr", void 0);
|
|
114
|
+
__decorate([
|
|
115
|
+
Config("mail:jobs:calendar_reminder:batch_size", 200),
|
|
116
|
+
__metadata("design:type", Number)
|
|
117
|
+
], CalendarReminderJob.prototype, "batchSize", void 0);
|
|
118
|
+
__decorate([
|
|
119
|
+
Config("mail:jobs:calendar_reminder:window_seconds", 60),
|
|
120
|
+
__metadata("design:type", Number)
|
|
121
|
+
], CalendarReminderJob.prototype, "windowSeconds", void 0);
|
|
122
|
+
__decorate([
|
|
123
|
+
Logger,
|
|
124
|
+
__metadata("design:type", Object)
|
|
125
|
+
], CalendarReminderJob.prototype, "logger", void 0);
|
|
126
|
+
__decorate([
|
|
127
|
+
Init,
|
|
128
|
+
__metadata("design:type", Function),
|
|
129
|
+
__metadata("design:paramtypes", []),
|
|
130
|
+
__metadata("design:returntype", Promise)
|
|
131
|
+
], CalendarReminderJob.prototype, "init", null);
|
|
132
|
+
//# sourceMappingURL=CalendarReminderJob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CalendarReminderJob.js","sourceRoot":"","sources":["../../../src/jobs/CalendarReminderJob.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
11
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
12
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
13
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
14
|
+
import { ObjectDecorators } from "@rapidrest/core";
|
|
15
|
+
import { BackgroundService, RepoUtils } from "@rapidrest/service-core";
|
|
16
|
+
const { Config, Init, Logger } = ObjectDecorators;
|
|
17
|
+
/**
|
|
18
|
+
* Prunes `DeviceSyncState` rows for EAS devices that haven't synced in more than `device_ttl_days` days (or
|
|
19
|
+
* that have never successfully synced at all), keeping the collection from growing unbounded with abandoned
|
|
20
|
+
* device pairings.
|
|
21
|
+
*
|
|
22
|
+
* Concrete entity classes are supplied by the Mongo/SQL subclasses (`EasDeviceStateCleanupJobMongo`/
|
|
23
|
+
* `EasDeviceStateCleanupJobSQL`), following the same generic pattern `ScanQueueJob` uses.
|
|
24
|
+
*
|
|
25
|
+
* @author Jean-Philippe Steinmetz
|
|
26
|
+
*/
|
|
27
|
+
export class EasDeviceStateCleanupJob extends BackgroundService {
|
|
28
|
+
constructor() {
|
|
29
|
+
super(...arguments);
|
|
30
|
+
this.scheduleExpr = "0 0 4 * * *";
|
|
31
|
+
this.batchSize = 500;
|
|
32
|
+
this.deviceTtlDays = 90;
|
|
33
|
+
}
|
|
34
|
+
get schedule() {
|
|
35
|
+
return this.scheduleExpr;
|
|
36
|
+
}
|
|
37
|
+
async init() {
|
|
38
|
+
this.deviceSyncStateRepo = await this._objectFactory.newInstance(RepoUtils, {
|
|
39
|
+
name: this.deviceSyncStateClass.name,
|
|
40
|
+
args: [this.deviceSyncStateClass],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async start() {
|
|
44
|
+
// Nothing to do at startup beyond `init()` above; processing happens entirely in `run()`.
|
|
45
|
+
}
|
|
46
|
+
stop() {
|
|
47
|
+
// Do nothing
|
|
48
|
+
}
|
|
49
|
+
async run() {
|
|
50
|
+
if (!this.deviceSyncStateRepo) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const cutoff = new Date(Date.now() - this.deviceTtlDays * 24 * 60 * 60 * 1000);
|
|
54
|
+
// `ModelUtils.buildSearchQuery` supports single-sided `lt(...)` comparisons with correct Date
|
|
55
|
+
// coercion on both backends, so the "stale" half of this job's criteria is pushed into the query. The
|
|
56
|
+
// "never synced at all" half (`lastSyncAt` unset) is a separate, plain-equality query rather than an
|
|
57
|
+
// `$or` of the two — `$or` support isn't confirmed identical across both backends' query builders, and
|
|
58
|
+
// a second bounded query is just as cheap for a low-frequency daily cleanup job.
|
|
59
|
+
//
|
|
60
|
+
// `limit` is passed both via `options` (all the Mongo backend of `RepoUtils.find()` actually reads)
|
|
61
|
+
// *and* baked into each query object (all `ModelUtils.buildSearchQuerySQL` reads - it ignores
|
|
62
|
+
// `options.limit` entirely and falls back to its own default of 100 otherwise). Confirmed by
|
|
63
|
+
// real-database testing: on the SQL backend, `options.limit` alone silently caps at 100 regardless of
|
|
64
|
+
// the configured batch size.
|
|
65
|
+
const [stale, neverSynced] = await Promise.all([
|
|
66
|
+
this.deviceSyncStateRepo.find({ lastSyncAt: `lt(${cutoff.toISOString()})`, limit: this.batchSize }, { ignoreACL: true, limit: this.batchSize }),
|
|
67
|
+
this.deviceSyncStateRepo.find({ lastSyncAt: null, limit: this.batchSize }, { ignoreACL: true, limit: this.batchSize }),
|
|
68
|
+
]);
|
|
69
|
+
for (const row of [...stale, ...neverSynced]) {
|
|
70
|
+
try {
|
|
71
|
+
await this.deviceSyncStateRepo.delete(row.uid, { ignoreACL: true, purge: true });
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
this.logger?.warn(`EasDeviceStateCleanupJob: failed to delete stale device sync state ${row.uid}: ${err.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
__decorate([
|
|
80
|
+
Config("mail:jobs:eas_device_cleanup:schedule", "0 0 4 * * *"),
|
|
81
|
+
__metadata("design:type", String)
|
|
82
|
+
], EasDeviceStateCleanupJob.prototype, "scheduleExpr", void 0);
|
|
83
|
+
__decorate([
|
|
84
|
+
Config("mail:jobs:eas_device_cleanup:batch_size", 500),
|
|
85
|
+
__metadata("design:type", Number)
|
|
86
|
+
], EasDeviceStateCleanupJob.prototype, "batchSize", void 0);
|
|
87
|
+
__decorate([
|
|
88
|
+
Config("mail:jobs:eas_device_cleanup:device_ttl_days", 90),
|
|
89
|
+
__metadata("design:type", Number)
|
|
90
|
+
], EasDeviceStateCleanupJob.prototype, "deviceTtlDays", void 0);
|
|
91
|
+
__decorate([
|
|
92
|
+
Logger,
|
|
93
|
+
__metadata("design:type", Object)
|
|
94
|
+
], EasDeviceStateCleanupJob.prototype, "logger", void 0);
|
|
95
|
+
__decorate([
|
|
96
|
+
Init,
|
|
97
|
+
__metadata("design:type", Function),
|
|
98
|
+
__metadata("design:paramtypes", []),
|
|
99
|
+
__metadata("design:returntype", Promise)
|
|
100
|
+
], EasDeviceStateCleanupJob.prototype, "init", null);
|
|
101
|
+
//# sourceMappingURL=EasDeviceStateCleanupJob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EasDeviceStateCleanupJob.js","sourceRoot":"","sources":["../../../src/jobs/EasDeviceStateCleanupJob.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
11
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
12
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
13
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
14
|
+
import { ObjectDecorators } from "@rapidrest/core";
|
|
15
|
+
import { BackgroundService, ACLUtils, RepoUtils } from "@rapidrest/service-core";
|
|
16
|
+
const { Config, Init, Inject, Logger } = ObjectDecorators;
|
|
17
|
+
/**
|
|
18
|
+
* Garbage-collects expired `CalendarShareLink` rows (anonymous external calendar-sharing links whose
|
|
19
|
+
* `expiresAt` has passed), also revoking the `ACLRecord` each link's `token` was granted on its shared
|
|
20
|
+
* folder's `AccessControlList` (see `BaseCalendarShareLinkRoute`, which grants/upserts that same record on
|
|
21
|
+
* create/update — this job is the other half of keeping it in sync, for links that expire rather than being
|
|
22
|
+
* explicitly deleted via the API).
|
|
23
|
+
*
|
|
24
|
+
* Concrete entity classes are supplied by the Mongo/SQL subclasses (`ExternalShareExpirationJobMongo`/
|
|
25
|
+
* `ExternalShareExpirationJobSQL`), following the same generic pattern `ScanQueueJob` uses.
|
|
26
|
+
*
|
|
27
|
+
* @author Jean-Philippe Steinmetz
|
|
28
|
+
*/
|
|
29
|
+
export class ExternalShareExpirationJob extends BackgroundService {
|
|
30
|
+
constructor() {
|
|
31
|
+
super(...arguments);
|
|
32
|
+
this.scheduleExpr = "0 0 5 * * *";
|
|
33
|
+
this.batchSize = 500;
|
|
34
|
+
}
|
|
35
|
+
get schedule() {
|
|
36
|
+
return this.scheduleExpr;
|
|
37
|
+
}
|
|
38
|
+
async init() {
|
|
39
|
+
this.calendarShareLinkRepo = await this._objectFactory.newInstance(RepoUtils, {
|
|
40
|
+
name: this.calendarShareLinkClass.name,
|
|
41
|
+
args: [this.calendarShareLinkClass],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async start() {
|
|
45
|
+
// Nothing to do at startup beyond `init()` above; processing happens entirely in `run()`.
|
|
46
|
+
}
|
|
47
|
+
stop() {
|
|
48
|
+
// Do nothing
|
|
49
|
+
}
|
|
50
|
+
async run() {
|
|
51
|
+
if (!this.calendarShareLinkRepo) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const now = new Date();
|
|
55
|
+
// `ModelUtils.buildSearchQuery` supports single-sided `lt(...)`/`gt(...)`/`gte(...)`/`lte(...)`
|
|
56
|
+
// comparisons with correct Date coercion on both backends (only its two-sided `range(...)` operator
|
|
57
|
+
// has a documented Date-coercion gap) - a plain `lt(now)` on `expiresAt` also naturally excludes rows
|
|
58
|
+
// where it's unset, on both Mongo (`$lt` against a missing field never matches) and SQL (comparing
|
|
59
|
+
// NULL is never true), so this needs no additional in-process filtering for that case.
|
|
60
|
+
//
|
|
61
|
+
// `limit` is passed both via `options` (all the Mongo backend of `RepoUtils.find()` actually reads)
|
|
62
|
+
// *and* baked into the query object itself (all `ModelUtils.buildSearchQuerySQL` reads - it ignores
|
|
63
|
+
// `options.limit` entirely and falls back to its own default of 100 otherwise). Confirmed by
|
|
64
|
+
// real-database testing: on the SQL backend, `options.limit` alone silently caps at 100 regardless of
|
|
65
|
+
// the configured batch size.
|
|
66
|
+
const links = await this.calendarShareLinkRepo.find({ expiresAt: `lt(${now.toISOString()})`, limit: this.batchSize }, { ignoreACL: true, limit: this.batchSize });
|
|
67
|
+
for (const link of links) {
|
|
68
|
+
try {
|
|
69
|
+
await this.calendarShareLinkRepo.delete(link.uid, { ignoreACL: true, purge: true });
|
|
70
|
+
await this.revokeShareTokenAccess(link.folderUid, link.token);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
this.logger?.warn(`ExternalShareExpirationJob: failed to delete expired share link ${link.uid}: ${err.message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Removes any ACL record for `token` from `folderUid`'s ACL - see `BaseCalendarShareLinkRoute`'s identical
|
|
78
|
+
* helper for the full rationale (kept as a separate copy here rather than a shared import, since this job
|
|
79
|
+
* has no other dependency on the routes layer and the logic is a few lines). A no-op if the folder has no
|
|
80
|
+
* ACL document or no matching record - failing open here keeps a missing/corrupt folder ACL from turning a
|
|
81
|
+
* routine expiration sweep into a failed job run for every OTHER link in the same batch. */
|
|
82
|
+
async revokeShareTokenAccess(folderUid, token) {
|
|
83
|
+
const acl = await this.aclUtils?.findACL(folderUid);
|
|
84
|
+
if (!acl) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const records = acl.records.filter((record) => record.userOrRoleId !== token);
|
|
88
|
+
if (records.length === acl.records.length) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
acl.records = records;
|
|
92
|
+
await this.aclUtils.saveACL(acl);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
__decorate([
|
|
96
|
+
Inject(ACLUtils),
|
|
97
|
+
__metadata("design:type", ACLUtils)
|
|
98
|
+
], ExternalShareExpirationJob.prototype, "aclUtils", void 0);
|
|
99
|
+
__decorate([
|
|
100
|
+
Config("mail:jobs:external_share_expiration:schedule", "0 0 5 * * *"),
|
|
101
|
+
__metadata("design:type", String)
|
|
102
|
+
], ExternalShareExpirationJob.prototype, "scheduleExpr", void 0);
|
|
103
|
+
__decorate([
|
|
104
|
+
Config("mail:jobs:external_share_expiration:batch_size", 500),
|
|
105
|
+
__metadata("design:type", Number)
|
|
106
|
+
], ExternalShareExpirationJob.prototype, "batchSize", void 0);
|
|
107
|
+
__decorate([
|
|
108
|
+
Logger,
|
|
109
|
+
__metadata("design:type", Object)
|
|
110
|
+
], ExternalShareExpirationJob.prototype, "logger", void 0);
|
|
111
|
+
__decorate([
|
|
112
|
+
Init,
|
|
113
|
+
__metadata("design:type", Function),
|
|
114
|
+
__metadata("design:paramtypes", []),
|
|
115
|
+
__metadata("design:returntype", Promise)
|
|
116
|
+
], ExternalShareExpirationJob.prototype, "init", null);
|
|
117
|
+
//# sourceMappingURL=ExternalShareExpirationJob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExternalShareExpirationJob.js","sourceRoot":"","sources":["../../../src/jobs/ExternalShareExpirationJob.ts"],"names":[],"mappings":"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"}
|