fix path controller

This commit is contained in:
David 2025-10-27 20:49:06 +01:00
parent 436a406af4
commit 07b08e3014
19 changed files with 2628 additions and 525 deletions

View File

@ -22,7 +22,12 @@
"Bash(PGPASSWORD=xpeditis_dev_password psql -h localhost -p 5432 -U xpeditis -d xpeditis_dev -c \"SELECT id, name FROM organizations LIMIT 5;\")",
"Read(//Users/david/Documents/xpeditis/**)",
"Bash(lsof:*)",
"Bash(xargs kill:*)"
"Bash(xargs kill:*)",
"Bash(TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMzg1MDVkMi1hMmVlLTQ5NmMtOWNjZC1iNjUyN2FjMzcxODgiLCJlbWFpbCI6InRlc3Q0QHhwZWRpdGlzLmNvbSIsInJvbGUiOiJ1c2VyIiwib3JnYW5pemF0aW9uSWQiOiJhMTIzNDU2Ny0wMDAwLTQwMDAtODAwMC0wMDAwMDAwMDAwMDEiLCJ0eXBlIjoiYWNjZXNzIiwiaWF0IjoxNzYxNTkwNzYxLCJleHAiOjE3NjE1OTE2NjF9.Jr9BbldL3TGW4pbXXc1XomzVMBRHn4lIgkKJ7XyjJgw\")",
"Bash(PGPASSWORD=xpeditis_dev_password psql -h localhost -p 5432 -U xpeditis -d xpeditis_dev -c \"SELECT id, email, role FROM users LIMIT 5;\")",
"Bash(PGPASSWORD=xpeditis_dev_password psql -h localhost -p 5432 -U xpeditis -d xpeditis_dev -c \"UPDATE users SET role = ''ADMIN'' WHERE email = ''test4@xpeditis.com''; SELECT id, email, role FROM users WHERE email = ''test4@xpeditis.com'';\")",
"Bash(TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMzg1MDVkMi1hMmVlLTQ5NmMtOWNjZC1iNjUyN2FjMzcxODgiLCJlbWFpbCI6InRlc3Q0QHhwZWRpdGlzLmNvbSIsInJvbGUiOiJBRE1JTiIsIm9yZ2FuaXphdGlvbklkIjoiYTEyMzQ1NjctMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAxIiwidHlwZSI6ImFjY2VzcyIsImlhdCI6MTc2MTU5MDg0OSwiZXhwIjoxNzYxNTkxNzQ5fQ.CPFhvgASXuklZ81FiuX_XwYZfh8xKG4tNG70JQ4Dv8M\")",
"Bash(PGPASSWORD=xpeditis_dev_password psql -h localhost -p 5432 -U xpeditis -d xpeditis_dev -c \"UPDATE users SET role = ''ADMIN'' WHERE email = ''dharnaud77@hotmail.fr''; SELECT id, email, role FROM users WHERE email = ''dharnaud77@hotmail.fr'';\")"
],
"deny": [],
"ask": []

View File

@ -45,7 +45,7 @@ import { CsvRateMapper } from '../../mappers/csv-rate.mapper';
* Protected by JWT + Roles guard
*/
@ApiTags('Admin - CSV Rates')
@Controller('api/v1/admin/csv-rates')
@Controller('admin/csv-rates')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('ADMIN') // ⚠️ ONLY ADMIN can access these endpoints

View File

@ -52,7 +52,7 @@ class AuditLogQueryDto {
@ApiTags('Audit Logs')
@ApiBearerAuth()
@Controller('api/v1/audit-logs')
@Controller('audit-logs')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AuditController {
constructor(private readonly auditService: AuditService) {}

View File

@ -56,7 +56,7 @@ import { WebhookService } from '../services/webhook.service';
import { WebhookEvent } from '../../domain/entities/webhook.entity';
@ApiTags('Bookings')
@Controller('api/v1/bookings')
@Controller('bookings')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
export class BookingsController {

View File

@ -44,7 +44,7 @@ class NotificationResponseDto {
@ApiTags('Notifications')
@ApiBearerAuth()
@Controller('api/v1/notifications')
@Controller('notifications')
@UseGuards(JwtAuthGuard)
export class NotificationsController {
constructor(private readonly notificationService: NotificationService) {}

View File

@ -54,7 +54,7 @@ import { v4 as uuidv4 } from 'uuid';
* - List organizations
*/
@ApiTags('Organizations')
@Controller('api/v1/organizations')
@Controller('organizations')
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth()
export class OrganizationsController {

View File

@ -29,7 +29,7 @@ import { AvailableCompaniesDto, FilterOptionsDto } from '../dto/csv-rate-upload.
import { CsvRateMapper } from '../mappers/csv-rate.mapper';
@ApiTags('Rates')
@Controller('api/v1/rates')
@Controller('rates')
@ApiBearerAuth()
export class RatesController {
private readonly logger = new Logger(RatesController.name);

View File

@ -61,7 +61,7 @@ import * as crypto from 'crypto';
* - Update own password
*/
@ApiTags('Users')
@Controller('api/v1/users')
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth()
export class UsersController {

View File

@ -59,7 +59,7 @@ class WebhookResponseDto {
@ApiTags('Webhooks')
@ApiBearerAuth()
@Controller('api/v1/webhooks')
@Controller('webhooks')
@UseGuards(JwtAuthGuard, RolesGuard)
export class WebhooksController {
constructor(private readonly webhookService: WebhookService) {}

View File

@ -8,7 +8,7 @@ import { Controller, Get, UseGuards, Request } from '@nestjs/common';
import { AnalyticsService } from '../services/analytics.service';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
@Controller('api/v1/dashboard')
@Controller('dashboard')
@UseGuards(JwtAuthGuard)
export class DashboardController {
constructor(private readonly analyticsService: AnalyticsService) {}

View File

@ -1,52 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class EnableFuzzySearch1700000000000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Enable pg_trgm extension for trigram similarity search
await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS pg_trgm;`);
// Create GIN indexes for full-text search on bookings
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_booking_number_trgm
ON bookings USING gin(booking_number gin_trgm_ops);
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_shipper_name_trgm
ON bookings USING gin(shipper_name gin_trgm_ops);
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_consignee_name_trgm
ON bookings USING gin(consignee_name gin_trgm_ops);
`);
// Create full-text search indexes using ts_vector
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_booking_number_fts
ON bookings USING gin(to_tsvector('english', booking_number));
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_shipper_name_fts
ON bookings USING gin(to_tsvector('english', shipper_name));
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_consignee_name_fts
ON bookings USING gin(to_tsvector('english', consignee_name));
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Drop indexes
await queryRunner.query(`DROP INDEX IF EXISTS idx_booking_number_trgm;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_shipper_name_trgm;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_consignee_name_trgm;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_booking_number_fts;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_shipper_name_fts;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_consignee_name_fts;`);
// Note: We don't drop the pg_trgm extension as other parts of the system might use it
}
}

View File

@ -1,66 +1,66 @@
/**
* Migration: Create Users Table
*/
import { MigrationInterface, QueryRunner } from 'typeorm';
export class CreateUsers1730000000002 implements MigrationInterface {
name = 'CreateUsers1730000000002';
public async up(queryRunner: QueryRunner): Promise<void> {
// Create users table
await queryRunner.query(`
CREATE TABLE "users" (
"id" UUID NOT NULL DEFAULT uuid_generate_v4(),
"organization_id" UUID NOT NULL,
"email" VARCHAR(255) NOT NULL,
"password_hash" VARCHAR(255) NOT NULL,
"role" VARCHAR(50) NOT NULL,
"first_name" VARCHAR(100) NOT NULL,
"last_name" VARCHAR(100) NOT NULL,
"phone_number" VARCHAR(20) NULL,
"totp_secret" VARCHAR(255) NULL,
"is_email_verified" BOOLEAN NOT NULL DEFAULT FALSE,
"is_active" BOOLEAN NOT NULL DEFAULT TRUE,
"last_login_at" TIMESTAMP NULL,
"created_at" TIMESTAMP NOT NULL DEFAULT NOW(),
"updated_at" TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT "pk_users" PRIMARY KEY ("id"),
CONSTRAINT "uq_users_email" UNIQUE ("email"),
CONSTRAINT "fk_users_organization" FOREIGN KEY ("organization_id")
REFERENCES "organizations"("id") ON DELETE CASCADE,
CONSTRAINT "chk_users_email" CHECK (LOWER("email") = "email"),
CONSTRAINT "chk_users_role" CHECK ("role" IN ('ADMIN', 'MANAGER', 'USER', 'VIEWER'))
)
`);
// Create indexes
await queryRunner.query(`
CREATE INDEX "idx_users_email" ON "users" ("email")
`);
await queryRunner.query(`
CREATE INDEX "idx_users_organization" ON "users" ("organization_id")
`);
await queryRunner.query(`
CREATE INDEX "idx_users_role" ON "users" ("role")
`);
await queryRunner.query(`
CREATE INDEX "idx_users_active" ON "users" ("is_active")
`);
// Add comments
await queryRunner.query(`
COMMENT ON TABLE "users" IS 'User accounts for authentication and authorization'
`);
await queryRunner.query(`
COMMENT ON COLUMN "users"."password_hash" IS 'Bcrypt hash (12+ rounds)'
`);
await queryRunner.query(`
COMMENT ON COLUMN "users"."totp_secret" IS 'TOTP secret for 2FA (optional)'
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "users"`);
}
}
/**
* Migration: Create Users Table
*/
import { MigrationInterface, QueryRunner } from 'typeorm';
export class CreateUsers1730000000002 implements MigrationInterface {
name = 'CreateUsers1730000000002';
public async up(queryRunner: QueryRunner): Promise<void> {
// Create users table
await queryRunner.query(`
CREATE TABLE "users" (
"id" UUID NOT NULL DEFAULT uuid_generate_v4(),
"organization_id" UUID NOT NULL,
"email" VARCHAR(255) NOT NULL,
"password_hash" VARCHAR(255) NOT NULL,
"role" VARCHAR(50) NOT NULL,
"first_name" VARCHAR(100) NOT NULL,
"last_name" VARCHAR(100) NOT NULL,
"phone_number" VARCHAR(20) NULL,
"totp_secret" VARCHAR(255) NULL,
"is_email_verified" BOOLEAN NOT NULL DEFAULT FALSE,
"is_active" BOOLEAN NOT NULL DEFAULT TRUE,
"last_login_at" TIMESTAMP NULL,
"created_at" TIMESTAMP NOT NULL DEFAULT NOW(),
"updated_at" TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT "pk_users" PRIMARY KEY ("id"),
CONSTRAINT "uq_users_email" UNIQUE ("email"),
CONSTRAINT "fk_users_organization" FOREIGN KEY ("organization_id")
REFERENCES "organizations"("id") ON DELETE CASCADE,
CONSTRAINT "chk_users_email" CHECK (LOWER("email") = "email"),
CONSTRAINT "chk_users_role" CHECK ("role" IN ('ADMIN', 'MANAGER', 'USER', 'VIEWER'))
)
`);
// Create indexes
await queryRunner.query(`
CREATE INDEX "idx_users_email" ON "users" ("email")
`);
await queryRunner.query(`
CREATE INDEX "idx_users_organization" ON "users" ("organization_id")
`);
await queryRunner.query(`
CREATE INDEX "idx_users_role" ON "users" ("role")
`);
await queryRunner.query(`
CREATE INDEX "idx_users_active" ON "users" ("is_active")
`);
// Add comments
await queryRunner.query(`
COMMENT ON TABLE "users" IS 'User accounts for authentication and authorization'
`);
await queryRunner.query(`
COMMENT ON COLUMN "users"."password_hash" IS 'Bcrypt hash (12+ rounds)'
`);
await queryRunner.query(`
COMMENT ON COLUMN "users"."totp_secret" IS 'TOTP secret for 2FA (optional)'
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "users"`);
}
}

View File

@ -1,52 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class EnableFuzzySearch1730000000007 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Enable pg_trgm extension for trigram similarity search
await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS pg_trgm;`);
// Create GIN indexes for full-text search on bookings
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_booking_number_trgm
ON bookings USING gin(booking_number gin_trgm_ops);
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_shipper_name_trgm
ON bookings USING gin(shipper_name gin_trgm_ops);
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_consignee_name_trgm
ON bookings USING gin(consignee_name gin_trgm_ops);
`);
// Create full-text search indexes using ts_vector
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_booking_number_fts
ON bookings USING gin(to_tsvector('english', booking_number));
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_shipper_name_fts
ON bookings USING gin(to_tsvector('english', shipper_name));
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS idx_consignee_name_fts
ON bookings USING gin(to_tsvector('english', consignee_name));
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Drop indexes
await queryRunner.query(`DROP INDEX IF EXISTS idx_booking_number_trgm;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_shipper_name_trgm;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_consignee_name_trgm;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_booking_number_fts;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_shipper_name_fts;`);
await queryRunner.query(`DROP INDEX IF EXISTS idx_consignee_name_fts;`);
// Note: We don't drop the pg_trgm extension as other parts of the system might use it
}
}

View File

@ -1,137 +0,0 @@
import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
export class CreateAuditLogsTable1730000000008 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'audit_logs',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
},
{
name: 'action',
type: 'varchar',
length: '100',
isNullable: false,
},
{
name: 'status',
type: 'varchar',
length: '20',
isNullable: false,
},
{
name: 'user_id',
type: 'uuid',
isNullable: false,
},
{
name: 'user_email',
type: 'varchar',
length: '255',
isNullable: false,
},
{
name: 'organization_id',
type: 'uuid',
isNullable: false,
},
{
name: 'resource_type',
type: 'varchar',
length: '100',
isNullable: true,
},
{
name: 'resource_id',
type: 'varchar',
length: '255',
isNullable: true,
},
{
name: 'resource_name',
type: 'varchar',
length: '255',
isNullable: true,
},
{
name: 'metadata',
type: 'jsonb',
isNullable: true,
},
{
name: 'ip_address',
type: 'varchar',
length: '45',
isNullable: true,
},
{
name: 'user_agent',
type: 'text',
isNullable: true,
},
{
name: 'error_message',
type: 'text',
isNullable: true,
},
{
name: 'timestamp',
type: 'timestamp',
default: 'CURRENT_TIMESTAMP',
isNullable: false,
},
],
}),
true,
);
// Create indexes for efficient querying
await queryRunner.createIndex(
'audit_logs',
new TableIndex({
name: 'idx_audit_logs_organization_timestamp',
columnNames: ['organization_id', 'timestamp'],
}),
);
await queryRunner.createIndex(
'audit_logs',
new TableIndex({
name: 'idx_audit_logs_user_timestamp',
columnNames: ['user_id', 'timestamp'],
}),
);
await queryRunner.createIndex(
'audit_logs',
new TableIndex({
name: 'idx_audit_logs_resource',
columnNames: ['resource_type', 'resource_id'],
}),
);
await queryRunner.createIndex(
'audit_logs',
new TableIndex({
name: 'idx_audit_logs_action',
columnNames: ['action'],
}),
);
await queryRunner.createIndex(
'audit_logs',
new TableIndex({
name: 'idx_audit_logs_timestamp',
columnNames: ['timestamp'],
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('audit_logs');
}
}

View File

@ -1,109 +0,0 @@
import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
export class CreateNotificationsTable1730000000009 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'notifications',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
},
{
name: 'user_id',
type: 'uuid',
isNullable: false,
},
{
name: 'organization_id',
type: 'uuid',
isNullable: false,
},
{
name: 'type',
type: 'varchar',
length: '50',
isNullable: false,
},
{
name: 'priority',
type: 'varchar',
length: '20',
isNullable: false,
},
{
name: 'title',
type: 'varchar',
length: '255',
isNullable: false,
},
{
name: 'message',
type: 'text',
isNullable: false,
},
{
name: 'metadata',
type: 'jsonb',
isNullable: true,
},
{
name: 'read',
type: 'boolean',
default: false,
isNullable: false,
},
{
name: 'read_at',
type: 'timestamp',
isNullable: true,
},
{
name: 'action_url',
type: 'varchar',
length: '500',
isNullable: true,
},
{
name: 'created_at',
type: 'timestamp',
default: 'CURRENT_TIMESTAMP',
isNullable: false,
},
],
}),
true,
);
// Create indexes for efficient querying
await queryRunner.createIndex(
'notifications',
new TableIndex({
name: 'idx_notifications_user_read_created',
columnNames: ['user_id', 'read', 'created_at'],
}),
);
await queryRunner.createIndex(
'notifications',
new TableIndex({
name: 'idx_notifications_organization_created',
columnNames: ['organization_id', 'created_at'],
}),
);
await queryRunner.createIndex(
'notifications',
new TableIndex({
name: 'idx_notifications_user_created',
columnNames: ['user_id', 'created_at'],
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('notifications');
}
}

View File

@ -1,99 +0,0 @@
import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
export class CreateWebhooksTable1730000000010 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'webhooks',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
},
{
name: 'organization_id',
type: 'uuid',
isNullable: false,
},
{
name: 'url',
type: 'varchar',
length: '500',
isNullable: false,
},
{
name: 'events',
type: 'text',
isNullable: false,
},
{
name: 'secret',
type: 'varchar',
length: '255',
isNullable: false,
},
{
name: 'status',
type: 'varchar',
length: '20',
isNullable: false,
},
{
name: 'description',
type: 'text',
isNullable: true,
},
{
name: 'headers',
type: 'jsonb',
isNullable: true,
},
{
name: 'retry_count',
type: 'int',
default: 0,
isNullable: false,
},
{
name: 'last_triggered_at',
type: 'timestamp',
isNullable: true,
},
{
name: 'failure_count',
type: 'int',
default: 0,
isNullable: false,
},
{
name: 'created_at',
type: 'timestamp',
default: 'CURRENT_TIMESTAMP',
isNullable: false,
},
{
name: 'updated_at',
type: 'timestamp',
default: 'CURRENT_TIMESTAMP',
isNullable: false,
},
],
}),
true,
);
// Create index for efficient querying
await queryRunner.createIndex(
'webhooks',
new TableIndex({
name: 'idx_webhooks_organization_status',
columnNames: ['organization_id', 'status'],
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('webhooks');
}
}

455
postman/README.md Normal file
View File

@ -0,0 +1,455 @@
# Xpeditis API - Postman Collection
Complete Postman collection for testing the Xpeditis B2B Maritime Freight Platform API.
## Overview
This Postman collection provides comprehensive API testing coverage for all Xpeditis endpoints, including:
- Authentication (register, login, refresh tokens)
- Rate search (standard and CSV-based with advanced filters)
- Booking management (create, list, search, export)
- User management (CRUD operations)
- Organization management
- Notifications
- Audit logs
- GDPR compliance
- Webhooks
- Dashboard analytics
- Admin CSV rate uploads
- Health checks
## Files
- **Xpeditis_Complete_API.postman_collection.json** - Complete API collection with all endpoints
- **Xpeditis_Local.postman_environment.json** - Local development environment variables
## Quick Start
### 1. Import Collection and Environment
1. Open Postman
2. Click **Import** button
3. Select both files:
- `Xpeditis_Complete_API.postman_collection.json`
- `Xpeditis_Local.postman_environment.json`
4. Click **Import**
### 2. Select Environment
1. Click the environment dropdown (top right)
2. Select **Xpeditis Local Environment**
### 3. Start Backend Server
Ensure the Xpeditis backend is running:
```bash
cd apps/backend
npm run dev
```
Backend will be available at: `http://localhost:4000`
### 4. Test Authentication
1. Open the **Authentication** folder
2. Run **Register New User** or **Login**
3. The access token will be automatically saved to the environment
Now you're ready to test all authenticated endpoints!
## API Endpoint Summary
### Authentication (5 endpoints)
- **POST** `/auth/register` - Register new user (Public)
- **POST** `/auth/login` - Login with credentials (Public)
- **POST** `/auth/refresh` - Refresh access token (Public)
- **POST** `/auth/logout` - Logout user (Auth required)
- **GET** `/auth/me` - Get current user profile (Auth required)
### Rates (4 endpoints)
- **POST** `/api/v1/rates/search` - Search shipping rates (Auth required)
- **POST** `/api/v1/rates/search-csv` - Advanced CSV rate search (Auth required)
- **GET** `/api/v1/rates/companies` - Get available companies (Auth required)
- **GET** `/api/v1/rates/filters/options` - Get filter options (Auth required)
### Bookings (8 endpoints)
- **POST** `/api/v1/bookings` - Create booking (Auth required)
- **GET** `/api/v1/bookings/:id` - Get booking by ID (Auth required)
- **GET** `/api/v1/bookings/number/:bookingNumber` - Get booking by number (Auth required)
- **GET** `/api/v1/bookings` - List bookings with pagination (Auth required)
- **GET** `/api/v1/bookings/search/fuzzy` - Fuzzy search bookings (Auth required)
- **GET** `/api/v1/bookings/advanced/search` - Advanced search with filters (Auth required)
- **POST** `/api/v1/bookings/export` - Export bookings (CSV/Excel/JSON) (Auth required)
### Users (6 endpoints)
- **POST** `/api/v1/users` - Create user (Admin/Manager)
- **GET** `/api/v1/users/:id` - Get user by ID (Auth required)
- **PATCH** `/api/v1/users/:id` - Update user (Admin/Manager)
- **DELETE** `/api/v1/users/:id` - Delete user (Admin only)
- **GET** `/api/v1/users` - List users (Auth required)
- **PATCH** `/api/v1/users/me/password` - Update own password (Auth required)
### Organizations (4 endpoints)
- **POST** `/api/v1/organizations` - Create organization (Admin only)
- **GET** `/api/v1/organizations/:id` - Get organization by ID (Auth required)
- **PATCH** `/api/v1/organizations/:id` - Update organization (Admin/Manager)
- **GET** `/api/v1/organizations` - List organizations (Auth required)
### Notifications (6 endpoints)
- **GET** `/api/v1/notifications` - Get notifications (Auth required)
- **GET** `/api/v1/notifications/unread` - Get unread notifications (Auth required)
- **GET** `/api/v1/notifications/unread/count` - Get unread count (Auth required)
- **PATCH** `/api/v1/notifications/:id/read` - Mark as read (Auth required)
- **POST** `/api/v1/notifications/read-all` - Mark all as read (Auth required)
- **DELETE** `/api/v1/notifications/:id` - Delete notification (Auth required)
### Audit Logs (4 endpoints)
- **GET** `/api/v1/audit-logs` - Get audit logs with filters (Admin/Manager)
- **GET** `/api/v1/audit-logs/resource/:type/:id` - Get resource audit trail (Auth required)
- **GET** `/api/v1/audit-logs/organization/activity` - Get organization activity (Admin/Manager)
- **GET** `/api/v1/audit-logs/user/:userId/activity` - Get user activity (Admin/Manager)
### GDPR (6 endpoints)
- **GET** `/gdpr/export` - Export user data as JSON (Auth required)
- **GET** `/gdpr/export/csv` - Export user data as CSV (Auth required)
- **DELETE** `/gdpr/delete-account` - Delete account (Auth required)
- **POST** `/gdpr/consent` - Record consent (Auth required)
- **POST** `/gdpr/consent/withdraw` - Withdraw consent (Auth required)
- **GET** `/gdpr/consent` - Get consent status (Auth required)
### Webhooks (7 endpoints)
- **POST** `/api/v1/webhooks` - Create webhook (Admin/Manager)
- **GET** `/api/v1/webhooks` - Get all webhooks (Admin/Manager)
- **GET** `/api/v1/webhooks/:id` - Get webhook by ID (Admin/Manager)
- **PATCH** `/api/v1/webhooks/:id` - Update webhook (Admin/Manager)
- **POST** `/api/v1/webhooks/:id/activate` - Activate webhook (Admin/Manager)
- **POST** `/api/v1/webhooks/:id/deactivate` - Deactivate webhook (Admin/Manager)
- **DELETE** `/api/v1/webhooks/:id` - Delete webhook (Admin/Manager)
### Dashboard (4 endpoints)
- **GET** `/api/v1/dashboard/kpis` - Get dashboard KPIs (Auth required)
- **GET** `/api/v1/dashboard/bookings-chart` - Get bookings chart data (Auth required)
- **GET** `/api/v1/dashboard/top-trade-lanes` - Get top trade lanes (Auth required)
- **GET** `/api/v1/dashboard/alerts` - Get dashboard alerts (Auth required)
### Admin - CSV Rates (5 endpoints)
- **POST** `/api/v1/admin/csv-rates/upload` - Upload CSV rate file (Admin only)
- **GET** `/api/v1/admin/csv-rates/config` - Get all configurations (Admin only)
- **GET** `/api/v1/admin/csv-rates/config/:companyName` - Get config by company (Admin only)
- **POST** `/api/v1/admin/csv-rates/validate/:companyName` - Validate CSV file (Admin only)
- **DELETE** `/api/v1/admin/csv-rates/config/:companyName` - Delete configuration (Admin only)
### Health (3 endpoints)
- **GET** `/health` - Health check (Public)
- **GET** `/health/ready` - Readiness check (Public)
- **GET** `/health/live` - Liveness check (Public)
**Total: 68 endpoints**
## Environment Variables
The environment file includes the following variables:
| Variable | Description | Auto-populated |
|----------|-------------|----------------|
| `baseUrl` | API base URL | No (default: `http://localhost:4000`) |
| `token` | JWT access token | Yes (after login/register) |
| `refreshToken` | JWT refresh token | Yes (after login/register) |
| `userId` | Current user ID | Yes (after login/register) |
| `userEmail` | Current user email | Yes (after login/register) |
| `organizationId` | Current user's organization ID | Yes (after login/register) |
| `rateQuoteId` | Last searched rate quote ID | Yes (after rate search) |
| `bookingId` | Last created booking ID | Yes (after booking creation) |
| `bookingNumber` | Last created booking number | Yes (after booking creation) |
Variables marked "Yes" under **Auto-populated** are automatically set by test scripts when you run the corresponding requests.
## Common Workflows
### 1. Complete Booking Flow
1. **Register/Login** → Auto-saves token
2. **Search Rates** → Auto-saves `rateQuoteId`
3. **Create Booking** → Auto-saves `bookingId` and `bookingNumber`
4. **Get Booking Details** → Use saved `bookingId`
5. **List Bookings** → View all bookings
6. **Export Bookings** → Download CSV/Excel
### 2. User Management Flow
1. **Login as Admin** → Auto-saves token
2. **Create User** → Create new team member
3. **List Users** → View all users
4. **Update User** → Change role or details
5. **Delete User** → Deactivate account
### 3. CSV Rate Upload Flow (Admin)
1. **Login as Admin** → Auto-saves token
2. **Upload CSV File** → Upload carrier rates
3. **Validate CSV** → Check file structure
4. **Search CSV Rates** → Test the uploaded rates
5. **Get Filter Options** → View available carriers
## Authentication
Most endpoints require authentication. The collection uses **Bearer Token** authentication.
### How It Works
1. Run **Register** or **Login** request
2. Test script automatically saves `accessToken` to `{{token}}` variable
3. All authenticated requests use `{{token}}` in the Authorization header
4. Tokens expire after 15 minutes (configurable)
5. Use **Refresh Access Token** to get a new token
### Manual Token Setup
If needed, you can manually set the token:
1. Copy the access token from a login response
2. Go to Environments → Xpeditis Local Environment
3. Paste into the `token` variable
4. Save
## Role-Based Access Control (RBAC)
Different endpoints require different roles:
- **Public**: No authentication required
- **User**: Any authenticated user
- **Manager**: Manager or Admin role
- **Admin**: Admin role only
| Role | Permissions |
|------|-------------|
| **viewer** | Read-only access to bookings and rates |
| **user** | Create and view own bookings, search rates |
| **manager** | Manage organization bookings and users |
| **admin** | Full system access, manage all organizations |
## Testing Best Practices
### 1. Run Requests in Order
For best results, run requests in this order:
1. **Health Check** (verify backend is running)
2. **Register/Login** (get authentication token)
3. **Test feature-specific endpoints**
### 2. Check Response Status
- ✅ **200/201** - Success
- ⚠️ **400** - Validation error (check request body)
- 🔒 **401** - Unauthorized (token missing/expired)
- 🚫 **403** - Forbidden (insufficient permissions)
- ❌ **404** - Not found
- 💥 **500** - Server error
### 3. Use Console for Debugging
The collection includes test scripts that log useful info:
1. Open Postman Console (bottom left)
2. Run a request
3. View logged variables and debug info
### 4. Validate Responses
Test scripts automatically validate responses and extract key data. Check the **Test Results** tab after each request.
## Example Request Bodies
### Register User
```json
{
"email": "john.doe@acme.com",
"password": "SecurePassword123!",
"firstName": "John",
"lastName": "Doe"
}
```
### Search Rates
```json
{
"origin": "NLRTM",
"destination": "CNSHA",
"containerType": "40HC",
"mode": "FCL",
"departureDate": "2025-02-15",
"quantity": 2,
"weight": 20000,
"volume": 50.5,
"isHazmat": false
}
```
### Create Booking
```json
{
"rateQuoteId": "{{rateQuoteId}}",
"shipper": {
"name": "Acme Corporation",
"address": {
"street": "123 Main Street",
"city": "Rotterdam",
"postalCode": "3000 AB",
"country": "NL"
},
"contactName": "John Doe",
"contactEmail": "john.doe@acme.com",
"contactPhone": "+31612345678"
},
"consignee": {
"name": "Global Trading Ltd",
"address": {
"street": "456 Harbor Road",
"city": "Shanghai",
"postalCode": "200000",
"country": "CN"
},
"contactName": "Jane Smith",
"contactEmail": "jane.smith@globaltrading.com",
"contactPhone": "+8613800000000"
},
"cargoDescription": "Electronics and consumer goods - fragile items",
"containers": [
{
"type": "40HC",
"vgm": 22000,
"sealNumber": "SEAL123456"
}
],
"specialInstructions": "Please handle with care. Delivery before 5 PM."
}
```
## Advanced Features
### 1. Fuzzy Search
The fuzzy search endpoint is tolerant to typos and partial matches:
```
GET /api/v1/bookings/search/fuzzy?q=WCM-2025&limit=20
```
Searches across:
- Booking number
- Shipper name
- Consignee name
### 2. Advanced Filtering
Use the advanced search endpoint for complex queries:
```
GET /api/v1/bookings/advanced/search?
status=confirmed,in_transit&
createdFrom=2025-01-01&
shipper=Acme&
sortBy=createdAt&
sortOrder=desc
```
### 3. Export Formats
Export bookings in multiple formats:
- **CSV** - Spreadsheet format
- **XLSX** - Excel format
- **JSON** - Raw data format
Specify fields to export:
```json
{
"format": "csv",
"fields": [
"bookingNumber",
"status",
"shipper",
"consignee",
"origin",
"destination",
"carrier",
"createdAt"
]
}
```
### 4. Webhook Events
Available webhook events:
- `booking.created`
- `booking.confirmed`
- `booking.cancelled`
- `shipment.departed`
- `shipment.arrived`
- `document.uploaded`
## Troubleshooting
### Issue: 401 Unauthorized
**Solution**: Token expired or missing
1. Run **Login** request again
2. Or use **Refresh Access Token**
### Issue: 403 Forbidden
**Solution**: Insufficient permissions
- Check your user role
- Some endpoints require Admin or Manager role
### Issue: 404 Not Found
**Solution**: Resource doesn't exist
- Verify the ID in the URL
- Check if the resource belongs to your organization
### Issue: 500 Internal Server Error
**Solution**: Backend error
1. Check backend logs
2. Verify database is running
3. Check Redis connection
### Issue: Connection Refused
**Solution**: Backend not running
```bash
cd apps/backend
npm run dev
```
## Additional Resources
- **Swagger UI**: http://localhost:4000/api/docs
- **Architecture Docs**: [ARCHITECTURE.md](../ARCHITECTURE.md)
- **Deployment Guide**: [DEPLOYMENT.md](../DEPLOYMENT.md)
- **Test Coverage**: [TEST_COVERAGE_REPORT.md](../TEST_COVERAGE_REPORT.md)
## Support
For issues or questions:
1. Check the [API documentation](http://localhost:4000/api/docs)
2. Review controller source code in `apps/backend/src/application/controllers/`
3. Check DTO definitions in `apps/backend/src/application/dto/`
## Version History
- **v1.0.0** (2025-10-27) - Initial complete collection with all 68 endpoints
---
**Happy Testing!** 🚢✨

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,63 @@
{
"id": "xpeditis-local-env",
"name": "Xpeditis Local Environment",
"values": [
{
"key": "baseUrl",
"value": "http://localhost:4000",
"type": "default",
"enabled": true
},
{
"key": "token",
"value": "",
"type": "secret",
"enabled": true
},
{
"key": "refreshToken",
"value": "",
"type": "secret",
"enabled": true
},
{
"key": "userId",
"value": "",
"type": "default",
"enabled": true
},
{
"key": "userEmail",
"value": "",
"type": "default",
"enabled": true
},
{
"key": "organizationId",
"value": "",
"type": "default",
"enabled": true
},
{
"key": "rateQuoteId",
"value": "",
"type": "default",
"enabled": true
},
{
"key": "bookingId",
"value": "",
"type": "default",
"enabled": true
},
{
"key": "bookingNumber",
"value": "",
"type": "default",
"enabled": true
}
],
"_postman_variable_scope": "environment",
"_postman_exported_at": "2025-10-27T00:00:00.000Z",
"_postman_exported_using": "Postman/10.0.0"
}