Compare commits

..

8 Commits

7 changed files with 100 additions and 25 deletions

2
.gitignore vendored
View File

@@ -37,3 +37,5 @@ __screenshots__/
# Vite
*.timestamp-*-*.mjs
public/covers/*

21
Dockerfile Normal file
View File

@@ -0,0 +1,21 @@
FROM node:20-slim AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
COPY . /app
WORKDIR /app
FROM base AS prod-deps
ARG CI=true
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
FROM base AS build
ARG CI=true
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm run build
FROM base
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist
EXPOSE 8000
CMD [ "pnpm", "start" ]

6
docker-compose.yml Normal file
View File

@@ -0,0 +1,6 @@
services:
mva-backend:
image: mva-backend:latest
container_name: mva-backend
ports:
- 6767:3000

View File

@@ -11,7 +11,7 @@
},
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc src/index.ts --outDir dist",
"build": "tsc",
"start": "NODE_ENV=production node dist/index.js"
},
"devDependencies": {

View File

@@ -1,5 +1,6 @@
import axios from "axios";
import fs from "node:fs";
import path from "node:path";
export async function download_image_from_caa(mbid: string) {
console.log("[DEBUG]: download_image_from_caa:", mbid);
@@ -14,7 +15,14 @@ export async function download_image_from_caa(mbid: string) {
},
);
fs.writeFileSync(`public/covers/mb/${mbid}.png`, img_response.data);
const mb_dist = path.join(
process.cwd(),
"public",
"covers",
"mb",
`${mbid}.png`,
);
fs.writeFileSync(mb_dist, img_response.data);
} catch (error) {
console.error("Error fetching data:", error);
throw error;
@@ -32,14 +40,20 @@ export async function itunes_cover_url(artist: string, album: string) {
.replace(/[-_,"':;|]/g, " ")
.toLowerCase();
const apple_music_results = await fetch("https://itunes.apple.com/search", {
const response = await fetch("https://itunes.apple.com/search", {
body: new URLSearchParams({
term: query,
entity: "song",
limit: "10",
}),
method: "POST",
}).then((res) => res.json());
});
if (!response.ok) {
throw new Error(`iTunes API failed with status ${response.status}`);
}
const apple_music_results = await response.json();
for (let result of apple_music_results.results) {
if (artist == result.artistName.toLowerCase()) {
@@ -49,27 +63,35 @@ export async function itunes_cover_url(artist: string, album: string) {
}
export async function download_image_from_itunes(
artist: string,
album: string,
rawArtist: string,
rawAlbum: string,
safeArtist: string,
safeAlbum: string,
) {
console.log("[DEBUG]: download_image_from_itunes:", artist, album);
console.log("[DEBUG]: download_image_from_itunes:", rawArtist, rawAlbum);
try {
const itunes_url = await itunes_cover_url(artist, album);
const itunes_url = await itunes_cover_url(rawArtist, rawAlbum);
if (!itunes_url) {
throw new Error(`iTunes URL not found for ${artist} - ${album}`);
throw new Error(`iTunes URL not found for ${rawArtist} - ${rawAlbum}`);
}
const artist_dir = `public/covers/itunes/${artist}`;
if (!fs.existsSync(artist_dir)) {
fs.mkdirSync(artist_dir, { recursive: true });
const artist_dist = path.join(
process.cwd(),
"public",
"covers",
"itunes",
safeArtist,
);
if (!fs.existsSync(artist_dist)) {
fs.mkdirSync(artist_dist, { recursive: true });
}
const img_response = await axios.get(itunes_url, {
responseType: "arraybuffer",
});
fs.writeFileSync(`${artist_dir}/${album}.png`, img_response.data);
fs.writeFileSync(`${artist_dist}/${safeAlbum}.png`, img_response.data);
} catch (error) {
console.error("Error fetching data:", error);
throw error;

View File

@@ -1,6 +1,7 @@
import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";
import { node } from "@elysiajs/node";
import path from "node:path";
import fs from "node:fs";
@@ -18,7 +19,7 @@ app.get("/cover/:artist/:album/:id", ({ params: { artist, album, id } }) =>
get_image_from_server(artist, album, id),
);
app.listen(3000, ({ hostname, port }) => {
app.listen({ hostname: "0.0.0.0", port: 3000 }, ({ hostname, port }) => {
console.log(`🦊 Elysia is running at ${hostname}:${port}`);
});
@@ -27,22 +28,45 @@ async function get_image_from_server(
album: string,
mbid: string,
) {
const MBFilePath = `public/covers/mb/${mbid}.png`;
const iTunesFilePath = `public/covers/itunes/${artist}/${album}.png`;
const safeArtist = artist
.replace(/[^a-zA-Z0-9_\-\.\u0400-\u04FF\s]/g, "")
.trim();
const safeAlbum = album
.replace(/[^a-zA-Z0-9_\-\.\u0400-\u04FF\s]/g, "")
.trim();
const safeMbid = mbid.replace(/[^a-zA-Z0-9\-]/g, "");
const MBFilePath = path.join(
process.cwd(),
"public",
"covers",
"mb",
`${safeMbid}.png`,
);
const iTunesFilePath = path.join(
process.cwd(),
"public",
"covers",
"itunes",
safeArtist,
`${safeAlbum}.png`,
);
let resultFilePath = MBFilePath;
if (fs.existsSync(MBFilePath) || fs.existsSync(iTunesFilePath)) {
if (fs.existsSync(MBFilePath)) resultFilePath = MBFilePath;
else resultFilePath = iTunesFilePath;
console.log("[DEBUG]: cover found on server");
if (fs.existsSync(MBFilePath)) {
resultFilePath = MBFilePath;
} else if (fs.existsSync(iTunesFilePath)) {
resultFilePath = iTunesFilePath;
} else {
try {
await download_image_from_itunes(artist, album);
await download_image_from_itunes(artist, album, safeArtist, safeAlbum);
resultFilePath = iTunesFilePath;
} catch (first_error) {
try {
await download_image_from_caa(mbid);
if (safeMbid && safeMbid != "0")
await download_image_from_caa(safeMbid);
else throw first_error;
resultFilePath = MBFilePath;
} catch (secong_error) {
console.error(
@@ -50,7 +74,7 @@ async function get_image_from_server(
first_error,
secong_error,
);
resultFilePath = "public/covers/0.png";
return new Response("Cover not found", { status: 404 });
}
}
}

View File

@@ -2,8 +2,8 @@
// Visit https://aka.ms/tsconfig to read more about this file
"compilerOptions": {
// File Layout
// "rootDir": "./src",
// "outDir": "./dist",
"rootDir": "./src",
"outDir": "./dist",
// Environment Settings
// See also https://aka.ms/tsconfig/module
"target": "esnext",