mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 00:27:30 +00:00
feat(secret-reminders): addressed PR suggestions and improvements
This commit is contained in:
Vendored
+2
-2
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 = (
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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" });
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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" };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 +1 @@
|
|||||||
export { useDeleteReminder } from "./queries";
|
export { useCreateReminder, useDeleteReminder, useGetReminder } from "./queries";
|
||||||
export { useCreateReminder } from "./queries";
|
|
||||||
export { useGetReminder } from "./queries";
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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[];
|
|
||||||
};
|
|
||||||
|
|||||||
+8
-9
@@ -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>
|
||||||
|
|||||||
+1
-1
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user