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
50 changes: 50 additions & 0 deletions docs/accessible-dialog-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Accessible Dialog Migration Guide

## Problem

Several components use hand-rolled modal patterns (e.g. a `<div>` with
`position: fixed` and a manual backdrop) that lack proper ARIA semantics,
focus trapping, and keyboard dismissal.

## Solution

The project already has `components/ui/dialog.tsx` built on **Radix UI**
`Dialog.Root` which provides:

- `role="dialog"` and `aria-modal="true"` automatically
- Focus trap when open
- `Escape` key dismissal
- Screen reader announcement via `DialogTitle` and `DialogDescription`

## Migration pattern

**Before (hand-rolled):**
```tsx
{isOpen && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center">
<div className="bg-white rounded p-6">
<h2>My Modal</h2>
...
</div>
</div>
)}
```

**After (accessible Dialog):**
```tsx
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';

<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>My Modal</DialogTitle>
</DialogHeader>
...
</DialogContent>
</Dialog>
```

## Components to migrate

Audit `components/escrow/modals/` and `components/settings/` for any
components that render a fixed overlay manually rather than using `Dialog`.
36 changes: 36 additions & 0 deletions docs/concurrent-session-management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Concurrent Session Management

## Overview

This document describes the design for limiting concurrent sessions per user
and revoking tokens when the session limit is exceeded.

## Design

### Session limit

Each user may have at most **5** active sessions simultaneously. When a new
login exceeds this limit the oldest refresh token is revoked automatically.

### Token revocation flow

1. User logs in → `AuthService.verifySignature` creates a refresh token via `UserService.createRefreshToken`.
2. Before saving, query active tokens for the user ordered by `createdAt ASC`.
3. If `count >= MAX_SESSIONS`, call `UserService.invalidateRefreshToken` on the oldest token.
4. Proceed with saving the new token and returning the access/refresh pair.

### Key constants

```ts
const MAX_SESSIONS = 5;
```

### API endpoint

`POST /v1/auth/logout-all` — invalidates **all** active refresh tokens for the
authenticated user. Requires a valid `Authorization: Bearer <accessToken>` header.

## Future work

- Emit a `session.revoked` event so connected WebSocket clients can notify the user.
- Add a `deviceInfo` field to `RefreshToken` to display session metadata in the UI.
38 changes: 38 additions & 0 deletions docs/database-constraints-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Database Constraints Audit

## Purpose

Ensure all TypeORM entities have appropriate database-level constraints
to prevent invalid data from entering the system.

## Findings & Recommendations

### `users` table

| Column | Current | Recommended |
|---|---|---|
| `walletAddress` | `UNIQUE` | ✅ OK |
| `email` | `UNIQUE, nullable` | ✅ OK |
| `displayName` | `varchar(100), nullable` | ✅ OK |
| `role` | `text enum` | Add `CHECK` via migration |

### `refresh_tokens` table

| Column | Current | Recommended |
|---|---|---|
| `token` | no index | Add `UNIQUE` constraint |
| `expiresAt` | no constraint | Add `CHECK (expires_at > created_at)` |

### `escrow` entities

- Add `CHECK` constraint: `amount > 0`
- Add `NOT NULL` on `status` column where currently missing

## Migration Plan

Generate a migration after applying the entity changes:

```bash
npm run migration:generate -- --name AddMissingConstraints -d src/data-source.ts
npm run migration:run
```
44 changes: 44 additions & 0 deletions docs/graceful-shutdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Graceful Shutdown Handler

## Overview

NestJS provides built-in lifecycle hooks for graceful shutdown.
This document describes how to enable them in `main.ts` and implement
`OnModuleDestroy` in long-running services.

## Enabling shutdown hooks in main.ts

Add the following line after `NestFactory.create`:

```ts
app.enableShutdownHooks();
```

This ensures NestJS listens for `SIGTERM` / `SIGINT` and calls `onModuleDestroy`
on every provider that implements it before the process exits.

## Implementing OnModuleDestroy

For services that hold open connections (DB, Redis, WebSocket, scheduled jobs):

```ts
import { Injectable, OnModuleDestroy } from '@nestjs/common';

@Injectable()
export class SomeService implements OnModuleDestroy {
onModuleDestroy() {
// close connections, flush buffers, cancel scheduled jobs
}
}
```

## Services that need this

- `SchedulerService` — cancel cron jobs
- `StellarService` — close streaming connections
- `GatewayService` — close WebSocket server gracefully

## Kubernetes / Docker

Set `terminationGracePeriodSeconds: 30` in the pod spec so the container
has enough time to finish in-flight requests before being killed.
Loading