Realizzare una versione dell’esercizio “Q&A” che memorizzi domande, risposte e utenti in un database SQLite (questions.sqlite).
Occorre:
user, question, answer.Question, Answer e QuestionList.async-await) che interagiscono con il DB.
QuestionList.getQuestionQuestionList.addQuestionQuestion.getAnswerQuestion.addAnswerQuestion.voteAnswerCreazione (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.
Definizione delle classi base
Gli oggetti contengono solo dati; i metodi che accedono al DB restituiscono Promise per permettere await.
Pattern asincrono con Promise
db.get/db.all/db.run.new Promise((resolve, reject) => {...}).await (ES2017) per un codice lineare e leggibile.Query parametrizzate
Utilizzare i ? segnaposto per prevenire SQL-injection.
Aggiornamento punteggio
L’operazione voteAnswer incrementa/decrementa la colonna score con una UPDATE; il metodo risolve la Promise quando this.changes === 1.
database.mjsimport sqlite3 from 'sqlite3';
export const db = new sqlite3.Database('questions.sqlite', err => {
if (err)
throw err;
});
models.mjsimport 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)