Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
4fc3545
fix(auth): eliminate any type usage in auth.service.ts
abdoolyaro Jul 26, 2026
437e3d6
fix(transactions): eliminate any type usage in transactions.service.ts
abdoolyaro Jul 26, 2026
af40569
fix(dashboard): eliminate any type usage in dashboard.service.ts
abdoolyaro Jul 26, 2026
69b8c4d
fix(search): eliminate any type usage in search-filters.service.ts
abdoolyaro Jul 26, 2026
87c934d
fix: eliminate any type usage across 34 small/simple files
abdoolyaro Jul 26, 2026
7dcee21
test: eliminate remaining any usage across 18 spec files (#891)
abdoolyaro Jul 26, 2026
417c007
fix: move EmailBouncePayload interface above decorators to fix ESLint…
abdoolyaro Jul 26, 2026
cbcc5c5
Merge branch 'main' into chore/891-eliminate-any-usage
abdoolyaro Jul 26, 2026
683ba13
test: fix stale specs exposed by main merge (unrelated to #891)
abdoolyaro Jul 27, 2026
6d26a0b
test: pass missing I18nService mock to EmailService constructor
abdoolyaro Jul 27, 2026
13b6d46
Merge branch 'main' into chore/891-eliminate-any-usage
abdoolyaro Jul 29, 2026
98aaf59
fix: resolve merge regressions and Prisma type gaps in transactions/a…
abdoolyaro Jul 30, 2026
40c247b
fix: complete Keyv/redis cache migration and rbac.spec AuthUserPayloa…
abdoolyaro Jul 30, 2026
36b4279
fix: remove unused imports/vars flagged by CI lint (zero-warnings gate)
abdoolyaro Jul 30, 2026
ef4e84e
Merge branch 'main' into chore/891-eliminate-any-usage
nanaf6203-bit Jul 30, 2026
7c2b741
fix: remove duplicate/orphaned code left by merge conflict resolution…
abdoolyaro Jul 30, 2026
061a3e2
test: add coverage for NotificationsGateway to clear src/notification…
abdoolyaro Jul 30, 2026
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
838 changes: 838 additions & 0 deletions issue-891-test-files-fix.patch

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions src/admin/admin-audit.interceptor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import { AdminAuditInterceptor } from './admin-audit.interceptor';
import { ExecutionContext, CallHandler } from '@nestjs/common';
import { of } from 'rxjs';
import { PrismaService } from '../database/prisma.service';

interface MockPrisma {
activityLog: {
create: jest.Mock;
};
}

describe('AdminAuditInterceptor', () => {
let interceptor: AdminAuditInterceptor;
let mockPrisma: any;
let mockPrisma: MockPrisma;

beforeEach(() => {
mockPrisma = {
activityLog: {
create: jest.fn().mockResolvedValue({}),
},
};
interceptor = new AdminAuditInterceptor(mockPrisma);
interceptor = new AdminAuditInterceptor(mockPrisma as unknown as PrismaService);
});

function makeContext(overrides: Partial<any> = {}): ExecutionContext {
function makeContext(overrides: Record<string, unknown> = {}): ExecutionContext {
const request = {
authUser: { sub: 'admin-1' },
headers: { 'user-agent': 'test-agent' },
Expand Down
2 changes: 1 addition & 1 deletion src/admin/admin-audit.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { PrismaService } from '../database/prisma.service';
export class AdminAuditInterceptor implements NestInterceptor {
constructor(private readonly prisma: PrismaService) {}

intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const request = context.switchToHttp().getRequest();
const user = request.authUser;
const ip =
Expand Down
6 changes: 1 addition & 5 deletions src/admin/admin.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,7 @@ export class AdminController {
}

@Get('email/preview/:templateName')
async previewEmailTemplate(@Param('templateName') templateName: string): Promise<{
templateName: string;
sampleData: Record<string, unknown>;
note: string;
}> {
async previewEmailTemplate(@Param('templateName') templateName: string) {
const sampleDataMap: Record<string, Record<string, unknown>> = {
'password-reset': {
resetUrl: 'http://localhost:3000/reset-password?token=sample-token-123',
Expand Down
2 changes: 1 addition & 1 deletion src/admin/interceptors/admin-access-logging.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { AuditService } from '../../audit/audit.service';
export class AdminAccessLoggingInterceptor implements NestInterceptor {
constructor(private readonly auditService: AuditService) {}

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

const response = context.switchToHttp().getResponse();
Expand Down
2 changes: 1 addition & 1 deletion src/analytics/analytics.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { AuthUserPayload } from '../auth/types/auth-user.type';
export class AnalyticsInterceptor implements NestInterceptor {
constructor(private readonly analytics: AnalyticsService) {}

intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const req = context.switchToHttp().getRequest();
const res = context.switchToHttp().getResponse();
const start = Date.now();
Expand Down
38 changes: 29 additions & 9 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Prisma } from '@prisma/client';
import { Prisma, BlacklistedToken, Document, ApiKey, PasswordHistory } from '@prisma/client';
import { randomUUID } from 'crypto';
import * as jwt from 'jsonwebtoken';
import { PrismaService } from '../database/prisma.service';
Expand Down Expand Up @@ -63,6 +63,23 @@ type JwtPayload = {
exp?: number;
};

type TransactionWithPropertyTitle = Prisma.TransactionGetPayload<{
include: { property: { select: { title: true } } };
}>;

type PropertyWithOwnerName = Prisma.PropertyGetPayload<{
include: { owner: { select: { firstName: true; lastName: true } } };
}>;

interface RecaptchaVerifyResponse {
success: boolean;
score?: number;
action?: string;
challenge_ts?: string;
hostname?: string;
'error-codes'?: string[];
}

@Injectable()
export class AuthService {
private readonly logger = new Logger(AuthService.name);
Expand Down Expand Up @@ -123,7 +140,10 @@ export class AuthService {
/**
* Helper to map transactions to activity items for dashboard
*/
private transactionsToActivityItems(transactions: any[], type: 'purchase' | 'sale') {
private transactionsToActivityItems(
transactions: TransactionWithPropertyTitle[],
type: 'purchase' | 'sale',
) {
return transactions.map((tx) => ({
type: 'transaction' as const,
id: tx.id,
Expand Down Expand Up @@ -495,7 +515,7 @@ export class AuthService {
* Handle token reuse detection - invalidate entire token family
*/
private async handleTokenReuse(
blacklistedToken: any,
blacklistedToken: BlacklistedToken,
reusedJti: string,
ipAddress?: string,
userAgent?: string,
Expand Down Expand Up @@ -776,7 +796,7 @@ export class AuthService {
const recentActivity = [
...this.transactionsToActivityItems(buyerTransactions, 'purchase'),
...this.transactionsToActivityItems(sellerTransactions, 'sale'),
...documents.map((doc: any) => ({
...documents.map((doc: Document) => ({
type: 'document' as const,
id: doc.id,
title: doc.fileName,
Expand All @@ -800,7 +820,7 @@ export class AuthService {
apiKeysCount: apiKeys.length,
},
recentActivity,
recommendations: recommendationProperties.map((p: any) => ({
recommendations: recommendationProperties.map((p: PropertyWithOwnerName) => ({
id: p.id,
title: p.title,
address: p.address,
Expand Down Expand Up @@ -1061,7 +1081,7 @@ export class AuthService {
orderBy: { createdAt: 'desc' },
});

return apiKeys.map((apiKey: any) => this.toApiKeyResponse(apiKey));
return apiKeys.map((apiKey: ApiKey) => this.toApiKeyResponse(apiKey));
}

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

private toApiKeyResponse(apiKey: any) {
private toApiKeyResponse(apiKey: ApiKey) {
return {
id: apiKey.id,
name: apiKey.name,
Expand Down Expand Up @@ -1575,7 +1595,7 @@ export class AuthService {
if (historyEntries.length > 0) {
await tx.passwordHistory.deleteMany({
where: {
id: { in: historyEntries.map((entry: any) => entry.id) },
id: { in: historyEntries.map((entry: PasswordHistory) => entry.id) },
},
});
}
Expand Down Expand Up @@ -1652,7 +1672,7 @@ export class AuthService {
body: `secret=${secret}&response=${token}`,
});

const data = (await response.json()) as any;
const data = (await response.json()) as RecaptchaVerifyResponse;

// reCAPTCHA v3 returns a score between 0.0 and 1.0. Typically, 0.5 is a good threshold.
if (data.success && data.score !== undefined && data.score >= 0.5) {
Expand Down
3 changes: 2 additions & 1 deletion src/auth/guards/rate-limit.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Logger,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { RateLimitService } from '../rate-limit.service';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { RATE_LIMIT_HEADERS } from '../rate-limit.config';
Expand Down Expand Up @@ -159,7 +160,7 @@ export class RateLimitGuard implements CanActivate {
/**
* Extract client IP from request
*/
private getClientIp(request: any): string {
private getClientIp(request: Request): string {
return (
request.headers['x-forwarded-for']?.split(',')[0].trim() ||
request.connection?.remoteAddress ||
Expand Down
4 changes: 1 addition & 3 deletions src/auth/interceptors/rate-limit-headers.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ import { RATE_LIMIT_HEADERS } from '../rate-limit.config';
*/
@Injectable()
export class RateLimitHeadersInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const request = context.switchToHttp().getRequest();
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const response = context.switchToHttp().getResponse();

return next.handle().pipe(
Expand Down
14 changes: 12 additions & 2 deletions src/auth/login-rate-limit.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { LoginRateLimitService } from './login-rate-limit.service';
import { PrismaService } from '../database/prisma.service';

interface MockPrisma {
loginAttempt: {
findFirst: jest.Mock;
count: jest.Mock;
create: jest.Mock;
updateMany: jest.Mock;
};
}

describe('LoginRateLimitService', () => {
let service: LoginRateLimitService;
let mockPrisma: any;
let mockPrisma: MockPrisma;

const email = 'test@example.com';
const ip = '1.2.3.4';
Expand All @@ -16,7 +26,7 @@ describe('LoginRateLimitService', () => {
updateMany: jest.fn().mockResolvedValue({ count: 1 }),
},
};
service = new LoginRateLimitService(mockPrisma);
service = new LoginRateLimitService(mockPrisma as unknown as PrismaService);
});

describe('isAccountLocked', () => {
Expand Down
11 changes: 6 additions & 5 deletions src/auth/rate-limit.guard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { RateLimitService } from './rate-limit.service';
import { ExecutionContext, HttpException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

function makeContext(overrides: Partial<any> = {}): ExecutionContext {
function makeContext(overrides: Record<string, unknown> = {}): ExecutionContext {
const request = {
method: 'POST',
url: '/auth/login',
Expand Down Expand Up @@ -41,7 +41,7 @@ describe('RateLimitGuard - auth/signup endpoints', () => {
checkUserIpRateLimit: jest.fn().mockResolvedValue(notExceeded),
checkEndpointRateLimit: jest.fn().mockResolvedValue({ ...notExceeded, limit: 0 }),
getHeaders: jest.fn().mockReturnValue({}),
} as any;
} as unknown as jest.Mocked<RateLimitService>;

guard = new RateLimitGuard(reflector, rateLimitService);
});
Expand Down Expand Up @@ -69,10 +69,11 @@ describe('RateLimitGuard - auth/signup endpoints', () => {
try {
await guard.canActivate(ctx);
fail('should have thrown');
} catch (e: any) {
} catch (e: unknown) {
expect(e).toBeInstanceOf(HttpException);
expect(e.getStatus()).toBe(429);
const body = e.getResponse();
const httpException = e as HttpException;
expect(httpException.getStatus()).toBe(429);
const body = httpException.getResponse();
expect(body).toMatchObject({ retryAfter: 60 });
}
});
Expand Down
Loading
Loading