diff --git a/.gitignore b/.gitignore index cb3f9358e..b4b457984 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,15 @@ coverage .DS_Store globalConfig.json *.log +*.js +!jest.config.js +!jest.global-teardown.js +!jest.setup.js +!jest.setup.mongo-repl-set.js +!jest.setup.redis-mock.js +!jest-mongodb-config.js +!migrate-mongo-config.js +!/env.js +!convertors/**/*.js +!tools/**/*.js +!bin/**/*.js diff --git a/lib/utils/payday.ts b/lib/utils/payday.ts new file mode 100644 index 000000000..0826c8169 --- /dev/null +++ b/lib/utils/payday.ts @@ -0,0 +1,52 @@ +import { HOURS_IN_DAY, MINUTES_IN_HOUR, SECONDS_IN_MINUTE, MS_IN_SEC } from './consts'; + +/** + * Milliseconds in day. Needs for calculating difference between dates in days. + */ +const MILLISECONDS_IN_DAY = HOURS_IN_DAY * MINUTES_IN_HOUR * SECONDS_IN_MINUTE * MS_IN_SEC; + +/** + * Returns difference between now and payday in days + * + * Pay day is calculated by formula: paidUntil date or last charge date + 1 month + * + * @param date - last charge date + * @param paidUntil - paid until date + * @param isDebug - flag for debug purposes + */ +export function daysBeforePayday(date: Date, paidUntil: Date = null, isDebug = false): number { + const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date); + + if (isDebug) { + expectedPayDay.setDate(date.getDate() + 1); + } else if (!paidUntil) { + expectedPayDay.setMonth(date.getMonth() + 1); + } + + const now = new Date().getTime(); + + return Math.floor((expectedPayDay.getTime() - now) / MILLISECONDS_IN_DAY); +} + +/** + * Returns difference between payday and now in days + * + * Pay day is calculated by formula: paidUntil date or last charge date + 1 month + * + * @param date - last charge date + * @param paidUntil - paid until date + * @param isDebug - flag for debug purposes + */ +export function daysAfterPayday(date: Date, paidUntil: Date = null, isDebug = false): number { + const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date); + + if (isDebug) { + expectedPayDay.setDate(date.getDate() + 1); + } else if (!paidUntil) { + expectedPayDay.setMonth(date.getMonth() + 1); + } + + const now = new Date().getTime(); + + return Math.floor((now - expectedPayDay.getTime()) / MILLISECONDS_IN_DAY); +} \ No newline at end of file diff --git a/workers/email/scripts/emailOverview.ts b/workers/email/scripts/emailOverview.ts index 981093dbd..aa31a0998 100644 --- a/workers/email/scripts/emailOverview.ts +++ b/workers/email/scripts/emailOverview.ts @@ -19,6 +19,7 @@ import { ObjectId } from 'mongodb'; import * as path from 'path'; import * as dotenv from 'dotenv'; import { HttpStatusCode } from '../../../lib/utils/consts'; +import { daysAfterPayday } from '../../../lib/utils/payday'; /** * Merge email worker .env and root workers .env @@ -147,6 +148,7 @@ class EmailTestServer { user, period: 10, reason: 'error on the payment server side', + daysAfterPayday: await this.calculateDaysAfterPayday(workspace), }; try { @@ -210,7 +212,7 @@ class EmailTestServer { */ private sendHTML(html: string, response: http.ServerResponse): void { response.writeHead(HttpStatusCode.Ok, { - 'Content-Type': 'text/html', + 'Content-Type': 'text/html; charset=utf-8', }); response.write(html); response.end(); @@ -323,6 +325,25 @@ class EmailTestServer { return connection.collection('workspaces').findOne({ _id: new ObjectId(workspaceId) }); } + /** + * Calculate days after payday + * Return number of days after payday. If payday is in the future, return 0 + * + * @param workspace - workspace data + * @returns {Promise} number of days after payday + */ + private async calculateDaysAfterPayday( + workspace: WorkspaceDBScheme + ): Promise { + if (!workspace.lastChargeDate) { + return 0; + } + + const days = daysAfterPayday(workspace.lastChargeDate, workspace.paidUntil); + + return days > 0 ? days : 0; + } + /** * Get user info * diff --git a/workers/email/src/provider.ts b/workers/email/src/provider.ts index b8d0ce478..9ffaba12c 100644 --- a/workers/email/src/provider.ts +++ b/workers/email/src/provider.ts @@ -44,6 +44,7 @@ export default class EmailProvider extends NotificationsProvider { switch (notification.type) { case 'assignee': templateName = Templates.Assignee; break; case 'block-workspace': templateName = Templates.BlockWorkspace; break; + case 'blocked-workspace-reminder': templateName = Templates.BlockedWorkspaceReminder; break; case 'days-limit-almost-reached': templateName = Templates.DaysLimitAlmostReached; break; case 'event': templateName = Templates.Event; break; case 'events-limit-almost-reached': templateName = Templates.EventsLimitAlmostReached; break; diff --git a/workers/email/src/templates/emails/blocked-workspace-reminder/html.twig b/workers/email/src/templates/emails/blocked-workspace-reminder/html.twig new file mode 100644 index 000000000..80828db6f --- /dev/null +++ b/workers/email/src/templates/emails/blocked-workspace-reminder/html.twig @@ -0,0 +1,39 @@ +{% extends '../../components/layout.twig' %} + +{% block header %} + {% include '../../components/workspace.twig' with {workspace: workspace} %} +{% endblock %} + +{% block content %} + + + + + + + + + + {{ daysAfterPayday }} {{ pluralize_ru(daysAfterPayday, ['день', 'дня', 'дней']) }} без мониторинга + + + + + + + +

+ Напоминаем, что мониторинг ошибок в «{{ workspace.name | escape }}» всё ещё остановлен, потому что закончился лимит или срок действия тарифного плана. +

+

+ Чтобы снова видеть актуальные события, выберите подходящий тарифный план и продлите подписку в настройках оплаты. +

+
+ + + + + {% include '../../components/button.twig' with {href: host ~ '/workspace/' ~ workspace._id ~ '/settings/billing', label: workspace.tariffPlanId is same as('5f47f031ff71510040f433c1') ? 'Выбрать тариф от 99 ₽' : 'Открыть настройки'} %} + + +{% endblock %} diff --git a/workers/email/src/templates/emails/blocked-workspace-reminder/subject.twig b/workers/email/src/templates/emails/blocked-workspace-reminder/subject.twig new file mode 100644 index 000000000..e2c4b7a0a --- /dev/null +++ b/workers/email/src/templates/emails/blocked-workspace-reminder/subject.twig @@ -0,0 +1 @@ +Требуется действие: мониторинг ошибок в {{ workspace.name }} не работает уже {{ daysAfterPayday }} {{ pluralize_ru(daysAfterPayday, ['день', 'дня', 'дней']) }} \ No newline at end of file diff --git a/workers/email/src/templates/emails/blocked-workspace-reminder/text.twig b/workers/email/src/templates/emails/blocked-workspace-reminder/text.twig new file mode 100644 index 000000000..235015668 --- /dev/null +++ b/workers/email/src/templates/emails/blocked-workspace-reminder/text.twig @@ -0,0 +1,10 @@ +Требуется действие: мониторинг ошибок в {{ workspace.name }} не работает уже {{ daysAfterPayday }} {{ pluralize_ru(daysAfterPayday, ['день', 'дня', 'дней']) }} + +Чтобы снова видеть актуальные события, выберите подходящий тарифный план и продлите подписку в настройках оплаты: {{ host }}/workspace/{{ workspace._id }}/settings/billing + +*** + +Хоук +Российский трекер ошибок + +Made by CodeX \ No newline at end of file diff --git a/workers/email/src/templates/extensions.ts b/workers/email/src/templates/extensions.ts index a61ac35ff..2df6a526e 100644 --- a/workers/email/src/templates/extensions.ts +++ b/workers/email/src/templates/extensions.ts @@ -154,3 +154,29 @@ Twig.extendFilter('abbrNumber', (value: number): string => { Twig.extendFilter('sortEvents', (events: TemplateEventData[]): TemplateEventData[] => { return events.sort((a, b) => a.newCount - b.newCount); }); + +/** + * Pluralize Russian words based on a number + * + * @param {number} n - the number to determine the form + * @param {string[]} forms - array of word forms [singular, few, many] + * @returns {string} + */ +Twig.extendFunction('pluralize_ru', (n: number, forms: string[]): string => { + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + if (n % 100 >= 11 && n % 100 <= 19) { + return forms[2]; + } + + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + const last = n % 10; + + if (last === 1) { + return forms[0]; + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + } else if (last >= 2 && last <= 4) { + return forms[1]; + } else { + return forms[2]; + } +}); \ No newline at end of file diff --git a/workers/email/src/templates/names.ts b/workers/email/src/templates/names.ts index e01db2921..82983533f 100644 --- a/workers/email/src/templates/names.ts +++ b/workers/email/src/templates/names.ts @@ -4,6 +4,7 @@ enum Templates { Assignee = 'assignee', BlockWorkspace = 'block-workspace', + BlockedWorkspaceReminder = 'blocked-workspace-reminder', DaysLimitAlmostReached = 'days-limit-almost-reached', Event = 'event', EventsLimitAlmostReached = 'events-limit-almost-reached', diff --git a/workers/paymaster/src/index.ts b/workers/paymaster/src/index.ts index 0ce4d681e..a57b54c16 100644 --- a/workers/paymaster/src/index.ts +++ b/workers/paymaster/src/index.ts @@ -7,19 +7,14 @@ import { Collection } from 'mongodb'; import { PlanDBScheme, WorkspaceDBScheme } from '@hawk.so/types'; import { EventType, PaymasterEvent } from '../types/paymaster-worker-events'; import axios from 'axios'; -import { HOURS_IN_DAY, MINUTES_IN_HOUR, MS_IN_SEC, SECONDS_IN_MINUTE } from '../../../lib/utils/consts'; import * as WorkerNames from '../../../lib/workerNames'; import HawkCatcher from '@hawk.so/nodejs'; +import { daysBeforePayday, daysAfterPayday } from '../../../lib/utils/payday'; dotenv.config({ path: path.resolve(__dirname, '../.env'), }); -/** - * Milliseconds in day. Needs for calculating difference between dates in days. - */ -const MILLISECONDS_IN_DAY = HOURS_IN_DAY * MINUTES_IN_HOUR * SECONDS_IN_MINUTE * MS_IN_SEC; - /** * Days after payday to try paying in actual subscription * When days after payday is more than this const and we still @@ -33,6 +28,12 @@ const DAYS_AFTER_PAYDAY_TO_TRY_PAYING = 3; // eslint-disable-next-line @typescript-eslint/no-magic-numbers const DAYS_LEFT_ALERT = [3, 2, 1, 0]; +/** + * Days after payday to remind admins about blocked workspace + */ +// eslint-disable-next-line @typescript-eslint/no-magic-numbers +const DAYS_AFTER_PAYDAY_TO_REMIND = [1, 2, 3, 5, 7, 30]; + /** * Worker to check workspaces subscription status and ban workspaces without actual subscription */ @@ -103,52 +104,6 @@ export default class PaymasterWorker extends Worker { return endDate; } - /** - * Returns difference between now and payday in days - * - * Pay day is calculated by formula: paidUntil date or last charge date + 1 month - * - * @param date - last charge date - * @param paidUntil - paid until date - * @param isDebug - flag for debug purposes - */ - private static daysBeforePayday(date: Date, paidUntil: Date = null, isDebug = false): number { - const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date); - - if (isDebug) { - expectedPayDay.setDate(date.getDate() + 1); - } else if (!paidUntil) { - expectedPayDay.setMonth(date.getMonth() + 1); - } - - const now = new Date().getTime(); - - return Math.floor((expectedPayDay.getTime() - now) / MILLISECONDS_IN_DAY); - } - - /** - * Returns difference between payday and now in days - * - * Pay day is calculated by formula: paidUntil date or last charge date + 1 month - * - * @param date - last charge date - * @param paidUntil - paid until date - * @param isDebug - flag for debug purposes - */ - private static daysAfterPayday(date: Date, paidUntil: Date = null, isDebug = false): number { - const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date); - - if (isDebug) { - expectedPayDay.setDate(date.getDate() + 1); - } else if (!paidUntil) { - expectedPayDay.setMonth(date.getMonth() + 1); - } - - const now = new Date().getTime(); - - return Math.floor((now - expectedPayDay.getTime()) / MILLISECONDS_IN_DAY); - } - /** * Start consuming messages */ @@ -247,13 +202,13 @@ export default class PaymasterWorker extends Worker { * How many days have passed since payments the expected day of payments */ // @ts-expect-error debug - const daysAfterPayday = PaymasterWorker.daysAfterPayday(workspace.lastChargeDate, workspace.paidUntil, workspace.isDebug); + const daysAfterPaydayValue = daysAfterPayday(workspace.lastChargeDate, workspace.paidUntil, workspace.isDebug); /** * How many days left for the expected day of payments */ // @ts-expect-error debug - const daysLeft = PaymasterWorker.daysBeforePayday(workspace.lastChargeDate, workspace.paidUntil, workspace.isDebug); + const daysLeft = daysBeforePayday(workspace.lastChargeDate, workspace.paidUntil, workspace.isDebug); /** * Do we need to ask for money @@ -319,9 +274,15 @@ export default class PaymasterWorker extends Worker { /** * Time to pay but workspace has paid plan - * If it is blocked then do nothing + * If it is blocked then remind admins about it */ if (workspace.isBlocked) { + // Send reminders on certain days after payday + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + if (DAYS_AFTER_PAYDAY_TO_REMIND.includes(daysAfterPaydayValue)) { + await this.sendBlockedWorkspaceReminders(workspace, daysAfterPaydayValue); + } + return [workspace, true]; } @@ -338,7 +299,7 @@ export default class PaymasterWorker extends Worker { * Block workspace if it has paid subscription, * but a few days have passed after payday */ - if (daysAfterPayday > DAYS_AFTER_PAYDAY_TO_TRY_PAYING) { + if (daysAfterPaydayValue > DAYS_AFTER_PAYDAY_TO_TRY_PAYING) { await this.blockWorkspace(workspace); return [workspace, true]; @@ -403,6 +364,26 @@ export default class PaymasterWorker extends Worker { }); } + + /** + * Sends reminder emails to blocked workspace admins + * + * @param workspace - workspace to send reminders for + * @param days - number of days the workspace spent after payday + */ + private async sendBlockedWorkspaceReminders( + workspace: WorkspaceDBScheme, + days: number + ): Promise { + await this.addTask(WorkerNames.EMAIL, { + type: 'blocked-workspace-reminder', + payload: { + workspaceId: workspace._id.toString(), + daysAfterPayday: days, + }, + }); + } + /** * Sets BillingPeriodEventsCount to 0 in workspace * diff --git a/workers/paymaster/tests/index.test.ts b/workers/paymaster/tests/index.test.ts index 74f52e1f3..964f32e06 100644 --- a/workers/paymaster/tests/index.test.ts +++ b/workers/paymaster/tests/index.test.ts @@ -210,7 +210,7 @@ describe('PaymasterWorker', () => { /** * Arrange */ - const currentDate = new Date('2005-12-26'); + const currentDate = new Date('2005-12-27'); const plan = createPlanMock({ monthlyCharge: 100, isDefault: true, @@ -257,6 +257,101 @@ describe('PaymasterWorker', () => { MockDate.reset(); }); + /** + * Helper function to run blocked workspace reminder test + * + * @param lastChargeDate - date of last charge + * @param currentDate - current date to test + * @param shouldBeCalled - whether the reminder should be called + * @param expectedDaysAfterPayday - expected days after payday in the call + */ + const testBlockedWorkspaceReminder = async ( + lastChargeDate: Date, + currentDate: Date, + shouldBeCalled: boolean, + expectedDaysAfterPayday?: number + ): Promise => { + const plan = createPlanMock({ + monthlyCharge: 100, + isDefault: true, + }); + const workspace = createWorkspaceMock({ + plan, + subscriptionId: 'some-subscription-id', + lastChargeDate, + isBlocked: true, + billingPeriodEventsCount: 10, + }); + + await fillDatabaseWithMockedData({ + workspace, + plan, + }); + + MockDate.set(currentDate); + + const worker = new PaymasterWorker(); + const addTaskSpy = jest.spyOn(worker, 'addTask'); + + await worker.start(); + await worker.handle(WORKSPACE_SUBSCRIPTION_CHECK); + await worker.finish(); + + if (shouldBeCalled) { + expect(addTaskSpy).toHaveBeenCalledWith('sender/email', { + type: 'blocked-workspace-reminder', + payload: { + workspaceId: workspace._id.toString(), + daysAfterPayday: expectedDaysAfterPayday, + }, + }); + } else { + expect(addTaskSpy).not.toHaveBeenCalledWith('sender/email', expect.objectContaining({ + type: 'blocked-workspace-reminder', + })); + } + + MockDate.reset(); + return addTaskSpy; + }; + + describe('Blocked workspace reminder tests', () => { + test('Should remind admins for blocked workspace if it has subscription and after payday passed 1 day', async () => { + await testBlockedWorkspaceReminder( + new Date('2005-11-22'), + new Date('2005-12-23'), + true, + 1 + ); + }); + + test('Should remind admins for blocked workspace if it has subscription and after payday passed 5 days', async () => { + await testBlockedWorkspaceReminder( + new Date('2005-11-22'), + new Date('2005-12-27'), + true, + 5 + ); + }); + + test('Should remind admins for blocked workspace if it has subscription and after payday passed 30 days', async () => { + await testBlockedWorkspaceReminder( + new Date('2005-11-22'), + new Date('2006-01-21'), + true, + 30 + ); + }); + + test('Should not remind admins for blocked workspace on days not in reminder schedule (day 4)', async () => { + await testBlockedWorkspaceReminder( + new Date('2005-11-22'), + new Date('2005-12-26'), + false + ); + }); + }); + test('Should update lastChargeDate and billingPeriodEventsCount if workspace has free tariff plan and it\'s time to pay', async () => { /** * Arrange diff --git a/workers/sender/src/index.ts b/workers/sender/src/index.ts index a92c9d190..022f63f60 100644 --- a/workers/sender/src/index.ts +++ b/workers/sender/src/index.ts @@ -29,7 +29,8 @@ import { SenderWorkerEventsLimitAlmostReachedTask, SenderWorkerSignUpTask, SenderWorkerPasswordResetTask, - SenderWorkerWorkspaceInviteTask + SenderWorkerWorkspaceInviteTask, + SenderWorkerBlockedWorkspaceReminderTask } from '../types/sender-task'; import { decodeUnsafeFields } from '../../../lib/utils/unsafeFields'; import { Notification, EventNotification, SeveralEventsNotification, PaymentFailedNotification, AssigneeNotification, SignUpNotification } from '../types/template-variables'; @@ -115,6 +116,8 @@ export default abstract class SenderWorker extends Worker { return this.handleAssigneeTask(task as SenderWorkerAssigneeTask); case 'block-workspace': return this.handleBlockWorkspaceTask(task as SenderWorkerBlockWorkspaceTask); + case 'blocked-workspace-reminder': + return this.handleBlockedWorkspaceReminderTask(task as SenderWorkerBlockedWorkspaceReminderTask); case 'days-limit-almost-reached': return this.handleDaysLimitAlmostReachedTask(task as SenderWorkerDaysLimitAlmostReachedTask); case 'event': @@ -196,7 +199,7 @@ export default abstract class SenderWorker extends Worker { project, events: eventsData, period: channel.minPeriod, - notificationRuleId: rule._id, + notificationRuleId: rule._id.toString(), }, } as EventNotification | SeveralEventsNotification); } @@ -297,6 +300,63 @@ export default abstract class SenderWorker extends Worker { })); } + private async handleBlockedWorkspaceReminderTask(task: SenderWorkerBlockedWorkspaceReminderTask): Promise { + const eventType = 'blocked-workspace-reminder'; + + /** + * Send message not often than once per day + */ + const throttleInterval = TimeMs.DAY; + + const { workspaceId, daysAfterPayday } = task.payload; + + const workspace = await this.getWorkspace(workspaceId); + + if (!workspace) { + this.logger.error(`Cannot send blocked workspace reminder notification: workspace not found. Payload: ${JSON.stringify(task)}`); + + return; + } + + const allowToSendNotification = this.needToSendNextNotification(workspace, eventType, throttleInterval); + + /** + * Do not send any notifications if we have already done in target throttle time + */ + if (!allowToSendNotification) { + return; + } + + const admins = await this.getWorkspaceAdmins(workspaceId); + + if (!admins) { + this.logger.error(`Cannot send blocked workspace reminder notification: workspace team not found. Payload: ${JSON.stringify(task)}`); + + return; + } + + const adminIds = admins.map(admin => admin.userId.toString()); + const users = await this.getUsers(adminIds); + + await Promise.all(users.map(async user => { + const channel = user.notifications.channels[this.channelType]; + + if (channel.isEnabled) { + await this.provider.send(channel.endpoint, { + type: 'blocked-workspace-reminder', + payload: { + host: process.env.GARAGE_URL, + hostOfStatic: process.env.API_STATIC_URL, + workspace, + daysAfterPayday, + }, + }); + } + })); + + await this.updateLastNotificationDate(workspace, eventType); + } + /** * Handle task when days limit is almost reached * @@ -356,13 +416,10 @@ export default abstract class SenderWorker extends Worker { daysLeft, }, }); - - /** - * Update last notification data in DB - */ - await this.updateLastNoticationDate(workspace, eventType); } })); + + await this.updateLastNotificationDate(workspace, eventType); } /** @@ -422,10 +479,10 @@ export default abstract class SenderWorker extends Worker { eventsLimit, }, }); - - await this.updateLastNoticationDate(workspace, eventType); } })); + + await this.updateLastNotificationDate(workspace, eventType); } /** @@ -715,7 +772,7 @@ export default abstract class SenderWorker extends Worker { * @param {string} type - event type * @param {number} date - date to be set */ - private async updateLastNoticationDate(workspace: WorkspaceDBScheme, type: string, date = new Date()): Promise { + private async updateLastNotificationDate(workspace: WorkspaceDBScheme, type: string, date = new Date()): Promise { /** * Throw an error if workspace is missing */ diff --git a/workers/sender/types/sender-task/blockWorkspace.ts b/workers/sender/types/sender-task/block-workspace.ts similarity index 100% rename from workers/sender/types/sender-task/blockWorkspace.ts rename to workers/sender/types/sender-task/block-workspace.ts diff --git a/workers/sender/types/sender-task/blocked-workspace-reminder.ts b/workers/sender/types/sender-task/blocked-workspace-reminder.ts new file mode 100644 index 000000000..df502a67d --- /dev/null +++ b/workers/sender/types/sender-task/blocked-workspace-reminder.ts @@ -0,0 +1,29 @@ +/** + * Payload for task for blocked workspace reminder + */ +export interface SenderWorkerBlockedWorkspaceReminderPayload { + /** + * Blocked workspace id + */ + workspaceId: string; + + /** + * Days after payday + */ + daysAfterPayday: number; +} + +/** + * Payload of an event for blocked workspace reminder + */ +export interface SenderWorkerBlockedWorkspaceReminderTask { + /** + * Task for blocked workspace reminder + */ + type: 'blocked-workspace-reminder'; + + /** + * Payload for task for blocked workspace reminder + */ + payload: SenderWorkerBlockedWorkspaceReminderPayload; +} diff --git a/workers/sender/types/sender-task/index.ts b/workers/sender/types/sender-task/index.ts index fb9e89c87..5733e6681 100644 --- a/workers/sender/types/sender-task/index.ts +++ b/workers/sender/types/sender-task/index.ts @@ -1,6 +1,7 @@ import { SenderWorkerAssigneeTask } from './assignee'; import { SenderWorkerEventTask } from './event'; -import { SenderWorkerBlockWorkspaceTask } from './blockWorkspace'; +import { SenderWorkerBlockWorkspaceTask } from './block-workspace'; +import { SenderWorkerBlockedWorkspaceReminderTask } from './blocked-workspace-reminder'; import { SenderWorkerPaymentFailedTask } from './payment-failed'; import { SenderWorkerPaymentSuccessTask } from './payment-success'; import { SenderWorkerDaysLimitAlmostReachedTask } from './days-limit-almost-reached'; @@ -11,7 +12,8 @@ import { SenderWorkerWorkspaceInviteTask } from './workspace-invite'; export { SenderWorkerEventTask, SenderWorkerEventPayload } from './event'; export { SenderWorkerAssigneeTask, SenderWorkerAssigneePayload } from './assignee'; -export { SenderWorkerBlockWorkspaceTask, SenderWorkerBlockWorkspacePayload } from './blockWorkspace'; +export { SenderWorkerBlockWorkspaceTask, SenderWorkerBlockWorkspacePayload } from './block-workspace'; +export { SenderWorkerBlockedWorkspaceReminderTask, SenderWorkerBlockedWorkspaceReminderPayload } from './blocked-workspace-reminder'; export { SenderWorkerPaymentFailedTask, SenderWorkerPaymentFailedPayload } from './payment-failed'; export { SenderWorkerPaymentSuccessTask, SenderWorkerPaymentSuccessPayload } from './payment-success'; export { SenderWorkerDaysLimitAlmostReachedTask, SenderWorkerDaysLimitAlmostReachedPayload } from './days-limit-almost-reached'; @@ -23,6 +25,7 @@ export { SenderWorkerWorkspaceInviteTask, SenderWorkerWorkspaceInvitePayload } f export type SenderWorkerTask = SenderWorkerEventTask | SenderWorkerAssigneeTask | SenderWorkerBlockWorkspaceTask + | SenderWorkerBlockedWorkspaceReminderTask | SenderWorkerPaymentFailedTask | SenderWorkerPaymentSuccessTask | SenderWorkerDaysLimitAlmostReachedTask diff --git a/workers/sender/types/template-variables/blockWorkspace.ts b/workers/sender/types/template-variables/block-workspace.ts similarity index 100% rename from workers/sender/types/template-variables/blockWorkspace.ts rename to workers/sender/types/template-variables/block-workspace.ts diff --git a/workers/sender/types/template-variables/blocked-workspace-reminder.ts b/workers/sender/types/template-variables/blocked-workspace-reminder.ts new file mode 100644 index 000000000..9b3aaa96b --- /dev/null +++ b/workers/sender/types/template-variables/blocked-workspace-reminder.ts @@ -0,0 +1,33 @@ +import { CommonTemplateVariables } from './common-template'; +import { WorkspaceDBScheme } from '@hawk.so/types'; +import { Notification } from './notification'; + +/** + * Variables for block workspace template + */ +export interface BlockedWorkspaceReminderTemplateVariables extends CommonTemplateVariables { + /** + * Blocked workspace data + */ + workspace: WorkspaceDBScheme; + + /** + * Number of days after payday when workspace was blocked + */ + daysAfterPayday: number; +} + +/** + * Object with notification type and variables for the block workspace event template + */ +export interface BlockedWorkspaceReminderNotification extends Notification { + /** + * Notification when workspace blocked + */ + type: 'blocked-workspace-reminder'; + + /** + * Notification payload + */ + payload: BlockedWorkspaceReminderTemplateVariables; +} diff --git a/workers/sender/types/template-variables/index.ts b/workers/sender/types/template-variables/index.ts index 058ad34f3..864060b52 100644 --- a/workers/sender/types/template-variables/index.ts +++ b/workers/sender/types/template-variables/index.ts @@ -1,7 +1,8 @@ import { EventsTemplateVariables, EventNotification } from './event'; import { SeveralEventsNotification } from './several-events'; import { AssigneeTemplateVariables, AssigneeNotification } from './assignee'; -import { BlockWorkspaceTemplateVariables, BlockWorkspaceNotification } from './blockWorkspace'; +import { BlockWorkspaceTemplateVariables, BlockWorkspaceNotification } from './block-workspace'; +import { BlockedWorkspaceReminderTemplateVariables, BlockedWorkspaceReminderNotification } from './blocked-workspace-reminder'; import { PaymentFailedTemplateVariables, PaymentFailedNotification } from './payment-failed'; import { PaymentSuccessNotification, PaymentSuccessTemplateVariables } from './payment-success'; import { DaysLimitAlmostReachedTemplateVariables, DaysLimitAlmostReachedNotification } from './days-limit-almost-reached'; @@ -14,7 +15,8 @@ export { CommonTemplateVariables } from './common-template'; export { TemplateEventData, EventsTemplateVariables, EventNotification } from './event'; export { SeveralEventsNotification } from './several-events'; export { AssigneeTemplateVariables, AssigneeNotification } from './assignee'; -export { BlockWorkspaceTemplateVariables, BlockWorkspaceNotification } from './blockWorkspace'; +export { BlockWorkspaceTemplateVariables, BlockWorkspaceNotification } from './block-workspace'; +export { BlockedWorkspaceReminderTemplateVariables, BlockedWorkspaceReminderNotification } from './blocked-workspace-reminder'; export { PaymentFailedTemplateVariables, PaymentFailedNotification } from './payment-failed'; export { PaymentSuccessNotification, PaymentSuccessTemplateVariables } from './payment-success'; export { SignUpNotification, SignUpVariables } from './sign-up'; @@ -28,6 +30,7 @@ export type Notification = EventNotification | SeveralEventsNotification | AssigneeNotification | BlockWorkspaceNotification + | BlockedWorkspaceReminderNotification | PaymentFailedNotification | PaymentSuccessNotification | DaysLimitAlmostReachedNotification @@ -42,6 +45,7 @@ export type Notification = EventNotification export type TemplateVariables = EventsTemplateVariables | AssigneeTemplateVariables | BlockWorkspaceTemplateVariables + | BlockedWorkspaceReminderTemplateVariables | PaymentFailedTemplateVariables | PaymentSuccessTemplateVariables | DaysLimitAlmostReachedTemplateVariables