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
13 changes: 13 additions & 0 deletions apps/forcast-api/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=rahat_triggers

# Application Configuration
PORT=8000
NODE_ENV=development

# Optional: You can also set DATABASE_URL directly if you prefer
# DATABASE_URL=postgresql://postgres:postgres@localhost:5432/rahat_triggers
56 changes: 56 additions & 0 deletions apps/forcast-api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# compiled output
/dist
/node_modules
/build

# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# OS
.DS_Store

# Tests
/coverage
/.nyc_output

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# temp directory
.temp
.tmp

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
4 changes: 4 additions & 0 deletions apps/forcast-api/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}
187 changes: 187 additions & 0 deletions apps/forcast-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# Mock API - GLOFAS & GFH Data Simulator

A NestJS-based mock API server that simulates external data sources (GLOFAS, GFH, DHM) for testing and development purposes. This mock server eliminates the need to call real external APIs during development and testing, providing fast, reliable, and predictable responses.

## 🎯 Overview

The Mock API is built with NestJS and provides mock endpoints that replicate the behavior of external flood forecasting systems. It uses the `mock` database schema to store and serve test data, allowing developers to test trigger systems without depending on external API availability, rate limits, or network connectivity.

## 🚀 Purpose

**Why Mock API?**

Instead of calling real external APIs during development/testing:

- ❌ **Real APIs**: Slow, rate-limited, require authentication, unstable
- ✅ **Mock API**: Fast, unlimited calls, no auth needed, always available

This mock server enables:

- 🧪 **Isolated Testing**: Test without external dependencies
- ⚡ **Fast Development**: No network latency or API delays
- 🎭 **Scenario Simulation**: Test edge cases (high floods, API failures, etc.)
- 💰 **Cost Savings**: No API usage costs or rate limit concerns

## ✨ Features

- **Mock Forecast Endpoints**: Simulates GLOFAS and GFH flood forecast APIs
- **Mock Trigger Management**: CRUD operations for testing trigger workflows
- **Database Integration**: Uses `@lib/database` with `mock` schema
- **Swagger Documentation**: Interactive API documentation at `/swagger`
- **Winston Logging**: Structured logging for debugging
- **CORS Enabled**: Cross-origin requests supported

## 📁 Project Structure

```text
src/
├── app.controller.ts # Main application controller
├── app.module.ts # Root module with database & HTTP setup
├── app.service.ts # Application service
├── main.ts # Bootstrap file (port: 3005)
├── all-exceptions.filter.ts # Global exception handler
├── helpers/
│ └── winston.logger.ts # Winston logger configuration
├── forecast/
│ ├── forecast.controller.ts # Mock forecast endpoints (GLOFAS, GFH)
│ ├── forecast.service.ts # Forecast data service
│ └── forecast.module.ts # Forecast module
├── types/ # TypeScript type definitions
└── utils/ # Utility functions
```

## 🛠️ Environment Configuration

Copy the environment example file and update the values:

```bash
cp .env.example .env
```

Update the `.env` file:

```bash
# Database Configuration (uses mock schema)
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=rahat_triggers

# Application Configuration
PORT=3005
NODE_ENV=development

# Optional: Direct DATABASE_URL
# DATABASE_URL=postgresql://postgres:postgres@localhost:5432/rahat_triggers?schema=mock
```

**Note:** The mock API uses the same database as the triggers app but operates in the `mock` schema for data isolation.

## 📦 Installation & Setup

### 1. Install Dependencies

From the monorepo root:

```bash
pnpm install
```

### 2. Setup Database

Generate Prisma client and run migrations:

```bash
# Generate Prisma client
pnpm --filter @lib/database db:generate

# Run migrations
pnpm --filter @lib/database db:migrate

# Optional: Seed mock data
pnpm --filter @lib/database seed
```

### 3. Build the Package

```bash
# Build mock-api
pnpm --filter mock-api build

# Or build all packages
pnpm build
```

## 🚀 Running the Application

### Development Mode

```bash
# From monorepo root
pnpm --filter mock-api dev

# Or from app directory
cd apps/mock-api
pnpm dev
```

The server will start at: **`http://localhost:3005`**

### Code Quality

```bash
# Run ESLint
pnpm lint

# Format code with Prettier
pnpm format
```

## API Endpoints

The application provides the following main endpoints:

### Health Check

- `GET /v1` - Basic health check endpoint
- `GET /v1/health` - Detailed health status

## API Documentation

When the application is running, you can access the interactive Swagger documentation at:

```
http://localhost:8000/swagger
```

The Swagger UI provides detailed information about all available endpoints, request/response schemas, and allows you to test the API directly from the browser.

## Database Integration

The application uses the shared `@lib/database` package which provides:

- **Prisma ORM Integration**: Type-safe database operations
- **Connection Management**: Automatic database connection handling
- **Exception Handling**: Comprehensive error handling for database operations
- **Migration Support**: Database schema migrations

The database configuration is handled automatically through environment variables, supporting both direct DATABASE_URL and individual connection parameters.

## Error Handling

The application includes comprehensive error handling:

- **Global Exception Filter**: Catches and formats all unhandled exceptions
- **Prisma Exception Filter**: Specifically handles database-related errors
- **Validation Errors**: Automatic validation of request data
- **Structured Error Responses**: Consistent error response format

## Development Workflow

1. **Start the database**: Ensure PostgreSQL is running
2. **Set up environment**: Configure your `.env` file
3. **Install dependencies**: Run `pnpm install` from the root
4. **Generate Prisma client**: Run `pnpm --filter @lib/database db:generate`
5. **Run migrations**: Run `pnpm --filter @lib/database db:migrate`
6. **Start development server**: Run `pnpm dev`
5 changes: 5 additions & 0 deletions apps/forcast-api/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import("eslint").Linter.Config[]} */

import { nestjs } from '@workspace/eslint-config/nest-js';

export default [...nestjs];
10 changes: 10 additions & 0 deletions apps/forcast-api/nest-cli.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"assets": [],
"watchAssets": false
}
}
71 changes: 71 additions & 0 deletions apps/forcast-api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"name": "forcast-api",
"version": "0.0.1",
"description": "Nestjs API Seed",
"author": "Dipesh Kumar Sah",
"private": true,
"license": "UNLICENSED",
"module": "commonjs",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"dev": "nest start --watch",
"dev:debug": "nest start --watch --debug",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@lib/core": "workspace:*",
"@lib/database": "workspace:*",
"@nestjs/platform-express": "^10.3.7",
"cheerio": "^1.1.2",
"expr-eval": "^2.0.2",
"luxon": "^3.7.2",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.1",
"@swc/cli": "^0.6.0",
"@swc/core": "^1.10.7",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.14",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.2",
"@workspace/eslint-config": "workspace:*",
"@workspace/typescript-config": "workspace:*",
"jest": "^29.7.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"tsconfig-paths": "^4.2.0"
},
"packageManager": "pnpm@8.14.1",
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
38 changes: 38 additions & 0 deletions apps/forcast-api/src/all-exceptions.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();

let status = HttpStatus.INTERNAL_SERVER_ERROR;
let message = 'Internal server error';

if (exception instanceof HttpException) {
status = exception.getStatus();
const exceptionResponse = exception.getResponse();
message =
typeof exceptionResponse === 'string'
? exceptionResponse
: (exceptionResponse as any).message || message;
} else if (exception instanceof Error) {
message = exception.message;
}

response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message,
});
}
}
Loading