-
Notifications
You must be signed in to change notification settings - Fork 128
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
Setharika Sok task-list-api #118
base: main
Are you sure you want to change the base?
Changes from all commits
68d26c7
f5c5af2
f247e87
e220f20
e646228
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 |
---|---|---|
@@ -1 +1,275 @@ | ||
from flask import Blueprint | ||
from flask import Blueprint, jsonify, abort, make_response, request | ||
from app import db | ||
from app.models.task import Task | ||
from app.models.goal import Goal | ||
from datetime import datetime | ||
import os | ||
import requests | ||
|
||
task_bp = Blueprint("tasks", __name__, url_prefix="/tasks") | ||
|
||
def validate_model(cls, model_id): | ||
try: | ||
model_id = int(model_id) | ||
except: | ||
abort(make_response({"details":"Invalid data"}, 400)) | ||
|
||
model = cls.query.get(model_id) | ||
|
||
if not model: | ||
abort(make_response({"details": f"{cls.__name__} {model_id} is not found"}, 404)) | ||
|
||
return model | ||
|
||
def validate_request(cls, request): | ||
request_body = request.get_json() | ||
|
||
if not request_body.get("title"): | ||
abort(make_response({"details":"Invalid data"}, 400)) | ||
|
||
elif not request_body.get("description") and cls == Task: | ||
Comment on lines
+27
to
+30
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. We could write the conditions for this logic in one line like this: if not (request_body.get('title') or request_body.get('description')): 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. We could also generalize your def validate_request_body(request_body, keys):
for key in keys:
if not request_body.get(key):
abort(make_response({
'Invalid Data': f'missing key: {key}'
}, 400))
return True We can pass in the |
||
abort(make_response({"details":f"Invalid data"}, 400)) | ||
|
||
return request_body | ||
|
||
|
||
@task_bp.route("", methods=["POST"]) | ||
def create_task(): | ||
request_body = validate_request(Task, request) | ||
|
||
new_task = Task.from_dict(request_body) | ||
|
||
db.session.add(new_task) | ||
db.session.commit() | ||
|
||
return {"task":new_task.to_dict()}, 201 | ||
|
||
@task_bp.route("", methods=["GET"]) | ||
def read_all_tasks(): | ||
|
||
sort_query = request.args.get("sort", "asc") | ||
title_query = request.args.get("title") | ||
description_query = request.args.get("description") | ||
|
||
|
||
if sort_query == "asc": | ||
tasks = Task.query.order_by(Task.title.asc()) | ||
|
||
elif sort_query == "desc": | ||
tasks = Task.query.order_by(Task.title.desc()) | ||
|
||
elif title_query: | ||
tasks = Task.query.filter_by(title = title_query) | ||
Comment on lines
+55
to
+62
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 work on getting the database to do most of the lifting when it comes to sorting the tasks |
||
|
||
elif description_query: | ||
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 job! |
||
tasks = Task.query.filter_by(description = description_query) | ||
|
||
else: | ||
tasks = Task.query.all() | ||
|
||
tasks_response = [] | ||
|
||
for task in tasks: | ||
tasks_response.append(task.to_dict()) | ||
return jsonify(tasks_response), 200 | ||
|
||
|
||
@task_bp.route("/<id>", methods=["GET"]) | ||
def read_single_task(id): | ||
task = validate_model(Task, id) | ||
|
||
goal = Goal.query.get(id) | ||
|
||
if goal != None: | ||
task_dict = task.to_dict() | ||
task_dict["goal_id"] = goal.goal_id | ||
|
||
else: | ||
task_dict = task.to_dict() | ||
|
||
return ({"task":task_dict}),200 | ||
|
||
|
||
@task_bp.route("/<task_id>", methods=["PUT"]) | ||
def update_task(task_id): | ||
task = validate_model(Task, task_id) | ||
|
||
request_body = request.get_json() | ||
|
||
task.title = request_body["title"] | ||
task.description = request_body["description"] | ||
Comment on lines
+99
to
+100
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. Right now, if a user sends a request without the keys title or description your server would crash. There's a couple of ways to handle this, you could call the |
||
|
||
db.session.commit() | ||
|
||
return task.to_dict_one_task(), 200 | ||
|
||
@task_bp.route("/<task_id>", methods=["DELETE"]) | ||
def delete_task(task_id): | ||
task = validate_model(Task, task_id) | ||
|
||
db.session.delete(task) | ||
db.session.commit() | ||
|
||
return jsonify({"details":f'Task {task_id} "{task.title}" successfully deleted'}), 200 | ||
|
||
|
||
@task_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||
def mark_incomplete(task_id): | ||
task = validate_model(Task, task_id) | ||
|
||
if task.completed_at is not None: | ||
task.completed_at = None | ||
|
||
task.is_complete = False | ||
db.session.commit() | ||
return jsonify({"task": task.to_dict()}), 200 | ||
|
||
|
||
@task_bp.route("<task_id>/mark_complete", methods=['PATCH']) | ||
def mark_task_complete_slack(task_id): | ||
task = validate_model(Task, task_id) | ||
|
||
if task.completed_at is not None: | ||
return jsonify({"task": task.to_dict()}), 200 | ||
|
||
task.completed_at = datetime.utcnow() | ||
db.session.commit() | ||
|
||
SLACK_BOT_TOKEN = os.environ.get('SLACK_BOT_TOKEN') | ||
SLACK_CHANNEL = '#task-notifications' | ||
|
||
#respone notification to Slack | ||
|
||
message = f"Someone just completed the task{task.title}" | ||
slack_data = {'text': message, 'channel': SLACK_CHANNEL} | ||
headers = {'Authorization': f'Bearer {SLACK_BOT_TOKEN}'} | ||
|
||
try: | ||
response = requests.post('https://slack.com/api/chat.postMessage', json=slack_data, headers=headers) | ||
response.raise_for_status() | ||
|
||
except requests.exceptions.RequestException as e: | ||
return jsonify({'message': 'Failed to send Slack notification: {e}'}) | ||
Comment on lines
+147
to
+152
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 work with the |
||
|
||
return jsonify({"task": task.to_dict()}), 200 | ||
|
||
goal_bp = Blueprint("goals", __name__, url_prefix="/goals") | ||
@goal_bp.route("", methods=['POST']) | ||
|
||
# creating a goal resource | ||
def create_goal(): | ||
request_body = validate_request(Goal, request) | ||
|
||
new_goal = Goal( | ||
title=request_body["title"] | ||
) | ||
|
||
db.session.add(new_goal) | ||
db.session.commit() | ||
|
||
return ({"goal":new_goal.goal_dict()}), 201 | ||
|
||
@goal_bp.route("", methods=['GET']) | ||
def read_all_goals(): | ||
goals = Goal.query.all() | ||
|
||
goals_response = [] | ||
|
||
for goal in goals: | ||
goals_response.append({ "title": goal.title, "id": goal.goal_id}) | ||
Comment on lines
+177
to
+179
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. Do you think that this could be a class method? If so, how would your code look different? |
||
|
||
return jsonify(goals_response), 200 | ||
|
||
@goal_bp.route("/<goal_id>", methods=['GET']) | ||
def read_one_goal(goal_id): | ||
|
||
goal = validate_model(Goal, goal_id) | ||
|
||
return ({"goal": goal.goal_dict()}), 200 | ||
|
||
@goal_bp.route("/<goal_id>", methods=['PUT']) | ||
def update_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
|
||
request_body = request.get_json() | ||
|
||
goal.title = request_body["title"] | ||
db.session.commit() | ||
|
||
return jsonify({"goal": goal.goal_dict()}), 200 | ||
|
||
@goal_bp.route("/<goal_id>", methods=['DELETE']) | ||
def delete_task(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
|
||
db.session.delete(goal) | ||
db.session.commit() | ||
|
||
return jsonify({"details":f'Goal {goal_id} "{goal.title}" successfully deleted'}), 200 | ||
|
||
|
||
@goal_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
|
||
def add_tasks_to_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
|
||
if 'task_ids' not in request.json: | ||
abort(400) | ||
|
||
task_ids = request.json['task_ids'] | ||
|
||
tasks = Task.query.filter(Task.task_id.in_(task_ids)).all() | ||
|
||
if len(tasks) != len(task_ids): | ||
abort(400) | ||
|
||
goal.task_ids = task_ids | ||
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. So here you are making a new attribute on this goal instance that will hold a list of task ids that were last added. This doesn't actually make an association between the two models. Lines |
||
|
||
for task in tasks: | ||
task.goal_id = goal_id | ||
|
||
db.session.commit() | ||
|
||
return jsonify({'id': goal.goal_id, 'task_ids': goal.task_ids}) | ||
|
||
@goal_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
def get_tasks_for_specific_goal(goal_id): | ||
goal = validate_model(Goal, goal_id) | ||
|
||
tasks_response = [] | ||
|
||
for task in goal.tasks: | ||
task_dict = task.to_dict() | ||
task_dict["goal_id"] = goal.goal_id | ||
tasks_response.append(task_dict) | ||
|
||
goal_dict = goal.goal_dict() | ||
goal_dict["tasks"] = tasks_response | ||
|
||
return jsonify(goal_dict), 200 | ||
|
||
|
||
|
||
|
||
|
||
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. Well done on this project, I didn't have much to comment on and that is a good thing! Keep up the good work! Really looking forward to what you create in the frontend! Please feel free to reach out if you have any questions about the feedback that I left! ✨💫🤭 |
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Generic single-database configuration. |
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.
Love the ternary you implemented here!