From bff3f3ca5e954b2c960815d092819a5cf77bfe1d Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 21 Mar 2022 10:54:50 +0000 Subject: [PATCH] big refactor - move into src/worker and src/server --- Dockerfile | 2 +- README.md | 12 +- docker-compose.yml | 2 +- ecosystem.config.js | 4 +- package.json | 4 +- server.js | 129 ------------------ {models => src/models}/accounts.model.js | 0 .../models}/athena_action_log.model.js | 0 .../models}/athena_returned_data.model.js | 0 .../models}/device_authorised_users.model.js | 0 {models => src/models}/devices.model.js | 0 .../models}/drive_segments.model.js | 0 {models => src/models}/drives.model.js | 0 {models => src/models}/index.model.js | 0 .../models}/oauth_accounts.model.js | 0 src/server/app.js | 84 ++++++++++++ .../server/consistency}/terms.js | 0 .../server/controllers}/admin.js | 2 +- .../controllers}/authentication/index.js | 2 +- .../authentication/oauth/google.js | 0 .../authentication/oauth/index.js | 0 .../controllers}/authentication/register.js | 0 .../controllers}/authentication/twofactor.js | 2 +- .../server/controllers}/devices.js | 2 +- .../server/controllers}/helpers.js | 0 .../server/controllers}/index.js | 0 .../server/controllers}/mailing.js | 0 .../server/controllers}/storage.js | 0 .../server/controllers}/upload.js | 0 .../server/controllers}/users.js | 2 +- src/server/index.js | 36 +++++ .../server/routes}/administration/adminApi.js | 0 {routes => src/server/routes}/api.js | 16 +-- .../server/routes}/api/authentication.js | 0 .../routes}/api/authentication/oauth.js | 0 .../routes}/api/authentication/twofactor.js | 0 {routes => src/server/routes}/api/devices.js | 0 {routes => src/server/routes}/api/realtime.js | 2 +- .../server/routes}/api/registration.js | 0 {routes => src/server/routes}/index.js | 0 {routes => src/server/routes}/userAdminApi.js | 0 {routes => src/server/routes}/useradmin.js | 10 +- .../server/schema}/authentication.js | 0 {schema => src/server/schema}/index.js | 0 .../server/schema}/routes/devices.js | 0 .../websocket}/athena/helpers.js | 0 {websocket => src/websocket}/athena/index.js | 2 +- {websocket => src/websocket}/index.js | 0 {websocket => src/websocket}/web/commands.js | 6 +- {websocket => src/websocket}/web/controls.js | 2 +- {websocket => src/websocket}/web/index.js | 2 +- worker.js => src/worker/index.js | 2 +- test/routes/api.test.js | 127 +++++++++-------- test/routes/userAdminApi.test.js | 70 ++++------ test/routes/useradmin.test.js | 84 ++++++------ test/test.js | 12 +- 56 files changed, 293 insertions(+), 325 deletions(-) delete mode 100644 server.js rename {models => src/models}/accounts.model.js (100%) rename {models => src/models}/athena_action_log.model.js (100%) rename {models => src/models}/athena_returned_data.model.js (100%) rename {models => src/models}/device_authorised_users.model.js (100%) rename {models => src/models}/devices.model.js (100%) rename {models => src/models}/drive_segments.model.js (100%) rename {models => src/models}/drives.model.js (100%) rename {models => src/models}/index.model.js (100%) rename {models => src/models}/oauth_accounts.model.js (100%) create mode 100644 src/server/app.js rename {consistency => src/server/consistency}/terms.js (100%) rename {controllers => src/server/controllers}/admin.js (96%) rename {controllers => src/server/controllers}/authentication/index.js (98%) rename {controllers => src/server/controllers}/authentication/oauth/google.js (100%) rename {controllers => src/server/controllers}/authentication/oauth/index.js (100%) rename {controllers => src/server/controllers}/authentication/register.js (100%) rename {controllers => src/server/controllers}/authentication/twofactor.js (96%) rename {controllers => src/server/controllers}/devices.js (99%) rename {controllers => src/server/controllers}/helpers.js (100%) rename {controllers => src/server/controllers}/index.js (100%) rename {controllers => src/server/controllers}/mailing.js (100%) rename {controllers => src/server/controllers}/storage.js (100%) rename {controllers => src/server/controllers}/upload.js (100%) rename {controllers => src/server/controllers}/users.js (98%) create mode 100644 src/server/index.js rename {routes => src/server/routes}/administration/adminApi.js (100%) rename {routes => src/server/routes}/api.js (98%) rename {routes => src/server/routes}/api/authentication.js (100%) rename {routes => src/server/routes}/api/authentication/oauth.js (100%) rename {routes => src/server/routes}/api/authentication/twofactor.js (100%) rename {routes => src/server/routes}/api/devices.js (100%) rename {routes => src/server/routes}/api/realtime.js (99%) rename {routes => src/server/routes}/api/registration.js (100%) rename {routes => src/server/routes}/index.js (100%) rename {routes => src/server/routes}/userAdminApi.js (100%) rename {routes => src/server/routes}/useradmin.js (97%) rename {schema => src/server/schema}/authentication.js (100%) rename {schema => src/server/schema}/index.js (100%) rename {schema => src/server/schema}/routes/devices.js (100%) rename {websocket => src/websocket}/athena/helpers.js (100%) rename {websocket => src/websocket}/athena/index.js (98%) rename {websocket => src/websocket}/index.js (100%) rename {websocket => src/websocket}/web/commands.js (92%) rename {websocket => src/websocket}/web/controls.js (94%) rename {websocket => src/websocket}/web/index.js (97%) rename worker.js => src/worker/index.js (99%) diff --git a/Dockerfile b/Dockerfile index e681a17..24d9d78 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,4 +11,4 @@ RUN npm ci COPY . . EXPOSE 3000 -CMD ["node", "-r", "esm", "server.js"] +CMD ["node", "-r", "esm", "src/server"] diff --git a/README.md b/README.md index 7af699a..bc5def5 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ If you don't want to host your own instance, check out https://api.retropilot.or The server consists of 2 node scripts. -`server.js` is using expressjs and runs the backend (file upload / communication with openpilot) and the useradmin dashboard to manage / view / download drives & logs. -`worker.js` is a background worker that is processing drives (analyzing video files & logs) to prepare drives for playback in cabana and to gather statistics. It automatically terminates itself after 60 minutes to make sure the video/log libraries do not cause memory leaks. +`src/server` is using expressjs and runs the backend (file upload / communication with openpilot) and the useradmin dashboard to manage / view / download drives & logs. +`src/worker` is a background worker that is processing drives (analyzing video files & logs) to prepare drives for playback in cabana and to gather statistics. It automatically terminates itself after 60 minutes to make sure the video/log libraries do not cause memory leaks. Both scripts can be started with a cronjob each minute, they use locking to make sure they run exclusively. @@ -27,15 +27,15 @@ cp database.empty.sqlite database.sqlite ### [Server] Running ``` -node -r esm server.js +node -r esm src/server ``` ``` -node -r esm worker.js +node -r esm src/worker ``` ### [Server] CABANA Support -A compiled version of a custom cabana fork (https://github.com/florianbrede-ayet/retropilot-cabana) is directly bundled in the `cabana/` subdirectory and will be served by the express app. After starting `server.js`, cabana is ready to use. +A compiled version of a custom cabana fork (https://github.com/florianbrede-ayet/retropilot-cabana) is directly bundled in the `cabana/` subdirectory and will be served by the express app. After starting `index.js`, cabana is ready to use. ----- @@ -96,4 +96,4 @@ The athena websockets interface is not implemented yet, so the comma app and ath Launch with: ``` docker-compose -f docker-compose.yml -f docker-compose.uat.yml up -d -``` \ No newline at end of file +``` diff --git a/docker-compose.yml b/docker-compose.yml index 2eac0f5..d03f15a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: - "4040:4040" worker: build: . - command: node -r esm worker.js + command: node -r esm src/worker restart: unless-stopped depends_on: - db diff --git a/ecosystem.config.js b/ecosystem.config.js index 5a1715b..c06c2a9 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -1,9 +1,9 @@ module.exports = [{ name: 'server', - script: 'server.js', + script: 'src/server', node_args: '-r esm', }, { name: 'worker', - script: 'worker.js', + script: 'src/worker', node_args: '-r esm', }]; diff --git a/package.json b/package.json index fa2d0b6..ac35b22 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,10 @@ "name": "retropilot-server", "version": "1.0.0", "description": "replacement for comma.ai backend and useradmin dashboard. can be combined with a modified cabana instance.", - "main": "server.js", + "main": "src/server/index.js", "scripts": { "test": "mocha", - "start": "node --es-module-specifier-resolution=node server.js", + "start": "node -r esm src/server", "lint": "eslint . --ext .js", "lint:fix": "eslint . --ext .js --fix" }, diff --git a/server.js b/server.js deleted file mode 100644 index 44137c1..0000000 --- a/server.js +++ /dev/null @@ -1,129 +0,0 @@ -/* eslint-disable global-require */ -import 'dotenv/config' - -import log4js from 'log4js'; -import lockfile from 'proper-lockfile'; -import http from 'http'; -import express from 'express'; -import cors from 'cors'; -import rateLimit from 'express-rate-limit'; -import cookieParser from 'cookie-parser'; -import storageController from './controllers/storage.js'; - -/* eslint-disable no-unused-vars */ - -import athena from './websocket/athena/index.js'; -import routers from './routes/index.js'; -import controllers from './controllers/index.js'; - -/* eslint-enable no-unused-vars */ - -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; - -process.on('unhandledRejection', (error, p) => { - console.log('=== UNHANDLED REJECTION ==='); - console.log(error.promise, p); - console.dir(error.stack); -}); - -log4js.configure({ - appenders: { logfile: { type: 'file', filename: 'server.log' }, out: { type: 'console' } /* {type: "file", filename: "server1.log"} */ }, - categories: { default: { appenders: ['out', 'logfile'], level: 'info' } }, -}); - -const logger = log4js.getLogger('default'); -// TODO evaluate if this is the best way to determine the root of project - - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -global.__basedir = __dirname; - -function runAsyncWrapper(callback) { - return function wrapper(req, res, next) { - callback(req, res, next) - .catch(next); - }; -} - -const web = async () => { - const app = express(); - - app.use((req, res, next) => { - res.header('Access-Control-Allow-Origin', `${process.env.BASE_URL}`); - res.header('Access-Control-Allow-Credentials', true); - res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); - next(); - }); - - storageController.initializeStorage(); - await storageController.updateTotalStorageUsed(); - - app.use(routers.api); - app.use(routers.useradmin); - app.use(routers.authenticationApi); - - if (process.env.ATHENA_ENABLED) { - const athenaRateLimit = rateLimit({ - windowMs: 30000, - max: process.env.ATHENA_API_RATE_LIMIT, - }); - - app.use((req, res, next) => { - req.athenaWebsocketTemp = athena; - return next(); - }); - - app.use('/admin', routers.admin); - app.use('/realtime', athenaRateLimit); - app.use('/realtime', routers.realtime); - //app.use(routers.oauthAuthenticator) - } else { - logger.log('Athena disabled'); - } - - app.use(cors({ origin: 'http://localhost:3000' })); - app.use(cookieParser()); - app.use('/favicon.ico', express.static('static/favicon.ico')); - app.use(process.env.BASE_DRIVE_DOWNLOAD_PATH_MAPPING, express.static(process.env.STORAGE_PATH)); - - app.use(routers.deviceApi); - - app.use('/.well-known', express.static('.well-known')); - - app.use('/cabana', express.static('cabana/')); - - app.get('/', async (req, res) => { - res.redirect('/useradmin') - }); - - app.get('*', runAsyncWrapper(async (req, res) => { - logger.error(`HTTP.GET unhandled request: ${controllers.helpers.simpleStringify(req)}, ${controllers.helpers.simpleStringify(res)}`); - res.status(404); - res.send('Not Implemented'); - })); - - app.post('*', runAsyncWrapper(async (req, res) => { - logger.error(`HTTP.POST unhandled request: ${controllers.helpers.simpleStringify(req)}, ${controllers.helpers.simpleStringify(res)}`); - res.status(404); - res.send('Not Implemented'); - })); - - return app; -}; - -lockfile.lock('retropilot_server', { realpath: false, stale: 30000, update: 2000 }) - .then(async () => { - console.log('STARTING SERVER...'); - const app = await web(); - const httpServer = http.createServer(app); - - httpServer.listen(process.env.HTTP_PORT, () => { - logger.info(`RetroPilot Server listening at ${process.env.BASE_URL}`); - }); - - }).catch((e) => { - console.error(e); - process.exit(); - }); diff --git a/models/accounts.model.js b/src/models/accounts.model.js similarity index 100% rename from models/accounts.model.js rename to src/models/accounts.model.js diff --git a/models/athena_action_log.model.js b/src/models/athena_action_log.model.js similarity index 100% rename from models/athena_action_log.model.js rename to src/models/athena_action_log.model.js diff --git a/models/athena_returned_data.model.js b/src/models/athena_returned_data.model.js similarity index 100% rename from models/athena_returned_data.model.js rename to src/models/athena_returned_data.model.js diff --git a/models/device_authorised_users.model.js b/src/models/device_authorised_users.model.js similarity index 100% rename from models/device_authorised_users.model.js rename to src/models/device_authorised_users.model.js diff --git a/models/devices.model.js b/src/models/devices.model.js similarity index 100% rename from models/devices.model.js rename to src/models/devices.model.js diff --git a/models/drive_segments.model.js b/src/models/drive_segments.model.js similarity index 100% rename from models/drive_segments.model.js rename to src/models/drive_segments.model.js diff --git a/models/drives.model.js b/src/models/drives.model.js similarity index 100% rename from models/drives.model.js rename to src/models/drives.model.js diff --git a/models/index.model.js b/src/models/index.model.js similarity index 100% rename from models/index.model.js rename to src/models/index.model.js diff --git a/models/oauth_accounts.model.js b/src/models/oauth_accounts.model.js similarity index 100% rename from models/oauth_accounts.model.js rename to src/models/oauth_accounts.model.js diff --git a/src/server/app.js b/src/server/app.js new file mode 100644 index 0000000..7b4631c --- /dev/null +++ b/src/server/app.js @@ -0,0 +1,84 @@ +import cookieParser from 'cookie-parser'; +import cors from 'cors'; +import express from 'express'; +import rateLimit from 'express-rate-limit'; +import log4js from 'log4js'; + +import storageController from './controllers/storage'; +import athena from '../websocket/athena'; +import controllers from './controllers'; +import routers from './routes'; + +const logger = log4js.getLogger('default'); + +function runAsyncWrapper(callback) { + return function wrapper(req, res, next) { + callback(req, res, next) + .catch(next); + }; +} + +const tasks = []; +const app = express(); + +app.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', `${process.env.BASE_URL}`); + res.header('Access-Control-Allow-Credentials', true); + res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); + next(); +}); + +storageController.initializeStorage(); +tasks.push(storageController.updateTotalStorageUsed()); + +app.use(routers.api); +app.use(routers.useradmin); +app.use(routers.authenticationApi); + +if (process.env.ATHENA_ENABLED) { + const athenaRateLimit = rateLimit({ + windowMs: 30000, + max: process.env.ATHENA_API_RATE_LIMIT, + }); + + app.use((req, res, next) => { + req.athenaWebsocketTemp = athena; + return next(); + }); + + app.use('/admin', routers.admin); + app.use('/realtime', athenaRateLimit); + app.use('/realtime', routers.realtime); + // app.use(routers.oauthAuthenticator) +} else { + logger.log('Athena disabled'); +} + +app.use(cors({ origin: 'http://localhost:3000' })); +app.use(cookieParser()); +app.use('/favicon.ico', express.static('static/favicon.ico')); +app.use(process.env.BASE_DRIVE_DOWNLOAD_PATH_MAPPING, express.static(process.env.STORAGE_PATH)); + +app.use(routers.deviceApi); + +app.use('/.well-known', express.static('.well-known')); + +app.use('/cabana', express.static('cabana/')); + +app.get('/', async (req, res) => { + res.redirect('/useradmin'); +}); + +app.get('*', runAsyncWrapper(async (req, res) => { + logger.error(`HTTP.GET unhandled request: ${controllers.helpers.simpleStringify(req)}, ${controllers.helpers.simpleStringify(res)}`); + res.status(404); + res.send('Not Implemented'); +})); + +app.post('*', runAsyncWrapper(async (req, res) => { + logger.error(`HTTP.POST unhandled request: ${controllers.helpers.simpleStringify(req)}, ${controllers.helpers.simpleStringify(res)}`); + res.status(404); + res.send('Not Implemented'); +})); + +export default Promise.all(tasks).then(() => app); diff --git a/consistency/terms.js b/src/server/consistency/terms.js similarity index 100% rename from consistency/terms.js rename to src/server/consistency/terms.js diff --git a/controllers/admin.js b/src/server/controllers/admin.js similarity index 96% rename from controllers/admin.js rename to src/server/controllers/admin.js index 945d7c5..50c20ae 100644 --- a/controllers/admin.js +++ b/src/server/controllers/admin.js @@ -1,4 +1,4 @@ -import orm from '../models/index.model'; +import orm from '../../models/index.model'; // TODO move everythijng away from this dumb intertwined style diff --git a/controllers/authentication/index.js b/src/server/controllers/authentication/index.js similarity index 98% rename from controllers/authentication/index.js rename to src/server/controllers/authentication/index.js index 7be2ca9..a1cf02d 100644 --- a/controllers/authentication/index.js +++ b/src/server/controllers/authentication/index.js @@ -1,7 +1,7 @@ import crypto from 'crypto'; import jsonwebtoken from 'jsonwebtoken'; import log4js from 'log4js'; -import orm from '../../models/index.model'; +import orm from '../../../models/index.model'; const logger = log4js.getLogger('default'); diff --git a/controllers/authentication/oauth/google.js b/src/server/controllers/authentication/oauth/google.js similarity index 100% rename from controllers/authentication/oauth/google.js rename to src/server/controllers/authentication/oauth/google.js diff --git a/controllers/authentication/oauth/index.js b/src/server/controllers/authentication/oauth/index.js similarity index 100% rename from controllers/authentication/oauth/index.js rename to src/server/controllers/authentication/oauth/index.js diff --git a/controllers/authentication/register.js b/src/server/controllers/authentication/register.js similarity index 100% rename from controllers/authentication/register.js rename to src/server/controllers/authentication/register.js diff --git a/controllers/authentication/twofactor.js b/src/server/controllers/authentication/twofactor.js similarity index 96% rename from controllers/authentication/twofactor.js rename to src/server/controllers/authentication/twofactor.js index aa35c12..61511f0 100644 --- a/controllers/authentication/twofactor.js +++ b/src/server/controllers/authentication/twofactor.js @@ -6,7 +6,7 @@ import { AUTH_2FA_ENROLLED, AUTH_2FA_BAD_TOKEN, } from '../../consistency/terms'; -import orm from '../../models/index.model'; +import orm from '../../../models/index.model'; export async function twoFactorOnboard(account) { if (!account || !account.dataValues) { return { success: false, ...AUTH_2FA_BAD_ACCOUNT }; } diff --git a/controllers/devices.js b/src/server/controllers/devices.js similarity index 99% rename from controllers/devices.js rename to src/server/controllers/devices.js index 0e92252..2edf61d 100644 --- a/controllers/devices.js +++ b/src/server/controllers/devices.js @@ -2,7 +2,7 @@ import sanitizeFactory from 'sanitize'; import crypto from 'crypto'; import dirTree from 'directory-tree'; import log4js from 'log4js'; -import orm from '../models/index.model'; +import orm from '../../models/index.model'; import { readJWT, validateJWT } from './authentication'; import { getAccountFromId } from './users'; diff --git a/controllers/helpers.js b/src/server/controllers/helpers.js similarity index 100% rename from controllers/helpers.js rename to src/server/controllers/helpers.js diff --git a/controllers/index.js b/src/server/controllers/index.js similarity index 100% rename from controllers/index.js rename to src/server/controllers/index.js diff --git a/controllers/mailing.js b/src/server/controllers/mailing.js similarity index 100% rename from controllers/mailing.js rename to src/server/controllers/mailing.js diff --git a/controllers/storage.js b/src/server/controllers/storage.js similarity index 100% rename from controllers/storage.js rename to src/server/controllers/storage.js diff --git a/controllers/upload.js b/src/server/controllers/upload.js similarity index 100% rename from controllers/upload.js rename to src/server/controllers/upload.js diff --git a/controllers/users.js b/src/server/controllers/users.js similarity index 98% rename from controllers/users.js rename to src/server/controllers/users.js index 0df1af7..25118e2 100644 --- a/controllers/users.js +++ b/src/server/controllers/users.js @@ -1,6 +1,6 @@ import crypto from 'crypto'; import log4js from 'log4js'; -import orm from '../models/index.model'; +import orm from '../../models/index.model'; const logger = log4js.getLogger('default'); diff --git a/src/server/index.js b/src/server/index.js new file mode 100644 index 0000000..5e93938 --- /dev/null +++ b/src/server/index.js @@ -0,0 +1,36 @@ +import 'dotenv/config'; +import http from 'http'; +import log4js from 'log4js'; +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; + +log4js.configure({ + appenders: { logfile: { type: 'file', filename: 'server.log' }, out: { type: 'console' } /* {type: "file", filename: "server1.log"} */ }, + categories: { default: { appenders: ['out', 'logfile'], level: 'info' } }, +}); + +process.on('unhandledRejection', (error, p) => { + console.log('=== UNHANDLED REJECTION ==='); + console.log(error.promise, p); + console.dir(error.stack); +}); + +// TODO evaluate if this is the best way to determine the root of project +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +global.__basedir = __dirname; + +const main = async () => { + const logger = log4js.getLogger('default'); + const httpServer = http.createServer(await require('./app').default); + + httpServer.listen(process.env.HTTP_PORT, () => { + logger.info(`RetroPilot Server listening at ${process.env.BASE_URL}`); + }); +}; + +try { + main(); +} catch (e) { + console.error(e); +} diff --git a/routes/administration/adminApi.js b/src/server/routes/administration/adminApi.js similarity index 100% rename from routes/administration/adminApi.js rename to src/server/routes/administration/adminApi.js diff --git a/routes/api.js b/src/server/routes/api.js similarity index 98% rename from routes/api.js rename to src/server/routes/api.js index a59a12e..2921a91 100644 --- a/routes/api.js +++ b/src/server/routes/api.js @@ -5,8 +5,8 @@ import crypto from 'crypto'; import log4js from 'log4js'; import storageController from '../controllers/storage'; import deviceController from '../controllers/devices'; -import authenticationController from './../controllers/authentication'; -import userController from './../controllers/users'; +import authenticationController from '../controllers/authentication'; +import userController from '../controllers/users'; const logger = log4js.getLogger('default'); const router = express.Router(); @@ -314,14 +314,14 @@ async function upload(req, res) { }) await deviceController.updateOrCreateDriveSegment(dongleId, driveName, segment, { - duration: 0, - distance_meters: 0, - upload_complete: false, - is_processed: false, - is_stalled: false, + duration: 0, + distance_meters: 0, + upload_complete: false, + is_processed: false, + is_stalled: false, created: Date.now() }) - + logger.info(`HTTP.UPLOAD_URL updated existing drive: ${JSON.stringify(drive)}`); } diff --git a/routes/api/authentication.js b/src/server/routes/api/authentication.js similarity index 100% rename from routes/api/authentication.js rename to src/server/routes/api/authentication.js diff --git a/routes/api/authentication/oauth.js b/src/server/routes/api/authentication/oauth.js similarity index 100% rename from routes/api/authentication/oauth.js rename to src/server/routes/api/authentication/oauth.js diff --git a/routes/api/authentication/twofactor.js b/src/server/routes/api/authentication/twofactor.js similarity index 100% rename from routes/api/authentication/twofactor.js rename to src/server/routes/api/authentication/twofactor.js diff --git a/routes/api/devices.js b/src/server/routes/api/devices.js similarity index 100% rename from routes/api/devices.js rename to src/server/routes/api/devices.js diff --git a/routes/api/realtime.js b/src/server/routes/api/realtime.js similarity index 99% rename from routes/api/realtime.js rename to src/server/routes/api/realtime.js index b01a6ba..9d097a6 100644 --- a/routes/api/realtime.js +++ b/src/server/routes/api/realtime.js @@ -4,7 +4,7 @@ import authenticationController from '../../controllers/authentication'; import userController from '../../controllers/users'; import deviceController from '../../controllers/devices'; -import models from '../../models/index.model'; +import models from '../../../models/index.model'; /* eslint-enable no-unused-vars */ const router = express.Router(); const whitelistParams = { diff --git a/routes/api/registration.js b/src/server/routes/api/registration.js similarity index 100% rename from routes/api/registration.js rename to src/server/routes/api/registration.js diff --git a/routes/index.js b/src/server/routes/index.js similarity index 100% rename from routes/index.js rename to src/server/routes/index.js diff --git a/routes/userAdminApi.js b/src/server/routes/userAdminApi.js similarity index 100% rename from routes/userAdminApi.js rename to src/server/routes/userAdminApi.js diff --git a/routes/useradmin.js b/src/server/routes/useradmin.js similarity index 97% rename from routes/useradmin.js rename to src/server/routes/useradmin.js index 1c14dad..0d6b098 100644 --- a/routes/useradmin.js +++ b/src/server/routes/useradmin.js @@ -601,11 +601,11 @@ router.get('/useradmin/drive/:dongleId/:driveIdentifier', runAsyncWrapper(async var qlog = '--'; var rlog = '--'; for (var c in directoryTree.children[i].children) { - if (directoryTree.children[i].children[c].name == 'fcamera.hevc') fcamera = '' + directoryTree.children[i].children[c].name + ''; - if (directoryTree.children[i].children[c].name == 'dcamera.hevc') fcamera = '' + directoryTree.children[i].children[c].name + ''; - if (directoryTree.children[i].children[c].name == 'qcamera.ts') qcamera = '' + directoryTree.children[i].children[c].name + ''; - if (directoryTree.children[i].children[c].name == 'qlog.bz2') qlog = '' + directoryTree.children[i].children[c].name + ''; - if (directoryTree.children[i].children[c].name == 'rlog.bz2') rlog = '' + directoryTree.children[i].children[c].name + ''; + if (directoryTree.children[i].children[c].name == 'fcamera.hevc') fcamera = '' + driveUrl + segment + '' + directoryTree.children[i].children[c].name + '' + directoryTree.children[i].children[c].name + ''; + if (directoryTree.children[i].children[c].name == 'dcamera.hevc') fcamera = '' + driveUrl + segment + '' + directoryTree.children[i].children[c].name + '' + directoryTree.children[i].children[c].name + ''; + if (directoryTree.children[i].children[c].name == 'qcamera.ts') qcamera = '' + driveUrl + segment + '' + directoryTree.children[i].children[c].name + '' + directoryTree.children[i].children[c].name + ''; + if (directoryTree.children[i].children[c].name == 'qlog.bz2') qlog = '' + driveUrl + segment + '' + directoryTree.children[i].children[c].name + '' + directoryTree.children[i].children[c].name + ''; + if (directoryTree.children[i].children[c].name == 'rlog.bz2') rlog = '' + driveUrl + segment + '' + directoryTree.children[i].children[c].name + '' + directoryTree.children[i].children[c].name + ''; } var isProcessed = '?'; diff --git a/schema/authentication.js b/src/server/schema/authentication.js similarity index 100% rename from schema/authentication.js rename to src/server/schema/authentication.js diff --git a/schema/index.js b/src/server/schema/index.js similarity index 100% rename from schema/index.js rename to src/server/schema/index.js diff --git a/schema/routes/devices.js b/src/server/schema/routes/devices.js similarity index 100% rename from schema/routes/devices.js rename to src/server/schema/routes/devices.js diff --git a/websocket/athena/helpers.js b/src/websocket/athena/helpers.js similarity index 100% rename from websocket/athena/helpers.js rename to src/websocket/athena/helpers.js diff --git a/websocket/athena/index.js b/src/websocket/athena/index.js similarity index 98% rename from websocket/athena/index.js rename to src/websocket/athena/index.js index 59f4ab4..6fc8b3b 100644 --- a/websocket/athena/index.js +++ b/src/websocket/athena/index.js @@ -7,7 +7,7 @@ import log4js from 'log4js'; import models from '../../models/index.model'; import helperFunctions from './helpers'; -import deviceController from '../../controllers/devices'; +import deviceController from '../../server/controllers/devices'; const logger = log4js.getLogger('default'); diff --git a/websocket/index.js b/src/websocket/index.js similarity index 100% rename from websocket/index.js rename to src/websocket/index.js diff --git a/websocket/web/commands.js b/src/websocket/web/commands.js similarity index 92% rename from websocket/web/commands.js rename to src/websocket/web/commands.js index 49d893f..7488d2b 100644 --- a/websocket/web/commands.js +++ b/src/websocket/web/commands.js @@ -1,8 +1,8 @@ // eslint-disable-next-line no-unused-vars -import authenticationController from '../../controllers/authentication'; +import authenticationController from '../../server/controllers/authentication'; -import deviceController from '../../controllers/devices'; -import athenaRealtime from '../athena/index'; +import deviceController from '../../server/controllers/devices'; +import athenaRealtime from '../athena'; // Checks if device is currently online in Athena diff --git a/websocket/web/controls.js b/src/websocket/web/controls.js similarity index 94% rename from websocket/web/controls.js rename to src/websocket/web/controls.js index 54e5a4e..9218445 100644 --- a/websocket/web/controls.js +++ b/src/websocket/web/controls.js @@ -1,4 +1,4 @@ -import deviceController from '../../controllers/devices'; +import deviceController from '../../server/controllers/devices'; let wss; diff --git a/websocket/web/index.js b/src/websocket/web/index.js similarity index 97% rename from websocket/web/index.js rename to src/websocket/web/index.js index 221443f..b26408d 100644 --- a/websocket/web/index.js +++ b/src/websocket/web/index.js @@ -2,7 +2,7 @@ import { WebSocketServer } from 'ws'; import cookie from 'cookie'; import httpServer from 'http'; import log4js from 'log4js'; -import authenticationController from '../../controllers/authentication'; +import authenticationController from '../../server/controllers/authentication'; import athenaRealtime from '../athena'; import controlsFunction from './controls'; diff --git a/worker.js b/src/worker/index.js similarity index 99% rename from worker.js rename to src/worker/index.js index 54a1e04..2df1ea1 100644 --- a/worker.js +++ b/src/worker/index.js @@ -12,7 +12,7 @@ import Reader from '@commaai/log_reader'; import ffprobe from 'ffprobe'; import ffprobeStatic from 'ffprobe-static'; -import orm from './models/index.model'; +import orm from '../models/index.model'; let lastCleaningTime = 0; let startTime = Date.now(); diff --git a/test/routes/api.test.js b/test/routes/api.test.js index c08d5c1..6e6dd83 100644 --- a/test/routes/api.test.js +++ b/test/routes/api.test.js @@ -1,74 +1,69 @@ import request from 'supertest'; import dummyGenerator from './../dummyGenerator'; -let app; +export default (app) => { + describe('/v2/pilotauth/ - Testing device registration', function () { + it('Returns dongle ID on valid registration', function (done) { + request(app) + .post('/v2/pilotauth/') + .query({ + imei: dummyGenerator.getImei(), + serial: dummyGenerator.getSerial(), + public_key: dummyGenerator.devicePubKey, + register_token: dummyGenerator.makeJWT(), + }) - -export default (server) => { - app = server; - - describe('/v2/pilotauth/ - Testing device registration', function() { - it('Returns dongle ID on valid registration', function(done) { - request(server) - .post('/v2/pilotauth/') - .query({ - imei: dummyGenerator.getImei(), - serial: dummyGenerator.getSerial(), - public_key: dummyGenerator.devicePubKey, - register_token: dummyGenerator.makeJWT() - }) - - .set('Accept', 'application/x-www-form-urlencoded') - .expect('Content-Type', /json/) - .expect(200) - .expect((res) => { - if (!res.body.dongle_id) throw new Error("API Failed to return dongle_id on status 200") - }) - .end(done) - }); - - it('Returns 400 when incorrect public key given', function(done) { - request(server) - .post('/v2/pilotauth/') - .query({ - imei: dummyGenerator.getImei(), - serial: dummyGenerator.getSerial(), - public_key: dummyGenerator.rougePublicKey, - register_token: dummyGenerator.makeJWT() - }) - - .set('Accept', 'application/x-www-form-urlencoded') - .expect('Content-Type', /text/) - .expect(400) - .end(done) - }); - - it('Returns 400 when missing register_token', function(done) { - request(server) - .post('/v2/pilotauth/') - .query({ - imei: dummyGenerator.getImei(), - serial: dummyGenerator.getSerial(), - public_key: dummyGenerator.rougePublicKey, - register_token: "" - }) - - .set('Accept', 'application/x-www-form-urlencoded') - .expect('Content-Type', /text/) - .expect(400) - .end(done) - }); - - it('Returns 400 when missing query', function(done) { - request(server) - .post('/v2/pilotauth/') - - .set('Accept', 'application/x-www-form-urlencoded') - .expect('Content-Type', /text/) - .expect(400) - .end(done) - }); + .set('Accept', 'application/x-www-form-urlencoded') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + if (!res.body.dongle_id) { + throw new Error('API Failed to return dongle_id on status 200'); + } + }) + .end(done); }); + it('Returns 400 when incorrect public key given', function (done) { + request(app) + .post('/v2/pilotauth/') + .query({ + imei: dummyGenerator.getImei(), + serial: dummyGenerator.getSerial(), + public_key: dummyGenerator.rougePublicKey, + register_token: dummyGenerator.makeJWT(), + }) + .set('Accept', 'application/x-www-form-urlencoded') + .expect('Content-Type', /text/) + .expect(400) + .end(done); + }); + + it('Returns 400 when missing register_token', function (done) { + request(app) + .post('/v2/pilotauth/') + .query({ + imei: dummyGenerator.getImei(), + serial: dummyGenerator.getSerial(), + public_key: dummyGenerator.rougePublicKey, + register_token: '', + }) + + .set('Accept', 'application/x-www-form-urlencoded') + .expect('Content-Type', /text/) + .expect(400) + .end(done); + }); + + it('Returns 400 when missing query', function (done) { + request(app) + .post('/v2/pilotauth/') + + .set('Accept', 'application/x-www-form-urlencoded') + .expect('Content-Type', /text/) + .expect(400) + .end(done); + }); + }); }; diff --git a/test/routes/userAdminApi.test.js b/test/routes/userAdminApi.test.js index 01992fe..603681f 100644 --- a/test/routes/userAdminApi.test.js +++ b/test/routes/userAdminApi.test.js @@ -1,46 +1,34 @@ import request from 'supertest'; -import dummyGenerator from './../dummyGenerator'; -let app; - - - -export default (server) => { - app = server; - - describe('/api', function() { - it('Load general server stats', function (done) { - request(server) - .get('/retropilot/0/useradmin') - .expect('Content-Type', /json/) - .expect(200) - .expect((req) => { - const body = req.body; - - try { - if ( - body.hasOwnProperty('success') && body.success === true && - body.hasOwnProperty('data') && - body.data.hasOwnProperty('serverStats') && - body.data.serverStats.hasOwnProperty('config') && - typeof body.data.serverStats.config.registerAllowed === "boolean" && - typeof body.data.serverStats.process.env.WELCOME_MESSAGE === "string" && - typeof body.data.serverStats['accounts'] === "number" && - typeof body.data.serverStats['devices'] === "number" && - typeof body.data.serverStats['drives'] === "number" && - (typeof body.data.serverStats['storageUsed'] === "number" || body.data.serverStats['storageUsed'] === "Unsupported Platform")) - { - return true; - } else { - throw new Error('Invalid returned parameters in GET /retropilot/0/useradmin') - } - } catch (exception) { - throw new Error('Invalid returned parameters in GET /retropilot/0/useradmin ') - } - }) - .end(done) - }); +export default (app) => { + describe('/api', function () { + it('Load general app stats', function (done) { + request(app) + .get('/retropilot/0/useradmin') + .expect('Content-Type', /json/) + .expect(200) + .expect((req) => { + const body = req.body; + try { + if ( + body.hasOwnProperty('success') && body.success === true && + body.hasOwnProperty('data') && + body.data.hasOwnProperty('appStats') && + body.data.appStats.hasOwnProperty('config') && + typeof body.data.appStats.config.registerAllowed === 'boolean' && + typeof body.data.appStats.process.env.WELCOME_MESSAGE === 'string' && + typeof body.data.appStats['accounts'] === 'number' && + typeof body.data.appStats['devices'] === 'number' && + typeof body.data.appStats['drives'] === 'number' && + (typeof body.data.appStats['storageUsed'] === 'number' || body.data.appStats['storageUsed'] === 'Unsupported Platform')) { + return true; + } + } catch (ignored) { + } + throw new Error('Invalid returned parameters in GET /retropilot/0/useradmin '); + }) + .end(done); }); - + }); }; diff --git a/test/routes/useradmin.test.js b/test/routes/useradmin.test.js index 35758fe..16387bf 100644 --- a/test/routes/useradmin.test.js +++ b/test/routes/useradmin.test.js @@ -1,52 +1,46 @@ import request from 'supertest'; import dummyGenerator from './../dummyGenerator'; -let app; - - -export default (server) => { - app = server; - - describe('/useradmin', function() { - it('Page load', function (done) { - request(server) - .get('/useradmin') - .expect('Content-Type', /html/) - .expect(200) - .end(done) - }); - - it('Redirect on existing session', function(done) { - request(server) - .get('/useradmin') - // pull sessions from a store - .set('Cookie', ['session=s%3Aj%3A%7B%22account%22%3A%22adam%40adamblack.us%22%2C%22expires%22%3A1653171350726%7D.cRX19pNfx6mCGZ9ZYHcUIyy5CAQVMDgKrp%2F%2Bf7NFVYA;']) - .expect('Location', '/useradmin/overview') - .expect(302) - .end(done) - }); +export default (app) => { + describe('/useradmin', function () { + it('Page load', function (done) { + request(app) + .get('/useradmin') + .expect('Content-Type', /html/) + .expect(200) + .end(done); }); - describe('/useradmin/register/token', function() { - it('No duplicate emails', function (done) { - request(server) - .post('/useradmin/register/token') - // TODO add dedicated DB/user account for tests to run on - .send(`email=${dummyGenerator.alreadyRegisteredEmail}`) - .set('Accept', 'application/x-www-form-urlencoded') - .expect('Location', `/useradmin/register?status=${encodeURIComponent('Email is already registered')}`) - .end(done) - }); - - it('Accepts new accounts', function (done) { - request(server) - .post('/useradmin/register/token') - // TODO add dedicated DB/user account for tests to run on - .send(`email=${dummyGenerator.newUserEmail}`) - .set('Accept', 'application/x-www-form-urlencoded') - .expect(200) - .end(done) - }); - + it('Redirect on existing session', function (done) { + request(app) + .get('/useradmin') + // pull sessions from a store + .set('Cookie', ['session=s%3Aj%3A%7B%22account%22%3A%22adam%40adamblack.us%22%2C%22expires%22%3A1653171350726%7D.cRX19pNfx6mCGZ9ZYHcUIyy5CAQVMDgKrp%2F%2Bf7NFVYA;']) + .expect('Location', '/useradmin/overview') + .expect(302) + .end(done); }); + }); + + describe('/useradmin/register/token', function () { + it('No duplicate emails', function (done) { + request(app) + .post('/useradmin/register/token') + // TODO add dedicated DB/user account for tests to run on + .send(`email=${dummyGenerator.alreadyRegisteredEmail}`) + .set('Accept', 'application/x-www-form-urlencoded') + .expect('Location', `/useradmin/register?status=${encodeURIComponent('Email is already registered')}`) + .end(done); + }); + + it('Accepts new accounts', function (done) { + request(app) + .post('/useradmin/register/token') + // TODO add dedicated DB/user account for tests to run on + .send(`email=${dummyGenerator.newUserEmail}`) + .set('Accept', 'application/x-www-form-urlencoded') + .expect(200) + .end(done); + }); + }); }; diff --git a/test/test.js b/test/test.js index 104b90d..5f1c339 100644 --- a/test/test.js +++ b/test/test.js @@ -1,20 +1,20 @@ import request from 'supertest'; -import server from '../server'; +import app from '../src/server/app'; // TODO better way to only run tests once server is up describe('loading express', () => { it('responds to /', (done) => { - request(server) + request(app) .get('/') .expect(200, done); }); it('404 everything else', (done) => { - request(server) + request(app) .get('/foo/bar') .expect(404, done); }); }); -require('./routes/api.test')(server); -require('./routes/useradmin.test')(server); -if (process.env.USE_USER_ADMIN_API) require('./routes/userAdminApi.test')(server); +require('./routes/api.test')(app); +require('./routes/useradmin.test')(app); +if (process.env.USE_USER_ADMIN_API) require('./routes/userAdminApi.test')(app);