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
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx lint-staged
6 changes: 6 additions & 0 deletions .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"*.ts": [
"eslint --fix",
"prettier --write"
]
}
32 changes: 32 additions & 0 deletions docs/audit-logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Audit Logging for Sensitive Actions

Sensitive operations (payments, wallet and admin actions, auth events) are
recorded to an immutable audit trail via the `audit` module. The set of audited
operations is enumerated in `src/audit/audit-actions.enum.ts` (`AuditAction`).

## What is recorded

Every audit entry captures:

- **who** — the acting user id (and role where relevant)
- **when** — a server timestamp
- **what** — the `AuditAction` and structured `details` (ids, amounts, target)
- **where** — request metadata (IP, correlation id)

## Immutability

- Audit records are **append-only**: the service exposes create/read only, never
update or delete.
- In production, the database role used by the app should have `INSERT`/`SELECT`
on the audit table but not `UPDATE`/`DELETE`, so records are tamper-evident.

## Viewing

- Admins can browse the trail through the admin endpoints
(`GET /admin/audit-logs`) with filtering by user, action and date range.

## Adding a new audited action

1. Add a value to the `AuditAction` enum.
2. Call the audit service from the relevant service method with the action and
its details.
32 changes: 32 additions & 0 deletions docs/logging-strategy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Logging Strategy

Structured logging is provided by `AppLoggerService`
(`src/common/logging/app-logger.service.ts`).

## Principles

- **Structured (JSON) output** so logs are machine-parseable and shippable to a
log aggregator.
- **Log levels** — `debug`, `verbose`, `info`, `warn`, `error`. Use `debug`
liberally in development; keep `info`+ meaningful in production.
- **Per-module context** — call `setContext('PaymentsService')` so each log line
carries the emitting component.
- **Correlation IDs** — set a per-request correlation id
(`setCorrelationId(...)`) so all log lines for one request can be traced.

## Usage

```ts
constructor(private readonly logger: AppLoggerService) {
this.logger.setContext(PaymentsService.name);
}

this.logger.log('Escrow created', /* context */ undefined);
this.logger.error('Payment failed', err.stack);
```

## Notes

- Never log secrets, tokens or full card/wallet credentials.
- The service is `TRANSIENT`-scoped so each injecting class gets its own
context.
22 changes: 22 additions & 0 deletions docs/typing-guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# TypeScript Typing Guidelines

`strict` mode is enabled in `tsconfig.json`. Follow these guidelines so the
codebase stays type-safe.

## Rules

- **No implicit `any`.** Prefer precise types; use `unknown` and narrow when a
value's type is genuinely dynamic.
- **Null-safety.** `strictNullChecks` is on — handle `null`/`undefined`
explicitly (optional chaining, guards, or non-null assertions only when
provably safe).
- **Type external libraries.** Add `@types/*` packages or local `.d.ts`
declarations for untyped dependencies rather than casting to `any`.
- **Avoid `as any`.** If a cast is unavoidable, cast to the narrowest correct
type and add a comment explaining why.

## Notes

- `strictPropertyInitialization` is intentionally disabled because NestJS
DTO/entity classes declare properties that are populated by the framework
(validation, ORM) rather than in a constructor.
275 changes: 184 additions & 91 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,91 +1,184 @@
{
"name": "stellarAid-api",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"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": {
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.4",
"helmet": "^8.0.0",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.4.6",
"@prisma/client": "5.17.0",
"@stellar/stellar-sdk": "13.3.0",
"@types/bcrypt": "^6.0.0",
"@types/passport-jwt": "^4.0.1",
"bcrypt": "^6.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.15.1",
"ioredis": "^5.11.1",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"swagger-ui-express": "^5.0.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.24",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.0",
"@types/supertest": "^7.0.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^17.0.0",
"jest": "^30.0.0",
"prettier": "^3.4.2",
"prisma": "5.17.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
},
"prisma": {
"seed": "ts-node prisma/seed.ts"
}
}
{
"name": "stellarAid-api",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"prepare": "husky",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"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": {
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.4",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.4.6",
"@prisma/client": "5.17.0",
"@stellar/stellar-sdk": "13.3.0",
"@types/bcrypt": "^6.0.0",
"@types/passport-jwt": "^4.0.1",
"bcrypt": "^6.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.15.1",
"ioredis": "^5.11.1",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"swagger-ui-express": "^5.0.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.24",
"@nestjs/schematics": "^11.0.0",
"husky": "^9.1.7",
"lint-staged": "^15.2.10",
"@nestjs/testing": "^11.0.1",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.0",
"@types/supertest": "^7.0.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^17.0.0",
"jest": "^30.0.0",
"prettier": "^3.4.2",
"prisma": "5.17.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
},
"prisma": {
"seed": "ts-node prisma/seed.ts"
}
}
{
"name": "stellarAid-api",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"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": {
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.4",
"helmet": "^8.0.0",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.4.6",
"@prisma/client": "5.17.0",
"@stellar/stellar-sdk": "13.3.0",
"@types/bcrypt": "^6.0.0",
"@types/passport-jwt": "^4.0.1",
"bcrypt": "^6.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.15.1",
"ioredis": "^5.11.1",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"swagger-ui-express": "^5.0.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.24",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.0",
"@types/supertest": "^7.0.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^17.0.0",
"jest": "^30.0.0",
"prettier": "^3.4.2",
"prisma": "5.17.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
},
"prisma": {
"seed": "ts-node prisma/seed.ts"
}
}
Loading