Esercizio 4 - Q&A con database

Obiettivo

Realizzare una versione dell’esercizio “Q&A” che memorizzi domande, risposte e utenti in un database SQLite (questions.sqlite).

Occorre:

Procedura/Spiegazione passo-passo

  1. Creazione (o apertura del database)

    import sqlite3 from 'sqlite3';
    const db = new sqlite3.Database('questions.sqlite', err => {
    	if (err)
    		throw err;
    });
    

    Il metodo sqlite3 apre un file DB locale; tutte le chiamate sono non-bloccanti e necessitano di callback o Promise.

  2. Definizione delle classi base

    Gli oggetti contengono solo dati; i metodi che accedono al DB restituiscono Promise per permettere await.

  3. Pattern asincrono con Promise

  4. Query parametrizzate

    Utilizzare i ? segnaposto per prevenire SQL-injection.

  5. Aggiornamento punteggio

    L’operazione voteAnswer incrementa/decrementa la colonna score con una UPDATE; il metodo risolve la Promise quando this.changes === 1.

Codice/Snippet

database.mjs

import sqlite3 from 'sqlite3';
export const db = new sqlite3.Database('questions.sqlite', err => {
    if (err) 
        throw err;
});

models.mjs

import dayjs from 'dayjs';
import { db } from './database.mjs';

/** ---------- Answer model ---------- **/
export function Answer(id, text, email, userId, date, score = 0) {
    this.id = id;
    this.text = text;
    this.email = email;
    this.userId = userId;
    this.date = dayjs(date);
    this.score = score;
}

/** ---------- Questoin model ---------- **/
export function Question(id, text, email, userId, date) {
    this.id = id;
    this.text = text;
    this.email = email;
    this.userId = userId;
    this.date = dayjs(date);

    /* (3) ottieni tutte le risposte */
    this.getAnswers = () => new Promise((res, rej) => {
        const sql = `SELECT a.id, a.text, u.email, a.authorId, a.date, a.score
                     FROM answer a JOIN user u ON a.authorId = u.id
                     WHERE a.questionId = ?`;
        db.all(sql, [this.id], (err, rows) => {
            if (err)
                return rej(err);
            res(rows.map(r => new Answer(r.id, r.text, r.email, r.authorId, r.date, r.score)));
        });
    });

    /* (4) aggiungi risposta */
    this.addAnswer = answer => new Promise((res, rej) => {
        const sql = `INSERT INTO answer (text, authorId, date, score, questionId)
                     VALUES (?, ?, ?, ?, ?)`;
        db.run(sql, [answer.text, answer.userId, answer.date.format(), answer.score, this.id], function(err) {
            if (err)
                return rej(err);
            res(this.lastID);   // nuovo id risposta
        });
    });

    /* (5) voto risposta */
    this.voteAnswer = (answerId, value) => new Promise((res, rej) => {
        const delta = (value === 'up') ? 1 : -1;
        const sql = `UPDATE answer SET score = score + ?
                     WHERE id = ?`;
        db.run(sql, [delta, answerId], function (err) {
            if (err)
                return rej(err);
        res(this.changes);  // 1 se ok, 0 se id inesistente
        });
    });
}

/** ---------- QuestionList ---------- **/
export function QuestionList() {

    /* (1) recupera domanda per id */
    this.getQuestion = id => new Promise((res, rej) => {
        const sql = `SELECT q.id, q.text, u.email, q.authorId, q.date
                     FROM question q JOIN user u ON q.authorId = u.id
                     WHERE q.id = ?`;
        db.get(sql, [id], (err, row) => {
            if (err) 
                return rej(err);
            if (!row)
                return res(undefined);
            res(new Question(row.id, row.text, row.email, row.authorId, row.date));
        });
    });

    /* (2) inserisci nuova domanda */
    this.addQuestion = question => new Promise((res, rej) => {
        const sql = `INSERT INTO question (text, authorId, date)
                     VALUES (?, ?, ?)`;
        db.run(sql, [question.text, question.userId, question.date.format()], function (err) {
            if (err)
                return rej(err);
            res(this.lastID);   // nuovo id domanda
        });
    });
}

/* ----------- Esempio di uso ----------- */
async function main() {
    const ql = new QuestionList();
    const newQ = new Question(undefined, 'Che cos\\'è Node.js?', '[email protected]', 1, dayjs());
    const qId = await ql.addQuestion(newQ);

    const q = await ql.getQuestion(qId);
    await q.addAnswer(new Answer(undefined, 'Un runtime JS server-side', '[email protected]', 2, dayjs()));
    const answers = await q.getAnswers();
    console.log(answers);
}

main().catch(console.error);

(i metodi risolvono sempre una Promise; gestire gli errori con try/catch o .catch() in chiamata)

Osservazioni critiche