mirror of
https://github.com/FoxxMD/multi-scrobbler.git
synced 2026-05-05 23:50:21 +00:00
refactor: Restructure server directory
* Rename to backend to better reflect what it is * Move server functionality out of entry file and into own server subdirectory * Rename 'apis' folder to 'vendor' to reflect usage and move into common * Rename 'clients' folder to 'scrobblers' reflect usage and prevent ambiguity with client CRA folder
This commit is contained in:
parent
4cccdeeff0
commit
04ffc0a589
105 changed files with 172 additions and 423 deletions
|
|
@ -17,5 +17,6 @@ flatpak/generated-sources.json
|
|||
flatpak/.flatpak-builder
|
||||
docsite/build
|
||||
docsite/node_modules
|
||||
docsite/.docusaurus
|
||||
docsite/.cache-loader
|
||||
build
|
||||
yarn.lock
|
||||
|
|
|
|||
10
.nycrc.json
10
.nycrc.json
|
|
@ -2,16 +2,16 @@
|
|||
"extends": "@istanbuljs/nyc-config-typescript",
|
||||
"exclude": [
|
||||
"node_modules/",
|
||||
"**/src/server/common/schema/**",
|
||||
"**/src/server/tests/**",
|
||||
"**/src/backend/common/schema/**",
|
||||
"**/src/backend/tests/**",
|
||||
"register.js",
|
||||
"**/src/server/**/*.d.ts",
|
||||
"**/src/client/**"
|
||||
],
|
||||
"include": [
|
||||
"**/src/server/**/*.ts",
|
||||
"**/src/**/server/*.js",
|
||||
"**/src/**/server/*.js.map"
|
||||
"**/src/backend/**/*.ts",
|
||||
"**/src/**/backend/*.js",
|
||||
"**/src/**/backend/*.js.map"
|
||||
],
|
||||
"extension": [
|
||||
".ts"
|
||||
|
|
|
|||
14
package-lock.json
generated
14
package-lock.json
generated
|
|
@ -27,7 +27,6 @@
|
|||
"concat-stream": "^2.0.0",
|
||||
"dayjs": "^1.10.4",
|
||||
"dbus-next": "0.10.2",
|
||||
"ejs": "^3.1.6",
|
||||
"es6-error": "^4.1.1",
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.2",
|
||||
|
|
@ -8830,6 +8829,7 @@
|
|||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
|
||||
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
},
|
||||
|
|
@ -10295,6 +10295,7 @@
|
|||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
||||
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimatch": "^5.0.1"
|
||||
}
|
||||
|
|
@ -10303,6 +10304,7 @@
|
|||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
|
|
@ -10311,6 +10313,7 @@
|
|||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
|
|
@ -12722,6 +12725,7 @@
|
|||
"version": "10.8.7",
|
||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
|
||||
"integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"async": "^3.2.3",
|
||||
"chalk": "^4.0.2",
|
||||
|
|
@ -12739,6 +12743,7 @@
|
|||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
|
|
@ -12753,6 +12758,7 @@
|
|||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
|
|
@ -12768,6 +12774,7 @@
|
|||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
|
|
@ -12778,12 +12785,14 @@
|
|||
"node_modules/jake/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jake/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -12792,6 +12801,7 @@
|
|||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
"build:client": "react-scripts build",
|
||||
"build:server": "webpack --config webpack.config.server.js",
|
||||
"start": "node build/server.js",
|
||||
"fileEndings": "jscodeshift --transformFrom js --transformTo none --importTypes relative --extensions=ts --parser tsx --transform codeshift/transform.ts src/server"
|
||||
"fileEndings": "jscodeshift --transformFrom js --transformTo none --importTypes relative --extensions=ts --parser tsx --transform codeshift/transform.ts src/backend"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
|
|
@ -64,7 +64,6 @@
|
|||
"concat-stream": "^2.0.0",
|
||||
"dayjs": "^1.10.4",
|
||||
"dbus-next": "0.10.2",
|
||||
"ejs": "^3.1.6",
|
||||
"es6-error": "^4.1.1",
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.2",
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
|
||||
const tsNode = require('ts-node');
|
||||
const tsConfigPaths = require('tsconfig-paths');
|
||||
const mainTSConfig = require('./src/server/tsconfig.json');
|
||||
const mainTSConfig = require('./src/backend/tsconfig.json');
|
||||
|
||||
tsConfigPaths.register({
|
||||
baseUrl: './src/server/tests',
|
||||
baseUrl: './src/backend/tests',
|
||||
paths: {
|
||||
...mainTSConfig.compilerOptions.paths,
|
||||
}
|
||||
|
|
@ -17,5 +17,5 @@ tsConfigPaths.register({
|
|||
tsNode.register({
|
||||
files: true,
|
||||
transpileOnly: true,
|
||||
project: './src/server/tsconfig.json'
|
||||
project: './src/backend/tsconfig.json'
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { capitalize, mergeArr } from "../utils";
|
||||
import { capitalize, mergeArr } from "../../utils";
|
||||
import {Logger} from '@foxxmd/winston';
|
||||
import { FormatPlayObjectOptions } from "../common/infrastructure/Atomic";
|
||||
import { FormatPlayObjectOptions } from "../infrastructure/Atomic";
|
||||
import winston from '@foxxmd/winston';
|
||||
import { PlayObject } from "../../core/Atomic";
|
||||
import { PlayObject } from "../../../core/Atomic";
|
||||
|
||||
export default abstract class AbstractApiClient {
|
||||
name: string;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import AbstractApiClient from "./AbstractApiClient";
|
||||
import {JRiverData} from "../common/infrastructure/config/source/jriver";
|
||||
import AbstractApiClient from "./AbstractApiClient.js";
|
||||
import {JRiverData} from "../infrastructure/config/source/jriver.js";
|
||||
import request, {Request, Response} from 'superagent';
|
||||
import xml2js from 'xml2js';
|
||||
import {ErrorWithCause} from "pony-cause";
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import AbstractApiClient from "./AbstractApiClient";
|
||||
import {ErrorWithCause} from "pony-cause";
|
||||
import { KodiData } from "../common/infrastructure/config/source/kodi";
|
||||
import { KodiData } from "../infrastructure/config/source/kodi";
|
||||
import { KodiClient } from 'kodi-api'
|
||||
import normalizeUrl from "normalize-url";
|
||||
import {URL} from "url";
|
||||
import { RecentlyPlayedOptions } from "../sources/AbstractSource";
|
||||
import { FormatPlayObjectOptions } from "../common/infrastructure/Atomic";
|
||||
import { RecentlyPlayedOptions } from "../../sources/AbstractSource";
|
||||
import { FormatPlayObjectOptions } from "../infrastructure/Atomic";
|
||||
import dayjs from "dayjs";
|
||||
import { PlayObject } from "../../core/Atomic";
|
||||
import { PlayObject } from "../../../core/Atomic";
|
||||
|
||||
interface KodiDuration {
|
||||
hours: number
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import LastFm, {AuthGetSessionResponse, TrackObject, UserGetInfoResponse} from "lastfm-node-client";
|
||||
import AbstractApiClient from "./AbstractApiClient";
|
||||
import dayjs from "dayjs";
|
||||
import { readJson, sleep, writeFile } from "../utils";
|
||||
import { FormatPlayObjectOptions } from "../common/infrastructure/Atomic";
|
||||
import { LastfmData } from "../common/infrastructure/config/client/lastfm";
|
||||
import { PlayObject } from "../../core/Atomic";
|
||||
import { readJson, sleep, writeFile } from "../../utils";
|
||||
import { FormatPlayObjectOptions } from "../infrastructure/Atomic";
|
||||
import { LastfmData } from "../infrastructure/config/client/lastfm";
|
||||
import { PlayObject } from "../../../core/Atomic";
|
||||
|
||||
const badErrors = [
|
||||
'api key suspended',
|
||||
|
|
@ -1,20 +1,22 @@
|
|||
import AbstractApiClient from "./AbstractApiClient";
|
||||
import request, {Request} from 'superagent';
|
||||
import { ListenBrainzClientData } from "../common/infrastructure/config/client/listenbrainz";
|
||||
import {DELIMITERS, FormatPlayObjectOptions} from "../common/infrastructure/Atomic";
|
||||
import { ListenBrainzClientData } from "../infrastructure/config/client/listenbrainz";
|
||||
import { DELIMITERS, FormatPlayObjectOptions } from "../infrastructure/Atomic";
|
||||
import dayjs from "dayjs";
|
||||
import { stringSameness } from '@foxxmd/string-sameness';
|
||||
import {
|
||||
containsDelimiters,
|
||||
findDelimiters,
|
||||
normalizeStr, parseArtistCredits,
|
||||
normalizeStr,
|
||||
parseArtistCredits,
|
||||
parseCredits,
|
||||
parseStringList, parseTrackCredits,
|
||||
parseStringList,
|
||||
parseTrackCredits,
|
||||
unique,
|
||||
uniqueNormalizedStrArr,
|
||||
} from "../utils";
|
||||
import { PlayObject } from "../../core/Atomic";
|
||||
import {slice} from "../../core/StringUtils";
|
||||
} from "../../utils";
|
||||
import { PlayObject } from "../../../core/Atomic";
|
||||
import { slice } from "../../../core/StringUtils";
|
||||
|
||||
|
||||
export interface ArtistMBIDMapping {
|
||||
|
|
@ -1,6 +1,3 @@
|
|||
import {addAsync, Router} from '@awaitjs/express';
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import {Logger} from '@foxxmd/winston';
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc.js';
|
||||
|
|
@ -8,10 +5,7 @@ import isBetween from 'dayjs/plugin/isBetween.js';
|
|||
import relativeTime from 'dayjs/plugin/relativeTime.js';
|
||||
import duration from 'dayjs/plugin/duration.js';
|
||||
import timezone from 'dayjs/plugin/timezone.js';
|
||||
import passport from 'passport';
|
||||
import session from 'express-session';
|
||||
import {
|
||||
getAddress,
|
||||
parseBool,
|
||||
readJson,
|
||||
sleep
|
||||
|
|
@ -22,11 +16,8 @@ import SpotifySource from "./sources/SpotifySource";
|
|||
import { AIOConfig } from "./common/infrastructure/config/aioConfig";
|
||||
import { getRoot } from "./ioc";
|
||||
import {getLogger} from "./common/logging";
|
||||
import { setupApi } from "./api/api";
|
||||
import {LogInfo} from "../core/Atomic";
|
||||
import {stripIndents} from "common-tags";
|
||||
|
||||
const buildDir = path.join(process.cwd() + "/build");
|
||||
import {initServer} from "./server/index";
|
||||
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
|
@ -35,32 +26,8 @@ dayjs.extend(relativeTime);
|
|||
dayjs.extend(duration);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
const app = addAsync(express());
|
||||
const router = Router();
|
||||
|
||||
const isProd = process.env.NODE_ENV !== undefined && (process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'prod');
|
||||
|
||||
const apiPort = process.env.API_PORT ?? 9079;
|
||||
const mainPort = process.env.PORT ?? 3000;
|
||||
|
||||
let envPort = isProd ? mainPort : apiPort;
|
||||
|
||||
(async function () {
|
||||
|
||||
app.use(router);
|
||||
app.use(bodyParser.json());
|
||||
app.use(
|
||||
bodyParser.urlencoded({
|
||||
extended: true,
|
||||
})
|
||||
);
|
||||
|
||||
app.use(express.static(buildDir));
|
||||
|
||||
app.use(session({secret: 'keyboard cat', resave: false, saveUninitialized: false}));
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
let output: LogInfo[] = []
|
||||
|
||||
const initLogger = getLogger({file: false}, 'init');
|
||||
|
|
@ -86,7 +53,7 @@ const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`)
|
|||
|
||||
const {
|
||||
webhooks = [],
|
||||
port = envPort,
|
||||
port,
|
||||
logging = {},
|
||||
debugMode,
|
||||
} = (config || {}) as AIOConfig;
|
||||
|
|
@ -100,12 +67,11 @@ const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`)
|
|||
process.env.DEBUG_MODE = b.toString();
|
||||
}
|
||||
|
||||
app.listen(port);
|
||||
const root = getRoot(port);
|
||||
|
||||
logger = getLogger(logging, 'app');
|
||||
|
||||
setupApi(app, logger, output);
|
||||
initServer(logger, output);
|
||||
|
||||
if(process.env.IS_LOCAL === 'true') {
|
||||
logger.info('multi-scrobbler can be run as a background service! See: https://github.com/FoxxMD/multi-scrobbler/blob/develop/docs/service.md');
|
||||
|
|
@ -116,8 +82,6 @@ const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`)
|
|||
logger.warn(appConfigFail);
|
||||
}
|
||||
|
||||
const localUrl = root.get('localUrl');
|
||||
|
||||
const notifiers = root.get('notifiers');
|
||||
await notifiers.buildWebhooks(webhooks);
|
||||
|
||||
|
|
@ -168,50 +132,9 @@ const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`)
|
|||
}
|
||||
}
|
||||
if (anyNotReady) {
|
||||
logger.info(`Some sources are not ready, open ${localUrl} to continue`);
|
||||
logger.info(`Some sources are not ready, open the dashboard to continue`);
|
||||
}
|
||||
|
||||
app.get("/*", function (req, res) {
|
||||
if(!isProd) {
|
||||
logger.warn(`In development environment this path (on port ${apiPort}) does nothing. You most likely want port ${mainPort}`)
|
||||
}
|
||||
res.sendFile(path.join(buildDir, "index.html"));
|
||||
});
|
||||
|
||||
app.set('views', path.resolve(projectDir, 'src/views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
|
||||
const addy = getAddress();
|
||||
const addresses: string[] = [];
|
||||
let dockerHint = '';
|
||||
if(parseBool(process.env.IS_DOCKER) && addy.v4 !== undefined && addy.v4.includes('172')) {
|
||||
dockerHint = stripIndents`
|
||||
--- HINT ---
|
||||
MS is likely being run in a container with BRIDGE networking which means the above addresses are not accessible from outside this container.
|
||||
To ensure the container is accessible make sure you have mapped the *container* port ${port} to a *host* port. https://foxxmd.github.io/multi-scrobbler/docs/installation#networking
|
||||
The container will then be accessible at http://HOST_MACHINE_IP:HOST_PORT
|
||||
--- HINT ---
|
||||
`;
|
||||
}
|
||||
for(const [k, v] of Object.entries(addy)) {
|
||||
if(v !== undefined) {
|
||||
switch(k) {
|
||||
case 'host':
|
||||
case 'v4':
|
||||
addresses.push(`---> ${k === 'host' ? 'Local'.padEnd(14, ' ') : 'Network'.padEnd(14, ' ')} http://${v}:${port}`);
|
||||
break;
|
||||
case 'v6':
|
||||
addresses.push(`---> Network (IPv6) http://[${v}]:${port}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
const start = stripIndents`\n
|
||||
${isProd ? 'Server' : 'API Backend'} started:
|
||||
${addresses.join('\n')}${dockerHint !== '' ? `\n${dockerHint}` : ''}`
|
||||
|
||||
logger.info(start);
|
||||
|
||||
} catch (e) {
|
||||
logger.error('Exited with uncaught error');
|
||||
logger.error(e);
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import {createContainer} from "iti";
|
||||
import path from "path";
|
||||
import {projectDir} from "./common/index";
|
||||
import ScrobbleClients from "./clients/ScrobbleClients";
|
||||
import ScrobbleClients from "./scrobblers/ScrobbleClients";
|
||||
import ScrobbleSources from "./sources/ScrobbleSources";
|
||||
import {Notifiers} from "./notifier/Notifiers";
|
||||
import {EventEmitter} from "events";
|
||||
|
|
@ -20,12 +20,15 @@ if(typeof process.env.CONFIG_DIR === 'string') {
|
|||
|
||||
let root: ReturnType<typeof createRoot>;
|
||||
|
||||
const createRoot = (port: number | string) => {
|
||||
const createRoot = (port: number | string | undefined) => {
|
||||
return createContainer().add({
|
||||
configDir: configDir,
|
||||
logDir: logPath,
|
||||
localUrl: `http://localhost:${port}`,
|
||||
isProd: process.env.NODE_ENV !== undefined && (process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'prod'),
|
||||
configPort: port,
|
||||
apiPort: process.env.API_PORT ?? 9079,
|
||||
mainPort: process.env.PORT ?? 3000,
|
||||
clientEmitter: () => new WildcardEmitter(),
|
||||
sourceEmitter: () => new WildcardEmitter(),
|
||||
notifierEmitter: () => new EventEmitter(),
|
||||
|
|
@ -33,6 +36,12 @@ const createRoot = (port: number | string) => {
|
|||
clients: () => new ScrobbleClients(items.clientEmitter, items.sourceEmitter, items.configDir),
|
||||
sources: () => new ScrobbleSources(items.sourceEmitter, items.localUrl, items.configDir),
|
||||
notifiers: () => new Notifiers(items.notifierEmitter, items.clientEmitter, items.sourceEmitter),
|
||||
port: () => {
|
||||
if(items.configPort !== undefined) {
|
||||
return items.configPort;
|
||||
}
|
||||
return items.isProd ? items.mainPort : items.apiPort;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ import {
|
|||
sleep,
|
||||
sortByOldestPlayDate,
|
||||
} from "../utils";
|
||||
import LastfmApiClient from "../apis/LastfmApiClient";
|
||||
import LastfmApiClient from "../common/vendor/LastfmApiClient";
|
||||
import { FormatPlayObjectOptions, INITIALIZING, ScrobbledPlayObject } from "../common/infrastructure/Atomic";
|
||||
import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm";
|
||||
import {TrackScrobbleResponse, UserGetRecentTracksResponse} from "lastfm-node-client";
|
||||
|
|
@ -9,12 +9,12 @@ import {
|
|||
sleep,
|
||||
sortByOldestPlayDate,
|
||||
} from "../utils";
|
||||
import LastfmApiClient from "../apis/LastfmApiClient";
|
||||
import LastfmApiClient from "../common/vendor/LastfmApiClient";
|
||||
import { FormatPlayObjectOptions, INITIALIZING } from "../common/infrastructure/Atomic";
|
||||
import { Notifiers } from "../notifier/Notifiers";
|
||||
import {Logger} from '@foxxmd/winston';
|
||||
import { ListenBrainzClientConfig } from "../common/infrastructure/config/client/listenbrainz";
|
||||
import { ListenbrainzApiClient } from "../apis/ListenbrainzApiClient";
|
||||
import { ListenbrainzApiClient } from "../common/vendor/ListenbrainzApiClient";
|
||||
import { PlayObject, TrackStringOptions } from "../../core/Atomic";
|
||||
import { buildTrackString } from "../../core/StringUtils";
|
||||
import EventEmitter from "events";
|
||||
|
|
@ -20,7 +20,7 @@ import {
|
|||
MalojaScrobbleRequestData,
|
||||
MalojaScrobbleV2RequestData,
|
||||
MalojaScrobbleV3RequestData, MalojaScrobbleV3ResponseData, MalojaV2ScrobbleData, MalojaV3ScrobbleData
|
||||
} from "../apis/maloja/interfaces";
|
||||
} from "../common/vendor/maloja/interfaces";
|
||||
import { PlayObject, TrackStringOptions } from "../../core/Atomic";
|
||||
import { buildTrackString } from "../../core/StringUtils";
|
||||
import EventEmitter from "events";
|
||||
|
|
@ -12,7 +12,7 @@ import {
|
|||
SourceStatusData,
|
||||
} from "../../core/Atomic";
|
||||
import {Logger} from "@foxxmd/winston";
|
||||
import {formatLogToHtml, isLogLineMinLevel} from "../common/logging";
|
||||
import {formatLogToHtml, getLogger, isLogLineMinLevel} from "../common/logging";
|
||||
import {MESSAGE} from "triple-beam";
|
||||
import {Transform} from "stream";
|
||||
import {createSession} from "better-sse";
|
||||
|
|
@ -21,12 +21,8 @@ import {setupPlexRoutes} from "./plexRoutes";
|
|||
import {setupJellyfinRoutes} from "./jellyfinRoutes";
|
||||
import {setupDeezerRoutes} from "./deezerRoutes";
|
||||
import {setupAuthRoutes} from "./auth";
|
||||
import path from "path";
|
||||
import {source} from "common-tags";
|
||||
import { ExpressHandler } from "../common/infrastructure/Atomic";
|
||||
|
||||
const buildDir = path.join(process.cwd() + "/build");
|
||||
|
||||
let output: LogInfo[] = []
|
||||
|
||||
const availableLevels = ['error', 'warn', 'info', 'verbose', 'debug'];
|
||||
|
|
@ -56,7 +52,8 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput
|
|||
console.log(e);
|
||||
}
|
||||
|
||||
logger.stream().on('log', (log: LogInfo) => {
|
||||
const appLogger = getLogger({}, 'app');
|
||||
appLogger.stream().on('log', (log: LogInfo) => {
|
||||
output.unshift(log);
|
||||
output = output.slice(0, 501);
|
||||
if(isLogLineMinLevel(log, logConfig.level)) {
|
||||
|
|
@ -3,8 +3,8 @@ import {Logger} from "@foxxmd/winston";
|
|||
import ScrobbleSources from "../sources/ScrobbleSources";
|
||||
import passport from "passport";
|
||||
import { ExpressHandler } from "../common/infrastructure/Atomic";
|
||||
import ScrobbleClients from "../clients/ScrobbleClients";
|
||||
import LastfmScrobbler from "../clients/LastfmScrobbler";
|
||||
import ScrobbleClients from "../scrobblers/ScrobbleClients";
|
||||
import LastfmScrobbler from "../scrobblers/LastfmScrobbler";
|
||||
import LastfmSource from "../sources/LastfmSource";
|
||||
import SpotifySource from "../sources/SpotifySource";
|
||||
|
||||
89
src/backend/server/index.ts
Normal file
89
src/backend/server/index.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import {addAsync, Router} from '@awaitjs/express';
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import passport from 'passport';
|
||||
import session from 'express-session';
|
||||
import path from "path";
|
||||
import { getRoot } from "../ioc";
|
||||
import {Logger} from "@foxxmd/winston";
|
||||
import { LogInfo } from "../../core/Atomic";
|
||||
import { setupApi } from "./api";
|
||||
import { getAddress, mergeArr, parseBool } from "../utils";
|
||||
import {stripIndents} from "common-tags";
|
||||
import {ErrorWithCause} from "pony-cause";
|
||||
|
||||
const buildDir = path.join(process.cwd() + "/build");
|
||||
|
||||
const app = addAsync(express());
|
||||
const router = Router();
|
||||
|
||||
export const initServer = async (parentLogger: Logger, initialOutput: LogInfo[] = []) => {
|
||||
|
||||
const logger = parentLogger.child({labels: ['API']}, mergeArr);
|
||||
|
||||
try {
|
||||
app.use(router);
|
||||
app.use(bodyParser.json());
|
||||
app.use(
|
||||
bodyParser.urlencoded({
|
||||
extended: true,
|
||||
})
|
||||
);
|
||||
|
||||
app.use(express.static(buildDir));
|
||||
|
||||
app.use(session({secret: 'keyboard cat', resave: false, saveUninitialized: false}));
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
const root = getRoot();
|
||||
|
||||
const isProd = root.get('isProd');
|
||||
const apiPort = root.get('apiPort');
|
||||
const mainPort = root.get('mainPort');
|
||||
const port = root.get('port');
|
||||
|
||||
setupApi(app, logger, initialOutput);
|
||||
|
||||
app.get("/*", function (req, res) {
|
||||
if (!isProd) {
|
||||
logger.warn(`In development environment this path (on port ${apiPort}) does nothing. You most likely want port ${mainPort}`)
|
||||
}
|
||||
res.sendFile(path.join(buildDir, "index.html"));
|
||||
});
|
||||
|
||||
app.listen(port);
|
||||
|
||||
const addy = getAddress();
|
||||
const addresses: string[] = [];
|
||||
let dockerHint = '';
|
||||
if (parseBool(process.env.IS_DOCKER) && addy.v4 !== undefined && addy.v4.includes('172')) {
|
||||
dockerHint = stripIndents`
|
||||
--- HINT ---
|
||||
MS is likely being run in a container with BRIDGE networking which means the above addresses are not accessible from outside this container.
|
||||
To ensure the container is accessible make sure you have mapped the *container* port ${port} to a *host* port. https://foxxmd.github.io/multi-scrobbler/docs/installation#networking
|
||||
The container will then be accessible at http://HOST_MACHINE_IP:HOST_PORT
|
||||
--- HINT ---
|
||||
`;
|
||||
}
|
||||
for (const [k, v] of Object.entries(addy)) {
|
||||
if (v !== undefined) {
|
||||
switch (k) {
|
||||
case 'host':
|
||||
case 'v4':
|
||||
addresses.push(`---> ${k === 'host' ? 'Local'.padEnd(14, ' ') : 'Network'.padEnd(14, ' ')} http://${v}:${port}`);
|
||||
break;
|
||||
case 'v6':
|
||||
addresses.push(`---> Network (IPv6) http://[${v}]:${port}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
const start = stripIndents`\n
|
||||
${isProd ? 'Server' : 'API Backend'} started:
|
||||
${addresses.join('\n')}${dockerHint !== '' ? `\n${dockerHint}` : ''}`
|
||||
|
||||
logger.info(start);
|
||||
} catch (e) {
|
||||
logger.error(new ErrorWithCause('Server crashed with uncaught exception', {cause: e}));
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import normalizeUrl from 'normalize-url';
|
|||
import {EventEmitter} from "events";
|
||||
import { RecentlyPlayedOptions } from "./AbstractSource";
|
||||
import { JRiverSourceConfig } from "../common/infrastructure/config/source/jriver";
|
||||
import { Info, JRiverApiClient, PLAYER_STATE } from "../apis/JRiverApiClient";
|
||||
import { Info, JRiverApiClient, PLAYER_STATE } from "../common/vendor/JRiverApiClient";
|
||||
import { PlayObject } from "../../core/Atomic";
|
||||
|
||||
export class JRiverSource extends MemorySource {
|
||||
|
|
@ -3,7 +3,7 @@ import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructur
|
|||
import {EventEmitter} from "events";
|
||||
import { RecentlyPlayedOptions } from "./AbstractSource";
|
||||
import { KodiSourceConfig } from "../common/infrastructure/config/source/kodi";
|
||||
import { KodiApiClient } from "../apis/KodiApiClient";
|
||||
import { KodiApiClient } from "../common/vendor/KodiApiClient";
|
||||
import { PlayObject } from "../../core/Atomic";
|
||||
|
||||
export class KodiSource extends MemorySource {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource";
|
||||
import LastfmApiClient from "../apis/LastfmApiClient";
|
||||
import LastfmApiClient from "../common/vendor/LastfmApiClient";
|
||||
import { sortByOldestPlayDate } from "../utils";
|
||||
import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm";
|
||||
import { FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic";
|
||||
|
|
@ -2,7 +2,7 @@ import AbstractSource, { RecentlyPlayedOptions } from "./AbstractSource";
|
|||
import { FormatPlayObjectOptions, INITIALIZING, InternalConfig } from "../common/infrastructure/Atomic";
|
||||
import EventEmitter from "events";
|
||||
import { ListenBrainzSourceConfig } from "../common/infrastructure/config/source/listenbrainz";
|
||||
import { ListenbrainzApiClient } from "../apis/ListenbrainzApiClient";
|
||||
import { ListenbrainzApiClient } from "../common/vendor/ListenbrainzApiClient";
|
||||
|
||||
export default class ListenbrainzSource extends AbstractSource {
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ import slightlyDifferentNames from './listenbrainz/correctlyMapped/trackNameSlig
|
|||
import incorrectMultiArtistsTrackName from './listenbrainz/incorrectlyMapped/multiArtistsInTrackName.json';
|
||||
import veryWrong from './listenbrainz/incorrectlyMapped/veryWrong.json';
|
||||
|
||||
import {ListenbrainzApiClient, ListenResponse} from "../apis/ListenbrainzApiClient";
|
||||
import {ListenbrainzApiClient, ListenResponse} from "../common/vendor/ListenbrainzApiClient";
|
||||
|
||||
interface ExpectedResults {
|
||||
artists: string[]
|
||||
|
|
@ -17,15 +17,16 @@
|
|||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js"
|
||||
"**/*.js",
|
||||
"../core/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"../../web",
|
||||
"../../node_modules",
|
||||
"../../coverage",
|
||||
"../../tests/*.ts",
|
||||
"../../_site",
|
||||
"../../docsite",
|
||||
"../../build", "../client"
|
||||
"../../build",
|
||||
"../client"
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue