Last active
August 19, 2021 13:36
-
-
Save tecklund/6ee19a0cb3aa3053a8ef5fbe935947f6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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