Skip to content

Commit ac4c7a0

Browse files
Chore/891 eliminate any usage (#994)
* fix(auth): eliminate any type usage in auth.service.ts Replace 8 any annotations with Prisma-derived and explicit types: - transactionsToActivityItems: TransactionWithPropertyTitle[] - handleTokenReuse: Prisma.BlacklistedToken - document/apiKey/passwordHistory map callbacks: Prisma model types - recaptcha response: new RecaptchaVerifyResponse interface Part of #891 * fix(transactions): eliminate any type usage in transactions.service.ts Replace 24 any annotations with Prisma-derived and explicit types: - create/update DTO fields: Prisma.TransactionType, Prisma.TransactionStatus - where clauses: Prisma.TransactionWhereInput - update data: Prisma.TransactionUpdateInput - blockchain methods: BlockchainTransactionDto, BlockchainVerificationResultDto - new TransactionWithFullRelations type for createTransaction return - new UpdateEscrowDto interface - toResponseDto param: Prisma.Transaction - removed unnecessary any casts, let TS infer map/filter/reduce callbacks - tax strategy methods: Prisma.TransactionTaxStrategy Verified clean with npx tsc --noEmit --skipLibCheck Part of #891 * fix(dashboard): eliminate any type usage in dashboard.service.ts Remove 4 any annotations from map/filter callbacks over Prisma query results, letting TS infer the correct type from the source array instead. admin.service.ts already had zero any usages — no change needed there. This completes the 4 files named directly in the issue. Verified clean with npx tsc --noEmit --skipLibCheck. Part of #891 * fix(search): eliminate any type usage in search-filters.service.ts Replace 19 any annotations: - whereClause params and return types: Prisma.PropertyWhereInput - individual filter values (price/bedrooms/city/etc): unknown, since they're genuinely polymorphic and already narrowed via typeof/ property checks in each method body - Record<string, any> in SavedFilter/FilterCombination: Record<string, unknown> - new SaveFilterDto interface for saveFilter's input shape Verified clean with npx tsc --noEmit --skipLibCheck Part of #891 * fix: eliminate any type usage across 34 small/simple files Batch of straightforward any removals across interceptors, controllers, and small services: - 6 NestJS interceptors: Observable<any> -> Observable<unknown> - Express Request augmentation for custom properties (apiVersion, openAPIDocument) via local extended interfaces instead of casts to any - Prisma-derived where/update input types (User, TransactionMilestone, TransactionNote, VerificationDocument) - New interfaces for previously-untyped payloads (EmailBouncePayload, EmailJobData, CreateDocumentDto wiring) - redisStore cast in cache.config.ts documented as unavoidable third-party interop rather than silently any - Fixed 2 downstream type errors surfaced by these changes: activity-log metadata (object, not string, despite @IsJSON decorator) and a spec.ts mock missing required CreateDocumentDto fields Note: activity-log.controller.ts now correctly types user as AuthUserPayload, which exposes (but does not fix) a pre-existing bug where the code reads user.id but the payload only has .sub -- flagging for a separate issue, out of scope here. Verified clean with npx tsc --noEmit --skipLibCheck Part of #891 * test: eliminate remaining any usage across 18 spec files (#891) * fix: move EmailBouncePayload interface above decorators to fix ESLint parse error (#891) The interface was inserted directly between @apitags and @controller, which is invalid -- a decorator must be immediately followed by the declaration it decorates. This passed tsc --noEmit (suppressed by // @ts-nocheck in this file) but failed ESLint parsing with Decorators are not valid here. * test: fix stale specs exposed by main merge (unrelated to #891) PR #994's test check failed after merging upstream main, but not from the any-removal work -- two pre-existing test/implementation gaps got exposed: - webhooks.service.spec.ts: create() test still expected a 'not yet implemented' throw, but the service has a real implementation (generates a secret, writes to DB) merged in from upstream. Rewrote the test to assert the real behavior. - trust-score.service.spec.ts: mockPrismaService was missing a verificationDocument mock that calculateBreakdown's new ID-verification check (also from an upstream merge) calls. Added the missing mock. Verified locally: npx jest src/trust-score/trust-score.service.spec.ts src/webhooks/webhooks.service.spec.ts -> 2 suites, 15 tests, all passing. * test: pass missing I18nService mock to EmailService constructor EmailService's constructor gained an I18nService parameter (from a merged i18n feature) but this spec still called it with only 4 positional args, misassigning the queue mock into the i18nService slot and leaving mailQueue undefined -- caught by nest build's TS2554 'Expected 5 arguments, but got 4'. Verified: npx jest src/email/email.service.spec.ts and npx nest build both pass clean. Follow-up to the earlier trust-score/webhooks stale-test fix -- same root cause pattern (upstream merge outpacing tests), unrelated to #891. * fix: resolve merge regressions and Prisma type gaps in transactions/auth services (#891) - Restore two try blocks left without catch handlers after merging main (transactions.service.ts: recordOnBlockchain, verifyOnBlockchain) - Remove duplicate/conflicting 'updated' declaration in update() - Restore transaction-not-found check dropped from update() during the merge - Restore nextCursor/previousCursor pagination fields dropped from findAll() during the merge - Fix TypeScript errors from Prisma client not re-exporting model types and enums under the Prisma namespace in this generator version (Transaction, TransactionTaxStrategy, TransactionType, TransactionStatus in transactions.service.ts; BlacklistedToken, Document, ApiKey, PasswordHistory in auth.service.ts) - Add explicit guard rejecting TransactionStatusDto.FAILED, which has no corresponding value in the Prisma schema's TransactionStatus enum - Use existing this.toNumber() helper for Decimal conversion instead of calling .toNumber() directly, matching the pattern already used in getAnalytics() - Normalize nullable Prisma fields (blockchainHash, contractAddress, notes) to undefined to match TransactionResponseDto's optional fields - Remove remaining 'as any' cast in findAll()'s cursor construction - Fix misplaced docstring on updateEscrow() and restore its unused-param lint suppression * fix: complete Keyv/redis cache migration and rbac.spec AuthUserPayload fixture (#891) - Finish migrating REDIS_CONFIG from the old cache-manager-redis-store store-based API (incompatible with cache-manager v7 / @nestjs/cache-manager v3) to the Keyv-based stores API using @keyv/redis - Add missing UserTier import and tier field to rbac.spec.ts's mock AuthUserPayload, which became a required field on the type * fix: remove unused imports/vars flagged by CI lint (zero-warnings gate) - rate-limit-headers.interceptor.ts: remove unused 'request' const - api-docs.controller.ts: remove unused ApiVersionEnum import - notifications.gateway.ts: remove unused SubscribeMessage, ConnectedSocket, MessageBody imports - webhooks.service.spec.ts: remove unused NotFoundException import and unused mockPrisma declaration * fix: remove duplicate/orphaned code left by merge conflict resolution in getQuickStats The conflict resolution for #994 merged in both the old per-role query code and the new #911 single-query optimization instead of cleanly replacing one with the other, leaving an unused buyerTransactions variable and a duplicate totalProperties/activeListings declaration. * test: add coverage for NotificationsGateway to clear src/notifications/ coverage threshold Was at 19.77% lines (threshold: 20%), driven almost entirely by notifications.gateway.ts sitting at 9.61%. Covers connection tracking and all emit helper methods. --------- Co-authored-by: nanaf6203-bit <nanaf6203@gmail.com>
1 parent 28bf4f3 commit ac4c7a0

56 files changed

Lines changed: 1458 additions & 360 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

issue-891-test-files-fix.patch

Lines changed: 838 additions & 0 deletions
Large diffs are not rendered by default.

src/admin/admin-audit.interceptor.spec.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
import { AdminAuditInterceptor } from './admin-audit.interceptor';
22
import { ExecutionContext, CallHandler } from '@nestjs/common';
33
import { of } from 'rxjs';
4+
import { PrismaService } from '../database/prisma.service';
5+
6+
interface MockPrisma {
7+
activityLog: {
8+
create: jest.Mock;
9+
};
10+
}
411

512
describe('AdminAuditInterceptor', () => {
613
let interceptor: AdminAuditInterceptor;
7-
let mockPrisma: any;
14+
let mockPrisma: MockPrisma;
815

916
beforeEach(() => {
1017
mockPrisma = {
1118
activityLog: {
1219
create: jest.fn().mockResolvedValue({}),
1320
},
1421
};
15-
interceptor = new AdminAuditInterceptor(mockPrisma);
22+
interceptor = new AdminAuditInterceptor(mockPrisma as unknown as PrismaService);
1623
});
1724

18-
function makeContext(overrides: Partial<any> = {}): ExecutionContext {
25+
function makeContext(overrides: Record<string, unknown> = {}): ExecutionContext {
1926
const request = {
2027
authUser: { sub: 'admin-1' },
2128
headers: { 'user-agent': 'test-agent' },

src/admin/admin-audit.interceptor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { PrismaService } from '../database/prisma.service';
66
export class AdminAuditInterceptor implements NestInterceptor {
77
constructor(private readonly prisma: PrismaService) {}
88

9-
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
9+
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
1010
const request = context.switchToHttp().getRequest();
1111
const user = request.authUser;
1212
const ip =

src/admin/admin.controller.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,7 @@ export class AdminController {
256256
}
257257

258258
@Get('email/preview/:templateName')
259-
async previewEmailTemplate(@Param('templateName') templateName: string): Promise<{
260-
templateName: string;
261-
sampleData: Record<string, unknown>;
262-
note: string;
263-
}> {
259+
async previewEmailTemplate(@Param('templateName') templateName: string) {
264260
const sampleDataMap: Record<string, Record<string, unknown>> = {
265261
'password-reset': {
266262
resetUrl: 'http://localhost:3000/reset-password?token=sample-token-123',

src/admin/interceptors/admin-access-logging.interceptor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { AuditService } from '../../audit/audit.service';
1111
export class AdminAccessLoggingInterceptor implements NestInterceptor {
1212
constructor(private readonly auditService: AuditService) {}
1313

14-
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
14+
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
1515
const request = context.switchToHttp().getRequest();
1616

1717
const response = context.switchToHttp().getResponse();

src/analytics/analytics.interceptor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { AuthUserPayload } from '../auth/types/auth-user.type';
1111
export class AnalyticsInterceptor implements NestInterceptor {
1212
constructor(private readonly analytics: AnalyticsService) {}
1313

14-
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
14+
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
1515
const req = context.switchToHttp().getRequest();
1616
const res = context.switchToHttp().getResponse();
1717
const start = Date.now();

src/auth/auth.service.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
UnauthorizedException,
88
} from '@nestjs/common';
99
import { ConfigService } from '@nestjs/config';
10-
import { Prisma } from '@prisma/client';
10+
import { Prisma, BlacklistedToken, Document, ApiKey, PasswordHistory } from '@prisma/client';
1111
import { randomUUID } from 'crypto';
1212
import * as jwt from 'jsonwebtoken';
1313
import { PrismaService } from '../database/prisma.service';
@@ -63,6 +63,23 @@ type JwtPayload = {
6363
exp?: number;
6464
};
6565

66+
type TransactionWithPropertyTitle = Prisma.TransactionGetPayload<{
67+
include: { property: { select: { title: true } } };
68+
}>;
69+
70+
type PropertyWithOwnerName = Prisma.PropertyGetPayload<{
71+
include: { owner: { select: { firstName: true; lastName: true } } };
72+
}>;
73+
74+
interface RecaptchaVerifyResponse {
75+
success: boolean;
76+
score?: number;
77+
action?: string;
78+
challenge_ts?: string;
79+
hostname?: string;
80+
'error-codes'?: string[];
81+
}
82+
6683
@Injectable()
6784
export class AuthService {
6885
private readonly logger = new Logger(AuthService.name);
@@ -123,7 +140,10 @@ export class AuthService {
123140
/**
124141
* Helper to map transactions to activity items for dashboard
125142
*/
126-
private transactionsToActivityItems(transactions: any[], type: 'purchase' | 'sale') {
143+
private transactionsToActivityItems(
144+
transactions: TransactionWithPropertyTitle[],
145+
type: 'purchase' | 'sale',
146+
) {
127147
return transactions.map((tx) => ({
128148
type: 'transaction' as const,
129149
id: tx.id,
@@ -495,7 +515,7 @@ export class AuthService {
495515
* Handle token reuse detection - invalidate entire token family
496516
*/
497517
private async handleTokenReuse(
498-
blacklistedToken: any,
518+
blacklistedToken: BlacklistedToken,
499519
reusedJti: string,
500520
ipAddress?: string,
501521
userAgent?: string,
@@ -776,7 +796,7 @@ export class AuthService {
776796
const recentActivity = [
777797
...this.transactionsToActivityItems(buyerTransactions, 'purchase'),
778798
...this.transactionsToActivityItems(sellerTransactions, 'sale'),
779-
...documents.map((doc: any) => ({
799+
...documents.map((doc: Document) => ({
780800
type: 'document' as const,
781801
id: doc.id,
782802
title: doc.fileName,
@@ -800,7 +820,7 @@ export class AuthService {
800820
apiKeysCount: apiKeys.length,
801821
},
802822
recentActivity,
803-
recommendations: recommendationProperties.map((p: any) => ({
823+
recommendations: recommendationProperties.map((p: PropertyWithOwnerName) => ({
804824
id: p.id,
805825
title: p.title,
806826
address: p.address,
@@ -1061,7 +1081,7 @@ export class AuthService {
10611081
orderBy: { createdAt: 'desc' },
10621082
});
10631083

1064-
return apiKeys.map((apiKey: any) => this.toApiKeyResponse(apiKey));
1084+
return apiKeys.map((apiKey: ApiKey) => this.toApiKeyResponse(apiKey));
10651085
}
10661086

10671087
async rotateApiKey(user: AuthUserPayload, apiKeyId: string) {
@@ -1431,7 +1451,7 @@ export class AuthService {
14311451
return `pc_${randomToken(24)}`;
14321452
}
14331453

1434-
private toApiKeyResponse(apiKey: any) {
1454+
private toApiKeyResponse(apiKey: ApiKey) {
14351455
return {
14361456
id: apiKey.id,
14371457
name: apiKey.name,
@@ -1575,7 +1595,7 @@ export class AuthService {
15751595
if (historyEntries.length > 0) {
15761596
await tx.passwordHistory.deleteMany({
15771597
where: {
1578-
id: { in: historyEntries.map((entry: any) => entry.id) },
1598+
id: { in: historyEntries.map((entry: PasswordHistory) => entry.id) },
15791599
},
15801600
});
15811601
}
@@ -1652,7 +1672,7 @@ export class AuthService {
16521672
body: `secret=${secret}&response=${token}`,
16531673
});
16541674

1655-
const data = (await response.json()) as any;
1675+
const data = (await response.json()) as RecaptchaVerifyResponse;
16561676

16571677
// reCAPTCHA v3 returns a score between 0.0 and 1.0. Typically, 0.5 is a good threshold.
16581678
if (data.success && data.score !== undefined && data.score >= 0.5) {

src/auth/guards/rate-limit.guard.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Logger,
1111
} from '@nestjs/common';
1212
import { Reflector } from '@nestjs/core';
13+
import { Request } from 'express';
1314
import { RateLimitService } from '../rate-limit.service';
1415
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1516
import { RATE_LIMIT_HEADERS } from '../rate-limit.config';
@@ -159,7 +160,7 @@ export class RateLimitGuard implements CanActivate {
159160
/**
160161
* Extract client IP from request
161162
*/
162-
private getClientIp(request: any): string {
163+
private getClientIp(request: Request): string {
163164
return (
164165
request.headers['x-forwarded-for']?.split(',')[0].trim() ||
165166
request.connection?.remoteAddress ||

src/auth/interceptors/rate-limit-headers.interceptor.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ import { RATE_LIMIT_HEADERS } from '../rate-limit.config';
1111
*/
1212
@Injectable()
1313
export class RateLimitHeadersInterceptor implements NestInterceptor {
14-
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
15-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
16-
const request = context.switchToHttp().getRequest();
14+
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
1715
const response = context.switchToHttp().getResponse();
1816

1917
return next.handle().pipe(

src/auth/login-rate-limit.service.spec.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
import { LoginRateLimitService } from './login-rate-limit.service';
2+
import { PrismaService } from '../database/prisma.service';
3+
4+
interface MockPrisma {
5+
loginAttempt: {
6+
findFirst: jest.Mock;
7+
count: jest.Mock;
8+
create: jest.Mock;
9+
updateMany: jest.Mock;
10+
};
11+
}
212

313
describe('LoginRateLimitService', () => {
414
let service: LoginRateLimitService;
5-
let mockPrisma: any;
15+
let mockPrisma: MockPrisma;
616

717
const email = 'test@example.com';
818
const ip = '1.2.3.4';
@@ -16,7 +26,7 @@ describe('LoginRateLimitService', () => {
1626
updateMany: jest.fn().mockResolvedValue({ count: 1 }),
1727
},
1828
};
19-
service = new LoginRateLimitService(mockPrisma);
29+
service = new LoginRateLimitService(mockPrisma as unknown as PrismaService);
2030
});
2131

2232
describe('isAccountLocked', () => {

0 commit comments

Comments
 (0)