@breadstone/archipel-mcp 0.0.10 → 0.0.11
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/data/guides/ai-text-generation.md +361 -0
- package/data/guides/analytics-and-error-tracking.md +189 -0
- package/data/guides/authentication-and-authorization.md +657 -0
- package/data/guides/blob-storage.md +242 -0
- package/data/guides/caching.md +255 -0
- package/data/guides/cryptography-and-otp.md +240 -0
- package/data/guides/document-generation.md +174 -0
- package/data/guides/email-delivery.md +196 -0
- package/data/guides/esigning-integration.md +231 -0
- package/data/guides/getting-started.md +351 -0
- package/data/guides/implementing-ports.md +317 -0
- package/data/guides/index.md +61 -0
- package/data/guides/mcp-server.md +222 -0
- package/data/guides/openapi-and-feature-discovery.md +266 -0
- package/data/guides/payments-and-feature-gating.md +244 -0
- package/data/guides/resource-management.md +352 -0
- package/data/guides/telemetry-and-observability.md +190 -0
- package/data/guides/testing.md +319 -0
- package/data/guides/tsdoc-guidelines.md +45 -0
- package/data/packages/platform-openapi/api/Class.SwaggerFeatureDiscovery.md +10 -6
- package/package.json +1 -1
- package/src/GuidesLoader.d.ts +13 -0
- package/src/GuidesLoader.js +81 -0
- package/src/main.js +36 -198
- package/src/models/IGuideDoc.d.ts +15 -0
- package/src/models/IGuideDoc.js +3 -0
- package/src/tools/registerGetConfigPatternTool.d.ts +5 -0
- package/src/tools/registerGetConfigPatternTool.js +15 -0
- package/src/tools/registerGetDtoPatternTool.d.ts +5 -0
- package/src/tools/registerGetDtoPatternTool.js +15 -0
- package/src/tools/registerGetErrorHandlingPatternTool.d.ts +5 -0
- package/src/tools/registerGetErrorHandlingPatternTool.js +15 -0
- package/src/tools/registerGetGuardPatternTool.d.ts +5 -0
- package/src/tools/registerGetGuardPatternTool.js +15 -0
- package/src/tools/registerGetGuideTool.d.ts +6 -0
- package/src/tools/registerGetGuideTool.js +32 -0
- package/src/tools/registerGetMappingPatternTool.d.ts +5 -0
- package/src/tools/registerGetMappingPatternTool.js +34 -0
- package/src/tools/registerGetModulePatternTool.d.ts +5 -0
- package/src/tools/registerGetModulePatternTool.js +29 -0
- package/src/tools/registerGetPackageDocTool.d.ts +6 -0
- package/src/tools/registerGetPackageDocTool.js +43 -0
- package/src/tools/registerGetQueryPatternTool.d.ts +5 -0
- package/src/tools/registerGetQueryPatternTool.js +29 -0
- package/src/tools/registerGetRepositoryPatternTool.d.ts +5 -0
- package/src/tools/registerGetRepositoryPatternTool.js +29 -0
- package/src/tools/registerGetTestingPatternTool.d.ts +5 -0
- package/src/tools/registerGetTestingPatternTool.js +15 -0
- package/src/tools/registerListGuidesTool.d.ts +6 -0
- package/src/tools/registerListGuidesTool.js +22 -0
- package/src/tools/registerListPackagesTool.d.ts +6 -0
- package/src/tools/registerListPackagesTool.js +20 -0
- package/src/tools/registerSearchDocsTool.d.ts +6 -0
- package/src/tools/registerSearchDocsTool.js +35 -0
- package/src/tools/registerSearchGuidesTool.d.ts +6 -0
- package/src/tools/registerSearchGuidesTool.js +28 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: OpenAPI & Feature Discovery
|
|
3
|
+
description: Set up multi-document Swagger UI, annotate controllers with the @Api decorator, and use @SwaggerFeature for automatic per-module documentation discovery.
|
|
4
|
+
order: 16
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# OpenAPI & Feature Discovery
|
|
8
|
+
|
|
9
|
+
This guide covers the full lifecycle of API documentation with `platform-openapi`: installing the package, configuring environment variables, annotating controllers, registering feature modules, and bootstrapping the multi-document Swagger UI.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn add @breadstone/archipel-platform-openapi
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
|
|
23
|
+
### Environment Variables
|
|
24
|
+
|
|
25
|
+
All four variables are **required**:
|
|
26
|
+
|
|
27
|
+
```env
|
|
28
|
+
APP_VERSION=1.0.0
|
|
29
|
+
SWAGGER_CONTACT_NAME=Your Team
|
|
30
|
+
SWAGGER_CONTACT_URL=https://yourcompany.com
|
|
31
|
+
SWAGGER_CONTACT_EMAIL=api@yourcompany.com
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
| Variable | Purpose |
|
|
35
|
+
| ----------------------- | -------------------------------------------------- |
|
|
36
|
+
| `APP_VERSION` | Version shown in every generated OpenAPI document. |
|
|
37
|
+
| `SWAGGER_CONTACT_NAME` | Contact name in the info block. |
|
|
38
|
+
| `SWAGGER_CONTACT_URL` | Contact URL in the info block. |
|
|
39
|
+
| `SWAGGER_CONTACT_EMAIL` | Contact email in the info block. |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Overview: How It Works
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
@SwaggerFeature() SwaggerFeatureDiscovery SwaggerFeatureRegistry SwaggerMultiDocumentService
|
|
47
|
+
────────────────── ────────────────────── ────────────────────── ───────────────────────────
|
|
48
|
+
Decorator stores Reads global store and Holds finalized list of Builds per-feature OpenAPI
|
|
49
|
+
metadata in global registers features into features. Sorted, locked, documents, sets up Swagger
|
|
50
|
+
store at import time. the registry. immutable after finalize(). UI routes, and index page.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
1. Each feature module is decorated with `@SwaggerFeature()` — metadata is stored globally when the module is imported.
|
|
54
|
+
2. During bootstrap, `SwaggerFeatureDiscovery` reads all registered features and pushes them into `SwaggerFeatureRegistry`.
|
|
55
|
+
3. `SwaggerMultiDocumentService` finalizes the registry, builds one OpenAPI document per feature (filtered by tag), and mounts Swagger UI + JSON endpoints.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Step 1: Annotate Feature Modules
|
|
60
|
+
|
|
61
|
+
Decorate each feature module with `@SwaggerFeature()`. This tells the discovery system which tag corresponds to which documentation section.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { Module } from '@nestjs/common';
|
|
65
|
+
import { SwaggerFeature } from '@breadstone/archipel-platform-openapi';
|
|
66
|
+
import { TipController } from './TipController';
|
|
67
|
+
import { TipService } from './TipService';
|
|
68
|
+
|
|
69
|
+
@SwaggerFeature({
|
|
70
|
+
name: 'tip',
|
|
71
|
+
title: 'My App API — Tips',
|
|
72
|
+
description: 'API endpoints for tip management.',
|
|
73
|
+
tag: 'Tip',
|
|
74
|
+
tags: ['Tip'],
|
|
75
|
+
})
|
|
76
|
+
@Module({
|
|
77
|
+
controllers: [TipController],
|
|
78
|
+
providers: [TipService],
|
|
79
|
+
exports: [TipService],
|
|
80
|
+
})
|
|
81
|
+
export class TipModule {}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
| Property | Required | Description |
|
|
85
|
+
| ------------- | -------- | ---------------------------------------------------------------------------- |
|
|
86
|
+
| `name` | Yes | Short identifier used for the URL path (e.g. `docs/tip`). |
|
|
87
|
+
| `title` | Yes | Human-readable title shown in the Swagger UI header. |
|
|
88
|
+
| `description` | Yes | Description shown in the Swagger UI header and on the index page. |
|
|
89
|
+
| `tag` | Yes | Primary OpenAPI tag — operations with this tag are included in the document. |
|
|
90
|
+
| `tags` | Yes | Array of all tags to include. Usually `[tag]`. |
|
|
91
|
+
| `path` | No | Custom URL path. Defaults to `docs/{name}`. |
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Step 2: Annotate Controllers with `@Api()`
|
|
96
|
+
|
|
97
|
+
`@Api()` is a convenience decorator that combines multiple `@nestjs/swagger` decorators into one. Use it on methods or classes to keep controller annotations concise.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { Controller, Get, Post, Body, Param, HttpCode, HttpStatus } from '@nestjs/common';
|
|
101
|
+
import { Api } from '@breadstone/archipel-platform-openapi';
|
|
102
|
+
import { CreateTipRequest } from './dto/CreateTipRequest';
|
|
103
|
+
import { TipResponse } from './dto/TipResponse';
|
|
104
|
+
|
|
105
|
+
@Controller('api/v1/tips')
|
|
106
|
+
export class TipController {
|
|
107
|
+
@Get()
|
|
108
|
+
@Api({
|
|
109
|
+
tags: 'Tip',
|
|
110
|
+
operation: { summary: 'List all tips' },
|
|
111
|
+
responses: [{ status: 200, description: 'Tips retrieved.', type: [TipResponse] }],
|
|
112
|
+
})
|
|
113
|
+
public listTips(): Promise<TipResponse[]> {
|
|
114
|
+
// ...
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@Get(':tipId')
|
|
118
|
+
@Api({
|
|
119
|
+
tags: 'Tip',
|
|
120
|
+
auth: 'bearer',
|
|
121
|
+
operation: { summary: 'Get a tip by ID' },
|
|
122
|
+
params: { name: 'tipId', type: String },
|
|
123
|
+
responses: [
|
|
124
|
+
{ status: 200, description: 'Tip found.', type: TipResponse },
|
|
125
|
+
{ status: 404, description: 'Tip not found.' },
|
|
126
|
+
],
|
|
127
|
+
})
|
|
128
|
+
public getTip(@Param('tipId') tipId: string): Promise<TipResponse> {
|
|
129
|
+
// ...
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@Post()
|
|
133
|
+
@HttpCode(HttpStatus.CREATED)
|
|
134
|
+
@Api({
|
|
135
|
+
tags: 'Tip',
|
|
136
|
+
auth: 'bearer',
|
|
137
|
+
operation: { summary: 'Create a new tip' },
|
|
138
|
+
body: { type: CreateTipRequest },
|
|
139
|
+
responses: [
|
|
140
|
+
{ status: 201, description: 'Tip created.', type: TipResponse },
|
|
141
|
+
{ status: 400, description: 'Validation failed.' },
|
|
142
|
+
],
|
|
143
|
+
})
|
|
144
|
+
public createTip(@Body() body: CreateTipRequest): Promise<TipResponse> {
|
|
145
|
+
// ...
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### `@Api()` Options Reference
|
|
151
|
+
|
|
152
|
+
| Option | Type | Description |
|
|
153
|
+
| ------------ | --------------------------------------------- | ---------------------------------------------------- |
|
|
154
|
+
| `auth` | `'bearer' \| 'basic' \| 'oauth2' \| 'cookie'` | Adds the corresponding auth decorator. |
|
|
155
|
+
| `tags` | `string \| string[]` | OpenAPI tag(s) for the operation. |
|
|
156
|
+
| `operation` | `ApiOperationOptions` | Summary, description, deprecation flag, etc. |
|
|
157
|
+
| `responses` | `ApiResponseOptions \| ApiResponseOptions[]` | One or more response definitions. |
|
|
158
|
+
| `headers` | `ApiHeaderOptions[]` | Expected request headers. |
|
|
159
|
+
| `params` | `ApiParamOptions \| ApiParamOptions[]` | Path parameter definitions. |
|
|
160
|
+
| `query` | `ApiQueryOptions \| ApiQueryOptions[]` | Query parameter definitions. |
|
|
161
|
+
| `body` | `ApiBodyOptions` | Request body definition. |
|
|
162
|
+
| `scopes` | `string[]` | OAuth2 scopes (only relevant when `auth: 'oauth2'`). |
|
|
163
|
+
| `extensions` | `Record<string, string \| number \| boolean>` | Custom OpenAPI extension properties (`x-*`). |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Step 3: Bootstrap the Swagger UI
|
|
168
|
+
|
|
169
|
+
In your application bootstrap (`main.ts`), wire up discovery and the multi-document service:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { NestFactory } from '@nestjs/core';
|
|
173
|
+
import {
|
|
174
|
+
SwaggerFeatureDiscovery,
|
|
175
|
+
SwaggerFeatureRegistry,
|
|
176
|
+
SwaggerMultiDocumentService,
|
|
177
|
+
} from '@breadstone/archipel-platform-openapi';
|
|
178
|
+
import { ConfigService, ContentTemplateEngine, HostService, ResourceManager } from '@breadstone/archipel-platform-core';
|
|
179
|
+
import { AppModule } from './AppModule';
|
|
180
|
+
|
|
181
|
+
async function bootstrap() {
|
|
182
|
+
const app = await NestFactory.create(AppModule);
|
|
183
|
+
|
|
184
|
+
// Resolve services
|
|
185
|
+
const configService = app.get(ConfigService);
|
|
186
|
+
const hostService = app.get(HostService);
|
|
187
|
+
const resourceManager = app.get(ResourceManager);
|
|
188
|
+
const contentTemplateEngine = app.get(ContentTemplateEngine);
|
|
189
|
+
|
|
190
|
+
// 1. Discover features
|
|
191
|
+
const featureRegistry = new SwaggerFeatureRegistry();
|
|
192
|
+
const discovery = new SwaggerFeatureDiscovery();
|
|
193
|
+
discovery.registerDiscoveredFeatures(featureRegistry);
|
|
194
|
+
discovery.registerDefaultFeatures(featureRegistry);
|
|
195
|
+
|
|
196
|
+
// 2. Setup multi-document Swagger UI
|
|
197
|
+
const swaggerService = new SwaggerMultiDocumentService(
|
|
198
|
+
app,
|
|
199
|
+
configService,
|
|
200
|
+
hostService,
|
|
201
|
+
featureRegistry,
|
|
202
|
+
resourceManager,
|
|
203
|
+
contentTemplateEngine,
|
|
204
|
+
);
|
|
205
|
+
await swaggerService.setupMultipleSwaggerDocuments();
|
|
206
|
+
|
|
207
|
+
await app.listen(3000);
|
|
208
|
+
}
|
|
209
|
+
bootstrap();
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
After bootstrap the following routes are available:
|
|
213
|
+
|
|
214
|
+
| Route | What it serves |
|
|
215
|
+
| ------------------ | --------------------------------------------------- |
|
|
216
|
+
| `/docs` | Main index page with a searchable feature grid. |
|
|
217
|
+
| `/docs/{name}` | Swagger UI for a single feature (e.g. `/docs/tip`). |
|
|
218
|
+
| `/api/{name}.json` | Raw OpenAPI JSON for a single feature. |
|
|
219
|
+
| `/docs/all` | Combined Swagger UI with every endpoint. |
|
|
220
|
+
| `/api/all.json` | Combined OpenAPI JSON. |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## How Tag Filtering Works
|
|
225
|
+
|
|
226
|
+
Each feature declares a `tag` (e.g. `'Tip'`). When the document is built:
|
|
227
|
+
|
|
228
|
+
1. A full OpenAPI document is created from the entire application.
|
|
229
|
+
2. All paths that do **not** have an operation tagged with the feature's tag are removed.
|
|
230
|
+
3. Schema references used only by removed operations are pruned.
|
|
231
|
+
4. The special tag `'*'` (used by the default `all` feature) skips filtering entirely.
|
|
232
|
+
|
|
233
|
+
This means the `tag` value in `@SwaggerFeature()` must match the `tags` value you pass to `@Api()` on your controllers.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Exporting OpenAPI Specs Programmatically
|
|
238
|
+
|
|
239
|
+
If you need the generated OpenAPI documents outside of a running HTTP server (e.g. for CI contract checks or SDK generation), use `generateOpenApiDocuments()`:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
const documents = swaggerService.generateOpenApiDocuments();
|
|
243
|
+
|
|
244
|
+
for (const { feature, document } of documents) {
|
|
245
|
+
const json = JSON.stringify(document, null, 2);
|
|
246
|
+
writeFileSync(`specs/${feature.name}.json`, json);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Theme Customization
|
|
253
|
+
|
|
254
|
+
`SwaggerTheme` loads `swagger.css` and `swagger.js` from the package assets via `ResourceManager`. These files customize the Swagger UI appearance (colors, dark theme, responsive layout). The theme is applied automatically — no additional configuration is needed.
|
|
255
|
+
|
|
256
|
+
To override the theme, provide your own `swagger.css` / `swagger.js` files through a `FileResourceStrategy` with a higher-priority base path so that `ResourceManager` picks them up before the built-in assets.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Checklist
|
|
261
|
+
|
|
262
|
+
- [ ] All four environment variables set (`APP_VERSION`, `SWAGGER_CONTACT_*`)
|
|
263
|
+
- [ ] Every feature module decorated with `@SwaggerFeature()`
|
|
264
|
+
- [ ] Tags in `@SwaggerFeature({ tag })` match tags in `@Api({ tags })`
|
|
265
|
+
- [ ] `SwaggerFeatureDiscovery` called before `setupMultipleSwaggerDocuments()`
|
|
266
|
+
- [ ] `registerDefaultFeatures()` called for the combined `/docs/all` document
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Payments & Feature Gating
|
|
3
|
+
description: Integrate payment providers (Stripe, Paddle, LemonSqueezy, Mollie) and gate features by subscription tier.
|
|
4
|
+
order: 9
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Payments & Feature Gating
|
|
8
|
+
|
|
9
|
+
This guide covers payment integration with `platform-payments`: choosing a payment provider, handling webhooks, gating features by subscription tier, and tracking feature usage.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn add @breadstone/archipel-platform-payments
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Choose a Payment Provider
|
|
22
|
+
|
|
23
|
+
`platform-payments` normalizes payment operations across multiple providers. You implement the `PaymentClientPort` for your chosen provider:
|
|
24
|
+
|
|
25
|
+
| Provider | What it provides |
|
|
26
|
+
| ---------------- | --------------------------------------- |
|
|
27
|
+
| **Stripe** | Cards, subscriptions, invoices |
|
|
28
|
+
| **Paddle** | SaaS billing, tax handling |
|
|
29
|
+
| **LemonSqueezy** | Digital products, memberships |
|
|
30
|
+
| **Mollie** | European payment methods, subscriptions |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Module Registration
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { Module } from '@nestjs/common';
|
|
38
|
+
import { PaymentModule } from '@breadstone/archipel-platform-payments';
|
|
39
|
+
import { StripePaymentClient } from './adapters/StripePaymentClient';
|
|
40
|
+
import { PrismaFeatureAccessAdapter } from './adapters/PrismaFeatureAccessAdapter';
|
|
41
|
+
import { STRIPE_CONFIG_ENTRIES } from './env';
|
|
42
|
+
|
|
43
|
+
@Module({
|
|
44
|
+
imports: [
|
|
45
|
+
PaymentModule.register({
|
|
46
|
+
paymentClient: StripePaymentClient,
|
|
47
|
+
featureAccess: PrismaFeatureAccessAdapter,
|
|
48
|
+
configEntries: [...STRIPE_CONFIG_ENTRIES],
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
})
|
|
52
|
+
export class AppModule {}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Implementing the Payment Client Port
|
|
58
|
+
|
|
59
|
+
The `PaymentClientPort` defines the contract between the library and your payment provider. Implement it for your chosen provider:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { Injectable } from '@nestjs/common';
|
|
63
|
+
import {
|
|
64
|
+
PaymentClientPort,
|
|
65
|
+
type INormalizedCheckoutSession,
|
|
66
|
+
type INormalizedSubscription,
|
|
67
|
+
type INormalizedWebhookEvent,
|
|
68
|
+
} from '@breadstone/archipel-platform-payments';
|
|
69
|
+
|
|
70
|
+
@Injectable()
|
|
71
|
+
export class StripePaymentClient extends PaymentClientPort {
|
|
72
|
+
public async createCheckoutSession(params: Record<string, unknown>): Promise<INormalizedCheckoutSession> {
|
|
73
|
+
// Use Stripe SDK to create a checkout session
|
|
74
|
+
// Return a normalized result
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public async getSubscription(subscriptionId: string): Promise<INormalizedSubscription | null> {
|
|
78
|
+
// Fetch subscription details from Stripe
|
|
79
|
+
// Return normalized subscription data
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public async handleWebhook(payload: Buffer, signature: string): Promise<INormalizedWebhookEvent> {
|
|
83
|
+
// Verify webhook signature and normalize the event
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The library uses **normalized models** (`INormalizedSubscription`, `INormalizedCheckoutSession`, `INormalizedWebhookEvent`) so your application code works identically regardless of the payment provider.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Feature Gating
|
|
93
|
+
|
|
94
|
+
The `FeatureAccessPort` lets you restrict functionality based on subscription tiers.
|
|
95
|
+
|
|
96
|
+
### Step 1: Implement the Port
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { Injectable } from '@nestjs/common';
|
|
100
|
+
import { FeatureAccessPort } from '@breadstone/archipel-platform-payments';
|
|
101
|
+
import { PrismaService } from '@breadstone/archipel-platform-database';
|
|
102
|
+
|
|
103
|
+
@Injectable()
|
|
104
|
+
export class PrismaFeatureAccessAdapter extends FeatureAccessPort {
|
|
105
|
+
private readonly _prisma: PrismaService;
|
|
106
|
+
|
|
107
|
+
constructor(prisma: PrismaService) {
|
|
108
|
+
super();
|
|
109
|
+
this._prisma = prisma;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public async hasAccess(userId: string, feature: string): Promise<boolean> {
|
|
113
|
+
const subscription = await this._prisma.subscription.findFirst({
|
|
114
|
+
where: { userId, status: 'active' },
|
|
115
|
+
include: { plan: true },
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (!subscription) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return subscription.plan.features.includes(feature);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public async recordUsage(userId: string, feature: string): Promise<void> {
|
|
126
|
+
await this._prisma.featureUsage.create({
|
|
127
|
+
data: { userId, feature, usedAt: new Date() },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public async getUsageCount(userId: string, feature: string, since: Date): Promise<number> {
|
|
132
|
+
return this._prisma.featureUsage.count({
|
|
133
|
+
where: { userId, feature, usedAt: { gte: since } },
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Step 2: Use the Feature Guard
|
|
140
|
+
|
|
141
|
+
The `FeatureGuard` decorator restricts endpoints to users with access to specific features:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { Controller, Post, UseGuards } from '@nestjs/common';
|
|
145
|
+
import { JwtAuthGuard } from '@breadstone/archipel-platform-authentication';
|
|
146
|
+
import { FeatureGuard } from '@breadstone/archipel-platform-payments';
|
|
147
|
+
|
|
148
|
+
@Controller('exports')
|
|
149
|
+
@UseGuards(JwtAuthGuard)
|
|
150
|
+
export class ExportController {
|
|
151
|
+
@Post('pdf')
|
|
152
|
+
@FeatureGuard('export:pdf')
|
|
153
|
+
public async exportPdf(): Promise<void> {
|
|
154
|
+
// Only accessible to users with 'export:pdf' feature
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@Post('csv')
|
|
158
|
+
@FeatureGuard('export:csv')
|
|
159
|
+
public async exportCsv(): Promise<void> {
|
|
160
|
+
// Only accessible to users with 'export:csv' feature
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Step 3: Track Usage with Interceptor
|
|
166
|
+
|
|
167
|
+
`FeatureUsageInterceptor` automatically records feature usage after a successful request:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { Controller, Post, UseGuards, UseInterceptors } from '@nestjs/common';
|
|
171
|
+
import { JwtAuthGuard } from '@breadstone/archipel-platform-authentication';
|
|
172
|
+
import { FeatureGuard, FeatureUsageInterceptor } from '@breadstone/archipel-platform-payments';
|
|
173
|
+
|
|
174
|
+
@Controller('ai')
|
|
175
|
+
@UseGuards(JwtAuthGuard)
|
|
176
|
+
export class AiController {
|
|
177
|
+
@Post('generate')
|
|
178
|
+
@FeatureGuard('ai:generate')
|
|
179
|
+
@UseInterceptors(FeatureUsageInterceptor)
|
|
180
|
+
public async generate(): Promise<string> {
|
|
181
|
+
// Usage is recorded automatically after success
|
|
182
|
+
return 'generated content';
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Handling Webhooks
|
|
190
|
+
|
|
191
|
+
Payment providers send webhook events for subscription changes, payment failures, etc. Create a dedicated webhook controller:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { Controller, Post, Req, Headers, RawBodyRequest } from '@nestjs/common';
|
|
195
|
+
import { PaymentClientPort } from '@breadstone/archipel-platform-payments';
|
|
196
|
+
import { Request } from 'express';
|
|
197
|
+
|
|
198
|
+
@Controller('webhooks/payments')
|
|
199
|
+
export class PaymentWebhookController {
|
|
200
|
+
private readonly _paymentClient: PaymentClientPort;
|
|
201
|
+
|
|
202
|
+
constructor(paymentClient: PaymentClientPort) {
|
|
203
|
+
this._paymentClient = paymentClient;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@Post()
|
|
207
|
+
public async handleWebhook(
|
|
208
|
+
@Req() request: RawBodyRequest<Request>,
|
|
209
|
+
@Headers('stripe-signature') signature: string,
|
|
210
|
+
): Promise<void> {
|
|
211
|
+
const event = await this._paymentClient.handleWebhook(request.rawBody!, signature);
|
|
212
|
+
|
|
213
|
+
switch (event.type) {
|
|
214
|
+
case 'subscription.created':
|
|
215
|
+
case 'subscription.updated':
|
|
216
|
+
// Update subscription in your database
|
|
217
|
+
break;
|
|
218
|
+
case 'subscription.deleted':
|
|
219
|
+
// Handle cancellation
|
|
220
|
+
break;
|
|
221
|
+
case 'payment.failed':
|
|
222
|
+
// Notify user, retry logic
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Best Practices
|
|
232
|
+
|
|
233
|
+
1. **Define feature keys as constants.** Never use inline strings for feature identifiers.
|
|
234
|
+
2. **Verify webhook signatures.** Always validate the webhook payload before processing.
|
|
235
|
+
3. **Use normalized models.** Don't leak provider-specific types into your business logic.
|
|
236
|
+
4. **Implement idempotent webhook handlers.** Webhooks can be delivered multiple times.
|
|
237
|
+
5. **Separate feature gating from billing logic.** The `FeatureAccessPort` should only check access, not manage subscriptions.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Next Steps
|
|
242
|
+
|
|
243
|
+
- See the [Authentication & Authorization](/guides/authentication-and-authorization) guide for securing payment endpoints
|
|
244
|
+
- Browse the [platform-payments API reference](/packages/platform-payments/api/) for complete method signatures
|