Skip to content

Commit

Permalink
Add API endpoints for story admin (#16)
Browse files Browse the repository at this point in the history
* Add story admin API routes

* Convert dmx code to TypeScript

* TypeScript refactoring

Refactor Socket.IO initialization to a separate file to reduce circular
dependencies.

Refactor messaging to TypeScript.

* Fix API doc generation
  • Loading branch information
nicou authored Feb 2, 2024
1 parent aecdee3 commit 33a074f
Show file tree
Hide file tree
Showing 24 changed files with 692 additions and 134 deletions.
2 changes: 1 addition & 1 deletion db/seeds/06-story-admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { parsedBoolean, parseCommaSeparatedString, trimmedStringOrNull, parsedIn
import { StoryPlot, StoryPlotEventLink, StoryPlotArtifactLink, StoryPlotPersonLink, StoryPlotMessagesLink } from "../../src/models/story-plots";
import { StoryEvent, StoryEventArtifactLink, StoryEventMessagesLink, StoryEventPersonLink } from "../../src/models/story-events";
import { StoryMessage, StoryMessagePersonLink } from "../../src/models/story-messages";
import { StoryPersonRelation } from "../../src/models/story-person-relations";
import { StoryPersonRelation } from "../../src/models/story-person";

const OptionalString = z.preprocess(trimmedStringOrNull, z.string().nullable());
const OptionalInt = z.preprocess(parsedIntOrNull, z.number().nullable());
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
},
"devDependencies": {
"@types/body-parser": "^1.19.2",
"@types/express": "^4.17.17",
"@types/express": "^4.17.21",
"@types/http-errors": "^2.0.3",
"@types/lodash": "^4.14.200",
"@types/node": "^18.14.6",
Expand Down
38 changes: 20 additions & 18 deletions src/dmx.js → src/dmx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { logger } from './logger';
import DMX from 'dmx';
import { isNumber } from 'lodash';


const UNIVERSE_NAME = 'backend';
const EVENT_DURATION = 1000; // ms
const EVENT_DURATION = 1000; // ms

export const CHANNELS = {
JumpFixed: 100,
Expand Down Expand Up @@ -88,28 +87,41 @@ export const CHANNELS = {
HangarBayDoorMalfunction: 197,
HangarBayPressurize: 198,
HangarBayDepressurize: 199,
} as const;

type Channel = typeof CHANNELS[keyof typeof CHANNELS];

interface Dmx {
update: (universe: string, value: Record<number, number>) => void;
};

const dmx = init();

function init() {
function init(): Dmx {
if (process.env.DMX_DRIVER) {
const dmx = new DMX();
dmx.addUniverse(UNIVERSE_NAME, process.env.DMX_DRIVER, process.env.DMX_DEVICE_PATH);
return dmx;
} else {
return {
update() {}
update: (universe: string, value: Record<number, number>) => {
logger.debug(`DMX update on universe ${universe}: ${JSON.stringify(value)}`);
},
};
}
}

export function setValue(channel, value) {
logger.debug(`Setting DMX channel ${channel} (${findChannelName(channel)}) to ${value}`);
dmx.update(UNIVERSE_NAME, { [channel]: value });
function findChannelName(channel: Channel) {
for (const name of Object.keys(CHANNELS)) {
if (CHANNELS[name] === channel) {
return name;
}
}
logger.error(`Unknown DMX channel ${channel} used`);
return 'UNKNOWN';
}

export function fireEvent(channel, value = 255) {
export function fireEvent(channel: Channel, value = 255) {
if (!isNumber(channel) || !isNumber(value) || channel < 0 || channel > 255 || value < 0 || value > 255) {
logger.error(`Attempted DMX fireEvent with invalid channel=${channel} or value=${value}`);
return;
Expand All @@ -118,13 +130,3 @@ export function fireEvent(channel, value = 255) {
dmx.update(UNIVERSE_NAME, { [channel]: value });
setTimeout(() => dmx.update(UNIVERSE_NAME, { [channel]: 0 }), EVENT_DURATION);
}

function findChannelName(channel) {
for (const name of Object.keys(CHANNELS)) {
if (CHANNELS[name] === channel) {
return name;
}
}
logger.error(`Unknown DMX channel ${channel} used`);
return 'UNKNOWN';
}
2 changes: 1 addition & 1 deletion src/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const options = {
securityDefinitions: {}
},
basedir: __dirname,
files: ['./routes/**/*.js', './models/**/*.js', './models/**/*.ts', './index.js', './messaging.js', './emptyepsilon.js']
files: ['./routes/**/*.{js,ts}', './models/**/*.{js,ts}', './index.ts', './messaging.ts', './emptyepsilon.js']
};

export function loadSwagger(app: Express) {
Expand Down
37 changes: 9 additions & 28 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import 'dotenv/config';
import { Server } from 'http';
import { HttpError } from 'http-errors';
import express, { NextFunction, Request, Response } from 'express';
import express, { Request, Response } from 'express';
import bodyParser from 'body-parser';
import socketIo from 'socket.io';
import { logger, loggerMiddleware } from './logger';
import { loadSwagger } from './docs';
import { getEmptyEpsilonClient, setStateRouteHandler } from './emptyepsilon';
import { loadEvents } from './eventhandler';
import { loadMessaging, router as messaging } from './messaging';
import { Store } from './models/store';
import { handleAsyncErrors } from './routes/helpers';
import { errorHandlingMiddleware, handleAsyncErrors } from './routes/helpers';
import { get, isEqual, omit, isEmpty } from 'lodash';
import cors from 'cors';

import { initSocketIoClient } from './websocket';
import prometheusIoMetrics from 'socket.io-prometheus';
import prometheusMiddleware from 'express-prometheus-middleware';

Expand All @@ -31,15 +29,15 @@ import science from './routes/science';
import tag from './routes/tag';
import operation from './routes/operation';
import sip from './routes/sip';
import storyAdminRoutes from './routes/story-admin';
import { setData, getData, router as data } from './routes/data';
import infoboard from './routes/infoboard';
import dmxRoutes from './routes/dmx';

import { loadRules } from './rules/rules';

const app = express();
const http = new Server(app);
const io = socketIo(http);
const io = initSocketIoClient(http);

// Setup logging middleware and body parsing
app.use(bodyParser.json());
Expand Down Expand Up @@ -84,6 +82,7 @@ app.use('/messaging', messaging);
app.use('/tag', tag);
app.use('/operation', operation);
app.use('/sip', sip);
app.use('/story', storyAdminRoutes);

// Empty Epsilon routes
app.put('/state', setStateRouteHandler);
Expand Down Expand Up @@ -117,22 +116,9 @@ app.post('/emit/:eventName', (req, res) => {
});

// Error handling middleware
app.use(async (err: HttpError, req: Request, res: Response, _next: NextFunction) => {
let status = 500;
if ('statusCode' in err) {
status = err.statusCode;
}
logger.error(err.message);
return res.status(status).json({ error: err.message });
});
app.use(errorHandlingMiddleware);

// Setup Socket.IO
io.on('connection', socket => {
logger.info('Socket.IO Client connected');
socket.on('disconnect', () => {
logger.info('Socket.IO Client disconnected');
});
});
// Setup EOS Datahub messaging
loadMessaging(io);

// Get latest Empty Epsilon game state and save it to store
Expand Down Expand Up @@ -186,15 +172,10 @@ initStoreSocket(io);

function startServer() {
const { APP_PORT } = process.env;
http.listen(APP_PORT, () => logger.start(`Odysseus backend listening to port ${APP_PORT}`));
http.listen(APP_PORT, () => logger.start(`Odysseus backend listening on http://localhost:${APP_PORT}`));
}

// Health check route
app.get('/ping', (req, res) => {
res.send('pong');
});

// For emitting Socket.IO events from rule files
export function getSocketIoClient() {
return io;
}
Loading

0 comments on commit 33a074f

Please sign in to comment.