Skip to content
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,7 @@ dist
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

node_modules/
package-lock.json
.env
.DS_store
7 changes: 7 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80
}
24 changes: 24 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very elaborate package,json file. Good attention to detail!

"name": "c55-core-week-6",
"version": "1.0.0",
"description": "The week 6 assignment for the HackYourFuture Core program can be found at the following link: https://hub.hackyourfuture.nl/core-program-week-6-assignment",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/hannahwn/c55-core-week-6.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"bugs": {
"url": "https://github.com/hannahwn/c55-core-week-6/issues"
},
"homepage": "https://github.com/hannahwn/c55-core-week-6#readme",
"dependencies": {
"chalk": "^4.1.2"
}
}
24 changes: 19 additions & 5 deletions reading-list-manager/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,26 @@
// node app.js

// TODO: Implement the main application logic here
// 1. Load books on startup
// 2. Display all books
// 3. Show summary statistics
// 4. Add example of filtering by genre or read/unread status
// 5. Add example of marking a book as read
const readingList = require('./readingList');
const chalk = require('chalk');

console.log('📚 MY READING LIST 📚\n');

// Your implementation here
function main() {
console.log(chalk.blue('=== MY READING LIST ==='));

// 1. Load books on startup
let myBooks = readingList.loadBooks();
// 2. Display all books
readingList.printAllBooks(myBooks);
// 3. Show summary statistics
readingList.printSummary(myBooks);
// 4. Add example of filtering by genre or read/unread status
readingList.getBooksByGenre(myBooks, 'Fiction');
// 5. Add example of marking a book as read
console.log(chalk.blue('/n === Marking a book as read ==='));
myBooks = readingList.markAsRead(myBooks, 7);
}

main();
52 changes: 51 additions & 1 deletion reading-list-manager/books.json
Original file line number Diff line number Diff line change
@@ -1 +1,51 @@
[]
[
{
"id": 1,
"title": "1984",
"author": "George Orwell",
"genre": "Fiction",
"read": false
},
{
"id": 2,
"title": "The Havoc of Choice",
"author": "Wanjiru Koinange",
"genre": "African Literature",
"read": true
},
{
"id": 3,
"title": "CreepShow",
"author": "Stephen King",
"genre": "Comic",
"read": false
},
{
"id": 4,
"title": "Fire and Ice",
"author": "Erin Hunter",
"genre": "Fiction",
"read": true
},
{
"id": 5,
"title": "Toxic Bachelors",
"author": "Daniella Steel",
"genre": "Adult",
"read": false
},
{
"id": 6,
"title": "Foxes in a fix",
"author": "Bruce Cameron",
"genre": "Animals",
"read": true
},
{
"id": 7,
"title": "The Luzhin Defense",
"author": "Vladimir Nabokov",
"genre": "Chess",
"read": true
}
]
216 changes: 202 additions & 14 deletions reading-list-manager/readingList.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,241 @@
// Place here the file operation functions for loading and saving books
const fs = require('fs');
const chalk = require('chalk');

const FILE_NAME = 'books.json';

function loadBooks() {
// TODO: Implement this function
// Read from books.json
// Handle missing file (create empty array)
// Handle invalid JSON (notify user, use empty array)
// Use try-catch for error handling
try {
const content = fs.readFileSync('./books.json', 'utf8');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to check first if file exsts - If yes: then read file. Then check if read content is an array. If yes: continue. Else return empty array

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fs.readFileSync('./books.json', 'utf8'); can use FILE_NAME too

const books = JSON.parse(content);

if (!Array.isArray(books)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good check...

console.log(chalk.yellow('Books data was not an array – starting fresh'));
return [];
}

return books;
} catch (error) {
if (error.code === 'ENOENT') {
console.log(
chalk.yellow('No books.json found. Starting with empty list.')
);
return [];
}
if (error instanceof SyntaxError) {
console.log(
chalk.red(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to check for specific errors and inform the user

'Invalid JSON in books.json – file is broken. Starting fresh!'
)
);
return [];
}
console.log(chalk.red('Error loading books:'), error.message);
return [];
}
}

function saveBooks(books) {
// TODO: Implement this function
// Write books array to books.json
// Use try-catch for error handling
try {
const data = JSON.stringify(books, null, 2);
fs.writeFileSync(FILE_NAME, data);
console.log(chalk.green('Saved :'));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console.log(chalk.green('Books saved')); With 'Saved :' it looks like something is missing

} catch (error) {
console.log(chalk.red('Save failed'), error.message);
}
}

function addBook(book) {
function addBook(books, titles, author, genre) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: addBook(books, titles, author, genre) titles = title

// TODO: Implement this function
//Create new book
const newBook = {
id: books.length + 1,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this works if the id's reflect the book's index. If the id's don't match the index you may add a book with a duplicate ID: [{id:22}, {id:2}, {id: 1}]

title: title,
author: author,
genre: genre,
read: false,
};

books.push(newBook);

saveBooks(books);

console.log(chalk.green(`✓ Added "${title}"`));

return books;
}

function getUnreadBooks() {
function getUnreadBooks(books) {
// TODO: Implement this function using filter()
const unreadBooks = books.filter(function (book) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use function expression here but anonymous function: books.filter(() => book.read === false)

return book.read === false;
});

console.log(chalk.blue('\n--- UNREAD BOOKS ---'));
if (unreadBooks.length === 0) {
console.log(chalk.yellow('No unread books!'));
} else {
for (let i = 0; i < unreadBooks.length; i++) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

books.forEach(book => console.log(${book.id}. ${book.title} by ${book.author})) should work as well and is less complex to read

const book = unreadBooks[i];
console.log(`${book.id}. ${book.title} by ${book.author}`);
}
}

return unreadBooks;
}

function getBooksByGenre(genre) {
function getBooksByGenre(books, genre) {
// TODO: Implement this function using filter()

console.log('DEBUG → books type:', typeof books);
console.log('DEBUG → is array?:', Array.isArray(books));
console.log(
'DEBUG → books length:',
books?.length ?? 'N/A (books not array)'
);

// Safe filter – prevents crash even if array has holes, undefined items, null, etc.
const genreBooks = books.filter(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very elaborate and fits 'production code' but for a small app these checks are overkill. Anyway, if you want to do this it can be done easier like this:

const genreBooks = books.filter(
(book) => book?.genre?.toLowerCase() === genre.toLowerCase()
);

? (optional chaining operator) does the same as

book &&
typeof book === 'object' && // exists & is object (not null/undefined)
typeof book.genre === 'string' && // genre exists and is actually a string
book.genre.toLowerCase() === genre.toLowerCase()

(book) =>
book &&
typeof book === 'object' && // exists & is object (not null/undefined)
typeof book.genre === 'string' && // genre exists and is actually a string
book.genre.toLowerCase() === genre.toLowerCase()
);

console.log(chalk.blue(`\n--- ${genre} BOOKS ---`));

if (genreBooks.length === 0) {
console.log(chalk.yellow(`No ${genre} books!`));
} else {
for (let i = 0; i < genreBooks.length; i++) {
const book = genreBooks[i];
const readStatus = book.read ? '✓' : '○';
console.log(`${readStatus} ${book.id}. ${book.title} by ${book.author}`);
}
}

return genreBooks;
}

function markAsRead(id) {
function markAsRead(books, id) {
// TODO: Implement this function using map()
const updatedBooks = books.map(function (book) {
if (book.id === id) {
return {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw this elsewhere:
return {...books, read: true}.
Saves writing out a whole new object. You just create a new object from the existing object and overwrite the read property with another value

id: book.id,
title: book.title,
author: book.author,
genre: book.genre,
read: true,
};
}

return book;
});

//Does book exist?

const oldBook = books.find(function (book) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

books.find(book => book.id === id) does the same because you don't use a function expression (function(book)...). Devs don't use function expressions in array methods

return book.id === id;
});

if (!oldBook) {
console.log(chalk.red(`✗ Book #${id} not found!`));
return books;
}

// Save changes
saveBooks(updatedBooks);
console.log(chalk.green(`✓ Marked "${oldBook.title}" as read!`));

return updatedBooks;
}

function getTotalBooks() {
function getTotalBooks(books) {
// TODO: Implement this function using length
const total = books.length;
console.log(chalk.blue(`\nTotal books: ${total}`));
return total;
}

function hasUnreadBooks() {
function hasUnreadBooks(books) {
// TODO: Implement this function using some()
const hasUnread = books.some(function (book) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no function(book)... but (book) =>

return book.read === false;
});

if (hasUnread) {
console.log(chalk.yellow('You have books to read!'));
} else {
console.log(chalk.green('All books are read!'));
}

return hasUnread;
}

function printAllBooks() {
function printAllBooks(books) {
// TODO: Implement this function
// Loop through and display with chalk
// Use green for read books, yellow for unread
// Use cyan for titles
console.log(chalk.blue('\n--- ALL BOOKS ---'));

if (books.length === 0) {
console.log(chalk.yellow('No books yet!'));
return;
}

for (let i = 0; i < books.length; i++) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

books.forEach(book => {const status = book.read ? chalk.green('✓ Read') : chalk.red('○ Unread'))
console.log(...)
}
is less code and does the same

const book = books[i];
let status;
if (book.read) {
status = chalk.green('✓ Read');
} else {
status = chalk.red('○ Unread');
}
console.log(
`${book.id}.${chalk.cyan(book.title)} by ${book.author} - ${status}`
);
}
}

function printSummary() {
function printSummary(books) {
// TODO: Implement this function
// Show statistics with chalk
// Display total books, read count, unread count
// Use bold for stats
}
console.log(chalk.blue('\n--- READING SUMMARY ---'));

// Total books
const total = books.length;
console.log(`Total: ${total}`);

// Count read books
let readCount = 0;
for (let i = 0; i < books.length; i++) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const readCount = books.reduce((acc,book) => acc += book.read ? 1 : 0).
Looks a bit complicated but once you get it, it is easier and much less code to remember ('cognitive load')

if (books[i].read) {
readCount++;
}
}
const unreadCount = total - readCount;

console.log(chalk.green(`Read: ${readCount}`));
console.log(chalk.red(`Unread: ${unreadCount}`));
}

module.exports = {
loadBooks,
saveBooks,
addBook,
getUnreadBooks,
getBooksByGenre,
markAsRead,
getTotalBooks,
hasUnreadBooks,
printAllBooks,
printSummary,
};