Questo documento rappresenta la relazione del progetto “Social Network for Music”, sviluppato nel contesto del corso “Programmazione e Linguaggi per il Web” durante l’anno accademico 2023.
Il progetto è stato realizzato da:
Front End: Il Front-End è la parte dell’applicazione che si occupa dell’interfaccia utente e dell’interazione con l’utente. Si concentra sulla progettazione e sull’implementazione dell’aspetto visivo dell’applicazione e sulla gestione delle interazioni utente.
All’interno della directory /src/html/
, sono presenti i seguenti elementi principali:
Elementi HTML: Questi file definiscono l’interfaccia grafica dell’applicazione, determinando come l’applicazione appare nel browser.
/css/
: Questa directory contiene i file di stile che definiscono l’aspetto visivo dell’applicazione. Alcuni dei file principali includono:
/src/scripts/
: Questa directory contiene file JavaScript (JScript) che gestiscono la logica del Front-End. Alcuni dei file principali includono:
La suddivisione chiara tra file HTML, file CSS e file JavaScript (JScript) consente una gestione efficiente del Front-End e garantisce un’esperienza utente di alta qualità.
Per ulteriori dettagli sull’implementazione del Front-End, si rimanda alle specifiche sezioni dei file e dei componenti menzionati sopra.
Back End: Il Back-End è responsabile delle funzionalità e della logica dell’applicazione lato server. Esso comprende una serie di elementi chiave presenti nella nostra struttura di lavoro. Possiamo suddividere il backend in 3 sezioni principali
Node.js ed Express costituiscono un binomio potente nell’ambito dello sviluppo web di applicazioni scalabili ed efficienti.
Node.js fornisce un ambiente runtime JavaScript server-side, ottimizzato per l’efficienza e la scalabilità.
Express, un framework web basato su Node.js, semplifica la creazione di applicazioni web, offrendo funzionalità come la gestione delle richieste HTTP e dell’autenticazione.
Ulteriori info a questa pagina.
/serverlogs/
: Questa directory contiene il file di log ‘serverlogs.log’, che registra i log del server per monitorare il suo funzionamento e le policy associate.
/src/api/docs/
: In questa directory sono presenti i file utilizzati per la gestione della documentazione pubblica delle nostre API, inclusi:
/src/config/
: Questa cartella contiene i file dedicati alla configurazione dell’applicazione, ad eccezione delle variabili d’ambiente. Al suo interno, sono presenti:
/src/lib/
: La directory lib contiene tutte le funzioni Node.js utilizzate per le funzionalità degli endpoint. Inoltre, include le immagini pubbliche del sito. Alcuni dei file e delle directory principali sono:
app.js
: Questo file rappresenta il punto di ingresso principale dell’applicazione, contenente le istruzioni per l’avvio dell’app e la definizione degli endpoint.La struttura ben organizzata del Back-End garantisce una gestione efficiente delle funzionalità server-side e contribuisce al corretto funzionamento dell’applicazione.
Nel corso di sviluppo della nostra applicazione, abbiamo fatto largo uso del database MongoDB. Qui di seguito, presentiamo le collezioni che abbiamo creato e utilizzato per immagazzinare i dati essenziali dell’applicazione.
MongoDB: MongoDB è un database NoSQL (non relazionale), flessibile e scalabile, noto per la sua struttura orientata ai documenti. Un documento è un record dati in formato BSON (Binary JSON) che può contenere dati di varie forme e dimensioni. Ogni documento è organizzato in collezioni, offrendo flessibilità nella modellazione dei dati.
Per questa applicazione abbiamo deciso di utilizzare le seguenti collections:
Di seguito viene riportata una descrizione delle collections, del loro schema di validation JSON e dei tipi di dato
Validazione JSON: La validazione JSON è un processo cruciale per garantire che i dati immagazzinati nei database siano coerenti e rispettino gli standard dell’applicazione. Definendo regole specifiche per la struttura e il formato dei dati, la validazione riduce il rischio di errori e contribuisce all’affidabilità e all’integrità del sistema.
La collezione community ha lo scopo di raccogliere informazioni relative alle comunità all’interno della nostra applicazione.
{
"$jsonSchema": {
"bsonType": "object",
"required": [
"_id",
"creatorId",
"name"
],
"properties": {
"_id": {
"bsonType": "objectId",
"description": "_id must be an ObjectId and is required"
},
"creatorId": {
"bsonType": "objectId",
"description": "creatorId must be an ObjectId and is required"
},
"name": {
"bsonType": "string",
"description": "name must be a string and is required"
},
"desc": {
"bsonType": "string",
"description": "desc must be a string"
},
"members": {
"bsonType": "array",
"description": "members must be an array of ObjectIds"
},
"playlists": {
"bsonType": "array",
"description": "playlists must be an array of ObjectIds"
}
}
}
}
_id: identificatore univoco di una community, di tipo ObjectId. È un campo obbligatorio per identificare univocamente una community nel db.
creatorId: identificatore dell’utente creatore della community, di tipo ObjectId. È un campo obbligatorio e serve a linkare la community al suo creatore.
name: nome della community, di tipo stringa. È un campo obbligatorio e contiene il nome della community.
desc: rappresenta la descrizione della community, di tipo stringa. È un campo facoltativo e contiene una descrizione testuale della community.
members: lista di membri della community, di tipo array. Contiene una serie di ObjectId che identificano gli utenti che fanno parte della community.
playlists: lista di playlist associate alla community, di tipo array. Contiene una serie di ObjectId che identificano le playlist associate a questa community.
DESCRIZIONE
La collezione playlists è stata creata per rappresentare le playlist musicali all’interno della nostra applicazione.
{
"$jsonSchema": {
"bsonType": "object",
"required": [
"_id",
"owner_id",
"title"
],
"properties": {
"_id": {
"bsonType": "objectId",
"description": "_id must be a ObjectId and is required"
},
"owner_id": {
"bsonType": "objectId",
"description": "owner_id must be a ObjectId and is required"
},
"title": {
"bsonType": "string",
"description": "title must be a string and is required"
},
"description": {
"bsonType": "string",
"description": "name must be a string"
},
"tags": {
"bsonType": "array",
"description": "name must be an array of ObjectIds"
},
"songs": {
"bsonType": "array",
"description": "name must be a array of ObjectIds"
}
}
}
}
_id: identificatore univoco di una playlist, di tipo ObjectId. È un campo obbligatorio per identificare univocamente una playlist nel database.
owner_id: identificatore dell’utente proprietario della playlist, di tipo ObjectId. È un campo obbligatorio e serve a linkare la playlist al suo proprietario.
title: titolo della playlist, di tipo stringa. È un campo obbligatorio e contiene il titolo della playlist.
description: descrizione della playlist, di tipo stringa. È un campo facoltativo e contiene una descrizione testuale della playlist.
tags: lista di tag associati alla playlist, di tipo array. Contiene una serie di strings che identificano i tag associati a questa playlist.
songs: lista di brani musicali presenti nella playlist, di tipo array. Contiene una serie di ObjectIds che identificano i brani musicali presenti in questa playlist.
La collezione users è destinata a contenere i dati degli utenti all’interno dell’applicazione.
{
"$jsonSchema": {
"bsonType": "object",
"required": [
"_id",
"nickname",
"email",
"password"
],
"properties": {
"_id": {
"bsonType": "objectId",
"description": "_id must be an ObjectId and is required"
},
"name": {
"bsonType": "string",
"description": "name must be a string and is required"
},
"nickname": {
"bsonType": "objectId",
"description": "nickname must be an ObjectId and is required"
},
"email": {
"bsonType": "string",
"description": "email must be a string and is required"
},
"password": {
"bsonType": "string",
"description": "password must be a string"
},
"date": {
"bsonType": "string",
"description": "date must be a string"
},
"genres": {
"bsonType": "array",
"description": "genres must be an array of ObjectIds"
}
}
}
}
_id: identificatore univoco di un utente, di tipo ObjectId. È un campo obbligatorio per identificare univocamente un utente nel database.
name: nome dell’utente, di tipo stringa. È un campo obbligatorio e contiene il nome dell’utente.
nickname: nickname dell’utente, di tipo ObjectId. È un campo obbligatorio e serve a collegare il soprannome dell’utente.
email: indirizzo email dell’utente, di tipo stringa. È un campo obbligatorio e contiene l’indirizzo email dell’utente.
password: password dell’utente, di tipo stringa. È un campo facoltativo e contiene la password dell’utente.
date: data associata all’utente, di tipo stringa. È un campo facoltativo e contiene una data associata all’utente.
genres: lista di generi musicali preferiti dall’utente, di tipo array. Contiene una serie di ObjectIds che identificano i generi musicali preferiti dall’utente.
Il sistema di logging o auditing in un’applicazione web rappresenta un componente fondamentale per la tracciabilità delle operazioni e la gestione degli errori.
Questo sistema registra le attività cruciali e gli errori nell’applicazione, offrendo una visione dettagliata delle interazioni e delle problematiche riscontrate.
In questa applicazione ho realizzato questo meccanismo tramite due funzioni presenti nel file utils.js: log e logonly.
log(msg)
: effettua operazioni di log e auditing
logonly(msg)
: effettua solo operazioni di log
Le operazioni di Log e Auditing funzionano nel seguente modo
export function log(message) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}]: ${message}\n`;
var stream = fs.createWriteStream("serverlogs/serverlogs.log", {flags:'a'});
stream.write(logMessage);
console.log(logMessage);
}
export function logonly(message) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}]: ${message}\n`;
var stream = fs.createWriteStream("serverlogs/serverlogs.log", {flags:'a'});
stream.write(logMessage);
}
if (!isValidNickname(nickname)) {
res.status(404).send("invalid nickname");
utils.log("[REGISTER]> register > ERROR 400: invalid nickname");
return;
}
[2023-09-20T17:06:01.826Z]: [REGISTER]> register > ERROR 400: invalid nickname
Il progetto necessita di un file .env
nella directory principale del dove sono contenuti i
parametri necessari per il funzionamento.
Il file .env
è gestito attraverso il pacchetto npm dotenv
che si occupa di popolare le relative variabili d’ambienti e renderne semplice l’utilizzo e accesso
tramite JavaScript.
Un esempio di file env
# Server HOST and PORT
HOST='localhost'
PORT=3000
# Parametri e Credenziali MongoDB
DATABASE='mongodb-cluster'
DB_NAME='mongodb-name'
DB_URI="mongodb+srv://user:token@DATABASE.server_id.mongodb.net/"
# Parametri e Credenziali Spotify
BASE_URL="https://api.spotify.com/v1"
TOKEN_URL="https://accounts.spotify.com/api/token"
CLIENT_ID='token_client_generated_from_spotify'
CLIENT_SECRET='token_generated_from_spotify'
Swagger: è un framework open-source per la progettazione, la creazione e la documentazione di API RESTful. La sua utilità si concentra sulla semplificazione del processo di sviluppo API, consentendo agli sviluppatori di definire chiaramente le specifiche delle API, testarle e generare automaticamente documentazione dettagliata.
Per la generazione dello swagger ho utilizzato il module swagger-autogen.
Tramite la creazione di un file swagger.js (/src/docs/) con una apposita configurazione e determinati commenti nella sezione degli endpoint, è possibile generare automaticamente una documentazione per gli endpoint.
è possibile visualizzare lo swagger generato all’endpoint /api-docs
NB: Il codice riportato di seguito non è completo, rappresenta solo un esempio molto vicino a quello utilizzato in questa applicazione! ```javascript const doc = { info: { version: “1.0.0”, title: “SNM API”, description: “Documentation for the APIs of our website: Social Network for Music.” }, host:
${config.host}:${config.port}
, basePath: “/”, schemes: [‘http’], consumes: [‘application/json’], produces: [‘application/json’], tags: [ { “name”: “fetch”, “description”: “Endpoints for fetching and searching content.” }, { “name”: “users”, “description”: “…” }, { “name”: “auth”, “description”: “…” }, { “name”: “playlist”, “description”: “…” }, { “name”: “community”, “description”: “…” }, { “name”: “tracks”, “description”: “…” }, { “name”: “misc”, “description”: “…” }, { “name”: “artists”, “description”: “…” }
] , definitions: { … user: { _id: “ObjectId(‘64df73b31e5eda5eb868ddcd’)”, name: “Joe”, nickname: “joedough”, surname: “Joe”, email: “joedough@example.com”, password: “md5 hashed password”, date: “2001-09-11”, genres: { 0: “pop”, 1: “rock”, 2: “metal” } }, playlists: { _id: “ObjectId(‘64e748f0cb18ad90657b9043’)”, owner_id: “ObjectId(‘64df73b31e5eda5eb868ddcd’)”, title: “Example Playlist”, description: “Description of playlist”, public: true, tags: { 0: “chill”, 1: “relax”, 2: “vibes” }, songs: { 0:{ title: “Song 1”, artist: “Artist1, Artist2, Artist3”, duraion: “00:01:11” }, 1:{ title: “Song 2”, artist: “Artist1, Artist2, Artist3”, duraion: “00:02:22” }, 2:{ title: “Song 3”, artist: “Artist1, Artist2, Artist3”, duraion: “00:03:33” } }, private: true }, song: { $_id: “78kf73b31e6yda5eb868dder”, $artist:”[‘artist1’,’artist2’]”, $duration: “00:11:22”, $year: “1984”, $album: “Album Name” } … } }
const generateSwagger = async () => { try { await swaggerAutogen()(outputFile, endpointsFiles,doc); utils.log(‘SWAGGER DOCUMENTATION GENERATED.’); } catch (error) { utils.error(‘ERROR WHILE GENERATING SWAGGER DOCUMENTATION:’, error); } };
generateSwagger();
##### INTERFACCIA GRAFICA SWAGGER
![Alt text](/progettoTLW/docs/assets/image-3.png)
![Alt text](/progettoTLW/docs/assets/image-4.png)
![Alt text](/progettoTLW/docs/assets/image-5.png)
![Alt text](/progettoTLW/docs/assets/image-6.png)
##### INSTALLAZIONE
``` npm install --save-dev swagger-autogen ```<br>
ulteriori informazioni sono presenti al link sopra riportato
## Documentazione JavaScript-Doc
La maggior parte delle funzioni ( principalmente back-end ) in questa applicazione sono state descritte tramite la convenzione **jsdoc**
>La convenzione **JSDoc**, ampiamente utilizzata nella programmazione JavaScript, consiste nell'includere commenti strutturati nel codice per documentare funzioni, classi e metodi. Questi commenti migliorano la chiarezza del codice, facilitano la comprensione e consentono la generazione automatica di documentazione tecnica. Questo standard è cruciale per progetti complessi e la collaborazione tra sviluppatori.
##### Esempio commento JavaDoc
Di seguito un esempio di un commento utilizzando lo standard JavaDoc
```javascript
/**
* Retrieves a playlist by its ID.
*
* @description This function retrieves a playlist by its unique ID.
* It checks the validity of the provided
* playlist ID and returns the playlist data if found.
* If the playlist does not exist, it returns a 404 Not Found response.
* In case of any unexpected errors, it sends a 500 Internal Server Error response.
* @param {Object} res - The HTTP response object.
* @param {string} playlistid - The ID of the playlist to retrieve.
*
* @returns {void}
*
* @throws {Object} 400 Bad Request if the playlist ID is missing or invalid.
* @throws {Object} 404 Not Found if the playlist with the provided ID does not exist.
* @throws {Object} 500 Internal Server Error if any unexpected error occurs during the operation.
*
*/
export async function getPlaylistFromId(res, playlistid) {
if(playlistid==undefined){
res.status(400).send("Missing playlist id");
utils.log("[PLAYLIST]> getPlaylistFromId > ERROR 400: Missing playlist id");
return;
}
if(!utils.isValidString(playlistid)){
res.status(400).send("Invalid playlistid");
utils.log("[PLAYLIST]> getPlaylistFromId > ERROR 400: Invalid playlist id");
return;
}
try {
const collection = await dbPlaylistCollection();
const playlist = await collection.findOne({ _id: new ObjectId(playlistid) });
if (!playlist) {
res.status(404).send("Playlist not found");
utils.log("[PLAYLIST]> getPlaylistFromId > ERROR 404: Playlist not found");
return;
}
res.json(playlist);
utils.log("[PLAYLIST]> getPlaylistFromId > SUCCESS: SUCCESFULLY FETCHED PLAYLIST "+playlistid);
return;
} catch (error) {
res.status(500).send("INTERNAL ERROR");
utils.log("[PLAYLIST]> getPlaylistFromId > ERROR 500: INTERNAL ERROR "+error);
return;
}
}
I codici HTTP sono standard utilizzati per indicare lo stato di una richiesta HTTP effettuata tra un client (spesso un browser web) e un server. Nell’applicazione, vengono ampiamente utilizzati alcuni di questi codici per comunicare lo stato delle richieste e delle risposte:
Codice 400 (BAD REQUEST): Questo codice indica che la richiesta effettuata dal client è stata malformata o non valida. Viene utilizzato quando i dati inviati non corrispondono alle aspettative del server.
Codice 401 (UNAUTHORIZED): Indica che l’accesso a una risorsa richiede l’autenticazione.
Codice 404 (NOT FOUND): Indica che la risorsa richiesta non è stata trovata sul server.
Codice 500 (INTERNAL SERVER ERROR): Questo codice indica un errore interno del server.
Codice 200 (OK): Codice di successo. Indica che la richiesta è stata elaborata correttamente e che il server sta restituendo i dati richiesti al client.
La gestione che abbiamo deciso di attuare è stata quella di comunicare al sender il codice che la sua richiesta ha “generato” Nell’esempio di seguito è possibile vedere la gestione dei codici 400,404,500 200
NB: res.json(data) viene percepito dal client come un codice 200 ```javascript export async function getPlaylistFromId(res, playlistid) { if(playlistid==undefined){ res.status(400).send(“Missing playlist id”); utils.log(“[PLAYLIST]> getPlaylistFromId > ERROR 400: Missing playlist id”); return;
} if(!utils.isValidString(playlistid)){ res.status(400).send(“Invalid playlistid”); utils.log(“[PLAYLIST]> getPlaylistFromId > ERROR 400: Invalid playlist id”); return;
} try { const collection = await dbPlaylistCollection(); const playlist = await collection.findOne({ _id: new ObjectId(playlistid) }); if (!playlist) { res.status(404).send(“Playlist not found”); utils.log(“[PLAYLIST]> getPlaylistFromId > ERROR 404: Playlist not found”); return; }
res.json(playlist);
utils.log("[PLAYLIST]> getPlaylistFromId > SUCCESS: SUCCESFULLY FETCHED PLAYLIST "+playlistid);
return;
} catch (error) { res.status(500).send(“INTERNAL ERROR”); utils.log(“[PLAYLIST]> getPlaylistFromId > ERROR 500: INTERNAL ERROR “+error); return; } } ``` —
Pagina Iniziale
Profilo
Playlist Personali
Nuova Playlist
Cerca Playlist pubbliche
Esplora e importa Playlist
Comunità
Modifica Comunità
La scelta di utilizzare la lingua inglese, come standard di programmazione, è ampiamente diffusa nell’industria del software ed è guidata principalmente dal desiderio di aderire allo standard internazionale. Questo standard è anche noto nella community di programmatori come “English-based programming” .
Adottare questa convenzione ha numerosi vantaggi, in quanto rende il codice più leggibile e comprensibile per un pubblico globale di sviluppatori.