WIP. A little custom music server
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: add better album query

+62 -34
+1 -1
api-testing/get-album-by-id.bru
··· 5 5 } 6 6 7 7 get { 8 - url: http://localhost:3003/album2/:id 8 + url: http://localhost:3003/album/:id 9 9 body: none 10 10 auth: inherit 11 11 }
+48 -10
backend/src/api.ts
··· 22 22 23 23 type SongWithArtists = Song & { artists: Artist[] }; 24 24 25 - type GetAlbum = Album & { songs: Array<Omit<SongWithArtists, "albumId">> }; 25 + type GetAlbum = Album & { 26 + artists: Artist[]; 27 + songs: Array<Omit<SongWithArtists, "albumId">>; 28 + }; 26 29 27 30 class ApiService extends Context.Tag("ApiService")< 28 31 ApiService, ··· 41 44 const db = yield* DatabaseLive; 42 45 43 46 return { 44 - getAlbum2: (id: string) => 47 + getAlbum: (id: string) => 45 48 Effect.gen(function* () { 46 - const rows = yield* db.query.albumTable.findMany({ 47 - //where: eq(albumTable.id, id), 49 + const album = yield* db.query.albumTable 50 + .findMany({ 51 + where: eq(albumTable.id, id), 52 + limit: 1, 53 + with: { 54 + artists: { 55 + with: { 56 + artist: true, 57 + }, 58 + }, 59 + songs: { 60 + with: { 61 + artists: { 62 + with: { 63 + artist: true, 64 + }, 65 + }, 66 + }, 67 + }, 68 + }, 69 + }) 70 + .pipe( 71 + Effect.map((x) => x.at(0)), 72 + Effect.flatMap(Effect.fromNullable), 73 + Effect.mapError( 74 + (e) => 75 + new AlbumNotFoundError({ 76 + message: "Album not found", 77 + cause: e, 78 + }), 79 + ), 80 + ); 48 81 49 - with: { 50 - songs: true, 51 - }, 52 - }); 82 + const artists = album.artists.map((a) => a.artist); 83 + const songs = album.songs.map((s) => ({ 84 + ...s, 85 + artists: s.artists.map((a) => a.artist), 86 + })); 53 87 54 - return rows; 88 + return { 89 + ...album, 90 + artists, 91 + songs, 92 + }; 55 93 }), 56 - getAlbum: (id: string) => 94 + getAlbum2: (id: string) => 57 95 Effect.gen(function* () { 58 96 const rows = yield* db 59 97 .select({
+13 -23
web/src/pages/album/[id].tsx
··· 1 1 import { SongRow } from "@/components/song-row"; 2 + import { client } from "@/lib/api"; 2 3 import { FetchFailedError, JsonParseError } from "@/lib/errors"; 3 - import { Console, Effect, Schema } from "effect"; 4 + import { Console, Effect, pipe, Schema } from "effect"; 4 5 import { Fragment } from "react/jsx-runtime"; 5 6 import { Link } from "waku"; 6 7 import { PageProps } from "waku/router"; ··· 81 82 songs: Schema.Array(SongSchema), 82 83 }); 83 84 84 - function fetchAlbum(id: string) { 85 - return Effect.gen(function* () { 86 - const request = yield* Effect.tryPromise({ 87 - try: () => fetch(`http://localhost:3003/album/${id}`), 85 + const fetchAlbum = (id: string) => 86 + pipe( 87 + Effect.tryPromise({ 88 + try: () => client.album({ id }).get(), 88 89 catch: (err) => 89 90 new FetchFailedError({ 90 91 cause: err, 91 92 message: "Failed to fetch album", 92 93 }), 93 - }); 94 - 95 - const json = yield* Effect.tryPromise({ 96 - try: () => request.json().then((x) => x as unknown), 97 - catch: (err) => 98 - new JsonParseError({ 99 - cause: err, 100 - message: "Failed to parse json", 101 - }), 102 - }); 103 - 104 - const parsed = yield* Schema.decodeUnknown(AlbumSchema)(json); 105 - return parsed; 106 - }).pipe(Effect.tapError((err) => Console.error(err))); 107 - } 94 + }), 95 + Effect.map((x) => x.data), 96 + Effect.flatMap(Effect.fromNullable), 97 + ); 108 98 109 99 export default async function AlbumPage({ id }: PageProps<"/album/[id]">) { 110 100 return await Effect.runPromise( ··· 130 120 {album.title} 131 121 </h1> 132 122 <h2 className="text-xl font-medium text-primary"> 133 - {data.artists.map((artist, idx) => ( 134 - <Fragment key={artist.name + idx}> 123 + {album.artists.map((artist, idx) => ( 124 + <Fragment key={artist.id}> 135 125 {/* @ts-ignore */} 136 - <Link to={`#`} key={artist.name + idx}> 126 + <Link to={`#`}> 137 127 {idx !== 0 ? ", " : ""} 138 128 {artist.name} 139 129 </Link>