Questo progetto è stato sviluppato per un esame. Il source code verrà reso pubblico a conclusione del tempo disponibile alla consegna di tutti i progetti del corso. Di seguito una relazione dettagliata su tutti gli aspetti del servizio creato da me.

Social Network for Music - Mainstage

Mainstage è un web service per la condivisone di playlist, sviluppato con Express.js. La piattaforma offre funzionalità di ricerca di canzoni e playlist della piattaforma create dagli utenti, nonchè la creazione, gestione e organizzazione di playlist.

Core functionalities

Gestione Utenti

Il servizio permette una gestione completa dell’utente: creazione, modifica e cancellazione dalla piattaforma. Di seguito tutte le informazioni del documento mongo della collezione users.

Tabella: Users Descrizione index
Object_id id univoco del documento
name Nome dell’utente  
surname Cognome dell’utente  
nickname Nickname unico
generiPreferiti Generi musicali preferiti dall’utente  
gruppiPreferiti Gruppi musicali preferiti dall’utente  
mail Indirizzo email dell’utente
password Password dell’utente (hash)  
birthdate Data di nascita dell’utente  
playlistSeguite Playlist seguite dall’utente  

esempio di documento della collezione users, il nome dei parametri può variare

endpoint REST relativi

screenshot dello swagger

Playlist

Le playlist sono la funzionalità principale del servizio. Le capacità sono affini alla logica degli utenti, quindi è possibile crearle, modificarle ed eliminarle. Vi è inoltre la possibilità di riordinare le canzoni in modo semplice e immediato.

Condivisione tra utenti

Il servizio permette la condivisione di playlist: in fase di creazione e modifica verrà chiesto all’utente se desidera rendere la playlist privata, ovvero visibile solo a lui, o pubblica, visibile a tutti gli utenti della piattaforma. Si ha anche la possibilità, tramite la homepage, di seguire una playlist, permettendo la visualizzazione rapida, tramite la pagina di playlist, di tutte le playlist seguite.

Tabella: Playlist Descrizione index
Object_id id univoco del documento
name Nome della playlist  
descrizione Descrizione della playlist  
hashtags hashtag associati alla playlist  
visibility Visibilità della playlist (pubblica, privata, condivisa)  
img riferimento all’immagine associata alla playlist  
dataCreazione Data di creazione della playlist  
canzoni array di id_canzoni nella playlist  
user_id ID univoco dell’utente che ha creato la playlist  

esempio di documento della collezione users, il nome dei parametri può variare

endpoint REST relativi

screenshot dello swagger

Songs

Le canzoni, componente principale della piattaforma, non vengono direttamente salvate nel database, bensì ottenute in base al loro id.

screenshot di un documento ‘playlist’

Le informazioni principali mostrate sono:

  • Titolo
  • Artista/i
  • Album
  • Durata
  • Copertina

Architettura del servizio

immagine esplicativa dell’architettura del servizio

MongoDB & Spotify API

Le basi di dati utilizzate sono due:

  • MongoDB: database non relazionale in cui sono conservate le collezioni users e playlists
  • Spotify API: API proprietarie che forniscono informazioni su brani, artisti e album mostrati in Mainstage

Express.js

Express.js è un web framework basato su Node.js che permette di realizzare in modo efficiente API.

Scelte implementative & Security

Di seguito i tool più rilevanti utilizzati per la realizzazione del servizio.

JWT

I token JWT, JSON Web Token, sono dei token che permettono l’autenticazione di un’utente in modo immediato e sicuro.

immagine esemplificativa sul funzionamento dei token JWT

Funzionamento

Il funzionamento dei token JWT si basa su tre componenti principali: l’intestazione (header), il payload (contenuto) e la firma (signature).

L’intestazione contiene informazioni sul tipo di token e l’algoritmo di firma utilizzato. Il payload contiene le informazioni specifiche dell’applicazione o i dati personalizzati che vengono trasmessi insieme al token, come l’identità dell’utente o le autorizzazioni. Entrambe le sezioni sono rappresentate come oggetti JSON e vengono codificate in Base64 per creare una stringa.

La firma viene utilizzata per verificare l’autenticità del token e proteggerne l’integrità. Viene calcolata utilizzando una chiave segreta con un algoritmo di firma specificato nell’intestazione. La firma viene aggiunta al token come terza parte separata da un punto.

screenshot del sito jwt.io, utilizzato per analizzare in modo rapido token

Quando un client invia una richiesta a un server, include il token JWT nell’intestazione dell’autorizzazione o in un altro campo appropriato. Il server riceve il token e verifica la sua validità. La verifica coinvolge la decodifica del token, il controllo dell’intestazione e della firma e, se necessario, la verifica delle informazioni nel payload.

client=req.cookies.token
  try {
    jwt.verify(client,process.env.JWT_SECRET) 
  } catch (error) {
    console.log("err "+error)
    res.status(401).send({"code":401, "message":"JWT error"})
    return
  }

snippet di codice di Mainstage utilizzato per la verifica del token


MongoSanitize

mongoSanitize è un middleware node scaricabile che permette l’individuazione e rimozione di eventuali tentativi di noSQL injection.

esempio di richiesta con possibile noSQL injection

input sanitized da Mongosanitize


Validator

validator è un middleware node scaricabile che permette di verificare la conformità di vari dati secondo uno standard

if(!validator.isEmail(user.mail)){
    response['message']='invalid email'
    
  }

snippet di codice con utilizzo di validator


Multer

Multer è un middleware node scaricabile che permette l’upload di file.

  • plus: facile implementazione di upload di immagini per le copertine delle playlist.
  • malus: con docker, il path uploads non è persistente. Ciò significa che quando il container viene eliminato, di conseguenza lo saranno pure le immagini.

folder uploads con immagini caricate


Dotenv

dotenv è un modulo node che permette l’implementazione delle variabili salvate nel file .env. In Mainstage viene implementato in due modi:

  • in locale: con l’utilizzo del file
# THESE ARE FOR LOCAL SETTINGS, FOR DOCKER REFER TO DOCKER-COMPOSE
PORT="3300"
CLIENT_ID="xxx"
SECRET="xxx"
TOKEN_URL="https://accounts.spotify.com/api/token"
MONGO_URL="xxx"
JWT_SECRET="xxx"

esempio di file .env

  • con docker: implementando le variabili nel docker-compose.yaml
env_file: ./mainstage-app/.env
    environment:
      - MONGO_URL=xxx
      - CLIENT_ID=xxx
      - SECRET=xxx
      - TOKEN_URL=https://accounts.spotify.com/api/token
      - PORT=3300
      - JWT_SECRET=xxx


snippet del file docker-compose.yaml


Swagger-ui & autogen

swagger-UI e autogen sono moduli node che permettono la realizzazione dello swagger.

Swagger è un insieme di strumenti open-source che facilitano la progettazione, la creazione, la documentazione e il consumo di servizi web basati su API (Application Programming Interface). Fornisce un modo standardizzato per descrivere le API RESTful, inclusi gli endpoint, i parametri, i tipi di dati, le risposte e altre informazioni pertinenti.

parte dello swagger di mainstage, accessibile a /api-docs

Front-end

Di seguito tutte le scelte implementative lato Front-end.

Bootstrap

Bootstrap è un framework front-end open-source molto popolare per lo sviluppo di siti web e applicazioni web responsive. Componente principale di Mainstage e del suo stile.

Mainstage Design

Mainstage segue un design minimalista, pulito e facilmente comprensibile.

screenshot della pagina di login di Mainstage

Core project quests

Di seguito le quest richieste per la realizzazione del progetto

  • Registrazione e login al sito
  • Gestione dati utente
  • Aggiunta/modifica/Cancellazione playlist pubbliche e private
  • visualizzazione informazioni playlist, brani e utenti
  • ricerca e visualizzazione brani

Registrazione e Login a Mainstage

pagina di registazione di Mainstage

La registazione e il login al sito vengono effettuate a /login, con un bottone che permette lo switch tramite javascript per la selezione di login e signup.

Endpoint REST implementati

  • per login: Viene implementato l’endpoint POST /login che, oltre al verificare la correttezza sintattica delle informazioni, verifica che l’utente esista e che email e password coincidano.
  • per signup: Vengono implementati diversi endpoint:
    • /getGenres, endpoint GET per ottenere da spotify i generi da selezionare come preferiti.
    • /getArtists/{name}: endpoint GET per ottenere da spotify un’artista in base alla query effettuata nella searchbar per selezionare gli artisti preferiti.
    • /signup, endpoint POST che permette la verifica sintattica delle informazioni inserite, che il nickname o la mail non siano già presenti e, infine, l’inserimento dell’utente nel database.

Extra

Particolare lavoro è stato fatto per la realizzazione della selezione degli artisti preferiti. Permette di cercare e scegliere tutti gli artisti presenti su spotify e, grazie al pratico menu dropdown, si ha la possibilità di selezionare, deselezionare e vedere gli artisti scelti.

screenshot esemplificativo

Gestione dati utente

pagina di gestione utente

I dati dell’utente, inseriti in fase di signup, sono modificabili interamente nella pagina /profile. Inoltre si ha la possibilità di eseguire logout e l’eliminazione del profilo che, oltre che a cancellare l’utente, cacellerà tutte le playlist create e eliminerà a tutti gli utenti riferimenti a tali playlist se seguite.

//delete user
    var dbUser= await pwmClient.db("pwm").collection('users').deleteOne({"nick" : userJWT.username})
    //delete followed playlist
    //find plist id
    arrayFollowed=await pwmClient.db("pwm").collection('playlists').find({"user_id" : findUser._id},{ projection: {_id: 1 } }).toArray()
    arrayNoObj=[]
    console.log(arrayFollowed)
    //stringify id
    arrayFollowed.forEach(element => {
      arrayNoObj.push(element._id.toString())
    });
    console.log(arrayNoObj)
    //delete references 
    deleteFollowed = await pwmClient.db("pwm").collection('users').updateMany({"followed": {$in : arrayNoObj}}, {$pull : {"followed" :  {$in : arrayNoObj}}} )
    //delete playlist linked to user
    deletePlaylist=  await pwmClient.db("pwm").collection('playlists').deleteMany({"user_id" : findUser._id})

NB: non tutto il codice di eliminazione presente, solo uno snippet

Endpoint REST implementati

Oltre che ai sopracitati /getGenres e /getArtists/{name}, per la modifica delle preferenze di artisti e generi, abbiamo:

  • /logout: il token jwt viene revocato, l’utente sarà reindirizzato al login.
  • /user: richiesta con metodo DELETE, permette la cancellazione totale dell’utente (come spiegato prima).
  • /user: richiesta con metodo GET, permette di ottenere le informazioni presenti nel db dell’utente.
  • /user: richiesta con metodo PUT, permette la modifica di tutte le informazioni dell’utente, anche mail, nickname e password che sono indici univoci.

screenshot dello swagger con gli endpoint utente mostrato in precedenza

Aggiunta/modifica/Cancellazione playlist pubbliche e private

screenshot della pagina playlist

Alla pagina /playlist possiamo eseguire una prima parte delle operazioni riguardanti alle playlist, core feature del servizio. Si possono visualizzare le proprie playlist pubbliche, private e quelle seguite, realizzate dagli altri utenti della piattaforma. Con l’apposito tasto è possibile aprire un menu che permette l’inserimento dei dati di una nuova playlist. Si è scelto di rendere la copertina della playlist caricabile dall’utente per renderle personalizzabili in ogni loro punto.

screenshot menù creazione di playlist

Endpoint REST implementati

  • /playlist: richiesta con metodo POST per la creazione delle playlist. Vengono usati i multipart/form-datacome richiesto da multer.
  • /playlist-yours: endpoint GET per ottenere i dati delle proprie playlist mostrate.
  • /playlist-followed: endpoint GET per ottenere i dati delle playlist seguite.

Extra

Il tasto di creazione delle playlist è stato disegnato e successivamente sviluppato tramite il programma photoshop.

sketch vs risultato finale

Visualizzazione informazioni playlist, brani e utenti

Vediamo ora la pagina di una playlist, situata a /playlistPage?p={playlist_id}.

pagina di playlist

Qui possiamo trovare tutte le funzioni rimanenti per le playlist, che sono disponibili in base a se si è proprietari o no, ovvero:

  • per i proprietari di playlist:
    • modifica
    • visualizzazione
    • cancellazione della playlist
    • riordine
    • cancellazione dei singoli brani
  • per gli altri utenti:
    • visualizzazione
    • possibilità di seguire la playlist

Endpoint REST implementati

È già stato precedentemente mostrato lo swagger relativo, in breve, il funzionamento:

  • /verified/{id} per verificare che l’utente sia il proprietario della playlist e, nel caso sia privata e non proprietaria, l’invio di un errore.
  • /playlist-song: con metodo GET per ottenere le canzoni della playlist,
  • se lo è:
    • /playlist: con i metodi PATCH e DELETE, rispettivamente per riordinare i brani e cancellare la playlist.
    • /playlist-song: con metodo DELETE che permette l’eliminazione dei singoli brani.
    • /playlist-info: con metodo PUT, permette di modificare i dati della playlist.
  • se non lo è:
    • /isfollowed/{id}: con metodo GET, verifica se l’utente segue la playlist
    • /follow-playlist: con metodo POST, permette di seguire un playlist.
    • /unfollow-playlist: con metodo POST, permette di smettere di seguire un playlist.

Extra

La funzione di riordine delle canzoni all’interno della playlist è implementato tramite una libreria esterna chiamata sortable.js. Permette facilmente di implementare un ordinamento drag and drop intuitivo.

screenshot durante il riordine delle canzoni

Ricerca e visualizzazione brani

La ricerca e la visualizzazione dei brani può essere effettuata tramite la pagina /search.

In questa pagina è possibile cercare qualsiasi brano presente su spotify e le playlist su Mainstage. Si ha la possibilità di cercare in base a: brani:

  • titolo canzone
  • artista
  • genere
  • anno

mentre per le playlist:

  • nome playlist
  • hashtags
  • brani all’interno

Vi è data particolare attenzione agli edge cases di combinazioni possibili, rendendolo robusto e affidabile.

Endpoint REST implementati

L’endpoint principale utilizzato è /search, metodo POST che permette di eseguire la ricerca sia per i brani che per le playlist. Viene poi usato l’endpoint /playlist con metodo PUT, che permette l’aggiunta dei brani alle proprie playlist.

Homepage - Extra

screenshot della homepage

Una volta effettuato l’accesso a Mainstage, la homepage accoglierà l’utente con una selezione di brani da spotify e le playlist pubbliche create dagli utenti della piattaforma. Da desktop, come nelle pagine search e playlists, si hanno a disposizione dei controlli (previous e next) che permettono lo scorrimento orizzontale degli elementi. Vengono utilizzati i due endpoint /topFiftye /playlist-public che servono rispettivamente ad ottenere i brani top 50 global e ottenere le playlist pubbliche.

Responsiveness - Mobile view

Particolare attenzione è stata data alla versione mobile, adattata alle esigenze dello schermo di dimensioni ridotte e dell’input touch. L’esempio più rilevante è la visualizzazione dei brani nella homepage e delle playlist in tutta la piattaforma, adattate con uno scorrimento touch che sostituiscono i comani previous e next usati sul desktop.

Virtualizzazione & server

Il servizio è stato virtualizzato tramite docker, tramite il file docker-compose.yamlverranno generati due container: mongodb_snm e snm-mainstage_app.

screenshot dei container running sul server

Architettura del servizio

Il servizio è reso disponibile dal mio server personale, tramite una virtual machine utilizzando l’OS unraid. Sulla repository di github si possono trovare le istruzioni su come avviare i container sul proprio server. tramite un Dynamic Domain Name System (DDNS) associo il mio ip ad un dominio intermedio, che verrà a sua volta associato tramite cloudflare al mio personale dominio: mainstage.mattiaperfumo.it