Skip to content

Commit

Permalink
Feature/process io (#14)
Browse files Browse the repository at this point in the history
* Upgraded lib

* Beginning to refactor away non-IO uses of Process API

* Trying to integrate Process IO code into express server startup

* Finished refactoring express startup to use IO

* Refactored more uses of the Process API

* Even more process refactoring

* More process refactoring

* Added TODO

* Migrated more logic to new IO-based Process API

* More progress on refactoring Process API uses

* More process refactoring

* Small refactoring

* Even more process refactoring

* Removed emergency error log

* Continuing the process refactor

* Even more progress with refactor

* Continuing the refactor

* Still just continuing the refactor

* Continuing refactor

* Even more progress

* Continuing cleanup

* More refactoring

* Fixed a bunch of compile errors

* More refactoring

* Even more progress

* Cleaned up TODO

* Fixing broken tests

* Fixed broken route

* Fixed another broken test

* Fixed all tests
  • Loading branch information
craigmiller160 authored Feb 11, 2022
1 parent ef6eee7 commit bc924e8
Show file tree
Hide file tree
Showing 27 changed files with 341 additions and 304 deletions.
8 changes: 7 additions & 1 deletion src/express/Route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextFunction, Request, Response } from 'express';
import { ReaderTaskT, TaskT } from '@craigmiller160/ts-functions/types';
import { IOT, ReaderTaskT, TaskT } from '@craigmiller160/ts-functions/types';
import { ExpressDependencies } from './ExpressDependencies';

export type Route = (req: Request, res: Response, next: NextFunction) => void;
Expand All @@ -15,3 +15,9 @@ export type TaskRoute<T> = (
res: Response,
next: NextFunction
) => TaskT<T>;

export type IORoute<T> = (
req: Request,
res: Response,
next: NextFunction
) => IOT<T>;
22 changes: 12 additions & 10 deletions src/express/auth/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@ import { Request } from 'express';
import * as Option from 'fp-ts/Option';
import { pipe } from 'fp-ts/function';
import { ExtractJwt, JwtFromRequestFunction } from 'passport-jwt';
import * as Pred from 'fp-ts/Predicate';
import { PredicateT, OptionT } from '@craigmiller160/ts-functions/types';
import * as Process from '@craigmiller160/ts-functions/Process';
import * as IO from 'fp-ts/IO';

export const isJwtInCookie: Pred.Predicate<Request> = (req) =>
export const isJwtInCookie: PredicateT<Request> = (req) =>
pipe(
Option.fromNullable(process.env.COOKIE_NAME),
Option.chain((_) => Option.fromNullable(req.cookies[_])),
Option.isSome
);
Process.envLookupO('COOKIE_NAME'),
IO.map(Option.chain((_) => Option.fromNullable(req.cookies[_]))),
IO.map(Option.isSome)
)();

const getJwtFromCookie = (req: Request): Option.Option<string> =>
const getJwtFromCookie = (req: Request): OptionT<string> =>
pipe(
Option.fromNullable(process.env.COOKIE_NAME),
Option.chain((_) => Option.fromNullable(req.cookies[_]))
);
Process.envLookupO('COOKIE_NAME'),
IO.map(Option.chain((_) => Option.fromNullable(req.cookies[_])))
)();

export const jwtFromRequest: JwtFromRequestFunction = (req) =>
pipe(
Expand Down
41 changes: 26 additions & 15 deletions src/express/auth/passport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,37 @@ import { logger } from '../../logger';
import { Strategy as JwtStrategy, StrategyOptions } from 'passport-jwt';
import passport from 'passport';
import { pipe } from 'fp-ts/function';
import * as Either from 'fp-ts/Either';
import { UnauthorizedError } from '../../error/UnauthorizedError';
import { AccessToken } from './AccessToken';
import * as Pred from 'fp-ts/Predicate';
import * as Try from '@craigmiller160/ts-functions/Try';
import { jwtFromRequest } from './jwt';
import { getRequiredValues } from '../../function/Values';
import { ReaderT } from '@craigmiller160/ts-functions/types';
import {
IOT,
IOTryT,
OptionT,
ReaderT
} from '@craigmiller160/ts-functions/types';
import { ExpressDependencies } from '../ExpressDependencies';
import * as Process from '@craigmiller160/ts-functions/Process';
import { getRequiredValues } from '../../function/Values';
import * as IO from 'fp-ts/IO';
import * as IOEither from 'fp-ts/IOEither';

interface ClientKeyName {
readonly clientKey: string;
readonly clientName: string;
}

const getClientKeyAndName = (): Try.Try<ClientKeyName> => {
const envArray: ReadonlyArray<string | undefined> = [
process.env.CLIENT_KEY,
process.env.CLIENT_NAME
const getClientKeyAndName = (): IOTryT<ClientKeyName> => {
const envArray: ReadonlyArray<IOT<OptionT<string>>> = [
Process.envLookupO('CLIENT_KEY'),
Process.envLookupO('CLIENT_NAME')
];

return pipe(
getRequiredValues(envArray),
Either.map(
IO.sequenceArray(envArray),
IO.map(getRequiredValues),
IOEither.map(
([clientKey, clientName]): ClientKeyName => ({
clientKey,
clientName
Expand Down Expand Up @@ -54,18 +61,22 @@ export const createPassportValidation: ReaderT<ExpressDependencies, void> = ({
const doValidatePayload = validatePayload(payload);
pipe(
getClientKeyAndName(),
Either.filterOrElse<ClientKeyName, Error>(
IOEither.filterOrElse<Error, ClientKeyName>(
doValidatePayload,
() =>
new UnauthorizedError(
'Invalid token payload attributes'
)
),
Either.fold(
(ex) => done(ex, null),
() => done(null, payload)
IOEither.fold(
(ex) => () => {
done(ex, null);
},
() => () => {
done(null, payload);
}
)
);
)();
})
);
};
4 changes: 2 additions & 2 deletions src/express/controllers/oauth.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { secure, secureReaderTask } from '../auth/secure';
import * as oAuthService from '../../services/routes/OAuthService';
import * as Reader from 'fp-ts/Reader';
import { Controller } from './Controller';
import { readerTaskRouteToReaderRoute } from '../readerTaskRouteToReaderRoute';
import { ioRouteToReaderRoute } from '../ioRouteToReaderRoute';

export const getAuthUser: Controller = secure(oAuthService.getAuthUser);

export const getAuthCodeLogin: Controller = Reader.of(
export const getAuthCodeLogin: Controller = ioRouteToReaderRoute(
oAuthService.getAuthCodeLogin
);

Expand Down
2 changes: 0 additions & 2 deletions src/express/expressErrorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { pipe } from 'fp-ts/function';
import { ReaderT } from '@craigmiller160/ts-functions/types';
import * as Reader from 'fp-ts/Reader';
import { ExpressDependencies } from './ExpressDependencies';
import { emergencyErrorLog } from '../logger/emergencyErrorLog';

interface ErrorResponse {
readonly timestamp: string;
Expand Down Expand Up @@ -49,7 +48,6 @@ const createErrorResponse = (
const fullQueryString = queryString.length > 0 ? `?${queryString}` : '';

const timestamp = format(new Date(), 'yyyy-MM-dd HH:mm:ss.SSS');
emergencyErrorLog(timestamp, err);

return {
timestamp,
Expand Down
41 changes: 30 additions & 11 deletions src/express/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import * as Option from 'fp-ts/Option';
import * as TaskEither from 'fp-ts/TaskEither';
import { TaskTryT, OptionT, TaskT } from '@craigmiller160/ts-functions/types';

import {
TaskTryT,
OptionT,
TaskT,
IOT
} from '@craigmiller160/ts-functions/types';
import * as Process from '@craigmiller160/ts-functions/Process';
import bodyParer from 'body-parser';
import { logger } from '../logger';
import { flow, pipe } from 'fp-ts/function';
Expand All @@ -27,16 +32,22 @@ import {
watchlistRepository
} from '../data/repo';
import * as Reader from 'fp-ts/Reader';
import * as IO from 'fp-ts/IO';
import * as IOEither from 'fp-ts/IOEither';

const safeParseInt = (text: string): OptionT<number> =>
match(parseInt(text))
.with(__.NaN, () => Option.none)
.otherwise((_) => Option.some(_));

const expressListen = (app: Express, port: number): TaskTryT<Server> => {
const expressListen = (
app: Express,
port: number,
nodeEnv: string
): TaskTryT<Server> => {
const server = https.createServer(httpsOptions, app);

return match(process.env.NODE_ENV)
return match(nodeEnv)
.with('test', () => TaskEither.right(server))
.otherwise(() => wrapListen(server, port));
};
Expand Down Expand Up @@ -99,24 +110,32 @@ const createExpressApp = (tokenKey: TokenKey): Express => {
return app;
};

const getPort = (): number =>
const getPort = (): IOT<number> =>
pipe(
Option.fromNullable(process.env.EXPRESS_PORT),
Option.chain(safeParseInt),
Option.getOrElse(() => 8080)
Process.envLookupO('EXPRESS_PORT'),
IO.map(
flow(
Option.chain(safeParseInt),
Option.getOrElse(() => 8080)
)
)
);

export const startExpressServer = (
tokenKey: TokenKey
): TaskTryT<ExpressServer> => {
const port = getPort();

logger.debug('Starting server');

const app = createExpressApp(tokenKey);

return pipe(
expressListen(app, port),
IOEither.fromIO<number, Error>(getPort()),
IOEither.bindTo('port'),
IOEither.bind('nodeEnv', () => Process.envLookupE('NODE_ENV')),
TaskEither.fromIOEither,
TaskEither.chain(({ port, nodeEnv }) =>
expressListen(app, port, nodeEnv)
),
TaskEither.map((_) => ({
server: _,
app
Expand Down
9 changes: 9 additions & 0 deletions src/express/ioRouteToReaderRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ReaderT } from '@craigmiller160/ts-functions/types';
import { IORoute, Route } from './Route';
import { ExpressDependencies } from './ExpressDependencies';

export const ioRouteToReaderRoute =
<T>(fn: IORoute<T>): ReaderT<ExpressDependencies, Route> =>
() =>
(req, res, next) =>
fn(req, res, next)();
19 changes: 19 additions & 0 deletions src/function/Values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,31 @@ import * as Option from 'fp-ts/Option';
import { flow, pipe } from 'fp-ts/function';
import { MissingValuesError } from '../error/MissingValuesError';
import {
OptionT,
ReadonlyNonEmptyArrayT,
TryT
} from '@craigmiller160/ts-functions/types';
import * as RNonEmptyArray from 'fp-ts/ReadonlyNonEmptyArray';
import * as Json from '@craigmiller160/ts-functions/Json';

export const getRequiredValues = (
valuesArray: ReadonlyArray<OptionT<string>>
): TryT<ReadonlyArray<string>> =>
pipe(
valuesArray,
Option.sequenceArray,
Either.fromOption(
() =>
new MissingValuesError(
`Missing required values: ${Option.getOrElse(() => 'Error')(
Json.stringifyO(valuesArray)
)}`
)
)
);

// TODO delete this
export const getRequiredValues2 = (
valuesArray: ReadonlyArray<string | undefined>
): TryT<ReadonlyNonEmptyArrayT<string>> =>
pipe(
Expand Down
10 changes: 6 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import * as TaskEither from 'fp-ts/TaskEither';
import { startExpressServer } from './express';
import { logAndReturn, logger } from './logger';
import { loadTokenKey } from './services/auth/TokenKey';
import * as Process from '@craigmiller160/ts-functions/Process';
import * as Task from 'fp-ts/Task';

logger.info('Starting application');

Expand All @@ -13,8 +15,8 @@ pipe(
TaskEither.chainFirst(connectToMongo),
TaskEither.chain(startExpressServer),
TaskEither.mapLeft(logAndReturn('error', 'Error starting application')),
TaskEither.mapLeft((_) => {
process.exit(1);
return _;
})
TaskEither.fold(
() => Task.fromIO(Process.exit(1)),
() => async () => ''
)
)();
31 changes: 0 additions & 31 deletions src/logger/emergencyErrorLog.ts

This file was deleted.

47 changes: 30 additions & 17 deletions src/logger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import path from 'path';
import * as RArray from 'fp-ts/ReadonlyArray';
import * as RArrayExt from '@craigmiller160/ts-functions/ReadonlyArrayExt';
import { pipe } from 'fp-ts/function';
import { PredicateT } from '@craigmiller160/ts-functions/types';
import * as Process from '@craigmiller160/ts-functions/Process';
import * as Option from 'fp-ts/Option';
import * as IO from 'fp-ts/IO';

type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'verbose';

Expand All @@ -15,25 +19,34 @@ const myFormat = format.printf(
`[${timestamp}] [${level}] - ${stack ?? message}`
);

const isNotProduction = (): boolean => process.env.NODE_ENV !== 'production';
const isNotProduction: PredicateT<void> = pipe(
Process.envLookupO('NODE_ENV'),
IO.map(Option.filter((_) => _ !== 'production')),
IO.map(Option.isSome)
);

const theTransports: ReadonlyArray<TransportStream> = pipe(
[
new transports.Console(),
isNotProduction()
? new transports.File({
filename: path.join(
process.cwd(),
'logs',
'market-tracker.log'
),
maxsize: 100_000,
maxFiles: 10
})
: null
],
RArray.filter((_) => _ !== null)
) as ReadonlyArray<TransportStream>;
Process.cwd(),
IO.map((cwd) =>
pipe(
[
new transports.Console(),
isNotProduction()
? new transports.File({
filename: path.join(
cwd,
'logs',
'market-tracker.log'
),
maxsize: 100_000,
maxFiles: 10
})
: null
],
RArray.filter((_) => _ !== null)
)
)
)() as ReadonlyArray<TransportStream>;

export const logger = createLogger({
level: 'debug',
Expand Down
Loading

0 comments on commit bc924e8

Please sign in to comment.