Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update prod #337

Merged
merged 3 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ ecosystem.config.js
node_modules
/workers/release/tests/mock
/workers/migrations
/workers/sentry/playground
.github
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ RUN yarn install
COPY runner.ts tsconfig.json ./
COPY lib/ ./lib/

RUN yarn tsc
RUN yarn build
25 changes: 23 additions & 2 deletions jest.setup.redis-mock.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
const { GenericContainer } = require('testcontainers');

let redisTestContainer;

/**
* Mock redis library
* Create test container with Redis, which could be used in tests
*/
jest.mock('redis', () => jest.requireActual('redis-mock'));
beforeAll(async () => {
redisTestContainer = await new GenericContainer('redis')
.withExposedPorts(6379)
.start();

const port = redisTestContainer.getMappedPort(6379);
const host = redisTestContainer.getContainerIpAddress();

/**
* Set environment variable for redisHelper to connect to redis container
*/
process.env.REDIS_URL = `redis://${host}:${port}`;
}
);

afterAll(async () => {
await redisTestContainer.stop();
});
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"workers/*"
],
"scripts": {
"build": "tsc",
"worker": "ts-node -T ./runner.ts",
"migration-add": "migrate-mongo create",
"migrate:up": "migrate-mongo up",
Expand Down Expand Up @@ -66,16 +67,17 @@
"typescript": "^3.8.3",
"uuid": "^8.3.0",
"winston": "^3.2.1",
"yup": "^0.28.5"
"yup": "^0.28.5",
"redis": "^4.7.0"
},
"devDependencies": {
"@shelf/jest-mongodb": "^1.2.3",
"testcontainers": "^3.0.0",
"eslint": "^7.14.0",
"eslint-config-codex": "^1.6.1",
"jest": "25.5.4",
"nodemon": "^2.0.3",
"random-words": "^1.1.1",
"redis-mock": "^0.56.3",
"ts-jest": "25.4.0",
"wait-for-expect": "^3.0.2",
"webpack": "^4.43.0",
Expand Down
14 changes: 12 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"lib": ["ES2020"], /* Specify library files to be included in the compilation. */
"allowJs": true, /* Allow javascript files to be compiled. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
Expand Down Expand Up @@ -64,5 +64,15 @@
/* Advanced Options */
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
"resolveJsonModule": true
}
},
"exclude": [
"node_modules",
"dist",
"bin",
"migrations",
"tools",
"coverage",
"workers/sentry/playground",
"tests"
]
}
3 changes: 1 addition & 2 deletions workers/grouper/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"workerType": "grouper",
"dependencies": {
"@types/redis": "^2.8.28",
"js-levenshtein": "^1.1.6",
"redis": "^3.1.1"
"js-levenshtein": "^1.1.6"
}
}
3 changes: 3 additions & 0 deletions workers/grouper/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export default class GrouperWorker extends Worker {
public async start(): Promise<void> {
await this.db.connect();
this.prepareCache();
await this.redis.initialize();

await super.start();
}

Expand All @@ -61,6 +63,7 @@ export default class GrouperWorker extends Worker {
await super.finish();
this.prepareCache();
await this.db.close();
await this.redis.close();
}

/**
Expand Down
57 changes: 50 additions & 7 deletions workers/grouper/src/redisHelper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import HawkCatcher from '@hawk.so/nodejs';
import redis from 'redis';
import { createClient, RedisClientType } from 'redis';
import createLogger from '../../../lib/logger';

/**
Expand All @@ -14,27 +14,70 @@
/**
* Redis client for making queries
*/
private readonly redisClient = redis.createClient({ url: process.env.REDIS_URL });
private readonly redisClient: RedisClientType;

/**
* Logger instance
* (default level='info')
*/
private logger = createLogger();

/**
* Constructor of the Redis helper class
* Initializes the Redis client and sets up error handling
*/
constructor() {
this.redisClient = createClient({ url: process.env.REDIS_URL });

this.redisClient.on('error', (error) => {
if (error) {
this.logger.error(error);
HawkCatcher.send(error);
}
});
}

/**
* Connect to redis client
*/
public async initialize(): Promise<void> {
try {
await this.redisClient.connect();
} catch (error) {
console.error('Error connecting to redis', error);
}
}

/**
* Close redis client
*/
public async close(): Promise<void> {
if (this.redisClient.isOpen) {
await this.redisClient.quit();
}
}

/**
* Checks if a lock exists on the given group hash and identifier pair. If it does not exist, creates a lock.
* Returns true if lock exists
*
* @param groupHash - event group hash
* @param userId - event user id
*/
public checkOrSetEventLock(groupHash: string, userId: string): Promise<boolean> {
return new Promise((resolve, reject) => {
const callback = this.createCallback(resolve, reject);
public async checkOrSetEventLock(groupHash: string, userId: string): Promise<boolean> {
const result = await this.redisClient.set(
`${groupHash}:${userId}`,
'1',
{
EX: RedisHelper.LOCK_TTL,
NX: true,
} as const
);

this.redisClient.set(`${groupHash}:${userId}`, '1', 'EX', RedisHelper.LOCK_TTL, 'NX', callback);
});
/**
* Result would be null if lock already exists, false otherwise
*/
return result === null;
}

/**
Expand All @@ -43,7 +86,7 @@
* @param resolve - callback that will be called if no errors occurred
* @param reject - callback that will be called any error occurred
*/
private createCallback(resolve: (result: boolean) => void, reject: (reason?: unknown) => void) {

Check warning on line 89 in workers/grouper/src/redisHelper.ts

View workflow job for this annotation

GitHub Actions / ESlint

Method 'createCallback' is declared but its value is never read. Allowed unused names must match /^_/
return (execError: Error | null, resp: string): void => {
if (execError) {
this.logger.error(execError);
Expand Down
18 changes: 12 additions & 6 deletions workers/grouper/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import '../../../env-test';
import GrouperWorker from '../src';
import { GroupWorkerTask } from '../types/group-worker-task';
import redis from 'redis';
import { createClient, RedisClientType } from 'redis';
import { Collection, MongoClient } from 'mongodb';
import { EventAddons, EventDataAccepted } from '@hawk.so/types';

Expand Down Expand Up @@ -74,14 +74,16 @@ function generateTask(event: Partial<EventDataAccepted<EventAddons>> = undefined
}

describe('GrouperWorker', () => {
const worker = new GrouperWorker();
let connection: MongoClient;
let eventsCollection: Collection;
let dailyEventsCollection: Collection;
let repetitionsCollection: Collection;
let redisClient;
let redisClient: RedisClientType;
let worker: GrouperWorker;

beforeAll(async () => {
worker = new GrouperWorker();

await worker.start();
connection = await MongoClient.connect(process.env.MONGO_EVENTS_DATABASE_URI, {
useNewUrlParser: true,
Expand All @@ -90,7 +92,10 @@ describe('GrouperWorker', () => {
eventsCollection = connection.db().collection('events:' + projectIdMock);
dailyEventsCollection = connection.db().collection('dailyEvents:' + projectIdMock);
repetitionsCollection = connection.db().collection('repetitions:' + projectIdMock);
redisClient = redis.createClient({ url: process.env.REDIS_URL });

redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();

jest.resetAllMocks();
});

Expand All @@ -104,8 +109,8 @@ describe('GrouperWorker', () => {
await repetitionsCollection.deleteMany({});
});

afterEach((done) => {
redisClient.flushall(done);
afterEach(async () => {
await redisClient.flushAll();
});

describe('Saving events', () => {
Expand Down Expand Up @@ -299,6 +304,7 @@ describe('GrouperWorker', () => {
});

afterAll(async () => {
await redisClient.quit();
await worker.finish();
await connection.close();
});
Expand Down
3 changes: 1 addition & 2 deletions workers/limiter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"workerType": "cron-tasks/limiter",
"dependencies": {
"@types/redis": "^2.8.28",
"axios": "^0.21.2",
"redis": "^3.1.1"
"axios": "^0.21.2"
}
}
4 changes: 4 additions & 0 deletions workers/limiter/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ export default class LimiterWorker extends Worker {

this.projectsCollection = accountDbConnection.collection<ProjectDBScheme>('projects');
this.workspacesCollection = accountDbConnection.collection<WorkspaceDBScheme>('workspaces');

await this.redis.initialize();

await super.start();
}

Expand All @@ -91,6 +94,7 @@ export default class LimiterWorker extends Worker {
await super.finish();
await this.eventsDb.close();
await this.accountsDb.close();
await this.redis.close();
}

/**
Expand Down
66 changes: 57 additions & 9 deletions workers/limiter/src/redisHelper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import HawkCatcher from '@hawk.so/nodejs';
import redis from 'redis';
import { createClient, RedisClientType } from 'redis';
import createLogger from '../../../lib/logger';

/**
Expand All @@ -9,7 +9,8 @@ export default class RedisHelper {
/**
* Redis client for making queries
*/
private readonly redisClient = redis.createClient({ url: process.env.REDIS_URL });
// private readonly redisClient = redis.createClient({ url: process.env.REDIS_URL });
private readonly redisClient: RedisClientType;

/**
* Logger instance
Expand All @@ -22,6 +23,35 @@ export default class RedisHelper {
*/
private readonly redisDisabledProjectsKey = 'DisabledProjectsSet';

/**
* Constructor of the Redis helper class
* Initializes the Redis client and sets up error handling
*/
constructor() {
this.redisClient = createClient({ url: process.env.REDIS_URL });

this.redisClient.on('error', (error) => {
this.logger.error(error);
HawkCatcher.send(error);
});
}

/**
* Connect to redis client
*/
public async initialize(): Promise<void> {
await this.redisClient.connect();
}

/**
* Close redis client
*/
public async close(): Promise<void> {
if (this.redisClient.isOpen) {
await this.redisClient.quit();
}
}

/**
* Saves banned project ids to redis
* If there is no projects, then previous data in Redis will be erased
Expand All @@ -33,12 +63,26 @@ export default class RedisHelper {
const callback = this.createCallback(resolve, reject);

if (projectIdsToBan.length) {
this.redisClient.multi()
.del(this.redisDisabledProjectsKey)
.sadd(this.redisDisabledProjectsKey, projectIdsToBan)
.exec(callback);
const pipeline = this.redisClient.multi();

pipeline.del(this.redisDisabledProjectsKey);

pipeline.sAdd(this.redisDisabledProjectsKey, projectIdsToBan);

try {
pipeline.exec();
callback(null);
} catch (err) {
callback(err);
}
} else {
this.redisClient.del(this.redisDisabledProjectsKey, callback);
this.redisClient.del(this.redisDisabledProjectsKey)
.then(() => {
callback(null);
})
.catch((err) => {
callback(err);
});
}
});
}
Expand All @@ -53,7 +97,9 @@ export default class RedisHelper {
const callback = this.createCallback(resolve, reject);

if (projectIds.length) {
this.redisClient.sadd(this.redisDisabledProjectsKey, projectIds, callback);
this.redisClient.sAdd(this.redisDisabledProjectsKey, projectIds)
.then(() => callback(null))
.catch((err) => callback(err));
} else {
resolve();
}
Expand All @@ -70,7 +116,9 @@ export default class RedisHelper {
const callback = this.createCallback(resolve, reject);

if (projectIds.length) {
this.redisClient.srem(this.redisDisabledProjectsKey, projectIds, callback);
this.redisClient.sRem(this.redisDisabledProjectsKey, projectIds)
.then(() => callback(null))
.catch((err) => callback(err));
} else {
resolve();
}
Expand Down
Loading
Loading