WIP. A little custom music server
0
fork

Configure Feed

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

feat: move get album to effect service

+83 -50
+15
api-testing/get-albums.bru
··· 1 + meta { 2 + name: get-albums 3 + type: http 4 + seq: 4 5 + } 6 + 7 + get { 8 + url: http://localhost:3003/albums 9 + body: none 10 + auth: inherit 11 + } 12 + 13 + settings { 14 + encodeUrl: true 15 + }
+68 -50
backend/src/api.ts
··· 8 8 import { eq } from "drizzle-orm"; 9 9 import { openapi } from "@elysiajs/openapi"; 10 10 import { first } from "effect/GroupBy"; 11 - import type { Album, AlbumWithArtist } from "./db/types"; 11 + import type { Album, AlbumWithArtist, Artist, Song } from "./db/types"; 12 12 import type { SqlError } from "@effect/sql"; 13 13 14 14 class FileNotFoundError extends Data.TaggedError("FileNotFoundError")<{ 15 15 message: string; 16 16 cause?: unknown; 17 17 }> {} 18 + class AlbumNotFoundError extends Data.TaggedError("AlbumNotFoundError")<{ 19 + message: string; 20 + cause?: unknown; 21 + }> {} 22 + 23 + type SongWithArtists = Song & { artists: Artist[] }; 24 + 25 + type GetAlbum = Album & { songs: Array<Omit<SongWithArtists, "albumId">> }; 18 26 19 27 class ApiService extends Context.Tag("ApiService")< 20 28 ApiService, 21 29 { 22 30 readonly getAlbumList: () => Effect.Effect<AlbumWithArtist[], SqlError.SqlError, DatabaseLive>; 31 + readonly getAlbum: ( 32 + id: string, 33 + ) => Effect.Effect<GetAlbum, SqlError.SqlError | AlbumNotFoundError, DatabaseLive>; 23 34 } 24 35 >() {} 25 36 ··· 29 40 const db = yield* DatabaseLive; 30 41 31 42 return { 32 - getAlbumList: () => 43 + getAlbum: (id: string) => 33 44 Effect.gen(function* () { 34 45 const rows = yield* db 35 46 .select({ 36 47 album: albumTable, 37 - artist: artistTable, 38 - }) 39 - .from(albumTable) 40 - .innerJoin(artistToAlbumTable, eq(albumTable.id, artistToAlbumTable.albumId)) 41 - .innerJoin(artistTable, eq(artistTable.id, artistToAlbumTable.artistId)); 42 - 43 - const result = rows.reduce<Record<string, AlbumWithArtist>>((acc, cur) => { 44 - const albumId = cur.album.id; 45 - 46 - if (!acc[albumId]) { 47 - acc[albumId] = { 48 - ...cur.album, 49 - artists: [], 50 - }; 51 - } 52 - 53 - acc[albumId]?.artists.push(cur.artist); 54 - return acc; 55 - }, {}); 56 - 57 - return Object.values(result); 58 - }), 59 - }; 60 - }), 61 - ); 62 - 63 - export function startApi() { 64 - new Elysia() 65 - .use(openapi()) 66 - .get("/", "Hello Elysia") 67 - .get("/albums", () => 68 - runtime.runPromise( 69 - Effect.gen(function* () { 70 - const api = yield* ApiService; 71 - return yield* api.getAlbumList(); 72 - }), 73 - ), 74 - ) 75 - .get("/album/:id", ({ params: { id } }) => 76 - runtime.runPromise( 77 - Effect.gen(function* () { 78 - const db = yield* DatabaseLive; 79 - const rows = yield* db 80 - .select({ 81 - album: albumTable, 82 48 song: { 83 49 id: songTable.id, 84 50 fileId: songTable.fileId, ··· 96 62 97 63 const firstRow = rows.at(0); 98 64 if (!firstRow) { 99 - return []; 65 + return yield* new AlbumNotFoundError({ 66 + message: "Can't extract album from selected rows", 67 + cause: rows, 68 + }); 100 69 } 101 70 102 - const songsMap = rows.reduce<Record<string, any>>((acc, row) => { 71 + const songsMap = rows.reduce<Record<string, Omit<SongWithArtists, "albumId">>>((acc, row) => { 103 72 const { song, artist } = row; 104 73 105 74 if (!acc[song.id]) { ··· 107 76 id: song.id, 108 77 fileId: song.fileId, 109 78 title: song.title, 110 - artists: [], 79 + artists: [] as Artist[], 111 80 }; 112 81 } 113 82 ··· 125 94 }; 126 95 127 96 return album; 97 + }), 98 + getAlbumList: () => 99 + Effect.gen(function* () { 100 + const rows = yield* db 101 + .select({ 102 + album: albumTable, 103 + artist: artistTable, 104 + }) 105 + .from(albumTable) 106 + .innerJoin(artistToAlbumTable, eq(albumTable.id, artistToAlbumTable.albumId)) 107 + .innerJoin(artistTable, eq(artistTable.id, artistToAlbumTable.artistId)); 108 + 109 + const result = rows.reduce<Record<string, AlbumWithArtist>>((acc, cur) => { 110 + const albumId = cur.album.id; 111 + 112 + if (!acc[albumId]) { 113 + acc[albumId] = { 114 + ...cur.album, 115 + artists: [], 116 + }; 117 + } 118 + 119 + acc[albumId]?.artists.push(cur.artist); 120 + return acc; 121 + }, {}); 122 + 123 + return Object.values(result); 124 + }), 125 + }; 126 + }), 127 + ); 128 + 129 + export function startApi() { 130 + new Elysia() 131 + .use(openapi()) 132 + .get("/", "Hello Elysia") 133 + .get("/albums", () => 134 + runtime.runPromise( 135 + Effect.gen(function* () { 136 + const api = yield* ApiService; 137 + return yield* api.getAlbumList(); 138 + }), 139 + ), 140 + ) 141 + .get("/album/:id", ({ params: { id } }) => 142 + runtime.runPromise( 143 + Effect.gen(function* () { 144 + const api = yield* ApiService; 145 + return yield* api.getAlbum(id); 128 146 }), 129 147 ), 130 148 )