feat(secret-reminders): addressed PR suggestions and improvements

This commit is contained in:
Carlos Monastyrski
2025-07-04 11:58:09 -03:00
parent 0efc314f33
commit e2559f10bc
26 changed files with 276 additions and 186 deletions
+2 -2
View File
@@ -1217,8 +1217,8 @@ declare module "knex/types/tables" {
TSecretScanningConfigsInsert, TSecretScanningConfigsInsert,
TSecretScanningConfigsUpdate TSecretScanningConfigsUpdate
>; >;
[TableName.Reminders]: KnexOriginal.CompositeTableType<TReminders, TRemindersInsert, TRemindersUpdate>; [TableName.Reminder]: KnexOriginal.CompositeTableType<TReminders, TRemindersInsert, TRemindersUpdate>;
[TableName.RemindersRecipients]: KnexOriginal.CompositeTableType< [TableName.ReminderRecipient]: KnexOriginal.CompositeTableType<
TRemindersRecipients, TRemindersRecipients,
TRemindersRecipientsInsert, TRemindersRecipientsInsert,
TRemindersRecipientsUpdate TRemindersRecipientsUpdate
@@ -4,39 +4,41 @@ import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils"; import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.Reminders))) { if (!(await knex.schema.hasTable(TableName.Reminder))) {
await knex.schema.createTable(TableName.Reminders, (t) => { await knex.schema.createTable(TableName.Reminder, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("secretId").nullable(); t.uuid("secretId").nullable();
t.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE"); t.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
t.string("message").nullable(); t.string("message", 1024).nullable();
t.integer("repeatDays").nullable(); t.integer("repeatDays").checkPositive().nullable();
t.timestamp("nextReminderDate").notNullable(); t.timestamp("nextReminderDate").notNullable();
t.timestamps(true, true, true); t.timestamps(true, true, true);
t.index("secretId"); t.index("secretId");
t.unique("secretId");
}); });
} }
if (!(await knex.schema.hasTable(TableName.RemindersRecipients))) { if (!(await knex.schema.hasTable(TableName.ReminderRecipient))) {
await knex.schema.createTable(TableName.RemindersRecipients, (t) => { await knex.schema.createTable(TableName.ReminderRecipient, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("reminderId").notNullable(); t.uuid("reminderId").notNullable();
t.foreign("reminderId").references("id").inTable(TableName.Reminders).onDelete("CASCADE"); t.foreign("reminderId").references("id").inTable(TableName.Reminder).onDelete("CASCADE");
t.uuid("userId").notNullable(); t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE"); t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.timestamps(true, true, true); t.timestamps(true, true, true);
t.index("reminderId"); t.index("reminderId");
t.index("userId"); t.index("userId");
t.unique(["reminderId", "userId"]);
}); });
} }
await createOnUpdateTrigger(knex, TableName.Reminders); await createOnUpdateTrigger(knex, TableName.Reminder);
await createOnUpdateTrigger(knex, TableName.RemindersRecipients); await createOnUpdateTrigger(knex, TableName.ReminderRecipient);
} }
export async function down(knex: Knex): Promise<void> { export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.Reminders); await dropOnUpdateTrigger(knex, TableName.Reminder);
await knex.schema.dropTableIfExists(TableName.RemindersRecipients); await dropOnUpdateTrigger(knex, TableName.ReminderRecipient);
await dropOnUpdateTrigger(knex, TableName.Reminders); await knex.schema.dropTableIfExists(TableName.Reminder);
await dropOnUpdateTrigger(knex, TableName.RemindersRecipients); await knex.schema.dropTableIfExists(TableName.ReminderRecipient);
} }
+2 -2
View File
@@ -175,8 +175,8 @@ export enum TableName {
SecretScanningConfig = "secret_scanning_configs", SecretScanningConfig = "secret_scanning_configs",
// reminders // reminders
Reminders = "reminders", Reminder = "reminders",
RemindersRecipients = "reminders_recipients" ReminderRecipient = "reminders_recipients"
} }
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId"; export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId";
@@ -458,7 +458,11 @@ export enum EventType {
CREATE_PROJECT = "create-project", CREATE_PROJECT = "create-project",
UPDATE_PROJECT = "update-project", UPDATE_PROJECT = "update-project",
DELETE_PROJECT = "delete-project" DELETE_PROJECT = "delete-project",
CREATE_SECRET_REMINDER = "create-secret-reminder",
GET_SECRET_REMINDER = "get-secret-reminder",
DELETE_SECRET_REMINDER = "delete-secret-reminder"
} }
export const filterableSecretEvents: EventType[] = [ export const filterableSecretEvents: EventType[] = [
@@ -3292,6 +3296,31 @@ interface SecretScanningConfigUpdateEvent {
}; };
} }
interface SecretReminderCreateEvent {
type: EventType.CREATE_SECRET_REMINDER;
metadata: {
secretId: string;
message?: string | null;
repeatDays?: number | null;
nextReminderDate?: string | null;
recipients?: string[] | null;
};
}
interface SecretReminderGetEvent {
type: EventType.GET_SECRET_REMINDER;
metadata: {
secretId: string;
};
}
interface SecretReminderDeleteEvent {
type: EventType.DELETE_SECRET_REMINDER;
metadata: {
secretId: string;
};
}
interface SecretScanningConfigReadEvent { interface SecretScanningConfigReadEvent {
type: EventType.SECRET_SCANNING_CONFIG_GET; type: EventType.SECRET_SCANNING_CONFIG_GET;
metadata?: Record<string, never>; // not needed, based off projectId metadata?: Record<string, never>; // not needed, based off projectId
@@ -3654,4 +3683,7 @@ export type Event =
| OrgUpdateEvent | OrgUpdateEvent
| ProjectCreateEvent | ProjectCreateEvent
| ProjectUpdateEvent | ProjectUpdateEvent
| ProjectDeleteEvent; | ProjectDeleteEvent
| SecretReminderCreateEvent
| SecretReminderGetEvent
| SecretReminderDeleteEvent;
+1 -1
View File
@@ -405,7 +405,7 @@ export type TQueueServiceFactory = {
name: QueueName, name: QueueName,
startOffset?: number, startOffset?: number,
endOffset?: number endOffset?: number
) => Promise<{ delay: number; timestamp: number; repeatJobKey?: string }[]>; ) => Promise<{ delay: number; timestamp: number; repeatJobKey?: string; data?: unknown }[]>;
}; };
export const queueServiceFactory = ( export const queueServiceFactory = (
+3 -2
View File
@@ -248,7 +248,7 @@ import { projectRoleServiceFactory } from "@app/services/project-role/project-ro
import { reminderDALFactory } from "@app/services/reminder/reminder-dal"; import { reminderDALFactory } from "@app/services/reminder/reminder-dal";
import { dailyReminderQueueServiceFactory } from "@app/services/reminder/reminder-queue"; import { dailyReminderQueueServiceFactory } from "@app/services/reminder/reminder-queue";
import { reminderServiceFactory } from "@app/services/reminder/reminder-service"; import { reminderServiceFactory } from "@app/services/reminder/reminder-service";
import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-dal"; import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-recipient-dal";
import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue"; import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue";
import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal"; import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { secretDALFactory } from "@app/services/secret/secret-dal"; import { secretDALFactory } from "@app/services/secret/secret-dal";
@@ -743,7 +743,8 @@ export const registerRoutes = async (
reminderRecipientDAL, reminderRecipientDAL,
smtpService, smtpService,
projectMembershipDAL, projectMembershipDAL,
permissionService permissionService,
secretV2BridgeDAL
}); });
const orgService = orgServiceFactory({ const orgService = orgServiceFactory({
+1 -2
View File
@@ -85,6 +85,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
await server.register(registerInviteOrgRouter, { prefix: "/invite-org" }); await server.register(registerInviteOrgRouter, { prefix: "/invite-org" });
await server.register(registerUserActionRouter, { prefix: "/user-action" }); await server.register(registerUserActionRouter, { prefix: "/user-action" });
await server.register(registerSecretImportRouter, { prefix: "/secret-imports" }); await server.register(registerSecretImportRouter, { prefix: "/secret-imports" });
await server.register(registerReminderRouter, { prefix: "/reminders" });
await server.register(registerSecretFolderRouter, { prefix: "/folders" }); await server.register(registerSecretFolderRouter, { prefix: "/folders" });
await server.register( await server.register(
@@ -173,6 +174,4 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
}, },
{ prefix: "/secret-syncs" } { prefix: "/secret-syncs" }
); );
await server.register(registerReminderRouter, { prefix: "/reminders" });
}; };
+62 -10
View File
@@ -1,28 +1,33 @@
import { z } from "zod"; import { z } from "zod";
import { RemindersSchema } from "@app/db/schemas/reminders"; import { RemindersSchema } from "@app/db/schemas/reminders";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
export const registerReminderRouter = async (server: FastifyZodProvider) => { export const registerReminderRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
url: "/:projectId/reminder", url: "/:projectId/reminder/:secretId",
method: "POST", method: "POST",
config: { config: {
rateLimit: writeLimit rateLimit: writeLimit
}, },
schema: { schema: {
params: z.object({ params: z.object({
projectId: z.string().uuid() projectId: z.string().uuid(),
}), secretId: z.string().uuid()
body: z.object({
message: z.string().trim(),
repeatDays: z.number().min(1).nullable().optional(),
nextReminderDate: z.string().datetime().nullable().optional(),
secretId: z.string().uuid(),
recipients: z.string().array().optional()
}), }),
body: z
.object({
message: z.string().trim().max(1024).optional(),
repeatDays: z.number().min(1).nullable().optional(),
nextReminderDate: z.string().datetime().nullable().optional(),
recipients: z.string().array().optional()
})
.refine((data) => {
return data.repeatDays || data.nextReminderDate;
}, "At least one of repeatDays or nextReminderDate is required"),
response: { response: {
200: z.object({ 200: z.object({
message: z.string() message: z.string()
@@ -37,8 +42,31 @@ export const registerReminderRouter = async (server: FastifyZodProvider) => {
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
projectId: req.params.projectId, projectId: req.params.projectId,
reminder: req.body reminder: {
secretId: req.params.secretId,
message: req.body.message,
repeatDays: req.body.repeatDays,
nextReminderDate: req.body.nextReminderDate,
recipients: req.body.recipients
}
}); });
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: req.params.projectId,
event: {
type: EventType.CREATE_SECRET_REMINDER,
metadata: {
secretId: req.params.secretId,
message: req.body.message,
repeatDays: req.body.repeatDays,
nextReminderDate: req.body.nextReminderDate,
recipients: req.body.recipients
}
}
});
return { message: "Successfully created reminder" }; return { message: "Successfully created reminder" };
} }
}); });
@@ -74,6 +102,18 @@ export const registerReminderRouter = async (server: FastifyZodProvider) => {
secretId: req.params.secretId, secretId: req.params.secretId,
projectId: req.params.projectId projectId: req.params.projectId
}); });
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: req.params.projectId,
event: {
type: EventType.GET_SECRET_REMINDER,
metadata: {
secretId: req.params.secretId
}
}
});
return { reminder }; return { reminder };
} }
}); });
@@ -105,6 +145,18 @@ export const registerReminderRouter = async (server: FastifyZodProvider) => {
secretId: req.params.secretId, secretId: req.params.secretId,
projectId: req.params.projectId projectId: req.params.projectId
}); });
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: req.params.projectId,
event: {
type: EventType.DELETE_SECRET_REMINDER,
metadata: {
secretId: req.params.secretId
}
}
});
return { message: "Successfully deleted reminder" }; return { message: "Successfully deleted reminder" };
} }
}); });
@@ -5,7 +5,7 @@ import { ormify } from "@app/lib/knex";
export type TReminderRecipientDALFactory = ReturnType<typeof reminderRecipientDALFactory>; export type TReminderRecipientDALFactory = ReturnType<typeof reminderRecipientDALFactory>;
export const reminderRecipientDALFactory = (db: TDbClient) => { export const reminderRecipientDALFactory = (db: TDbClient) => {
const reminderRecipientOrm = ormify(db, TableName.RemindersRecipients); const reminderRecipientOrm = ormify(db, TableName.ReminderRecipient);
return { ...reminderRecipientOrm }; return { ...reminderRecipientOrm };
}; };
@@ -1,6 +0,0 @@
export type TReminderRecipient = {
id: string;
reminderId: string;
userId: string;
createdAt: Date;
};
+15 -27
View File
@@ -16,7 +16,7 @@ import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"
export type TReminderDALFactory = ReturnType<typeof reminderDALFactory>; export type TReminderDALFactory = ReturnType<typeof reminderDALFactory>;
export const reminderDALFactory = (db: TDbClient) => { export const reminderDALFactory = (db: TDbClient) => {
const reminderOrm = ormify(db, TableName.Reminders); const reminderOrm = ormify(db, TableName.Reminder);
const getTodayDateRange = () => { const getTodayDateRange = () => {
const now = new Date(); const now = new Date();
@@ -29,15 +29,11 @@ export const reminderDALFactory = (db: TDbClient) => {
const findSecretDailyReminders = async (tx?: Knex) => { const findSecretDailyReminders = async (tx?: Knex) => {
const { startOfDay, endOfDay } = getTodayDateRange(); const { startOfDay, endOfDay } = getTodayDateRange();
const rawReminders = await (tx || db)(TableName.Reminders) const rawReminders = await (tx || db)(TableName.Reminder)
.whereBetween("nextReminderDate", [startOfDay, endOfDay]) .whereBetween("nextReminderDate", [startOfDay, endOfDay])
.leftJoin( .leftJoin(TableName.ReminderRecipient, `${TableName.Reminder}.id`, `${TableName.ReminderRecipient}.reminderId`)
TableName.RemindersRecipients, .leftJoin<TUsers>(TableName.Users, `${TableName.ReminderRecipient}.userId`, `${TableName.Users}.id`)
`${TableName.Reminders}.id`, .leftJoin<TSecretsV2>(TableName.SecretV2, `${TableName.Reminder}.secretId`, `${TableName.SecretV2}.id`)
`${TableName.RemindersRecipients}.reminderId`
)
.leftJoin<TUsers>(TableName.Users, `${TableName.RemindersRecipients}.userId`, `${TableName.Users}.id`)
.leftJoin<TSecretsV2>(TableName.SecretV2, `${TableName.Reminders}.secretId`, `${TableName.SecretV2}.id`)
.leftJoin<TSecretFolders>( .leftJoin<TSecretFolders>(
TableName.SecretFolder, TableName.SecretFolder,
`${TableName.SecretV2}.folderId`, `${TableName.SecretV2}.folderId`,
@@ -50,7 +46,7 @@ export const reminderDALFactory = (db: TDbClient) => {
) )
.leftJoin<TProjects>(TableName.Project, `${TableName.Environment}.projectId`, `${TableName.Project}.id`) .leftJoin<TProjects>(TableName.Project, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.leftJoin<TOrganizations>(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`) .leftJoin<TOrganizations>(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
.select(selectAllTableCols(TableName.Reminders)) .select(selectAllTableCols(TableName.Reminder))
.select(db.ref("email").withSchema(TableName.Users)) .select(db.ref("email").withSchema(TableName.Users))
.select(db.ref("name").withSchema(TableName.Project).as("projectName")) .select(db.ref("name").withSchema(TableName.Project).as("projectName"))
.select(db.ref("id").withSchema(TableName.Project).as("projectId")) .select(db.ref("id").withSchema(TableName.Project).as("projectId"))
@@ -84,30 +80,22 @@ export const reminderDALFactory = (db: TDbClient) => {
const futureDate = new Date(startOfDay); const futureDate = new Date(startOfDay);
futureDate.setDate(futureDate.getDate() + daysAhead); futureDate.setDate(futureDate.getDate() + daysAhead);
const reminders = await (tx || db)(TableName.Reminders) const reminders = await (tx || db)(TableName.Reminder)
.where("nextReminderDate", ">=", startOfDay) .where("nextReminderDate", ">=", startOfDay)
.where("nextReminderDate", "<=", futureDate) .where("nextReminderDate", "<=", futureDate)
.orderBy("nextReminderDate", "asc") .orderBy("nextReminderDate", "asc")
.leftJoin( .leftJoin(TableName.ReminderRecipient, `${TableName.Reminder}.id`, `${TableName.ReminderRecipient}.reminderId`)
TableName.RemindersRecipients, .select(selectAllTableCols(TableName.Reminder))
`${TableName.Reminders}.id`, .select(db.ref("userId").withSchema(TableName.ReminderRecipient));
`${TableName.RemindersRecipients}.reminderId`
)
.select(selectAllTableCols(TableName.Reminders))
.select(db.ref("userId").withSchema(TableName.RemindersRecipients));
return reminders; return reminders;
}; };
const findSecretReminder = async (secretId: string, tx?: Knex) => { const findSecretReminder = async (secretId: string, tx?: Knex) => {
const rawReminders = await (tx || db)(TableName.Reminders) const rawReminders = await (tx || db)(TableName.Reminder)
.where(`${TableName.Reminders}.secretId`, secretId) .where(`${TableName.Reminder}.secretId`, secretId)
.leftJoin( .leftJoin(TableName.ReminderRecipient, `${TableName.Reminder}.id`, `${TableName.ReminderRecipient}.reminderId`)
TableName.RemindersRecipients, .select(selectAllTableCols(TableName.Reminder))
`${TableName.Reminders}.id`, .select(db.ref("userId").withSchema(TableName.ReminderRecipient));
`${TableName.RemindersRecipients}.reminderId`
)
.select(selectAllTableCols(TableName.Reminders))
.select(db.ref("userId").withSchema(TableName.RemindersRecipients));
const reminders = sqlNestRelationships({ const reminders = sqlNestRelationships({
data: rawReminders, data: rawReminders,
key: "id", key: "id",
+25 -19
View File
@@ -11,7 +11,7 @@ import { TReminderServiceFactory } from "./reminder-service";
type TDailyReminderQueueServiceFactoryDep = { type TDailyReminderQueueServiceFactoryDep = {
reminderService: TReminderServiceFactory; reminderService: TReminderServiceFactory;
queueService: TQueueServiceFactory; queueService: TQueueServiceFactory;
secretDAL: Pick<TSecretV2BridgeDALFactory, "findSecretsWithReminderRecipients">; secretDAL: Pick<TSecretV2BridgeDALFactory, "transaction" | "findSecretsWithReminderRecipients">;
secretReminderRecipientsDAL: Pick<TSecretReminderRecipientsDALFactory, "delete">; secretReminderRecipientsDAL: Pick<TSecretReminderRecipientsDALFactory, "delete">;
}; };
@@ -51,11 +51,12 @@ export const dailyReminderQueueServiceFactory = ({
if (match) { if (match) {
map.set(match[0], { map.set(match[0], {
timestamp: job.timestamp, timestamp: job.timestamp,
delay: job.delay delay: job.delay,
data: job.data
}); });
} }
return map; return map;
}, new Map<string, { timestamp: number; delay: number }>()); }, new Map<string, { timestamp: number; delay: number; data: unknown }>());
if (reminderJobs.length === 0) { if (reminderJobs.length === 0) {
logger.info(`${QueueName.SecretReminderMigration}: no reminder jobs found`); logger.info(`${QueueName.SecretReminderMigration}: no reminder jobs found`);
return; return;
@@ -96,21 +97,26 @@ export const dailyReminderQueueServiceFactory = ({
} }
} }
await reminderService.batchCreateReminders( await secretDAL.transaction(async (tx) => {
secretsWithReminder.map((secret) => { await reminderService.batchCreateReminders(
const delayedJob = reminderDelayedJobs.get(secret.id); secretsWithReminder.map((secret) => {
const nextDate = delayedJob ? new Date(delayedJob.timestamp + delayedJob.delay) : undefined; const delayedJob = reminderDelayedJobs.get(secret.id);
return { const projectId = (delayedJob?.data as { projectId?: string })?.projectId;
secretId: secret.id, const nextDate = delayedJob ? new Date(delayedJob.timestamp + delayedJob.delay) : undefined;
message: secret.reminderNote, return {
repeatDays: secret.reminderRepeatDays, secretId: secret.id,
nextReminderDate: nextDate, message: secret.reminderNote,
recipients: secret.recipients || [] repeatDays: secret.reminderRepeatDays,
}; nextReminderDate: nextDate,
}) recipients: secret.recipients || [],
); projectId
};
}),
tx
);
await secretReminderRecipientsDAL.delete({ $in: { secretId: secretsWithReminder.map((s) => s.id) } }); await secretReminderRecipientsDAL.delete({ $in: { secretId: secretsWithReminder.map((s) => s.id) } }, tx);
});
numberOfRetryOnFailure = 0; numberOfRetryOnFailure = 0;
} catch (error) { } catch (error) {
@@ -176,11 +182,11 @@ export const dailyReminderQueueServiceFactory = ({
}; };
queueService.listen(QueueName.DailyReminders, "failed", (_, err) => { queueService.listen(QueueName.DailyReminders, "failed", (_, err) => {
logger.error(err, `${QueueName.DailyReminders}: resource cleanup failed`); logger.error(err, `${QueueName.DailyReminders}: daily reminder processing failed`);
}); });
queueService.listen(QueueName.SecretReminderMigration, "failed", (_, err) => { queueService.listen(QueueName.SecretReminderMigration, "failed", (_, err) => {
logger.error(err, `${QueueName.SecretReminderMigration}: resource cleanup failed`); logger.error(err, `${QueueName.SecretReminderMigration}: secret reminder migration failed`);
}); });
return { return {
@@ -9,7 +9,8 @@ import { logger } from "@app/lib/logger";
import { ActorAuthMethod, ActorType } from "../auth/auth-type"; import { ActorAuthMethod, ActorType } from "../auth/auth-type";
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal"; import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
import { TReminderRecipientDALFactory } from "../reminder-recipients/reminder-dal"; import { TReminderRecipientDALFactory } from "../reminder-recipients/reminder-recipient-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service"; import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { TReminderDALFactory } from "./reminder-dal"; import { TReminderDALFactory } from "./reminder-dal";
import { TBatchCreateReminderDTO, TCreateReminderDTO } from "./reminder-types"; import { TBatchCreateReminderDTO, TCreateReminderDTO } from "./reminder-types";
@@ -20,6 +21,7 @@ type TReminderServiceFactoryDep = {
smtpService: TSmtpService; smtpService: TSmtpService;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findAllProjectMembers">; projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findAllProjectMembers">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
secretV2BridgeDAL: Pick<TSecretV2BridgeDALFactory, "invalidateSecretCacheByProjectId">;
}; };
export type TReminderServiceFactory = ReturnType<typeof reminderServiceFactory>; export type TReminderServiceFactory = ReturnType<typeof reminderServiceFactory>;
@@ -29,7 +31,8 @@ export const reminderServiceFactory = ({
reminderRecipientDAL, reminderRecipientDAL,
smtpService, smtpService,
projectMembershipDAL, projectMembershipDAL,
permissionService permissionService,
secretV2BridgeDAL
}: TReminderServiceFactoryDep) => { }: TReminderServiceFactoryDep) => {
const addDays = (days: number, fromDate: Date = new Date()): Date => { const addDays = (days: number, fromDate: Date = new Date()): Date => {
const result = new Date(fromDate); const result = new Date(fromDate);
@@ -76,13 +79,15 @@ export const reminderServiceFactory = ({
message, message,
repeatDays, repeatDays,
nextReminderDate: nextReminderDateInput, nextReminderDate: nextReminderDateInput,
recipients recipients,
projectId
}: { }: {
secretId?: string; secretId?: string;
message?: string | null; message?: string | null;
repeatDays?: number | null; repeatDays?: number | null;
nextReminderDate?: string | null; nextReminderDate?: string | null;
recipients?: string[] | null; recipients?: string[] | null;
projectId: string;
}) => { }) => {
if (!secretId) { if (!secretId) {
throw new BadRequestError({ message: "secretId is required" }); throw new BadRequestError({ message: "secretId is required" });
@@ -124,7 +129,7 @@ export const reminderServiceFactory = ({
// Manage recipients (add/update/delete as needed) // Manage recipients (add/update/delete as needed)
await $manageReminderRecipients(reminderId, recipients); await $manageReminderRecipients(reminderId, recipients);
await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId);
return { id: reminderId, created: !existingReminder }; return { id: reminderId, created: !existingReminder };
}; };
@@ -145,7 +150,10 @@ export const reminderServiceFactory = ({
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionSecretActions.Edit, ProjectPermissionSub.Secrets); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionSecretActions.Edit, ProjectPermissionSub.Secrets);
const response = await createReminderInternal(reminder); const response = await createReminderInternal({
...reminder,
projectId
});
return response; return response;
}; };
@@ -184,28 +192,30 @@ export const reminderServiceFactory = ({
for (const reminder of remindersToSend) { for (const reminder of remindersToSend) {
try { try {
const recipients: string[] = reminder.recipients await reminderDAL.transaction(async (tx) => {
.map((r) => r.email) const recipients: string[] = reminder.recipients
.filter((email): email is string => Boolean(email)); .map((r) => r.email)
if (recipients.length === 0) { .filter((email): email is string => Boolean(email));
const members = await projectMembershipDAL.findAllProjectMembers(reminder.projectId); if (recipients.length === 0) {
recipients.push(...members.map((m) => m.user.email).filter((email): email is string => Boolean(email))); const members = await projectMembershipDAL.findAllProjectMembers(reminder.projectId);
} recipients.push(...members.map((m) => m.user.email).filter((email): email is string => Boolean(email)));
await smtpService.sendMail({ }
template: SmtpTemplates.SecretReminder, await smtpService.sendMail({
subjectLine: "Infisical secret reminder", template: SmtpTemplates.SecretReminder,
recipients, subjectLine: "Infisical secret reminder",
substitutions: { recipients,
reminderNote: reminder.message || "", substitutions: {
projectName: reminder.projectName || "", reminderNote: reminder.message || "",
organizationName: reminder.organizationName || "" projectName: reminder.projectName || "",
organizationName: reminder.organizationName || ""
}
});
if (reminder.repeatDays) {
await reminderDAL.updateById(reminder.id, { nextReminderDate: addDays(reminder.repeatDays) }, tx);
} else {
await reminderDAL.deleteById(reminder.id, tx);
} }
}); });
if (reminder.repeatDays) {
await reminderDAL.updateById(reminder.id, { nextReminderDate: addDays(reminder.repeatDays) });
} else {
await reminderDAL.deleteById(reminder.id);
}
} catch (error) { } catch (error) {
logger.error( logger.error(
error, error,
@@ -240,27 +250,30 @@ export const reminderServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionSecretActions.Edit, ProjectPermissionSub.Secrets); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionSecretActions.Edit, ProjectPermissionSub.Secrets);
await reminderDAL.delete({ secretId }); await reminderDAL.delete({ secretId });
await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId);
}; };
const removeReminderRecipients = async (secretId: string, tx?: Knex) => { const removeReminderRecipients = async (secretId: string, projectId: string, tx?: Knex) => {
const reminder = await reminderDAL.findOne({ secretId }, tx); const reminder = await reminderDAL.findOne({ secretId }, tx);
if (!reminder) { if (!reminder) {
return; return;
} }
await reminderRecipientDAL.delete({ reminderId: reminder.id }, tx); await reminderRecipientDAL.delete({ reminderId: reminder.id }, tx);
await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId);
}; };
const deleteReminderBySecretId = async (secretId: string, tx?: Knex) => { const deleteReminderBySecretId = async (secretId: string, projectId: string, tx?: Knex) => {
await reminderDAL.delete({ secretId }, tx); await reminderDAL.delete({ secretId }, tx);
await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId);
}; };
const batchCreateReminders = async (remindersData: TBatchCreateReminderDTO) => { const batchCreateReminders = async (remindersData: TBatchCreateReminderDTO, tx?: Knex) => {
if (!remindersData || remindersData.length === 0) { if (!remindersData || remindersData.length === 0) {
return { created: 0, reminderIds: [] }; return { created: 0, reminderIds: [] };
} }
const processedReminders = remindersData.map( const processedReminders = remindersData.map(
({ secretId, message, repeatDays, nextReminderDate: nextReminderDateInput, recipients }) => { ({ secretId, message, repeatDays, nextReminderDate: nextReminderDateInput, recipients, projectId }) => {
let nextReminderDate; let nextReminderDate;
if (nextReminderDateInput) { if (nextReminderDateInput) {
nextReminderDate = new Date(nextReminderDateInput); nextReminderDate = new Date(nextReminderDateInput);
@@ -281,18 +294,21 @@ export const reminderServiceFactory = ({
message, message,
repeatDays, repeatDays,
nextReminderDate, nextReminderDate,
recipients: recipients ? [...new Set(recipients)] : [] recipients: recipients ? [...new Set(recipients)] : [],
projectId
}; };
} }
); );
const newReminders = await reminderDAL.insertMany( const newReminders = await reminderDAL.insertMany(
processedReminders.map(({ secretId, message, repeatDays, nextReminderDate }) => ({ processedReminders.map(({ secretId, message, repeatDays, nextReminderDate, projectId }) => ({
secretId, secretId,
message, message,
repeatDays, repeatDays,
nextReminderDate nextReminderDate,
})) projectId
})),
tx
); );
const allRecipientInserts: Array<{ reminderId: string; userId: string }> = []; const allRecipientInserts: Array<{ reminderId: string; userId: string }> = [];
@@ -310,7 +326,12 @@ export const reminderServiceFactory = ({
}); });
if (allRecipientInserts.length > 0) { if (allRecipientInserts.length > 0) {
await reminderRecipientDAL.insertMany(allRecipientInserts); await reminderRecipientDAL.insertMany(allRecipientInserts, tx);
}
const projectIds = new Set(processedReminders.map((r) => r.projectId).filter((id): id is string => Boolean(id)));
for (const projectId of projectIds) {
await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId);
} }
return { return {
@@ -24,21 +24,11 @@ export type TCreateReminderDTO = {
}; };
}; };
export type TCreateSecretReminderDTO = {
secretName: string;
projectId: string;
environment: string;
secretPath: string;
message?: string | null;
repeatDays?: number | null;
nextReminderDate?: string | null;
recipients?: string[] | null;
};
export type TBatchCreateReminderDTO = { export type TBatchCreateReminderDTO = {
secretId: string; secretId: string;
message?: string | null; message?: string | null;
repeatDays?: number | null; repeatDays?: number | null;
nextReminderDate?: string | Date | null; nextReminderDate?: string | Date | null;
recipients?: string[] | null; recipients?: string[] | null;
projectId?: string;
}[]; }[];
@@ -519,13 +519,9 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
`${TableName.SecretV2}.id`, `${TableName.SecretV2}.id`,
`${TableName.SecretRotationV2SecretMapping}.secretId` `${TableName.SecretRotationV2SecretMapping}.secretId`
) )
.leftJoin(TableName.Reminders, `${TableName.SecretV2}.id`, `${TableName.Reminders}.secretId`) .leftJoin(TableName.Reminder, `${TableName.SecretV2}.id`, `${TableName.Reminder}.secretId`)
.leftJoin( .leftJoin(TableName.ReminderRecipient, `${TableName.Reminder}.id`, `${TableName.ReminderRecipient}.reminderId`)
TableName.RemindersRecipients, .leftJoin(TableName.Users, `${TableName.ReminderRecipient}.userId`, `${TableName.Users}.id`)
`${TableName.Reminders}.id`,
`${TableName.RemindersRecipients}.reminderId`
)
.leftJoin(TableName.Users, `${TableName.RemindersRecipients}.userId`, `${TableName.Users}.id`)
.where((qb) => { .where((qb) => {
if (filters?.metadataFilter && filters.metadataFilter.length > 0) { if (filters?.metadataFilter && filters.metadataFilter.length > 0) {
filters.metadataFilter.forEach((meta) => { filters.metadataFilter.forEach((meta) => {
@@ -548,11 +544,11 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
}) as rank` }) as rank`
) )
) )
.select(db.ref("id").withSchema(TableName.Reminders).as("reminderId")) .select(db.ref("id").withSchema(TableName.Reminder).as("reminderId"))
.select(db.ref("message").withSchema(TableName.Reminders).as("reminderNote")) .select(db.ref("message").withSchema(TableName.Reminder).as("reminderNote"))
.select(db.ref("repeatDays").withSchema(TableName.Reminders).as("reminderRepeatDays")) .select(db.ref("repeatDays").withSchema(TableName.Reminder).as("reminderRepeatDays"))
.select(db.ref("nextReminderDate").withSchema(TableName.Reminders).as("nextReminderDate")) .select(db.ref("nextReminderDate").withSchema(TableName.Reminder).as("nextReminderDate"))
.select(db.ref("id").withSchema(TableName.RemindersRecipients).as("reminderRecipientId")) .select(db.ref("id").withSchema(TableName.ReminderRecipient).as("reminderRecipientId"))
.select(db.ref("username").withSchema(TableName.Users).as("reminderRecipientUsername")) .select(db.ref("username").withSchema(TableName.Users).as("reminderRecipientUsername"))
.select(db.ref("email").withSchema(TableName.Users).as("reminderRecipientEmail")) .select(db.ref("email").withSchema(TableName.Users).as("reminderRecipientEmail"))
.select(db.ref("id").withSchema(TableName.Users).as("reminderRecipientUserId")) .select(db.ref("id").withSchema(TableName.Users).as("reminderRecipientUserId"))
@@ -825,13 +821,10 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
// Join with all recipients for the limited secrets // Join with all recipients for the limited secrets
const docs = await (tx || db)(TableName.SecretV2) const docs = await (tx || db)(TableName.SecretV2)
.whereIn(`${TableName.SecretV2}.id`, limitedSecretIds) .whereIn(`${TableName.SecretV2}.id`, limitedSecretIds)
.leftJoin( .leftJoin(TableName.Reminder, `${TableName.SecretV2}.id`, `${TableName.Reminder}.secretId`)
TableName.SecretReminderRecipients, .leftJoin(TableName.ReminderRecipient, `${TableName.Reminder}.id`, `${TableName.ReminderRecipient}.reminderId`)
`${TableName.SecretV2}.id`,
`${TableName.SecretReminderRecipients}.secretId`
)
.select(selectAllTableCols(TableName.SecretV2)) .select(selectAllTableCols(TableName.SecretV2))
.select(db.ref("userId").withSchema(TableName.SecretReminderRecipients).as("reminderRecipientUserId")); .select(db.ref("userId").withSchema(TableName.ReminderRecipient).as("reminderRecipientUserId"));
const data = sqlNestRelationships({ const data = sqlNestRelationships({
data: docs, data: docs,
@@ -376,7 +376,8 @@ export const fnSecretBulkDelete = async ({
secretDAL, secretDAL,
secretQueueService, secretQueueService,
folderCommitService, folderCommitService,
secretVersionDAL secretVersionDAL,
projectId
}: TFnSecretBulkDelete) => { }: TFnSecretBulkDelete) => {
const deletedSecrets = await secretDAL.deleteMany( const deletedSecrets = await secretDAL.deleteMany(
inputSecrets.map(({ type, secretKey }) => ({ inputSecrets.map(({ type, secretKey }) => ({
@@ -392,7 +393,10 @@ export const fnSecretBulkDelete = async ({
deletedSecrets deletedSecrets
.filter(({ reminderRepeatDays }) => Boolean(reminderRepeatDays)) .filter(({ reminderRepeatDays }) => Boolean(reminderRepeatDays))
.map(({ id, reminderRepeatDays }) => .map(({ id, reminderRepeatDays }) =>
secretQueueService.removeSecretReminder({ secretId: id, repeatDays: reminderRepeatDays as number }, tx) secretQueueService.removeSecretReminder(
{ secretId: id, repeatDays: reminderRepeatDays as number, projectId },
tx
)
) )
); );
@@ -246,6 +246,7 @@ export type TCreateSecretReminderDTO = {
export type TRemoveSecretReminderDTO = { export type TRemoveSecretReminderDTO = {
secretId: string; secretId: string;
repeatDays: number; repeatDays: number;
projectId: string;
}; };
export type TBackFillSecretReferencesDTO = TProjectPermission; export type TBackFillSecretReferencesDTO = TProjectPermission;
+7 -3
View File
@@ -740,7 +740,8 @@ export const fnSecretBulkDelete = async ({
tx, tx,
actorId, actorId,
secretDAL, secretDAL,
secretQueueService secretQueueService,
projectId
}: TFnSecretBulkDelete) => { }: TFnSecretBulkDelete) => {
const deletedSecrets = await secretDAL.deleteMany( const deletedSecrets = await secretDAL.deleteMany(
inputSecrets.map(({ type, secretBlindIndex }) => ({ inputSecrets.map(({ type, secretBlindIndex }) => ({
@@ -756,7 +757,10 @@ export const fnSecretBulkDelete = async ({
deletedSecrets deletedSecrets
.filter(({ secretReminderRepeatDays }) => Boolean(secretReminderRepeatDays)) .filter(({ secretReminderRepeatDays }) => Boolean(secretReminderRepeatDays))
.map(({ id, secretReminderRepeatDays }) => .map(({ id, secretReminderRepeatDays }) =>
secretQueueService.removeSecretReminder({ secretId: id, repeatDays: secretReminderRepeatDays as number }, tx) secretQueueService.removeSecretReminder(
{ secretId: id, repeatDays: secretReminderRepeatDays as number, projectId },
tx
)
) )
); );
@@ -1211,7 +1215,7 @@ export const fnDeleteProjectSecretReminders = async (
: (secret as { secretReminderRepeatDays: number }).secretReminderRepeatDays; : (secret as { secretReminderRepeatDays: number }).secretReminderRepeatDays;
if (repeatDays) { if (repeatDays) {
await reminderService.deleteReminderBySecretId(secret.id); await reminderService.deleteReminderBySecretId(secret.id, projectId);
} }
} }
}; };
+13 -5
View File
@@ -186,7 +186,7 @@ export const secretQueueFactory = ({
const removeSecretReminder = async ({ deleteRecipients = true, ...dto }: TRemoveSecretReminderDTO, tx?: Knex) => { const removeSecretReminder = async ({ deleteRecipients = true, ...dto }: TRemoveSecretReminderDTO, tx?: Knex) => {
if (deleteRecipients) { if (deleteRecipients) {
await reminderService.deleteReminderBySecretId(dto.secretId, tx); await reminderService.deleteReminderBySecretId(dto.secretId, dto.projectId, tx);
} }
}; };
@@ -223,7 +223,12 @@ export const secretQueueFactory = ({
.replace(":", "-"); .replace(":", "-");
}; };
const addSecretReminder = async ({ oldSecret, newSecret, secretReminderRecipients }: TCreateSecretReminderDTO) => { const addSecretReminder = async ({
oldSecret,
newSecret,
projectId,
secretReminderRecipients
}: TCreateSecretReminderDTO) => {
try { try {
if (oldSecret.id !== newSecret.id) { if (oldSecret.id !== newSecret.id) {
throw new BadRequestError({ throw new BadRequestError({
@@ -243,7 +248,8 @@ export const secretQueueFactory = ({
secretId: newSecret.id, secretId: newSecret.id,
message: newSecret.secretReminderNote, message: newSecret.secretReminderNote,
repeatDays: newSecret.secretReminderRepeatDays, repeatDays: newSecret.secretReminderRepeatDays,
recipients: secretReminderRecipients recipients: secretReminderRecipients,
projectId
}); });
} catch (err) { } catch (err) {
logger.error(err, "Failed to create secret reminder."); logger.error(err, "Failed to create secret reminder.");
@@ -254,7 +260,7 @@ export const secretQueueFactory = ({
} }
}; };
const handleSecretReminder = async ({ newSecret, oldSecret }: THandleReminderDTO) => { const handleSecretReminder = async ({ newSecret, oldSecret, projectId }: THandleReminderDTO) => {
const { secretReminderRepeatDays, secretReminderNote, secretReminderRecipients } = newSecret; const { secretReminderRepeatDays, secretReminderNote, secretReminderRecipients } = newSecret;
if (newSecret.type !== SecretType.Personal && secretReminderRepeatDays !== undefined) { if (newSecret.type !== SecretType.Personal && secretReminderRepeatDays !== undefined) {
@@ -265,6 +271,7 @@ export const secretQueueFactory = ({
await addSecretReminder({ await addSecretReminder({
oldSecret, oldSecret,
newSecret, newSecret,
projectId,
secretReminderRecipients: secretReminderRecipients ?? [], secretReminderRecipients: secretReminderRecipients ?? [],
deleteRecipients: false deleteRecipients: false
}); });
@@ -275,7 +282,8 @@ export const secretQueueFactory = ({
) { ) {
await removeSecretReminder({ await removeSecretReminder({
secretId: oldSecret.id, secretId: oldSecret.id,
repeatDays: oldSecret.secretReminderRepeatDays repeatDays: oldSecret.secretReminderRepeatDays,
projectId
}); });
} }
} }
@@ -393,7 +393,8 @@ export const secretServiceFactory = ({
id: secrets[0].id, id: secrets[0].id,
...inputSecret ...inputSecret
}, },
oldSecret: secrets[0] oldSecret: secrets[0],
projectId
}); });
const tags = inputSecret.tags ? await secretTagDAL.findManyTagsById(projectId, inputSecret.tags) : []; const tags = inputSecret.tags ? await secretTagDAL.findManyTagsById(projectId, inputSecret.tags) : [];
@@ -548,7 +549,8 @@ export const secretServiceFactory = ({
await secretQueueService.removeSecretReminder( await secretQueueService.removeSecretReminder(
{ {
repeatDays: secret.secretReminderRepeatDays, repeatDays: secret.secretReminderRepeatDays,
secretId: secret.id secretId: secret.id,
projectId
}, },
tx tx
); );
@@ -1074,7 +1076,8 @@ export const secretServiceFactory = ({
await secretQueueService.removeSecretReminder( await secretQueueService.removeSecretReminder(
{ {
repeatDays: secret.secretReminderRepeatDays, repeatDays: secret.secretReminderRepeatDays,
secretId: secret.id secretId: secret.id,
projectId
}, },
tx tx
); );
@@ -404,11 +404,13 @@ export type TFnSecretBlindIndexCheckV2 = {
export type THandleReminderDTO = { export type THandleReminderDTO = {
newSecret: TPartialInputSecret; newSecret: TPartialInputSecret;
oldSecret: TPartialSecret; oldSecret: TPartialSecret;
projectId: string;
}; };
export type TCreateSecretReminderDTO = { export type TCreateSecretReminderDTO = {
oldSecret: TPartialSecret; oldSecret: TPartialSecret;
newSecret: TPartialSecret; newSecret: TPartialSecret;
projectId: string;
secretReminderRecipients: string[]; secretReminderRecipients: string[];
deleteRecipients?: boolean; deleteRecipients?: boolean;
@@ -417,6 +419,7 @@ export type TCreateSecretReminderDTO = {
export type TRemoveSecretReminderDTO = { export type TRemoveSecretReminderDTO = {
secretId: string; secretId: string;
repeatDays: number; repeatDays: number;
projectId: string;
deleteRecipients?: boolean; deleteRecipients?: boolean;
}; };
+1 -3
View File
@@ -1,3 +1 @@
export { useDeleteReminder } from "./queries"; export { useCreateReminder, useDeleteReminder, useGetReminder } from "./queries";
export { useCreateReminder } from "./queries";
export { useGetReminder } from "./queries";
+1 -2
View File
@@ -15,9 +15,8 @@ export const useCreateReminder = (secretId: string, projectId: string) => {
return useMutation<Reminder, object, CreateReminderDTO>({ return useMutation<Reminder, object, CreateReminderDTO>({
mutationFn: async ({ message, repeatDays, nextReminderDate, recipients }) => { mutationFn: async ({ message, repeatDays, nextReminderDate, recipients }) => {
const { data } = await apiRequest.post<{ reminder: Reminder }>( const { data } = await apiRequest.post<{ reminder: Reminder }>(
`/api/v1/reminders/${projectId}/reminder`, `/api/v1/reminders/${projectId}/reminder/${secretId}`,
{ {
secretId,
message, message,
repeatDays, repeatDays,
nextReminderDate, nextReminderDate,
+1 -8
View File
@@ -11,11 +11,4 @@ export type DeleteReminderDTO = {
reminderId: string; reminderId: string;
}; };
export type Reminder = { export type Reminder = { id: string } & CreateReminderDTO;
id: string;
message?: string | null;
repeatDays?: number | null;
nextReminderDate?: Date | null;
secretId: string;
recipients?: string[];
};
@@ -53,9 +53,7 @@ interface ReminderFormProps {
workspaceId: string; workspaceId: string;
environment: string; environment: string;
secretPath: string; secretPath: string;
secret: { secretId: string;
id: string;
};
reminder?: Reminder; reminder?: Reminder;
} }
@@ -134,7 +132,7 @@ export const CreateReminderForm = ({
workspaceId, workspaceId,
environment, environment,
secretPath, secretPath,
secret, secretId,
reminder reminder
}: ReminderFormProps) => { }: ReminderFormProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@@ -145,8 +143,8 @@ export const CreateReminderForm = ({
const { memberOptions } = useWorkspaceMembers(); const { memberOptions } = useWorkspaceMembers();
// API mutations // API mutations
const { mutateAsync: createReminder } = useCreateReminder(secret?.id, workspaceId); const { mutateAsync: createReminder } = useCreateReminder(secretId, workspaceId);
const { mutateAsync: deleteReminder } = useDeleteReminder(secret?.id, workspaceId); const { mutateAsync: deleteReminder } = useDeleteReminder(secretId, workspaceId);
// Form setup // Form setup
const form = useForm<TReminderFormSchema>({ const form = useForm<TReminderFormSchema>({
@@ -185,7 +183,7 @@ export const CreateReminderForm = ({
queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) queryKey: secretKeys.getProjectSecret({ workspaceId, environment, secretPath })
}); });
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: reminderKeys.getReminder(secret.id, workspaceId) queryKey: reminderKeys.getReminder(secretId, workspaceId)
}); });
}; };
@@ -196,7 +194,7 @@ export const CreateReminderForm = ({
repeatDays: data.repeatDays, repeatDays: data.repeatDays,
message: data.message, message: data.message,
recipients: data.recipients?.map((r) => r.value) || [], recipients: data.recipients?.map((r) => r.value) || [],
secretId: secret.id, secretId,
nextReminderDate: data.nextReminderDate nextReminderDate: data.nextReminderDate
}); });
@@ -221,7 +219,7 @@ export const CreateReminderForm = ({
// Delete reminder handler // Delete reminder handler
const handleDeleteReminder = async () => { const handleDeleteReminder = async () => {
try { try {
await deleteReminder({ reminderId: reminder?.id || "", secretId: secret.id }); await deleteReminder({ reminderId: reminder?.id || "", secretId });
invalidateQueries(); invalidateQueries();
reset(); reset();
onOpenChange(); onOpenChange();
@@ -387,6 +385,7 @@ export const CreateReminderForm = ({
}} }}
popUpContentProps={{}} popUpContentProps={{}}
hideTime hideTime
hidden={{ before: new Date(Date.now() + 86400000) }}
/> />
</FormControl> </FormControl>
</div> </div>
@@ -314,7 +314,7 @@ export const SecretDetailSidebar = ({
workspaceId={currentWorkspace.id} workspaceId={currentWorkspace.id}
environment={environment} environment={environment}
secretPath={secretPath} secretPath={secretPath}
secret={secret} secretId={secret?.id}
reminder={reminderData} reminder={reminderData}
/> />
<UpgradePlanModal <UpgradePlanModal