Skip to content

Commit 0bf70da

Browse files
talygurynCopilotneSpecc
authored
Fix: save workspace blocked date (#491)
* pull new hawk types * Track blocked date for workspaces in LimiterWorker Adds a 'blockedDate' property to workspaces when blocking and unblocking them. This allows tracking when a workspace was blocked or cleared, improving auditability and state management. * Add and populate blockedDate for blocked workspaces Introduces the blockedDate field to workspace updates in dbHelper and ensures it is set for all blocked workspaces, including a temporary fix for existing records missing this field. This change supports better tracking of when a workspace was blocked and prepares for future removal of the migration code. * Add tests for blockedDate handling in workspaces Extended tests to cover setting and clearing the blockedDate field when blocking and unblocking workspaces. Added scenarios for updating blockedDate when missing and verifying correct behavior during workspace state transitions. * Update index.ts * Refactor blocked workspace reminder to use days after block Renamed variables and template placeholders from 'daysAfterPayday' to 'daysAfterBlock' to more accurately reflect the time since a workspace was blocked. Updated related logic, types, and templates to use the new naming and calculation. Added a utility function to compute days after block in payday.ts. * Update yarn.lock to deduplicate and reorder entries This commit cleans up the yarn.lock file by removing duplicate entries, consolidating package references, and reordering dependencies for consistency. No package versions were changed; this improves maintainability and reduces lockfile bloat. * Rename daysAfterPayday to daysAfterBlock in payload Updated variable and property names from daysAfterPayday to daysAfterBlock in SenderWorker to reflect the correct payload structure and improve clarity. * Handle undefined daysAfterBlock in reminders logic Updated countDaysAfterBlock to return undefined instead of null when blockedDate is missing. Adjusted paymaster worker logic to check for undefined, ensuring reminders are sent only when daysAfterBlock is valid. * Adjust blockedDate calculation in PaymasterWorker test Updates the blockedDate assignment to subtract days based on expectedDaysAfterBlock in the PaymasterWorker test, improving test accuracy for blocked subscriptions. * Update workers/paymaster/tests/index.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update workers/paymaster/src/index.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update lib/utils/payday.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update dbHelper.test.ts * Remove obsolete test and update blocked workspace logic Deleted a test for setting blockedDate on already blocked workspaces in limiter tests, as the scenario is no longer relevant. Updated workspace mock to include blockedDate as undefined. Simplified reminder logic in PaymasterWorker by removing unnecessary undefined check for daysAfterBlock. * Refactor blockedDate handling in workspace mocks Removed redundant logic for setting blockedDate when workspace is already blocked in LimiterWorker. Standardized blockedDate type to Date (not Date | null/undefined) in test mocks and updated test cases to use null instead of undefined where appropriate. * Update index.test.ts * Update dbHelper.test.ts * Update index.test.ts * Update index.test.ts * Update workers/paymaster/tests/index.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update index.test.ts * Update workers/paymaster/src/index.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update lib/utils/payday.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update workers/paymaster/src/index.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update emailOverview.ts * Update workers/limiter/src/index.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update lib/utils/payday.ts Co-authored-by: Peter <specc.dev@gmail.com> * Update index.test.ts * Update index.test.ts * Update index.ts --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Peter <specc.dev@gmail.com>
1 parent 60a29b6 commit 0bf70da

16 files changed

Lines changed: 1208 additions & 1411 deletions

File tree

lib/utils/payday.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { WorkspaceDBScheme } from '@hawk.so/types';
12
import { HOURS_IN_DAY, MINUTES_IN_HOUR, SECONDS_IN_MINUTE, MS_IN_SEC } from './consts';
23

34
/**
@@ -14,7 +15,7 @@ const MILLISECONDS_IN_DAY = HOURS_IN_DAY * MINUTES_IN_HOUR * SECONDS_IN_MINUTE *
1415
* @param paidUntil - paid until date
1516
* @param isDebug - flag for debug purposes
1617
*/
17-
export function daysBeforePayday(date: Date, paidUntil: Date = null, isDebug = false): number {
18+
export function countDaysBeforePayday(date: Date, paidUntil: Date = null, isDebug = false): number {
1819
const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date);
1920

2021
if (isDebug) {
@@ -37,7 +38,7 @@ export function daysBeforePayday(date: Date, paidUntil: Date = null, isDebug = f
3738
* @param paidUntil - paid until date
3839
* @param isDebug - flag for debug purposes
3940
*/
40-
export function daysAfterPayday(date: Date, paidUntil: Date = null, isDebug = false): number {
41+
export function countDaysAfterPayday(date: Date, paidUntil: Date = null, isDebug = false): number {
4142
const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date);
4243

4344
if (isDebug) {
@@ -49,4 +50,21 @@ export function daysAfterPayday(date: Date, paidUntil: Date = null, isDebug = fa
4950
const now = new Date().getTime();
5051

5152
return Math.floor((now - expectedPayDay.getTime()) / MILLISECONDS_IN_DAY);
53+
}
54+
55+
/**
56+
* Returns difference between day when workspace was blocked and now in days. Undefined for workspaces blocked before the "blockedDate" implemented.
57+
*
58+
* @param workspace - workspace object
59+
*/
60+
export function countDaysAfterBlock(workspace: WorkspaceDBScheme): number | undefined {
61+
if (!workspace.blockedDate) {
62+
return undefined;
63+
}
64+
65+
const blockedDay = new Date(workspace.blockedDate);
66+
67+
const now = new Date().getTime();
68+
69+
return Math.floor((now - blockedDay.getTime()) / MILLISECONDS_IN_DAY);
5270
}

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@
5050
"run-limiter": "yarn worker hawk-worker-limiter"
5151
},
5252
"dependencies": {
53+
"@babel/parser": "^7.26.9",
54+
"@babel/traverse": "7.26.9",
5355
"@hawk.so/nodejs": "^3.1.1",
54-
"@hawk.so/types": "^0.1.35",
56+
"@hawk.so/types": "^0.2.0",
5557
"@types/amqplib": "^0.8.2",
5658
"@types/jest": "^29.2.3",
5759
"@types/mongodb": "^3.5.15",
@@ -73,9 +75,7 @@
7375
"typescript": "^4.9.4",
7476
"uuid": "^8.3.0",
7577
"winston": "^3.2.1",
76-
"yup": "^0.28.5",
77-
"@babel/parser": "^7.26.9",
78-
"@babel/traverse": "7.26.9"
78+
"yup": "^0.28.5"
7979
},
8080
"devDependencies": {
8181
"@shelf/jest-mongodb": "^1.2.3",

workers/email/scripts/emailOverview.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { ObjectId } from 'mongodb';
1919
import * as path from 'path';
2020
import * as dotenv from 'dotenv';
2121
import { HttpStatusCode } from '../../../lib/utils/consts';
22-
import { daysAfterPayday } from '../../../lib/utils/payday';
22+
import { countDaysAfterPayday } from '../../../lib/utils/payday';
2323

2424
/**
2525
* Merge email worker .env and root workers .env
@@ -148,7 +148,7 @@ class EmailTestServer {
148148
user,
149149
period: 10,
150150
reason: 'error on the payment server side',
151-
daysAfterPayday: await this.calculateDaysAfterPayday(workspace),
151+
daysAfterPayday: countDaysAfterPayday(workspace.lastChargeDate, workspace.paidUntil),
152152
};
153153

154154
try {
@@ -339,7 +339,7 @@ class EmailTestServer {
339339
return 0;
340340
}
341341

342-
const days = daysAfterPayday(workspace.lastChargeDate, workspace.paidUntil);
342+
const days = countDaysAfterPayday(workspace.lastChargeDate, workspace.paidUntil);
343343

344344
return days > 0 ? days : 0;
345345
}

workers/email/src/templates/emails/blocked-workspace-reminder/html.twig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<td align="center" style="padding: 15px 0;">
1515
<font color="#dbe6ff" style="font-size: 15px; text-align: center; color: #dbe6ff; letter-spacing: 0.4px;">
1616
<span style="vertical-align: middle; display: inline-block;">
17-
{{ daysAfterPayday }} {{ pluralize_ru(daysAfterPayday, ['день', 'дня', 'дней']) }} без мониторинга
17+
{{ daysAfterBlock }} {{ pluralize_ru(daysAfterBlock, ['день', 'дня', 'дней']) }} без мониторинга
1818
</span>
1919
</font>
2020
</td>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Требуется действие: мониторинг ошибок в {{ workspace.name }} не работает уже {{ daysAfterPayday }} {{ pluralize_ru(daysAfterPayday, ['день', 'дня', 'дней']) }}
1+
Требуется действие: мониторинг ошибок в {{ workspace.name }} не работает уже {{ daysAfterBlock }} {{ pluralize_ru(daysAfterBlock, ['день', 'дня', 'дней']) }}

workers/email/src/templates/emails/blocked-workspace-reminder/text.twig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Требуется действие: мониторинг ошибок в {{ workspace.name }} не работает уже {{ daysAfterPayday }} {{ pluralize_ru(daysAfterPayday, ['день', 'дня', 'дней']) }}
1+
Требуется действие: мониторинг ошибок в {{ workspace.name }} не работает уже {{ daysAfterBlock }} {{ pluralize_ru(daysAfterBlock, ['день', 'дня', 'дней']) }}
22

33
Чтобы снова видеть актуальные события, выберите подходящий тарифный план и продлите подписку в настройках оплаты: {{ host }}/workspace/{{ workspace._id }}/settings/billing
44

workers/limiter/src/dbHelper.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export class DbHelper {
100100
$set: {
101101
billingPeriodEventsCount: workspace.billingPeriodEventsCount,
102102
isBlocked: workspace.isBlocked,
103+
blockedDate: workspace.blockedDate,
103104
},
104105
},
105106
},

workers/limiter/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ export default class LimiterWorker extends Worker {
131131
const { updatedWorkspace } = await this.prepareWorkspaceUsageUpdate(workspace, workspaceProjects);
132132

133133
updatedWorkspace.isBlocked = true;
134+
updatedWorkspace.blockedDate = new Date();
134135
await this.dbHelper.updateWorkspacesEventsCountAndIsBlocked([ updatedWorkspace ]);
135136

136137
this.logger.info('workspace blocked in db ', event.workspaceId);
@@ -174,6 +175,7 @@ export default class LimiterWorker extends Worker {
174175
}
175176

176177
updatedWorkspace.isBlocked = false;
178+
updatedWorkspace.blockedDate = null;
177179

178180
await this.dbHelper.updateWorkspacesEventsCountAndIsBlocked([ updatedWorkspace ]);
179181
await this.redis.removeBannedProjects(projectIds);
@@ -218,6 +220,9 @@ export default class LimiterWorker extends Worker {
218220
if (shouldBeBlockedByQuota) {
219221
const projectIds = projectsToUpdate.map(project => project._id.toString());
220222

223+
updatedWorkspace.isBlocked = true;
224+
updatedWorkspace.blockedDate = new Date();
225+
221226
this.redis.appendBannedProjects(projectIds);
222227
message += this.formSingleWorkspaceMessage(updatedWorkspace, projectsToUpdate, 'blocked');
223228
}

workers/limiter/tests/dbHelper.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ describe('DbHelper', () => {
2828
billingPeriodEventsCount: number;
2929
lastChargeDate: Date;
3030
isBlocked?: boolean;
31+
blockedDate?: Date;
3132
}): WorkspaceDBScheme => {
3233
return {
3334
_id: new ObjectId(),
@@ -39,6 +40,7 @@ describe('DbHelper', () => {
3940
accountId: '',
4041
balance: 0,
4142
isBlocked: parameters.isBlocked,
43+
blockedDate: parameters.blockedDate,
4244
};
4345
};
4446

@@ -213,17 +215,20 @@ describe('DbHelper', () => {
213215

214216
await workspaceCollection.insertMany([workspace1, workspace2]);
215217

218+
const blockedDate = new Date();
216219
const updatedWorkspaces = [
217220
{
218221
...workspace1,
219222
billingPeriodEventsCount: 5,
220223
isBlocked: true,
224+
blockedDate: blockedDate,
221225
tariffPlan: mockedPlans.eventsLimit10,
222226
},
223227
{
224228
...workspace2,
225229
billingPeriodEventsCount: 5000,
226230
isBlocked: true,
231+
blockedDate: blockedDate,
227232
tariffPlan: mockedPlans.eventsLimit10000,
228233
},
229234
];
@@ -241,8 +246,10 @@ describe('DbHelper', () => {
241246

242247
expect(updatedWorkspace1.billingPeriodEventsCount).toBe(5);
243248
expect(updatedWorkspace1.isBlocked).toBe(true);
249+
expect(updatedWorkspace1.blockedDate).toEqual(blockedDate);
244250
expect(updatedWorkspace2.billingPeriodEventsCount).toBe(5000);
245251
expect(updatedWorkspace2.isBlocked).toBe(true);
252+
expect(updatedWorkspace2.blockedDate).toEqual(blockedDate);
246253
});
247254

248255
test('Should not update anything if empty array provided', async () => {
@@ -254,6 +261,7 @@ describe('DbHelper', () => {
254261
billingPeriodEventsCount: 0,
255262
lastChargeDate: new Date(),
256263
isBlocked: false,
264+
blockedDate: null,
257265
});
258266

259267
await workspaceCollection.insertOne(workspace);
@@ -270,6 +278,42 @@ describe('DbHelper', () => {
270278

271279
expect(unchangedWorkspace).toEqual(workspace);
272280
});
281+
282+
test('Should set blockedDate to null when unblocking workspace', async () => {
283+
/**
284+
* Arrange
285+
*/
286+
const blockedDate = new Date();
287+
const workspace = createWorkspaceMock({
288+
plan: mockedPlans.eventsLimit10,
289+
billingPeriodEventsCount: 0,
290+
lastChargeDate: new Date(),
291+
isBlocked: true,
292+
blockedDate: blockedDate,
293+
});
294+
295+
await workspaceCollection.insertOne(workspace);
296+
297+
const updatedWorkspace = {
298+
...workspace,
299+
isBlocked: false,
300+
blockedDate: null,
301+
tariffPlan: mockedPlans.eventsLimit10,
302+
};
303+
304+
/**
305+
* Act
306+
*/
307+
await dbHelper.updateWorkspacesEventsCountAndIsBlocked([updatedWorkspace]);
308+
309+
/**
310+
* Assert
311+
*/
312+
const result = await workspaceCollection.findOne({ _id: workspace._id });
313+
314+
expect(result.isBlocked).toBe(false);
315+
expect(result.blockedDate).toBeNull();
316+
});
273317
});
274318

275319
describe('getEventsCountByProject', () => {

workers/limiter/tests/index.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ describe('Limiter worker', () => {
4545
billingPeriodEventsCount: number;
4646
lastChargeDate: Date;
4747
isBlocked?: boolean;
48+
blockedDate?: Date;
4849
}): WorkspaceDBScheme => {
4950
return {
5051
_id: new ObjectId(),
@@ -56,6 +57,7 @@ describe('Limiter worker', () => {
5657
accountId: '',
5758
balance: 0,
5859
isBlocked: parameters.isBlocked,
60+
blockedDate: parameters.blockedDate,
5961
};
6062
};
6163

@@ -391,6 +393,7 @@ describe('Limiter worker', () => {
391393
const blockedProjects = await redisClient.sMembers('DisabledProjectsSet');
392394

393395
expect(updatedWorkspace.isBlocked).toBe(true);
396+
expect(updatedWorkspace.blockedDate).toBeInstanceOf(Date);
394397
expect(blockedProjects).toContain(project._id.toString());
395398
expect(telegram.sendMessage).toHaveBeenCalledWith(
396399
expect.stringContaining('⛔️ Workspace <b>Mocked workspace</b> blocked <b>(id: <code>'),
@@ -416,6 +419,7 @@ describe('Limiter worker', () => {
416419
billingPeriodEventsCount: 0,
417420
lastChargeDate: LAST_CHARGE_DATE,
418421
isBlocked: true,
422+
blockedDate: new Date(),
419423
});
420424
const project = createProjectMock({ workspaceId: workspace._id });
421425

@@ -449,6 +453,7 @@ describe('Limiter worker', () => {
449453
const blockedProjects = await redisClient.sMembers('DisabledProjectsSet');
450454

451455
expect(updatedWorkspace.isBlocked).toBe(false);
456+
expect(updatedWorkspace.blockedDate).toBeNull();
452457
expect(blockedProjects).not.toContain(project._id.toString());
453458
expect(telegram.sendMessage).toHaveBeenCalledWith(
454459
expect.stringContaining('✅ Workspace <b>Mocked workspace</b> unblocked <b>(id: <code>'),

0 commit comments

Comments
 (0)