mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-12 10:53:51 +00:00
Merge branch 'master' into feat/oscfeedback
This commit is contained in:
+81
-34
@@ -1,11 +1,22 @@
|
||||
// get config
|
||||
// get environment vars
|
||||
import 'dotenv/config.js';
|
||||
import { sessionId, user } from './utils/analytics.js';
|
||||
user.screenview('Node service', 'ontime').send();
|
||||
user.event('NODE', 'started', 'starting node service').send();
|
||||
|
||||
import { config } from './config/config.js';
|
||||
|
||||
// init database
|
||||
import { Low, JSONFile } from 'lowdb';
|
||||
import { join } from 'path';
|
||||
|
||||
const file = join('data/', config.database.filename);
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const env = process.env.NODE_ENV || 'prod';
|
||||
|
||||
const file = path.join(__dirname, 'data/', config.database.filename);
|
||||
const adapter = new JSONFile(file);
|
||||
export const db = new Low(adapter);
|
||||
|
||||
@@ -15,6 +26,7 @@ import express from 'express';
|
||||
import http from 'http';
|
||||
import cors from 'cors';
|
||||
import { dbModel } from './data/dataModel.js';
|
||||
import ua from 'universal-analytics';
|
||||
|
||||
// Read data from JSON file, this will set db.data content
|
||||
await db.read();
|
||||
@@ -35,9 +47,6 @@ import { router as eventsRouter } from './routes/eventsRouter.js';
|
||||
import { router as eventRouter } from './routes/eventRouter.js';
|
||||
import { router as ontimeRouter } from './routes/ontimeRouter.js';
|
||||
|
||||
// Setup default port
|
||||
const port = process.env.PORT || config.server.port;
|
||||
|
||||
// Global Objects
|
||||
import { EventTimer } from './classes/EventTimer.js';
|
||||
|
||||
@@ -51,52 +60,90 @@ app.use(cors());
|
||||
app.options('*', cors());
|
||||
|
||||
// Implement middleware
|
||||
app.use('/uploads', express.static('uploads'));
|
||||
|
||||
app.use(ua.middleware(process.env.ANALYTICS_ID, sessionId));
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(express.json({ limit: '1mb' }));
|
||||
|
||||
app.use('/uploads', express.static('uploads'));
|
||||
|
||||
// Implement route endpoints
|
||||
app.use('/events', eventsRouter);
|
||||
app.use('/event', eventRouter);
|
||||
app.use('/ontime', ontimeRouter);
|
||||
|
||||
// implement general router
|
||||
app.get('/', (req, res) => {
|
||||
res.send('ontime API');
|
||||
// serve react
|
||||
app.use(
|
||||
express.static(
|
||||
path.join(__dirname, env == 'prod' ? '../' : '../../', 'client/build')
|
||||
)
|
||||
);
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
env == 'prod' ? '../' : '../../',
|
||||
'client',
|
||||
'build',
|
||||
'index.html'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
// Implement route for errors
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).send('Something broke!');
|
||||
res.status(500).send(err.stack);
|
||||
});
|
||||
|
||||
// create HTTP server
|
||||
const server = http.createServer(app);
|
||||
|
||||
// Start OSC Client
|
||||
let oscClient = null;
|
||||
export const startOSCClient = (overrideConfig = null) => {
|
||||
// Setup IP
|
||||
const ipOut = overrideConfig?.ip || config.osc.ipOut;
|
||||
export const startServer = (overrideConfig = null) => {
|
||||
// Setup default port
|
||||
const oscOutPort = overrideConfig?.port || config.osc.portOut;
|
||||
oscClient = new Client(ipOut, oscOutPort);
|
||||
console.log(`OSC Client sending to ${ipOut}:${oscOutPort}`);
|
||||
const serverPort = overrideConfig?.port || config.server.port;
|
||||
|
||||
// Start server
|
||||
const returnMessage = `HTTP Server is listening on port ${serverPort}`;
|
||||
server.listen(serverPort, '0.0.0.0', () => console.log(returnMessage));
|
||||
|
||||
// init timer
|
||||
global.timer = new EventTimer(server, config);
|
||||
global.timer.setupWithEventList(data.events);
|
||||
|
||||
return returnMessage;
|
||||
};
|
||||
|
||||
startOSCClient();
|
||||
|
||||
// init timer
|
||||
global.timer = new EventTimer(server, oscClient, config);
|
||||
global.timer.setupWithEventList(data.events);
|
||||
|
||||
// Start server
|
||||
server.listen(port, () =>
|
||||
console.log(`HTTP Server is listening on port ${port}`)
|
||||
);
|
||||
|
||||
// Start OSC server
|
||||
import { initiateOSC } from './controllers/OscController.js';
|
||||
initiateOSC(config.osc);
|
||||
import { initiateOSC, shutdownOSCServer } from './controllers/OscController.js';
|
||||
|
||||
export const startOSCServer = (overrideConfig = null) => {
|
||||
// Setup default port
|
||||
const oscInPort = overrideConfig?.port || config.osc.port;
|
||||
initiateOSC(config.osc);
|
||||
};
|
||||
|
||||
export const startOSCClient = (overrideConfig = null) => {
|
||||
// Setup default port
|
||||
const oscOutPort = overrideConfig?.port || config.osc.portOut;
|
||||
console.log('initialise OSC Client at port: ', oscOutPort);
|
||||
};
|
||||
|
||||
export const shutdown = () => {
|
||||
console.log('Node service shutdown');
|
||||
|
||||
user.event('NODE', 'shutdown', 'requesting node shutfown').send();
|
||||
|
||||
// shutdown express server
|
||||
server.close();
|
||||
// shutdown OSC Server
|
||||
shutdownOSCServer();
|
||||
// shutdown OSC Client
|
||||
|
||||
// shutdown timer
|
||||
global.timer.shutdown();
|
||||
};
|
||||
|
||||
if (env == 'development') {
|
||||
startServer();
|
||||
startOSCClient();
|
||||
}
|
||||
|
||||
@@ -48,9 +48,11 @@ export class EventTimer extends Timer {
|
||||
titleNow: null,
|
||||
subtitleNow: null,
|
||||
presenterNow: null,
|
||||
noteNow: null,
|
||||
titleNext: null,
|
||||
subtitleNext: null,
|
||||
presenterNext: null,
|
||||
noteNext: null,
|
||||
};
|
||||
|
||||
selectedEventIndex = null;
|
||||
@@ -180,6 +182,14 @@ export class EventTimer extends Timer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Shutdown process
|
||||
*/
|
||||
shutdown() {
|
||||
console.log('Closing socket server');
|
||||
this.io.close();
|
||||
}
|
||||
|
||||
// send current timer
|
||||
broadcastTimer() {
|
||||
// through websockets
|
||||
@@ -199,6 +209,7 @@ export class EventTimer extends Timer {
|
||||
this.io.emit('selected', {
|
||||
id: this.selectedEventId,
|
||||
index: this.selectedEventIndex,
|
||||
total: this.numEvents,
|
||||
});
|
||||
this.io.emit('selected-id', this.selectedEventId);
|
||||
this.io.emit('next-id', this.nextEventId);
|
||||
@@ -383,6 +394,7 @@ export class EventTimer extends Timer {
|
||||
socket.emit('selected', {
|
||||
id: this.selectedEventId,
|
||||
index: this.selectedEventIndex,
|
||||
total: this.numEvents,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -682,6 +694,7 @@ export class EventTimer extends Timer {
|
||||
this.titles.titleNow = e.title;
|
||||
this.titles.subtitleNow = e.subtitle;
|
||||
this.titles.presenterNow = e.presenter;
|
||||
this.titles.noteNow = e.note;
|
||||
this.selectedEventId = e.id;
|
||||
|
||||
break;
|
||||
@@ -695,6 +708,7 @@ export class EventTimer extends Timer {
|
||||
this.titles.titleNow = e.title;
|
||||
this.titles.subtitleNow = e.subtitle;
|
||||
this.titles.presenterNow = e.presenter;
|
||||
this.titles.noteNow = e.note;
|
||||
this.selectedEventId = e.id;
|
||||
break;
|
||||
|
||||
@@ -710,6 +724,7 @@ export class EventTimer extends Timer {
|
||||
this.titles.titleNext = e.title;
|
||||
this.titles.subtitleNext = e.subtitle;
|
||||
this.titles.presenterNext = e.presenter;
|
||||
this.titles.noteNext = e.note;
|
||||
this.nextEventId = e.id;
|
||||
break;
|
||||
case 'next-public':
|
||||
@@ -722,6 +737,7 @@ export class EventTimer extends Timer {
|
||||
this.titles.titleNext = e.title;
|
||||
this.titles.subtitleNext = e.subtitle;
|
||||
this.titles.presenterNext = e.presenter;
|
||||
this.titles.noteNext = e.note;
|
||||
this.nextEventId = e.id;
|
||||
break;
|
||||
|
||||
@@ -738,6 +754,7 @@ export class EventTimer extends Timer {
|
||||
this.titles.titleNext = null;
|
||||
this.titles.subtitleNext = null;
|
||||
this.titles.presenterNext = null;
|
||||
this.titles.noteNext = null;
|
||||
this.nextEventId = null;
|
||||
|
||||
this.titlesPublic.titleNext = null;
|
||||
@@ -776,9 +793,11 @@ export class EventTimer extends Timer {
|
||||
titleNow: null,
|
||||
subtitleNow: null,
|
||||
presenterNow: null,
|
||||
noteNow: null,
|
||||
titleNext: null,
|
||||
subtitleNext: null,
|
||||
presenterNext: null,
|
||||
noteNext: null,
|
||||
};
|
||||
|
||||
this.publicTitles = {
|
||||
@@ -825,9 +844,11 @@ export class EventTimer extends Timer {
|
||||
Title Now = ${this.titles.titleNow}
|
||||
Subtitle Now = ${this.titles.subtitleNow}
|
||||
Presenter Now = ${this.titles.presenterNow}
|
||||
Note Now = ${this.titles.noteNow}
|
||||
Title Next = ${this.titles.titleNext}
|
||||
Subtitle Next = ${this.titles.subtitleNext}
|
||||
Presenter Next = ${this.titles.presenterNext}
|
||||
Note Next = ${this.titles.noteNext}
|
||||
|
||||
Public Titles
|
||||
------------------------------
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { Server } from 'node-osc';
|
||||
|
||||
let oscServer = null;
|
||||
|
||||
export const shutdownOSCServer = () => {
|
||||
if (oscServer != null) oscServer.close();
|
||||
};
|
||||
|
||||
export const initiateOSC = (config) => {
|
||||
const oscServer = new Server(config.port, '0.0.0.0', () => {
|
||||
oscServer = new Server(config.port, '0.0.0.0', () => {
|
||||
console.log(`OSC Server is listening on port ${config.port}`);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// get database
|
||||
import { db, data } from '../app.js';
|
||||
import fs from 'fs';
|
||||
import {
|
||||
event as eventDef,
|
||||
delay as delayDef,
|
||||
block as blockDef,
|
||||
} from '../data/eventsDefinition.js';
|
||||
import { dbModel } from '../data/dataModel.js';
|
||||
import { networkInterfaces } from 'os';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
function getEventTitle() {
|
||||
return data.event.title;
|
||||
@@ -23,6 +30,7 @@ async function deleteFile(file) {
|
||||
|
||||
// parses version 1 of the data system
|
||||
async function parsev1(jsonData) {
|
||||
let numEntries = 0;
|
||||
if ('events' in jsonData) {
|
||||
let events = [];
|
||||
let ids = [];
|
||||
@@ -45,15 +53,19 @@ async function parsev1(jsonData) {
|
||||
isPublic: e.isPublic,
|
||||
id: e.id,
|
||||
});
|
||||
numEntries++;
|
||||
} else if (e.type === 'delay') {
|
||||
events.push({ ...delayDef, duration: e.duration });
|
||||
numEntries++;
|
||||
} else if (e.type === 'block') {
|
||||
events.push({ ...blockDef });
|
||||
numEntries++;
|
||||
}
|
||||
}
|
||||
// write to db
|
||||
db.data.events = events;
|
||||
db.write();
|
||||
console.log(`Uploaded file with ${numEntries} entries`);
|
||||
}
|
||||
|
||||
if ('event' in jsonData) {
|
||||
@@ -82,7 +94,9 @@ async function parsev1(jsonData) {
|
||||
// Returns -
|
||||
export const dbDownload = async (req, res) => {
|
||||
const fileTitle = getEventTitle() || 'ontime events';
|
||||
res.download('db.json', `${fileTitle}.json`, (err) => {
|
||||
const dbFile = path.resolve(__dirname, '../', 'data/db.json');
|
||||
|
||||
res.download(dbFile, `${fileTitle}.json`, (err) => {
|
||||
if (err) {
|
||||
res.status(500).send({
|
||||
message: 'Could not download the file. ' + err,
|
||||
@@ -91,15 +105,7 @@ export const dbDownload = async (req, res) => {
|
||||
});
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/db'
|
||||
// Returns -
|
||||
export const dbUpload = async (req, res) => {
|
||||
if (!req.file) {
|
||||
res.status(400).send({ message: 'File not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
const file = req.file.path;
|
||||
const upload = async (file, req, res) => {
|
||||
if (!fs.existsSync(file)) {
|
||||
res.status(500).send({ message: 'Upload failed' });
|
||||
return;
|
||||
@@ -107,8 +113,8 @@ export const dbUpload = async (req, res) => {
|
||||
|
||||
try {
|
||||
// get file
|
||||
let rawdata = fs.readFileSync(file);
|
||||
let uploadedJson = JSON.parse(rawdata);
|
||||
const rawdata = fs.readFileSync(file);
|
||||
const uploadedJson = JSON.parse(rawdata);
|
||||
|
||||
// delete file
|
||||
deleteFile(file);
|
||||
@@ -126,3 +132,57 @@ export const dbUpload = async (req, res) => {
|
||||
res.status(400).send({ message: error });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Gets information on IPV4 non internal interfaces
|
||||
* @returns {array} - Array of objects {name: ip}
|
||||
*/
|
||||
const getNetworkInterfaces = () => {
|
||||
const nets = networkInterfaces();
|
||||
const results = [];
|
||||
|
||||
for (const name of Object.keys(nets)) {
|
||||
for (const net of nets[name]) {
|
||||
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||
if (net.family === 'IPv4' && !net.internal) {
|
||||
results.push({
|
||||
name: name,
|
||||
address: net.address,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/info'
|
||||
// Returns -
|
||||
// TODO: Add version
|
||||
export const getInfo = async (req, res) => {
|
||||
const ni = getNetworkInterfaces();
|
||||
res.status(200).send({
|
||||
networkInterfaces: ni,
|
||||
});
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/db'
|
||||
// Returns -
|
||||
export const dbUpload = async (req, res) => {
|
||||
if (!req.file) {
|
||||
res.status(400).send({ message: 'File not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
const file = req.file.path;
|
||||
upload(file, req, res);
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/dbpath'
|
||||
// Returns -
|
||||
export const dbPathToUpload = async (req, res) => {
|
||||
if (!req.body.path) {
|
||||
res.status(400).send({ message: 'Path to file not found' });
|
||||
return;
|
||||
}
|
||||
upload(req.body.path, req, res);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"body-parser": "~1.19.0",
|
||||
"express": "~4.17.1",
|
||||
"express-session": "~1.17.1",
|
||||
"lowdb": "2.1.0",
|
||||
"multer": "^1.4.2",
|
||||
"nanoid": "^3.1.22",
|
||||
"node-osc": "6.0.2",
|
||||
"passport": "~0.4.1",
|
||||
"passport-local": "~1.0.0",
|
||||
"socket.io": "^4.0.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"universal-analytics": "^0.4.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.26.0",
|
||||
"eslint-config-airbnb": "^18.2.1",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"nodestart": "nodemon app.js",
|
||||
"start": "node app.js"
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,21 @@ import express from 'express';
|
||||
import uploadJson from '../utils/upload.js';
|
||||
export const router = express.Router();
|
||||
|
||||
import { dbDownload, dbUpload } from '../controllers/ontimeController.js';
|
||||
import {
|
||||
dbDownload,
|
||||
dbUpload,
|
||||
getInfo,
|
||||
dbPathToUpload,
|
||||
} from '../controllers/ontimeController.js';
|
||||
|
||||
// create route between controller and '/ontime/db' endpoint
|
||||
router.get('/db', dbDownload);
|
||||
|
||||
// create route between controller and '/ontime/db' endpoint
|
||||
router.post('/db', uploadJson, dbUpload);
|
||||
|
||||
// create route between controller and '/ontime/info' endpoint
|
||||
router.get('/info', uploadJson, getInfo);
|
||||
|
||||
// create route between controller and '/ontime/dbpath' endpoint
|
||||
router.post('/dbpath', dbPathToUpload);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// get environment vars
|
||||
import 'dotenv/config.js';
|
||||
|
||||
import ua from 'universal-analytics';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export const sessionId = nanoid();
|
||||
export const user = ua(process.env.ANALYTICS_ID);
|
||||
|
||||
// Track session
|
||||
user.set('uid', sessionId);
|
||||
|
||||
// Allows filtering by the 'Application?' field in GA
|
||||
user.set('ds', 'app');
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user