diff --git a/package-lock.json b/package-lock.json index f46fdf39..540a9537 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "@kenyip/backoff-strategies": "^1.0.4", "@keyv/valkey": "^1.0.8", "@lukehagar/plexjs": "^0.39.0", - "@minisylar/express-typed-router": "^1.9.5", + "@minisylar/express-typed-router": "^1.9.6", "@pierre/diffs": "^1.2.4", "@rocksky/sdk": "^0.3.0", "@supercharge/promise-pool": "^3.0.0", @@ -3007,9 +3007,9 @@ } }, "node_modules/@minisylar/express-typed-router": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/@minisylar/express-typed-router/-/express-typed-router-1.9.5.tgz", - "integrity": "sha512-HmkgEXctMEff4RfimAkbthTcmfK0xkyz43WrxlsiU2B9UsPGhtj7lv5ja6b5glIWL3NOiuo9QyZRkm7R2R4y8A==", + "version": "1.9.6", + "resolved": "https://registry.npmjs.org/@minisylar/express-typed-router/-/express-typed-router-1.9.6.tgz", + "integrity": "sha512-U3mD/CC3bBQLkHNdRSdFkr5uk4NC3eIFbuJBOybIIKq66KMWRX2T5PqHXb77gxcaOArbk23avOMmQhtaGminzA==", "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", diff --git a/package.json b/package.json index 83608814..bf12ef18 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@kenyip/backoff-strategies": "^1.0.4", "@keyv/valkey": "^1.0.8", "@lukehagar/plexjs": "^0.39.0", - "@minisylar/express-typed-router": "^1.9.5", + "@minisylar/express-typed-router": "^1.9.6", "@pierre/diffs": "^1.2.4", "@rocksky/sdk": "^0.3.0", "@supercharge/promise-pool": "^3.0.0", diff --git a/src/backend/server/endpointLastfmRoutes.ts b/src/backend/server/endpointLastfmRoutes.ts index 0279b083..3268c3a2 100644 --- a/src/backend/server/endpointLastfmRoutes.ts +++ b/src/backend/server/endpointLastfmRoutes.ts @@ -1,22 +1,23 @@ /* eslint-disable prefer-arrow-functions/prefer-arrow-functions */ -import type {Express, Request, Response} from 'express'; +import type {Express, Request} from 'express'; import { childLogger, type Logger } from "@foxxmd/logging"; +import type {Writable} from 'ts-essentials' import bodyParser from "body-parser"; import type ScrobbleSources from "../sources/ScrobbleSources.ts"; import { nonEmptyBody } from "./middleware.ts"; import { LFMEndpointNotifier } from "../sources/ingressNotifiers/LFMEndpointNotifier.ts"; import type { EndpointLastfmSource} from "../sources/EndpointLastfmSource.ts"; import { playStateFromRequest, requestMatchers } from "../sources/EndpointLastfmSource.ts"; -import {lastfmAuthRequestPayloadSchema, lastfmScrobbleRequestSchema, playToNowPlayingApiResponseJson, playToNowPlayingApiResponseXml, playToScrobbleApiResponseJson, playToScrobbleApiResponseXml, type LastFMPayloadkey, type LastFMScrobbleRequestPayload} from "../common/vendor/LastfmApiClient.ts"; +import {lastfmAuthRequestPayloadSchema, lastfmScrobbleRequestSchema, playToNowPlayingApiResponseJson, playToNowPlayingApiResponseXml, playToScrobbleApiResponseJson, playToScrobbleApiResponseXml} from "../common/vendor/LastfmApiClient.ts"; import xml2js from 'xml2js'; import crypto from 'node:crypto'; -import type { createTypedRouter, SchemaRequest, TypedMiddleware } from "@minisylar/express-typed-router"; +import type { createTypedRouter, TypedMiddleware, InferSchemaHandler } from "@minisylar/express-typed-router"; import { parseDisplayIdentifiersFromRequest } from '../utils/RequestUtils.ts'; import * as z from 'zod'; const unmatchIdentifierWarn: string[] = []; -const looseFmBody = z.xor([lastfmScrobbleRequestSchema, lastfmAuthRequestPayloadSchema]); +const looseFmBody = z.union([lastfmScrobbleRequestSchema, lastfmAuthRequestPayloadSchema]); type LooseFmBody = typeof looseFmBody; const looseQuery = z.looseObject({format: z.string().optional()}); type LooseQuery = typeof looseQuery; @@ -38,7 +39,15 @@ export const setupLastfmEndpointRoutes = (app: Express, router: ReturnType, res: Response) => { + const middleware = [rawIngress,bodyParser.urlencoded({ extended: true }),nonEmptyCheck] as const; + + type SubmitHandler = InferSchemaHandler<{ + bodySchema: LooseFmBody, + querySchema: LooseQuery + middleware: typeof middleware, + }>; + + const submitRoute: SubmitHandler = async (req, res) => { webhookIngress.trackIngress(req as Request, false); const sources = scrobbleSources.getByType('endpointlfm') as EndpointLastfmSource[]; @@ -146,18 +155,18 @@ export const setupLastfmEndpointRoutes = (app: Express, router: ReturnType, + bodySchema: looseFmBody, + querySchema: looseQuery, tags: ['Lastfm Ingress'], summary: 'Accept a Last.fm Scrobble (Slug)', description: 'Accepts the standard Last.fm `track.scrobble` payload at this endpoint.' }, submitRoute); router.post('/2.0/', { - middleware: [rawIngress,bodyParser.urlencoded({ extended: true }),nonEmptyCheck], - bodySchema: z.union([lastfmScrobbleRequestSchema, lastfmAuthRequestPayloadSchema]), - querySchema: z.looseObject({format: z.string().optional()}), + middleware: middleware as Writable, + bodySchema: looseFmBody, + querySchema: looseQuery, tags: ['Lastfm Ingress'], summary: 'Accept a Last.fm Scrobble (Standard)', description: 'Accepts the standard Last.fm `track.scrobble` payload at this endpoint.' diff --git a/src/backend/server/endpointListenbrainzRoutes.ts b/src/backend/server/endpointListenbrainzRoutes.ts index e93665e6..7750cbe6 100644 --- a/src/backend/server/endpointListenbrainzRoutes.ts +++ b/src/backend/server/endpointListenbrainzRoutes.ts @@ -1,6 +1,7 @@ /* eslint-disable prefer-arrow-functions/prefer-arrow-functions */ -import type {Express, Request, Response} from 'express'; +import type {Express, Request} from 'express'; import { childLogger, type Logger } from "@foxxmd/logging"; +import type {Writable} from 'ts-essentials'; import bodyParser from "body-parser"; import type { EndpointListenbrainzSource} from "../sources/EndpointListenbrainzSource.ts"; import { playStateFromRequest, requestMatchers } from "../sources/EndpointListenbrainzSource.ts"; @@ -12,7 +13,7 @@ import type ScrobbleClients from '../scrobblers/ScrobbleClients.ts'; import { playToListenPayload } from '../common/vendor/listenbrainz/lzUtils.ts'; import { stringToDeterministicNumber } from '../utils/StringUtils.ts'; import { messageWithCauses } from '../../core/ErrorUtils.ts'; -import type { createTypedRouter, SchemaRequest, TypedMiddleware } from "@minisylar/express-typed-router"; +import type { createTypedRouter, TypedMiddleware, InferSchemaHandler } from "@minisylar/express-typed-router"; import * as z from 'zod'; import { stripIndents } from 'common-tags'; import { parseDisplayIdentifiersFromRequest } from '../utils/RequestUtils.ts'; @@ -45,7 +46,15 @@ export const setupLZEndpointRoutes = (app: Express, router: ReturnType, undefined, Record, undefined>, res: Response) => { + const lzBodySchema = z.looseObject({}); + const middleware = [rawIngress,lzJsonParser,nonEmptyCheck] as const; + + type SubmitHandler = InferSchemaHandler<{ + bodySchema: typeof lzBodySchema, + middleware: typeof middleware, + }>; + + const submitRoute: SubmitHandler = async (req, res) => { webhookIngress.trackIngress(req as Request, false); logger.trace({body: req.body}, "Recieved request Body"); @@ -83,14 +92,14 @@ export const setupLZEndpointRoutes = (app: Express, router: ReturnType, tags: ['Listenbrainz Ingress'], summary: 'Accept a Listenbrainz Scrobble (Slug)', description: 'Accepts the standard Listenbrainz `submit-listens` payload at this endpoint.' }, submitRoute); router.post('/1/submit-listens', { - middleware: [rawIngress,lzJsonParser,nonEmptyCheck], + middleware: middleware as Writable, bodySchema: z.looseObject({}), tags: ['Listenbrainz Ingress'], summary: 'Accept a Listenbrainz Scrobble (Standard)', diff --git a/src/backend/tests/ingress/ingress.test.ts b/src/backend/tests/ingress/ingress.test.ts index 3c2d79b1..61176b14 100644 --- a/src/backend/tests/ingress/ingress.test.ts +++ b/src/backend/tests/ingress/ingress.test.ts @@ -11,7 +11,6 @@ import ScrobbleClients from '../../scrobblers/ScrobbleClients.ts'; import { zocker } from "zocker"; import { webScrobblePayloadSchema } from '../../common/vendor/webscrobbler/interfaces.ts'; import type { WebScrobblerSourceConfig } from '../../common/infrastructure/config/source/webscrobbler.ts'; -import { sleep } from '../../utils.ts'; import { playToSubmitPayload } from '../../common/vendor/listenbrainz/lzUtils.ts'; import { generatePlay } from '../../../core/tests/utils/PlayTestUtils.ts'; import dayjs from 'dayjs'; @@ -96,7 +95,7 @@ describe('Listenbrainz Endpoint', function() { source.queueIdleMs = 2; await source.initialize(); - const [_, __] = await Promise.all([ + await Promise.all([ request(app).post('/1/submit-listens') .set('Content-Type', 'application/json') .send(JSON.stringify({...playToSubmitPayload(generatePlay()), listen_type: 'playing_now'})),