Added saved artists page
This commit is contained in:
1 parent
8df84bce86
commit
84658e4499
4 files changed
+142
-1
No files matched your search
@@ -0,0 +1,41 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { Artist } from "@/utils/types"
|
||||||
|
|
||||||
|
const props = defineProps<{ artist: Artist }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<article class="card">
|
||||||
|
<NuxtLink :to="`/artist/${props.artist._id}`">
|
||||||
|
<NuxtImg
|
||||||
|
:src="GET_FILE(props.artist.file)"
|
||||||
|
:alt="props.artist.name"
|
||||||
|
placeholder
|
||||||
|
/>
|
||||||
|
<p>{{ props.artist.name }}</p>
|
||||||
|
</NuxtLink>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/util" as *;
|
||||||
|
@use "@/styles/variables" as *;
|
||||||
|
|
||||||
|
.card {
|
||||||
|
@include fontSize(14px);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: start;
|
||||||
|
|
||||||
|
& > a {
|
||||||
|
& img {
|
||||||
|
border-radius: $border-radius-standard;
|
||||||
|
}
|
||||||
|
|
||||||
|
& p {
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,63 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { Artist } from "@/utils/types"
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: artistsData,
|
||||||
|
status,
|
||||||
|
error,
|
||||||
|
refresh,
|
||||||
|
} = await useApiFetch<Artist[]>("/user/artists")
|
||||||
|
const artists = computed(() => artistsData.value ?? [])
|
||||||
|
|
||||||
useHead({ title: "Artists" })
|
useHead({ title: "Artists" })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template></template>
|
<template>
|
||||||
|
<section class="artists-page">
|
||||||
|
<h1>Artists</h1>
|
||||||
|
|
||||||
|
<p v-if="status === 'pending'" class="message" role="status">
|
||||||
|
Loading your artists…
|
||||||
|
</p>
|
||||||
|
<div v-else-if="error" class="message" role="alert">
|
||||||
|
<p>Your artists could not be loaded.</p>
|
||||||
|
<Button variant="ghost" @click="refresh">Try again</Button>
|
||||||
|
</div>
|
||||||
|
<p v-else-if="artists.length === 0" class="message">
|
||||||
|
No artists in your saved songs yet.
|
||||||
|
</p>
|
||||||
|
<div v-else class="artist-list">
|
||||||
|
<ArtistCard
|
||||||
|
v-for="artist in artists"
|
||||||
|
:key="artist._id"
|
||||||
|
:artist="artist"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use "@/styles/util" as *;
|
||||||
|
|
||||||
|
.artists-page {
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
@include fontSize(32px);
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.artist-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
@include fontSize(14px);
|
||||||
|
opacity: 0.7;
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { setResponseStatus } from "h3"
|
||||||
|
import { Artist } from "#server/models/artist"
|
||||||
|
import { Track } from "#server/models/track"
|
||||||
|
import { User } from "#server/models/user"
|
||||||
|
import { defineAuthenticatedEventHandler, requireAuthenticatedUser } from "#server/utils/auth"
|
||||||
|
|
||||||
|
export default defineAuthenticatedEventHandler(async (event) => {
|
||||||
|
const authenticatedUser = await requireAuthenticatedUser(event)
|
||||||
|
const user = await User.findById(authenticatedUser._id).select("savedTracks")
|
||||||
|
if (!user) {
|
||||||
|
setResponseStatus(event, 404)
|
||||||
|
return { message: "User not found" }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user.savedTracks.length) return []
|
||||||
|
|
||||||
|
const tracks = await Track.find({ _id: { $in: user.savedTracks } })
|
||||||
|
.select("_id artists")
|
||||||
|
.exec()
|
||||||
|
const artistsByTrack = new Map(
|
||||||
|
tracks.map((track: any) => [
|
||||||
|
track._id.toString(),
|
||||||
|
(track.artists ?? []).map((artist: any) => artist.toString()),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
const artistIds: string[] = []
|
||||||
|
const seen = new Set<string>()
|
||||||
|
for (const savedTrack of user.savedTracks) {
|
||||||
|
for (const artistId of artistsByTrack.get(savedTrack.toString()) ?? []) {
|
||||||
|
if (!seen.has(artistId)) {
|
||||||
|
seen.add(artistId)
|
||||||
|
artistIds.push(artistId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!artistIds.length) return []
|
||||||
|
|
||||||
|
const artists = await Artist.find({ _id: { $in: artistIds } }).exec()
|
||||||
|
const artistsById = new Map(artists.map((artist: any) => [artist._id.toString(), artist]))
|
||||||
|
return artistIds.map((id) => artistsById.get(id)).filter((artist) => artist != null)
|
||||||
|
})
|
||||||
@@ -39,6 +39,7 @@ const operations: ApiOperation[] = [
|
|||||||
{ method: "get", path: "/all/tracks", tag: "All", summary: "Get all tracks", auth: true, response: "TrackList" },
|
{ method: "get", path: "/all/tracks", tag: "All", summary: "Get all tracks", auth: true, response: "TrackList" },
|
||||||
{ method: "get", path: "/search", tag: "Search", summary: "Search tracks, albums and artists", auth: true, query: ["q", "type", "limit"], response: "SearchResults" },
|
{ method: "get", path: "/search", tag: "Search", summary: "Search tracks, albums and artists", auth: true, query: ["q", "type", "limit"], response: "SearchResults" },
|
||||||
{ method: "get", path: "/user", tag: "User", summary: "Get the current user", auth: true, response: "User" },
|
{ method: "get", path: "/user", tag: "User", summary: "Get the current user", auth: true, response: "User" },
|
||||||
|
{ method: "get", path: "/user/artists", tag: "User", summary: "Get artists represented by saved tracks", auth: true, response: "ArtistList" },
|
||||||
{ method: "get", path: "/user/tracks", tag: "User", summary: "Get saved tracks", auth: true, response: "TrackList" },
|
{ method: "get", path: "/user/tracks", tag: "User", summary: "Get saved tracks", auth: true, response: "TrackList" },
|
||||||
{ method: "patch", path: "/user/tracks", tag: "User", summary: "Save a track", auth: true, query: ["id"], response: "EmptyObject" },
|
{ method: "patch", path: "/user/tracks", tag: "User", summary: "Save a track", auth: true, query: ["id"], response: "EmptyObject" },
|
||||||
{ method: "delete", path: "/user/tracks", tag: "User", summary: "Remove saved tracks", auth: true, query: ["id", "ids"], response: "EmptyObject" },
|
{ method: "delete", path: "/user/tracks", tag: "User", summary: "Remove saved tracks", auth: true, query: ["id", "ids"], response: "EmptyObject" },
|
||||||
|
|||||||
Reference in new issue
Block a user