mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Support for paging of albums for artist
This commit is contained in:
82
src/smapi.ts
82
src/smapi.ts
@@ -6,12 +6,7 @@ import path from "path";
|
|||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
|
|
||||||
import { LinkCodes } from "./link_codes";
|
import { LinkCodes } from "./link_codes";
|
||||||
import {
|
import { Album, MusicLibrary, MusicService, slice2 } from "./music_service";
|
||||||
Album,
|
|
||||||
ArtistSummary,
|
|
||||||
MusicLibrary,
|
|
||||||
MusicService,
|
|
||||||
} from "./music_service";
|
|
||||||
|
|
||||||
export const LOGIN_ROUTE = "/login";
|
export const LOGIN_ROUTE = "/login";
|
||||||
export const SOAP_PATH = "/ws/sonos";
|
export const SOAP_PATH = "/ws/sonos";
|
||||||
@@ -152,7 +147,7 @@ export type Container = {
|
|||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const container = ({
|
const container = ({
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
}: {
|
}: {
|
||||||
@@ -164,6 +159,12 @@ export const container = ({
|
|||||||
title,
|
title,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const album = (album: Album) => ({
|
||||||
|
itemType: "album",
|
||||||
|
id: `album:${album.id}`,
|
||||||
|
title: album.name,
|
||||||
|
});
|
||||||
|
|
||||||
type SoapyHeaders = {
|
type SoapyHeaders = {
|
||||||
credentials?: Credentials;
|
credentials?: Credentials;
|
||||||
};
|
};
|
||||||
@@ -223,51 +224,46 @@ function bindSmapiSoapServiceToExpress(
|
|||||||
case "root":
|
case "root":
|
||||||
return getMetadataResult({
|
return getMetadataResult({
|
||||||
mediaCollection: [
|
mediaCollection: [
|
||||||
{ itemType: "container", id: "artists", title: "Artists" },
|
container({ id: "artists", title: "Artists" }),
|
||||||
{ itemType: "container", id: "albums", title: "Albums" },
|
container({ id: "albums", title: "Albums" }),
|
||||||
],
|
],
|
||||||
index: 0,
|
index: 0,
|
||||||
total: 2,
|
total: 2,
|
||||||
});
|
});
|
||||||
case "artists":
|
case "artists":
|
||||||
|
return await musicLibrary.artists(paging).then((result) =>
|
||||||
|
getMetadataResult({
|
||||||
|
mediaCollection: result.results.map((it) => ({
|
||||||
|
itemType: "artist",
|
||||||
|
id: `artist:${it.id}`,
|
||||||
|
artistId: it.id,
|
||||||
|
title: it.name,
|
||||||
|
albumArtURI: it.image.small,
|
||||||
|
})),
|
||||||
|
index: paging._index,
|
||||||
|
total: result.total,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
case "artist":
|
||||||
return await musicLibrary
|
return await musicLibrary
|
||||||
.artists(paging)
|
.artist(typeId!)
|
||||||
.then(
|
.then((artist) => artist.albums)
|
||||||
({
|
.then(slice2(paging))
|
||||||
results,
|
.then(([page, total]) =>
|
||||||
|
getMetadataResult({
|
||||||
|
mediaCollection: page.map(album),
|
||||||
|
index: 0,
|
||||||
total,
|
total,
|
||||||
}: {
|
})
|
||||||
results: ArtistSummary[];
|
|
||||||
total: number;
|
|
||||||
}) =>
|
|
||||||
getMetadataResult({
|
|
||||||
mediaCollection: results.map((it) => ({
|
|
||||||
itemType: "artist",
|
|
||||||
id: `artist:${it.id}`,
|
|
||||||
artistId: it.id,
|
|
||||||
title: it.name,
|
|
||||||
albumArtURI: it.image.small,
|
|
||||||
})),
|
|
||||||
index: paging._index,
|
|
||||||
total,
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
case "albums":
|
case "albums":
|
||||||
return await musicLibrary
|
return await musicLibrary.albums(paging).then((result) =>
|
||||||
.albums(paging)
|
getMetadataResult({
|
||||||
.then(
|
mediaCollection: result.results.map(album),
|
||||||
({ results, total }: { results: Album[]; total: number }) =>
|
index: paging._index,
|
||||||
getMetadataResult({
|
total: result.total,
|
||||||
mediaCollection: results.map((it) =>
|
})
|
||||||
container({
|
);
|
||||||
id: `album:${it.id}`,
|
|
||||||
title: it.name,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
index: paging._index,
|
|
||||||
total,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
throw `Unsupported id:${id}`;
|
throw `Unsupported id:${id}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { v4 as uuid } from "uuid";
|
|||||||
import { Credentials } from "../src/smapi";
|
import { Credentials } from "../src/smapi";
|
||||||
|
|
||||||
import { Service, Device } from "../src/sonos";
|
import { Service, Device } from "../src/sonos";
|
||||||
import { Artist } from "../src/music_service";
|
import { Album, Artist } from "../src/music_service";
|
||||||
|
|
||||||
const randomInt = (max: number) => Math.floor(Math.random() * max);
|
const randomInt = (max: number) => Math.floor(Math.random() * max);
|
||||||
const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`;
|
const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`;
|
||||||
@@ -68,20 +68,31 @@ export function someCredentials(token: string): Credentials {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BOB_MARLEY: Artist = {
|
export function anArtist(fields: Partial<Artist> = {}): Artist {
|
||||||
id: uuid(),
|
const id = uuid();
|
||||||
name: "Bob Marley",
|
return {
|
||||||
albums: [
|
id,
|
||||||
{ id: uuid(), name: "Burin'", year: "1973", genre: "Reggae" },
|
name: `Artist ${id}`,
|
||||||
{ id: uuid(), name: "Exodus", year: "1977", genre: "Reggae" },
|
albums: [],
|
||||||
{ id: uuid(), name: "Kaya", year: "1978", genre: "Ska" },
|
image: {
|
||||||
],
|
small: undefined,
|
||||||
image: {
|
medium: undefined,
|
||||||
small: "http://localhost/BOB_MARLEY/sml",
|
large: undefined
|
||||||
medium: "http://localhost/BOB_MARLEY/med",
|
},
|
||||||
large: "http://localhost/BOB_MARLEY/lge",
|
...fields
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export function anAlbum(fields: Partial<Album> = {}): Album {
|
||||||
|
const id = uuid();
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: `Album ${id}`,
|
||||||
|
genre: "Metal",
|
||||||
|
year: "1900",
|
||||||
|
...fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const BLONDIE: Artist = {
|
export const BLONDIE: Artist = {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
@@ -107,6 +118,21 @@ export const BLONDIE: Artist = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const BOB_MARLEY: Artist = {
|
||||||
|
id: uuid(),
|
||||||
|
name: "Bob Marley",
|
||||||
|
albums: [
|
||||||
|
{ id: uuid(), name: "Burin'", year: "1973", genre: "Reggae" },
|
||||||
|
{ id: uuid(), name: "Exodus", year: "1977", genre: "Reggae" },
|
||||||
|
{ id: uuid(), name: "Kaya", year: "1978", genre: "Ska" },
|
||||||
|
],
|
||||||
|
image: {
|
||||||
|
small: "http://localhost/BOB_MARLEY/sml",
|
||||||
|
medium: "http://localhost/BOB_MARLEY/med",
|
||||||
|
large: "http://localhost/BOB_MARLEY/lge",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const MADONNA: Artist = {
|
export const MADONNA: Artist = {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
name: "Madonna",
|
name: "Madonna",
|
||||||
@@ -142,9 +168,6 @@ export const METALLICA: Artist = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ALL_ALBUMS = [
|
export const ALL_ARTISTS = [BOB_MARLEY, BLONDIE, MADONNA, METALLICA]
|
||||||
...BOB_MARLEY.albums,
|
|
||||||
...BLONDIE.albums,
|
export const ALL_ALBUMS = ALL_ARTISTS.flatMap(it => it.albums || []);
|
||||||
...MADONNA.albums,
|
|
||||||
...METALLICA.albums,
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -6,19 +6,14 @@ import X2JS from "x2js";
|
|||||||
import { InMemoryLinkCodes, LinkCodes } from "../src/link_codes";
|
import { InMemoryLinkCodes, LinkCodes } from "../src/link_codes";
|
||||||
import makeServer from "../src/server";
|
import makeServer from "../src/server";
|
||||||
import { bonobService, SONOS_DISABLED } from "../src/sonos";
|
import { bonobService, SONOS_DISABLED } from "../src/sonos";
|
||||||
import {
|
import { STRINGS_ROUTE, LOGIN_ROUTE, getMetadataResult } from "../src/smapi";
|
||||||
STRINGS_ROUTE,
|
|
||||||
LOGIN_ROUTE,
|
|
||||||
getMetadataResult,
|
|
||||||
container,
|
|
||||||
} from "../src/smapi";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
aService,
|
aService,
|
||||||
BLONDIE,
|
|
||||||
BOB_MARLEY,
|
|
||||||
getAppLinkMessage,
|
getAppLinkMessage,
|
||||||
someCredentials,
|
someCredentials,
|
||||||
|
anArtist,
|
||||||
|
anAlbum,
|
||||||
} from "./builders";
|
} from "./builders";
|
||||||
import { InMemoryMusicService } from "./in_memory_music_service";
|
import { InMemoryMusicService } from "./in_memory_music_service";
|
||||||
import supersoap from "./supersoap";
|
import supersoap from "./supersoap";
|
||||||
@@ -356,8 +351,8 @@ describe("api", () => {
|
|||||||
expect(root[0]).toEqual(
|
expect(root[0]).toEqual(
|
||||||
getMetadataResult({
|
getMetadataResult({
|
||||||
mediaCollection: [
|
mediaCollection: [
|
||||||
container({ id: "artists", title: "Artists" }),
|
{ itemType: "container", id: "artists", title: "Artists" },
|
||||||
container({ id: "albums", title: "Albums" }),
|
{ itemType: "container", id: "albums", title: "Albums" },
|
||||||
],
|
],
|
||||||
index: 0,
|
index: 0,
|
||||||
total: 2,
|
total: 2,
|
||||||
@@ -366,83 +361,218 @@ describe("api", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("asking for a single artist", () => {
|
||||||
|
const artistWithManyAlbums = anArtist({
|
||||||
|
albums: [
|
||||||
|
anAlbum(),
|
||||||
|
anAlbum(),
|
||||||
|
anAlbum(),
|
||||||
|
anAlbum(),
|
||||||
|
anAlbum(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
musicService.hasArtists(artistWithManyAlbums);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("asking for all albums", () => {
|
||||||
|
it("should return a collection of albums", async () => {
|
||||||
|
const result = await ws.getMetadataAsync({
|
||||||
|
id: `artist:${artistWithManyAlbums.id}`,
|
||||||
|
index: 0,
|
||||||
|
count: 100,
|
||||||
|
});
|
||||||
|
expect(result[0]).toEqual(
|
||||||
|
getMetadataResult({
|
||||||
|
mediaCollection: artistWithManyAlbums.albums.map((it) => ({
|
||||||
|
itemType: "album",
|
||||||
|
id: `album:${it.id}`,
|
||||||
|
title: it.name,
|
||||||
|
})),
|
||||||
|
index: 0,
|
||||||
|
total: artistWithManyAlbums.albums.length,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("asking for a page of albums", () => {
|
||||||
|
it("should return just that page", async () => {
|
||||||
|
const result = await ws.getMetadataAsync({
|
||||||
|
id: `artist:${artistWithManyAlbums.id}`,
|
||||||
|
index: 2,
|
||||||
|
count: 2,
|
||||||
|
});
|
||||||
|
expect(result[0]).toEqual(
|
||||||
|
getMetadataResult({
|
||||||
|
mediaCollection: [
|
||||||
|
artistWithManyAlbums.albums[2]!,
|
||||||
|
artistWithManyAlbums.albums[3]!,
|
||||||
|
].map((it) => ({
|
||||||
|
itemType: "album",
|
||||||
|
id: `album:${it.id}`,
|
||||||
|
title: it.name,
|
||||||
|
})),
|
||||||
|
index: 0,
|
||||||
|
total: artistWithManyAlbums.albums.length,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("asking for artists", () => {
|
describe("asking for artists", () => {
|
||||||
it("should return it", async () => {
|
const artists = [
|
||||||
musicService.hasArtists(BLONDIE, BOB_MARLEY);
|
anArtist(),
|
||||||
|
anArtist(),
|
||||||
|
anArtist(),
|
||||||
|
anArtist(),
|
||||||
|
anArtist(),
|
||||||
|
];
|
||||||
|
|
||||||
const artists = await ws.getMetadataAsync({
|
beforeEach(() => {
|
||||||
id: "artists",
|
musicService.hasArtists(...artists);
|
||||||
index: 0,
|
});
|
||||||
count: 100,
|
|
||||||
});
|
describe("asking for all artists", () => {
|
||||||
expect(artists[0]).toEqual(
|
it("should return them all", async () => {
|
||||||
getMetadataResult({
|
const result = await ws.getMetadataAsync({
|
||||||
mediaCollection: [BLONDIE, BOB_MARLEY].map((it) => ({
|
id: "artists",
|
||||||
itemType: "artist",
|
|
||||||
id: `artist:${it.id}`,
|
|
||||||
artistId: it.id,
|
|
||||||
title: it.name,
|
|
||||||
albumArtURI: it.image.small,
|
|
||||||
})),
|
|
||||||
index: 0,
|
index: 0,
|
||||||
total: 2,
|
count: 100,
|
||||||
})
|
});
|
||||||
);
|
expect(result[0]).toEqual(
|
||||||
|
getMetadataResult({
|
||||||
|
mediaCollection: artists.map((it) => ({
|
||||||
|
itemType: "artist",
|
||||||
|
id: `artist:${it.id}`,
|
||||||
|
artistId: it.id,
|
||||||
|
title: it.name,
|
||||||
|
albumArtURI: it.image.small,
|
||||||
|
})),
|
||||||
|
index: 0,
|
||||||
|
total: artists.length,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("asking for a page of artists", () => {
|
||||||
|
it("should return it", async () => {
|
||||||
|
const result = await ws.getMetadataAsync({
|
||||||
|
id: "artists",
|
||||||
|
index: 1,
|
||||||
|
count: 3,
|
||||||
|
});
|
||||||
|
expect(result[0]).toEqual(
|
||||||
|
getMetadataResult({
|
||||||
|
mediaCollection: [artists[1]!, artists[2]!, artists[3]!].map(
|
||||||
|
(it) => ({
|
||||||
|
itemType: "artist",
|
||||||
|
id: `artist:${it.id}`,
|
||||||
|
artistId: it.id,
|
||||||
|
title: it.name,
|
||||||
|
albumArtURI: it.image.small,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
index: 1,
|
||||||
|
total: artists.length,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("asking for all albums", () => {
|
// describe("asking for an album by id", () => {
|
||||||
it("should return it", async () => {
|
// it("should return it", async () => {
|
||||||
musicService.hasArtists(BLONDIE, BOB_MARLEY);
|
// musicService.hasArtists(BLONDIE, BOB_MARLEY);
|
||||||
|
// const album = BOB_MARLEY.albums[0]!;
|
||||||
|
|
||||||
const albums = await ws.getMetadataAsync({
|
// const result = await ws.getMetadataAsync({
|
||||||
id: "albums",
|
// id: `album:${album.id}`,
|
||||||
index: 0,
|
// index: 0,
|
||||||
count: 100,
|
// count: 100,
|
||||||
});
|
// });
|
||||||
expect(albums[0]).toEqual(
|
// expect(result).toEqual(
|
||||||
getMetadataResult({
|
// getMetadataResult({
|
||||||
mediaCollection: [
|
// mediaCollection: [
|
||||||
...BLONDIE.albums,
|
// ...BLONDIE.albums,
|
||||||
...BOB_MARLEY.albums,
|
// ...BOB_MARLEY.albums,
|
||||||
].map((it) =>
|
// ].map((it) =>
|
||||||
container({ id: `album:${it.id}`, title: it.name })
|
// ({ itemType: "album", id: `album:${it.id}`, title: it.name })
|
||||||
),
|
// ),
|
||||||
index: 0,
|
// index: 0,
|
||||||
total: BLONDIE.albums.length + BOB_MARLEY.albums.length,
|
// total: BLONDIE.albums.length + BOB_MARLEY.albums.length,
|
||||||
})
|
// })
|
||||||
);
|
// );
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
describe("asking for albums", () => {
|
||||||
|
const artist1 = anArtist({
|
||||||
|
albums: [anAlbum(), anAlbum(), anAlbum()],
|
||||||
|
});
|
||||||
|
const artist2 = anArtist({
|
||||||
|
albums: [anAlbum(), anAlbum()],
|
||||||
|
});
|
||||||
|
const artist3 = anArtist({
|
||||||
|
albums: [],
|
||||||
|
});
|
||||||
|
const artist4 = anArtist({
|
||||||
|
albums: [anAlbum()],
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe("asking for albums with paging", () => {
|
beforeEach(() => {
|
||||||
it("should return it", async () => {
|
musicService.hasArtists(artist1, artist2, artist3, artist4);
|
||||||
musicService.hasArtists(BLONDIE, BOB_MARLEY);
|
});
|
||||||
|
|
||||||
expect(BLONDIE.albums.length).toEqual(2);
|
describe("asking for all albums", () => {
|
||||||
expect(BOB_MARLEY.albums.length).toEqual(3);
|
it("should return them all", async () => {
|
||||||
|
const result = await ws.getMetadataAsync({
|
||||||
const albums = await ws.getMetadataAsync({
|
id: "albums",
|
||||||
id: "albums",
|
index: 0,
|
||||||
index: 2,
|
count: 100,
|
||||||
count: 2,
|
});
|
||||||
|
expect(result[0]).toEqual(
|
||||||
|
getMetadataResult({
|
||||||
|
mediaCollection: [artist1, artist2, artist3, artist4]
|
||||||
|
.flatMap((it) => it.albums)
|
||||||
|
.map((it) => ({
|
||||||
|
itemType: "album",
|
||||||
|
id: `album:${it.id}`,
|
||||||
|
title: it.name,
|
||||||
|
})),
|
||||||
|
index: 0,
|
||||||
|
total: 6,
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
expect(albums[0]).toEqual(
|
});
|
||||||
getMetadataResult({
|
|
||||||
mediaCollection: [
|
describe("asking for a page of albums", () => {
|
||||||
container({
|
it("should return only that page", async () => {
|
||||||
id: `album:${BOB_MARLEY.albums[0]!.id}`,
|
const result = await ws.getMetadataAsync({
|
||||||
title: BOB_MARLEY.albums[0]!.name,
|
id: "albums",
|
||||||
}),
|
|
||||||
container({
|
|
||||||
id: `album:${BOB_MARLEY.albums[1]!.id}`,
|
|
||||||
title: BOB_MARLEY.albums[1]!.name,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
index: 2,
|
index: 2,
|
||||||
total: BLONDIE.albums.length + BOB_MARLEY.albums.length,
|
count: 3,
|
||||||
})
|
});
|
||||||
);
|
expect(result[0]).toEqual(
|
||||||
|
getMetadataResult({
|
||||||
|
mediaCollection: [
|
||||||
|
artist1.albums[2]!,
|
||||||
|
artist2.albums[0]!,
|
||||||
|
artist2.albums[1]!,
|
||||||
|
].map((it) => ({
|
||||||
|
itemType: "album",
|
||||||
|
id: `album:${it.id}`,
|
||||||
|
title: it.name,
|
||||||
|
})),
|
||||||
|
index: 2,
|
||||||
|
total: 6,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user