Skip to content
Sep Duijkersloot edited this page Jun 13, 2024 · 16 revisions

Verslag per week

Week 3

Wat heb ik deze week gedaan per competentie?

  • Orienteren en begrijpen -> Ik heb deze week op mijzelf onderzoek gedaan naar de OAuth van spotify en de diverse endpoints die hun Web API heeft, meer info is te vinden bij het onderzoek. Daarnaast heb ik verder de volgende tutorials gevolgd om github beter te begrijpen/leren: Github crashcourse

  • Prototpyen en uitwerken -> Ik heb een werkend prototype gemaakt waarin ik kan verbinden met de Spotify Web API met de OAuth. In dit prototype krijg ik ook wat nummers aanbevolen, op basis van artiesten en nummers die ik heb aangegeven. Op dit principe gaat onze app werken. Meer info bij het onderzoek

Welke hulpvragen heb ik?

Deze week nog niks.

Welke feedbackvragen heb ik?

  • (Team) Is het concept wat jullie ook voor ogen hadden?

Week 4

Wat heb ik deze week gedaan per competentie?

  • Verbeelden en conceptualiseren -> Ik heb gewerkt (samen met het team) aan ons concept, wat nu Juke is geworden. Hier heb ik meegeholpen aan een moodboard, logo ideatie. Daarnaast heb ik een persona, empathy map en job stories gemaakt, wat aansluit bij ons persona en hun benodigheden. Resultaten zijn te zien op de ontwerp pagina (Moodboard, logo, stylesheet)

  • Samen ontwerpen -> In groepsverband hebben we gewerkt aan het design en concept van onze web-app, wat nu Juke gaat heten. Tijdens deze werkgroep hebben we actief feedback aan elkaar gevraagd en zijn zo samen tot een mooi en passende oplossing gekomen, resultaat hiervan is te vinden op de ontwerp pagina (Moodboard, logo, stylesheet)

Welke hulpvragen heb ik?

Welke dingen zijn nog niet duidelijk of moeilijk, waarbij kun je hulp gebruiken?

  • (Docent) Vraag over endpoints, hoe gaat het werken met dat :id? Bijvoorbeeld om een genre te discoveren

Welke feedbackvragen heb ik?

Benoem concrete vragen over het werk wat je deze week hebt gedaan, die je aan je teamgenoten en/of docenten wilt stellen

  • (Team) Wat zouden jullie veranderen aan de wireframes?
  • (Team) Denk je dat je als buitenstaander zou kunnen begrijpen wat de app nu doet?
  • (Team) Zijn de huidige job stories in orde, en wat zouden jullie nog toevoegen?

Welke feedback heb ik ontvangen?

Beschrijf de feedback die je hebt ontvangen en hoe je deze gaat gebruiken om je werk verder te verbeteren.

  • De menubalk is niet ideaal voor een web applicatie, daarom gaan we dit veranderen naar een soort humburger menu variant. Verder onderzoek naar mogelijkheden is nog nodig.
  • Job stories, waren in orde. Hebben wel een User Requirement list opgesteld, die we hebben gerangschikt op belangrijk, m.b.v. de MoSCoW techniek, zie ontwerp pagina voor meer info.

Week 5

Wat heb ik deze week gedaan per competentie?

  • Prototypen en uitwerken -> Authorization prototype gemaakt, maar nu zonder PKCE standard. Dus standard "authorization code", hiervoor gekozen omdat het beter past bij server-side JavaScript. Link naar prototype

  • Oriënteren en begrijpen -> Verder onderzoek gedaan naar spotify authorization, ben erachter gekomen dat we de "authorization code" flow moesten gebruiken, aangezien deze beter past bij bij een server-side JS. Meer info te vinden bij het onderzoek over de spotify OAuth

  • Oriënteren en begrijpen -> Ik heb, samen met Vivanne, de User Requirement List opgesteld, zodat we de eindbelangen van de gebruiker goed kunnen neerzetten. Daarna hadden we deze gerangschikt met de MoSCoW methode, dit helpt met onze prioriteiten.

  • Samen ontwerpen -> Zoals ik net had vermeld, had ik samen met Vivanne gewerkt aan de User Requirement List en deze gerangschikt met de MoSCoW methode.

Welke hulpvragen heb ik?

Welke dingen zijn nog niet duidelijk of moeilijk, waarbij kun je hulp gebruiken?

  • Welke Authorization flow moet ik gaan gebruiken?

Welke feedbackvragen heb ik?

Benoem concrete vragen over het werk wat je deze week hebt gedaan, die je aan je teamgenoten en/of docenten wilt stellen

  • Geen vraag, maar ik ga het hebben over code standards met het team.
  • Navragen over login page design.

Welke feedback heb ik ontvangen?

Beschrijf de feedback die je hebt ontvangen en hoe je deze gaat gebruiken om je werk verder te verbeteren.

Week 6

Wat heb ik deze week gedaan per competentie?

Beschrijf wat je deze week hebt gedaan in termen van de competenties waaraan we in dit project werken. Je zult niet elke week aan elke competentie werken, maar in het hele project komen ze als het goed is wel allemaal aan de orde. De voorbeeldvragen die zijn genoemd bij de uitleg over de wiki op DLO kunnen je hierbij helpen.

  • Samen ontwerpen -> Ik had met Vivanne (back-ender) gewerkt aan een ToDo list voor onze back-end taken, zodat we deze konden verdelen, om zo een goede balans te behouden tussen ons werk.

  • Oriënteren en begrijpen -> Ik heb mijzelf verdiept in database structuren. Voor onze applicatie willen we voornamelijk: de recommended nummers en gebruikers activiteit opslaan in de database. Ik heb dit geprompt bij wat AI-tools:

ChatGPT

User collection:

  "_id": ObjectId("user_id"),
  "spotifyId": "user_spotify_id",
  "displayName": "User Name",
  "email": "user@example.com",
  "likedSongs": [
    ObjectId("song_id1"),
    ObjectId("song_id2"),
    ...
  ],
  "dislikedSongs": [
    ObjectId("song_id3"),
    ObjectId("song_id4"),
    ...
  ],
  "swipeHistory": [
    {
      "songId": ObjectId("song_id1"),
      "action": "liked",
      "timestamp": ISODate("2024-05-30T12:34:56Z")
    },
    {
      "songId": ObjectId("song_id3"),
      "action": "disliked",
      "timestamp": ISODate("2024-05-30T12:35:56Z")
    },
    ...
  ]
}

Songs collection:

{
  "_id": ObjectId("song_id"),
  "title": "Song Title",
  "artist": "Artist Name",
  "album": "Album Name",
  "spotifyId": "song_spotify_id",
  "duration": 210, // duration in seconds
  "genre": "Genre",
  ...
}

Swipe collection (geschiedenis)

{
  "_id": ObjectId("swipe_id"),
  "userId": ObjectId("user_id"),
  "songId": ObjectId("song_id"),
  "action": "liked", // or "disliked"
  "timestamp": ISODate("2024-05-30T12:34:56Z")
}

Claude AI

User collection:

db.users.insertOne({
  name: "John Doe",
  email: "john@example.com",
  likedSongs: [],
  dislikedSongs: []
})

Songs collection:

db.songs.insertOne({
  title: "Song Title",
  artist: "Artist Name",
  album: "Album Name",
  likes: [],
  dislikes: [],
  swipes: 0
})

Ik ga de database structuur baseren op het idee van Claude AI, omdat deze efficienter voor ons geval en doeleind is. De structuur van Claude AI is ook overzichtelijker, door maar 2 collections te gebruiken i.p.v. 3.

  • Prototypen en uitwerken -> Ik ben bezig geweest met de functionaliteit van nummers liken en disliken. Ik heb er voornamelijk voor gezorgd dat de back-end de data van de front-end goed krijgt doorgegeven en daarna heb ervoor gezorgd dat de liked en disliked nummer worden opslagen in de database onder de juiste gebruiker. Ook heb ik gewerkt aan de functionaliteit van playlists toevoegen aan Spotify. Een obstakel waar ik tegenaan liep was dat ik wou kijken of er al een "My Juke Playlist" was aangemaakt, zodat we daar weer nummers in konden plaatsen. Ik had toen dit geschreven:
async function fetchUserPlaylists(req) {
    try {
        const playlists = await fetchWebApi(
            req,
            'v1/me/playlists', 
            'GET'
        )
        console.log('Fetched playlists:', playlists.items) // Debug log
        return playlists.items
    } catch (error) {
        console.error('Error fetching user playlists:', error)
    }
}

// Function to create the Juke Playlist
async function createJukePlaylist(req) {
    try {
        const userInfo = req.session.user

        // Fetch user's playlists
        const userPlaylists = await fetchUserPlaylists(req)

        if (!userPlaylists) {
            throw new Error('Failed to fetch user playlists.')
        }

        // Check if "Your Juke Playlist" exists
        const existingPlaylist = userPlaylists.find(playlist => playlist.name === "Your Juke Playlist")

        if (existingPlaylist) {
            console.log('Playlist "Your Juke Playlist" already exists:', existingPlaylist)
            return existingPlaylist
        } else {
            // Create the playlist if it doesn't exist
            const newPlaylist = await fetchWebApi(
                req,
                `v1/users/${userInfo.id}/playlists`, 
                'POST', 
                {
                    name: "Your Juke Playlist",
                    description: "Playlist created by the tutorial on developer.spotify.com",
                    public: false
                }
            )
            console.log('Created new playlist:', newPlaylist)
            return newPlaylist
        }
    } catch (error) {
        console.error('Error creating playlist:', error)
    }
}

// Route to handle playlist creation
app.get('/create-playlist', async (req, res) => {
    try {
        const playlist = await createJukePlaylist(req)
        console.log("createdPlaylist", playlist)
        res.redirect('/')
    } catch (error) {
        console.error('Error url - creating playlist:', error)
    }
})

Maar ik besefte mij dat dit erg veel request en code was voor een eigenlijk simpele taak. Ook zou deze code breken als de gebruiker de naam van de playlist zou veranderen etc. Ik ben toen op het idee gekomen om gewoon het ID van de playlist 1 keer op te slaan in de database onder de gebruikers naam, zodat ik deze altijd snel en makkelijk kon vinden en het minder ingewikkeld werd.

Dit is de uiteindelijke code geworden:

async function JukePlaylist(req) {
    try {
        const userInfo = req.session.user
        const user = await usersCollection.findOne({ _id: userInfo.id }, { playlist_id: 1 }) // playlist_id: 1, to only return the playlist_id field

        if (user.playlist_id) {
            console.log('Playlist_id exists:', user.playlist_id)

            return user.playlist_id
        } else {
            console.log('Playlist does not exist yet')

            const playlist = await fetchWebApi(
                req,
                `v1/users/${userInfo.id}/playlists`, 
                'POST', 
                {
                    name: "My Juke Playlist",
                    description: "Playlist created by the tutorial on developer.spotify.com",
                    public: false
                }
            )

            // Set playlist_id in the user's DB object
            await usersCollection.updateOne(
                { _id: userInfo.id },
                { $set: { playlist_id: playlist.id } }
            )

            console.log('Created playlist:', playlist.id)
            return playlist.id
        }
    } catch (error) {
        console.error('Error creating playlist:', error)
    }
}

// Route to handle playlist creation
app.get('/create-playlist', async (req, res) => {
    try {
        const playlist = await JukePlaylist(req)
        // console.log("createdPlaylist", playlist)
        res.redirect('/')
    } catch (error) {
        console.error('Error url - creating playlist:', error)
    }
})

Welke hulpvragen heb ik?

  • Vraag over database structuur, had aan wat AI tools gevraagd om te helpen met database structuren te bedenken. Samen met docent kijken welke we kunnen toepassen of wat we moeten aanpassen.
  • Vraag over hoe we vanuit front-end, back-end functies moeten aanroepen? Bv. Song liken: event triggered, en hoe dan naar database en in spotify playlist?

Welke feedbackvragen heb ik?

Benoem concrete vragen over het werk wat je deze week hebt gedaan, die je aan je teamgenoten en/of docenten wilt stellen

  • Info verzamelen data dat we verzamelen: Even op een lijstje zetten, wat voor data willen we allemaal verzamelen en opslaan. Loop de app samen door en denk erover na
  • Wanneer willen we front-end en back-end gaan samenvoegen?

Welke feedback heb ik ontvangen?

Beschrijf de feedback die je hebt ontvangen en hoe je deze gaat gebruiken om je werk verder te verbeteren.

Week 7

Wat heb ik deze week gedaan per competentie?

Beschrijf wat je deze week hebt gedaan in termen van de competenties waaraan we in dit project werken. Je zult niet elke week aan elke competentie werken, maar in het hele project komen ze als het goed is wel allemaal aan de orde. De voorbeeldvragen die zijn genoemd bij de uitleg over de wiki op DLO kunnen je hierbij helpen.

  • Evalueren -> Tijdens het testen van de site kwam Vivanne erachter dat niet elke recommendation die wij opvragen van de Spotify web API, een preview_url heeft die wij kunnen gebruiken om een kort voorstuk van het nummer te laten horen aan de gebruiker. Dit zou voor een slechte gebruikers ervaring zorgen, daarom vroeg Vivanne aan mij: "Sommige recommendations hebben geen preview link, zou je ervoor kunnen zorgen dat we alleen de nummers gebruiken die wel een preview hebben?". Daarom ben ik aan de slag gegaan met het maken van een functie die de recommendation filtert. Tijdens dit process was ik erachter gekomen dat nummers soms dubbel werden aanbevolen, terwijl je ze al had geliked of disliked. Dit zou ook echt vervelend zijn voor de gebruikers ervaring. Dus ben ik gaan door itereren op de filter functionaliteit en hier een oplossing voor gemaakt.

  • Prototypen en uitwerken -> Zoals ik net had vermeld bij de competentie: evalueren, Om de functionaliteit en gebruikers vriendelijkheid heb ik de functionaliteit van de recommendations fetchen aangepast. Ik heb een functionaliteit gemaakt die de recommendations, die geen preview_url hebben en recommendations die al eerder zijn aanbevolen, eruit filtert. Ik was begonnen met het maken van de filter voor de preview_urls, maar toen eenmaal bezig was kwam ik erachter dat sommige recommendations meerdere malen voor voorgesteld aan de gebruiker terwijl ze deze al eerder hebben geliked/disliked. Ik heb dus meteen dit meegepakt in de filterRecommendation functie.
    Dit is de vernieuwde code geworden voor het fetchen van de recommendations, inclusief filter:

async function getRecommendations(req, seedTracks, limit) {
    try {
        let approvedRecommendations = [];
        let remainingLimit = limit;

        while (remainingLimit > 0) {
            const recommendations = (
                await fetchWebApi(
                    req,
                    `v1/recommendations?limit=${remainingLimit}&seed_tracks=${seedTracks.join(',')}`,
                    'GET'
                )
            ).tracks;

            const filteredRecommendations = await filterRecommendations(req, recommendations);
            approvedRecommendations.push(...filteredRecommendations);
            remainingLimit = limit - approvedRecommendations.length;
        }

        return approvedRecommendations.slice(0, limit);
    } catch (error) {
        console.error('Error getting recommendations:', error);
    }
}

async function filterRecommendations(req, recommendations) {
    try {
        const filteredRecommendations = recommendations.filter(async (track) => {
            if (!hasPreviewUrl(track)) {
                console.log(`Track "${track.name}" doesn't have a preview_url`);
                return null;
            }

            const userRecommendations = await usersCollection.findOne(
                {
                    _id: req.session.user.id,
                    'recommendations._id': track.id
                },
                { projection: { _id: 1 } }
            );

            if (userRecommendations) {
                console.log(`Track "${track.name}" already registered.`);
                return null;
            }

            return track;
        });

        return filteredRecommendations.filter(Boolean);
    } catch (error) {
        console.error('Error filtering recommendations:', error);
    }
}

function hasPreviewUrl(track) {
    return track.preview_url !== null && track.preview_url !== '';
}

Week 8

Wat heb ik deze week gedaan per competentie?

Beschrijf wat je deze week hebt gedaan in termen van de competenties waaraan we in dit project werken. Je zult niet elke week aan elke competentie werken, maar in het hele project komen ze als het goed is wel allemaal aan de orde. De voorbeeldvragen die zijn genoemd bij de uitleg over de wiki op DLO kunnen je hierbij helpen.

  • Samen ontwerpen -> Vivanne was bezig met het maken van functionaliteit waar de gebruiker kan zoeken op nummers en artiesten, zodat ze vervolgens op een specifiek nummer/artiest recommendations kunnen krijgen. Zij had de zoekbalk en resultaten al helemaal werkend, ze had alleen hulp nodig met het goed werkend krijgen van deze specifieke recommendations, ze had het aanbevelen op basis van nummers al redelijk werkend, maar de artiesten wou maar niet lukken. Ze vroeg het volgende aan mij: "Kan je mij helpen om deze recommendations te laten werken? Ik krijg de artiesten maar niet werkend". Ik ben vervolgens gaan door itereren op haar eigen code en heb de volgende aanpassingen gemaakt:

    • Form method naar GET,
    • getRecommendations functie laten werken met diverse parameters
    • seed_uri van het (gezoekte) nummers ophalen uit de url. Met deze aanpassing heb ik samen met Vivanne deze functionaliteit werkend gekregen.
  • Prototypen en uitwerken -> Ik heb door geïtereerd op de zoek-functionaliteit van Vivanne. Zij had de zoekbalk en resultaten al helemaal werkend, ze had alleen hulp nodig met het goed werkend krijgen van deze specifieke recommendations. De aanpassing wat ik heb gemaakt, om dit te laten werken zijn:

    • Form method van de "swipe" knop naar GET veranderd, dit heb ik gedaan zodat de parameters in de url komen. Een vraag die ik mijzelf stelde was namelijk: "Hoe kan ik het beste de gegevens van het nummer waar ik recommendations op zoek, opslaan", de conclusie hiervan was de url, aangezien deze globaal beschikbaar is en niet weggaat als er een nummer wordt geliked/disliked.
    • De getRecommendations functie aangepast zodat deze werkt met allerlei verschillende parameters. Nu kan het in dezelfde functie beide gevallen: seed_tracks & seed_artists, afhandelen.
    • In de front-end code de parameters uit de url halen, zodat deze gebruikt kan worden om een nieuw nummer aan te maken gebaseert op het juiste nummer/artiest. \

Dit is de code geworden:

Server.js:

/*==========================================\

           Searched recommendations

===========================================*/
app.get('/search-recommendations', async (req, res) => {
    try {
        const { seed_uri, seed_type, seed_name } = req.query

        if (!seed_uri || !seed_type) {
            return res.status(400).json({ error: 'No query provided' })
        }
        const limit = 2
        const recommendedTracks = await getRecommendations(req, limit, seed_type, [seed_uri])
        res.render('pages/recommendations', { tracks: recommendedTracks, user: req.session.user, seed_name: seed_name })
    } catch (error) {
        console.error('Error fetching recommendations:', error)
        res.status(500).json({ error: 'An error occurred while fetching recommendations.' })
    }
})

app.get('/new-search-recommendation', async (req, res) => {
    try {
        const { seed_type, seed_uri } = req.query

        if (!seed_type || !seed_uri) {
            return res.status(400).json({ error: 'Missing seed_type or seed_uri' })
        }

        const limit = 1
        const recommendedTracks = await getRecommendations(req, limit, seed_type, [seed_uri])
        res.json({ recommendation: recommendedTracks[0] })
    } catch (error) {
        console.error('Error fetching new recommendation:', error)
        res.status(500).json({ error: 'An error occurred while fetching a new recommendation.' })
    }
})


async function getRecommendations(req, limit, seed_type, seed_uri) {
    try {
        let approvedRecommendations = []
        let remainingLimit = limit
        
        while (remainingLimit > 0) {
                const recommendations = (
                    await fetchWebApi(
                        req,
                        `v1/recommendations?limit=${remainingLimit}&${seed_type}=${seed_uri.join(',')}`,
                        'GET'
                    )
                ).tracks

            const filteredRecommendations = await filterRecommendations(req, recommendations)
            approvedRecommendations.push(...filteredRecommendations)
            remainingLimit = limit - approvedRecommendations.length
        }

        return approvedRecommendations.slice(0, limit)
    } catch (error) {
        console.error('Error getting recommendations:', error)
    }
}

Welke hulpvragen heb ik?

Welke dingen zijn nog niet duidelijk of moeilijk, waarbij kun je hulp gebruiken?

Welke feedbackvragen heb ik?

Benoem concrete vragen over het werk wat je deze week hebt gedaan, die je aan je teamgenoten en/of docenten wilt stellen

Welke feedback heb ik ontvangen?

Beschrijf de feedback die je hebt ontvangen en hoe je deze gaat gebruiken om je werk verder te verbeteren.

Technisch onderzoek

Spotify API OAuth (& Endpoints)

  • Ik heb mijzelf verdiept in de Spotify Web API, met name hoe de autorisatie werkt end ook een eerste duik in de verschillende endpoints.
  • Voor dit onderzoek heb ik de Spotify developer documentatie gebruikt: https://developer.spotify.com/documentation/web-api
  • Uit mijn onderzoek is gebleken dat spotify een zeer uitgebreide documentatie heeft, wat erg fijn was en mij enorm heeft geholpen met het uitvinden van hoe alles werkt. Daarnaast heb ik een werkend protoype kunnen maken waar de gebruiker zich verifiërd en vervolgens een lijst van aanbevolen nummers krijgen (voor nu in de console), die gebaseert is op de top 5 nummers van de gebruiker. Dit idee lijkt erg op ons concept voor de app, dus dit heeft veel waarde voor ons team, zodat we weten dat we verder kunnen gaan met dit concept. Link naar de demo GitHub repo: https://github.com/SF-Duijkersloot/spotify-api-test

Verdiepend OAuth flow onderzoek

Beschrijf het volgende:

  • Voor dit onderzoek heb ik gekeken naar welke OAuth flow we moeten/het beste kunnen gebruiken. Aangezien ik hier een fout in had gemaakt bij mijn vorige onderzoek.
  • Voor dit onderzoek heb ik de Spotify developer documentatie gebruikt: https://developer.spotify.com/documentation/web-api
    • Bij mijn eerste onderzoek had ik de "Authorization code with PKCE" flow gebruikt. Maar die flow was blijkbaar bedoelt voor client-side code. Tijdens mijn onderzoek ben ik erachter gekomen dat we de "Authoruization flow" kunnen gebruiken en daar een server-side secret key te gebruiken. Daarnaast had ik een probleem met het laten zien van de scopes zodat gebruiker hierop kan agree-en. Dit kon ik oplossen door paramater: "show_dialog: true" mee te geven aan "https://accounts.spotify.com/authorize?"
  • Ik kreeg een tijd de authorization niet werkend met de "Authorization flow" en had daarom de "Authorization flow PKCE" geprobeerd. deze werkte wel met voorbereide code van spotify, maar dit bleek toch niet de juiste en gepaste flow te zijn voor onze app.
  • Wij gaan dus verder met de authorization flow aangezien we deze veilig werkend kunnen krijgen op onze server-side code en dit maakt de code voor het inloggen ook een stukje eenvoudiger dan met PKCE

Evaluatie

Beschrijf het volgende:

  • Op welke manieren heb je je prototype geevalueerd? Benoem gebruikte CMD methods
  • Beschrijf de aanpak van je test / evaluatie
  • Beschrijf de resultaten van je test / evaluatie
  • Hoe ga je deze resultaten gebruiken om je prototype verder te verbeteren?
Clone this wiki locally