feat(docs): Remove outdated service templates and enhance Vietnamese architecture documentation
- Deleted obsolete service architecture templates in both English and Vietnamese to streamline content. - Updated the Vietnamese architecture documentation with improved Mermaid diagrams for better visual clarity. - Enhanced color coding in diagrams to improve readability and consistency across documentation. - Added a new section detailing visual indicators for better understanding of architecture components.
This commit is contained in:
124
services/_template_nodejs/src/docs/__tests__/swagger.test.ts
Normal file
124
services/_template_nodejs/src/docs/__tests__/swagger.test.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import request from 'supertest';
|
||||
import express from 'express';
|
||||
import { setupSwagger, specs } from '../swagger';
|
||||
|
||||
// EN: Import actual swagger specs for testing
|
||||
// VI: Import actual swagger specs để test
|
||||
|
||||
// EN: Type definition for OpenAPI specs
|
||||
// VI: Định nghĩa type cho OpenAPI specs
|
||||
interface OpenAPISpec {
|
||||
openapi: string;
|
||||
info: {
|
||||
title: string;
|
||||
version: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
servers?: Array<{ url: string; [key: string]: any }>;
|
||||
components?: {
|
||||
securitySchemes?: {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
scheme: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
};
|
||||
schemas?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
[key: string]: any;
|
||||
};
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
describe('Swagger Documentation', () => {
|
||||
let app: express.Application;
|
||||
|
||||
beforeEach(() => {
|
||||
app = express();
|
||||
app.use(express.json());
|
||||
// Reset mock
|
||||
(setupSwagger as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
describe('specs', () => {
|
||||
it('should have valid OpenAPI structure', () => {
|
||||
const typedSpecs = specs as OpenAPISpec;
|
||||
expect(typedSpecs.openapi).toBe('3.0.0');
|
||||
expect(typedSpecs.info).toBeDefined();
|
||||
expect(typedSpecs.info.title).toBe('Microservice Template API');
|
||||
expect(typedSpecs.info.version).toBe('1.0.0');
|
||||
expect(typedSpecs.servers).toBeDefined();
|
||||
expect(typedSpecs.components).toBeDefined();
|
||||
});
|
||||
|
||||
it('should define security schemes', () => {
|
||||
const typedSpecs = specs as OpenAPISpec;
|
||||
expect(typedSpecs.components?.securitySchemes).toBeDefined();
|
||||
expect(typedSpecs.components?.securitySchemes?.bearerAuth).toBeDefined();
|
||||
expect(typedSpecs.components?.securitySchemes?.bearerAuth?.type).toBe('http');
|
||||
expect(typedSpecs.components?.securitySchemes?.bearerAuth?.scheme).toBe('bearer');
|
||||
});
|
||||
|
||||
it('should define response schemas', () => {
|
||||
const typedSpecs = specs as OpenAPISpec;
|
||||
const schemas = typedSpecs.components?.schemas;
|
||||
expect(schemas?.ApiResponse).toBeDefined();
|
||||
expect(schemas?.ErrorResponse).toBeDefined();
|
||||
expect(schemas?.Feature).toBeDefined();
|
||||
expect(schemas?.CreateFeatureRequest).toBeDefined();
|
||||
expect(schemas?.UpdateFeatureRequest).toBeDefined();
|
||||
expect(schemas?.UserInfo).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have server configurations', () => {
|
||||
const typedSpecs = specs as OpenAPISpec;
|
||||
expect(typedSpecs.servers).toBeInstanceOf(Array);
|
||||
expect(typedSpecs.servers?.length).toBeGreaterThan(0);
|
||||
expect(typedSpecs.servers?.[0]?.url).toContain('localhost');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setupSwagger', () => {
|
||||
it('should be callable', () => {
|
||||
expect(typeof setupSwagger).toBe('function');
|
||||
});
|
||||
|
||||
it('should accept app and basePath parameters', () => {
|
||||
const mockApp = {
|
||||
use: jest.fn(),
|
||||
get: jest.fn(),
|
||||
} as any;
|
||||
|
||||
setupSwagger(mockApp, '/docs');
|
||||
|
||||
expect(setupSwagger).toHaveBeenCalledWith(mockApp, '/docs');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Swagger UI endpoints', () => {
|
||||
beforeEach(() => {
|
||||
// Setup real swagger for integration test
|
||||
const realSetupSwagger = jest.requireActual('../swagger').setupSwagger;
|
||||
realSetupSwagger(app, '/test-docs');
|
||||
});
|
||||
|
||||
it('should serve swagger json endpoint', async () => {
|
||||
const response = await request(app)
|
||||
.get('/test-docs.json')
|
||||
.expect(200);
|
||||
|
||||
expect(response.headers['content-type']).toContain('application/json');
|
||||
expect(response.body.openapi).toBe('3.0.0');
|
||||
});
|
||||
|
||||
it('should serve swagger yaml endpoint', async () => {
|
||||
const response = await request(app)
|
||||
.get('/test-docs.yaml')
|
||||
.expect(200);
|
||||
|
||||
expect(response.headers['content-type']).toContain('application/yaml');
|
||||
expect(response.text).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
364
services/_template_nodejs/src/docs/swagger.ts
Normal file
364
services/_template_nodejs/src/docs/swagger.ts
Normal file
@@ -0,0 +1,364 @@
|
||||
import { Application } from 'express';
|
||||
import swaggerJSDoc from 'swagger-jsdoc';
|
||||
import swaggerUi from 'swagger-ui-express';
|
||||
|
||||
/**
|
||||
* 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: '5000',
|
||||
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'],
|
||||
},
|
||||
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/*/feature.module.ts'], // Paths to files containing OpenAPI definitions
|
||||
};
|
||||
|
||||
/**
|
||||
* 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:5000${basePath}`);
|
||||
};
|
||||
|
||||
export { specs };
|
||||
export default specs;
|
||||
Reference in New Issue
Block a user