Skip to content

Commit

Permalink
Move constants & env to central config (#107)
Browse files Browse the repository at this point in the history
* Move constants & process.env to central config

* Update jest test mocking to match config change
  • Loading branch information
Timothy-Gonzalez authored Nov 3, 2023
1 parent 5e09100 commit a9d49e3
Show file tree
Hide file tree
Showing 25 changed files with 213 additions and 133 deletions.
8 changes: 7 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module.exports = {
"arrow-body-style": ["error", "always"],
curly: ["error", "all"],
"no-lonely-if": ["error"],
"no-magic-numbers": ["error", { ignoreClassFieldInitialValues: true }],
"no-magic-numbers": ["error", { ignoreClassFieldInitialValues: true, ignore: [0] }],
"no-multi-assign": ["error"],
"no-nested-ternary": ["error"],
"no-var": ["error"],
Expand All @@ -43,5 +43,11 @@ module.exports = {
"@typescript-eslint/no-var-requires": "off", // Required for jest
},
},
{
files: ["src/config.ts"], // Disable specific rules for config
rules: {
"no-magic-numbers": "off",
},
},
],
};
7 changes: 7 additions & 0 deletions .test.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# IMPORTANT: DO NOT PUT REAL CREDENTIALS HERE!!!!

# Regex for CORS policies
PROD_REGEX=".*"
DEPLOY_REGEX=".*"

# ----- GENERAL CREDENTIALS -----
DB_USERNAME=test-username
DB_PASSWORD=test-password
Expand All @@ -14,3 +18,6 @@ GOOGLE_OAUTH_ID="123456789.apps.googleusercontent.com"
GOOGLE_OAUTH_SECRET="123456789"

JWT_SECRET="123456789"

# System administrators
SYSTEM_ADMINS=""
11 changes: 6 additions & 5 deletions jest.presetup.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { configDotenv } from "dotenv";
import dotenv from "dotenv";
import path from "path";
import { jest } from "@jest/globals";
import { readFileSync } from "fs";

// Mock the env loading to load from .test.env instead
jest.mock("./src/env.js", () => {
configDotenv({
path: path.join(__dirname, ".test.env"),
});
const rawEnv = readFileSync(path.join(__dirname, ".test.env"));
const env = dotenv.parse(rawEnv);

return {
TEST: true,
default: env,
__esModule: true,
};
});

Expand Down
23 changes: 20 additions & 3 deletions jest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
import { beforeEach, afterEach, expect, jest } from "@jest/globals";
import { MatcherState } from "expect";
import { MongoMemoryServer } from "mongodb-memory-server";
import * as Config from "./src/config.js";

function mockConfig(dbUrl: string) {
jest.mock("./src/config.js", () => {
const actual = jest.requireActual("./src/config.js") as typeof Config;

const newConfig: typeof Config.default = {
...actual.default,
TEST: true,
DB_URL: dbUrl,
};

return {
...actual,
default: newConfig,
__esModule: true,
};
});
}

function getIdForState(state: MatcherState): string {
return `${state.testPath}: ${state.currentTestName}`;
Expand All @@ -9,8 +28,6 @@ function getIdForState(state: MatcherState): string {
const servers = new Map<string, MongoMemoryServer>();

beforeEach(async () => {
const baseUrl = require("./src/database/base-url.js");

const id = getIdForState(expect.getState());

if (servers.has(id)) {
Expand All @@ -21,7 +38,7 @@ beforeEach(async () => {

servers.set(id, mongod);

jest.spyOn(baseUrl, "getBaseURL").mockReturnValue(mongod.getUri());
mockConfig(mongod.getUri());
});

afterEach(async () => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"format": "yarn run prettier --write '**/*.{ts,js,cjs,json}'",
"lint:check": "yarn run eslint src --ext .js,.jsx,.ts,.tsx",
"lint": "yarn run eslint src --ext .js,.jsx,.ts,.tsx --fix",
"test": "jest",
"test": "jest --verbose",
"build": "tsc",
"verify": "yarn build && yarn lint:check && yarn format:check",
"serve": "node scripts/serve.mjs",
Expand Down
7 changes: 3 additions & 4 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { TEST } from "./env.js";

import morgan from "morgan";
import express, { Application, Request, Response } from "express";

Expand All @@ -15,6 +13,7 @@ import admissionRouter from "./services/admission/admission-router.js";
import { InitializeConfigReader } from "./middleware/config-reader.js";
import Models from "./database/models.js";
import { StatusCode } from "status-code-enum";
import Config from "./config.js";

const app: Application = express();

Expand All @@ -24,7 +23,7 @@ const app: Application = express();
app.use(InitializeConfigReader);

// Enable request output when not a test
if (!TEST) {
if (!Config.TEST) {
app.use(morgan("dev"));
}

Expand Down Expand Up @@ -58,7 +57,7 @@ export function setupServer(): void {

export function startServer(): Promise<Express.Application> {
// eslint-disable-next-line no-magic-numbers
const port = process.env.PORT || 3000;
const port = Config.PORT;

return new Promise((resolve) => {
// Setup server
Expand Down
89 changes: 89 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* This file defines all config used anywhere in the api. These values need to be defined on import.
*
* By moving all env variable usage to one place, we also make managing their usage much easier, and
* can error if they are not defined.
*/

import env from "./env.js";

export enum Device {
ADMIN = "admin",
DEV = "dev",
WEB = "web",
IOS = "ios",
ANDROID = "android",
}

function requireEnv(name: string): string {
const value = env[name];

if (value === undefined) {
throw new Error(`Env variable ${name} is not defined!`);
}

return value;
}

const Config = {
/* Jest */
TEST: false, // False by default, will be mocked over

/* URLs */
PORT: env.PORT ? parseInt(env.PORT) : 3000,

DEFAULT_DEVICE: Device.WEB,

REDIRECT_URLS: new Map([
[Device.ADMIN, "https://admin.hackillinois.org/auth/"],
[Device.DEV, "https://adonix.hackillinois.org/auth/dev/"],
[Device.WEB, "https://www.hackillinois.org/auth/"],
[Device.IOS, "hackillinois://login/"],
[Device.ANDROID, "hackillinois://login/"],
]) as Map<string, string>,

CALLBACK_URLS: {
GITHUB: "https://adonix.hackillinois.org/auth/github/callback/",
// GITHUB: "http://localhost:3000/auth/github/callback/",
GOOGLE: "https://adonix.hackillinois.org/auth/google/callback/",
// GOOGLE: "http://127.0.0.1:3000/auth/google/callback/",
},

METADATA_URL: "https://hackillinois.github.io/adonix-metadata/config.json",

/* OAuth, Keys, & Permissions */
DB_URL: `mongodb+srv://${requireEnv("DB_USERNAME")}:${requireEnv("DB_PASSWORD")}@${requireEnv("DB_SERVER")}/`,

GITHUB_OAUTH_ID: requireEnv("GITHUB_OAUTH_ID"),
GITHUB_OAUTH_SECRET: requireEnv("GITHUB_OAUTH_SECRET"),

GOOGLE_OAUTH_ID: requireEnv("GOOGLE_OAUTH_ID"),
GOOGLE_OAUTH_SECRET: requireEnv("GOOGLE_OAUTH_SECRET"),

JWT_SECRET: requireEnv("JWT_SECRET"),

NEWSLETTER_CORS: {
PROD_REGEX: requireEnv("PROD_REGEX"),
DEPLOY_REGEX: requireEnv("DEPLOY_REGEX"),
},

SYSTEM_ADMIN_LIST: requireEnv("SYSTEM_ADMINS").split(","),

/* Timings */
MILLISECONDS_PER_SECOND: 1000,
DEFAULT_JWT_EXPIRY_TIME: "24h",
QR_EXPIRY_TIME: "20s",

/* Defaults */
DEFAULT_POINT_VALUE: 0,
DEFAULT_FOOD_WAVE: 0,

/* Limits */
LEADERBOARD_QUERY_LIMIT: 25,

/* Misc */
EVENT_ID_LENGTH: 32,
EVENT_BYTES_GEN: 16,
};

export default Config;
48 changes: 0 additions & 48 deletions src/constants.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/database.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { IModelOptions } from "@typegoose/typegoose/lib/types.js";
import { getBaseURL } from "./database/base-url.js";
import Config from "./config.js";
import mongoose from "mongoose";

const params: string = "?retryWrites=true&w=majority";
const existingConnections: Map<string, mongoose.Connection> = new Map();

export function connectToMongoose(dbName: string): mongoose.Connection {
const url: string = `${getBaseURL()}${dbName}${params}`;
const url: string = `${Config.DB_URL}${dbName}${params}`;

let database: mongoose.Connection | undefined = existingConnections.get(dbName);

Expand Down
10 changes: 0 additions & 10 deletions src/database/base-url.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/database/event-db.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { modelOptions, prop } from "@typegoose/typegoose";

import Constants from "../constants.js";
import Config from "../config.js";
import { GenericEventFormat } from "../services/event/event-formats.js";

// Interface for the location of the event
Expand Down Expand Up @@ -102,7 +102,7 @@ export class PublicEvent extends BaseEvent {
this.isPrivate = baseEvent.isPrivate ?? false;
this.displayOnStaffCheckIn = baseEvent.displayOnStaffCheckIn ?? false;
this.sponsor = baseEvent.sponsor ?? "";
this.points = baseEvent.points ?? Constants.DEFAULT_POINT_VALUE;
this.points = baseEvent.points ?? Config.DEFAULT_POINT_VALUE;
}
}

Expand Down
28 changes: 25 additions & 3 deletions src/env.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
import { configDotenv } from "dotenv";
/*
* This file loads the env variables we will use at runtime. Instead of relying on system envs dynamically,
* we instead parse the .env file, and overwrite any existing variables with system variables.
* Basically, .env file vars can be overwritten by system level env vars.
*
* The .env is also optional so that env vars can be entirely defined with system vars if needed, like for vercel.
*/

export const TEST = false;
import dotenv from "dotenv";
import { existsSync, readFileSync } from "fs";
import path from "path";

configDotenv();
const envFilePath = path.join(process.cwd(), ".env");
const rawEnv = existsSync(envFilePath) ? readFileSync(envFilePath) : "";
const env = dotenv.parse(rawEnv);

for (const key in process.env) {
const value = process.env[key];

if (value === undefined) {
continue;
}

env[key] = value;
}

export default env;
4 changes: 2 additions & 2 deletions src/middleware/config-reader.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextFunction, Request, Response } from "express";
import Constants from "../constants.js";
import Config from "../config.js";
import axios, { AxiosResponse } from "axios";

interface ConfigFormat {
Expand All @@ -20,7 +20,7 @@ export class ConfigReader {
static androidVersion: string;

async initialize(): Promise<void> {
const url: string = Constants.METADATA_URL;
const url: string = Config.METADATA_URL;

const response: AxiosResponse = await axios.get(url);
const configData: ConfigFormat = response.data as ConfigFormat;
Expand Down
Loading

0 comments on commit a9d49e3

Please sign in to comment.