Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
Refactor media services (#867)
Browse files Browse the repository at this point in the history
* Create a single media-service data fetching object

* Move slug creation from media service to the api service

* Use typing data from #868

* Update src/constants/media.js

Co-authored-by: sarayourfriend <24264157+sarayourfriend@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: sarayourfriend <24264157+sarayourfriend@users.noreply.github.com>

* edit api-service styles; format

* fix decode-media-data type import

* Return decoded data from the media services

Co-authored-by: sarayourfriend <24264157+sarayourfriend@users.noreply.github.com>
  • Loading branch information
obulat and sarayourfriend authored Feb 23, 2022
1 parent c1eb220 commit e77826c
Show file tree
Hide file tree
Showing 15 changed files with 190 additions and 209 deletions.
12 changes: 4 additions & 8 deletions src/composables/use-related.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
import AudioService from '~/data/audio-service'
import ImageService from '~/data/image-service'
import { ref, useFetch } from '@nuxtjs/composition-api'
import { AUDIO, IMAGE } from '~/constants/media'

const services = { [AUDIO]: AudioService, [IMAGE]: ImageService }
import { mediaServices } from '~/store/media'

export default function useRelated({
mediaType,
mediaId,
service = services[mediaType],
service = mediaServices[mediaType],
}) {
const media = ref([])
// fetch and fetchState are available as this.$fetch and this.$fetchState
// in components, so there's no need to export them,
// see https://composition-api.nuxtjs.org/lifecycle/usefetch/
// eslint-disable-next-line no-unused-vars
const { fetch } = useFetch(async () => {
const response = await service.getRelatedMedia({
const data = await service.getRelatedMedia({
id: mediaId.value,
})
media.value = response.data.results
media.value = data.results
})
fetch()
return { media }
Expand Down
26 changes: 20 additions & 6 deletions src/data/api-service.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import axios from 'axios'
import { warn } from '~/utils/warn'
import { AUDIO, IMAGE } from '~/constants/media'

const DEFAULT_REQUEST_TIMEOUT = 30000

/**
* Returns a slug with trailing slash for a given resource name.
* For media types, converts the name into resource slug when necessary (i.e. pluralizes 'image'),
* for other resources uses the resource name as the slug.
* @param {string} resource
* @returns {string}
*/
const getResourceSlug = (resource) => {
const slug = { [AUDIO]: 'audio', [IMAGE]: 'images' }[resource] ?? resource
return `${slug}/`
}
/**
* @param {boolean} errorCondition
* @param {string} message
Expand Down Expand Up @@ -62,7 +74,7 @@ export const createApiService = (baseUrl = process.env.apiUrl) => {
* @returns {Promise<import('axios').AxiosResponse<T>>} response The API response object
*/
query(resource, params) {
return client.get(resource, { params })
return client.get(`${getResourceSlug(resource)}`, { params })
},

/**
Expand All @@ -72,7 +84,7 @@ export const createApiService = (baseUrl = process.env.apiUrl) => {
* @returns {Promise<import('axios').AxiosResponse<T>>} Response The API response object
*/
get(resource, slug) {
return client.get(`${resource}/${slug}/`)
return client.get(`${getResourceSlug(resource)}${slug}/`)
},

/**
Expand All @@ -82,7 +94,7 @@ export const createApiService = (baseUrl = process.env.apiUrl) => {
* @returns {Promise<import('axios').AxiosResponse<T>>} Response The API response object
*/
post(resource, data) {
return client.post(resource, data)
return client.post(getResourceSlug(resource), data)
},

/**
Expand All @@ -94,7 +106,9 @@ export const createApiService = (baseUrl = process.env.apiUrl) => {
* @returns {Promise<import('axios').AxiosResponse<T>>} Response The API response object
*/
update(resource, slug, data, headers) {
return client.put(`${resource}/${slug}`, data, { headers })
return client.put(`${getResourceSlug(resource)}${slug}`, data, {
headers,
})
},

/**
Expand All @@ -103,7 +117,7 @@ export const createApiService = (baseUrl = process.env.apiUrl) => {
* @returns {Promise<import('axios').AxiosResponse<any>>} Response The API response object
*/
put(resource, params) {
return client.put(resource, params)
return client.put(getResourceSlug(resource), params)
},

/**
Expand All @@ -113,7 +127,7 @@ export const createApiService = (baseUrl = process.env.apiUrl) => {
* @returns {Promise<import('axios').AxiosResponse<any>>} Response The API response object
*/
delete(resource, slug, headers) {
return client.delete(`${resource}/${slug}`, { headers })
return client.delete(`${getResourceSlug(resource)}${slug}`, { headers })
},
}
}
Expand Down
52 changes: 0 additions & 52 deletions src/data/audio-service.js

This file was deleted.

13 changes: 0 additions & 13 deletions src/data/base-media-service.js

This file was deleted.

47 changes: 0 additions & 47 deletions src/data/image-service.js

This file was deleted.

83 changes: 83 additions & 0 deletions src/data/media-service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import ApiService from '~/data/api-service'

import decodeMediaData from '~/utils/decode-media-data'

/**
* @template {import('../store/types').FrontendMediaType} [T=any]
*/
class MediaService {
/**
* @param {T} mediaType
*/
constructor(mediaType) {
/** @type {T} */
this.mediaType = mediaType
}

/**
* Decodes the text data to avoid encoding problems.
* Also, converts the results from an array of media objects into an object with
* media id as keys.
* @param {import('axios').AxiosResponse<T>} data
* @returns {import('../store/types').MediaStoreResult<T>}
*/
transformResults(data) {
return {
...data,
results: data.results.reduce((acc, item) => {
acc[item.id] = decodeMediaData(item, this.mediaType)
return acc
}, /** @type {Record<string, import('../store/types').DetailFromMediaType<T>>} */ ({})),
}
}

/**
* Search for media items by keyword.
* @param {import('../store/types').ApiQueryParams} params
* @return {Promise<import('axios').AxiosResponse<import('../store/types').MediaResult<T[]>>>}
*/
async search(params) {
const res = await ApiService.query(this.mediaType, params)
return this.transformResults(res.data)
}

/**
* Retrieve media details by its id.
* SSR-called
* @param {{ id: string }} params
* @return {Promise<import('axios').AxiosResponse<import('../store/types').MediaResult<T>>>}
*/
async getMediaDetail(params) {
if (!params.id) {
throw new Error(
`MediaService.getMediaDetail() id parameter required to retrieve ${this.mediaType} details.`
)
}

const res = await ApiService.get(this.mediaType, params.id)
return decodeMediaData(res.data, this.mediaType)
}

/**
* Retrieve related media
* @param {{ id: string }} params
* @return {Promise<import('axios').AxiosResponse<import('../store/types').MediaResult<T[]>>>}
*/
async getRelatedMedia(params) {
if (!params.id) {
throw new Error(
`MediaService.getRelatedMedia() id parameter required to retrieve related media.`
)
}

const res = await ApiService.get(this.mediaType, `${params.id}/related`)
return {
...res.data,
results: res.data.results.map((item) =>
decodeMediaData(item, this.mediaType)
),
}
}
}

export default MediaService
23 changes: 11 additions & 12 deletions src/store/media.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import prepareSearchQueryParams from '~/utils/prepare-search-query-params'
import decodeMediaData from '~/utils/decode-media-data'
import {
FETCH_MEDIA,
FETCH_SINGLE_MEDIA_TYPE,
Expand All @@ -23,8 +22,7 @@ import {
} from '~/constants/usage-data-analytics-types'
import { AUDIO, IMAGE, ALL_MEDIA, supportedMediaTypes } from '~/constants/media'
import { USAGE_DATA } from '~/constants/store-modules'
import AudioService from '~/data/audio-service'
import ImageService from '~/data/image-service'
import MediaService from '~/data/media-service'

/**
* @return {import('./types').MediaState}
Expand Down Expand Up @@ -60,7 +58,12 @@ export const state = () => ({
image: {},
})

export const createActions = (services) => ({
export const mediaServices = {
[AUDIO]: new MediaService(AUDIO),
[IMAGE]: new MediaService(IMAGE),
}

export const createActions = (services = mediaServices) => ({
/**
*
* @param {import('vuex').ActionContext} context
Expand Down Expand Up @@ -127,13 +130,12 @@ export const createActions = (services) => ({
try {
const mediaPage = typeof page === 'undefined' ? page : page[mediaType]

const res = await services[mediaType].search({
const data = await services[mediaType].search({
...queryParams,
page: mediaPage,
})

commit(FETCH_END_MEDIA, { mediaType })
const data = services[mediaType].transformResults(res.data)
const mediaCount = data.result_count
commit(SET_MEDIA, {
mediaType,
Expand Down Expand Up @@ -176,8 +178,7 @@ export const createActions = (services) => ({
)
commit(SET_MEDIA_ITEM, { item: {}, mediaType })
try {
const res = await services[mediaType].getMediaDetail(params)
const { data } = res
const data = await services[mediaType].getMediaDetail(params)
commit(SET_MEDIA_ITEM, { item: data, mediaType })
} catch (error) {
if (error.response && error.response.status === 404) {
Expand Down Expand Up @@ -223,6 +224,7 @@ export const createActions = (services) => ({
}
},
})
const actions = createActions()

export const getters = {
/**
Expand Down Expand Up @@ -328,7 +330,7 @@ export const mutations = {
},
[SET_MEDIA_ITEM](_state, params) {
const { item, mediaType } = params
_state[mediaType] = decodeMediaData(item, mediaType)
_state[mediaType] = item
},
[SET_MEDIA](_state, params) {
const {
Expand Down Expand Up @@ -368,9 +370,6 @@ export const mutations = {
},
}

const mediaServices = { [AUDIO]: AudioService, [IMAGE]: ImageService }
const actions = createActions(mediaServices)

export default {
state,
getters,
Expand Down
Loading

0 comments on commit e77826c

Please sign in to comment.