feat: data storage

This commit is contained in:
Cory Dransfeldt 2024-05-07 17:09:47 -07:00
parent bcc6ea0987
commit d8137dca96
No known key found for this signature in database
29 changed files with 428 additions and 14449 deletions

View file

@ -1,4 +1,8 @@
import { getStore } from '@netlify/blobs'
import { createClient } from '@supabase/supabase-js';
const SUPABASE_URL = process.env.SUPABASE_URL
const SUPABASE_KEY = process.env.SUPABASE_KEY
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
const emojiMap = (genre, artist) => {
const DEFAULT = "🎧"
@ -77,28 +81,43 @@ const emojiMap = (genre, artist) => {
}
export default async () => {
const scrobbles = getStore('scrobbles')
const { data, error } = await supabase
.from('listens')
.select(`
track_name,
artist_name,
artists (mbid, genre),
`)
.order('listened_at', { ascending: false })
.limit(1);
const headers = {
"Content-Type": "application/json",
"Cache-Control": "public, s-maxage=360, stale-while-revalidate=1080",
}
const scrobbleData = await scrobbles.get('now-playing', { type: 'json'})
};
if (!scrobbleData) return new Response(JSON.stringify({}, { headers }))
if (error) {
console.error('Error fetching data:', error);
return new Response(JSON.stringify({ error: "Failed to fetch the latest track" }), { headers });
}
if (data.length === 0) {
return new Response(JSON.stringify({ message: "No recent tracks found" }), { headers });
}
const scrobbleData = data[0]
return new Response(JSON.stringify({
content: `${emojiMap(
scrobbleData['genre'],
scrobbleData['artist']
)} ${scrobbleData['track']} by <a href="${scrobbleData['url']}">${
scrobbleData['artist']
}</a>`,
}),
{ headers }
)
}
content: `${emojiMap(
scrobbleData.artists.genre,
scrobbleData.artist_name
)} ${scrobbleData.track_name} by <a href="http://musicbrainz.org/artist/${scrobbleData.artists.mbid}">${
scrobbleData.artist_name
}</a>`,
}), { headers });
};
export const config = {
cache: "manual",
path: "/api/now-playing"
}
};

View file

@ -1,90 +1,78 @@
import { getStore } from '@netlify/blobs'
import { createClient } from '@supabase/supabase-js'
import { DateTime } from 'luxon'
const SUPABASE_URL = Netlify.env.get('SUPABASE_URL')
const SUPABASE_KEY = Netlify.env.get('SUPABASE_API_KEY')
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
const sanitizeMediaString = (string) => string.normalize('NFD').replace(/[\u0300-\u036f\u2010—\.\?\(\)\[\]\{\}]/g, '').replace(/\.{3}/g, '')
const weekKey = () => {
const currentDate = DateTime.now();
return `${currentDate.year}-${currentDate.weekNumber}`
}
const filterOldScrobbles = (scrobbles) => {
const windowEnd = DateTime.now().minus({ days: 7 });
return scrobbles.filter(scrobble => {
const timestamp = DateTime.fromISO(scrobble.timestamp);
return timestamp >= windowEnd;
});
}
export default async (request) => {
const ACCOUNT_ID_PLEX = Netlify.env.get('ACCOUNT_ID_PLEX');
const MUSIC_KEY = Netlify.env.get('API_KEY_LASTFM');
const params = new URL(request['url']).searchParams
const ACCOUNT_ID_PLEX = process.env.ACCOUNT_ID_PLEX
const params = new URL(request.url).searchParams
const id = params.get('id')
if (!id) return new Response(JSON.stringify({
status: 'Bad request',
}),
{ headers: { "Content-Type": "application/json" } }
)
if (id !== ACCOUNT_ID_PLEX) return new Response(JSON.stringify({
status: 'Forbidden',
}),
{ headers: { "Content-Type": "application/json" } }
)
if (!id) return new Response(JSON.stringify({ status: 'Bad request' }), { headers: { "Content-Type": "application/json" } })
if (id !== ACCOUNT_ID_PLEX) return new Response(JSON.stringify({ status: 'Forbidden' }), { headers: { "Content-Type": "application/json" } })
const data = await request.formData()
const payload = JSON.parse(data.get('payload'))
const artists = getStore('artists')
const scrobbles = getStore('scrobbles')
if (payload?.event === 'media.scrobble') {
const artist = payload['Metadata']['grandparentTitle']
const album = payload['Metadata']['parentTitle']
const track = payload['Metadata']['title']
const trackNumber = payload['Metadata']['index']
const timestamp = DateTime.now()
const artistsMap = await artists.get('artists-map', { type: 'json' })
const artistSanitizedKey = `${sanitizeMediaString(artist).replace(/\s+/g, '-').toLowerCase()}`
const trackScrobbleData = {
track,
album,
artist,
trackNumber,
timestamp,
genre: artistsMap[artistSanitizedKey]?.['genre'] || ''
const artist = payload.Metadata.grandparentTitle
const album = payload.Metadata.parentTitle
const track = payload.Metadata.title
const listenedAt = DateTime.now().toISO()
const artistKey = sanitizeMediaString(artist).replace(/\s+/g, '-').toLowerCase()
const albumKey = `${artistKey}-${sanitizeMediaString(album).replace(/\s+/g, '-').toLowerCase()}`
const { data: albumData, error: albumError } = await supabase
.from('albums')
.select('*')
.eq('key', albumKey)
.single()
if (albumError && albumError.code === 'PGRST116') {
const albumImageUrl = `https://coryd.dev/media/albums/${albumKey}.jpg`
const albumMBID = null
const { error: insertAlbumError } = await supabase.from('albums').insert([
{
mbid: albumMBID,
image: albumImageUrl,
key: albumKey,
name: album,
tentative: true
}
])
if (insertAlbumError) {
console.error('Error inserting album into Supabase:', insertAlbumError.message)
return new Response(JSON.stringify({ status: 'error', message: insertAlbumError.message }), { headers: { "Content-Type": "application/json" } })
}
} else if (albumError) {
console.error('Error querying album from Supabase:', albumError.message)
return new Response(JSON.stringify({ status: 'error', message: albumError.message }), { headers: { "Content-Type": "application/json" } })
}
const scrobbleData = await scrobbles.get(`${weekKey()}`, { type: 'json'})
const windowData = await scrobbles.get('window', { type: 'json'})
const artistUrl = (artistsMap[artistSanitizedKey]?.['mbid'] && artistsMap[artistSanitizedKey]?.['mbid'] !== '') ? `http://musicbrainz.org/artist/${artistsMap[artistSanitizedKey]?.['mbid']}` : `https://musicbrainz.org/search?query=${artist.replace(
/\s+/g,
'+'
)}&type=artist`
const { error: listenError } = await supabase.from('listens').insert([
{
artist_name: artist,
album_name: album,
track_name: track,
listened_at: listenedAt,
album_key: albumKey
}
])
await scrobbles.setJSON('now-playing', {...trackScrobbleData, ...{ url: artistUrl }})
let scrobbleUpdate = scrobbleData
let windowUpdate = windowData;
if (scrobbleUpdate?.['data']) scrobbleUpdate['data'].push(trackScrobbleData)
if (!scrobbleUpdate?.['data']) scrobbleUpdate = { data: [trackScrobbleData] }
if (windowData?.['data']) windowUpdate['data'].push(trackScrobbleData)
if (!windowData?.['data']) windowUpdate = { data: [trackScrobbleData] }
windowUpdate = { data: filterOldScrobbles(windowUpdate.data) }
await scrobbles.setJSON(`${weekKey()}`, scrobbleUpdate)
await scrobbles.setJSON('window', windowUpdate)
if (listenError) {
console.error('Error inserting data into Supabase:', listenError.message)
return new Response(JSON.stringify({ status: 'error', message: listenError.message }), { headers: { "Content-Type": "application/json" } })
}
}
return new Response(JSON.stringify({
status: 'success',
}),
{ headers: { "Content-Type": "application/json" } }
)
return new Response(JSON.stringify({ status: 'success' }), { headers: { "Content-Type": "application/json" } })
}
export const config = {
path: '/api/scrobble',
}
}