Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/common/api-version/middleware/api-version.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ export const VERSION_HEADER = 'Accept-Version';
export const VERSION_QUERY_PARAM = 'version';

/**
* Path pattern to extract version from URL
* Path pattern to extract version from URL (matches /v1.0/ or /v1.0 at start or after /)
*/
const VERSION_PATH_PATTERN = /^\/v(\d+\.\d+)/;
const VERSION_PATH_PATTERN = /\/v(\d+\.\d+)(?:\/|$)/;

@Injectable()
export class ApiVersionMiddleware implements NestMiddleware {
Expand Down Expand Up @@ -92,7 +92,7 @@ export class ApiVersionMiddleware implements NestMiddleware {
*/
private extractFromPath(path: string): string | null {
const match = path.match(VERSION_PATH_PATTERN);
return match ? `1.${match[1]}` : null;
return match ? match[1] : null;
}

/**
Expand Down
24 changes: 20 additions & 4 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,20 @@ async function bootstrap() {
.setTitle('PropChain API')
.setDescription('Decentralized Real Estate Infrastructure - Backend API')
.setVersion(DEFAULT_API_VERSION)
.addTag('properties')
.addTag('transactions')
.addTag('users')
.addTag('blockchain')
.addTag('auth', 'Authentication and Authorization')
.addTag('users', 'User management and profiles')
.addTag('properties', 'Property listings and management')
.addTag('transactions', 'Escrowed real estate transactions')
.addTag('valuation', 'Automated property valuation and market trends')
.addTag('documents', 'Secure document storage and versioning')
.addTag('blockchain', 'Web3 and smart contract interactions')
.addTag('Audit & Compliance', 'Regulatory auditing and compliance reporting')
.addTag('search', 'Advanced search across properties and users')
.addTag('security', 'Security headers and system safety')
.addTag('health', 'System health and status monitoring')
.addTag('backup-recovery', 'Disaster recovery and backup management')
.addTag('API Keys', 'Key management for external integrations')
.addTag('feature-flags', 'Dynamic feature toggles')
.addBearerAuth()
.addApiKey({ type: 'apiKey', name: 'X-API-KEY', in: 'header' }, 'apiKey')
.addApiKey({ type: 'apiKey', name: 'Accept-Version', in: 'header' }, 'version')
Expand All @@ -142,6 +152,12 @@ async function bootstrap() {
customSiteTitle: 'PropChain API Documentation',
customCss: '.swagger-ui .topbar { display: none }',
customfavIcon: '/favicon.ico',
swaggerOptions: {
persistAuthorization: true,
docExpansion: 'none',
filter: true,
showRequestDuration: true,
},
});

logger.log(`Swagger documentation available at /${apiPrefix}/docs`);
Expand Down
29 changes: 28 additions & 1 deletion src/transactions/transactions.controller.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,60 @@
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
import { Body, Controller, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiBearerAuth } from '@nestjs/swagger';
import { TransactionsService } from './transactions.service';
import { CreateTransactionDto, DisputeDto } from './dto/create-transaction.dto';
import { TransactionQueryDto } from './dto/transaction-query.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';

@ApiTags('transactions')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Controller('transactions')
export class TransactionsController {
constructor(private readonly service: TransactionsService) {}

@Post()
@ApiOperation({ summary: 'Create a new transaction' })
@ApiResponse({ status: 201, description: 'Transaction created successfully.', type: CreateTransactionDto })
@ApiResponse({ status: 400, description: 'Invalid transaction data.' })
@ApiResponse({ status: 401, description: 'Unauthorized.' })
create(@Body() dto: CreateTransactionDto) {
return this.service.createTransaction(dto);
}

@Post(':id/escrow')
@ApiOperation({ summary: 'Fund escrow for a transaction' })
@ApiParam({ name: 'id', description: 'Transaction ID' })
@ApiResponse({ status: 200, description: 'Escrow funded successfully.' })
@ApiResponse({ status: 404, description: 'Transaction not found.' })
@ApiResponse({ status: 401, description: 'Unauthorized.' })
fundEscrow(@Param('id') id: string) {
return this.service.fundEscrow(id);
}

@Get(':id')
@ApiOperation({ summary: 'Get transaction by ID' })
@ApiParam({ name: 'id', description: 'Transaction ID' })
@ApiResponse({ status: 200, description: 'Transaction found.' })
@ApiResponse({ status: 404, description: 'Transaction not found.' })
@ApiResponse({ status: 401, description: 'Unauthorized.' })
findOne(@Param('id') id: string) {
return this.service.getTransaction(id);
}

@Get()
@ApiOperation({ summary: 'List transactions with filters' })
@ApiResponse({ status: 200, description: 'List of transactions.' })
@ApiResponse({ status: 401, description: 'Unauthorized.' })
findAll(@Query() query: TransactionQueryDto) {
return this.service.findAll(query);
}

@Post(':id/dispute')
@ApiOperation({ summary: 'Raise a dispute for a transaction' })
@ApiParam({ name: 'id', description: 'Transaction ID' })
@ApiResponse({ status: 200, description: 'Dispute raised successfully.', type: DisputeDto })
@ApiResponse({ status: 404, description: 'Transaction not found.' })
@ApiResponse({ status: 401, description: 'Unauthorized.' })
dispute(@Param('id') id: string, @Body() dto: DisputeDto) {
return this.service.raiseDispute(id, dto);
}
Expand Down
47 changes: 17 additions & 30 deletions src/valuation/valuation.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { Controller, Get, Post, Param, Body, ValidationPipe, HttpCode, HttpStatus, Logger } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiBody } from '@nestjs/swagger';
import { Controller, Get, Post, Param, Body, ValidationPipe, HttpCode, HttpStatus, Logger, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiBody, ApiBearerAuth } from '@nestjs/swagger';
import { ValuationService } from './valuation.service';
import { ValuationResult } from './valuation.types';
import { PropertyFeaturesDto } from './dto/property-features.dto';
import { BatchValuationRequestDto } from './dto/batch-valuation-request.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { ApiStandardErrorResponse } from '../common/errors/api-standard-error-response.decorator';

@ApiTags('valuation')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Controller('valuation')
export class ValuationController {
private readonly logger = new Logger(ValuationController.name);
Expand All @@ -15,10 +19,9 @@ export class ValuationController {
@Post(':propertyId')
@ApiOperation({ summary: 'Get property valuation' })
@ApiParam({ name: 'propertyId', description: 'ID of the property to value' })
@ApiBody({ description: 'Property features for valuation', required: false })
@ApiBody({ type: PropertyFeaturesDto, description: 'Property features for valuation', required: false })
@ApiResponse({ status: 200, description: 'Property valuation successful' })
@ApiResponse({ status: 404, description: 'Property not found' })
@ApiResponse({ status: 422, description: 'Invalid property features' })
@ApiStandardErrorResponse([400, 401, 404, 422])
@HttpCode(HttpStatus.OK)
async getValuation(
@Param('propertyId') propertyId: string,
Expand All @@ -32,27 +35,27 @@ export class ValuationController {
@ApiOperation({ summary: 'Get property valuation history' })
@ApiParam({ name: 'propertyId', description: 'ID of the property' })
@ApiResponse({ status: 200, description: 'Property valuation history retrieved' })
@ApiResponse({ status: 404, description: 'Property not found' })
@ApiStandardErrorResponse([401, 404])
async getPropertyHistory(@Param('propertyId') propertyId: string): Promise<ValuationResult[]> {
this.logger.log(`Requesting valuation history for property ${propertyId}`);
return this.valuationService.getPropertyHistory(propertyId);
}

@Get('trends/:location')
@ApiOperation({ summary: 'Get market trend analysis for a location' })
@ApiParam({ name: 'location', description: 'Location to analyze market trends' })
@ApiParam({ name: 'location', description: 'Location (address or ZIP) to analyze market trends' })
@ApiResponse({ status: 200, description: 'Market trend analysis retrieved' })
@ApiResponse({ status: 404, description: 'Location not found in records' })
@ApiStandardErrorResponse([400, 401, 404])
async getMarketTrendAnalysis(@Param('location') location: string) {
this.logger.log(`Requesting market trend analysis for location ${location}`);
return this.valuationService.getMarketTrendAnalysis(location);
}

@Get(':propertyId/latest')
@ApiOperation({ summary: 'Get latest valuation for a property' })
@ApiOperation({ summary: 'Get latest valuation for a property', description: 'Retrieves the most recent valuation from history.' })
@ApiParam({ name: 'propertyId', description: 'ID of the property' })
@ApiResponse({ status: 200, description: 'Latest valuation retrieved' })
@ApiResponse({ status: 404, description: 'Property not found' })
@ApiStandardErrorResponse([401, 404])
async getLatestValuation(@Param('propertyId') propertyId: string): Promise<ValuationResult> {
const history = await this.valuationService.getPropertyHistory(propertyId);
if (history.length === 0) {
Expand All @@ -62,26 +65,10 @@ export class ValuationController {
}

@Post('batch')
@ApiOperation({ summary: 'Get valuations for multiple properties' })
@ApiBody({
description: 'Array of property IDs and features',
schema: {
type: 'object',
properties: {
properties: {
type: 'array',
items: {
type: 'object',
properties: {
propertyId: { type: 'string' },
features: { $ref: '#/components/schemas/PropertyFeatures' },
},
},
},
},
},
})
@ApiResponse({ status: 200, description: 'Batch valuations retrieved' })
@ApiOperation({ summary: 'Get valuations for multiple properties', description: 'Processes a batch of property IDs and optional features for valuation.' })
@ApiBody({ type: BatchValuationRequestDto })
@ApiResponse({ status: 200, description: 'Batch valuations processed.' })
@ApiStandardErrorResponse([400, 401])
@HttpCode(HttpStatus.OK)
async getBatchValuations(
@Body() requestBody: BatchValuationRequestDto,
Expand Down
Loading