Skip to content

Commit

Permalink
feat(ocean-api): initial commit for apps/ocean-api (#926)
Browse files Browse the repository at this point in the history
* initial package setup

* add Actuator, Blockchain and Controller modules

* fix README.md

* fix test and build

* fix error casting
  • Loading branch information
fuxingloh authored Dec 31, 2021
1 parent 1a0ca2d commit bb5da70
Show file tree
Hide file tree
Showing 21 changed files with 4,557 additions and 71 deletions.
3 changes: 2 additions & 1 deletion .idea/dictionaries/fuxing.xml

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

42 changes: 42 additions & 0 deletions apps/ocean-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# DeFiChain Ocean API

DeFiChain Ocean API, next^2 generation API for building scalable Native DeFi Apps.

## Motivation

> https://github.com/DeFiCh/jellyfish/issues/580
As part of [#580](https://github.com/DeFiCh/jellyfish/issues/580) consolidation efforts. We had multiple projects that
were extensions of the jellyfish project. The separated projects allowed us to move quickly initially but proves to be a
bottleneck when it comes to development.

By including Ocean API development with jellyfish, it creates a better synergy of DeFiChain open source development
across concerns. Singular versioning, source of truth, documentation of entirety of defichain
via [jellyfish.defichain.com](https://jellyfish.defichain.com).

## `/apps/ocean-api`

The server for ocean-api, build with @nestjs it uses aspect-oriented programming methodology to allow the modular design
of `ocean-api`. Featuring 2 main directories `/controllers` and `/modules`.

## Related Packages

### `/packages/ocean-api-client`

> Provides the protocol core for communicating between client and server. Within `ocean-api-client`, it contains a shared response and exception structure.
The official JS client for ocean-api. As the development of ocean-api client and server are closely intertwined, this
allows the project to move iteratively together. With them packaged together within the same repo, the server and client
can be released together. This allows us to be the consumer of our own client implementation. Testing each server
endpoint directly with `ocean-api-client`, dogfooding at the maximum.

### `/packages/playground`

> This package is not published, for internal use within `@defichain-apps/ocean-api` only.
`@defichain/playground` is a specialized testing blockchain isolated from MainNet for testing DeFi applications. Assets
are not real, they can be minted by anyone. Blocks are configured to generate every 3 seconds, the chain can reset
anytime.

A bot-like design centers the playground as a mechanism that allows bootstrapping with an interval cycle. This allows
the developer to mock any behaviors they want with a simulated testing blockchain.
151 changes: 151 additions & 0 deletions apps/ocean-api/__tests__/controllers/ActuatorController.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { OceanApiTesting } from '../../testing/OceanApiTesting'
import { TestingGroup } from '@defichain/jellyfish-testing'

describe('no peers', () => {
const apiTesting = OceanApiTesting.create()

beforeAll(async () => {
await apiTesting.start()
})

afterAll(async () => {
await apiTesting.stop()
})

it('/_actuator/probes/liveness healthy', async () => {
const res = await apiTesting.app.inject({
method: 'GET',
url: '/_actuator/probes/liveness'
})

expect(res.json()).toStrictEqual({
data: {
details: {
blockchain: {
status: 'up'
}
},
error: {},
info: {
blockchain: {
status: 'up'
}
},
status: 'ok'
}
})
expect(res.statusCode).toStrictEqual(200)
})

it('/_actuator/probes/readiness unhealthy', async () => {
const res = await apiTesting.app.inject({
method: 'GET',
url: '/_actuator/probes/readiness'
})

expect(res.json()).toStrictEqual({
data: {
details: {
blockchain: {
blocks: expect.any(Number),
headers: expect.any(Number),
initialBlockDownload: expect.any(Boolean),
peers: 0,
status: 'down'
}
},
error: {
blockchain: {
blocks: expect.any(Number),
headers: expect.any(Number),
initialBlockDownload: expect.any(Boolean),
peers: 0,
status: 'down'
}
},
info: {},
status: 'error'
},
error: {
at: expect.any(Number),
code: 503,
message: 'Service Unavailable Exception',
type: 'ServiceUnavailable',
url: '/_actuator/probes/readiness'
}
})
expect(res.statusCode).toStrictEqual(503)
})
})

describe('with peers', () => {
const apiTesting = OceanApiTesting.create(TestingGroup.create(2))

beforeAll(async () => {
await apiTesting.start()
await apiTesting.container.waitForWalletCoinbaseMaturity()
})

afterAll(async () => {
await apiTesting.stop()
})

it('/_actuator/probes/liveness healthy', async () => {
const res = await apiTesting.app.inject({
method: 'GET',
url: '/_actuator/probes/liveness'
})

expect(res.json()).toStrictEqual({
data: {
details: {
blockchain: {
status: 'up'
}
},
error: {},
info: {
blockchain: {
status: 'up'
}
},
status: 'ok'
}
})
expect(res.statusCode).toStrictEqual(200)
})

it('/_actuator/probes/readiness healthy', async () => {
const res = await apiTesting.app.inject({
method: 'GET',
url: '/_actuator/probes/readiness'
})

expect(res.json()).toStrictEqual({
data: {
details: {
blockchain: {
blocks: expect.any(Number),
headers: expect.any(Number),
initialBlockDownload: false,
peers: 1,
status: 'up'
}

},
error: {},
info: {
blockchain: {
blocks: expect.any(Number),
headers: expect.any(Number),
initialBlockDownload: false,
peers: 1,
status: 'up'
}
},
status: 'ok'
}
})
expect(res.statusCode).toStrictEqual(200)
})
})
41 changes: 41 additions & 0 deletions apps/ocean-api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"private": false,
"name": "@defichain-apps/ocean-api",
"version": "0.0.0",
"description": "DeFiChain Jellyfish Ecosystem",
"repository": "DeFiCh/jellyfish",
"bugs": "https://github.com/DeFiCh/jellyfish/issues",
"license": "MIT",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsc -b tsconfig.build.json"
},
"dependencies": {
"@defichain/ocean-api-client": "0.0.0",
"@defichain/jellyfish-api-core": "0.0.0",
"@defichain/jellyfish-api-jsonrpc": "0.0.0",
"@defichain/playground": "0.0.0",
"@nestjs/common": "^8.2.4",
"@nestjs/config": "^1.1.5",
"@nestjs/core": "^8.2.4",
"@nestjs/platform-fastify": "^8.2.4",
"@nestjs/schedule": "^1.0.2",
"@nestjs/terminus": "^8.0.3",
"cache-manager": "^3.6.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"joi": "^17.5.0"
},
"devDependencies": {
"@defichain/ocean-api-client": "0.0.0",
"@nestjs/cli": "^8.1.6",
"@nestjs/schematics": "^8.0.5",
"@nestjs/testing": "^8.2.4",
"@types/cache-manager": "^3.4.2",
"@types/cron": "^1.7.3"
}
}
37 changes: 37 additions & 0 deletions apps/ocean-api/src/controllers/ActuatorController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Controller, Get } from '@nestjs/common'
import { HealthCheck, HealthCheckResult, HealthCheckService } from '@nestjs/terminus'
import { ActuatorProbes } from '../modules/ActuatorModule'

@Controller('/_actuator')
export class ActuatorController {
constructor (
private readonly probes: ActuatorProbes,
private readonly health: HealthCheckService) {
}

/**
* Indicates whether the service is running.
* If the liveness probe fails, the service should be killed and subjected to a restart policy.
* @see https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#when-should-you-use-a-liveness-probe
*/
@Get('/probes/liveness')
@HealthCheck()
async liveness (): Promise<HealthCheckResult> {
return await this.health.check(this.probes.map(probe => {
return async () => await probe.liveness()
}))
}

/**
* Indicates whether the service is ready to respond to requests.
* If the readiness probe fails, the endpoints are not ready to receive and respond to any request.
* @see https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#when-should-you-use-a-readiness-probe
*/
@Get('/probes/readiness')
@HealthCheck()
async readiness (): Promise<HealthCheckResult> {
return await this.health.check(this.probes.map(probe => {
return async () => await probe.readiness()
}))
}
}
Loading

0 comments on commit bb5da70

Please sign in to comment.