-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adding a proposed solution for Lab 3
- Loading branch information
1 parent
94c5e62
commit 0f0cfac
Showing
13 changed files
with
2,887 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
# Lab 03 - APIs with Express | ||
|
||
This repository contains a proposed solution for the third laboratory of the courses. Specifically, this README includes an overview of the files contained in the `solution` folder and a description of each API offered by the server. | ||
|
||
## File overview | ||
|
||
- `server.mjs`: the main file of the server. It defines all the API endpoints and behavior. It interacts with the database and returns to the client the desired data. | ||
- `db.mjs`: it opens the database. It has to be imported (e.g., by `dao-film.mjs`) to interact with the db. | ||
- `dao-films.mjs`: it contains all the method for interacting with the database (specifically, to interact with the `film` table). | ||
- `films.mjs`: the same data model for Film objects used in the previous labs. | ||
- `test-api.http`: this file can be used for testing the API with a dedicated Visual Studio Code extension. | ||
|
||
## List of APIs offered by the server | ||
|
||
### Film Management | ||
|
||
#### Get all films | ||
|
||
HTTP method: `GET` URL: `/api/films` | ||
|
||
- Description: Get the full list of films or the films that match the query filter parameter | ||
- Request body: _None_ | ||
- Request query parameter: _filter_ name of the filter to apply (filter-all, filter-favorite, filter-best, filter-lastmonth, filter-unseen) | ||
- Response: `200 OK` (success) | ||
- Response body: Array of objects, each describing one film: | ||
|
||
``` json | ||
[ | ||
{ | ||
"id": 1, | ||
"title": "Pulp Fiction", | ||
"favorite": true, | ||
"watchDate": "2023-03-11", | ||
"rating": 5, | ||
"userId": 1 | ||
}, | ||
{ | ||
"id": 2, | ||
"title": "21 Grams", | ||
"favorite": true, | ||
"watchDate": "2023-03-17", | ||
"rating": 4, | ||
"userId": 1 | ||
}, | ||
... | ||
] | ||
``` | ||
|
||
- Error responses: `500 Internal Server Error` (generic error) | ||
|
||
#### Get film by id | ||
|
||
HTTP method: `GET` URL: `/api/films/:id` | ||
|
||
- Description: Get the film corresponding to the id | ||
- Request body: _None_ | ||
- Response: `200 OK` (success) | ||
- Response body: One object describing the required film: | ||
|
||
``` JSON | ||
[ | ||
{ | ||
"id": 2, | ||
"title": "21 Grams", | ||
"favorite": true, | ||
"watchDate": "2023-03-17", | ||
"rating": 4, | ||
"userId": 1 | ||
} | ||
] | ||
``` | ||
|
||
- Error responses: `500 Internal Server Error` (generic error), `404 Not Found` (not present or unavailable) | ||
|
||
#### Add a new film | ||
|
||
HTTP method: `POST` URL: `/api/films` | ||
|
||
- Description: Add a new film to the films of a specified user | ||
- Request body: description of the object to add | ||
|
||
``` JSON | ||
{ | ||
"title": "21 Grams", | ||
"favorite": true, | ||
"watchDate": "2023-03-17", | ||
"rating": 4, | ||
"userId": 1 | ||
} | ||
``` | ||
|
||
- Response: `200 OK` (success) | ||
- Response body: the entire representation of the newly-added film | ||
|
||
- Error responses: `404 Not Found` (not present or unavailable), `422 Unprocessable Entity` (invalid input), `503 Service Unavailable` (database error) | ||
|
||
#### Update an existing film | ||
|
||
HTTP method: `PUT` URL: `/api/films/:id` | ||
|
||
- Description: Update values of an existing film, except the id | ||
- Request body: description of the object to update | ||
|
||
``` JSON | ||
{ | ||
"title": "The Matrix", | ||
"favorite": true, | ||
"watchDate": "2023-03-31", | ||
"rating": 5, | ||
"userId": 1 | ||
} | ||
``` | ||
|
||
- Response: `200 OK` (success) | ||
- Response body: the entire representation of the newly-added film | ||
|
||
- Error responses: `404 Not Found` (not present or unavailable), `422 Unprocessable Entity` (invalid input), `503 Service Unavailable` (database error) | ||
|
||
#### Delete an existing film | ||
|
||
HTTP method: `DELETE` URL: `/api/films/:id` | ||
|
||
- Description: Delete an existing film | ||
- Request body: _None_ | ||
|
||
- Response: `200 OK` (success) | ||
- Response body: _None_ | ||
|
||
- Error responses: `404 Not Found` (not present or unavailable), `503 Service Unavailable` (database error) | ||
|
||
#### Update whether a film is favorite | ||
|
||
HTTP method: `PUT` URL: `/api/films/:id/favorite` | ||
|
||
- Description: Update favorite value of an existing film | ||
- Request body: value of the favorite property | ||
|
||
``` JSON | ||
{ | ||
"favorite": true, | ||
} | ||
``` | ||
|
||
- Response: `200 OK` (success) | ||
- Response body: the object as represented in the database | ||
|
||
- Error responses: `404 Not Found` (not present or unavailable), `422 Unprocessable Entity` (invalid input), `503 Service Unavailable` (database error) | ||
|
||
#### Update the rating of an existing film | ||
|
||
HTTP method: `PUT` URL: `/api/films/:id/rating` | ||
|
||
- Description: Update the rating of an existing film | ||
- Request body: value of the rating property | ||
|
||
``` JSON | ||
{ | ||
"rating": 5, | ||
} | ||
``` | ||
|
||
- Response: `200 OK` (success) | ||
- Response body: the object as represented in the database | ||
|
||
- Error responses: `404 Not Found` (not present or unavailable), `422 Unprocessable Entity` (invalid input), `503 Service Unavailable` (database error) |
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* 01UDFOV Applicazioni Web I / 01TXYOV Web Applications I | ||
* Model from lab 1 - 2024 | ||
*/ | ||
|
||
import dayjs from "dayjs"; | ||
|
||
export default function Film(id, title, isFavorite = false, watchDate = null, rating = null, userId = 1) { | ||
this.id = id; | ||
this.title = title; | ||
this.favorite = isFavorite; | ||
this.rating = rating; | ||
// saved as dayjs object only if watchDate is truthy | ||
this.watchDate = watchDate && dayjs(watchDate); | ||
this.userId = userId; | ||
|
||
// customize toJSON method to return the object with date only, no time | ||
this.toJSON = () => { | ||
return { | ||
...this, | ||
watchDate: this.watchDate ? this.watchDate.format("YYYY-MM-DD") : null, | ||
}; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* Data Access Object (DAO) module for accessing films data */ | ||
|
||
import dayjs from "dayjs"; | ||
import db from "./db.mjs"; | ||
import Film from "./Film.mjs"; | ||
|
||
|
||
const filters = { | ||
'filter-favorite': {label: 'Favorites', filterFunction: film => film.favorite}, | ||
'filter-best': {label: 'Best Rated', filterFunction: film => film.rating >= 5}, | ||
'filter-lastmonth': {label: 'Seen Last Month', filterFunction: film => isSeenLastMonth(film)}, | ||
'filter-unseen': {label: 'Unseen', filterFunction: film => !film.watchDate} | ||
}; | ||
|
||
const isSeenLastMonth = (film) => { | ||
if ('watchDate' in film && film.watchDate) { // Accessing watchDate only if defined | ||
const diff = film.watchDate.diff(dayjs(), 'month'); | ||
const isLastMonth = diff <= 0 && diff > -1; // last month | ||
return isLastMonth; | ||
} | ||
}; | ||
|
||
function mapRowsToFilms(rows) { | ||
// Note: the parameters must follow the same order specified in the constructor. | ||
return rows.map(row => new Film(row.id, row.title, row.isFavorite === 1, row.watchDate, row.rating, row.userId)); | ||
} | ||
|
||
|
||
// NOTE: all functions return error messages as json object { error: <string> } | ||
export default function FilmDao() { | ||
|
||
// This function retrieves the whole list of films from the database. | ||
this.getFilms = (filter) => { | ||
return new Promise((resolve, reject) => { | ||
const query = 'SELECT * FROM films'; | ||
db.all(query, (err, rows) => { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
const films = mapRowsToFilms(rows); | ||
|
||
if (filters.hasOwnProperty(filter)) | ||
resolve(films.filter(filters[filter].filterFunction)); | ||
else // if an invalid filter is specified, all the films are returned. | ||
resolve(films); | ||
} | ||
}); | ||
}); | ||
}; | ||
|
||
// This function retrieves a film given its id and the associated user id. | ||
this.getFilm = (id) => { | ||
return new Promise((resolve, reject) => { | ||
const query = 'SELECT * FROM films WHERE id=?'; | ||
db.get(query, [id], (err, row) => { | ||
if (err) { | ||
reject(err); | ||
} | ||
if (row === undefined) { | ||
resolve({error: 'Film not found.'}); | ||
} else { | ||
resolve(mapRowsToFilms([row])[0]); | ||
} | ||
}); | ||
}); | ||
}; | ||
|
||
|
||
/** | ||
* This function adds a new film in the database. | ||
* The film id is added automatically by the DB, and it is returned as this.lastID. | ||
*/ | ||
this.addFilm = (film) => { | ||
return new Promise((resolve, reject) => { | ||
const query = 'INSERT INTO films (title, isFavorite, rating, watchDate, userId) VALUES(?, ?, ?, ?, ?)'; | ||
const watchDate = film.watchDate ? film.watchDate.format("YYYY-MM-DD") : null; | ||
let rating; | ||
if (!film.rating || film.rating < 1 || film.rating > 5) | ||
rating = null; | ||
else | ||
rating = film.rating; | ||
|
||
db.run(query, [film.title, film.favorite, rating, watchDate, film.userId], function (err) { | ||
if (err) { | ||
reject(err); | ||
} | ||
film.id = this.lastID; | ||
resolve(film); | ||
}); | ||
}); | ||
}; | ||
|
||
// This function updates an existing film given its id and the new properties. | ||
this.updateFilm = (id, film) => { | ||
return new Promise((resolve, reject) => { | ||
const query = 'UPDATE films SET title = ?, isFavorite = ?, rating = ?, watchDate = ? WHERE id = ?'; | ||
const watchDate = film.watchDate ? film.watchDate.format("YYYY-MM-DD") : null; | ||
let rating; | ||
if (!film.rating || film.rating < 1 || film.rating > 5) | ||
rating = null; | ||
else | ||
rating = film.rating; | ||
|
||
db.run(query, [film.title, film.favorite, rating, watchDate, id], function (err) { | ||
if (err) { | ||
reject(err); | ||
} | ||
if (this.changes !== 1) { | ||
resolve({error: 'Film not found.'}); | ||
} else { | ||
resolve(film); | ||
} | ||
}); | ||
}); | ||
}; | ||
|
||
// This function deletes an existing film given its id. | ||
this.deleteFilm = (id) => { | ||
return new Promise((resolve, reject) => { | ||
const query = 'DELETE FROM films WHERE id = ?'; | ||
db.run(query, [id], function (err) { | ||
if (err) { | ||
reject(err); | ||
} else | ||
resolve(this.changes); | ||
}); | ||
}); | ||
}; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* Data Access Object (DAO) module for accessing users data */ | ||
|
||
import db from "./db.mjs"; | ||
|
||
|
||
// NOTE: all functions return error messages as json object { error: <string> } | ||
export default function UserDao() { | ||
|
||
// This function retrieves one user by id | ||
this.getUser = (id) => { | ||
return new Promise((resolve, reject) => { | ||
const query = 'SELECT * FROM users WHERE id=?'; | ||
db.get(query, [id], (err, row) => { | ||
if (err) { | ||
reject(err); | ||
} | ||
if (row === undefined) { | ||
resolve({error: 'User not found.'}); | ||
} else { | ||
resolve(row); | ||
} | ||
}); | ||
}); | ||
}; | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/** DB access module **/ | ||
|
||
import sqlite3 from "sqlite3"; | ||
|
||
// Opening the database | ||
// NOTE: if you are running the solution from the root folder this path should be: `./lab03-express/solution/films.db` | ||
const db = new sqlite3.Database('films.db', (err) => { | ||
if (err) throw err; | ||
}); | ||
|
||
export default db; |
Binary file not shown.
Oops, something went wrong.