WIP. A little custom music server
0
fork

Configure Feed

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

implement album get

+75 -7
+62 -3
backend/src/api.ts
··· 1 1 import { SqliteDrizzle } from "@effect/sql-drizzle/Sqlite"; 2 - import { Console, Data, Effect, Layer, ManagedRuntime } from "effect"; 2 + import { Console, Context, Data, Effect, Layer, ManagedRuntime } from "effect"; 3 3 import { Elysia, status, t } from "elysia"; 4 4 import { DatabaseLive } from "./db"; 5 5 import { BunContext } from "@effect/platform-bun"; 6 6 import { EnvLive } from "./env"; 7 - import { albumTable, artistTable, fileTable, songTable, songToArtistTable } from "./db/schema"; 7 + import { albumTable, artistTable, artistToAlbumTable, fileTable, songTable, songToArtistTable } from "./db/schema"; 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"; 12 + import type { SqlError } from "@effect/sql"; 11 13 12 14 class FileNotFoundError extends Data.TaggedError("FileNotFoundError")<{ 13 15 message: string; 14 16 cause?: unknown; 15 17 }> {} 16 18 19 + class ApiService extends Context.Tag("ApiService")< 20 + ApiService, 21 + { 22 + readonly getAlbumList: () => Effect.Effect<AlbumWithArtist[], SqlError.SqlError, DatabaseLive>; 23 + } 24 + >() {} 25 + 26 + const ApiLive = Layer.effect( 27 + ApiService, 28 + Effect.gen(function* () { 29 + const db = yield* DatabaseLive; 30 + 31 + return { 32 + getAlbumList: () => 33 + Effect.gen(function* () { 34 + const rows = yield* db 35 + .select({ 36 + 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 + 17 63 export function startApi() { 18 64 new Elysia() 19 65 .use(openapi()) 20 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 + ) 21 75 .get("/album/:id", ({ params: { id } }) => 22 76 runtime.runPromise( 23 77 Effect.gen(function* () { ··· 185 239 }>; 186 240 }; 187 241 188 - const layers = Layer.mergeAll(BunContext.layer, EnvLive, DatabaseLive.Default); 242 + const layers = Layer.mergeAll( 243 + BunContext.layer, 244 + EnvLive, 245 + DatabaseLive.Default, 246 + Layer.provide(ApiLive, DatabaseLive.Default), 247 + ); 189 248 const runtime = ManagedRuntime.make(layers);
+9
backend/src/db/types.ts
··· 1 + import type { albumTable, artistTable, songTable } from "./schema"; 2 + 3 + export type Album = typeof albumTable.$inferSelect; 4 + export type Song = typeof songTable.$inferSelect; 5 + export type Artist = typeof artistTable.$inferSelect; 6 + 7 + export type AlbumWithArtist = Album & { 8 + artists: Artist[]; 9 + };
+4 -4
web/src/pages.gen.ts
··· 4 4 import type { PathsForPages, GetConfigResponse } from 'waku/router'; 5 5 6 6 // prettier-ignore 7 - import type { getConfig as File_About_getConfig } from './pages/about'; 8 - // prettier-ignore 9 7 import type { getConfig as File_Index_getConfig } from './pages/index'; 8 + // prettier-ignore 9 + import type { getConfig as File_About_getConfig } from './pages/about'; 10 10 11 11 // prettier-ignore 12 12 type Page = 13 - | ({ path: '/about' } & GetConfigResponse<typeof File_About_getConfig>) 13 + | ({ path: '/' } & GetConfigResponse<typeof File_Index_getConfig>) 14 14 | { path: '/album'; render: 'dynamic' } 15 - | ({ path: '/' } & GetConfigResponse<typeof File_Index_getConfig>); 15 + | ({ path: '/about' } & GetConfigResponse<typeof File_About_getConfig>); 16 16 17 17 // prettier-ignore 18 18 declare module 'waku/router' {