Skip to content

Commit

Permalink
WIP: finally figured out all of the modules nonsense, good grief what…
Browse files Browse the repository at this point in the history
… a mess.
  • Loading branch information
jeffdc committed Dec 24, 2023
1 parent a49c736 commit 4bb48fc
Show file tree
Hide file tree
Showing 17 changed files with 88 additions and 47 deletions.
26 changes: 26 additions & 0 deletions __tests__/unit/utils/io-ts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { fromEnum, decodeWithDefault } from '../../../libs/utils/io-ts';
import * as E from 'fp-ts/lib/Either';

enum Foo {
FOO = 'foo',
BAR = 'bar',
}

const FooSchema = fromEnum('Foo', Foo);

test('fromEnum works', () => {
expect(FooSchema).toBeTruthy();
expect(FooSchema.name).toBe('Foo');
expect(FooSchema.is(Foo.FOO)).toBeTruthy();
expect(FooSchema.is(Foo.BAR)).toBeTruthy();
expect(FooSchema.is(null)).toBeFalsy();
expect(FooSchema.is(true)).toBeFalsy();
expect(FooSchema.encode(Foo.FOO)).toBe(Foo.FOO);
expect(E.isRight(FooSchema.decode(Foo.FOO))).toBeTruthy();
expect(E.isLeft(FooSchema.decode(null))).toBeTruthy();
});

test('decodeWithDefault works', () => {
expect(decodeWithDefault(E.right(Foo.FOO), Foo.BAR)).toBe(Foo.FOO);
expect(decodeWithDefault(E.left(Foo.FOO), Foo.BAR)).toBe(Foo.BAR);
});
8 changes: 3 additions & 5 deletions components/images.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import useIsMounted from '../hooks/useIsMounted';
import useWindowDimensions from '../hooks/usewindowdimension';
import { ImageApi, ImageLicenseValues, ImageNoSourceApi, SpeciesApi, TaxonCodeValues } from '../libs/api/apitypes';
import { hasProp } from '../libs/utils/util';
import NoImage from '../public/images/noimage.jpg';
import NoImageHost from '../public/images/noimagehost.jpg';

// type guard for dealing with possible Images without Source data. If this happens there is an upstream
// programming error so we will fail fast and hard.
Expand Down Expand Up @@ -50,11 +52,7 @@ const Images = ({ sp }: Props): JSX.Element => {
return species.images.length < 1 ? (
<div className="p-2">
<Image
src={
species.taxoncode === TaxonCodeValues.GALL
? '../public/images/noimage.jpg'
: '../public/images/noimagehost.jpg'
}
src={species.taxoncode === TaxonCodeValues.GALL ? NoImage : NoImageHost}
alt={`missing image of ${species.name}`}
className="img-fluid d-block"
/>
Expand Down
14 changes: 9 additions & 5 deletions components/seealso.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import React from 'react';
import { Col, Row } from 'react-bootstrap';
import Image from 'next/image.js';
import iNatLogo from '../public/images/inatlogo-small.png';
import BugGuideLogo from '../public/images/bugguide-small.png';
import GScholarLogo from '../public/images/gscholar-small.png';
import BHLLogo from '../public/images/bhllogo.png';

// we allow species names to contain subspecies of the form 'Genus species subspecies' and for gallformers
// sexual generation info 'Genus species (sexgen)'. For external linking we want to only link to the main species.
Expand Down Expand Up @@ -53,7 +57,7 @@ const SeeAlso = ({ name, undescribed }: Props): JSX.Element => {
rel="noreferrer"
aria-label="Search for more information about this species on iNaturalist."
>
<Image src="../public/images/inatlogo-small.png" alt="iNaturalist logo" />
<Image src={iNatLogo} alt="iNaturalist logo" />
</a>
</Col>
<Col xs={12} md={6} lg={3} className="align-self-center">
Expand All @@ -63,7 +67,7 @@ const SeeAlso = ({ name, undescribed }: Props): JSX.Element => {
rel="noreferrer"
aria-label="Search for more information about this species on BugGuide."
>
<Image src="../public/images/bugguide-small.png" alt="BugGuide logo" />
<Image src={BugGuideLogo} alt="BugGuide logo" />
</a>
</Col>
<Col xs={12} md={6} lg={3} className="align-self-center">
Expand All @@ -73,7 +77,7 @@ const SeeAlso = ({ name, undescribed }: Props): JSX.Element => {
rel="noreferrer"
aria-label="Search for more information about this species on Google Scholar."
>
<Image src="../public/images/gscholar-small.png" alt="Google Scholar logo" />
<Image src={GScholarLogo} alt="Google Scholar logo" />
</a>
</Col>
<Col xs={12} md={6} lg={3} className="align-self-center">
Expand All @@ -83,7 +87,7 @@ const SeeAlso = ({ name, undescribed }: Props): JSX.Element => {
rel="noreferrer"
aria-label="Search for more information about this species at the Biodiversity Heritage Library."
>
<Image src="../public/images/bhllogo.png" alt="Biodiversity Heritage Library logo" />
<Image src={BHLLogo} alt="Biodiversity Heritage Library logo" />
</a>
</Col>
</Row>
Expand All @@ -103,7 +107,7 @@ const SeeAlso = ({ name, undescribed }: Props): JSX.Element => {
rel="noreferrer"
aria-label="Search for more information about this species on iNaturalist."
>
<Image src="../public/images/inatlogo-small.png" alt="iNaturalist logo" />
<Image src={iNatLogo} alt="iNaturalist logo" />
</a>
</Col>
</Row>
Expand Down
5 changes: 2 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const nextJest = require('next/jest');
import nextJest from 'next/jest.js';
const createJestConfig = nextJest({
dir: './',
});
Expand All @@ -16,4 +15,4 @@ const customJestConfig = {
moduleDirectories: ['node_modules', '<rootDir>/'],
testEnvironment: 'jest-environment-jsdom',
};
module.exports = createJestConfig(customJestConfig);
export default createJestConfig(customJestConfig);
2 changes: 1 addition & 1 deletion layouts/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const Footer = (): JSX.Element => {
};

return (
<Navbar expand="sm" variant="dark" collapseOnSelect className="navbar-footer container-fluid px-4">
<Navbar expand="sm" variant="dark" collapseOnSelect className="navbar-footer fixed-bottom container-fluid px-4">
<Navbar.Collapse>
{mounted && session && (
<>
Expand Down
2 changes: 1 addition & 1 deletion libs/api/apipage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export async function apiSearchEndpoint<T>(
dbSearch: (s: string) => TE.TaskEither<Error, T[]>,
) {
const errMsg = (q: string) => (): TE.TaskEither<Err, unknown> => {
return TE.left({ status: 400, msg: `Failed to provide the ${q} d as a query param.` });
return TE.left({ status: 400, msg: `Failed to provide a value for ${q} as a query param. e.g., ?q=Andricus` });
};

return await pipe(
Expand Down
4 changes: 2 additions & 2 deletions libs/api/apitypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as O from 'fp-ts/lib/Option';
import { Option } from 'fp-ts/lib/Option';
import * as t from 'io-ts';
import * as tt from 'io-ts-types';
import { fromEnum } from '../utils/io-ts.ts';
import { decodeWithDefault, fromEnum } from '../utils/io-ts.ts';

export type Deletable = {
delete?: boolean;
Expand Down Expand Up @@ -545,7 +545,7 @@ export enum FilterFieldTypeValue {
export const FilterFieldTypeSchema = fromEnum<FilterFieldTypeValue>('FilterFieldTypeValue', FilterFieldTypeValue);
// export type FilterFieldType = t.TypeOf<typeof FilterFieldTypeSchema>;
export const asFilterType = (possibleFilterType?: string | null): FilterFieldTypeValue =>
FilterFieldTypeValue[possibleFilterType?.toUpperCase() as keyof typeof FilterFieldTypeValue];
decodeWithDefault(FilterFieldTypeSchema.decode(possibleFilterType), FilterFieldTypeValue.ALIGNMENTS);

export const FilterFieldWithTypeSchema = t.intersection([FilterFieldSchema, t.type({ fieldType: FilterFieldTypeSchema })]);

Expand Down
4 changes: 2 additions & 2 deletions libs/db/gall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import {
} from '../api/apitypes';
import { SMALL, deleteImagesBySpeciesId, makePath } from '../images/images';
import { defaultSource } from '../pages/renderhelpers';
import { unsafeDecode } from '../utils/io-ts.ts';
import { decodeWithDefault } from '../utils/io-ts.ts';
import { logger } from '../utils/logger.ts';
import { ExtractTFromPromise } from '../utils/types';
import { handleError, optionalWith } from '../utils/util';
Expand Down Expand Up @@ -220,7 +220,7 @@ export const getGalls = (
name: g.species.name,
datacomplete: g.species.datacomplete,
speciessource: g.species.speciessource,
taxoncode: unsafeDecode(TaxonCodeSchema.decode(g.species.taxoncode)),
taxoncode: decodeWithDefault(TaxonCodeSchema.decode(g.species.taxoncode), TaxonCodeValues.GALL),
description: O.fromNullable(d),
abundance: optionalWith(g.species.abundance, adaptAbundance),
gall_id: g.gall_id,
Expand Down
5 changes: 3 additions & 2 deletions libs/db/images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { constant, pipe } from 'fp-ts/lib/function';
import * as O from 'fp-ts/lib/Option';
import * as TE from 'fp-ts/lib/TaskEither';
import { TaskEither } from 'fp-ts/lib/TaskEither';
import { ImageApi, ImageLicenseValues, ImageNoSourceApi } from '../api/apitypes';
import { ImageApi, ImageLicenseValues, ImageLicenseValuesSchema, ImageNoSourceApi } from '../api/apitypes';
import {
createOtherSizes,
deleteImagesByPaths,
Expand All @@ -19,6 +19,7 @@ import { ExtractTFromPromise } from '../utils/types';
import { handleError } from '../utils/util';
import db from './db';
import { connectIfNotNull } from './utils';
import { decodeWithDefault } from '../utils/io-ts';

export const addImages = (images: ImageApi[]): TaskEither<Error, ImageApi[]> => {
// N.B. - the default will also be false for new images, only later can it be changed. So we do not need to worry about
Expand Down Expand Up @@ -144,7 +145,7 @@ export const adaptImage = <T extends ImageWithSource>(img: T): ImageApi => ({
xlarge: makePath(img.path, XLARGE),
original: makePath(img.path, ORIGINAL),
source: O.fromNullable(img.source),
license: ImageLicenseValues[img.license as keyof typeof ImageLicenseValues],
license: decodeWithDefault(ImageLicenseValuesSchema.decode(img.license), ImageLicenseValues.NONE),
});

export const adaptImageNoSource = <T extends image>(img: T): ImageNoSourceApi => ({
Expand Down
4 changes: 3 additions & 1 deletion libs/db/taxonomy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
TaxonCodeValues,
TaxonomyEntry,
TaxonomyType,
TaxonomyTypeSchema,
TaxonomyTypeValues,
TaxonomyUpsertFields,
} from '../api/apitypes';
Expand All @@ -28,6 +29,7 @@ import { ExtractTFromPromise } from '../utils/types';
import { handleError } from '../utils/util';
import db from './db';
import { extractId } from './utils';
import { decodeWithDefault } from '../utils/io-ts.ts';

export type TaxonomyTree = taxonomy & {
parent: taxonomy | null;
Expand Down Expand Up @@ -68,7 +70,7 @@ const toTaxonomyEntry = (dbTax: DBTaxonomyWithParent): TaxonomyEntry => {
id: dbTax.id,
description: dbTax.description == null ? '' : dbTax.description,
name: dbTax.name,
type: TaxonomyTypeValues[dbTax.type.toUpperCase() as keyof typeof TaxonomyTypeValues],
type: decodeWithDefault(TaxonomyTypeSchema.decode(dbTax.type), TaxonomyTypeValues.GENUS),
parent: pipe(dbTax.parent, O.fromNullable, O.map(toTaxonomyEntry)),
};
};
Expand Down
32 changes: 19 additions & 13 deletions libs/utils/io-ts.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
// stuff for making io-ts nicer
import * as E from 'fp-ts/lib/Either';
import { identity, pipe } from 'fp-ts/lib/function';
import t, { Type } from 'io-ts';
import { pipe } from 'fp-ts/lib/function';
import * as t from 'io-ts';

// From: https://github.com/gcanti/io-ts/issues/216#issuecomment-599020040
/**
* this utility function can be used to turn a TypeScript enum into a io-ts codec.
* this utility function can be used to turn a TypeScript enum into an io-ts codec.
*/
export function fromEnum<EnumType>(enumName: string, theEnum: Record<string, string | number>) {
export function fromEnum<EnumType extends string>(
enumName: string,
theEnum: Record<string, EnumType>,
): t.Type<EnumType, EnumType, unknown> {
const isEnumValue = (input: unknown): input is EnumType => Object.values<unknown>(theEnum).includes(input);

return new Type<EnumType>(
return new t.Type<EnumType>(
enumName,
isEnumValue,
(input, context) => (isEnumValue(input) ? t.success(input) : t.failure(input, context)),
identity,
t.identity,
);
}

/**
* Takes the result of a decode (an Either) and unpacks the value. If there is any error it will get logged to
* the Console and will then return and empty object cast to the T typw. I know, janky.
* */
export function unsafeDecode<T, E>(e: E.Either<E, T>): T {
*
* @param e an Either
* @param defaultValue the default value to return if isLeft(e) === true
* @returns if isRight(e) then the value in e, otherwise defaultValue with a console.error logged
*/
export function decodeWithDefault<E, T>(e: E.Either<E, T>, defaultValue: T): T {
return pipe(
e,
E.match((err) => {
console.error(err);
return {} as T;
}, identity),
console.error(`Failed to decode value from Either "${e}". Got error below`);
console.dir(err);
return defaultValue;
}, t.identity),
);
}
11 changes: 10 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@ export default (phase) => {
BUILD_ID: buildid,
},
images: {
domains: ['static.gallformers.org', 'dhz6u1p7t6okk.cloudfront.net'],
remotePatterns: [
{
protocol: 'https',
hostname: 'static.gallformers.org',
},
{
protocol: 'https',
hostname: 'dhz6u1p7t6okk.cloudfront.net',
},
],
},
generateBuildId: async () => {
//TODO convert this to the latest git hash
Expand Down
3 changes: 2 additions & 1 deletion pages/404.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Image from 'next/image.js';
import Link from 'next/link';
import React from 'react';
import { Container, Row, Col } from 'react-bootstrap';
import Scale from '../public/images/scale.jpg';

export default function FourOhFour(): JSX.Element {
return (
Expand All @@ -20,7 +21,7 @@ export default function FourOhFour(): JSX.Element {
</Row>
<Row className="p-1 justify-content-md-center">
<a href="https://www.inaturalist.org/observations/58767231" target="_blank" rel="noreferrer">
<Image src="../public/images/scale.jpg" alt="A scale insect not a gall." />
<Image src={Scale} alt="A scale insect not a gall." />
</a>
</Row>
<Row className="p-3">
Expand Down
2 changes: 1 addition & 1 deletion pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function Gallformers({ Component, pageProps }: AppProps): JSX.Element {
</Col>
</Row>
<Row>
<Col className="ms-5 me-5 p-2">
<Col className="m-3 mb-5 p-2">
<ConfirmationServiceProvider>
<Component {...pageProps} />
</ConfirmationServiceProvider>
Expand Down
9 changes: 2 additions & 7 deletions pages/about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from 'react';
import { Accordion, Card, Col, Row } from 'react-bootstrap';
import { getCurrentStats, Stat } from '../libs/db/stats.ts';
import { mightFailWithArray } from '../libs/utils/util';
import GallMeMaybe from '../public/images/gallmemaybe.jpg';

type Props = {
stats: Stat[];
Expand Down Expand Up @@ -210,13 +211,7 @@ const About = ({ stats, genTime }: Props): JSX.Element => {
<Accordion.Header>Dare You Click?</Accordion.Header>
<Accordion.Body>
<Card.Body className="d-flex justify-content-center">
<Image
src="../public/images/gallmemaybe.jpg"
alt="Gall Me Maybe"
width="300"
height="532"
layout="fixed"
/>
<Image src={GallMeMaybe} alt="Gall Me Maybe" width="300" height="532" />
</Card.Body>
</Accordion.Body>
</Accordion.Item>
Expand Down
2 changes: 1 addition & 1 deletion pages/api/gall/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default async (req: NextApiRequest, res: NextApiResponse): Promise<void>
TE.fold(sendErrorResponse(res), sendSuccessResponse(res)),
)();
} else if (params && O.isSome(params['q'])) {
apiSearchEndpoint(req, res, searchGalls);
await apiSearchEndpoint(req, res, searchGalls);
} else if (params && O.isSome(params['name'])) {
await pipe(
params['name'],
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
, "jest.config.js" ],
"exclude": [
"node_modules"
]
Expand Down

0 comments on commit 4bb48fc

Please sign in to comment.