From 6c60d015a2bc485ffb86bd3c0fa3f0edc697ce81 Mon Sep 17 00:00:00 2001 From: Yana Pechenenko Date: Mon, 16 Feb 2026 19:12:06 +0100 Subject: [PATCH 1/3] Add set up & configuration, data structure, file operations with error handling --- .prettierrc | 7 ++ package-lock.json | 86 +++++++++++++++++++ package.json | 16 ++++ reading-list-manager/books.json | 45 +++++++++- reading-list-manager/readingList.js | 126 +++++++++++++++++++++++++--- 5 files changed, 269 insertions(+), 11 deletions(-) create mode 100644 .prettierrc create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..eba5a68 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "printWidth": 80, + "tabWidth": 2 +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a5f0883 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,86 @@ +{ + "name": "c55-core-week-6", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "c55-core-week-6", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "chalk": "^4.1.2" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0ce4600 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "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" + }, + "type": "module", + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "chalk": "^4.1.2" + } +} diff --git a/reading-list-manager/books.json b/reading-list-manager/books.json index 0637a08..0267891 100644 --- a/reading-list-manager/books.json +++ b/reading-list-manager/books.json @@ -1 +1,44 @@ -[] \ No newline at end of file +[ + { + "id": 1, + "title": "Kobzar", + "author": "Taras Shevchenko", + "genre": "Poetry", + "read": false + }, + { + "id": 2, + "title": "Forest Song", + "author": "Lesya Ukrainka", + "genre": "Drama", + "read": false + }, + { + "id": 3, + "title": "The Enchanted Desna", + "author": "Oleksandr Dovzhenko", + "genre": "Memoir", + "read": false + }, + { + "id": 4, + "title": "The City", + "author": "Valerian Pidmohylny", + "genre": "Novel", + "read": false + }, + { + "id": 5, + "title": "Marusia Churai", + "author": "Lina Kostenko", + "genre": "Historical Novel", + "read": false + }, + { + "id": 6, + "title": "Death and the Penguin", + "author": "Andrey Kurkov", + "genre": "Novel", + "read": false + } +] \ No newline at end of file diff --git a/reading-list-manager/readingList.js b/reading-list-manager/readingList.js index 84febab..4f64def 100644 --- a/reading-list-manager/readingList.js +++ b/reading-list-manager/readingList.js @@ -1,23 +1,129 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +const dataDir = 'reading-list-manager'; +const filePath = path.join(dataDir, 'books.json'); +const defaultArray = []; + +function isDataFull(book) { + const isDataFull = book.id && book.title && book.author && book.genre; + return isDataFull; +} + // Place here the file operation functions for loading and saving books 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 data = JSON.parse(fs.readFileSync(filePath, 'utf-8')); + + if (!Array.isArray(data)) { + throw new Error("Books file doesn't contain an array"); + } + + return data; + } catch (error) { + if (error.code === 'ENOENT') { + console.log('Books file not found'); + fs.writeFileSync(filePath, JSON.stringify(defaultArray, null, 2)); + return defaultArray; + } else if (error.name === 'SyntaxError') { + console.log('Invalid JSON. Resetting...'); + fs.writeFileSync(filePath, JSON.stringify(defaultArray, null, 2)); + return defaultArray; + } else { + return `Unexpected error: ${error.message}`; + } + } } +// console.log(loadBooks()); + function saveBooks(books) { - // TODO: Implement this function - // Write books array to books.json - // Use try-catch for error handling + try { + if (!Array.isArray(books)) { + throw new Error("Books file doesn't contain an array"); + } + + if (!books.every((book) => isDataFull(book))) { + throw new Error('Book must has id, title, author and genre'); + } + + fs.writeFileSync(filePath, JSON.stringify(books, null, 2)); + return 'Books saved'; + } catch (error) { + return `Cannot write: ${error.message}`; + } } +// console.log( +// saveBooks([ +// { +// id: 1, +// title: 'Kobzar', +// author: 'Taras Shevchenko', +// genre: 'Poetry', +// read: false, +// }, +// { +// id: 2, +// title: 'Forest Song', +// author: 'Lesya Ukrainka', +// genre: 'Drama', +// read: false, +// }, +// { +// id: 3, +// title: 'The Enchanted Desna', +// author: 'Oleksandr Dovzhenko', +// genre: 'Memoir', +// read: false, +// }, +// { +// id: 4, +// title: 'The City', +// author: 'Valerian Pidmohylny', +// genre: 'Novel', +// read: false, +// }, +// { +// id: 5, +// title: 'Marusia Churai', +// author: 'Lina Kostenko', +// genre: 'Historical Novel', +// read: false, +// }, +// ]) +// ); + function addBook(book) { - // TODO: Implement this function + try { + if (!isDataFull(book)) { + throw new Error('Book must has id, title, author and genre'); + } + const books = loadBooks(); + + if (books.some((item) => item.id === book.id)) { + throw new Error(`Book with ${book.id} already exists`); + } + + books.push(book); + saveBooks(books); + return 'Book successfully added'; + } catch (error) { + return `Cannot append: ${error.message}`; + } } +// console.log( +// addBook({ +// id: 6, +// title: 'Death and the Penguin', +// author: 'Andrey Kurkov', +// genre: 'Novel', +// read: false, +// }) +// ); + function getUnreadBooks() { // TODO: Implement this function using filter() } @@ -50,4 +156,4 @@ function printSummary() { // Show statistics with chalk // Display total books, read count, unread count // Use bold for stats -} \ No newline at end of file +} From 0d4f4817b186d790c3e91055b230c6b86e146644 Mon Sep 17 00:00:00 2001 From: Yana Pechenenko Date: Wed, 18 Feb 2026 18:56:54 +0100 Subject: [PATCH 2/3] Add array method functions --- reading-list-manager/books.json | 4 +- reading-list-manager/readingList.js | 132 ++++++++++++++++------------ 2 files changed, 80 insertions(+), 56 deletions(-) diff --git a/reading-list-manager/books.json b/reading-list-manager/books.json index 0267891..c5f307c 100644 --- a/reading-list-manager/books.json +++ b/reading-list-manager/books.json @@ -18,7 +18,7 @@ "title": "The Enchanted Desna", "author": "Oleksandr Dovzhenko", "genre": "Memoir", - "read": false + "read": true }, { "id": 4, @@ -39,6 +39,6 @@ "title": "Death and the Penguin", "author": "Andrey Kurkov", "genre": "Novel", - "read": false + "read": true } ] \ No newline at end of file diff --git a/reading-list-manager/readingList.js b/reading-list-manager/readingList.js index 4f64def..c07de63 100644 --- a/reading-list-manager/readingList.js +++ b/reading-list-manager/readingList.js @@ -36,7 +36,7 @@ function loadBooks() { } } -// console.log(loadBooks()); +console.log(loadBooks()); function saveBooks(books) { try { @@ -55,45 +55,45 @@ function saveBooks(books) { } } -// console.log( -// saveBooks([ -// { -// id: 1, -// title: 'Kobzar', -// author: 'Taras Shevchenko', -// genre: 'Poetry', -// read: false, -// }, -// { -// id: 2, -// title: 'Forest Song', -// author: 'Lesya Ukrainka', -// genre: 'Drama', -// read: false, -// }, -// { -// id: 3, -// title: 'The Enchanted Desna', -// author: 'Oleksandr Dovzhenko', -// genre: 'Memoir', -// read: false, -// }, -// { -// id: 4, -// title: 'The City', -// author: 'Valerian Pidmohylny', -// genre: 'Novel', -// read: false, -// }, -// { -// id: 5, -// title: 'Marusia Churai', -// author: 'Lina Kostenko', -// genre: 'Historical Novel', -// read: false, -// }, -// ]) -// ); +console.log( + saveBooks([ + { + id: 1, + title: 'Kobzar', + author: 'Taras Shevchenko', + genre: 'Poetry', + read: false, + }, + { + id: 2, + title: 'Forest Song', + author: 'Lesya Ukrainka', + genre: 'Drama', + read: false, + }, + { + id: 3, + title: 'The Enchanted Desna', + author: 'Oleksandr Dovzhenko', + genre: 'Memoir', + read: true, + }, + { + id: 4, + title: 'The City', + author: 'Valerian Pidmohylny', + genre: 'Novel', + read: false, + }, + { + id: 5, + title: 'Marusia Churai', + author: 'Lina Kostenko', + genre: 'Historical Novel', + read: false, + }, + ]) +); function addBook(book) { try { @@ -114,36 +114,60 @@ function addBook(book) { } } -// console.log( -// addBook({ -// id: 6, -// title: 'Death and the Penguin', -// author: 'Andrey Kurkov', -// genre: 'Novel', -// read: false, -// }) -// ); +console.log( + addBook({ + id: 6, + title: 'Death and the Penguin', + author: 'Andrey Kurkov', + genre: 'Novel', + read: true, + }) +); function getUnreadBooks() { - // TODO: Implement this function using filter() + const books = loadBooks(); + const unreadBooks = books.filter((book) => !book.read); + return unreadBooks; } +console.log('Get unread books:', getUnreadBooks()); + function getBooksByGenre(genre) { - // TODO: Implement this function using filter() + const books = loadBooks(); + const booksByGenre = books.filter((book) => book.genre === genre); + return booksByGenre; } +console.log('Get books by genre:', getBooksByGenre('Novel')); + function markAsRead(id) { - // TODO: Implement this function using map() + const books = loadBooks(); + const updateForRead = books.map((book) => { + if (book.id === id) { + return { ...book, read: true }; + } + return book; + }); + return updateForRead; } +console.log('After read update:', markAsRead(1)); + function getTotalBooks() { - // TODO: Implement this function using length + const books = loadBooks(); + return books.length; } +console.log('Total books:', getTotalBooks()); + function hasUnreadBooks() { - // TODO: Implement this function using some() + const books = loadBooks(); + const hasUnreadBooks = books.some((book) => !book.read); + return hasUnreadBooks; } +console.log('Has unread books?', hasUnreadBooks()); + function printAllBooks() { // TODO: Implement this function // Loop through and display with chalk From c474a41107f09b2f0fe983478917b502a0ea2968 Mon Sep 17 00:00:00 2001 From: Yana Pechenenko Date: Wed, 18 Feb 2026 19:42:47 +0100 Subject: [PATCH 3/3] Add display functions --- package.json | 2 +- reading-list-manager/app.js | 15 ++- reading-list-manager/readingList.js | 160 +++++++++++++++------------- 3 files changed, 98 insertions(+), 79 deletions(-) diff --git a/package.json b/package.json index 0ce4600..36d6256 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "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" + "start": "node reading-list-manager/app.js" }, "type": "module", "keywords": [], diff --git a/reading-list-manager/app.js b/reading-list-manager/app.js index b0365ef..78f6965 100644 --- a/reading-list-manager/app.js +++ b/reading-list-manager/app.js @@ -1,6 +1,14 @@ // This is the entrypoint for your application. // node app.js +import { + getBooksByGenre, + loadBooks, + markAsRead, + printAllBooks, + printSummary, +} from './readingList.js'; + // TODO: Implement the main application logic here // 1. Load books on startup // 2. Display all books @@ -8,6 +16,11 @@ // 4. Add example of filtering by genre or read/unread status // 5. Add example of marking a book as read -console.log('šŸ“š MY READING LIST šŸ“š\n'); +// console.log('šŸ“š MY READING LIST šŸ“š\n'); // Your implementation here +console.log(loadBooks()); +console.log(printAllBooks()); +console.log(printSummary()); +console.log('Get books by novel genre:', getBooksByGenre('Novel')); +console.log('After read update:', markAsRead(1)); diff --git a/reading-list-manager/readingList.js b/reading-list-manager/readingList.js index c07de63..eb00ff6 100644 --- a/reading-list-manager/readingList.js +++ b/reading-list-manager/readingList.js @@ -1,5 +1,6 @@ import fs from 'node:fs'; import path from 'node:path'; +import chalk from 'chalk'; const dataDir = 'reading-list-manager'; const filePath = path.join(dataDir, 'books.json'); @@ -12,7 +13,7 @@ function isDataFull(book) { // Place here the file operation functions for loading and saving books -function loadBooks() { +export function loadBooks() { try { const data = JSON.parse(fs.readFileSync(filePath, 'utf-8')); @@ -36,9 +37,7 @@ function loadBooks() { } } -console.log(loadBooks()); - -function saveBooks(books) { +export function saveBooks(books) { try { if (!Array.isArray(books)) { throw new Error("Books file doesn't contain an array"); @@ -55,47 +54,47 @@ function saveBooks(books) { } } -console.log( - saveBooks([ - { - id: 1, - title: 'Kobzar', - author: 'Taras Shevchenko', - genre: 'Poetry', - read: false, - }, - { - id: 2, - title: 'Forest Song', - author: 'Lesya Ukrainka', - genre: 'Drama', - read: false, - }, - { - id: 3, - title: 'The Enchanted Desna', - author: 'Oleksandr Dovzhenko', - genre: 'Memoir', - read: true, - }, - { - id: 4, - title: 'The City', - author: 'Valerian Pidmohylny', - genre: 'Novel', - read: false, - }, - { - id: 5, - title: 'Marusia Churai', - author: 'Lina Kostenko', - genre: 'Historical Novel', - read: false, - }, - ]) -); - -function addBook(book) { +// console.log( +// saveBooks([ +// { +// id: 1, +// title: 'Kobzar', +// author: 'Taras Shevchenko', +// genre: 'Poetry', +// read: false, +// }, +// { +// id: 2, +// title: 'Forest Song', +// author: 'Lesya Ukrainka', +// genre: 'Drama', +// read: false, +// }, +// { +// id: 3, +// title: 'The Enchanted Desna', +// author: 'Oleksandr Dovzhenko', +// genre: 'Memoir', +// read: true, +// }, +// { +// id: 4, +// title: 'The City', +// author: 'Valerian Pidmohylny', +// genre: 'Novel', +// read: false, +// }, +// { +// id: 5, +// title: 'Marusia Churai', +// author: 'Lina Kostenko', +// genre: 'Historical Novel', +// read: false, +// }, +// ]) +// ); + +export function addBook(book) { try { if (!isDataFull(book)) { throw new Error('Book must has id, title, author and genre'); @@ -114,33 +113,31 @@ function addBook(book) { } } -console.log( - addBook({ - id: 6, - title: 'Death and the Penguin', - author: 'Andrey Kurkov', - genre: 'Novel', - read: true, - }) -); - -function getUnreadBooks() { +// console.log( +// addBook({ +// id: 6, +// title: 'Death and the Penguin', +// author: 'Andrey Kurkov', +// genre: 'Novel', +// read: true, +// }) +// ); + +export function getUnreadBooks() { const books = loadBooks(); const unreadBooks = books.filter((book) => !book.read); return unreadBooks; } -console.log('Get unread books:', getUnreadBooks()); +// console.log('Get unread books:', getUnreadBooks()); -function getBooksByGenre(genre) { +export function getBooksByGenre(genre) { const books = loadBooks(); const booksByGenre = books.filter((book) => book.genre === genre); return booksByGenre; } -console.log('Get books by genre:', getBooksByGenre('Novel')); - -function markAsRead(id) { +export function markAsRead(id) { const books = loadBooks(); const updateForRead = books.map((book) => { if (book.id === id) { @@ -151,33 +148,42 @@ function markAsRead(id) { return updateForRead; } -console.log('After read update:', markAsRead(1)); - -function getTotalBooks() { +export function getTotalBooks() { const books = loadBooks(); return books.length; } -console.log('Total books:', getTotalBooks()); +// console.log('Total books:', getTotalBooks()); -function hasUnreadBooks() { +export function hasUnreadBooks() { const books = loadBooks(); const hasUnreadBooks = books.some((book) => !book.read); return hasUnreadBooks; } -console.log('Has unread books?', hasUnreadBooks()); +// console.log('Has unread books?', hasUnreadBooks()); + +export function printAllBooks() { + const books = loadBooks(); + let output = '\nšŸ“š MY READING LIST šŸ“š \n\nAll books:\n\n'; + + for (const book of books) { + const { id, title, author, genre, read } = book; + const readChalk = read ? chalk.green('šŸ“— Read') : chalk.yellow('šŸ“• Unread'); + const titleChalk = chalk.cyan(title); + + output += `${id + 1}. ${titleChalk} by ${author} (${genre}) ${readChalk}\n`; + } -function printAllBooks() { - // TODO: Implement this function - // Loop through and display with chalk - // Use green for read books, yellow for unread - // Use cyan for titles + return output; } -function printSummary() { - // TODO: Implement this function - // Show statistics with chalk - // Display total books, read count, unread count - // Use bold for stats +export function printSummary() { + const totalBooks = getTotalBooks(); + const unreadBooks = getUnreadBooks().length; + const readBooks = totalBooks - unreadBooks; + + const output = `\nšŸ“Š SUMMARY šŸ“Š\n\n${chalk.bold(`Total Books: ${totalBooks}\nRead: ${readBooks}\nUnread: ${unreadBooks}`)}`; + + return output; }