servcraft 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +30 -0
- package/.github/CODEOWNERS +18 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
- package/.github/dependabot.yml +59 -0
- package/.github/workflows/ci.yml +188 -0
- package/.github/workflows/release.yml +195 -0
- package/AUDIT.md +602 -0
- package/LICENSE +21 -0
- package/README.md +1102 -1
- package/dist/cli/index.cjs +2026 -2168
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2026 -2168
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +595 -616
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -52
- package/dist/index.d.ts +114 -52
- package/dist/index.js +595 -616
- package/dist/index.js.map +1 -1
- package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
- package/docs/DATABASE_MULTI_ORM.md +399 -0
- package/docs/PHASE1_BREAKDOWN.md +346 -0
- package/docs/PROGRESS.md +550 -0
- package/docs/modules/ANALYTICS.md +226 -0
- package/docs/modules/API-VERSIONING.md +252 -0
- package/docs/modules/AUDIT.md +192 -0
- package/docs/modules/AUTH.md +431 -0
- package/docs/modules/CACHE.md +346 -0
- package/docs/modules/EMAIL.md +254 -0
- package/docs/modules/FEATURE-FLAG.md +291 -0
- package/docs/modules/I18N.md +294 -0
- package/docs/modules/MEDIA-PROCESSING.md +281 -0
- package/docs/modules/MFA.md +266 -0
- package/docs/modules/NOTIFICATION.md +311 -0
- package/docs/modules/OAUTH.md +237 -0
- package/docs/modules/PAYMENT.md +804 -0
- package/docs/modules/QUEUE.md +540 -0
- package/docs/modules/RATE-LIMIT.md +339 -0
- package/docs/modules/SEARCH.md +288 -0
- package/docs/modules/SECURITY.md +327 -0
- package/docs/modules/SESSION.md +382 -0
- package/docs/modules/SWAGGER.md +305 -0
- package/docs/modules/UPLOAD.md +296 -0
- package/docs/modules/USER.md +505 -0
- package/docs/modules/VALIDATION.md +294 -0
- package/docs/modules/WEBHOOK.md +270 -0
- package/docs/modules/WEBSOCKET.md +691 -0
- package/package.json +53 -38
- package/prisma/schema.prisma +395 -1
- package/src/cli/commands/add-module.ts +520 -87
- package/src/cli/commands/db.ts +3 -4
- package/src/cli/commands/docs.ts +256 -6
- package/src/cli/commands/generate.ts +12 -19
- package/src/cli/commands/init.ts +384 -214
- package/src/cli/index.ts +0 -4
- package/src/cli/templates/repository.ts +6 -1
- package/src/cli/templates/routes.ts +6 -21
- package/src/cli/utils/docs-generator.ts +6 -7
- package/src/cli/utils/env-manager.ts +717 -0
- package/src/cli/utils/field-parser.ts +16 -7
- package/src/cli/utils/interactive-prompt.ts +223 -0
- package/src/cli/utils/template-manager.ts +346 -0
- package/src/config/database.config.ts +183 -0
- package/src/config/env.ts +0 -10
- package/src/config/index.ts +0 -14
- package/src/core/server.ts +1 -1
- package/src/database/adapters/mongoose.adapter.ts +132 -0
- package/src/database/adapters/prisma.adapter.ts +118 -0
- package/src/database/connection.ts +190 -0
- package/src/database/interfaces/database.interface.ts +85 -0
- package/src/database/interfaces/index.ts +7 -0
- package/src/database/interfaces/repository.interface.ts +129 -0
- package/src/database/models/mongoose/index.ts +7 -0
- package/src/database/models/mongoose/payment.schema.ts +347 -0
- package/src/database/models/mongoose/user.schema.ts +154 -0
- package/src/database/prisma.ts +1 -4
- package/src/database/redis.ts +101 -0
- package/src/database/repositories/mongoose/index.ts +7 -0
- package/src/database/repositories/mongoose/payment.repository.ts +380 -0
- package/src/database/repositories/mongoose/user.repository.ts +255 -0
- package/src/database/seed.ts +6 -1
- package/src/index.ts +9 -20
- package/src/middleware/security.ts +2 -6
- package/src/modules/analytics/analytics.routes.ts +80 -0
- package/src/modules/analytics/analytics.service.ts +364 -0
- package/src/modules/analytics/index.ts +18 -0
- package/src/modules/analytics/types.ts +180 -0
- package/src/modules/api-versioning/index.ts +15 -0
- package/src/modules/api-versioning/types.ts +86 -0
- package/src/modules/api-versioning/versioning.middleware.ts +120 -0
- package/src/modules/api-versioning/versioning.routes.ts +54 -0
- package/src/modules/api-versioning/versioning.service.ts +189 -0
- package/src/modules/audit/audit.repository.ts +206 -0
- package/src/modules/audit/audit.service.ts +27 -59
- package/src/modules/auth/auth.controller.ts +2 -2
- package/src/modules/auth/auth.middleware.ts +3 -9
- package/src/modules/auth/auth.routes.ts +10 -107
- package/src/modules/auth/auth.service.ts +126 -23
- package/src/modules/auth/index.ts +3 -4
- package/src/modules/cache/cache.service.ts +367 -0
- package/src/modules/cache/index.ts +10 -0
- package/src/modules/cache/types.ts +44 -0
- package/src/modules/email/email.service.ts +3 -10
- package/src/modules/email/templates.ts +2 -8
- package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
- package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
- package/src/modules/feature-flag/feature-flag.service.ts +566 -0
- package/src/modules/feature-flag/index.ts +20 -0
- package/src/modules/feature-flag/types.ts +192 -0
- package/src/modules/i18n/i18n.middleware.ts +186 -0
- package/src/modules/i18n/i18n.routes.ts +191 -0
- package/src/modules/i18n/i18n.service.ts +456 -0
- package/src/modules/i18n/index.ts +18 -0
- package/src/modules/i18n/types.ts +118 -0
- package/src/modules/media-processing/index.ts +17 -0
- package/src/modules/media-processing/media-processing.routes.ts +111 -0
- package/src/modules/media-processing/media-processing.service.ts +245 -0
- package/src/modules/media-processing/types.ts +156 -0
- package/src/modules/mfa/index.ts +20 -0
- package/src/modules/mfa/mfa.repository.ts +206 -0
- package/src/modules/mfa/mfa.routes.ts +595 -0
- package/src/modules/mfa/mfa.service.ts +572 -0
- package/src/modules/mfa/totp.ts +150 -0
- package/src/modules/mfa/types.ts +57 -0
- package/src/modules/notification/index.ts +20 -0
- package/src/modules/notification/notification.repository.ts +356 -0
- package/src/modules/notification/notification.service.ts +483 -0
- package/src/modules/notification/types.ts +119 -0
- package/src/modules/oauth/index.ts +20 -0
- package/src/modules/oauth/oauth.repository.ts +219 -0
- package/src/modules/oauth/oauth.routes.ts +446 -0
- package/src/modules/oauth/oauth.service.ts +293 -0
- package/src/modules/oauth/providers/apple.provider.ts +250 -0
- package/src/modules/oauth/providers/facebook.provider.ts +181 -0
- package/src/modules/oauth/providers/github.provider.ts +248 -0
- package/src/modules/oauth/providers/google.provider.ts +189 -0
- package/src/modules/oauth/providers/twitter.provider.ts +214 -0
- package/src/modules/oauth/types.ts +94 -0
- package/src/modules/payment/index.ts +19 -0
- package/src/modules/payment/payment.repository.ts +733 -0
- package/src/modules/payment/payment.routes.ts +390 -0
- package/src/modules/payment/payment.service.ts +354 -0
- package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
- package/src/modules/payment/providers/paypal.provider.ts +190 -0
- package/src/modules/payment/providers/stripe.provider.ts +215 -0
- package/src/modules/payment/types.ts +140 -0
- package/src/modules/queue/cron.ts +438 -0
- package/src/modules/queue/index.ts +87 -0
- package/src/modules/queue/queue.routes.ts +600 -0
- package/src/modules/queue/queue.service.ts +842 -0
- package/src/modules/queue/types.ts +222 -0
- package/src/modules/queue/workers.ts +366 -0
- package/src/modules/rate-limit/index.ts +59 -0
- package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
- package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
- package/src/modules/rate-limit/rate-limit.service.ts +348 -0
- package/src/modules/rate-limit/stores/memory.store.ts +165 -0
- package/src/modules/rate-limit/stores/redis.store.ts +322 -0
- package/src/modules/rate-limit/types.ts +153 -0
- package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
- package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
- package/src/modules/search/adapters/memory.adapter.ts +278 -0
- package/src/modules/search/index.ts +21 -0
- package/src/modules/search/search.service.ts +234 -0
- package/src/modules/search/types.ts +214 -0
- package/src/modules/security/index.ts +40 -0
- package/src/modules/security/sanitize.ts +223 -0
- package/src/modules/security/security-audit.service.ts +388 -0
- package/src/modules/security/security.middleware.ts +398 -0
- package/src/modules/session/index.ts +3 -0
- package/src/modules/session/session.repository.ts +159 -0
- package/src/modules/session/session.service.ts +340 -0
- package/src/modules/session/types.ts +38 -0
- package/src/modules/swagger/index.ts +7 -1
- package/src/modules/swagger/schema-builder.ts +16 -4
- package/src/modules/swagger/swagger.service.ts +9 -10
- package/src/modules/swagger/types.ts +0 -2
- package/src/modules/upload/index.ts +14 -0
- package/src/modules/upload/types.ts +83 -0
- package/src/modules/upload/upload.repository.ts +199 -0
- package/src/modules/upload/upload.routes.ts +311 -0
- package/src/modules/upload/upload.service.ts +448 -0
- package/src/modules/user/index.ts +3 -3
- package/src/modules/user/user.controller.ts +15 -9
- package/src/modules/user/user.repository.ts +237 -113
- package/src/modules/user/user.routes.ts +39 -164
- package/src/modules/user/user.service.ts +4 -3
- package/src/modules/validation/validator.ts +12 -17
- package/src/modules/webhook/index.ts +91 -0
- package/src/modules/webhook/retry.ts +196 -0
- package/src/modules/webhook/signature.ts +135 -0
- package/src/modules/webhook/types.ts +181 -0
- package/src/modules/webhook/webhook.repository.ts +358 -0
- package/src/modules/webhook/webhook.routes.ts +442 -0
- package/src/modules/webhook/webhook.service.ts +457 -0
- package/src/modules/websocket/features.ts +504 -0
- package/src/modules/websocket/index.ts +106 -0
- package/src/modules/websocket/middlewares.ts +298 -0
- package/src/modules/websocket/types.ts +181 -0
- package/src/modules/websocket/websocket.service.ts +692 -0
- package/src/utils/errors.ts +7 -0
- package/src/utils/pagination.ts +4 -1
- package/tests/helpers/db-check.ts +79 -0
- package/tests/integration/auth-redis.test.ts +94 -0
- package/tests/integration/cache-redis.test.ts +387 -0
- package/tests/integration/mongoose-repositories.test.ts +410 -0
- package/tests/integration/payment-prisma.test.ts +637 -0
- package/tests/integration/queue-bullmq.test.ts +417 -0
- package/tests/integration/user-prisma.test.ts +441 -0
- package/tests/integration/websocket-socketio.test.ts +552 -0
- package/tests/setup.ts +11 -9
- package/vitest.config.ts +3 -8
- package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
- package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
- package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
- package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
- package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Analytics Module
|
|
2
|
+
|
|
3
|
+
Prometheus-style metrics collection for monitoring and observability.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Counters** - Monotonically increasing values
|
|
8
|
+
- **Gauges** - Values that can go up and down
|
|
9
|
+
- **Histograms** - Distribution of values with buckets
|
|
10
|
+
- **Events** - Track custom application events
|
|
11
|
+
- **Prometheus Export** - Native Prometheus format output
|
|
12
|
+
- **Metric Queries** - Query and aggregate metrics
|
|
13
|
+
|
|
14
|
+
## Metric Types
|
|
15
|
+
|
|
16
|
+
| Type | Description | Example |
|
|
17
|
+
|------|-------------|---------|
|
|
18
|
+
| Counter | Only increases | Request count, errors |
|
|
19
|
+
| Gauge | Can increase/decrease | Active connections, queue size |
|
|
20
|
+
| Histogram | Value distribution | Response times, payload sizes |
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Basic Setup
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { AnalyticsService } from 'servcraft/modules/analytics';
|
|
28
|
+
|
|
29
|
+
const analytics = new AnalyticsService({
|
|
30
|
+
enabled: true,
|
|
31
|
+
prefix: 'myapp',
|
|
32
|
+
defaultLabels: { env: 'production' },
|
|
33
|
+
flushInterval: 60000,
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Counters
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// Simple increment
|
|
41
|
+
analytics.incrementCounter('http_requests_total');
|
|
42
|
+
|
|
43
|
+
// Increment by value
|
|
44
|
+
analytics.incrementCounter('bytes_received', 1024);
|
|
45
|
+
|
|
46
|
+
// With labels
|
|
47
|
+
analytics.incrementCounter('http_requests_total', 1, {
|
|
48
|
+
method: 'GET',
|
|
49
|
+
path: '/api/users',
|
|
50
|
+
status: '200',
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Gauges
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// Set gauge value
|
|
58
|
+
analytics.setGauge('active_connections', 42);
|
|
59
|
+
|
|
60
|
+
// With labels
|
|
61
|
+
analytics.setGauge('queue_size', 150, { queue: 'emails' });
|
|
62
|
+
|
|
63
|
+
// Update periodically
|
|
64
|
+
setInterval(() => {
|
|
65
|
+
analytics.setGauge('memory_usage_bytes', process.memoryUsage().heapUsed);
|
|
66
|
+
}, 5000);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Histograms
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Observe value with default buckets
|
|
73
|
+
analytics.observeHistogram('http_request_duration_seconds', 0.125);
|
|
74
|
+
|
|
75
|
+
// Custom buckets
|
|
76
|
+
analytics.observeHistogram(
|
|
77
|
+
'http_request_duration_seconds',
|
|
78
|
+
0.125,
|
|
79
|
+
[0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
|
|
80
|
+
{ method: 'GET', path: '/api/users' }
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Timing helper
|
|
84
|
+
const start = Date.now();
|
|
85
|
+
// ... do work ...
|
|
86
|
+
const duration = (Date.now() - start) / 1000;
|
|
87
|
+
analytics.observeHistogram('operation_duration_seconds', duration);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Events
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// Track event
|
|
94
|
+
analytics.trackEvent({
|
|
95
|
+
name: 'user_signup',
|
|
96
|
+
properties: {
|
|
97
|
+
source: 'google',
|
|
98
|
+
plan: 'free',
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Get recent events
|
|
103
|
+
const events = analytics.getEvents(100);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Querying Metrics
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const result = await analytics.queryMetrics({
|
|
110
|
+
name: 'myapp_http_requests_total',
|
|
111
|
+
startTime: new Date('2024-01-01'),
|
|
112
|
+
endTime: new Date('2024-01-31'),
|
|
113
|
+
labels: { method: 'GET' },
|
|
114
|
+
groupBy: ['path'],
|
|
115
|
+
aggregation: 'sum',
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// result: { name, data: [...], aggregated: 12345 }
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Prometheus Export
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Get Prometheus format
|
|
125
|
+
const metrics = analytics.getPrometheusMetrics();
|
|
126
|
+
|
|
127
|
+
// Example output:
|
|
128
|
+
// myapp_http_requests_total{method="GET",status="200"} 1234
|
|
129
|
+
// myapp_active_connections{} 42
|
|
130
|
+
// myapp_http_request_duration_seconds_bucket{le="0.1"} 500
|
|
131
|
+
// myapp_http_request_duration_seconds_bucket{le="0.5"} 900
|
|
132
|
+
// myapp_http_request_duration_seconds_sum{} 125.5
|
|
133
|
+
// myapp_http_request_duration_seconds_count{} 1000
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Fastify Integration
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// Metrics endpoint
|
|
140
|
+
fastify.get('/metrics', async (request, reply) => {
|
|
141
|
+
reply.type('text/plain').send(analytics.getPrometheusMetrics());
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Request duration middleware
|
|
145
|
+
fastify.addHook('onRequest', async (request) => {
|
|
146
|
+
request.startTime = Date.now();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
fastify.addHook('onResponse', async (request, reply) => {
|
|
150
|
+
const duration = (Date.now() - request.startTime) / 1000;
|
|
151
|
+
|
|
152
|
+
analytics.incrementCounter('http_requests_total', 1, {
|
|
153
|
+
method: request.method,
|
|
154
|
+
path: request.routerPath || request.url,
|
|
155
|
+
status: String(reply.statusCode),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
analytics.observeHistogram('http_request_duration_seconds', duration, undefined, {
|
|
159
|
+
method: request.method,
|
|
160
|
+
path: request.routerPath || request.url,
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Configuration
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
interface AnalyticsConfig {
|
|
169
|
+
enabled?: boolean; // Enable/disable metrics (default: true)
|
|
170
|
+
prefix?: string; // Metric name prefix (default: 'app')
|
|
171
|
+
defaultLabels?: Record<string, string>; // Labels added to all metrics
|
|
172
|
+
prometheusEnabled?: boolean; // Enable Prometheus export (default: true)
|
|
173
|
+
flushInterval?: number; // Cleanup interval in ms (default: 60000)
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Aggregation Types
|
|
178
|
+
|
|
179
|
+
| Aggregation | Description |
|
|
180
|
+
|-------------|-------------|
|
|
181
|
+
| `sum` | Sum of all values |
|
|
182
|
+
| `avg` | Average of values |
|
|
183
|
+
| `min` | Minimum value |
|
|
184
|
+
| `max` | Maximum value |
|
|
185
|
+
| `count` | Number of data points |
|
|
186
|
+
|
|
187
|
+
## Utility Methods
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// Get all counters
|
|
191
|
+
const counters = analytics.getCounters();
|
|
192
|
+
|
|
193
|
+
// Get all gauges
|
|
194
|
+
const gauges = analytics.getGauges();
|
|
195
|
+
|
|
196
|
+
// Clear all metrics
|
|
197
|
+
analytics.clear();
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Prometheus Configuration
|
|
201
|
+
|
|
202
|
+
```yaml
|
|
203
|
+
# prometheus.yml
|
|
204
|
+
scrape_configs:
|
|
205
|
+
- job_name: 'myapp'
|
|
206
|
+
static_configs:
|
|
207
|
+
- targets: ['localhost:3000']
|
|
208
|
+
metrics_path: '/metrics'
|
|
209
|
+
scrape_interval: 15s
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Grafana Dashboard
|
|
213
|
+
|
|
214
|
+
Common panels:
|
|
215
|
+
- Request rate: `rate(myapp_http_requests_total[5m])`
|
|
216
|
+
- Error rate: `rate(myapp_http_requests_total{status=~"5.."}[5m])`
|
|
217
|
+
- P99 latency: `histogram_quantile(0.99, rate(myapp_http_request_duration_seconds_bucket[5m]))`
|
|
218
|
+
- Active connections: `myapp_active_connections`
|
|
219
|
+
|
|
220
|
+
## Best Practices
|
|
221
|
+
|
|
222
|
+
1. **Use Labels Wisely** - Don't create high-cardinality labels
|
|
223
|
+
2. **Consistent Naming** - Follow Prometheus naming conventions
|
|
224
|
+
3. **Default Buckets** - Use appropriate histogram buckets for your use case
|
|
225
|
+
4. **Memory Management** - Configure flush interval to prevent memory growth
|
|
226
|
+
5. **Metric Types** - Use the right metric type for your use case
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# API Versioning Module
|
|
2
|
+
|
|
3
|
+
API versioning support with multiple detection strategies.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multiple Strategies** - URL path, header, query param, Accept header
|
|
8
|
+
- **Version Detection** - Automatic version detection from requests
|
|
9
|
+
- **Deprecation Warnings** - Warn clients about deprecated versions
|
|
10
|
+
- **Migration Support** - Data migration between versions
|
|
11
|
+
- **Sunset Dates** - Track version end-of-life dates
|
|
12
|
+
|
|
13
|
+
## Strategies
|
|
14
|
+
|
|
15
|
+
| Strategy | Example | Best For |
|
|
16
|
+
|----------|---------|----------|
|
|
17
|
+
| URL Path | `/v1/users`, `/v2/users` | RESTful APIs, clear versioning |
|
|
18
|
+
| Header | `X-API-Version: v2` | Clean URLs, flexible |
|
|
19
|
+
| Query | `?version=v2` | Simple implementation |
|
|
20
|
+
| Accept Header | `Accept: application/vnd.api.v2+json` | Content negotiation |
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Configuration
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { VersioningService } from 'servcraft/modules/api-versioning';
|
|
28
|
+
|
|
29
|
+
const versionService = new VersioningService({
|
|
30
|
+
strategy: 'url',
|
|
31
|
+
defaultVersion: 'v1',
|
|
32
|
+
versions: [
|
|
33
|
+
{
|
|
34
|
+
version: 'v1',
|
|
35
|
+
status: 'deprecated',
|
|
36
|
+
releasedAt: new Date('2023-01-01'),
|
|
37
|
+
sunsetAt: new Date('2024-06-01'),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
version: 'v2',
|
|
41
|
+
status: 'active',
|
|
42
|
+
releasedAt: new Date('2024-01-01'),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
version: 'v3',
|
|
46
|
+
status: 'active',
|
|
47
|
+
releasedAt: new Date('2024-06-01'),
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
headerName: 'X-API-Version',
|
|
51
|
+
queryParam: 'version',
|
|
52
|
+
strict: true,
|
|
53
|
+
deprecationWarnings: true,
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Version Detection
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// Detect version from request
|
|
61
|
+
const result = versionService.detectVersion({
|
|
62
|
+
url: '/v2/users',
|
|
63
|
+
headers: { 'x-api-version': 'v2' },
|
|
64
|
+
query: { version: 'v2' },
|
|
65
|
+
});
|
|
66
|
+
// {
|
|
67
|
+
// version: 'v2',
|
|
68
|
+
// source: 'url',
|
|
69
|
+
// isValid: true,
|
|
70
|
+
// warning: undefined
|
|
71
|
+
// }
|
|
72
|
+
|
|
73
|
+
// Deprecated version warning
|
|
74
|
+
const deprecatedResult = versionService.detectVersion({
|
|
75
|
+
url: '/v1/users',
|
|
76
|
+
});
|
|
77
|
+
// {
|
|
78
|
+
// version: 'v1',
|
|
79
|
+
// source: 'url',
|
|
80
|
+
// isValid: true,
|
|
81
|
+
// warning: 'API version v1 is deprecated and will be removed on 2024-06-01'
|
|
82
|
+
// }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Version Information
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Get version info
|
|
89
|
+
const info = versionService.getVersion('v2');
|
|
90
|
+
// { version: 'v2', status: 'active', releasedAt: Date }
|
|
91
|
+
|
|
92
|
+
// Get all versions
|
|
93
|
+
const all = versionService.getAllVersions();
|
|
94
|
+
|
|
95
|
+
// Get active versions
|
|
96
|
+
const active = versionService.getActiveVersions();
|
|
97
|
+
|
|
98
|
+
// Check if version is valid
|
|
99
|
+
const isValid = versionService.isVersionValid('v3');
|
|
100
|
+
|
|
101
|
+
// Compare versions
|
|
102
|
+
const comparison = versionService.compareVersions('v1', 'v2');
|
|
103
|
+
// Returns: -1 (v1 < v2)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Migrations
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Add migration
|
|
110
|
+
versionService.addMigration({
|
|
111
|
+
from: 'v1',
|
|
112
|
+
to: 'v2',
|
|
113
|
+
transform: (data) => ({
|
|
114
|
+
...data,
|
|
115
|
+
// v2 renames 'name' to 'fullName'
|
|
116
|
+
fullName: data.name,
|
|
117
|
+
name: undefined,
|
|
118
|
+
// v2 adds required field
|
|
119
|
+
version: 2,
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Get migration
|
|
124
|
+
const migration = versionService.getMigration('v1', 'v2');
|
|
125
|
+
if (migration) {
|
|
126
|
+
const v2Data = migration.transform(v1Data);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Configuration Types
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
interface VersioningConfig {
|
|
134
|
+
strategy: 'url' | 'header' | 'query' | 'accept-header';
|
|
135
|
+
defaultVersion: string;
|
|
136
|
+
versions: ApiVersion[];
|
|
137
|
+
headerName?: string; // default: 'X-API-Version'
|
|
138
|
+
queryParam?: string; // default: 'version'
|
|
139
|
+
strict?: boolean; // Reject invalid versions
|
|
140
|
+
deprecationWarnings?: boolean;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
interface ApiVersion {
|
|
144
|
+
version: string;
|
|
145
|
+
status: 'active' | 'deprecated' | 'sunset';
|
|
146
|
+
releasedAt: Date;
|
|
147
|
+
sunsetAt?: Date;
|
|
148
|
+
changelog?: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
interface VersionMigration {
|
|
152
|
+
from: string;
|
|
153
|
+
to: string;
|
|
154
|
+
transform: (data: unknown) => unknown;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Fastify Integration
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { versioningMiddleware } from 'servcraft/modules/api-versioning';
|
|
162
|
+
|
|
163
|
+
// Register middleware
|
|
164
|
+
fastify.addHook('preHandler', async (request, reply) => {
|
|
165
|
+
const result = versionService.detectVersion({
|
|
166
|
+
url: request.url,
|
|
167
|
+
headers: request.headers,
|
|
168
|
+
query: request.query,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (!result.isValid && versionService.config.strict) {
|
|
172
|
+
return reply.status(400).send({
|
|
173
|
+
error: `Invalid API version: ${result.version}`,
|
|
174
|
+
supportedVersions: versionService.getActiveVersions().map(v => v.version),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
request.apiVersion = result.version;
|
|
179
|
+
|
|
180
|
+
// Add deprecation warning header
|
|
181
|
+
if (result.warning) {
|
|
182
|
+
reply.header('X-API-Deprecation-Warning', result.warning);
|
|
183
|
+
reply.header('Sunset', versionService.getVersion(result.version)?.sunsetAt?.toISOString());
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Versioned Routes
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
// URL-based versioning
|
|
192
|
+
fastify.get('/v1/users', async (request, reply) => {
|
|
193
|
+
// v1 response format
|
|
194
|
+
return users.map(u => ({ name: u.name, email: u.email }));
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
fastify.get('/v2/users', async (request, reply) => {
|
|
198
|
+
// v2 response format with pagination
|
|
199
|
+
return {
|
|
200
|
+
data: users.map(u => ({ fullName: u.fullName, email: u.email })),
|
|
201
|
+
meta: { total: users.length },
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Header-based versioning (single route)
|
|
206
|
+
fastify.get('/users', async (request, reply) => {
|
|
207
|
+
const version = request.apiVersion;
|
|
208
|
+
|
|
209
|
+
if (version === 'v1') {
|
|
210
|
+
return users.map(u => ({ name: u.name }));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// v2 and later
|
|
214
|
+
return {
|
|
215
|
+
data: users.map(u => ({ fullName: u.fullName })),
|
|
216
|
+
meta: { total: users.length },
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Response Headers
|
|
222
|
+
|
|
223
|
+
```http
|
|
224
|
+
# Deprecation warning
|
|
225
|
+
X-API-Deprecation-Warning: API version v1 is deprecated and will be removed on 2024-06-01
|
|
226
|
+
Sunset: 2024-06-01T00:00:00.000Z
|
|
227
|
+
|
|
228
|
+
# Current version
|
|
229
|
+
X-API-Version: v2
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Version Lifecycle
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
236
|
+
│ Version Lifecycle │
|
|
237
|
+
├─────────────────────────────────────────────────────────────┤
|
|
238
|
+
│ ACTIVE ──────► DEPRECATED ──────► SUNSET ──────► REMOVED │
|
|
239
|
+
│ │ │ │
|
|
240
|
+
│ Warning Error │
|
|
241
|
+
│ Headers Response │
|
|
242
|
+
└─────────────────────────────────────────────────────────────┘
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Best Practices
|
|
246
|
+
|
|
247
|
+
1. **Semantic Versioning** - Use v1, v2, v3 for major breaking changes
|
|
248
|
+
2. **Deprecation Period** - Give clients 6+ months notice
|
|
249
|
+
3. **Sunset Headers** - Include Sunset header for deprecated versions
|
|
250
|
+
4. **Documentation** - Document changes between versions
|
|
251
|
+
5. **Default Version** - Don't change default version without notice
|
|
252
|
+
6. **Migration Guides** - Provide clear migration instructions
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Audit Module
|
|
2
|
+
|
|
3
|
+
Audit logging for tracking user actions and system events.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Automatic Logging** - Track CRUD operations automatically
|
|
8
|
+
- **User Attribution** - Link actions to users
|
|
9
|
+
- **Resource Tracking** - Track changes by resource type
|
|
10
|
+
- **IP & User Agent** - Capture request metadata
|
|
11
|
+
- **Data Retention** - Configurable log retention
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Basic Setup
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { getAuditService } from 'servcraft/modules/audit';
|
|
19
|
+
|
|
20
|
+
const auditService = getAuditService();
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Logging Actions
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// Generic log entry
|
|
27
|
+
await auditService.log({
|
|
28
|
+
action: 'create',
|
|
29
|
+
resource: 'order',
|
|
30
|
+
resourceId: 'order-123',
|
|
31
|
+
userId: 'user-456',
|
|
32
|
+
newValue: { total: 99.99, items: 3 },
|
|
33
|
+
ipAddress: request.ip,
|
|
34
|
+
userAgent: request.headers['user-agent'],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Shortcut methods
|
|
38
|
+
await auditService.logCreate('order', 'order-123', 'user-456', { total: 99.99 });
|
|
39
|
+
|
|
40
|
+
await auditService.logUpdate('order', 'order-123', 'user-456',
|
|
41
|
+
{ status: 'pending' }, // old value
|
|
42
|
+
{ status: 'shipped' } // new value
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
await auditService.logDelete('order', 'order-123', 'user-456', { total: 99.99 });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Auth Events
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// Login event
|
|
52
|
+
await auditService.logLogin('user-123', {
|
|
53
|
+
ipAddress: '192.168.1.1',
|
|
54
|
+
userAgent: 'Mozilla/5.0...',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Logout event
|
|
58
|
+
await auditService.logLogout('user-123');
|
|
59
|
+
|
|
60
|
+
// Password change
|
|
61
|
+
await auditService.logPasswordChange('user-123', {
|
|
62
|
+
ipAddress: '192.168.1.1',
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Querying Logs
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// Query with filters
|
|
70
|
+
const result = await auditService.query({
|
|
71
|
+
userId: 'user-123',
|
|
72
|
+
action: 'update',
|
|
73
|
+
resource: 'order',
|
|
74
|
+
startDate: new Date('2024-01-01'),
|
|
75
|
+
endDate: new Date('2024-01-31'),
|
|
76
|
+
page: 1,
|
|
77
|
+
limit: 50,
|
|
78
|
+
});
|
|
79
|
+
// { data: [...], total: 150, page: 1, totalPages: 3 }
|
|
80
|
+
|
|
81
|
+
// Find by user
|
|
82
|
+
const userLogs = await auditService.findByUser('user-123', 100);
|
|
83
|
+
|
|
84
|
+
// Find by resource
|
|
85
|
+
const orderLogs = await auditService.findByResource('order', 'order-123', 50);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Data Retention
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Delete logs older than 90 days
|
|
92
|
+
const deleted = await auditService.cleanupOldLogs(90);
|
|
93
|
+
console.log(`Deleted ${deleted} old audit logs`);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Types
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
interface AuditLogEntry {
|
|
100
|
+
action: string;
|
|
101
|
+
resource: string;
|
|
102
|
+
resourceId?: string;
|
|
103
|
+
userId?: string;
|
|
104
|
+
oldValue?: Record<string, unknown>;
|
|
105
|
+
newValue?: Record<string, unknown>;
|
|
106
|
+
ipAddress?: string;
|
|
107
|
+
userAgent?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface AuditLogQuery {
|
|
111
|
+
userId?: string;
|
|
112
|
+
action?: string;
|
|
113
|
+
resource?: string;
|
|
114
|
+
resourceId?: string;
|
|
115
|
+
startDate?: Date;
|
|
116
|
+
endDate?: Date;
|
|
117
|
+
page?: number;
|
|
118
|
+
limit?: number;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface AuditLogRecord extends AuditLogEntry {
|
|
122
|
+
id: string;
|
|
123
|
+
timestamp: Date;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Common Actions
|
|
128
|
+
|
|
129
|
+
| Action | Description |
|
|
130
|
+
|--------|-------------|
|
|
131
|
+
| `create` | Resource created |
|
|
132
|
+
| `update` | Resource updated |
|
|
133
|
+
| `delete` | Resource deleted |
|
|
134
|
+
| `login` | User logged in |
|
|
135
|
+
| `logout` | User logged out |
|
|
136
|
+
| `password_change` | Password changed |
|
|
137
|
+
| `permission_change` | Permissions modified |
|
|
138
|
+
| `export` | Data exported |
|
|
139
|
+
| `import` | Data imported |
|
|
140
|
+
|
|
141
|
+
## Middleware Integration
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// Fastify hook for automatic audit logging
|
|
145
|
+
fastify.addHook('onResponse', async (request, reply) => {
|
|
146
|
+
// Only log mutations
|
|
147
|
+
if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(request.method)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const userId = request.user?.id;
|
|
152
|
+
const resource = request.routerPath.split('/')[2]; // e.g., /api/orders -> orders
|
|
153
|
+
|
|
154
|
+
await auditService.log({
|
|
155
|
+
action: request.method.toLowerCase(),
|
|
156
|
+
resource,
|
|
157
|
+
resourceId: request.params?.id,
|
|
158
|
+
userId,
|
|
159
|
+
ipAddress: request.ip,
|
|
160
|
+
userAgent: request.headers['user-agent'],
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Database Schema
|
|
166
|
+
|
|
167
|
+
```sql
|
|
168
|
+
CREATE TABLE audit_logs (
|
|
169
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
170
|
+
action TEXT NOT NULL,
|
|
171
|
+
resource TEXT NOT NULL,
|
|
172
|
+
resource_id TEXT,
|
|
173
|
+
user_id TEXT,
|
|
174
|
+
old_value JSONB,
|
|
175
|
+
new_value JSONB,
|
|
176
|
+
ip_address TEXT,
|
|
177
|
+
user_agent TEXT,
|
|
178
|
+
timestamp TIMESTAMP DEFAULT NOW()
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
CREATE INDEX idx_audit_user ON audit_logs(user_id);
|
|
182
|
+
CREATE INDEX idx_audit_resource ON audit_logs(resource, resource_id);
|
|
183
|
+
CREATE INDEX idx_audit_timestamp ON audit_logs(timestamp);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Best Practices
|
|
187
|
+
|
|
188
|
+
1. **Log Sensitive Actions** - Always log auth, permission, and data changes
|
|
189
|
+
2. **Don't Log Sensitive Data** - Exclude passwords, tokens from logs
|
|
190
|
+
3. **Retention Policy** - Implement data retention per compliance requirements
|
|
191
|
+
4. **Async Logging** - Use async logging to not block requests
|
|
192
|
+
5. **Structured Data** - Use consistent action and resource names
|