WebAPP Exam 2025

This commit is contained in:
2025-06-28 18:31:48 +02:00
commit 0e9ec24d94
42 changed files with 7546 additions and 0 deletions

8
server/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# dependencies
node_modules/
# misc
.DS_Store
Thumbs.db
Desktop.ini

391
server/dao-forum.js Normal file
View File

@ -0,0 +1,391 @@
'use strict';
/* Data Access Object (DAO) module for accessing forum data */
const db = require('./db');
const dayjs = require("dayjs");
const crypto = require("crypto");
exports.getUserById = (id) => {
return new Promise((resolve, reject) => {
const sql = 'SELECT * FROM User WHERE id=?';
db.get(sql, [id], (err, row) => {
if (err)
reject(err);
else if (row === undefined)
resolve({ error: 'User not found.' });
else {
const user = { id: row.ID, username: row.Username, type: row.Type}
resolve(user);
}
});
});
};
exports.getUserSecret = (id) => {
return new Promise((resolve, reject) => {
const sql = 'SELECT Secret FROM User WHERE id=?';
db.get(sql, [id], (err, row) => {
if (err)
reject(err);
else if (row === undefined)
resolve({ error: 'Secret not found.' });
else {
const secret = row.Secret;
resolve(secret);
}
});
});
}
exports.getUser = (username, password) => {
return new Promise((resolve, reject) => {
const sql = 'SELECT * FROM User WHERE username=?';
db.get(sql, [username], (err, row) => {
if (err) {
reject(err);
} else if (row === undefined) {
resolve(false);
}
else {
const user = { id: row.ID, username: row.Username, type: row.Type};
crypto.scrypt(password, row.Salt, 64, function (err, hashedPassword) {
if (err) reject(err);
if (!crypto.timingSafeEqual(Buffer.from(row.Password, 'hex'), hashedPassword))
resolve(false);
else
resolve(user);
});
}
});
});
};
const convertPostFromDbRecord = (dbRecord) => {
const post = {};
post.id = dbRecord.ID;
post.title = dbRecord.Title;
post.authorid = dbRecord.AuthorID;
post.maxcomments = dbRecord.MaxComments;
post.publication = dbRecord.Publication;
post.text = dbRecord.Text;
return post;
}
const convertCommentFromDbRecord = (dbRecord) => {
const comment = {};
comment.id = dbRecord.ID;
comment.text = dbRecord.Text;
comment.publication = dbRecord.Publication;
comment.authorid = dbRecord.AuthorID;
comment.postid = dbRecord.PostID;
return comment;
}
// This function retrieves the whole list of posts from the database.
exports.listPosts = () => {
return new Promise((resolve, reject) => {
const sql = "SELECT * FROM Post";
db.all(sql, (err,rows) => {
if (err) { reject(err); }
const posts = rows.map((e) => {
const post = convertPostFromDbRecord(e);
return post;
});
resolve(posts);
});
});
}
exports.listCommentsOfPost = (PostID) => {
return new Promise((resolve, reject) => {
const sql = "SELECT * FROM Comment AS C WHERE C.PostID = ?";
db.all(sql, [PostID], (err,rows) => {
if (err) { reject(err); }
const comments = rows.map((e) => {
const comment = convertCommentFromDbRecord(e);
return comment;
});
resolve(comments);
});
});
}
exports.listAnonCommentsOfPost = (PostID) => {
return new Promise((resolve, reject) => {
const sql = "SELECT * FROM Comment AS C WHERE C.PostID = ?";
db.all(sql, [PostID], (err,rows) => {
if (err) { reject(err); }
const comments = rows.filter( (e) => e.AuthorID == null)
.map((e) => {
const comment = convertCommentFromDbRecord(e);
return comment;
});
comments.forEach( c => console.log(c.text));
resolve(comments);
});
});
}
exports.getNumCommentsOfPost = (PostID) => {
return new Promise((resolve, reject)=>{
const sql = 'SELECT COUNT(*) AS N FROM Comment AS C WHERE C.PostID=?';
db.get(sql,[PostID], (err,row)=>{
if(err){
reject(err);
}
if( row == undefined){
resolve({error: 'Post not found.'});
} else {
const num = row.N;
resolve(num);
}
});
});
}
exports.getPost = (id) => {
return new Promise((resolve, reject)=>{
const sql = 'SELECT * FROM Post WHERE id=?';
db.get(sql,[id], (err,row)=>{
if(err){
reject(err);
}
if( row == undefined){
resolve({error: 'Post not found.'});
} else {
const post = convertPostFromDbRecord(row);
resolve(post);
}
});
});
}
exports.getComment = (id) => {
return new Promise((resolve, reject)=>{
const sql = 'SELECT * FROM Comment WHERE ID=?';
db.get(sql,[id], (err,row)=>{
if(err){
reject(err);
}
if( row == undefined){
resolve({error: 'Comment not found.'});
} else {
const comment = convertCommentFromDbRecord(row);
resolve(comment);
}
});
});
}
exports.getAuthorOfPost = (PostID) => {
return new Promise((resolve, reject)=>{
const sql = 'SELECT Username FROM User as U, Post as P WHERE P.ID = ? AND P.AuthorID = U.ID';
db.get(sql,[PostID], (err,row)=>{
if(err){
reject(err);
}
if( row == undefined){
resolve({error: 'Author not found.'});
} else {
const author = row.Username;
resolve(author);
}
});
});
}
exports.getAuthorOfComment = (CommentID) => {
return new Promise((resolve, reject)=>{
const sql = 'SELECT Username FROM User as U, Comment as C WHERE C.ID = ? AND C.AuthorID = U.ID';
db.get(sql,[CommentID], (err,row)=>{
if(err){
reject(err);
}
if( row == undefined){
resolve({error: 'Author not found.'});
} else {
const author = row.Username;
resolve(author);
}
});
});
}
exports.getAuthorIDOfPost = (PostID) => {
return new Promise((resolve, reject)=>{
const sql = 'SELECT AuthorID FROM Post as P WHERE P.ID = ?';
db.get(sql,[PostID], (err,row)=>{
if(err){
reject(err);
}
if( row == undefined){
resolve({error: 'Author not found.'});
} else {
const id = row.AuthorID;
resolve(id);
}
});
});
}
exports.getAuthorIDOfComment = (CommentID) => {
return new Promise((resolve, reject)=>{
const sql = 'SELECT AuthorID FROM Comment as C WHERE C.ID = ?';
db.get(sql,[CommentID], (err,row)=>{
if(err){
reject(err);
}
if( row == undefined){
resolve({error: 'Author not found.'});
} else {
const id = row.AuthorID;
resolve(id);
}
});
});
}
exports.createPost = (post) => {
return new Promise((resolve, reject) => {
console.log("POST DAO:"+post.text);
const sql = 'INSERT INTO Post (Title, AuthorID, MaxComments, Publication, Text) VALUES(?, ?, ?, ?, ?)';
db.run(sql, [post.title, post.authorid, post.maxcomments, post.publication, post.text], function(err){
if (err) {
reject(err);
}
resolve(exports.getPost(this.lastID));
});
});
}
exports.createComment = (comment) => {
return new Promise((resolve, reject) => {
const sql = 'INSERT INTO Comment (Text, Publication, AuthorID, PostID) VALUES(?, ?, ?, ?)';
db.run(sql, [comment.text, comment.publication, comment.authorid, comment.postid], function(err){
if (err) {
reject(err);
}
resolve(exports.getComment(this.lastID));
});
});
}
exports.setInteresting = (UserID, CommentID) => {
return new Promise((resolve, reject) => {
const sql = 'INSERT INTO Interesting(UserID, CommentID) VALUES(?, ?)';
db.run(sql, [UserID, CommentID], function(err){
if (err){
reject(err);
}
resolve(exports.isUserInterested(UserID, CommentID));
});
});
}
exports.getNumInterested = (CommentID) => {
return new Promise((resolve, reject) => {
const sql = 'SELECT COUNT(UserID) AS N FROM Interesting AS I WHERE I.CommentID = ?'
db.get(sql, [CommentID], (err,row) => {
if(err){
reject(err);
}
if( row == undefined){
resolve({error: 'Comment not found.'});
} else {
const num = row.N;
resolve(num);
}
});
});
}
// Return true if user is interested to comment, otherwise return false
exports.isUserInterested = (UserID, CommentID) => {
return new Promise((resolve, reject) => {
const sql = 'SELECT * FROM Interesting AS I WHERE I.CommentID = ? AND I.UserID=?'
db.get(sql, [CommentID, UserID], (err,row) => {
if(err){
reject(err);
}
if( row == undefined){
resolve(false);
} else {
resolve(true);
}
});
});
}
exports.deletePost = (PostID) => {
return new Promise((resolve, reject) => {
db.serialize(() =>{
db.run('DELETE FROM Interesting as I, Comment as C WHERE I.CommentID = C.ID AND C.PostID = ? ', [PostID], (err) =>{
if(err){
reject(err);
}
})
.run('DELETE FROM Comment as C WHERE C.PostID = ?', [PostID], (err)=>{
if(err){
reject(err);
}
})
.run('DELETE FROM Post as P WHERE P.ID = ?',[PostID], (err)=>{
if(err){
reject(err);
}
});
});
resolve(exports.listPosts());
});
}
exports.deleteComment = (CommentID) => {
return new Promise((resolve, reject) => {
db.serialize(() =>{
db.run('DELETE FROM Interesting as I WHERE I.CommentID = ?', [CommentID], (err) =>{
if(err){
reject(err);
}
})
.run('DELETE FROM Comment as C WHERE C.ID = ?', [CommentID], (err)=>{
if(err){
reject(err);
}
})
});
resolve();
});
}
exports.deleteInteresting = (UserID,CommentID) => {
return new Promise((resolve, reject) => {
db.serialize(() =>{
db.run('DELETE FROM Interesting as I WHERE I.CommentID = ? AND I.UserID= ?', [CommentID, UserID], (err) =>{
if(err){
reject(err);
}
})
});
resolve(exports.isUserInterested(UserID, CommentID));
});
}
exports.editComment = (commentID, text) => {
return new Promise((resolve, reject) => {
const sql = 'UPDATE Comment SET Text=? WHERE ID = ?';
db.run(sql, [text, commentID], function (err) {
if (err) {
reject(err);
}
if (this.changes !== 1) {
resolve({ error: 'Comment not found.' });
} else {
resolve();
}
});
});
};

11
server/db.js Normal file
View File

@ -0,0 +1,11 @@
'use strict';
const sqlite = require('sqlite3');
// open the database
const db = new sqlite.Database('test.db', (err) => {
if (err) throw(err);
});
module.exports = db;

353
server/index.js Normal file
View File

@ -0,0 +1,353 @@
'use strict';
const express = require('express');
const morgan = require('morgan'); // logging middleware
const { check, validationResult, oneOf } = require('express-validator'); // validation middleware
const cors = require('cors');
const forumDao = require('./dao-forum');
const dayjs = require('dayjs');
const app = express();
app.use(morgan('dev'));
app.use(express.json());
const corsOptions = {
origin: 'http://localhost:5173',
credentials: true,
};
app.use(cors(corsOptions));
/* Passport */
const passport = require('passport'); // authentication middleware
const LocalStrategy = require('passport-local'); // authentication strategy (username and password)
const base32 = require('thirty-two');
const TotpStrategy = require('passport-totp').Strategy; // totp
passport.use(new LocalStrategy(async function verify(username, password, callback) {
const user = await forumDao.getUser(username, password)
if(!user)
return callback(null, false, 'Incorrect username or password');
return callback(null, user); // NOTE: user info in the session (all fields returned by userDao.getUser, i.e, id, username, name)
}));
passport.serializeUser(function (user, callback) {
callback(null, user);
});
passport.deserializeUser(function (user, callback) {
return callback(null, user);
});
const session = require('express-session');
/* The secret field is used to hash the session with HMAC */
app.use(session({
secret: "very l0ng s3cr3t used h4sh with HM4C the sessi0n",
resave: false,
saveUninitialized: false,
}));
app.use(passport.authenticate('session'));
/* If user does not exist the db will simply throw an error and everything will be fine */
passport.use(new TotpStrategy(
async function (user, done) {
const secret = await forumDao.getUserSecret(user.id);
if(!secret)
return done(null, false, {message: "TOTP is not enabled for this account"});
return done(null, base32.decode(secret), 30);
})
)
const isLoggedIn = (req, res, next) => {
if(req.isAuthenticated()) {
return next();
}
return res.status(401).json({error: 'Not authorized'});
}
/* This one is used because we still need to serve even anonymous user in some queries */
/* If Authenticated is True otherwise is False and therefore anonymous */
const isLoggedInOrAnon = (req, res) => {
if(req.isAuthenticated()) {
return true;
}
return false;
}
function isTotp(req, res, next) {
if(req.session.method === 'totp')
return next();
return res.status(401).json({ error: 'Missing TOTP authentication'});
}
// Created to check this not as a middleware
function isLoggedInTotp(req, res){
if(req.session.method === 'totp')
return true;
return false;
}
const maxTitleLength = 160;
const maxCommentLength = 1000;
const maxTextLength = 1500;
const errorFormatter = ({ location, msg, param, value, nestedErrors }) => {
return `${location}[${param}]: ${msg}`;
};
app.get('/api/posts', async (req, res) => {
const isLogged = isLoggedInOrAnon(req);
try{
let posts = await forumDao.listPosts();
posts = posts.map( async (p) => {
let comments;
/* If authorid is null the Author of the comment is not searched at all
The field author exists only if there is a valid author id */
if( isLogged == true ){
comments = await forumDao.listCommentsOfPost(p.id);
comments = comments.map( async (c) => {
/* numInterested and isInterested should be visible only to authenticated users. */
/* isInterested should be true ONLY for the exact user that has marked the comment interesting */
c.numInterested = await forumDao.getNumInterested(c.id);
c.isInterested = await forumDao.isUserInterested(req.user.id, c.id);
if(c.authorid != null){
c.author = await forumDao.getAuthorOfComment(c.id);
}
return c;
});
}
else{
comments = await forumDao.listAnonCommentsOfPost(p.id);
}
p.comments = await Promise.all(comments);
p.author = await forumDao.getAuthorOfPost(p.id);
p.numComments = await forumDao.getNumCommentsOfPost(p.id);
return p;
});
const postsWithComments = await Promise.all(posts);
res.json(postsWithComments);
}
catch(err){
console.log(err);
res.status(503).json({error: 'Database error'});
}
});
/* Create a new post (Only authenticated user) */
/* INPUT: Title, Text, Max number of comments */
app.post('/api/posts', isLoggedIn, [check('title').isLength({min:1, max: maxTitleLength}).withMessage(`The text of the post MUST be >=1 and <= ${maxTitleLength}`),
check('text').isLength({min:1, max: maxTextLength}).withMessage(`The title of the post MUST be >=1 and <= ${maxTextLength}`),
check('max').isInt({min:-1}).withMessage("The max number of comments should be specificed and >= -1")],async (req, res) => {
const errors = validationResult(req).formatWith(errorFormatter); // format error message
if (!errors.isEmpty()) {
return res.status(422).json( errors.errors ); // error message is sent back as a json with the error info
}
const post = {title: req.body.title, text: req.body.text, publication: dayjs().format("YYYY-MM-DD HH:mm:ss"), maxcomments: req.body.max, authorid: req.user.id};
try{
const result = await forumDao.createPost(post);
res.status(201).json(result);
}catch(err){
console.log(err);
res.status(503).json({error: 'Database error'});
}
});
/* Create a new comment related to a post (Anyone) */
/* INPUT: Post ID, Text*/
app.post('/api/posts/:id/comments', [ check('id').isInt({min: 1}).withMessage("Post ID MUST be an integer >= 1"),
check('text').isLength({min: 1, max: maxCommentLength}).withMessage(`The text of the comment MUST be >=1 and <= ${maxCommentLength}`) ],async (req,res) => {
const errors = validationResult(req).formatWith(errorFormatter); // format error message
if (!errors.isEmpty()) {
return res.status(422).json( errors.errors ); // error message is sent back as a json with the error info
}
const comment = {text: req.body.text, publication: dayjs().format("YYYY-MM-DD HH:mm:ss") ,authorid: req.user == null ? null : req.user.id , postid: req.params.id};
try{
const checkPost = await forumDao.getPost(req.params.id);
const numcomments = await forumDao.getNumCommentsOfPost(req.params.id);
if(checkPost.maxcomments != -1 && numcomments == checkPost.maxcomments){
return res.status(400).json({error: "Reached max number of comments"});
}
const result = await forumDao.createComment(comment);
res.status(201).json(result);
}catch(err){
console.log(err);
res.status(503).json({error: 'Database error'});
}
});
/* Toggle true/false Interesting flag on comment (Only authenticated and owner) */
//Handled as POST/DELETE requests because we are creating a new record in a table.
app.post('/api/posts/:id/comments/:commentId/interest', isLoggedIn, [check('id').isInt({min:1}).withMessage("Post ID MUST be an integer >= 1"),
check('commentId').isInt({min:1}).withMessage("Comment ID MUST be an integer >= 1")], async (req,res) => {
const errors = validationResult(req).formatWith(errorFormatter); // format error message
if (!errors.isEmpty()) {
return res.status(422).json( errors.errors ); // error message is sent back as a json with the error info
}
try{
const result = await forumDao.setInteresting(req.user.id,req.params.commentId);
res.status(201).json({});
}catch(err){
console.log(err);
res.status(503).json({error: 'Database error'});
}
});
app.delete('/api/posts/:id/comments/:commentId/interest', [check('id').isInt({min:1}).withMessage("Post ID MUST be an integer >= 1"),
check('commentId').isInt({min:1}).withMessage("Comment ID MUST be an integer >= 1")], async (req,res) => {
const errors = validationResult(req).formatWith(errorFormatter); // format error message
if (!errors.isEmpty()) {
return res.status(422).json( errors.errors ); // error message is sent back as a json with the error info
}
try{
const result = await forumDao.deleteInteresting(req.user.id,req.params.commentId);
res.status(201).json(result);
}catch(err){
console.log(err);
res.status(503).json({error: 'Database error'});
}
});
/* Edit comment (Only authenticated and owner) */
/* INPUT: Text */
app.patch('/api/posts/:id/comments/:commentId', isLoggedIn,[check('id').isInt({min:1}).withMessage("Post ID MUST be an integer >= 1"),
check('commentId').isInt({min:1}).withMessage("Comment ID MUST be an integer >= 1"),
check('text').isLength({min: 1, max: maxCommentLength}).withMessage(`The text of the comment MUST be >=1 and <= ${maxCommentLength}`)], async (req, res) => {
const errors = validationResult(req).formatWith(errorFormatter); // format error message
if (!errors.isEmpty()) {
return res.status(422).json( errors.errors ); // error message is sent back as a json with the error info
}
try{
// Check if user.id is the author id of comment
const authorid = await forumDao.getAuthorIDOfComment(req.params.commentId);
if( !isLoggedInTotp(req, res) && req.user.id != authorid )
return res.status(401).json({error: 'Not authorized'});
const result = await forumDao.editComment(req.params.commentId, req.body.text);
/* Must be 200 and not 204, because 204 implies empty body and the client want a json eachtime even empty*/
res.status(200).json({});
}catch(err){
console.log(err);
res.status(503).json({error: 'Database error'});
}
});
/* Delete selected post (Only authenticated totp and owner)*/
/* INPUT: Post ID */
app.delete('/api/posts/:id', isLoggedIn, [check('id').isInt({min:1}).withMessage("Post ID MUST be an integer >= 1")], async (req, res) => {
const errors = validationResult(req).formatWith(errorFormatter); // format error message
if (!errors.isEmpty()) {
return res.status(422).json( errors.errors ); // error message is sent back as a json with the error info
}
try{
const authorid = await forumDao.getAuthorIDOfPost(req.params.id);
if( !isLoggedInTotp(req,res) && req.user.id != authorid )
return res.status(401).json({error: 'Not authorized'});
const checkPost = await forumDao.getPost(req.params.id);
if( checkPost.error != undefined ){
return res.status(404).json({error: checkPost.error})
}
const result = await forumDao.deletePost(req.params.id);
res.status(200).json({});
}catch(err){
console.log(err);
res.status(503).json({error: 'Database error'});
}
});
/* Delete selected comment (Only authenticated and owner)*/
app.delete('/api/posts/:id/comments/:commentId',isLoggedIn, [check('id').isInt({min:1}).withMessage("Post ID MUST be an integer >= 1"),
check('commentId').isInt({min:1}).withMessage("Comment ID MUST be an integer >= 1")], async (req, res) => {
const errors = validationResult(req).formatWith(errorFormatter); // format error message
if (!errors.isEmpty()) {
return res.status(422).json( errors.errors ); // error message is sent back as a json with the error info
}
try{
const authorid = await forumDao.getAuthorIDOfComment(req.params.commentId);
if( !isLoggedInTotp(req, res) && req.user.id != authorid )
return res.status(401).json({error: 'Not authorized'});
const checkComment = await forumDao.getComment(req.params.commentId);
if( checkComment.error != undefined ){
return res.status(404).json({error: checkComment.error})
}
const result = await forumDao.deleteComment(req.params.commentId);
res.status(200).json({});
}catch(err){
console.log(err);
res.status(503).json({error: 'Database error'});
}
});
function clientUserInfo(req) {
const user=req.user;
return {id: user.id, username: user.username, canDoTotp: user.secret? true: false, isTotp: req.session.method === 'totp'};
}
/* POST /api/sessions
Used to login */
app.post('/api/sessions', function(req, res, next) {
passport.authenticate('local', (err, user, info) => {
if (err)
return next(err);
if (!user) {
return res.status(401).json({ error: info});
}
req.login(user, (err) => {
if (err)
return next(err);
return res.json(req.user);
});
})(req, res, next);
});
/* Perform OTP (only after login) */
app.post('/api/login-totp', isLoggedIn,
passport.authenticate('totp'),
function(req, res) {
req.session.method = 'totp';
res.json({otp: 'authorized'});
}
);
/* GET /api/sessions/current
This route checks whether the user is logged in or not. */
app.get('/api/sessions/current', (req, res) => {
if(req.isAuthenticated()) {
res.status(200).json(req.user);}
else
res.status(401).json({error: 'Not authenticated'});
});
/* DELETE /api/session/current
This route is used for loggin out the current user. */
app.delete('/api/sessions/current', isLoggedIn, (req, res) => {
req.logout(() => {
res.status(200).json({});
});
});
const PORT = 3001;
// Activate the server
app.listen(PORT, (err) => {
if (err)
console.log(err);
else
console.log(`Server listening at http://localhost:${PORT}`);
});

2330
server/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
server/package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"dayjs": "^1.11.13",
"express": "^5.1.0",
"express-session": "^1.18.1",
"express-validator": "^7.2.1",
"morgan": "^1.10.0",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"passport-totp": "^0.0.2",
"sqlite3": "^5.1.7",
"thirty-two": "^1.0.2"
}
}

118
server/scripts/initdb.js Normal file
View File

@ -0,0 +1,118 @@
'use strict';
const db = require('../db')
const crypto = require('crypto')
const dayjs = require('dayjs')
const insertHashedPwd = (UserID, password) => {
return new Promise((resolve, reject) => {
const hashLen = 64;
const salt = crypto.randomBytes(16).toString("hex");
const sql = "UPDATE User SET password=?, salt=? WHERE User.ID=?"
crypto.scrypt(password,salt, hashLen, function(err,hash){
if(err) reject(err);
db.run(sql,[hash.toString("hex"),salt,UserID], function(err){
if(err)
reject(err);
resolve();
});
});
});
}
const createDB = () => {
return new Promise((resolve, reject) => {
db.serialize(() => {
db.run(`CREATE TABLE "User" (
"ID" INTEGER NOT NULL UNIQUE,
"Username" TEXT NOT NULL,
"Password" TEXT NOT NULL,
"Salt" TEXT NOT NULL,
"Secret" TEXT NULL,
"Type" TEXT NOT NULL,
PRIMARY KEY("ID" AUTOINCREMENT));`)
/* At least five users, 2 are Administrators*/
.run(`INSERT INTO User(Username, Password, Salt, Secret, Type) VALUES('AdminUno', 'placeholder', 'placeholder', 'LXBSMDTMSP2I5XFXIYRGFVWSFI', 'administrator')`)
.run(`INSERT INTO User(Username, Password, Salt, Secret, Type) VALUES('AdminDue', 'ph', 'ph', 'LXBSMDTMSP2I5XFXIYRGFVWSFI', 'administrator')`)
.run(`INSERT INTO User(Username, Password, Salt, Type) VALUES('UtenteUno', 'ph','ph', 'viewer')`)
.run(`INSERT INTO User(Username, Password, Salt, Type) VALUES('UtenteDue', 'ph','ph', 'viewer')`)
.run(`INSERT INTO User(Username, Password, Salt, Type) VALUES('UtenteTre', 'ph','ph', 'viewer')`)
.run(`CREATE TABLE "Post" (
"Title" TEXT NOT NULL UNIQUE,
"ID" INTEGER NOT NULL UNIQUE,
"AuthorID" INTEGER NOT NULL,
"MaxComments" INTEGER,
"Publication" INTEGER NOT NULL,
"Text" TEXT NOT NULL,
PRIMARY KEY("ID" AUTOINCREMENT),
FOREIGN KEY("AuthorID") REFERENCES "User"("ID"));`)
/* Four users including two administrators should have published two posts each */
/*Post of AdminUno*/
.run(`INSERT INTO Post (Title, AuthorID, Publication, Text, MaxComments) VALUES('Example post 1',1,'2025-06-06 12:20:05','Lorem ipsum dolor sit amet consectetur adipiscing elit. ', -1)`)
.run(`INSERT INTO Post (Title, AuthorID, Publication, Text, MaxComments) VALUES('Example post 2',1,'2025-06-12 10:59:01','Quisque faucibus ex sapien vitae pellentesque sem placerat.', -1)`)
/*Post of AdminDue*/
.run(`INSERT INTO Post (Title, AuthorID, Publication, Text, MaxComments) VALUES('Example post 3',2,'2025-06-18 08:23:12','In id cursus mi pretium tellus duis convallis.', -1)`)
.run(`INSERT INTO Post (Title, AuthorID, MaxComments, Publication, Text) VALUES('Example post 4',2,2,'2025-06-01 23:20:11','Tempus leo eu aenean sed diam urna tempor.')`)
/*Post of UtenteUno */
.run(`INSERT INTO Post (Title, AuthorID, Publication, Text, MaxComments) VALUES('Example post 5',3,'2025-06-09 07:20:05','Pulvinar vivamus fringilla lacus nec metus bibendum egestas.', -1)`)
.run(`INSERT INTO Post (Title, AuthorID, Publication, Text, MaxComments) VALUES('Example post 6',3,'2025-06-20 07:25:59','Iaculis massa nisl malesuada lacinia integer nunc posuere.', -1)`)
/*Post of UtenteDue */
.run(`INSERT INTO Post (Title, AuthorID, MaxComments, Publication, Text) VALUES('Example post 7',4, 3, '2025-06-03 13:20:05','Ut hendrerit semper vel class aptent taciti sociosqu.')`)
.run(`INSERT INTO Post (Title, AuthorID, Publication, Text, MaxComments) VALUES('Example post 8',4,'2025-06-15 16:20:00','Ad litora torquent per conubia nostra inceptos himenaeos.', -1)`)
.run(`CREATE TABLE "Comment" (
"ID" INTEGER NOT NULL UNIQUE,
"Text" TEXT NOT NULL,
"Publication" TEXT NOT NULL,
"AuthorID" INTEGER,
"PostID" INTEGER NOT NULL,
PRIMARY KEY("ID" AUTOINCREMENT),
FOREIGN KEY("AuthorID") REFERENCES "User"("ID"),
FOREIGN KEY("PostID") REFERENCES "Post"("ID"));`)
// Comments for PostID 4 (AdminDue's post with MaxComments = 2)
// This post should have 1 comment (one less than the limit)
.run(`INSERT INTO Comment(Text, Publication, AuthorID, PostID) VALUES('This post has a comment limit, interesting!', '2025-06-05 14:35:10', 3, 4);`) // UtenteUno comments on AdminDue's post
// Comments ensuring each user has comments from other users (2-3 comments each)
//Comments from AdminUno (ID: 1) on other users' posts
.run(`INSERT INTO Comment(Text, Publication, AuthorID, PostID) VALUES('Good point!', '2025-06-18 09:02:45', 1, 5);`) // AdminUno on UtenteUno's post
.run(`INSERT INTO Comment(Text, Publication, AuthorID, PostID) VALUES('Enjoyed reading this.', '2025-06-01 23:59:01', 1, 7);`) // AdminUno on UtenteDue's post
// Comments from AdminDue (ID: 2) on other users' posts
.run(`INSERT INTO Comment(Text, Publication, AuthorID, PostID) VALUES('Nice work!', '2025-06-11 04:17:30', 2, 6);`) // AdminDue on UtenteUno's post
// Comments from UtenteUno (ID: 3) on other users' posts (already one on PostID 4)
.run(`INSERT INTO Comment(Text, Publication, AuthorID, PostID) VALUES('Totally agree!', '2025-06-14 10:05:00', 3, 2);`) // UtenteUno on AdminUno's post
.run(`INSERT INTO Comment(Text, Publication, AuthorID, PostID) VALUES('Great thoughts.', '2025-06-03 01:50:55', 3, 3);`) // UtenteUno on AdminDue's post
// Comments from UtenteDue (ID: 4) on other users' posts
.run(`INSERT INTO Comment(Text, Publication, AuthorID, PostID) VALUES('Well said.', '2025-06-19 16:30:20', 4, 1);`) // UtenteDue on AdminUno's post
.run(`INSERT INTO Comment(Text, Publication, AuthorID, PostID) VALUES('This is very helpful!', '2025-06-02 21:12:05', 4, 5);`) // UtenteDue on UtenteUno's post
// Comments from UtenteTre (ID: 5) on other users' posts
.run(`INSERT INTO Comment(Text, Publication, AuthorID, PostID) VALUES('Appreciate your perspective.', '2025-06-16 12:08:50', 5, 3);`) // UtenteTre on AdminDue's post
.run(`INSERT INTO Comment(Text, Publication, AuthorID, PostID) VALUES('Inspiring content.', '2025-06-10 06:25:30', 5, 8);`) // UtenteTre on UtenteDue's post
// Anonymous comments
.run(`INSERT INTO Comment(Text, Publication, PostID) VALUES('Appreciate my anonymous perspective.', '2025-06-04 17:00:00', 1);`)
.run(`INSERT INTO Comment(Text, Publication, PostID) VALUES('Dont appreciate my anonymous perspective.', '2025-06-17 22:45:10', 1);`)
.run(`INSERT INTO Comment(Text, Publication, PostID) VALUES('Dont appreciate my anonymous perspective.', '2025-06-13 03:00:00', 3);`)
.run(`INSERT INTO Comment(Text, Publication, PostID) VALUES('Totally an anonymous comment, you will never know the author!', '2025-06-13 03:00:00', 7);`)
// Create Interesting table
.run(`CREATE TABLE "Interesting" (
"UserID" INTEGER NOT NULL,
"CommentID" INTEGER NOT NULL,
PRIMARY KEY("UserID", "CommentID"),
FOREIGN KEY("CommentID") REFERENCES "Comment"("ID"),
FOREIGN KEY("UserID") REFERENCES "User"("ID"));`).run(`INSERT INTO Interesting(UserID,CommentID) VALUES(1,1)`)
.run(`INSERT INTO Interesting(UserID,CommentID) VALUES(2,1)`)
.run(`INSERT INTO Interesting(UserID,CommentID) VALUES(1,2)`)
.run(`INSERT INTO Interesting(UserID,CommentID) VALUES(1,3)`)
.run(`INSERT INTO Interesting(UserID,CommentID) VALUES(2,2)`);
});
resolve();
});
}
//Ugly but ensures that the passwords are updated after the creation of db
createDB().then(
insertHashedPwd(1,"PasswordAdmin1").then(
insertHashedPwd(2,"PasswordAdmin2").then(
insertHashedPwd(3,"PasswordUtente1").then(
insertHashedPwd(4,"PasswordUtente2").then(
insertHashedPwd(5,"PasswordUtente3")
)
)
)
)
);

BIN
server/test.db Normal file

Binary file not shown.

18
server/test.js Normal file
View File

@ -0,0 +1,18 @@
const forumDao = require('./dao-forum.js');
const crypto = require('crypto');
/*/forumDao.listPosts().then(console.log);
forumDao.listCommentsOfPost(1).then(console.log);
forumDao.getNumCommentsOfPost(1).then(console.log);
forumDao.getPost(1).then(console.log);
forumDao.getAuthorOfPost(1).then(console.log);*/
//forumDao.createPost({title: "Post Creato",authorid: 1, maxcomments: null, publication: "2023-03-02", text: "Testo post creato"}).then(console.log);
//forumDao.createComment({text: "Commento creato 2", publication:"2024-02-01",authorid: 1, postid: 1}).then(console.log);
//forumDao.listCommentsOfPost(1).then(console.log);
//forumDao.setInterestingComment(1,2).then(console.log);
//forumDao.deletePost(2).then(console.log)
//forumDao.deleteComment(2).then(console.log)
//forumDao.listCommentsOfPost(3).then(console.log);