Skip to content

Commit

Permalink
feat: Add swagger (#189)
Browse files Browse the repository at this point in the history
* docs: Add swagger

* build: Update dockerfile
  • Loading branch information
DaevMithran authored Mar 22, 2023
1 parent 0bbedfe commit 3c3ef1b
Show file tree
Hide file tree
Showing 8 changed files with 514 additions and 56 deletions.
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ ENV RESOLVER_URL ${RESOLVER_URL}

# We don't have the node_modules directory
# this image only has the output worker.js file.
RUN chown -R node:node /home/node/app && \
# Install pre-requisites
RUN npm install swagger-ui-express@4.5.0 && \
chown -R node:node /home/node/app && \
apk update && \
apk add --no-cache bash ca-certificates

Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The purpose of this service is to issue and verify credentials. This service by

### Issue a credential

- **Endpoint** POST `/api/credentials/issue`
- **Endpoint** POST `/1.0/api/credentials/issue`
- **Accepts**: `application/json`
- **Request Body**: JSON object with following fields
- `attributes` - A json object with all the credential attributes
Expand All @@ -23,22 +23,24 @@ The purpose of this service is to issue and verify credentials. This service by
- `@context` - context of the issued credential (optional)
- `expirationDate` - Date of expiration of the JWT (optional)
- **Success Response Code**: 200
- **Error Response Code** - 400
- **Invalid Request Response Code** - 400
- **Internal Error Response Code** - 500

### Verify a Credential

- **Endpoint** POST `/api/credentials/verify`
- **Endpoint** POST `/1.0/api/credentials/verify`
- **Accepts**: `application/json`
- **Request Body**: JSON object with following fields:
- `credential` - A verifiable credential or the JWT string
- **Success Response Code** - 200
- **Error Response Codes**:
- **Invalid Request Response Code**:
- 400: Bad request body
- 405: Wrong content type
- **Internal Error Response Code** - 500

### Health Check

- **Endpoint**: `/api/credentials` (This endpoint only returns a "PONG" as response with status code 200)
- **Endpoint**: `/` (This endpoint redirects to the swagger api docs)

## 🧑‍💻🛠 Developer Guide

Expand Down
60 changes: 57 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"author": "Cheqd Foundation Limited (https://github.com/cheqd)",
"repository": "https://github.com/cheqd/credential-service.git",
"scripts": {
"build": "esbuild src/index.ts --platform=node --bundle --minify --outdir=dist",
"build": "esbuild src/index.ts --platform=node --bundle --minify --outdir=dist --external:swagger-ui-express",
"start": "node dist/index.js",
"format": "prettier --write '*.{json,js}' 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'",
"lint": "eslint --max-warnings=0 src && prettier --check '*.{json,js}' 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'",
Expand Down Expand Up @@ -43,7 +43,8 @@
"express": "^4.18.2",
"express-validator": "^6.15.0",
"helmet": "^6.0.1",
"node-cache": "^5.1.2"
"node-cache": "^5.1.2",
"swagger-ui-express": "^4.6.1"
},
"devDependencies": {
"@semantic-release/changelog": "^6.0.2",
Expand All @@ -59,6 +60,7 @@
"@types/jest": "^29.2.5",
"@types/node": "^18.11.18",
"@types/uuid": "^9.0.0",
"@types/swagger-ui-express": "^4.1.3",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"buffer": "6.0.3",
Expand Down
16 changes: 9 additions & 7 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { CredentialController } from './controllers/credentials'
import { StoreController } from './controllers/store'
import cors from 'cors'
import { CORS_ERROR_MSG } from './types/constants'
import * as swagger from 'swagger-ui-express'
import * as swaggerJson from '../swagger.json'

require('dotenv').config()

Expand All @@ -30,22 +32,22 @@ class App {
return callback(null, true)
}
}))
this.express.use('/api-docs', swagger.serve, swagger.setup(swaggerJson))
}

private routes() {
const app = this.express
const URL_CREDENTIAL_PREFIX = '/api/credentials'
const URL_STORE_PREFIX = '/store'
const URL_PREFIX = '/1.0/api'

app.get('/', (req, res) => res.json({ ping: 'pong' }))
app.get('/', (req, res) => res.redirect('api-docs'))

// credentials
app.post(`${URL_CREDENTIAL_PREFIX}/issue`, CredentialController.issueValidator, new CredentialController().issue)
app.post(`${URL_CREDENTIAL_PREFIX}/verify`, CredentialController.verifyValidator, new CredentialController().verify)
app.post(`${URL_PREFIX}/credentials/issue`, CredentialController.issueValidator, new CredentialController().issue)
app.post(`${URL_PREFIX}/credentials/verify`, CredentialController.verifyValidator, new CredentialController().verify)

// store
app.post(`${URL_STORE_PREFIX}/`, new StoreController().set)
app.get(`${URL_CREDENTIAL_PREFIX}/:id`, new StoreController().get)
app.post(`${URL_PREFIX}/store`, new StoreController().set)
app.get(`${URL_PREFIX}/store/:id`, new StoreController().get)

// 404 for all other requests
app.all('*', (req, res) => res.status(400).send('Bad request'))
Expand Down
86 changes: 48 additions & 38 deletions src/controllers/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,53 @@ import { check, validationResult } from 'express-validator'

export class CredentialController {

public static issueValidator = [
check('subjectDid')
.exists().withMessage('subjectDid is required')
.isString().withMessage('subjectDid should be a string')
.contains('did:').withMessage('subjectDid should be a DID'),
check('attributes')
.exists().withMessage('attributes are required')
.isObject().withMessage('attributes should be an object'),
check('type').optional().isArray().withMessage('type should be a string array'),
check('@context').optional().isArray().withMessage('@context should be a string array'),
check('expirationDate').optional().isDate().withMessage('Invalid expiration date')
]

public static verifyValidator = [
check('credential').exists().withMessage('W3c verifiable credential was not provided')
]

public async issue(request: Request, response: Response) {
const result = validationResult(request);
if (!result.isEmpty()) {
return response.status(400).json({ error: result.array()[0].msg })
}

response.json(await Credentials.instance.issue_credential(request.body))
}

public async verify(request: Request, response: Response) {
if (request?.headers && (!request.headers['content-type'] || request.headers['content-type'] != 'application/json')) {
return response.status(405).json({ error: 'Unsupported media type.' })
}

const result = validationResult(request);
if (!result.isEmpty()) {
return response.status(400).json({ error: result.array()[0].msg })
}

return response.json(await Credentials.instance.verify_credentials(request.body.credential))
}
public static issueValidator = [
check('subjectDid')
.exists().withMessage('subjectDid is required')
.isString().withMessage('subjectDid should be a string')
.contains('did:').withMessage('subjectDid should be a DID'),
check('attributes')
.exists().withMessage('attributes are required')
.isObject().withMessage('attributes should be an object'),
check('type').optional().isArray().withMessage('type should be a string array'),
check('@context').optional().isArray().withMessage('@context should be a string array'),
check('expirationDate').optional().isDate().withMessage('Invalid expiration date')
]

public static verifyValidator = [
check('credential').exists().withMessage('W3c verifiable credential was not provided')
]

public async issue(request: Request, response: Response) {
const result = validationResult(request);
if (!result.isEmpty()) {
return response.status(400).json({ error: result.array()[0].msg })
}
try {
response.json(await Credentials.instance.issue_credential(request.body))
} catch (error) {
response.status(500).json({
error: `Internal error: ${error}`
})
}
}

public async verify(request: Request, response: Response) {
if (request?.headers && (!request.headers['content-type'] || request.headers['content-type'] != 'application/json')) {
return response.status(405).json({ error: 'Unsupported media type.' })
}

const result = validationResult(request);
if (!result.isEmpty()) {
return response.status(400).json({ error: result.array()[0].msg })
}
try {
return response.json(await Credentials.instance.verify_credentials(request.body.credential))
} catch (error) {
response.status(500).json({
error: `Internal error: ${error}`
})
}
}

}
1 change: 1 addition & 0 deletions src/services/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export class Credentials {
credential
}
)
delete(result.payload)
return result
}

Expand Down
Loading

0 comments on commit 3c3ef1b

Please sign in to comment.