Skip to content
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
10 changes: 9 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nx"],
"extends": ["prettier"],
"plugins": ["@nx", "prettier"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"prettier/prettier": "warn",
"@nx/enforce-module-boundaries": [
"error",
{
Expand All @@ -26,6 +28,12 @@
"extends": ["plugin:@nx/typescript"],
"rules": {}
},
{
"files": ["libs/repositories/src/gen/**/*.ts"],
"rules": {
"prettier/prettier": "off"
}
},
{
"files": ["*.js", "*.jsx"],
"extends": ["plugin:@nx/javascript"],
Expand Down
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Add files here to ignore them from prettier formatting
/dist
/coverage
/coverage
/libs/repositories/src/gen
4 changes: 3 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"singleQuote": true
"semi": false,
"singleQuote": true,
"printWidth": 120
}
157 changes: 157 additions & 0 deletions apps/api/src/app/inversify.config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import 'reflect-metadata';
import { decorate, injectable } from 'inversify';

const symbol = (name: string): symbol => Symbol.for(name);
const emptyObject = (): Record<string, never> => ({});
const getter = () => jest.fn(emptyObject);

const affiliateStatsServiceSymbol = symbol('AffiliateStatsService');
const duneRepositorySymbol = symbol('DuneRepository');

const getters = {
getAffiliatesRepository: getter(),
getCacheRepository: getter(),
getDuneRepository: jest.fn(() => ({ getQueryResults: jest.fn() })),
getErc20Repository: getter(),
getPushNotificationsRepository: getter(),
getPushSubscriptionsRepository: getter(),
getSimulationRepository: getter(),
getTokenBalancesRepository: getter(),
getTokenHolderRepository: getter(),
getUserBalanceRepository: getter(),
getUsdRepository: getter(),
};

const plainClasses = Object.fromEntries(
[
'AffiliatesRepository',
'AffiliateProgramExportService',
'AffiliateProgramExportServiceImpl',
'AffiliateStatsService',
'CacheRepository',
'DuneRepository',
'Erc20Repository',
'HooksService',
'HooksServiceImpl',
'Logger',
'PushNotificationsRepository',
'PushSubscriptionsRepository',
'SimulationRepository',
'SlippageService',
'SSEService',
'TokenBalancesRepository',
'TokenBalancesService',
'TokenDetailService',
'TokenHolderRepository',
'TokenHolderService',
'UsdRepository',
'UsdService',
'UserBalanceRepository',
'BalanceTrackingService',
].map((name) => [name, class {}])
);

class InjectableStub {}
decorate(injectable(), InjectableStub);

class MockAffiliateStatsServiceImpl {
static instances: MockAffiliateStatsServiceImpl[] = [];

constructor(
public readonly duneRepository: unknown,
public readonly cacheTtlMs: number
) {
MockAffiliateStatsServiceImpl.instances.push(this);
}
}

const symbols = {
affiliatesRepositorySymbol: symbol('AffiliatesRepository'),
affiliateProgramExportServiceSymbol: symbol('AffiliateProgramExportService'),
affiliateStatsServiceSymbol,
balanceTrackingServiceSymbol: symbol('BalanceTrackingService'),
cacheRepositorySymbol: symbol('CacheRepository'),
duneRepositorySymbol,
erc20RepositorySymbol: symbol('Erc20Repository'),
hooksServiceSymbol: symbol('HooksService'),
pushNotificationsRepositorySymbol: symbol('PushNotificationsRepository'),
pushSubscriptionsRepositorySymbol: symbol('PushSubscriptionsRepository'),
simulationServiceSymbol: symbol('SimulationService'),
slippageServiceSymbol: symbol('SlippageService'),
sseServiceSymbol: symbol('SSEService'),
tenderlyRepositorySymbol: symbol('SimulationRepository'),
tokenBalancesRepositorySymbol: symbol('TokenBalancesRepository'),
tokenBalancesServiceSymbol: symbol('TokenBalancesService'),
tokenDetailServiceSymbol: symbol('TokenDetailService'),
tokenHolderRepositorySymbol: symbol('TokenHolderRepository'),
tokenHolderServiceSymbol: symbol('TokenHolderService'),
usdRepositorySymbol: symbol('UsdRepository'),
usdServiceSymbol: symbol('UsdService'),
userBalanceRepositorySymbol: symbol('UserBalanceRepository'),
};

jest.mock('@cowprotocol/repositories', () => ({
...plainClasses,
...symbols,
...getters,
isCmsEnabled: false,
isDuneEnabled: true,
}));

jest.mock('@cowprotocol/shared', () => ({
Logger: plainClasses.Logger,
logger: { warn: jest.fn() },
}));

jest.mock('@cowprotocol/services', () => ({
...plainClasses,
...symbols,
...getters,
AffiliateStatsServiceImpl: MockAffiliateStatsServiceImpl,
BalanceTrackingServiceMain: InjectableStub,
SSEServiceMain: InjectableStub,
SimulationService: InjectableStub,
SlippageServiceMain: InjectableStub,
TokenBalancesServiceMain: InjectableStub,
TokenDetailServiceMain: InjectableStub,
TokenHolderServiceMain: InjectableStub,
UsdServiceMain: InjectableStub,
}));

describe('getApiContainer', () => {
const originalTtl = process.env.DUNE_AFFILIATE_STATS_CACHE_TTL_MS;

beforeEach(() => {
MockAffiliateStatsServiceImpl.instances = [];
process.env.DUNE_AFFILIATE_STATS_CACHE_TTL_MS = '1234';
jest.resetModules();
});

afterAll(() => {
if (originalTtl === undefined) {
delete process.env.DUNE_AFFILIATE_STATS_CACHE_TTL_MS;
return;
}

process.env.DUNE_AFFILIATE_STATS_CACHE_TTL_MS = originalTtl;
});

it('reuses the same affiliate stats service instance', async () => {
const { getApiContainer } = await import('./inversify.config');

const container = getApiContainer();
const first = container.get<MockAffiliateStatsServiceImpl>(
affiliateStatsServiceSymbol
);
const second = container.get<MockAffiliateStatsServiceImpl>(
affiliateStatsServiceSymbol
);

expect(first).toBe(second);
expect(MockAffiliateStatsServiceImpl.instances).toHaveLength(1);
expect(first.cacheTtlMs).toBe(1234);
expect(first.duneRepository).toBe(
getters.getDuneRepository.mock.results.at(-1)?.value
);
});
});
7 changes: 4 additions & 3 deletions apps/api/src/app/inversify.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ import {
import { Container } from 'inversify';
import { Logger, logger } from '@cowprotocol/shared';

const DEFAULT_AFFILIATE_STATS_CACHE_TTL_MS = 3600000;
const DEFAULT_AFFILIATE_STATS_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes

function getAffiliateStatsCacheTtlMs(): number {
const rawValue = process.env.DUNE_AFFILIATE_STATS_CACHE_TTL_MS;
Expand All @@ -93,7 +93,7 @@ function getAffiliateStatsCacheTtlMs(): number {
return parsed;
}

function getApiContainer(): Container {
export function getApiContainer(): Container {
const apiContainer = new Container();

// Bind logger
Expand Down Expand Up @@ -163,7 +163,8 @@ function getApiContainer(): Container {
duneRepository,
affiliateStatsCacheTtlMs
)
);
)
.inSingletonScope();
}

if (isDuneEnabled && isCmsEnabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const affiliateStats: FastifyPluginAsync = async (fastify): Promise<void> => {
lastUpdatedAt: result.lastUpdatedAt,
});
} catch (error) {
fastify.log.error('Error fetching affiliate stats:', error);
fastify.log.error({ err: error }, 'Error fetching affiliate stats');
return reply.status(500).send({ message: 'Unexpected error' });
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const traderStats: FastifyPluginAsync = async (fastify): Promise<void> => {
lastUpdatedAt: result.lastUpdatedAt,
});
} catch (error) {
fastify.log.error('Error fetching affiliate trader stats:', error);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not urgent, but if both the previous version and the new one are correct, but behave differently, maybe we should create a util function so that we don't make this mistake again?

fastify.log.error({ err: error }, 'Error fetching affiliate trader stats');
return reply.status(500).send({ message: 'Unexpected error' });
}
}
Expand Down
5 changes: 3 additions & 2 deletions nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"inputs": [
"default",
"{workspaceRoot}/.eslintrc.json",
"{workspaceRoot}/.eslintignore"
"{workspaceRoot}/.eslintignore",
"{workspaceRoot}/.prettierrc"
]
}
},
Expand All @@ -56,4 +57,4 @@
"appsDir": "apps",
"libsDir": "libs"
}
}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@cowprotocol/root",
"name": "cowswap-bff",
"description": "Backend for frontend is a series of backend services and libraries that enhance user experience for the frontend",
"version": "0.28.0",
"license": "MIT",
Expand All @@ -17,6 +17,7 @@
"build": "nx run-many --all --target=build",
"test": "nx run-many --all --target=test",
"lint": "nx run-many --all --target=lint",
"lint:fix": "nx run-many --all --target=lint --fix --skip-nx-cache",
"new:fastify": "nx generate @nx/node:application --framework=fastify --docker --directory=apps",
"new:node": "nx generate @nx/node:application --directory=apps --docker",
"new:lib": "nx g @nx/node:library --directory=libs",
Expand Down Expand Up @@ -98,6 +99,7 @@
"esbuild": "^0.17.17",
"eslint": "~8.15.0",
"eslint-config-prettier": "8.1.0",
"eslint-plugin-prettier": "^4.2.1",
"fastify-tsconfig": "^1.0.1",
"jest": "^29.5.0",
"jest-environment-node": "^29.4.1",
Expand Down
19 changes: 19 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4875,6 +4875,13 @@ eslint-config-prettier@8.1.0:
resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.1.0.tgz"
integrity sha512-oKMhGv3ihGbCIimCAjqkdzx2Q+jthoqnXSP+d86M9tptwugycmTFdVR4IpLgq2c4SHifbwO90z2fQ8/Aio73yw==

eslint-plugin-prettier@^4.2.1:
version "4.2.5"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.5.tgz#91ca3f2f01a84f1272cce04e9717550494c0fe06"
integrity sha512-9Ni+xgemM2IWLq6aXEpP2+V/V30GeA/46Ar629vcMqVPodFFWC9skHu/D1phvuqtS8bJCFnNf01/qcmqYEwNfg==
dependencies:
prettier-linter-helpers "^1.0.0"

eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz"
Expand Down Expand Up @@ -5130,6 +5137,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==

fast-diff@^1.1.2:
version "1.3.0"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==

fast-glob@3.2.7:
version "3.2.7"
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz"
Expand Down Expand Up @@ -8013,6 +8025,13 @@ prelude-ls@^1.2.1:
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==

prettier-linter-helpers@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz#6a31f88a4bad6c7adda253de12ba4edaea80ebcd"
integrity sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==
dependencies:
fast-diff "^1.1.2"

prettier@^2.3.1, prettier@^2.6.2:
version "2.8.8"
resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz"
Expand Down
Loading