611 lines
19 KiB
TypeScript
611 lines
19 KiB
TypeScript
import { Application } from 'express';
|
|
import swaggerJSDoc from 'swagger-jsdoc';
|
|
import swaggerUi from 'swagger-ui-express';
|
|
import { appConfig } from '../config/app.config';
|
|
|
|
/**
|
|
* EN: Swagger/OpenAPI configuration for API documentation
|
|
* VI: Cấu hình Swagger/OpenAPI cho tài liệu API
|
|
*/
|
|
|
|
const options = {
|
|
definition: {
|
|
openapi: '3.0.0',
|
|
info: {
|
|
title: 'Microservice Template API',
|
|
version: '1.0.0',
|
|
description: 'A production-ready microservice template with comprehensive features',
|
|
contact: {
|
|
name: 'Development Team',
|
|
email: 'dev@goodgo.com',
|
|
},
|
|
license: {
|
|
name: 'MIT',
|
|
url: 'https://opensource.org/licenses/MIT',
|
|
},
|
|
},
|
|
servers: [
|
|
{
|
|
url: 'http://localhost:{port}',
|
|
description: 'Development server',
|
|
variables: {
|
|
port: {
|
|
default: String(appConfig.port),
|
|
description: 'Port number for the development server',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
url: 'https://api.example.com',
|
|
description: 'Production server',
|
|
},
|
|
],
|
|
components: {
|
|
securitySchemes: {
|
|
bearerAuth: {
|
|
type: 'http',
|
|
scheme: 'bearer',
|
|
bearerFormat: 'JWT',
|
|
description: 'JWT Authorization header using the Bearer scheme',
|
|
},
|
|
},
|
|
schemas: {
|
|
ApiResponse: {
|
|
type: 'object',
|
|
properties: {
|
|
success: {
|
|
type: 'boolean',
|
|
description: 'Indicates if the request was successful',
|
|
},
|
|
data: {
|
|
description: 'Response data (varies by endpoint)',
|
|
},
|
|
message: {
|
|
type: 'string',
|
|
description: 'Human-readable message',
|
|
},
|
|
timestamp: {
|
|
type: 'string',
|
|
format: 'date-time',
|
|
description: 'ISO 8601 timestamp of the response',
|
|
},
|
|
},
|
|
required: ['success', 'timestamp'],
|
|
},
|
|
ErrorResponse: {
|
|
type: 'object',
|
|
properties: {
|
|
success: {
|
|
type: 'boolean',
|
|
example: false,
|
|
},
|
|
error: {
|
|
type: 'object',
|
|
properties: {
|
|
code: {
|
|
type: 'string',
|
|
description: 'Error code for programmatic handling',
|
|
example: 'VALIDATION_ERROR',
|
|
},
|
|
message: {
|
|
type: 'string',
|
|
description: 'Human-readable error message',
|
|
example: 'Validation failed',
|
|
},
|
|
details: {
|
|
description: 'Additional error details (optional)',
|
|
},
|
|
},
|
|
required: ['code', 'message'],
|
|
},
|
|
timestamp: {
|
|
type: 'string',
|
|
format: 'date-time',
|
|
},
|
|
},
|
|
required: ['success', 'error', 'timestamp'],
|
|
},
|
|
RegisterRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
email: { type: 'string', format: 'email', example: 'user@example.com' },
|
|
password: { type: 'string', minLength: 8, example: 'StrongP@ssw0rd!' },
|
|
username: { type: 'string', minLength: 3, example: 'johndoe' },
|
|
},
|
|
required: ['email', 'password'],
|
|
},
|
|
RegisterResponse: {
|
|
type: 'object',
|
|
properties: {
|
|
user: { $ref: '#/components/schemas/User' },
|
|
token: { type: 'string' },
|
|
},
|
|
},
|
|
LoginRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
email: { type: 'string', format: 'email', example: 'user@example.com' },
|
|
password: { type: 'string', example: 'StrongP@ssw0rd!' },
|
|
},
|
|
required: ['email', 'password'],
|
|
},
|
|
LoginResponse: {
|
|
type: 'object',
|
|
properties: {
|
|
accessToken: { type: 'string' },
|
|
refreshToken: { type: 'string' },
|
|
user: { $ref: '#/components/schemas/User' },
|
|
},
|
|
},
|
|
User: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', example: 'cuid12345' },
|
|
email: { type: 'string', format: 'email' },
|
|
username: { type: 'string' },
|
|
firstName: { type: 'string' },
|
|
lastName: { type: 'string' },
|
|
isActive: { type: 'boolean' },
|
|
isEmailVerified: { type: 'boolean' },
|
|
createdAt: { type: 'string', format: 'date-time' },
|
|
updatedAt: { type: 'string', format: 'date-time' },
|
|
},
|
|
},
|
|
CreateUserRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
email: { type: 'string', format: 'email' },
|
|
username: { type: 'string' },
|
|
firstName: { type: 'string' },
|
|
lastName: { type: 'string' },
|
|
phone: { type: 'string' },
|
|
},
|
|
required: ['email'],
|
|
},
|
|
Organization: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
name: { type: 'string' },
|
|
domain: { type: 'string' },
|
|
isActive: { type: 'boolean' },
|
|
parentId: { type: 'string' },
|
|
createdAt: { type: 'string', format: 'date-time' },
|
|
updatedAt: { type: 'string', format: 'date-time' },
|
|
},
|
|
},
|
|
CreateOrganizationRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
name: { type: 'string' },
|
|
domain: { type: 'string' },
|
|
parentId: { type: 'string' },
|
|
settings: { type: 'object' },
|
|
},
|
|
required: ['name'],
|
|
},
|
|
UpdateOrganizationRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
name: { type: 'string' },
|
|
domain: { type: 'string' },
|
|
parentId: { type: 'string' },
|
|
settings: { type: 'object' },
|
|
isActive: { type: 'boolean' },
|
|
},
|
|
},
|
|
Group: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
name: { type: 'string' },
|
|
description: { type: 'string' },
|
|
organizationId: { type: 'string' },
|
|
isActive: { type: 'boolean' },
|
|
createdAt: { type: 'string', format: 'date-time' },
|
|
updatedAt: { type: 'string', format: 'date-time' },
|
|
},
|
|
},
|
|
CreateGroupRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
name: { type: 'string' },
|
|
description: { type: 'string' },
|
|
organizationId: { type: 'string' },
|
|
},
|
|
required: ['name'],
|
|
},
|
|
UpdateGroupRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
name: { type: 'string' },
|
|
description: { type: 'string' },
|
|
isActive: { type: 'boolean' },
|
|
},
|
|
},
|
|
UserProfile: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
userId: { type: 'string' },
|
|
avatarUrl: { type: 'string' },
|
|
bio: { type: 'string' },
|
|
phoneNumber: { type: 'string' },
|
|
address: { type: 'string' },
|
|
preferences: { type: 'object' },
|
|
},
|
|
},
|
|
UpdateProfileRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
firstName: { type: 'string' },
|
|
lastName: { type: 'string' },
|
|
phone: { type: 'string' },
|
|
avatarUrl: { type: 'string' },
|
|
preferences: { type: 'object' },
|
|
},
|
|
},
|
|
UpdateUserRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
username: { type: 'string' },
|
|
email: { type: 'string', format: 'email' },
|
|
isActive: { type: 'boolean' },
|
|
organizationId: { type: 'string' },
|
|
},
|
|
},
|
|
AccessRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
resource: { type: 'string' },
|
|
action: { type: 'string' },
|
|
status: { type: 'string', enum: ['PENDING', 'APPROVED', 'REJECTED'] },
|
|
reason: { type: 'string' },
|
|
requesterId: { type: 'string' },
|
|
createdAt: { type: 'string', format: 'date-time' },
|
|
},
|
|
},
|
|
CreateAccessRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
resource: { type: 'string' },
|
|
action: { type: 'string' },
|
|
reason: { type: 'string' },
|
|
expiresAt: { type: 'string', format: 'date-time' },
|
|
},
|
|
required: ['resource', 'action'],
|
|
},
|
|
AccessReview: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
name: { type: 'string' },
|
|
status: { type: 'string' },
|
|
type: { type: 'string' },
|
|
startDate: { type: 'string', format: 'date-time' },
|
|
endDate: { type: 'string', format: 'date-time' },
|
|
},
|
|
},
|
|
CreateAccessReviewRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
name: { type: 'string' },
|
|
type: { type: 'string', enum: ['PERIODIC', 'ADHOC'] },
|
|
startDate: { type: 'string', format: 'date-time' },
|
|
endDate: { type: 'string', format: 'date-time' },
|
|
},
|
|
required: ['name', 'type'],
|
|
},
|
|
Policy: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
name: { type: 'string' },
|
|
type: { type: 'string' },
|
|
content: { type: 'object' },
|
|
isActive: { type: 'boolean' },
|
|
},
|
|
},
|
|
ComplianceReport: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
type: { type: 'string' },
|
|
status: { type: 'string' },
|
|
generatedAt: { type: 'string', format: 'date-time' },
|
|
url: { type: 'string' },
|
|
},
|
|
},
|
|
GenerateComplianceReportRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
type: { type: 'string' },
|
|
periodStart: { type: 'string', format: 'date-time' },
|
|
periodEnd: { type: 'string', format: 'date-time' },
|
|
},
|
|
required: ['type'],
|
|
},
|
|
Session: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string' },
|
|
userId: { type: 'string' },
|
|
deviceId: { type: 'string' },
|
|
deviceName: { type: 'string' },
|
|
ipAddress: { type: 'string' },
|
|
userAgent: { type: 'string' },
|
|
isActive: { type: 'boolean' },
|
|
expiresAt: { type: 'string', format: 'date-time' },
|
|
createdAt: { type: 'string', format: 'date-time' },
|
|
lastActivityAt: { type: 'string', format: 'date-time' },
|
|
},
|
|
},
|
|
Feature: {
|
|
type: 'object',
|
|
properties: {
|
|
id: {
|
|
type: 'string',
|
|
description: 'Unique identifier',
|
|
example: 'clh1x8qkq0000abcdefghijk',
|
|
},
|
|
name: {
|
|
type: 'string',
|
|
description: 'Unique feature name',
|
|
example: 'user-management',
|
|
},
|
|
title: {
|
|
type: 'string',
|
|
description: 'Human-readable title',
|
|
example: 'User Management',
|
|
},
|
|
description: {
|
|
type: 'string',
|
|
description: 'Detailed description',
|
|
example: 'Complete user management system',
|
|
},
|
|
config: {
|
|
type: 'object',
|
|
description: 'Feature-specific configuration',
|
|
example: { enabled: true, priority: 1 },
|
|
},
|
|
enabled: {
|
|
type: 'boolean',
|
|
description: 'Whether the feature is enabled',
|
|
example: true,
|
|
},
|
|
version: {
|
|
type: 'string',
|
|
description: 'Feature version',
|
|
example: '1.0.0',
|
|
},
|
|
tags: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
description: 'Categorization tags',
|
|
example: ['auth', 'users'],
|
|
},
|
|
createdAt: {
|
|
type: 'string',
|
|
format: 'date-time',
|
|
description: 'Creation timestamp',
|
|
},
|
|
updatedAt: {
|
|
type: 'string',
|
|
format: 'date-time',
|
|
description: 'Last update timestamp',
|
|
},
|
|
},
|
|
required: ['id', 'name', 'enabled', 'tags', 'createdAt', 'updatedAt'],
|
|
},
|
|
CreateFeatureRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
name: {
|
|
type: 'string',
|
|
minLength: 1,
|
|
maxLength: 100,
|
|
description: 'Unique feature name',
|
|
example: 'new-feature',
|
|
},
|
|
title: {
|
|
type: 'string',
|
|
maxLength: 200,
|
|
description: 'Human-readable title',
|
|
example: 'New Feature',
|
|
},
|
|
description: {
|
|
type: 'string',
|
|
maxLength: 1000,
|
|
description: 'Detailed description',
|
|
example: 'A new feature for the system',
|
|
},
|
|
config: {
|
|
type: 'object',
|
|
description: 'Feature configuration',
|
|
example: { enabled: true },
|
|
},
|
|
tags: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
description: 'Categorization tags',
|
|
example: ['feature', 'new'],
|
|
},
|
|
},
|
|
required: ['name'],
|
|
},
|
|
UpdateFeatureRequest: {
|
|
type: 'object',
|
|
properties: {
|
|
title: {
|
|
type: 'string',
|
|
maxLength: 200,
|
|
description: 'Human-readable title',
|
|
},
|
|
description: {
|
|
type: 'string',
|
|
maxLength: 1000,
|
|
description: 'Detailed description',
|
|
},
|
|
config: {
|
|
type: 'object',
|
|
description: 'Feature configuration',
|
|
},
|
|
enabled: {
|
|
type: 'boolean',
|
|
description: 'Whether the feature is enabled',
|
|
},
|
|
tags: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
description: 'Categorization tags',
|
|
},
|
|
},
|
|
},
|
|
UserInfo: {
|
|
type: 'object',
|
|
properties: {
|
|
userId: {
|
|
type: 'string',
|
|
description: 'Unique user identifier',
|
|
example: 'user-123',
|
|
},
|
|
email: {
|
|
type: 'string',
|
|
format: 'email',
|
|
description: 'User email address',
|
|
example: 'user@example.com',
|
|
},
|
|
role: {
|
|
type: 'string',
|
|
description: 'User role',
|
|
example: 'admin',
|
|
enum: ['admin', 'user', 'moderator'],
|
|
},
|
|
iat: {
|
|
type: 'number',
|
|
description: 'Token issued at timestamp',
|
|
},
|
|
exp: {
|
|
type: 'number',
|
|
description: 'Token expiration timestamp',
|
|
},
|
|
},
|
|
required: ['userId', 'email', 'role'],
|
|
},
|
|
HealthResponse: {
|
|
type: 'object',
|
|
properties: {
|
|
success: { type: 'boolean', example: true },
|
|
data: {
|
|
type: 'object',
|
|
properties: {
|
|
status: { type: 'string', example: 'ok' },
|
|
timestamp: { type: 'string', format: 'date-time' },
|
|
},
|
|
},
|
|
timestamp: { type: 'string', format: 'date-time' },
|
|
},
|
|
},
|
|
ReadinessResponse: {
|
|
type: 'object',
|
|
properties: {
|
|
success: { type: 'boolean', example: true },
|
|
data: {
|
|
type: 'object',
|
|
properties: {
|
|
status: { type: 'string', example: 'ready' },
|
|
},
|
|
},
|
|
timestamp: { type: 'string', format: 'date-time' },
|
|
},
|
|
},
|
|
LivenessResponse: {
|
|
type: 'object',
|
|
properties: {
|
|
success: { type: 'boolean', example: true },
|
|
data: {
|
|
type: 'object',
|
|
properties: {
|
|
status: { type: 'string', example: 'live' },
|
|
},
|
|
},
|
|
timestamp: { type: 'string', format: 'date-time' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
security: [
|
|
{
|
|
bearerAuth: [],
|
|
},
|
|
],
|
|
},
|
|
apis: [
|
|
'./src/routes/*.ts',
|
|
'./src/modules/auth/*.routes.ts',
|
|
'./src/modules/session/*.routes.ts',
|
|
'./src/modules/oidc/*.routes.ts',
|
|
'./src/modules/rbac/*.routes.ts',
|
|
'./src/modules/mfa/*.routes.ts',
|
|
'./src/modules/identity/*.routes.ts',
|
|
'./src/modules/access/*.routes.ts',
|
|
'./src/modules/governance/*.routes.ts'
|
|
],
|
|
};
|
|
|
|
/**
|
|
* EN: Generate OpenAPI specification
|
|
* VI: Tạo OpenAPI specification
|
|
*/
|
|
const specs = swaggerJSDoc(options);
|
|
|
|
/**
|
|
* EN: Setup Swagger UI middleware
|
|
* VI: Thiết lập Swagger UI middleware
|
|
*/
|
|
export const setupSwagger = (app: Application, basePath: string = '/api-docs') => {
|
|
// EN: Swagger page
|
|
// VI: Trang Swagger
|
|
app.use(basePath, swaggerUi.serve, swaggerUi.setup(specs, {
|
|
explorer: true,
|
|
swaggerOptions: {
|
|
persistAuthorization: true,
|
|
displayRequestDuration: true,
|
|
docExpansion: 'none',
|
|
filter: true,
|
|
showExtensions: true,
|
|
showCommonExtensions: true,
|
|
syntaxHighlight: {
|
|
activate: true,
|
|
theme: 'arta',
|
|
},
|
|
},
|
|
customCss: `
|
|
.swagger-ui .topbar { display: none }
|
|
.swagger-ui .info .title { color: #3b4151 }
|
|
`,
|
|
customSiteTitle: 'Microservice Template API Documentation',
|
|
customfavIcon: '/favicon.ico',
|
|
}));
|
|
|
|
// EN: Swagger JSON endpoint
|
|
// VI: Endpoint Swagger JSON
|
|
app.get(`${basePath}.json`, (_req, res) => {
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.send(specs);
|
|
});
|
|
|
|
// EN: Swagger YAML endpoint
|
|
// VI: Endpoint Swagger YAML
|
|
app.get(`${basePath}.yaml`, (_req, res) => {
|
|
res.setHeader('Content-Type', 'application/yaml');
|
|
// Note: Would need yaml package for full YAML support
|
|
res.send(JSON.stringify(specs, null, 2));
|
|
});
|
|
|
|
console.log(`📚 Swagger documentation available at: http://localhost:${appConfig.port}${basePath}`);
|
|
};
|
|
|
|
export { specs };
|
|
export default specs; |