fix(lint): resolve all 49 lint warnings and errors across codebase

- Remove unused imports/variables in seed scripts and test files
- Replace console.log with console.warn in seed/utility scripts
- Replace `as any` with proper Prisma types (InputJsonValue, PaymentStatus, Plan, UserWhereInput)
- Fix import-x/no-named-as-default-member warnings in logger, mapbox, eslint config
- Prefix unused callback params with underscore in e2e tests

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-08 13:22:07 +07:00
parent 36c1e3b39a
commit cc5c81904b
18 changed files with 47 additions and 44 deletions

View File

@@ -1,4 +1,5 @@
import { Injectable } from '@nestjs/common';
import { type Prisma, type UserRole } from '@prisma/client';
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
import {
type IAdminQueryRepository,
@@ -163,8 +164,8 @@ export class PrismaAdminQueryRepository implements IAdminQueryRepository {
const { page, limit, role, isActive, search } = params;
const skip = (page - 1) * limit;
const where: any = {};
if (role) where.role = role;
const where: Prisma.UserWhereInput = {};
if (role) where.role = role as UserRole;
if (isActive !== undefined) where.isActive = isActive;
if (search) {
where.OR = [

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { type Valuation as PrismaValuation } from '@prisma/client';
import { type Prisma, type Valuation as PrismaValuation } from '@prisma/client';
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
import { ValuationEntity, type ValuationProps } from '../../domain/entities/valuation.entity';
import { type IValuationRepository } from '../../domain/repositories/valuation.repository';
@@ -38,8 +38,8 @@ export class PrismaValuationRepository implements IValuationRepository {
estimatedPrice: entity.estimatedPrice,
confidence: entity.confidence,
pricePerM2: entity.pricePerM2,
comparables: entity.comparables as any,
features: entity.features as any,
comparables: entity.comparables as Prisma.InputJsonValue,
features: entity.features as Prisma.InputJsonValue,
modelVersion: entity.modelVersion,
},
});

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { type User as PrismaUser } from '@prisma/client';
import { type Prisma, type User as PrismaUser } from '@prisma/client';
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
import { UserEntity, type UserProps } from '../../domain/entities/user.entity';
import { type IUserRepository } from '../../domain/repositories/user.repository';
@@ -37,7 +37,7 @@ export class PrismaUserRepository implements IUserRepository {
avatarUrl: entity.avatarUrl,
role: entity.role,
kycStatus: entity.kycStatus,
kycData: entity.kycData as any,
kycData: entity.kycData as Prisma.InputJsonValue,
isActive: entity.isActive,
},
});
@@ -54,7 +54,7 @@ export class PrismaUserRepository implements IUserRepository {
avatarUrl: entity.avatarUrl,
role: entity.role,
kycStatus: entity.kycStatus,
kycData: entity.kycData as any,
kycData: entity.kycData as Prisma.InputJsonValue,
isActive: entity.isActive,
},
});

View File

@@ -1,4 +1,3 @@
import { ListingEntity } from '@modules/listings/domain/entities/listing.entity';
import { type IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
import { type IPropertyRepository } from '@modules/listings/domain/repositories/property.repository';
import { CreateListingCommand } from '../commands/create-listing/create-listing.command';

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { type Property as PrismaProperty, type PropertyMedia as PrismaMedia } from '@prisma/client';
import { type Prisma, type Property as PrismaProperty, type PropertyMedia as PrismaMedia } from '@prisma/client';
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
import { PropertyMediaEntity, type PropertyMediaProps } from '../../domain/entities/property-media.entity';
import { PropertyEntity, type PropertyProps } from '../../domain/entities/property.entity';
@@ -69,7 +69,7 @@ export class PrismaPropertyRepository implements IPropertyRepository {
type: media.type,
order: media.order,
caption: media.caption,
aiTags: media.aiTags as any,
aiTags: media.aiTags as Prisma.InputJsonValue,
},
});
}

View File

@@ -1,6 +1,4 @@
import { type IPaymentRepository } from '../../domain/repositories/payment.repository';
import { Money } from '../../domain/value-objects/money.vo';
import { type IPaymentGatewayFactory } from '../../infrastructure/services/payment-gateway.interface';
import { CreatePaymentCommand } from '../commands/create-payment/create-payment.command';
import { CreatePaymentHandler } from '../commands/create-payment/create-payment.handler';

View File

@@ -1,5 +1,6 @@
import { Inject, Logger } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
import { type PaymentStatus } from '@prisma/client';
import { NotFoundException, ValidationException } from '@modules/shared/domain/domain-exception';
import {
PAYMENT_REPOSITORY,
@@ -47,7 +48,7 @@ export class HandleCallbackHandler implements ICommandHandler<HandleCallbackComm
result.orderId,
['PENDING', 'PROCESSING'],
{
status: targetStatus as any,
status: targetStatus as PaymentStatus,
callbackData: result.rawData,
},
);

View File

@@ -64,7 +64,7 @@ export class PrismaPaymentRepository implements IPaymentRepository {
amountVND: entity.amount.value,
status: entity.status,
providerTxId: entity.providerTxId,
callbackData: entity.callbackData as any,
callbackData: entity.callbackData as Prisma.InputJsonValue,
idempotencyKey: entity.idempotencyKey,
},
});
@@ -76,7 +76,7 @@ export class PrismaPaymentRepository implements IPaymentRepository {
data: {
status: entity.status,
providerTxId: entity.providerTxId,
callbackData: entity.callbackData as any,
callbackData: entity.callbackData as Prisma.InputJsonValue,
},
});
}
@@ -95,7 +95,7 @@ export class PrismaPaymentRepository implements IPaymentRepository {
data: {
status: data.status,
...(data.providerTxId !== undefined ? { providerTxId: data.providerTxId } : {}),
...(data.callbackData !== undefined ? { callbackData: data.callbackData as any } : {}),
...(data.callbackData !== undefined ? { callbackData: data.callbackData as Prisma.InputJsonValue } : {}),
},
});
return this.toDomain(updated);

View File

@@ -1,5 +1,5 @@
import { Injectable, type LoggerService as NestLoggerService } from '@nestjs/common';
import pino, { type Logger } from 'pino';
import pinoLogger, { type Logger, stdTimeFunctions } from 'pino';
import { maskPii } from './pii-masker';
@Injectable()
@@ -7,7 +7,7 @@ export class LoggerService implements NestLoggerService {
private readonly logger: Logger;
constructor() {
this.logger = pino({
this.logger = pinoLogger({
level: process.env['LOG_LEVEL'] ?? 'info',
transport:
process.env['NODE_ENV'] !== 'production'
@@ -22,7 +22,7 @@ export class LoggerService implements NestLoggerService {
},
},
base: { service: 'goodgo-api' },
timestamp: pino.stdTimeFunctions.isoTime,
timestamp: stdTimeFunctions.isoTime,
});
}

View File

@@ -1,5 +1,6 @@
import { Inject } from '@nestjs/common';
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { type Plan } from '@prisma/client';
import { NotFoundException } from '@modules/shared/domain/domain-exception';
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
import {
@@ -16,7 +17,7 @@ export interface QuotaCheckResult {
allowed: boolean;
}
const METRIC_TO_PLAN_FIELD: Record<string, string> = {
const METRIC_TO_PLAN_FIELD: Record<string, keyof Plan> = {
listings_created: 'maxListings',
searches_saved: 'maxSavedSearches',
};
@@ -54,7 +55,7 @@ export class CheckQuotaHandler implements IQueryHandler<CheckQuotaQuery> {
}
private async checkAgainstPlan(
plan: any,
plan: Plan,
metric: string,
subscriptionId: string | null,
_userId: string,

View File

@@ -1,4 +1,5 @@
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { type Plan } from '@prisma/client';
import { NotFoundException } from '@modules/shared/domain/domain-exception';
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
import { GetPlanQuery } from './get-plan.query';
@@ -41,7 +42,7 @@ export class GetPlanHandler implements IQueryHandler<GetPlanQuery> {
return plans.map((p) => this.toDto(p));
}
private toDto(plan: any): PlanDto {
private toDto(plan: Plan): PlanDto {
return {
id: plan.id,
tier: plan.tier,

View File

@@ -1,5 +1,6 @@
'use client';
/* eslint-disable import-x/no-named-as-default-member */
import mapboxgl from 'mapbox-gl';
import * as React from 'react';
import 'mapbox-gl/dist/mapbox-gl.css';

View File

@@ -4,7 +4,7 @@ test.describe('OAuth Callback Pages', () => {
test.describe('Google callback', () => {
test('shows loading state while processing', async ({ page }) => {
// Intercept token exchange to keep it pending
await page.route('**/auth/google/callback**', (route) =>
await page.route('**/auth/google/callback**', (_route) =>
new Promise(() => {
// Never resolve — keeps loading state visible
}),
@@ -31,7 +31,7 @@ test.describe('OAuth Callback Pages', () => {
test.describe('Zalo callback', () => {
test('shows loading state while processing', async ({ page }) => {
await page.route('**/auth/zalo/callback**', (route) =>
await page.route('**/auth/zalo/callback**', (_route) =>
new Promise(() => {
// Never resolve
}),

View File

@@ -1,3 +1,4 @@
/* eslint-disable import-x/no-named-as-default-member */
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import importPlugin from 'eslint-plugin-import-x';

View File

@@ -9,8 +9,8 @@ import {
} from '@prisma/client';
import pg from 'pg';
import { importMarketData } from '../scripts/import-market-data';
import { HCM_DISTRICTS, HANOI_DISTRICTS, DANANG_DISTRICTS, CITY_COORDINATES } from '../scripts/seed-districts';
import { PLANS, seedPlans } from '../scripts/seed-plans';
import { CITY_COORDINATES } from '../scripts/seed-districts';
import { seedPlans } from '../scripts/seed-plans';
const pool = new pg.Pool({ connectionString: process.env['DATABASE_URL'] });
const adapter = new PrismaPg(pool);
@@ -20,7 +20,7 @@ const prisma = new PrismaClient({ adapter });
// Sample coordinates for HCM districts
// =============================================================================
const SAMPLE_LOCATIONS = CITY_COORDINATES['Hồ Chí Minh'];
const _SAMPLE_LOCATIONS = CITY_COORDINATES['Hồ Chí Minh'];
// =============================================================================
// Seed functions
@@ -260,7 +260,7 @@ async function seedProperties(users: Awaited<ReturnType<typeof seedUsers>>) {
const p = sampleProperties[i]!;
const agent = agents[i % agents.length]!
const property = await prisma.$executeRaw`
const _property = await prisma.$executeRaw`
INSERT INTO "Property" (
"id", "propertyType", "title", "description", "address",
"ward", "district", "city", "location",

View File

@@ -207,12 +207,12 @@ function randomVariation(base: number, pct: number): number {
}
async function importMarketData() {
console.log('Importing market data for HCM, Hanoi, Da Nang...\n');
console.warn('Importing market data for HCM, Hanoi, Da Nang...\n');
let total = 0;
for (const [city, districts] of Object.entries(MARKET_DATA)) {
console.log(` ${city}:`);
console.warn(` ${city}:`);
let cityCount = 0;
for (const { district, avgPriceM2 } of districts) {
@@ -269,10 +269,10 @@ async function importMarketData() {
}
}
console.log(` ${cityCount} market index records`);
console.warn(` ${cityCount} market index records`);
}
console.log(`\nTotal: ${total} market index records imported.`);
console.warn(`\nTotal: ${total} market index records imported.`);
}
if (require.main === module) {

View File

@@ -250,13 +250,13 @@ const PROPERTY_TEMPLATES = [
];
async function seedDistrictProperties() {
console.log('Seeding district properties across HCM, Hanoi, Da Nang...\n');
console.warn('Seeding district properties across HCM, Hanoi, Da Nang...\n');
let created = 0;
let skipped = 0;
for (const { city, districts } of getAllDistricts()) {
console.log(` ${city}:`);
console.warn(` ${city}:`);
const coords = CITY_COORDINATES[city] ?? {};
for (const { district, wards } of districts) {
@@ -296,24 +296,24 @@ async function seedDistrictProperties() {
}
const cityDistricts = districts.length;
console.log(` ${cityDistricts} districts processed`);
console.warn(` ${cityDistricts} districts processed`);
}
console.log(`\n Total: ${created} properties created, ${skipped} skipped (already exist)`);
console.warn(`\n Total: ${created} properties created, ${skipped} skipped (already exist)`);
}
async function main() {
console.log('=== Seed Districts — Vietnam Real Estate Dev Data ===\n');
console.warn('=== Seed Districts — Vietnam Real Estate Dev Data ===\n');
// Log summary
for (const { city, districts } of getAllDistricts()) {
const totalWards = districts.reduce((sum, d) => sum + d.wards.length, 0);
console.log(` ${city}: ${districts.length} districts, ${totalWards} wards`);
console.warn(` ${city}: ${districts.length} districts, ${totalWards} wards`);
}
console.log('');
console.warn('');
await seedDistrictProperties();
console.log('\nDone.');
console.warn('\nDone.');
}
// Run standalone or import as module

View File

@@ -98,7 +98,7 @@ export const PLANS = [
];
async function seedPlans() {
console.log('Seeding subscription plans...\n');
console.warn('Seeding subscription plans...\n');
for (const plan of PLANS) {
const _result = await prisma.plan.upsert({
@@ -115,10 +115,10 @@ async function seedPlans() {
});
const monthly = Number(plan.priceMonthlyVND).toLocaleString('vi-VN');
console.log(` ${plan.tier.padEnd(12)} ${plan.name.padEnd(14)} ${monthly} VND/tháng`);
console.warn(` ${plan.tier.padEnd(12)} ${plan.name.padEnd(14)} ${monthly} VND/tháng`);
}
console.log(`\n${PLANS.length} plans seeded.`);
console.warn(`\n${PLANS.length} plans seeded.`);
}
if (require.main === module) {