WIP. A little custom music server
0
fork

Configure Feed

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

cleanup coverart service

+75 -74
+70 -71
backend/src/cover-art.ts
··· 1 - import { Console, Context, Data, Effect, Layer } from "effect"; 1 + import { Context, Data, Effect, Layer, Schema } from "effect"; 2 + 3 + export class RateLimitError extends Schema.TaggedError<RateLimitError>()("RateLimitError", { 4 + message: Schema.NonEmptyString, 5 + status: Schema.Number, 6 + }) {} 7 + 8 + export class InvalidReleaseGroupIdError extends Schema.TaggedError<InvalidReleaseGroupIdError>()( 9 + "InvalidReleaseGroupIdError", 10 + { 11 + message: Schema.NonEmptyString, 12 + status: Schema.Number, 13 + cause: Schema.Defect, 14 + }, 15 + ) {} 16 + 17 + export class UnknownFetchError extends Schema.TaggedError<UnknownFetchError>()("UnknownFetchError", { 18 + message: Schema.NonEmptyString, 19 + status: Schema.optional(Schema.Number), 20 + cause: Schema.Defect, 21 + }) {} 22 + 23 + const ErrorUnion = Schema.Union(RateLimitError, InvalidReleaseGroupIdError, UnknownFetchError); 2 24 3 25 export class CoverartService extends Context.Tag("CoverartService")< 4 26 CoverartService, 5 27 { 6 28 readonly fetchFrontByReleaseGroupId: ( 7 29 rgId: string, 8 - ) => Effect.Effect< 9 - Uint8Array<ArrayBuffer>, 10 - InvalidReleaseGroupIdError | RateLimitError | UnknownFetchError, 11 - unknown 12 - >; 30 + ) => Effect.Effect<Uint8Array<ArrayBuffer>, typeof ErrorUnion.Type, unknown>; 13 31 } 14 - >() {} 32 + >() { 33 + static readonly layer = Layer.succeed( 34 + CoverartService, 35 + CoverartService.of({ 36 + fetchFrontByReleaseGroupId: (rgId) => 37 + Effect.gen(function* () { 38 + const endpoint = `http://coverartarchive.org/release-group/${rgId}/front`; 15 39 16 - export const MusicbrainzCoverartLayer = Layer.succeed( 17 - CoverartService, 18 - CoverartService.of({ 19 - fetchFrontByReleaseGroupId: (rgId) => 20 - Effect.gen(function* () { 21 - const endpoint = `http://coverartarchive.org/release-group/${rgId}/front`; 40 + const response = yield* Effect.tryPromise({ 41 + try: () => fetch(endpoint), 42 + catch: (err) => 43 + UnknownFetchError.make({ 44 + message: "Unknown error", 45 + cause: err, 46 + }), 47 + }); 48 + const status = response.status; 22 49 23 - const response = yield* Effect.tryPromise({ 24 - try: () => fetch(endpoint), 25 - catch: (err) => 26 - new UnknownFetchError({ 27 - message: "Unknown error", 28 - cause: err, 29 - }), 30 - }); 31 - const status = response.status; 32 - 33 - if (status === 400 || status === 404) { 34 - return yield* Effect.fail( 35 - new InvalidReleaseGroupIdError({ 50 + if (status === 400 || status === 404) { 51 + return yield* InvalidReleaseGroupIdError.make({ 36 52 message: "This release group id is unparsable or invalid", 53 + status, 37 54 cause: { 38 55 releaseGroupId: rgId, 39 56 }, 40 - }), 41 - ); 42 - } 57 + }); 58 + } 43 59 44 - if (status === 503) { 45 - return yield* Effect.fail( 46 - new RateLimitError({ 60 + if (status === 503) { 61 + return yield* RateLimitError.make({ 47 62 message: "Rate limit hit", 48 - }), 49 - ); 50 - } 63 + status, 64 + }); 65 + } 51 66 52 - if (!response.ok) { 53 - return yield* Effect.fail( 54 - new UnknownFetchError({ 67 + if (!response.ok) { 68 + return yield* UnknownFetchError.make({ 55 69 message: "Unknown Error", 56 - }), 57 - ); 58 - } 59 - 60 - const file = yield* Effect.tryPromise({ 61 - try: () => response.bytes(), 62 - catch: (err) => 63 - new UnknownFetchError({ 64 - message: "Unknown error", 65 - cause: err, 66 - }), 67 - }); 68 - 69 - return file; 70 - }).pipe(), 71 - }), 72 - ); 70 + status, 71 + cause: response, 72 + }); 73 + } 73 74 74 - // Errors 75 - // 75 + const file = yield* Effect.tryPromise({ 76 + try: () => response.bytes(), 77 + catch: (err) => 78 + UnknownFetchError.make({ 79 + message: "Unknown error", 80 + status, 81 + cause: err, 82 + }), 83 + }); 76 84 77 - export class RateLimitError extends Data.TaggedError("RateLimitError")<{ 78 - message: string; 79 - cause?: unknown; 80 - }> {} 81 - 82 - export class InvalidReleaseGroupIdError extends Data.TaggedError("InvalidReleaseGroupIdError")<{ 83 - message: string; 84 - cause?: unknown; 85 - }> {} 86 - 87 - export class UnknownFetchError extends Data.TaggedError("UnknownFetchError")<{ 88 - message: string; 89 - cause?: unknown; 90 - }> {} 85 + return file; 86 + }), 87 + }), 88 + ); 89 + }
+5 -3
backend/test/cover-art.test.ts
··· 1 1 import { expect, it } from "@effect/vitest"; 2 2 import { Effect, Exit } from "effect"; 3 - import { CoverartService, MusicbrainzCoverartLayer } from "../src/cover-art"; 3 + import { CoverartService } from "../src/cover-art"; 4 + 5 + const layer = CoverartService.layer; 4 6 5 7 it.effect("should fetch cover art if release group is correct", () => 6 8 Effect.gen(function* () { ··· 11 13 .pipe(Effect.exit); 12 14 13 15 expect(Exit.isSuccess(result)).toBeTruthy(); 14 - }).pipe(Effect.provide(MusicbrainzCoverartLayer)), 16 + }).pipe(Effect.provide(layer)), 15 17 ); 16 18 17 19 it.effect("should return InvalidReleaseGroupId if id is invalid", () => ··· 23 25 const result = yield* coverartService.fetchFrontByReleaseGroupId(rgId).pipe(Effect.exit); 24 26 25 27 expect(Exit.isFailure(result)).toBeTruthy(); 26 - }).pipe(Effect.provide(MusicbrainzCoverartLayer)), 28 + }).pipe(Effect.provide(layer)), 27 29 );