Initial project setup: Add contracts, API definitions, tests, and documentation
- Add Foundry project configuration (foundry.toml, foundry.lock) - Add Solidity contracts (TokenFactory138, BridgeVault138, ComplianceRegistry, etc.) - Add API definitions (OpenAPI, GraphQL, gRPC, AsyncAPI) - Add comprehensive test suite (unit, integration, fuzz, invariants) - Add API services (REST, GraphQL, orchestrator, packet service) - Add documentation (ISO20022 mapping, runbooks, adapter guides) - Add development tools (RBC tool, Swagger UI, mock server) - Update OpenZeppelin submodules to v5.0.0
This commit is contained in:
115
api/tools/README.md
Normal file
115
api/tools/README.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# API Development Tools
|
||||
|
||||
This directory contains development tools for the eMoney Token Factory API.
|
||||
|
||||
## Tools
|
||||
|
||||
### OpenAPI Generator
|
||||
|
||||
Generates SDKs and Postman collections from OpenAPI specifications.
|
||||
|
||||
```bash
|
||||
cd openapi-generator
|
||||
pnpm install
|
||||
pnpm run generate:typescript
|
||||
pnpm run generate:python
|
||||
pnpm run generate:go
|
||||
pnpm run generate:java
|
||||
pnpm run generate:postman
|
||||
```
|
||||
|
||||
Or use the shell script:
|
||||
|
||||
```bash
|
||||
./generate-sdks.sh
|
||||
```
|
||||
|
||||
### Mock Servers
|
||||
|
||||
Mock servers for testing without full infrastructure.
|
||||
|
||||
#### REST API Mock (Prism)
|
||||
|
||||
```bash
|
||||
cd mock-server
|
||||
pnpm install
|
||||
pnpm run start:rest
|
||||
```
|
||||
|
||||
Mocks all REST endpoints based on OpenAPI spec.
|
||||
|
||||
#### GraphQL Mock
|
||||
|
||||
```bash
|
||||
npm run start:graphql
|
||||
```
|
||||
|
||||
Mocks GraphQL queries, mutations, and subscriptions.
|
||||
|
||||
#### Rail Simulator
|
||||
|
||||
```bash
|
||||
npm run start:rail
|
||||
```
|
||||
|
||||
Simulates Fedwire/SWIFT/SEPA/RTGS responses.
|
||||
|
||||
#### Packet Simulator
|
||||
|
||||
```bash
|
||||
npm run start:packet
|
||||
```
|
||||
|
||||
Simulates AS4 receipts and email acknowledgements.
|
||||
|
||||
#### Start All
|
||||
|
||||
```bash
|
||||
npm run start:all
|
||||
```
|
||||
|
||||
Starts all mock servers concurrently.
|
||||
|
||||
### SDK Templates
|
||||
|
||||
Templates and examples for SDK implementations:
|
||||
|
||||
- `typescript-sdk-template/` - TypeScript SDK structure
|
||||
- Generated SDKs from OpenAPI (after running generator)
|
||||
|
||||
## Usage
|
||||
|
||||
### Generating SDKs
|
||||
|
||||
1. Ensure OpenAPI spec is up to date
|
||||
2. Run generator: `cd openapi-generator && pnpm run generate:typescript`
|
||||
3. SDKs will be generated in `sdk-templates/` directory
|
||||
4. Copy to separate repositories for publishing
|
||||
|
||||
### Using Mock Servers
|
||||
|
||||
1. Start mock servers: `cd mock-server && pnpm run start:all`
|
||||
2. Point tests to mock endpoints
|
||||
3. Use for local development and CI/CD
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### Generate SDKs in CI
|
||||
|
||||
```yaml
|
||||
- name: Generate SDKs
|
||||
run: |
|
||||
cd api/tools/openapi-generator
|
||||
pnpm install
|
||||
pnpm run generate:typescript
|
||||
pnpm run generate:python
|
||||
```
|
||||
|
||||
### Run Contract Tests
|
||||
|
||||
```yaml
|
||||
- name: Validate OpenAPI Contract
|
||||
run: |
|
||||
pnpm test -- test/api/contract
|
||||
```
|
||||
|
||||
30
api/tools/mock-server/package.json
Normal file
30
api/tools/mock-server/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@emoney/mock-server",
|
||||
"version": "1.0.0",
|
||||
"description": "Mock servers for eMoney API testing",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start:rest": "node dist/rest-mock.js",
|
||||
"start:graphql": "node dist/graphql-mock.js",
|
||||
"start:all": "concurrently \"pnpm run start:rest\" \"pnpm run start:graphql\"",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stoplight/prism-http": "^5.1.0",
|
||||
"@stoplight/prism-cli": "^5.1.0",
|
||||
"@graphql-tools/mock": "^9.0.0",
|
||||
"@graphql-tools/schema": "^10.0.0",
|
||||
"express": "^4.18.2",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-yoga": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.10.0",
|
||||
"typescript": "^5.3.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"jest": "^29.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
74
api/tools/mock-server/src/graphql-mock.ts
Normal file
74
api/tools/mock-server/src/graphql-mock.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* GraphQL Mock Server
|
||||
* Mocks GraphQL schema for testing
|
||||
*/
|
||||
|
||||
import { createYoga } from 'graphql-yoga';
|
||||
import { createServer } from 'http';
|
||||
import { addMocksToSchema } from '@graphql-tools/mock';
|
||||
import { makeExecutableSchema } from '@graphql-tools/schema';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
const SCHEMA_PATH = join(__dirname, '../../packages/graphql/schema.graphql');
|
||||
|
||||
function startGraphQLMockServer() {
|
||||
const typeDefs = readFileSync(SCHEMA_PATH, 'utf-8');
|
||||
|
||||
const schema = makeExecutableSchema({
|
||||
typeDefs,
|
||||
});
|
||||
|
||||
// Add mocks
|
||||
const mockedSchema = addMocksToSchema({
|
||||
schema,
|
||||
mocks: {
|
||||
Token: () => ({
|
||||
code: 'USDW',
|
||||
address: '0x1234567890123456789012345678901234567890',
|
||||
name: 'USD Wrapped',
|
||||
symbol: 'USDW',
|
||||
decimals: 18,
|
||||
issuer: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd',
|
||||
policy: {
|
||||
paused: false,
|
||||
bridgeOnly: false,
|
||||
lienMode: 'ENCUMBERED',
|
||||
forceTransferMode: false,
|
||||
routes: ['FEDWIRE', 'SWIFT'],
|
||||
},
|
||||
}),
|
||||
Lien: () => ({
|
||||
lienId: '123',
|
||||
debtor: '0xabcd...',
|
||||
amount: '1000000000000000000',
|
||||
active: true,
|
||||
priority: 1,
|
||||
reasonCode: 'DEBT_ENFORCEMENT',
|
||||
}),
|
||||
Trigger: () => ({
|
||||
triggerId: 'abc123',
|
||||
rail: 'FEDWIRE',
|
||||
msgType: 'pacs.008',
|
||||
state: 'PENDING',
|
||||
instructionId: '0x1234...',
|
||||
amount: '1000000000000000000',
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const yoga = createYoga({
|
||||
schema: mockedSchema,
|
||||
graphqlEndpoint: '/graphql',
|
||||
});
|
||||
|
||||
const server = createServer(yoga);
|
||||
const PORT = process.env.MOCK_GRAPHQL_PORT || 4020;
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`GraphQL Mock Server running on http://localhost:${PORT}/graphql`);
|
||||
});
|
||||
}
|
||||
|
||||
startGraphQLMockServer();
|
||||
|
||||
26
api/tools/mock-server/src/index.ts
Normal file
26
api/tools/mock-server/src/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Start all mock servers
|
||||
*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import { join } from 'path';
|
||||
|
||||
const servers = [
|
||||
{ name: 'REST Mock', script: 'rest-mock.js' },
|
||||
{ name: 'GraphQL Mock', script: 'graphql-mock.js' },
|
||||
{ name: 'Rail Simulator', script: 'rail-simulator.js' },
|
||||
{ name: 'Packet Simulator', script: 'packet-simulator.js' },
|
||||
];
|
||||
|
||||
console.log('Starting all mock servers...');
|
||||
|
||||
servers.forEach(({ name, script }) => {
|
||||
const proc = spawn('node', [join(__dirname, script)], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
proc.on('error', (error) => {
|
||||
console.error(`Failed to start ${name}:`, error);
|
||||
});
|
||||
});
|
||||
|
||||
57
api/tools/mock-server/src/packet-simulator.ts
Normal file
57
api/tools/mock-server/src/packet-simulator.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Packet Simulator
|
||||
* Simulates AS4 receipts and email acknowledgements for testing
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
const PORT = process.env.PACKET_SIMULATOR_PORT || 4040;
|
||||
|
||||
// Simulate AS4 receipt
|
||||
app.post('/simulate/as4/receipt', (req, res) => {
|
||||
const { packetId, messageRef } = req.body;
|
||||
|
||||
setTimeout(() => {
|
||||
res.json({
|
||||
packetId,
|
||||
messageRef,
|
||||
ackId: `AS4-ACK-${Date.now()}`,
|
||||
status: 'RECEIVED',
|
||||
receivedAt: new Date().toISOString(),
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// Simulate email acknowledgement
|
||||
app.post('/simulate/email/ack', (req, res) => {
|
||||
const { packetId, recipient } = req.body;
|
||||
|
||||
setTimeout(() => {
|
||||
res.json({
|
||||
packetId,
|
||||
recipient,
|
||||
ackId: `EMAIL-ACK-${Date.now()}`,
|
||||
status: 'ACCEPTED',
|
||||
receivedAt: new Date().toISOString(),
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Simulate packet delivery failure
|
||||
app.post('/simulate/failure', (req, res) => {
|
||||
const { packetId, reason } = req.body;
|
||||
|
||||
res.status(400).json({
|
||||
packetId,
|
||||
error: reason || 'Delivery failed',
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Packet Simulator running on http://localhost:${PORT}`);
|
||||
});
|
||||
|
||||
73
api/tools/mock-server/src/rail-simulator.ts
Normal file
73
api/tools/mock-server/src/rail-simulator.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Rail Simulator
|
||||
* Simulates Fedwire/SWIFT/SEPA/RTGS responses for testing
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
const PORT = process.env.RAIL_SIMULATOR_PORT || 4030;
|
||||
|
||||
// Simulate Fedwire response
|
||||
app.post('/simulate/fedwire', (req, res) => {
|
||||
const { instructionId, amount } = req.body;
|
||||
|
||||
// Simulate processing delay
|
||||
setTimeout(() => {
|
||||
res.json({
|
||||
railTxRef: `FED-${Date.now()}`,
|
||||
status: 'ACCEPTED',
|
||||
settlementDate: new Date().toISOString(),
|
||||
instructionId,
|
||||
amount,
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Simulate SWIFT response
|
||||
app.post('/simulate/swift', (req, res) => {
|
||||
const { instructionId, amount } = req.body;
|
||||
|
||||
setTimeout(() => {
|
||||
res.json({
|
||||
railTxRef: `SWIFT-${Date.now()}`,
|
||||
status: 'ACCEPTED',
|
||||
settlementDate: new Date().toISOString(),
|
||||
instructionId,
|
||||
amount,
|
||||
});
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
// Simulate SEPA response
|
||||
app.post('/simulate/sepa', (req, res) => {
|
||||
const { instructionId, amount } = req.body;
|
||||
|
||||
setTimeout(() => {
|
||||
res.json({
|
||||
railTxRef: `SEPA-${Date.now()}`,
|
||||
status: 'ACCEPTED',
|
||||
settlementDate: new Date().toISOString(),
|
||||
instructionId,
|
||||
amount,
|
||||
});
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
// Simulate status update (pacs.002)
|
||||
app.post('/simulate/status', (req, res) => {
|
||||
const { railTxRef, status } = req.body;
|
||||
|
||||
res.json({
|
||||
railTxRef,
|
||||
status: status || 'ACSC', // ACSC = AcceptedSettlementCompleted
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Rail Simulator running on http://localhost:${PORT}`);
|
||||
});
|
||||
|
||||
37
api/tools/mock-server/src/rest-mock.ts
Normal file
37
api/tools/mock-server/src/rest-mock.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* REST API Mock Server using Prism
|
||||
* Mocks OpenAPI specification for testing
|
||||
*/
|
||||
|
||||
import { createServer } from '@stoplight/prism-http';
|
||||
import { createHttpServer } from '@stoplight/prism-http-server';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
const OPENAPI_SPEC = join(__dirname, '../../packages/openapi/v1/openapi.yaml');
|
||||
|
||||
async function startMockServer() {
|
||||
const spec = readFileSync(OPENAPI_SPEC, 'utf-8');
|
||||
|
||||
const server = createHttpServer({
|
||||
document: spec,
|
||||
config: {
|
||||
mock: {
|
||||
dynamic: true,
|
||||
exampleKey: 'default',
|
||||
},
|
||||
cors: true,
|
||||
errors: false,
|
||||
},
|
||||
});
|
||||
|
||||
const PORT = process.env.MOCK_PORT || 4010;
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`REST API Mock Server running on http://localhost:${PORT}`);
|
||||
console.log(`OpenAPI spec: ${OPENAPI_SPEC}`);
|
||||
});
|
||||
}
|
||||
|
||||
startMockServer().catch(console.error);
|
||||
|
||||
17
api/tools/mock-server/tsconfig.json
Normal file
17
api/tools/mock-server/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
45
api/tools/openapi-generator/generate-sdks.sh
Executable file
45
api/tools/openapi-generator/generate-sdks.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
# Generate SDKs from OpenAPI specification
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
API_DIR="$SCRIPT_DIR/../.."
|
||||
OPENAPI_SPEC="$API_DIR/packages/openapi/v1/openapi.yaml"
|
||||
|
||||
echo "Generating SDKs from OpenAPI specification..."
|
||||
|
||||
# TypeScript SDK
|
||||
echo "Generating TypeScript SDK..."
|
||||
pnpm exec @openapitools/openapi-generator-cli generate \
|
||||
-i "$OPENAPI_SPEC" \
|
||||
-g typescript-axios \
|
||||
-o "$API_DIR/sdk-templates/typescript" \
|
||||
--additional-properties=npmName=@emoney/sdk-js,npmVersion=1.0.0,withInterfaces=true
|
||||
|
||||
# Python SDK
|
||||
echo "Generating Python SDK..."
|
||||
pnpm exec @openapitools/openapi-generator-cli generate \
|
||||
-i "$OPENAPI_SPEC" \
|
||||
-g python \
|
||||
-o "$API_DIR/sdk-templates/python" \
|
||||
--additional-properties=packageName=emoney_sdk,packageVersion=1.0.0
|
||||
|
||||
# Go SDK
|
||||
echo "Generating Go SDK..."
|
||||
pnpm exec @openapitools/openapi-generator-cli generate \
|
||||
-i "$OPENAPI_SPEC" \
|
||||
-g go \
|
||||
-o "$API_DIR/sdk-templates/go" \
|
||||
--additional-properties=packageName=emoney,packageVersion=1.0.0
|
||||
|
||||
# Java SDK
|
||||
echo "Generating Java SDK..."
|
||||
pnpm exec @openapitools/openapi-generator-cli generate \
|
||||
-i "$OPENAPI_SPEC" \
|
||||
-g java \
|
||||
-o "$API_DIR/sdk-templates/java" \
|
||||
--additional-properties=groupId=com.emoney,artifactId=emoney-sdk,packageVersion=1.0.0
|
||||
|
||||
echo "SDK generation complete!"
|
||||
|
||||
20
api/tools/openapi-generator/package.json
Normal file
20
api/tools/openapi-generator/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@emoney/openapi-generator",
|
||||
"version": "1.0.0",
|
||||
"description": "OpenAPI to SDK generation tooling",
|
||||
"scripts": {
|
||||
"generate:typescript": "pnpm exec openapi-generator-cli generate -i ../../packages/openapi/v1/openapi.yaml -g typescript-axios -o ../../sdk-templates/typescript",
|
||||
"generate:python": "pnpm exec openapi-generator-cli generate -i ../../packages/openapi/v1/openapi.yaml -g python -o ../../sdk-templates/python",
|
||||
"generate:go": "pnpm exec openapi-generator-cli generate -i ../../packages/openapi/v1/openapi.yaml -g go -o ../../sdk-templates/go",
|
||||
"generate:java": "pnpm exec openapi-generator-cli generate -i ../../packages/openapi/v1/openapi.yaml -g java -o ../../sdk-templates/java",
|
||||
"generate:postman": "pnpm exec openapi2postmanv2 -s ../../packages/openapi/v1/openapi.yaml -o ../../packages/postman/eMoney-API.postman_collection.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openapitools/openapi-generator-cli": "^2.7.0",
|
||||
"openapi-to-postmanv2": "^4.24.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
112
api/tools/sdk-templates/typescript-sdk-template/README.md
Normal file
112
api/tools/sdk-templates/typescript-sdk-template/README.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# eMoney Token Factory TypeScript SDK
|
||||
|
||||
TypeScript/JavaScript SDK for the eMoney Token Factory API.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pnpm add @emoney/sdk-js
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Setup
|
||||
|
||||
```typescript
|
||||
import { createSDK } from '@emoney/sdk-js';
|
||||
|
||||
const sdk = createSDK({
|
||||
baseUrl: 'https://api.emoney.example.com/v1',
|
||||
graphqlUrl: 'https://api.emoney.example.com/graphql',
|
||||
wsUrl: 'wss://api.emoney.example.com/graphql',
|
||||
accessToken: 'your-oauth-token',
|
||||
});
|
||||
```
|
||||
|
||||
### REST API Examples
|
||||
|
||||
```typescript
|
||||
// Deploy a token
|
||||
const token = await sdk.tokens.deploy({
|
||||
name: 'USD Wrapped',
|
||||
symbol: 'USDW',
|
||||
decimals: 18,
|
||||
issuer: '0x1234...',
|
||||
});
|
||||
|
||||
// Place a lien
|
||||
const lien = await sdk.liens.place({
|
||||
debtor: '0xabcd...',
|
||||
amount: '1000000000000000000',
|
||||
priority: 1,
|
||||
});
|
||||
|
||||
// Submit ISO-20022 message
|
||||
const trigger = await sdk.triggers.submitOutbound({
|
||||
msgType: 'pacs.008',
|
||||
instructionId: '0x1234...',
|
||||
payloadHash: '0xabcd...',
|
||||
payload: '<Document>...</Document>',
|
||||
rail: 'FEDWIRE',
|
||||
token: '0x5678...',
|
||||
amount: '1000000000000000000',
|
||||
accountRefId: '0xdef0...',
|
||||
counterpartyRefId: '0x9876...',
|
||||
});
|
||||
```
|
||||
|
||||
### GraphQL Examples
|
||||
|
||||
```typescript
|
||||
// Query
|
||||
const result = await sdk.query(`
|
||||
query GetToken($code: String!) {
|
||||
token(code: $code) {
|
||||
code
|
||||
address
|
||||
policy {
|
||||
lienMode
|
||||
}
|
||||
}
|
||||
}
|
||||
`, { code: 'USDW' });
|
||||
|
||||
// Mutation
|
||||
const token = await sdk.mutate(`
|
||||
mutation DeployToken($input: DeployTokenInput!) {
|
||||
deployToken(input: $input) {
|
||||
code
|
||||
address
|
||||
}
|
||||
}
|
||||
`, {
|
||||
input: {
|
||||
name: 'USD Wrapped',
|
||||
symbol: 'USDW',
|
||||
decimals: 18,
|
||||
issuer: '0x1234...',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Idempotency
|
||||
|
||||
```typescript
|
||||
const idempotencyKey = sdk.generateIdempotencyKey();
|
||||
|
||||
// Use in requests that require idempotency
|
||||
const token = await sdk.tokens.deploy(
|
||||
{ /* config */ },
|
||||
{ headers: { 'Idempotency-Key': idempotencyKey } }
|
||||
);
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ Full REST API coverage
|
||||
- ✅ GraphQL query/mutation support
|
||||
- ✅ WebSocket subscriptions (coming soon)
|
||||
- ✅ TypeScript types
|
||||
- ✅ Idempotency helpers
|
||||
- ✅ OAuth2 authentication
|
||||
|
||||
33
api/tools/sdk-templates/typescript-sdk-template/package.json
Normal file
33
api/tools/sdk-templates/typescript-sdk-template/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@emoney/sdk-js",
|
||||
"version": "1.0.0",
|
||||
"description": "TypeScript/JavaScript SDK for eMoney Token Factory API",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest",
|
||||
"prepublishOnly": "pnpm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.2",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-request": "^6.1.0",
|
||||
"graphql-ws": "^5.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.0",
|
||||
"typescript": "^5.3.0",
|
||||
"jest": "^29.7.0",
|
||||
"@types/jest": "^29.5.11"
|
||||
},
|
||||
"keywords": [
|
||||
"emoney",
|
||||
"token-factory",
|
||||
"api",
|
||||
"sdk"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
147
api/tools/sdk-templates/typescript-sdk-template/src/index.ts
Normal file
147
api/tools/sdk-templates/typescript-sdk-template/src/index.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* eMoney Token Factory TypeScript SDK
|
||||
* Generated from OpenAPI specification with additional GraphQL support
|
||||
*/
|
||||
|
||||
import { Configuration, DefaultApi } from './generated';
|
||||
import { GraphQLClient } from 'graphql-request';
|
||||
import { Client } from 'graphql-ws';
|
||||
|
||||
export interface SDKConfig {
|
||||
baseUrl: string;
|
||||
graphqlUrl?: string;
|
||||
wsUrl?: string;
|
||||
apiKey?: string;
|
||||
accessToken?: string;
|
||||
}
|
||||
|
||||
export class EMoneySDK {
|
||||
private restClient: DefaultApi;
|
||||
private graphqlClient?: GraphQLClient;
|
||||
private wsClient?: Client;
|
||||
|
||||
constructor(config: SDKConfig) {
|
||||
// Initialize REST client
|
||||
const restConfig = new Configuration({
|
||||
basePath: config.baseUrl,
|
||||
accessToken: config.accessToken,
|
||||
apiKey: config.apiKey,
|
||||
});
|
||||
this.restClient = new DefaultApi(restConfig);
|
||||
|
||||
// Initialize GraphQL client
|
||||
if (config.graphqlUrl) {
|
||||
this.graphqlClient = new GraphQLClient(config.graphqlUrl, {
|
||||
headers: {
|
||||
Authorization: config.accessToken ? `Bearer ${config.accessToken}` : '',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize WebSocket client for subscriptions
|
||||
if (config.wsUrl) {
|
||||
this.wsClient = new Client({
|
||||
url: config.wsUrl,
|
||||
connectionParams: {
|
||||
Authorization: config.accessToken ? `Bearer ${config.accessToken}` : '',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// REST API methods (generated from OpenAPI)
|
||||
get tokens() {
|
||||
return {
|
||||
deploy: (config: any) => this.restClient.deployToken(config),
|
||||
list: (filters?: any) => this.restClient.listTokens(filters),
|
||||
get: (code: string) => this.restClient.getToken(code),
|
||||
updatePolicy: (code: string, policy: any) => this.restClient.updateTokenPolicy(code, policy),
|
||||
mint: (code: string, params: any) => this.restClient.mintTokens(code, params),
|
||||
burn: (code: string, params: any) => this.restClient.burnTokens(code, params),
|
||||
};
|
||||
}
|
||||
|
||||
get liens() {
|
||||
return {
|
||||
place: (params: any) => this.restClient.placeLien(params),
|
||||
get: (lienId: string) => this.restClient.getLien(lienId),
|
||||
list: (filters?: any) => this.restClient.listLiens(filters),
|
||||
reduce: (lienId: string, reduceBy: string) => this.restClient.reduceLien(lienId, reduceBy),
|
||||
release: (lienId: string) => this.restClient.releaseLien(lienId),
|
||||
};
|
||||
}
|
||||
|
||||
get compliance() {
|
||||
return {
|
||||
setAccount: (accountRefId: string, profile: any) =>
|
||||
this.restClient.setAccountCompliance(accountRefId, profile),
|
||||
getAccount: (accountRefId: string) => this.restClient.getAccountCompliance(accountRefId),
|
||||
setFreeze: (refId: string, frozen: boolean) => this.restClient.setFreeze(refId, frozen),
|
||||
};
|
||||
}
|
||||
|
||||
get triggers() {
|
||||
return {
|
||||
list: (filters?: any) => this.restClient.listTriggers(filters),
|
||||
get: (triggerId: string) => this.restClient.getTrigger(triggerId),
|
||||
submitInbound: (message: any) => this.restClient.submitInboundMessage(message),
|
||||
submitOutbound: (message: any) => this.restClient.submitOutboundMessage(message),
|
||||
};
|
||||
}
|
||||
|
||||
get packets() {
|
||||
return {
|
||||
generate: (params: any) => this.restClient.generatePacket(params),
|
||||
get: (packetId: string) => this.restClient.getPacket(packetId),
|
||||
dispatch: (packetId: string, params: any) => this.restClient.dispatchPacket(packetId, params),
|
||||
acknowledge: (packetId: string, params: any) =>
|
||||
this.restClient.acknowledgePacket(packetId, params),
|
||||
};
|
||||
}
|
||||
|
||||
get bridge() {
|
||||
return {
|
||||
lock: (params: any) => this.restClient.bridgeLock(params),
|
||||
unlock: (params: any) => this.restClient.bridgeUnlock(params),
|
||||
getLock: (lockId: string) => this.restClient.getBridgeLock(lockId),
|
||||
};
|
||||
}
|
||||
|
||||
// GraphQL methods
|
||||
async query<T = any>(query: string, variables?: any): Promise<T> {
|
||||
if (!this.graphqlClient) {
|
||||
throw new Error('GraphQL client not configured');
|
||||
}
|
||||
return this.graphqlClient.request<T>(query, variables);
|
||||
}
|
||||
|
||||
async mutate<T = any>(mutation: string, variables?: any): Promise<T> {
|
||||
if (!this.graphqlClient) {
|
||||
throw new Error('GraphQL client not configured');
|
||||
}
|
||||
return this.graphqlClient.request<T>(mutation, variables);
|
||||
}
|
||||
|
||||
// WebSocket subscriptions
|
||||
subscribe<T = any>(subscription: string, variables?: any): AsyncIterator<T> {
|
||||
if (!this.wsClient) {
|
||||
throw new Error('WebSocket client not configured');
|
||||
}
|
||||
// TODO: Implement subscription iterator
|
||||
throw new Error('Subscriptions not yet implemented');
|
||||
}
|
||||
|
||||
// Idempotency helper
|
||||
generateIdempotencyKey(): string {
|
||||
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Export convenience function
|
||||
export function createSDK(config: SDKConfig): EMoneySDK {
|
||||
return new EMoneySDK(config);
|
||||
}
|
||||
|
||||
// Re-export generated types
|
||||
export * from './generated';
|
||||
|
||||
7
api/tools/swagger-ui/.dockerignore
Normal file
7
api/tools/swagger-ui/.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
dist
|
||||
*.log
|
||||
.env
|
||||
.git
|
||||
.DS_Store
|
||||
|
||||
28
api/tools/swagger-ui/Dockerfile
Normal file
28
api/tools/swagger-ui/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
COPY tsconfig.json ./
|
||||
|
||||
# Install pnpm
|
||||
RUN npm install -g pnpm
|
||||
|
||||
# Install dependencies
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Copy source files
|
||||
COPY src/ ./src/
|
||||
COPY ../../packages/openapi/v1/openapi.yaml ./packages/openapi/v1/openapi.yaml
|
||||
COPY static/ ./static/
|
||||
|
||||
# Build
|
||||
RUN pnpm run build
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8080
|
||||
|
||||
# Start server
|
||||
CMD ["pnpm", "start"]
|
||||
|
||||
33
api/tools/swagger-ui/Makefile
Normal file
33
api/tools/swagger-ui/Makefile
Normal file
@@ -0,0 +1,33 @@
|
||||
.PHONY: install build start dev generate-standalone docker-build docker-run help
|
||||
|
||||
help:
|
||||
@echo "Swagger UI Server - Available commands:"
|
||||
@echo " make install - Install dependencies"
|
||||
@echo " make build - Build TypeScript"
|
||||
@echo " make start - Start server"
|
||||
@echo " make dev - Start in development mode"
|
||||
@echo " make generate-standalone - Generate standalone HTML"
|
||||
@echo " make docker-build - Build Docker image"
|
||||
@echo " make docker-run - Run Docker container"
|
||||
|
||||
install:
|
||||
pnpm install
|
||||
|
||||
build:
|
||||
pnpm run build
|
||||
|
||||
start: build
|
||||
pnpm start
|
||||
|
||||
dev:
|
||||
pnpm run dev
|
||||
|
||||
generate-standalone:
|
||||
pnpm run generate:standalone
|
||||
|
||||
docker-build:
|
||||
docker build -t emoney-swagger-ui .
|
||||
|
||||
docker-run:
|
||||
docker run -p 8080:8080 emoney-swagger-ui
|
||||
|
||||
126
api/tools/swagger-ui/QUICKSTART.md
Normal file
126
api/tools/swagger-ui/QUICKSTART.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Swagger UI Quick Start Guide
|
||||
|
||||
## Local Development
|
||||
|
||||
### Option 1: Node.js (Recommended)
|
||||
|
||||
```bash
|
||||
cd api/tools/swagger-ui
|
||||
pnpm install
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
Visit: http://localhost:8080/api-docs
|
||||
|
||||
### Option 2: Docker
|
||||
|
||||
```bash
|
||||
cd api/tools/swagger-ui
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
Visit: http://localhost:8080/api-docs
|
||||
|
||||
## Features
|
||||
|
||||
### Interactive Documentation
|
||||
- Browse all API endpoints
|
||||
- View request/response schemas
|
||||
- See example payloads
|
||||
- Explore data models
|
||||
|
||||
### Try It Out
|
||||
- Test API calls directly from the browser
|
||||
- Set authentication tokens
|
||||
- View real responses
|
||||
- Debug API interactions
|
||||
|
||||
### Authentication Testing
|
||||
- OAuth2 client credentials flow
|
||||
- mTLS configuration
|
||||
- API key testing
|
||||
- Token persistence
|
||||
|
||||
### Export Options
|
||||
- Download OpenAPI spec as JSON
|
||||
- Download OpenAPI spec as YAML
|
||||
- Share documentation links
|
||||
|
||||
## API Endpoints
|
||||
|
||||
The Swagger UI server provides:
|
||||
|
||||
- `GET /api-docs` - Interactive documentation
|
||||
- `GET /openapi.json` - OpenAPI spec (JSON)
|
||||
- `GET /openapi.yaml` - OpenAPI spec (YAML)
|
||||
- `GET /health` - Health check
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
SWAGGER_PORT=8080 # Server port (default: 8080)
|
||||
```
|
||||
|
||||
### Customization
|
||||
|
||||
Edit `src/index.ts` to customize:
|
||||
- Swagger UI theme
|
||||
- Default expansion level
|
||||
- Supported HTTP methods
|
||||
- OAuth2 redirect URL
|
||||
|
||||
## Integration with Main API
|
||||
|
||||
To integrate Swagger UI into the main REST API server:
|
||||
|
||||
```typescript
|
||||
// In api/services/rest-api/src/index.ts
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
import YAML from 'yamljs';
|
||||
import { join } from 'path';
|
||||
|
||||
const openapiSpec = YAML.load(join(__dirname, '../../packages/openapi/v1/openapi.yaml'));
|
||||
|
||||
app.use('/docs', swaggerUi.serve, swaggerUi.setup(openapiSpec));
|
||||
```
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Standalone Server
|
||||
|
||||
Deploy as separate service:
|
||||
- Lightweight Express server
|
||||
- Serves only documentation
|
||||
- Can be behind CDN
|
||||
- No API dependencies
|
||||
|
||||
### Embedded in API
|
||||
|
||||
Include in main API server:
|
||||
- Single deployment
|
||||
- Shared authentication
|
||||
- Live spec updates
|
||||
- Integrated experience
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### OpenAPI Spec Not Loading
|
||||
|
||||
1. Check file path: `../../packages/openapi/v1/openapi.yaml`
|
||||
2. Verify YAML syntax is valid
|
||||
3. Check file permissions
|
||||
|
||||
### OAuth2 Not Working
|
||||
|
||||
1. Verify redirect URL matches configuration
|
||||
2. Check CORS settings
|
||||
3. Ensure OAuth2 server is accessible
|
||||
|
||||
### Styles Not Loading
|
||||
|
||||
1. Check network tab for 404s
|
||||
2. Verify CDN is accessible
|
||||
3. Check custom CSS syntax
|
||||
|
||||
55
api/tools/swagger-ui/README.md
Normal file
55
api/tools/swagger-ui/README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Swagger UI Server
|
||||
|
||||
Interactive API documentation server for the eMoney Token Factory API.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Start server
|
||||
pnpm start
|
||||
|
||||
# Or in development mode
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
The Swagger UI will be available at:
|
||||
- **Documentation**: http://localhost:8080/api-docs
|
||||
- **OpenAPI JSON**: http://localhost:8080/openapi.json
|
||||
- **OpenAPI YAML**: http://localhost:8080/openapi.yaml
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ Interactive API documentation
|
||||
- ✅ Try-it-out functionality
|
||||
- ✅ Request/response examples
|
||||
- ✅ Authentication testing (OAuth2, mTLS, API Key)
|
||||
- ✅ Schema exploration
|
||||
- ✅ Export OpenAPI spec (JSON/YAML)
|
||||
|
||||
## Configuration
|
||||
|
||||
Set environment variables:
|
||||
|
||||
```bash
|
||||
export SWAGGER_PORT=8080 # Default: 8080
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Navigate to http://localhost:8080/api-docs
|
||||
2. Click "Authorize" to set up authentication
|
||||
3. Explore endpoints by expanding sections
|
||||
4. Use "Try it out" to test API calls
|
||||
5. View request/response schemas
|
||||
|
||||
## Integration
|
||||
|
||||
This server can be:
|
||||
- Deployed standalone for documentation
|
||||
- Integrated into main API server
|
||||
- Used in CI/CD for API validation
|
||||
- Embedded in developer portals
|
||||
|
||||
278
api/tools/swagger-ui/SWAGGER_DOCS.md
Normal file
278
api/tools/swagger-ui/SWAGGER_DOCS.md
Normal file
@@ -0,0 +1,278 @@
|
||||
# Swagger Documentation - Complete Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Full Swagger/OpenAPI documentation for the eMoney Token Factory API is available through an interactive Swagger UI server.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Option 1: Node.js Server (Recommended)
|
||||
|
||||
```bash
|
||||
cd api/tools/swagger-ui
|
||||
pnpm install
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
**Access**: http://localhost:8080/api-docs
|
||||
|
||||
### Option 2: Docker
|
||||
|
||||
```bash
|
||||
cd api/tools/swagger-ui
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
**Access**: http://localhost:8080/api-docs
|
||||
|
||||
### Option 3: Standalone HTML
|
||||
|
||||
```bash
|
||||
cd api/tools/swagger-ui
|
||||
pnpm install
|
||||
pnpm run build
|
||||
pnpm run generate:standalone
|
||||
```
|
||||
|
||||
**Output**: `static/standalone.html` (open directly in browser)
|
||||
|
||||
## Documentation Endpoints
|
||||
|
||||
When the server is running:
|
||||
|
||||
- **Interactive Docs**: http://localhost:8080/api-docs
|
||||
- **OpenAPI JSON**: http://localhost:8080/openapi.json
|
||||
- **OpenAPI YAML**: http://localhost:8080/openapi.yaml
|
||||
- **Standalone HTML**: http://localhost:8080/standalone
|
||||
- **Health Check**: http://localhost:8080/health
|
||||
|
||||
## Features
|
||||
|
||||
### 1. Complete API Coverage
|
||||
|
||||
All API endpoints documented:
|
||||
- ✅ Token operations (deploy, mint, burn, clawback, force-transfer)
|
||||
- ✅ Lien management (place, reduce, release, query)
|
||||
- ✅ Compliance operations (set, freeze, query)
|
||||
- ✅ Account-Wallet mappings
|
||||
- ✅ Trigger management (ISO-20022 processing)
|
||||
- ✅ Packet operations (generate, dispatch, acknowledge)
|
||||
- ✅ Bridge operations (lock, unlock)
|
||||
|
||||
### 2. Interactive Testing
|
||||
|
||||
- **Try It Out**: Test any endpoint directly from the browser
|
||||
- **Request Builder**: Fill in parameters and see request format
|
||||
- **Response Viewer**: See actual API responses
|
||||
- **Error Handling**: View error responses with reason codes
|
||||
|
||||
### 3. Authentication Support
|
||||
|
||||
- **OAuth2**: Test client credentials flow
|
||||
- **mTLS**: Configure mutual TLS for adapters
|
||||
- **API Key**: Set API keys for internal services
|
||||
- **Token Persistence**: Tokens saved across page reloads
|
||||
|
||||
### 4. Schema Documentation
|
||||
|
||||
- **Data Models**: All request/response schemas
|
||||
- **Enums**: All enum values (ReasonCodes, TriggerStates, Rails, etc.)
|
||||
- **Examples**: Example payloads for each endpoint
|
||||
- **Validation Rules**: Field requirements and constraints
|
||||
|
||||
### 5. Export Options
|
||||
|
||||
- **Download JSON**: Get OpenAPI spec as JSON
|
||||
- **Download YAML**: Get OpenAPI spec as YAML
|
||||
- **Share Links**: Share specific endpoint documentation
|
||||
- **Embed**: Embed Swagger UI in other applications
|
||||
|
||||
## API Modules Documented
|
||||
|
||||
### Tokens Module
|
||||
- Deploy new eMoney tokens
|
||||
- Configure token policies
|
||||
- Mint and burn operations
|
||||
- Clawback and force transfer
|
||||
- Query token metadata
|
||||
|
||||
### Liens Module
|
||||
- Place liens on accounts
|
||||
- Reduce lien amounts
|
||||
- Release liens
|
||||
- Query lien information
|
||||
- Check encumbrance summaries
|
||||
|
||||
### Compliance Module
|
||||
- Set compliance profiles
|
||||
- Freeze/unfreeze accounts
|
||||
- Manage risk tiers
|
||||
- Set jurisdiction information
|
||||
- Query compliance status
|
||||
|
||||
### Mappings Module
|
||||
- Link accounts to wallets
|
||||
- Unlink mappings
|
||||
- Query account wallets
|
||||
- Query wallet accounts
|
||||
|
||||
### Triggers Module
|
||||
- Submit ISO-20022 messages
|
||||
- Query trigger status
|
||||
- Manage trigger lifecycle
|
||||
- View trigger history
|
||||
|
||||
### ISO-20022 Module
|
||||
- Submit inbound messages (from rails)
|
||||
- Submit outbound messages (to rails)
|
||||
- Message normalization
|
||||
- Instruction ID tracking
|
||||
|
||||
### Packets Module
|
||||
- Generate packets (PDF/AS4/Email)
|
||||
- Dispatch packets
|
||||
- Track acknowledgements
|
||||
- Download packet files
|
||||
|
||||
### Bridge Module
|
||||
- Lock tokens for cross-chain
|
||||
- Unlock tokens with proofs
|
||||
- Query lock status
|
||||
- View supported corridors
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Testing Token Deployment
|
||||
|
||||
1. Navigate to `/tokens` endpoint
|
||||
2. Click "Try it out"
|
||||
3. Fill in token configuration:
|
||||
```json
|
||||
{
|
||||
"name": "USD Wrapped",
|
||||
"symbol": "USDW",
|
||||
"decimals": 18,
|
||||
"issuer": "0x1234...",
|
||||
"defaultLienMode": "ENCUMBERED"
|
||||
}
|
||||
```
|
||||
4. Click "Execute"
|
||||
5. View response with token address
|
||||
|
||||
### Testing Lien Placement
|
||||
|
||||
1. Navigate to `/liens` endpoint
|
||||
2. Click "Try it out"
|
||||
3. Fill in lien details:
|
||||
```json
|
||||
{
|
||||
"debtor": "0xabcd...",
|
||||
"amount": "1000000000000000000",
|
||||
"priority": 1,
|
||||
"reasonCode": "DEBT_ENFORCEMENT"
|
||||
}
|
||||
```
|
||||
4. Click "Execute"
|
||||
5. View response with lien ID
|
||||
|
||||
### Setting Authentication
|
||||
|
||||
1. Click "Authorize" button at top
|
||||
2. Enter OAuth2 token in "Value" field
|
||||
3. Click "Authorize"
|
||||
4. Token will be used for all requests
|
||||
5. Click "Logout" to clear
|
||||
|
||||
## Integration
|
||||
|
||||
### Embed in Main API Server
|
||||
|
||||
Add to `api/services/rest-api/src/index.ts`:
|
||||
|
||||
```typescript
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
import YAML from 'yamljs';
|
||||
import { join } from 'path';
|
||||
|
||||
const openapiSpec = YAML.load(join(__dirname, '../../packages/openapi/v1/openapi.yaml'));
|
||||
|
||||
app.use('/docs', swaggerUi.serve, swaggerUi.setup(openapiSpec));
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
|
||||
1. **Standalone Service**: Deploy as separate service
|
||||
2. **CDN Distribution**: Serve via CDN for performance
|
||||
3. **Embedded**: Include in main API server
|
||||
4. **Static HTML**: Generate and serve static file
|
||||
|
||||
## Customization
|
||||
|
||||
### Change Port
|
||||
|
||||
```bash
|
||||
export SWAGGER_PORT=9000
|
||||
pnpm start
|
||||
```
|
||||
|
||||
### Custom Theme
|
||||
|
||||
Edit `src/index.ts`:
|
||||
|
||||
```typescript
|
||||
const swaggerOptions = {
|
||||
customCss: `
|
||||
.swagger-ui .info .title {
|
||||
color: #your-brand-color;
|
||||
font-family: 'Your Font';
|
||||
}
|
||||
`,
|
||||
};
|
||||
```
|
||||
|
||||
### Default Server
|
||||
|
||||
Set default API server:
|
||||
|
||||
```typescript
|
||||
swaggerOptions: {
|
||||
url: 'https://api.emoney.example.com/v1',
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Server Won't Start
|
||||
|
||||
- Check if port 8080 is available
|
||||
- Verify OpenAPI spec path is correct
|
||||
- Check YAML syntax is valid
|
||||
|
||||
### Spec Not Loading
|
||||
|
||||
- Verify `openapi.yaml` exists
|
||||
- Check file permissions
|
||||
- Validate YAML syntax
|
||||
|
||||
### Try It Out Fails
|
||||
|
||||
- Check CORS settings on API server
|
||||
- Verify authentication is set
|
||||
- Check network tab for errors
|
||||
- Ensure API server is running
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Keep Spec Updated**: Update OpenAPI spec as API changes
|
||||
2. **Use Examples**: Provide realistic examples in spec
|
||||
3. **Document Errors**: Include error response examples
|
||||
4. **Test Regularly**: Use Try It Out to validate endpoints
|
||||
5. **Share Links**: Share specific endpoint URLs with team
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [OpenAPI Specification](https://swagger.io/specification/)
|
||||
- [Swagger UI Documentation](https://swagger.io/tools/swagger-ui/)
|
||||
- [API Integration Cookbook](../docs/api/integration-cookbook.md)
|
||||
- [Error Catalog](../docs/api/error-catalog.md)
|
||||
|
||||
13
api/tools/swagger-ui/docker-compose.yml
Normal file
13
api/tools/swagger-ui/docker-compose.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
swagger-ui:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- SWAGGER_PORT=8080
|
||||
volumes:
|
||||
- ../packages/openapi/v1/openapi.yaml:/app/packages/openapi/v1/openapi.yaml:ro
|
||||
restart: unless-stopped
|
||||
|
||||
28
api/tools/swagger-ui/package.json
Normal file
28
api/tools/swagger-ui/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@emoney/swagger-ui",
|
||||
"version": "1.0.0",
|
||||
"description": "Swagger UI server for eMoney API documentation",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
|
||||
"generate:standalone": "ts-node src/generate-standalone.ts",
|
||||
"docker:build": "docker build -t emoney-swagger-ui .",
|
||||
"docker:run": "docker run -p 8080:8080 emoney-swagger-ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"yamljs": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/swagger-ui-express": "^4.1.4",
|
||||
"@types/node": "^20.10.0",
|
||||
"typescript": "^5.3.0",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
87
api/tools/swagger-ui/src/generate-standalone.ts
Normal file
87
api/tools/swagger-ui/src/generate-standalone.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Generate standalone HTML documentation
|
||||
* Creates a self-contained HTML file with embedded OpenAPI spec
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import YAML from 'yamljs';
|
||||
|
||||
const openapiPath = join(__dirname, '../../packages/openapi/v1/openapi.yaml');
|
||||
const openapiSpec = YAML.load(openapiPath);
|
||||
const outputPath = join(__dirname, '../swagger-ui/static/standalone.html');
|
||||
|
||||
const htmlTemplate = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>eMoney Token Factory API Documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui.css" />
|
||||
<style>
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #fafafa;
|
||||
}
|
||||
.swagger-ui .topbar {
|
||||
display: none;
|
||||
}
|
||||
.swagger-ui .info {
|
||||
margin: 50px 0;
|
||||
}
|
||||
.swagger-ui .info .title {
|
||||
font-size: 36px;
|
||||
color: #3b4151;
|
||||
}
|
||||
.swagger-ui .info .description {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui-bundle.js"></script>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui-standalone-preset.js"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
const spec = ${JSON.stringify(openapiSpec, null, 2)};
|
||||
|
||||
const ui = SwaggerUIBundle({
|
||||
spec: spec,
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout",
|
||||
persistAuthorization: true,
|
||||
displayRequestDuration: true,
|
||||
filter: true,
|
||||
tryItOutEnabled: true,
|
||||
supportedSubmitMethods: ['get', 'post', 'put', 'patch', 'delete'],
|
||||
docExpansion: 'list',
|
||||
defaultModelsExpandDepth: 2,
|
||||
defaultModelExpandDepth: 2,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
writeFileSync(outputPath, htmlTemplate);
|
||||
console.log(`Standalone HTML documentation generated: ${outputPath}`);
|
||||
|
||||
85
api/tools/swagger-ui/src/index.ts
Normal file
85
api/tools/swagger-ui/src/index.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Swagger UI Server
|
||||
* Serves interactive API documentation from OpenAPI specification
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
import YAML from 'yamljs';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import path from 'path';
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.SWAGGER_PORT || 8080;
|
||||
|
||||
// Load OpenAPI specification
|
||||
const openapiPath = join(__dirname, '../../packages/openapi/v1/openapi.yaml');
|
||||
const openapiSpec = YAML.load(openapiPath);
|
||||
|
||||
// Serve static files (OAuth2 redirect, standalone HTML)
|
||||
app.use('/static', express.static(join(__dirname, '../../swagger-ui/static')));
|
||||
|
||||
// Swagger UI options
|
||||
const swaggerOptions = {
|
||||
customCss: `
|
||||
.swagger-ui .topbar { display: none; }
|
||||
.swagger-ui .info { margin: 50px 0; }
|
||||
.swagger-ui .info .title { font-size: 36px; }
|
||||
.swagger-ui .info .description { font-size: 16px; line-height: 1.6; }
|
||||
.swagger-ui .scheme-container { margin: 20px 0; }
|
||||
`,
|
||||
customSiteTitle: 'eMoney Token Factory API Documentation',
|
||||
customfavIcon: '/favicon.ico',
|
||||
swaggerOptions: {
|
||||
persistAuthorization: true,
|
||||
displayRequestDuration: true,
|
||||
filter: true,
|
||||
tryItOutEnabled: true,
|
||||
supportedSubmitMethods: ['get', 'post', 'put', 'patch', 'delete'],
|
||||
docExpansion: 'list',
|
||||
defaultModelsExpandDepth: 2,
|
||||
defaultModelExpandDepth: 2,
|
||||
oauth2RedirectUrl: '/static/oauth2-redirect.html',
|
||||
},
|
||||
};
|
||||
|
||||
// Serve Swagger UI
|
||||
app.use('/api-docs', swaggerUi.serve);
|
||||
app.get('/api-docs', swaggerUi.setup(openapiSpec, swaggerOptions));
|
||||
|
||||
// Serve OpenAPI spec as JSON
|
||||
app.get('/openapi.json', (req, res) => {
|
||||
res.json(openapiSpec);
|
||||
});
|
||||
|
||||
// Serve OpenAPI spec as YAML
|
||||
app.get('/openapi.yaml', (req, res) => {
|
||||
res.setHeader('Content-Type', 'text/yaml');
|
||||
res.send(readFileSync(openapiPath, 'utf-8'));
|
||||
});
|
||||
|
||||
// Serve standalone HTML
|
||||
app.get('/standalone', (req, res) => {
|
||||
res.sendFile(join(__dirname, '../../swagger-ui/static/standalone.html'));
|
||||
});
|
||||
|
||||
// Redirect root to docs
|
||||
app.get('/', (req, res) => {
|
||||
res.redirect('/api-docs');
|
||||
});
|
||||
|
||||
// Health check
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok', service: 'swagger-ui' });
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Swagger UI server running on http://localhost:${PORT}`);
|
||||
console.log(`API Documentation: http://localhost:${PORT}/api-docs`);
|
||||
console.log(`OpenAPI JSON: http://localhost:${PORT}/openapi.json`);
|
||||
console.log(`OpenAPI YAML: http://localhost:${PORT}/openapi.yaml`);
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
66
api/tools/swagger-ui/static/index.html
Normal file
66
api/tools/swagger-ui/static/index.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>eMoney Token Factory API Documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui.css" />
|
||||
<style>
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #fafafa;
|
||||
}
|
||||
.swagger-ui .topbar {
|
||||
display: none;
|
||||
}
|
||||
.swagger-ui .info {
|
||||
margin: 50px 0;
|
||||
}
|
||||
.swagger-ui .info .title {
|
||||
font-size: 36px;
|
||||
color: #3b4151;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui-bundle.js"></script>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5.10.3/swagger-ui-standalone-preset.js"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "/openapi.yaml",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout",
|
||||
persistAuthorization: true,
|
||||
displayRequestDuration: true,
|
||||
filter: true,
|
||||
tryItOutEnabled: true,
|
||||
supportedSubmitMethods: ['get', 'post', 'put', 'patch', 'delete'],
|
||||
docExpansion: 'list',
|
||||
defaultModelsExpandDepth: 2,
|
||||
defaultModelExpandDepth: 2,
|
||||
oauth2RedirectUrl: window.location.origin + '/oauth2-redirect.html'
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
76
api/tools/swagger-ui/static/oauth2-redirect.html
Normal file
76
api/tools/swagger-ui/static/oauth2-redirect.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&");
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
||||
qp = qp ? JSON.parse('{' + arr.join() +'}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value);
|
||||
}
|
||||
) : {};
|
||||
|
||||
isValid = qp.state === sentState;
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorization_code"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
err: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg;
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
err: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
if (document.readyState !== 'loading') {
|
||||
run();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', run);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
18
api/tools/swagger-ui/tsconfig.json
Normal file
18
api/tools/swagger-ui/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user