Skip to content
Open
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
26 changes: 26 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,31 @@ JWT_EXPIRES_IN=7d
BCRYPT_ROUNDS=10
LOG_LEVEL=debug
CORS_ORIGIN=http://localhost:3000

# Number of reverse-proxy hops to trust when resolving the client IP.
# Leave unset for direct connections; set to 1 behind a single load balancer.
TRUST_PROXY=

# Public origin advertised in the OpenAPI "servers" block, e.g.
# https://api.swiftchain.io
API_PUBLIC_URL=

# Baseline limit applied to the whole /api surface.
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100

# Login and other credential-checking endpoints. Only failed attempts count.
AUTH_RATE_LIMIT_WINDOW_MS=900000
AUTH_RATE_LIMIT_MAX_REQUESTS=5

# Account creation.
REGISTRATION_RATE_LIMIT_WINDOW_MS=3600000
REGISTRATION_RATE_LIMIT_MAX_REQUESTS=10

# Escrow reads.
ESCROW_RATE_LIMIT_WINDOW_MS=900000
ESCROW_RATE_LIMIT_MAX_REQUESTS=30

# Irreversible escrow settlement operations (refund / release).
ESCROW_MUTATION_RATE_LIMIT_WINDOW_MS=3600000
ESCROW_MUTATION_RATE_LIMIT_MAX_REQUESTS=10
12 changes: 12 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Normalize line endings so the repository always stores LF, regardless of the
# contributor's platform. Without this, a Windows checkout rewrites files to
# CRLF and Prettier (which expects LF) reports the whole file as changed.
* text=auto eol=lf

# Binary assets must never be normalized.
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.pdf binary
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,85 @@ The backend serves as the central hub connecting the frontend, database, and blo

- `POST /uploads` - Upload proof of delivery or documents.

### Audit Logs

- `GET /audit-logs` - List administrative actions (admin only).
- `GET /audit-logs/:targetType/:targetId` - Audit trail for a single record.

---

## 📖 API Documentation

Interactive OpenAPI 3.0 documentation is served by the running application:

- **Swagger UI:** `http://localhost:3000/api-docs`
- **Raw specification:** `http://localhost:3000/api-docs.json`

Use `POST /api/v1/auth/login` to obtain a token, then click **Authorize** in
Swagger UI to attach it to subsequent requests.

---

## 📄 Pagination, Sorting & Filtering

Collection endpoints share a common query interface and return a `meta` block
alongside the data:

| Parameter | Description | Example |
| --------- | ----------- | ------- |
| `page` | 1-based page number | `?page=2` |
| `limit` | Items per page, clamped to the route maximum | `?limit=50` |
| `sort` | Comma-separated fields, `-` prefix for descending | `?sort=-createdAt,name` |
| `search` | Case-insensitive search across searchable fields | `?search=lagos` |

Filters accept direct equality or the comparison operators `eq`, `ne`, `gt`,
`gte`, `lt`, `lte`, `in` and `nin` in bracket notation:

```bash
GET /api/v1/deliveries?status=pending&amount[gte]=100&sort=-amount&page=1&limit=20
```

```json
{
"status": "success",
"message": "Deliveries retrieved successfully",
"data": [],
"meta": {
"totalItems": 137,
"totalPages": 7,
"currentPage": 1,
"limit": 20,
"hasNextPage": true,
"hasPreviousPage": false,
"nextPage": 2,
"previousPage": null
}
}
```

Only fields a route explicitly whitelists may be filtered or sorted on;
anything else is ignored or rejected.

---

## 🛡 Rate Limiting

Endpoints are protected against brute-force and abuse. Every response carries
the standard `RateLimit-*` headers, and exceeding a limit returns `429` with a
`Retry-After` header.

| Scope | Default limit | Notes |
| ----- | ------------- | ----- |
| API-wide | 100 / 15 min | Baseline safety net. |
| `POST /auth/login` | 5 / 15 min | Keyed by IP **and** targeted account. Successful logins are not counted. |
| `POST /auth/register` | 10 / hour | Curbs automated signup abuse. |
| Escrow endpoints | 30 / 15 min | Applies to the whole escrow router. |
| Escrow refund / release | 10 / hour | Irreversible fund movement. |

Every limit is configurable through the environment variables documented in
`.env.example`. Behind a reverse proxy, set `TRUST_PROXY` to the number of
hops so the real client IP is used rather than the proxy's.

---

## 🗺 Development Roadmap
Expand Down Expand Up @@ -257,6 +336,10 @@ Run the test suite using Jest:
pnpm test
```

Integration tests run against an in-memory MongoDB instance
(`mongodb-memory-server`), so they exercise real Mongoose queries, indexes and
validation rather than mocked data access. No local database is required.

---

## 🤝 Contribution
Expand Down
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ module.exports = {
transform: {
...tsJestTransformCfg,
},
setupFiles: ['<rootDir>/tests/setupEnv.ts'],
testMatch: ['<rootDir>/tests/**/*.test.ts'],
// The in-memory MongoDB server needs headroom on a cold start.
testTimeout: 30000,
collectCoverageFrom: ['src/**/*.ts', '!src/server.ts'],
};
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
"http-status-codes": "2.3.0",
"jsonwebtoken": "9.0.2",
"mongoose": "7.6.3",
"swagger-ui-express": "5.0.0",
"uuid": "9.0.1",
"winston": "3.11.0"
},
"devDependencies": {
"@apidevtools/swagger-parser": "10.1.0",
"@types/bcryptjs": "2.4.6",
"@types/compression": "1.7.5",
"@types/cors": "2.8.17",
Expand All @@ -38,6 +40,7 @@
"@types/mongoose": "5.11.97",
"@types/node": "20.10.0",
"@types/supertest": "^7.2.0",
"@types/swagger-ui-express": "4.1.6",
"@typescript-eslint/eslint-plugin": "6.13.2",
"@typescript-eslint/parser": "6.13.2",
"eslint": "8.55.0",
Expand Down
Loading