Skip to content

Commit 6e92e18

Browse files
authored
Merge branch 'master' into feat/openai
2 parents 58a335e + b295da1 commit 6e92e18

21 files changed

+608
-31
lines changed

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hawk.api",
3-
"version": "1.2.23",
3+
"version": "1.2.26",
44
"main": "index.ts",
55
"license": "BUSL-1.1",
66
"scripts": {
@@ -28,6 +28,7 @@
2828
"jest": "^26.2.2",
2929
"mongodb-memory-server": "^6.6.1",
3030
"nodemon": "^2.0.2",
31+
"redis-mock": "^0.56.3",
3132
"ts-jest": "^26.1.4",
3233
"ts-node": "^10.9.1",
3334
"typescript": "^4.7.4"
@@ -39,7 +40,7 @@
3940
"@graphql-tools/schema": "^8.5.1",
4041
"@graphql-tools/utils": "^8.9.0",
4142
"@hawk.so/nodejs": "^3.1.1",
42-
"@hawk.so/types": "^0.1.33",
43+
"@hawk.so/types": "^0.1.37",
4344
"@n1ru4l/json-patch-plus": "^0.2.0",
4445
"@types/amqp-connection-manager": "^2.0.4",
4546
"@types/bson": "^4.0.5",
@@ -83,6 +84,7 @@
8384
"mongodb": "^3.7.3",
8485
"morgan": "^1.10.1",
8586
"prom-client": "^15.1.3",
87+
"redis": "^4.7.0",
8688
"safe-regex": "^2.1.0",
8789
"ts-node-dev": "^2.0.0",
8890
"uuid": "^8.3.2",

src/index.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { graphqlUploadExpress } from 'graphql-upload';
2929
import { metricsMiddleware, createMetricsServer, graphqlMetricsPlugin } from './metrics';
3030
import { requestLogger } from './utils/logger';
3131
import ReleasesFactory from './models/releasesFactory';
32+
import RedisHelper from './redisHelper';
3233

3334
/**
3435
* Option to enable playground
@@ -148,6 +149,7 @@ class HawkAPI {
148149
/**
149150
* Creates factories to work with models
150151
* @param dataLoaders - dataLoaders for fetching data form database
152+
* @returns factories object
151153
*/
152154
private static setupFactories(dataLoaders: DataLoaders): ContextFactories {
153155
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -212,23 +214,9 @@ class HawkAPI {
212214

213215
/**
214216
* Initializing accounting SDK
215-
*/
216-
let tlsVerify;
217-
218-
/**
219217
* Checking env variables
220218
* If at least one path is not transmitted, the variable tlsVerify is undefined
221219
*/
222-
if (
223-
![process.env.TLS_CA_CERT, process.env.TLS_CERT, process.env.TLS_KEY].some(value => value === undefined || value.length === 0)
224-
) {
225-
tlsVerify = {
226-
tlsCaCertPath: `${process.env.TLS_CA_CERT}`,
227-
tlsCertPath: `${process.env.TLS_CERT}`,
228-
tlsKeyPath: `${process.env.TLS_KEY}`,
229-
};
230-
}
231-
232220
/*
233221
* const accounting = new Accounting({
234222
* baseURL: `${process.env.CODEX_ACCOUNTING_URL}`,
@@ -252,6 +240,12 @@ class HawkAPI {
252240
public async start(): Promise<void> {
253241
await mongo.setupConnections();
254242
await rabbitmq.setupConnections();
243+
244+
// Initialize Redis singleton with auto-reconnect
245+
const redis = RedisHelper.getInstance();
246+
247+
await redis.initialize();
248+
255249
await this.server.start();
256250
this.app.use(graphqlUploadExpress());
257251
this.server.applyMiddleware({ app: this.app });

src/models/eventsFactory.js

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { getMidnightWithTimezoneOffset, getUTCMidnight } from '../utils/dates';
22
import safe from 'safe-regex';
33
import { createProjectEventsByIdLoader } from '../dataLoaders';
4+
import RedisHelper from '../redisHelper';
5+
import ChartDataService from '../services/chartDataService';
46

57
const Factory = require('./modelFactory');
68
const mongo = require('../mongo');
@@ -85,11 +87,21 @@ class EventsFactory extends Factory {
8587

8688
/**
8789
* Creates Event instance
88-
* @param {ObjectId} projectId - project ID
90+
* @param {ObjectId} projectId
8991
*/
9092
constructor(projectId) {
9193
super();
9294

95+
/**
96+
* Redis helper instance (singleton)
97+
*/
98+
this.redis = RedisHelper.getInstance();
99+
100+
/**
101+
* Chart data service for fetching data from Redis TimeSeries
102+
*/
103+
this.chartDataService = new ChartDataService(this.redis);
104+
93105
if (!projectId) {
94106
throw new Error('Can not construct Event model, because projectId is not provided');
95107
}
@@ -280,6 +292,12 @@ class EventsFactory extends Factory {
280292
const searchFilter = search.trim().length > 0
281293
? {
282294
$or: [
295+
{
296+
'repetition.delta': {
297+
$regex: escapedSearch,
298+
$options: 'i',
299+
},
300+
},
283301
{
284302
'event.payload.title': {
285303
$regex: escapedSearch,
@@ -414,6 +432,57 @@ class EventsFactory extends Factory {
414432
};
415433
}
416434

435+
/**
436+
* Get project chart data from Redis or fallback to MongoDB
437+
*
438+
* @param {string} projectId - project ID
439+
* @param {string} startDate - start date (ISO string)
440+
* @param {string} endDate - end date (ISO string)
441+
* @param {number} groupBy - grouping interval in minutes (1=minute, 60=hour, 1440=day)
442+
* @param {number} timezoneOffset - user's local timezone offset in minutes
443+
* @returns {Promise<Array>}
444+
*/
445+
async getProjectChartData(projectId, startDate, endDate, groupBy = 60, timezoneOffset = 0) {
446+
// Calculate days for MongoDB fallback
447+
const start = new Date(startDate).getTime();
448+
const end = new Date(endDate).getTime();
449+
const days = Math.ceil((end - start) / (24 * 60 * 60 * 1000));
450+
451+
try {
452+
const redisData = await this.chartDataService.getProjectChartData(
453+
projectId,
454+
startDate,
455+
endDate,
456+
groupBy,
457+
timezoneOffset
458+
);
459+
460+
if (redisData && redisData.length > 0) {
461+
return redisData;
462+
}
463+
464+
// Fallback to Mongo (empty groupHash for project-level data)
465+
return this.findChartData(days, timezoneOffset, '');
466+
} catch (err) {
467+
console.error('[EventsFactory] getProjectChartData error:', err);
468+
469+
// Fallback to Mongo on error (empty groupHash for project-level data)
470+
return this.findChartData(days, timezoneOffset, '');
471+
}
472+
}
473+
474+
/**
475+
* Get event daily chart data from MongoDB only
476+
*
477+
* @param {string} groupHash - event's group hash
478+
* @param {number} days - how many days to fetch
479+
* @param {number} timezoneOffset - user's local timezone offset in minutes
480+
* @returns {Promise<Array>}
481+
*/
482+
async getEventDailyChart(groupHash, days, timezoneOffset = 0) {
483+
return this.findChartData(days, timezoneOffset, groupHash);
484+
}
485+
417486
/**
418487
* Fetch timestamps and total count of errors (or target error) for each day since
419488
*
@@ -528,7 +597,6 @@ class EventsFactory extends Factory {
528597
/**
529598
* Returns Event repetitions
530599
*
531-
* @param {string|ObjectID} eventId - Event's id, could be repetitionId in case when we want to get repetitions portion by one repetition
532600
* @param {string|ObjectID} originalEventId - id of the original event
533601
* @param {Number} limit - count limitations
534602
* @param {Number} cursor - pointer to the next repetition

src/models/notify.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ class Notify {
9090
endpoint: 'slack',
9191
minPeriod: 60,
9292
},
93+
loop: {
94+
isEnabled: true,
95+
endpoint: 'loop',
96+
minPeriod: 60,
97+
},
9398
telegram: {
9499
isEnabled: true,
95100
endpoint: 'telegram',

src/rabbitmq.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export enum Queues {
2727
Email = 'sender/email',
2828
Telegram = 'notify/telegram',
2929
Slack = 'notify/slack',
30+
Loop = 'notify/loop',
3031
Limiter = 'cron-tasks/limiter',
3132
}
3233

@@ -81,6 +82,14 @@ export const WorkerPaths: Record<string, WorkerPath> = {
8182
queue: Queues.Slack,
8283
},
8384

85+
/**
86+
* Path to loop worker
87+
*/
88+
Loop: {
89+
exchange: Exchanges.Notify,
90+
queue: Queues.Loop,
91+
},
92+
8493
/**
8594
* Path to limiter worker
8695
*/

0 commit comments

Comments
 (0)