Skip to content

Instantly share code, notes, and snippets.

@tecklund
Last active August 19, 2021 13:36
Show Gist options
  • Save tecklund/6ee19a0cb3aa3053a8ef5fbe935947f6 to your computer and use it in GitHub Desktop.
Save tecklund/6ee19a0cb3aa3053a8ef5fbe935947f6 to your computer and use it in GitHub Desktop.
import * as E from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'
import * as t from 'io-ts'
import { failure } from 'io-ts/PathReporter'
import { toRequestHandler } from 'hyper-ts/lib/express'
import express from 'express'
import * as H from 'hyper-ts'
import * as M from 'hyper-ts/lib/Middleware'
import pgPromise from 'pg-promise'
import * as TE from 'fp-ts/TaskEither'
import { IntFromString } from 'io-ts-types/IntFromString'
import { sequenceT } from 'fp-ts/Apply'
import * as D from 'io-ts/Decoder'
import * as A from 'fp-ts/Array'
import * as O from 'fp-ts/Option'
import {split} from 'fp-ts/string'
import * as RA from 'fp-ts/ReadonlyArray'
import * as NA from 'fp-ts/ReadonlyNonEmptyArray'
const pgp: pgPromise.IMain = pgPromise({})
const db = pgp('postgres://tim:postgres@localhost:5432/test');
const QuestionCodec = t.strict({
name: t.string,
text: t.string
})
export type Question = t.TypeOf<typeof QuestionCodec>
const badRequest = (message: Error) =>
pipe(
M.status(H.Status.BadRequest),
M.ichain(() => M.closeHeaders()),
M.ichain(() => M.send(message.message))
)
const username = 'tim'
const password = 'tim'
const checkAuth = (u: string, p:string) => `${u}:${p}` == `${username}:${password}`
const parseBasicAuth = (a:NA.ReadonlyNonEmptyArray<string>):O.Option<[string, string, string]> =>
pipe(
RA.lookup(1)(a),
O.map(s => Buffer.from(s, 'base64').toString()),
O.map(split(':')),
O.chain(dec => pipe(dec, RA.lookup(1), O.map(pass => [NA.head(a), NA.head(dec), pass])))
)
export const basicAuthDecoder = {
decode: (u:string) => pipe(
u,
split(' '),
parseBasicAuth,
O.map(([t, u, p]) => (t === 'Basic' && checkAuth(u, p)) ? D.success(u) : D.failure(u, "")),
O.getOrElse(() => D.failure(u, ""))
)
}
const basicAuthDecode = (x: unknown) =>
pipe(
D.string.decode(x),
E.chain(basicAuthDecoder.decode),
E.mapLeft(e => new Error("Unable to authenticate user"))
)
const headerDecode = M.decodeHeader('Authorization', basicAuthDecode)
const paramDecode = pipe(
M.decodeParam('question_id', IntFromString.decode),
M.mapLeft((e) => new Error(failure(e).join('\n')))
)
const doAPIWork = (id:number) =>
M.fromTaskEither(TE.tryCatch(() =>
db.any<Question>('select * from screening.question where question_id = $1', [id]), E.toError ))
const questionHandler = pipe(
sequenceT(M.ApplySeq)(headerDecode, paramDecode),
M.ichain(([user, id]) => doAPIWork(id)),
M.ichain((questions:Question[]) =>
pipe(
M.status<Error>(H.Status.OK),
M.ichain(() => M.json(questions, E.toError))
)
),
M.orElse(badRequest)
)
const app = express()
app
.get('/questions/:question_id', toRequestHandler(questionHandler))
.use((req, res, next) => res.status(404).send("Sorry can't find that!"))
.listen(3000, () => console.log('Express listening on port 3000. Use: GET /'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment