-
Notifications
You must be signed in to change notification settings - Fork 97
Pine - Areeba #73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Pine - Areeba #73
Changes from all commits
a19eda1
fd7fecb
8f15549
a4145c9
fe23fca
02fb668
d0a07a5
6fac97b
aad8170
8be10c8
b5179c4
61930de
1ab59ff
bb21ae2
2484f3f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| web: gunicorn 'app:create_app()' |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,2 +1,292 @@ | ||||||
| from flask import Blueprint | ||||||
| from flask.wrappers import Response | ||||||
| from app import db | ||||||
| from app.models.task import Task | ||||||
| from app.models.goal import Goal | ||||||
| from flask import Blueprint, jsonify, make_response, request | ||||||
| from datetime import datetime, timezone | ||||||
| import os | ||||||
| from slack_sdk import WebClient | ||||||
| from slack_sdk.errors import SlackApiError | ||||||
|
|
||||||
| #create the blueprint for the endpoints | ||||||
| tasks_bp = Blueprint("tasks", __name__, url_prefix="/tasks") | ||||||
| goals_bp = Blueprint("goals", __name__, url_prefix="/goals") | ||||||
|
|
||||||
| #Create a Task: Valid Task With null completed_at | ||||||
| @tasks_bp.route("", methods=["POST"]) | ||||||
| def create_task(): | ||||||
| request_body = request.get_json() | ||||||
|
|
||||||
| if "title" not in request_body or "description" not in request_body or "completed_at" not in request_body: | ||||||
| return make_response({"details": "Invalid data"}, 400) | ||||||
|
|
||||||
| new_task = set_task(request_body) | ||||||
| db.session.add(new_task) | ||||||
| db.session.commit() | ||||||
|
|
||||||
| response = {} | ||||||
| response['task'] = format_task_dictionary(new_task) | ||||||
| return make_response(response, 201) | ||||||
|
|
||||||
| ################### | ||||||
| #Helper functions for tasks | ||||||
| def set_task(request_body): | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice helper function! |
||||||
| new_task = Task(title=request_body["title"], | ||||||
| description=request_body["description"]) | ||||||
|
|
||||||
| if request_body["completed_at"] is not None: | ||||||
| new_task.completed_at=request_body["completed_at"] | ||||||
| else: | ||||||
| new_task.completed_at = None | ||||||
|
|
||||||
| return new_task | ||||||
|
|
||||||
| def format_task_dictionary(new_task): | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function, as I remember you mentioning, would work better as an instance method of the |
||||||
| task = {} | ||||||
| task['id'] = new_task.task_id | ||||||
| task['title'] = new_task.title #request_body["title"] | ||||||
| task['description'] = new_task.description #request_body["description"] | ||||||
| if new_task.completed_at is not None: | ||||||
| task['is_complete'] = True | ||||||
| else: | ||||||
| task['is_complete'] = False | ||||||
|
|
||||||
| if new_task.goal_id is not None: | ||||||
| task['goal_id'] = new_task.goal_id | ||||||
|
|
||||||
| return task | ||||||
| #################### | ||||||
|
|
||||||
| #Get Tasks: Getting Saved Tasks | ||||||
| @tasks_bp.route("", methods=["GET"]) | ||||||
| def get_tasks(): | ||||||
|
|
||||||
| #this can go in a helper function | ||||||
| sort_query = request.args.get("sort") | ||||||
| if(sort_query == "asc"): | ||||||
| tasks = Task.query.order_by(Task.title).all() | ||||||
| elif(sort_query == "desc"): | ||||||
| tasks = Task.query.order_by(Task.title.desc()).all() | ||||||
| else: | ||||||
| tasks = Task.query.all() | ||||||
|
|
||||||
| response = [] | ||||||
| for task in tasks: | ||||||
| task_dict = format_task_dictionary(task) | ||||||
| response.append(task_dict) | ||||||
|
|
||||||
| return jsonify(response) | ||||||
|
|
||||||
| #Get a Task | ||||||
| @tasks_bp.route("/<task_id>", methods=["GET"]) | ||||||
| def get_task(task_id): | ||||||
| task = Task.query.get(task_id) | ||||||
|
|
||||||
| if task is None: | ||||||
| return make_response("Not Found", 404) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just for consistency
Suggested change
|
||||||
|
|
||||||
| response = {} | ||||||
| response['task'] = format_task_dictionary(task) | ||||||
| return response | ||||||
|
|
||||||
| #Update One Task | ||||||
| @tasks_bp.route("/<task_id>", methods=["PUT"]) | ||||||
| def update_task(task_id): | ||||||
| task = Task.query.get(task_id) | ||||||
|
|
||||||
| if task is None: | ||||||
| return make_response(f"Task {task_id} not found", 404) | ||||||
|
|
||||||
| form_data = request.get_json() | ||||||
| task.title = form_data["title"] | ||||||
| task.description = form_data["description"] | ||||||
|
|
||||||
| db.session.commit() | ||||||
|
|
||||||
| response = {} | ||||||
| response['task'] = format_task_dictionary(task) | ||||||
| return make_response(response, 200) | ||||||
|
|
||||||
|
|
||||||
| # Delete One Task | ||||||
| @tasks_bp.route("/<task_id>", methods=["DELETE"]) | ||||||
| def delete_task(task_id): | ||||||
| task = Task.query.get(task_id) | ||||||
| if task is None: | ||||||
| return make_response(f"Task {task_id} not found", 404) | ||||||
|
Comment on lines
+114
to
+116
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just want to point out that you are repeating this code segment a lot. It would be nice to be able to DRY this up a bit. In the future that might be something to research. |
||||||
| response = {"details": f'Task {task.task_id} "{task.title}" successfully deleted' } | ||||||
| db.session.delete(task) | ||||||
| db.session.commit() | ||||||
| return make_response(response, 200) | ||||||
|
|
||||||
|
|
||||||
| #Mark Complete | ||||||
| @tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||||||
| def mark_complete(task_id): | ||||||
|
Comment on lines
+123
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest making the Slack interaction a separate helper function. That way each function is doing one thing. |
||||||
|
|
||||||
| #set up the token and client | ||||||
| slack_token = os.environ["SLACK_API_TOKEN"] | ||||||
| client = WebClient(token=slack_token) | ||||||
|
|
||||||
| task = Task.query.get(task_id) | ||||||
| if task is None: | ||||||
| return make_response("Not Found", 404) | ||||||
|
|
||||||
| response_json = {} | ||||||
|
|
||||||
| task.completed_at = datetime.now(timezone.utc) | ||||||
| db.session.commit() | ||||||
|
|
||||||
| response_json["task"]= format_task_dictionary(task) | ||||||
|
|
||||||
| try: | ||||||
| response = client.chat_postMessage( | ||||||
| channel="task-notifications", | ||||||
| text=f"Someone just completed the task {task.title} :tada:") | ||||||
|
|
||||||
| except SlackApiError as e: | ||||||
| # You will get a SlackApiError if "ok" is False | ||||||
| assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' | ||||||
| # | ||||||
| return make_response(response_json, 200) | ||||||
|
|
||||||
| #Mark Incomplete | ||||||
| @tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||||||
| def mark_incomplete(task_id): | ||||||
| task = Task.query.get(task_id) | ||||||
| if task is None: | ||||||
| return make_response("Not Found", 404) | ||||||
|
|
||||||
| response = {} | ||||||
| task_dict = {} | ||||||
|
|
||||||
| task.completed_at = None | ||||||
| response["task"] = format_task_dictionary(task) | ||||||
| return make_response(response, 200) | ||||||
|
|
||||||
| ############--------------------------------------------------------- | ||||||
|
|
||||||
| #Create a Goal | ||||||
| @goals_bp.route("", methods=["POST"]) | ||||||
| def create_goal(): | ||||||
| request_body = request.get_json() | ||||||
|
|
||||||
| if "title" not in request_body: | ||||||
| return make_response({"details": "Invalid data"}, 400) | ||||||
|
|
||||||
| new_goal = Goal(title=request_body["title"]) | ||||||
| db.session.add(new_goal) | ||||||
| db.session.commit() | ||||||
|
|
||||||
| response = {} | ||||||
| response["goal"] = format_goal_dictionary(new_goal) | ||||||
|
|
||||||
| return make_response(response, 201) | ||||||
|
|
||||||
| ############ | ||||||
| def format_goal_dictionary(new_goal): | ||||||
| goal = {} | ||||||
| goal['id'] = new_goal.goal_id | ||||||
| goal['title'] = new_goal.title | ||||||
|
|
||||||
| return goal | ||||||
|
Comment on lines
+187
to
+192
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would also make an excellent model method. |
||||||
| ############ | ||||||
|
|
||||||
| #Get Goals | ||||||
| @goals_bp.route("", methods=["GET"]) | ||||||
| def get_goals(): | ||||||
|
|
||||||
| sort_query = request.args.get("sort") | ||||||
| if(sort_query == "asc"): | ||||||
| goals = Goal.query.order_by(Goal.title).all() | ||||||
| elif(sort_query == "desc"): | ||||||
| goals = Goal.query.order_by(Goal.title.desc()).all() | ||||||
| else: | ||||||
| goals = Goal.query.all() | ||||||
|
|
||||||
| response = [] | ||||||
|
|
||||||
| for goal in goals: | ||||||
| goal_dict = format_goal_dictionary(goal) | ||||||
| response.append(goal_dict) | ||||||
|
|
||||||
| return jsonify(response) | ||||||
|
|
||||||
| #Get a Goal | ||||||
| @goals_bp.route("/<goal_id>", methods=["GET"]) | ||||||
| def get_goal(goal_id): | ||||||
| goal = Goal.query.get(goal_id) | ||||||
|
|
||||||
| if goal is None: | ||||||
| return make_response("Not Found", 404) | ||||||
|
|
||||||
| response = {} | ||||||
| response["goal"] = format_goal_dictionary(goal) | ||||||
| return jsonify(response) | ||||||
|
|
||||||
| #Update One Goal | ||||||
| @goals_bp.route("/<goal_id>", methods=["PUT"]) | ||||||
| def update_goal(goal_id): | ||||||
| goal = Goal.query.get(goal_id) | ||||||
|
|
||||||
| if goal is None: | ||||||
| return make_response(f"Goal {goal_id} not found", 404) | ||||||
|
|
||||||
| form_data = request.get_json() | ||||||
| goal.title = form_data["title"] | ||||||
| db.session.commit() | ||||||
|
|
||||||
| response = {} | ||||||
| response["goal"] = format_goal_dictionary(goal) | ||||||
| return make_response(response, 200) | ||||||
|
|
||||||
|
|
||||||
| # Delete One Goal | ||||||
| @goals_bp.route("/<goal_id>", methods=["DELETE"]) | ||||||
| def delete_goal(goal_id): | ||||||
| goal = Goal.query.get(goal_id) | ||||||
| if goal is None: | ||||||
| return make_response(f"Goal {goal_id} not found", 404) | ||||||
| response = {"details": f'Goal {goal.goal_id} "{goal.title}" successfully deleted' } | ||||||
| db.session.delete(goal) | ||||||
| db.session.commit() | ||||||
| return make_response(response, 200) | ||||||
|
|
||||||
| @goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||||||
| def post_task_ids_to_goal(goal_id): | ||||||
| goal = Goal.query.get(goal_id) #we grabbed the goal for the provided id | ||||||
|
|
||||||
| if goal is None: #error checking | ||||||
| return make_response("", 404) | ||||||
|
|
||||||
| request_body = request.get_json() #grab form data | ||||||
| task_ids = request_body["task_ids"] #storing the list in a cleaner variable reference | ||||||
|
|
||||||
| for task in range(len(task_ids)): #for each index in this list | ||||||
| this_task = Task.query.get(task_ids[task]) #get one task at a time | ||||||
| this_task.goal_id = goal.goal_id #set its goal id to the id of this goal | ||||||
| db.session.commit() | ||||||
|
|
||||||
| response = {"id": goal.goal_id, "task_ids": task_ids} | ||||||
| return make_response(response, 200) | ||||||
|
|
||||||
|
|
||||||
| @goals_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||||||
| def get_tasks_for_specific_goals(goal_id): | ||||||
|
|
||||||
| #get the goal cuz we need its details | ||||||
| #get tasks that have that goal id | ||||||
| #if no tasks have that goal id, return a specific response | ||||||
|
|
||||||
| goal = Goal.query.get(goal_id) #grab the specific goal | ||||||
| if goal is None: | ||||||
| return make_response("", 404) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be best to include in the body some indication of why it failed in addition to the response code. |
||||||
|
|
||||||
| tasks = Task.query.filter(Task.goal_id==goal_id).all() | ||||||
| response = {"id": goal.goal_id, "title": goal.title, "tasks": []} | ||||||
|
|
||||||
| for task in tasks: | ||||||
| task_dict = format_task_dictionary(task) | ||||||
| response["tasks"].append(task_dict) | ||||||
|
Comment on lines
+286
to
+290
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would make a great helper method in the |
||||||
|
|
||||||
| return make_response(response, 200) | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Generic single-database configuration. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # A generic, single database configuration. | ||
|
|
||
| [alembic] | ||
| # template used to generate migration files | ||
| # file_template = %%(rev)s_%%(slug)s | ||
|
|
||
| # set to 'true' to run the environment during | ||
| # the 'revision' command, regardless of autogenerate | ||
| # revision_environment = false | ||
|
|
||
|
|
||
| # Logging configuration | ||
| [loggers] | ||
| keys = root,sqlalchemy,alembic | ||
|
|
||
| [handlers] | ||
| keys = console | ||
|
|
||
| [formatters] | ||
| keys = generic | ||
|
|
||
| [logger_root] | ||
| level = WARN | ||
| handlers = console | ||
| qualname = | ||
|
|
||
| [logger_sqlalchemy] | ||
| level = WARN | ||
| handlers = | ||
| qualname = sqlalchemy.engine | ||
|
|
||
| [logger_alembic] | ||
| level = INFO | ||
| handlers = | ||
| qualname = alembic | ||
|
|
||
| [handler_console] | ||
| class = StreamHandler | ||
| args = (sys.stderr,) | ||
| level = NOTSET | ||
| formatter = generic | ||
|
|
||
| [formatter_generic] | ||
| format = %(levelname)-5.5s [%(name)s] %(message)s | ||
| datefmt = %H:%M:%S |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Slack SDK is fine, but you can also do this with the built-in request object from Python.