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:
83
CLAUDE.md
Normal file
83
CLAUDE.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# GoodGo Platform
|
||||||
|
|
||||||
|
Vietnamese real estate platform — monorepo powered by pnpm workspaces + Turborepo.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
pnpm db:generate # Generate Prisma client
|
||||||
|
pnpm db:migrate:dev # Run migrations (needs PostgreSQL 16 + PostGIS)
|
||||||
|
pnpm db:seed # Seed sample data (users, listings, districts)
|
||||||
|
pnpm dev # Start all apps (API :3001, Web :3000)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- **apps/api** — NestJS backend (CQRS, DDD, clean architecture)
|
||||||
|
- **apps/web** — Next.js 14 frontend (App Router, Tailwind, Zustand)
|
||||||
|
- **libs/mcp-servers** — MCP tool server library
|
||||||
|
- **prisma/** — Schema, migrations, seed scripts
|
||||||
|
- **e2e/** — Playwright E2E tests (API + Web projects)
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `pnpm lint` | ESLint (auto-fixable with `--fix`) |
|
||||||
|
| `pnpm typecheck` | TypeScript type checking |
|
||||||
|
| `pnpm test` | Unit tests via Vitest (API only) |
|
||||||
|
| `pnpm build` | Production build (all packages) |
|
||||||
|
| `pnpm test:e2e` | Playwright E2E tests |
|
||||||
|
| `pnpm db:studio` | Prisma Studio GUI |
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Runtime**: Node.js >= 22, pnpm 10
|
||||||
|
- **Backend**: NestJS, Prisma ORM, PostgreSQL 16 + PostGIS, Redis
|
||||||
|
- **Frontend**: Next.js 14, React 18, Tailwind CSS 3, Zustand, Mapbox GL
|
||||||
|
- **Testing**: Vitest (unit), Playwright (E2E)
|
||||||
|
- **CI**: GitHub Actions (lint → typecheck → test → build)
|
||||||
|
|
||||||
|
## Project Structure (API)
|
||||||
|
|
||||||
|
```
|
||||||
|
apps/api/src/modules/
|
||||||
|
auth/ — Authentication (JWT, OAuth, refresh tokens, CSRF)
|
||||||
|
listings/ — Property listings CRUD
|
||||||
|
payments/ — VNPay payment integration
|
||||||
|
subscriptions/ — Plans, quotas, usage tracking
|
||||||
|
admin/ — Moderation, KYC, user management
|
||||||
|
analytics/ — Market data, heatmaps, price trends
|
||||||
|
search/ — Geo search, full-text search (Typesense)
|
||||||
|
notifications/ — Email, push (FCM), in-app notifications
|
||||||
|
metrics/ — Prometheus metrics
|
||||||
|
mcp/ — MCP tool server endpoints
|
||||||
|
shared/ — Domain primitives, guards, pipes, logging
|
||||||
|
```
|
||||||
|
|
||||||
|
Each module follows DDD layers: `domain/` → `application/` → `infrastructure/` → `presentation/`.
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
- PostgreSQL 16 with PostGIS extension for geospatial queries
|
||||||
|
- 22 models (User, Property, Listing, Payment, Subscription, etc.)
|
||||||
|
- Migrations in `prisma/migrations/`
|
||||||
|
- Seed data covers: users, agents, Ho Chi Minh City districts/wards, sample properties, subscription plans
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Required in `.env`:
|
||||||
|
- `DATABASE_URL` — PostgreSQL connection string
|
||||||
|
- `JWT_SECRET`, `JWT_REFRESH_SECRET` — Auth tokens
|
||||||
|
- `VNPAY_*` — Payment gateway config
|
||||||
|
- `MAPBOX_TOKEN` — Map rendering (frontend)
|
||||||
|
- `REDIS_URL` — Cache layer (optional for dev)
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Import order enforced by eslint-plugin-import-x (external → internal → relative)
|
||||||
|
- Path aliases: `@modules/*` in API, `@/*` in Web
|
||||||
|
- Vietnamese UI text throughout (property types, districts, currency in VND)
|
||||||
|
- All handlers return typed `Result<T>` or throw `DomainException`
|
||||||
|
- Commit messages follow conventional commits
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
import { SharedModule } from '@modules/shared';
|
|
||||||
import { AuthModule } from '@modules/auth';
|
|
||||||
import { ListingsModule } from '@modules/listings';
|
|
||||||
import { SearchModule } from '@modules/search';
|
|
||||||
import { NotificationsModule } from '@modules/notifications';
|
|
||||||
import { PaymentsModule } from '@modules/payments';
|
|
||||||
import { SubscriptionsModule } from '@modules/subscriptions';
|
|
||||||
import { AdminModule } from '@modules/admin';
|
|
||||||
import { AnalyticsModule } from '@modules/analytics';
|
|
||||||
import { MetricsModule } from '@modules/metrics';
|
|
||||||
import { McpIntegrationModule } from '@modules/mcp';
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { APP_GUARD } from '@nestjs/core';
|
import { APP_GUARD } from '@nestjs/core';
|
||||||
import { CqrsModule } from '@nestjs/cqrs';
|
import { CqrsModule } from '@nestjs/cqrs';
|
||||||
import { ThrottlerModule } from '@nestjs/throttler';
|
import { ThrottlerModule } from '@nestjs/throttler';
|
||||||
|
import { AdminModule } from '@modules/admin';
|
||||||
|
import { AnalyticsModule } from '@modules/analytics';
|
||||||
|
import { AuthModule } from '@modules/auth';
|
||||||
|
import { ListingsModule } from '@modules/listings';
|
||||||
|
import { McpIntegrationModule } from '@modules/mcp';
|
||||||
|
import { MetricsModule } from '@modules/metrics';
|
||||||
|
import { NotificationsModule } from '@modules/notifications';
|
||||||
|
import { PaymentsModule } from '@modules/payments';
|
||||||
|
import { SearchModule } from '@modules/search';
|
||||||
|
import { SharedModule } from '@modules/shared';
|
||||||
import { ThrottlerBehindProxyGuard } from '@modules/shared/infrastructure/guards/throttler-behind-proxy.guard';
|
import { ThrottlerBehindProxyGuard } from '@modules/shared/infrastructure/guards/throttler-behind-proxy.guard';
|
||||||
|
import { SubscriptionsModule } from '@modules/subscriptions';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
|
||||||
import { ValidationPipe } from '@nestjs/common';
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
import { LoggerService, validateEnv } from '@modules/shared';
|
|
||||||
import cookieParser from 'cookie-parser';
|
import cookieParser from 'cookie-parser';
|
||||||
import helmet from 'helmet';
|
import helmet from 'helmet';
|
||||||
|
import { LoggerService, validateEnv } from '@modules/shared';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
|
|||||||
@@ -3,32 +3,22 @@ import { CqrsModule } from '@nestjs/cqrs';
|
|||||||
import { AuthModule } from '@modules/auth';
|
import { AuthModule } from '@modules/auth';
|
||||||
import { ListingsModule } from '@modules/listings';
|
import { ListingsModule } from '@modules/listings';
|
||||||
import { SubscriptionsModule } from '@modules/subscriptions';
|
import { SubscriptionsModule } from '@modules/subscriptions';
|
||||||
|
|
||||||
// Domain
|
|
||||||
import { ADMIN_QUERY_REPOSITORY } from './domain/repositories/admin-query.repository';
|
|
||||||
|
|
||||||
// Infrastructure
|
|
||||||
import { PrismaAdminQueryRepository } from './infrastructure/repositories/prisma-admin-query.repository';
|
|
||||||
|
|
||||||
// Application — Commands
|
|
||||||
import { ApproveListingHandler } from './application/commands/approve-listing/approve-listing.handler';
|
|
||||||
import { RejectListingHandler } from './application/commands/reject-listing/reject-listing.handler';
|
|
||||||
import { BanUserHandler } from './application/commands/ban-user/ban-user.handler';
|
|
||||||
import { AdjustSubscriptionHandler } from './application/commands/adjust-subscription/adjust-subscription.handler';
|
import { AdjustSubscriptionHandler } from './application/commands/adjust-subscription/adjust-subscription.handler';
|
||||||
import { UpdateUserStatusHandler } from './application/commands/update-user-status/update-user-status.handler';
|
|
||||||
import { ApproveKycHandler } from './application/commands/approve-kyc/approve-kyc.handler';
|
import { ApproveKycHandler } from './application/commands/approve-kyc/approve-kyc.handler';
|
||||||
import { RejectKycHandler } from './application/commands/reject-kyc/reject-kyc.handler';
|
import { ApproveListingHandler } from './application/commands/approve-listing/approve-listing.handler';
|
||||||
|
import { BanUserHandler } from './application/commands/ban-user/ban-user.handler';
|
||||||
import { BulkModerateListingsHandler } from './application/commands/bulk-moderate-listings/bulk-moderate-listings.handler';
|
import { BulkModerateListingsHandler } from './application/commands/bulk-moderate-listings/bulk-moderate-listings.handler';
|
||||||
|
import { RejectKycHandler } from './application/commands/reject-kyc/reject-kyc.handler';
|
||||||
// Application — Queries
|
import { RejectListingHandler } from './application/commands/reject-listing/reject-listing.handler';
|
||||||
import { GetModerationQueueHandler } from './application/queries/get-moderation-queue/get-moderation-queue.handler';
|
import { UpdateUserStatusHandler } from './application/commands/update-user-status/update-user-status.handler';
|
||||||
import { GetDashboardStatsHandler } from './application/queries/get-dashboard-stats/get-dashboard-stats.handler';
|
import { GetDashboardStatsHandler } from './application/queries/get-dashboard-stats/get-dashboard-stats.handler';
|
||||||
import { GetRevenueStatsHandler } from './application/queries/get-revenue-stats/get-revenue-stats.handler';
|
|
||||||
import { GetUsersHandler } from './application/queries/get-users/get-users.handler';
|
|
||||||
import { GetUserDetailHandler } from './application/queries/get-user-detail/get-user-detail.handler';
|
|
||||||
import { GetKycQueueHandler } from './application/queries/get-kyc-queue/get-kyc-queue.handler';
|
import { GetKycQueueHandler } from './application/queries/get-kyc-queue/get-kyc-queue.handler';
|
||||||
|
import { GetModerationQueueHandler } from './application/queries/get-moderation-queue/get-moderation-queue.handler';
|
||||||
// Presentation
|
import { GetRevenueStatsHandler } from './application/queries/get-revenue-stats/get-revenue-stats.handler';
|
||||||
|
import { GetUserDetailHandler } from './application/queries/get-user-detail/get-user-detail.handler';
|
||||||
|
import { GetUsersHandler } from './application/queries/get-users/get-users.handler';
|
||||||
|
import { ADMIN_QUERY_REPOSITORY } from './domain/repositories/admin-query.repository';
|
||||||
|
import { PrismaAdminQueryRepository } from './infrastructure/repositories/prisma-admin-query.repository';
|
||||||
import { AdminController } from './presentation/controllers/admin.controller';
|
import { AdminController } from './presentation/controllers/admin.controller';
|
||||||
|
|
||||||
const CommandHandlers = [
|
const CommandHandlers = [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AdjustSubscriptionHandler } from '../commands/adjust-subscription/adjust-subscription.handler';
|
|
||||||
import { AdjustSubscriptionCommand } from '../commands/adjust-subscription/adjust-subscription.command';
|
|
||||||
import { type ISubscriptionRepository } from '@modules/subscriptions/domain/repositories/subscription.repository';
|
|
||||||
import { SubscriptionEntity } from '@modules/subscriptions/domain/entities/subscription.entity';
|
import { SubscriptionEntity } from '@modules/subscriptions/domain/entities/subscription.entity';
|
||||||
|
import { type ISubscriptionRepository } from '@modules/subscriptions/domain/repositories/subscription.repository';
|
||||||
|
import { AdjustSubscriptionCommand } from '../commands/adjust-subscription/adjust-subscription.command';
|
||||||
|
import { AdjustSubscriptionHandler } from '../commands/adjust-subscription/adjust-subscription.handler';
|
||||||
|
|
||||||
describe('AdjustSubscriptionHandler', () => {
|
describe('AdjustSubscriptionHandler', () => {
|
||||||
let handler: AdjustSubscriptionHandler;
|
let handler: AdjustSubscriptionHandler;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { ApproveKycHandler } from '../commands/approve-kyc/approve-kyc.handler';
|
|
||||||
import { ApproveKycCommand } from '../commands/approve-kyc/approve-kyc.command';
|
|
||||||
import { type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
|
||||||
import { UserEntity } from '@modules/auth/domain/entities/user.entity';
|
import { UserEntity } from '@modules/auth/domain/entities/user.entity';
|
||||||
import { Phone } from '@modules/auth/domain/value-objects/phone.vo';
|
import { type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
||||||
import { HashedPassword } from '@modules/auth/domain/value-objects/hashed-password.vo';
|
import { HashedPassword } from '@modules/auth/domain/value-objects/hashed-password.vo';
|
||||||
|
import { Phone } from '@modules/auth/domain/value-objects/phone.vo';
|
||||||
|
import { ApproveKycCommand } from '../commands/approve-kyc/approve-kyc.command';
|
||||||
|
import { ApproveKycHandler } from '../commands/approve-kyc/approve-kyc.handler';
|
||||||
|
|
||||||
async function createUser(kycStatus = 'PENDING' as any): Promise<UserEntity> {
|
async function createUser(kycStatus = 'PENDING' as any): Promise<UserEntity> {
|
||||||
const phone = Phone.create('0901234567').unwrap();
|
const phone = Phone.create('0901234567').unwrap();
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ApproveListingHandler } from '../commands/approve-listing/approve-listing.handler';
|
|
||||||
import { ApproveListingCommand } from '../commands/approve-listing/approve-listing.command';
|
|
||||||
import { type IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
|
|
||||||
import { ListingEntity } from '@modules/listings/domain/entities/listing.entity';
|
import { ListingEntity } from '@modules/listings/domain/entities/listing.entity';
|
||||||
|
import { type IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
|
||||||
import { Price } from '@modules/listings/domain/value-objects/price.vo';
|
import { Price } from '@modules/listings/domain/value-objects/price.vo';
|
||||||
|
import { ApproveListingCommand } from '../commands/approve-listing/approve-listing.command';
|
||||||
|
import { ApproveListingHandler } from '../commands/approve-listing/approve-listing.handler';
|
||||||
|
|
||||||
function createPendingListing(id = 'listing-1'): ListingEntity {
|
function createPendingListing(id = 'listing-1'): ListingEntity {
|
||||||
const price = Price.create(1_000_000_000n).unwrap();
|
const price = Price.create(1_000_000_000n).unwrap();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { BanUserHandler } from '../commands/ban-user/ban-user.handler';
|
|
||||||
import { BanUserCommand } from '../commands/ban-user/ban-user.command';
|
|
||||||
import { type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
|
||||||
import { UserEntity } from '@modules/auth/domain/entities/user.entity';
|
import { UserEntity } from '@modules/auth/domain/entities/user.entity';
|
||||||
import { Phone } from '@modules/auth/domain/value-objects/phone.vo';
|
import { type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
||||||
import { HashedPassword } from '@modules/auth/domain/value-objects/hashed-password.vo';
|
import { HashedPassword } from '@modules/auth/domain/value-objects/hashed-password.vo';
|
||||||
|
import { Phone } from '@modules/auth/domain/value-objects/phone.vo';
|
||||||
|
import { BanUserCommand } from '../commands/ban-user/ban-user.command';
|
||||||
|
import { BanUserHandler } from '../commands/ban-user/ban-user.handler';
|
||||||
|
|
||||||
async function createUser(role = 'BUYER' as any, isActive = true): Promise<UserEntity> {
|
async function createUser(role = 'BUYER' as any, isActive = true): Promise<UserEntity> {
|
||||||
const phone = Phone.create('0901234567').unwrap();
|
const phone = Phone.create('0901234567').unwrap();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BulkModerateListingsHandler } from '../commands/bulk-moderate-listings/bulk-moderate-listings.handler';
|
|
||||||
import { BulkModerateListingsCommand } from '../commands/bulk-moderate-listings/bulk-moderate-listings.command';
|
|
||||||
import { type IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
|
import { type IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
|
||||||
|
import { BulkModerateListingsCommand } from '../commands/bulk-moderate-listings/bulk-moderate-listings.command';
|
||||||
|
import { BulkModerateListingsHandler } from '../commands/bulk-moderate-listings/bulk-moderate-listings.handler';
|
||||||
|
|
||||||
function createMockListing(id: string, status = 'PENDING_REVIEW') {
|
function createMockListing(id: string, status = 'PENDING_REVIEW') {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { type IAdminQueryRepository } from '../../domain/repositories/admin-query.repository';
|
||||||
import { GetDashboardStatsHandler } from '../queries/get-dashboard-stats/get-dashboard-stats.handler';
|
import { GetDashboardStatsHandler } from '../queries/get-dashboard-stats/get-dashboard-stats.handler';
|
||||||
import { GetDashboardStatsQuery } from '../queries/get-dashboard-stats/get-dashboard-stats.query';
|
import { GetDashboardStatsQuery } from '../queries/get-dashboard-stats/get-dashboard-stats.query';
|
||||||
import { type IAdminQueryRepository } from '../../domain/repositories/admin-query.repository';
|
|
||||||
|
|
||||||
describe('GetDashboardStatsHandler', () => {
|
describe('GetDashboardStatsHandler', () => {
|
||||||
let handler: GetDashboardStatsHandler;
|
let handler: GetDashboardStatsHandler;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { type KycQueueResult } from '../../domain/repositories/admin-query.repository';
|
||||||
import { GetKycQueueHandler } from '../queries/get-kyc-queue/get-kyc-queue.handler';
|
import { GetKycQueueHandler } from '../queries/get-kyc-queue/get-kyc-queue.handler';
|
||||||
import { GetKycQueueQuery } from '../queries/get-kyc-queue/get-kyc-queue.query';
|
import { GetKycQueueQuery } from '../queries/get-kyc-queue/get-kyc-queue.query';
|
||||||
import { type KycQueueResult } from '../../domain/repositories/admin-query.repository';
|
|
||||||
|
|
||||||
describe('GetKycQueueHandler', () => {
|
describe('GetKycQueueHandler', () => {
|
||||||
let handler: GetKycQueueHandler;
|
let handler: GetKycQueueHandler;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { type IAdminQueryRepository } from '../../domain/repositories/admin-query.repository';
|
||||||
import { GetModerationQueueHandler } from '../queries/get-moderation-queue/get-moderation-queue.handler';
|
import { GetModerationQueueHandler } from '../queries/get-moderation-queue/get-moderation-queue.handler';
|
||||||
import { GetModerationQueueQuery } from '../queries/get-moderation-queue/get-moderation-queue.query';
|
import { GetModerationQueueQuery } from '../queries/get-moderation-queue/get-moderation-queue.query';
|
||||||
import { type IAdminQueryRepository } from '../../domain/repositories/admin-query.repository';
|
|
||||||
|
|
||||||
describe('GetModerationQueueHandler', () => {
|
describe('GetModerationQueueHandler', () => {
|
||||||
let handler: GetModerationQueueHandler;
|
let handler: GetModerationQueueHandler;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { type UserListResult } from '../../domain/repositories/admin-query.repository';
|
||||||
import { GetUsersHandler } from '../queries/get-users/get-users.handler';
|
import { GetUsersHandler } from '../queries/get-users/get-users.handler';
|
||||||
import { GetUsersQuery } from '../queries/get-users/get-users.query';
|
import { GetUsersQuery } from '../queries/get-users/get-users.query';
|
||||||
import { type IAdminQueryRepository, type UserListResult } from '../../domain/repositories/admin-query.repository';
|
|
||||||
|
|
||||||
describe('GetUsersHandler', () => {
|
describe('GetUsersHandler', () => {
|
||||||
let handler: GetUsersHandler;
|
let handler: GetUsersHandler;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { RejectKycHandler } from '../commands/reject-kyc/reject-kyc.handler';
|
|
||||||
import { RejectKycCommand } from '../commands/reject-kyc/reject-kyc.command';
|
|
||||||
import { type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
|
||||||
import { UserEntity } from '@modules/auth/domain/entities/user.entity';
|
import { UserEntity } from '@modules/auth/domain/entities/user.entity';
|
||||||
import { Phone } from '@modules/auth/domain/value-objects/phone.vo';
|
import { type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
||||||
import { HashedPassword } from '@modules/auth/domain/value-objects/hashed-password.vo';
|
import { HashedPassword } from '@modules/auth/domain/value-objects/hashed-password.vo';
|
||||||
|
import { Phone } from '@modules/auth/domain/value-objects/phone.vo';
|
||||||
|
import { RejectKycCommand } from '../commands/reject-kyc/reject-kyc.command';
|
||||||
|
import { RejectKycHandler } from '../commands/reject-kyc/reject-kyc.handler';
|
||||||
|
|
||||||
async function createUser(kycStatus = 'PENDING' as any): Promise<UserEntity> {
|
async function createUser(kycStatus = 'PENDING' as any): Promise<UserEntity> {
|
||||||
const phone = Phone.create('0901234567').unwrap();
|
const phone = Phone.create('0901234567').unwrap();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { UpdateUserStatusHandler } from '../commands/update-user-status/update-user-status.handler';
|
|
||||||
import { UpdateUserStatusCommand } from '../commands/update-user-status/update-user-status.command';
|
|
||||||
import { type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
|
||||||
import { UserEntity } from '@modules/auth/domain/entities/user.entity';
|
import { UserEntity } from '@modules/auth/domain/entities/user.entity';
|
||||||
import { Phone } from '@modules/auth/domain/value-objects/phone.vo';
|
import { type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
||||||
import { HashedPassword } from '@modules/auth/domain/value-objects/hashed-password.vo';
|
import { HashedPassword } from '@modules/auth/domain/value-objects/hashed-password.vo';
|
||||||
|
import { Phone } from '@modules/auth/domain/value-objects/phone.vo';
|
||||||
|
import { UpdateUserStatusCommand } from '../commands/update-user-status/update-user-status.command';
|
||||||
|
import { UpdateUserStatusHandler } from '../commands/update-user-status/update-user-status.handler';
|
||||||
|
|
||||||
async function createUser(role = 'BUYER' as any, isActive = true): Promise<UserEntity> {
|
async function createUser(role = 'BUYER' as any, isActive = true): Promise<UserEntity> {
|
||||||
const phone = Phone.create('0901234567').unwrap();
|
const phone = Phone.create('0901234567').unwrap();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared';
|
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { SUBSCRIPTION_REPOSITORY, type ISubscriptionRepository } from '@modules/subscriptions/domain/repositories/subscription.repository';
|
|
||||||
import { PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
|
||||||
import { type PlanTier } from '@prisma/client';
|
import { type PlanTier } from '@prisma/client';
|
||||||
|
import { NotFoundException, ValidationException } from '@modules/shared';
|
||||||
|
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
||||||
|
import { SUBSCRIPTION_REPOSITORY, type ISubscriptionRepository } from '@modules/subscriptions/domain/repositories/subscription.repository';
|
||||||
import { SubscriptionAdjustedEvent } from '../../../domain/events/subscription-adjusted.event';
|
import { SubscriptionAdjustedEvent } from '../../../domain/events/subscription-adjusted.event';
|
||||||
import { AdjustSubscriptionCommand } from './adjust-subscription.command';
|
import { AdjustSubscriptionCommand } from './adjust-subscription.command';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared';
|
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
||||||
|
import { NotFoundException, ValidationException } from '@modules/shared';
|
||||||
import { KycApprovedEvent } from '../../../domain/events/kyc-approved.event';
|
import { KycApprovedEvent } from '../../../domain/events/kyc-approved.event';
|
||||||
import { ApproveKycCommand } from './approve-kyc.command';
|
import { ApproveKycCommand } from './approve-kyc.command';
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared';
|
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
|
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
|
||||||
|
import { NotFoundException, ValidationException } from '@modules/shared';
|
||||||
import { ListingApprovedEvent } from '../../../domain/events/listing-approved.event';
|
import { ListingApprovedEvent } from '../../../domain/events/listing-approved.event';
|
||||||
import { ListingRejectedEvent } from '../../../domain/events/listing-rejected.event';
|
|
||||||
import { ApproveListingCommand } from './approve-listing.command';
|
import { ApproveListingCommand } from './approve-listing.command';
|
||||||
|
|
||||||
export interface ApproveListingResult {
|
export interface ApproveListingResult {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared';
|
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
||||||
|
import { NotFoundException, ValidationException } from '@modules/shared';
|
||||||
import { UserBannedEvent } from '../../../domain/events/user-banned.event';
|
import { UserBannedEvent } from '../../../domain/events/user-banned.event';
|
||||||
import { UserUnbannedEvent } from '../../../domain/events/user-unbanned.event';
|
import { UserUnbannedEvent } from '../../../domain/events/user-unbanned.event';
|
||||||
import { BanUserCommand } from './ban-user.command';
|
import { BanUserCommand } from './ban-user.command';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { ValidationException } from '@modules/shared';
|
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
|
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
|
||||||
|
import { ValidationException } from '@modules/shared';
|
||||||
import { ListingApprovedEvent } from '../../../domain/events/listing-approved.event';
|
import { ListingApprovedEvent } from '../../../domain/events/listing-approved.event';
|
||||||
import { ListingRejectedEvent } from '../../../domain/events/listing-rejected.event';
|
import { ListingRejectedEvent } from '../../../domain/events/listing-rejected.event';
|
||||||
import { BulkModerateListingsCommand } from './bulk-moderate-listings.command';
|
import { BulkModerateListingsCommand } from './bulk-moderate-listings.command';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared';
|
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
||||||
|
import { NotFoundException, ValidationException } from '@modules/shared';
|
||||||
import { KycRejectedEvent } from '../../../domain/events/kyc-rejected.event';
|
import { KycRejectedEvent } from '../../../domain/events/kyc-rejected.event';
|
||||||
import { RejectKycCommand } from './reject-kyc.command';
|
import { RejectKycCommand } from './reject-kyc.command';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared';
|
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
|
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
|
||||||
|
import { NotFoundException, ValidationException } from '@modules/shared';
|
||||||
import { ListingRejectedEvent } from '../../../domain/events/listing-rejected.event';
|
import { ListingRejectedEvent } from '../../../domain/events/listing-rejected.event';
|
||||||
import { RejectListingCommand } from './reject-listing.command';
|
import { RejectListingCommand } from './reject-listing.command';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared';
|
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth/domain/repositories/user.repository';
|
||||||
|
import { NotFoundException, ValidationException } from '@modules/shared';
|
||||||
import { UserBannedEvent } from '../../../domain/events/user-banned.event';
|
import { UserBannedEvent } from '../../../domain/events/user-banned.event';
|
||||||
import { UserUnbannedEvent } from '../../../domain/events/user-unbanned.event';
|
import { UserUnbannedEvent } from '../../../domain/events/user-unbanned.event';
|
||||||
import { UpdateUserStatusCommand } from './update-user-status.command';
|
import { UpdateUserStatusCommand } from './update-user-status.command';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type DashboardStats } from '../../../domain/repositories/admin-query.repository';
|
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type DashboardStats } from '../../../domain/repositories/admin-query.repository';
|
||||||
import { GetDashboardStatsQuery } from './get-dashboard-stats.query';
|
import { GetDashboardStatsQuery } from './get-dashboard-stats.query';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type KycQueueResult } from '../../../domain/repositories/admin-query.repository';
|
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type KycQueueResult } from '../../../domain/repositories/admin-query.repository';
|
||||||
import { GetKycQueueQuery } from './get-kyc-queue.query';
|
import { GetKycQueueQuery } from './get-kyc-queue.query';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type ModerationQueueResult } from '../../../domain/repositories/admin-query.repository';
|
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type ModerationQueueResult } from '../../../domain/repositories/admin-query.repository';
|
||||||
import { GetModerationQueueQuery } from './get-moderation-queue.query';
|
import { GetModerationQueueQuery } from './get-moderation-queue.query';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type RevenueStatsItem } from '../../../domain/repositories/admin-query.repository';
|
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type RevenueStatsItem } from '../../../domain/repositories/admin-query.repository';
|
||||||
import { GetRevenueStatsQuery } from './get-revenue-stats.query';
|
import { GetRevenueStatsQuery } from './get-revenue-stats.query';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { NotFoundException } from '@modules/shared';
|
import { NotFoundException } from '@modules/shared';
|
||||||
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type UserDetail } from '../../../domain/repositories/admin-query.repository';
|
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type UserDetail } from '../../../domain/repositories/admin-query.repository';
|
||||||
import { GetUserDetailQuery } from './get-user-detail.query';
|
import { GetUserDetailQuery } from './get-user-detail.query';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type UserListResult } from '../../../domain/repositories/admin-query.repository';
|
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type UserListResult } from '../../../domain/repositories/admin-query.repository';
|
||||||
import { GetUsersQuery } from './get-users.query';
|
import { GetUsersQuery } from './get-users.query';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
||||||
import {
|
import {
|
||||||
type IAdminQueryRepository,
|
type IAdminQueryRepository,
|
||||||
type ModerationQueueResult,
|
type ModerationQueueResult,
|
||||||
|
|||||||
@@ -8,48 +8,35 @@ import {
|
|||||||
Query,
|
Query,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { type CommandBus, type QueryBus } from '@nestjs/cqrs';
|
||||||
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery, ApiParam } from '@nestjs/swagger';
|
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery, ApiParam } from '@nestjs/swagger';
|
||||||
import { CommandBus, QueryBus } from '@nestjs/cqrs';
|
import { type JwtPayload } from '@modules/auth/infrastructure/services/token.service';
|
||||||
|
import { CurrentUser } from '@modules/auth/presentation/decorators/current-user.decorator';
|
||||||
|
import { Roles } from '@modules/auth/presentation/decorators/roles.decorator';
|
||||||
import { JwtAuthGuard } from '@modules/auth/presentation/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '@modules/auth/presentation/guards/jwt-auth.guard';
|
||||||
import { RolesGuard } from '@modules/auth/presentation/guards/roles.guard';
|
import { RolesGuard } from '@modules/auth/presentation/guards/roles.guard';
|
||||||
import { Roles } from '@modules/auth/presentation/decorators/roles.decorator';
|
|
||||||
import { CurrentUser } from '@modules/auth/presentation/decorators/current-user.decorator';
|
|
||||||
import { type JwtPayload } from '@modules/auth/infrastructure/services/token.service';
|
|
||||||
|
|
||||||
import { ApproveListingCommand } from '../../application/commands/approve-listing/approve-listing.command';
|
|
||||||
import { RejectListingCommand } from '../../application/commands/reject-listing/reject-listing.command';
|
|
||||||
import { BanUserCommand } from '../../application/commands/ban-user/ban-user.command';
|
|
||||||
import { AdjustSubscriptionCommand } from '../../application/commands/adjust-subscription/adjust-subscription.command';
|
import { AdjustSubscriptionCommand } from '../../application/commands/adjust-subscription/adjust-subscription.command';
|
||||||
import { UpdateUserStatusCommand } from '../../application/commands/update-user-status/update-user-status.command';
|
|
||||||
import { ApproveKycCommand } from '../../application/commands/approve-kyc/approve-kyc.command';
|
|
||||||
import { RejectKycCommand } from '../../application/commands/reject-kyc/reject-kyc.command';
|
|
||||||
import { BulkModerateListingsCommand } from '../../application/commands/bulk-moderate-listings/bulk-moderate-listings.command';
|
|
||||||
import { GetModerationQueueQuery } from '../../application/queries/get-moderation-queue/get-moderation-queue.query';
|
|
||||||
import { GetDashboardStatsQuery } from '../../application/queries/get-dashboard-stats/get-dashboard-stats.query';
|
|
||||||
import { GetRevenueStatsQuery } from '../../application/queries/get-revenue-stats/get-revenue-stats.query';
|
|
||||||
import { GetUsersQuery } from '../../application/queries/get-users/get-users.query';
|
|
||||||
import { GetUserDetailQuery } from '../../application/queries/get-user-detail/get-user-detail.query';
|
|
||||||
import { GetKycQueueQuery } from '../../application/queries/get-kyc-queue/get-kyc-queue.query';
|
|
||||||
|
|
||||||
import { ApproveListingDto } from '../dto/approve-listing.dto';
|
|
||||||
import { RejectListingDto } from '../dto/reject-listing.dto';
|
|
||||||
import { BanUserDto } from '../dto/ban-user.dto';
|
|
||||||
import { AdjustSubscriptionDto } from '../dto/adjust-subscription.dto';
|
|
||||||
import { RevenueStatsDto } from '../dto/revenue-stats.dto';
|
|
||||||
import { UpdateUserStatusDto } from '../dto/update-user-status.dto';
|
|
||||||
import { ApproveKycDto } from '../dto/approve-kyc.dto';
|
|
||||||
import { RejectKycDto } from '../dto/reject-kyc.dto';
|
|
||||||
import { BulkModerateDto } from '../dto/bulk-moderate.dto';
|
|
||||||
import { GetUsersQueryDto } from '../dto/get-users-query.dto';
|
|
||||||
|
|
||||||
import { type ApproveListingResult } from '../../application/commands/approve-listing/approve-listing.handler';
|
|
||||||
import { type RejectListingResult } from '../../application/commands/reject-listing/reject-listing.handler';
|
|
||||||
import { type BanUserResult } from '../../application/commands/ban-user/ban-user.handler';
|
|
||||||
import { type AdjustSubscriptionResult } from '../../application/commands/adjust-subscription/adjust-subscription.handler';
|
import { type AdjustSubscriptionResult } from '../../application/commands/adjust-subscription/adjust-subscription.handler';
|
||||||
import { type UpdateUserStatusResult } from '../../application/commands/update-user-status/update-user-status.handler';
|
import { ApproveKycCommand } from '../../application/commands/approve-kyc/approve-kyc.command';
|
||||||
import { type ApproveKycResult } from '../../application/commands/approve-kyc/approve-kyc.handler';
|
import { type ApproveKycResult } from '../../application/commands/approve-kyc/approve-kyc.handler';
|
||||||
import { type RejectKycResult } from '../../application/commands/reject-kyc/reject-kyc.handler';
|
import { ApproveListingCommand } from '../../application/commands/approve-listing/approve-listing.command';
|
||||||
|
import { type ApproveListingResult } from '../../application/commands/approve-listing/approve-listing.handler';
|
||||||
|
import { BanUserCommand } from '../../application/commands/ban-user/ban-user.command';
|
||||||
|
import { type BanUserResult } from '../../application/commands/ban-user/ban-user.handler';
|
||||||
|
import { BulkModerateListingsCommand } from '../../application/commands/bulk-moderate-listings/bulk-moderate-listings.command';
|
||||||
import { type BulkModerateResult } from '../../application/commands/bulk-moderate-listings/bulk-moderate-listings.handler';
|
import { type BulkModerateResult } from '../../application/commands/bulk-moderate-listings/bulk-moderate-listings.handler';
|
||||||
|
import { RejectKycCommand } from '../../application/commands/reject-kyc/reject-kyc.command';
|
||||||
|
import { type RejectKycResult } from '../../application/commands/reject-kyc/reject-kyc.handler';
|
||||||
|
import { RejectListingCommand } from '../../application/commands/reject-listing/reject-listing.command';
|
||||||
|
import { type RejectListingResult } from '../../application/commands/reject-listing/reject-listing.handler';
|
||||||
|
import { UpdateUserStatusCommand } from '../../application/commands/update-user-status/update-user-status.command';
|
||||||
|
import { type UpdateUserStatusResult } from '../../application/commands/update-user-status/update-user-status.handler';
|
||||||
|
import { GetDashboardStatsQuery } from '../../application/queries/get-dashboard-stats/get-dashboard-stats.query';
|
||||||
|
import { GetKycQueueQuery } from '../../application/queries/get-kyc-queue/get-kyc-queue.query';
|
||||||
|
import { GetModerationQueueQuery } from '../../application/queries/get-moderation-queue/get-moderation-queue.query';
|
||||||
|
import { GetRevenueStatsQuery } from '../../application/queries/get-revenue-stats/get-revenue-stats.query';
|
||||||
|
import { GetUserDetailQuery } from '../../application/queries/get-user-detail/get-user-detail.query';
|
||||||
|
import { GetUsersQuery } from '../../application/queries/get-users/get-users.query';
|
||||||
import {
|
import {
|
||||||
type ModerationQueueResult,
|
type ModerationQueueResult,
|
||||||
type DashboardStats,
|
type DashboardStats,
|
||||||
@@ -58,6 +45,16 @@ import {
|
|||||||
type UserDetail,
|
type UserDetail,
|
||||||
type KycQueueResult,
|
type KycQueueResult,
|
||||||
} from '../../domain/repositories/admin-query.repository';
|
} from '../../domain/repositories/admin-query.repository';
|
||||||
|
import { type AdjustSubscriptionDto } from '../dto/adjust-subscription.dto';
|
||||||
|
import { type ApproveKycDto } from '../dto/approve-kyc.dto';
|
||||||
|
import { type ApproveListingDto } from '../dto/approve-listing.dto';
|
||||||
|
import { type BanUserDto } from '../dto/ban-user.dto';
|
||||||
|
import { type BulkModerateDto } from '../dto/bulk-moderate.dto';
|
||||||
|
import { type GetUsersQueryDto } from '../dto/get-users-query.dto';
|
||||||
|
import { type RejectKycDto } from '../dto/reject-kyc.dto';
|
||||||
|
import { type RejectListingDto } from '../dto/reject-listing.dto';
|
||||||
|
import { type RevenueStatsDto } from '../dto/revenue-stats.dto';
|
||||||
|
import { type UpdateUserStatusDto } from '../dto/update-user-status.dto';
|
||||||
|
|
||||||
@ApiTags('admin')
|
@ApiTags('admin')
|
||||||
@ApiBearerAuth('JWT')
|
@ApiBearerAuth('JWT')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IsString, MinLength } from 'class-validator';
|
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsString, MinLength } from 'class-validator';
|
||||||
|
|
||||||
export class AdjustSubscriptionDto {
|
export class AdjustSubscriptionDto {
|
||||||
@ApiProperty({ description: 'ID of the user whose subscription to adjust', example: 'usr_abc123' })
|
@ApiProperty({ description: 'ID of the user whose subscription to adjust', example: 'usr_abc123' })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IsOptional, IsString } from 'class-validator';
|
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class ApproveKycDto {
|
export class ApproveKycDto {
|
||||||
@ApiProperty({ description: 'ID of the user whose KYC to approve', example: 'usr_abc123' })
|
@ApiProperty({ description: 'ID of the user whose KYC to approve', example: 'usr_abc123' })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IsOptional, IsString } from 'class-validator';
|
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class ApproveListingDto {
|
export class ApproveListingDto {
|
||||||
@ApiProperty({ description: 'ID of the listing to approve', example: 'lst_abc123' })
|
@ApiProperty({ description: 'ID of the listing to approve', example: 'lst_abc123' })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IsBoolean, IsOptional, IsString, MinLength } from 'class-validator';
|
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { IsBoolean, IsOptional, IsString, MinLength } from 'class-validator';
|
||||||
|
|
||||||
export class BanUserDto {
|
export class BanUserDto {
|
||||||
@ApiProperty({ description: 'ID of the user to ban/unban', example: 'usr_abc123' })
|
@ApiProperty({ description: 'ID of the user to ban/unban', example: 'usr_abc123' })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IsArray, IsIn, IsOptional, IsString, ArrayMaxSize, ArrayMinSize, MinLength } from 'class-validator';
|
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { IsArray, IsIn, IsOptional, IsString, ArrayMaxSize, ArrayMinSize, MinLength } from 'class-validator';
|
||||||
|
|
||||||
export class BulkModerateDto {
|
export class BulkModerateDto {
|
||||||
@ApiProperty({ description: 'Array of listing IDs to moderate (1-50)', example: ['lst_abc123', 'lst_def456'], minItems: 1, maxItems: 50 })
|
@ApiProperty({ description: 'Array of listing IDs to moderate (1-50)', example: ['lst_abc123', 'lst_def456'], minItems: 1, maxItems: 50 })
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IsOptional, IsString, IsIn, IsBoolean, IsInt, Min, Max } from 'class-validator';
|
|
||||||
import { Transform, Type } from 'class-transformer';
|
|
||||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { Transform, Type } from 'class-transformer';
|
||||||
|
import { IsOptional, IsString, IsIn, IsBoolean, IsInt, Min, Max } from 'class-validator';
|
||||||
|
|
||||||
export class GetUsersQueryDto {
|
export class GetUsersQueryDto {
|
||||||
@ApiPropertyOptional({ description: 'Page number', example: 1, minimum: 1 })
|
@ApiPropertyOptional({ description: 'Page number', example: 1, minimum: 1 })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IsString, MinLength } from 'class-validator';
|
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsString, MinLength } from 'class-validator';
|
||||||
|
|
||||||
export class RejectKycDto {
|
export class RejectKycDto {
|
||||||
@ApiProperty({ description: 'ID of the user whose KYC to reject', example: 'usr_abc123' })
|
@ApiProperty({ description: 'ID of the user whose KYC to reject', example: 'usr_abc123' })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IsString, MinLength } from 'class-validator';
|
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsString, MinLength } from 'class-validator';
|
||||||
|
|
||||||
export class RejectListingDto {
|
export class RejectListingDto {
|
||||||
@ApiProperty({ description: 'ID of the listing to reject', example: 'lst_abc123' })
|
@ApiProperty({ description: 'ID of the listing to reject', example: 'lst_abc123' })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IsDateString, IsIn, IsOptional } from 'class-validator';
|
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { IsDateString, IsIn, IsOptional } from 'class-validator';
|
||||||
|
|
||||||
export class RevenueStatsDto {
|
export class RevenueStatsDto {
|
||||||
@ApiProperty({ description: 'Start date (ISO 8601)', example: '2025-01-01' })
|
@ApiProperty({ description: 'Start date (ISO 8601)', example: '2025-01-01' })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IsBoolean, IsString, MinLength } from 'class-validator';
|
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsBoolean, IsString, MinLength } from 'class-validator';
|
||||||
|
|
||||||
export class UpdateUserStatusDto {
|
export class UpdateUserStatusDto {
|
||||||
@ApiProperty({ description: 'ID of the user to update', example: 'usr_abc123' })
|
@ApiProperty({ description: 'ID of the user to update', example: 'usr_abc123' })
|
||||||
|
|||||||
@@ -1,26 +1,16 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { CqrsModule } from '@nestjs/cqrs';
|
import { CqrsModule } from '@nestjs/cqrs';
|
||||||
|
import { GenerateReportHandler } from './application/commands/generate-report/generate-report.handler';
|
||||||
// Domain
|
import { TrackEventHandler } from './application/commands/track-event/track-event.handler';
|
||||||
|
import { UpdateMarketIndexHandler } from './application/commands/update-market-index/update-market-index.handler';
|
||||||
|
import { GetDistrictStatsHandler } from './application/queries/get-district-stats/get-district-stats.handler';
|
||||||
|
import { GetHeatmapHandler } from './application/queries/get-heatmap/get-heatmap.handler';
|
||||||
|
import { GetMarketReportHandler } from './application/queries/get-market-report/get-market-report.handler';
|
||||||
|
import { GetPriceTrendHandler } from './application/queries/get-price-trend/get-price-trend.handler';
|
||||||
import { MARKET_INDEX_REPOSITORY } from './domain/repositories/market-index.repository';
|
import { MARKET_INDEX_REPOSITORY } from './domain/repositories/market-index.repository';
|
||||||
import { VALUATION_REPOSITORY } from './domain/repositories/valuation.repository';
|
import { VALUATION_REPOSITORY } from './domain/repositories/valuation.repository';
|
||||||
|
|
||||||
// Infrastructure
|
|
||||||
import { PrismaMarketIndexRepository } from './infrastructure/repositories/prisma-market-index.repository';
|
import { PrismaMarketIndexRepository } from './infrastructure/repositories/prisma-market-index.repository';
|
||||||
import { PrismaValuationRepository } from './infrastructure/repositories/prisma-valuation.repository';
|
import { PrismaValuationRepository } from './infrastructure/repositories/prisma-valuation.repository';
|
||||||
|
|
||||||
// Application — Commands
|
|
||||||
import { TrackEventHandler } from './application/commands/track-event/track-event.handler';
|
|
||||||
import { GenerateReportHandler } from './application/commands/generate-report/generate-report.handler';
|
|
||||||
import { UpdateMarketIndexHandler } from './application/commands/update-market-index/update-market-index.handler';
|
|
||||||
|
|
||||||
// Application — Queries
|
|
||||||
import { GetMarketReportHandler } from './application/queries/get-market-report/get-market-report.handler';
|
|
||||||
import { GetHeatmapHandler } from './application/queries/get-heatmap/get-heatmap.handler';
|
|
||||||
import { GetPriceTrendHandler } from './application/queries/get-price-trend/get-price-trend.handler';
|
|
||||||
import { GetDistrictStatsHandler } from './application/queries/get-district-stats/get-district-stats.handler';
|
|
||||||
|
|
||||||
// Presentation
|
|
||||||
import { AnalyticsController } from './presentation/controllers/analytics.controller';
|
import { AnalyticsController } from './presentation/controllers/analytics.controller';
|
||||||
|
|
||||||
const CommandHandlers = [
|
const CommandHandlers = [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { GenerateReportHandler } from '../commands/generate-report/generate-report.handler';
|
|
||||||
import { GenerateReportCommand } from '../commands/generate-report/generate-report.command';
|
|
||||||
import { type IMarketIndexRepository, type MarketReportResult } from '../../domain/repositories/market-index.repository';
|
import { type IMarketIndexRepository, type MarketReportResult } from '../../domain/repositories/market-index.repository';
|
||||||
|
import { GenerateReportCommand } from '../commands/generate-report/generate-report.command';
|
||||||
|
import { GenerateReportHandler } from '../commands/generate-report/generate-report.handler';
|
||||||
|
|
||||||
describe('GenerateReportHandler', () => {
|
describe('GenerateReportHandler', () => {
|
||||||
let handler: GenerateReportHandler;
|
let handler: GenerateReportHandler;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { type CacheService } from '@modules/shared/infrastructure/cache.service';
|
||||||
|
import { type IMarketIndexRepository, type DistrictStatsResult } from '../../domain/repositories/market-index.repository';
|
||||||
import { GetDistrictStatsHandler } from '../queries/get-district-stats/get-district-stats.handler';
|
import { GetDistrictStatsHandler } from '../queries/get-district-stats/get-district-stats.handler';
|
||||||
import { GetDistrictStatsQuery } from '../queries/get-district-stats/get-district-stats.query';
|
import { GetDistrictStatsQuery } from '../queries/get-district-stats/get-district-stats.query';
|
||||||
import { type IMarketIndexRepository, type DistrictStatsResult } from '../../domain/repositories/market-index.repository';
|
|
||||||
import { type CacheService } from '@modules/shared/infrastructure/cache.service';
|
|
||||||
|
|
||||||
describe('GetDistrictStatsHandler', () => {
|
describe('GetDistrictStatsHandler', () => {
|
||||||
let handler: GetDistrictStatsHandler;
|
let handler: GetDistrictStatsHandler;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { type CacheService } from '@modules/shared/infrastructure/cache.service';
|
||||||
|
import { type IMarketIndexRepository, type HeatmapDataPoint } from '../../domain/repositories/market-index.repository';
|
||||||
import { GetHeatmapHandler } from '../queries/get-heatmap/get-heatmap.handler';
|
import { GetHeatmapHandler } from '../queries/get-heatmap/get-heatmap.handler';
|
||||||
import { GetHeatmapQuery } from '../queries/get-heatmap/get-heatmap.query';
|
import { GetHeatmapQuery } from '../queries/get-heatmap/get-heatmap.query';
|
||||||
import { type IMarketIndexRepository, type HeatmapDataPoint } from '../../domain/repositories/market-index.repository';
|
|
||||||
import { type CacheService } from '@modules/shared/infrastructure/cache.service';
|
|
||||||
|
|
||||||
describe('GetHeatmapHandler', () => {
|
describe('GetHeatmapHandler', () => {
|
||||||
let handler: GetHeatmapHandler;
|
let handler: GetHeatmapHandler;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { type CacheService } from '@modules/shared/infrastructure/cache.service';
|
||||||
|
import { type IMarketIndexRepository, type MarketReportResult } from '../../domain/repositories/market-index.repository';
|
||||||
import { GetMarketReportHandler } from '../queries/get-market-report/get-market-report.handler';
|
import { GetMarketReportHandler } from '../queries/get-market-report/get-market-report.handler';
|
||||||
import { GetMarketReportQuery } from '../queries/get-market-report/get-market-report.query';
|
import { GetMarketReportQuery } from '../queries/get-market-report/get-market-report.query';
|
||||||
import { type IMarketIndexRepository, type MarketReportResult } from '../../domain/repositories/market-index.repository';
|
|
||||||
import { type CacheService } from '@modules/shared/infrastructure/cache.service';
|
|
||||||
|
|
||||||
describe('GetMarketReportHandler', () => {
|
describe('GetMarketReportHandler', () => {
|
||||||
let handler: GetMarketReportHandler;
|
let handler: GetMarketReportHandler;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { type CacheService } from '@modules/shared/infrastructure/cache.service';
|
||||||
|
import { type IMarketIndexRepository, type PriceTrendPoint } from '../../domain/repositories/market-index.repository';
|
||||||
import { GetPriceTrendHandler } from '../queries/get-price-trend/get-price-trend.handler';
|
import { GetPriceTrendHandler } from '../queries/get-price-trend/get-price-trend.handler';
|
||||||
import { GetPriceTrendQuery } from '../queries/get-price-trend/get-price-trend.query';
|
import { GetPriceTrendQuery } from '../queries/get-price-trend/get-price-trend.query';
|
||||||
import { type IMarketIndexRepository, type PriceTrendPoint } from '../../domain/repositories/market-index.repository';
|
|
||||||
import { type CacheService } from '@modules/shared/infrastructure/cache.service';
|
|
||||||
|
|
||||||
describe('GetPriceTrendHandler', () => {
|
describe('GetPriceTrendHandler', () => {
|
||||||
let handler: GetPriceTrendHandler;
|
let handler: GetPriceTrendHandler;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TrackEventHandler } from '../commands/track-event/track-event.handler';
|
|
||||||
import { TrackEventCommand } from '../commands/track-event/track-event.command';
|
import { TrackEventCommand } from '../commands/track-event/track-event.command';
|
||||||
|
import { TrackEventHandler } from '../commands/track-event/track-event.handler';
|
||||||
|
|
||||||
describe('TrackEventHandler', () => {
|
describe('TrackEventHandler', () => {
|
||||||
let handler: TrackEventHandler;
|
let handler: TrackEventHandler;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { UpdateMarketIndexHandler } from '../commands/update-market-index/update-market-index.handler';
|
|
||||||
import { UpdateMarketIndexCommand } from '../commands/update-market-index/update-market-index.command';
|
|
||||||
import { type IMarketIndexRepository } from '../../domain/repositories/market-index.repository';
|
|
||||||
import { MarketIndexEntity } from '../../domain/entities/market-index.entity';
|
|
||||||
import { type CacheService } from '@modules/shared/infrastructure/cache.service';
|
import { type CacheService } from '@modules/shared/infrastructure/cache.service';
|
||||||
|
import { MarketIndexEntity } from '../../domain/entities/market-index.entity';
|
||||||
|
import { type IMarketIndexRepository } from '../../domain/repositories/market-index.repository';
|
||||||
|
import { UpdateMarketIndexCommand } from '../commands/update-market-index/update-market-index.command';
|
||||||
|
import { UpdateMarketIndexHandler } from '../commands/update-market-index/update-market-index.handler';
|
||||||
|
|
||||||
function createExistingEntity(): MarketIndexEntity {
|
function createExistingEntity(): MarketIndexEntity {
|
||||||
return new MarketIndexEntity('idx-1', {
|
return new MarketIndexEntity('idx-1', {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { GenerateReportCommand } from './generate-report.command';
|
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import {
|
import {
|
||||||
MARKET_INDEX_REPOSITORY,
|
MARKET_INDEX_REPOSITORY,
|
||||||
type IMarketIndexRepository,
|
type IMarketIndexRepository,
|
||||||
type MarketReportResult,
|
type MarketReportResult,
|
||||||
} from '../../../domain/repositories/market-index.repository';
|
} from '../../../domain/repositories/market-index.repository';
|
||||||
|
import { GenerateReportCommand } from './generate-report.command';
|
||||||
|
|
||||||
export interface GenerateReportResult {
|
export interface GenerateReportResult {
|
||||||
city: string;
|
city: string;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
|
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { TrackEventCommand } from './track-event.command';
|
import { TrackEventCommand } from './track-event.command';
|
||||||
|
|
||||||
export interface TrackEventResult {
|
export interface TrackEventResult {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { CacheService, CachePrefix } from '@modules/shared/infrastructure/cache.service';
|
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { UpdateMarketIndexCommand } from './update-market-index.command';
|
import { type CacheService, CachePrefix } from '@modules/shared/infrastructure/cache.service';
|
||||||
|
import { MarketIndexEntity } from '../../../domain/entities/market-index.entity';
|
||||||
import {
|
import {
|
||||||
MARKET_INDEX_REPOSITORY,
|
MARKET_INDEX_REPOSITORY,
|
||||||
type IMarketIndexRepository,
|
type IMarketIndexRepository,
|
||||||
} from '../../../domain/repositories/market-index.repository';
|
} from '../../../domain/repositories/market-index.repository';
|
||||||
import { MarketIndexEntity } from '../../../domain/entities/market-index.entity';
|
import { UpdateMarketIndexCommand } from './update-market-index.command';
|
||||||
|
|
||||||
export interface UpdateMarketIndexResult {
|
export interface UpdateMarketIndexResult {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared/infrastructure/cache.service';
|
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared/infrastructure/cache.service';
|
||||||
import { GetDistrictStatsQuery } from './get-district-stats.query';
|
|
||||||
import {
|
import {
|
||||||
MARKET_INDEX_REPOSITORY,
|
MARKET_INDEX_REPOSITORY,
|
||||||
type IMarketIndexRepository,
|
type IMarketIndexRepository,
|
||||||
type DistrictStatsResult,
|
type DistrictStatsResult,
|
||||||
} from '../../../domain/repositories/market-index.repository';
|
} from '../../../domain/repositories/market-index.repository';
|
||||||
|
import { GetDistrictStatsQuery } from './get-district-stats.query';
|
||||||
|
|
||||||
export interface DistrictStatsDto {
|
export interface DistrictStatsDto {
|
||||||
city: string;
|
city: string;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared/infrastructure/cache.service';
|
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared/infrastructure/cache.service';
|
||||||
import { GetHeatmapQuery } from './get-heatmap.query';
|
|
||||||
import {
|
import {
|
||||||
MARKET_INDEX_REPOSITORY,
|
MARKET_INDEX_REPOSITORY,
|
||||||
type IMarketIndexRepository,
|
type IMarketIndexRepository,
|
||||||
type HeatmapDataPoint,
|
type HeatmapDataPoint,
|
||||||
} from '../../../domain/repositories/market-index.repository';
|
} from '../../../domain/repositories/market-index.repository';
|
||||||
|
import { GetHeatmapQuery } from './get-heatmap.query';
|
||||||
|
|
||||||
export interface HeatmapDto {
|
export interface HeatmapDto {
|
||||||
city: string;
|
city: string;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared/infrastructure/cache.service';
|
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared/infrastructure/cache.service';
|
||||||
import { GetMarketReportQuery } from './get-market-report.query';
|
|
||||||
import {
|
import {
|
||||||
MARKET_INDEX_REPOSITORY,
|
MARKET_INDEX_REPOSITORY,
|
||||||
type IMarketIndexRepository,
|
type IMarketIndexRepository,
|
||||||
type MarketReportResult,
|
type MarketReportResult,
|
||||||
} from '../../../domain/repositories/market-index.repository';
|
} from '../../../domain/repositories/market-index.repository';
|
||||||
|
import { GetMarketReportQuery } from './get-market-report.query';
|
||||||
|
|
||||||
export interface MarketReportDto {
|
export interface MarketReportDto {
|
||||||
city: string;
|
city: string;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared/infrastructure/cache.service';
|
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared/infrastructure/cache.service';
|
||||||
import { GetPriceTrendQuery } from './get-price-trend.query';
|
|
||||||
import {
|
import {
|
||||||
MARKET_INDEX_REPOSITORY,
|
MARKET_INDEX_REPOSITORY,
|
||||||
type IMarketIndexRepository,
|
type IMarketIndexRepository,
|
||||||
type PriceTrendPoint,
|
type PriceTrendPoint,
|
||||||
} from '../../../domain/repositories/market-index.repository';
|
} from '../../../domain/repositories/market-index.repository';
|
||||||
|
import { GetPriceTrendQuery } from './get-price-trend.query';
|
||||||
|
|
||||||
export interface PriceTrendDto {
|
export interface PriceTrendDto {
|
||||||
district: string;
|
district: string;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AggregateRoot } from '@modules/shared/domain/aggregate-root';
|
|
||||||
import { type PropertyType } from '@prisma/client';
|
import { type PropertyType } from '@prisma/client';
|
||||||
|
import { AggregateRoot } from '@modules/shared/domain/aggregate-root';
|
||||||
import { MarketIndexUpdatedEvent } from '../events/market-index-updated.event';
|
import { MarketIndexUpdatedEvent } from '../events/market-index-updated.event';
|
||||||
|
|
||||||
export interface MarketIndexProps {
|
export interface MarketIndexProps {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
|
||||||
import { type MarketIndex as PrismaMarketIndex, type PropertyType } from '@prisma/client';
|
import { type MarketIndex as PrismaMarketIndex, type PropertyType } from '@prisma/client';
|
||||||
|
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
||||||
|
import { MarketIndexEntity, type MarketIndexProps } from '../../domain/entities/market-index.entity';
|
||||||
import {
|
import {
|
||||||
type IMarketIndexRepository,
|
type IMarketIndexRepository,
|
||||||
type MarketReportResult,
|
type MarketReportResult,
|
||||||
@@ -8,7 +9,6 @@ import {
|
|||||||
type PriceTrendPoint,
|
type PriceTrendPoint,
|
||||||
type DistrictStatsResult,
|
type DistrictStatsResult,
|
||||||
} from '../../domain/repositories/market-index.repository';
|
} from '../../domain/repositories/market-index.repository';
|
||||||
import { MarketIndexEntity, type MarketIndexProps } from '../../domain/entities/market-index.entity';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PrismaMarketIndexRepository implements IMarketIndexRepository {
|
export class PrismaMarketIndexRepository implements IMarketIndexRepository {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
|
||||||
import { type Valuation as PrismaValuation } from '@prisma/client';
|
import { type Valuation as PrismaValuation } from '@prisma/client';
|
||||||
import { type IValuationRepository } from '../../domain/repositories/valuation.repository';
|
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';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PrismaValuationRepository implements IValuationRepository {
|
export class PrismaValuationRepository implements IValuationRepository {
|
||||||
|
|||||||
@@ -3,20 +3,20 @@ import {
|
|||||||
Get,
|
Get,
|
||||||
Query,
|
Query,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { type QueryBus } from '@nestjs/cqrs';
|
||||||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||||
import { QueryBus } from '@nestjs/cqrs';
|
|
||||||
import { GetMarketReportQuery } from '../../application/queries/get-market-report/get-market-report.query';
|
|
||||||
import { GetHeatmapQuery } from '../../application/queries/get-heatmap/get-heatmap.query';
|
|
||||||
import { GetPriceTrendQuery } from '../../application/queries/get-price-trend/get-price-trend.query';
|
|
||||||
import { GetDistrictStatsQuery } from '../../application/queries/get-district-stats/get-district-stats.query';
|
|
||||||
import { GetMarketReportDto } from '../dto/get-market-report.dto';
|
|
||||||
import { GetHeatmapDto } from '../dto/get-heatmap.dto';
|
|
||||||
import { GetPriceTrendDto } from '../dto/get-price-trend.dto';
|
|
||||||
import { GetDistrictStatsDto } from '../dto/get-district-stats.dto';
|
|
||||||
import { type MarketReportDto } from '../../application/queries/get-market-report/get-market-report.handler';
|
|
||||||
import { type HeatmapDto } from '../../application/queries/get-heatmap/get-heatmap.handler';
|
|
||||||
import { type PriceTrendDto } from '../../application/queries/get-price-trend/get-price-trend.handler';
|
|
||||||
import { type DistrictStatsDto } from '../../application/queries/get-district-stats/get-district-stats.handler';
|
import { type DistrictStatsDto } from '../../application/queries/get-district-stats/get-district-stats.handler';
|
||||||
|
import { GetDistrictStatsQuery } from '../../application/queries/get-district-stats/get-district-stats.query';
|
||||||
|
import { type HeatmapDto } from '../../application/queries/get-heatmap/get-heatmap.handler';
|
||||||
|
import { GetHeatmapQuery } from '../../application/queries/get-heatmap/get-heatmap.query';
|
||||||
|
import { type MarketReportDto } from '../../application/queries/get-market-report/get-market-report.handler';
|
||||||
|
import { GetMarketReportQuery } from '../../application/queries/get-market-report/get-market-report.query';
|
||||||
|
import { type PriceTrendDto } from '../../application/queries/get-price-trend/get-price-trend.handler';
|
||||||
|
import { GetPriceTrendQuery } from '../../application/queries/get-price-trend/get-price-trend.query';
|
||||||
|
import { type GetDistrictStatsDto } from '../dto/get-district-stats.dto';
|
||||||
|
import { type GetHeatmapDto } from '../dto/get-heatmap.dto';
|
||||||
|
import { type GetMarketReportDto } from '../dto/get-market-report.dto';
|
||||||
|
import { type GetPriceTrendDto } from '../dto/get-price-trend.dto';
|
||||||
|
|
||||||
@ApiTags('analytics')
|
@ApiTags('analytics')
|
||||||
@Controller('analytics')
|
@Controller('analytics')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IsString } from 'class-validator';
|
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsString } from 'class-validator';
|
||||||
|
|
||||||
export class GetDistrictStatsDto {
|
export class GetDistrictStatsDto {
|
||||||
@ApiProperty({ description: 'City name' })
|
@ApiProperty({ description: 'City name' })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IsString } from 'class-validator';
|
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsString } from 'class-validator';
|
||||||
|
|
||||||
export class GetHeatmapDto {
|
export class GetHeatmapDto {
|
||||||
@ApiProperty({ description: 'City name' })
|
@ApiProperty({ description: 'City name' })
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IsEnum, IsOptional, IsString } from 'class-validator';
|
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
import { PropertyType } from '@prisma/client';
|
import { PropertyType } from '@prisma/client';
|
||||||
|
import { IsEnum, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class GetMarketReportDto {
|
export class GetMarketReportDto {
|
||||||
@ApiProperty({ description: 'City name' })
|
@ApiProperty({ description: 'City name' })
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { IsArray, IsEnum, IsString } from 'class-validator';
|
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Transform } from 'class-transformer';
|
|
||||||
import { PropertyType } from '@prisma/client';
|
import { PropertyType } from '@prisma/client';
|
||||||
|
import { Transform } from 'class-transformer';
|
||||||
|
import { IsArray, IsEnum, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class GetPriceTrendDto {
|
export class GetPriceTrendDto {
|
||||||
@ApiProperty({ description: 'District name' })
|
@ApiProperty({ description: 'District name' })
|
||||||
|
|||||||
@@ -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 { type INestApplication, ValidationPipe } from '@nestjs/common';
|
||||||
import { CqrsModule } from '@nestjs/cqrs';
|
import { CqrsModule } from '@nestjs/cqrs';
|
||||||
|
import { Test, type TestingModule } from '@nestjs/testing';
|
||||||
import request from 'supertest';
|
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 { PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
||||||
import { SharedModule } from '@modules/shared/shared.module';
|
import { SharedModule } from '@modules/shared/shared.module';
|
||||||
|
import { AuthModule } from '../auth.module';
|
||||||
|
|
||||||
describe('Auth Controller (Integration)', () => {
|
describe('Auth Controller (Integration)', () => {
|
||||||
let app: INestApplication;
|
let app: INestApplication;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
|
import { type TokenService, type TokenPair } from '../../../infrastructure/services/token.service';
|
||||||
import { LoginUserCommand } from './login-user.command';
|
import { LoginUserCommand } from './login-user.command';
|
||||||
import { TokenService, type TokenPair } from '../../../infrastructure/services/token.service';
|
|
||||||
|
|
||||||
@CommandHandler(LoginUserCommand)
|
@CommandHandler(LoginUserCommand)
|
||||||
export class LoginUserHandler implements ICommandHandler<LoginUserCommand> {
|
export class LoginUserHandler implements ICommandHandler<LoginUserCommand> {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { UnauthorizedException } from '@modules/shared/domain/domain-exception';
|
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 { 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)
|
@CommandHandler(RefreshTokenCommand)
|
||||||
export class RefreshTokenHandler implements ICommandHandler<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 { 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 { createId } from '@paralleldrive/cuid2';
|
||||||
import { RegisterUserCommand } from './register-user.command';
|
import { ConflictException, ValidationException } from '@modules/shared/domain/domain-exception';
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
|
||||||
import { UserEntity } from '../../../domain/entities/user.entity';
|
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 { Email } from '../../../domain/value-objects/email.vo';
|
||||||
import { HashedPassword } from '../../../domain/value-objects/hashed-password.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)
|
@CommandHandler(RegisterUserCommand)
|
||||||
export class RegisterUserHandler implements ICommandHandler<RegisterUserCommand> {
|
export class RegisterUserHandler implements ICommandHandler<RegisterUserCommand> {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
||||||
import { CacheService, CachePrefix } from '@modules/shared/infrastructure/cache.service';
|
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 { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
||||||
|
import { VerifyKycCommand } from './verify-kyc.command';
|
||||||
|
|
||||||
@CommandHandler(VerifyKycCommand)
|
@CommandHandler(VerifyKycCommand)
|
||||||
export class VerifyKycHandler implements ICommandHandler<VerifyKycCommand> {
|
export class VerifyKycHandler implements ICommandHandler<VerifyKycCommand> {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
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';
|
import { GetAgentByUserIdQuery } from './get-agent-by-user-id.query';
|
||||||
|
|
||||||
export interface AgentDto {
|
export interface AgentDto {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||||
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
||||||
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared/infrastructure/cache.service';
|
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 { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
||||||
|
import { GetProfileQuery } from './get-profile.query';
|
||||||
|
|
||||||
export interface UserProfileDto {
|
export interface UserProfileDto {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -2,27 +2,19 @@ import { Module } from '@nestjs/common';
|
|||||||
import { CqrsModule } from '@nestjs/cqrs';
|
import { CqrsModule } from '@nestjs/cqrs';
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
import { PassportModule } from '@nestjs/passport';
|
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 { LoginUserHandler } from './application/commands/login-user/login-user.handler';
|
||||||
import { RefreshTokenHandler } from './application/commands/refresh-token/refresh-token.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 { 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';
|
import { GetAgentByUserIdHandler } from './application/queries/get-agent-by-user-id/get-agent-by-user-id.handler';
|
||||||
|
import { GetProfileHandler } from './application/queries/get-profile/get-profile.handler';
|
||||||
// Presentation
|
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';
|
import { AuthController } from './presentation/controllers/auth.controller';
|
||||||
|
|
||||||
const CommandHandlers = [
|
const CommandHandlers = [
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
import { UserEntity } from '../entities/user.entity';
|
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 { 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', () => {
|
describe('UserEntity', () => {
|
||||||
let phone: Phone;
|
let phone: Phone;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { AggregateRoot } from '@modules/shared/domain/aggregate-root';
|
|
||||||
import { type UserRole, type KYCStatus } from '@prisma/client';
|
import { type UserRole, type KYCStatus } from '@prisma/client';
|
||||||
|
import { AggregateRoot } from '@modules/shared/domain/aggregate-root';
|
||||||
import { UserRegisteredEvent } from '../events/user-registered.event';
|
import { UserRegisteredEvent } from '../events/user-registered.event';
|
||||||
import { type Email } from '../value-objects/email.vo';
|
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 HashedPassword } from '../value-objects/hashed-password.vo';
|
||||||
|
import { type Phone } from '../value-objects/phone.vo';
|
||||||
|
|
||||||
export interface UserProps {
|
export interface UserProps {
|
||||||
email: Email | null;
|
email: Email | null;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type DomainEvent } from '@modules/shared/domain/domain-event';
|
|
||||||
import { type UserRole } from '@prisma/client';
|
import { type UserRole } from '@prisma/client';
|
||||||
|
import { type DomainEvent } from '@modules/shared/domain/domain-event';
|
||||||
|
|
||||||
export class UserRegisteredEvent implements DomainEvent {
|
export class UserRegisteredEvent implements DomainEvent {
|
||||||
readonly eventName = 'user.registered';
|
readonly eventName = 'user.registered';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ValueObject } from '@modules/shared/domain/value-object';
|
|
||||||
import { Result } from '@modules/shared/domain/result';
|
import { Result } from '@modules/shared/domain/result';
|
||||||
|
import { ValueObject } from '@modules/shared/domain/value-object';
|
||||||
|
|
||||||
interface EmailProps {
|
interface EmailProps {
|
||||||
value: string;
|
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 * as bcrypt from 'bcrypt';
|
||||||
|
import { Result } from '@modules/shared/domain/result';
|
||||||
|
import { ValueObject } from '@modules/shared/domain/value-object';
|
||||||
|
|
||||||
interface HashedPasswordProps {
|
interface HashedPasswordProps {
|
||||||
value: string;
|
value: string;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ValueObject } from '@modules/shared/domain/value-object';
|
|
||||||
import { Result } from '@modules/shared/domain/result';
|
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';
|
import { isValidVietnamPhone, normalizeVietnamPhone } from '@modules/shared/utils/vietnam-phone.validator';
|
||||||
|
|
||||||
interface PhoneProps {
|
interface PhoneProps {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
||||||
import {
|
import {
|
||||||
type IRefreshTokenRepository,
|
type IRefreshTokenRepository,
|
||||||
type RefreshTokenRecord,
|
type RefreshTokenRecord,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PrismaService } from '@modules/shared/infrastructure/prisma.service';
|
|
||||||
import { type User as PrismaUser } from '@prisma/client';
|
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 { 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 { 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 { HashedPassword } from '../../domain/value-objects/hashed-password.vo';
|
||||||
|
import { Phone } from '../../domain/value-objects/phone.vo';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PrismaUserRepository implements IUserRepository {
|
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 { randomBytes, createHash } from 'crypto';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { type JwtService } from '@nestjs/jwt';
|
||||||
import {
|
import {
|
||||||
REFRESH_TOKEN_REPOSITORY,
|
REFRESH_TOKEN_REPOSITORY,
|
||||||
IRefreshTokenRepository,
|
type IRefreshTokenRepository,
|
||||||
} from '../../domain/repositories/refresh-token.repository';
|
} from '../../domain/repositories/refresh-token.repository';
|
||||||
|
|
||||||
export interface JwtPayload {
|
export interface JwtPayload {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
||||||
import type { Request } from 'express';
|
import type { Request } from 'express';
|
||||||
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
import { type JwtPayload } from '../services/token.service';
|
import { type JwtPayload } from '../services/token.service';
|
||||||
|
|
||||||
function extractJwtFromCookieOrHeader(req: Request): string | null {
|
function extractJwtFromCookieOrHeader(req: Request): string | null {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { Strategy } from 'passport-local';
|
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 { normalizeVietnamPhone } from '@modules/shared/utils/vietnam-phone.validator';
|
||||||
|
import { USER_REPOSITORY, type IUserRepository } from '../../domain/repositories/user.repository';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||||
|
|||||||
@@ -9,28 +9,28 @@ import {
|
|||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { type CommandBus, type QueryBus } from '@nestjs/cqrs';
|
||||||
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiBody } from '@nestjs/swagger';
|
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiBody } from '@nestjs/swagger';
|
||||||
import { Throttle } from '@nestjs/throttler';
|
import { Throttle } from '@nestjs/throttler';
|
||||||
import { CommandBus, QueryBus } from '@nestjs/cqrs';
|
|
||||||
import type { Request, Response } from 'express';
|
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 { LoginUserCommand } from '../../application/commands/login-user/login-user.command';
|
||||||
import { RefreshTokenCommand } from '../../application/commands/refresh-token/refresh-token.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 { 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 { 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 { LoginDto } from '../dto/login.dto';
|
||||||
import { RefreshTokenDto } from '../dto/refresh-token.dto';
|
import { type RefreshTokenDto } from '../dto/refresh-token.dto';
|
||||||
import { VerifyKycDto } from '../dto/verify-kyc.dto';
|
import { type RegisterDto } from '../dto/register.dto';
|
||||||
|
import { type VerifyKycDto } from '../dto/verify-kyc.dto';
|
||||||
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
||||||
import { LocalAuthGuard } from '../guards/local-auth.guard';
|
import { LocalAuthGuard } from '../guards/local-auth.guard';
|
||||||
import { RolesGuard } from '../guards/roles.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 IS_PRODUCTION = process.env['NODE_ENV'] === 'production';
|
||||||
const ACCESS_TOKEN_MAX_AGE = 15 * 60 * 1000; // 15 minutes
|
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 { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsString } from 'class-validator';
|
||||||
|
|
||||||
export class LoginDto {
|
export class LoginDto {
|
||||||
@ApiProperty({ example: '0901234567' })
|
@ApiProperty({ example: '0901234567' })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IsOptional, IsString } from 'class-validator';
|
|
||||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class RefreshTokenDto {
|
export class RefreshTokenDto {
|
||||||
@ApiPropertyOptional({ description: 'JWT refresh token (optional if sent via cookie)' })
|
@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 { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { IsString, IsOptional, IsEmail, MinLength } from 'class-validator';
|
||||||
|
|
||||||
export class RegisterDto {
|
export class RegisterDto {
|
||||||
@ApiProperty({ example: '0901234567', description: 'Phone number' })
|
@ApiProperty({ example: '0901234567', description: 'Phone number' })
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IsEnum, IsOptional, IsObject } from 'class-validator';
|
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
import { KYCStatus } from '@prisma/client';
|
import { KYCStatus } from '@prisma/client';
|
||||||
|
import { IsEnum, IsOptional, IsObject } from 'class-validator';
|
||||||
|
|
||||||
export class VerifyKycDto {
|
export class VerifyKycDto {
|
||||||
@ApiProperty({ enum: KYCStatus, description: 'New KYC status' })
|
@ApiProperty({ enum: KYCStatus, description: 'New KYC status' })
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable, Logger, type CanActivate, type ExecutionContext } from '@nestjs/common';
|
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 { type UserRole } from '@prisma/client';
|
||||||
import { ROLES_KEY } from '../decorators/roles.decorator';
|
import { ROLES_KEY } from '../decorators/roles.decorator';
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { ValidationException } from '@modules/shared/domain/domain-exception';
|
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { createId } from '@paralleldrive/cuid2';
|
import { createId } from '@paralleldrive/cuid2';
|
||||||
import { CacheService, CachePrefix } from '@modules/shared/infrastructure/cache.service';
|
import { ValidationException } from '@modules/shared/domain/domain-exception';
|
||||||
import { CreateListingCommand } from './create-listing.command';
|
import { type CacheService, CachePrefix } from '@modules/shared/infrastructure/cache.service';
|
||||||
import { PROPERTY_REPOSITORY, type IPropertyRepository } from '../../../domain/repositories/property.repository';
|
|
||||||
import { LISTING_REPOSITORY, type IListingRepository } from '../../../domain/repositories/listing.repository';
|
|
||||||
import { PropertyEntity } from '../../../domain/entities/property.entity';
|
|
||||||
import { ListingEntity } from '../../../domain/entities/listing.entity';
|
import { ListingEntity } from '../../../domain/entities/listing.entity';
|
||||||
|
import { PropertyEntity } from '../../../domain/entities/property.entity';
|
||||||
|
import { LISTING_REPOSITORY, type IListingRepository } from '../../../domain/repositories/listing.repository';
|
||||||
|
import { PROPERTY_REPOSITORY, type IPropertyRepository } from '../../../domain/repositories/property.repository';
|
||||||
import { Address } from '../../../domain/value-objects/address.vo';
|
import { Address } from '../../../domain/value-objects/address.vo';
|
||||||
import { GeoPoint } from '../../../domain/value-objects/geo-point.vo';
|
import { GeoPoint } from '../../../domain/value-objects/geo-point.vo';
|
||||||
import { Price } from '../../../domain/value-objects/price.vo';
|
import { Price } from '../../../domain/value-objects/price.vo';
|
||||||
|
import { CreateListingCommand } from './create-listing.command';
|
||||||
|
|
||||||
export interface CreateListingResult {
|
export interface CreateListingResult {
|
||||||
listingId: string;
|
listingId: string;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
||||||
import { CacheService, CachePrefix } from '@modules/shared/infrastructure/cache.service';
|
import { CacheService, CachePrefix } from '@modules/shared/infrastructure/cache.service';
|
||||||
import { ModerateListingCommand } from './moderate-listing.command';
|
|
||||||
import { LISTING_REPOSITORY, type IListingRepository } from '../../../domain/repositories/listing.repository';
|
import { LISTING_REPOSITORY, type IListingRepository } from '../../../domain/repositories/listing.repository';
|
||||||
|
import { ModerateListingCommand } from './moderate-listing.command';
|
||||||
|
|
||||||
@CommandHandler(ModerateListingCommand)
|
@CommandHandler(ModerateListingCommand)
|
||||||
export class ModerateListingHandler implements ICommandHandler<ModerateListingCommand> {
|
export class ModerateListingHandler implements ICommandHandler<ModerateListingCommand> {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
||||||
import { CacheService, CachePrefix } from '@modules/shared/infrastructure/cache.service';
|
import { CacheService, CachePrefix } from '@modules/shared/infrastructure/cache.service';
|
||||||
import { UpdateListingStatusCommand } from './update-listing-status.command';
|
|
||||||
import { LISTING_REPOSITORY, type IListingRepository } from '../../../domain/repositories/listing.repository';
|
import { LISTING_REPOSITORY, type IListingRepository } from '../../../domain/repositories/listing.repository';
|
||||||
|
import { UpdateListingStatusCommand } from './update-listing-status.command';
|
||||||
|
|
||||||
@CommandHandler(UpdateListingStatusCommand)
|
@CommandHandler(UpdateListingStatusCommand)
|
||||||
export class UpdateListingStatusHandler implements ICommandHandler<UpdateListingStatusCommand> {
|
export class UpdateListingStatusHandler implements ICommandHandler<UpdateListingStatusCommand> {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { createId } from '@paralleldrive/cuid2';
|
import { createId } from '@paralleldrive/cuid2';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared/domain/domain-exception';
|
import { NotFoundException, ValidationException } from '@modules/shared/domain/domain-exception';
|
||||||
import { UploadMediaCommand } from './upload-media.command';
|
|
||||||
import { PROPERTY_REPOSITORY, type IPropertyRepository } from '../../../domain/repositories/property.repository';
|
|
||||||
import { PropertyMediaEntity } from '../../../domain/entities/property-media.entity';
|
import { PropertyMediaEntity } from '../../../domain/entities/property-media.entity';
|
||||||
|
import { PROPERTY_REPOSITORY, type IPropertyRepository } from '../../../domain/repositories/property.repository';
|
||||||
import { MEDIA_STORAGE_SERVICE, type IMediaStorageService } from '../../../infrastructure/services/media-storage.service';
|
import { MEDIA_STORAGE_SERVICE, type IMediaStorageService } from '../../../infrastructure/services/media-storage.service';
|
||||||
|
import { UploadMediaCommand } from './upload-media.command';
|
||||||
|
|
||||||
const MAX_MEDIA_PER_PROPERTY = 20;
|
const MAX_MEDIA_PER_PROPERTY = 20;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
import { NotFoundException } from '@modules/shared/domain/domain-exception';
|
||||||
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared/infrastructure/cache.service';
|
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared/infrastructure/cache.service';
|
||||||
import { GetListingQuery } from './get-listing.query';
|
|
||||||
import { LISTING_REPOSITORY, type IListingRepository } from '../../../domain/repositories/listing.repository';
|
|
||||||
import { type ListingDetailData } from '../../../domain/repositories/listing-read.dto';
|
import { type ListingDetailData } from '../../../domain/repositories/listing-read.dto';
|
||||||
|
import { LISTING_REPOSITORY, type IListingRepository } from '../../../domain/repositories/listing.repository';
|
||||||
|
import { GetListingQuery } from './get-listing.query';
|
||||||
|
|
||||||
/** @deprecated Use ListingDetailData from listing-read.dto instead */
|
/** @deprecated Use ListingDetailData from listing-read.dto instead */
|
||||||
export type ListingDetailDto = ListingDetailData;
|
export type ListingDetailDto = ListingDetailData;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { GetPendingModerationQuery } from './get-pending-moderation.query';
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { LISTING_REPOSITORY, type IListingRepository, type PaginatedResult } from '../../../domain/repositories/listing.repository';
|
|
||||||
import { type ListingSearchItem } from '../../../domain/repositories/listing-read.dto';
|
import { type ListingSearchItem } from '../../../domain/repositories/listing-read.dto';
|
||||||
|
import { LISTING_REPOSITORY, type IListingRepository, type PaginatedResult } from '../../../domain/repositories/listing.repository';
|
||||||
|
import { GetPendingModerationQuery } from './get-pending-moderation.query';
|
||||||
|
|
||||||
@QueryHandler(GetPendingModerationQuery)
|
@QueryHandler(GetPendingModerationQuery)
|
||||||
export class GetPendingModerationHandler implements IQueryHandler<GetPendingModerationQuery> {
|
export class GetPendingModerationHandler implements IQueryHandler<GetPendingModerationQuery> {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { SearchListingsQuery } from './search-listings.query';
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { LISTING_REPOSITORY, type IListingRepository, type PaginatedResult } from '../../../domain/repositories/listing.repository';
|
|
||||||
import { type ListingSearchItem } from '../../../domain/repositories/listing-read.dto';
|
import { type ListingSearchItem } from '../../../domain/repositories/listing-read.dto';
|
||||||
|
import { LISTING_REPOSITORY, type IListingRepository, type PaginatedResult } from '../../../domain/repositories/listing.repository';
|
||||||
|
import { SearchListingsQuery } from './search-listings.query';
|
||||||
|
|
||||||
@QueryHandler(SearchListingsQuery)
|
@QueryHandler(SearchListingsQuery)
|
||||||
export class SearchListingsHandler implements IQueryHandler<SearchListingsQuery> {
|
export class SearchListingsHandler implements IQueryHandler<SearchListingsQuery> {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { AggregateRoot } from '@modules/shared/domain/aggregate-root';
|
|
||||||
import { type ListingStatus, type TransactionType } from '@prisma/client';
|
import { type ListingStatus, type TransactionType } from '@prisma/client';
|
||||||
import { type Price } from '../value-objects/price.vo';
|
import { AggregateRoot } from '@modules/shared/domain/aggregate-root';
|
||||||
import { ListingCreatedEvent } from '../events/listing-created.event';
|
|
||||||
import { ListingApprovedEvent } from '../events/listing-approved.event';
|
|
||||||
import { ListingSoldEvent } from '../events/listing-sold.event';
|
|
||||||
import { ValidationException } from '@modules/shared/domain/domain-exception';
|
import { ValidationException } from '@modules/shared/domain/domain-exception';
|
||||||
|
import { ListingApprovedEvent } from '../events/listing-approved.event';
|
||||||
|
import { ListingCreatedEvent } from '../events/listing-created.event';
|
||||||
|
import { ListingSoldEvent } from '../events/listing-sold.event';
|
||||||
|
import { type Price } from '../value-objects/price.vo';
|
||||||
|
|
||||||
const VALID_TRANSITIONS: Record<ListingStatus, ListingStatus[]> = {
|
const VALID_TRANSITIONS: Record<ListingStatus, ListingStatus[]> = {
|
||||||
DRAFT: ['PENDING_REVIEW'],
|
DRAFT: ['PENDING_REVIEW'],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AggregateRoot } from '@modules/shared/domain/aggregate-root';
|
|
||||||
import { type PropertyType, type Direction } from '@prisma/client';
|
import { type PropertyType, type Direction } from '@prisma/client';
|
||||||
|
import { AggregateRoot } from '@modules/shared/domain/aggregate-root';
|
||||||
import { type Address } from '../value-objects/address.vo';
|
import { type Address } from '../value-objects/address.vo';
|
||||||
import { type GeoPoint } from '../value-objects/geo-point.vo';
|
import { type GeoPoint } from '../value-objects/geo-point.vo';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type DomainEvent } from '@modules/shared/domain/domain-event';
|
|
||||||
import { type TransactionType } from '@prisma/client';
|
import { type TransactionType } from '@prisma/client';
|
||||||
|
import { type DomainEvent } from '@modules/shared/domain/domain-event';
|
||||||
|
|
||||||
export class ListingCreatedEvent implements DomainEvent {
|
export class ListingCreatedEvent implements DomainEvent {
|
||||||
readonly eventName = 'listing.created';
|
readonly eventName = 'listing.created';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type DomainEvent } from '@modules/shared/domain/domain-event';
|
|
||||||
import { type ListingStatus } from '@prisma/client';
|
import { type ListingStatus } from '@prisma/client';
|
||||||
|
import { type DomainEvent } from '@modules/shared/domain/domain-event';
|
||||||
|
|
||||||
export class ListingSoldEvent implements DomainEvent {
|
export class ListingSoldEvent implements DomainEvent {
|
||||||
readonly eventName = 'listing.sold';
|
readonly eventName = 'listing.sold';
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user