183 lines
4.7 KiB
JavaScript
183 lines
4.7 KiB
JavaScript
const express = require("express");
|
|
const {
|
|
createDetailedHealthRouter,
|
|
createHealthRouter,
|
|
} = require("./health/routes");
|
|
const { createAssetRouter } = require("./assets/routes");
|
|
const { createMediaRouter } = require("./assets/media-routes");
|
|
const { createAssetService } = require("./assets/service");
|
|
const { createPlaybackService } = require("./playback");
|
|
const { createPlaybackRouter } = require("./playback/routes");
|
|
const {
|
|
createPlaylistRouter,
|
|
createPlaylistService,
|
|
} = require("./playlists");
|
|
const { createSettingsRouter } = require("./settings/routes");
|
|
const { createSettingsService } = require("./settings/service");
|
|
|
|
function safeNotifier(notifier, logger, failureMessage) {
|
|
const warn = (error) => {
|
|
try {
|
|
logger?.warn?.(failureMessage, error);
|
|
} catch {
|
|
// A best-effort notification or warning must not undo a database commit.
|
|
}
|
|
};
|
|
return (event) => {
|
|
try {
|
|
const result = notifier(event);
|
|
result?.catch?.(warn);
|
|
} catch (error) {
|
|
warn(error);
|
|
}
|
|
};
|
|
}
|
|
|
|
function createApp({
|
|
config,
|
|
database,
|
|
imageProcessor,
|
|
lifecycle,
|
|
logger = console,
|
|
migrations,
|
|
now = Date.now,
|
|
onPlaylistsChanged = () => {},
|
|
playbackService: suppliedPlaybackService,
|
|
storage,
|
|
}) {
|
|
const app = express();
|
|
const notifyPlaylistsChanged = safeNotifier(
|
|
onPlaylistsChanged,
|
|
logger,
|
|
"Playlist invalidation notification failed",
|
|
);
|
|
const playbackService =
|
|
suppliedPlaybackService ||
|
|
createPlaybackService({
|
|
database,
|
|
now,
|
|
onActivationChanged: notifyPlaylistsChanged,
|
|
});
|
|
if (!suppliedPlaybackService) {
|
|
playbackService.initialize();
|
|
}
|
|
const reconcilePlaybackDomain = (change) =>
|
|
playbackService.domainChanged(change);
|
|
const reconcileBeforeDomainChange = () =>
|
|
playbackService.reconcile("before_domain_change");
|
|
const notifyPlaylistMutation = (change) => {
|
|
reconcilePlaybackDomain(change);
|
|
const publicChange = { ...change };
|
|
delete publicChange.invalidBoundaryChanged;
|
|
notifyPlaylistsChanged(publicChange);
|
|
};
|
|
const settingsService = createSettingsService(database);
|
|
const assetService = createAssetService({
|
|
database,
|
|
imageProcessor,
|
|
storage,
|
|
});
|
|
const playlistService = createPlaylistService({
|
|
database,
|
|
now,
|
|
onBeforeChange: reconcileBeforeDomainChange,
|
|
onChanged: notifyPlaylistMutation,
|
|
});
|
|
app.locals.playbackService = playbackService;
|
|
|
|
app.disable("x-powered-by");
|
|
app.set("trust proxy", config.trustProxy);
|
|
app.use(express.json({ limit: "1mb" }));
|
|
|
|
app.get("/api", (request, response) => {
|
|
response.json({ name: "Announcements API", version: 2 });
|
|
});
|
|
app.use(
|
|
"/health",
|
|
createHealthRouter({ database, lifecycle, migrations, storage }),
|
|
);
|
|
const detailedHealthRouterOptions = {
|
|
config,
|
|
database,
|
|
lifecycle,
|
|
migrations,
|
|
storage,
|
|
};
|
|
app.use(
|
|
"/api/health",
|
|
createDetailedHealthRouter(detailedHealthRouterOptions),
|
|
);
|
|
app.use(
|
|
"/api/status",
|
|
createDetailedHealthRouter(detailedHealthRouterOptions),
|
|
);
|
|
app.use(
|
|
"/api/settings",
|
|
createSettingsRouter(settingsService, {
|
|
onBeforePlaybackDomainChange: reconcileBeforeDomainChange,
|
|
onPlaylistsChanged: notifyPlaylistsChanged,
|
|
onPlaybackDomainChanged: reconcilePlaybackDomain,
|
|
}),
|
|
);
|
|
app.use(
|
|
"/api/assets",
|
|
createAssetRouter({
|
|
assetService,
|
|
onBeforePlaybackDomainChange: reconcileBeforeDomainChange,
|
|
onPlaylistsChanged: notifyPlaylistsChanged,
|
|
onPlaybackDomainChanged: reconcilePlaybackDomain,
|
|
processingConfig: config.processing,
|
|
}),
|
|
);
|
|
app.use(
|
|
"/api/playlists",
|
|
createPlaylistRouter(playlistService, {
|
|
playbackService,
|
|
}),
|
|
);
|
|
app.use("/api/playback", createPlaybackRouter(playbackService));
|
|
app.use("/media", createMediaRouter(assetService));
|
|
|
|
app.use((request, response) => {
|
|
response.status(404).json({
|
|
error: {
|
|
code: "NOT_FOUND",
|
|
message: "Route not found",
|
|
},
|
|
});
|
|
});
|
|
|
|
app.use((error, request, response, next) => {
|
|
if (response.headersSent) {
|
|
next(error);
|
|
return;
|
|
}
|
|
|
|
if (error instanceof SyntaxError && error.status === 400 && "body" in error) {
|
|
response.status(400).json({
|
|
error: {
|
|
code: "INVALID_JSON",
|
|
message: "Request body must contain valid JSON",
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
const statusCode = error.statusCode || 500;
|
|
response.status(statusCode).json({
|
|
error: {
|
|
code: error.code || "INTERNAL_ERROR",
|
|
message:
|
|
statusCode >= 500 ? "An internal server error occurred" : error.message,
|
|
...(error.details ? { details: error.details } : {}),
|
|
},
|
|
});
|
|
});
|
|
|
|
return app;
|
|
}
|
|
|
|
module.exports = {
|
|
createApp,
|
|
};
|