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
8 changes: 4 additions & 4 deletions apps/api-e2e/src/bff/example.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from 'axios';
import axios from 'axios'

test('GET /example', async () => {
const res = await axios.get('/example');
const res = await axios.get('/example')

expect(res.data).toEqual('this is an example');
});
expect(res.data).toEqual('this is an example')
})
57 changes: 23 additions & 34 deletions apps/api/src/app/affiliateProgramExportPoller.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,33 @@
import { apiContainer } from './inversify.config';
import { logger } from '@cowprotocol/shared';
import {
isCmsEnabled,
isDuneEnabled,
} from '@cowprotocol/repositories';
import { apiContainer } from './inversify.config'
import { logger } from '@cowprotocol/shared'
import { isCmsEnabled, isDuneEnabled } from '@cowprotocol/repositories'
import {
AffiliateProgramExportService,
AffiliateProgramSignature,
affiliateProgramExportServiceSymbol,
} from '@cowprotocol/services';
} from '@cowprotocol/services'

const POLL_INTERVAL_MS = 5 * 60 * 1000;
const POLL_INTERVAL_MS = 5 * 60 * 1000

let lastSignature: AffiliateProgramSignature | null = null;
let inFlight = false;
let lastSignature: AffiliateProgramSignature | null = null
let inFlight = false

export function startAffiliateProgramExportPoller():
| (() => void)
| undefined {
export function startAffiliateProgramExportPoller(): (() => void) | undefined {
if (!isCmsEnabled || !isDuneEnabled) {
logger.warn(
'Affiliate export poller disabled (CMS or Dune not enabled).'
);
return;
logger.warn('Affiliate export poller disabled (CMS or Dune not enabled).')
return
}

const run = async () => {
if (inFlight) {
return;
return
}

inFlight = true;
inFlight = true
try {
const exportService = apiContainer.get<AffiliateProgramExportService>(
affiliateProgramExportServiceSymbol
);
const result = await exportService.exportAffiliateProgramDataIfChanged(
lastSignature
);
lastSignature = result.result.signature;
const exportService = apiContainer.get<AffiliateProgramExportService>(affiliateProgramExportServiceSymbol)
const result = await exportService.exportAffiliateProgramDataIfChanged(lastSignature)
lastSignature = result.result.signature

if (result.uploaded) {
logger.info(
Expand All @@ -47,24 +36,24 @@ export function startAffiliateProgramExportPoller():
maxUpdatedAt: result.result.signature.maxUpdatedAt,
},
'Affiliate program export poller uploaded data'
);
)
} else {
logger.debug(
{
rows: result.result.rows,
maxUpdatedAt: result.result.signature.maxUpdatedAt,
},
'Affiliate program export poller skipped (no change)'
);
)
}
} catch (error) {
logger.error({ error }, 'Affiliate program export poller failed');
logger.error({ error }, 'Affiliate program export poller failed')
} finally {
inFlight = false;
inFlight = false
}
};
}

void run();
const intervalId = setInterval(run, POLL_INTERVAL_MS);
return () => clearInterval(intervalId);
void run()
const intervalId = setInterval(run, POLL_INTERVAL_MS)
return () => clearInterval(intervalId)
}
29 changes: 13 additions & 16 deletions apps/api/src/app/app.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import 'reflect-metadata';
import 'reflect-metadata'

import { join } from 'path';
import AutoLoad, { AutoloadPluginOptions } from '@fastify/autoload';
import { FastifyPluginAsync } from 'fastify';
import { join } from 'path'
import AutoLoad, { AutoloadPluginOptions } from '@fastify/autoload'
import { FastifyPluginAsync } from 'fastify'

export type AppOptions = {
// Place your custom options for app below here.
} & Partial<AutoloadPluginOptions>;
} & Partial<AutoloadPluginOptions>

// Pass --options via CLI arguments in command to enable these options.
const options: AppOptions = {};
const options: AppOptions = {}

const app: FastifyPluginAsync<AppOptions> = async (
fastify,
opts
): Promise<void> => {
const app: FastifyPluginAsync<AppOptions> = async (fastify, opts): Promise<void> => {
// Place here your custom code!
const appOpts = {
...opts,
prefix: '/',
};
}
// Do not touch the following lines

// This loads all plugins defined in plugins
Expand All @@ -28,16 +25,16 @@ const app: FastifyPluginAsync<AppOptions> = async (
void fastify.register(AutoLoad, {
dir: join(__dirname, 'plugins'),
options: appOpts,
});
})

// This loads all plugins defined in routes
// define your routes in one of these
void fastify.register(AutoLoad, {
dir: join(__dirname, 'routes'),
options: appOpts,
routeParams: true,
});
};
})
}

export default app;
export { app, options };
export default app
export { app, options }
2 changes: 1 addition & 1 deletion apps/api/src/app/config/affiliate.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const AFFILIATE_CODE_REGEX = /^[A-Z0-9_-]{5,20}$/;
export const AFFILIATE_CODE_REGEX = /^[A-Z0-9_-]{5,20}$/
22 changes: 9 additions & 13 deletions apps/api/src/app/data/poolInfo.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
import {
Column,
Entity,
PrimaryColumn
} from 'typeorm';
import { bufferToString, stringToBuffer } from '@cowprotocol/shared';
import { Column, Entity, PrimaryColumn } from 'typeorm'
import { bufferToString, stringToBuffer } from '@cowprotocol/shared'

@Entity({ name: 'cow_amm_competitor_info', schema: 'public' })
export class PoolInfo {
@PrimaryColumn('bytea', {
transformer: { from: bufferToString, to: stringToBuffer },
})
contract_address: string;
contract_address: string

@Column('int')
chain_id: number;
chain_id: number

@Column('varchar')
project: string;
project: string

@Column('double precision')
apr: number;
apr: number

@Column('double precision')
fee: number;
fee: number

@Column('double precision')
tvl: number;
tvl: number

@Column('double precision')
volume: number;
volume: number
}
85 changes: 38 additions & 47 deletions apps/api/src/app/inversify.config.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import 'reflect-metadata';
import { decorate, injectable } from 'inversify';
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 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 affiliateStatsServiceSymbol = symbol('AffiliateStatsService')
const duneRepositorySymbol = symbol('DuneRepository')

const getters = {
getAffiliatesRepository: getter(),
Expand All @@ -20,7 +20,7 @@ const getters = {
getTokenHolderRepository: getter(),
getUserBalanceRepository: getter(),
getUsdRepository: getter(),
};
}

const plainClasses = Object.fromEntries(
[
Expand Down Expand Up @@ -49,19 +49,16 @@ const plainClasses = Object.fromEntries(
'UserBalanceRepository',
'BalanceTrackingService',
].map((name) => [name, class {}])
);
)

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

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

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

Expand All @@ -88,20 +85,20 @@ const symbols = {
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,
Expand All @@ -116,42 +113,36 @@ jest.mock('@cowprotocol/services', () => ({
TokenDetailServiceMain: InjectableStub,
TokenHolderServiceMain: InjectableStub,
UsdServiceMain: InjectableStub,
}));
}))

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

beforeEach(() => {
MockAffiliateStatsServiceImpl.instances = [];
process.env.DUNE_AFFILIATE_STATS_CACHE_TTL_MS = '1234';
jest.resetModules();
});
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;
delete process.env.DUNE_AFFILIATE_STATS_CACHE_TTL_MS
return
}

process.env.DUNE_AFFILIATE_STATS_CACHE_TTL_MS = originalTtl;
});
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
);
});
});
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)
})
})
Loading
Loading