Skip to content

Commit 91bc772

Browse files
authored
Merge pull request #626 from charityagbenu12-cmd/somzilla_Issues
Implement somzilla_Issues: clean up duplicates, fix bugs, add balance…
2 parents 2f1b964 + 13611fc commit 91bc772

9 files changed

Lines changed: 59 additions & 159 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,4 @@ src/generated/prisma/
6464

6565
# Personal notes
6666
vrickish.md
67+
somzilla.md

src/app.module.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ import { KeyManagementModule } from './key-management/key-management.module';
2323
import { BalanceIndexerModule } from './balance-indexer/balance-indexer.module';
2424
import { WebhookModule } from './webhooks/webhook.module';
2525
import { TransactionsModule } from './transactions/transactions.module';
26-
import { HealthModule } from './health/health.module';
27-
2826
import { DevelopersModule } from './developers/developers.module';
2927
import { ProjectsModule } from './projects/projects.module';
3028
import { HealthModule } from './health/health.module';

src/balance-indexer/balance-indexer.controller.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,17 @@ export class BalanceIndexerController {
142142
@Query(ValidationPipe) pagination: PaginationDto,
143143
@Query(ValidationPipe) filters: BalanceFilterDto,
144144
) {
145-
if (assetType) {
145+
if (filters.assetType) {
146146
const asset: Asset = {
147-
type: (assetType as AssetType) || AssetType.NATIVE,
148-
code: assetCode,
149-
issuer: assetIssuer,
147+
type: filters.assetType,
148+
code: filters.assetCode,
149+
issuer: filters.assetIssuer,
150150
};
151151
const balance = await this.balanceIndexerService.getBalance(
152152
walletId,
153153
asset,
154154
);
155-
return balance ?? { balance: '0', assetType, assetCode, assetIssuer };
155+
return balance ?? { balance: '0', assetType: filters.assetType, assetCode: filters.assetCode, assetIssuer: filters.assetIssuer };
156156
}
157157

158158
const balances = await this.balanceIndexerService.getAllBalances(walletId);
@@ -357,8 +357,7 @@ export class BalanceIndexerController {
357357
* - When a mismatch is found: updates the index, increments
358358
* `reconciliationAttempts`, and emits a `balance.mismatch` webhook event.
359359
* - When balances match: clears any prior `mismatchDetectedAt` timestamp.
360-
return await this.balanceIndexerService.syncWalletBalances(request);
361-
}
360+
*/
362361

363362
/**
364363
* Manually triggers a full balance sync across all active wallets.
@@ -469,8 +468,7 @@ export class BalanceIndexerController {
469468
* - Emits `balance.mismatch` events for every divergence found.
470469
* - Recommended: protect this endpoint with an admin-level API key scope
471470
* in a future iteration.
472-
return await this.balanceIndexerService.reconcileBalance(walletId, asset);
473-
}
471+
*/
474472

475473
/**
476474
* Reconciles all balances for all active wallets.

src/balance-indexer/balance-indexer.service.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,7 +1049,6 @@ export class BalanceIndexerService implements OnModuleInit, OnModuleDestroy {
10491049
}
10501050

10511051
private isBalanceStale(balance: WalletBalance): boolean {
1052-
private isBalanceStale(balance: any): boolean {
10531052
if (!balance.lastSyncedAt) return true;
10541053
return Date.now() - balance.lastSyncedAt.getTime() > this.staleThresholdMs;
10551054
}
@@ -1060,12 +1059,6 @@ export class BalanceIndexerService implements OnModuleInit, OnModuleDestroy {
10601059

10611060
private calculateDifference(a: string, b: string): string {
10621061
return (parseFloat(a) - parseFloat(b)).toFixed(7);
1063-
private assetsMatch(asset1: Asset, asset2: Asset): boolean {
1064-
return (
1065-
asset1.type === asset2.type &&
1066-
asset1.code === asset2.code &&
1067-
asset1.issuer === asset2.issuer
1068-
);
10691062
}
10701063

10711064
private assetCompoundKey(walletId: string, asset: Asset) {
@@ -1077,10 +1070,6 @@ export class BalanceIndexerService implements OnModuleInit, OnModuleDestroy {
10771070
} as any;
10781071
}
10791072

1080-
private calculateDifference(balance1: string, balance2: string): string {
1081-
return (parseFloat(balance1) - parseFloat(balance2)).toFixed(7);
1082-
}
1083-
10841073
private balanceEventKey(event: BalanceChangeEvent): string {
10851074
const assetKey = [
10861075
event.asset.type,

src/balance-indexer/dto/balance-filter.dto.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,13 @@ export class BalanceFilterDto {
2121
@IsString({ message: 'assetCode must be a string' })
2222
@IsOptional()
2323
assetCode?: string;
24+
25+
@ApiProperty({
26+
example: 'GBUQWP3BOUZX34ZONKXRBTLNNDOWR5HLCVPL2B4XNCLJTLMUMLTSOGBM',
27+
description: 'Filter by asset issuer',
28+
required: false,
29+
})
30+
@IsString({ message: 'assetIssuer must be a string' })
31+
@IsOptional()
32+
assetIssuer?: string;
2433
}

src/balance-indexer/stellar-horizon.service.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ export class StellarHorizonService {
8181
250,
8282
);
8383

84-
this.logger.log(`Initialized Stellar Horizon client: ${this.horizonUrl}`);
8584
this.server = new Server(horizonUrl, { allowHttp: false });
8685
this.circuitBreaker = new CircuitBreaker('stellar-horizon', {
8786
failureThreshold: this.configService.get<number>(
@@ -208,8 +207,6 @@ export class StellarHorizonService {
208207
const requestId = this.requestContext.getRequestId();
209208
const logPrefix = requestId ? `[${requestId}] ` : '';
210209
try {
211-
await this.withRetry(
212-
() => this.mockHorizonRequest(publicKey),
213210
await this.executeWithRetry(
214211
() => this.server.loadAccount(publicKey),
215212
`accountExists(${publicKey.substring(0, 8)}...)`,

src/webhooks/webhook.controller.ts

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
Query,
1010
HttpCode,
1111
HttpStatus,
12-
BadRequestException,
1312
UseGuards,
1413
} from '@nestjs/common';
1514
import {
@@ -22,9 +21,6 @@ import {
2221
} from '@nestjs/swagger';
2322
import { WebhookService } from './webhook.service';
2423
import { WebhookDispatcherService } from './webhook-dispatcher.service';
25-
import { DeliveryStatus } from './domain/webhook-events';
26-
27-
const MAX_DELIVERIES_LIMIT = 200;
2824
import { CreateWebhookEndpointDto } from './dto/create-webhook-endpoint.dto';
2925
import { UpdateWebhookEndpointDto } from './dto/update-webhook-endpoint.dto';
3026
import { FeatureFlagGuard } from '../common/feature-flags/feature-flag.guard';
@@ -402,16 +398,6 @@ export class WebhookController {
402398
@Get('endpoints/:id/deliveries')
403399
async getDeliveries(
404400
@Param('id') id: string,
405-
@Query('limit') limit?: string,
406-
@Query('status') status?: string,
407-
) {
408-
const deliveryLimit = this.parseLimit(limit);
409-
const deliveryStatus = this.parseStatus(status);
410-
411-
const deliveries = await this.webhookService.getDeliveries(
412-
id,
413-
deliveryLimit,
414-
deliveryStatus,
415401
@Query('page') page?: string,
416402
@Query('limit') limit?: string,
417403
) {
@@ -535,43 +521,4 @@ export class WebhookController {
535521
};
536522
}
537523

538-
/**
539-
* Parses and validates the `limit` query param for delivery history
540-
*/
541-
private parseLimit(limit?: string): number {
542-
if (limit === undefined) {
543-
return 50;
544-
}
545-
546-
const parsed = Number(limit);
547-
548-
if (!Number.isInteger(parsed) || parsed < 1 || parsed > MAX_DELIVERIES_LIMIT) {
549-
throw new BadRequestException(
550-
`limit must be an integer between 1 and ${MAX_DELIVERIES_LIMIT}`,
551-
);
552-
}
553-
554-
return parsed;
555-
}
556-
557-
/**
558-
* Parses and validates the `status` query param for delivery history
559-
*/
560-
private parseStatus(status?: string): DeliveryStatus | undefined {
561-
if (status === undefined) {
562-
return undefined;
563-
}
564-
565-
const normalized = status.toUpperCase();
566-
567-
if (
568-
!Object.values(DeliveryStatus).includes(normalized as DeliveryStatus)
569-
) {
570-
throw new BadRequestException(
571-
`status must be one of: ${Object.values(DeliveryStatus).join(', ')}`,
572-
);
573-
}
574-
575-
return normalized as DeliveryStatus;
576-
}
577524
}

src/webhooks/webhook.service.spec.ts

Lines changed: 7 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
22
import { NotFoundException } from '@nestjs/common';
33
import { WebhookService } from './webhook.service';
44
import { PrismaService } from '../prisma/prisma.service';
5-
import { CacheService } from '../common/cache/cache.service';
6-
import { RequestContextService } from '../common/request-context/request-context.service';
75
import { EndpointStatus } from './domain/webhook-events';
8-
import { WEBHOOK_ENDPOINT_CACHE_PREFIX } from './webhook.service';
96

107
const PROJECT_ID = 'project-1';
118
const ENDPOINT_ID = 'endpoint-1';
@@ -52,14 +49,11 @@ describe('WebhookService', () => {
5249
const module: TestingModule = await Test.createTestingModule({
5350
providers: [
5451
WebhookService,
55-
CacheService,
56-
RequestContextService,
5752
{ provide: PrismaService, useValue: mockPrisma },
5853
],
5954
}).compile();
6055

6156
service = module.get<WebhookService>(WebhookService);
62-
cache = module.get<CacheService>(CacheService);
6357
});
6458

6559
it('should be defined', () => {
@@ -188,25 +182,13 @@ describe('WebhookService', () => {
188182
expect(result.id).toBe(ENDPOINT_ID);
189183
});
190184

191-
it('returns cached endpoint on second call without hitting the database', async () => {
185+
it('hits the database each time', async () => {
192186
mockPrisma.webhookEndpoint.findUnique.mockResolvedValue(mockEndpoint);
193187

194188
await service.getEndpoint(ENDPOINT_ID);
195189
await service.getEndpoint(ENDPOINT_ID);
196190

197-
expect(mockPrisma.webhookEndpoint.findUnique).toHaveBeenCalledTimes(1);
198-
expect(
199-
cache.get(`${WEBHOOK_ENDPOINT_CACHE_PREFIX}${ENDPOINT_ID}`),
200-
).toBeTruthy();
201-
});
202-
203-
it('falls through to database on cache miss', async () => {
204-
mockPrisma.webhookEndpoint.findUnique.mockResolvedValue(mockEndpoint);
205-
206-
expect(cache.get(`${WEBHOOK_ENDPOINT_CACHE_PREFIX}${ENDPOINT_ID}`)).toBeNull();
207-
await service.getEndpoint(ENDPOINT_ID);
208-
209-
expect(mockPrisma.webhookEndpoint.findUnique).toHaveBeenCalledTimes(1);
191+
expect(mockPrisma.webhookEndpoint.findUnique).toHaveBeenCalledTimes(2);
210192
});
211193

212194
it('throws NotFoundException when endpoint not found', async () => {
@@ -231,22 +213,6 @@ describe('WebhookService', () => {
231213

232214
expect(result.url).toBe('https://new.example.com/hook');
233215
});
234-
235-
it('invalidates cache after update', async () => {
236-
mockPrisma.webhookEndpoint.findUnique.mockResolvedValue(mockEndpoint);
237-
mockPrisma.webhookEndpoint.update.mockResolvedValue(mockEndpoint);
238-
239-
await service.getEndpoint(ENDPOINT_ID);
240-
expect(
241-
cache.get(`${WEBHOOK_ENDPOINT_CACHE_PREFIX}${ENDPOINT_ID}`),
242-
).toBeTruthy();
243-
244-
await service.updateEndpoint(ENDPOINT_ID, { description: 'updated' });
245-
246-
expect(
247-
cache.get(`${WEBHOOK_ENDPOINT_CACHE_PREFIX}${ENDPOINT_ID}`),
248-
).toBeNull();
249-
});
250216
});
251217

252218
// ─── deleteEndpoint ──────────────────────────────────────────────────────────
@@ -261,18 +227,6 @@ describe('WebhookService', () => {
261227
where: { id: ENDPOINT_ID },
262228
});
263229
});
264-
265-
it('invalidates cache after delete', async () => {
266-
mockPrisma.webhookEndpoint.findUnique.mockResolvedValue(mockEndpoint);
267-
mockPrisma.webhookEndpoint.delete.mockResolvedValue(mockEndpoint);
268-
269-
await service.getEndpoint(ENDPOINT_ID);
270-
await service.deleteEndpoint(ENDPOINT_ID);
271-
272-
expect(
273-
cache.get(`${WEBHOOK_ENDPOINT_CACHE_PREFIX}${ENDPOINT_ID}`),
274-
).toBeNull();
275-
});
276230
});
277231

278232
// ─── rotateSecret ─────────────────────────────────────────────────────────────
@@ -296,20 +250,22 @@ describe('WebhookService', () => {
296250

297251
describe('getDeliveries', () => {
298252
it('returns paginated deliveries with default page and limit', async () => {
253+
mockPrisma.webhookEndpoint.findUnique.mockResolvedValue(mockEndpoint);
299254
mockPrisma.webhookDelivery.findMany.mockResolvedValue([]);
300255
mockPrisma.webhookDelivery.count.mockResolvedValue(0);
301256

302257
const result = await service.getDeliveries(ENDPOINT_ID);
303258

304259
expect(mockPrisma.webhookDelivery.findMany).toHaveBeenCalledWith(
305-
expect.objectContaining({ skip: 0, take: 20 }),
260+
expect.objectContaining({ skip: 0, take: 50 }),
306261
);
307262
expect(result.page).toBe(1);
308-
expect(result.limit).toBe(20);
263+
expect(result.limit).toBe(50);
309264
expect(result.total).toBe(0);
310265
});
311266

312267
it('respects custom page and limit', async () => {
268+
mockPrisma.webhookEndpoint.findUnique.mockResolvedValue(mockEndpoint);
313269
mockPrisma.webhookDelivery.findMany.mockResolvedValue([]);
314270
mockPrisma.webhookDelivery.count.mockResolvedValue(50);
315271

@@ -324,6 +280,7 @@ describe('WebhookService', () => {
324280
});
325281

326282
it('returns deliveries in the response', async () => {
283+
mockPrisma.webhookEndpoint.findUnique.mockResolvedValue(mockEndpoint);
327284
const delivery = {
328285
id: 'delivery-1',
329286
endpointId: ENDPOINT_ID,

0 commit comments

Comments
 (0)