From 63c86883edc0def4aa2f432ab2ba58dc143599be Mon Sep 17 00:00:00 2001 From: Mnigos Date: Fri, 19 Jan 2024 22:00:00 +0100 Subject: [PATCH 1/2] refactor(common/adapters): create module --- src/common/adapters/adapters.module.ts | 29 ++++ src/common/adapters/adapters.service.ts | 21 +++ src/common/adapters/artists.adapter.spec.ts | 63 +++++--- src/common/adapters/artists.adapter.ts | 109 ++++++++----- .../adapters/audio-features.adapter.spec.ts | 32 +++- src/common/adapters/audio-features.adapter.ts | 76 +++++---- src/common/adapters/devices.adapter.spec.ts | 30 +++- src/common/adapters/devices.adapter.ts | 54 ++++--- src/common/adapters/genres.adapter.spec.ts | 30 +++- src/common/adapters/genres.adapter.ts | 23 ++- src/common/adapters/index.ts | 10 +- src/common/adapters/paginated.adapter.spec.ts | 49 +++--- src/common/adapters/paginated.adapter.ts | 31 ++-- .../adapters/playback-state.adapter.spec.ts | 36 ++++- src/common/adapters/playback-state.adapter.ts | 44 ++--- src/common/adapters/profile.adapter.spec.ts | 26 ++- src/common/adapters/profile.adapter.ts | 54 ++++--- .../adapters/secret-data.adapter.spec.ts | 40 ++--- src/common/adapters/secret-data.adapter.ts | 24 +-- src/common/adapters/tracks.adapter.spec.ts | 72 ++++++--- src/common/adapters/tracks.adapter.ts | 152 +++++++++++------- src/common/mocks/album.mock.ts | 44 ++++- src/common/mocks/artist.mock.ts | 13 +- src/common/mocks/genres.mock.ts | 4 + src/common/mocks/playback-state.mock.ts | 13 +- src/common/mocks/profile.mock.ts | 6 +- src/common/mocks/spotify-response.mock.ts | 26 +-- src/common/mocks/track.mock.ts | 34 ++-- src/common/types/spotify/artist.ts | 1 + src/common/types/spotify/device.ts | 8 +- src/common/types/spotify/spotify-response.ts | 2 +- 31 files changed, 774 insertions(+), 382 deletions(-) create mode 100644 src/common/adapters/adapters.module.ts create mode 100644 src/common/adapters/adapters.service.ts diff --git a/src/common/adapters/adapters.module.ts b/src/common/adapters/adapters.module.ts new file mode 100644 index 00000000..4438585b --- /dev/null +++ b/src/common/adapters/adapters.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common' + +import { ArtistsAdapter } from './artists.adapter' +import { AudioFeaturesAdapter } from './audio-features.adapter' +import { DevicesAdapter } from './devices.adapter' +import { GenresAdapter } from './genres.adapter' +import { PaginatedAdapter } from './paginated.adapter' +import { TracksAdapter } from './tracks.adapter' +import { PlaybackStateAdapter } from './playback-state.adapter' +import { ProfileAdapter } from './profile.adapter' +import { SecretDataAdapter } from './secret-data.adapter' +import { AdaptersService } from './adapters.service' + +@Module({ + imports: [AdaptersService], + providers: [ + ArtistsAdapter, + TracksAdapter, + AudioFeaturesAdapter, + DevicesAdapter, + GenresAdapter, + PaginatedAdapter, + PlaybackStateAdapter, + ProfileAdapter, + SecretDataAdapter, + ], + exports: [AdaptersService], +}) +export class AdaptersModule {} diff --git a/src/common/adapters/adapters.service.ts b/src/common/adapters/adapters.service.ts new file mode 100644 index 00000000..0e006a26 --- /dev/null +++ b/src/common/adapters/adapters.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common' + +import { AudioFeaturesAdapter } from './audio-features.adapter' +import { DevicesAdapter } from './devices.adapter' +import { GenresAdapter } from './genres.adapter' +import { PaginatedAdapter } from './paginated.adapter' +import { TracksAdapter } from './tracks.adapter' +import { PlaybackStateAdapter } from './playback-state.adapter' + +@Injectable() +export class AdaptersService { + constructor( + readonly artists: AdaptersService, + readonly tracks: TracksAdapter, + readonly audioFeatures: AudioFeaturesAdapter, + readonly devices: DevicesAdapter, + readonly genres: GenresAdapter, + readonly paginated: PaginatedAdapter, + readonly playbackState: PlaybackStateAdapter + ) {} +} diff --git a/src/common/adapters/artists.adapter.spec.ts b/src/common/adapters/artists.adapter.spec.ts index f19bdbac..e251cdd3 100644 --- a/src/common/adapters/artists.adapter.spec.ts +++ b/src/common/adapters/artists.adapter.spec.ts @@ -1,31 +1,56 @@ +import { Test } from '@nestjs/testing' + +import { ArtistsAdapter } from './artists.adapter' +import { PaginatedAdapter } from './paginated.adapter' + import { - spotifyArtistMock, artistMock, - spotifyArtistsMock, - artistsMock, + spotifyArtistMock, spotifyResponseWithOffsetMockFactory, -} from '../mocks' + spotifyTrackArtistMock, + trackArtistMock, +} from '@common/mocks' -import { - adaptArtist, - adaptArtists, - adaptPaginatedArtists, -} from './artists.adapter' - -describe('adaptArtists', () => { - test('should adapt artist', () => { - expect(adaptArtist(spotifyArtistMock)).toEqual(artistMock) +describe('ArtistsAdapter', () => { + let artistsAdapter: ArtistsAdapter + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [ArtistsAdapter, PaginatedAdapter], + }).compile() + + artistsAdapter = module.get(ArtistsAdapter) + }) + + test('should be defined', () => { + expect(artistsAdapter).toBeDefined() + }) + + test('should adapt a single artist', () => { + expect(artistsAdapter.adapt(spotifyArtistMock)).toEqual(artistMock) + }) + + test('should adapt a single simplified artist', () => { + expect(artistsAdapter.adapt(spotifyTrackArtistMock)).toEqual( + trackArtistMock + ) + }) + + test('should adapt an array of artists', () => { + expect(artistsAdapter.adapt([spotifyArtistMock])).toEqual([artistMock]) }) - test('should adapt artists', () => { - expect(adaptArtists(spotifyArtistsMock)).toEqual(artistsMock) + test('should adapt an array of simplified artists', () => { + expect(artistsAdapter.adapt([spotifyTrackArtistMock])).toEqual([ + trackArtistMock, + ]) }) - test('should adapt paginated artists', () => { + test('should adapt a paginated list of artists', () => { expect( - adaptPaginatedArtists( - spotifyResponseWithOffsetMockFactory(spotifyArtistsMock) + artistsAdapter.adapt( + spotifyResponseWithOffsetMockFactory([spotifyArtistMock]) ) - ).toEqual(spotifyResponseWithOffsetMockFactory(artistsMock)) + ).toEqual(spotifyResponseWithOffsetMockFactory([artistMock])) }) }) diff --git a/src/common/adapters/artists.adapter.ts b/src/common/adapters/artists.adapter.ts index df600adc..04bcc171 100644 --- a/src/common/adapters/artists.adapter.ts +++ b/src/common/adapters/artists.adapter.ts @@ -1,44 +1,67 @@ +import { Injectable } from '@nestjs/common' import { - Artist, - SpotifyArtist, - SpotifyResponseWithOffset, - SpotifyTrackArtist, - TrackArtist, -} from '../types/spotify' - -import { adaptPaginated } from './paginated.adapter' - -export const adaptArtist = ({ - id, - name, - genres, - external_urls: { spotify: href }, - images, -}: SpotifyArtist): Artist => ({ - id, - name, - genres, - href, - images, -}) - -export const adaptArtists = (artists: SpotifyArtist[]): Artist[] => - artists.map(artist => adaptArtist(artist)) - -export const adaptTrackArtist = ({ - name, - id, - external_urls: { spotify: href }, -}: SpotifyTrackArtist): TrackArtist => ({ - name, - id, - href, -}) - -export const adaptTrackArtists = ( - artists: SpotifyTrackArtist[] -): TrackArtist[] => artists.map(artist => adaptTrackArtist(artist)) - -export const adaptPaginatedArtists = ( - data: SpotifyResponseWithOffset -) => adaptPaginated(data, adaptArtists) + Page, + SimplifiedArtist, + Artist as SpotifyArtist, +} from '@spotify/web-api-ts-sdk' + +import { PaginatedAdapter } from './paginated.adapter' + +import { Artist, TrackArtist } from '@common/types/spotify' + +@Injectable() +export class ArtistsAdapter { + constructor(private readonly paginatedAdapter: PaginatedAdapter) {} + + public adapt(data: SpotifyArtist[]): Artist[] + public adapt(data: SimplifiedArtist[]): TrackArtist[] + public adapt(data: SpotifyArtist): Artist + public adapt(data: SimplifiedArtist): TrackArtist + public adapt(data: Page): Page + + adapt( + data: + | SpotifyArtist + | SimplifiedArtist + | (SpotifyArtist | SimplifiedArtist)[] + | Page + ) { + if (Array.isArray(data)) return this.adaptArtists(data) + + if ('offset' in data) return this.adaptPaginatedArtists(data) + + return this.adaptArtist(data) + } + + adaptArtist({ + name, + id, + external_urls: { spotify: href }, + ...rest + }: SpotifyArtist | SimplifiedArtist): Artist | TrackArtist { + console.log(href) + + return { + id, + name, + href, + ...('genres' in rest && { + genres: rest.genres, + images: rest.images, + popularity: rest.popularity, + }), + } + } + + adaptArtists( + artists: (SpotifyArtist | SimplifiedArtist)[] + ): (Artist | TrackArtist)[] { + return artists.map(artist => this.adaptArtist(artist)) + } + + adaptPaginatedArtists(data: Page) { + return this.paginatedAdapter.adapt(data, artists => + this.adaptArtists(artists) + ) + } +} diff --git a/src/common/adapters/audio-features.adapter.spec.ts b/src/common/adapters/audio-features.adapter.spec.ts index 54583a3d..03e372a9 100644 --- a/src/common/adapters/audio-features.adapter.spec.ts +++ b/src/common/adapters/audio-features.adapter.spec.ts @@ -1,11 +1,33 @@ -import { spotifyAudioFeaturesMock, audioFeaturesMock } from '../mocks' +import { Test } from '@nestjs/testing' -import { adaptAudioFeatures } from './audio-features.adapter' +import { AudioFeaturesAdapter } from './audio-features.adapter' -describe('adaptAudioFeatures', () => { - test('should adapt audio features', () => { - expect(adaptAudioFeatures(spotifyAudioFeaturesMock)).toEqual( +import { audioFeaturesMock, spotifyAudioFeaturesMock } from '@common/mocks' + +describe('AudioFeaturesAdapter', () => { + let audioFeaturesAdapter: AudioFeaturesAdapter + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [AudioFeaturesAdapter], + }).compile() + + audioFeaturesAdapter = module.get(AudioFeaturesAdapter) + }) + + test('should be defined', () => { + expect(audioFeaturesAdapter).toBeDefined() + }) + + test('should adapt a single audio feature', () => { + expect(audioFeaturesAdapter.adapt(spotifyAudioFeaturesMock)).toEqual( audioFeaturesMock ) }) + + test('should adapt an array of audio features', () => { + expect(audioFeaturesAdapter.adapt([spotifyAudioFeaturesMock])).toEqual([ + audioFeaturesMock, + ]) + }) }) diff --git a/src/common/adapters/audio-features.adapter.ts b/src/common/adapters/audio-features.adapter.ts index 0fae6b62..161666eb 100644 --- a/src/common/adapters/audio-features.adapter.ts +++ b/src/common/adapters/audio-features.adapter.ts @@ -1,31 +1,47 @@ -import { SpotifyAudioFeatures, AudioFeatures } from '../types/spotify' +import { Injectable } from '@nestjs/common' +import { AudioFeatures as SpotifyAudioFeatures } from '@spotify/web-api-ts-sdk' -export const adaptAudioFeatures = ({ - id, - track_href, - danceability, - acousticness, - instrumentalness, - speechiness, - liveness, - loudness, - energy, - tempo, - mode, - key, - valence, -}: SpotifyAudioFeatures): AudioFeatures => ({ - id, - trackHref: track_href, - danceability, - acousticness, - instrumentalness, - speechiness, - liveness, - loudness, - energy, - tempo, - mode, - key, - valence, -}) +import { AudioFeatures } from '@common/types/spotify' + +@Injectable() +export class AudioFeaturesAdapter { + public adapt(data: SpotifyAudioFeatures[]): AudioFeatures[] + public adapt(data: SpotifyAudioFeatures): AudioFeatures + + adapt(data: SpotifyAudioFeatures | SpotifyAudioFeatures[]) { + if (Array.isArray(data)) + return data.map(audioFeatures => this.adaptAudioFeatures(audioFeatures)) + + return this.adaptAudioFeatures(data) + } + + adaptAudioFeatures = ({ + id, + track_href, + danceability, + acousticness, + instrumentalness, + speechiness, + liveness, + loudness, + energy, + tempo, + mode, + key, + valence, + }: SpotifyAudioFeatures): AudioFeatures => ({ + id, + trackHref: track_href, + danceability, + acousticness, + instrumentalness, + speechiness, + liveness, + loudness, + energy, + tempo, + mode, + key, + valence, + }) +} diff --git a/src/common/adapters/devices.adapter.spec.ts b/src/common/adapters/devices.adapter.spec.ts index d9c6188b..12711852 100644 --- a/src/common/adapters/devices.adapter.spec.ts +++ b/src/common/adapters/devices.adapter.spec.ts @@ -1,9 +1,29 @@ -import { spotifyDevicesMock, devicesMock } from '../mocks' +import { Test } from '@nestjs/testing' -import { adaptDevices } from './devices.adapter' +import { DevicesAdapter } from './devices.adapter' -describe('adaptDevices', () => { - test('should adapt devices', () => { - expect(adaptDevices(spotifyDevicesMock)).toEqual(devicesMock) +import { deviceMock, spotifyDeviceMock } from '@common/mocks' + +describe('DevicesAdapter', () => { + let devicesAdapter: DevicesAdapter + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [DevicesAdapter], + }).compile() + + devicesAdapter = module.get(DevicesAdapter) + }) + + test('should be defined', () => { + expect(devicesAdapter).toBeDefined() + }) + + test('should adapt a single device', () => { + expect(devicesAdapter.adapt(spotifyDeviceMock)).toEqual(deviceMock) + }) + + test('should adapt an array of devices', () => { + expect(devicesAdapter.adapt([spotifyDeviceMock])).toEqual([deviceMock]) }) }) diff --git a/src/common/adapters/devices.adapter.ts b/src/common/adapters/devices.adapter.ts index 13b48ab2..5e2c9c0f 100644 --- a/src/common/adapters/devices.adapter.ts +++ b/src/common/adapters/devices.adapter.ts @@ -1,22 +1,34 @@ -import { Device, SpotifyDevice } from '../types/spotify' +import { Injectable } from '@nestjs/common' +import { Device as SpotifyDevice } from '@spotify/web-api-ts-sdk' -export const adaptDevices = (devices: SpotifyDevice[]): Device[] => - devices.map( - ({ - id, - name, - type, - is_active: isActive, - is_private_session: isPrivateSession, - is_restricted: isRestricted, - volume_percent: volumePercent, - }) => ({ - id, - name, - type, - isActive, - isPrivateSession, - isRestricted, - volumePercent, - }) - ) +import { Device } from '@common/types/spotify' + +@Injectable() +export class DevicesAdapter { + public adapt(data: SpotifyDevice[]): Device[] + public adapt(data: SpotifyDevice): Device + + adapt(data: SpotifyDevice | SpotifyDevice[]) { + if (Array.isArray(data)) return data.map(device => this.adaptDevice(device)) + + return this.adaptDevice(data) + } + + adaptDevice = ({ + id, + name, + type, + is_active: isActive, + is_private_session: isPrivateSession, + is_restricted: isRestricted, + volume_percent: volumePercent, + }: SpotifyDevice): Device => ({ + id, + name, + type, + isActive, + isPrivateSession, + isRestricted, + volumePercent, + }) +} diff --git a/src/common/adapters/genres.adapter.spec.ts b/src/common/adapters/genres.adapter.spec.ts index b7982f9c..9719d8a9 100644 --- a/src/common/adapters/genres.adapter.spec.ts +++ b/src/common/adapters/genres.adapter.spec.ts @@ -1,9 +1,29 @@ -import { spotifyArtistsMock, topGenresMock } from '../mocks' +import { Test } from '@nestjs/testing' -import { adaptGenres } from './genres.adapter' +import { GenresAdapter } from './genres.adapter' -describe('adaptGenres', () => { - test('should adapt genres', () => { - expect(adaptGenres(spotifyArtistsMock, 3)).toEqual(topGenresMock) +import { spotifyArtistMock, topGenresMock } from '@common/mocks' + +describe('GenresAdapter', () => { + let genresAdapter: GenresAdapter + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [GenresAdapter], + }).compile() + + genresAdapter = module.get(GenresAdapter) + }) + + test('should be defined', () => { + expect(genresAdapter).toBeDefined() + }) + + test('should adapt a single artist', () => { + expect(genresAdapter.adapt([spotifyArtistMock])).toEqual(topGenresMock) + }) + + test('should adapt an array of artists', () => { + expect(genresAdapter.adapt([spotifyArtistMock])).toEqual(topGenresMock) }) }) diff --git a/src/common/adapters/genres.adapter.ts b/src/common/adapters/genres.adapter.ts index 7ddf835b..6a90f240 100644 --- a/src/common/adapters/genres.adapter.ts +++ b/src/common/adapters/genres.adapter.ts @@ -1,9 +1,18 @@ -import { SpotifyArtist, Genres } from '../types/spotify' +import { Injectable } from '@nestjs/common' +import { Artist as SpotifyArtist } from '@spotify/web-api-ts-sdk' + import { getMostFrequentItems } from '../utils' -export const adaptGenres = (artists: SpotifyArtist[], limit = 20): Genres => ({ - genres: getMostFrequentItems( - artists.flatMap(({ genres }) => genres), - limit - ), -}) +import { Genres } from '@common/types/spotify' + +@Injectable() +export class GenresAdapter { + adapt(artists: SpotifyArtist[], limit = 20): Genres { + return { + genres: getMostFrequentItems( + artists.flatMap(({ genres }) => genres), + limit + ), + } + } +} diff --git a/src/common/adapters/index.ts b/src/common/adapters/index.ts index a42f2494..78312ae7 100644 --- a/src/common/adapters/index.ts +++ b/src/common/adapters/index.ts @@ -1,8 +1,2 @@ -export * from './artists.adapter' -export * from './devices.adapter' -export * from './genres.adapter' -export * from './playback-state.adapter' -export * from './tracks.adapter' -export * from './profile.adapter' -export * from './audio-features.adapter' -export * from './secret-data.adapter' +export * from './adapters.module' +export * from './adapters.service' diff --git a/src/common/adapters/paginated.adapter.spec.ts b/src/common/adapters/paginated.adapter.spec.ts index 9db1b4fc..e3176636 100644 --- a/src/common/adapters/paginated.adapter.spec.ts +++ b/src/common/adapters/paginated.adapter.spec.ts @@ -1,26 +1,37 @@ +import { Test } from '@nestjs/testing' + +import { PaginatedAdapter } from './paginated.adapter' +import { ArtistsAdapter } from './artists.adapter' + import { - artistsMock, - spotifyArtistsMock, - spotifyResponseMockFactory, -} from '../mocks' + artistMock, + spotifyArtistMock, + spotifyResponseWithOffsetMockFactory, +} from '@common/mocks' -import { adaptArtists } from './artists.adapter' -import { adaptPaginated } from './paginated.adapter' +describe('PaginatedAdapter', () => { + let paginatedAdapter: PaginatedAdapter + let artistsAdapter: ArtistsAdapter -describe('adaptPaginated', () => { - const spotifyArtistsResponseMock = { - ...spotifyResponseMockFactory(spotifyArtistsMock), - offset: 0, - } + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [PaginatedAdapter, ArtistsAdapter], + }).compile() - const formattedArtistsResponseMock = { - ...spotifyResponseMockFactory(artistsMock), - offset: 0, - } + paginatedAdapter = module.get(PaginatedAdapter) + artistsAdapter = module.get(ArtistsAdapter) + }) + + test('should be defined', () => { + expect(paginatedAdapter).toBeDefined() + }) - test('should adapt paginated data', () => { - expect(adaptPaginated(spotifyArtistsResponseMock, adaptArtists)).toEqual( - formattedArtistsResponseMock - ) + test('should adapt a paginated list', () => { + expect( + paginatedAdapter.adapt( + spotifyResponseWithOffsetMockFactory([spotifyArtistMock]), + data => artistsAdapter.adapt(data) + ) + ).toEqual(spotifyResponseWithOffsetMockFactory([artistMock])) }) }) diff --git a/src/common/adapters/paginated.adapter.ts b/src/common/adapters/paginated.adapter.ts index 9625add1..cf3c39d8 100644 --- a/src/common/adapters/paginated.adapter.ts +++ b/src/common/adapters/paginated.adapter.ts @@ -1,17 +1,20 @@ -import { SpotifyResponseWithOffset } from '../types/spotify' +import { Injectable } from '@nestjs/common' +import { Page } from '@spotify/web-api-ts-sdk' -export const adaptPaginated = ( - data: SpotifyResponseWithOffset, - adaptFunction: (items: TItems[]) => TAdaptedItems[] -): SpotifyResponseWithOffset => { - const { items, next, href, limit, offset } = data - - return { - offset, - limit, - next, - href, - items: adaptFunction(items), - total: items.length, +@Injectable() +export class PaginatedAdapter { + adapt( + { items, next, href, limit, offset, previous }: Page, + adaptFunction: (items: TItems[]) => TAdaptedItems[] + ): Page { + return { + offset, + limit, + next, + previous, + href, + items: adaptFunction(items), + total: items.length, + } } } diff --git a/src/common/adapters/playback-state.adapter.spec.ts b/src/common/adapters/playback-state.adapter.spec.ts index ab82e076..0c8d30cb 100644 --- a/src/common/adapters/playback-state.adapter.spec.ts +++ b/src/common/adapters/playback-state.adapter.spec.ts @@ -1,10 +1,36 @@ -import { spotifyPlaybackStateMock, playbackStateMock } from '../mocks' +import { Test } from '@nestjs/testing' -import { adaptPlaybackState } from './playback-state.adapter' +import { PlaybackStateAdapter } from './playback-state.adapter' +import { DevicesAdapter } from './devices.adapter' +import { TracksAdapter } from './tracks.adapter' +import { PaginatedAdapter } from './paginated.adapter' +import { ArtistsAdapter } from './artists.adapter' -describe('adaptPlaybackState', () => { - test('should adapt playback state', () => { - expect(adaptPlaybackState(spotifyPlaybackStateMock)).toEqual( +import { playbackStateMock, spotifyPlaybackStateMock } from '@common/mocks' + +describe('PlaybackStateAdapter', () => { + let playbackStateAdapter: PlaybackStateAdapter + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [ + PlaybackStateAdapter, + DevicesAdapter, + TracksAdapter, + PaginatedAdapter, + ArtistsAdapter, + ], + }).compile() + + playbackStateAdapter = module.get(PlaybackStateAdapter) + }) + + test('should be defined', () => { + expect(playbackStateAdapter).toBeDefined() + }) + + test('should adapt a single playback state', () => { + expect(playbackStateAdapter.adapt(spotifyPlaybackStateMock)).toEqual( playbackStateMock ) }) diff --git a/src/common/adapters/playback-state.adapter.ts b/src/common/adapters/playback-state.adapter.ts index d7bc6d92..ff10b73c 100644 --- a/src/common/adapters/playback-state.adapter.ts +++ b/src/common/adapters/playback-state.adapter.ts @@ -1,25 +1,31 @@ -import { - PlaybackState, - RepeatedState, - SpotifyPlaybackState, -} from '../types/spotify' +import { Injectable } from '@nestjs/common' +import { PlaybackState as SpotifyPlaybackState } from '@spotify/web-api-ts-sdk' -import { adaptDevices } from './devices.adapter' -import { adaptTrack } from './tracks.adapter' +import { DevicesAdapter } from './devices.adapter' +import { TracksAdapter } from './tracks.adapter' -export const adaptPlaybackState = ( - playbackState: SpotifyPlaybackState | null -): PlaybackState | null => { - if (!playbackState) return playbackState +import { PlaybackState, RepeatedState } from '@common/types/spotify' - const { device, repeat_state, shuffle_state, is_playing, item } = - playbackState +@Injectable() +export class PlaybackStateAdapter { + constructor( + private readonly devicesAdapter: DevicesAdapter, + private readonly tracksAdapter: TracksAdapter + ) {} - return { - device: device ? adaptDevices([device])[0] : undefined, - repeatState: repeat_state as RepeatedState, - shuffleState: shuffle_state, - isPlaying: is_playing, - track: item && 'is_local' in item ? adaptTrack(item) : undefined, + adapt(playbackState: SpotifyPlaybackState | null): PlaybackState | null { + if (!playbackState) return playbackState + + const { device, repeat_state, shuffle_state, is_playing, item } = + playbackState + + return { + device: this.devicesAdapter.adapt(device), + repeatState: repeat_state as RepeatedState, + shuffleState: shuffle_state, + isPlaying: is_playing, + track: + 'is_local' in item ? this.tracksAdapter.adaptTrack(item) : undefined, + } } } diff --git a/src/common/adapters/profile.adapter.spec.ts b/src/common/adapters/profile.adapter.spec.ts index d1ffea69..f1e532c5 100644 --- a/src/common/adapters/profile.adapter.spec.ts +++ b/src/common/adapters/profile.adapter.spec.ts @@ -1,9 +1,25 @@ -import { spotifyProfileMock, profileMock } from '../mocks' +import { Test } from '@nestjs/testing' -import { adaptProfile } from './profile.adapter' +import { ProfileAdapter } from './profile.adapter' -describe('adaptProfile', () => { - test('should adapt profile', () => { - expect(adaptProfile(spotifyProfileMock)).toEqual(profileMock) +import { profileMock, spotifyProfileMock } from '@common/mocks' + +describe('ProfileAdapter', () => { + let profileAdapter: ProfileAdapter + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [ProfileAdapter], + }).compile() + + profileAdapter = module.get(ProfileAdapter) + }) + + test('should be defined', () => { + expect(profileAdapter).toBeDefined() + }) + + test('should adapt a user profile', () => { + expect(profileAdapter.adapt(spotifyProfileMock)).toEqual(profileMock) }) }) diff --git a/src/common/adapters/profile.adapter.ts b/src/common/adapters/profile.adapter.ts index 1b463ae0..26c10a9a 100644 --- a/src/common/adapters/profile.adapter.ts +++ b/src/common/adapters/profile.adapter.ts @@ -1,25 +1,31 @@ -import { Profile, SpotifyProfile } from '../types/spotify' +import { Injectable } from '@nestjs/common' +import { UserProfile } from '@spotify/web-api-ts-sdk' -export const adaptProfile = ({ - id, - display_name, - email, - images, - country, - product, - type, - uri, - external_urls: { spotify: href }, - followers, -}: SpotifyProfile): Profile => ({ - id, - displayName: display_name ?? id, - email, - images, - country, - href, - product, - type, - uri, - followers: followers.total, -}) +import { Profile } from '@common/types/spotify' + +@Injectable() +export class ProfileAdapter { + adapt = ({ + id, + display_name, + email, + images, + country, + product, + type, + uri, + external_urls: { spotify: href }, + followers, + }: UserProfile): Profile => ({ + id, + displayName: display_name, + email, + images, + country, + href, + product, + type, + uri, + followers: followers.total, + }) +} diff --git a/src/common/adapters/secret-data.adapter.spec.ts b/src/common/adapters/secret-data.adapter.spec.ts index 2f594c12..f7dbaeda 100644 --- a/src/common/adapters/secret-data.adapter.spec.ts +++ b/src/common/adapters/secret-data.adapter.spec.ts @@ -1,25 +1,29 @@ -import { SpotifyToken } from '../types/spotify' +import { Test } from '@nestjs/testing' -import { adaptSecretData } from './secret-data.adapter' +import { SecretDataAdapter } from './secret-data.adapter' -import { SecretData } from '@modules/auth/dtos' +import { accessTokenMock } from '@common/mocks' -describe('adaptSecretData', () => { - test('should adapt secret data', () => { - const spotifyTokenMock: SpotifyToken = { - access_token: 'accessToken', - token_type: 'tokenType', - scope: 'scope', - expires_in: 3600, - refresh_token: 'refreshToken', - } +describe('SecretDataAdapter', () => { + let secretDataAdapter: SecretDataAdapter - const secretDataMock: SecretData = { - accessToken: 'accessToken', - expiresIn: 3600, - refreshToken: 'refreshToken', - } + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [SecretDataAdapter], + }).compile() - expect(adaptSecretData(spotifyTokenMock)).toEqual(secretDataMock) + secretDataAdapter = module.get(SecretDataAdapter) + }) + + test('should be defined', () => { + expect(secretDataAdapter).toBeDefined() + }) + + test('should adapt an access token', () => { + expect(secretDataAdapter.adapt(accessTokenMock)).toEqual({ + accessToken: accessTokenMock.access_token, + expiresIn: accessTokenMock.expires_in, + refreshToken: accessTokenMock.refresh_token, + }) }) }) diff --git a/src/common/adapters/secret-data.adapter.ts b/src/common/adapters/secret-data.adapter.ts index e3a4240a..0137e5a1 100644 --- a/src/common/adapters/secret-data.adapter.ts +++ b/src/common/adapters/secret-data.adapter.ts @@ -1,13 +1,17 @@ -import { SpotifyToken } from '../types/spotify' +import { Injectable } from '@nestjs/common' +import { AccessToken } from '@spotify/web-api-ts-sdk' import { SecretData } from '@modules/auth/dtos' -export const adaptSecretData = ({ - access_token, - refresh_token, - expires_in, -}: SpotifyToken): SecretData => ({ - accessToken: access_token, - refreshToken: refresh_token, - expiresIn: expires_in, -}) +@Injectable() +export class SecretDataAdapter { + adapt = ({ + access_token, + refresh_token, + expires_in, + }: AccessToken): SecretData => ({ + accessToken: access_token, + refreshToken: refresh_token, + expiresIn: expires_in, + }) +} diff --git a/src/common/adapters/tracks.adapter.spec.ts b/src/common/adapters/tracks.adapter.spec.ts index d07df167..47edb6b6 100644 --- a/src/common/adapters/tracks.adapter.spec.ts +++ b/src/common/adapters/tracks.adapter.spec.ts @@ -1,44 +1,68 @@ +import { Test } from '@nestjs/testing' + +import { TracksAdapter } from './tracks.adapter' +import { PaginatedAdapter } from './paginated.adapter' +import { ArtistsAdapter } from './artists.adapter' + import { - spotifyTracksMock, - tracksMock, spotifyResponseWithCursorsMockFactory, spotifyResponseWithOffsetMockFactory, -} from '../mocks' + spotifyTrackMock, + spotifyTracksMock, + trackMock, + tracksMock, +} from '@common/mocks' -import { - adaptLastTracks, - adaptPaginatedTracks, - adaptTracks, -} from './tracks.adapter' - -describe('adaptTracks', () => { - test('should adapt tracks', () => { - expect(adaptTracks(spotifyTracksMock)).toEqual(tracksMock) +describe('TracksAdapter', () => { + let tracksAdapter: TracksAdapter + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [TracksAdapter, PaginatedAdapter, ArtistsAdapter], + }).compile() + + tracksAdapter = module.get(TracksAdapter) }) - test('should adapt tracks without duration', () => { - expect( - adaptTracks(spotifyTracksMock.map(({ progress_ms, ...rest }) => rest)) - ).toEqual(tracksMock.map(({ progress, ...rest }) => rest)) + test('should be defined', () => { + expect(tracksAdapter).toBeDefined() }) - test('should adapt tracks without playedAt field', () => { - expect( - adaptTracks(spotifyTracksMock.map(({ played_at, ...rest }) => rest)) - ).toEqual(tracksMock.map(({ playedAt, ...rest }) => rest)) + test('should adapt a single track', () => { + expect(tracksAdapter.adapt(spotifyTrackMock)).toEqual(trackMock) }) - test('should adapt paginated tracks', () => { + test('should adapt multiple tracks', () => { + expect(tracksAdapter.adapt(spotifyTracksMock)).toEqual(tracksMock) + }) + + test('should adapt a paginated list of tracks', () => { expect( - adaptPaginatedTracks( + tracksAdapter.adapt( spotifyResponseWithOffsetMockFactory(spotifyTracksMock) ) ).toEqual(spotifyResponseWithOffsetMockFactory(tracksMock)) }) - test('should adapt last tracks', () => { + test('should adapt recently played tracks page', () => { expect( - adaptLastTracks(spotifyResponseWithCursorsMockFactory(spotifyTracksMock)) + tracksAdapter.adapt( + spotifyResponseWithCursorsMockFactory( + spotifyTracksMock.map(track => ({ + track, + played_at: trackMock.playedAt!, + context: { + type: 'playlist', + href: 'https://api.spotify.com/v1/playlists/37i9dQZF1DXcBWIGoYBM5M', + uri: 'spotify:user:spotify:playlist:37i9dQZF1DXcBWIGoYBM5M', + external_urls: { + spotify: + 'https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M', + }, + }, + })) + ) + ) ).toEqual(spotifyResponseWithCursorsMockFactory(tracksMock)) }) }) diff --git a/src/common/adapters/tracks.adapter.ts b/src/common/adapters/tracks.adapter.ts index a9a6429f..2cc2f5cd 100644 --- a/src/common/adapters/tracks.adapter.ts +++ b/src/common/adapters/tracks.adapter.ts @@ -1,59 +1,95 @@ +import { Injectable } from '@nestjs/common' import { - Track, - SpotifyResponseWithCursors, - SpotifyResponseWithOffset, - SpotifyTrack, -} from '../types/spotify' - -import { adaptTrackArtists } from './artists.adapter' -import { adaptPaginated } from './paginated.adapter' - -export const adaptTrack = ({ - id, - name, - album, - artists, - external_urls: { spotify: href }, - duration_ms, - progress_ms, - played_at, -}: SpotifyTrack): Track => ({ - id, - name, - album: { - id: album.id, - name: album.name, - images: album.images, - href: album.external_urls.spotify, - artists: adaptTrackArtists(album.artists), - releaseDate: album.release_date, - totalTracks: album.total_tracks, - }, - artists: adaptTrackArtists(artists), - href, - duration: duration_ms, - ...(progress_ms && { progress: progress_ms }), - ...(played_at && { playedAt: played_at }), -}) - -export const adaptTracks = (tracks: SpotifyTrack[]): Track[] => - tracks.map(track => adaptTrack(track)) - -export const adaptPaginatedTracks = ( - data: SpotifyResponseWithOffset -) => adaptPaginated(data, adaptTracks) - -export const adaptLastTracks = ({ - limit, - next, - href, - cursors, - items, -}: SpotifyResponseWithCursors): SpotifyResponseWithCursors => ({ - limit, - next, - href, - cursors, - items: adaptTracks(items), - total: items.length, -}) + Page, + RecentlyPlayedTracksPage, + Track as SpotifyTrack, +} from '@spotify/web-api-ts-sdk' + +import { PaginatedAdapter } from './paginated.adapter' +import { ArtistsAdapter } from './artists.adapter' + +import { SpotifyResponseWithCursors, Track } from '@common/types/spotify' + +@Injectable() +export class TracksAdapter { + constructor( + private readonly paginatedAdapter: PaginatedAdapter, + private readonly artistsAdapter: ArtistsAdapter + ) {} + + public adapt(data: SpotifyTrack): Track + public adapt(data: SpotifyTrack[]): Track[] + public adapt(data: Page): Page + public adapt( + data: RecentlyPlayedTracksPage + ): SpotifyResponseWithCursors + + adapt( + data: + | SpotifyTrack + | SpotifyTrack[] + | Page + | RecentlyPlayedTracksPage + ) { + if (Array.isArray(data)) return this.adaptTracks(data) + if ('items' in data) { + if ('cursors' in data) return this.adaptRecentlyPlayed(data) + + return this.adaptPaginatedTracks(data) + } + + return this.adaptTrack(data) + } + + adaptTrack = ({ + id, + name, + album, + artists, + external_urls: { spotify: href }, + duration_ms, + }: SpotifyTrack): Track => ({ + id, + name, + album: { + id: album.id, + name: album.name, + images: album.images, + href: album.external_urls.spotify, + artists: this.artistsAdapter.adapt(album.artists), + releaseDate: album.release_date, + totalTracks: album.total_tracks, + }, + artists: this.artistsAdapter.adapt(artists), + href, + duration: duration_ms, + }) + + adaptTracks(tracks: SpotifyTrack[]): Track[] { + return tracks.map(track => this.adaptTrack(track)) + } + + adaptPaginatedTracks = (data: Page): Page => { + return this.paginatedAdapter.adapt(data, tracks => this.adaptTracks(tracks)) + } + + adaptRecentlyPlayed({ + limit, + next, + href, + cursors, + items, + }: RecentlyPlayedTracksPage): SpotifyResponseWithCursors { + return { + limit, + next, + href, + cursors, + items: items.map(({ track, played_at }) => ({ + ...this.adaptTrack(track), + playedAt: played_at, + })), + total: items.length, + } + } +} diff --git a/src/common/mocks/album.mock.ts b/src/common/mocks/album.mock.ts index c537be42..afb329d2 100644 --- a/src/common/mocks/album.mock.ts +++ b/src/common/mocks/album.mock.ts @@ -1,10 +1,33 @@ -import { spotifyTrackArtistsMock, trackArtistsMock } from './artist.mock' +import { SimplifiedAlbum, Album as SpotifyAlbum } from '@spotify/web-api-ts-sdk' + +import { + spotifyArtistsMock, + spotifyTrackArtistsMock, + trackArtistsMock, +} from './artist.mock' import { imagesMock } from './image.mock' +import { spotifySimplifiedTracksMock } from './track.mock' +import { spotifyResponseWithOffsetMockFactory } from './spotify-response.mock' +import { topGenresArrayMock } from './genres.mock' -import { Album, SpotifyAlbum } from '@common/types/spotify' +import { Album } from '@common/types/spotify' -export const spotifyAlbumMock: SpotifyAlbum = { +export const spotifySimplifiedAlbumMock: SimplifiedAlbum = { album_type: 'album', + album_group: 'album', + copyrights: [ + { + text: '1997 Beatservice Records', + type: 'C', + }, + { + text: '1997 Beatservice Records', + type: 'P', + }, + ], + genres: topGenresArrayMock, + label: 'Beatservice Records', + popularity: 36, artists: spotifyTrackArtistsMock, available_markets: [ 'AD', @@ -38,6 +61,21 @@ export const spotifyAlbumMock: SpotifyAlbum = { total_tracks: 21, type: 'album', uri: 'spotify:album:5QIf4hNIAksV1uMCXHVkAZ', + external_ids: { + upc: '5051083100020', + isrc: 'GBBPC9700031', + ean: '5051083100020', + }, +} + +export const spotifySimplifiedAlbumsMock = Array.from({ length: 5 }).map( + () => spotifySimplifiedAlbumMock +) + +export const spotifyAlbumMock: SpotifyAlbum = { + ...spotifySimplifiedAlbumMock, + tracks: spotifyResponseWithOffsetMockFactory(spotifySimplifiedTracksMock), + artists: spotifyArtistsMock, } export const spotifyAlbumsMock = Array.from({ length: 5 }).map( diff --git a/src/common/mocks/artist.mock.ts b/src/common/mocks/artist.mock.ts index 4e3b96cd..db39cbfc 100644 --- a/src/common/mocks/artist.mock.ts +++ b/src/common/mocks/artist.mock.ts @@ -1,9 +1,9 @@ import { - Artist, - SpotifyArtist, - SpotifyTrackArtist, - TrackArtist, -} from '../types/spotify' + Artist as SpotifyArtist, + SimplifiedArtist, +} from '@spotify/web-api-ts-sdk' + +import { Artist, TrackArtist } from '../types/spotify' import { imagesMock } from './image.mock' @@ -43,11 +43,12 @@ export const artistMock: Artist = { genres: spotifyArtistMock.genres, href: spotifyArtistMock.external_urls.spotify, images: imagesMock, + popularity: spotifyArtistMock.popularity, } export const artistsMock = Array.from({ length: 5 }).map(() => artistMock) -export const spotifyTrackArtistMock: SpotifyTrackArtist = { +export const spotifyTrackArtistMock: SimplifiedArtist = { external_urls: spotifyArtistMock.external_urls, href: spotifyArtistMock.href, id: spotifyArtistMock.id, diff --git a/src/common/mocks/genres.mock.ts b/src/common/mocks/genres.mock.ts index e33bf9ff..416b6115 100644 --- a/src/common/mocks/genres.mock.ts +++ b/src/common/mocks/genres.mock.ts @@ -4,6 +4,10 @@ export const topGenresArrayMock = [ "black 'n' roll", 'black metal', 'blackened crust', + 'metal', + 'norwegian black metal', + 'norwegian death metal', + 'norwegian metal', ] export const topGenresMock: Genres = { diff --git a/src/common/mocks/playback-state.mock.ts b/src/common/mocks/playback-state.mock.ts index 2bdbfcb1..1eb0cdde 100644 --- a/src/common/mocks/playback-state.mock.ts +++ b/src/common/mocks/playback-state.mock.ts @@ -1,8 +1,6 @@ -import { - PlaybackState, - RepeatedState, - SpotifyPlaybackState, -} from '../types/spotify' +import { PlaybackState as SpotifyPlaybackState } from '@spotify/web-api-ts-sdk' + +import { PlaybackState, RepeatedState } from '../types/spotify' import { deviceMock, trackMock, spotifyDeviceMock, spotifyTrackMock } from '.' @@ -12,6 +10,11 @@ export const spotifyPlaybackStateMock: SpotifyPlaybackState = { shuffle_state: false, is_playing: true, item: spotifyTrackMock, + context: null, + timestamp: 0, + progress_ms: 0, + currently_playing_type: 'track', + actions: {}, } export const playbackStateMock: PlaybackState = { diff --git a/src/common/mocks/profile.mock.ts b/src/common/mocks/profile.mock.ts index 60a587ab..57cd1625 100644 --- a/src/common/mocks/profile.mock.ts +++ b/src/common/mocks/profile.mock.ts @@ -1,10 +1,10 @@ -import { SpotifyProfile } from '../types/spotify' +import { UserProfile } from '@spotify/web-api-ts-sdk' import { imagesMock } from './image.mock' import { Profile } from '@modules/profiles' -export const spotifyProfileMock: SpotifyProfile = { +export const spotifyProfileMock: UserProfile = { country: 'US', display_name: 'Spotify User', email: 'spotify-user@example.com', @@ -29,7 +29,7 @@ export const spotifyProfileMock: SpotifyProfile = { export const profileMock: Profile = { id: spotifyProfileMock.id, - displayName: spotifyProfileMock.display_name!, + displayName: spotifyProfileMock.display_name, followers: spotifyProfileMock.followers.total, images: imagesMock, href: spotifyProfileMock.external_urls.spotify, diff --git a/src/common/mocks/spotify-response.mock.ts b/src/common/mocks/spotify-response.mock.ts index e72497fe..fa1ce3df 100644 --- a/src/common/mocks/spotify-response.mock.ts +++ b/src/common/mocks/spotify-response.mock.ts @@ -1,23 +1,31 @@ +import { Page } from '@spotify/web-api-ts-sdk' + import { SpotifyResponse, SpotifyResponseWithCursors, - SpotifyResponseWithOffset, } from '@common/types/spotify' export const spotifyResponseMockFactory = ( items: TItems[] -): SpotifyResponse => ({ - href: 'https://api.spotify.com/v1/search?query=metallica&type=artist&offset=0&limit=20', - limit: 20, - next: 'https://api.spotify.com/v1/search?query=metallica&type=artist&offset=20&limit=20', - total: items.length, - items, -}) +): SpotifyResponse => { + console.log(items) + + return { + href: 'https://api.spotify.com/v1/search?query=metallica&type=artist&offset=0&limit=20', + limit: 20, + next: 'https://api.spotify.com/v1/search?query=metallica&type=artist&offset=20&limit=20', + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + total: items?.length ?? 0, + items, + } +} export const spotifyResponseWithOffsetMockFactory = ( items: TItems[] -): SpotifyResponseWithOffset => ({ +): Page => ({ ...spotifyResponseMockFactory(items), + previous: + 'https://api.spotify.com/v1/search?query=metallica&type=artist&offset=0&limit=20', offset: 0, }) diff --git a/src/common/mocks/track.mock.ts b/src/common/mocks/track.mock.ts index 40fcfaa0..66e727eb 100644 --- a/src/common/mocks/track.mock.ts +++ b/src/common/mocks/track.mock.ts @@ -1,13 +1,16 @@ /* eslint-disable sonarjs/no-duplicate-string */ -import { Track, SpotifyTrack } from '../types/spotify' +import { SimplifiedTrack, Track as SpotifyTrack } from '@spotify/web-api-ts-sdk' -import { albumMock, spotifyAlbumMock } from './album.mock' +import { albumMock, spotifySimplifiedAlbumMock } from './album.mock' import { spotifyTrackArtistsMock, trackArtistsMock } from './artist.mock' -export const spotifyTrackMock: SpotifyTrack = { - album: spotifyAlbumMock, +import { Track } from '@common/types/spotify' + +export const spotifySimplifiedTrackMock: SimplifiedTrack = { artists: spotifyTrackArtistsMock, + track: true, + episode: false, available_markets: [ 'AD', 'AE', @@ -30,11 +33,7 @@ export const spotifyTrackMock: SpotifyTrack = { ], disc_number: 1, duration_ms: 1000, - progress_ms: 1000, explicit: false, - external_ids: { - isrc: 'NOBIP1701070', - }, external_urls: { spotify: 'https://open.spotify.com/track/5O6MFTh1rd9PeN8XEn1yCS', }, @@ -42,13 +41,26 @@ export const spotifyTrackMock: SpotifyTrack = { id: '5O6MFTh1rd9PeN8XEn1yCS', is_local: false, name: 'Kobresia', - popularity: 34, preview_url: 'https://p.scdn.co/mp3-preview/8c2821473d727e05b137d82d5a484076888aa4be?cid=774b29d4f13844c495f206cafdad9c86', track_number: 7, type: 'track', uri: 'spotify:track:5O6MFTh1rd9PeN8XEn1yCS', - played_at: '2022-11-26T11:01:10.040Z', +} + +export const spotifySimplifiedTracksMock = Array.from({ length: 5 }).map( + () => spotifySimplifiedTrackMock +) + +export const spotifyTrackMock: SpotifyTrack = { + ...spotifySimplifiedTrackMock, + album: spotifySimplifiedAlbumMock, + external_ids: { + isrc: 'GBBPC9700031', + upc: '5051083100020', + ean: '5051083100020', + }, + popularity: 43, } export const spotifyTracksMock = Array.from({ length: 5 }).map( @@ -61,9 +73,7 @@ export const trackMock: Track = { album: albumMock, name: spotifyTrackMock.name, duration: spotifyTrackMock.duration_ms, - progress: spotifyTrackMock.progress_ms, href: spotifyTrackMock.external_urls.spotify, - playedAt: spotifyTrackMock.played_at, } export const tracksMock = Array.from({ length: 5 }).map(() => trackMock) diff --git a/src/common/types/spotify/artist.ts b/src/common/types/spotify/artist.ts index 8b8daefe..3cd00752 100644 --- a/src/common/types/spotify/artist.ts +++ b/src/common/types/spotify/artist.ts @@ -6,6 +6,7 @@ export interface Artist { genres: string[] href: string images: SpotifyImage[] + popularity: number } export interface SpotifyTrackArtist { diff --git a/src/common/types/spotify/device.ts b/src/common/types/spotify/device.ts index e0575073..1c9e9e45 100644 --- a/src/common/types/spotify/device.ts +++ b/src/common/types/spotify/device.ts @@ -1,19 +1,19 @@ export interface Device { - id?: string | null + id: string | null name: string type: string isActive: boolean isPrivateSession: boolean isRestricted: boolean - volumePercent?: number | null + volumePercent: number | null } export interface SpotifyDevice { - id?: string | null + id: string | null is_active: boolean is_private_session: boolean is_restricted: boolean name: string type: string - volume_percent?: number | null + volume_percent: number | null } diff --git a/src/common/types/spotify/spotify-response.ts b/src/common/types/spotify/spotify-response.ts index f644accb..952f14e5 100644 --- a/src/common/types/spotify/spotify-response.ts +++ b/src/common/types/spotify/spotify-response.ts @@ -4,7 +4,6 @@ export interface SpotifyResponse { href: string limit: number next: string | null - previous?: string | null total: number items: TItems[] } @@ -12,6 +11,7 @@ export interface SpotifyResponse { export interface SpotifyResponseWithOffset extends SpotifyResponse { offset: number + previous: string | null } export interface SpotifyResponseWithCursors From b82711238ba3bbbabd60ce2bbfeb403642328f9e Mon Sep 17 00:00:00 2001 From: Mnigos Date: Fri, 26 Jan 2024 12:20:32 +0100 Subject: [PATCH 2/2] refactor: adapters and types --- src/common/adapters/adapters.module.ts | 9 ++- src/common/adapters/adapters.service.ts | 13 +++- src/common/adapters/artists.adapter.spec.ts | 32 ++++---- src/common/adapters/artists.adapter.ts | 73 +++++++++---------- .../adapters/audio-features.adapter.spec.ts | 6 +- src/common/adapters/audio-features.adapter.ts | 11 ++- src/common/adapters/devices.adapter.spec.ts | 6 +- src/common/adapters/devices.adapter.ts | 11 ++- src/common/adapters/genres.adapter.spec.ts | 6 +- src/common/adapters/genres.adapter.ts | 5 +- src/common/adapters/index.ts | 10 +++ src/common/adapters/page.adapter.spec.ts | 32 ++++++++ .../{paginated.adapter.ts => page.adapter.ts} | 2 +- src/common/adapters/paginated.adapter.spec.ts | 37 ---------- .../adapters/playback-state.adapter.spec.ts | 8 +- src/common/adapters/playback-state.adapter.ts | 9 ++- src/common/adapters/profile.adapter.spec.ts | 4 +- src/common/adapters/profile.adapter.ts | 5 +- src/common/adapters/tracks.adapter.spec.ts | 30 ++++---- src/common/adapters/tracks.adapter.ts | 49 ++++++------- src/common/mocks/album.mock.ts | 46 ++++++------ src/common/mocks/artist.mock.ts | 54 +++++++------- src/common/mocks/audio-features.mock.ts | 30 ++++---- src/common/mocks/device.mock.ts | 22 +++--- src/common/mocks/image.mock.ts | 11 +-- src/common/mocks/index.ts | 2 +- src/common/mocks/page.mock.ts | 44 +++++++++++ src/common/mocks/playback-state.mock.ts | 14 ++-- src/common/mocks/profile.mock.ts | 20 ++--- src/common/mocks/spotify-response.mock.ts | 40 ---------- src/common/mocks/track.mock.ts | 32 ++++---- src/common/types/spotify/album.ts | 27 +++---- src/common/types/spotify/artist.ts | 31 ++------ src/common/types/spotify/audio-features.ts | 23 +----- src/common/types/spotify/device.ts | 10 +-- src/common/types/spotify/image.ts | 5 -- src/common/types/spotify/index.ts | 6 +- src/common/types/spotify/page.ts | 14 ++++ src/common/types/spotify/playback-state.ts | 14 +--- src/common/types/spotify/profile.ts | 27 +------ src/common/types/spotify/spotify-response.ts | 20 ----- src/common/types/spotify/spotify-token.ts | 7 -- src/common/types/spotify/track.ts | 27 +------ src/modules/app/app.module.ts | 2 + src/modules/auth/auth.controller.spec.ts | 8 ++ src/modules/auth/auth.controller.ts | 9 ++- src/modules/images/image.entity.ts | 4 +- src/modules/images/images.repository.spec.ts | 8 +- .../profiles/dtos/create-profile.dto.ts | 4 +- .../spotify/auth/spotify-auth.service.spec.ts | 15 ++-- .../spotify/auth/spotify-auth.service.ts | 12 +-- .../spotify/player/spotify-player.service.ts | 18 ++--- .../spotify/users/spotify-users.service.ts | 34 +++++---- .../users/users-profile.controller.spec.ts | 28 +++---- 54 files changed, 457 insertions(+), 569 deletions(-) create mode 100644 src/common/adapters/page.adapter.spec.ts rename src/common/adapters/{paginated.adapter.ts => page.adapter.ts} (93%) delete mode 100644 src/common/adapters/paginated.adapter.spec.ts create mode 100644 src/common/mocks/page.mock.ts delete mode 100644 src/common/mocks/spotify-response.mock.ts delete mode 100644 src/common/types/spotify/image.ts create mode 100644 src/common/types/spotify/page.ts delete mode 100644 src/common/types/spotify/spotify-response.ts delete mode 100644 src/common/types/spotify/spotify-token.ts diff --git a/src/common/adapters/adapters.module.ts b/src/common/adapters/adapters.module.ts index 4438585b..ba130f93 100644 --- a/src/common/adapters/adapters.module.ts +++ b/src/common/adapters/adapters.module.ts @@ -1,28 +1,29 @@ -import { Module } from '@nestjs/common' +import { Global, Module } from '@nestjs/common' import { ArtistsAdapter } from './artists.adapter' import { AudioFeaturesAdapter } from './audio-features.adapter' import { DevicesAdapter } from './devices.adapter' import { GenresAdapter } from './genres.adapter' -import { PaginatedAdapter } from './paginated.adapter' +import { PageAdapter } from './page.adapter' import { TracksAdapter } from './tracks.adapter' import { PlaybackStateAdapter } from './playback-state.adapter' import { ProfileAdapter } from './profile.adapter' import { SecretDataAdapter } from './secret-data.adapter' import { AdaptersService } from './adapters.service' +@Global() @Module({ - imports: [AdaptersService], providers: [ ArtistsAdapter, TracksAdapter, AudioFeaturesAdapter, DevicesAdapter, GenresAdapter, - PaginatedAdapter, + PageAdapter, PlaybackStateAdapter, ProfileAdapter, SecretDataAdapter, + AdaptersService, ], exports: [AdaptersService], }) diff --git a/src/common/adapters/adapters.service.ts b/src/common/adapters/adapters.service.ts index 0e006a26..820934c6 100644 --- a/src/common/adapters/adapters.service.ts +++ b/src/common/adapters/adapters.service.ts @@ -3,19 +3,24 @@ import { Injectable } from '@nestjs/common' import { AudioFeaturesAdapter } from './audio-features.adapter' import { DevicesAdapter } from './devices.adapter' import { GenresAdapter } from './genres.adapter' -import { PaginatedAdapter } from './paginated.adapter' +import { PageAdapter } from './page.adapter' import { TracksAdapter } from './tracks.adapter' import { PlaybackStateAdapter } from './playback-state.adapter' +import { ProfileAdapter } from './profile.adapter' +import { ArtistsAdapter } from './artists.adapter' +import { SecretDataAdapter } from './secret-data.adapter' @Injectable() export class AdaptersService { constructor( - readonly artists: AdaptersService, + readonly artists: ArtistsAdapter, readonly tracks: TracksAdapter, readonly audioFeatures: AudioFeaturesAdapter, readonly devices: DevicesAdapter, readonly genres: GenresAdapter, - readonly paginated: PaginatedAdapter, - readonly playbackState: PlaybackStateAdapter + readonly page: PageAdapter, + readonly playbackState: PlaybackStateAdapter, + readonly profile: ProfileAdapter, + readonly secretData: SecretDataAdapter ) {} } diff --git a/src/common/adapters/artists.adapter.spec.ts b/src/common/adapters/artists.adapter.spec.ts index e251cdd3..2de7eeca 100644 --- a/src/common/adapters/artists.adapter.spec.ts +++ b/src/common/adapters/artists.adapter.spec.ts @@ -1,14 +1,14 @@ import { Test } from '@nestjs/testing' import { ArtistsAdapter } from './artists.adapter' -import { PaginatedAdapter } from './paginated.adapter' +import { PageAdapter } from './page.adapter' import { artistMock, - spotifyArtistMock, - spotifyResponseWithOffsetMockFactory, - spotifyTrackArtistMock, - trackArtistMock, + sdkArtistMock, + pageMockFactory, + sdkSimplifiedArtistMock, + simplifiedArtistMock, } from '@common/mocks' describe('ArtistsAdapter', () => { @@ -16,7 +16,7 @@ describe('ArtistsAdapter', () => { beforeEach(async () => { const module = await Test.createTestingModule({ - providers: [ArtistsAdapter, PaginatedAdapter], + providers: [ArtistsAdapter, PageAdapter], }).compile() artistsAdapter = module.get(ArtistsAdapter) @@ -27,30 +27,28 @@ describe('ArtistsAdapter', () => { }) test('should adapt a single artist', () => { - expect(artistsAdapter.adapt(spotifyArtistMock)).toEqual(artistMock) + expect(artistsAdapter.adapt(sdkArtistMock)).toEqual(artistMock) }) test('should adapt a single simplified artist', () => { - expect(artistsAdapter.adapt(spotifyTrackArtistMock)).toEqual( - trackArtistMock + expect(artistsAdapter.adapt(sdkSimplifiedArtistMock)).toEqual( + simplifiedArtistMock ) }) test('should adapt an array of artists', () => { - expect(artistsAdapter.adapt([spotifyArtistMock])).toEqual([artistMock]) + expect(artistsAdapter.adapt([sdkArtistMock])).toEqual([artistMock]) }) test('should adapt an array of simplified artists', () => { - expect(artistsAdapter.adapt([spotifyTrackArtistMock])).toEqual([ - trackArtistMock, + expect(artistsAdapter.adapt([sdkSimplifiedArtistMock])).toEqual([ + simplifiedArtistMock, ]) }) test('should adapt a paginated list of artists', () => { - expect( - artistsAdapter.adapt( - spotifyResponseWithOffsetMockFactory([spotifyArtistMock]) - ) - ).toEqual(spotifyResponseWithOffsetMockFactory([artistMock])) + expect(artistsAdapter.adapt(pageMockFactory([sdkArtistMock]))).toEqual( + pageMockFactory([artistMock]) + ) }) }) diff --git a/src/common/adapters/artists.adapter.ts b/src/common/adapters/artists.adapter.ts index 04bcc171..658f1afb 100644 --- a/src/common/adapters/artists.adapter.ts +++ b/src/common/adapters/artists.adapter.ts @@ -1,67 +1,62 @@ import { Injectable } from '@nestjs/common' -import { - Page, - SimplifiedArtist, - Artist as SpotifyArtist, -} from '@spotify/web-api-ts-sdk' +import { Page } from '@spotify/web-api-ts-sdk' -import { PaginatedAdapter } from './paginated.adapter' +import { PageAdapter } from './page.adapter' -import { Artist, TrackArtist } from '@common/types/spotify' +import { + Artist, + SdkArtist, + SdkSimplifiedArtist, + SimplifiedArtist, +} from '@common/types/spotify' @Injectable() export class ArtistsAdapter { - constructor(private readonly paginatedAdapter: PaginatedAdapter) {} + constructor(private readonly pageAdapter: PageAdapter) {} - public adapt(data: SpotifyArtist[]): Artist[] - public adapt(data: SimplifiedArtist[]): TrackArtist[] - public adapt(data: SpotifyArtist): Artist - public adapt(data: SimplifiedArtist): TrackArtist - public adapt(data: Page): Page + public adapt(data: SdkArtist[]): Artist[] + public adapt(data: SdkSimplifiedArtist[]): SimplifiedArtist[] + public adapt(data: SdkArtist): Artist + public adapt(data: SdkSimplifiedArtist): SimplifiedArtist + public adapt(data: Page): Page adapt( data: - | SpotifyArtist - | SimplifiedArtist - | (SpotifyArtist | SimplifiedArtist)[] - | Page + | SdkArtist + | SdkSimplifiedArtist + | (SdkArtist | SdkSimplifiedArtist)[] + | Page ) { if (Array.isArray(data)) return this.adaptArtists(data) - if ('offset' in data) return this.adaptPaginatedArtists(data) + if ('offset' in data) return this.adaptArtistsPage(data) return this.adaptArtist(data) } - adaptArtist({ + adaptArtist = ({ name, id, external_urls: { spotify: href }, ...rest - }: SpotifyArtist | SimplifiedArtist): Artist | TrackArtist { - console.log(href) - - return { - id, - name, - href, - ...('genres' in rest && { - genres: rest.genres, - images: rest.images, - popularity: rest.popularity, - }), - } - } + }: SdkArtist | SdkSimplifiedArtist): Artist | SimplifiedArtist => ({ + id, + name, + href, + ...('genres' in rest && { + genres: rest.genres, + images: rest.images, + popularity: rest.popularity, + }), + }) adaptArtists( - artists: (SpotifyArtist | SimplifiedArtist)[] - ): (Artist | TrackArtist)[] { + artists: (SdkArtist | SdkSimplifiedArtist)[] + ): (Artist | SimplifiedArtist)[] { return artists.map(artist => this.adaptArtist(artist)) } - adaptPaginatedArtists(data: Page) { - return this.paginatedAdapter.adapt(data, artists => - this.adaptArtists(artists) - ) + adaptArtistsPage(data: Page) { + return this.pageAdapter.adapt(data, artists => this.adaptArtists(artists)) } } diff --git a/src/common/adapters/audio-features.adapter.spec.ts b/src/common/adapters/audio-features.adapter.spec.ts index 03e372a9..fa7a6b2e 100644 --- a/src/common/adapters/audio-features.adapter.spec.ts +++ b/src/common/adapters/audio-features.adapter.spec.ts @@ -2,7 +2,7 @@ import { Test } from '@nestjs/testing' import { AudioFeaturesAdapter } from './audio-features.adapter' -import { audioFeaturesMock, spotifyAudioFeaturesMock } from '@common/mocks' +import { audioFeaturesMock, sdkAudioFeaturesMock } from '@common/mocks' describe('AudioFeaturesAdapter', () => { let audioFeaturesAdapter: AudioFeaturesAdapter @@ -20,13 +20,13 @@ describe('AudioFeaturesAdapter', () => { }) test('should adapt a single audio feature', () => { - expect(audioFeaturesAdapter.adapt(spotifyAudioFeaturesMock)).toEqual( + expect(audioFeaturesAdapter.adapt(sdkAudioFeaturesMock)).toEqual( audioFeaturesMock ) }) test('should adapt an array of audio features', () => { - expect(audioFeaturesAdapter.adapt([spotifyAudioFeaturesMock])).toEqual([ + expect(audioFeaturesAdapter.adapt([sdkAudioFeaturesMock])).toEqual([ audioFeaturesMock, ]) }) diff --git a/src/common/adapters/audio-features.adapter.ts b/src/common/adapters/audio-features.adapter.ts index 161666eb..1e4bb659 100644 --- a/src/common/adapters/audio-features.adapter.ts +++ b/src/common/adapters/audio-features.adapter.ts @@ -1,14 +1,13 @@ import { Injectable } from '@nestjs/common' -import { AudioFeatures as SpotifyAudioFeatures } from '@spotify/web-api-ts-sdk' -import { AudioFeatures } from '@common/types/spotify' +import { AudioFeatures, SdkAudioFeatures } from '@common/types/spotify' @Injectable() export class AudioFeaturesAdapter { - public adapt(data: SpotifyAudioFeatures[]): AudioFeatures[] - public adapt(data: SpotifyAudioFeatures): AudioFeatures + public adapt(data: SdkAudioFeatures[]): AudioFeatures[] + public adapt(data: SdkAudioFeatures): AudioFeatures - adapt(data: SpotifyAudioFeatures | SpotifyAudioFeatures[]) { + adapt(data: SdkAudioFeatures | SdkAudioFeatures[]) { if (Array.isArray(data)) return data.map(audioFeatures => this.adaptAudioFeatures(audioFeatures)) @@ -29,7 +28,7 @@ export class AudioFeaturesAdapter { mode, key, valence, - }: SpotifyAudioFeatures): AudioFeatures => ({ + }: SdkAudioFeatures): AudioFeatures => ({ id, trackHref: track_href, danceability, diff --git a/src/common/adapters/devices.adapter.spec.ts b/src/common/adapters/devices.adapter.spec.ts index 12711852..cd136e2f 100644 --- a/src/common/adapters/devices.adapter.spec.ts +++ b/src/common/adapters/devices.adapter.spec.ts @@ -2,7 +2,7 @@ import { Test } from '@nestjs/testing' import { DevicesAdapter } from './devices.adapter' -import { deviceMock, spotifyDeviceMock } from '@common/mocks' +import { deviceMock, sdkDeviceMock } from '@common/mocks' describe('DevicesAdapter', () => { let devicesAdapter: DevicesAdapter @@ -20,10 +20,10 @@ describe('DevicesAdapter', () => { }) test('should adapt a single device', () => { - expect(devicesAdapter.adapt(spotifyDeviceMock)).toEqual(deviceMock) + expect(devicesAdapter.adapt(sdkDeviceMock)).toEqual(deviceMock) }) test('should adapt an array of devices', () => { - expect(devicesAdapter.adapt([spotifyDeviceMock])).toEqual([deviceMock]) + expect(devicesAdapter.adapt([sdkDeviceMock])).toEqual([deviceMock]) }) }) diff --git a/src/common/adapters/devices.adapter.ts b/src/common/adapters/devices.adapter.ts index 5e2c9c0f..3e5471ae 100644 --- a/src/common/adapters/devices.adapter.ts +++ b/src/common/adapters/devices.adapter.ts @@ -1,14 +1,13 @@ import { Injectable } from '@nestjs/common' -import { Device as SpotifyDevice } from '@spotify/web-api-ts-sdk' -import { Device } from '@common/types/spotify' +import { Device, SdkDevice } from '@common/types/spotify' @Injectable() export class DevicesAdapter { - public adapt(data: SpotifyDevice[]): Device[] - public adapt(data: SpotifyDevice): Device + public adapt(data: SdkDevice[]): Device[] + public adapt(data: SdkDevice): Device - adapt(data: SpotifyDevice | SpotifyDevice[]) { + adapt(data: SdkDevice | SdkDevice[]) { if (Array.isArray(data)) return data.map(device => this.adaptDevice(device)) return this.adaptDevice(data) @@ -22,7 +21,7 @@ export class DevicesAdapter { is_private_session: isPrivateSession, is_restricted: isRestricted, volume_percent: volumePercent, - }: SpotifyDevice): Device => ({ + }: SdkDevice): Device => ({ id, name, type, diff --git a/src/common/adapters/genres.adapter.spec.ts b/src/common/adapters/genres.adapter.spec.ts index 9719d8a9..b4f41bd1 100644 --- a/src/common/adapters/genres.adapter.spec.ts +++ b/src/common/adapters/genres.adapter.spec.ts @@ -2,7 +2,7 @@ import { Test } from '@nestjs/testing' import { GenresAdapter } from './genres.adapter' -import { spotifyArtistMock, topGenresMock } from '@common/mocks' +import { sdkArtistMock, topGenresMock } from '@common/mocks' describe('GenresAdapter', () => { let genresAdapter: GenresAdapter @@ -20,10 +20,10 @@ describe('GenresAdapter', () => { }) test('should adapt a single artist', () => { - expect(genresAdapter.adapt([spotifyArtistMock])).toEqual(topGenresMock) + expect(genresAdapter.adapt([sdkArtistMock])).toEqual(topGenresMock) }) test('should adapt an array of artists', () => { - expect(genresAdapter.adapt([spotifyArtistMock])).toEqual(topGenresMock) + expect(genresAdapter.adapt([sdkArtistMock])).toEqual(topGenresMock) }) }) diff --git a/src/common/adapters/genres.adapter.ts b/src/common/adapters/genres.adapter.ts index 6a90f240..4c369b57 100644 --- a/src/common/adapters/genres.adapter.ts +++ b/src/common/adapters/genres.adapter.ts @@ -1,13 +1,12 @@ import { Injectable } from '@nestjs/common' -import { Artist as SpotifyArtist } from '@spotify/web-api-ts-sdk' import { getMostFrequentItems } from '../utils' -import { Genres } from '@common/types/spotify' +import { Genres, SdkArtist } from '@common/types/spotify' @Injectable() export class GenresAdapter { - adapt(artists: SpotifyArtist[], limit = 20): Genres { + adapt(artists: SdkArtist[], limit = 20): Genres { return { genres: getMostFrequentItems( artists.flatMap(({ genres }) => genres), diff --git a/src/common/adapters/index.ts b/src/common/adapters/index.ts index 78312ae7..d0edbc80 100644 --- a/src/common/adapters/index.ts +++ b/src/common/adapters/index.ts @@ -1,2 +1,12 @@ export * from './adapters.module' export * from './adapters.service' + +export * from './artists.adapter' +export * from './audio-features.adapter' +export * from './devices.adapter' +export * from './genres.adapter' +export * from './page.adapter' +export * from './tracks.adapter' +export * from './playback-state.adapter' +export * from './profile.adapter' +export * from './secret-data.adapter' diff --git a/src/common/adapters/page.adapter.spec.ts b/src/common/adapters/page.adapter.spec.ts new file mode 100644 index 00000000..bc2d0cef --- /dev/null +++ b/src/common/adapters/page.adapter.spec.ts @@ -0,0 +1,32 @@ +import { Test } from '@nestjs/testing' + +import { PageAdapter } from './page.adapter' +import { ArtistsAdapter } from './artists.adapter' + +import { artistMock, sdkArtistMock, pageMockFactory } from '@common/mocks' + +describe('PaginatedAdapter', () => { + let paginatedAdapter: PageAdapter + let artistsAdapter: ArtistsAdapter + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [PageAdapter, ArtistsAdapter], + }).compile() + + paginatedAdapter = module.get(PageAdapter) + artistsAdapter = module.get(ArtistsAdapter) + }) + + test('should be defined', () => { + expect(paginatedAdapter).toBeDefined() + }) + + test('should adapt a paginated list', () => { + expect( + paginatedAdapter.adapt(pageMockFactory([sdkArtistMock]), data => + artistsAdapter.adapt(data) + ) + ).toEqual(pageMockFactory([artistMock])) + }) +}) diff --git a/src/common/adapters/paginated.adapter.ts b/src/common/adapters/page.adapter.ts similarity index 93% rename from src/common/adapters/paginated.adapter.ts rename to src/common/adapters/page.adapter.ts index cf3c39d8..a1c8051e 100644 --- a/src/common/adapters/paginated.adapter.ts +++ b/src/common/adapters/page.adapter.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common' import { Page } from '@spotify/web-api-ts-sdk' @Injectable() -export class PaginatedAdapter { +export class PageAdapter { adapt( { items, next, href, limit, offset, previous }: Page, adaptFunction: (items: TItems[]) => TAdaptedItems[] diff --git a/src/common/adapters/paginated.adapter.spec.ts b/src/common/adapters/paginated.adapter.spec.ts deleted file mode 100644 index e3176636..00000000 --- a/src/common/adapters/paginated.adapter.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Test } from '@nestjs/testing' - -import { PaginatedAdapter } from './paginated.adapter' -import { ArtistsAdapter } from './artists.adapter' - -import { - artistMock, - spotifyArtistMock, - spotifyResponseWithOffsetMockFactory, -} from '@common/mocks' - -describe('PaginatedAdapter', () => { - let paginatedAdapter: PaginatedAdapter - let artistsAdapter: ArtistsAdapter - - beforeEach(async () => { - const module = await Test.createTestingModule({ - providers: [PaginatedAdapter, ArtistsAdapter], - }).compile() - - paginatedAdapter = module.get(PaginatedAdapter) - artistsAdapter = module.get(ArtistsAdapter) - }) - - test('should be defined', () => { - expect(paginatedAdapter).toBeDefined() - }) - - test('should adapt a paginated list', () => { - expect( - paginatedAdapter.adapt( - spotifyResponseWithOffsetMockFactory([spotifyArtistMock]), - data => artistsAdapter.adapt(data) - ) - ).toEqual(spotifyResponseWithOffsetMockFactory([artistMock])) - }) -}) diff --git a/src/common/adapters/playback-state.adapter.spec.ts b/src/common/adapters/playback-state.adapter.spec.ts index 0c8d30cb..bb9fa199 100644 --- a/src/common/adapters/playback-state.adapter.spec.ts +++ b/src/common/adapters/playback-state.adapter.spec.ts @@ -3,10 +3,10 @@ import { Test } from '@nestjs/testing' import { PlaybackStateAdapter } from './playback-state.adapter' import { DevicesAdapter } from './devices.adapter' import { TracksAdapter } from './tracks.adapter' -import { PaginatedAdapter } from './paginated.adapter' +import { PageAdapter } from './page.adapter' import { ArtistsAdapter } from './artists.adapter' -import { playbackStateMock, spotifyPlaybackStateMock } from '@common/mocks' +import { playbackStateMock, sdkPlaybackStateMock } from '@common/mocks' describe('PlaybackStateAdapter', () => { let playbackStateAdapter: PlaybackStateAdapter @@ -17,7 +17,7 @@ describe('PlaybackStateAdapter', () => { PlaybackStateAdapter, DevicesAdapter, TracksAdapter, - PaginatedAdapter, + PageAdapter, ArtistsAdapter, ], }).compile() @@ -30,7 +30,7 @@ describe('PlaybackStateAdapter', () => { }) test('should adapt a single playback state', () => { - expect(playbackStateAdapter.adapt(spotifyPlaybackStateMock)).toEqual( + expect(playbackStateAdapter.adapt(sdkPlaybackStateMock)).toEqual( playbackStateMock ) }) diff --git a/src/common/adapters/playback-state.adapter.ts b/src/common/adapters/playback-state.adapter.ts index ff10b73c..44de408a 100644 --- a/src/common/adapters/playback-state.adapter.ts +++ b/src/common/adapters/playback-state.adapter.ts @@ -1,10 +1,13 @@ import { Injectable } from '@nestjs/common' -import { PlaybackState as SpotifyPlaybackState } from '@spotify/web-api-ts-sdk' import { DevicesAdapter } from './devices.adapter' import { TracksAdapter } from './tracks.adapter' -import { PlaybackState, RepeatedState } from '@common/types/spotify' +import { + PlaybackState, + RepeatedState, + SdkPlaybackState, +} from '@common/types/spotify' @Injectable() export class PlaybackStateAdapter { @@ -13,7 +16,7 @@ export class PlaybackStateAdapter { private readonly tracksAdapter: TracksAdapter ) {} - adapt(playbackState: SpotifyPlaybackState | null): PlaybackState | null { + adapt(playbackState: SdkPlaybackState | null): PlaybackState | null { if (!playbackState) return playbackState const { device, repeat_state, shuffle_state, is_playing, item } = diff --git a/src/common/adapters/profile.adapter.spec.ts b/src/common/adapters/profile.adapter.spec.ts index f1e532c5..0db77102 100644 --- a/src/common/adapters/profile.adapter.spec.ts +++ b/src/common/adapters/profile.adapter.spec.ts @@ -2,7 +2,7 @@ import { Test } from '@nestjs/testing' import { ProfileAdapter } from './profile.adapter' -import { profileMock, spotifyProfileMock } from '@common/mocks' +import { profileMock, sdkProfileMock } from '@common/mocks' describe('ProfileAdapter', () => { let profileAdapter: ProfileAdapter @@ -20,6 +20,6 @@ describe('ProfileAdapter', () => { }) test('should adapt a user profile', () => { - expect(profileAdapter.adapt(spotifyProfileMock)).toEqual(profileMock) + expect(profileAdapter.adapt(sdkProfileMock)).toEqual(profileMock) }) }) diff --git a/src/common/adapters/profile.adapter.ts b/src/common/adapters/profile.adapter.ts index 26c10a9a..abc66ab4 100644 --- a/src/common/adapters/profile.adapter.ts +++ b/src/common/adapters/profile.adapter.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common' -import { UserProfile } from '@spotify/web-api-ts-sdk' -import { Profile } from '@common/types/spotify' +import { Profile, SdkProfile } from '@common/types/spotify' @Injectable() export class ProfileAdapter { @@ -16,7 +15,7 @@ export class ProfileAdapter { uri, external_urls: { spotify: href }, followers, - }: UserProfile): Profile => ({ + }: SdkProfile): Profile => ({ id, displayName: display_name, email, diff --git a/src/common/adapters/tracks.adapter.spec.ts b/src/common/adapters/tracks.adapter.spec.ts index 47edb6b6..b0fd7f62 100644 --- a/src/common/adapters/tracks.adapter.spec.ts +++ b/src/common/adapters/tracks.adapter.spec.ts @@ -1,14 +1,14 @@ import { Test } from '@nestjs/testing' import { TracksAdapter } from './tracks.adapter' -import { PaginatedAdapter } from './paginated.adapter' +import { PageAdapter } from './page.adapter' import { ArtistsAdapter } from './artists.adapter' import { - spotifyResponseWithCursorsMockFactory, - spotifyResponseWithOffsetMockFactory, - spotifyTrackMock, - spotifyTracksMock, + recentlyPlayedTracksPageMockFactory, + pageMockFactory, + sdkTrackMock, + sdkTracksMock, trackMock, tracksMock, } from '@common/mocks' @@ -18,7 +18,7 @@ describe('TracksAdapter', () => { beforeEach(async () => { const module = await Test.createTestingModule({ - providers: [TracksAdapter, PaginatedAdapter, ArtistsAdapter], + providers: [TracksAdapter, PageAdapter, ArtistsAdapter], }).compile() tracksAdapter = module.get(TracksAdapter) @@ -29,26 +29,24 @@ describe('TracksAdapter', () => { }) test('should adapt a single track', () => { - expect(tracksAdapter.adapt(spotifyTrackMock)).toEqual(trackMock) + expect(tracksAdapter.adapt(sdkTrackMock)).toEqual(trackMock) }) test('should adapt multiple tracks', () => { - expect(tracksAdapter.adapt(spotifyTracksMock)).toEqual(tracksMock) + expect(tracksAdapter.adapt(sdkTracksMock)).toEqual(tracksMock) }) test('should adapt a paginated list of tracks', () => { - expect( - tracksAdapter.adapt( - spotifyResponseWithOffsetMockFactory(spotifyTracksMock) - ) - ).toEqual(spotifyResponseWithOffsetMockFactory(tracksMock)) + expect(tracksAdapter.adapt(pageMockFactory(sdkTracksMock))).toEqual( + pageMockFactory(tracksMock) + ) }) test('should adapt recently played tracks page', () => { expect( tracksAdapter.adapt( - spotifyResponseWithCursorsMockFactory( - spotifyTracksMock.map(track => ({ + recentlyPlayedTracksPageMockFactory( + sdkTracksMock.map(track => ({ track, played_at: trackMock.playedAt!, context: { @@ -63,6 +61,6 @@ describe('TracksAdapter', () => { })) ) ) - ).toEqual(spotifyResponseWithCursorsMockFactory(tracksMock)) + ).toEqual(recentlyPlayedTracksPageMockFactory(tracksMock)) }) }) diff --git a/src/common/adapters/tracks.adapter.ts b/src/common/adapters/tracks.adapter.ts index 2cc2f5cd..e6050d73 100644 --- a/src/common/adapters/tracks.adapter.ts +++ b/src/common/adapters/tracks.adapter.ts @@ -1,41 +1,36 @@ import { Injectable } from '@nestjs/common' -import { - Page, - RecentlyPlayedTracksPage, - Track as SpotifyTrack, -} from '@spotify/web-api-ts-sdk' +import { Page } from '@spotify/web-api-ts-sdk' -import { PaginatedAdapter } from './paginated.adapter' +import { PageAdapter } from './page.adapter' import { ArtistsAdapter } from './artists.adapter' -import { SpotifyResponseWithCursors, Track } from '@common/types/spotify' +import { + RecentlyPlayedTracksPage, + SdkRecentlyPlayedTracksPage, + SdkTrack, + Track, +} from '@common/types/spotify' @Injectable() export class TracksAdapter { constructor( - private readonly paginatedAdapter: PaginatedAdapter, + private readonly pageAdapter: PageAdapter, private readonly artistsAdapter: ArtistsAdapter ) {} - public adapt(data: SpotifyTrack): Track - public adapt(data: SpotifyTrack[]): Track[] - public adapt(data: Page): Page - public adapt( - data: RecentlyPlayedTracksPage - ): SpotifyResponseWithCursors + public adapt(data: SdkTrack): Track + public adapt(data: SdkTrack[]): Track[] + public adapt(data: Page): Page + public adapt(data: SdkRecentlyPlayedTracksPage): RecentlyPlayedTracksPage adapt( - data: - | SpotifyTrack - | SpotifyTrack[] - | Page - | RecentlyPlayedTracksPage + data: SdkTrack | SdkTrack[] | Page | SdkRecentlyPlayedTracksPage ) { if (Array.isArray(data)) return this.adaptTracks(data) if ('items' in data) { - if ('cursors' in data) return this.adaptRecentlyPlayed(data) + if ('cursors' in data) return this.adaptRecentlyPlayedTracksPage(data) - return this.adaptPaginatedTracks(data) + return this.adaptTracksPage(data) } return this.adaptTrack(data) @@ -48,7 +43,7 @@ export class TracksAdapter { artists, external_urls: { spotify: href }, duration_ms, - }: SpotifyTrack): Track => ({ + }: SdkTrack): Track => ({ id, name, album: { @@ -65,21 +60,21 @@ export class TracksAdapter { duration: duration_ms, }) - adaptTracks(tracks: SpotifyTrack[]): Track[] { + adaptTracks(tracks: SdkTrack[]): Track[] { return tracks.map(track => this.adaptTrack(track)) } - adaptPaginatedTracks = (data: Page): Page => { - return this.paginatedAdapter.adapt(data, tracks => this.adaptTracks(tracks)) + adaptTracksPage = (data: Page): Page => { + return this.pageAdapter.adapt(data, tracks => this.adaptTracks(tracks)) } - adaptRecentlyPlayed({ + adaptRecentlyPlayedTracksPage({ limit, next, href, cursors, items, - }: RecentlyPlayedTracksPage): SpotifyResponseWithCursors { + }: SdkRecentlyPlayedTracksPage): RecentlyPlayedTracksPage { return { limit, next, diff --git a/src/common/mocks/album.mock.ts b/src/common/mocks/album.mock.ts index afb329d2..ce100617 100644 --- a/src/common/mocks/album.mock.ts +++ b/src/common/mocks/album.mock.ts @@ -1,18 +1,16 @@ -import { SimplifiedAlbum, Album as SpotifyAlbum } from '@spotify/web-api-ts-sdk' - import { - spotifyArtistsMock, - spotifyTrackArtistsMock, - trackArtistsMock, + sdkArtistsMock, + sdkSimplifiedArtistsMock, + simplifiedArtistsMock, } from './artist.mock' import { imagesMock } from './image.mock' -import { spotifySimplifiedTracksMock } from './track.mock' -import { spotifyResponseWithOffsetMockFactory } from './spotify-response.mock' +import { sdkSimplifiedTracksMock } from './track.mock' +import { pageMockFactory } from './page.mock' import { topGenresArrayMock } from './genres.mock' -import { Album } from '@common/types/spotify' +import { Album, SdkAlbum, SdkSimplifiedAlbum } from '@common/types/spotify' -export const spotifySimplifiedAlbumMock: SimplifiedAlbum = { +export const sdkSimplifiedAlbumMock: SdkSimplifiedAlbum = { album_type: 'album', album_group: 'album', copyrights: [ @@ -28,7 +26,7 @@ export const spotifySimplifiedAlbumMock: SimplifiedAlbum = { genres: topGenresArrayMock, label: 'Beatservice Records', popularity: 36, - artists: spotifyTrackArtistsMock, + artists: sdkSimplifiedArtistsMock, available_markets: [ 'AD', 'AE', @@ -68,28 +66,26 @@ export const spotifySimplifiedAlbumMock: SimplifiedAlbum = { }, } -export const spotifySimplifiedAlbumsMock = Array.from({ length: 5 }).map( - () => spotifySimplifiedAlbumMock +export const sdkSimplifiedAlbumsMock = Array.from({ length: 5 }).map( + () => sdkSimplifiedAlbumMock ) -export const spotifyAlbumMock: SpotifyAlbum = { - ...spotifySimplifiedAlbumMock, - tracks: spotifyResponseWithOffsetMockFactory(spotifySimplifiedTracksMock), - artists: spotifyArtistsMock, +export const sdkAlbumMock: SdkAlbum = { + ...sdkSimplifiedAlbumMock, + tracks: pageMockFactory(sdkSimplifiedTracksMock), + artists: sdkArtistsMock, } -export const spotifyAlbumsMock = Array.from({ length: 5 }).map( - () => spotifyAlbumMock -) +export const sdkAlbumsMock = Array.from({ length: 5 }).map(() => sdkAlbumMock) export const albumMock: Album = { - id: spotifyAlbumMock.id, - name: spotifyAlbumMock.name, - artists: trackArtistsMock, + id: sdkAlbumMock.id, + name: sdkAlbumMock.name, + artists: simplifiedArtistsMock, images: imagesMock, - releaseDate: spotifyAlbumMock.release_date, - totalTracks: spotifyAlbumMock.total_tracks, - href: spotifyAlbumMock.external_urls.spotify, + releaseDate: sdkAlbumMock.release_date, + totalTracks: sdkAlbumMock.total_tracks, + href: sdkAlbumMock.external_urls.spotify, } export const albumsMock = Array.from({ length: 5 }).map(() => albumMock) diff --git a/src/common/mocks/artist.mock.ts b/src/common/mocks/artist.mock.ts index db39cbfc..8aa36253 100644 --- a/src/common/mocks/artist.mock.ts +++ b/src/common/mocks/artist.mock.ts @@ -1,13 +1,13 @@ import { - Artist as SpotifyArtist, + Artist, + SdkArtist, + SdkSimplifiedArtist, SimplifiedArtist, -} from '@spotify/web-api-ts-sdk' - -import { Artist, TrackArtist } from '../types/spotify' +} from '../types/spotify' import { imagesMock } from './image.mock' -export const spotifyArtistMock: SpotifyArtist = { +export const sdkArtistMock: SdkArtist = { external_urls: { spotify: 'https://open.spotify.com/artist/7kWnE981vITXDnAD2cZmCV', }, @@ -33,40 +33,38 @@ export const spotifyArtistMock: SpotifyArtist = { uri: 'spotify:artist:7kWnE981vITXDnAD2cZmCV', } -export const spotifyArtistsMock = Array.from({ length: 5 }).map( - () => spotifyArtistMock -) +export const sdkArtistsMock = Array.from({ length: 5 }).map(() => sdkArtistMock) export const artistMock: Artist = { - id: spotifyArtistMock.id, - name: spotifyArtistMock.name, - genres: spotifyArtistMock.genres, - href: spotifyArtistMock.external_urls.spotify, + id: sdkArtistMock.id, + name: sdkArtistMock.name, + genres: sdkArtistMock.genres, + href: sdkArtistMock.external_urls.spotify, images: imagesMock, - popularity: spotifyArtistMock.popularity, + popularity: sdkArtistMock.popularity, } export const artistsMock = Array.from({ length: 5 }).map(() => artistMock) -export const spotifyTrackArtistMock: SimplifiedArtist = { - external_urls: spotifyArtistMock.external_urls, - href: spotifyArtistMock.href, - id: spotifyArtistMock.id, - name: spotifyArtistMock.name, - type: spotifyArtistMock.type, - uri: spotifyArtistMock.uri, +export const sdkSimplifiedArtistMock: SdkSimplifiedArtist = { + external_urls: sdkArtistMock.external_urls, + href: sdkArtistMock.href, + id: sdkArtistMock.id, + name: sdkArtistMock.name, + type: sdkArtistMock.type, + uri: sdkArtistMock.uri, } -export const spotifyTrackArtistsMock = Array.from({ length: 5 }).map( - () => spotifyTrackArtistMock +export const sdkSimplifiedArtistsMock = Array.from({ length: 5 }).map( + () => sdkSimplifiedArtistMock ) -export const trackArtistMock: TrackArtist = { - id: spotifyTrackArtistMock.id, - name: spotifyTrackArtistMock.name, - href: spotifyTrackArtistMock.external_urls.spotify, +export const simplifiedArtistMock: SimplifiedArtist = { + id: sdkSimplifiedArtistMock.id, + name: sdkSimplifiedArtistMock.name, + href: sdkSimplifiedArtistMock.external_urls.spotify, } -export const trackArtistsMock = Array.from({ length: 5 }).map( - () => trackArtistMock +export const simplifiedArtistsMock = Array.from({ length: 5 }).map( + () => simplifiedArtistMock ) diff --git a/src/common/mocks/audio-features.mock.ts b/src/common/mocks/audio-features.mock.ts index 7e337922..50c49c84 100644 --- a/src/common/mocks/audio-features.mock.ts +++ b/src/common/mocks/audio-features.mock.ts @@ -1,6 +1,6 @@ -import { AudioFeatures, SpotifyAudioFeatures, Analysis } from '../types/spotify' +import { AudioFeatures, SdkAudioFeatures, Analysis } from '../types/spotify' -export const spotifyAudioFeaturesMock: SpotifyAudioFeatures = { +export const sdkAudioFeaturesMock: SdkAudioFeatures = { acousticness: 0.016, analysis_url: 'https://api.spotify.com/v1/audio-analysis/2JIRtFAIUkd86PQD12Hm7r', @@ -23,21 +23,21 @@ export const spotifyAudioFeaturesMock: SpotifyAudioFeatures = { } export const analysisMock: Analysis = { - acousticness: spotifyAudioFeaturesMock.acousticness, - danceability: spotifyAudioFeaturesMock.danceability, - energy: spotifyAudioFeaturesMock.energy, - instrumentalness: spotifyAudioFeaturesMock.instrumentalness, - liveness: spotifyAudioFeaturesMock.liveness, - loudness: spotifyAudioFeaturesMock.loudness, - speechiness: spotifyAudioFeaturesMock.speechiness, - tempo: spotifyAudioFeaturesMock.tempo, - valence: spotifyAudioFeaturesMock.valence, - mode: spotifyAudioFeaturesMock.mode, - key: spotifyAudioFeaturesMock.key, + acousticness: sdkAudioFeaturesMock.acousticness, + danceability: sdkAudioFeaturesMock.danceability, + energy: sdkAudioFeaturesMock.energy, + instrumentalness: sdkAudioFeaturesMock.instrumentalness, + liveness: sdkAudioFeaturesMock.liveness, + loudness: sdkAudioFeaturesMock.loudness, + speechiness: sdkAudioFeaturesMock.speechiness, + tempo: sdkAudioFeaturesMock.tempo, + valence: sdkAudioFeaturesMock.valence, + mode: sdkAudioFeaturesMock.mode, + key: sdkAudioFeaturesMock.key, } export const audioFeaturesMock: AudioFeatures = { - id: spotifyAudioFeaturesMock.id, - trackHref: spotifyAudioFeaturesMock.track_href, + id: sdkAudioFeaturesMock.id, + trackHref: sdkAudioFeaturesMock.track_href, ...analysisMock, } diff --git a/src/common/mocks/device.mock.ts b/src/common/mocks/device.mock.ts index f8f966c1..6fa7e40e 100644 --- a/src/common/mocks/device.mock.ts +++ b/src/common/mocks/device.mock.ts @@ -1,6 +1,6 @@ -import { Device, SpotifyDevice } from '../types/spotify' +import { Device, SdkDevice } from '../types/spotify' -export const spotifyDeviceMock: SpotifyDevice = { +export const sdkDeviceMock: SdkDevice = { id: 'id', is_active: true, is_private_session: false, @@ -10,19 +10,19 @@ export const spotifyDeviceMock: SpotifyDevice = { volume_percent: 100, } -export const spotifyDevicesMock: SpotifyDevice[] = Array.from( +export const sdkDevicesMock: SdkDevice[] = Array.from( { length: 5 }, - () => spotifyDeviceMock + () => sdkDeviceMock ) export const deviceMock: Device = { - id: spotifyDeviceMock.id, - name: spotifyDeviceMock.name, - type: spotifyDeviceMock.type, - isActive: spotifyDeviceMock.is_active, - isPrivateSession: spotifyDeviceMock.is_private_session, - isRestricted: spotifyDeviceMock.is_restricted, - volumePercent: spotifyDeviceMock.volume_percent, + id: sdkDeviceMock.id, + name: sdkDeviceMock.name, + type: sdkDeviceMock.type, + isActive: sdkDeviceMock.is_active, + isPrivateSession: sdkDeviceMock.is_private_session, + isRestricted: sdkDeviceMock.is_restricted, + volumePercent: sdkDeviceMock.volume_percent, } export const devicesMock: Device[] = Array.from({ length: 5 }, () => deviceMock) diff --git a/src/common/mocks/image.mock.ts b/src/common/mocks/image.mock.ts index 8439dc52..a64838cd 100644 --- a/src/common/mocks/image.mock.ts +++ b/src/common/mocks/image.mock.ts @@ -1,8 +1,8 @@ -import { SpotifyImage } from '../types/spotify' +import { SdkImage } from '../types/spotify' import { Image } from '@modules/images' -export const spotifyImageMock: SpotifyImage = { +export const sdkImageMock: SdkImage = { height: 300, url: 'https://i.scdn.co/image/ab67616d00001e023f1900e26ff44e8821bd8350', width: 300, @@ -10,11 +10,8 @@ export const spotifyImageMock: SpotifyImage = { export const imageMock: Image = { id: '123', - ...spotifyImageMock, + ...sdkImageMock, } -export const spotifyImagesMock = Array.from( - { length: 3 }, - () => spotifyImageMock -) +export const sdkImagesMock = Array.from({ length: 3 }, () => sdkImageMock) export const imagesMock = Array.from({ length: 3 }, () => imageMock) diff --git a/src/common/mocks/index.ts b/src/common/mocks/index.ts index 974d84c4..7008fd40 100644 --- a/src/common/mocks/index.ts +++ b/src/common/mocks/index.ts @@ -5,7 +5,7 @@ export * from './device.mock' export * from './playback-state.mock' export * from './genres.mock' export * from './audio-features.mock' -export * from './spotify-response.mock' +export * from './page.mock' export * from './image.mock' export * from './user.mock' export * from './axios.factory.mock' diff --git a/src/common/mocks/page.mock.ts b/src/common/mocks/page.mock.ts new file mode 100644 index 00000000..43bef2db --- /dev/null +++ b/src/common/mocks/page.mock.ts @@ -0,0 +1,44 @@ +import { Page, PlayHistory } from '@spotify/web-api-ts-sdk' + +import { + RecentlyPlayedTracksPage, + SdkRecentlyPlayedTracksPage, + Track, +} from '@common/types/spotify' + +export const pageMockFactory = (items: TItems[]): Page => ({ + href: 'https://api.spotify.com/v1/search?query=metallica&type=artist&offset=0&limit=20', + limit: 20, + next: 'https://api.spotify.com/v1/search?query=metallica&type=artist&offset=20&limit=20', + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + total: items?.length ?? 0, + items, + previous: + 'https://api.spotify.com/v1/search?query=metallica&type=artist&offset=0&limit=20', + offset: 0, +}) + +export function recentlyPlayedTracksPageMockFactory( + items: Track[] +): RecentlyPlayedTracksPage +export function recentlyPlayedTracksPageMockFactory( + items: PlayHistory[] +): SdkRecentlyPlayedTracksPage + +export function recentlyPlayedTracksPageMockFactory( + items: Track[] | PlayHistory[] +): RecentlyPlayedTracksPage | SdkRecentlyPlayedTracksPage { + // @ts-expect-error - this is a mock + return { + href: 'https://api.spotify.com/v1/search?query=metallica&type=artist&offset=0&limit=20', + limit: 20, + next: 'https://api.spotify.com/v1/search?query=metallica&type=artist&offset=20&limit=20', + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + total: items?.length ?? 0, + items, + cursors: { + after: '1693946946214', + before: '1693946946214', + }, + } +} diff --git a/src/common/mocks/playback-state.mock.ts b/src/common/mocks/playback-state.mock.ts index 1eb0cdde..27f75fc6 100644 --- a/src/common/mocks/playback-state.mock.ts +++ b/src/common/mocks/playback-state.mock.ts @@ -2,14 +2,14 @@ import { PlaybackState as SpotifyPlaybackState } from '@spotify/web-api-ts-sdk' import { PlaybackState, RepeatedState } from '../types/spotify' -import { deviceMock, trackMock, spotifyDeviceMock, spotifyTrackMock } from '.' +import { deviceMock, trackMock, sdkDeviceMock, sdkTrackMock } from '.' -export const spotifyPlaybackStateMock: SpotifyPlaybackState = { - device: spotifyDeviceMock, +export const sdkPlaybackStateMock: SpotifyPlaybackState = { + device: sdkDeviceMock, repeat_state: RepeatedState.OFF, shuffle_state: false, is_playing: true, - item: spotifyTrackMock, + item: sdkTrackMock, context: null, timestamp: 0, progress_ms: 0, @@ -19,8 +19,8 @@ export const spotifyPlaybackStateMock: SpotifyPlaybackState = { export const playbackStateMock: PlaybackState = { device: deviceMock, - repeatState: spotifyPlaybackStateMock.repeat_state as RepeatedState, - shuffleState: spotifyPlaybackStateMock.shuffle_state, - isPlaying: spotifyPlaybackStateMock.is_playing, + repeatState: sdkPlaybackStateMock.repeat_state as RepeatedState, + shuffleState: sdkPlaybackStateMock.shuffle_state, + isPlaying: sdkPlaybackStateMock.is_playing, track: trackMock, } diff --git a/src/common/mocks/profile.mock.ts b/src/common/mocks/profile.mock.ts index 57cd1625..dd9bb6f6 100644 --- a/src/common/mocks/profile.mock.ts +++ b/src/common/mocks/profile.mock.ts @@ -4,7 +4,7 @@ import { imagesMock } from './image.mock' import { Profile } from '@modules/profiles' -export const spotifyProfileMock: UserProfile = { +export const sdkProfileMock: UserProfile = { country: 'US', display_name: 'Spotify User', email: 'spotify-user@example.com', @@ -28,16 +28,16 @@ export const spotifyProfileMock: UserProfile = { } export const profileMock: Profile = { - id: spotifyProfileMock.id, - displayName: spotifyProfileMock.display_name, - followers: spotifyProfileMock.followers.total, + id: sdkProfileMock.id, + displayName: sdkProfileMock.display_name, + followers: sdkProfileMock.followers.total, images: imagesMock, - href: spotifyProfileMock.external_urls.spotify, - type: spotifyProfileMock.type, - uri: spotifyProfileMock.uri, - product: spotifyProfileMock.product, - email: spotifyProfileMock.email, - country: spotifyProfileMock.country, + href: sdkProfileMock.external_urls.spotify, + type: sdkProfileMock.type, + uri: sdkProfileMock.uri, + product: sdkProfileMock.product, + email: sdkProfileMock.email, + country: sdkProfileMock.country, } export const profilesMock = Array.from({ length: 3 }, () => profileMock) diff --git a/src/common/mocks/spotify-response.mock.ts b/src/common/mocks/spotify-response.mock.ts deleted file mode 100644 index fa1ce3df..00000000 --- a/src/common/mocks/spotify-response.mock.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Page } from '@spotify/web-api-ts-sdk' - -import { - SpotifyResponse, - SpotifyResponseWithCursors, -} from '@common/types/spotify' - -export const spotifyResponseMockFactory = ( - items: TItems[] -): SpotifyResponse => { - console.log(items) - - return { - href: 'https://api.spotify.com/v1/search?query=metallica&type=artist&offset=0&limit=20', - limit: 20, - next: 'https://api.spotify.com/v1/search?query=metallica&type=artist&offset=20&limit=20', - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - total: items?.length ?? 0, - items, - } -} - -export const spotifyResponseWithOffsetMockFactory = ( - items: TItems[] -): Page => ({ - ...spotifyResponseMockFactory(items), - previous: - 'https://api.spotify.com/v1/search?query=metallica&type=artist&offset=0&limit=20', - offset: 0, -}) - -export const spotifyResponseWithCursorsMockFactory = ( - items: TItems[] -): SpotifyResponseWithCursors => ({ - ...spotifyResponseMockFactory(items), - cursors: { - after: '1693946946214', - before: '1693946946214', - }, -}) diff --git a/src/common/mocks/track.mock.ts b/src/common/mocks/track.mock.ts index 66e727eb..fe336260 100644 --- a/src/common/mocks/track.mock.ts +++ b/src/common/mocks/track.mock.ts @@ -2,13 +2,13 @@ import { SimplifiedTrack, Track as SpotifyTrack } from '@spotify/web-api-ts-sdk' -import { albumMock, spotifySimplifiedAlbumMock } from './album.mock' -import { spotifyTrackArtistsMock, trackArtistsMock } from './artist.mock' +import { albumMock, sdkSimplifiedAlbumMock } from './album.mock' +import { sdkSimplifiedArtistsMock, simplifiedArtistsMock } from './artist.mock' import { Track } from '@common/types/spotify' -export const spotifySimplifiedTrackMock: SimplifiedTrack = { - artists: spotifyTrackArtistsMock, +export const sdkSimplifiedTrackMock: SimplifiedTrack = { + artists: sdkSimplifiedArtistsMock, track: true, episode: false, available_markets: [ @@ -48,13 +48,13 @@ export const spotifySimplifiedTrackMock: SimplifiedTrack = { uri: 'spotify:track:5O6MFTh1rd9PeN8XEn1yCS', } -export const spotifySimplifiedTracksMock = Array.from({ length: 5 }).map( - () => spotifySimplifiedTrackMock +export const sdkSimplifiedTracksMock = Array.from({ length: 5 }).map( + () => sdkSimplifiedTrackMock ) -export const spotifyTrackMock: SpotifyTrack = { - ...spotifySimplifiedTrackMock, - album: spotifySimplifiedAlbumMock, +export const sdkTrackMock: SpotifyTrack = { + ...sdkSimplifiedTrackMock, + album: sdkSimplifiedAlbumMock, external_ids: { isrc: 'GBBPC9700031', upc: '5051083100020', @@ -63,17 +63,15 @@ export const spotifyTrackMock: SpotifyTrack = { popularity: 43, } -export const spotifyTracksMock = Array.from({ length: 5 }).map( - () => spotifyTrackMock -) +export const sdkTracksMock = Array.from({ length: 5 }).map(() => sdkTrackMock) export const trackMock: Track = { - id: spotifyTrackMock.id, - artists: trackArtistsMock, + id: sdkTrackMock.id, + artists: simplifiedArtistsMock, album: albumMock, - name: spotifyTrackMock.name, - duration: spotifyTrackMock.duration_ms, - href: spotifyTrackMock.external_urls.spotify, + name: sdkTrackMock.name, + duration: sdkTrackMock.duration_ms, + href: sdkTrackMock.external_urls.spotify, } export const tracksMock = Array.from({ length: 5 }).map(() => trackMock) diff --git a/src/common/types/spotify/album.ts b/src/common/types/spotify/album.ts index ce7cd87f..b69ada92 100644 --- a/src/common/types/spotify/album.ts +++ b/src/common/types/spotify/album.ts @@ -1,27 +1,18 @@ -import { SpotifyImage, SpotifyTrackArtist, TrackArtist } from '.' +import { SimplifiedArtist } from './artist' + +import { SdkImage } from '.' export interface Album { id: string - artists: TrackArtist[] + artists: SimplifiedArtist[] name: string - images: SpotifyImage[] + images: SdkImage[] releaseDate: string totalTracks: number href: string } -export interface SpotifyAlbum { - album_type: string - artists: SpotifyTrackArtist[] - available_markets: string[] - external_urls: { spotify: string } - href: string - id: string - images: SpotifyImage[] - name: string - type: string - uri: string - release_date: string - release_date_precision: string - total_tracks: number -} +export { + Album as SdkAlbum, + SimplifiedAlbum as SdkSimplifiedAlbum, +} from '@spotify/web-api-ts-sdk' diff --git a/src/common/types/spotify/artist.ts b/src/common/types/spotify/artist.ts index 3cd00752..725087fc 100644 --- a/src/common/types/spotify/artist.ts +++ b/src/common/types/spotify/artist.ts @@ -1,38 +1,21 @@ -import { SpotifyImage } from '.' +import { SdkImage } from '.' export interface Artist { id: string name: string genres: string[] href: string - images: SpotifyImage[] + images: SdkImage[] popularity: number } -export interface SpotifyTrackArtist { - name: string - href: string - external_urls: { spotify: string } - id: string - type: string - uri: string -} - -export interface TrackArtist { +export interface SimplifiedArtist { name: string id: string href: string } -export interface SpotifyArtist { - external_urls: { spotify: string } - followers: { href: string | null; total: number } - genres: string[] - href: string - id: string - images: SpotifyImage[] - name: string - popularity: number - type: string - uri: string -} +export { + Artist as SdkArtist, + SimplifiedArtist as SdkSimplifiedArtist, +} from '@spotify/web-api-ts-sdk' diff --git a/src/common/types/spotify/audio-features.ts b/src/common/types/spotify/audio-features.ts index 1d82f52e..a3ec9f83 100644 --- a/src/common/types/spotify/audio-features.ts +++ b/src/common/types/spotify/audio-features.ts @@ -1,24 +1,3 @@ -export interface SpotifyAudioFeatures { - acousticness: number - analysis_url: string - danceability: number - duration_ms: number - energy: number - id: string - instrumentalness: number - key: number - liveness: number - loudness: number - mode: number - speechiness: number - tempo: number - time_signature: number - track_href: string - type: string - uri: string - valence: number -} - export interface AudioFeatures extends Analysis { id: string trackHref: string @@ -37,3 +16,5 @@ export interface Analysis { key: number valence: number } + +export { AudioFeatures as SdkAudioFeatures } from '@spotify/web-api-ts-sdk' diff --git a/src/common/types/spotify/device.ts b/src/common/types/spotify/device.ts index 1c9e9e45..83970aa1 100644 --- a/src/common/types/spotify/device.ts +++ b/src/common/types/spotify/device.ts @@ -8,12 +8,4 @@ export interface Device { volumePercent: number | null } -export interface SpotifyDevice { - id: string | null - is_active: boolean - is_private_session: boolean - is_restricted: boolean - name: string - type: string - volume_percent: number | null -} +export { Device as SdkDevice } from '@spotify/web-api-ts-sdk' diff --git a/src/common/types/spotify/image.ts b/src/common/types/spotify/image.ts deleted file mode 100644 index d0dda42d..00000000 --- a/src/common/types/spotify/image.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface SpotifyImage { - height: number - width: number - url: string -} diff --git a/src/common/types/spotify/index.ts b/src/common/types/spotify/index.ts index f1a1b3e8..feab49b3 100644 --- a/src/common/types/spotify/index.ts +++ b/src/common/types/spotify/index.ts @@ -1,12 +1,12 @@ -export * from './image' -export * from './spotify-response' +export * from './page' export * from './album' export * from './artist' export * from './track' export * from './profile' -export * from './spotify-token' export * from './device' export * from './playback-state' export * from './audio-features' export * from './cursors' export * from './genres' + +export { Image as SdkImage } from '@spotify/web-api-ts-sdk' diff --git a/src/common/types/spotify/page.ts b/src/common/types/spotify/page.ts new file mode 100644 index 00000000..58f0d4c8 --- /dev/null +++ b/src/common/types/spotify/page.ts @@ -0,0 +1,14 @@ +import { Cursors } from './cursors' + +import { Track } from '@common/types/spotify' + +export interface RecentlyPlayedTracksPage { + href: string + limit: number + next: string | null + total: number + items: Track[] + cursors: Cursors +} + +export { RecentlyPlayedTracksPage as SdkRecentlyPlayedTracksPage } from '@spotify/web-api-ts-sdk' diff --git a/src/common/types/spotify/playback-state.ts b/src/common/types/spotify/playback-state.ts index 9208b596..1552810d 100644 --- a/src/common/types/spotify/playback-state.ts +++ b/src/common/types/spotify/playback-state.ts @@ -1,6 +1,4 @@ -import { Episode } from '@spotify/web-api-ts-sdk' - -import { Device, Track, SpotifyDevice, SpotifyTrack } from '.' +import { Device, Track } from '.' export enum RepeatedState { TRACK = 'track', @@ -8,14 +6,6 @@ export enum RepeatedState { OFF = 'off', } -export interface SpotifyPlaybackState { - device?: SpotifyDevice - repeat_state?: string - shuffle_state?: boolean - is_playing?: boolean - item?: SpotifyTrack | Episode -} - export interface PlaybackState { device?: Device repeatState?: RepeatedState @@ -23,3 +13,5 @@ export interface PlaybackState { isPlaying?: boolean track?: Track } + +export { PlaybackState as SdkPlaybackState } from '@spotify/web-api-ts-sdk' diff --git a/src/common/types/spotify/profile.ts b/src/common/types/spotify/profile.ts index cbdf19c1..5ad89f16 100644 --- a/src/common/types/spotify/profile.ts +++ b/src/common/types/spotify/profile.ts @@ -1,9 +1,9 @@ -import { SpotifyImage } from './image' +import { SdkImage } from '.' export interface Profile { id: string displayName: string - images?: SpotifyImage[] + images?: SdkImage[] followers: number country?: string email?: string @@ -13,25 +13,4 @@ export interface Profile { uri: string } -export interface SpotifyProfile { - country?: string - display_name?: string - email?: string - explicit_content?: { - filter_enabled: boolean - filter_locked: boolean - } - external_urls: { - spotify: string - } - followers: { - href?: string | null - total: number - } - href: string - id: string - images: SpotifyImage[] - product?: string - type: string - uri: string -} +export { UserProfile as SdkProfile } from '@spotify/web-api-ts-sdk' diff --git a/src/common/types/spotify/spotify-response.ts b/src/common/types/spotify/spotify-response.ts deleted file mode 100644 index 952f14e5..00000000 --- a/src/common/types/spotify/spotify-response.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Cursors } from './cursors' - -export interface SpotifyResponse { - href: string - limit: number - next: string | null - total: number - items: TItems[] -} - -export interface SpotifyResponseWithOffset - extends SpotifyResponse { - offset: number - previous: string | null -} - -export interface SpotifyResponseWithCursors - extends SpotifyResponse { - cursors: Cursors -} diff --git a/src/common/types/spotify/spotify-token.ts b/src/common/types/spotify/spotify-token.ts deleted file mode 100644 index 6c86357e..00000000 --- a/src/common/types/spotify/spotify-token.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface SpotifyToken { - access_token: string - token_type: string - scope: string - expires_in: number - refresh_token: string -} diff --git a/src/common/types/spotify/track.ts b/src/common/types/spotify/track.ts index 9e7c9198..b4dd2c5e 100644 --- a/src/common/types/spotify/track.ts +++ b/src/common/types/spotify/track.ts @@ -1,34 +1,15 @@ -import { TrackArtist, SpotifyAlbum, SpotifyTrackArtist, Album } from '.' +import { Album } from './album' +import { SimplifiedArtist } from './artist' export interface Track { id: string name: string album: Album - artists: TrackArtist[] + artists: SimplifiedArtist[] href: string duration: number progress?: number playedAt?: string } -export interface SpotifyTrack { - name: string - href: string - type: string - uri: string - available_markets: string[] - disc_number: number - duration_ms: number - progress_ms?: number - explicit: boolean - external_urls: { spotify: string } - external_ids: { isrc: string } - id: string - is_local: boolean - popularity: number - preview_url?: string | null - track_number: number - artists: SpotifyTrackArtist[] - album: SpotifyAlbum - played_at?: string -} +export { Track as SdkTrack } from '@spotify/web-api-ts-sdk' diff --git a/src/modules/app/app.module.ts b/src/modules/app/app.module.ts index 7bcb7ad1..4b92034b 100644 --- a/src/modules/app/app.module.ts +++ b/src/modules/app/app.module.ts @@ -8,9 +8,11 @@ import { AuthModule } from '@modules/auth' import { ImagesModule } from '@modules/images' import { ProfilesModule } from '@modules/profiles' import { UsersModule } from '@modules/users' +import { AdaptersModule } from '@common/adapters' @Module({ imports: [ + AdaptersModule, AuthModule, ImagesModule, ProfilesModule, diff --git a/src/modules/auth/auth.controller.spec.ts b/src/modules/auth/auth.controller.spec.ts index 6cd01b9c..4f84f36c 100644 --- a/src/modules/auth/auth.controller.spec.ts +++ b/src/modules/auth/auth.controller.spec.ts @@ -14,6 +14,8 @@ import { userMock, } from '@common/mocks' import { SpotifyAuthService } from '@modules/spotify/auth' +import { AdaptersService } from '@common/adapters' +import { SecretDataAdapter } from '@common/adapters' describe('AuthController', () => { const redirectUrl = 'http://test.com' @@ -26,6 +28,12 @@ describe('AuthController', () => { const module: TestingModule = await Test.createTestingModule({ providers: [ AuthController, + { + provide: AdaptersService, + useValue: { + secretData: new SecretDataAdapter(), + }, + }, { provide: SpotifyAuthService, useValue: { diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index de576541..3bcc0114 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -23,7 +23,7 @@ import { AuthService } from './auth.service' import { SpotifyAuthService } from '@modules/spotify/auth' import { Environment } from '@config/environment' -import { adaptSecretData } from '@common/adapters' +import { AdaptersService } from '@common/adapters' const { SPOTIFY_CALLBACK_URL, @@ -38,7 +38,8 @@ export class AuthController { constructor( private readonly configService: ConfigService, private readonly authService: AuthService, - private readonly spotifyAuthService: SpotifyAuthService + private readonly spotifyAuthService: SpotifyAuthService, + private readonly adaptersService: AdaptersService ) {} @Get('login') @@ -92,6 +93,8 @@ export class AuthController { type: RefreshToken, }) refresh(@Body() { refreshToken }: RefreshToken) { - return this.spotifyAuthService.token({ refreshToken }).then(adaptSecretData) + return this.spotifyAuthService + .token({ refreshToken }) + .then(data => this.adaptersService.secretData.adapt(data)) } } diff --git a/src/modules/images/image.entity.ts b/src/modules/images/image.entity.ts index 7a54b30f..2e5987db 100644 --- a/src/modules/images/image.entity.ts +++ b/src/modules/images/image.entity.ts @@ -1,10 +1,10 @@ import { ApiProperty } from '@nestjs/swagger' import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm' -import { SpotifyImage } from '@common/types/spotify' +import { SdkImage } from '@common/types/spotify' @Entity() -export class Image implements SpotifyImage { +export class Image implements SdkImage { @PrimaryGeneratedColumn('uuid') @ApiProperty() id: string diff --git a/src/modules/images/images.repository.spec.ts b/src/modules/images/images.repository.spec.ts index 0207d125..9236151c 100644 --- a/src/modules/images/images.repository.spec.ts +++ b/src/modules/images/images.repository.spec.ts @@ -3,7 +3,7 @@ import { DataSource } from 'typeorm' import { ImagesRepository } from './images.repository' -import { imageMock, spotifyImageMock } from '@common/mocks' +import { imageMock, sdkImageMock } from '@common/mocks' describe('ImagesRepository', () => { let imagesRepository: ImagesRepository @@ -36,10 +36,8 @@ describe('ImagesRepository', () => { .spyOn(imagesRepository, 'save') .mockResolvedValue(imageMock) - expect(await imagesRepository.createImage(spotifyImageMock)).toEqual( - imageMock - ) - expect(createSpy).toHaveBeenCalledWith(spotifyImageMock) + expect(await imagesRepository.createImage(sdkImageMock)).toEqual(imageMock) + expect(createSpy).toHaveBeenCalledWith(sdkImageMock) expect(saveSpy).toHaveBeenCalledWith(imageMock) }) }) diff --git a/src/modules/profiles/dtos/create-profile.dto.ts b/src/modules/profiles/dtos/create-profile.dto.ts index 79c71807..861d7404 100644 --- a/src/modules/profiles/dtos/create-profile.dto.ts +++ b/src/modules/profiles/dtos/create-profile.dto.ts @@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger' import { Profile } from '../profile.entity' -import { SpotifyImage } from '@common/types/spotify' +import { SdkImage } from '@common/types/spotify' export abstract class CreateProfile implements Omit { @ApiProperty() @@ -12,7 +12,7 @@ export abstract class CreateProfile implements Omit { displayName: string @ApiProperty() - images?: SpotifyImage[] + images?: SdkImage[] @ApiProperty({ type: Number }) followers: number diff --git a/src/modules/spotify/auth/spotify-auth.service.spec.ts b/src/modules/spotify/auth/spotify-auth.service.spec.ts index 7144c837..0545868c 100644 --- a/src/modules/spotify/auth/spotify-auth.service.spec.ts +++ b/src/modules/spotify/auth/spotify-auth.service.spec.ts @@ -10,9 +10,10 @@ import { SpotifyToken } from './types/spotify' import { axiosResponseMockFactory, profileMock, - spotifyProfileMock, + sdkProfileMock, } from '@common/mocks' -import { SpotifyProfile } from '@common/types/spotify' +import { SdkProfile } from '@common/types/spotify' +import { AdaptersService, ProfileAdapter } from '@common/adapters' describe('SpotifyAuthService', () => { let spotifyAuthService: SpotifyAuthService @@ -24,6 +25,12 @@ describe('SpotifyAuthService', () => { imports: [HttpModule], providers: [ SpotifyAuthService, + { + provide: AdaptersService, + useValue: { + profile: new ProfileAdapter(), + }, + }, { provide: ConfigService, useValue: { @@ -81,9 +88,7 @@ describe('SpotifyAuthService', () => { test('should get me profile', async () => { const getSpy = vi .spyOn(httpService, 'get') - .mockReturnValue( - of(axiosResponseMockFactory(spotifyProfileMock)) - ) + .mockReturnValue(of(axiosResponseMockFactory(sdkProfileMock))) expect(await spotifyAuthService.getMeProfile('token')).toEqual(profileMock) expect(getSpy).toHaveBeenCalled() diff --git a/src/modules/spotify/auth/spotify-auth.service.ts b/src/modules/spotify/auth/spotify-auth.service.ts index 8ee0d526..ca26b39c 100644 --- a/src/modules/spotify/auth/spotify-auth.service.ts +++ b/src/modules/spotify/auth/spotify-auth.service.ts @@ -8,14 +8,15 @@ import { TokenOptions } from './types' import { Environment } from '@config/environment' import { applyAuthorizationHeader, catchSpotifyError } from '@common/utils' -import { Profile, SpotifyProfile } from '@common/types/spotify' -import { adaptProfile } from '@common/adapters' +import { Profile, SdkProfile } from '@common/types/spotify' +import { AdaptersService } from '@common/adapters' @Injectable() export class SpotifyAuthService { constructor( private readonly httpService: HttpService, - private readonly configService: ConfigService + private readonly configService: ConfigService, + private readonly adaptersService: AdaptersService ) {} token({ refreshToken, code }: TokenOptions) { @@ -65,10 +66,9 @@ export class SpotifyAuthService { getMeProfile(accessToken: string): Promise { return firstValueFrom( this.httpService - .get('/me', applyAuthorizationHeader(accessToken)) + .get('/me', applyAuthorizationHeader(accessToken)) .pipe( - map(response => response.data), - map(adaptProfile), + map(response => this.adaptersService.profile.adapt(response.data)), catchError(catchSpotifyError) ) ) diff --git a/src/modules/spotify/player/spotify-player.service.ts b/src/modules/spotify/player/spotify-player.service.ts index a97611cd..a6d374e3 100644 --- a/src/modules/spotify/player/spotify-player.service.ts +++ b/src/modules/spotify/player/spotify-player.service.ts @@ -3,13 +3,16 @@ import { ConfigService } from '@nestjs/config' import { AccessToken, MaxInt, SpotifyApi } from '@spotify/web-api-ts-sdk' import { Environment } from '@config/environment' -import { adaptPlaybackState, adaptTrack } from '@common/adapters' +import { AdaptersService } from '@common/adapters' @Injectable() export class SpotifyPlayerService { private spotifySdk: SpotifyApi | undefined - constructor(private readonly configService: ConfigService) {} + constructor( + private readonly configService: ConfigService, + private readonly adaptersService: AdaptersService + ) {} async getRecentlyPlayedTracks( token: AccessToken, @@ -37,12 +40,7 @@ export class SpotifyPlayerService { } : undefined ) - .then(({ items, ...rest }) => ({ - ...rest, - items: items.map(({ track, played_at }) => - adaptTrack({ ...track, played_at }) - ), - })) + .then(data => this.adaptersService.tracks.adapt(data)) } async getPlaybackState(token: AccessToken) { @@ -51,7 +49,9 @@ export class SpotifyPlayerService { token ) - return this.spotifySdk.player.getPlaybackState().then(adaptPlaybackState) + return this.spotifySdk.player + .getPlaybackState() + .then(data => this.adaptersService.playbackState.adapt(data)) } async pausePlayback(token: AccessToken) { diff --git a/src/modules/spotify/users/spotify-users.service.ts b/src/modules/spotify/users/spotify-users.service.ts index 3edbaba8..19cbd50f 100644 --- a/src/modules/spotify/users/spotify-users.service.ts +++ b/src/modules/spotify/users/spotify-users.service.ts @@ -6,19 +6,16 @@ import { analysisFactory } from './utils' import { TimeRange } from './enums' import { Environment } from '@config/environment' -import { - adaptAudioFeatures, - adaptGenres, - adaptPaginatedArtists, - adaptPaginatedTracks, - adaptProfile, -} from '@common/adapters' +import { AdaptersService } from '@common/adapters' @Injectable() export class SpotifyUsersService { private spotifySdk: SpotifyApi | undefined - constructor(private readonly configService: ConfigService) {} + constructor( + private readonly configService: ConfigService, + private readonly adaptersService: AdaptersService + ) {} async profile(token: AccessToken) { this.spotifySdk = SpotifyApi.withAccessToken( @@ -26,7 +23,9 @@ export class SpotifyUsersService { token ) - return this.spotifySdk.currentUser.profile().then(adaptProfile) + return this.spotifySdk.currentUser + .profile() + .then(data => this.adaptersService.profile.adapt(data)) } async getTopArtists( @@ -42,7 +41,7 @@ export class SpotifyUsersService { return this.spotifySdk.currentUser .topItems('artists', timeRange, limit, offset) - .then(adaptPaginatedArtists) + .then(data => this.adaptersService.artists.adapt(data)) } async getTopTracks( @@ -58,7 +57,7 @@ export class SpotifyUsersService { return this.spotifySdk.currentUser .topItems('tracks', timeRange, limit, offset) - .then(adaptPaginatedTracks) + .then(data => this.adaptersService.tracks.adapt(data)) } async getTopGenres( @@ -74,7 +73,7 @@ export class SpotifyUsersService { return this.spotifySdk.currentUser .topItems('artists', timeRange, 50, offset) - .then(({ items }) => adaptGenres(items, limit)) + .then(({ items }) => this.adaptersService.genres.adapt(items, limit)) } async getAnalysis(token: AccessToken, timeRange = TimeRange.LONG_TERM) { @@ -83,15 +82,18 @@ export class SpotifyUsersService { token ) - const { items } = await this.spotifySdk.currentUser - .topItems('tracks', timeRange, 50, 0) - .then(adaptPaginatedTracks) + const { items } = await this.spotifySdk.currentUser.topItems( + 'tracks', + timeRange, + 50, + 0 + ) const tracksIds = items.map(({ id }) => id) return this.spotifySdk.tracks .audioFeatures(tracksIds) - .then(audioFeatures => audioFeatures.map(adaptAudioFeatures)) + .then(data => this.adaptersService.audioFeatures.adapt(data)) .then(analysisFactory) } } diff --git a/src/modules/users/users-profile.controller.spec.ts b/src/modules/users/users-profile.controller.spec.ts index 03ca599d..5e725e01 100644 --- a/src/modules/users/users-profile.controller.spec.ts +++ b/src/modules/users/users-profile.controller.spec.ts @@ -6,10 +6,10 @@ import { UsersRepository } from './users.repository' import { TimeRange } from '@modules/spotify/users/enums' import { - spotifyResponseWithCursorsMockFactory, + recentlyPlayedTracksPageMockFactory, tracksMock, topGenresMock, - spotifyResponseWithOffsetMockFactory, + pageMockFactory, artistsMock, analysisMock, userMock, @@ -126,10 +126,10 @@ describe('UsersProfileController', () => { .mockResolvedValue(accessTokenMock) const getRecentlyPlayedTracksSpy = vi .spyOn(spotifyPlayerService, 'getRecentlyPlayedTracks') - .mockResolvedValue(spotifyResponseWithCursorsMockFactory(tracksMock)) + .mockResolvedValue(recentlyPlayedTracksPageMockFactory(tracksMock)) expect(await usersProfileController.getRecentlyPlayed(id, {})).toEqual( - spotifyResponseWithCursorsMockFactory(tracksMock) + recentlyPlayedTracksPageMockFactory(tracksMock) ) expect(findOneBySpy).toHaveBeenCalledWith({ id }) expect(tokenSpy).toHaveBeenCalled() @@ -167,7 +167,7 @@ describe('UsersProfileController', () => { .mockResolvedValue(accessTokenMock) const getRecentlyPlayedTracksSpy = vi .spyOn(spotifyPlayerService, 'getRecentlyPlayedTracks') - .mockResolvedValue(spotifyResponseWithCursorsMockFactory(tracksMock)) + .mockResolvedValue(recentlyPlayedTracksPageMockFactory(tracksMock)) expect( await usersProfileController.getRecentlyPlayed(id, { @@ -175,7 +175,7 @@ describe('UsersProfileController', () => { before, after, }) - ).toEqual(spotifyResponseWithCursorsMockFactory(tracksMock)) + ).toEqual(recentlyPlayedTracksPageMockFactory(tracksMock)) expect(findOneBySpy).toHaveBeenCalledWith({ id }) expect(tokenSpy).toHaveBeenCalled() expect(getRecentlyPlayedTracksSpy).toHaveBeenCalledWith( @@ -197,10 +197,10 @@ describe('UsersProfileController', () => { .mockResolvedValue(accessTokenMock) const getTopArtistsSpy = vi .spyOn(spotifyUsersService, 'getTopArtists') - .mockResolvedValue(spotifyResponseWithOffsetMockFactory(artistsMock)) + .mockResolvedValue(pageMockFactory(artistsMock)) expect(await usersProfileController.getTopArtists(id, {})).toEqual( - spotifyResponseWithOffsetMockFactory(artistsMock) + pageMockFactory(artistsMock) ) expect(findOneBySpy).toHaveBeenCalledWith({ id }) expect(tokenSpy).toHaveBeenCalled() @@ -235,7 +235,7 @@ describe('UsersProfileController', () => { .mockResolvedValue(accessTokenMock) const getTopArtistsSpy = vi .spyOn(spotifyUsersService, 'getTopArtists') - .mockResolvedValue(spotifyResponseWithOffsetMockFactory(artistsMock)) + .mockResolvedValue(pageMockFactory(artistsMock)) expect( await usersProfileController.getTopArtists(id, { @@ -243,7 +243,7 @@ describe('UsersProfileController', () => { timeRange, offset, }) - ).toEqual(spotifyResponseWithOffsetMockFactory(artistsMock)) + ).toEqual(pageMockFactory(artistsMock)) expect(findOneBySpy).toHaveBeenCalledWith({ id }) expect(tokenSpy).toHaveBeenCalled() expect(getTopArtistsSpy).toHaveBeenCalledWith( @@ -265,10 +265,10 @@ describe('UsersProfileController', () => { .mockResolvedValue(accessTokenMock) const getTopTracksSpy = vi .spyOn(spotifyUsersService, 'getTopTracks') - .mockResolvedValue(spotifyResponseWithOffsetMockFactory(tracksMock)) + .mockResolvedValue(pageMockFactory(tracksMock)) expect(await usersProfileController.getTopTracks(id, {})).toEqual( - spotifyResponseWithOffsetMockFactory(tracksMock) + pageMockFactory(tracksMock) ) expect(findOneBySpy).toHaveBeenCalledWith({ id }) expect(tokenSpy).toHaveBeenCalled() @@ -303,7 +303,7 @@ describe('UsersProfileController', () => { .mockResolvedValue(accessTokenMock) const getTopTracksSpy = vi .spyOn(spotifyUsersService, 'getTopTracks') - .mockResolvedValue(spotifyResponseWithOffsetMockFactory(tracksMock)) + .mockResolvedValue(pageMockFactory(tracksMock)) expect( await usersProfileController.getTopTracks(id, { @@ -311,7 +311,7 @@ describe('UsersProfileController', () => { timeRange, offset, }) - ).toEqual(spotifyResponseWithOffsetMockFactory(tracksMock)) + ).toEqual(pageMockFactory(tracksMock)) expect(findOneBySpy).toHaveBeenCalledWith({ id }) expect(tokenSpy).toHaveBeenCalled() expect(getTopTracksSpy).toHaveBeenCalledWith(