Skip to content

Commit

Permalink
adds environmental variable validation
Browse files Browse the repository at this point in the history
  • Loading branch information
andresgnlez committed Apr 17, 2024
1 parent 858ac92 commit 73ce768
Show file tree
Hide file tree
Showing 19 changed files with 155 additions and 50 deletions.
1 change: 0 additions & 1 deletion .github/workflows/testing-client.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ jobs:
command-prefix: yarn dlx
command: "yarn test:e2e"
env:
NEXT_PUBLIC_MAPBOX_API_TOKEN: ${{ secrets.NEXT_PUBLIC_MAPBOX_API_TOKEN }}
NEXT_PUBLIC_API_URL: ${{ secrets.CYPRESS_API_URL }}
NEXT_TELEMETRY_DISABLED: 1
NEXTAUTH_URL: http://localhost:3000
Expand Down
3 changes: 0 additions & 3 deletions client/.env.sample
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
NEXT_PUBLIC_MAPBOX_API_TOKEN="[mapbox-token]"
NEXT_PUBLIC_API_URL="http://[api-url]"
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="nyancat"
NEXT_PUBLIC_FILE_UPLOADER_MAX_SIZE=10000000
NEXT_TELEMETRY_DISABLED=1
2 changes: 0 additions & 2 deletions client/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ ARG UID=5000
ARG GID=5000
ARG NEXTAUTH_URL
ARG NEXTAUTH_SECRET
ARG NEXT_PUBLIC_MAPBOX_API_TOKEN
ARG NEXT_PUBLIC_API_URL
ARG CYPRESS_USERNAME
ARG CYPRESS_PASSWORD
Expand All @@ -16,7 +15,6 @@ ENV USER $NAME
ENV APP_HOME /opt/$NAME
ENV NEXTAUTH_URL $NEXTAUTH_URL
ENV NEXTAUTH_SECRET $NEXTAUTH_SECRET
ENV NEXT_PUBLIC_MAPBOX_API_TOKEN $NEXT_PUBLIC_MAPBOX_API_TOKEN
ENV NEXT_PUBLIC_API_URL $NEXT_PUBLIC_API_URL
ENV NEXT_TELEMETRY_DISABLED 1
ENV CYPRESS_USERNAME $CYPRESS_USERNAME
Expand Down
13 changes: 0 additions & 13 deletions client/ENV_VARS.md

This file was deleted.

11 changes: 11 additions & 0 deletions client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ Run the development server:
```bash
yarn dev
```

### Environmental variables
The application handles environmental variables using [@t3-oss/env-nextjs](https://env.t3.gg/docs). You can see the available (and required) variables in the `./src/env` file. **NOTE**: the application will NOT start if the required variables are not set previously.

#### Testing
Additionally, and exclusively for testing purposes, you can set the following environmental variables:

- `CYPRESS_USERNAME`: email to authenticate for the e2e tests.
- `CYPRESS_PASSWORD`: password to authenticate for the e2e tests.
- `CYPRESS_API_URL`: API used to run the e2e tests.

### Running tests

Run the tests locally:
Expand Down
1 change: 0 additions & 1 deletion client/docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ services:
args:
- NEXTAUTH_URL=http://localhost:3000
- NEXTAUTH_SECRET=secret
- NEXT_PUBLIC_MAPBOX_API_TOKEN=token
ports:
- "3000:3000"
container_name: landgriffon-client
Expand Down
20 changes: 10 additions & 10 deletions client/next.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import { fileURLToPath } from 'node:url';

import createJiti from 'jiti';

import { env } from './src/env.mjs';

const jiti = createJiti(fileURLToPath(import.meta.url));
jiti('./src/env.mjs');

/** @type {import('next').NextConfig} */
const nextConfig = {
poweredByHeader: false,
Expand All @@ -21,7 +30,7 @@ const nextConfig = {
destination: '/auth/signin',
permanent: false,
},
...(process.env.NEXT_PUBLIC_ENABLE_EUDR !== 'true'
...(!env.NEXT_PUBLIC_ENABLE_EUDR
? [
{
source: '/eudr',
Expand All @@ -32,15 +41,6 @@ const nextConfig = {
: []),
];
},
env: {
NEXT_PUBLIC_PLANET_API_KEY: 'PLAK6679039df83f414faf798ba4ad4530db',
NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN:
'eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfemsydWhpaDYiLCJqdGkiOiJjY2JlMjUyYSJ9.LoqzuDp076ESVYmHm1mZNtfhnqOVGmSxzp60Fht8PQw',
NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN:
'eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfemsydWhpaDYiLCJqdGkiOiJjZDk0ZWIyZSJ9.oqLagnOEc-j7Z4hY-MTP1yoZA_vJ7WYYAkOz_NUmCJo',
NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN:
'eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfemsydWhpaDYiLCJqdGkiOiI3NTFkNzA1YSJ9.jrVugV7HYfhmjxj-p2Iks8nL_AjHR91Q37JVP2fNmtc',
},
};

export default nextConfig;
5 changes: 4 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,11 @@
"tailwindcss": "3.4.1",
"tailwindcss-animate": "1.0.7",
"uuid": "8.3.2",
"yup": "0.32.11"
"yup": "0.32.11",
"zod": "3.22.4"
},
"devDependencies": {
"@t3-oss/env-nextjs": "0.9.2",
"@types/chroma-js": "2.1.3",
"@types/d3-format": "3.0.1",
"@types/d3-scale": "4.0.2",
Expand All @@ -116,6 +118,7 @@
"eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "5.1.3",
"istanbul-reports": "3.0.0",
"jiti": "1.21.0",
"nyc": "15.1.0",
"nyc-report-lcov-absolute": "1.0.0",
"prettier": "^3.1.1",
Expand Down
4 changes: 3 additions & 1 deletion client/src/components/map/layers/maplibre/raster/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import queryString from 'query-string';

import { env } from '@/env.mjs';

import type { ContextualLayerApiResponse } from 'hooks/layers/getContextualLayers';

export const getTiler = (
Expand All @@ -9,7 +11,7 @@ export const getTiler = (
return queryString.stringifyUrl({
url: tilerPath.match(/^(http|https):\/\//)
? tilerPath
: `${process.env.NEXT_PUBLIC_API_URL}${tilerPath}`,
: `${env.NEXT_PUBLIC_API_URL}${tilerPath}`,
query: tilerParams,
});
};
11 changes: 6 additions & 5 deletions client/src/containers/analysis-eudr/map/compare.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useAppSelector } from '@/store/hooks';
import { INITIAL_VIEW_STATE, MAP_STYLES } from '@/components/map';
import { useEUDRData, usePlotGeometries } from '@/hooks/eudr';
import { formatNumber } from '@/utils/number-format';
import { env } from '@/env.mjs';

import type { PickingInfo, MapViewState } from '@deck.gl/core/typed';

Expand Down Expand Up @@ -174,7 +175,7 @@ const EUDRCompareMap = () => {
credentials: {
apiVersion: API_VERSIONS.V3,
apiBaseUrl: 'https://gcp-us-east1.api.carto.com',
accessToken: process.env.NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN,
accessToken: env.NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN,
},
});

Expand All @@ -192,7 +193,7 @@ const EUDRCompareMap = () => {
credentials: {
apiVersion: API_VERSIONS.V3,
apiBaseUrl: 'https://gcp-us-east1.api.carto.com',
accessToken: process.env.NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN,
accessToken: env.NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN,
},
});

Expand All @@ -217,7 +218,7 @@ const EUDRCompareMap = () => {
credentials: {
apiVersion: API_VERSIONS.V3,
apiBaseUrl: 'https://gcp-us-east1.api.carto.com',
accessToken: process.env.NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN,
accessToken: env.NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN,
},
});

Expand Down Expand Up @@ -298,7 +299,7 @@ const EUDRCompareMap = () => {
planetLayer.year
}_${monthFormatter(
planetLayer.month.toString(),
)}_mosaic/gmap/{z}/{x}/{y}.png?api_key=${process.env.NEXT_PUBLIC_PLANET_API_KEY}`,
)}_mosaic/gmap/{z}/{x}/{y}.png?api_key=${env.NEXT_PUBLIC_PLANET_API_KEY}`,
]}
tileSize={256}
>
Expand All @@ -318,7 +319,7 @@ const EUDRCompareMap = () => {
planetCompareLayer.year
}_${monthFormatter(
planetCompareLayer.month.toString(),
)}_mosaic/gmap/{z}/{x}/{y}.png?api_key=${process.env.NEXT_PUBLIC_PLANET_API_KEY}`,
)}_mosaic/gmap/{z}/{x}/{y}.png?api_key=${env.NEXT_PUBLIC_PLANET_API_KEY}`,
]}
tileSize={256}
>
Expand Down
9 changes: 5 additions & 4 deletions client/src/containers/analysis-eudr/map/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useAppSelector } from '@/store/hooks';
import { INITIAL_VIEW_STATE, MAP_STYLES } from '@/components/map';
import { useEUDRData, usePlotGeometries } from '@/hooks/eudr';
import { formatNumber } from '@/utils/number-format';
import { env } from '@/env.mjs';

import type { PickingInfo, MapViewState } from '@deck.gl/core/typed';

Expand Down Expand Up @@ -169,7 +170,7 @@ const EUDRMap: React.FC<{ supplierId?: string }> = ({ supplierId }) => {
credentials: {
apiVersion: API_VERSIONS.V3,
apiBaseUrl: 'https://gcp-us-east1.api.carto.com',
accessToken: process.env.NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN,
accessToken: env.NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN,
},
});

Expand All @@ -187,7 +188,7 @@ const EUDRMap: React.FC<{ supplierId?: string }> = ({ supplierId }) => {
credentials: {
apiVersion: API_VERSIONS.V3,
apiBaseUrl: 'https://gcp-us-east1.api.carto.com',
accessToken: process.env.NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN,
accessToken: env.NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN,
},
});

Expand All @@ -212,7 +213,7 @@ const EUDRMap: React.FC<{ supplierId?: string }> = ({ supplierId }) => {
credentials: {
apiVersion: API_VERSIONS.V3,
apiBaseUrl: 'https://gcp-us-east1.api.carto.com',
accessToken: process.env.NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN,
accessToken: env.NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN,
},
});

Expand Down Expand Up @@ -277,7 +278,7 @@ const EUDRMap: React.FC<{ supplierId?: string }> = ({ supplierId }) => {
planetLayer.year
}_${monthFormatter(
planetLayer.month.toString(),
)}_mosaic/gmap/{z}/{x}/{y}.png?api_key=${process.env.NEXT_PUBLIC_PLANET_API_KEY}`,
)}_mosaic/gmap/{z}/{x}/{y}.png?api_key=${env.NEXT_PUBLIC_PLANET_API_KEY}`,
]}
tileSize={256}
>
Expand Down
5 changes: 2 additions & 3 deletions client/src/containers/uploader/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useRouter } from 'next/router';
import { useUploadDataSource } from 'hooks/sourcing-data';
import { useLasTask } from 'hooks/tasks';
import FileDropzone from 'components/file-dropzone';
import { env } from '@/env.mjs';

import type { FileDropZoneProps } from 'components/file-dropzone/types';
import type { Task } from 'types';
Expand All @@ -15,14 +16,12 @@ type DataUploaderProps = {
onUploadInProgress?: (inProgress: boolean) => void;
};

const MAX_SIZE = Number(process.env.NEXT_PUBLIC_FILE_UPLOADER_MAX_SIZE || '10000000');

const uploadOptions = {
accept: {
'application/vnd.ms-excel': ['.xls'],
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'],
},
maxSize: MAX_SIZE,
maxSize: env.NEXT_PUBLIC_FILE_UPLOADER_MAX_SIZE,
};

const DataUploader: React.FC<DataUploaderProps> = ({ variant = 'default', onUploadInProgress }) => {
Expand Down
63 changes: 63 additions & 0 deletions client/src/env.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';

export const env = createEnv({
shared: {
NODE_ENV: z.enum(['development', 'production', 'test']),
},

/*
* Serverside Environment variables, not available on the client.
* Will throw if you access these variables on the client.
*/
server: {
// ? URL (including protocol) of the client application
NEXTAUTH_URL: z
.string()
.url()
.default(`http://localhost:${process.env.PORT || 3000}`),
// ? Secret used for cryptographic operations in the client application. Generate using `openssl rand -base64 32`
NEXTAUTH_SECRET: z.string().min(1),
},
/*
* Environment variables available on the client (and server).
*
* 💡 You'll get type errors if these are not prefixed with NEXT_PUBLIC_.
*/
client: {
// ? URL (including protocol) of the API
NEXT_PUBLIC_API_URL: z.string().url(),
// ? enables access to EUDR page
NEXT_PUBLIC_ENABLE_EUDR: z.coerce.boolean().optional().default(false),
NEXT_PUBLIC_PLANET_API_KEY: z.string().default('PLAK6679039df83f414faf798ba4ad4530db'),
NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN: z
.string()
.default(
'eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfemsydWhpaDYiLCJqdGkiOiJjY2JlMjUyYSJ9.LoqzuDp076ESVYmHm1mZNtfhnqOVGmSxzp60Fht8PQw',
),
NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN: z
.string()
.default(
'eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfemsydWhpaDYiLCJqdGkiOiJjZDk0ZWIyZSJ9.oqLagnOEc-j7Z4hY-MTP1yoZA_vJ7WYYAkOz_NUmCJo',
),
NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN: z
.string()
.default(
'eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfemsydWhpaDYiLCJqdGkiOiI3NTFkNzA1YSJ9.jrVugV7HYfhmjxj-p2Iks8nL_AjHR91Q37JVP2fNmtc',
),
NEXT_PUBLIC_FILE_UPLOADER_MAX_SIZE: z.coerce.number().default(10000000),
},
runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
NEXT_PUBLIC_ENABLE_EUDR: process.env.NEXT_PUBLIC_ENABLE_EUDR,
NEXT_PUBLIC_PLANET_API_KEY: process.env.NEXT_PUBLIC_PLANET_API_KEY,
NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN: process.env.NEXT_PUBLIC_CARTO_FOREST_ACCESS_TOKEN,
NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN:
process.env.NEXT_PUBLIC_CARTO_DEFORESTATION_ACCESS_TOKEN,
NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN: process.env.NEXT_PUBLIC_CARTO_RADD_ACCESS_TOKEN,
NEXT_PUBLIC_FILE_UPLOADER_MAX_SIZE: process.env.NEXT_PUBLIC_FILE_UPLOADER_MAX_SIZE,
},
});
3 changes: 2 additions & 1 deletion client/src/layouts/application/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CollectionIcon as CollectionIconSolid } from '@heroicons/react/solid';

import { ChartBarIconOutline, ChartBarIconSolid } from './icons/chart-bar';

import { env } from '@/env.mjs';
import { useLasTask } from 'hooks/tasks';
import Navigation from 'containers/navigation/desktop';
import UserDropdown from 'containers/user-dropdown';
Expand All @@ -31,7 +32,7 @@ const ApplicationLayout: React.FC<React.PropsWithChildren> = ({ children }) => {
icon: { default: ChartBarIconOutline, active: ChartBarIconSolid },
disabled: !!(!lastTask || lastTask?.status === 'processing'),
},
...(process.env.NEXT_PUBLIC_ENABLE_EUDR === 'true'
...(env.NEXT_PUBLIC_ENABLE_EUDR
? [
{
name: 'EUDR',
Expand Down
4 changes: 3 additions & 1 deletion client/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Jsona from 'jsona';
import { getSession, signOut } from 'next-auth/react';
import toast from 'react-hot-toast';

import { env } from '@/env.mjs';

import type { ApiError, ErrorResponse } from 'types';
import type { AxiosRequestConfig, AxiosResponse } from 'axios';

Expand All @@ -14,7 +16,7 @@ import type { AxiosRequestConfig, AxiosResponse } from 'axios';
const dataFormatter = new Jsona();

const defaultConfig: AxiosRequestConfig = {
baseURL: `${process.env.NEXT_PUBLIC_API_URL}/api/v1`,
baseURL: `${env.NEXT_PUBLIC_API_URL}/api/v1`,
headers: { 'Content-Type': 'application/json' },
};

Expand Down
4 changes: 3 additions & 1 deletion client/src/services/authentication.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import axios from 'axios';

import { env } from '@/env.mjs';

export const authService = axios.create({
baseURL: `${process.env.NEXT_PUBLIC_API_URL}/auth`,
baseURL: `${env.NEXT_PUBLIC_API_URL}/auth`,
headers: { 'Content-Type': 'application/json' },
});

Expand Down
3 changes: 2 additions & 1 deletion client/src/services/ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import axios, { AxiosError } from 'axios';
import { getServerSession } from 'next-auth/next';

import { options as authOptions } from 'pages/api/auth/[...nextauth]';
import { env } from '@/env.mjs';

export const sessionSSR = async ({ req, res }) => await getServerSession(req, res, authOptions);

Expand All @@ -14,7 +15,7 @@ export const tasksSSR = async ({ req, res }) => {

return await axios({
method: 'GET',
url: `${process.env.NEXT_PUBLIC_API_URL}/api/v1/tasks`,
url: `${env.NEXT_PUBLIC_API_URL}/api/v1/tasks`,
params: { 'page[size]': 1, 'page[number]': 1, sort: '-createdAt' },
headers: {
Authorization: `Bearer ${session.accessToken}`,
Expand Down
Loading

0 comments on commit 73ce768

Please sign in to comment.