-
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
Amethyst - Mazzy #119
base: main
Are you sure you want to change the base?
Amethyst - Mazzy #119
Changes from all commits
88a9a46
7622bd3
86a87f7
5c6e3e7
72c00ef
94bc321
4751c94
31f6ac0
24d4469
7adc19d
1272799
d00d80b
56bc34a
5504056
f117305
8184a20
64b7bcc
9631d61
818e073
de9aead
3ec8f39
a4a9d2b
79d2158
dedf22e
e466074
b91bab4
29b0b0f
3418a8f
0060bfb
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,122 @@ | ||
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 .task_routes import validate_model | ||
import requests | ||
|
||
goals_bp = Blueprint("goals",__name__,url_prefix="/goals") | ||
|
||
@goals_bp.route("", methods=["POST"]) | ||
def create_goal(): | ||
|
||
request_body = request.get_json() | ||
|
||
try: | ||
new_goal = Goal(title=request_body["title"]) | ||
|
||
except: | ||
abort(make_response({ | ||
"details": "Invalid data" | ||
}, 400)) | ||
|
||
db.session.add(new_goal) | ||
db.session.commit() | ||
|
||
return make_response(jsonify({"goal":{ | ||
"id" : new_goal.goal_id, | ||
"title" : new_goal.title, | ||
}}),201) | ||
|
||
@goals_bp.route("", methods=["GET"]) | ||
def get_goals(): | ||
|
||
goals = Goal.query.all() | ||
|
||
goals_response = [] | ||
|
||
for goal in goals: | ||
goals_response.append( | ||
goal.to_dict() | ||
) | ||
Comment on lines
+38
to
+41
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 utilization of your |
||
|
||
return jsonify(goals_response) | ||
|
||
@goals_bp.route("/<goal_id>", methods=["GET"]) | ||
def get_one_goal(goal_id): | ||
|
||
goal = validate_model(Goal, goal_id) | ||
|
||
return {"goal":goal.to_dict()}, 200 | ||
|
||
@goals_bp.route("/<goal_id>", methods=["PUT"]) | ||
def update_one_goal(goal_id): | ||
|
||
goal = validate_model(Goal, goal_id) | ||
|
||
request_body = request.get_json() | ||
|
||
goal.title = request_body["title"] | ||
|
||
db.session.commit() | ||
|
||
return make_response(jsonify({"goal":goal.to_dict()}),200) | ||
|
||
@goals_bp.route("/<goal_id>", methods=["DELETE"]) | ||
def remove_one_goal(goal_id): | ||
|
||
goal = validate_model(Goal, goal_id) | ||
|
||
db.session.delete(goal) | ||
db.session.commit() | ||
|
||
return make_response({ | ||
"details": f'{Goal.__name__} {goal_id} "{goal.title}" successfully deleted' | ||
}), 200 | ||
|
||
# nested routes | ||
@goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
def create_tasks_for_goal(goal_id): | ||
|
||
goal = validate_model(Goal, goal_id) | ||
|
||
request_body = request.get_json() | ||
|
||
tasks_list = [] | ||
|
||
for task in request_body["task_ids"]: | ||
task = validate_model(Task, task) | ||
tasks_list.append(task) | ||
task.goal_id = goal.goal_id | ||
Comment on lines
+87
to
+90
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. ⭐️ |
||
|
||
goal.tasks = tasks_list | ||
|
||
db.session.commit() | ||
|
||
return make_response(jsonify({ | ||
"id" : goal.goal_id, | ||
"task_ids" : request_body["task_ids"], | ||
}),200) | ||
|
||
@goals_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
def get_tasks_for_goal(goal_id): | ||
|
||
goal = validate_model(Goal, goal_id) | ||
|
||
tasks_list = [] | ||
|
||
for task in goal.tasks: | ||
tasks_list.append({ | ||
"id" : task.task_id, | ||
"goal_id" : task.goal_id, | ||
"title" : task.title, | ||
"description" : task.description, | ||
"is_complete" : False | ||
}) | ||
Comment on lines
+108
to
+115
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! I have a question for you, do you think that this loop and the logic within could be moved to into the |
||
|
||
return { | ||
"id": goal.goal_id, | ||
"title" : goal.title, | ||
"tasks" : tasks_list | ||
}, 200 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,13 @@ | |
|
||
class Goal(db.Model): | ||
goal_id = db.Column(db.Integer, primary_key=True) | ||
title = db.Column(db.String) | ||
tasks = db.relationship("Task", back_populates="goal",lazy=True) | ||
|
||
def to_dict(self): | ||
goal_as_dict = {} | ||
goal_as_dict["id"] = self.goal_id | ||
goal_as_dict["title"] = self.title | ||
|
||
|
||
return goal_as_dict | ||
Comment on lines
+9
to
+15
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 method function! ⭐️ |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,26 @@ | |
|
||
class Task(db.Model): | ||
task_id = db.Column(db.Integer, primary_key=True) | ||
title = db.Column(db.String) | ||
description = db.Column(db.String) | ||
completed_at = db.Column(db.DateTime,nullable=True) | ||
goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'),nullable=True) | ||
goal = db.relationship("Goal", back_populates="tasks") | ||
|
||
def to_dict(self): | ||
task_as_dict = {} | ||
task_as_dict["id"] = self.task_id | ||
task_as_dict["title"] = self.title | ||
task_as_dict["description"] = self.description | ||
task_as_dict["is_complete"] = bool(self.completed_at) | ||
|
||
return task_as_dict | ||
|
||
@classmethod | ||
def from_dict(cls, task_data): | ||
new_task = Task( | ||
title = task_data["title"], | ||
description = task_data["description"] | ||
) | ||
|
||
return 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 file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
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 requests | ||
import os | ||
|
||
tasks_bp = Blueprint("tasks",__name__,url_prefix="/tasks") | ||
|
||
@tasks_bp.route("", methods=["POST"]) | ||
def create_task(): | ||
|
||
request_body = request.get_json() | ||
|
||
try: | ||
new_task = Task(title=request_body["title"], | ||
description=request_body["description"]) | ||
|
||
except: | ||
abort(make_response({ | ||
"details": "Invalid data" | ||
}, 400)) | ||
|
||
db.session.add(new_task) | ||
db.session.commit() | ||
|
||
return make_response(jsonify({"task":{ | ||
"id" : new_task.task_id, | ||
"title" : new_task.title, | ||
"description" : new_task.description, | ||
"is_complete" : False | ||
}}),201) | ||
|
||
@tasks_bp.route("", methods=["GET"]) | ||
def get_tasks(): | ||
|
||
sort_query = request.args.get("sort") | ||
|
||
if sort_query == "asc": | ||
tasks = Task.query.order_by(Task.title.asc()).all() | ||
elif sort_query == "desc": | ||
tasks = Task.query.order_by(Task.title.desc()).all() | ||
else: | ||
tasks = Task.query.all() | ||
Comment on lines
+40
to
+45
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. Awesome! 💫 |
||
|
||
tasks_response = [] | ||
|
||
for task in tasks: | ||
tasks_response.append( | ||
task.to_dict() | ||
) | ||
|
||
return jsonify(tasks_response) | ||
|
||
def validate_model(cls, model_id): | ||
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. Make sure your helper functions are in a place that's easy to find |
||
try: | ||
model_id = int(model_id) | ||
except: | ||
abort(make_response({"message":f"{model_id} is not valid type ({type(model_id)}) invalid. Please use integer."}, 400)) | ||
|
||
model = cls.query.get(model_id) | ||
|
||
if not model: | ||
abort(make_response({"message":f"{cls.__name__} {model_id} does not exist"}, 404)) | ||
|
||
return model | ||
|
||
@tasks_bp.route("/<task_id>", methods=["GET"]) | ||
def get_one_task(task_id): | ||
|
||
task = validate_model(Task, task_id) | ||
|
||
if task.goal_id: | ||
return { | ||
"task": { | ||
"id": task.task_id, | ||
"goal_id": task.goal_id, | ||
"title": task.title, | ||
"description": task.description, | ||
"is_complete": False | ||
} | ||
} | ||
|
||
return {"task":task.to_dict()}, 200 | ||
|
||
@tasks_bp.route("/<task_id>", methods=["PUT"]) | ||
def update_one_task(task_id): | ||
task = validate_model(Task, task_id) | ||
|
||
request_body = request.get_json() | ||
|
||
task.title = request_body["title"] | ||
task.description = request_body["description"] | ||
|
||
db.session.commit() | ||
|
||
return make_response(jsonify({"task":task.to_dict()}),200) | ||
|
||
def send_slack_message(task_title): | ||
|
||
path = 'https://slack.com/api/chat.postMessage' | ||
|
||
slack_key = os.environ.get("SLACK_API_KEY") | ||
|
||
params = { | ||
"channel":"general", | ||
"text":f"Someone just completed the task {task_title}" | ||
} | ||
headers = { | ||
"Authorization":slack_key | ||
} | ||
|
||
requests.get(path, headers=headers, params=params) | ||
|
||
@tasks_bp.route("<task_id>/mark_complete", methods=["PATCH"]) | ||
def mark_complete(task_id): | ||
|
||
task = validate_model(Task, task_id) | ||
|
||
send_slack_message(task.title) | ||
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, right now your route sends a message to slack saying that the task has been completed, before the task is marked complete. But what happens if your can't actually commit the change, your code will send out a false positive. You typically will want to send alerts like this after all your other logic has ran. |
||
|
||
task.completed_at = datetime.utcnow() | ||
|
||
db.session.commit() | ||
|
||
return make_response(jsonify({"task":task.to_dict()}),200) | ||
|
||
|
||
@tasks_bp.route("<task_id>/mark_incomplete", methods=["PATCH"]) | ||
def mark_incomplete(task_id): | ||
|
||
task = validate_model(Task, task_id) | ||
|
||
task.completed_at = None | ||
|
||
db.session.commit() | ||
|
||
return make_response(jsonify({"task":task.to_dict()}),200) | ||
|
||
@tasks_bp.route("/<task_id>", methods=["DELETE"]) | ||
def remove_one_task(task_id): | ||
|
||
task = validate_model(Task, task_id) | ||
|
||
db.session.delete(task) | ||
db.session.commit() | ||
|
||
return make_response({ | ||
"details": f'{Task.__name__} {task_id} "{task.title}" successfully deleted' | ||
}), 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 Maz, 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. |
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.
Like we validated models, could we also make a general function that validates request bodies? Here's an example:
We can pass in the
request_body
and a list of strings that arekeys
and then check to see if those keys are present.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.
Now we can have error handling and request body validation across all our routes more easily.