Skip to content

Commit

Permalink
feat(api): migrate API to TypeScript and fix stale mappings and examples
Browse files Browse the repository at this point in the history
  • Loading branch information
plouc committed Dec 17, 2021
1 parent 4ae8bc6 commit d53681b
Show file tree
Hide file tree
Showing 124 changed files with 2,667 additions and 2,484 deletions.
8 changes: 0 additions & 8 deletions api/misc/readmeData.js
@@ -1,11 +1,3 @@
/*
* This file is part of the nivo project.
*
* (c) 2016 Raphaël Benitte
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict'

const _ = require('lodash')
Expand Down
12 changes: 12 additions & 0 deletions api/nodemon.json
@@ -0,0 +1,12 @@
{
"restartable": "rs",
"ignore": ["node_modules/", "dist/", "coverage/"],
"watch": ["src/"],
"execMap": {
"ts": "node -r ts-node/register"
},
"env": {
"NODE_ENV": "development"
},
"ext": "js,json,ts"
}
23 changes: 16 additions & 7 deletions api/package.json
Expand Up @@ -42,22 +42,31 @@
"@nivo/treemap": "0.74.1",
"@nivo/voronoi": "0.74.1",
"@nivo/waffle": "0.74.1",
"@types/body-parser": "^1.19.2",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/node": "^16.11.12",
"@types/react": "17.0.11",
"@types/react-dom": "17.0.8",
"@types/uuid": "^8.3.3",
"body-parser": "^1.17.2",
"compression": "^1.7.0",
"cors": "^2.8.4",
"express": "^4.15.4",
"express-winston": "^2.4.0",
"joi": "^14.3.0",
"cors": "2.8.5",
"express": "4.17.1",
"express-winston": "4.2.0",
"joi": "17.5.0",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"ts-node": "^10.4.0",
"typescript": "^4.3.4",
"uuid": "^3.1.0",
"winston": "^2.3.1"
"winston": "3.3.3"
},
"scripts": {
"readme": "node misc/readmeData.js | mustache - misc/README.mustache > README.md",
"start": "node src/app.js",
"dev": "nodemon src/app.js"
"start": "node src/app.ts",
"dev": "nodemon src/app.ts"
},
"publishConfig": {
"access": "public"
Expand Down
68 changes: 32 additions & 36 deletions api/src/app.js → api/src/app.ts
@@ -1,41 +1,28 @@
/*
* This file is part of the nivo project.
*
* (c) 2016 Raphaël Benitte
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
'use strict'

const express = require('express')
const cors = require('cors')
const bodyParser = require('body-parser')
const compression = require('compression')
const path = require('path')
const uuid = require('uuid')
const _ = require('lodash')
const winston = require('winston')
const expressWinston = require('express-winston')
import path from 'path'
import express from 'express'
import { forOwn } from 'lodash'
import * as uuid from 'uuid'
import cors from 'cors'
import bodyParser from 'body-parser'
import compression from 'compression'
import winston from 'winston'
import expressWinston from 'express-winston'
import { renderChart } from './lib/renderer'
import * as storage from './lib/storage'
import { validate } from './lib/validation'
import samples from './samples'
import { chartsMapping, ChartType } from './mapping'

const app = express()
const validate = require('./lib/middlewares/validationMiddleware')
const storage = require('./lib/storage')
const mapping = require('./mapping')
const samples = require('./samples')
const render = require('./lib/render')

app.enable('trust proxy')
app.set('json spaces', 4)
app.use(cors())
app.use(bodyParser.json())
app.use(
expressWinston.logger({
transports: [
new winston.transports.Console({
json: false,
colorize: true,
}),
],
transports: [new winston.transports.Console()],
format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
meta: false,
expressFormat: true,
colorize: true,
Expand All @@ -44,7 +31,6 @@ app.use(
app.use(compression())

app.get('/', (req, res) => {
console.log('sending')
res.sendFile(path.resolve(__dirname, 'api.yml'))
})

Expand All @@ -55,13 +41,15 @@ app.get('/status', (req, res) => {
protocol: req.protocol,
host: req.get('host'),
env: {
// @ts-ignore
NODE_ENV: process.NODE_ENV,
},
})
})

_.forOwn(mapping, ({ schema }, type) => {
forOwn(chartsMapping, ({ schema }, type: ChartType) => {
app.post(`/charts/${type}`, validate(schema), (req, res) => {
// @ts-ignore
const props = req.payload
const id = uuid.v4()
const url = `${req.protocol}://${req.get('host')}/r/${id}`
Expand All @@ -82,20 +70,28 @@ app.get('/r', (req, res) => {

app.get('/r/:id', (req, res) => {
const { id } = req.params
const config = storage.get(req.params.id)

const config = storage.get(req.params.id)
if (!config) {
return res.status(404).send(`no chart found for id "${id}"`)
}

const rendered = render.chart(config, req.query)
const rendered = renderChart(config, req.query)

res.set('Content-Type', 'image/svg+xml').status(200).send(rendered)
})

_.forOwn(samples, (config, id) => {
app.get('/samples', (req, res) => {
res.status(200).json({
samples: Object.keys(samples).map(sample => {
return `${req.protocol}://${req.get('host')}/samples/${sample}.svg`
}),
})
})

forOwn(samples, (config, id) => {
app.get(`/samples/${id}.svg`, (req, res) => {
const rendered = render.chart(config, req.query)
const rendered = renderChart(config, req.query)

res.set('Content-Type', 'image/svg+xml').status(200).send(rendered)
})
Expand Down
36 changes: 0 additions & 36 deletions api/src/lib/middlewares/validationMiddleware.js

This file was deleted.

34 changes: 0 additions & 34 deletions api/src/lib/render/index.js

This file was deleted.

36 changes: 36 additions & 0 deletions api/src/lib/renderer.ts
@@ -0,0 +1,36 @@
import { pick } from 'lodash'
import React, { FunctionComponent } from 'react'
import { renderToStaticMarkup } from 'react-dom/server'
import { ChartProps, chartsMapping, ChartType } from '../mapping'

const staticProps = {
animate: false,
isInteractive: false,
renderWrapper: false,
theme: {},
}

export const renderChart = <T extends ChartType>(
{
type,
props,
}: {
type: T
props: ChartProps<T>
},
override
) => {
const chart = chartsMapping[type]
const component = chart.component as FunctionComponent<ChartProps<T>>
const mergedProps = {
...staticProps,
...chart.defaults,
...props,
...pick(override, chart.runtimeProps || []),
}
const rendered = renderToStaticMarkup(
React.createElement<ChartProps<T>>(component, mergedProps)
)

return `<?xml version="1.0" ?>${rendered}`
}
19 changes: 19 additions & 0 deletions api/src/lib/storage.ts
@@ -0,0 +1,19 @@
import { ChartProps, ChartType } from '../mapping'

export interface StorageEntry<T extends ChartType> {
type: T
props: ChartProps<T>
url: string
}

const store: Record<string, StorageEntry<ChartType>> = {}

export const set = (key: string, value: StorageEntry<ChartType>) => {
store[key] = value
}

export const get = (key: string): StorageEntry<ChartType> | undefined => {
return store[key]
}

export const dump = () => store
21 changes: 0 additions & 21 deletions api/src/lib/storage/index.js

This file was deleted.

1 change: 1 addition & 0 deletions api/src/lib/types.ts
@@ -0,0 +1 @@
export type OmitStrict<T, K extends keyof T> = T extends any ? Pick<T, Exclude<keyof T, K>> : never
33 changes: 33 additions & 0 deletions api/src/lib/validation.ts
@@ -0,0 +1,33 @@
import { omit } from 'lodash'
import joi from 'joi'

export const validate = (
schema: joi.Schema,
options: {
omit?: string[]
} = {}
) => {
const { omit: omitProps } = options

return (req, res, next) => {
let data = req.body
if (omit) {
data = omit(data, omitProps)
}

try {
req.payload = joi.attempt(data, schema, null, {
abortEarly: true,
convert: true,
})
next()
} catch (err) {
console.log('ERROR', err)
return res.status(400).json({
errors: err.details.map(({ message, path }) => {
return `${message}${path ? ` (${path})` : ''}`
}),
})
}
}
}

0 comments on commit d53681b

Please sign in to comment.