Commit 4b1ec755 authored by Alex Quispe's avatar Alex Quispe
Browse files

Se adicionaron funciones para incluir ficheros de ayuda (JSON y YAML), se...

Se adicionaron funciones para incluir ficheros de ayuda (JSON y YAML), se actualizó la documentación y se optimizó el código.
parent 1bf22c86
/public
/node_modules
/temp
......@@ -16,8 +16,7 @@ documentation
├─ src
│ ├─ api.js
│ └─ auth.js
├─ index.js
└─ swagger-server.js
└─ index.js
```
### Archivo `index.js`
......@@ -45,21 +44,24 @@ ApiGen.create(['auth', 'api']).catch(e => { console.log(e) })
const ApiGen = require('apidoc-generator')
module.exports = async () => {
await ApiGen.post('/api/auth/signin').data({ body: { user: 'demo1', password: 'Developer' } }).generate()
await ApiGen.post('/api/auth/signin').inputData({ body: { user: 'demo1', password: 'Developer' } }).generate() // Sin verificar
await ApiGen.post('/api/auth/signin').inputData({ body: { user: 'demo1', password: 'Developer' } }).request(true).generate() // Verifica la ruta
}
```
La propiedad `request(true)` indica si se va a ejecutar la petición, en tal caso se documenta el resultado de la petición automáticamente.
### Archivo `src/api.js`
```js
const ApiGen = require('apidoc-generator')
module.exports = async () => {
const login = await ApiGen.post('/api/auth/signin').data({ body: { user: 'demo1', password: 'Developer' } }).execute()
const login = await ApiGen.post('/api/auth/signin').inputData({ body: { user: 'demo1', password: 'Developer' } }).execute()
const AUTH_HEADER = { Authorization: `Bearer ${login.token}` }
await ApiGen.get('/api/v1/instituciones').data({ headers: AUTH_HEADER }).generate('CUSTOM GROUP')
await ApiGen.get('/api/v1/instituciones').inputData({ headers: AUTH_HEADER }).request(true).generate('CUSTOM GROUP')
}
```
......@@ -86,28 +88,31 @@ await ApiGen.delete('api/v1/users/:id').generate()
### Funciones disponibles:
| Función | Descripción | Valor por defecto |
| ------------- | ---------------------------------------------------------------------- | ----------------------- |
| `data` | Datos de entrada: `{ headers: {}, params: {}, query: {}, body: {} }` | `{}` |
| `name` | Nombre con el que se identificará a la ruta. | `<method>/<path></key>` |
| `key` | Palabra clave que se adiciona al final del nombre de la ruta. | `null` |
| `description` | Descripción de la ruta. | `null` |
| `group` | Grupo al que pertenece la ruta. | `<fileName>` |
| `version` | Versión. | `1` |
| `request` | Indica si se va a ejecutar la petición para crear los datos de salida. | `true` |
| `permissions` | Lista de los roles. Ej.: `.permissions(['admin', 'user'])` | `null` |
| Función | Descripción | Valor por defecto |
| ---------------- | ---------------------------------------------------------------------- | ----------------------- |
| `name` | Nombre con el que se identificará a la ruta. | `<method>/<path></key>` |
| `group` | Grupo al que pertenece la ruta. | `<fileName>` |
| `description` | Descripción de la ruta. | `null` |
| `version` | Versión. | `1` |
| `permissions` | Lista de los roles. Ej.: `.permissions(['admin', 'user'])` | `null` |
| `request` | Indica si se va a ejecutar la petición para crear los datos de salida. | `false` |
| `key` | Palabra clave que se adiciona al final del nombre de la ruta. | `null` |
| `inputData` | Datos de entrada: `{ headers: {}, params: {}, query: {}, body: {} }` | `{}` |
| `outputData` | Datos de salida: `body` | `{}` |
| `inputExamples` | Ejemplos de datos de entrada: `{ title: '', data: obj }` | `null` |
| `outputExamples` | Ejemplos de datos de salida: `{ title: '', data: obj }` | `null` |
**Ejemplo:**
```js
await ApiGen.get('/api/v1/users').generate()
await ApiGen.post('/api/v1/users').data({ body; { user: 'admin', pass: '123'} }).generate()
await ApiGen.post('/api/v1/users').data({ body; { user: 'admin', pass: '123'} }).name('Autenticar').generate()
await ApiGen.post('/api/v1/users').inputData({ body; { user: 'admin', pass: '123'} }).generate()
await ApiGen.post('/api/v1/users').inputData({ body; { user: 'admin', pass: '123'} }).name('Autenticar').generate()
await ApiGen.get('/api/v1/users').key('Admin').generate()
await ApiGen.get('/api/v1/users').key('User').generate()
await ApiGen.get('/documentando/solo/la/ruta').request(false).generate()
await ApiGen.get('/ruta/verificada').request(true).generate()
```
### Función `execute`
......@@ -129,9 +134,58 @@ Adicionalmente se puede pasar como parámetro el nombre del grupo al que pertene
// group = <fileName>
await ApiGen.get('/api/v1/users').generate()
// group = AUTH
await ApiGen.get('/api/v1/users').generate('AUTH')
// group = Auth
await ApiGen.get('/api/v1/users').generate('Auth')
// Devuelve el resultado de la petición (body)
const body = await ApiGen.get('/api/v1/users').generate()
```
## Configuración de los script de ejecución
Cada fichero se ejecuta de manera independiente.
Para el archivo `documentation/generate.js`
```js
const ApiGen = require('apidoc-generator')
const path = require('path')
ApiGen.API_URL = 'http://localhost:4000'
ApiGen.DOC_SERVER_PORT = 5000
ApiGen.BUILD_PATH = path.resolve(__dirname, 'build')
ApiGen.SRC_PATH = path.resolve(__dirname, 'src')
ApiGen.HELP_TYPE = 'YAML' // YAML o JSON
ApiGen.DESCRIPTION = 'Descripción general del Apidoc'
ApiGen.create().catch(e => console.log(e))
```
Para el archivo `documentation/scaffold.js`
```js
const ApiGen = require('apidoc-generator')
const path = require('path')
ApiGen.SRC_PATH = path.resolve(__dirname, 'src')
ApiGen.HELP_TYPE = 'YAML' // YAML o JSON
const app = require('../src/app')
ApiGen.scaffold(app)
```
Para el archivo `documentation/server.js`
```js
const ApiGen = require('apidoc-generator')
const path = require('path')
ApiGen.DOC_SERVER_PORT = 5000
ApiGen.BUILD_PATH = path.resolve(__dirname, 'build')
const SWAGGER_JSON_URL = `http://localhost:${ApiGen.DOC_SERVER_PORT}/swagger.json`
ApiGen.REDIRECT_PATH = `/swagger?url=${SWAGGER_JSON_URL}`
ApiGen.server()
```
......@@ -57,6 +57,7 @@ function _route (properties, onCreate) {
apidoc += _createApidoc('', INPUT.params, '@apiParam', 'Datos de entrada - params')
apidoc += _createApidoc('', INPUT.query, '@apiParam', 'Datos de entrada - query')
apidoc += _createApidoc('', INPUT.body, '@apiParam', 'Datos de entrada - body')
apidocSwagger.content.parameters = []
if (INPUT.headers && Object.keys(INPUT.headers).length > 0) {
const HEADERS_PARAM = _createApidocSwagger('', INPUT.headers, '@apiHeader', 'Datos de entrada - headers')
......@@ -189,7 +190,7 @@ function _headerSwagger (route) {
description = `<strong>PERMISOS:</strong> <code>${route.permissions}</code><br><br>${description}`
}
const swaggerDefinition = {
path: route.requestPathSwagger,
path: route.swaggerPath(),
method:route.method,
content: {
"tags": [route.group],
......
This diff is collapsed.
......@@ -241,6 +241,10 @@ class FieldCreator {
static validateGroup (fieldGroup) {
_validateGroup(fieldGroup)
}
static groupObj (obj) {
return _toFieldGroup(obj)
}
}
/**
......@@ -480,4 +484,48 @@ function _normalizeValidate (field) {
}
}
function _toFieldGroup (obj, fullPath = '') {
const RESULT = {}
if (Array.isArray(obj)) {
if (obj.length <= 0) { return [] }
const item = _getMaxItem(obj)
return (typeof item === 'object') ? [_toFieldGroup(item, fullPath)] : FieldCreator.ARRAY(getField(item), { example: [item], allowNull: false })
}
if (obj) {
Object.keys(obj).forEach(prop => {
const OBJ = obj[prop]
const fullPath2 = `${fullPath ? `${fullPath}.` : ''}${prop}`
if (typeof OBJ === 'object') {
RESULT[prop] = _toFieldGroup(OBJ, `${fullPath2}`)
return
}
RESULT[prop] = getField(OBJ)
})
return RESULT
}
}
function _getMaxItem (array) {
let maxId = -1
let propLength = 0
for (let i = 0; i <= array.length; i++) {
const item = array[i]
const length = item ? Object.keys(item).length : 0
if (length > propLength) {
maxId = i
propLength = length
}
}
return array[maxId]
}
function getField (OBJ) {
switch (typeof OBJ) {
case 'string': return FieldCreator.STRING({ example: OBJ, allowNull: false });
case 'boolean': return FieldCreator.BOOLEAN({ example: OBJ, allowNull: false });
case 'number': return FieldCreator.INTEGER({ example: OBJ, allowNull: false });
}
return OBJ
}
module.exports = FieldCreator
const _ = require('lodash')
const request = require('request')
const FieldCreator = require('./FieldCreator')
class Route {
constructor (groupName) {
constructor () {
this.method = 'get'
this.path = '/'
this.group = groupName
this.path = '/' // Ej.: /api/v1/users/:id
this.group = 'Default' // Ej.: Api V1 Users
this.groupKey = 'default' // Ej.: api-v1-users
this.version = 1
this.name = null
this.key = null
this.name = null // Ej.: Devuelve una lista de usuarios
this.key = null // Ej.: admin
this.description = null
this.permissions = null
this.requestPath = this.path
this.requestPathSwagger = this.path
this.input = {}
this.output = {}
this.inputData = {}
this.outputData = {}
this.request = true
}
_setURI (method, routePath) {
this.method = method
this.path = routePath
this.requestPath = routePath
this.requestPathSwagger = routePath
this.permissions = null // Ej.: ['admin', 'user']
this.input = null // FieldGroup
this.output = null // FieldGroup
this.inputData = null // Datos de entrada obj = { headers, params, query, body }
this.outputData = null // Datos de salida obj = body
this.request = false
}
setName (name) {
this.name = name
}
setGroup (groupName) {
this.group = _toWords(groupName)
this.groupKey = _toKebab(this.group)
}
setVersion (version) {
this.version = version
}
setKey (key) {
this.key = key
}
setDescription (description) {
this.description = description
}
setPersmissions (persmissions) {
this.persmissions = persmissions
}
setMethod (method) {
this.method = method
}
setPath (routePath) {
this.path = routePath
}
setInputData (inputData) {
this.inputData = inputData
}
setOutputData (outputData) {
this.outputData = outputData
}
setInput (input) {
this.input = input
}
setOutput (output) {
this.output = output
}
setRequest (value) {
this.request = value
}
async updateProperties (options = {}) {
const PROPS = options.properties || {}
this.key = this.key || PROPS.key
this.permissions = this.permissions || PROPS.permissions
this.version = this.version || PROPS.version
this.request = this.request || PROPS.request
this.name = this.name || PROPS.name || `[${this.method}] ${_toWords(this.path)}${this.key ? ` (${this.key})` : ''}`
this.description = this.description || PROPS.description || this.name
this.inputData = this.inputData || PROPS.inputData
this.outputData = this.outputData || PROPS.outputData
this.inputExamples = this.inputExamples || PROPS.inputExamples
this.outputExamples = this.outputExamples || PROPS.outputExamples
if (this.request === true) {
this.outputData = this.outputData || await _request(this, options)
}
this.input = this.input || FieldCreator.groupObj(this.inputData) || {}
this.output = this.output || FieldCreator.groupObj(this.outputData) || {}
}
swaggerPath () {
const split = this.path.split('/')
const path2 = []
let pathSwagger = this.path
split.forEach(prop => {
path2.push(prop.startsWith(':') ? `{${prop.substr(1)}}` : prop)
})
const swaggerPath = `/${_.trim(path2.join('/'), '/')}${this.path.endsWith('/') ? '/' : ''}`
return swaggerPath
}
requestPath (inputDataParams = {}) {
let requestPath = this.path
Object.keys(inputDataParams).forEach(prop => {
requestPath = requestPath.replace(`:${prop}`, `${inputDataParams[prop]}`)
})
return requestPath
}
}
function _toWords (text) {
const split = text.split('/')
let words = ''
split.forEach(e => {
words += _.upperFirst(_.deburr(e)) + ' '
})
return words.trim()
}
function _toKebab (text) {
const kebab = _.trim(text.toLowerCase().split(' ').join('-'), '-')
return kebab.trim()
}
function _request (route, config) {
const inputDataParams = route.inputData ? route.inputData.params : {}
const options = { method: route.method.toUpperCase(), uri: `${config.apiUrl}${route.requestPath(inputDataParams)}` }
if (route.inputData && route.inputData.body) { options.json = route.inputData.body }
if (route.inputData && route.inputData.headers) { options.headers = route.inputData.headers }
return new Promise((resolve, reject) => {
return request(options, (error, response, body) => {
if (error) {
if (error.code === 'ECONNREFUSED') {
console.log(' \x1b[31mError:\x1b[0m El servicio a documentar no se encuentra disponible :(\n')
}
return reject(error)
}
const CODE = response.statusCode
const STATUS = response.statusMessage
try { if (typeof body === 'string') body = JSON.parse(body) } catch (e) {}
if (CODE >= 200 && CODE < 300) { return resolve(body) }
console.log(`\n \x1b[31mError:\x1b[0m [${route.method}] ${route.path}\n`)
console.log('DATA:')
console.log(route.inputData)
console.log()
console.log('REQUEST:')
console.log(options)
console.log()
console.log(`RESPONSE (${CODE} ${STATUS}):`)
console.log(body)
console.log()
return reject(new Error(`La petición ha finalizado con un error.`))
})
})
}
module.exports = Route
const ApiGen = require('../../')
const ApiGen = require('./../../')
const path = require('path')
ApiGen.API_URL = process.env.API_URL || 'http://localhost:4000'
ApiGen.API_URL = process.env.API_URL || 'http://localhost:4000'
ApiGen.DOC_SERVER_PORT = process.env.DOC_SERVER_PORT || 5000
ApiGen.SRC_PATH = path.resolve(__dirname, 'src')
ApiGen.BUILD_PATH = path.resolve(__dirname, 'build')
ApiGen.SRC_PATH = path.resolve(__dirname, 'src')
ApiGen.HELP_TYPE = 'YAML' // YAML o JSON
const SWAGGER_JSON_URL = `http://localhost:${ApiGen.DOC_SERVER_PORT}/swagger.json`
let description = ''
description += `Swagger: http://localhost:${ApiGen.DOC_SERVER_PORT}/swagger?url=${SWAGGER_JSON_URL}\n`
description += `ApidocJS: http://localhost:${ApiGen.DOC_SERVER_PORT}/apidoc\n`
ApiGen.DESCRIPTION = description
ApiGen.create().catch(e => { console.log(e) })
ApiGen.create().catch(e => console.log(e))
const ApiGen = require('../../')
const ApiGen = require('./../../')
const path = require('path')
ApiGen.SRC_PATH = path.resolve(__dirname, 'src')
ApiGen.HELP_TYPE = 'YAML' // YAML o JSON
const app = require('../')
const app = require('../src/app')
ApiGen.scaffold(app).catch(e => { console.log(e) })
ApiGen.scaffold(app)
const ApiGen = require('./../../')
const path = require('path')
const ApiGen = require('../../')
ApiGen.SERVER_PORT = 5000
ApiGen.BUILD_PATH = path.resolve(__dirname, 'build')
ApiGen.DOC_SERVER_PORT = process.env.DOC_SERVER_PORT || 5000
ApiGen.BUILD_PATH = path.resolve(__dirname, 'build')
const SWAGGER_JSON_URL = `http://localhost:${ApiGen.DOC_SERVER_PORT}/swagger.json`
ApiGen.REDIRECT_PATH = `/swagger?url=${SWAGGER_JSON_URL}`
ApiGen.server()
const ApiGen = require('../../../')
module.exports = async () => {
await ApiGen.get('/api/custom/other/route').execute()
await ApiGen.get('/api/custom/other/route').execute()
await ApiGen.get('/api/custom/other/route').execute()
await ApiGen.get('/api/custom/other/route').permissions(['user']).request(false).generate()
await ApiGen.get('/api/custom/other/route').generate()
// <!-- [ROUTE DEFINITION] --!> //
}
const ApiGen = require('../../../')
module.exports = async () => {
await ApiGen.get('/api/v1/users/').permissions(['admin', 'user']).generate()
await ApiGen.get('/api/v1/users/').generate()
await ApiGen.get('/api/v1/users/:id').data({ params: { id: 1 } }).generate()
await ApiGen.get('/api/v1/users/:id').generate()
await ApiGen.post('/api/v1/users/').description('Crea un nuevo usuario').request(false).generate()
await ApiGen.post('/api/v1/users/').generate()
await ApiGen.post('/api/v1/users/bulk').request(false).generate()
await ApiGen.post('/api/v1/users/bulk').generate()
// <!-- [ROUTE DEFINITION] --!> //
}
const ApiGen = require('../../../')
module.exports = async () => {
await ApiGen.get('/ruta/sin/grupo').request(false).generate()
await ApiGen.get('/ruta/sin/grupo').generate()
// <!-- [ROUTE DEFINITION] --!> //
}
const ApiGen = require('../../../')
module.exports = async () => {
await ApiGen.post('/api/v1/users/').data({body:{name:'John',user:'admin',pass:'123'}}).key('Admin').generate('GRUPO 2')
await ApiGen.post('/api/v1/users/').data({body:{name:'Smith',user:'user',pass:'123'}}).key('User').generate('GRUPO 2')
}
const ApiGen = require('../../../')
module.exports = async () => {
await ApiGen.get('/api/v1/users/').generate()
await ApiGen.post('/api/v1/users/').data({body:{name:'rosa',user:'user100',pass:'123'}}).generate()
await ApiGen.get('/api/v1/users/').name('Devuelve una lista de usuarios').generate('GRUPO 1')
await ApiGen.get('/api/v1/users/').name('Listar usuarios').generate('GRUPO 1')
await ApiGen.get('/documentando/solo/la/ruta').request(false).generate('GRUPO 3')
const DATA = [
{ id: 1, name: 'Anna', user: 'admin', pass: '123' },
{ id: 2, name: 'Juan', user: 'user', pass: '123' }
]
await ApiGen.post('/api/v1/users/bulk').data({ body: DATA }).generate('GROUP 4')
}
......@@ -14,6 +14,11 @@ app.use(cors({
app.use(express.static('public'))
app.use((req, res, next) => {
console.log(` [${req.method}] ${req.url}`);
next()
})
const ROUTER1 = express.Router()
ROUTER1.get('/', (req, res, next) => {
......@@ -25,7 +30,7 @@ ROUTER1.get('/', (req, res, next) => {
})
ROUTER1.get('/:id', (req, res, next) => {
const user = { id: 1, name: 'John', user: 'admin', pass: '123' }
const user = { id: req.params.id, name: 'John', user: 'admin', pass: '123' }
res.status(200).json(user)
})
......@@ -37,15 +42,23 @@ ROUTER1.post('/', (req, res, next) => {
res.status(201).json(result)
})
// ROUTER1.post('/nueva/ruta1', (req, res, next) => { res.status(201).json({}) })
//
// ROUTER1.post('/nueva/ruta2', (req, res, next) => { res.status(201).json({}) })
ROUTER1.post('/bulk', (req, res, next) => {
const users = req.body
let cnt = 1
users.forEach(user => { user.id = cnt++ })
const result = {
finalizado: true,
datos: users
try {
const users = req.body
let cnt = 1
users.forEach(user => { user.id = cnt++ })
const result = {
finalizado: true,
datos: users
}
res.status(201).json(result)
} catch (e) {
res.status(500).json({ msg: 'Error' })
}
res.status(201).json(result)
})
const ROUTER2 = express.Router()
......@@ -59,6 +72,6 @@ app.use('/api/custom', ROUTER2)
const PORT = 4000
app.listen(PORT)
console.log(`\n App listening on port ${PORT}\n`);
console.log(`\n App listening on port ${PORT}\n`)
module.exports = app
......@@ -1124,6 +1124,15 @@