diff --git a/action/Action-Switcher.js b/action/Action-Switcher.js new file mode 100644 index 0000000..1863445 --- /dev/null +++ b/action/Action-Switcher.js @@ -0,0 +1,31 @@ +const CheckStatusAction = require("./Check-Status-Action"); +const StartFastingAction = require("./Start-Fasting-Action"); +const StopFastingAction = require("./Stop-Fasting-Action"); +const ListAllFastsAction = require("./List-All-Fasts-Action"); +const UpdateFastingAction = require("./Update-Fasting-Action"); + +class ActionSwitcher { + constructor() {} + + async startAction(menuObjects, action) { + switch (menuObjects[action - 1].handler) { + case "1": + await new CheckStatusAction().chackStatus(); + break; + case "2": + await new StartFastingAction().startFastingAction(); + break; + case "3": + await new StopFastingAction().stopFastingAction(); + break; + case "4": + await new UpdateFastingAction().updateFastingAction(); + break; + case "5": + await new ListAllFastsAction().listAllFastsAction(); + break; + } + } +} + +module.exports = ActionSwitcher; diff --git a/action/Check-Status-Action.js b/action/Check-Status-Action.js new file mode 100644 index 0000000..06e8879 --- /dev/null +++ b/action/Check-Status-Action.js @@ -0,0 +1,46 @@ +const InputOutput = require("../input-output/Input-Output"); +const FastFactory = require("../fast/Fast-Factory"); +const DataService = require("../data/Data-Service"); + +class CheckStatusAction { + constructor() {} + + async chackStatus() { + const res = await new FastFactory().createBulk(); + + res.forEach((fast) => { + let nowDateUTC = new Date().toISOString(); + + if (fast.end <= nowDateUTC) { + fast.active = false; + + console.log("Your last fast has already ended. Here are the details:"); + console.log("Start time: ", new Date(fast.start).toString()); + console.log("End time: ", new Date(fast.end).toString()); + + console.log( + `Hours spent in fasting: ${ + new Date(fast.end).getHours() - new Date(fast.start).getHours() + }` + ); + console.log("Status: ", fast.active); + console.log("\n"); + + new DataService().write(res); + } else { + console.log("You have an ongoing fast:"); + console.log("Start time: ", new Date(fast.start).toString()); + console.log("End time: ", new Date(fast.end).toString()); + console.log( + `Hours elapsed: ${ + new Date(nowDateUTC).getHours() - new Date(fast.start).getHours() + }` + ); + console.log("Status: ", fast.active); + console.log("\n"); + } + }); + } +} + +module.exports = CheckStatusAction; diff --git a/action/List-All-Fasts-Action.js b/action/List-All-Fasts-Action.js new file mode 100644 index 0000000..b2ffc3a --- /dev/null +++ b/action/List-All-Fasts-Action.js @@ -0,0 +1,28 @@ +const FastFactory = require("../fast/Fast-Factory"); + +class ListAllFastsAction { + constructor() {} + + async listAllFastsAction() { + const fasts = await new FastFactory().createBulk(); + + console.log( + "Following is a detailed list of all fasts (past and present): \n" + ); + + if (fasts.length < 1) + return console.log("You haven't started fasting yet."); + + fasts.forEach((fast) => { + console.log("Started at : ", new Date(fast.start).toString()); + console.log( + fast.active ? "To end at: " : "Ended at: ", + new Date(fast.end).toString() + ); + console.log("Duration of fast: ", fast.duration); + console.log("Status: ", fast.active ? "Active" : "Done", "\n"); + }); + } +} + +module.exports = ListAllFastsAction; diff --git a/action/Start-Fasting-Action.js b/action/Start-Fasting-Action.js new file mode 100644 index 0000000..cf1e5b4 --- /dev/null +++ b/action/Start-Fasting-Action.js @@ -0,0 +1,35 @@ +const InputOutput = require("../input-output/Input-Output"); +const FastFactory = require("../fast/Fast-Factory"); +const DataService = require("../data/Data-Service"); +const UserChoiceHandler = require("../user/User-Choice-Handler"); + +class StartFastingAction { + constructor() {} + + async startFastingAction() { + const inputOutput = new InputOutput(); + const duration = await inputOutput.question( + "Enter duration of fasting (i.e 12): " + ); + inputOutput.close(); + + const start = new Date(); + const end = new Date(); + end.setTime(end.getTime() + duration * 60 * 60 * 1000); + + const fasts = await new FastFactory().createBulk(); + + const newFast = { + start: start, + duration: duration, + end: end, + active: true, + }; + + fasts.push(newFast); + + await new DataService().write(fasts); + } +} + +module.exports = StartFastingAction; diff --git a/action/Stop-Fasting-Action.js b/action/Stop-Fasting-Action.js new file mode 100644 index 0000000..d66c64d --- /dev/null +++ b/action/Stop-Fasting-Action.js @@ -0,0 +1,38 @@ +const InputOutput = require("../input-output/Input-Output"); +const FastFactory = require("../fast/Fast-Factory"); +const DataService = require('../data/Data-Service') +const Application = require('../application/Application') + +class StopFastingAction { + constructor() {} + + async stopFastingAction() { + const inputOutput = new InputOutput(); + + const answer = await inputOutput.question( + "Are you sure you want to stop your fast?\n1.Yes\n2.No\n" + ); + inputOutput.close() + const fasts = await new FastFactory().createBulk(); + if (answer == 1) { + + fasts.forEach((fast) => { + if (fast.active) { + fast.active = false; + fast.end = new Date().toISOString(); + fast.duration = + new Date(fast.end).getHours() - new Date(fast.start).getHours(); + fast.duration = fast.duration.toString(); + } + }); + + await new DataService().write(fasts) + + } else { + console.log("Try again.\n") + } + // new Application() + } +} + +module.exports = StopFastingAction; diff --git a/action/Update-Fasting-Action.js b/action/Update-Fasting-Action.js new file mode 100644 index 0000000..ec8a3c7 --- /dev/null +++ b/action/Update-Fasting-Action.js @@ -0,0 +1,92 @@ +const InputOutput = require("../input-output/Input-Output"); +const FastFactory = require("../fast/Fast-Factory"); +const DataService = require("../data/Data-Service"); + +class UpdateFastingAction { + constructor() { + this.months = [ + "january", + "february", + "march", + "april", + "may", + "june", + "july", + "august", + "september", + "october", + "november", + "december", + ]; + this.fastChoice = ["1", "2", "3", "4"]; + this.fastTypes = [16, 18, 20, 36]; + } + + async updateFastingAction() { + // Enter Month + const inputOutput = new InputOutput(); + const month = await inputOutput.question( + "Enter the month of your fast: \n" + ); + + if (this.months.includes(month.toLowerCase())) { + // Enter Day of the month + const dayOfMonth = await inputOutput.question( + "Enter the day of month: \n" + ); + + if (dayOfMonth > 0 && dayOfMonth <= 31) { + // Enter the time when the fast has started (00:00 - 24:00): + const time = await inputOutput.question( + "Enter the time when the fast has started (00:00 - 24:00): \n" + ); + if (time.includes(":")) { + const hours = time.split(":")[0]; + const minutes = time.split(":")[1]; + if (hours <= 24 && hours >= 0 && minutes <= 59 && minutes >= 0) { + console.log("Legit hours and minutes"); + + // Enter the duration of the fast (16, 18, 20, 36) + const durationChoice = await inputOutput.question( + "1. 16 hour fast\n2. 18 hour fast\n3. 20 hour fast\n4. 36 hour fast\n" + ); + + if (this.fastChoice.includes(durationChoice)) { + // Recalculate end date of the updated fast + + const duration = this.fastTypes[durationChoice - 1]; + + const start = new Date( + `${month} ${dayOfMonth}, ${new Date().getFullYear()} ${hours}:${minutes}` + ); + const end = new Date(); + end.setTime(start.getTime() + duration * 60 * 60 * 1000); + + const fasts = await new FastFactory().createBulk(); + + // Find last active fast and update it. + const fast = fasts.pop(); + + fast.start = start; + fast.end = end; + fast.duration = duration.toString(); + + fasts.push(fast); + await new DataService().write(fasts); + + // console.log("Your current fast has been updated: ", fast) + console.log("Your fast has been updated successfully."); + } + } + } else { + console.log("Invalid input. Start again."); + } + } else { + console.log("Invalid input. Start again."); + } + } else console.log("Invalid input. Start again."); + inputOutput.close(); + } +} + +module.exports = UpdateFastingAction; diff --git a/application/Application.js b/application/Application.js new file mode 100644 index 0000000..1bcae09 --- /dev/null +++ b/application/Application.js @@ -0,0 +1,10 @@ +const UserChoiceHandler = require("../user/User-Choice-Handler"); + +class Application { + constructor() { + new UserChoiceHandler().process() + } +} + + +module.exports = Application \ No newline at end of file diff --git a/core/db.json b/core/db.json new file mode 100644 index 0000000..302b3ae --- /dev/null +++ b/core/db.json @@ -0,0 +1 @@ +[{"start":"2022-03-29T14:41:33.433Z","duration":"2","end":"2022-03-29T16:41:33.433Z","active":false},{"start":"2022-04-11T11:43:35.562Z","duration":"0","end":"2022-04-11T11:44:08.691Z","active":false},{"start":"2022-04-11T11:44:00.000Z","duration":"36","end":"2022-04-12T23:44:00.000Z","active":true}] \ No newline at end of file diff --git a/core/menu.json b/core/menu.json new file mode 100644 index 0000000..70603d6 --- /dev/null +++ b/core/menu.json @@ -0,0 +1,22 @@ +[ + { + "name": "Check Status\n", + "handler": "1" + }, + { + "name": "Start Fasting\n", + "handler": "2" + }, + { + "name": "Stop Fasting\n", + "handler": "3" + }, + { + "name": "Update Fasting\n", + "handler": "4" + }, + { + "name": "List all Fasts\n", + "handler": "5" + } +] diff --git a/data/Data-Service.js b/data/Data-Service.js new file mode 100644 index 0000000..df52b2e --- /dev/null +++ b/data/Data-Service.js @@ -0,0 +1,25 @@ +const fs = require("fs").promises; + +class DataService { + constructor() {} + + async read(filename) { + try { + const res = await fs.readFile(`core/${filename}.json`, "utf-8"); + return JSON.parse(res); + } catch (error) { + console.log("Data-Service -> read(): ", error); + } + } + + async write(newFasts) { + try { + const res = await fs.writeFile("core/db.json", JSON.stringify(newFasts)); + if (res) console.log("Successfully Written to File.\n"); + } catch (error) { + console.err("Data-Service => write(newFasts): ", error); + } + } +} + +module.exports = DataService; diff --git a/fast/Fast-Factory.js b/fast/Fast-Factory.js new file mode 100644 index 0000000..87d5d3a --- /dev/null +++ b/fast/Fast-Factory.js @@ -0,0 +1,25 @@ +const Fast = require("./Fast"); +const DataService = require("../data/Data-Service"); + +class FastFactory { + constructor() {} + + create(start, duration, end, active) { + return new Fast(start, duration, end, active); + } + + async createBulk() { + try { + const res = await new DataService().read("db"); + return res.map((fast) => { + const { start, duration, end, active } = fast; + return this.create(start, duration, end, active); + }); + } catch (error) { + console.error("Fast-Factory => createBulk(): ", error); + return []; + } + } +} + +module.exports = FastFactory; \ No newline at end of file diff --git a/fast/Fast.js b/fast/Fast.js new file mode 100644 index 0000000..9967e22 --- /dev/null +++ b/fast/Fast.js @@ -0,0 +1,10 @@ +class Fast { + constructor(start, duration, end, active) { + this.start = start; + this.duration = duration; + this.end = end; + this.active = active; + } +} + +module.exports = Fast; diff --git a/index.js b/index.js new file mode 100644 index 0000000..8ecebc5 --- /dev/null +++ b/index.js @@ -0,0 +1,8 @@ +const UserChoiceHandler = require("./user/User-Choice-Handler"); +const Application = require('./application/Application'); + +(async () => { + + await new UserChoiceHandler().process(); + +})(); diff --git a/input-output/Answer-Validator.js b/input-output/Answer-Validator.js new file mode 100644 index 0000000..b949343 --- /dev/null +++ b/input-output/Answer-Validator.js @@ -0,0 +1,16 @@ + + +class AnswerValidator { + constructor(){} + + // validate answer according to menu options + async validateMenuChoice(printMenu, answer){ + if(isNaN(answer)) return false + else if(printMenu.includes(answer)) return true + + return false + } +} + + +module.exports = AnswerValidator; \ No newline at end of file diff --git a/input-output/Input-Output.js b/input-output/Input-Output.js new file mode 100644 index 0000000..e335572 --- /dev/null +++ b/input-output/Input-Output.js @@ -0,0 +1,27 @@ +const readline = require("readline"); +const util = require("util"); + +class InputOutput { + constructor() { + this.rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + this.question = util.promisify(this.rl.question).bind(this.rl); + } + + question(question) { + return this.question(question); + } + + close() { + this.rl.close(); + } + + output(content){ + this.rl.output.write(content); + } +} + +module.exports = InputOutput; diff --git a/instructions.md b/instructions.md new file mode 100644 index 0000000..eb67f3e --- /dev/null +++ b/instructions.md @@ -0,0 +1,75 @@ +# Requirements: +1. Check the fast status +2. Start a fast (only if there isn't an active fast) +3. End a fast (only if there is an active fast) +4. Update an active fast (only if there is an active fast) +5. List all fasts. + +## Additional requirements: +- JSON file to act as a database +- Build using OOP concepts +- Branch every new added feature +- Use proper git commit messages, branch names, pull request titles etc +- Add Slobodan as a reviewer + + +## Anticipated App Progression +Start of CLI: +1. Check status of fasts +2. Prompt menus with options in accordance to status check: + - If there is an active fast: + 1. Check Fasting Status + 2. End fasting + 3. Update fasting + 4. List all fasts + - If there isn't an active fast: + 1. Check Fasting Status + 2. Start Fasting + 3. List all fasts +3. Act according to user input: + 1. Check Fast Status: + - Read db.json + - Find active Fast: + - Print Properties + - If search is failed, print that user isn't fasting. + - Print options in CLI + 2. Start Fasting + - Read user input: + - If invalid: + - Post respective message + - Re-print menus with respective options + - Prompt for Start date: + - If input invalid, ask again, stating wrong user input. + - Update new Fast + - Prompt for Fast Type: + - If input invalid, ask again, stating wrong user input. + - Update new Fast + - Calculate end date + - Update new Fast + - Save new state to db.json + - print successful message + - Check Status of fasts (leads to print options) + 3. End an Active Fast + - If input invalid, ask again, stating wrong user input. + - read from db.json + - find active fast + - change status to "false" + - update db.json + - print successful message + - Check Status of fasts (leads to print options) + 4. Update an active Fast + - If input invalid, ask again, stating wrong user input. + - read from db.json + - update: + - Update start + - Update fast type + - calculate new end + - Update end + - print successful message + - Check Status of fasts (leads to print options) + 5. List all Fasts + - If input is invalid, ask again, stating wrong user input. + - read from db.json + - If there are no fasts, print that there are no fasts + - Print array of fasts along with all of their properties each + - Check Status of fasts (leads to print options) diff --git a/menu/Menu-Generator.js b/menu/Menu-Generator.js new file mode 100644 index 0000000..d0a2721 --- /dev/null +++ b/menu/Menu-Generator.js @@ -0,0 +1,40 @@ +const MenuItemFactory = require("./Menu-Item-Factory"); +const FastFactory = require("../fast/Fast-Factory"); + +class MenuGenerator { + constructor() {} + + // getMenus will generate a menu according to current state + // It returns an array of objects + async getMenus() { + try { + let menuObjectsArray = await new MenuItemFactory().createBulk(); + let fastObjectsArray = await new FastFactory().createBulk(); + + let activeFast = [] + + fastObjectsArray.map((fast) => { + if(fast.active) activeFast.push(fast) + }); + + if (activeFast.length > 0) { + menuObjectsArray.splice(1, 1); + } else { + menuObjectsArray.shift(); + menuObjectsArray.splice(1, 1); + menuObjectsArray.splice(1, 1); + } + + return menuObjectsArray.map((item, index) => { + return { + name: `${index + 1}. ${item.name}`, + handler: item.handler, + }; + }); + } catch (error) { + console.log("Menu-Generator getMenus(): ", error); + } + } +} + +module.exports = MenuGenerator; diff --git a/menu/Menu-Item-Factory.js b/menu/Menu-Item-Factory.js new file mode 100644 index 0000000..3d8d102 --- /dev/null +++ b/menu/Menu-Item-Factory.js @@ -0,0 +1,27 @@ +const MenuItem = require('./Menu-Item') +const DataService = require('../data/Data-Service') + + +class MenuItemFactory { + constructor() {} + + create(name, handler) { + return new MenuItem(name, handler); + } + + async createBulk() { + try { + let menus = await new DataService().read("menu"); + return menus.map(item => { + return this.create(item.name, item.handler) + }) + } catch (error) { + console.log("Menu-Item-Factory createBulk(): ", error); + } + } + + +} + + +module.exports = MenuItemFactory \ No newline at end of file diff --git a/menu/Menu-Item.js b/menu/Menu-Item.js new file mode 100644 index 0000000..1427ab3 --- /dev/null +++ b/menu/Menu-Item.js @@ -0,0 +1,9 @@ +class MenuItem { + constructor(name, handler) { + this.name = name + this.handler = handler + } +} + + +module.exports = MenuItem \ No newline at end of file diff --git a/menu/Menu-Print-Console.js b/menu/Menu-Print-Console.js new file mode 100644 index 0000000..d9d9055 --- /dev/null +++ b/menu/Menu-Print-Console.js @@ -0,0 +1,16 @@ +const MenuGenerator = require("./Menu-Generator"); + +class MenuPrintConsole { + constructor() {} + + async menuToPrint() { + let menu = await new MenuGenerator().getMenus(); + let stringMenu = "" + for(let i = 0; i { + // let menuItems = await new MenuItemFactory().createBulk(); + // console.log(menuItems) + + // let menus = await new MenuGenerator().getMenus(); + // console.log(menus) + + // let fasts = await new MenuGenerator().getMenus(); + // console.log(fasts) + + // let menu = await new MenuPrintConsole().menuToPrint(); + // console.log(menu) + + // let data = await new DataService().read() + // console.log(data) + + // let fasts = await new FastFactory().createBulk(); + // console.log(fasts); + + // let answer = await new InputOutput().question("Dali ti odi dobro?\n") + // console.log(answer) + // new InputOutput().close(); + + await new UserChoiceHandler().process(); + +})(); diff --git a/user/User-Choice-Handler.js b/user/User-Choice-Handler.js new file mode 100644 index 0000000..bcbdd34 --- /dev/null +++ b/user/User-Choice-Handler.js @@ -0,0 +1,29 @@ +const MenuPrintConsole = require("../menu/Menu-Print-Console"); +const InputOutput = require("../input-output/Input-Output"); +const AnswerValidator = require("../input-output/Answer-Validator"); +const ActionSwitcher = require("../action/Action-Switcher"); +const MenuGenerator = require("../menu/Menu-Generator") + +class UserChoiceHandler { + constructor() {} + + async process() { + const printMenu = await new MenuPrintConsole().menuToPrint(); + const inputOutput = new InputOutput() + let answer = await inputOutput.question(printMenu); + inputOutput.close(); + let answerValidation = await new AnswerValidator().validateMenuChoice( + printMenu, + answer + ); + + if (answerValidation) { + let menuObjects = await new MenuGenerator().getMenus(); + // console.log(menuObjects) + await new ActionSwitcher().startAction(menuObjects, answer); + } + + } +} + +module.exports = UserChoiceHandler;