fix: production readiness — resolve build, lint, and code quality issues
- Fix Next.js build failure: remove duplicate route at (dashboard)/listings/[id] that conflicted with (public)/listings/[id] (same URL path in two route groups) - Fix 772 ESLint errors: auto-fix import ordering (import-x/order), remove unused imports/variables, convert empty interfaces to type aliases, replace require() with ESM imports, fix consistent-type-imports violations - Add CLAUDE.md for developer onboarding documentation - All checks pass: 0 lint errors, typecheck clean, 230 tests passing, build success Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { type INestApplication, ValidationPipe } from '@nestjs/common';
|
||||
import { CqrsModule } from '@nestjs/cqrs';
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import request from 'supertest';
|
||||
import { AuthModule } from '../auth.module';
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
||||
import { SharedModule } from '@modules/shared/shared.module';
|
||||
import { AuthModule } from '../auth.module';
|
||||
|
||||
describe('Auth Controller (Integration)', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||
import { type TokenService, type TokenPair } from '../../../infrastructure/services/token.service';
|
||||
import { LoginUserCommand } from './login-user.command';
|
||||
import { TokenService, type TokenPair } from '../../../infrastructure/services/token.service';
|
||||
|
||||
@CommandHandler(LoginUserCommand)
|
||||
export class LoginUserHandler implements ICommandHandler<LoginUserCommand> {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||
import { UnauthorizedException } from '@modules/shared/domain/domain-exception';
|
||||
import { RefreshTokenCommand } from './refresh-token.command';
|
||||
import { TokenService, type TokenPair } from '../../../infrastructure/services/token.service';
|
||||
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
||||
import { type TokenService, type TokenPair } from '../../../infrastructure/services/token.service';
|
||||
import { RefreshTokenCommand } from './refresh-token.command';
|
||||
|
||||
@CommandHandler(RefreshTokenCommand)
|
||||
export class RefreshTokenHandler implements ICommandHandler<RefreshTokenCommand> {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { ConflictException, ValidationException } from '@modules/shared/domain/domain-exception';
|
||||
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||
import { createId } from '@paralleldrive/cuid2';
|
||||
import { RegisterUserCommand } from './register-user.command';
|
||||
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
||||
import { ConflictException, ValidationException } from '@modules/shared/domain/domain-exception';
|
||||
import { UserEntity } from '../../../domain/entities/user.entity';
|
||||
import { Phone } from '../../../domain/value-objects/phone.vo';
|
||||
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
||||
import { Email } from '../../../domain/value-objects/email.vo';
|
||||
import { HashedPassword } from '../../../domain/value-objects/hashed-password.vo';
|
||||
import { TokenService, type TokenPair } from '../../../infrastructure/services/token.service';
|
||||
import { Phone } from '../../../domain/value-objects/phone.vo';
|
||||
import { type TokenService, type TokenPair } from '../../../infrastructure/services/token.service';
|
||||
import { RegisterUserCommand } from './register-user.command';
|
||||
|
||||
@CommandHandler(RegisterUserCommand)
|
||||
export class RegisterUserHandler implements ICommandHandler<RegisterUserCommand> {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
||||
import { CacheService, CachePrefix } from '@modules/shared/infrastructure/cache.service';
|
||||
import { VerifyKycCommand } from './verify-kyc.command';
|
||||
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
||||
import { VerifyKycCommand } from './verify-kyc.command';
|
||||
|
||||
@CommandHandler(VerifyKycCommand)
|
||||
export class VerifyKycHandler implements ICommandHandler<VerifyKycCommand> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
||||
import { GetAgentByUserIdQuery } from './get-agent-by-user-id.query';
|
||||
|
||||
export interface AgentDto {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
||||
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared/infrastructure/cache.service';
|
||||
import { GetProfileQuery } from './get-profile.query';
|
||||
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
||||
import { GetProfileQuery } from './get-profile.query';
|
||||
|
||||
export interface UserProfileDto {
|
||||
id: string;
|
||||
|
||||
@@ -2,27 +2,19 @@ import { Module } from '@nestjs/common';
|
||||
import { CqrsModule } from '@nestjs/cqrs';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
|
||||
// Domain
|
||||
import { USER_REPOSITORY } from './domain/repositories/user.repository';
|
||||
import { REFRESH_TOKEN_REPOSITORY } from './domain/repositories/refresh-token.repository';
|
||||
|
||||
// Infrastructure
|
||||
import { PrismaUserRepository } from './infrastructure/repositories/prisma-user.repository';
|
||||
import { PrismaRefreshTokenRepository } from './infrastructure/repositories/prisma-refresh-token.repository';
|
||||
import { JwtStrategy } from './infrastructure/strategies/jwt.strategy';
|
||||
import { LocalStrategy } from './infrastructure/strategies/local.strategy';
|
||||
import { TokenService } from './infrastructure/services/token.service';
|
||||
|
||||
// Application
|
||||
import { RegisterUserHandler } from './application/commands/register-user/register-user.handler';
|
||||
import { LoginUserHandler } from './application/commands/login-user/login-user.handler';
|
||||
import { RefreshTokenHandler } from './application/commands/refresh-token/refresh-token.handler';
|
||||
import { RegisterUserHandler } from './application/commands/register-user/register-user.handler';
|
||||
import { VerifyKycHandler } from './application/commands/verify-kyc/verify-kyc.handler';
|
||||
import { GetProfileHandler } from './application/queries/get-profile/get-profile.handler';
|
||||
import { GetAgentByUserIdHandler } from './application/queries/get-agent-by-user-id/get-agent-by-user-id.handler';
|
||||
|
||||
// Presentation
|
||||
import { GetProfileHandler } from './application/queries/get-profile/get-profile.handler';
|
||||
import { REFRESH_TOKEN_REPOSITORY } from './domain/repositories/refresh-token.repository';
|
||||
import { USER_REPOSITORY } from './domain/repositories/user.repository';
|
||||
import { PrismaRefreshTokenRepository } from './infrastructure/repositories/prisma-refresh-token.repository';
|
||||
import { PrismaUserRepository } from './infrastructure/repositories/prisma-user.repository';
|
||||
import { TokenService } from './infrastructure/services/token.service';
|
||||
import { JwtStrategy } from './infrastructure/strategies/jwt.strategy';
|
||||
import { LocalStrategy } from './infrastructure/strategies/local.strategy';
|
||||
import { AuthController } from './presentation/controllers/auth.controller';
|
||||
|
||||
const CommandHandlers = [
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { UserEntity } from '../entities/user.entity';
|
||||
import { Phone } from '../value-objects/phone.vo';
|
||||
import { HashedPassword } from '../value-objects/hashed-password.vo';
|
||||
import { Email } from '../value-objects/email.vo';
|
||||
import { UserRegisteredEvent } from '../events/user-registered.event';
|
||||
import { Email } from '../value-objects/email.vo';
|
||||
import { HashedPassword } from '../value-objects/hashed-password.vo';
|
||||
import { Phone } from '../value-objects/phone.vo';
|
||||
|
||||
describe('UserEntity', () => {
|
||||
let phone: Phone;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { AggregateRoot } from '@modules/shared/domain/aggregate-root';
|
||||
import { type UserRole, type KYCStatus } from '@prisma/client';
|
||||
import { AggregateRoot } from '@modules/shared/domain/aggregate-root';
|
||||
import { UserRegisteredEvent } from '../events/user-registered.event';
|
||||
import { type Email } from '../value-objects/email.vo';
|
||||
import { type Phone } from '../value-objects/phone.vo';
|
||||
import { type HashedPassword } from '../value-objects/hashed-password.vo';
|
||||
import { type Phone } from '../value-objects/phone.vo';
|
||||
|
||||
export interface UserProps {
|
||||
email: Email | null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type DomainEvent } from '@modules/shared/domain/domain-event';
|
||||
import { type UserRole } from '@prisma/client';
|
||||
import { type DomainEvent } from '@modules/shared/domain/domain-event';
|
||||
|
||||
export class UserRegisteredEvent implements DomainEvent {
|
||||
readonly eventName = 'user.registered';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ValueObject } from '@modules/shared/domain/value-object';
|
||||
import { Result } from '@modules/shared/domain/result';
|
||||
import { ValueObject } from '@modules/shared/domain/value-object';
|
||||
|
||||
interface EmailProps {
|
||||
value: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ValueObject } from '@modules/shared/domain/value-object';
|
||||
import { Result } from '@modules/shared/domain/result';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import { Result } from '@modules/shared/domain/result';
|
||||
import { ValueObject } from '@modules/shared/domain/value-object';
|
||||
|
||||
interface HashedPasswordProps {
|
||||
value: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ValueObject } from '@modules/shared/domain/value-object';
|
||||
import { Result } from '@modules/shared/domain/result';
|
||||
import { ValueObject } from '@modules/shared/domain/value-object';
|
||||
import { isValidVietnamPhone, normalizeVietnamPhone } from '@modules/shared/utils/vietnam-phone.validator';
|
||||
|
||||
interface PhoneProps {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
||||
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
||||
import {
|
||||
type IRefreshTokenRepository,
|
||||
type RefreshTokenRecord,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
||||
import { type User as PrismaUser } from '@prisma/client';
|
||||
import { type IUserRepository } from '../../domain/repositories/user.repository';
|
||||
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';
|
||||
import { Email } from '../../domain/value-objects/email.vo';
|
||||
import { Phone } from '../../domain/value-objects/phone.vo';
|
||||
import { HashedPassword } from '../../domain/value-objects/hashed-password.vo';
|
||||
import { Phone } from '../../domain/value-objects/phone.vo';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaUserRepository implements IUserRepository {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { randomBytes, createHash } from 'crypto';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { type JwtService } from '@nestjs/jwt';
|
||||
import {
|
||||
REFRESH_TOKEN_REPOSITORY,
|
||||
IRefreshTokenRepository,
|
||||
type IRefreshTokenRepository,
|
||||
} from '../../domain/repositories/refresh-token.repository';
|
||||
|
||||
export interface JwtPayload {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import type { Request } from 'express';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { type JwtPayload } from '../services/token.service';
|
||||
|
||||
function extractJwtFromCookieOrHeader(req: Request): string | null {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Strategy } from 'passport-local';
|
||||
import { USER_REPOSITORY, type IUserRepository } from '../../domain/repositories/user.repository';
|
||||
import { normalizeVietnamPhone } from '@modules/shared/utils/vietnam-phone.validator';
|
||||
import { USER_REPOSITORY, type IUserRepository } from '../../domain/repositories/user.repository';
|
||||
|
||||
@Injectable()
|
||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||
|
||||
@@ -9,28 +9,28 @@ import {
|
||||
UnauthorizedException,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { type CommandBus, type QueryBus } from '@nestjs/cqrs';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiBody } from '@nestjs/swagger';
|
||||
import { Throttle } from '@nestjs/throttler';
|
||||
import { CommandBus, QueryBus } from '@nestjs/cqrs';
|
||||
import type { Request, Response } from 'express';
|
||||
import { RegisterUserCommand } from '../../application/commands/register-user/register-user.command';
|
||||
import { LoginUserCommand } from '../../application/commands/login-user/login-user.command';
|
||||
import { RefreshTokenCommand } from '../../application/commands/refresh-token/refresh-token.command';
|
||||
import { RegisterUserCommand } from '../../application/commands/register-user/register-user.command';
|
||||
import { VerifyKycCommand } from '../../application/commands/verify-kyc/verify-kyc.command';
|
||||
import { GetProfileQuery } from '../../application/queries/get-profile/get-profile.query';
|
||||
import { type AgentDto } from '../../application/queries/get-agent-by-user-id/get-agent-by-user-id.handler';
|
||||
import { GetAgentByUserIdQuery } from '../../application/queries/get-agent-by-user-id/get-agent-by-user-id.query';
|
||||
import { RegisterDto } from '../dto/register.dto';
|
||||
import { type UserProfileDto } from '../../application/queries/get-profile/get-profile.handler';
|
||||
import { GetProfileQuery } from '../../application/queries/get-profile/get-profile.query';
|
||||
import { type TokenService, type JwtPayload, type TokenPair } from '../../infrastructure/services/token.service';
|
||||
import { CurrentUser } from '../decorators/current-user.decorator';
|
||||
import { Roles } from '../decorators/roles.decorator';
|
||||
import { LoginDto } from '../dto/login.dto';
|
||||
import { RefreshTokenDto } from '../dto/refresh-token.dto';
|
||||
import { VerifyKycDto } from '../dto/verify-kyc.dto';
|
||||
import { type RefreshTokenDto } from '../dto/refresh-token.dto';
|
||||
import { type RegisterDto } from '../dto/register.dto';
|
||||
import { type VerifyKycDto } from '../dto/verify-kyc.dto';
|
||||
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
||||
import { LocalAuthGuard } from '../guards/local-auth.guard';
|
||||
import { RolesGuard } from '../guards/roles.guard';
|
||||
import { CurrentUser } from '../decorators/current-user.decorator';
|
||||
import { Roles } from '../decorators/roles.decorator';
|
||||
import { TokenService, type JwtPayload, type TokenPair } from '../../infrastructure/services/token.service';
|
||||
import { type UserProfileDto } from '../../application/queries/get-profile/get-profile.handler';
|
||||
import { type AgentDto } from '../../application/queries/get-agent-by-user-id/get-agent-by-user-id.handler';
|
||||
|
||||
const IS_PRODUCTION = process.env['NODE_ENV'] === 'production';
|
||||
const ACCESS_TOKEN_MAX_AGE = 15 * 60 * 1000; // 15 minutes
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString } from 'class-validator';
|
||||
|
||||
export class LoginDto {
|
||||
@ApiProperty({ example: '0901234567' })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class RefreshTokenDto {
|
||||
@ApiPropertyOptional({ description: 'JWT refresh token (optional if sent via cookie)' })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IsString, IsOptional, IsEmail, MinLength } from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsString, IsOptional, IsEmail, MinLength } from 'class-validator';
|
||||
|
||||
export class RegisterDto {
|
||||
@ApiProperty({ example: '0901234567', description: 'Phone number' })
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IsEnum, IsOptional, IsObject } from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { KYCStatus } from '@prisma/client';
|
||||
import { IsEnum, IsOptional, IsObject } from 'class-validator';
|
||||
|
||||
export class VerifyKycDto {
|
||||
@ApiProperty({ enum: KYCStatus, description: 'New KYC status' })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable, Logger, type CanActivate, type ExecutionContext } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { type Reflector } from '@nestjs/core';
|
||||
import { type UserRole } from '@prisma/client';
|
||||
import { ROLES_KEY } from '../decorators/roles.decorator';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user