From 5a2ff6dd7aa9accdc35906c3ca5b979903043965 Mon Sep 17 00:00:00 2001 From: kenan Date: Thu, 16 Apr 2020 04:18:07 +0800 Subject: [PATCH 1/6] modify db methods --- .env | 6 + .gitignore | 307 +++++++++++++++++++++++++- db/db.js | 55 +++++ db/pool.js | 11 + index.js | 63 +++--- package-lock.json | 119 +++++++++++ package.json | 30 +++ todo.js | 93 ++++++++ utils/prompts.js | 118 ++++++++++ yarn.lock | 535 ++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 1298 insertions(+), 39 deletions(-) create mode 100644 .env create mode 100644 db/db.js create mode 100644 db/pool.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 todo.js create mode 100644 utils/prompts.js create mode 100644 yarn.lock diff --git a/.env b/.env new file mode 100644 index 00000000..747c1b70 --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +# Postgres configuration +PGHOST=localhost +PGUSERNAME=k +PGDATABASE=todo_db +PGPASSWORD="" +PGPORT=5432 diff --git a/.gitignore b/.gitignore index c5582434..acdc4d83 100755 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,305 @@ + +# Created by https://www.gitignore.io/api/osx,node,macos,windows,sublimetext,webstorm+all,visualstudiocode +# Edit at https://www.gitignore.io/?templates=osx,node,macos,windows,sublimetext,webstorm+all,visualstudiocode + +### macOS ### +# General .DS_Store -.svn -*~ -.*.swp +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# rollup.js default build output +dist/ + +# Uncomment the public line if your project uses Gatsby +# https://nextjs.org/blog/next-9-1#public-directory-support +# https://create-react-app.dev/docs/using-the-public-folder/#docsNav +# public + +# Storybook build outputs +.out +.storybook-out + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Temporary folders +tmp/ +temp/ + +### OSX ### +# General + +# Icon must end with two \r + +# Thumbnails + +# Files that might appear in the root of a volume + +# Directories potentially created on remote AFP share + +### SublimeText ### +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history + +### WebStorm+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### WebStorm+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/osx,node,macos,windows,sublimetext,webstorm+all,visualstudiocode \ No newline at end of file diff --git a/db/db.js b/db/db.js new file mode 100644 index 00000000..78d3746c --- /dev/null +++ b/db/db.js @@ -0,0 +1,55 @@ +const { Pool } = require('pg') +require('dotenv').config() + +class DB { + constructor () { + this.pool = new Pool() + this.dbName = process.env.PGDATABASE + this.createDbIfNotExist() + this.createTableIfNotExist() + } + + createDbIfNotExist () { + const text = `SELECT EXISTS + (SELECT datname FROM pg_catalog.pg_database + WHERE datname ='${this.dbName}');` + const func = res => {if (!res.rows[0].exists) this.createDB()} + this.execute(text,func).catch(e=>console.error(e)) + } + + createDB () { + const text = `create database ${this.dbName};` + const func = this.createTableIfNotExist() + this.execute(text,func).catch(e=>console.log(e)) + } + + createTableIfNotExist () { + const text = + `create table todo + ( _id serial + constraint todo_pk + primary key, + title text not null, + created_at timestamptz default current_timestamp, + is_done BOOLEAN default false + );` + this.execute(text).catch(e=> console.error(e)) + } + + async execute (text, func, values) { + const client = await this.pool.connect() + let res + try { + if (values) res = await client.query(text, values) + else res = await client.query(text) + if (func) func(res) + } finally { + client.release() + } + } + +} + +module.exports = DB + + diff --git a/db/pool.js b/db/pool.js new file mode 100644 index 00000000..c4c7405d --- /dev/null +++ b/db/pool.js @@ -0,0 +1,11 @@ +const {Pool , Client } = require('pg') +const pool = new Pool(); + +const handleErr = (err,client) => { + console.error('Unexpected error on idle client', err) + process.exit(-1) +} + +pool.on('error', handleErr) + +module.exports = pool diff --git a/index.js b/index.js index 3907d3b5..a21995e8 100644 --- a/index.js +++ b/index.js @@ -1,36 +1,27 @@ -console.log("works!!", process.argv[2]); - -const pg = require('pg'); - -const configs = { - user: 'akira', - host: '127.0.0.1', - database: 'todo', - port: 5432, -}; - -const client = new pg.Client(configs); - -let queryDoneCallback = (err, result) => { - if (err) { - console.log("query error", err.message); - } else { - console.log("result", result.rows ); - } - client.end(); -}; - -let clientConnectionCallback = (err) => { - - if( err ){ - console.log( "error", err.message ); - } - - let text = "INSERT INTO todo (name) VALUES ($1) RETURNING id"; - - const values = ["hello"]; - - client.query(text, values, queryDoneCallback); -}; - -client.connect(clientConnectionCallback); +const log = console.log; +const pool = require('./db/pool'); +const clear = require('clear'); +const chalk = require('chalk'); +const figlet = require('figlet'); +// const prompt = require('./utils/prompts'); +const DB = require('./db/db') +// const {TodoList, TodoItem} = require('./todo'); + +const TAG_LINE = 'DO IT !' +let todoList; + +const displayWelcomeText = () => { + const options = { font: 'Star Wars', horizontalLayout: 'full' } + log(chalk.blueBright(figlet.textSync(TAG_LINE,options))) +} + +const init = () => { + clear(); + const db = new DB() +} + +const run = async () => { + init(); +} + +run(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..2afe3c42 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,119 @@ +{ + "name": "cli-todo-sql", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "pg": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.0.2.tgz", + "integrity": "sha512-ngOUEDk69kLdH/k/YLT2NRIBcUiPFRcY4l51dviqn79P5qIa5jBIGIFTIGXh4OlT/6gpiCAza5a9uy08izpFQQ==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "0.1.3", + "pg-pool": "^3.1.0", + "pg-protocol": "^1.2.1", + "pg-types": "^2.1.0", + "pgpass": "1.x", + "semver": "4.3.2" + } + }, + "pg-connection-string": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", + "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.1.0.tgz", + "integrity": "sha512-CvxGctDwjZZad6Q7vvhFA4BsYdk26UFIZaFH0XXqHId5uBOc26vco/GFh/laUVIQUpD9IKe/f9/mr/OQHyQ2ZA==" + }, + "pg-protocol": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.2.1.tgz", + "integrity": "sha512-IqZ+VUOqg3yydxSt5NgNKLVK9JgPBuzq4ZbA9GmrmIkQjQAszPT9DLqTtID0mKsLEZB68PU0gjLla561WZ2QkQ==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", + "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", + "requires": { + "split": "^1.0.0" + } + }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + }, + "postgres-date": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.5.tgz", + "integrity": "sha512-pdau6GRPERdAYUQwkBnGKxEfPyhVZXG/JiS44iZWiNdSOWE09N2lUgN6yshuq6fVSon4Pm0VMXd1srUUkLe9iA==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, + "semver": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", + "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..fb39829f --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "cli-todo-sql", + "version": "1.0.0", + "description": "![https://i.giphy.com/media/26ufnwz3wDUli7GU0/giphy.webp](https://i.giphy.com/media/26ufnwz3wDUli7GU0/giphy.webp)", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wdi-sg/cli-todo-sql.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/wdi-sg/cli-todo-sql/issues" + }, + "homepage": "https://github.com/wdi-sg/cli-todo-sql#readme", + "dependencies": { + "chalk": "^4.0.0", + "clear": "^0.1.0", + "dotenv": "^8.2.0", + "figlet": "^1.3.0", + "inquirer": "^7.1.0", + "inquirer-search-checkbox": "^1.0.0", + "moment": "^2.24.0", + "pg": "^8.0.2" + } +} diff --git a/todo.js b/todo.js new file mode 100644 index 00000000..26a941e2 --- /dev/null +++ b/todo.js @@ -0,0 +1,93 @@ +const files = require('./utils/files'); + +class TodoList { + constructor(dataSource) { + this.list = dataSource.getData() || []; + } + + deSerializeJson(rawJson) { + return rawJson.map(json => { + return Object.create(TodoItem.prototype, Object.getOwnPropertyDescriptors(json)); + }); + } + + add(todoItem) { + this.list.push(todoItem); + } + + getItemById(id) { + return this.list.find(item => item.id === id); + } + + markAllIncomplete() { + this.list.forEach(item => { + item.markAsIncomplete(); + }); + } + + remove(id) { + const indexOfItemToRemove = this.list.findIndex(item => item.id === id); + this.list.splice(indexOfItemToRemove, 1); + } + + markAsDone(arrIndex) { + this.list[arrIndex].markAsDone(); + } + + markAsIncomplete(arrIndex) { + this.list[arrIndex].markAsIncomplete(); + } + + getTodoList() { + return this.list; + } + + toJson() { + return this.list; + } + + save() { + files.save(this.list).then(r => console.log("file saved")).catch(e => console.log(e)); + } + +} + +class TodoItem { + + static _numInstances = 0; + + constructor(content) { + this._id = TodoItem._generateId; + this.title = content; + this.createdAt = new Date(); + this.isDone = false; + } + + toggleDone() { + this.isDone ? this.isDone = false : this.isDone = true; + } + + get id() { + return this._id; + } + + + static get _generateId() { + return ++this._numInstances; + } + + markAsDone() { + this.isDone = true; + } + + markAsIncomplete() { + this.isDone = false; + } + +} + +module.exports = { + TodoList, + TodoItem +}; + diff --git a/utils/prompts.js b/utils/prompts.js new file mode 100644 index 00000000..05c943c9 --- /dev/null +++ b/utils/prompts.js @@ -0,0 +1,118 @@ +const inquirer = require('inquirer'); +const files = require('./files'); +const chalk = require('chalk'); +const moment = require('moment'); + +inquirer.registerPrompt('search-checkbox', require('inquirer-search-checkbox')); + +const currentPath = files.getCurrentDir(); + +const displayMenu = () => { + const choices = [ + { + name: 'Add new stuff to do', + value: 'add' + }, + { + name: 'View my list', + value: 'view' + }, + { + name: 'Delete items', + value: 'delete' + } + ]; + + const question = [{ + type: 'list', + name: 'command', + message: 'Select an option:', + choices: choices + }]; + return inquirer.prompt(question); +}; + +const deleteTodo = () => { + const choices = todoListArr.map((todoItem, index) => { + return { + name: chalk.blue(todoItem.title) + "\t" + chalk.white(moment(todoItem.createdAt).fromNow() + + "\t" + chalk.white(todoItem.createdAt)), + value: todoItem.id, + } + }); + const questions = { + type: "checkbox", + name: 'todoList', + message: "Select items to remove:", + choices: choices + }; + + return inquirer.prompt(questions); +}; + +const confirmDefaultDataPath = () => { + const choices = [ + { + name: `Create new storage at ${currentPath}`, + value: 0, + }, + { + name: "Choose a new path:", + value: 1, + }, + ]; + const questions = [{ + type: 'list', + name: 'useDefaultOrNew', + message: `No data found in storage location.`, + choices: choices + }, { + type: 'input', + name: 'newDataPath', + message: "Enter preferred path (e.g ~/mytodo.json): ", + when: (answer) => answer.useDefaultOrNew === choices[1].value + }]; + return inquirer.prompt(questions) +}; + +const addTodo = () => { + const questions = [{ + type: "input", + name: 'newTodo', + message: 'What would you like to do?' + }, { + type: "confirm", + name: "askAgain", + message: 'Add another one?' + }]; + return inquirer.prompt(questions); + +}; + + +const displayTodo = (todoListArr) => { + const choices = todoListArr.map((todoItem, index) => { + return { + name: chalk.blue(todoItem.title) + "\t" + chalk.white(moment(todoItem.createdAt).fromNow() + + "\t" + chalk.white(todoItem.createdAt)), + value: todoItem.id, + checked: todoItem.isDone + } + }); + const questions = { + type: "checkbox", + name: 'todoList', + message: "Select to mark/unmark as done:", + choices: choices + }; + + return inquirer.prompt(questions); +}; + +module.exports = { + displayMenu, + confirmAddNewDB: confirmDefaultDataPath, + displayTodo, + addTodo, + deleteTodo +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..55587e4e --- /dev/null +++ b/yarn.lock @@ -0,0 +1,535 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +ansi-escapes@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + +ansi-escapes@^4.2.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + dependencies: + type-fest "^0.11.0" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +buffer-writer@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" + integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== + +chalk@^2.0.0, chalk@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72" + integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chardet@^0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" + integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +clear@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/clear/-/clear-0.1.0.tgz#b81b1e03437a716984fd7ac97c87d73bdfe7048a" + integrity sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw== + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + dependencies: + restore-cursor "^2.0.0" + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-width@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" + integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +dotenv@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +external-editor@^2.0.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" + integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== + dependencies: + chardet "^0.4.0" + iconv-lite "^0.4.17" + tmp "^0.0.33" + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +figlet@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.3.0.tgz#c49e3d92907ba13bebadc7124f76ba71f1f32ef0" + integrity sha512-f7A8aOJAfyehLJ7lQ6rEA8WJw7kOk3lfWRi5piSjkzbK5YkI5sqO8eiLHz1ehO+DM0QYB85i8VfA6XIGUbU1dg== + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + dependencies: + escape-string-regexp "^1.0.5" + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +fuzzy@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/fuzzy/-/fuzzy-0.1.3.tgz#4c76ec2ff0ac1a36a9dccf9a00df8623078d4ed8" + integrity sha1-THbsL/CsGjap3M+aAN+GIweNTtg= + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +iconv-lite@^0.4.17, iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +inquirer-search-checkbox@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/inquirer-search-checkbox/-/inquirer-search-checkbox-1.0.0.tgz#ead16eaddb08d489aafdb82b3f500c26e2110a94" + integrity sha512-KR6kfe0+h7Zgyrj6GCBVgS4ZmmBhsXofcJoQv6EXZWxK+bpJZV9kOb2AaQ2fbjnH91G0tZWQaS5WteWygzXcmA== + dependencies: + chalk "^2.3.0" + figures "^2.0.0" + fuzzy "^0.1.3" + inquirer "^3.3.0" + +inquirer@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" + integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ== + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.0.4" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + +inquirer@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29" + integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg== + dependencies: + ansi-escapes "^4.2.1" + chalk "^3.0.0" + cli-cursor "^3.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.15" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.5.3" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= + +lodash@^4.17.15, lodash@^4.3.0: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +moment@^2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" + integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + dependencies: + mimic-fn "^1.0.0" + +onetime@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" + integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== + dependencies: + mimic-fn "^2.1.0" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +packet-reader@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" + integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== + +pg-connection-string@0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" + integrity sha1-2hhHsglA5C7hSSvq9l1J2RskXfc= + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-pool@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.1.0.tgz#65f24bbda56cf7368f03ecdfd65e1da571041901" + integrity sha512-CvxGctDwjZZad6Q7vvhFA4BsYdk26UFIZaFH0XXqHId5uBOc26vco/GFh/laUVIQUpD9IKe/f9/mr/OQHyQ2ZA== + +pg-protocol@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.2.1.tgz#60adffeef131418c58f0b20df01ae8f507a95370" + integrity sha512-IqZ+VUOqg3yydxSt5NgNKLVK9JgPBuzq4ZbA9GmrmIkQjQAszPT9DLqTtID0mKsLEZB68PU0gjLla561WZ2QkQ== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.0.2.tgz#883f869f61ab074ded386d305ad3f99056d0073e" + integrity sha512-ngOUEDk69kLdH/k/YLT2NRIBcUiPFRcY4l51dviqn79P5qIa5jBIGIFTIGXh4OlT/6gpiCAza5a9uy08izpFQQ== + dependencies: + buffer-writer "2.0.0" + packet-reader "1.0.0" + pg-connection-string "0.1.3" + pg-pool "^3.1.0" + pg-protocol "^1.2.1" + pg-types "^2.1.0" + pgpass "1.x" + semver "4.3.2" + +pgpass@1.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.2.tgz#2a7bb41b6065b67907e91da1b07c1847c877b306" + integrity sha1-Knu0G2BltnkH6R2hsHwYR8h3swY= + dependencies: + split "^1.0.0" + +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= + +postgres-date@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.5.tgz#710b27de5f27d550f6e80b5d34f7ba189213c2ee" + integrity sha512-pdau6GRPERdAYUQwkBnGKxEfPyhVZXG/JiS44iZWiNdSOWE09N2lUgN6yshuq6fVSon4Pm0VMXd1srUUkLe9iA== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +run-async@^2.2.0, run-async@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" + integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== + dependencies: + is-promise "^2.1.0" + +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74= + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= + +rxjs@^6.5.3: + version "6.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" + integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== + dependencies: + tslib "^1.9.0" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" + integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c= + +signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +split@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" + integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== + dependencies: + through "2" + +string-width@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +through@2, through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tslib@^1.9.0: + version "1.11.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" + integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== + +type-fest@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" + integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== From 2ae0699286b31d662acd4f02a6a564771d096e0a Mon Sep 17 00:00:00 2001 From: kenan Date: Thu, 16 Apr 2020 10:15:24 +0800 Subject: [PATCH 2/6] update db --- db/db.js | 12 ++++++++++-- index.js | 5 +++-- todo.js | 24 +++++++++++++++++++----- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/db/db.js b/db/db.js index 78d3746c..2dc59a80 100644 --- a/db/db.js +++ b/db/db.js @@ -25,7 +25,7 @@ class DB { createTableIfNotExist () { const text = - `create table todo + `create table if not exists todo ( _id serial constraint todo_pk primary key, @@ -36,13 +36,21 @@ class DB { this.execute(text).catch(e=> console.error(e)) } + // todo: this should be moved to dbTodo + async fetchToDoData () { + const text = `select * from todo;` + const res = await this.execute(text) + return res.rows; + } + async execute (text, func, values) { const client = await this.pool.connect() let res try { if (values) res = await client.query(text, values) else res = await client.query(text) - if (func) func(res) + if (func) return func(res) + else return res; } finally { client.release() } diff --git a/index.js b/index.js index a21995e8..c3cd6b4a 100644 --- a/index.js +++ b/index.js @@ -5,9 +5,9 @@ const chalk = require('chalk'); const figlet = require('figlet'); // const prompt = require('./utils/prompts'); const DB = require('./db/db') -// const {TodoList, TodoItem} = require('./todo'); - +const {TodoList, TodoItem} = require('./todo'); const TAG_LINE = 'DO IT !' + let todoList; const displayWelcomeText = () => { @@ -18,6 +18,7 @@ const displayWelcomeText = () => { const init = () => { clear(); const db = new DB() + todoList = new TodoList(db) } const run = async () => { diff --git a/todo.js b/todo.js index 26a941e2..d243d1d2 100644 --- a/todo.js +++ b/todo.js @@ -1,8 +1,22 @@ -const files = require('./utils/files'); class TodoList { + constructor(dataSource) { - this.list = dataSource.getData() || []; + this.dataSource = dataSource + this.setData = this.setData.bind(this); + this.setData() + this.test(); + } + + + setData () { + this.dataSource.fetchToDoData().then(data => { + this.list = data + }) + } + + test() { + console.log(this.list) } deSerializeJson(rawJson) { @@ -46,9 +60,9 @@ class TodoList { return this.list; } - save() { - files.save(this.list).then(r => console.log("file saved")).catch(e => console.log(e)); - } + // save() { + // files.save(this.list).then(r => console.log("file saved")).catch(e => console.log(e)); + // } } From 49b09b2378d695f4a267b1661fa25f816fd07036 Mon Sep 17 00:00:00 2001 From: kenan Date: Thu, 16 Apr 2020 10:27:08 +0800 Subject: [PATCH 3/6] update --- todo.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/todo.js b/todo.js index d243d1d2..069a3e73 100644 --- a/todo.js +++ b/todo.js @@ -3,20 +3,13 @@ class TodoList { constructor(dataSource) { this.dataSource = dataSource - this.setData = this.setData.bind(this); - this.setData() - this.test(); + this.list = this.getData() } - - setData () { - this.dataSource.fetchToDoData().then(data => { - this.list = data - }) - } - - test() { - console.log(this.list) + async getData () { + let data = this.dataSource.fetchToDoData() + .catch(e=>console.error(e)) + return data } deSerializeJson(rawJson) { From 41be1179594d2a22cc4be4c380e82de358f7ae82 Mon Sep 17 00:00:00 2001 From: kenan Date: Thu, 16 Apr 2020 14:23:04 +0800 Subject: [PATCH 4/6] update --- db/pool.js | 11 ----- index.js | 46 ++++++++++++++++--- todo.js | 115 +++++++++++++++++++++++++---------------------- utils/prompts.js | 40 ++++++++--------- 4 files changed, 121 insertions(+), 91 deletions(-) delete mode 100644 db/pool.js diff --git a/db/pool.js b/db/pool.js deleted file mode 100644 index c4c7405d..00000000 --- a/db/pool.js +++ /dev/null @@ -1,11 +0,0 @@ -const {Pool , Client } = require('pg') -const pool = new Pool(); - -const handleErr = (err,client) => { - console.error('Unexpected error on idle client', err) - process.exit(-1) -} - -pool.on('error', handleErr) - -module.exports = pool diff --git a/index.js b/index.js index c3cd6b4a..3e7c80b9 100644 --- a/index.js +++ b/index.js @@ -1,28 +1,62 @@ const log = console.log; -const pool = require('./db/pool'); const clear = require('clear'); const chalk = require('chalk'); const figlet = require('figlet'); -// const prompt = require('./utils/prompts'); +const prompt = require('./utils/prompts'); const DB = require('./db/db') const {TodoList, TodoItem} = require('./todo'); const TAG_LINE = 'DO IT !' let todoList; +const init = async () => { + const db = new DB() + todoList = new TodoList(db) +} + const displayWelcomeText = () => { const options = { font: 'Star Wars', horizontalLayout: 'full' } log(chalk.blueBright(figlet.textSync(TAG_LINE,options))) } -const init = () => { - clear(); - const db = new DB() - todoList = new TodoList(db) +const handleShowTodos = async () => { + const checkedIdsObj = await prompt.listTodos(todoList.getTodoItems()) + +} + +const addNewTodos = async () => { + +} + +const getNewTodoInput = async (todosToAdd) => { + let userInput = await prompt.addTodo(); + todosToAdd.push(userInput.newTodo); + if (userInput.askAgain) { + return await getNewTodoInput(todosToAdd) + } else { + return todosToAdd + } +} + + +const getUserAction = async () => { + let answer = await prompt.displayMenu(); + if (answer.command === 'view') { + await handleShowTodos() + } else if (answer.command === 'add') { + // await showTodo(); + } else if (answer.command === 'delete') { + // await deleteTodo(); + } else if (answer.command === 'quit') { + process.exit(0) + } + await getUserAction(); } const run = async () => { init(); + displayWelcomeText() + await getUserAction() } run(); \ No newline at end of file diff --git a/todo.js b/todo.js index 069a3e73..94137f23 100644 --- a/todo.js +++ b/todo.js @@ -3,59 +3,72 @@ class TodoList { constructor(dataSource) { this.dataSource = dataSource - this.list = this.getData() } - async getData () { - let data = this.dataSource.fetchToDoData() - .catch(e=>console.error(e)) - return data - } - - deSerializeJson(rawJson) { - return rawJson.map(json => { - return Object.create(TodoItem.prototype, Object.getOwnPropertyDescriptors(json)); - }); + async getTodoItems() { + const data = await this._getData(); + return this.deSerializeJson(data) } - add(todoItem) { - this.list.push(todoItem); - } - - getItemById(id) { - return this.list.find(item => item.id === id); - } - - markAllIncomplete() { - this.list.forEach(item => { - item.markAsIncomplete(); - }); - } - - remove(id) { - const indexOfItemToRemove = this.list.findIndex(item => item.id === id); - this.list.splice(indexOfItemToRemove, 1); - } - - markAsDone(arrIndex) { - this.list[arrIndex].markAsDone(); + async _getData () { + return this.dataSource + .fetchToDoData() + .catch(e=>console.error(e)) } - markAsIncomplete(arrIndex) { - this.list[arrIndex].markAsIncomplete(); + async deSerializeJson(arr) { + const todoItems = arr.map(rawObj=> { + return Object.create(TodoItem.prototype, + Object.getOwnPropertyDescriptors(rawObj)) + }) + return todoItems } - getTodoList() { - return this.list; - } + add(todoItem) { - toJson() { - return this.list; } - - // save() { - // files.save(this.list).then(r => console.log("file saved")).catch(e => console.log(e)); + // + // add(todoItem) { + // + // this.list.then( + // + // ) + // } + // + // getItemById(id) { + // return this.list.find(item => item.id === id); + // } + // + // markAllIncomplete() { + // this.list.forEach(item => { + // item.markAsIncomplete(); + // }); + // } + // + // remove(id) { + // const indexOfItemToRemove = this.list.findIndex(item => item.id === id); + // this.list.splice(indexOfItemToRemove, 1); + // } + // + // markAsDone(arrIndex) { + // this.list[arrIndex].markAsDone(); // } + // + // markAsIncomplete(arrIndex) { + // this.list[arrIndex].markAsIncomplete(); + // } + // + // getTodoList() { + // return this.list; + // } + // + // toJson() { + // return this.list; + // } + // + // // save() { + // // files.save(this.list).then(r => console.log("file saved")).catch(e => console.log(e)); + // // } } @@ -64,14 +77,15 @@ class TodoItem { static _numInstances = 0; constructor(content) { - this._id = TodoItem._generateId; + this._id = 0 this.title = content; - this.createdAt = new Date(); - this.isDone = false; + this.created_at = new Date(); + this.is_done = false; } toggleDone() { - this.isDone ? this.isDone = false : this.isDone = true; + this.is_done ? this.is_done = false : this.is_done = true; + return this.is_done; } get id() { @@ -79,18 +93,13 @@ class TodoItem { } - static get _generateId() { - return ++this._numInstances; - } - markAsDone() { - this.isDone = true; + this.is_done = true; } markAsIncomplete() { - this.isDone = false; + this.is_done = false; } - } module.exports = { diff --git a/utils/prompts.js b/utils/prompts.js index 05c943c9..179cbf09 100644 --- a/utils/prompts.js +++ b/utils/prompts.js @@ -1,12 +1,9 @@ const inquirer = require('inquirer'); -const files = require('./files'); const chalk = require('chalk'); const moment = require('moment'); inquirer.registerPrompt('search-checkbox', require('inquirer-search-checkbox')); -const currentPath = files.getCurrentDir(); - const displayMenu = () => { const choices = [ { @@ -20,6 +17,10 @@ const displayMenu = () => { { name: 'Delete items', value: 'delete' + }, + { + name:"Quit", + value: 'quit' } ]; @@ -86,33 +87,30 @@ const addTodo = () => { message: 'Add another one?' }]; return inquirer.prompt(questions); - }; - -const displayTodo = (todoListArr) => { - const choices = todoListArr.map((todoItem, index) => { +const listTodos = async toDoItems => { + const menuChoices = Promise.all( (await toDoItems).map(item => { return { - name: chalk.blue(todoItem.title) + "\t" + chalk.white(moment(todoItem.createdAt).fromNow() + - "\t" + chalk.white(todoItem.createdAt)), - value: todoItem.id, - checked: todoItem.isDone + name: chalk.blue(item.title) + "\t" + + chalk.white(item.created_at), + value: item.id, + checked: item.is_done } - }); - const questions = { - type: "checkbox", - name: 'todoList', - message: "Select to mark/unmark as done:", - choices: choices - }; + })); - return inquirer.prompt(questions); + const questions = [{ + type: 'checkbox', + name: 'todoList', + message: 'Select to mark/unmark as done:', + choices: await menuChoices + }]; + return inquirer.prompt(questions) }; module.exports = { displayMenu, - confirmAddNewDB: confirmDefaultDataPath, - displayTodo, + listTodos, addTodo, deleteTodo }; \ No newline at end of file From 60e2af999eb5249954cb19f8721b0bbb9546cca0 Mon Sep 17 00:00:00 2001 From: kenan Date: Thu, 16 Apr 2020 20:19:26 +0800 Subject: [PATCH 5/6] update structure --- README.md | 225 +++++++++++++++++++++++++---------------------- README1.md | 124 ++++++++++++++++++++++++++ db/db.js | 10 +++ index.js | 6 +- todo.js | 87 +++++++++--------- utils/prompts.js | 4 +- 6 files changed, 306 insertions(+), 150 deletions(-) create mode 100644 README1.md diff --git a/README.md b/README.md index 91dbd3ff..c5401e95 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,143 @@ -# cli-todo-sql +## A simple interactive cli todo app pet project -![https://i.giphy.com/media/26ufnwz3wDUli7GU0/giphy.webp](https://i.giphy.com/media/26ufnwz3wDUli7GU0/giphy.webp) +### Ideas and goals + +#### Goals + +1. To experiment and practice working with promises +2. To pracice on thinking and design in terms of high level abstractions, separation of concerns and ways to decouple concrete implentations from client. + + + +#### General ideas + +#### Main components -Create a commandline todo list app that you can use from your terminal and that will keep track of things you need to do. +- Database object handles only database related oprations + +- ToDoItem abstraction to keep and manipulate states -### Deliverables: +- ToDoList abstraction to perform bulk operations on ToDoItems -#### See the list +- Prompt to process command line IO -``` -node todo.js show -``` - -``` -1. [ ] - go shopping -2. [ ] - feed dog -3. [ ] - swim practice -4. [ ] - code app -5. [ ] - meet gabriel -``` + -#### Add to the list +### Challenges and Issues encountered: -``` -node todo.js add "eat bak kut teh" -``` +##### Who's responsibility it is to save a todoItem into database? + +The initial design was to have a ```save(db)``` method on TodoItem object, such that the TodoItem would use the the 'db' instance passed into it, and call the db's save method and update the corresponding record. -``` -1. [ ] - go shopping -2. [ ] - feed dog -3. [ ] - swim practice -4. [ ] - code app -5. [ ] - meet gabriel -6. [ ] - eat bak kut teh -``` +However, it became apparent that this had been a bad idea -#### Getting Started: -Create and install some things: -``` -npm init -npm install pg -``` +```javascript +Index.js +========= +const init = async () => { + const db = new DB() + todoList = new TodoList(db) +} -Create the database itself. +ToDoList.js +=========== +class TodoList { +constructor(dataSource) { + this.dataSource = dataSource + } -First, drop into `psql`: +async getTodoItems() { + const data = await this._getData(); + return this.deSerializeJson(data) +} + +async _getData () { + return this.dataSource + .fetchToDoData() + .catch(e=>console.error(e)) + } + +async deSerializeJson(arr) { + const todoItems = arr.map(rawObj=> { + return Object.create( + TodoItem.prototype, + Object.getOwnPropertyDescriptors(rawObj) + )}) + return todoItems + } +} -Create the DB: -``` -CREATE DATABASE todo -``` +ToDoItem +======== +class TodoItem { + static _numInstances = 0; + /* + constructor(content,db){ + this.db = db; + } + */ + constructor(content) { + this._id = 0 + this.title = content; + this.created_at = new Date(); + this.is_done = false; + } + +/* + setDb(db) { + this.db = db; + } +*/ + update([fields, newValues]) { + this.db.update(fields,newValues) + } -Create a table: -``` -CREATE TABLE IF NOT EXISTS items ( - id SERIAL PRIMARY KEY, - name text -); + toJSON +} ``` -Now you can start coding the `index.js` file. The one provided has some boilerplate code for you to start. - -#### Further: - -#### Mark as done +##### The obvious problem -``` -node todo.js done 4 -``` +The trouble comes with the db instance sitted inside TodoItem, if it is going to call on the db instance, the db instance has to be passed in somehow. But where to pass that in? How about the constructor? +```javascript + constructor(content,db){ + this._id = 0 + this.title = content; + this.created_at = new Date(); + this.is_done = false; + this.db = db; + } ``` -1. [ ] - go shopping -2. [ ] - feed dog -3. [ ] - swim practice -4. [x] - code app -5. [ ] - meet gabriel -6. [ ] - eat bak kut teh -``` -Note that you may need to change your table to allow for "completion" of an item. - -#### Further: -Add a column named `created_at` with data type date and display the date the item was added. Look ahead in the gitbook for how to format the date type with `pg` library [https://wdi-sg.github.io/gitbook-2019/04-databases/postgres/sql-working.html](https://wdi-sg.github.io/gitbook-2019/04-databases/postgres/sql-working.html) - -#### Further: -Add the ability to archive an item. When you archive an item it means it will no longer show in the list. - -#### Further: -Add a column named `updated_at` with data type date and display the date the item was marked completed. - -#### Further: -`node todo.js stats complete-time` give the average completion time of all items - -#### Further: -In order to have data in your system for items that have been created and completed, write a separate file that inserts data into your database so that you can test that functionality. - -Call it `test.js`. Inside create todo items and insert them into the database with the dates that will allow you to test your functionality. - -#### Further: -`node todo.js stats add-time` give the average amount of items added per day. - -#### Further: -`node todo.js stats best-worst` gives the item that was completed the fastest and the item that was completed the slowest. - -#### Further: -`node todo.js between 1/2/20 1/3/20` gives all the items added between these dates - -#### Further: -Add the same as above but for items completed. - -#### Further: -Get all the items completed between two dates and sort them by the time it took to complete. You can say ascending or descending order. - -Ex. `node todo.js stats between 1/2/20 1/3/20 complete-time asc` - -#### Further: -Use an ascii art generator to add style to your app: [http://patorjk.com/software/taag](http://patorjk.com/software/taag) - here you could use the ES6 string interpolation syntax. -#### Further: -There are frameworks to make a completely dynamic command line app. Use a framework to make the app interactive: [https://medium.freecodecamp.org/writing-command-line-applications-in-nodejs-2cf8327eee2](https://medium.freecodecamp.org/writing-command-line-applications-in-nodejs-2cf8327eee2) +This would mean that whenever user adds a new TodoItem, the db instance has to be passed in as together, like this. -##### Notes: +```javascript +db = new DB() +const newTodoContent = getUserInput() +const todoItem = new TodoItem(newTodoContent, db ) +``` + +Then how about using a setter instead? + +```javascript +setDb(db) { + this.db = db; +} +const todoItem = new TodoItem(newTodoContent, db ) +todoList.push(todoItem); +todoList.map(item=>item.setDb(db)) +``` + +That wasnt' much different. + + + +### Further work + +- separate data interface from its concrete implementation, to allow applicaiton to use any datasource without having to change the client code +- refactor and separate business logic from interface, to allow any type of interface to plug in without having to change business logic. +- Learn rxjs and obsservables + +1 -If you are working with dates, try the momentjs npm library. diff --git a/README1.md b/README1.md new file mode 100644 index 00000000..91dbd3ff --- /dev/null +++ b/README1.md @@ -0,0 +1,124 @@ +# cli-todo-sql + +![https://i.giphy.com/media/26ufnwz3wDUli7GU0/giphy.webp](https://i.giphy.com/media/26ufnwz3wDUli7GU0/giphy.webp) + +Create a commandline todo list app that you can use from your terminal and that will keep track of things you need to do. + +### Deliverables: + +#### See the list + +``` +node todo.js show +``` + +``` +1. [ ] - go shopping +2. [ ] - feed dog +3. [ ] - swim practice +4. [ ] - code app +5. [ ] - meet gabriel +``` + +#### Add to the list + +``` +node todo.js add "eat bak kut teh" +``` + +``` +1. [ ] - go shopping +2. [ ] - feed dog +3. [ ] - swim practice +4. [ ] - code app +5. [ ] - meet gabriel +6. [ ] - eat bak kut teh +``` + +#### Getting Started: +Create and install some things: +``` +npm init +npm install pg +``` + +Create the database itself. + +First, drop into `psql`: + +Create the DB: +``` +CREATE DATABASE todo +``` + +Create a table: +``` +CREATE TABLE IF NOT EXISTS items ( + id SERIAL PRIMARY KEY, + name text +); +``` + +Now you can start coding the `index.js` file. The one provided has some boilerplate code for you to start. + +#### Further: + +#### Mark as done + +``` +node todo.js done 4 +``` + +``` +1. [ ] - go shopping +2. [ ] - feed dog +3. [ ] - swim practice +4. [x] - code app +5. [ ] - meet gabriel +6. [ ] - eat bak kut teh +``` +Note that you may need to change your table to allow for "completion" of an item. + +#### Further: +Add a column named `created_at` with data type date and display the date the item was added. Look ahead in the gitbook for how to format the date type with `pg` library [https://wdi-sg.github.io/gitbook-2019/04-databases/postgres/sql-working.html](https://wdi-sg.github.io/gitbook-2019/04-databases/postgres/sql-working.html) + +#### Further: +Add the ability to archive an item. When you archive an item it means it will no longer show in the list. + +#### Further: +Add a column named `updated_at` with data type date and display the date the item was marked completed. + +#### Further: +`node todo.js stats complete-time` give the average completion time of all items + +#### Further: +In order to have data in your system for items that have been created and completed, write a separate file that inserts data into your database so that you can test that functionality. + +Call it `test.js`. Inside create todo items and insert them into the database with the dates that will allow you to test your functionality. + +#### Further: +`node todo.js stats add-time` give the average amount of items added per day. + +#### Further: +`node todo.js stats best-worst` gives the item that was completed the fastest and the item that was completed the slowest. + +#### Further: +`node todo.js between 1/2/20 1/3/20` gives all the items added between these dates + +#### Further: +Add the same as above but for items completed. + +#### Further: +Get all the items completed between two dates and sort them by the time it took to complete. You can say ascending or descending order. + +Ex. `node todo.js stats between 1/2/20 1/3/20 complete-time asc` + +#### Further: +Use an ascii art generator to add style to your app: [http://patorjk.com/software/taag](http://patorjk.com/software/taag) - here you could use the ES6 string interpolation syntax. + +#### Further: +There are frameworks to make a completely dynamic command line app. Use a framework to make the app interactive: [https://medium.freecodecamp.org/writing-command-line-applications-in-nodejs-2cf8327eee2](https://medium.freecodecamp.org/writing-command-line-applications-in-nodejs-2cf8327eee2) + +##### Notes: + +If you are working with dates, try the momentjs npm library. diff --git a/db/db.js b/db/db.js index 2dc59a80..60d3a688 100644 --- a/db/db.js +++ b/db/db.js @@ -43,6 +43,16 @@ class DB { return res.rows; } + async updateAll(objArr) { + console.log(objArr) + //this.update(objArr[0]) + } + + update(obj) { + console.log(Object.getOwnPropertyNames(obj)) + const text = `update todo set ` + } + async execute (text, func, values) { const client = await this.pool.connect() let res diff --git a/index.js b/index.js index 3e7c80b9..e40d5fb2 100644 --- a/index.js +++ b/index.js @@ -11,7 +11,8 @@ let todoList; const init = async () => { const db = new DB() - todoList = new TodoList(db) + const data = await db.fetchToDoData() + todoList = new TodoList(data) } const displayWelcomeText = () => { @@ -21,7 +22,8 @@ const displayWelcomeText = () => { const handleShowTodos = async () => { const checkedIdsObj = await prompt.listTodos(todoList.getTodoItems()) - + const checkedIds = await checkedIdsObj.todoList; + todoList.setChecked(checkedIds) } const addNewTodos = async () => { diff --git a/todo.js b/todo.js index 94137f23..c12e169e 100644 --- a/todo.js +++ b/todo.js @@ -1,33 +1,39 @@ - class TodoList { - constructor(dataSource) { - this.dataSource = dataSource + constructor (data) { + this.data = data + this.todoItems = this.deSerializeJson(data) + } + + async getTodoItems () { + return this.todoItems } - async getTodoItems() { - const data = await this._getData(); - return this.deSerializeJson(data) + async setChecked (checkedIds) { + this.markAllIncomplete(); + (await this.todoItems) + .filter(item => checkedIds.includes(item._id)) + .forEach(item => item.markAsDone()) } - async _getData () { - return this.dataSource - .fetchToDoData() - .catch(e=>console.error(e)) + getItemById (id) { + return this.todoItems.find(item => item.id === id) } - async deSerializeJson(arr) { - const todoItems = arr.map(rawObj=> { + async markAllIncomplete () { + (await this.todoItems).forEach(item => { + item.markAsIncomplete() + }) + } + + async deSerializeJson (arr) { + const todoItems = arr.map(rawObj => { return Object.create(TodoItem.prototype, Object.getOwnPropertyDescriptors(rawObj)) }) return todoItems } - add(todoItem) { - - } - // // add(todoItem) { // // this.list.then( @@ -35,15 +41,7 @@ class TodoList { // ) // } // - // getItemById(id) { - // return this.list.find(item => item.id === id); - // } - // - // markAllIncomplete() { - // this.list.forEach(item => { - // item.markAsIncomplete(); - // }); - // } + // // remove(id) { // const indexOfItemToRemove = this.list.findIndex(item => item.id === id); @@ -67,43 +65,44 @@ class TodoList { // } // // // save() { - // // files.save(this.list).then(r => console.log("file saved")).catch(e => console.log(e)); + // // files.save(this.list) + // .then(r => console.log("file saved")) + // .catch(e => console.log(e)); // // } } class TodoItem { + static _numInstances = 0 - static _numInstances = 0; - - constructor(content) { - this._id = 0 - this.title = content; - this.created_at = new Date(); - this.is_done = false; + constructor (content) { + this._id = -1 + this.title = content + this.created_at = new Date() + this.is_done = false } - toggleDone() { - this.is_done ? this.is_done = false : this.is_done = true; - return this.is_done; + get id () { + return this._id } - get id() { - return this._id; + toggleDone () { + this.is_done ? this.is_done = false : this.is_done = true + return this.is_done } - - markAsDone() { - this.is_done = true; + markAsDone () { + this.is_done = true } - markAsIncomplete() { - this.is_done = false; + markAsIncomplete () { + this.is_done = false } + } module.exports = { TodoList, TodoItem -}; +} diff --git a/utils/prompts.js b/utils/prompts.js index 179cbf09..20f3c507 100644 --- a/utils/prompts.js +++ b/utils/prompts.js @@ -90,7 +90,9 @@ const addTodo = () => { }; const listTodos = async toDoItems => { - const menuChoices = Promise.all( (await toDoItems).map(item => { + const menuChoices = Promise + .all( (await toDoItems) + .map(item => { return { name: chalk.blue(item.title) + "\t" + chalk.white(item.created_at), From 0b584d2747b8db51edaac8fc7848c92207842464 Mon Sep 17 00:00:00 2001 From: kenan Date: Fri, 17 Apr 2020 03:37:15 +0800 Subject: [PATCH 6/6] update readme --- README.md | 61 +++++++++++++++++++++++++++++++++++------------- db/db.js | 25 +++++++++++++------- index.js | 7 ++++-- utils/prompts.js | 5 ++-- 4 files changed, 70 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index c5401e95..29d57516 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ ### Ideas and goals -#### Goals +#### Personal learning goals out of the project -1. To experiment and practice working with promises -2. To pracice on thinking and design in terms of high level abstractions, separation of concerns and ways to decouple concrete implentations from client. +1. To experiment and practice working with promises and asynchronous design in general. +2. To pracice on thinking and design in terms of high level abstractions, to exercise separation of concerns and experiment on ways to decouple concrete implentations from client code @@ -13,23 +13,18 @@ #### Main components -- Database object handles only database related oprations - +- Database fascade handles only database related oprations - ToDoItem abstraction to keep and manipulate states - - ToDoList abstraction to perform bulk operations on ToDoItems - -- Prompt to process command line IO - - +- Prompt utitlity to process command line IO ### Challenges and Issues encountered: ##### Who's responsibility it is to save a todoItem into database? -The initial design was to have a ```save(db)``` method on TodoItem object, such that the TodoItem would use the the 'db' instance passed into it, and call the db's save method and update the corresponding record. +The initial design was to have a ```save(db)``` method on TodoItem object, such that the TodoItem would use the the 'db' instance passed into it, and call the db's save method ,which updates the corresponding database record. -However, it became apparent that this had been a bad idea +However, it quickly became apparent that this had been a bad idea. ```javascript Index.js @@ -98,7 +93,7 @@ class TodoItem { ##### The obvious problem -The trouble comes with the db instance sitted inside TodoItem, if it is going to call on the db instance, the db instance has to be passed in somehow. But where to pass that in? How about the constructor? +The trouble starts from the db instance sitted inside TodoItem. If the todoItem was to call the db instance, the db instance has to be passed into an todoItem instance in the first place, somehow. But when it should be passed in? And How? How about the constructor? ```javascript constructor(content,db){ @@ -118,7 +113,7 @@ const newTodoContent = getUserInput() const todoItem = new TodoItem(newTodoContent, db ) ``` -Then how about using a setter instead? +What about using a setter? ```javascript setDb(db) { @@ -131,13 +126,47 @@ todoList.map(item=>item.setDb(db)) That wasnt' much different. +#### The {not perfect} fix and the single responsibility principle + + An abstraction model object does one thing and only one thing - maintain and updates its own states. Therefore, it should not have the knowledge of persistence , neither the associated operations. + +The database facade does one and only one thing - controls the pesistence of states. + +It is, naturally the databases's , and not the model's responsibility, to perform actual saving, updating, deteting, archiving etc. states persistence operations + +```javascript +const init = async () => { + db = new DB() + const data = await db.fetchData() + todoList = new TodoList(data) +} +``` + +```javascript +const handleShowTodos = async () => { + const checkedIdsObj = await prompt.listTodos(todoList.getTodoItems()) + const checkedIds = await checkedIdsObj.todoList; + await todoList.setChecked(checkedIds) + await db.updateAll(todoList.getTodoItems()) + .catch(e=>console.log(e)) +} +``` + +The TodoList object takes in an array of json objects with each reprentating a todoItem, in the form of a flattened promise. + +Neither the TodoList nor the TodoItem has knowledge of the db fascade. + +The db object takes in an array of arbitary objects, and perform crud operations based on the object's own properties. + ### Further work - separate data interface from its concrete implementation, to allow applicaiton to use any datasource without having to change the client code +- To work on implementing an acual local storage adapter class to plug into the existing db class without having to modify client code. +- To allow crud methods of db object taking in options to modify how different objs should be parsed and fields to be used. - refactor and separate business logic from interface, to allow any type of interface to plug in without having to change business logic. -- Learn rxjs and obsservables +- Learn rxjs and obsservables and refactor existing code. + -1 diff --git a/db/db.js b/db/db.js index 60d3a688..6c1c72b1 100644 --- a/db/db.js +++ b/db/db.js @@ -14,13 +14,13 @@ class DB { (SELECT datname FROM pg_catalog.pg_database WHERE datname ='${this.dbName}');` const func = res => {if (!res.rows[0].exists) this.createDB()} - this.execute(text,func).catch(e=>console.error(e)) + this.execute(text, undefined, func).catch(e=>console.error(e)) } createDB () { const text = `create database ${this.dbName};` const func = this.createTableIfNotExist() - this.execute(text,func).catch(e=>console.log(e)) + this.execute(text, undefined, func).catch(e=>console.log(e)) } createTableIfNotExist () { @@ -44,16 +44,25 @@ class DB { } async updateAll(objArr) { - console.log(objArr) - //this.update(objArr[0]) + let objs = await objArr + objs = objs.filter(item=>item['_id']>=0) + const actions = objs.map(this.update.bind(this)) + const res = Promise.all(actions) + return res; } - update(obj) { - console.log(Object.getOwnPropertyNames(obj)) - const text = `update todo set ` + async update(obj) { + const id = obj['id'] || obj['_id'] + delete obj.id && delete obj._id + const fields = Object.keys(obj) + const vals = Object.values(obj) + const placeHolers = vals.map((v,i)=>`\$${i+1}`) + let text = `update todo set (${fields.join(',')}) = (${placeHolers.join()}) `; + text+= `where _id=${id}` + return this.execute(text,vals) } - async execute (text, func, values) { + async execute (text, values, func) { const client = await this.pool.connect() let res try { diff --git a/index.js b/index.js index e40d5fb2..a5e4b9fd 100644 --- a/index.js +++ b/index.js @@ -8,9 +8,10 @@ const {TodoList, TodoItem} = require('./todo'); const TAG_LINE = 'DO IT !' let todoList; +let db; const init = async () => { - const db = new DB() + db = new DB() const data = await db.fetchToDoData() todoList = new TodoList(data) } @@ -23,7 +24,9 @@ const displayWelcomeText = () => { const handleShowTodos = async () => { const checkedIdsObj = await prompt.listTodos(todoList.getTodoItems()) const checkedIds = await checkedIdsObj.todoList; - todoList.setChecked(checkedIds) + await todoList.setChecked(checkedIds) + await db.updateAll(todoList.getTodoItems()) + .catch(e=>console.log(e)) } const addNewTodos = async () => { diff --git a/utils/prompts.js b/utils/prompts.js index 20f3c507..1a31292a 100644 --- a/utils/prompts.js +++ b/utils/prompts.js @@ -94,8 +94,9 @@ const listTodos = async toDoItems => { .all( (await toDoItems) .map(item => { return { - name: chalk.blue(item.title) + "\t" + - chalk.white(item.created_at), + name: chalk.blue(item.title) + "\t" + + chalk.white(moment(item.created_at).fromNow() + "\t" + + chalk.white(item.created_at)), value: item.id, checked: item.is_done }