Skip to content

Commit

Permalink
Merge pull request #72 from citycoins/develop
Browse files Browse the repository at this point in the history
V2 Release: multiple version support
  • Loading branch information
whoabuddy authored May 31, 2022
2 parents 37225f9 + b30b1d4 commit fe4d6ac
Show file tree
Hide file tree
Showing 65 changed files with 2,863 additions and 1,180 deletions.
39 changes: 25 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ CF Workers + IttyRouter + micro-stacks + TypeScript
## Things to Note

- uses simple typed responses and provides detailed error messages
- all `:cityname` routes accept three letter city names, e.g. mia, nyc
- all `:blockheight` routes always follow `:cityname` routes when required
- all additional parameters follow `:cityname` and `:blockheight` routes
- all CityCoin contract routes start with `:version` and `:cityname`
- e.g. `/v1/mia/mining/get-mining-stats-at-block/57934`
- `:version` accepts the major CityCoins contract version, e.g. v1, v2
- `:cityname` routes accept three letter city names, e.g. mia, nyc
- all additional parameters follow the order of operations below
- `:blockheight > :cycleid > :userid > :address`
- routes are structured the same as the contract functions and documentation

## Implementation
Expand All @@ -24,35 +27,43 @@ Static assets in the `/static` folder are pushed to Cloudflare Workers KV store

All other paths are passed to the IttyRouter in `handler.ts`.

The API is divided into three main sections:
The API is divided into three main folders:

- handlers: individual endpoints that get/caculate a value and return the result
- handlers: individual endpoints that get/calculate a value and return the result
- lib: libraries to get or calculate data for handlers
- types: type definitions for utilities and responses

### How to Add a City

- add new city config as constant in `/src/types/cities.ts`
- update getCityConfig in `cities.ts` with case for new city
- update enum in `/static/openapi.yml` for reusable parameters
- add new CityInfo constant in `/src/types/cities.ts`
- add new CityConfig constant in `/src/types/cities.ts`
- update the functions below in `cities.ts` with case for new city
- getCityInfo()
- getFullCityInfo()
- getCityConfig()
- getFullCityConfig()
- update cityname enum in `/static/openapi.yml` for reusable parameters

### How to Add an Endpoint

- create a new handler file in `/src/handlers`
- all inputs must be checked or 400
- city config must resolve or 404
- any integers verified with `isStringAllDigits` or 400
- response from getter or calcualation checked or 404
- response from getter or calculation checked or 404
- returns successful response
- (optional) add new getters in `/lib`
- (optional) add new types in `/types`
- add new handler file to top-level export in `/src/handlers`
- e.g. `export { default as GetDateAtBlock } from './stacks/getdateatblock'`
- add new handler file and route to `/src/handler.ts`
- Order of Operations: `:cityname > :blockheight > :cycleid > :userid > :address`
- if querying city data, starts with: `:version/:cityname/`
- order of operations: `:blockheight > :cycleid > :userid > :address`
- add new endpoint to `/static/openapi.yml`
- routes get added to the corresponding section
- routes get tagged by their category (matches directory)
- routes always use ref for parameters and responses
- reusable parameters and responses are at the bottom of the file
- reusable parameters, schemas and responses are at the bottom of the file

**Special case:** if the response is a custom type, e.g. `MiningStatsAtBlock`, an example for the responses must be added manually to `/static/openapi.yml`

Expand Down Expand Up @@ -81,9 +92,9 @@ A full list of routes and responses can be found in the [OpenAPI documentation](
Some quick examples:

- [Get the current Stacks block height](https://api.citycoins.co/stacks/get-block-height)
- [Get the activation block height for MIA](https://api.citycoins.co/activation/get-activation-block/mia)
- [Get the mining stats at block 49000 for MIA](https://api.citycoins.co/mining/get-mining-stats-at-block/mia/49000)
- [Get the total supply for MIA](https://api.citycoins.co/token/get-total-supply/mia)
- [Get the activation block height for MIA](https://api.citycoins.co/v1/mia/activation/get-activation-block)
- [Get the mining stats at block 49000 for MIA](https://api.citycoins.co/v1/mia/mining/get-mining-stats-at-block/49000)
- [Get the total supply for MIA](https://api.citycoins.co/v2/mia/token/get-total-supply)

> “Continuous effort, not strength or intelligence
> is the key to unlocking our potential.”
Expand Down
28 changes: 14 additions & 14 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "citycoins-api",
"version": "1.1.0",
"version": "2.0.0",
"description": "A simple API to interact with Stacks and CityCoins data.",
"main": "dist/worker.js",
"scripts": {
Expand Down
85 changes: 0 additions & 85 deletions src/handler.ts

This file was deleted.

8 changes: 8 additions & 0 deletions src/handlers/activation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// activation exports

export { default as GetActivationBlock } from './activation/getactivationblock'
export { default as GetActivationTarget } from './activation/getactivationtarget'
export { default as GetCityWallet } from './activation/getcitywallet'
export { default as GetRegisteredUsersNonce } from './activation/getregisteredusersnonce'
export { default as GetUser } from './activation/getuser'
export { default as GetUserId } from './activation/getuserid'
41 changes: 25 additions & 16 deletions src/handlers/activation/getactivationblock.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
import { Request as IttyRequest } from 'itty-router'
import { getActivationBlock } from '../../lib/citycoins'
import { createSingleValue } from '../../lib/common'
import { getCityConfig } from '../../types/cities'
import { createResponse } from '../../lib/common'
import { CityConfig, getCityConfig } from '../../types/cities'
import { SingleValue } from '../../types/common'

const GetActivationBlock = async (request: IttyRequest): Promise<Response> => {
let cityConfig: CityConfig
let activationBlock: string
let response: SingleValue | boolean | number | string
// check inputs
const version = request.params?.version ?? undefined
const city = request.params?.cityname ?? undefined
if (city === undefined) {
return new Response(`Invalid request, missing parameter(s)`, { status: 400 })
if (version === undefined || city === undefined) {
return new Response(`Invalid request, missing parameter(s)`, {
status: 400,
})
}
// get city configuration object
const cityConfig = await getCityConfig(city)
if (cityConfig.deployer === '') {
return new Response(`City name not found: ${city}`, { status: 404 })
// check response output format
let format = 'json'
const { query } = request
if (Object.prototype.hasOwnProperty.call(query, 'format')) {
if (query?.format !== undefined) format = query.format
}
// get activation block
const activationBlock = await getActivationBlock(cityConfig)
// return response
const response: SingleValue = await createSingleValue(activationBlock)
const headers = {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
// get/calculate response
try {
cityConfig = await getCityConfig(city, version)
activationBlock = await getActivationBlock(cityConfig)
response = await createResponse(activationBlock, format)
} catch (err) {
if (err instanceof Error) return new Response(err.message, { status: 404 })
return new Response(String(err), { status: 404 })
}
return new Response(JSON.stringify(response), { headers })
// return response
return new Response(JSON.stringify(response))
}

export default GetActivationBlock
38 changes: 38 additions & 0 deletions src/handlers/activation/getactivationtarget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Request as IttyRequest } from 'itty-router'
import { getActivationTarget } from '../../lib/citycoins'
import { createResponse } from '../../lib/common'
import { CityConfig, getCityConfig } from '../../types/cities'
import { SingleValue } from '../../types/common'

const GetActivationTarget = async (request: IttyRequest): Promise<Response> => {
let cityConfig: CityConfig
let activationTarget: string
let response: SingleValue | boolean | number | string
// check inputs
const version = request.params?.version ?? undefined
const city = request.params?.cityname ?? undefined
if (version === undefined || city === undefined) {
return new Response(`Invalid request, missing parameter(s)`, {
status: 400,
})
}
// check response output format
let format = 'json'
const { query } = request
if (Object.prototype.hasOwnProperty.call(query, 'format')) {
if (query?.format !== undefined) format = query.format
}
// get/calculate response
try {
cityConfig = await getCityConfig(city, version)
activationTarget = await getActivationTarget(cityConfig)
response = await createResponse(activationTarget, format)
} catch (err) {
if (err instanceof Error) return new Response(err.message, { status: 404 })
return new Response(String(err), { status: 404 })
}
// return response
return new Response(JSON.stringify(response))
}

export default GetActivationTarget
38 changes: 38 additions & 0 deletions src/handlers/activation/getcitywallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Request as IttyRequest } from 'itty-router'
import { getCityWallet } from '../../lib/citycoins'
import { createResponse } from '../../lib/common'
import { CityConfig, getCityConfig } from '../../types/cities'
import { SingleValue } from '../../types/common'

const GetCityWallet = async (request: IttyRequest): Promise<Response> => {
let cityConfig: CityConfig
let cityWallet: string
let response: SingleValue | boolean | number | string
// check inputs
const version = request.params?.version ?? undefined
const city = request.params?.cityname ?? undefined
if (version === undefined || city === undefined) {
return new Response(`Invalid request, missing parameter(s)`, {
status: 400,
})
}
// check response output format
let format = 'json'
const { query } = request
if (Object.prototype.hasOwnProperty.call(query, 'format')) {
if (query?.format !== undefined) format = query.format
}
// get/calculate response
try {
cityConfig = await getCityConfig(city, version)
cityWallet = await getCityWallet(cityConfig)
response = await createResponse(cityWallet, format)
} catch (err) {
if (err instanceof Error) return new Response(err.message, { status: 404 })
return new Response(String(err), { status: 404 })
}
// return response
return new Response(JSON.stringify(response))
}

export default GetCityWallet
Loading

0 comments on commit fe4d6ac

Please sign in to comment.