mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
refactor
This commit is contained in:
@@ -5,11 +5,12 @@ import {
|
|||||||
ResponseType,
|
ResponseType,
|
||||||
} from "axios";
|
} from "axios";
|
||||||
|
|
||||||
|
// todo: do i need this anymore?
|
||||||
export interface Http {
|
export interface Http {
|
||||||
(config: AxiosRequestConfig): AxiosPromise<any>;
|
(config: AxiosRequestConfig): AxiosPromise<any>;
|
||||||
}
|
}
|
||||||
export interface Http2 extends Http {
|
export interface Http2 extends Http {
|
||||||
with: (defaults: Partial<RequestParams>) => Http2;
|
with: (params: Partial<RequestParams>) => Http2;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RequestParams = {
|
export type RequestParams = {
|
||||||
@@ -21,9 +22,9 @@ export type RequestParams = {
|
|||||||
method: Method;
|
method: Method;
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrap = (http2: Http2, defaults: Partial<RequestParams>): Http2 => {
|
const wrap = (http2: Http2, params: Partial<RequestParams>): Http2 => {
|
||||||
const f = ((config: AxiosRequestConfig) => http2(merge(defaults, config))) as Http2;
|
const f = ((config: AxiosRequestConfig) => http2(merge(params, config))) as Http2;
|
||||||
f.with = (defaults: Partial<RequestParams>) => wrap(f, defaults);
|
f.with = (params: Partial<RequestParams>) => wrap(f, params);
|
||||||
return f;
|
return f;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -768,67 +768,3 @@ export class SubsonicGenericMusicLibrary implements SubsonicMusicLibrary {
|
|||||||
total: albums.length == 500 ? total : (q._index || 0) + albums.length,
|
total: albums.length == 500 ? total : (q._index || 0) + albums.length,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const navidromeMusicLibrary = (
|
|
||||||
url: string,
|
|
||||||
subsonicLibrary: SubsonicMusicLibrary,
|
|
||||||
subsonicCredentials: SubsonicCredentials
|
|
||||||
): SubsonicMusicLibrary => ({
|
|
||||||
...subsonicLibrary,
|
|
||||||
flavour: () => "navidrome",
|
|
||||||
bearerToken: (
|
|
||||||
credentials: Credentials
|
|
||||||
): TE.TaskEither<Error, string | undefined> =>
|
|
||||||
pipe(
|
|
||||||
TE.tryCatch(
|
|
||||||
() =>
|
|
||||||
// todo: not hardcode axios in here
|
|
||||||
axios({
|
|
||||||
method: "post",
|
|
||||||
baseURL: url,
|
|
||||||
url: `/auth/login`,
|
|
||||||
data: _.pick(credentials, "username", "password"),
|
|
||||||
}),
|
|
||||||
() => new AuthFailure("Failed to get bearerToken")
|
|
||||||
),
|
|
||||||
TE.map((it) => it.data.token as string | undefined)
|
|
||||||
),
|
|
||||||
artists: async (
|
|
||||||
q: ArtistQuery
|
|
||||||
): Promise<Result<ArtistSummary & Sortable>> => {
|
|
||||||
let params: any = {
|
|
||||||
_sort: "name",
|
|
||||||
_order: "ASC",
|
|
||||||
_start: q._index || "0",
|
|
||||||
};
|
|
||||||
if (q._count) {
|
|
||||||
params = {
|
|
||||||
...params,
|
|
||||||
_end: (q._index || 0) + q._count,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const x: Promise<Result<ArtistSummary & Sortable>> = axios
|
|
||||||
.get(`${url}/api/artist`, {
|
|
||||||
params: asURLSearchParams(params),
|
|
||||||
headers: {
|
|
||||||
"User-Agent": USER_AGENT,
|
|
||||||
"x-nd-authorization": `Bearer ${subsonicCredentials.bearer}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
throw `Navidrome failed with: ${e}`;
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status != 200 && response.status != 206) {
|
|
||||||
throw `Navidrome failed with a ${response.status || "no!"} status`;
|
|
||||||
} else return response;
|
|
||||||
})
|
|
||||||
.then((it) => ({
|
|
||||||
results: (it.data as NDArtist[]).map(artistSummaryFromNDArtist),
|
|
||||||
total: Number.parseInt(it.headers["x-total-count"] || "0"),
|
|
||||||
}));
|
|
||||||
|
|
||||||
return x;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from "../music_service";
|
} from "../music_service";
|
||||||
import { b64Encode, b64Decode } from "../b64";
|
import { b64Encode, b64Decode } from "../b64";
|
||||||
import { axiosImageFetcher, ImageFetcher } from "../images";
|
import { axiosImageFetcher, ImageFetcher } from "../images";
|
||||||
import { navidromeMusicLibrary, SubsonicGenericMusicLibrary } from "./library";
|
import { navidromeMusicLibrary, SubsonicGenericMusicLibrary } from "./generic";
|
||||||
import { client } from "./subsonic_http";
|
import { client } from "./subsonic_http";
|
||||||
|
|
||||||
export const t = (password: string, s: string) =>
|
export const t = (password: string, s: string) =>
|
||||||
@@ -101,7 +101,7 @@ export class Subsonic implements MusicService {
|
|||||||
// todo: why is this in here?
|
// todo: why is this in here?
|
||||||
externalImageFetcher: ImageFetcher;
|
externalImageFetcher: ImageFetcher;
|
||||||
|
|
||||||
subsonic: Http2;
|
subsonicHttp: Http2;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
url: string,
|
url: string,
|
||||||
@@ -111,7 +111,7 @@ export class Subsonic implements MusicService {
|
|||||||
this.url = url;
|
this.url = url;
|
||||||
this.streamClientApplication = streamClientApplication;
|
this.streamClientApplication = streamClientApplication;
|
||||||
this.externalImageFetcher = externalImageFetcher;
|
this.externalImageFetcher = externalImageFetcher;
|
||||||
this.subsonic = http2From(axios).with({
|
this.subsonicHttp = http2From(axios).with({
|
||||||
baseURL: this.url,
|
baseURL: this.url,
|
||||||
params: { v: "1.16.1", c: DEFAULT_CLIENT_APPLICATION },
|
params: { v: "1.16.1", c: DEFAULT_CLIENT_APPLICATION },
|
||||||
headers: { "User-Agent": "bonob" },
|
headers: { "User-Agent": "bonob" },
|
||||||
@@ -126,7 +126,7 @@ export class Subsonic implements MusicService {
|
|||||||
generateToken = (credentials: Credentials) =>
|
generateToken = (credentials: Credentials) =>
|
||||||
pipe(
|
pipe(
|
||||||
TE.tryCatch(
|
TE.tryCatch(
|
||||||
() => client(this.subsonic.with({ params: this.asAuthParams(credentials) } ))({ method: 'get', url: "/rest/ping.view" }).asJSON<PingResponse>(),
|
() => client(this.subsonicHttp.with({ params: this.asAuthParams(credentials) } ))({ method: 'get', url: "/rest/ping.view" }).asJSON<PingResponse>(),
|
||||||
(e) => new AuthFailure(e as string)
|
(e) => new AuthFailure(e as string)
|
||||||
),
|
),
|
||||||
TE.chain(({ type }) =>
|
TE.chain(({ type }) =>
|
||||||
@@ -161,7 +161,7 @@ export class Subsonic implements MusicService {
|
|||||||
): Promise<SubsonicMusicLibrary> => {
|
): Promise<SubsonicMusicLibrary> => {
|
||||||
const subsonicGenericLibrary = new SubsonicGenericMusicLibrary(
|
const subsonicGenericLibrary = new SubsonicGenericMusicLibrary(
|
||||||
this.streamClientApplication,
|
this.streamClientApplication,
|
||||||
this.subsonic.with({ params: this.asAuthParams(credentials) } )
|
this.subsonicHttp.with({ params: this.asAuthParams(credentials) } )
|
||||||
);
|
);
|
||||||
if (credentials.type == "navidrome") {
|
if (credentials.type == "navidrome") {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import { ArtistSummary, Sortable } from "../music_service";
|
import { option as O, taskEither as TE } from "fp-ts";
|
||||||
import { artistImageURN } from "./library";
|
import * as A from "fp-ts/Array";
|
||||||
|
import { pipe } from "fp-ts/lib/function";
|
||||||
|
import { ordString } from "fp-ts/lib/Ord";
|
||||||
|
import { inject } from "underscore";
|
||||||
|
import _ from "underscore";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
import { SubsonicCredentials, SubsonicMusicLibrary } from ".";
|
||||||
|
import { ArtistQuery, ArtistSummary, AuthFailure, Credentials, Result, Sortable } from "../music_service";
|
||||||
|
import { artistImageURN } from "./generic";
|
||||||
|
|
||||||
export type NDArtist = {
|
export type NDArtist = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -20,3 +29,67 @@ export const artistSummaryFromNDArtist = (
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const navidromeMusicLibrary = (
|
||||||
|
url: string,
|
||||||
|
subsonicLibrary: SubsonicMusicLibrary,
|
||||||
|
subsonicCredentials: SubsonicCredentials
|
||||||
|
): SubsonicMusicLibrary => ({
|
||||||
|
...subsonicLibrary,
|
||||||
|
flavour: () => "navidrome",
|
||||||
|
bearerToken: (
|
||||||
|
credentials: Credentials
|
||||||
|
): TE.TaskEither<Error, string | undefined> =>
|
||||||
|
pipe(
|
||||||
|
TE.tryCatch(
|
||||||
|
() =>
|
||||||
|
// todo: not hardcode axios in here
|
||||||
|
axios({
|
||||||
|
method: "post",
|
||||||
|
baseURL: url,
|
||||||
|
url: `/auth/login`,
|
||||||
|
data: _.pick(credentials, "username", "password"),
|
||||||
|
}),
|
||||||
|
() => new AuthFailure("Failed to get bearerToken")
|
||||||
|
),
|
||||||
|
TE.map((it) => it.data.token as string | undefined)
|
||||||
|
),
|
||||||
|
artists: async (
|
||||||
|
q: ArtistQuery
|
||||||
|
): Promise<Result<ArtistSummary & Sortable>> => {
|
||||||
|
let params: any = {
|
||||||
|
_sort: "name",
|
||||||
|
_order: "ASC",
|
||||||
|
_start: q._index || "0",
|
||||||
|
};
|
||||||
|
if (q._count) {
|
||||||
|
params = {
|
||||||
|
...params,
|
||||||
|
_end: (q._index || 0) + q._count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const x: Promise<Result<ArtistSummary & Sortable>> = axios
|
||||||
|
.get(`${url}/api/artist`, {
|
||||||
|
params: asURLSearchParams(params),
|
||||||
|
headers: {
|
||||||
|
"User-Agent": USER_AGENT,
|
||||||
|
"x-nd-authorization": `Bearer ${subsonicCredentials.bearer}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
throw `Navidrome failed with: ${e}`;
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status != 200 && response.status != 206) {
|
||||||
|
throw `Navidrome failed with a ${response.status || "no!"} status`;
|
||||||
|
} else return response;
|
||||||
|
})
|
||||||
|
.then((it) => ({
|
||||||
|
results: (it.data as NDArtist[]).map(artistSummaryFromNDArtist),
|
||||||
|
total: Number.parseInt(it.headers["x-total-count"] || "0"),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return x;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
} from "../src/music_service";
|
} from "../src/music_service";
|
||||||
|
|
||||||
import { b64Encode } from "../src/b64";
|
import { b64Encode } from "../src/b64";
|
||||||
import { artistImageURN } from "../src/subsonic/library";
|
import { artistImageURN } from "../src/subsonic/generic";
|
||||||
|
|
||||||
const randomInt = (max: number) => Math.floor(Math.random() * Math.floor(max));
|
const randomInt = (max: number) => Math.floor(Math.random() * Math.floor(max));
|
||||||
const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`;
|
const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`;
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ describe("RangeBytesFromFilter", () => {
|
|||||||
|
|
||||||
|
|
||||||
describe("server", () => {
|
describe("server", () => {
|
||||||
jest.setTimeout(Number.parseInt(process.env["JEST_TIMEOUT"] || "2000"));
|
jest.setTimeout(Number.parseInt(process.env["JEST_TIMEOUT"] || "5000"));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ describe("rating to and from ints", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("service config", () => {
|
describe("service config", () => {
|
||||||
jest.setTimeout(Number.parseInt(process.env["JEST_TIMEOUT"] || "2000"));
|
jest.setTimeout(Number.parseInt(process.env["JEST_TIMEOUT"] || "5000"));
|
||||||
|
|
||||||
const bonobWithNoContextPath = url("http://localhost:1234");
|
const bonobWithNoContextPath = url("http://localhost:1234");
|
||||||
const bonobWithContextPath = url("http://localhost:5678/some-context-path");
|
const bonobWithContextPath = url("http://localhost:5678/some-context-path");
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import {
|
|||||||
isValidImage,
|
isValidImage,
|
||||||
song,
|
song,
|
||||||
SubsonicGenericMusicLibrary,
|
SubsonicGenericMusicLibrary,
|
||||||
} from "../../src/subsonic/library";
|
} from "../../src/subsonic/generic";
|
||||||
import { EMPTY, error, FAILURE, subsonicOK, ok } from "../subsonic.test";
|
import { EMPTY, error, FAILURE, subsonicOK, ok } from "../subsonic.test";
|
||||||
import { DODGY_IMAGE_NAME, t } from "../../src/subsonic";
|
import { DODGY_IMAGE_NAME, t } from "../../src/subsonic";
|
||||||
import { b64Encode } from "../../src/b64";
|
import { b64Encode } from "../../src/b64";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
import { DODGY_IMAGE_NAME } from "../../src/subsonic";
|
import { DODGY_IMAGE_NAME } from "../../src/subsonic";
|
||||||
import { artistImageURN } from "../../src/subsonic/library";
|
import { artistImageURN } from "../../src/subsonic/generic";
|
||||||
import { artistSummaryFromNDArtist } from "../../src/subsonic/navidrome";
|
import { artistSummaryFromNDArtist } from "../../src/subsonic/navidrome";
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user