@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,231 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: E-Signing Integration
|
|
3
|
+
description: Integrate electronic signature workflows with DocuSign, Adobe Sign, Dropbox Sign, SignNow, or an internal provider.
|
|
4
|
+
order: 12
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# E-Signing Integration
|
|
8
|
+
|
|
9
|
+
This guide covers electronic signature workflows with `platform-esigning`: provider setup, creating signing requests, tracking signer status, and handling webhook notifications.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn add @breadstone/archipel-platform-esigning
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Choose a Provider
|
|
22
|
+
|
|
23
|
+
`platform-esigning` abstracts e-signing operations behind a `EsigningClientPort`. Supported providers:
|
|
24
|
+
|
|
25
|
+
| Provider | Description |
|
|
26
|
+
| ---------------- | ---------------------------------------- |
|
|
27
|
+
| **DocuSign** | Industry-standard e-signature platform |
|
|
28
|
+
| **Adobe Sign** | Adobe Acrobat Sign integration |
|
|
29
|
+
| **Dropbox Sign** | Dropbox (formerly HelloSign) integration |
|
|
30
|
+
| **SignNow** | SignNow e-signature platform |
|
|
31
|
+
| **Internal** | Custom/self-managed signing |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Module Registration
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { Module } from '@nestjs/common';
|
|
39
|
+
import { EsigningModule } from '@breadstone/archipel-platform-esigning';
|
|
40
|
+
import { DocuSignClient } from './adapters/DocuSignClient';
|
|
41
|
+
import { PrismaSigningRequestAdapter } from './adapters/PrismaSigningRequestAdapter';
|
|
42
|
+
import { DOCUSIGN_CONFIG_ENTRIES } from './env';
|
|
43
|
+
|
|
44
|
+
@Module({
|
|
45
|
+
imports: [
|
|
46
|
+
EsigningModule.register({
|
|
47
|
+
provider: DocuSignClient,
|
|
48
|
+
persistence: PrismaSigningRequestAdapter,
|
|
49
|
+
configEntries: [...DOCUSIGN_CONFIG_ENTRIES],
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
})
|
|
53
|
+
export class AppModule {}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Configuration
|
|
59
|
+
|
|
60
|
+
```env
|
|
61
|
+
ESIGNING_API_BASE_URL=https://demo.docusign.net/restapi
|
|
62
|
+
ESIGNING_API_KEY=your-api-key
|
|
63
|
+
ESIGNING_WEBHOOK_SECRET=your-webhook-secret
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Implementing the Client Port
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { Injectable } from '@nestjs/common';
|
|
72
|
+
import {
|
|
73
|
+
EsigningClientPort,
|
|
74
|
+
type ISigningRequest,
|
|
75
|
+
type ISigningSession,
|
|
76
|
+
type ISignedDocument,
|
|
77
|
+
} from '@breadstone/archipel-platform-esigning';
|
|
78
|
+
|
|
79
|
+
@Injectable()
|
|
80
|
+
export class DocuSignClient extends EsigningClientPort {
|
|
81
|
+
public async createSigningSession(request: ISigningRequest): Promise<ISigningSession> {
|
|
82
|
+
// Call DocuSign API to create an envelope
|
|
83
|
+
// Return normalized signing session with redirect URLs
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public async getSigningStatus(sessionId: string): Promise<ISigningSession> {
|
|
87
|
+
// Fetch signing status from DocuSign
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public async downloadSignedDocument(sessionId: string): Promise<ISignedDocument> {
|
|
91
|
+
// Download the completed document from DocuSign
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public async cancelSigningSession(sessionId: string): Promise<void> {
|
|
95
|
+
// Void the envelope in DocuSign
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Implementing the Persistence Port
|
|
103
|
+
|
|
104
|
+
Track signing request state in your database:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { Injectable } from '@nestjs/common';
|
|
108
|
+
import { EsigningPersistencePort, type ISigningRequest } from '@breadstone/archipel-platform-esigning';
|
|
109
|
+
import { PrismaService } from '@breadstone/archipel-platform-database';
|
|
110
|
+
|
|
111
|
+
@Injectable()
|
|
112
|
+
export class PrismaSigningRequestAdapter extends EsigningPersistencePort {
|
|
113
|
+
private readonly _prisma: PrismaService;
|
|
114
|
+
|
|
115
|
+
constructor(prisma: PrismaService) {
|
|
116
|
+
super();
|
|
117
|
+
this._prisma = prisma;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public async save(request: ISigningRequest): Promise<void> {
|
|
121
|
+
await this._prisma.signingRequest.upsert({
|
|
122
|
+
where: { id: request.id },
|
|
123
|
+
create: {
|
|
124
|
+
id: request.id,
|
|
125
|
+
userId: request.userId,
|
|
126
|
+
documentId: request.documentId,
|
|
127
|
+
status: request.status,
|
|
128
|
+
providerSessionId: request.providerSessionId,
|
|
129
|
+
},
|
|
130
|
+
update: {
|
|
131
|
+
status: request.status,
|
|
132
|
+
providerSessionId: request.providerSessionId,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public async findById(id: string): Promise<ISigningRequest | null> {
|
|
138
|
+
return this._prisma.signingRequest.findUnique({ where: { id } });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public async findByUserId(userId: string): Promise<ReadonlyArray<ISigningRequest>> {
|
|
142
|
+
return this._prisma.signingRequest.findMany({ where: { userId } });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Creating a Signing Request
|
|
150
|
+
|
|
151
|
+
Use `EsigningService` to orchestrate the signing workflow:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { Injectable } from '@nestjs/common';
|
|
155
|
+
import { EsigningService } from '@breadstone/archipel-platform-esigning';
|
|
156
|
+
|
|
157
|
+
@Injectable()
|
|
158
|
+
export class ContractService {
|
|
159
|
+
private readonly _esigningService: EsigningService;
|
|
160
|
+
|
|
161
|
+
constructor(esigningService: EsigningService) {
|
|
162
|
+
this._esigningService = esigningService;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public async sendForSigning(userId: string, documentUrl: string, signerEmail: string): Promise<string> {
|
|
166
|
+
const session = await this._esigningService.createSession({
|
|
167
|
+
userId,
|
|
168
|
+
documentUrl,
|
|
169
|
+
signers: [
|
|
170
|
+
{
|
|
171
|
+
email: signerEmail,
|
|
172
|
+
name: 'Signer',
|
|
173
|
+
order: 1,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
callbackUrl: 'https://yourapp.com/webhooks/esigning',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return session.signingUrl; // Redirect the user to this URL
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Handling Webhooks
|
|
187
|
+
|
|
188
|
+
E-signing providers send webhook events when documents are signed, declined, or expired:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { Controller, Post, Req, Headers, RawBodyRequest } from '@nestjs/common';
|
|
192
|
+
import { EsigningService } from '@breadstone/archipel-platform-esigning';
|
|
193
|
+
import { Request } from 'express';
|
|
194
|
+
|
|
195
|
+
@Controller('webhooks/esigning')
|
|
196
|
+
export class EsigningWebhookController {
|
|
197
|
+
private readonly _esigningService: EsigningService;
|
|
198
|
+
|
|
199
|
+
constructor(esigningService: EsigningService) {
|
|
200
|
+
this._esigningService = esigningService;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@Post()
|
|
204
|
+
public async handleWebhook(
|
|
205
|
+
@Req() request: RawBodyRequest<Request>,
|
|
206
|
+
@Headers('x-webhook-signature') signature: string,
|
|
207
|
+
): Promise<void> {
|
|
208
|
+
await this._esigningService.handleWebhook(request.rawBody!, signature);
|
|
209
|
+
// The service verifies the signature, updates the signing request status,
|
|
210
|
+
// and emits domain events for your application to react to.
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Best Practices
|
|
218
|
+
|
|
219
|
+
1. **Always verify webhook signatures.** Never trust webhook payloads without signature validation.
|
|
220
|
+
2. **Use the persistence port.** Track signing request state to handle retries, status queries, and auditing.
|
|
221
|
+
3. **Implement idempotent webhook handlers.** Providers may deliver the same event multiple times.
|
|
222
|
+
4. **Store signed documents.** Use `platform-blob-storage` to archive completed signed documents.
|
|
223
|
+
5. **Handle expiration.** Set up a scheduled job to check for expired signing sessions and notify users.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Next Steps
|
|
228
|
+
|
|
229
|
+
- See the [Document Generation](/guides/document-generation) guide for creating documents to send for signing
|
|
230
|
+
- See the [Blob Storage](/guides/blob-storage) guide for storing signed documents
|
|
231
|
+
- Browse the [platform-esigning API reference](/packages/platform-esigning/api/) for complete method signatures
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Getting Started
|
|
3
|
+
description: Set up an Archipel-powered NestJS application from scratch.
|
|
4
|
+
order: 1
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Getting Started
|
|
8
|
+
|
|
9
|
+
This guide walks you through adding Archipel packages to a NestJS application. By the end, you'll have a working setup with configuration management, database access, and authentication.
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
- Node.js 20 or later
|
|
14
|
+
- A NestJS application (v10+)
|
|
15
|
+
- PostgreSQL database (for `platform-database`)
|
|
16
|
+
- Prisma CLI (`npx prisma`)
|
|
17
|
+
|
|
18
|
+
## Install the Foundation
|
|
19
|
+
|
|
20
|
+
Every Archipel application starts with `platform-core`. It provides configuration management, object mapping, caching, and cryptographic utilities that other packages depend on.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
yarn add @breadstone/archipel-platform-core
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Set Up Configuration
|
|
27
|
+
|
|
28
|
+
Archipel uses a type-safe configuration system. Instead of reading `process.env` directly, you define typed config keys and register them with the `ConfigModule`. This gives you compile-time safety, centralized validation, and default values — all scoped to your module.
|
|
29
|
+
|
|
30
|
+
Create a config file for your application:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// src/env.ts
|
|
34
|
+
import { createConfigKey, type IConfigRegistryEntry } from '@breadstone/archipel-platform-core';
|
|
35
|
+
|
|
36
|
+
export const DATABASE_URL = createConfigKey<string>('DATABASE_URL');
|
|
37
|
+
export const JWT_SECRET = createConfigKey<string>('JWT_SECRET');
|
|
38
|
+
export const JWT_EXPIRATION = createConfigKey<string>('JWT_EXPIRATION');
|
|
39
|
+
|
|
40
|
+
export const APP_CONFIG_ENTRIES: ReadonlyArray<IConfigRegistryEntry> = [
|
|
41
|
+
{ key: DATABASE_URL, module: 'app', required: true, description: 'PostgreSQL connection string' },
|
|
42
|
+
{ key: JWT_SECRET, module: 'app', required: true, description: 'Secret for signing JWTs' },
|
|
43
|
+
{ key: JWT_EXPIRATION, module: 'app', required: false, defaultValue: '1h', description: 'JWT token lifetime' },
|
|
44
|
+
];
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Register it in your root module:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// src/app.module.ts
|
|
51
|
+
import { Module } from '@nestjs/common';
|
|
52
|
+
import { ConfigModule } from '@breadstone/archipel-platform-core';
|
|
53
|
+
import { APP_CONFIG_ENTRIES } from './env';
|
|
54
|
+
|
|
55
|
+
@Module({
|
|
56
|
+
imports: [ConfigModule.register('app', APP_CONFIG_ENTRIES)],
|
|
57
|
+
})
|
|
58
|
+
export class AppModule {}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Now you can inject `ConfigService` anywhere and read values with full type safety:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { Injectable } from '@nestjs/common';
|
|
65
|
+
import { ConfigService } from '@breadstone/archipel-platform-core';
|
|
66
|
+
import { JWT_SECRET } from './env';
|
|
67
|
+
|
|
68
|
+
@Injectable()
|
|
69
|
+
export class TokenService {
|
|
70
|
+
private readonly _secret: string;
|
|
71
|
+
|
|
72
|
+
constructor(configService: ConfigService) {
|
|
73
|
+
this._secret = configService.get(JWT_SECRET);
|
|
74
|
+
// TypeScript knows this is a string — no cast needed
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The `ConfigModule` validates all required keys at startup. If `DATABASE_URL` is missing from the environment, the application fails immediately with a clear error message instead of crashing later at runtime.
|
|
80
|
+
|
|
81
|
+
### Set Up Object Mapping
|
|
82
|
+
|
|
83
|
+
The `MappingModule` provides a centralized service for transforming domain entities into response DTOs. This keeps transformation logic out of your services and controllers and makes it testable in isolation.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { Module } from '@nestjs/common';
|
|
87
|
+
import { ConfigModule, MappingModule } from '@breadstone/archipel-platform-core';
|
|
88
|
+
import { APP_CONFIG_ENTRIES } from './env';
|
|
89
|
+
import { UserMappingProfile } from './mapping/UserMappingProfile';
|
|
90
|
+
|
|
91
|
+
@Module({
|
|
92
|
+
imports: [ConfigModule.register('app', APP_CONFIG_ENTRIES), MappingModule],
|
|
93
|
+
providers: [UserMappingProfile],
|
|
94
|
+
})
|
|
95
|
+
export class AppModule {}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
You define mapping profiles that register transformations:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { Injectable } from '@nestjs/common';
|
|
102
|
+
import { MappingProfileBase, type MappingRegistry } from '@breadstone/archipel-platform-core';
|
|
103
|
+
import { USER_TO_RESPONSE } from './mapping-keys';
|
|
104
|
+
|
|
105
|
+
@Injectable()
|
|
106
|
+
export class UserMappingProfile extends MappingProfileBase {
|
|
107
|
+
public override configure(registry: MappingRegistry): void {
|
|
108
|
+
registry.createMap(USER_TO_RESPONSE, (source: IUserEntity) => ({
|
|
109
|
+
id: source.id,
|
|
110
|
+
name: source.userName,
|
|
111
|
+
email: source.email,
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Then use `MappingService.map()` in controllers to convert entities to responses without leaking mapping logic into services.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Add Database Access
|
|
122
|
+
|
|
123
|
+
Install the database package:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
yarn add @breadstone/archipel-platform-database
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`platform-database` provides a repository + query pattern on top of Prisma. Instead of calling Prisma directly from services, you define query objects and execute them through a repository base class. This keeps data access logic encapsulated, testable, and separate from business logic.
|
|
130
|
+
|
|
131
|
+
Register the module:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// src/app.module.ts
|
|
135
|
+
import { Module } from '@nestjs/common';
|
|
136
|
+
import { ConfigModule, MappingModule } from '@breadstone/archipel-platform-core';
|
|
137
|
+
import { DatabaseModule } from '@breadstone/archipel-platform-database';
|
|
138
|
+
import { APP_CONFIG_ENTRIES } from './env';
|
|
139
|
+
|
|
140
|
+
@Module({
|
|
141
|
+
imports: [ConfigModule.register('app', APP_CONFIG_ENTRIES), MappingModule, DatabaseModule.register()],
|
|
142
|
+
})
|
|
143
|
+
export class AppModule {}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Define Queries
|
|
147
|
+
|
|
148
|
+
A query is a plain object that describes a database operation. It receives a Prisma client and returns a result:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// src/users/queries/findUserByIdQuery.ts
|
|
152
|
+
import { type IRepositoryQuery } from '@breadstone/archipel-platform-database';
|
|
153
|
+
import { type PrismaClient } from '@prisma/client';
|
|
154
|
+
|
|
155
|
+
export interface IUserEntity {
|
|
156
|
+
id: string;
|
|
157
|
+
userName: string;
|
|
158
|
+
email: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const findUserByIdQuery = (id: string): IRepositoryQuery<PrismaClient, IUserEntity | null> => ({
|
|
162
|
+
execute: (prisma) =>
|
|
163
|
+
prisma.user.findUnique({
|
|
164
|
+
where: { id },
|
|
165
|
+
select: { id: true, userName: true, email: true },
|
|
166
|
+
}),
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Create a Repository
|
|
171
|
+
|
|
172
|
+
Repositories extend `RepositoryBase` and execute queries:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// src/users/UserRepository.ts
|
|
176
|
+
import { Injectable } from '@nestjs/common';
|
|
177
|
+
import { RepositoryBase } from '@breadstone/archipel-platform-database';
|
|
178
|
+
import { findUserByIdQuery, type IUserEntity } from './queries/findUserByIdQuery';
|
|
179
|
+
|
|
180
|
+
@Injectable()
|
|
181
|
+
export class UserRepository extends RepositoryBase {
|
|
182
|
+
public async findById(id: string): Promise<IUserEntity | null> {
|
|
183
|
+
return this.execute(findUserByIdQuery(id));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
This pattern means your services never touch Prisma directly. They call repository methods, which execute typed queries. When you need to test a service, you mock the repository — not Prisma internals.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Add Authentication
|
|
193
|
+
|
|
194
|
+
Install the authentication package:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
yarn add @breadstone/archipel-platform-authentication
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Authentication is the first package where you encounter **ports** — abstract classes that define contracts between the library and your application. `AuthModule` requires four port implementations that tell it how to look up users, sessions, MFA state, and verification records in your specific data store.
|
|
201
|
+
|
|
202
|
+
### Implement the Required Ports
|
|
203
|
+
|
|
204
|
+
Here's the most important one — `AuthSubjectPort`, which handles user lookup:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// src/adapters/PrismaAuthSubjectAdapter.ts
|
|
208
|
+
import { Injectable } from '@nestjs/common';
|
|
209
|
+
import { AuthSubjectPort, type IAuthSubject } from '@breadstone/archipel-platform-authentication';
|
|
210
|
+
import { PrismaService } from '@breadstone/archipel-platform-database';
|
|
211
|
+
|
|
212
|
+
@Injectable()
|
|
213
|
+
export class PrismaAuthSubjectAdapter extends AuthSubjectPort {
|
|
214
|
+
private readonly _prisma: PrismaService;
|
|
215
|
+
|
|
216
|
+
constructor(prisma: PrismaService) {
|
|
217
|
+
super();
|
|
218
|
+
this._prisma = prisma;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
public async findById(id: string): Promise<IAuthSubject | null> {
|
|
222
|
+
const user = await this._prisma.user.findUnique({
|
|
223
|
+
where: { id },
|
|
224
|
+
select: {
|
|
225
|
+
id: true,
|
|
226
|
+
userName: true,
|
|
227
|
+
email: true,
|
|
228
|
+
password: true,
|
|
229
|
+
isVerified: true,
|
|
230
|
+
isAnonymous: true,
|
|
231
|
+
roles: true,
|
|
232
|
+
mfaEnabled: true,
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return user ?? null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public async findByLogin(login: string): Promise<IAuthSubject | null> {
|
|
240
|
+
const user = await this._prisma.user.findFirst({
|
|
241
|
+
where: { OR: [{ userName: login }, { email: login }] },
|
|
242
|
+
select: {
|
|
243
|
+
id: true,
|
|
244
|
+
userName: true,
|
|
245
|
+
email: true,
|
|
246
|
+
password: true,
|
|
247
|
+
isVerified: true,
|
|
248
|
+
isAnonymous: true,
|
|
249
|
+
roles: true,
|
|
250
|
+
mfaEnabled: true,
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return user ?? null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
public async findAnonymous(userName: string): Promise<IAuthSubject | null> {
|
|
258
|
+
const user = await this._prisma.user.findFirst({
|
|
259
|
+
where: { userName, isAnonymous: true },
|
|
260
|
+
select: {
|
|
261
|
+
id: true,
|
|
262
|
+
userName: true,
|
|
263
|
+
email: true,
|
|
264
|
+
password: true,
|
|
265
|
+
isVerified: true,
|
|
266
|
+
isAnonymous: true,
|
|
267
|
+
roles: true,
|
|
268
|
+
mfaEnabled: true,
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return user ?? null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
You'll implement the same pattern for `MfaSubjectPort`, `SessionPersistencePort`, and `VerificationSubjectPort`. Each port's abstract methods are documented in the [platform-authentication API reference](/packages/platform-authentication/api/).
|
|
278
|
+
|
|
279
|
+
### Register the Module
|
|
280
|
+
|
|
281
|
+
Pass your adapter classes to `AuthModule.register()`:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// src/app.module.ts
|
|
285
|
+
import { Module } from '@nestjs/common';
|
|
286
|
+
import { ConfigModule, MappingModule } from '@breadstone/archipel-platform-core';
|
|
287
|
+
import { DatabaseModule } from '@breadstone/archipel-platform-database';
|
|
288
|
+
import { AuthModule } from '@breadstone/archipel-platform-authentication';
|
|
289
|
+
import { APP_CONFIG_ENTRIES } from './env';
|
|
290
|
+
import { PrismaAuthSubjectAdapter } from './adapters/PrismaAuthSubjectAdapter';
|
|
291
|
+
import { PrismaMfaSubjectAdapter } from './adapters/PrismaMfaSubjectAdapter';
|
|
292
|
+
import { PrismaSessionAdapter } from './adapters/PrismaSessionAdapter';
|
|
293
|
+
import { PrismaVerificationAdapter } from './adapters/PrismaVerificationAdapter';
|
|
294
|
+
|
|
295
|
+
@Module({
|
|
296
|
+
imports: [
|
|
297
|
+
ConfigModule.register('app', APP_CONFIG_ENTRIES),
|
|
298
|
+
MappingModule,
|
|
299
|
+
DatabaseModule.register(),
|
|
300
|
+
AuthModule.register({
|
|
301
|
+
authSubject: PrismaAuthSubjectAdapter,
|
|
302
|
+
mfaSubject: PrismaMfaSubjectAdapter,
|
|
303
|
+
sessionPersistence: PrismaSessionAdapter,
|
|
304
|
+
verificationSubject: PrismaVerificationAdapter,
|
|
305
|
+
}),
|
|
306
|
+
],
|
|
307
|
+
})
|
|
308
|
+
export class AppModule {}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The `AuthModule` now has everything it needs. Its internal services (`AuthTokenService`, `SessionService`, `MfaService`, `VerificationService`) are available for injection in your controllers and services.
|
|
312
|
+
|
|
313
|
+
### Optional Ports
|
|
314
|
+
|
|
315
|
+
`AuthModule` also accepts two optional ports:
|
|
316
|
+
|
|
317
|
+
- **`socialAuth`** — Provide a `SocialAuthPort` adapter to enable OAuth strategies (GitHub, Google, Microsoft, Apple). Without it, those routes don't exist.
|
|
318
|
+
- **`tokenEnricher`** — Provide a `TokenEnricherPort` adapter to add custom claims (subscription tier, org ID, etc.) to every JWT. Without it, tokens contain only the base claims.
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
AuthModule.register({
|
|
322
|
+
authSubject: PrismaAuthSubjectAdapter,
|
|
323
|
+
mfaSubject: PrismaMfaSubjectAdapter,
|
|
324
|
+
sessionPersistence: PrismaSessionAdapter,
|
|
325
|
+
verificationSubject: PrismaVerificationAdapter,
|
|
326
|
+
socialAuth: PrismaSocialAuthAdapter, // optional: enables OAuth
|
|
327
|
+
tokenEnricher: AppTokenEnricherAdapter, // optional: enriches JWTs
|
|
328
|
+
}),
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Adding More Packages
|
|
334
|
+
|
|
335
|
+
Every Archipel package follows the same integration pattern:
|
|
336
|
+
|
|
337
|
+
1. **Install** the package with `yarn add @breadstone/archipel-<name>`
|
|
338
|
+
2. **Check** its documentation page under [Packages](/packages/) for required ports and configuration
|
|
339
|
+
3. **Implement** adapter classes for required ports (if any)
|
|
340
|
+
4. **Register** the module with `SomeModule.register({ ... })`
|
|
341
|
+
5. **Inject** the package's services where you need them
|
|
342
|
+
|
|
343
|
+
Some packages have no ports at all — `platform-logging`, `platform-telemetry`, `platform-openapi`, and several others just need a module registration and you're done.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## Next Steps
|
|
348
|
+
|
|
349
|
+
- Read the [Implementing Ports](/guides/implementing-ports) guide for a thorough explanation of the port/adapter pattern across all packages
|
|
350
|
+
- Browse the [Packages](/packages/) section for detailed documentation on each module
|
|
351
|
+
- Check the [Architecture](/architecture/) overview for the dependency graph and design principles
|