Skip to content

Commit

Permalink
Use query parameters for additional search views in Nuxt (#3866)
Browse files Browse the repository at this point in the history
* Use query params for the frontend collections

Signed-off-by: Olga Bulat <[email protected]>

* Update link in sources table

Signed-off-by: Olga Bulat <[email protected]>

* Update tests and tapes

Signed-off-by: Olga Bulat <[email protected]>

* Simplify collection path and query building

Signed-off-by: Olga Bulat <[email protected]>

* Simplify collection middleware conditionals

Signed-off-by: Olga Bulat <[email protected]>

* Document why test assertions are commented out

Signed-off-by: Olga Bulat <[email protected]>

---------

Signed-off-by: Olga Bulat <[email protected]>
  • Loading branch information
obulat authored Mar 8, 2024
1 parent 344b04f commit 154109c
Show file tree
Hide file tree
Showing 32 changed files with 5,836 additions and 3,310 deletions.
2 changes: 1 addition & 1 deletion frontend/src/components/VMediaInfo/VMediaDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<div class="flex w-full flex-grow flex-col gap-6">
<p v-if="media.description">{{ media.description }}</p>
<VMediaTags :tags="media.tags" />
<VMediaTags :tags="media.tags" :media-type="media.frontendMediaType" />
<VMetadata v-if="metadata" :metadata="metadata" />
</div>
</div>
Expand Down
16 changes: 12 additions & 4 deletions frontend/src/components/VMediaInfo/VMediaTags.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from "vue"
import { useContext } from "@nuxtjs/composition-api"
import type { Tag } from "~/types/media"
import type { SupportedMediaType } from "~/constants/media"
import { useFeatureFlagStore } from "~/stores/feature-flag"
import { useSearchStore } from "~/stores/search"
import VMediaTag from "~/components/VMediaTag/VMediaTag.vue"
import VTag from "~/components/VTag/VTag.vue"
Expand All @@ -31,17 +32,24 @@ export default defineComponent({
type: Array as PropType<Tag[]>,
required: true,
},
mediaType: {
type: String as PropType<SupportedMediaType>,
required: true,
},
},
setup() {
const { app } = useContext()
setup(props) {
const searchStore = useSearchStore()
const featureFlagStore = useFeatureFlagStore()
const additionalSearchViews = computed(() =>
featureFlagStore.isOn("additional_search_views")
)
const localizedTagPath = (tag: Tag) => {
return app.localePath({ path: `tag/${tag.name}` })
return searchStore.getCollectionPath({
type: props.mediaType,
collectionParams: { collection: "tag", tag: tag.name },
})
}
return { additionalSearchViews, localizedTagPath }
Expand Down
17 changes: 11 additions & 6 deletions frontend/src/components/VSourcesTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@
</template>

<script lang="ts">
import { computed, defineComponent, PropType, reactive } from "vue"
import { useContext } from "@nuxtjs/composition-api"
import { computed, defineComponent, type PropType, reactive } from "vue"
import { useProviderStore } from "~/stores/provider"
import { useGetLocaleFormattedNumber } from "~/composables/use-get-locale-formatted-number"
Expand All @@ -71,6 +69,7 @@ import type { SupportedMediaType } from "~/constants/media"
import type { MediaProvider } from "~/types/media-provider"
import { useFeatureFlagStore } from "~/stores/feature-flag"
import { useSearchStore } from "~/stores/search"
import TableSortIcon from "~/components/TableSortIcon.vue"
import VLink from "~/components/VLink.vue"
Expand All @@ -88,8 +87,6 @@ export default defineComponent({
},
},
setup(props) {
const { app } = useContext()
const sorting = reactive({
direction: "asc",
field: "display_name" as keyof Omit<MediaProvider, "logo_url">,
Expand Down Expand Up @@ -148,8 +145,16 @@ export default defineComponent({
const additionalSearchViews = computed(() => {
return featureFlagStore.isOn("additional_search_views")
})
const searchStore = useSearchStore()
const providerViewUrl = (provider: MediaProvider) => {
return app.localePath(`/${props.media}/source/${provider.source_name}`)
return searchStore.getCollectionPath({
type: props.media,
collectionParams: {
collection: "source",
source: provider.source_name,
},
})
}
return {
getLocaleFormattedNumber,
Expand Down
5 changes: 1 addition & 4 deletions frontend/src/data/api-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ export interface ApiService {
client: AxiosInstance
query<T = unknown>(
resource: string,
slug: string,
params: Record<string, string>
): Promise<AxiosResponse<T>>
get<T = unknown>(
Expand Down Expand Up @@ -139,16 +138,14 @@ export const createApiService = ({

/**
* @param resource - The endpoint of the resource
* @param slug - the optional additional endpoint, used for collections.
* @param params - Url parameter object
* @returns response The API response object
*/
query<T = unknown>(
resource: string,
slug: string = "",
params: Record<string, string> = {}
): Promise<AxiosResponse<T>> {
return client.get(`${getResourceSlug(resource)}${slug}`, { params })
return client.get(`${getResourceSlug(resource)}`, { params })
},

/**
Expand Down
5 changes: 1 addition & 4 deletions frontend/src/data/media-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,9 @@ class MediaService<T extends Media> {
/**
* Search for media items by keyword.
* @param params - API search query parameters
* @param slug - optional slug to get a collection
*/
async search(
params: PaginatedSearchQuery | PaginatedCollectionQuery,
slug: string = ""
params: PaginatedSearchQuery | PaginatedCollectionQuery
): Promise<MediaResult<Record<string, Media>>> {
// Add the `peaks` param to all audio searches automatically
if (this.mediaType === AUDIO) {
Expand All @@ -64,7 +62,6 @@ class MediaService<T extends Media> {

const res = await this.apiService.query<MediaResult<T[]>>(
this.mediaType,
slug,
params as unknown as Record<string, string>
)
return this.transformResults(res.data)
Expand Down
86 changes: 85 additions & 1 deletion frontend/src/middleware/collection.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,99 @@
import { useFeatureFlagStore } from "~/stores/feature-flag"
import { useProviderStore } from "~/stores/provider"
import { useSearchStore } from "~/stores/search"

import type { Middleware } from "@nuxt/types"
import { queryDictionaryToQueryParams } from "~/utils/search-query-transform"
import {
isSupportedMediaType,
type SupportedMediaType,
} from "~/constants/media"
import type {
CollectionParams,
CreatorCollection,
SourceCollection,
TagCollection,
} from "~/types/search"

import type { Context, Middleware } from "@nuxt/types"
import type { Dictionary } from "vue-router/types/router"

const queryToCollectionParams = (
query: Dictionary<string | (string | null)[]>
): CollectionParams | undefined => {
query = queryDictionaryToQueryParams(query)
if ("tag" in query) {
return {
collection: "tag",
tag: query.tag,
} as TagCollection
}

if ("creator" in query && "source" in query) {
return {
collection: "creator",
creator: query.creator,
source: query.source,
} as CreatorCollection
}

if ("source" in query) {
return {
collection: "source",
source: query.source,
} as SourceCollection
}
return undefined
}

const routeNameToMediaType = (
route: Context["route"]
): SupportedMediaType | null => {
const firstPart = route.name?.split("-")[0]
return firstPart && isSupportedMediaType(firstPart) ? firstPart : null
}

/**
* Middleware for the collection routes.
* Checks that the feature flag is enabled and that the route (name and query) is valid.
* Extracts the collectionParams from the route and updates the search store.
* If the source name does not exist in the provider store, it will throw a 404 error.
*/

export const collectionMiddleware: Middleware = async ({
$pinia,
error: nuxtError,
route,
}) => {
if (!useFeatureFlagStore($pinia).isOn("additional_search_views")) {
nuxtError({
statusCode: 404,
message: "Additional search views are not enabled",
})
return
}

const searchStore = useSearchStore($pinia)
// Route name has the locale in it, e.g. `audio-collection__en`
const mediaType = routeNameToMediaType(route)
const collectionParams = queryToCollectionParams(route.query)

if (mediaType === null || collectionParams === undefined) {
nuxtError({
statusCode: 404,
message: "Invalid collection route",
})
return
}

if ("source" in collectionParams) {
const providerStore = useProviderStore($pinia)
if (!providerStore.isSourceNameValid(mediaType, collectionParams.source)) {
nuxtError({
statusCode: 404,
message: `Invalid source name ${collectionParams.source} for media type ${mediaType}`,
})
}
}

searchStore.setCollectionState(collectionParams, mediaType)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,19 @@
</template>

<script lang="ts">
import { defineComponent, useFetch, useRoute } from "@nuxtjs/composition-api"
import { defineComponent, useFetch } from "@nuxtjs/composition-api"
import { useMediaStore } from "~/stores/media"
import { useSearchStore } from "~/stores/search"
import { AUDIO } from "~/constants/media"
import type { TagCollection } from "~/types/search"
import { collectionMiddleware } from "~/middleware/collection"
import VCollectionPage from "~/components/VCollectionPage.vue"
export default defineComponent({
name: "VAudioTagPage",
name: "VAudioCollectionPage",
components: { VCollectionPage },
layout: "content-layout",
middleware: collectionMiddleware,
setup() {
const route = useRoute()
const collectionParams: TagCollection = {
tag: route.value.params.tag,
collection: "tag",
}
useSearchStore().setCollectionState(collectionParams, AUDIO)
const mediaStore = useMediaStore()
useFetch(async () => {
Expand Down
37 changes: 0 additions & 37 deletions frontend/src/pages/audio/source/_.vue

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,19 @@
</template>

<script lang="ts">
import { defineComponent, useFetch, useRoute } from "@nuxtjs/composition-api"
import { defineComponent, useFetch } from "@nuxtjs/composition-api"
import { useMediaStore } from "~/stores/media"
import { useSearchStore } from "~/stores/search"
import { IMAGE } from "~/constants/media"
import { collectionMiddleware } from "~/middleware/collection"
import type { TagCollection } from "~/types/search"
import VCollectionPage from "~/components/VCollectionPage.vue"
export default defineComponent({
name: "VImageTagPage",
name: "VImageCollectionPage",
components: { VCollectionPage },
layout: "content-layout",
middleware: collectionMiddleware,
setup() {
const route = useRoute()
const collectionParams: TagCollection = {
tag: route.value.params.tag,
collection: "tag",
}
useSearchStore().setCollectionState(collectionParams, IMAGE)
const mediaStore = useMediaStore()
useFetch(async () => {
Expand Down
36 changes: 0 additions & 36 deletions frontend/src/pages/image/source/_.vue

This file was deleted.

Loading

0 comments on commit 154109c

Please sign in to comment.