feat: import artist metadata

This commit is contained in:
Cory Dransfeldt 2024-11-10 17:33:40 -08:00
parent 1368b738ae
commit 96bff400e8
No known key found for this signature in database
4 changed files with 235 additions and 12 deletions

41
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "coryd.dev", "name": "coryd.dev",
"version": "2.8.5", "version": "2.9.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "coryd.dev", "name": "coryd.dev",
"version": "2.8.5", "version": "2.9.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@11ty/eleventy-fetch": "4.0.1", "@11ty/eleventy-fetch": "4.0.1",
@ -29,6 +29,7 @@
"html-minifier-terser": "^7.2.0", "html-minifier-terser": "^7.2.0",
"html-to-text": "^9.0.5", "html-to-text": "^9.0.5",
"http-proxy-middleware": "3.0.3", "http-proxy-middleware": "3.0.3",
"i18n-iso-countries": "7.13.0",
"ics": "^3.8.1", "ics": "^3.8.1",
"linkedom": "0.18.5", "linkedom": "0.18.5",
"luxon": "^3.5.0", "luxon": "^3.5.0",
@ -36,7 +37,7 @@
"markdown-it-anchor": "^9.2.0", "markdown-it-anchor": "^9.2.0",
"markdown-it-footnote": "^4.0.0", "markdown-it-footnote": "^4.0.0",
"markdown-it-prism": "^2.3.0", "markdown-it-prism": "^2.3.0",
"postcss": "^8.4.47", "postcss": "^8.4.48",
"postcss-import": "^16.1.0", "postcss-import": "^16.1.0",
"postcss-import-ext-glob": "^2.1.1", "postcss-import-ext-glob": "^2.1.1",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
@ -1166,9 +1167,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001679", "version": "1.0.30001680",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001679.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz",
"integrity": "sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==", "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -1667,6 +1668,13 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/diacritics": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz",
"integrity": "sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==",
"dev": true,
"license": "MIT"
},
"node_modules/dom-serializer": { "node_modules/dom-serializer": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@ -2700,6 +2708,19 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0" "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
} }
}, },
"node_modules/i18n-iso-countries": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.13.0.tgz",
"integrity": "sha512-pVh4CjdgAHZswI98hzG+1BItQlsQfR+yGDsjDISoWIV/jHDAvCmSyZ5vj2YWwAjfVZ8/BhBDqWcFvuGOyHe4vg==",
"dev": true,
"license": "MIT",
"dependencies": {
"diacritics": "1.3.0"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -3833,9 +3854,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.47", "version": "8.4.48",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.48.tgz",
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "integrity": "sha512-GCRK8F6+Dl7xYniR5a4FYbpBzU8XnZVeowqsQFYdcXuSbChgiks7qybSkbvnaeqv0G0B+dd9/jJgH8kkLDQeEA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -3854,7 +3875,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.3.7", "nanoid": "^3.3.7",
"picocolors": "^1.1.0", "picocolors": "^1.1.1",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
}, },
"engines": { "engines": {

View file

@ -1,6 +1,6 @@
{ {
"name": "coryd.dev", "name": "coryd.dev",
"version": "2.8.5", "version": "2.9.0",
"description": "The source for my personal site. Built using 11ty (and other tools).", "description": "The source for my personal site. Built using 11ty (and other tools).",
"type": "module", "type": "module",
"engines": { "engines": {
@ -46,6 +46,7 @@
"html-minifier-terser": "^7.2.0", "html-minifier-terser": "^7.2.0",
"html-to-text": "^9.0.5", "html-to-text": "^9.0.5",
"http-proxy-middleware": "3.0.3", "http-proxy-middleware": "3.0.3",
"i18n-iso-countries": "7.13.0",
"ics": "^3.8.1", "ics": "^3.8.1",
"linkedom": "0.18.5", "linkedom": "0.18.5",
"luxon": "^3.5.0", "luxon": "^3.5.0",
@ -53,7 +54,7 @@
"markdown-it-anchor": "^9.2.0", "markdown-it-anchor": "^9.2.0",
"markdown-it-footnote": "^4.0.0", "markdown-it-footnote": "^4.0.0",
"markdown-it-prism": "^2.3.0", "markdown-it-prism": "^2.3.0",
"postcss": "^8.4.47", "postcss": "^8.4.48",
"postcss-import": "^16.1.0", "postcss-import": "^16.1.0",
"postcss-import-ext-glob": "^2.1.1", "postcss-import-ext-glob": "^2.1.1",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",

View file

@ -0,0 +1,186 @@
import slugify from "slugify";
import countries from "i18n-iso-countries";
countries.registerLocale(require("i18n-iso-countries/langs/en.json"));
function sanitizeMediaString(str) {
const sanitizedString = str
.normalize("NFD")
.replace(/[\u0300-\u036f\u2010\-\.\?\(\)\[\]\{\}]/g, "")
.replace(/\.{3}/g, "");
return slugify(sanitizedString, {
replacement: "-",
remove: /[#,&,+()$~%.'\":*?<>{}]/g,
lower: true,
});
}
export default {
async fetch(request, env) {
const directusUrl = env["DIRECTUS_URL"];
const directusToken = env["DIRECTUS_API_TOKEN"];
const artistImportToken = env["ARTIST_IMPORT_TOKEN"];
const artistFlowID = env["ARTIST_FLOW_ID"];
const albumFlowID = env["ALBUM_FLOW_ID"];
const placeholderImageId = "4cef75db-831f-4f5d-9333-79eaa5bb55ee";
const requestUrl = new URL(request["url"]);
const providedToken = requestUrl.searchParams.get("token");
if (!providedToken || providedToken !== artistImportToken) {
return new Response("Unauthorized", { status: 401 });
}
async function saveToDirectus(endpoint, payload) {
const response = await fetch(`${directusUrl}/items/${endpoint}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${directusToken}`,
},
body: JSON.stringify(payload),
});
const data = await response.json();
if (!response.ok) {
throw new Error(
data["errors"]
? data["errors"][0]["message"]
: "Failed to save to Directus"
);
}
return data["data"];
}
async function findGenreIdByName(genreName) {
try {
const response = await fetch(
`${directusUrl}/items/genres?filter[name][_eq]=${encodeURIComponent(
genreName.toLowerCase()
)}`,
{ headers: { Authorization: `Bearer ${directusToken}` } }
);
const data = await response.json();
return data["data"].length > 0 ? data["data"][0]["id"] : null;
} catch (error) {
console.error("Error fetching genre ID:", error["message"]);
return null;
}
}
const artistId = requestUrl.searchParams.get("artist_id");
if (!artistId)
return new Response("artist_id parameter is required", { status: 400 });
let artistData;
try {
const artistResponse = await fetch(
`${directusUrl}/flows/trigger/${artistFlowID}?artist_id=${artistId}&import_token=${artistImportToken}`,
{ headers: { Authorization: `Bearer ${directusToken}` } }
);
artistData = await artistResponse.json();
artistData =
artistData["get_artist_data"]["data"]["MediaContainer"]["Metadata"][0];
} catch (error) {
console.error(
"Error fetching artist data from Directus flow:",
error["message"]
);
return new Response("Error fetching artist data", { status: 500 });
}
const artistName = artistData["title"] || "";
const artistKey = sanitizeMediaString(artistName);
const countryName = artistData["Country"]
? artistData["Country"][0]?.["tag"]
: "";
const countryIsoCode = countries.getAlpha2Code(countryName, "en") || "";
const slug = `/music/artists/${artistKey}-${countryName.toLowerCase()}`;
const description = artistData["summary"] || "";
const mbid = artistData["Guid"]?.[0]?.["id"]?.replace("mbid://", "") || "";
const genreNames = artistData["Genre"]
? artistData["Genre"].map((g) => g["tag"].toLowerCase())
: [];
let genreId = null;
for (const genreName of genreNames) {
genreId = await findGenreIdByName(genreName);
if (genreId) break;
}
const artistPayload = {
name: artistName,
name_string: artistName,
slug: slug,
description: description,
mbid: mbid,
tentative: true,
genres: genreId,
country: countryIsoCode,
art: placeholderImageId,
};
let insertedArtist;
try {
insertedArtist = await saveToDirectus("artists", artistPayload);
} catch (error) {
console.error("Error saving artist:", error["message"]);
return new Response("Error saving artist", { status: 500 });
}
let albumData;
try {
const albumResponse = await fetch(
`${directusUrl}/flows/trigger/${albumFlowID}?artist_id=${artistId}&import_token=${artistImportToken}`,
{ headers: { Authorization: `Bearer ${directusToken}` } }
);
albumData = await albumResponse.json();
albumData =
albumData["get_album_data"]["data"]["MediaContainer"]["Metadata"];
} catch (error) {
console.error(
"Error fetching album data from Directus flow:",
error["message"]
);
return new Response("Error fetching album data", { status: 500 });
}
for (const album of albumData) {
const albumName = album["title"] || "";
const albumKey = `${artistKey}-${sanitizeMediaString(albumName)}`;
const albumSlug = `/music/albums/${albumKey}`;
const albumDescription = album["summary"] || "";
const albumReleaseDate = album["originallyAvailableAt"] || "";
const albumReleaseYear = albumReleaseDate
? new Date(albumReleaseDate).getFullYear()
: null;
const albumGenres = album["Genre"]
? album["Genre"].map((g) => g["tag"])
: [];
const albumMbid =
album["Guid"]?.[0]?.["id"]?.replace("mbid://", "") || null;
const albumPayload = {
name: albumName,
key: albumKey,
slug: albumSlug,
mbid: albumMbid,
description: albumDescription,
release_year: albumReleaseYear,
artist: insertedArtist["id"],
artist_name: artistName,
genres: albumGenres,
art: placeholderImageId,
tentative: true,
};
try {
await saveToDirectus("albums", albumPayload);
} catch (error) {
console.error("Error saving album:", error["message"]);
}
}
return new Response("Artist and albums synced successfully", {
status: 200,
});
},
};

View file

@ -0,0 +1,15 @@
name = "import-artist-worker"
main = "./index.js"
compatibility_date = "2023-01-01"
account_id = "${CF_ACCOUNT_ID}"
workers_dev = true
[observability]
enabled = true
[env.production]
name = "import-artist-worker-production"
routes = [
{ pattern = "coryd.dev/api/import-artist*", zone_id = "${CF_ZONE_ID}" }
]