From 50ba2e543c44945d1ff7e6d43c859077f4bc9a99 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 11 Jul 2025 08:02:18 -0300 Subject: [PATCH] Minor improvements on new reminders logic --- backend/src/@types/fastify.d.ts | 2 +- backend/src/db/schemas/models.ts | 1 - backend/src/services/org/org-service.ts | 2 +- .../src/services/project/project-service.ts | 2 +- backend/src/services/reminder/reminder-dal.ts | 18 +++- .../src/services/reminder/reminder-queue.ts | 2 +- .../src/services/reminder/reminder-service.ts | 41 +++++---- .../src/services/reminder/reminder-types.ts | 90 ++++++++++++++++++- .../secret-v2-bridge/secret-v2-bridge-dal.ts | 1 + .../secret-v2-bridge/secret-v2-bridge-fns.ts | 2 +- .../secret-v2-bridge-service.ts | 2 +- backend/src/services/secret/secret-fns.ts | 2 +- backend/src/services/secret/secret-queue.ts | 4 +- backend/src/services/secret/secret-service.ts | 2 +- 14 files changed, 136 insertions(+), 35 deletions(-) diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index c3fb6b69c..1d2461acf 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -93,7 +93,7 @@ import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env import { TProjectKeyServiceFactory } from "@app/services/project-key/project-key-service"; import { TProjectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service"; import { TProjectRoleServiceFactory } from "@app/services/project-role/project-role-service"; -import { TReminderServiceFactory } from "@app/services/reminder/reminder-service"; +import { TReminderServiceFactory } from "@app/services/reminder/reminder-types"; import { TSecretServiceFactory } from "@app/services/secret/secret-service"; import { TSecretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service"; import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service"; diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index f200f499e..231603153 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -173,7 +173,6 @@ export enum TableName { SecretScanningScan = "secret_scanning_scans", SecretScanningFinding = "secret_scanning_findings", SecretScanningConfig = "secret_scanning_configs", - // reminders Reminder = "reminders", ReminderRecipient = "reminders_recipients" diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index 861038d5a..1d72da16e 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -63,7 +63,7 @@ import { TProjectKeyDALFactory } from "../project-key/project-key-dal"; import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal"; import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal"; import { TProjectRoleDALFactory } from "../project-role/project-role-dal"; -import { TReminderServiceFactory } from "../reminder/reminder-service"; +import { TReminderServiceFactory } from "../reminder/reminder-types"; import { TSecretDALFactory } from "../secret/secret-dal"; import { fnDeleteProjectSecretReminders } from "../secret/secret-fns"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 1dde73839..8d1689b97 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -59,7 +59,7 @@ import { TProjectMembershipDALFactory } from "../project-membership/project-memb import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal"; import { TProjectRoleDALFactory } from "../project-role/project-role-dal"; import { getPredefinedRoles } from "../project-role/project-role-fns"; -import { TReminderServiceFactory } from "../reminder/reminder-service"; +import { TReminderServiceFactory } from "../reminder/reminder-types"; import { TSecretDALFactory } from "../secret/secret-dal"; import { fnDeleteProjectSecretReminders } from "../secret/secret-fns"; import { ROOT_FOLDER_NAME, TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; diff --git a/backend/src/services/reminder/reminder-dal.ts b/backend/src/services/reminder/reminder-dal.ts index 97064c064..0e5d7b895 100644 --- a/backend/src/services/reminder/reminder-dal.ts +++ b/backend/src/services/reminder/reminder-dal.ts @@ -19,11 +19,21 @@ export const reminderDALFactory = (db: TDbClient) => { const reminderOrm = ormify(db, TableName.Reminder); const getTodayDateRange = () => { - const now = new Date(); - const startOfDay = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 0, 0, 0, 0)); - const endOfDay = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 23, 59, 59, 999)); + const today = new Date(); + const year = today.getUTCFullYear(); + const month = today.getUTCMonth(); + const date = today.getUTCDate(); - return { startOfDay, endOfDay }; + // Start of day: 00:00:00.000 UTC + const startOfDay = new Date(Date.UTC(year, month, date, 0, 0, 0, 0)); + + // End of day: 23:59:59.999 UTC + const endOfDay = new Date(Date.UTC(year, month, date, 23, 59, 59, 999)); + + return { + startOfDay, + endOfDay + }; }; const findSecretDailyReminders = async (tx?: Knex) => { diff --git a/backend/src/services/reminder/reminder-queue.ts b/backend/src/services/reminder/reminder-queue.ts index 233c8c7a8..4e31c8a6d 100644 --- a/backend/src/services/reminder/reminder-queue.ts +++ b/backend/src/services/reminder/reminder-queue.ts @@ -6,7 +6,7 @@ import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { TSecretReminderRecipientsDALFactory } from "../secret-reminder-recipients/secret-reminder-recipients-dal"; import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; -import { TReminderServiceFactory } from "./reminder-service"; +import { TReminderServiceFactory } from "./reminder-types"; type TDailyReminderQueueServiceFactoryDep = { reminderService: TReminderServiceFactory; diff --git a/backend/src/services/reminder/reminder-service.ts b/backend/src/services/reminder/reminder-service.ts index 078bde726..6842897e4 100644 --- a/backend/src/services/reminder/reminder-service.ts +++ b/backend/src/services/reminder/reminder-service.ts @@ -13,7 +13,7 @@ import { TReminderRecipientDALFactory } from "../reminder-recipients/reminder-re import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service"; import { TReminderDALFactory } from "./reminder-dal"; -import { TBatchCreateReminderDTO, TCreateReminderDTO } from "./reminder-types"; +import { TBatchCreateReminderDTO, TCreateReminderDTO, TReminderServiceFactory } from "./reminder-types"; type TReminderServiceFactoryDep = { reminderDAL: TReminderDALFactory; @@ -24,8 +24,6 @@ type TReminderServiceFactoryDep = { secretV2BridgeDAL: Pick; }; -export type TReminderServiceFactory = ReturnType; - export const reminderServiceFactory = ({ reminderDAL, reminderRecipientDAL, @@ -33,8 +31,8 @@ export const reminderServiceFactory = ({ projectMembershipDAL, permissionService, secretV2BridgeDAL -}: TReminderServiceFactoryDep) => { - const addDays = (days: number, fromDate: Date = new Date()): Date => { +}: TReminderServiceFactoryDep): TReminderServiceFactory => { + const $addDays = (days: number, fromDate: Date = new Date()): Date => { const result = new Date(fromDate); result.setDate(result.getDate() + days); return result; @@ -74,7 +72,7 @@ export const reminderServiceFactory = ({ } }; - const createReminderInternal = async ({ + const createReminderInternal: TReminderServiceFactory["createReminderInternal"] = async ({ secretId, message, repeatDays, @@ -98,7 +96,7 @@ export const reminderServiceFactory = ({ } if (repeatDays && repeatDays > 0) { - nextReminderDate = addDays(repeatDays); + nextReminderDate = $addDays(repeatDays); } if (!nextReminderDate) { @@ -133,7 +131,7 @@ export const reminderServiceFactory = ({ return { id: reminderId, created: !existingReminder }; }; - const createReminder = async ({ + const createReminder: TReminderServiceFactory["createReminder"] = async ({ actor, actorId, actorOrgId, @@ -157,7 +155,7 @@ export const reminderServiceFactory = ({ return response; }; - const getReminder = async ({ + const getReminder: TReminderServiceFactory["getReminder"] = async ({ secretId, projectId, actor, @@ -187,7 +185,7 @@ export const reminderServiceFactory = ({ return reminder; }; - const sendDailyReminders = async () => { + const sendDailyReminders: TReminderServiceFactory["sendDailyReminders"] = async () => { const remindersToSend = await reminderDAL.findSecretDailyReminders(); for (const reminder of remindersToSend) { @@ -211,7 +209,7 @@ export const reminderServiceFactory = ({ } }); if (reminder.repeatDays) { - await reminderDAL.updateById(reminder.id, { nextReminderDate: addDays(reminder.repeatDays) }, tx); + await reminderDAL.updateById(reminder.id, { nextReminderDate: $addDays(reminder.repeatDays) }, tx); } else { await reminderDAL.deleteById(reminder.id, tx); } @@ -225,7 +223,7 @@ export const reminderServiceFactory = ({ } }; - const deleteReminder = async ({ + const deleteReminder: TReminderServiceFactory["deleteReminder"] = async ({ actor, actorId, actorOrgId, @@ -253,7 +251,11 @@ export const reminderServiceFactory = ({ await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId); }; - const removeReminderRecipients = async (secretId: string, projectId: string, tx?: Knex) => { + const removeReminderRecipients: TReminderServiceFactory["removeReminderRecipients"] = async ( + secretId: string, + projectId: string, + tx?: Knex + ) => { const reminder = await reminderDAL.findOne({ secretId }, tx); if (!reminder) { return; @@ -262,12 +264,19 @@ export const reminderServiceFactory = ({ await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId); }; - const deleteReminderBySecretId = async (secretId: string, projectId: string, tx?: Knex) => { + const deleteReminderBySecretId: TReminderServiceFactory["deleteReminderBySecretId"] = async ( + secretId: string, + projectId: string, + tx?: Knex + ) => { await reminderDAL.delete({ secretId }, tx); await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId); }; - const batchCreateReminders = async (remindersData: TBatchCreateReminderDTO, tx?: Knex) => { + const batchCreateReminders: TReminderServiceFactory["batchCreateReminders"] = async ( + remindersData: TBatchCreateReminderDTO, + tx?: Knex + ) => { if (!remindersData || remindersData.length === 0) { return { created: 0, reminderIds: [] }; } @@ -280,7 +289,7 @@ export const reminderServiceFactory = ({ } if (repeatDays && repeatDays > 0 && !nextReminderDate) { - nextReminderDate = addDays(repeatDays); + nextReminderDate = $addDays(repeatDays); } if (!nextReminderDate) { diff --git a/backend/src/services/reminder/reminder-types.ts b/backend/src/services/reminder/reminder-types.ts index ca0d518b8..cc9c092d3 100644 --- a/backend/src/services/reminder/reminder-types.ts +++ b/backend/src/services/reminder/reminder-types.ts @@ -1,12 +1,15 @@ +import { Knex } from "knex"; + import { ActorAuthMethod, ActorType } from "../auth/auth-type"; export type TReminder = { id: string; - secretId?: string; - message?: string; - repeatDays?: number; - nextReminderDate?: Date; + secretId?: string | null; + message?: string | null; + repeatDays?: number | null; + nextReminderDate: Date; createdAt: Date; + updatedAt: Date; }; export type TCreateReminderDTO = { @@ -32,3 +35,82 @@ export type TBatchCreateReminderDTO = { recipients?: string[] | null; projectId?: string; }[]; + +export interface TReminderServiceFactory { + createReminder: ({ + actor, + actorId, + actorOrgId, + actorAuthMethod, + projectId, + reminder + }: TCreateReminderDTO) => Promise<{ + id: string; + created: boolean; + }>; + + getReminder: ({ + secretId, + projectId, + actor, + actorId, + actorOrgId, + actorAuthMethod + }: { + secretId: string; + projectId: string; + actor: ActorType; + actorId: string; + actorOrgId: string; + actorAuthMethod: ActorAuthMethod; + }) => Promise<(TReminder & { recipients: string[] }) | null>; + + sendDailyReminders: () => Promise; + + deleteReminder: ({ + actor, + actorId, + actorOrgId, + actorAuthMethod, + secretId, + projectId + }: { + actor: ActorType; + actorId: string; + actorOrgId: string; + actorAuthMethod: ActorAuthMethod; + secretId: string; + projectId: string; + }) => Promise; + + removeReminderRecipients: (secretId: string, projectId: string, tx?: Knex) => Promise; + + deleteReminderBySecretId: (secretId: string, projectId: string, tx?: Knex) => Promise; + + batchCreateReminders: ( + remindersData: TBatchCreateReminderDTO, + tx?: Knex + ) => Promise<{ + created: number; + reminderIds: string[]; + }>; + + createReminderInternal: ({ + secretId, + message, + repeatDays, + nextReminderDate, + recipients, + projectId + }: { + secretId?: string; + message?: string | null; + repeatDays?: number | null; + nextReminderDate?: string | null; + recipients?: string[] | null; + projectId: string; + }) => Promise<{ + id: string; + created: boolean; + }>; +} diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts index 196a86180..2b2347fb9 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts @@ -466,6 +466,7 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => { } }; + // This method currently uses too many joins which is not performant, in case we need to add more filters we should consider refactoring this method const findByFolderIds = async (dto: { folderIds: string[]; userId?: string; diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts index 23a69990b..061205645 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts @@ -389,7 +389,7 @@ export const fnSecretBulkDelete = async ({ tx ); - await Promise.allSettled( + await Promise.all( deletedSecrets .filter(({ reminderRepeatDays }) => Boolean(reminderRepeatDays)) .map(({ id, reminderRepeatDays }) => diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts index c66d1aea2..6712305bf 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts @@ -32,7 +32,7 @@ import { TFolderCommitServiceFactory } from "../folder-commit/folder-commit-serv import { TKmsServiceFactory } from "../kms/kms-service"; import { KmsDataKey } from "../kms/kms-types"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; -import { TReminderServiceFactory } from "../reminder/reminder-service"; +import { TReminderServiceFactory } from "../reminder/reminder-types"; import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal"; import { TSecretQueueFactory } from "../secret/secret-queue"; import { TGetASecretByIdDTO } from "../secret/secret-types"; diff --git a/backend/src/services/secret/secret-fns.ts b/backend/src/services/secret/secret-fns.ts index c0116e26e..987eee180 100644 --- a/backend/src/services/secret/secret-fns.ts +++ b/backend/src/services/secret/secret-fns.ts @@ -34,7 +34,7 @@ import { KmsDataKey } from "../kms/kms-types"; import { getBotKeyFnFactory } from "../project-bot/project-bot-fns"; import { TProjectBotServiceFactory } from "../project-bot/project-bot-service"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; -import { TReminderServiceFactory } from "../reminder/reminder-service"; +import { TReminderServiceFactory } from "../reminder/reminder-types"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; import { TSecretDALFactory } from "./secret-dal"; diff --git a/backend/src/services/secret/secret-queue.ts b/backend/src/services/secret/secret-queue.ts index 63c75e125..4540eef82 100644 --- a/backend/src/services/secret/secret-queue.ts +++ b/backend/src/services/secret/secret-queue.ts @@ -48,7 +48,7 @@ import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TProjectKeyDALFactory } from "../project-key/project-key-dal"; import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal"; import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal"; -import { TReminderServiceFactory } from "../reminder/reminder-service"; +import { TReminderServiceFactory } from "../reminder/reminder-types"; import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal"; import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; @@ -1044,7 +1044,7 @@ export const secretQueueFactory = ({ } }); - // TODO: remove this queue (needed for queue initialization and perform the migration) + // TODO(Carlos): remove this queue (needed for queue initialization and perform the migration) queueService.start(QueueName.SecretReminder, async ({ data }) => { logger.info(`(deprecated) secretReminderQueue.process: [secretDocument=${data.secretId}]`); }); diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 5c3a5c2a2..565788d74 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -48,7 +48,7 @@ import { ChangeType } from "../folder-commit/folder-commit-service"; import { TProjectDALFactory } from "../project/project-dal"; import { TProjectBotServiceFactory } from "../project-bot/project-bot-service"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; -import { TReminderServiceFactory } from "../reminder/reminder-service"; +import { TReminderServiceFactory } from "../reminder/reminder-types"; import { TSecretBlindIndexDALFactory } from "../secret-blind-index/secret-blind-index-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";