Skip to content

Commit

Permalink
feat(#130): env variables validation
Browse files Browse the repository at this point in the history
through `envalid`
  • Loading branch information
barthofu committed Feb 11, 2024
1 parent c570dde commit d901e4b
Show file tree
Hide file tree
Showing 19 changed files with 140 additions and 93 deletions.
4 changes: 2 additions & 2 deletions .docker/app/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
## build runner
FROM node:20.10-buster-slim as build-runner
FROM node:20.11-buster-slim as build-runner

# Set temp directory
WORKDIR /tmp/app
Expand All @@ -19,7 +19,7 @@ COPY tsconfig.json .
RUN npm run build

## producation runner
FROM node:20.10-buster-slim as prod-runner
FROM node:20.11-buster-slim as prod-runner

# set production mode
ARG NODE_ENV=production
Expand Down
16 changes: 8 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ TEST_GUILD_ID="TEST_GUILD_ID"
BOT_OWNER_ID="YOUR_DISCORD_ID"

# database
DATABASE_HOST="database" # if you use docker-compose, it should be the name of the service hosting the database
DATABASE_PORT=3306
DATABASE_NAME="tscord_bot"
DATABASE_USER="tscord"
DATABASE_PASSWORD="tscord123"
# DATABASE_HOST="database" # if you use docker-compose, it should be the name of the service hosting the database
# DATABASE_PORT=5432
# DATABASE_NAME="tscord_bot"
# DATABASE_USER="tscord"
# DATABASE_PASSWORD="tscord123"

# api
API_PORT=4000
API_ADMIN_TOKEN="PUT_A_RANDOM_TOKEN_HERE" # you can generate one here -> https://www.uuidgenerator.net/version1
# API_PORT=4000
# API_ADMIN_TOKEN="PUT_A_RANDOM_TOKEN_HERE" # you can generate one here -> https://www.uuidgenerator.net/version1

# external services
IMGUR_CLIENT_ID="YOUR_IMGUR_CLIENT_ID"
# IMGUR_CLIENT_ID="YOUR_IMGUR_CLIENT_ID"
5 changes: 2 additions & 3 deletions mikro-orm.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// @ts-nocheck

import process from 'node:process'

import { Options } from '@mikro-orm/core'

import * as entities from '@/entities'
import { env } from '@/env'
import { PluginsManager } from '@/services'
import { resolveDependency } from '@/utils/functions'

Expand All @@ -15,7 +14,7 @@ export default async () => {
await pluginsManager.loadPlugins()

return {
...mikroORMConfig[process.env.NODE_ENV || 'development'] as Options<DatabaseDriver>,
...mikroORMConfig[env.NODE_ENV || 'development'] as Options<DatabaseDriver>,
entities: [...Object.values(entities), ...pluginsManager.getEntities()],
}
}
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"description": "A fully-featured discord bot template written in Typescript, intended to provide a framework that's easy to use, extend and modify",
"license": "MIT",
"main": "build/main.js",
"tscord": {
"version": "2.2"
},
"scripts": {
"build": "npm run build:clean && npm run build:compile && npm run install:plugins",
"build:clean": "rimraf build/",
Expand Down Expand Up @@ -61,6 +64,7 @@
"discord.js": "~14.14.1",
"discordx": "~11.9.0",
"dotenv": "~16.4.1",
"envalid": "^8.0.0",
"express": "^4.18.2",
"fast-folder-size": "~2.2.0",
"fs": "~0.0.1-security",
Expand Down Expand Up @@ -110,11 +114,11 @@
"typescript": "~5.3.3"
},
"engines": {
"node": ">=20.10.0",
"node": ">=20.11.0",
"npm": ">=10.2.3"
},
"volta": {
"node": "20.10.0"
"node": "20.11.0"
},
"mikro-orm": {
"useTsNode": true,
Expand All @@ -128,8 +132,5 @@
"src/i18n/**/!(i18n-types.ts)",
"src/{events,commands}/**/*.{ts,js}"
]
},
"tscord": {
"version": "2.2"
}
}
7 changes: 3 additions & 4 deletions src/api/middlewares/devAuthenticated.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import process from 'node:process'

import { Context, Middleware, PlatformContext } from '@tsed/common'
import { BadRequest, Unauthorized } from '@tsed/exceptions'
import DiscordOauth2 from 'discord-oauth2'

import { env } from '@/env'
import { Store } from '@/services'
import { isDev, resolveDependency } from '@/utils/functions'

Expand All @@ -27,7 +26,7 @@ export class DevAuthenticated {

async use(@Context() { request }: PlatformContext) {
// if we are in development mode, we don't need to check the token
// if (process.env['NODE_ENV'] === 'development') return next()
// if (env.NODE_ENV === 'development') return next()

// check if the request includes valid authorization header
const authHeader = request.headers.authorization
Expand All @@ -40,7 +39,7 @@ export class DevAuthenticated {
throw new BadRequest('Invalid token')

// pass if the token is the admin token of the app
if (token === process.env.API_ADMIN_TOKEN)
if (token === env.API_ADMIN_TOKEN)
return

// verify that the token is a valid FMA protected (or not) OAuth2 token -> https://stackoverflow.com/questions/71166596/is-there-a-way-to-check-if-a-discord-account-token-is-valid-or-not
Expand Down
5 changes: 2 additions & 3 deletions src/api/server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import '@tsed/swagger'

import process from 'node:process'

import { MikroORM, UseRequestContext } from '@mikro-orm/core'
import { Inject, PlatformAcceptMimesMiddleware, PlatformApplication } from '@tsed/common'
import { PlatformExpress } from '@tsed/platform-express'
Expand All @@ -10,6 +8,7 @@ import { singleton } from 'tsyringe'

import * as controllers from '@/api/controllers'
import { Log } from '@/api/middlewares'
import { env } from '@/env'
import { Database, PluginsManager, Store } from '@/services'

@singleton()
Expand Down Expand Up @@ -41,7 +40,7 @@ export class Server {
async start(): Promise<void> {
const platform = await PlatformExpress.bootstrap(Server, {
rootDir: __dirname,
httpPort: Number.parseInt(process.env.API_PORT) || 4000,
httpPort: env.API_PORT,
httpsPort: false,
acceptMimes: ['application/json'],
mount: {
Expand Down
5 changes: 2 additions & 3 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import process from 'node:process'

import { GatewayIntentBits, Partials } from 'discord.js'
import { ClientOptions } from 'discordx'

import { generalConfig, logsConfig } from '@/configs'
import { env } from '@/env'
import { ExtractLocale, Maintenance, NotBot, RequestContextIsolator } from '@/guards'

export function clientConfig(): ClientOptions {
return {

// to only use global commands (use @Guild for specific guild command), comment this line
botGuilds: process.env.NODE_ENV === 'development' ? [process.env.TEST_GUILD_ID] : undefined,
botGuilds: env.NODE_ENV === 'development' ? [env.TEST_GUILD_ID] : undefined,

// discord intents
intents: [
Expand Down
4 changes: 2 additions & 2 deletions src/configs/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import process from 'node:process'
import { env } from '@/env'

export const apiConfig: APIConfigType = {

enabled: false, // is the API server enabled or not
port: process.env.API_PORT ? Number.parseInt(process.env.API_PORT) : 4000, // the port on which the API server should be exposed
port: env.API_PORT || 4000, // the port on which the API server should be exposed
}
35 changes: 19 additions & 16 deletions src/configs/database.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Options } from '@mikro-orm/core'
import { SqlHighlighter } from '@mikro-orm/sql-highlighter'

// eslint-disable-next-line unused-imports/no-unused-imports
import { env } from '@/env'

interface Config {
production: Options
development?: Options
Expand Down Expand Up @@ -31,37 +34,37 @@ const envMikroORMConfig = {
* MongoDB
*/
// type: 'mongo',
// clientUrl: process.env['DATABASE_HOST'],
// clientUrl: env['DATABASE_HOST'],

/**
* PostgreSQL
*/
// type: 'postgresql',
// dbName: process.env['DATABASE_NAME'],
// host: process.env['DATABASE_HOST'],
// port: Number(process.env['DATABASE_PORT']),,
// user: process.env['DATABASE_USER'],
// password: process.env['DATABASE_PASSWORD'],
// dbName: env['DATABASE_NAME'],
// host: env['DATABASE_HOST'],
// port: Number(env['DATABASE_PORT']),,
// user: env['DATABASE_USER'],
// password: env['DATABASE_PASSWORD'],

/**
* MySQL
*/
// type: 'mysql',
// dbName: process.env['DATABASE_NAME'],
// host: process.env['DATABASE_HOST'],
// port: Number(process.env['DATABASE_PORT']),
// user: process.env['DATABASE_USER'],
// password: process.env['DATABASE_PASSWORD'],
// dbName: env['DATABASE_NAME'],
// host: env['DATABASE_HOST'],
// port: Number(env['DATABASE_PORT']),
// user: env['DATABASE_USER'],
// password: env['DATABASE_PASSWORD'],

/**
* MariaDB
*/
// type: 'mariadb',
// dbName: process.env['DATABASE_NAME'],
// host: process.env['DATABASE_HOST'],
// port: Number(process.env['DATABASE_PORT']),
// user: process.env['DATABASE_USER'],
// password: process.env['DATABASE_PASSWORD'],
// dbName: env['DATABASE_NAME'],
// host: env['DATABASE_HOST'],
// port: Number(env['DATABASE_PORT']),
// user: env['DATABASE_USER'],
// password: env['DATABASE_PASSWORD'],

highlighter: new SqlHighlighter(),
debug: false,
Expand Down
4 changes: 2 additions & 2 deletions src/configs/general.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import process from 'node:process'
import { env } from '@/env'

export const generalConfig: GeneralConfigType = {

name: 'tscord', // the name of your bot
description: '', // the description of your bot
defaultLocale: 'en', // default language of the bot, must be a valid locale
ownerId: process.env.BOT_OWNER_ID || '',
ownerId: env.BOT_OWNER_ID,
timezone: 'Europe/Paris', // default TimeZone to well format and localize dates (logs, stats, etc)

simpleCommandsPrefix: '!', // default prefix for simple command messages (old way to do commands on discord)
Expand Down
12 changes: 8 additions & 4 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { container } from 'tsyringe'

import { Server } from '@/api/server'
import { apiConfig, generalConfig } from '@/configs'
import { checkEnvironmentVariables, env } from '@/env'
import { NoBotTokenError } from '@/errors'
import { Database, ErrorHandler, EventManager, ImagesUpload, Logger, PluginsManager, Store } from '@/services'
import { initDataTable, resolveDependency } from '@/utils/functions'
Expand Down Expand Up @@ -83,6 +84,9 @@ async function reload(client: Client) {
async function init() {
const logger = await resolveDependency(Logger)

// check environment variables
checkEnvironmentVariables()

// init error handler
await resolveDependency(ErrorHandler)

Expand Down Expand Up @@ -125,13 +129,13 @@ async function init() {
await pluginManager.execMains()

// log in with the bot token
if (!process.env.BOT_TOKEN) {
if (!env.BOT_TOKEN) {
throw new NoBotTokenError()
}

client.login(process.env.BOT_TOKEN)
client.login(env.BOT_TOKEN)
.then(async () => {
if (process.env.NODE_ENV === 'development') {
if (env.NODE_ENV === 'development') {
// reload commands and events when a file changes
watcher.on('change', () => reload(client))

Expand All @@ -149,7 +153,7 @@ async function init() {
}

// upload images to imgur if configured
if (process.env.IMGUR_CLIENT_ID && generalConfig.automaticUploadImagesToImgur) {
if (env.IMGUR_CLIENT_ID && generalConfig.automaticUploadImagesToImgur) {
const imagesUpload = await resolveDependency(ImagesUpload)
await imagesUpload.syncWithDatabase()
}
Expand Down
Loading

0 comments on commit d901e4b

Please sign in to comment.