diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..272cbd70 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/close_as_a_feature.yml b/.github/workflows/close_as_a_feature.yml new file mode 100644 index 00000000..6eca22b3 --- /dev/null +++ b/.github/workflows/close_as_a_feature.yml @@ -0,0 +1,14 @@ +name: Close as a feature +on: + issues: + types: [labeled] + +jobs: + build: + permissions: + issues: write + runs-on: ubuntu-latest + steps: + - name: Close Issue + uses: peter-evans/close-issue@v1 + if: contains(github.event.issue.labels.*.name, 'feature') \ No newline at end of file diff --git a/.github/workflows/code_cov.yml b/.github/workflows/code_cov.yml new file mode 100644 index 00000000..5c6fd4b9 --- /dev/null +++ b/.github/workflows/code_cov.yml @@ -0,0 +1,35 @@ +name: Running Code Coverage +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Install Python 3.9.7 + uses: actions/setup-python@v1 + with: + python-version: 3.9.7 + - name: Install dependencies + run: | + pip install pytest-cov + pip install -r requirements.txt + echo requirements installed + - name: Run the tests + run: | + cd src + python app.py & + sleep 5 + cd ../test + pytest --cov=./ + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/code_formatter.yml b/.github/workflows/code_formatter.yml new file mode 100644 index 00000000..08986fd5 --- /dev/null +++ b/.github/workflows/code_formatter.yml @@ -0,0 +1,29 @@ +name: Format +on: + pull_request: + branches: [main] + paths: + - '**.py' +jobs: + autoyapf: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + with: + ref: ${{ github.head_ref }} + - name: autoyapf + id: autoyapf + uses: mritunjaysharma394/autoyapf@v2 + with: + args: --style pep8 --recursive --in-place . + - name: Check for modified files + id: git-check + run: echo ::set-output name=modified::$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi) + - name: Push changes + if: steps.git-check.outputs.modified == 'true' + run: | + git config --global user.name 'PrakruthiSomashekar' + git config --global user.email 'prakruthi1796@gmail.com' + git remote set-url origin https://x-access-token:${{ secrets.GIT_TOKEN }}@github.com/${{ deekay2310/Simplii }} + git commit -am "Automated autoyapf fixes" + git push \ No newline at end of file diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index e559e1cb..ac6e7d71 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -33,4 +33,8 @@ jobs: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | + cd src + python app.py & + sleep 5 + cd .. pytest diff --git a/.github/workflows/style_checker.yml b/.github/workflows/style_checker.yml new file mode 100644 index 00000000..69d7bd9d --- /dev/null +++ b/.github/workflows/style_checker.yml @@ -0,0 +1,7 @@ +on: push +name: Python Style Checker +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: andymckay/pycodestyle-action@0.1.3 \ No newline at end of file diff --git a/.github/workflows/syntax_checker.yml b/.github/workflows/syntax_checker.yml new file mode 100644 index 00000000..c6cb4a5b --- /dev/null +++ b/.github/workflows/syntax_checker.yml @@ -0,0 +1,8 @@ +on: push +name: Lint Python +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: cclauss/Find-Python-syntax-errors-action@master \ No newline at end of file diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml new file mode 100644 index 00000000..89dcc125 --- /dev/null +++ b/.github/workflows/unit_test.yml @@ -0,0 +1,26 @@ +name: Run Tests On Push +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Python 3.9.7 + uses: actions/setup-python@v1 + with: + python-version: 3.9.7 + - name: Install dependencies + run: | + pip install -r requirements.txt + echo requirements installed + - name: Run tests + run: | + cd test + python -m unittest test \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 07ac783f..babcfb0d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ name: Testing language: python env: - - CODECOV_TOKEN='13d7f7c4-84e7-48ab-b951-8257e4432983' + - CODECOV_TOKEN='18a8beea-4407-498f-8b15-990517519ac2' os: - linux python: @@ -16,6 +16,6 @@ install: - pip install -r requirements.txt - pip install requests script: - - flask run & + - flask run - sleep 5 - - python -m unittest + - python -m unittest test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3b88bc00..b927c083 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,3 +53,15 @@ Maintain the projects quality Fix problems that are important to users Enable a sustainable system for the projects maintainers to review contributions + +## Tips to Extend + +Check the Projects tab for TO-DO list and pick the feature you find interesting to work on. + +Create a branch and implement the feature in Python and test it locally. + +Write corresponding test cases to ensure it is not breaking the existing system. + +Create pull request and request for the code review. Once the request is approved, merge to main. + +Any suggestions to improve the application is appreciated. Please request a feature/report bugs through issues section. diff --git a/Function Description.md b/Function Description.md new file mode 100644 index 00000000..027d57fb --- /dev/null +++ b/Function Description.md @@ -0,0 +1,35 @@ +refresh_data() +- This function loads all the data required to display the home page from file system + +getnewTaskID() +- This function is used to generate a new unique TaskID. All our TaskIDs are alphanumeric strings of length 5. Eg. “AAAA2”, “2342V”. No special characters are used for obtaining TaskIDs +- This function randomly produces a possible TaskID and checks if it is already in use. If the generated TaskID is in use, then we keep on generating random TaskIDs until we find one that is not in use + +homePage() +- This function renders the home page + +login() +- This function is used for login by the user +- Using userID, passoword is validated and the user is directed to index + +send_email() +- This function is used to send an email to user's email ID containing all tasks +- The email is sent from simplii.reminder@gmail.com + +logout() +- This function is used for login by the user +- Using userID, passoword is validated and the user is directed to index + +signUp() +- This function is used for registering new users +- Details of new users are stored in the database and the user is automatically logged in + +delete_task_byID() +- Deletes a task from the to-do list given its ID. This API moves a task from the TODO list to the COMPLETED list + +add_new_task() +- User fills details of tasks on a form which gets added to the database +- User can also see their tasks and their details on index page + +delete_task_byID() +- This function deletes the tasks that are already stored by the user diff --git a/README.md b/README.md index 82770df3..7c94c5c8 100644 --- a/README.md +++ b/README.md @@ -3,28 +3,88 @@ [![DOI](https://zenodo.org/badge/404911045.svg)](https://zenodo.org/badge/latestdoi/404911045) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -![GitHub issues](https://img.shields.io/github/issues-raw/ivbhatt/Simplii) -![Github closes issues](https://img.shields.io/github/issues-closed-raw/ivbhatt/Simplii) -![Github pull requests](https://img.shields.io/github/issues-pr/ivbhatt/Simplii) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Build Status](https://app.travis-ci.com/UnnatiPrema/Simplii.svg?branch=main)](https://app.travis-ci.com/UnnatiPrema/Simplii) +[![codecov](https://codecov.io/gh/deekay2310/Simplii/branch/main/graph/badge.svg?token=9ZXVRIPHLO)](https://codecov.io/gh/deekay2310/Simplii) +![github workflow](https://github.com/deekay2310/Simplii/actions/workflows/unit_test.yml/badge.svg) +![github workflow](https://github.com/deekay2310/Simplii/actions/workflows/style_checker.yml/badge.svg) +![github workflow](https://github.com/deekay2310/Simplii/actions/workflows/syntax_checker.yml/badge.svg) +![github workflow](https://github.com/deekay2310/Simplii/actions/workflows/close_as_a_feature.yml/badge.svg) + + +Simplii forks + + +Simplii stars + + +Simplii issues + + +Simplii issues closed + + +cheapBuy pull-requests + + + + + + + + + + + + + + + + + + +
+ +

+ Report Bug + / + Request Feature +

+ ## Why schedule tasks on this app? Do you feel that your basic task scheduling apps should also track your mood and suggest a task accordingly? We hear it. We are trying to build a tool that will enable fast execution of our tasks under any mood. `Simplii` parameterizes emotions, deadlines, task difficulty and moods to suggest a task to be done keeping you at your productive best. +

+ +

+ + ## Visual Walkthrough: https://user-images.githubusercontent.com/17769434/135383467-e197d4ca-74f8-4737-a979-4390026cb71d.mp4 + +https://user-images.githubusercontent.com/89432698/140456563-6b1126c5-7830-4fc5-bcac-a9463b96c135.mp4 + + + ## Built with: - - - - - - + + + + + + + + + + +
PythonJavaScriptHTML5CSS3BootstrapjQueryFlaskMongoDB
@@ -59,7 +119,7 @@ ER Diagram | Application Flowchart - ### How to Run: **To run the application locally:** - - Clone [this (Simplii) github repo] (https://github.com/ivbhatt/Simplii) + - Clone [this (Simplii) github repo] (https://github.com/deekay2310/Simplii) - Run `flask run` - Site will be hosted at: `http://127.0.0.1:5000/` @@ -68,15 +128,15 @@ ER Diagram | Application Flowchart
- - - - - + + + + +

Apurva Sonavane


Arpitha Vijayakumar

Ishan Bhatt


Krishika Shivnani


Unnati Nadupalli


Dev Kumar


Prakruthi Somashekar

Radhika Raman


Rohan Sinha


Sunidhi Hegde

## Support -For any queries and help, please reach out to us at: krishika510@gmail.com +For any queries and help, please reach out to us at: simplii.reminder@gmail.com diff --git a/app.py b/app.py deleted file mode 100644 index e01061f0..00000000 --- a/app.py +++ /dev/null @@ -1,165 +0,0 @@ -"""Importing all the standard Python modules.""" -import os -import random -import csv -import json -import string - -from flask import Flask -from flask import render_template -from flask import request, redirect - - -app = Flask(__name__) - -"""Global constant to store directory path""" -TODO_TASKS_PATH = os.path.join("static", "tasks", "todo") -COMPLETED_TASKS_PATH = os.path.join("static", "tasks", "completed") - -"""List declaration for storing the quotes and authors """ -ALL_QUOTES = [] -ALL_AUTHORS = [] - -"""Loading our code dataset in memory""" -with open(os.path.join("static", "quotes.csv"), "r", encoding="utf-8") as csv_file: - reader = csv.DictReader(csv_file) - - for row in reader: - ALL_QUOTES.append(row["Quote"]) - ALL_AUTHORS.append(row["Author"]) - - -def refresh_data(): - """This function loads all the data required to display the home page from file-system.""" - - ##### Load user information from file - with open(os.path.join("static", "user_information.json"), "r", encoding="utf-8") as json_file: - json_data = json.load(json_file) - - initialized = json_data["initialized"] - name = json_data["name"] - email_id = json_data["email_id"] - email_notifications = json_data["email_notifications"] - - ##### Load a random quote - index = random.randint(0, len(ALL_AUTHORS)-1) - - ##### Load the todo task list - tasks = {} - for file in os.listdir(TODO_TASKS_PATH): - if ".json" in file: - with open(os.path.join(TODO_TASKS_PATH, file), "r", encoding="utf-8") as json_file: - json_data = json.load(json_file) - - tasks[json_data["id"]] = json_data - - ##### Sorting the tasks - tasks = {key:value for (key,value) in sorted(tasks.items(), key = lambda item: item[1]["deadline"])} - ##### - - ##### Compile the data and send as json! - data = { - "name_block" : {"initialized":initialized, "name": name, "email_id": email_id, - "email_notifications": email_notifications}, - "quote_block" : {"quote": ALL_QUOTES[index], "author": ALL_AUTHORS[index]}, - "task-list-block" : {"task-list": tasks} - } - ##### - return data - -def getnewTaskID(): - """Gets a task to be shown to the user.""" - todo_ids = [f[0:6] for f in os.listdir(TODO_TASKS_PATH)] - completed_ids = [f[0:6] for f in os.listdir(COMPLETED_TASKS_PATH)] - while True: - possible_id = ''.join(random.choices(string.ascii_uppercase + string.digits, k = 5)) - - if possible_id not in todo_ids and possible_id not in completed_ids: - return possible_id - - - -@app.route("/") -def homePage(): - """This function renders the home page.""" - return render_template("index.html", data=refresh_data()) - - -@app.route("/update_user_info", methods = ["POST"]) -def update_user_information(): - """Updates user information into JSON.""" - user_information = request.form - new_info = {} - - new_info["name"] = user_information["name"] - new_info["email_id"] = user_information["email"] - new_info["initialized"] = "yes" - new_info["email_notifications"] = user_information["emailChoose"] - - with open(os.path.join("static", "user_information.json"), "w", encoding="utf-8") as json_file: - json.dump(new_info, json_file) - - return redirect("/") - - - -@app.route("/add_task", methods = ["POST"]) -def add_new_task(): - """Add a new task to the JSON.""" - form_data = request.values - - new_id = getnewTaskID() - - new_task_information = {} - - new_task_information["id"] = new_id - new_task_information["task_name"] = form_data["taskName"] - new_task_information["deadline"] = form_data["deadline"] - new_task_information["estimate"] = form_data["estimateInput"] - - new_task_information["task_type"] = form_data["taskType"] - - if new_task_information["task_type"] == "intellectual": - new_task_information["quant_verbal"] = form_data["quant/verbal"] - new_task_information["creat_consum"] = form_data["contentconsump"] - - elif new_task_information["task_type"] == "physical": - new_task_information["quant_verbal"] = "NA" - new_task_information["creat_consum"] = "NA" - - else: - print("Error! task_type is neither intellectual nor physical") - - - new_task_information["difficulty"] = form_data["difficulty"] - - print(new_task_information) - with open(os.path.join(TODO_TASKS_PATH, new_id+".json"), "w", encoding="utf-8") as json_file: - json.dump(new_task_information, json_file) - - return redirect("/") - -@app.route("/delete_task", methods = ["POST"]) -def delete_task_byID(): - """Deleting a task by its ID.""" - # task_id = request.data.decode("utf-8") - - print(request.values) - task_id = request.values["id"] - - if os.path.exists(os.path.join(TODO_TASKS_PATH, str(task_id)+".json")): - with open(os.path.join(TODO_TASKS_PATH, str(task_id)+".json"), - "r", encoding="utf-8") as json_file: - task_information = json.load(json_file) - - - os.remove(os.path.join(TODO_TASKS_PATH, str(task_id)+".json")) - - with open(os.path.join(COMPLETED_TASKS_PATH, str(task_id)+".json"), - "w", encoding="utf-8") as json_file: - json.dump(task_information, json_file) - - return redirect("/") - -if __name__ == "__main__": - app.run(debug = True) diff --git a/conn_test.py b/conn_test.py new file mode 100644 index 00000000..39a43727 --- /dev/null +++ b/conn_test.py @@ -0,0 +1,41 @@ +import pymongo +import datetime +import bcrypt + +from pymongo import MongoClient + +#MongoDB connection using cluster's connection string +client = pymongo.MongoClient("mongodb+srv://radhika:Radhika1997@simplii.tvhh1.mongodb.net/simplii?retryWrites=true&w=majority") + +#database to which connections are to be made, here the name of our database is "simplii" +db = client.simplii + +#testing of password hashing for security purposes +password = "abcd1234" +hashAndSalt = bcrypt.hashpw(password.encode(), bcrypt.gensalt()) +print(hashAndSalt) +# save "hashAndSalt" in database + +# To check: +# password = userInput +valid = bcrypt.checkpw(password.encode(), hashAndSalt) +print(valid) + +#testing of sample/dummy data insertion +#input to be taken from login/sign up form once created +''' +user1 = { + "user_id": "Radhika20", + "first_name":"Radhika", + "last_name":"Raman", + "email_id":"rbraman@ncsu.edu", + "password":"sdfsdf" +} + + +testUserInfo = db.testUserInfo +result = testUserInfo.insert_one(user1) +print(f"User 1: {result.inserted_id}") + +''' + diff --git a/docs/enhancements.md b/docs/enhancements.md new file mode 100644 index 00000000..e23f7645 --- /dev/null +++ b/docs/enhancements.md @@ -0,0 +1,19 @@ +

Changes made in Phase 2

+ +
    +
  1. Landing page has been created.
  2. +
  3. Database has been added to the system for better data management.
  4. +
  5. Multi-user functionality added with usage of database.
  6. +
  7. To facilitate multi-user functionality, login, sign-up and logout features are added.
  8. +
  9. Resolved feature request of e-mail functionality. This allows users to receive list of tasks via e-mail.
  10. +
  11. Fixed the time-stamp issue from previous phase.
  12. +
  13. Multiple test cases added. + +
  14. +
  15. Improved repository with multiple badges indicating development status of project.
  16. +
  17. Added code coverage for the repository.
  18. +
diff --git a/docs/proj2rubric.md b/docs/proj2rubric.md new file mode 100644 index 00000000..1286abc6 --- /dev/null +++ b/docs/proj2rubric.md @@ -0,0 +1,27 @@ +### Review +| Score | Notes | Evidence| Self Assessment| +| -------------- | ---------- |----------|----------| +|.5| short release cycles|https://github.com/deekay2310/Simplii/releases|0.5| +|.5| workload is spread over the whole team (so one team member is often Xtimes more productive than the others...|https://github.com/deekay2310/Simplii/pulse|0.5| +|.5|Docs: why: docs tell a story, motivate the whole thing, deliver a punchline that makes you want to rush out and use the thing |https://github.com/deekay2310/Simplii/blob/main/README.md |0.5| +|.5|the files CONTRIBUTING.md lists coding standards and lots of tips on how to extend the system without screwing things up |https://github.com/deekay2310/Simplii/blob/main/CONTRIBUTING.md |0.5| +|.5|Docs: doco generated , format not ugly | in GH: https://github.com/deekay2310/Simplii/tree/main/docs|0.5| +|.5|evidence that the whole team is using the same tools (e.g. config files in the repo, updated by lots of different people) |All team members are making use of PyCharm, Github Desktop, etc |0.5| +|.5|evidence that the members of the team are working across multiple places in the code base | || +|1|Docs: what: point descriptions of each class/function (in isolation) | https://github.com/deekay2310/Simplii/blob/main/Function%20Description.md|1| +|.5|Number of commits: by different people | in GH: https://github.com/deekay2310/Simplii/graphs/contributors https://github.com/deekay2310/Simplii/pulse|0.5| +|1|issues are being closed | https://github.com/deekay2310/Simplii/issues?q=is%3Aissue+is%3Aclosed|1| +|.5|issues are discussed before they are closed | Scheduled regular zoom meetings and met in person to discuss about various issues, implementations and tasks. Reviewed each other's changes before wrapping up issues. Had continuous discussions over team WhatsApp group as well.|0.5| +|.5|Use of syntax checkers. | https://github.com/deekay2310/Simplii/blob/main/.github/workflows/syntax_checker.yml|0.5| +|1|Issues reports: there are many |https://github.com/deekay2310/Simplii/issues?q=is%3Aopen+is%3Aissue |1| +|.5|Use of code formatters. | https://github.com/deekay2310/Simplii/blob/main/.github/workflows/code_formatter.yml|0.5| +|.5|Use of style checkers | https://github.com/deekay2310/Simplii/blob/main/.github/workflows/style_checker.yml|0.5| +|.5|Docs: short video, animated, hosted on your repo. That convinces people why they want to work on your code. | || +|.5|test cases exist | https://github.com/deekay2310/Simplii/tree/main/test|0.5| +|.5|Use of code coverage | https://github.com/deekay2310/Simplii/blob/main/.github/workflows/code_cov.yml|0.5| +|.5|other automated analysis tools | https://github.com/deekay2310/Simplii/blob/main/.github/workflows/close_as_a_feature.yml|0.5| +|.5|test cases:.a large proportion of the issues related to handling failing cases. | if a test case fails, open an issue and fix it|| +|.5|test cases are routinely executed | https://github.com/deekay2310/Simplii/blob/main/.travis.yml|0.5| +|1|Documentation describing how this version improves on the older version|https://github.com/deekay2310/Simplii/blob/main/docs/enhancements.md|1| +|3|This version is a little(1), some(2), much(3) improved on the last version.|Tutor's assessment.|| +|16| Total||| diff --git a/docs/screen-recording-2021-11-04-at-114141-pm_QVXROaaN.mp4 b/docs/screen-recording-2021-11-04-at-114141-pm_QVXROaaN.mp4 new file mode 100644 index 00000000..b1afebe1 Binary files /dev/null and b/docs/screen-recording-2021-11-04-at-114141-pm_QVXROaaN.mp4 differ diff --git a/requirements.txt b/requirements.txt index 37837a81..8f77c883 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,8 @@ itsdangerous==2.0.1 Jinja2==3.0.1 MarkupSafe==2.0.1 Werkzeug==2.0.1 +requests==2.26.0 +pymongo[srv]==3.12.1 +# Install bcrypt (and it's dependencies) +bcrypt==3.2.0 +tabulate==0.8.7 diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/app.py b/src/app.py new file mode 100644 index 00000000..78653204 --- /dev/null +++ b/src/app.py @@ -0,0 +1,320 @@ +"""Importing all the standard Python modules.""" +import csv + +import json +import os +import random +import string +import smtplib +import pymongo +import datetime +import bcrypt + +from pymongo import MongoClient +from tabulate import tabulate +from flask import Flask +from flask import render_template, url_for +from flask import request, redirect +from flask import flash, session + +#MongoDB connection using cluster's connection string +client = pymongo.MongoClient("mongodb+srv://radhika:Radhika1997@simplii.tvhh1.mongodb.net/simplii?retryWrites=true&w=majority") + +#database to which connections are to be made +db = client.simplii +testUserInfo = db.testUserInfo +testTaskInfo = db.testTaskInfo +det = [] + + +app = Flask(__name__, static_folder='static') +app.secret_key = "simpliitesting" +package_dir = os.path.dirname(os.path.abspath(__file__)) +"""Global constant to store directory path""" +TODO_TASKS_PATH = os.path.join(package_dir, "../static", "tasks", "todo") +COMPLETED_TASKS_PATH = os.path.join(package_dir, "../static", "tasks", "completed") + +"""List declaration for storing the quotes and authors """ +ALL_QUOTES = [] +ALL_AUTHORS = [] + +"""Loading our code dataset in memory""" + +with open(os.path.join(package_dir, "../static", "quotes.csv"), "r", encoding="utf-8") as csv_file: + reader = csv.DictReader(csv_file) + + for row in reader: + ALL_QUOTES.append(row["Quote"]) + ALL_AUTHORS.append(row["Author"]) + + +def refresh_data(): + """This function loads all the data required to display the home page from file-system.""" + + ##### Load user information from file + with open(os.path.join(package_dir,"../static","user_information.json"), "r", encoding="utf-8") as json_file: + json_data = json.load(json_file) + + initialized = json_data["initialized"] + name = json_data["name"] + email_id = json_data["email_id"] + email_notifications = json_data["email_notifications"] + + ##### Load a random quote + index = random.randint(0, len(ALL_AUTHORS)-1) + + ##### Load the todo task list + tasks = {} + for file in os.listdir(TODO_TASKS_PATH): + if ".json" in file: + with open(os.path.join(TODO_TASKS_PATH, file), "r", encoding="utf-8") as json_file: + json_data = json.load(json_file) + + tasks[json_data["id"]] = json_data + + ##### Sorting the tasks + tasks = {key:value for (key,value) in sorted(tasks.items(), key = lambda item: item[1]["deadline"])} + ##### + + ##### Compile the data and send as json! + data = { + "name_block" : {"initialized":initialized, "name": name, "email_id": email_id, + "email_notifications": email_notifications}, + "quote_block" : {"quote": ALL_QUOTES[index], "author": ALL_AUTHORS[index]}, + "task-list-block" : {"task-list": tasks} + } + ##### + return data + +def getnewTaskID(): + """Gets a task to be shown to the user.""" + todo_ids = [f[0:6] for f in os.listdir(TODO_TASKS_PATH)] + completed_ids = [f[0:6] for f in os.listdir(COMPLETED_TASKS_PATH)] + while True: + possible_id = ''.join(random.choices(string.ascii_uppercase + string.digits, k = 5)) + + if possible_id not in todo_ids and possible_id not in completed_ids: + return possible_id + + + +@app.route("/", methods=['GET']) +def homePage(): + """This function renders the landing page.""" + return render_template("base.html") + +@app.route("/login", methods=['GET']) +def login_get(): + return render_template('login.html') + +@app.route("/login", methods=['POST']) +def login_post(): + error = None + if request.method == 'POST': + user_id = request.form.get("userid") + #print(user_id) + email = request.form.get("email") + user_password = request.form.get("password") + #print(user_password) + + db_user = testUserInfo.find_one({"user_id": user_id}) + #print(db_user) + + if(db_user==None): + flash('The entered user ID does not exist, please login with valid credentials or sign up.') + return redirect(url_for('login_post')) + else: + db_userid = db_user['user_id'] + db_emailid = db_user['email_id'] + name = db_user['first_name'] + " " + db_user['last_name'] + #print(db_userid) + db_password = db_user['password'] + valid_password = bcrypt.checkpw(user_password.encode(), db_password) + #print(valid_password) + + if user_id == db_userid and valid_password != True: + flash('The entered password is invalid, please login with valid credentials.') + return redirect(url_for('login_post')) + + else: + #Storing user information in session variables + session['user_id'] = user_id + session['email'] = db_emailid + session['name'] = name + det = [] + for tmp in testTaskInfo.find({"user_id": session['user_id']}): + det.append([tmp['task_name'],tmp['estimate'],tmp['deadline']]) + return render_template("index.html",name=session['name'],e=session['email'],det=det,data=refresh_data()) + #return redirect(url_for('mainPage')) + return render_template('index.html') + +@app.route("/send_email", methods=['GET','POST']) +def send_email(): + #Connection to the server + server = smtplib.SMTP_SSL("smtp.gmail.com",465) + + #Storing sender's email address and password + sender_email = "simplii.reminder@gmail.com" + sender_password = "Temp@1234" + + #Logging in with sender details + server.login(sender_email,sender_password) + user_id = session['user_id'] + db_task = "" + table = [['Task_name','Deadline','Estimate','Task_type','Difficulty','Status']] + for db_task in testTaskInfo.find({"user_id": user_id}): + a = [db_task['task_name'],db_task['deadline'],db_task['estimate'],db_task['task_type'],db_task['difficulty'],db_task['task_status']] + table.append(a) + message = 'Subject: Task List\n\n{}'.format(tabulate(table)) + server.sendmail(sender_email,session['email'],message) + server.quit() + return redirect("/index") + +@app.route("/signup", methods=['GET']) +def signup_get(): + return render_template('signup.html') + +@app.route("/signup", methods=['POST']) +def signup_post(): + message = '' + + ''' + if "email" in session: + return redirect(url_for("logged_in")) + + ''' + + if request.method == 'POST': + user_id = request.form.get("userid") + user_first_name = request.form.get("firstname") + user_last_name = request.form.get("lastname") + email = request.form.get("email") + user_password = request.form.get("password") + + user_found = testUserInfo.find_one({"user_id": user_id}) + email_found = testUserInfo.find_one({"email_id": email}) + + + if user_found: + flash('User ID already taken, please enter a different user ID') + #message = 'User ID already taken, please enter a different user ID' + + return redirect(url_for('signup_post')) + + #return render_template('signup.html', message=message) + + + if email_found: + message = 'This email already exists in database' + return render_template('signup.html', message=message) + + else: + pw_hashAndSalt = bcrypt.hashpw(user_password.encode(), bcrypt.gensalt()) + + user_input_data = {'user_id': user_id, 'first_name': user_first_name, 'last_name': user_last_name, 'email_id': email, 'password': pw_hashAndSalt} + #user_input_data = {'first_name': user_name, 'email_id': email, 'password': pw_hashAndSalt} + + testUserInfo.insert_one(user_input_data) + + user_data = testUserInfo.find_one({"email_id": email}) + new_email = user_data['email_id'] + session['user_id'] = user_id + session['email'] = email + session['name'] = user_first_name + " " + user_last_name + det = [] + for tmp in testTaskInfo.find({"user_id": session['user_id']}): + det.append([tmp['task_name'],tmp['estimate'],tmp['deadline']]) + + return render_template("index.html",name=session['name'],e=session['email'],det=det,data=refresh_data()) + #return redirect(url_for('mainPage')) + return render_template('login.html') + +@app.route("/index") +def mainPage(): + """This function renders the home page.""" + #email = session["email"] + #Sending user details to HTML page + return render_template("index.html", name = session["name"], e = session["email"], data=refresh_data()) + +@app.route('/logout') +def logout(): + """This function ends the session and logs the user out.""" + session.pop('user_id', None) + return redirect('/') + +@app.route("/add_task", methods = ["POST"]) +def add_new_task(): + """Add a new task to the JSON.""" + form_data = request.values + + new_id = getnewTaskID() + + new_task_information = {} + + new_task_information["id"] = new_id + new_task_information["task_name"] = form_data["taskName"] + new_task_information["deadline"] = form_data["deadline"].replace("T"," ") + new_task_information["estimate"] = form_data["estimateInput"] + + new_task_information["task_type"] = form_data["taskType"] + + if new_task_information["task_type"] == "intellectual": + new_task_information["quant_verbal"] = form_data["quant/verbal"] + new_task_information["creat_consum"] = form_data["contentconsump"] + + elif new_task_information["task_type"] == "physical": + new_task_information["quant_verbal"] = "NA" + new_task_information["creat_consum"] = "NA" + + else: + print("Error! task_type is neither intellectual nor physical") + + + new_task_information["difficulty"] = form_data["difficulty"] + new_task_information["task_status"] = "Pending" + + print(new_task_information) + print(session['user_id']) + + curr_user = session['user_id'] + + #add task to db + user_task_data = {'user_id': session['user_id'], 'task_id': new_id, 'task_name': new_task_information["task_name"], 'deadline': new_task_information["deadline"], 'estimate': new_task_information["estimate"], 'task_type': new_task_information["task_type"], 'quant_verbal': new_task_information["quant_verbal"], 'creat_consum': new_task_information["creat_consum"], 'difficulty': new_task_information["difficulty"], 'task_status': new_task_information["task_status"]} + testTaskInfo.insert_one(user_task_data) + flash('Your new task has been recorded!') + + with open(os.path.join(TODO_TASKS_PATH, new_id+".json"), "w", encoding="utf-8") as json_file: + json.dump(new_task_information, json_file) + + det = [] + for tmp in testTaskInfo.find({"user_id": session['user_id']}): + det.append([tmp['task_name'],tmp['estimate'],tmp['deadline']]) + + #return redirect("/index") + return render_template("index.html",name=session['user_id'],e=session['email'],det=det,data=refresh_data()) + + +@app.route("/delete_task", methods = ["POST"]) +def delete_task_byID(): + """Deleting a task by its ID.""" + # task_id = request.data.decode("utf-8") + + print(request.values) + task_id = request.values["id"] + + if os.path.exists(os.path.join(TODO_TASKS_PATH, str(task_id)+".json")): + with open(os.path.join(TODO_TASKS_PATH, str(task_id)+".json"), + "r", encoding="utf-8") as json_file: + task_information = json.load(json_file) + + + os.remove(os.path.join(TODO_TASKS_PATH, str(task_id)+".json")) + + with open(os.path.join(COMPLETED_TASKS_PATH, str(task_id)+".json"), + "w", encoding="utf-8") as json_file: + json.dump(task_information, json_file) + + return redirect("/index") + +if __name__ == "__main__": + app.run(debug=True) diff --git a/src/templates/base.html b/src/templates/base.html new file mode 100644 index 00000000..c69cdca0 --- /dev/null +++ b/src/templates/base.html @@ -0,0 +1,113 @@ + + + + + + + + + Simplii + + + + +
+ +
+ +
+ +
+
+ + + + + +

+ Simplii +

+

+ A Simple Task Scheduler +

+ +
+
+
+ + + + + + + diff --git a/templates/index.html b/src/templates/index.html similarity index 66% rename from templates/index.html rename to src/templates/index.html index 84639ad9..4a47ae86 100644 --- a/templates/index.html +++ b/src/templates/index.html @@ -1,6 +1,179 @@ + @@ -17,7 +190,7 @@ - + @@ -32,9 +205,23 @@ console.log(page_data["task-list-block"]); - Simpli + Simplii + + + + + {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + @@ -42,10 +229,13 @@
+

+ Logout +

-

Welcome to Task Scheduler

+

Welcome to Simplii - A Custom Task Scheduler

-

Schedule for : +

Schedule for:

@@ -53,17 +243,19 @@

Welcome to Task Scheduler

- Hi, {{data["name_block"]["name"]}}
- email-id: {{data["name_block"]["email_id"]}}
- E-mail notifications: {{data["name_block"]["email_notifications"]}} + Hi, {{name}}
+ email-id: {{e}}
+ E-mail notifications: {{e}}
- - + +
+ +