From 7b43aa3eff157c98e669eace8b7de4c2bfc257a4 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Sat, 8 Jan 2022 20:43:57 +0000 Subject: [PATCH] apply eslint fixes --- config.sample.js | 97 +- controllers/admin.js | 7 +- controllers/authentication.js | 15 +- controllers/devices.js | 40 +- controllers/helpers.js | 2 +- controllers/index.js | 2 +- controllers/mailing.js | 13 +- controllers/storage.js | 14 +- controllers/upload.js | 5 +- controllers/users.js | 14 +- ecosystem.config.js | 12 +- models/athena_action_log.model.js | 22 +- models/athena_returned_data.model.js | 18 +- models/device_authorised_users.model.js | 18 +- models/devices.js | 2 +- models/devices.model.js | 32 +- models/drives.js | 26 +- models/drives.model.js | 38 +- models/index.js | 84 +- models/index.model.js | 4 +- models/users.js | 39 +- models/users.model.js | 18 +- routes/administration/adminApi.js | 130 ++- routes/api.js | 54 +- routes/api/authentication.js | 29 +- routes/api/devices.js | 3 +- routes/api/realtime.js | 8 +- routes/index.js | 2 +- routes/userAdminApi.js | 25 +- server.js | 235 ++--- websocket/athena/helpers.js | 7 +- websocket/athena/index.js | 19 +- websocket/web/commands.js | 23 +- websocket/web/controls.js | 2 +- websocket/web/index.js | 6 +- worker.js | 1208 +++++++++++------------ 36 files changed, 1081 insertions(+), 1192 deletions(-) diff --git a/config.sample.js b/config.sample.js index 8dd9585..bb90d08 100644 --- a/config.sample.js +++ b/config.sample.js @@ -1,61 +1,60 @@ var config = { - applicationSalt: 'RANDOM_SEED', - - databaseFile: 'database.sqlite', - - allowAccountRegistration: true, - - httpInterface: '0.0.0.0', - httpPort: 3000, - - httpsInterface: '0.0.0.0', - httpsPort: 4430, - sslKey: 'certs/retropilot.key', - sslCrt: 'certs/retropilot.crt', + applicationSalt: 'RANDOM_SEED', - canSendMail: true, // Skips sending mail, all attempted mail is logged under DEBUG - smtpHost: "localhost", // credentials for smtp server to send account registration mails. if not filled in, get the generated tokens from the server.log manually - smtpPort: 25, - smtpUser: "root", - smtpPassword: "", - smtpFrom: "no-reply@retropilot.org", + databaseFile: 'database.sqlite', - baseUrl: 'http://192.168.1.165:3000/', // base url of the retropilot server - baseUploadUrl: 'http://192.168.1.165:3000/backend/post_upload', // base url sent to devices for POSTing drives & logs - - baseDriveDownloadUrl: 'http://192.168.1.165:3000/realdata/', // base download url for drive & log data - baseDriveDownloadPathMapping: '/realdata', // path mapping of above download url for expressjs, prefix with "/" - storagePath: 'realdata/', // relative or absolute ( "/..." for absolute path ) + allowAccountRegistration: true, - cabanaUrl: 'http://192.168.1.165:3000/cabana/index.html', + httpInterface: '0.0.0.0', + httpPort: 3000, - deviceStorageQuotaMb: 200000, - deviceDriveExpirationDays: 30, - + httpsInterface: '0.0.0.0', + httpsPort: 4430, + sslKey: 'certs/retropilot.key', + sslCrt: 'certs/retropilot.crt', - welcomeMessage: `<><><><><><><><><><><><><><><><><><><><><><>
2021 RetroPilot`, + canSendMail: true, // Skips sending mail, all attempted mail is logged under DEBUG + smtpHost: 'localhost', // credentials for smtp server to send account registration mails. if not filled in, get the generated tokens from the server.log manually + smtpPort: 25, + smtpUser: 'root', + smtpPassword: '', + smtpFrom: 'no-reply@retropilot.org', - flags: { - useUserAdminApi: false, + baseUrl: 'http://192.168.1.165:3000/', // base url of the retropilot server + baseUploadUrl: 'http://192.168.1.165:3000/backend/post_upload', // base url sent to devices for POSTing drives & logs + + baseDriveDownloadUrl: 'http://192.168.1.165:3000/realdata/', // base download url for drive & log data + baseDriveDownloadPathMapping: '/realdata', // path mapping of above download url for expressjs, prefix with "/" + storagePath: 'realdata/', // relative or absolute ( "/..." for absolute path ) + + cabanaUrl: 'http://192.168.1.165:3000/cabana/index.html', + + deviceStorageQuotaMb: 200000, + deviceDriveExpirationDays: 30, + + welcomeMessage: '<><><><><><><><><><><><><><><><><><><><><><>
2021 RetroPilot', + + flags: { + useUserAdminApi: false, + }, + + clientSocket: { // Used in development, remove before prod + port: 81, + host: '0.0.0.0', + + }, + + athena: { + enabled: true, // Enables Athena service + secure: true, // Disables crypto on Websocket server - use for testing on local network, change ATHENA_HOST in openpilot to ws:// instead of wss:// + api: { + ratelimit: 100, // Maxmium hits to /realtime/* per 30s }, - - clientSocket: { // Used in development, remove before prod - port: 81, - host: "0.0.0.0" - + socket: { + port: 4040, + heartbeatFrequency: 5000, // Higher the number = lower traffic, varies on how many devices are connected }, - - athena: { - enabled: true, // Enables Athena service - secure: true, // Disables crypto on Websocket server - use for testing on local network, change ATHENA_HOST in openpilot to ws:// instead of wss:// - api: { - ratelimit: 100 // Maxmium hits to /realtime/* per 30s - }, - socket: { - port: 4040, - heartbeatFrequency: 5000 // Higher the number = lower traffic, varies on how many devices are connected - } - } + }, }; module.exports = config; diff --git a/controllers/admin.js b/controllers/admin.js index 5518648..d3d1a2a 100644 --- a/controllers/admin.js +++ b/controllers/admin.js @@ -25,14 +25,13 @@ async function banAccount(ban, userId) { let cleanBan; if (ban === 'true' || ban === 'false') { cleanBan = ban === 'true'; - } - else { + } else { return { success: false, status: 400, data: { bad_data: true } }; } const update = await models_orm.models.accounts.update( { banned: cleanBan ? 1 : 0 }, - { where: { id: userId } } + { where: { id: userId } }, ); const verify = await models_orm.models.accounts.findOne({ where: { id: userId } }); @@ -45,5 +44,5 @@ async function banAccount(ban, userId) { module.exports = { banAccount, - isCurrentUserAdmin + isCurrentUserAdmin, }; diff --git a/controllers/authentication.js b/controllers/authentication.js index 91ea6ad..70689c3 100644 --- a/controllers/authentication.js +++ b/controllers/authentication.js @@ -8,8 +8,7 @@ const config = require('../config'); async function validateJWT(token, key) { try { return jsonwebtoken.verify(token.replace('JWT ', ''), key, { algorithms: ['RS256'], ignoreNotBefore: true }); - } - catch (exception) { + } catch (exception) { console.log(`failed to validate JWT ${exception}`); } return null; @@ -18,8 +17,7 @@ async function validateJWT(token, key) { async function readJWT(token) { try { return jsonwebtoken.decode(token); - } - catch (exception) { + } catch (exception) { logger.warn(`failed to read JWT ${exception}`); } return null; @@ -54,7 +52,7 @@ async function changePassword(account, newPassword, oldPassword) { const update = models_orm.models.accounts.update( { password: newPasswordHash }, - { where: { id: account.id } } + { where: { id: account.id } }, ); return { success: true, msg: 'PASSWORD CHANGED', changed: true }; @@ -80,8 +78,7 @@ async function getAccountFromJWT(jwt, limitData) { try { token = jsonwebtoken.verify(jwt, config.applicationSalt); - } - catch (err) { + } catch (err) { return null;// {success: false, msg: 'BAD_JWT'} } @@ -94,7 +91,7 @@ async function getAccountFromJWT(jwt, limitData) { if (account.dataValues) { const update = models_orm.models.accounts.update( { last_ping: Date.now() }, - { where: { id: account.id } } + { where: { id: account.id } }, ); if (!account || account.banned) { @@ -113,5 +110,5 @@ module.exports = { changePassword, signIn, readJWT, - getAccountFromJWT + getAccountFromJWT, }; diff --git a/controllers/devices.js b/controllers/devices.js index e0daae3..41f90ac 100644 --- a/controllers/devices.js +++ b/controllers/devices.js @@ -21,14 +21,12 @@ async function pairDevice(account, qr_string) { if (qrCodeParts.length > 1) { deviceQuery = await models_orm.models.device.findOne({ where: { serial: qrCodeParts[1] } }); pairJWT = qrCodeParts[2]; - } - else { + } else { pairJWT = qr_string; const data = await authenticationController.readJWT(qr_string); if (data.pair === true) { deviceQuery = await models_orm.models.device.findOne({ where: { dongle_id: data.identity } }); - } - else { + } else { return { success: false, noPair: true }; } } @@ -52,15 +50,15 @@ async function pairDevice(account, qr_string) { async function pairDeviceToAccountId(dongle_id, account_id) { const update = await models_orm.models.device.update( { account_id }, - { where: { dongle_id } } + { where: { dongle_id } }, ); const check = await models_orm.models.device.findOne( - { where: { dongle_id, account_id } } + { where: { dongle_id, account_id } }, ); if (check.dataValues) { return { - success: true, paired: true, dongle_id, account_id + success: true, paired: true, dongle_id, account_id, }; } return { success: false, paired: false }; @@ -68,13 +66,13 @@ async function pairDeviceToAccountId(dongle_id, account_id) { async function unpairDevice(account, dongleId) { const device = await models_orm.models.device.getOne( - { where: { account_id: account.id, dongle_id: dongleId } } + { where: { account_id: account.id, dongle_id: dongleId } }, ); if (device && device.dataValues) { await models_orm.models.device.update( { account_id: 0 }, - { where: { dongle_id: dongleId } } + { where: { dongle_id: dongleId } }, ); return { success: true }; } @@ -83,7 +81,7 @@ async function unpairDevice(account, dongleId) { async function setDeviceNickname(account, dongleId, nickname) { const device = await models_orm.models.device.getOne( - { where: { account_id: account.id, dongle_id: dongleId } } + { where: { account_id: account.id, dongle_id: dongleId } }, ); const cleanNickname = sanitize.value(nickname, 'string'); @@ -91,7 +89,7 @@ async function setDeviceNickname(account, dongleId, nickname) { if (device && device.dataValues) { await models_orm.models.device.update( { nickname: cleanNickname }, - { where: { dongle_id: dongleId } } + { where: { dongle_id: dongleId } }, ); return { success: true, data: { nickname: cleanNickname } }; } @@ -112,7 +110,7 @@ async function getDeviceFromDongle(dongleId) { async function setIgnoredUploads(dongleId, isIgnored) { const update = models_orm.models.accounts.update( { dongle_id: dongleId }, - { where: { uploads_ignored: isIgnored } } + { where: { uploads_ignored: isIgnored } }, ); // TODO check this change was processed.. @@ -128,7 +126,7 @@ async function getAllDevicesFiltered() { async function updateLastPing(device_id, dongle_id) { models_orm.models.device.update( { last_ping: Date.now() }, - { where: { [Op.or]: [{ id: device_id }, { dongle_id }] } } + { where: { [Op.or]: [{ id: device_id }, { dongle_id }] } }, ); } @@ -150,8 +148,8 @@ async function isUserAuthorised(account_id, dongle_id) { return { success: true, data: { - authorised: true, account_id: account.id, dongle_id: device.dongle_id - } + authorised: true, account_id: account.id, dongle_id: device.dongle_id, + }, }; } return { success: false, msg: 'not_authorised', data: { authorised: false, account_id: account.id, dongle_id: device.dongle_id } }; @@ -199,15 +197,14 @@ async function getCrashlogs(dongle_id) { let dateObj = null; try { dateObj = Date.parse(timeString); - } - catch (exception) {} + } catch (exception) {} if (!dateObj) dateObj = new Date(0); crashlogFiles.push({ name: crashlogDirectoryTree.children[i].name, size: crashlogDirectoryTree.children[i].size, date: dateObj, - permalink: `${config.baseDriveDownloadUrl}${dongle_id}/${dongleIdHash}/crash/${crashlogDirectoryTree.children[i].name}` + permalink: `${config.baseDriveDownloadUrl}${dongle_id}/${dongleIdHash}/crash/${crashlogDirectoryTree.children[i].name}`, }); } crashlogFiles.sort((a, b) => ((a.date < b.date) ? 1 : -1)); @@ -228,15 +225,14 @@ async function getBootlogs(dongle_id) { let dateObj = null; try { dateObj = Date.parse(timeString); - } - catch (exception) {} + } catch (exception) {} if (!dateObj) dateObj = new Date(0); bootlogFiles.push({ name: bootlogDirectoryTree.children[i].name, size: bootlogDirectoryTree.children[i].size, date: dateObj, - permalink: `${config.baseDriveDownloadUrl}${dongle_id}/${dongleIdHash}/boot/${bootlogDirectoryTree.children[i].name}` + permalink: `${config.baseDriveDownloadUrl}${dongle_id}/${dongleIdHash}/boot/${bootlogDirectoryTree.children[i].name}`, }); } bootlogFiles.sort((a, b) => ((a.date < b.date) ? 1 : -1)); @@ -261,5 +257,5 @@ module.exports = { // drive stuff, move maybe? getDrives, getBootlogs, - getCrashlogs + getCrashlogs, }; diff --git a/controllers/helpers.js b/controllers/helpers.js index 8754728..31d5053 100644 --- a/controllers/helpers.js +++ b/controllers/helpers.js @@ -39,5 +39,5 @@ function formatDate(timestampMs) { } module.exports = { - formatDuration, simpleStringify, formatDate + formatDuration, simpleStringify, formatDate, }; diff --git a/controllers/index.js b/controllers/index.js index 6164572..af088d0 100644 --- a/controllers/index.js +++ b/controllers/index.js @@ -9,5 +9,5 @@ module.exports = async (models, logger, models_sqli) => ({ mailing: require('./mailing')(models, logger), users: require('./users'), admin: require('./admin'), - devices: require('./devices') + devices: require('./devices'), }); diff --git a/controllers/mailing.js b/controllers/mailing.js index e913b6a..978142b 100644 --- a/controllers/mailing.js +++ b/controllers/mailing.js @@ -10,12 +10,12 @@ const transporter = nodemailer.createTransport( port: config.smtpPort, auth: { user: config.smtpUser, - pass: config.smtpPassword + pass: config.smtpPassword, }, logger: true, - debug: false + debug: false, }, - { from: config.smtpFrom } + { from: config.smtpFrom }, ); async function sendEmailVerification(token, email) { @@ -30,12 +30,11 @@ async function sendEmailVerification(token, email) { from: config.smtpFrom, to: email.trim(), subject: 'RetroPilot Registration Token', - text: `Your Email Registration Token Is: "${token}"` + text: `Your Email Registration Token Is: "${token}"`, }; error, info = await transporter.sendMail(message); - } - catch (exception) { + } catch (exception) { logger.warn(`Email to ${email} FAILED ${exception} - ${token}`); } @@ -52,6 +51,6 @@ module.exports = (_models, _logger) => { logger = _logger; return { - sendEmailVerification + sendEmailVerification, }; }; diff --git a/controllers/storage.js b/controllers/storage.js index d5b5631..f29867f 100644 --- a/controllers/storage.js +++ b/controllers/storage.js @@ -12,8 +12,7 @@ function initializeStorage() { var verifiedPath = mkDirByPathSync(config.storagePath, { isRelativeToScript: (config.storagePath.indexOf('/') !== 0) }); if (verifiedPath != null) { logger.info(`Verified storage path ${verifiedPath}`); - } - else { + } else { logger.error(`Unable to verify storage path '${config.storagePath}', check filesystem / permissions`); process.exit(); } @@ -29,8 +28,7 @@ function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) { const curDir = path.resolve(baseDir, parentDir, childDir); try { fs.mkdirSync(curDir); - } - catch (err) { + } catch (err) { // console.debug(err); if (err.code === 'EEXIST') { // curDir already exists! return curDir; @@ -57,8 +55,7 @@ function writeFileSync(path, buffer, permission) { let fileDescriptor; try { fileDescriptor = fs.openSync(path, 'w', permission); - } - catch (e) { + } catch (e) { fs.chmodSync(path, permission); fileDescriptor = fs.openSync(path, 'w', permission); } @@ -104,8 +101,7 @@ async function updateTotalStorageUsed() { if (verifiedPath !== null) { try { totalStorageUsed = execSync(`du -hs ${verifiedPath} | awk -F'\t' '{print $1;}'`).toString(); - } - catch (exception) { + } catch (exception) { totalStorageUsed = 'Unsupported Platform'; logger.debug('Unable to calculate storage used, only supported on systems with \'du\' available'); } @@ -129,6 +125,6 @@ module.exports = (_models, _logger) => { writeFileSync, moveUploadedFile, updateTotalStorageUsed, - getTotalStorageUsed + getTotalStorageUsed, }; }; diff --git a/controllers/upload.js b/controllers/upload.js index 3499d8b..e0b9230 100644 --- a/controllers/upload.js +++ b/controllers/upload.js @@ -1,4 +1 @@ - - - -function getUploadURL(driveType) {} \ No newline at end of file +function getUploadURL(driveType) {} diff --git a/controllers/users.js b/controllers/users.js index bda3cb6..48f96b3 100644 --- a/controllers/users.js +++ b/controllers/users.js @@ -25,7 +25,7 @@ async function createAccount(email, password) { password, created: Date.now(), last_ping: Date.now(), - email_verify_token: emailToken + email_verify_token: emailToken, }); const didAccountRegister = await models_orm.models.accounts.findOne({ where: { email } }); @@ -38,7 +38,7 @@ async function createAccount(email, password) { async function verifyEmailToken(token) { if (!token) return { success: false, status: 400, data: { missingToken: true } }; const account = await models_orm.models.accounts.findOne( - { where: { email_verify_token: token } } + { where: { email_verify_token: token } }, ); if (account === null) return { success: false, status: 404, data: { badToken: true } }; @@ -48,13 +48,13 @@ async function verifyEmailToken(token) { const update = models_orm.models.accounts.update( { - verified: true + verified: true, }, { where: { - id: account.id - } - } + id: account.id, + }, + }, ); return { success: true, status: 200, data: { successfullyVerified: true } }; @@ -69,5 +69,5 @@ module.exports = { createAccount, verifyEmailToken, getAccountFromId, - getAllUsers + getAllUsers, }; diff --git a/ecosystem.config.js b/ecosystem.config.js index 8fa1f23..a798413 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -1,10 +1,10 @@ module.exports = { - apps : [{ - name : "Retropilot Service", - script : "./server.js", + apps: [{ + name: 'Retropilot Service', + script: './server.js', env_development: { - NODE_ENV: "development", - } + NODE_ENV: 'development', + }, }], -} +}; diff --git a/models/athena_action_log.model.js b/models/athena_action_log.model.js index 7dedcbb..715c7dc 100644 --- a/models/athena_action_log.model.js +++ b/models/athena_action_log.model.js @@ -6,41 +6,41 @@ module.exports = (sequelize) => { allowNull: false, autoIncrement: true, primaryKey: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, account_id: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, device_id: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, action: { allowNull: true, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, user_ip: { allowNull: true, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, device_ip: { allowNull: true, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, meta: { allowNull: true, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, created_at: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, dongle_id: { allowNull: true, - type: DataTypes.TEXT - } + type: DataTypes.TEXT, + }, }, { - timestamps: false + timestamps: false, }); }; diff --git a/models/athena_returned_data.model.js b/models/athena_returned_data.model.js index 9c6e8e6..d284c32 100644 --- a/models/athena_returned_data.model.js +++ b/models/athena_returned_data.model.js @@ -6,34 +6,34 @@ module.exports = (sequelize) => { allowNull: false, autoIncrement: true, primaryKey: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, device_id: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, type: { allowNull: true, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, data: { allowNull: true, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, created_at: { allowNull: true, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, uuid: { allowNull: false, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, resolved_at: { allowNull: true, - type: DataTypes.INTEGER - } + type: DataTypes.INTEGER, + }, }, { - timestamps: false + timestamps: false, }); }; diff --git a/models/device_authorised_users.model.js b/models/device_authorised_users.model.js index 843db42..4303f51 100644 --- a/models/device_authorised_users.model.js +++ b/models/device_authorised_users.model.js @@ -6,34 +6,34 @@ module.exports = (sequelize) => { allowNull: false, autoIncrement: true, primaryKey: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, account_id: { allowNull: false, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, device_id: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, athena: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, unpair: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, view_drives: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, created_at: { allowNull: true, - type: DataTypes.INTEGER - } + type: DataTypes.INTEGER, + }, }, { - timestamps: false + timestamps: false, }); }; diff --git a/models/devices.js b/models/devices.js index 7945879..cf11b57 100644 --- a/models/devices.js +++ b/models/devices.js @@ -14,6 +14,6 @@ module.exports = (_db) => { getAccountFromId, getAccountFromVerifyToken, verifyAccountEmail, - banAccount + banAccount, }; }; diff --git a/models/devices.model.js b/models/devices.model.js index d50ba7f..c706d5a 100644 --- a/models/devices.model.js +++ b/models/devices.model.js @@ -8,60 +8,60 @@ module.exports = (sequelize) => { allowNull: false, autoIncrement: true, primaryKey: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, dongle_id: { allowNull: false, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, account_id: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, imei: { allowNull: true, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, serial: { allowNull: true, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, device_type: { allowNull: true, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, public_key: { allowNull: true, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, created: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, last_ping: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, storage_used: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, max_storage: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, ignore_uploads: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, nickname: { allowNull: true, - type: DataTypes.TEXT - } + type: DataTypes.TEXT, + }, }, { - timestamps: false - } + timestamps: false, + }, ); }; diff --git a/models/drives.js b/models/drives.js index 79a16f5..fb0967d 100644 --- a/models/drives.js +++ b/models/drives.js @@ -1,6 +1,3 @@ - - - let db; function getDrives(dongleId) { @@ -8,21 +5,22 @@ function getDrives(dongleId) { } async function getDevice(dongleId) { - return await db.get('SELECT * FROM devices WHERE dongle_id = ?', dongleId); + return await db.get('SELECT * FROM devices WHERE dongle_id = ?', dongleId); } async function deviceCheckIn(dongleId) { - return await db.run( - 'UPDATE devices SET last_ping = ? WHERE dongle_id = ?', - Date.now(), dongleId - ); + return await db.run( + 'UPDATE devices SET last_ping = ? WHERE dongle_id = ?', + Date.now(), + dongleId, + ); } module.exports = (_db) => { - db = _db; + db = _db; - return { - getDevice, - deviceCheckIn - } -} \ No newline at end of file + return { + getDevice, + deviceCheckIn, + }; +}; diff --git a/models/drives.model.js b/models/drives.model.js index 973e1e4..53c0156 100644 --- a/models/drives.model.js +++ b/models/drives.model.js @@ -8,71 +8,71 @@ module.exports = (sequelize) => { allowNull: false, autoIncrement: true, primaryKey: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, identifier: { allowNull: false, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, dongle_id: { allowNull: true, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, max_segment: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, upload_complete: { allowedNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, duration: { allowNull: true, - type: DataTypes.NUMBER + type: DataTypes.NUMBER, }, distance_meters: { allowNull: true, - type: DataTypes.NUMBER + type: DataTypes.NUMBER, }, filesize: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, is_processed: { allowNull: true, - type: DataTypes.BOOLEAN + type: DataTypes.BOOLEAN, }, created: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, last_upload: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, is_preserved: { allowNull: true, - type: DataTypes.BOOLEAN + type: DataTypes.BOOLEAN, }, is_deleted: { allowNull: true, - type: DataTypes.BOOLEAN + type: DataTypes.BOOLEAN, }, drive_date: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, is_physically_removed: { allowNull: true, - type: DataTypes.BOOLEAN + type: DataTypes.BOOLEAN, }, metadata: { allowNull: true, - type: DataTypes.TEXT - } + type: DataTypes.TEXT, + }, }, { - timestamps: false - } + timestamps: false, + }, ); }; diff --git a/models/index.js b/models/index.js index 324c559..c4a4ea4 100644 --- a/models/index.js +++ b/models/index.js @@ -1,55 +1,49 @@ -const sqlite3 = require('sqlite3') -const {open} = require('sqlite') -const config = require('./../config'); +const sqlite3 = require('sqlite3'); +const { open } = require('sqlite'); +const config = require('../config'); async function validateDatabase(db, logger) { - try { - db = await open({ - filename: config.databaseFile, - driver: sqlite3.Database, - mode: sqlite3.OPEN_READWRITE - }); - await db.get('SELECT * FROM accounts LIMIT 1') - await db.get('SELECT * FROM devices LIMIT 1') - await db.get('SELECT * FROM drives LIMIT 1') - await db.get('SELECT * FROM drive_segments LIMIT 1') - - } catch (exception) { - logger.error(exception); - process.exit(); - } + try { + db = await open({ + filename: config.databaseFile, + driver: sqlite3.Database, + mode: sqlite3.OPEN_READWRITE, + }); + await db.get('SELECT * FROM accounts LIMIT 1'); + await db.get('SELECT * FROM devices LIMIT 1'); + await db.get('SELECT * FROM drives LIMIT 1'); + await db.get('SELECT * FROM drive_segments LIMIT 1'); + } catch (exception) { + logger.error(exception); + process.exit(); + } } - - - module.exports = async (logger) => { - let db; + let db; - try { - db = await open({ - filename: config.databaseFile, - driver: sqlite3.Database, - mode: sqlite3.OPEN_READWRITE - }); + try { + db = await open({ + filename: config.databaseFile, + driver: sqlite3.Database, + mode: sqlite3.OPEN_READWRITE, + }); + } catch (exception) { + logger.error(exception); + process.exit(); + } - } catch (exception) { - logger.error(exception); - process.exit(); - } + // I'm not sure we _really_ need to wait for this, since it'll exit the application if it's invalid anyway. - // I'm not sure we _really_ need to wait for this, since it'll exit the application if it's invalid anyway. + await validateDatabase(db, logger); - await validateDatabase(db, logger); + return { + models: { + drivesModel: require('./drives')(db), + users: require('./users')(db), - - return { - models: { - drivesModel: require('./drives')(db), - users: require('./users')(db), - - // TODO remove access to DB queries from non models - __db: db // to be removed when db queries are removed from outside models. - } - } -} \ No newline at end of file + // TODO remove access to DB queries from non models + __db: db, // to be removed when db queries are removed from outside models. + }, + }; +}; diff --git a/models/index.model.js b/models/index.model.js index 79f4a1f..4c9e391 100644 --- a/models/index.model.js +++ b/models/index.model.js @@ -5,7 +5,7 @@ const { Sequelize } = require('sequelize'); const sequelize = new Sequelize({ dialect: 'sqlite', - storage: 'database.sqlite' + storage: 'database.sqlite', }); sequelize.options.logging = () => {}; @@ -16,7 +16,7 @@ const modelDefiners = [ require('./users.model'), require('./athena_action_log.model'), require('./athena_returned_data.model'), - require('./device_authorised_users.model') + require('./device_authorised_users.model'), ]; for (const modelDefiner of modelDefiners) { diff --git a/models/users.js b/models/users.js index 081e582..3f7fbc9 100644 --- a/models/users.js +++ b/models/users.js @@ -1,45 +1,44 @@ let db; async function userPing(email) { - return await db.run('UPDATE accounts SET last_ping = ? WHERE email = ?', Date.now(), email); + return await db.run('UPDATE accounts SET last_ping = ? WHERE email = ?', Date.now(), email); } async function getAccountFromEmail(email) { - return await db.get('SELECT * FROM accounts WHERE LOWER(email) = ?', email); + return await db.get('SELECT * FROM accounts WHERE LOWER(email) = ?', email); } async function getAccountFromVerifyToken(token) { - return await db.get('SELECT * FROM accounts WHERE email_verify_token = ?', token); + return await db.get('SELECT * FROM accounts WHERE email_verify_token = ?', token); } async function getAccountFromId(id) { - return await db.get('SELECT * FROM accounts WHERE id = ?', id); + return await db.get('SELECT * FROM accounts WHERE id = ?', id); } async function createUser(email, password, created, lastPing, emailToken) { - return await db.get('INSERT INTO accounts (email, password, created, last_ping, email_verify_token) VALUES (?,?,?,?,?)', email, password, created, lastPing, emailToken) + return await db.get('INSERT INTO accounts (email, password, created, last_ping, email_verify_token) VALUES (?,?,?,?,?)', email, password, created, lastPing, emailToken); } async function verifyAccountEmail(email, verified, newToken) { - verified = verified === true ? 1 : 0 - return await db.get('UPDATE accounts SET verified=? WHERE email = ?', verified, email); + verified = verified === true ? 1 : 0; + return await db.get('UPDATE accounts SET verified=? WHERE email = ?', verified, email); } async function banAccount(ban, userId) { - return await db.get('UPDATE accounts SET banned=? WHERE id = ?', ban ? 1 : 0, userId) + return await db.get('UPDATE accounts SET banned=? WHERE id = ?', ban ? 1 : 0, userId); } - module.exports = (_db) => { - db = _db; + db = _db; - return { - userPing, - getAccountFromEmail, - createUser, - getAccountFromId, - getAccountFromVerifyToken, - verifyAccountEmail, - banAccount - } -} \ No newline at end of file + return { + userPing, + getAccountFromEmail, + createUser, + getAccountFromId, + getAccountFromVerifyToken, + verifyAccountEmail, + banAccount, + }; +}; diff --git a/models/users.model.js b/models/users.model.js index a55c9a6..ed7c1c0 100644 --- a/models/users.model.js +++ b/models/users.model.js @@ -6,33 +6,33 @@ module.exports = (sequelize) => { allowNull: false, autoIncrement: true, primaryKey: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, email: { allowNull: false, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, password: { allowNull: true, - type: DataTypes.INTEGER + type: DataTypes.INTEGER, }, created: { allowNull: true, - type: DataTypes.NUMBER + type: DataTypes.NUMBER, }, last_ping: { allowNull: true, - type: DataTypes.NUMBER + type: DataTypes.NUMBER, }, '2fa_token': { allowNull: true, - type: DataTypes.TEXT + type: DataTypes.TEXT, }, admin: { allowNull: true, - type: DataTypes.INTEGER - } + type: DataTypes.INTEGER, + }, }, { - timestamps: false + timestamps: false, }); }; diff --git a/routes/administration/adminApi.js b/routes/administration/adminApi.js index c0d9ee2..bf046a0 100644 --- a/routes/administration/adminApi.js +++ b/routes/administration/adminApi.js @@ -2,128 +2,108 @@ const router = require('express').Router(); const bodyParser = require('body-parser'); const crypto = require('crypto'); const { route } = require('../../server'); -const config = require('./../../config'); -const deviceController = require('./../../controllers/devices') +const config = require('../../config'); +const deviceController = require('../../controllers/devices'); function runAsyncWrapper(callback) { - return function (req, res, next) { - callback(req, res, next) - .catch(next) - } + return function (req, res, next) { + callback(req, res, next) + .catch(next); + }; } let models; let controllers; let logger; - - // probs should put middleware somewhere else -router.use(async function (req, res, next) { - const currentAdmin = await controllers.admin.isCurrentUserAdmin(true, req); - if (currentAdmin.isAdmin === false) { - return res.status(402).json({error:true, msg: 'NOT AUTHORISED', status: 403}).end(); - } else { - next(); - } +router.use(async (req, res, next) => { + const currentAdmin = await controllers.admin.isCurrentUserAdmin(true, req); + if (currentAdmin.isAdmin === false) { + return res.status(402).json({ error: true, msg: 'NOT AUTHORISED', status: 403 }).end(); + } + next(); }); - -// TODO - +// TODO router.get('/user/:userId/ban/:ban', runAsyncWrapper(async (req, res) => { - const banResult = await controllers.admin.banAccount(req.params.ban, req.params.userId) - if (banResult.hasOwnProperty('success') && banResult.success === true) { - res.status(200).json(banResult); - } else { - res.status(500).json(banResult) - } - + const banResult = await controllers.admin.banAccount(req.params.ban, req.params.userId); + if (banResult.hasOwnProperty('success') && banResult.success === true) { + res.status(200).json(banResult); + } else { + res.status(500).json(banResult); + } })); router.get('/user/:userId/get/devices', runAsyncWrapper(async (req, res) => { - if (!req.params.userId) { return req.status(400).json({error: true, msg: 'MISSING DATA', status: 400})} + if (!req.params.userId) { return req.status(400).json({ error: true, msg: 'MISSING DATA', status: 400 }); } - return res.status(200).json({success: true, data: controllers.devices.getDevices(req.params.userId)}) + return res.status(200).json({ success: true, data: controllers.devices.getDevices(req.params.userId) }); })); - - router.get('/user/', runAsyncWrapper(async (req, res) => { - console.warn("PROCESSED") - - - return res.status(200).json({success: true, data: await controllers.users.getAllUsers()}) + console.warn('PROCESSED'); + return res.status(200).json({ success: true, data: await controllers.users.getAllUsers() }); })); router.get('/device/:dongle_id', runAsyncWrapper(async (req, res) => { - if (!req.params.dongle_id) { return req.status(400).json({error: true, msg: 'MISSING DATA', status: 400})} - - - return res.status(200).json({success: true, data: await controllers.devices.getDeviceFromDongle(getDeviceFromDongle)}) + if (!req.params.dongle_id) { return req.status(400).json({ error: true, msg: 'MISSING DATA', status: 400 }); } + return res.status(200).json({ success: true, data: await controllers.devices.getDeviceFromDongle(getDeviceFromDongle) }); })); router.get('/device/:dongle_id/pair/:user_id', runAsyncWrapper(async (req, res) => { - if (!req.params.dongle_id || !req.params.user_id) { return req.status(400).json({error: true, msg: 'MISSING DATA', status: 400})} - - const pairDeviceToAccountId = await controllers.devices.pairDeviceToAccountId(req.params.dongle_id, req.params.user_id) + if (!req.params.dongle_id || !req.params.user_id) { return req.status(400).json({ error: true, msg: 'MISSING DATA', status: 400 }); } - return res.status(200).json(pairDeviceToAccountId) + const pairDeviceToAccountId = await controllers.devices.pairDeviceToAccountId(req.params.dongle_id, req.params.user_id); + return res.status(200).json(pairDeviceToAccountId); })); router.get('/device', runAsyncWrapper(async (req, res) => { - const filteredDevices = await controllers.devices.getAllDevicesFiltered(); - console.log("fil", filteredDevices) - return res.status(200).json({success: true, data: filteredDevices}) - + const filteredDevices = await controllers.devices.getAllDevicesFiltered(); + console.log('fil', filteredDevices); + return res.status(200).json({ success: true, data: filteredDevices }); })); router.get('/device/:dongle_id/ignore/:ignore_uploads', runAsyncWrapper(async (req, res) => { - if (!req.params.dongle_id || !req.params.ignore_uploads) { return req.status(400).json({error: true, msg: 'MISSING DATA', status: 400})} - + if (!req.params.dongle_id || !req.params.ignore_uploads) { return req.status(400).json({ error: true, msg: 'MISSING DATA', status: 400 }); } })); router.get('/admin/device/:dongle_id/ignore/:ignore_uploads', runAsyncWrapper(async (req, res) => { - if (!req.params.dongle_id || !req.params.ignore_uploads) { return req.status(400).json({error: true, msg: 'MISSING DATA', status: 400})} + if (!req.params.dongle_id || !req.params.ignore_uploads) { return req.status(400).json({ error: true, msg: 'MISSING DATA', status: 400 }); } - let ignore = null; + let ignore = null; - switch (req.params.ignore_uploads) { - case "true": - ignore = true - break; - case "false": - ignore = false - break; - default: - return res.json({error: true, msg: 'MISSING DATA'}) - break - } + switch (req.params.ignore_uploads) { + case 'true': + ignore = true; + break; + case 'false': + ignore = false; + break; + default: + return res.json({ error: true, msg: 'MISSING DATA' }); + break; + } - if (ignore === null) {return} - - await controllers.devices.setIgnoredUploads(req.params.dongle_id); - return res.status(200).json({success: true}); + if (ignore === null) { return; } + await controllers.devices.setIgnoredUploads(req.params.dongle_id); + return res.status(200).json({ success: true }); })); - router.get('/device/:dongle_id/athena/reboot', runAsyncWrapper(async (req, res) => { - - req.athenaWebsocketTemp.rebootDevice(req.params.dongle_id) - res.send("ok"); - + req.athenaWebsocketTemp.rebootDevice(req.params.dongle_id); + res.send('ok'); })); - module.exports = (_models, _controllers, _logger) => { - models = _models; - controllers = _controllers; - logger = _logger; + models = _models; + controllers = _controllers; + logger = _logger; - return router; -} \ No newline at end of file + return router; +}; diff --git a/routes/api.js b/routes/api.js index 139c008..3728ab2 100644 --- a/routes/api.js +++ b/routes/api.js @@ -18,7 +18,7 @@ let logger; router.put('/backend/post_upload', bodyParser.raw({ inflate: true, limit: '100000kb', - type: '*/*' + type: '*/*', }), runAsyncWrapper(async (req, res) => { // TODO update buffer functions project wide var buf = new Buffer(req.body.toString('binary'), 'binary'); @@ -38,23 +38,20 @@ router.put('/backend/post_upload', bodyParser.raw({ logger.error(`HTTP.PUT /backend/post_upload token mismatch (${token} vs ${req.query.token})`); res.status(400); res.send('Malformed request'); - } - else { + } else { logger.info('HTTP.PUT /backend/post_upload permissions checked, calling moveUploadedFile'); const moveResult = controllers.storage.moveUploadedFile(buf, directory, filename); if (moveResult === false) { logger.error('HTTP.PUT /backend/post_upload moveUploadedFile failed'); res.status(500); res.send('Internal Server Error'); - } - else { + } else { logger.info(`HTTP.PUT /backend/post_upload succesfully uploaded to ${moveResult}`); res.status(200); res.json(['OK']); } } - } - else { // boot or crash upload + } else { // boot or crash upload var filename = req.query.file; var directory = req.query.dir; var token = crypto.createHmac('sha256', config.applicationSalt).update(dongleId + filename + directory + ts).digest('hex'); @@ -64,16 +61,14 @@ router.put('/backend/post_upload', bodyParser.raw({ logger.error(`HTTP.PUT /backend/post_upload token mismatch (${token} vs ${req.query.token})`); res.status(400); res.send('Malformed request'); - } - else { + } else { logger.info('HTTP.PUT /backend/post_upload permissions checked, calling moveUploadedFile'); var moveResult = controllers.storage.moveUploadedFile(buf, directory, filename); if (moveResult === false) { logger.error('HTTP.PUT /backend/post_upload moveUploadedFile failed'); res.status(500); res.send('Internal Server Error'); - } - else { + } else { logger.info(`HTTP.PUT /backend/post_upload succesfully uploaded to ${moveResult}`); res.status(200); res.json(['OK']); @@ -120,13 +115,13 @@ router.get('/v1.1/devices/:dongleId/stats', runAsyncWrapper(async (req, res) => all: { routes: 0, distance: 0, - minutes: 0 + minutes: 0, }, week: { routes: 0, distance: 0, - minutes: 0 - } + minutes: 0, + }, }; @@ -259,8 +254,7 @@ async function upload(req, res) { responseUrl = `${config.baseUploadUrl}?file=${filename}&dir=${directory}&dongleId=${dongleId}&ts=${ts}&token=${token}`; logger.info(`HTTP.UPLOAD_URL matched '${uploadType}' file upload, constructed responseUrl: ${responseUrl}`); - } - else { + } else { // "2021-04-12--01-44-25--0/qlog.bz2" for example const subdirPosition = path.split('--', 2).join('--').length; const filenamePosition = path.indexOf('/'); @@ -325,7 +319,7 @@ async function upload(req, res) { false, - false + false, ); const driveSegmentResult = await models.__db.run( @@ -346,12 +340,11 @@ async function upload(req, res) { false, - Date.now() + Date.now(), ); logger.info(`HTTP.UPLOAD_URL created new drive #${JSON.stringify(driveResult.lastID)}`); - } - else { + } else { const driveResult = await models.__db.run( 'UPDATE drives SET last_upload = ?, max_segment = ?, upload_complete = ?, is_processed = ? WHERE identifier = ? AND dongle_id = ?', Date.now(), @@ -364,7 +357,7 @@ async function upload(req, res) { driveName, - dongleId + dongleId, ); const drive_segment = await models.__db.get('SELECT * FROM drive_segments WHERE drive_identifier = ? AND dongle_id = ? AND segment_id = ?', driveName, dongleId, segment); @@ -388,10 +381,9 @@ async function upload(req, res) { false, - Date.now() + Date.now(), ); - } - else { + } else { const driveSegmentResult = await models.__db.run( 'UPDATE drive_segments SET upload_complete = ?, is_stalled = ? WHERE drive_identifier = ? AND dongle_id = ? AND segment_id = ?', false, @@ -402,7 +394,7 @@ async function upload(req, res) { dongleId, - segment + segment, ); } @@ -414,8 +406,7 @@ async function upload(req, res) { if (responseUrl != null) { res.status(200); res.json({ url: responseUrl, headers: { 'Content-Type': 'application/octet-stream' } }); - } - else { + } else { logger.error('HTTP.UPLOAD_URL unable to match request, responding with HTTP 400'); res.status(400); res.send('Malformed Request.'); @@ -473,7 +464,7 @@ router.post('/v2/pilotauth/', bodyParser.urlencoded({ extended: true }), async ( Date.now(), - 0 + 0, ); const device = await models.__db.get('SELECT * FROM devices WHERE dongle_id = ?', dongleId); @@ -484,15 +475,14 @@ router.post('/v2/pilotauth/', bodyParser.urlencoded({ extended: true }), async ( return; } } - } - else { + } else { const result = await models.__db.run( 'UPDATE devices SET last_ping = ?, public_key = ? WHERE dongle_id = ?', Date.now(), public_key, - device.dongle_id + device.dongle_id, ); logger.info(`HTTP.V2.PILOTAUTH REACTIVATING KNOWN DEVICE (${imei1}, ${serial}) with dongle_id ${device.dongle_id}`); @@ -545,7 +535,7 @@ router.get('/useradmin/cabana_drive/:extendedRouteIdentifier', runAsyncWrapper(a driveUrl, name: `${drive.dongle_id}|${drive.identifier}`, driveIdentifier: drive.identifier, - dongleId: drive.dongle_id + dongleId: drive.dongle_id, }); })); diff --git a/routes/api/authentication.js b/routes/api/authentication.js index f82a8a7..104a680 100644 --- a/routes/api/authentication.js +++ b/routes/api/authentication.js @@ -12,11 +12,10 @@ async function isAuthenticated(req, res, next) { res.json({ success: true, data: { - authenticated: false - } + authenticated: false, + }, }); - } - else { + } else { req.account = account; next(); } @@ -30,16 +29,16 @@ router.get('/retropilot/0/useradmin/session', isAuthenticated, async (req, res) success: true, data: { authenticated: true, - user: account.dataValues - } + user: account.dataValues, + }, }); } return res.json({ success: true, data: { - authenticated: false - } + authenticated: false, + }, }); }); @@ -56,16 +55,15 @@ router.post('/retropilot/0/useradmin/auth', bodyParser.urlencoded({ extended: tr data: { authenticated: true, jwt: signIn.jwt, - user: account.dataValues - } + user: account.dataValues, + }, }); - } - else { + } else { res.json({ success: true, data: { - authenticated: false - } + authenticated: false, + }, }); } }); @@ -80,8 +78,7 @@ router.get('/session/get', async (req, res) => { if (!account) { res.json({ success: true, hasSession: false, session: {} }); - } - else { + } else { res.json({ success: true, hasSession: false, session: account }); } }); diff --git a/routes/api/devices.js b/routes/api/devices.js index 78c7dca..b161360 100644 --- a/routes/api/devices.js +++ b/routes/api/devices.js @@ -12,8 +12,7 @@ async function isAuthenticated(req, res, next) { if (account === null) { res.json({ success: false, msg: 'NOT_AUTHENTICATED1' }); - } - else { + } else { req.account = account; next(); } diff --git a/routes/api/realtime.js b/routes/api/realtime.js index 5258f2e..f83db03 100644 --- a/routes/api/realtime.js +++ b/routes/api/realtime.js @@ -20,7 +20,7 @@ const whitelistParams = { getsiminfo: true, getnetworktype: true, getnetworks: true, - takesnapshot: true + takesnapshot: true, }; router.get('/dongle/:dongle_id/connected', async (req, res) => { @@ -42,7 +42,7 @@ router.get('/dongle/:dongle_id/connected', async (req, res) => { const deviceConnected = await req.athenaWebsocketTemp.isDeviceConnected(device.id, account.id, device.dongle_id); return res.status(200).json({ - success: true, dongle_id: device.dongle_id, data: deviceConnected + success: true, dongle_id: device.dongle_id, data: deviceConnected, }); }); @@ -69,7 +69,7 @@ router.get('/dongle/:dongle_id/send/:method/', async (req, res) => { const data = await req.athenaWebsocketTemp.invoke(req.params.method, null, device.dongle_id, account.id); return res.status(200).json({ - success: true, dongle_id: device.dongle_id, method: req.params.method, data + success: true, dongle_id: device.dongle_id, method: req.params.method, data, }); }); @@ -108,7 +108,7 @@ router.get('/dongle/:dongle_id/temp/nav/:lat/:long', async (req, res) => { const data = await req.athenaWebsocketTemp.invoke('setNavDestination', { latitude: req.params.lat, longitude: req.params.long }, device.dongle_id, account.id); return res.status(200).json({ - success: true, dongle_id: device.dongle_id, method: req.params.method, data + success: true, dongle_id: device.dongle_id, method: req.params.method, data, }); }); diff --git a/routes/index.js b/routes/index.js index cc15855..cccc0a6 100644 --- a/routes/index.js +++ b/routes/index.js @@ -7,5 +7,5 @@ module.exports = (_models, _controllers, _logger) => ({ realtime: require('./api/realtime'), deviceApi: require('./api/devices'), - authenticationApi: require('./api/authentication') + authenticationApi: require('./api/authentication'), }); diff --git a/routes/userAdminApi.js b/routes/userAdminApi.js index f5c6787..5c7d1e1 100644 --- a/routes/userAdminApi.js +++ b/routes/userAdminApi.js @@ -24,8 +24,7 @@ router.post('/retropilot/0/useradmin/auth', bodyParser.urlencoded({ extended: tr if (signIn.success) { res.cookie('jwt', signIn.jwt); res.redirect('/useradmin/overview'); - } - else { + } else { res.redirect(`/useradmin?status=${encodeURIComponent('Invalid credentials or banned account')}`); } })); @@ -46,14 +45,14 @@ router.get('/retropilot/0/useradmin', runAsyncWrapper(async (req, res) => { serverStats: { config: { registerAllowed: config.allowAccountRegistration, - welcomeMessage: config.welcomeMessage + welcomeMessage: config.welcomeMessage, }, accounts: accounts.num, devices: devices.num, drives: drives.num, - storageUsed: await controllers.storage.getTotalStorageUsed() - } - } + storageUsed: await controllers.storage.getTotalStorageUsed(), + }, + }, }).status(200); })); @@ -178,8 +177,8 @@ router.get('/retropilot/0/overview', runAsyncWrapper(async (req, res) => { success: true, data: { account, - devices - } + devices, + }, }).status(200); })); @@ -199,7 +198,7 @@ router.get('/retropilot/0/unpair_device/:dongleId', runAsyncWrapper(async (req, const result = await models.__db.run( 'UPDATE devices SET account_id = ? WHERE dongle_id = ?', 0, - req.params.dongleId + req.params.dongleId, ); res.json({ success: true, data: { unlink: true } }); @@ -215,10 +214,9 @@ router.post('/retropilot/0/pair_device', bodyParser.urlencoded({ extended: true if (pairDevice.success === true) { res.json({ - success: true, msg: 'Paired', status: 200, data: pairDevice + success: true, msg: 'Paired', status: 200, data: pairDevice, }); - } - else { + } else { res.json({ success: false, msg: 'error', data: pairDevice }); } })); @@ -233,8 +231,7 @@ router.post('/retropilot/0/password/change', bodyParser.urlencoded({ extended: t if (pwChange.success === true) { res.json({ success: true }); - } - else { + } else { res.json({ success: false, data: pwChange }); } })); diff --git a/server.js b/server.js index 13c18da..5d0f169 100644 --- a/server.js +++ b/server.js @@ -6,165 +6,134 @@ const http = require('http'); const https = require('https'); const express = require('express'); const cors = require('cors'); -const rateLimit = require("express-rate-limit"); +const rateLimit = require('express-rate-limit'); log4js.configure({ - appenders: {logfile: {type: "file", filename: "server.log"}, out: {type: 'console'} /*{type: "file", filename: "server1.log"}*/}, - categories: {default: {appenders: ['out', 'logfile'], level: 'info'}}, + 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 global.__basedir = __dirname; -let models = require('./models/index'); -let models_sqli = require('./models/index.model'); -let controllers = require('./controllers'); -let routers = require('./routes') -const athena = require('./websocket/athena'); -const webWebsocket = require('./websocket/web'); var cookieParser = require('cookie-parser'); +const webWebsocket = require('./websocket/web'); +const athena = require('./websocket/athena'); +let routers = require('./routes'); +const models_sqli = require('./models/index.model'); +let controllers = require('./controllers'); +let models = require('./models/index'); const router = require('./routes/api/realtime'); - - let db; - // TODO function runAsyncWrapper(callback) { - return function (req, res, next) { - callback(req, res, next) - .catch(next) - } + return function (req, res, next) { + callback(req, res, next) + .catch(next); + }; } - const app = express(); - const athenaRateLimit = rateLimit({ - windowMs: 30000, - max: config.athena.api.ratelimit + windowMs: 30000, + max: config.athena.api.ratelimit, }); - - - - const web = async () => { - // TODO clean up - const _models = await models(logger); - db = _models.models.__db; - models = _models.models; + // TODO clean up + const _models = await models(logger); + db = _models.models.__db; + models = _models.models; - app.use(function(req, res, next) { - res.header('Access-Control-Allow-Origin', "http://localhost:3000"); - res.header('Access-Control-Allow-Credentials', true); - res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); - next(); + app.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', 'http://localhost:3000'); + res.header('Access-Control-Allow-Credentials', true); + res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); + next(); + }); + + const _controllers = await controllers(models, logger); + controllers = _controllers; + + controllers.storage.initializeStorage(); + await controllers.storage.updateTotalStorageUsed(); + + routers = routers(models, controllers, logger); + app.use(routers.api); + app.use(routers.useradmin); + app.use(routers.authenticationApi); + + if (config.athena.enabled) { + app.use((req, res, next) => { + req.athenaWebsocketTemp = athena; + return next(); + }); + + app.use('/admin', routers.admin); + app.use('/realtime', athenaRateLimit); + app.use('/realtime', routers.realtime); + } 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(config.baseDriveDownloadPathMapping, express.static(config.storagePath)); + + app.use(routers.deviceApi); + + app.use('/.well-known', express.static('.well-known')); + + app.use('/cabana', express.static('cabana/')); + + app.get('/', async (req, res) => { + res.status(200); + var response = '

404 Not found

' + + 'Are you looking for the useradmin dashboard?'; + res.send(response); + }); + + 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'); + })); +}; + +lockfile.lock('retropilot_server.lock', { realpath: false, stale: 30000, update: 2000 }) + .then((release) => { + console.log('STARTING SERVER...'); + web(); + (async () => { + var privateKey = fs.readFileSync(config.sslKey, 'utf8'); + var certificate = fs.readFileSync(config.sslCrt, 'utf8'); + var sslCredentials = { key: privateKey, cert: certificate/* , ca: fs.readFileSync('certs/ca.crt') */ }; + + var httpServer = http.createServer(app); + var httpsServer = https.createServer(sslCredentials, app); + + httpServer.listen(config.httpPort, config.httpInterface, () => { + logger.info(`Retropilot Server listening at http://${config.httpInterface}:${config.httpPort}`); }); - - const _controllers = await controllers(models, logger); - controllers = _controllers; - - - controllers.storage.initializeStorage(); - await controllers.storage.updateTotalStorageUsed(); - - routers = routers(models, controllers, logger) - app.use(routers.api); - app.use(routers.useradmin); - app.use(routers.authenticationApi) - - if (config.athena.enabled) { - app.use((req, res, next) => { - req.athenaWebsocketTemp = athena; - return next(); - }); - - - app.use('/admin', routers.admin); - app.use('/realtime', athenaRateLimit); - app.use('/realtime', routers.realtime); - } 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(config.baseDriveDownloadPathMapping, express.static(config.storagePath)); - - app.use(routers.deviceApi) - - app.use('/.well-known', express.static('.well-known')); - - app.use('/cabana', express.static('cabana/')); - - - app.get('/', async (req, res) => { - res.status(200); - var response = '

404 Not found

' + - 'Are you looking for the useradmin dashboard?'; - res.send(response); - }) - - - 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'); - })); - -} - - -lockfile.lock('retropilot_server.lock', {realpath: false, stale: 30000, update: 2000}) - .then((release) => { - console.log("STARTING SERVER..."); - web(); - (async () => { - - - var privateKey = fs.readFileSync(config.sslKey, 'utf8'); - var certificate = fs.readFileSync(config.sslCrt, 'utf8'); - var sslCredentials = {key: privateKey, cert: certificate/* , ca: fs.readFileSync('certs/ca.crt') */}; - - var httpServer = http.createServer(app); - var httpsServer = https.createServer(sslCredentials, app); - - - httpServer.listen(config.httpPort, config.httpInterface, () => { - logger.info(`Retropilot Server listening at http://${config.httpInterface}:${config.httpPort}`) - }); - httpsServer.listen(config.httpsPort, config.httpsInterface, () => { - logger.info(`Retropilot Server listening at https://${config.httpsInterface}:${config.httpsPort}`) - }); - - })(); - - }).catch((e) => { - console.error(e) + httpsServer.listen(config.httpsPort, config.httpsInterface, () => { + logger.info(`Retropilot Server listening at https://${config.httpsInterface}:${config.httpsPort}`); + }); + })(); + }).catch((e) => { + console.error(e); process.exit(); -}); - - - + }); module.exports = app; diff --git a/websocket/athena/helpers.js b/websocket/athena/helpers.js index ffef210..0974a2f 100644 --- a/websocket/athena/helpers.js +++ b/websocket/athena/helpers.js @@ -25,8 +25,7 @@ function invoke(command, params, dongleId, accountId, id) { if (!id) { uniqueID = uuid(); - } - else { + } else { uniqueID = id; } @@ -36,7 +35,7 @@ function invoke(command, params, dongleId, accountId, id) { device_id: websocket.device_id, type: command, created_at: Date.now(), - uuid: uniqueID + uuid: uniqueID, }); websocket.send(JSON.stringify(wss.retropilotFunc.commandBuilder(command, params, uniqueID))); @@ -65,6 +64,6 @@ module.exports = (websocketServer) => { invoke, incoming, deviceStatus, - realtimeCallback + realtimeCallback, }; }; diff --git a/websocket/athena/index.js b/websocket/athena/index.js index 649775b..06496e0 100644 --- a/websocket/athena/index.js +++ b/websocket/athena/index.js @@ -22,10 +22,9 @@ function __server() { if (config.athena.secure) { server = httpsServer.createServer({ cert: readFileSync(config.sslCrt), - key: readFileSync(config.sslKey) + key: readFileSync(config.sslKey), }); - } - else { + } else { server = httpServer.createServer(); } @@ -91,7 +90,7 @@ async function manageConnection(ws, res) { console.log(await models.models.athena_returned_data.update({ data: JSON.stringify(json), - resolved_at: Date.now() + resolved_at: Date.now(), }, { where: { device_id: ws.device_id, uuid: json.id } })); wss.retropilotFunc.actionLogger(null, null, 'ATHENA_DEVICE_MESSAGE_UNKNOWN', null, ws._socket.remoteAddress, JSON.stringify([message]), ws.dongleId); @@ -126,8 +125,7 @@ wss.retropilotFunc = { authenticateDongle: async (ws, res, cookies) => { try { unsafeJwt = jsonwebtoken.decode(cookies.jwt); - } - catch (e) { + } catch (e) { logger.info(`Athena(Websocket) - AUTHENTICATION FAILED (INVALID JWT) IP: ${ws._socket.remoteAddress}`); wss.retropilotFunc.actionLogger(null, null, 'ATHENA_DEVICE_AUTHENTICATE_INVALID', null, ws._socket.remoteAddress, JSON.stringify({ jwt: cookies.jwt }), null); return false; @@ -139,8 +137,7 @@ wss.retropilotFunc = { try { verifiedJWT = jsonwebtoken.verify(cookies.jwt, device.public_key, { ignoreNotBefore: true }); - } - catch (err) { + } catch (err) { logger.info(`Athena(Websocket) - AUTHENTICATION FAILED (BAD JWT, CHECK SIGNATURE) IP: ${ws._socket.remoteAddress}`); wss.retropilotFunc.actionLogger(null, null, 'ATHENA_DEVICE_AUTHENTICATE_INVALID', null, ws._socket.remoteAddress, JSON.stringify({ jwt: cookies.jwt }), null); return false; @@ -160,14 +157,14 @@ wss.retropilotFunc = { }, commandBuilder: (method, params, id) => ({ - method, params, jsonrpc: '2.0', id + method, params, jsonrpc: '2.0', id, }), actionLogger: async (account_id, device_id, action, user_ip, device_ip, meta, dongle_id) => { models.models.athena_action_log.create({ - account_id, device_id, action, user_ip, device_ip, meta, created_at: Date.now(), dongle_id + account_id, device_id, action, user_ip, device_ip, meta, created_at: Date.now(), dongle_id, }); - } + }, }; diff --git a/websocket/web/commands.js b/websocket/web/commands.js index 6d7df39..337cf30 100644 --- a/websocket/web/commands.js +++ b/websocket/web/commands.js @@ -13,12 +13,11 @@ async function isDongleOnline(ws, msg) { command: msg.command, success: true, id: msg.id || null, - data: athenaRealtime.isDeviceConnected(ws.account.id, null, msg.data.dongleId) + data: athenaRealtime.isDeviceConnected(ws.account.id, null, msg.data.dongleId), })); - } - else { + } else { ws.send(JSON.stringify({ - command: msg.command, success: false, id: msg.id || null, msg: 'not_authorised' + command: msg.command, success: false, id: msg.id || null, msg: 'not_authorised', })); } } @@ -33,12 +32,11 @@ async function rebootDongle(ws, msg) { if (isAuthorised && isAuthorised.success === true) { await athenaRealtime.invoke('reboot', null, msg.data.dongleId, ws.account.id, msg.id || null); ws.send(JSON.stringify({ - command: msg.command, success: true, id: msg.id || null, data: { command_issued: true } + command: msg.command, success: true, id: msg.id || null, data: { command_issued: true }, })); - } - else { + } else { ws.send(JSON.stringify({ - command: msg.command, success: false, id: msg.id || null, msg: 'not_authorised' + command: msg.command, success: false, id: msg.id || null, msg: 'not_authorised', })); } } @@ -49,12 +47,11 @@ async function takeSnapshot(ws, msg) { if (isAuthorised && isAuthorised.success === true) { await athenaRealtime.invoke('takeSnapshot', null, msg.data.dongleId, ws.account.id, msg.id || null); ws.send(JSON.stringify({ - command: msg.command, success: true, id: msg.id || null, data: { command_issued: true } + command: msg.command, success: true, id: msg.id || null, data: { command_issued: true }, })); - } - else { + } else { ws.send(JSON.stringify({ - command: msg.command, success: false, id: msg.id || null, msg: 'not_authorised' + command: msg.command, success: false, id: msg.id || null, msg: 'not_authorised', })); } } @@ -62,5 +59,5 @@ async function takeSnapshot(ws, msg) { module.exports = { isDongleOnline, rebootDongle, - takeSnapshot + takeSnapshot, }; diff --git a/websocket/web/controls.js b/websocket/web/controls.js index c66eac4..264069f 100644 --- a/websocket/web/controls.js +++ b/websocket/web/controls.js @@ -35,6 +35,6 @@ module.exports = (websocket) => { return { getDongleOwners, dongleStatus, - passData + passData, }; }; diff --git a/websocket/web/index.js b/websocket/web/index.js index 2e3256b..9fe059d 100644 --- a/websocket/web/index.js +++ b/websocket/web/index.js @@ -36,7 +36,7 @@ function __server() { function buildResponse(ws, success, msg, data) { ws.send(JSON.stringify({ - success, msg, data, timestamp: Date.now() + success, msg, data, timestamp: Date.now(), })); } @@ -82,7 +82,7 @@ async function manageConnection(ws, res) { return realtimeCommands.takeSnapshot(ws, msg); default: return ws.send(JSON.stringify({ - error: true, id: msg.id || null, msg: 'VERIFY_DATA', data: { msg } + error: true, id: msg.id || null, msg: 'VERIFY_DATA', data: { msg }, })); } }); @@ -100,5 +100,5 @@ athenaRealtime.realtimeCallback(controls); module.exports = { controls, - websocketServer + websocketServer, }; diff --git a/worker.js b/worker.js index e50422e..4bcdb2d 100644 --- a/worker.js +++ b/worker.js @@ -2,10 +2,10 @@ const config = require('./config'); const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); -const log4js = require('log4js'); +const log4js = require('log4js'); -const sqlite3 = require('sqlite3') -const { open } = require('sqlite') +const sqlite3 = require('sqlite3'); +const { open } = require('sqlite'); const lockfile = require('proper-lockfile'); @@ -15,75 +15,70 @@ const https = require('https'); const express = require('express'); const cors = require('cors'); const bodyParser = require('body-parser'); -const cookieParser=require('cookie-parser'); +const cookieParser = require('cookie-parser'); const jwt = require('jsonwebtoken'); const htmlspecialchars = require('htmlspecialchars'); -const dirTree = require("directory-tree"); +const dirTree = require('directory-tree'); const { resolve } = require('path'); -const execSync = require('child_process').execSync; - +const { execSync } = require('child_process'); const Reader = require('@commaai/log_reader'); var ffprobe = require('ffprobe'), - ffprobeStatic = require('ffprobe-static'); + ffprobeStatic = require('ffprobe-static'); const { exception } = require('console'); - + var db = null; -var lastCleaningTime=0; -var startTime=Date.now(); - +var lastCleaningTime = 0; +var startTime = Date.now(); log4js.configure({ - appenders: { logfile: { type: "file", filename: "worker.log" }, out: { type: "console"} }, - categories: { default: { appenders: ['out', 'logfile'], level: 'info' } } + appenders: { logfile: { type: 'file', filename: 'worker.log' }, out: { type: 'console' } }, + categories: { default: { appenders: ['out', 'logfile'], level: 'info' } }, }); - -var logger = log4js.getLogger('default'); +var logger = log4js.getLogger('default'); function initializeStorage() { - var verifiedPath = mkDirByPathSync(config.storagePath, {isRelativeToScript: (config.storagePath.indexOf("/")===0 ? false : true)}); - if (verifiedPath!=null) - logger.info("Verified storage path "+verifiedPath); - else { - logger.error("Unable to verify storage path '"+config.storagePath+"', check filesystem / permissions"); - process.exit(); - } + var verifiedPath = mkDirByPathSync(config.storagePath, { isRelativeToScript: (config.storagePath.indexOf('/') !== 0) }); + if (verifiedPath != null) logger.info(`Verified storage path ${verifiedPath}`); + else { + logger.error(`Unable to verify storage path '${config.storagePath}', check filesystem / permissions`); + process.exit(); + } } - function validateJWTToken(token, publicKey) { - try { - var decoded = jwt.verify(token.replace("JWT ", ""), publicKey, { algorithms: ['RS256'] }); - return decoded; - } catch (exception) { - console.log(exception); - } - return null; + try { + var decoded = jwt.verify(token.replace('JWT ', ''), publicKey, { algorithms: ['RS256'] }); + return decoded; + } catch (exception) { + console.log(exception); + } + return null; } function formatDate(timestampMs) { - return new Date(timestampMs).toISOString().replace(/T/, ' ').replace(/\..+/, ''); + return new Date(timestampMs).toISOString().replace(/T/, ' ').replace(/\..+/, ''); } function formatDuration(durationSeconds) { - var secs = durationSeconds % 60; - var mins = Math.floor(durationSeconds / 60); - var hours = Math.floor(mins / 60); - mins = mins % 60; + var secs = durationSeconds % 60; + var mins = Math.floor(durationSeconds / 60); + var hours = Math.floor(mins / 60); + mins %= 60; - var response=''; - if (hours>0) response+=hours+'h '; - if (hours>0 || mins>0) response+=mins+'m '; - response+=secs+'s'; - return response; + var response = ''; + if (hours > 0) response += `${hours}h `; + if (hours > 0 || mins > 0) response += `${mins}m `; + response += `${secs}s`; + return response; } function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) { - const sep = path.sep; + const { sep } = path; const initDir = path.isAbsolute(targetDir) ? sep : ''; const baseDir = isRelativeToScript ? __dirname : '.'; @@ -113,691 +108,686 @@ function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) { }, initDir); } -function simpleStringify (object){ - var simpleObject = {}; - for (var prop in object ){ - if (!object.hasOwnProperty(prop)){ - continue; - } - if (typeof(object[prop]) == 'object'){ - continue; - } - if (typeof(object[prop]) == 'function'){ - continue; - } - simpleObject[prop] = object[prop]; +function simpleStringify(object) { + var simpleObject = {}; + for (var prop in object) { + if (!object.hasOwnProperty(prop)) { + continue; } - return JSON.stringify(simpleObject); // returns cleaned up JSON + if (typeof (object[prop]) === 'object') { + continue; + } + if (typeof (object[prop]) === 'function') { + continue; + } + simpleObject[prop] = object[prop]; + } + return JSON.stringify(simpleObject); // returns cleaned up JSON } function writeFileSync(path, buffer, permission) { - var fileDescriptor; - try { - fileDescriptor = fs.openSync(path, 'w', permission); - } catch (e) { - fs.chmodSync(path, permission); - fileDescriptor = fs.openSync(path, 'w', permission); - } + var fileDescriptor; + try { + fileDescriptor = fs.openSync(path, 'w', permission); + } catch (e) { + fs.chmodSync(path, permission); + fileDescriptor = fs.openSync(path, 'w', permission); + } - if (fileDescriptor) { - fs.writeSync(fileDescriptor, buffer, 0, buffer.length, 0); - fs.closeSync(fileDescriptor); - logger.info("writeFileSync wiriting to '"+path+"' successful"); - return true; - } - logger.error("writeFileSync writing to '"+path+"' failed"); - return false; + if (fileDescriptor) { + fs.writeSync(fileDescriptor, buffer, 0, buffer.length, 0); + fs.closeSync(fileDescriptor); + logger.info(`writeFileSync wiriting to '${path}' successful`); + return true; + } + logger.error(`writeFileSync writing to '${path}' failed`); + return false; } function moveUploadedFile(buffer, directory, filename) { - logger.info("moveUploadedFile called with '"+filename+"' -> '"+directory+"'"); + logger.info(`moveUploadedFile called with '${filename}' -> '${directory}'`); - if (directory.indexOf("..")>=0 || filename.indexOf("..")>=0) { - logger.error("moveUploadedFile failed, .. in directory or filename"); - return false; + if (directory.indexOf('..') >= 0 || filename.indexOf('..') >= 0) { + logger.error('moveUploadedFile failed, .. in directory or filename'); + return false; + } + + if (config.storagePath.lastIndexOf('/') !== config.storagePath.length - 1) directory = `/${directory}`; + if (directory.lastIndexOf('/') !== directory.length - 1) directory += '/'; + + var finalPath = mkDirByPathSync(config.storagePath + directory, { isRelativeToScript: (config.storagePath.indexOf('/') !== 0) }); + if (finalPath && finalPath.length > 0) { + if (writeFileSync(`${finalPath}/${filename}`, buffer, 0o660)) { + logger.info(`moveUploadedFile successfully written '${finalPath}/${filename}'`); + return `${finalPath}/${filename}`; } - - if (config.storagePath.lastIndexOf("/")!==config.storagePath.length-1) - directory='/'+directory; - if (directory.lastIndexOf("/")!==directory.length-1) - directory=directory+'/'; - - var finalPath = mkDirByPathSync(config.storagePath+directory, {isRelativeToScript: (config.storagePath.indexOf("/")===0 ? false : true)}); - if (finalPath && finalPath.length>0) { - if (writeFileSync(finalPath+"/"+filename, buffer, 0o660)) { - logger.info("moveUploadedFile successfully written '"+(finalPath+"/"+filename)+"'"); - return finalPath+"/"+filename; - } - logger.error("moveUploadedFile failed to writeFileSync"); - return false; - } - logger.error("moveUploadedFile invalid final path, check permissions to create / write '"+(config.storagePath+directory)+"'"); - return false; + logger.error('moveUploadedFile failed to writeFileSync'); + return false; + } + logger.error(`moveUploadedFile invalid final path, check permissions to create / write '${config.storagePath + directory}'`); + return false; } - function deleteFolderRecursive(directoryPath) { - if (fs.existsSync(directoryPath)) { - fs.readdirSync(directoryPath).forEach((file, index) => { - const curPath = path.join(directoryPath, file); - if (fs.lstatSync(curPath).isDirectory()) { - deleteFolderRecursive(curPath); - } else { - fs.unlinkSync(curPath); - } - }); - fs.rmdirSync(directoryPath); - } + if (fs.existsSync(directoryPath)) { + fs.readdirSync(directoryPath).forEach((file, index) => { + const curPath = path.join(directoryPath, file); + if (fs.lstatSync(curPath).isDirectory()) { + deleteFolderRecursive(curPath); + } else { + fs.unlinkSync(curPath); + } + }); + fs.rmdirSync(directoryPath); + } } - - async function dbProtectedRun() { - let retries=0; - while(true) { - try { - const result = await db.run(...arguments); - return result; - } - catch (error) { - logger.error(error); - retries++; - if (retries>=10) { - break; - } - await new Promise(r => setTimeout(r, 1000)); - continue; - } + let retries = 0; + while (true) { + try { + const result = await db.run(...arguments); + return result; + } catch (error) { + logger.error(error); + retries++; + if (retries >= 10) { break; + } + await new Promise((r) => setTimeout(r, 1000)); + continue; } - logger.error("unable to complete dbProtectedRun for "+arguments); - return null; + break; + } + logger.error(`unable to complete dbProtectedRun for ${arguments}`); + return null; } async function dbProtectedGet() { - let retries=0; - while(true) { - try { - const result = await db.get(...arguments); - return result; - } - catch (error) { - logger.error(error); - retries++; - if (retries>=10) { - break; - } - await new Promise(r => setTimeout(r, 1000)); - continue; - } + let retries = 0; + while (true) { + try { + const result = await db.get(...arguments); + return result; + } catch (error) { + logger.error(error); + retries++; + if (retries >= 10) { break; + } + await new Promise((r) => setTimeout(r, 1000)); + continue; } - logger.error("unable to complete dbProtectedGet for "+arguments); - return null; + break; + } + logger.error(`unable to complete dbProtectedGet for ${arguments}`); + return null; } async function dbProtectedAll() { - let retries=0; - while(true) { - try { - const result = await db.all(...arguments); - return result; - } - catch (error) { - logger.error(error); - retries++; - if (retries>=10) { - break; - } - await new Promise(r => setTimeout(r, 1000)); - continue; - } + let retries = 0; + while (true) { + try { + const result = await db.all(...arguments); + return result; + } catch (error) { + logger.error(error); + retries++; + if (retries >= 10) { break; + } + await new Promise((r) => setTimeout(r, 1000)); + continue; } - logger.error("unable to complete dbProtectedGet for "+arguments); - return null; + break; + } + logger.error(`unable to complete dbProtectedGet for ${arguments}`); + return null; } -var segmentProcessQueue=[]; -var segmentProcessPosition=0; +var segmentProcessQueue = []; +var segmentProcessPosition = 0; -var affectedDrives={}; -var affectedDriveInitData={}; -var affectedDriveCarParams={}; +var affectedDrives = {}; +var affectedDriveInitData = {}; +var affectedDriveCarParams = {}; -var affectedDevices={}; +var affectedDevices = {}; - -var rlog_lastTsInternal=0; -var rlog_prevLatInternal=-1000; -var rlog_prevLngInternal=-1000; +var rlog_lastTsInternal = 0; +var rlog_prevLatInternal = -1000; +var rlog_prevLngInternal = -1000; var rlog_totalDistInternal = 0; -var rlog_lastTsExternal=0; -var rlog_prevLatExternal=-1000; -var rlog_prevLngExternal=-1000; +var rlog_lastTsExternal = 0; +var rlog_prevLatExternal = -1000; +var rlog_prevLngExternal = -1000; var rlog_totalDistExternal = 0; var qcamera_duration = 0; - function processSegmentRLog(rLogPath) { + rlog_lastTsInternal = 0; + rlog_prevLatInternal = -1000; + rlog_prevLngInternal = -1000; + rlog_totalDistInternal = 0; + rlog_lastTsExternal = 0; + rlog_prevLatExternal = -1000; + rlog_prevLngExternal = -1000; + rlog_totalDistExternal = 0; + rlog_CarParams = null; + rlog_InitData = null; - rlog_lastTsInternal=0; - rlog_prevLatInternal=-1000; - rlog_prevLngInternal=-1000; - rlog_totalDistInternal = 0; - rlog_lastTsExternal=0; - rlog_prevLatExternal=-1000; - rlog_prevLngExternal=-1000; - rlog_totalDistExternal = 0; - rlog_CarParams=null; - rlog_InitData=null; + return new Promise( + ((resolve, reject) => { + var temporaryFile = rLogPath.replace('.bz2', ''); - return new Promise( - function(resolve, reject) { - var temporaryFile = rLogPath.replace(".bz2", ""); - - try { - execSync(`bunzip2 -k -f "${rLogPath}"`); - } - catch (exception) { // if bunzip2 fails, something was wrong with the file (corrupt / missing) - logger.error(exception); - try {fs.unlinkSync(temporaryFile);} catch (exception) {} - resolve(); - return; - } - - var readStream = fs.createReadStream(temporaryFile); - var reader = Reader(readStream); - readStream.on('close', function () { - logger.info("processSegmentRLog readStream close event triggered, resolving promise"); - try {fs.unlinkSync(temporaryFile);} catch (exception) {} - resolve(); - }); - - reader(function (obj) { - try { - if (obj['LogMonoTime']!==undefined && obj['LogMonoTime']-rlog_lastTsInternal>=1000000*1000*0.99 && obj['GpsLocation']!==undefined) { - logger.info('processSegmentRLog GpsLocation @ '+obj['LogMonoTime']+': '+obj['GpsLocation']['Latitude']+' '+obj['GpsLocation']['Longitude']); - - if (rlog_prevLatInternal!=-1000) { - var lat1=rlog_prevLatInternal; - var lat2=obj['GpsLocation']['Latitude']; - var lon1=rlog_prevLngInternal; - var lon2=obj['GpsLocation']['Longitude']; - var p = 0.017453292519943295; // Math.PI / 180 - var c = Math.cos; - var a = 0.5 - c((lat2 - lat1) * p)/2 + - c(lat1 * p) * c(lat2 * p) * - (1 - c((lon2 - lon1) * p))/2; - - var dist_m = 1000 * 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km - if (dist_m>70) dist_m=0; // each segment is max. 60s. if the calculated speed would exceed ~250km/h for this segment, we assume the coordinates off / defective and skip it - rlog_totalDistInternal+=dist_m; - } - rlog_prevLatInternal=obj['GpsLocation']['Latitude']; - rlog_prevLngInternal=obj['GpsLocation']['Longitude']; - rlog_lastTsInternal = obj['LogMonoTime']; - } - else if (obj['LogMonoTime']!==undefined && obj['LogMonoTime']-rlog_lastTsExternal>=1000000*1000*0.99 && obj['GpsLocationExternal']!==undefined) { - logger.info('processSegmentRLog GpsLocationExternal @ '+obj['LogMonoTime']+': '+obj['GpsLocationExternal']['Latitude']+' '+obj['GpsLocationExternal']['Longitude']); - - if (rlog_prevLatExternal!=-1000) { - var lat1=rlog_prevLatExternal; - var lat2=obj['GpsLocationExternal']['Latitude']; - var lon1=rlog_prevLngExternal; - var lon2=obj['GpsLocationExternal']['Longitude']; - var p = 0.017453292519943295; // Math.PI / 180 - var c = Math.cos; - var a = 0.5 - c((lat2 - lat1) * p)/2 + - c(lat1 * p) * c(lat2 * p) * - (1 - c((lon2 - lon1) * p))/2; - - var dist_m = 1000 * 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km - if (dist_m>70) dist_m=0; // each segment is max. 60s. if the calculated speed would exceed ~250km/h for this segment, we assume the coordinates off / defective and skip it - rlog_totalDistExternal+=dist_m; - } - rlog_prevLatExternal=obj['GpsLocationExternal']['Latitude']; - rlog_prevLngExternal=obj['GpsLocationExternal']['Longitude']; - rlog_lastTsExternal = obj['LogMonoTime']; - } - else if (obj['LogMonoTime']!==undefined && obj['CarParams']!==undefined && rlog_CarParams==null) { - rlog_CarParams = obj['CarParams']; - } - else if (obj['LogMonoTime']!==undefined && obj['InitData']!==undefined && rlog_InitData==null) { - rlog_InitData = obj['InitData']; - } - - } catch(exception) { - - } - }); + try { + execSync(`bunzip2 -k -f "${rLogPath}"`); + } catch (exception) { // if bunzip2 fails, something was wrong with the file (corrupt / missing) + logger.error(exception); + try { fs.unlinkSync(temporaryFile); } catch (exception) {} + resolve(); + return; } + + var readStream = fs.createReadStream(temporaryFile); + var reader = Reader(readStream); + readStream.on('close', () => { + logger.info('processSegmentRLog readStream close event triggered, resolving promise'); + try { fs.unlinkSync(temporaryFile); } catch (exception) {} + resolve(); + }); + + reader((obj) => { + try { + if (obj.LogMonoTime !== undefined && obj.LogMonoTime - rlog_lastTsInternal >= 1000000 * 1000 * 0.99 && obj.GpsLocation !== undefined) { + logger.info(`processSegmentRLog GpsLocation @ ${obj.LogMonoTime}: ${obj.GpsLocation.Latitude} ${obj.GpsLocation.Longitude}`); + + if (rlog_prevLatInternal != -1000) { + var lat1 = rlog_prevLatInternal; + var lat2 = obj.GpsLocation.Latitude; + var lon1 = rlog_prevLngInternal; + var lon2 = obj.GpsLocation.Longitude; + var p = 0.017453292519943295; // Math.PI / 180 + var c = Math.cos; + var a = 0.5 - c((lat2 - lat1) * p) / 2 + + c(lat1 * p) * c(lat2 * p) + * (1 - c((lon2 - lon1) * p)) / 2; + + var dist_m = 1000 * 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km + if (dist_m > 70) dist_m = 0; // each segment is max. 60s. if the calculated speed would exceed ~250km/h for this segment, we assume the coordinates off / defective and skip it + rlog_totalDistInternal += dist_m; + } + rlog_prevLatInternal = obj.GpsLocation.Latitude; + rlog_prevLngInternal = obj.GpsLocation.Longitude; + rlog_lastTsInternal = obj.LogMonoTime; + } else if (obj.LogMonoTime !== undefined && obj.LogMonoTime - rlog_lastTsExternal >= 1000000 * 1000 * 0.99 && obj.GpsLocationExternal !== undefined) { + logger.info(`processSegmentRLog GpsLocationExternal @ ${obj.LogMonoTime}: ${obj.GpsLocationExternal.Latitude} ${obj.GpsLocationExternal.Longitude}`); + + if (rlog_prevLatExternal != -1000) { + var lat1 = rlog_prevLatExternal; + var lat2 = obj.GpsLocationExternal.Latitude; + var lon1 = rlog_prevLngExternal; + var lon2 = obj.GpsLocationExternal.Longitude; + var p = 0.017453292519943295; // Math.PI / 180 + var c = Math.cos; + var a = 0.5 - c((lat2 - lat1) * p) / 2 + + c(lat1 * p) * c(lat2 * p) + * (1 - c((lon2 - lon1) * p)) / 2; + + var dist_m = 1000 * 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km + if (dist_m > 70) dist_m = 0; // each segment is max. 60s. if the calculated speed would exceed ~250km/h for this segment, we assume the coordinates off / defective and skip it + rlog_totalDistExternal += dist_m; + } + rlog_prevLatExternal = obj.GpsLocationExternal.Latitude; + rlog_prevLngExternal = obj.GpsLocationExternal.Longitude; + rlog_lastTsExternal = obj.LogMonoTime; + } else if (obj.LogMonoTime !== undefined && obj.CarParams !== undefined && rlog_CarParams == null) { + rlog_CarParams = obj.CarParams; + } else if (obj.LogMonoTime !== undefined && obj.InitData !== undefined && rlog_InitData == null) { + rlog_InitData = obj.InitData; + } + } catch (exception) { + + } + }); + }), ); } function processSegmentVideo(qcameraPath) { - qcamera_duration=0; - return new Promise(function(resolve, reject) { - ffprobe(qcameraPath, { path: ffprobeStatic.path }) - .then(function (info) { - if (info['streams']!==undefined && info['streams'][0]!==undefined && info['streams'][0]['duration']!==undefined) - qcamera_duration = info['streams'][0]['duration']; - logger.info('processSegmentVideo duration: '+qcamera_duration+'s'); - resolve(); - }) - .catch(function (err) { - console.error(err); - logger.error('processSegmentVideo error: '+err); - resolve(); - }); - }); + qcamera_duration = 0; + return new Promise((resolve, reject) => { + ffprobe(qcameraPath, { path: ffprobeStatic.path }) + .then((info) => { + if (info.streams !== undefined && info.streams[0] !== undefined && info.streams[0].duration !== undefined) qcamera_duration = info.streams[0].duration; + logger.info(`processSegmentVideo duration: ${qcamera_duration}s`); + resolve(); + }) + .catch((err) => { + console.error(err); + logger.error(`processSegmentVideo error: ${err}`); + resolve(); + }); + }); } function processSegmentsRecursive() { - if (segmentProcessQueue.length<=segmentProcessPosition) - return updateDrives(); + if (segmentProcessQueue.length <= segmentProcessPosition) return updateDrives(); - var segmentWrapper = segmentProcessQueue[segmentProcessPosition]; - const segment = segmentWrapper.segment; + var segmentWrapper = segmentProcessQueue[segmentProcessPosition]; + const { segment } = segmentWrapper; - const uploadComplete = segmentWrapper.uploadComplete; - const driveIdentifier = segmentWrapper.driveIdentifier; - const fileStatus = segmentWrapper.fileStatus; + const { uploadComplete } = segmentWrapper; + const { driveIdentifier } = segmentWrapper; + const { fileStatus } = segmentWrapper; - logger.info('processSegmentsRecursive '+segment.dongle_id+' '+segment.drive_identifier+' '+segment.segment_id); + logger.info(`processSegmentsRecursive ${segment.dongle_id} ${segment.drive_identifier} ${segment.segment_id}`); - var p1 = processSegmentRLog(fileStatus['rlog.bz2']); - var p2 = processSegmentVideo(fileStatus['qcamera.ts']); - Promise.all([p1, p2]).then((values) => { - (async () => { - logger.info('processSegmentsRecursive '+segment.dongle_id+' '+segment.drive_identifier+' '+segment.segment_id+' internal gps: '+(Math.round(rlog_totalDistInternal*100)/100)+'m, external gps: '+(Math.round(rlog_totalDistExternal*100)/100)+'m, duration: '+qcamera_duration+'s'); - - const driveSegmentResult = await dbProtectedRun( - 'UPDATE drive_segments SET duration = ?, distance_meters = ?, is_processed = ?, upload_complete = ?, is_stalled = ? WHERE id = ?', - qcamera_duration, Math.round(Math.max(rlog_totalDistInternal, rlog_totalDistExternal)*10)/10, true, uploadComplete, false, - segment.id - ); + var p1 = processSegmentRLog(fileStatus['rlog.bz2']); + var p2 = processSegmentVideo(fileStatus['qcamera.ts']); + Promise.all([p1, p2]).then((values) => { + (async () => { + logger.info(`processSegmentsRecursive ${segment.dongle_id} ${segment.drive_identifier} ${segment.segment_id} internal gps: ${Math.round(rlog_totalDistInternal * 100) / 100}m, external gps: ${Math.round(rlog_totalDistExternal * 100) / 100}m, duration: ${qcamera_duration}s`); - if (driveSegmentResult===null) // if the update failed, stop right here with segment processing and try to update the drives at least - segmentProcessPosition=segmentProcessQueue.length; - - affectedDrives[driveIdentifier]=true; - if (rlog_CarParams!=null) - affectedDriveCarParams[driveIdentifier]=rlog_CarParams; - if (rlog_InitData!=null) - affectedDriveInitData[driveIdentifier]=rlog_InitData; - - segmentProcessPosition++; - setTimeout(function() {processSegmentsRecursive();}, 0); - })(); - }).catch((error) => { - logger.error(error); - }); + const driveSegmentResult = await dbProtectedRun( + 'UPDATE drive_segments SET duration = ?, distance_meters = ?, is_processed = ?, upload_complete = ?, is_stalled = ? WHERE id = ?', + qcamera_duration, + Math.round(Math.max(rlog_totalDistInternal, rlog_totalDistExternal) * 10) / 10, + true, + uploadComplete, + false, + segment.id, + ); + + if (driveSegmentResult === null) // if the update failed, stop right here with segment processing and try to update the drives at least + { segmentProcessPosition = segmentProcessQueue.length; } + + affectedDrives[driveIdentifier] = true; + if (rlog_CarParams != null) affectedDriveCarParams[driveIdentifier] = rlog_CarParams; + if (rlog_InitData != null) affectedDriveInitData[driveIdentifier] = rlog_InitData; + + segmentProcessPosition++; + setTimeout(() => { processSegmentsRecursive(); }, 0); + })(); + }).catch((error) => { + logger.error(error); + }); } async function updateSegments() { - segmentProcessQueue=[]; - segmentProcessPosition=0; - affectedDrives={}; - affectedDriveCarParams={}; - affectedDriveInitData={}; + segmentProcessQueue = []; + segmentProcessPosition = 0; + affectedDrives = {}; + affectedDriveCarParams = {}; + affectedDriveInitData = {}; - const drive_segments = await dbProtectedAll('SELECT * FROM drive_segments WHERE upload_complete = ? AND is_stalled = ? ORDER BY created ASC', false, false); - if (drive_segments!=null) { - for (var t=0; t10*24*3600*1000) { // ignore non-uploaded segments after 10 days until a new upload_url is requested (which resets is_stalled) - logger.info('updateSegments isStalled for '+segment.dongle_id+' '+segment.drive_identifier+' '+segment.segment_id); + for (var i in directoryTree.children) { + fileStatus[directoryTree.children[i].name] = directoryTree.children[i].path; + } - const driveSegmentResult = await dbProtectedRun( - 'UPDATE drive_segments SET is_stalled = ? WHERE id = ?', - true, segment.id); + var uploadComplete = false; + if (fileStatus['qcamera.ts'] !== false && fileStatus['fcamera.hevc'] !== false && fileStatus['rlog.bz2'] !== false && fileStatus['qlog.bz2'] !== false) // upload complete + { uploadComplete = true; } - } + if (fileStatus['qcamera.ts'] !== false && fileStatus['rlog.bz2'] !== false && !segment.is_processed) { // can process + segmentProcessQueue.push({ + segment, fileStatus, uploadComplete, driveIdentifier: `${segment.dongle_id}|${segment.drive_identifier}`, + }); + } else if (uploadComplete) { + logger.info(`updateSegments uploadComplete for ${segment.dongle_id} ${segment.drive_identifier} ${segment.segment_id}`); - if (segmentProcessQueue.length>=15) // we process at most 15 segments per batch - break; - } + const driveSegmentResult = await dbProtectedRun( + 'UPDATE drive_segments SET upload_complete = ?, is_stalled = ? WHERE id = ?', + true, + false, + segment.id, + ); + + affectedDrives[`${segment.dongle_id}|${segment.drive_identifier}`] = true; + } else if (Date.now() - segment.created > 10 * 24 * 3600 * 1000) { // ignore non-uploaded segments after 10 days until a new upload_url is requested (which resets is_stalled) + logger.info(`updateSegments isStalled for ${segment.dongle_id} ${segment.drive_identifier} ${segment.segment_id}`); + + const driveSegmentResult = await dbProtectedRun( + 'UPDATE drive_segments SET is_stalled = ? WHERE id = ?', + true, + segment.id, + ); + } + + if (segmentProcessQueue.length >= 15) // we process at most 15 segments per batch + { break; } } + } - if (segmentProcessQueue.length>0) - processSegmentsRecursive(); - else // if no data is to be collected, call updateDrives to update those where eventually just the last segment completed the upload - updateDrives(); - + if (segmentProcessQueue.length > 0) processSegmentsRecursive(); + else // if no data is to be collected, call updateDrives to update those where eventually just the last segment completed the upload + { updateDrives(); } } async function updateDevices() { - // go through all affected devices (with deleted or updated drives) and update them (storage_used) - logger.info("updateDevices - affected drives: "+JSON.stringify(affectedDevices)); - for (const [key, value] of Object.entries(affectedDevices)) { - var dongleId = key; + // go through all affected devices (with deleted or updated drives) and update them (storage_used) + logger.info(`updateDevices - affected drives: ${JSON.stringify(affectedDevices)}`); + for (const [key, value] of Object.entries(affectedDevices)) { + var dongleId = key; - const device = await dbProtectedGet('SELECT * FROM devices WHERE dongle_id = ?', dongleId); - if (device==null) continue; + const device = await dbProtectedGet('SELECT * FROM devices WHERE dongle_id = ?', dongleId); + if (device == null) continue; - var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(device.dongle_id).digest('hex'); - var devicePath=config.storagePath+device.dongle_id+"/"+dongleIdHash; - var deviceQuotaMb = Math.round(parseInt(execSync("du -s "+devicePath+" | awk -F'\t' '{print $1;}'").toString())/1024); - logger.info("updateDevices device "+dongleId+" has an updated storage_used of: "+deviceQuotaMb+" MB"); - - const deviceResult = await dbProtectedRun( - 'UPDATE devices SET storage_used = ? WHERE dongle_id = ?', - deviceQuotaMb, device.dongle_id); - } - affectedDevices=[]; + var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(device.dongle_id).digest('hex'); + var devicePath = `${config.storagePath + device.dongle_id}/${dongleIdHash}`; + var deviceQuotaMb = Math.round(parseInt(execSync(`du -s ${devicePath} | awk -F'\t' '{print $1;}'`).toString()) / 1024); + logger.info(`updateDevices device ${dongleId} has an updated storage_used of: ${deviceQuotaMb} MB`); + + const deviceResult = await dbProtectedRun( + 'UPDATE devices SET storage_used = ? WHERE dongle_id = ?', + deviceQuotaMb, + device.dongle_id, + ); + } + affectedDevices = []; } async function updateDrives() { - // go through all affected drives and update them / complete and/or build m3u8 - logger.info("updateDrives - affected drives: "+JSON.stringify(affectedDrives)); - for (const [key, value] of Object.entries(affectedDrives)) { - var dongleId, driveIdentifier; - [dongleId, driveIdentifier] = key.split('|'); - const drive = await dbProtectedGet('SELECT * FROM drives WHERE identifier = ? AND dongle_id = ?', driveIdentifier, dongleId); - if (drive==null) continue; + // go through all affected drives and update them / complete and/or build m3u8 + logger.info(`updateDrives - affected drives: ${JSON.stringify(affectedDrives)}`); + for (const [key, value] of Object.entries(affectedDrives)) { + var dongleId, + driveIdentifier; + [dongleId, driveIdentifier] = key.split('|'); + const drive = await dbProtectedGet('SELECT * FROM drives WHERE identifier = ? AND dongle_id = ?', driveIdentifier, dongleId); + if (drive == null) continue; - var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(drive.dongle_id).digest('hex'); - var driveIdentifierHash = crypto.createHmac('sha256', config.applicationSalt).update(drive.identifier).digest('hex'); - var driveUrl=config.baseDriveDownloadUrl+drive.dongle_id+"/"+dongleIdHash+"/"+driveIdentifierHash+"/"+drive.identifier; - var drivePath=config.storagePath+drive.dongle_id+"/"+dongleIdHash+"/"+driveIdentifierHash+"/"+drive.identifier; + var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(drive.dongle_id).digest('hex'); + var driveIdentifierHash = crypto.createHmac('sha256', config.applicationSalt).update(drive.identifier).digest('hex'); + var driveUrl = `${config.baseDriveDownloadUrl + drive.dongle_id}/${dongleIdHash}/${driveIdentifierHash}/${drive.identifier}`; + var drivePath = `${config.storagePath + drive.dongle_id}/${dongleIdHash}/${driveIdentifierHash}/${drive.identifier}`; - var uploadComplete=true; - var isProcessed=true; - - var totalDistanceMeters=0; - var totalDurationSeconds=0; - var playlistSegmentStrings=''; + var uploadComplete = true; + var isProcessed = true; - const drive_segments = await dbProtectedAll('SELECT * FROM drive_segments WHERE drive_identifier = ? AND dongle_id = ? ORDER BY segment_id ASC', driveIdentifier, dongleId); - if (drive_segments!=null) { - for (var t=0; t { mainWorkerLoop(); }, 0); } async function deleteExpiredDrives() { - var expirationTs = Date.now()-config.deviceDriveExpirationDays*24*3600*1000; + var expirationTs = Date.now() - config.deviceDriveExpirationDays * 24 * 3600 * 1000; - const expiredDrives = await dbProtectedAll('SELECT * FROM drives WHERE is_preserved = ? AND is_deleted = ? AND created < ?', false, false, expirationTs); - if (expiredDrives!=null) { - for (var t=0; t ?', config.deviceStorageQuotaMb); - if (devices==null) - return; - - for (var t=0; t ?', config.deviceStorageQuotaMb); + if (devices == null) return; - const driveNormal = await dbProtectedGet('SELECT * FROM drives WHERE dongle_id = ? AND is_preserved = ? AND is_deleted = ? ORDER BY created ASC LIMIT 1', devices[t].dongle_id, false, false); - if (driveNormal!=null) { - logger.info("deleteOverQuotaDrives drive "+driveNormal.dongle_id+" "+driveNormal.identifier+" (normal) is deleted for over-quota"); - const driveResult = await dbProtectedRun( - 'UPDATE drives SET is_deleted = ? WHERE id = ?', - true, driveNormal.id); - foundDriveToDelete=true; - } + for (var t = 0; t < devices.length; t++) { + var foundDriveToDelete = false; - if (!foundDriveToDelete) { - const drivePreserved = await dbProtectedGet('SELECT * FROM drives WHERE dongle_id = ? AND is_preserved = ? AND is_deleted = ? ORDER BY created ASC LIMIT 1', devices[t].dongle_id, true, false); - if (drivePreserved!=null) { - logger.info("deleteOverQuotaDrives drive "+drivePreserved.dongle_id+" "+drivePreserved.identifier+" (preserved!) is deleted for over-quota"); - const driveResult = await dbProtectedRun( - 'UPDATE drives SET is_deleted = ? WHERE id = ?', - true, drivePreserved.id); - foundDriveToDelete=true; - } - } + const driveNormal = await dbProtectedGet('SELECT * FROM drives WHERE dongle_id = ? AND is_preserved = ? AND is_deleted = ? ORDER BY created ASC LIMIT 1', devices[t].dongle_id, false, false); + if (driveNormal != null) { + logger.info(`deleteOverQuotaDrives drive ${driveNormal.dongle_id} ${driveNormal.identifier} (normal) is deleted for over-quota`); + const driveResult = await dbProtectedRun( + 'UPDATE drives SET is_deleted = ? WHERE id = ?', + true, + driveNormal.id, + ); + foundDriveToDelete = true; } -} + if (!foundDriveToDelete) { + const drivePreserved = await dbProtectedGet('SELECT * FROM drives WHERE dongle_id = ? AND is_preserved = ? AND is_deleted = ? ORDER BY created ASC LIMIT 1', devices[t].dongle_id, true, false); + if (drivePreserved != null) { + logger.info(`deleteOverQuotaDrives drive ${drivePreserved.dongle_id} ${drivePreserved.identifier} (preserved!) is deleted for over-quota`); + const driveResult = await dbProtectedRun( + 'UPDATE drives SET is_deleted = ? WHERE id = ?', + true, + drivePreserved.id, + ); + foundDriveToDelete = true; + } + } + } +} async function deleteBootAndCrashLogs() { - const devices = await dbProtectedAll('SELECT * FROM devices'); - if (devices==null) - return; + const devices = await dbProtectedAll('SELECT * FROM devices'); + if (devices == null) return; - for (var t=0; t (a.date < b.date) ? 1 : -1); - for (var c=5; c (a.date < b.date) ? 1 : -1); - for (var c=5; c ((a.date < b.date) ? 1 : -1)); + for (var c = 5; c < bootlogFiles.length; c++) { + logger.info(`deleteBootAndCrashLogs deleting boot log ${bootlogFiles[c].path}`); + try { + fs.unlinkSync(bootlogFiles[c].path); + affectedDevices[device.dongle_id] = true; + } catch (exception) { + logger.error(exception); } + } } + + const crashlogDirectoryTree = dirTree(`${config.storagePath + device.dongle_id}/${dongleIdHash}/crash/`, { attributes: ['size'] }); + var crashlogFiles = []; + if (crashlogDirectoryTree != undefined) { + for (var i = 0; i < crashlogDirectoryTree.children.length; i++) { + var timeSplit = crashlogDirectoryTree.children[i].name.replace('boot-', '').replace('crash-', '').replace('\.bz2', '').split('--'); + var timeString = `${timeSplit[0]} ${timeSplit[1].replace(/-/g, ':')}`; + crashlogFiles.push({ + name: crashlogDirectoryTree.children[i].name, size: crashlogDirectoryTree.children[i].size, date: Date.parse(timeString), path: crashlogDirectoryTree.children[i].path, + }); + } + crashlogFiles.sort((a, b) => ((a.date < b.date) ? 1 : -1)); + for (var c = 5; c < crashlogFiles.length; c++) { + logger.info(`deleteBootAndCrashLogs deleting crash log ${crashlogFiles[c].path}`); + try { + fs.unlinkSync(crashlogFiles[c].path); + affectedDevices[device.dongle_id] = true; + } catch (exception) { + logger.error(exception); + } + } + } + } } - async function mainWorkerLoop() { - if (Date.now()-startTime>60*60*1000) { - logger.info("EXIT WORKER AFTER 1 HOUR TO PREVENT MEMORY LEAKS..."); - process.exit(); + if (Date.now() - startTime > 60 * 60 * 1000) { + logger.info('EXIT WORKER AFTER 1 HOUR TO PREVENT MEMORY LEAKS...'); + process.exit(); + } + + try { + if (Date.now() - lastCleaningTime > 20 * 60 * 1000) { + await deleteBootAndCrashLogs(); + await deleteExpiredDrives(); + await deleteOverQuotaDrives(); + await removeDeletedDrivesPhysically(); + lastCleaningTime = Date.now(); } - try { - if (Date.now()-lastCleaningTime>20*60*1000) { - await deleteBootAndCrashLogs(); - await deleteExpiredDrives(); - await deleteOverQuotaDrives(); - await removeDeletedDrivesPhysically(); - lastCleaningTime=Date.now(); - } - - setTimeout(function() {updateSegments();}, 5000); - } catch (e) { - logger.error(e); - } - - + setTimeout(() => { updateSegments(); }, 5000); + } catch (e) { + logger.error(e); + } } // make sure bunzip2 is available try { - execSync(`bunzip2 --help`); + execSync('bunzip2 --help'); +} catch (exception) { + logger.error('bunzip2 is not installed or not available in environment path'); + process.exit(); } -catch (exception) { - logger.error("bunzip2 is not installed or not available in environment path") - process.exit(); -} - lockfile.lock('retropilot_worker.lock', { realpath: false, stale: 90000, update: 2000 }) -.then((release) => { - logger.info("STARTING WORKER..."); + .then((release) => { + logger.info('STARTING WORKER...'); (async () => { - try { - db = await open({ - filename: config.databaseFile, - driver: sqlite3.Database, - mode: sqlite3.OPEN_READWRITE - }); - await db.get('SELECT * FROM accounts LIMIT 1') - await db.get('SELECT * FROM devices LIMIT 1') - await db.get('SELECT * FROM drives LIMIT 1') - await db.get('SELECT * FROM drive_segments LIMIT 1') - } catch(exception) { - logger.error(exception); - process.exit(); - } + try { + db = await open({ + filename: config.databaseFile, + driver: sqlite3.Database, + mode: sqlite3.OPEN_READWRITE, + }); + await db.get('SELECT * FROM accounts LIMIT 1'); + await db.get('SELECT * FROM devices LIMIT 1'); + await db.get('SELECT * FROM drives LIMIT 1'); + await db.get('SELECT * FROM drive_segments LIMIT 1'); + } catch (exception) { + logger.error(exception); + process.exit(); + } - initializeStorage(); - setTimeout(function() {mainWorkerLoop();}, 0); + initializeStorage(); + setTimeout(() => { mainWorkerLoop(); }, 0); })(); -}).catch((e) => { - console.error(e) - process.exit(); -}); + }).catch((e) => { + console.error(e); + process.exit(); + });