fix(api): resolve NestJS DI + ValidationPipe bugs from type-only imports

- Remove `type` modifier from imports used as DI constructor params
  across ~235 files (@Injectable, @Controller, @Module, @Catch,
  @CommandHandler, @QueryHandler, @EventsHandler, @WebSocketGateway).
  TypeScript emitDecoratorMetadata strips type-only imports, leaving
  Reflect.metadata with Function placeholder and breaking Nest DI.
- Fix controllers: DTOs used with @Body/@Query/@Param must be runtime
  imports so ValidationPipe can whitelist properties. Previously
  returned 400 "property X should not exist" on every request.
- Register ProjectsModule in AppModule (was defined but never wired).
- Add approve()/reject() methods to TransferListingEntity referenced by
  ModerateTransferListingHandler.
- Export BankTransferConfirmedEvent from payments barrel for
  subscription activation handler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ho Ngoc Hai
2026-04-18 21:50:30 +07:00
parent 4143c4dcb9
commit 312532b1cb
242 changed files with 460 additions and 442 deletions

View File

@@ -1,6 +1,6 @@
import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
import { DomainException, type EventBusService, type LoggerService } from '@modules/shared';
import { DomainException, EventBusService, LoggerService } from '@modules/shared';
import { NotificationSentEvent } from '../../../domain/events/notification-sent.event';
import {
NOTIFICATION_PREFERENCE_REPOSITORY,
@@ -10,11 +10,11 @@ import {
NOTIFICATION_REPOSITORY,
type INotificationRepository,
} from '../../../domain/repositories/notification.repository';
import { type EmailService } from '../../../infrastructure/services/email.service';
import { type FcmService } from '../../../infrastructure/services/fcm.service';
import { type StringeeSmsService } from '../../../infrastructure/services/stringee-sms.service';
import { type TemplateService } from '../../../infrastructure/services/template.service';
import { type ZaloOaService } from '../../../infrastructure/services/zalo-oa.service';
import { EmailService } from '../../../infrastructure/services/email.service';
import { FcmService } from '../../../infrastructure/services/fcm.service';
import { StringeeSmsService } from '../../../infrastructure/services/stringee-sms.service';
import { TemplateService } from '../../../infrastructure/services/template.service';
import { ZaloOaService } from '../../../infrastructure/services/zalo-oa.service';
import { getZaloZnsTemplates } from '../../../infrastructure/services/zalo-zns-templates';
import { SendNotificationCommand } from './send-notification.command';

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type AgentVerifiedEvent } from '@modules/auth';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
@Injectable()

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type EmailChangeRequestedEvent } from '@modules/auth';
import { type LoggerService } from '@modules/shared';
import { LoggerService } from '@modules/shared';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
@Injectable()

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
export interface InquiryReceivedEvent {

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type ListingApprovedEvent } from '@modules/admin';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
@Injectable()

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type ListingRejectedEvent } from '@modules/admin';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
@Injectable()

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type ListingSoldEvent } from '@modules/listings';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
@Injectable()

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type PaymentCompletedEvent } from '@modules/payments';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
@Injectable()

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type PaymentFailedEvent } from '@modules/payments';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
@Injectable()

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type PaymentRefundedEvent } from '@modules/payments';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
@Injectable()

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type PhoneChangeRequestedEvent } from '@modules/auth';
import { type LoggerService } from '@modules/shared';
import { LoggerService } from '@modules/shared';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
@Injectable()

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { type QuotaExceededEvent } from '@modules/subscriptions';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';

View File

@@ -1,9 +1,9 @@
import { EventsHandler, type IEventHandler } from '@nestjs/cqrs';
import { ListingApprovedEvent } from '@modules/admin';
import { InquiryReadEvent } from '@modules/inquiries';
import { ListingPriceChangedEvent } from '@modules/listings';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { type NotificationsGateway } from '../../presentation/gateways/notifications.gateway';
import { ListingApprovedEvent } from '@modules/admin/domain/events/listing-approved.event';
import { InquiryReadEvent } from '@modules/inquiries/domain/events/inquiry-read.event';
import { ListingPriceChangedEvent } from '@modules/listings/domain/events/listing-price-changed.event';
import { LoggerService, PrismaService } from '@modules/shared';
import { NotificationsGateway } from '../../presentation/gateways/notifications.gateway';
const CONTEXT = 'ResidentialEventsListener';

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { type SubscriptionExpiredEvent } from '@modules/subscriptions';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { type SubscriptionCancelledEvent } from '@modules/subscriptions';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { type SubscriptionRenewedEvent } from '@modules/subscriptions';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type UserKycUpdatedEvent } from '@modules/auth';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
@Injectable()

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs';
import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter';
import { type UserRegisteredEvent } from '@modules/auth';
import { type LoggerService, type PrismaService } from '@modules/shared';
import { LoggerService, PrismaService } from '@modules/shared';
import { SendNotificationCommand } from '../commands/send-notification/send-notification.command';
@Injectable()

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { type PrismaService } from '@modules/shared';
import { PrismaService } from '@modules/shared';
import { type NotificationPreferenceEntity } from '../../domain/entities/notification-preference.entity';
import { type INotificationPreferenceRepository } from '../../domain/repositories/notification-preference.repository';
import { type NotificationChannel } from '../../domain/value-objects/notification-channel.vo';

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { type Prisma } from '@prisma/client';
import { type PrismaService } from '@modules/shared';
import { PrismaService } from '@modules/shared';
import { type NotificationEntity, type NotificationStatus } from '../../domain/entities/notification.entity';
import {
type INotificationRepository,

View File

@@ -1,6 +1,6 @@
import { Injectable, type OnModuleInit } from '@nestjs/common';
import * as nodemailer from 'nodemailer';
import { type LoggerService } from '@modules/shared';
import { LoggerService } from '@modules/shared';
export interface SendEmailDto {
to: string;

View File

@@ -6,7 +6,7 @@ import {
messaging,
type ServiceAccount,
} from 'firebase-admin';
import { type LoggerService } from '@modules/shared';
import { LoggerService } from '@modules/shared';
export interface SendPushDto {
token: string;

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import { type LoggerService, type RedisService } from '@modules/shared';
import { LoggerService, RedisService } from '@modules/shared';
export interface SmsRateLimitOptions {
limit: number;

View File

@@ -1,5 +1,5 @@
import { HttpStatus, Injectable, type OnModuleInit } from '@nestjs/common';
import { DomainException, ErrorCode, type LoggerService } from '@modules/shared';
import { DomainException, ErrorCode, LoggerService } from '@modules/shared';
import type {
NotificationChannelPort,
SendChannelMessageDto,
@@ -8,7 +8,7 @@ import type {
import { type NotificationChannel } from '../../domain/value-objects/notification-channel.vo';
import {
type SmsRateLimitBucket,
type SmsRateLimiterService,
SmsRateLimiterService,
} from './sms-rate-limiter.service';
export interface SendSmsDto {

View File

@@ -1,5 +1,5 @@
import { Injectable, type OnModuleInit } from '@nestjs/common';
import { type LoggerService } from '@modules/shared';
import { LoggerService } from '@modules/shared';
export interface SendZaloOaDto {
/** Zalo user ID (follower UID from OA) */

View File

@@ -20,8 +20,8 @@ import {
NOTIFICATION_PREFERENCE_REPOSITORY,
type INotificationPreferenceRepository,
} from '../../domain';
import { type TemplateService } from '../../infrastructure/services/template.service';
import { type NotificationsGateway } from '../gateways/notifications.gateway';
import { TemplateService } from '../../infrastructure/services/template.service';
import { NotificationsGateway } from '../gateways/notifications.gateway';
class UpdatePreferenceDto {
@ApiProperty({ enum: PrismaChannel, description: 'Notification channel' })