diff --git a/README.md b/README.md index 134fe6c..7296dd2 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ pm2 monit - [ ] API keys for users - [ ] Move to Oauth2 for authentication - [ ] Allow session management (Track/logout from sessions) +- [ ] Add support for multiple databases - [ ] Add support for more notification services - [ ] Better role based access control diff --git a/app/components/home/menu/mobile.jsx b/app/components/home/menu/mobile.jsx index 612425a..9ace574 100644 --- a/app/components/home/menu/mobile.jsx +++ b/app/components/home/menu/mobile.jsx @@ -1,12 +1,14 @@ // import dependencies import PropTypes from 'prop-types'; +import { FaCog, FaPlus } from 'react-icons/fa'; import { observer } from 'mobx-react-lite'; // import local files -import useDropdown from '../../../hooks/useDropdown'; -import { FaEllipsisVertical } from '../../icons'; -import Dropdown from '../../ui/dropdown'; import useContextStore from '../../../context'; +import Modal from '../../ui/modal'; +import Button from '../../ui/button'; +import HomeMobileMenuStatus from './mobile/status'; +import HomeMobileMenuLayout from './mobile/layout'; import MonitorConfigureModal from '../../modal/monitor/configure'; const HomeMenuMobile = ({ handleReset }) => { @@ -15,40 +17,54 @@ const HomeMenuMobile = ({ handleReset }) => { globalStore: { addMonitor }, } = useContextStore(); - const { toggleDropdown, dropdownIsOpen } = useDropdown(true); return ( - - - - - - - openModal( - , - false - ) - } - > - Add Monitor - - { - handleReset(); - toggleDropdown(); - }} - > - Reset - - - + + + + Close + { + handleReset(); + closeModal(); + }} + > + Reset + + + , + false + ); + }} + /> ); }; diff --git a/app/components/home/menu/mobile/layout.jsx b/app/components/home/menu/mobile/layout.jsx new file mode 100644 index 0000000..7e00200 --- /dev/null +++ b/app/components/home/menu/mobile/layout.jsx @@ -0,0 +1,71 @@ +// import dependencies +import { observer } from 'mobx-react-lite'; + +// import local files +import useDropdown from '../../../../hooks/useDropdown'; +import Dropdown from '../../../ui/dropdown'; +import useLocalStorageContext from '../../../../hooks/useLocalstorage'; +import { FaBars, IoGrid } from '../../../icons'; + +const views = [ + { + text: 'Cards', + id: 'cards', + icon: , + }, + { + text: 'Compact', + id: 'compact', + icon: , + }, +]; + +const HomeMobileMenuLayout = () => { + const { toggleDropdown, dropdownIsOpen } = useDropdown(true); + + const { layout, setLayout } = useLocalStorageContext(); + + const dropdownItems = views.map((view) => ( + { + setLayout(view.id); + toggleDropdown(); + }} + showDot + isSelected={layout === view.id} + dotColor="primary" + > +
+ {view.icon} + {view.text} +
+
+ )); + + return ( + + + {layout.charAt(0).toUpperCase() + layout.slice(1)} + + + {dropdownItems} + + + ); +}; + +HomeMobileMenuLayout.displayName = 'HomeMobileMenuLayout'; + +HomeMobileMenuLayout.propTypes = {}; + +export default observer(HomeMobileMenuLayout); diff --git a/app/components/home/menu/mobile/status.jsx b/app/components/home/menu/mobile/status.jsx new file mode 100644 index 0000000..5b59113 --- /dev/null +++ b/app/components/home/menu/mobile/status.jsx @@ -0,0 +1,73 @@ +// import local files +import useDropdown from '../../../../hooks/useDropdown'; +import Dropdown from '../../../ui/dropdown'; +import useLocalStorageContext from '../../../../hooks/useLocalstorage'; + +import { HiStatusOffline, HiStatusOnline, FaBars } from '../../../icons'; + +const statusOptions = [ + { + text: 'All', + id: 'all', + icon: , + }, + { + text: 'Up', + id: 'up', + icon: , + }, + { + text: 'Down', + id: 'down', + icon: , + }, +]; + +const HomeMobileMenuStatus = () => { + const { toggleDropdown, dropdownIsOpen } = useDropdown(true); + + const { status, setStatus } = useLocalStorageContext(); + + const dropdownItems = statusOptions.map((view) => ( + { + setStatus(view.id); + toggleDropdown(); + }} + showDot + isSelected={status === view.id} + > +
+ {view.icon} + {view.text} +
+
+ )); + + return ( + + + {status.charAt(0).toUpperCase() + status.slice(1)} + + + {dropdownItems} + + + ); +}; + +HomeMobileMenuStatus.displayName = 'HomeMobileMenuStatus'; + +HomeMobileMenuStatus.propTypes = {}; + +export default HomeMobileMenuStatus; diff --git a/app/components/home/menu/styles.scss b/app/components/home/menu/styles.scss index cc932e4..76f5165 100644 --- a/app/components/home/menu/styles.scss +++ b/app/components/home/menu/styles.scss @@ -27,12 +27,6 @@ display: none; } -@include tablet { - #home-menu-status { - display: none; - } -} - @include mobile { .home-menu-buttons { display: none; diff --git a/app/components/icons/index.jsx b/app/components/icons/index.jsx index 93f27d9..68ddae7 100644 --- a/app/components/icons/index.jsx +++ b/app/components/icons/index.jsx @@ -22,6 +22,7 @@ import { FaCheck, FaBars, FaTrashCan, + FaFilter, } from 'react-icons/fa6'; import { IoArrowBack, IoColorPalette, IoGrid, IoReload } from 'react-icons/io5'; import { RiStackFill } from 'react-icons/ri'; @@ -38,6 +39,7 @@ export { FaCircleCheck, FaCog, FaEllipsisVertical, + FaFilter, FaHome, FaPlus, FaSignOutAlt, diff --git a/app/components/navigation/left.scss b/app/components/navigation/left.scss index 281a32f..282b02c 100644 --- a/app/components/navigation/left.scss +++ b/app/components/navigation/left.scss @@ -72,17 +72,10 @@ } .left-actions-bottom { - display: flex; - justify-content: center; - align-items: flex-end; - gap: 20px; + display: none; } .navigation-left-top-action { flex: 1; } - - .navigation-left-signout-button { - display: none; - } } diff --git a/app/components/notifications/menu/styles.scss b/app/components/notifications/menu/styles.scss index cc932e4..76f5165 100644 --- a/app/components/notifications/menu/styles.scss +++ b/app/components/notifications/menu/styles.scss @@ -27,12 +27,6 @@ display: none; } -@include tablet { - #home-menu-status { - display: none; - } -} - @include mobile { .home-menu-buttons { display: none; diff --git a/app/main.jsx b/app/main.jsx index da9e8cc..8137b2b 100644 --- a/app/main.jsx +++ b/app/main.jsx @@ -68,6 +68,7 @@ ReactDOM.createRoot(document.getElementById('root')).render( } /> } /> } /> + } /> diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index a58741f..04f11e5 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -58,6 +58,7 @@ export default defineConfig({ // { text: 'Overview', link: '/internals/overview' }, { text: 'Changelog', link: '/internals/changelog' }, // { text: 'Flows', link: '/internals/flows' }, + { text: 'Config', link: '/internals/config' }, { text: 'Notifications', link: '/internals/notifications' }, { text: 'Permissions', link: '/internals/permissions' }, { text: 'Roadmap', link: '/internals/roadmap' }, diff --git a/docs/components/guides.vue b/docs/components/guides.vue new file mode 100644 index 0000000..66d936a --- /dev/null +++ b/docs/components/guides.vue @@ -0,0 +1,174 @@ + + + + + + + diff --git a/docs/guides/discord/create-webhook.md b/docs/guides/discord/create-webhook.md new file mode 100644 index 0000000..cdf90db --- /dev/null +++ b/docs/guides/discord/create-webhook.md @@ -0,0 +1,59 @@ +--- +aside: false +sidebar: false +prev: + text: 'Guides' + link: '/guides' +next: false +--- + +# Creating a Discord webhook + +Discord has a [Webhooks](https://discord.com/developers/docs/resources/webhook) feature that allows you to create a webhook that can be used to send messages to a Discord channel. This is an easy way to get notifications from Lunalytics straight to your Discord server. + +## How to create a webhook + +::: warning +In order to create a webhook, you need to have `Manage Webhooks` permission in the server. +::: + +1. Open the server settings and head to the `Integrations` tab. +2. Click on the webhooks tab, this will open a list of currently available webhooks. + +![Discord Integration](/guides/Discord_Integration.webp 'Discord Integration') + +3. Click on the `New Webhook` button and this will create a new webhook for you. (You can update the name, avatar, and channel for the webhook) + +4. Click on the `Copy Webhook URL` button to copy the webhook URL. + +![Discord Webhook](/guides/Discord_Webhook.webp 'Discord Webhook') + +## Adding Webhook to Lunalytics + +Once you have created the webhook in Discord, you will need to create a new notification in Lunalytics. This will allow you to send notifications to the webhook you just created. + +1. Go to the Lunalytics home page and click the notifications icon in the left sidebar +2. Click the `New` button on the top right + +![Lunalytics Create Notification](/guides/Lunalytics_Create_Notification.webp 'Lunalytics Create Notification') + +3. Paste the webhook URL you previous copied into the `Webhook URL` field +4. Input any extra information you want to send to the webhook +5. Click the `Create` button + +![Lunalytics Discord Create Notification](/guides/Lunalytics_Discord_Create_Notification.webp 'Lunalytics Discord Create Notification') + +## Adding a Webhook to a monitor + +Once you've created a notification, you can add the notification to a monitor. This will automatically send notifications using the webhook to the channel you specified in the Discord webhook about when the monitor has any outages and recovers from outages. + +1. Go to the Lunalytics homepage, and click the `New` button on the top right (Or edit an existing monitor) + +![Lunalytics Create Monitor](/guides/Lunalytics_Create_Monitor.webp 'Lunalytics Create Monitor') + +2. In this modal enter the information about your monitor, and then click the `Advanced Settings` button +3. Here click on the dropdown for the `Notifications` field and select the notification you just created +4. Update any extra information you want to add for the monitor +5. Click the `Create` button + +![Lunalytics Add Monitor Notification](/guides/Lunalytics_Add_Monitor_Notification.webp 'Lunalytics Add Monitor Notification') diff --git a/docs/guides/index.md b/docs/guides/index.md new file mode 100644 index 0000000..28556a8 --- /dev/null +++ b/docs/guides/index.md @@ -0,0 +1,13 @@ +--- +aside: false +sidebar: false +layout: page +prev: false +next: false +--- + + + + diff --git a/docs/guides/slack/create-webhook.md b/docs/guides/slack/create-webhook.md new file mode 100644 index 0000000..b75ebca --- /dev/null +++ b/docs/guides/slack/create-webhook.md @@ -0,0 +1,12 @@ +--- +aside: false +sidebar: false +prev: + text: 'Guides' + link: '/guides' +next: false +--- + +## Currently a work in progress. + +For now check out the [Slack official documentation](https://api.slack.com/messaging/webhooks) for more information. diff --git a/docs/guides/telegram/create-bot.md b/docs/guides/telegram/create-bot.md new file mode 100644 index 0000000..e48bab9 --- /dev/null +++ b/docs/guides/telegram/create-bot.md @@ -0,0 +1,12 @@ +--- +aside: false +sidebar: false +prev: + text: 'Guides' + link: '/guides' +next: false +--- + +## Currently a work in progress. + +For now check out the [Telegram official documentation](https://core.telegram.org/bots/tutorial) for more information. diff --git a/docs/guides/telegram/find-chat-id.md b/docs/guides/telegram/find-chat-id.md new file mode 100644 index 0000000..dc6617c --- /dev/null +++ b/docs/guides/telegram/find-chat-id.md @@ -0,0 +1,8 @@ +--- +aside: false +sidebar: false +prev: + text: 'Guides' + link: '/guides' +next: false +--- diff --git a/docs/guides/webhook/index.md b/docs/guides/webhook/index.md new file mode 100644 index 0000000..e3e5fdf --- /dev/null +++ b/docs/guides/webhook/index.md @@ -0,0 +1,15 @@ +--- +aside: false +sidebar: false +prev: + text: 'Guides' + link: '/guides' +next: false +--- + +## Currently a work in progress. + +You can create webhooks using various different services. Below are a few services that you can possibly use. + +- [Zapier](https://ifttt.com/) +- [IFTTT](https://zapier.com/) diff --git a/docs/internals/config.md b/docs/internals/config.md new file mode 100644 index 0000000..a68bbd9 --- /dev/null +++ b/docs/internals/config.md @@ -0,0 +1,32 @@ +--- +aside: false +--- + +# Config + +Configurations are used to set up the server and database. The configurations are stored in a JSON file called `config.json` in the root directory of the project. + +## Config variables + +| Key | Type | Default | Reqiured | Description | +| ------------- | ------- | ------------------------------ | -------- | -------------------------------------------------------------------- | +| cors | String | [] | false | Comma separated list of domains to allow CORS requests from | +| database | Object | {"name": "lunalytics"} | false | Name of the database to use | +| jwtSecret | String | Random UUID | false | Secret key used to sign/verify JWT tokens | +| isDemo | boolean | false | false | Set to `enabled` to enable demo mode | +| migrationType | String | "automatic" | false | Type of migration to run. Can be either "automatic" or "manual" | +| port | Number | 2308 | false | Port to run the server on | +| version | String | Current version of npm package | false | Version of Lunalytics, used to apply migrations scripts for database | + +### Example config.json + +```json +{ + "jwtSecret": "lunalyticsJwtSecretKeyHerePlease", + "port": 2308, + "database": { "name": "lunalytics" }, + "isDemo": false, + "cors": ["http://localhost:3000", "http://localhost:8080"], + "version": "0.6.0" +} +``` diff --git a/docs/public/guides/Discord_Integration.webp b/docs/public/guides/Discord_Integration.webp new file mode 100644 index 0000000..81ff2a2 Binary files /dev/null and b/docs/public/guides/Discord_Integration.webp differ diff --git a/docs/public/guides/Discord_Webhook.webp b/docs/public/guides/Discord_Webhook.webp new file mode 100644 index 0000000..d8c9562 Binary files /dev/null and b/docs/public/guides/Discord_Webhook.webp differ diff --git a/docs/public/guides/Lunalytics_Add_Monitor_Notification.webp b/docs/public/guides/Lunalytics_Add_Monitor_Notification.webp new file mode 100644 index 0000000..41b02c9 Binary files /dev/null and b/docs/public/guides/Lunalytics_Add_Monitor_Notification.webp differ diff --git a/docs/public/guides/Lunalytics_Create_Monitor.webp b/docs/public/guides/Lunalytics_Create_Monitor.webp new file mode 100644 index 0000000..b848513 Binary files /dev/null and b/docs/public/guides/Lunalytics_Create_Monitor.webp differ diff --git a/docs/public/guides/Lunalytics_Create_Notification.webp b/docs/public/guides/Lunalytics_Create_Notification.webp new file mode 100644 index 0000000..c0eae34 Binary files /dev/null and b/docs/public/guides/Lunalytics_Create_Notification.webp differ diff --git a/docs/public/guides/Lunalytics_Discord_Create_Notification.webp b/docs/public/guides/Lunalytics_Discord_Create_Notification.webp new file mode 100644 index 0000000..e2fb1d2 Binary files /dev/null and b/docs/public/guides/Lunalytics_Discord_Create_Notification.webp differ diff --git a/package-lock.json b/package-lock.json index 4eefdf8..817516b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "lunalytics", - "version": "0.6.0", + "version": "0.6.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lunalytics", - "version": "0.6.0", + "version": "0.6.2", "license": "SEE LICENSE IN LICENSE", "dependencies": { "axios": "^1.6.2", diff --git a/package.json b/package.json index 44c6af5..2c24f6d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lunalytics", - "version": "0.6.1", + "version": "0.6.2", "description": "Open source Node.js server/website monitoring tool", "private": true, "author": "KSJaay ", @@ -27,7 +27,8 @@ "preview": "vite preview", "reset:password": "node ./scripts/reset.js", "server:watch": "nodemon --delay 2 --watch server --watch shared ./server/index.js", - "setup": "npm install && node ./scripts/setup.js && npm run build", + "setup": "npm install && node ./scripts/basic_setup.js && npm run build", + "setup:advance": "npm install && node ./scripts/setup.js && npm run build", "start": "cross-env NODE_ENV=production node server/index.js", "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs", diff --git a/scripts/basic_setup.js b/scripts/basic_setup.js new file mode 100644 index 0000000..14ff660 --- /dev/null +++ b/scripts/basic_setup.js @@ -0,0 +1,50 @@ +// import dependencies +import fs from 'fs'; +import path from 'path'; +import { v4 as uuidv4 } from 'uuid'; + +// import local files +import logger from '../server/utils/logger.js'; +import { loadJSON } from '../shared/parseJson.js'; +const packageJson = loadJSON('../package.json'); + +const configExists = () => { + const configPath = path.join(process.cwd(), 'config.json'); + return fs.existsSync(configPath); +}; + +if (configExists()) { + logger.error('SETUP', { + message: + 'Configuration file already exists. Please manually edit to overwrite or delete the file.', + }); + process.exit(0); +} + +try { + logger.info('SETUP', { message: 'Setting up application...' }); + + // write to config.json file + const configPath = path.join(process.cwd(), 'config.json'); + const config = { + port: 2308, + database: { name: 'lunalytics' }, + jwtSecret: uuidv4(), + migrationType: 'automatic', + version: packageJson.version, + }; + + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + + logger.info('SETUP', { message: 'Application setup successfully.' }); + + process.exit(0); +} catch (error) { + logger.error('SETUP', { + message: 'Unable to setup application. Please try again.', + error: error.message, + stack: error.stack, + }); + + process.exit(1); +} diff --git a/scripts/loadEnv.js b/scripts/loadEnv.js deleted file mode 100644 index 6e3cf3b..0000000 --- a/scripts/loadEnv.js +++ /dev/null @@ -1,31 +0,0 @@ -import { existsSync, readFileSync } from 'fs'; -import path from 'path'; -import logger from '../server/utils/logger.js'; - -const configPath = path.join(process.cwd(), 'config.json'); - -if (!existsSync(configPath)) { - logger.info('SETUP', { - message: - 'Configuration file not found. Please run "npm run setup" (or "yarn setup") to create it.', - }); - process.exit(1); -} - -const config = JSON.parse(readFileSync(configPath, 'utf-8')); - -process.env.PORT = config.port; -process.env.JWT_SECRET = config.jwtSecret; -process.env.IS_DEMO = config.isDemo ? 'enabled' : 'disabled'; -process.env.DATABASE_NAME = config.database?.name; -process.env.CORS_LIST = config.cors; - -if (process.env.NODE_ENV === 'test') { - process.env.DATABASE_NAME = 'e2e-test'; - - logger.info('SETUP', { - message: 'Changed database name to "e2e-test" for testing purposes.', - }); -} - -logger.info('SETUP', { message: 'Environment variables loaded successfully.' }); diff --git a/scripts/migrations/index.js b/scripts/migrations/index.js index e518354..f43bf8b 100644 --- a/scripts/migrations/index.js +++ b/scripts/migrations/index.js @@ -1,5 +1,3 @@ -import '../../scripts/loadEnv.js'; - // import local files import { migrate as migrateTcpUpdate } from './tcpUpdate-0-4-0.js'; import { migrate as migrateNotifications } from './notifications-0-6-0.js'; diff --git a/scripts/reset.js b/scripts/reset.js index 8259276..4863263 100644 --- a/scripts/reset.js +++ b/scripts/reset.js @@ -1,5 +1,3 @@ -import '../scripts/loadEnv.js'; - // import dependencies import inquirer from 'inquirer'; @@ -57,9 +55,7 @@ inquirer await client('user').where({ email }).update({ password: hashedPassowrd }); - logger.notice('RESET PASSWORD', { - message: `Password has been reset to: ${newPassword}`, - }); + console.log(`Password has been reset to: ${newPassword}`); await client.destroy(); process.exit(0); diff --git a/scripts/setup.js b/scripts/setup.js index 0180de3..e108d11 100644 --- a/scripts/setup.js +++ b/scripts/setup.js @@ -94,7 +94,7 @@ inquirer }) .catch((error) => { logger.error('SETUP', { - message: 'Enable to setup application. Please try again.', + message: 'Unable to setup application. Please try again.', error: error.message, stack: error.stack, }); diff --git a/server/cache/index.js b/server/cache/index.js index 4b4b674..771de8f 100644 --- a/server/cache/index.js +++ b/server/cache/index.js @@ -8,6 +8,7 @@ import httpStatusCheck from '../tools/httpStatus.js'; import tcpStatusCheck from '../tools/tcpPing.js'; import Collection from '../../shared/utils/collection.js'; import NotificationServices from '../notifications/index.js'; +import logger from '../utils/logger.js'; class Master { constructor() { @@ -148,7 +149,10 @@ class Master { await service.sendRecovery(notification, monitor, heartbeat); } } catch (error) { - console.log(error); + logger.error('Notification - sendNotification', { + error: error.message, + stack: error.stack, + }); } } } diff --git a/server/database/sqlite/setup.js b/server/database/sqlite/setup.js index 740d5ee..9690413 100644 --- a/server/database/sqlite/setup.js +++ b/server/database/sqlite/setup.js @@ -2,6 +2,9 @@ import { existsSync, closeSync, openSync } from 'fs'; import knex from 'knex'; import logger from '../../utils/logger.js'; +import config from '../../utils/config.js'; + +const configDatabaseName = config.get('database')?.name || 'lunalytics'; export class SQLite { constructor() { @@ -12,7 +15,7 @@ export class SQLite { if (this.client) return this.client; const path = `${process.cwd()}/server/database/sqlite/${ - databaseName || process.env.DATABASE_NAME || 'lunalytics' + databaseName || configDatabaseName || 'lunalytics' }.db`; if (!existsSync(path)) { diff --git a/server/index.js b/server/index.js index 349cab7..d4f689f 100644 --- a/server/index.js +++ b/server/index.js @@ -1,5 +1,3 @@ -import '../scripts/loadEnv.js'; - // import dependencies import express from 'express'; import cors from 'cors'; @@ -15,9 +13,14 @@ import initialiseCronJobs from './utils/cron.js'; import authorization from './middleware/authorization.js'; import migrateDatabase from '../scripts/migrate.js'; import isDemo from './middleware/demo.js'; +import config from './utils/config.js'; const app = express(); +const corsList = config.get('cors'); +const isDemoMode = config.get('isDemo'); +const port = config.get('port'); + const init = async () => { // connect to database and setup database tables await SQLite.connect(); @@ -44,14 +47,12 @@ const init = async () => { app.use( cors({ credentials: true, - origin: process.env.CORS_LIST?.split(',') || ['http://localhost:3000'], + origin: corsList || ['http://localhost:3000'], }) ); } else { - if (process.env.CORS_LIST) { - app.use( - cors({ credentials: true, origin: process.env.CORS_LIST?.split(',') }) - ); + if (corsList) { + app.use(cors({ credentials: true, origin: corsList })); } logger.info('Express', { message: 'Serving production static files' }); @@ -62,7 +63,7 @@ const init = async () => { return res.status(200).send('Everything looks good :D'); }); - if (process.env.IS_DEMO === 'enabled') { + if (isDemoMode) { app.get('/api/kanban', (req, res) => { return res.sendFile(path.join(process.cwd(), '/public/kanban.json')); }); @@ -83,7 +84,7 @@ const init = async () => { } // Start the server - const server_port = process.env.PORT || 2308; + const server_port = port || 2308; app.listen(server_port, () => { logger.info('Express', { message: `Server is running on port ${server_port}`, diff --git a/server/middleware/demo.js b/server/middleware/demo.js index 8fb20c3..7606070 100644 --- a/server/middleware/demo.js +++ b/server/middleware/demo.js @@ -1,14 +1,13 @@ import { getDemoUser } from '../database/queries/user.js'; import { setDemoCookie } from '../../shared/utils/cookies.js'; +import config from '../utils/config.js'; + +const isDemoMode = config.get('isDemo'); const isDemo = async (request, response, next) => { const { access_token } = request.cookies; - if ( - process.env.NODE_ENV === 'production' && - process.env.IS_DEMO === 'enabled' && - !access_token - ) { + if (process.env.NODE_ENV === 'production' && isDemoMode && !access_token) { if ( !request.url.startsWith('/register') && !request.url.startsWith('/login') diff --git a/server/utils/config.js b/server/utils/config.js new file mode 100644 index 0000000..330e44c --- /dev/null +++ b/server/utils/config.js @@ -0,0 +1,70 @@ +import fs from 'fs'; +import logger from './logger.js'; + +class Config { + constructor() { + this.configPath = `${process.cwd()}/config.json`; + this.config = {}; + + try { + fs.watch(this.configPath, { persistent: false }, (eventType) => { + if (eventType === 'change') { + this.readConfigFile(); + } + }); + } catch (error) { + logger.error('CONFIG', { + message: error?.message, + stack: error?.stack, + }); + } + + logger.info('CONFIG', { message: 'Loading configuration...' }); + + this.readConfigFile(); + } + + readConfigFile() { + if (!fs.existsSync(this.configPath)) { + logger.error('CONFIG', { + message: + 'Configuration file not found. Please run "npm run setup" (or "yarn setup" or "pnpm setup") to create it.', + }); + return; + } + + const fileData = fs.readFileSync(this.configPath); + + try { + this.config = JSON.parse(fileData); + process.env.VITE_API_URL = `http://localhost:${this.config.port}`; + + if (process.env.NODE_ENV === 'test') { + if (!this.config.database) this.config.database = {}; + this.config.database.name = 'e2e-test'; + + logger.info('CONFIG', { + message: 'Changed database name to "e2e-test" for testing purposes.', + }); + } + + logger.info('CONFIG', { + message: 'Configuration has been setup successfully.', + }); + } catch (jsonError) { + logger.error(`CONFIG`, { + message: 'Unable to parse config file JSON', + jsonError, + }); + } + } + + get(key) { + const value = this.config[key]; + return value; + } +} + +const config = new Config(); + +export default config; diff --git a/server/utils/jwt.js b/server/utils/jwt.js index 5fd5f4e..c615d2c 100644 --- a/server/utils/jwt.js +++ b/server/utils/jwt.js @@ -3,12 +3,12 @@ import jwt from 'jsonwebtoken'; // import local files import logger from './logger.js'; +import config from './config.js'; + +const jwtSecret = config.get('jwtSecret'); const verifyCookie = (value) => { try { - const jwtSecret = - process.env.JWT_SECRET || 'lunalyticsJwtSecretKeyHerePlease'; - let token = jwt.verify(value, jwtSecret, { algorithms: ['HS256'], }); @@ -24,9 +24,6 @@ const verifyCookie = (value) => { const signCookie = (value) => { try { - const jwtSecret = - process.env.JWT_SECRET || 'lunalyticsJwtSecretKeyHerePlease'; - let token = jwt.sign(value, jwtSecret, { expiresIn: 2592000, });