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

View File

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

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common'; 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 { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
import { UserEntity, type UserProps } from '../../domain/entities/user.entity'; import { UserEntity, type UserProps } from '../../domain/entities/user.entity';
import { type IUserRepository } from '../../domain/repositories/user.repository'; import { type IUserRepository } from '../../domain/repositories/user.repository';
@@ -37,7 +37,7 @@ export class PrismaUserRepository implements IUserRepository {
avatarUrl: entity.avatarUrl, avatarUrl: entity.avatarUrl,
role: entity.role, role: entity.role,
kycStatus: entity.kycStatus, kycStatus: entity.kycStatus,
kycData: entity.kycData as any, kycData: entity.kycData as Prisma.InputJsonValue,
isActive: entity.isActive, isActive: entity.isActive,
}, },
}); });
@@ -54,7 +54,7 @@ export class PrismaUserRepository implements IUserRepository {
avatarUrl: entity.avatarUrl, avatarUrl: entity.avatarUrl,
role: entity.role, role: entity.role,
kycStatus: entity.kycStatus, kycStatus: entity.kycStatus,
kycData: entity.kycData as any, kycData: entity.kycData as Prisma.InputJsonValue,
isActive: entity.isActive, 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 IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
import { type IPropertyRepository } from '@modules/listings/domain/repositories/property.repository'; import { type IPropertyRepository } from '@modules/listings/domain/repositories/property.repository';
import { CreateListingCommand } from '../commands/create-listing/create-listing.command'; import { CreateListingCommand } from '../commands/create-listing/create-listing.command';

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common'; 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 { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
import { PropertyMediaEntity, type PropertyMediaProps } from '../../domain/entities/property-media.entity'; import { PropertyMediaEntity, type PropertyMediaProps } from '../../domain/entities/property-media.entity';
import { PropertyEntity, type PropertyProps } from '../../domain/entities/property.entity'; import { PropertyEntity, type PropertyProps } from '../../domain/entities/property.entity';
@@ -69,7 +69,7 @@ export class PrismaPropertyRepository implements IPropertyRepository {
type: media.type, type: media.type,
order: media.order, order: media.order,
caption: media.caption, 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 { 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 { CreatePaymentCommand } from '../commands/create-payment/create-payment.command';
import { CreatePaymentHandler } from '../commands/create-payment/create-payment.handler'; import { CreatePaymentHandler } from '../commands/create-payment/create-payment.handler';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -207,12 +207,12 @@ function randomVariation(base: number, pct: number): number {
} }
async function importMarketData() { 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; let total = 0;
for (const [city, districts] of Object.entries(MARKET_DATA)) { for (const [city, districts] of Object.entries(MARKET_DATA)) {
console.log(` ${city}:`); console.warn(` ${city}:`);
let cityCount = 0; let cityCount = 0;
for (const { district, avgPriceM2 } of districts) { 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) { if (require.main === module) {

View File

@@ -250,13 +250,13 @@ const PROPERTY_TEMPLATES = [
]; ];
async function seedDistrictProperties() { 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 created = 0;
let skipped = 0; let skipped = 0;
for (const { city, districts } of getAllDistricts()) { for (const { city, districts } of getAllDistricts()) {
console.log(` ${city}:`); console.warn(` ${city}:`);
const coords = CITY_COORDINATES[city] ?? {}; const coords = CITY_COORDINATES[city] ?? {};
for (const { district, wards } of districts) { for (const { district, wards } of districts) {
@@ -296,24 +296,24 @@ async function seedDistrictProperties() {
} }
const cityDistricts = districts.length; 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() { 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 // Log summary
for (const { city, districts } of getAllDistricts()) { for (const { city, districts } of getAllDistricts()) {
const totalWards = districts.reduce((sum, d) => sum + d.wards.length, 0); 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(); await seedDistrictProperties();
console.log('\nDone.'); console.warn('\nDone.');
} }
// Run standalone or import as module // Run standalone or import as module

View File

@@ -98,7 +98,7 @@ export const PLANS = [
]; ];
async function seedPlans() { async function seedPlans() {
console.log('Seeding subscription plans...\n'); console.warn('Seeding subscription plans...\n');
for (const plan of PLANS) { for (const plan of PLANS) {
const _result = await prisma.plan.upsert({ const _result = await prisma.plan.upsert({
@@ -115,10 +115,10 @@ async function seedPlans() {
}); });
const monthly = Number(plan.priceMonthlyVND).toLocaleString('vi-VN'); 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) { if (require.main === module) {