-
Notifications
You must be signed in to change notification settings - Fork 111
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
Sea Turtles - Shannon Bellemore - Task List API #116
base: master
Are you sure you want to change the base?
Changes from all commits
a24c58b
22f9d97
d420921
617a447
8b6bedb
fdd6c6f
b3c1688
792c307
71f4604
e624433
0a9bac6
40b6f03
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 |
---|---|---|
@@ -0,0 +1,105 @@ | ||
from flask import Blueprint, jsonify, abort, make_response, request | ||
from app import db | ||
from app.models.goal import Goal | ||
from datetime import datetime | ||
from app.models.task import Task | ||
from app.task_routes import validate_task | ||
|
||
goals_bp = Blueprint("goals_bp", __name__, url_prefix="/goals") | ||
|
||
# helper functions | ||
def validate_goal(goal_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. great validation and error handling. You can also move this function and other helper functions into a file |
||
try: | ||
goal_id = int(goal_id) | ||
except: | ||
abort(make_response({"error": f"Goal id invalid"}, 400)) | ||
|
||
goal = Goal.query.get(goal_id) | ||
if not goal: | ||
abort(make_response({"error":f"Goal not found"}, 404)) | ||
return goal | ||
|
||
|
||
# ----------------- END POINTS ------------------- | ||
|
||
# creates new goal to the database | ||
@goals_bp.route("", methods=["POST"]) | ||
def create_goal(): | ||
request_body = request.get_json() | ||
if "title" not in request_body: | ||
return make_response(jsonify({"details": "Invalid data"}), 400) | ||
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. you can add more specific details |
||
|
||
new_goal = Goal( | ||
title=request_body["title"]) | ||
|
||
db.session.add(new_goal) | ||
db.session.commit() | ||
response_body = { | ||
"goal": new_goal.make_dict()} | ||
return make_response(jsonify(response_body), 201) | ||
|
||
|
||
# get all saved goals | ||
@goals_bp.route("", methods=["GET"]) | ||
def get_all_goals(): | ||
goals = Goal.query.all() | ||
response_body = [goal.make_dict() for goal in goals] | ||
return make_response(jsonify(response_body), 200) | ||
|
||
|
||
# get one goal by task id | ||
@goals_bp.route("/<goal_id>", methods=["GET"]) | ||
def get_one_goal(goal_id): | ||
goal = validate_goal(goal_id) | ||
response_body = {"goal": goal.make_dict()} | ||
return make_response(jsonify(response_body), 200) | ||
|
||
|
||
# update goal | ||
@goals_bp.route("/<goal_id>", methods=["PUT"]) | ||
def update_goal(goal_id): | ||
request_body = request.get_json() | ||
goal = validate_goal(goal_id) | ||
goal.title = request_body["title"] | ||
db.session.commit() | ||
response_body = {"goal": goal.make_dict()} | ||
return make_response(jsonify(response_body), 200) | ||
|
||
|
||
# delete goal | ||
@goals_bp.route("/<goal_id>", methods=["DELETE"]) | ||
def delete_goal(goal_id): | ||
goal = validate_goal(goal_id) | ||
db.session.delete(goal) | ||
db.session.commit() | ||
response_body = { | ||
"details":f'Goal {goal.goal_id} "{goal.title}" successfully deleted'} | ||
return make_response(jsonify(response_body), 200) | ||
|
||
|
||
# adds tasks to a goal | ||
@goals_bp.route("/<goal_id>/tasks", methods=["POST"]) | ||
def add_task_to_goal(goal_id): | ||
goal = validate_goal(goal_id) | ||
request_body = request.get_json() | ||
task_ids = request_body["task_ids"] | ||
for task_id in task_ids: | ||
task = validate_task(task_id) | ||
task.goal_id = goal_id | ||
db.session.commit() | ||
|
||
response_body = {"id": goal.goal_id, "task_ids": task_ids} | ||
return make_response(jsonify(response_body), 200) | ||
|
||
|
||
# get tasks of a goal | ||
@goals_bp.route("/<goal_id>/tasks", methods=["GET"]) | ||
def get_tasks_of_goal(goal_id): | ||
goal = validate_goal(goal_id) | ||
tasks = goal.tasks | ||
list_of_tasks = [] | ||
for task in tasks: | ||
list_of_tasks.append(task.make_dict()) | ||
|
||
response_body = {"id": goal.goal_id, "title": goal.title, "tasks": list_of_tasks} | ||
return make_response(jsonify(response_body), 200) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,12 @@ | |
|
||
|
||
class Goal(db.Model): | ||
goal_id = db.Column(db.Integer, primary_key=True) | ||
goal_id = db.Column(db.Integer, primary_key=True, autoincrement=True) | ||
title = db.Column(db.String) | ||
tasks = db.relationship("Task", back_populates="goal", lazy=True) | ||
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. Because the default value for lazy is True you could technically leave it off. More info here https://docs.sqlalchemy.org/en/14/orm/loading_relationships.html |
||
|
||
def make_dict(self): | ||
return { | ||
"id": self.goal_id, | ||
"title": self.title, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,26 @@ | |
|
||
|
||
class Task(db.Model): | ||
task_id = db.Column(db.Integer, primary_key=True) | ||
task_id = db.Column(db.Integer, primary_key=True, autoincrement=True) | ||
title = db.Column(db.String, nullable=False) | ||
description = db.Column(db.String, nullable=False) | ||
completed_at = db.Column(db.DateTime) | ||
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. you could add nullable=True here because completed_at doesn't have to exist |
||
goal_id = db.Column(db.Integer, db.ForeignKey("goal.goal_id")) | ||
goal = db.relationship("Goal", back_populates="tasks") | ||
|
||
def make_dict(self): | ||
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. Because this is repetitive you could create a response dictionary to check for goal_id something like response_dict = dict(
id=self.task_id,
title=self.title,
description=self.description,
is_complete= bool(self.completed_at)
)
if self.goal_id:
response_dict["goal_id"] = self.goal_id
return response_dict |
||
if self.goal_id: | ||
return { | ||
"id": self.task_id, | ||
"goal_id": self.goal_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": True if self.completed_at else False | ||
} | ||
else: | ||
return { | ||
"id": self.task_id, | ||
"title": self.title, | ||
"description": self.description, | ||
"is_complete": True if self.completed_at else False | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
from flask import Blueprint, jsonify, abort, make_response, request | ||
from app import db | ||
from app.models.task import Task | ||
from datetime import datetime | ||
import requests, os | ||
from dotenv import load_dotenv | ||
|
||
tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") | ||
|
||
# helper functions | ||
def validate_task(task_id): | ||
try: | ||
task_id = int(task_id) | ||
except: | ||
abort(make_response({"error": f"Task id invalid"}, 400)) | ||
|
||
task = Task.query.get(task_id) | ||
if not task: | ||
abort(make_response({"error":f"Task not found"}, 404)) | ||
return task | ||
|
||
def slack_bot(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. 🙌🏽 |
||
load_dotenv() | ||
message = f"Someone just completed the task {task.title}" | ||
slack_url = "https://slack.com/api/chat.postMessage" | ||
header = {'Authorization': f'Bearer {os.environ.get("SLACK_API_TOKEN")}'} | ||
param = {"channel": "task-notifications", | ||
"text": message} | ||
|
||
return requests.post(url=slack_url, params=param, headers=header) | ||
|
||
|
||
# ----------------- END POINTS ------------------- | ||
|
||
# creates new task to the database | ||
@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: | ||
return make_response(jsonify({"details": "Invalid data"}), 400) | ||
|
||
if "completed_at" in request_body: | ||
new_task = Task( | ||
title=request_body["title"], | ||
description=request_body["description"], | ||
completed_at=request_body["completed_at"]) | ||
else: | ||
new_task = Task( | ||
title=request_body["title"], | ||
description=request_body["description"]) | ||
Comment on lines
+43
to
+51
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. How could you use something like a ternary or conditional on line 47 instead of an if/else? |
||
|
||
db.session.add(new_task) | ||
db.session.commit() | ||
response_body = {"task": new_task.make_dict()} | ||
return make_response(jsonify(response_body), 201) | ||
|
||
|
||
# get all saved tasks | ||
@tasks_bp.route("", methods=["GET"]) | ||
def get_all_tasks(): | ||
title_query = request.args.get("title") | ||
description_query = request.args.get("description") | ||
sort_query = request.args.get("sort") | ||
|
||
if sort_query == "desc": | ||
tasks = Task.query.order_by(Task.title.desc()) | ||
elif sort_query == "asc": | ||
tasks = Task.query.order_by(Task.title.asc()) | ||
elif title_query: | ||
tasks = Task.query.filter_by(title=title_query) | ||
elif description_query: | ||
tasks = Task.query.filter_by(description=description_query) | ||
else: | ||
tasks = Task.query.all() | ||
|
||
response_body = [task.make_dict() for task in tasks] | ||
return make_response(jsonify(response_body), 200) | ||
|
||
|
||
# get one task by task id | ||
@tasks_bp.route("/<task_id>", methods=["GET"]) | ||
def get_one_task(task_id): | ||
task = validate_task(task_id) | ||
response_body = { | ||
"task": task.make_dict()} | ||
return make_response(jsonify(response_body), 200) | ||
|
||
|
||
# update a task | ||
@tasks_bp.route("/<task_id>", methods=["PUT"]) | ||
def update_task(task_id): | ||
task = validate_task(task_id) | ||
request_body = request.get_json() | ||
task.title = request_body["title"] | ||
task.description = request_body["description"] | ||
|
||
db.session.commit() | ||
response_body = {"task": task.make_dict()} | ||
return make_response(jsonify(response_body), 200) | ||
|
||
# delete a task | ||
@tasks_bp.route("/<task_id>", methods=["DELETE"]) | ||
def delete_task(task_id): | ||
task = validate_task(task_id) | ||
|
||
db.session.delete(task) | ||
db.session.commit() | ||
|
||
response_body = { | ||
"details":f'Task {task.task_id} "{task.title}" successfully deleted'} | ||
return make_response(jsonify(response_body), 200) | ||
|
||
|
||
# patches a task to mark as complete | ||
@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"]) | ||
def mark_task_as_complete(task_id): | ||
task = validate_task(task_id) | ||
task.completed_at = datetime.utcnow() | ||
|
||
db.session.commit() | ||
slack_bot(task) | ||
response_body = {"task": task.make_dict()} | ||
return make_response(jsonify(response_body), 200) | ||
|
||
|
||
# patches a task to mark as incomplete | ||
@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"]) | ||
def mark_task_as_incomplete(task_id): | ||
task = validate_task(task_id) | ||
task.completed_at = None | ||
|
||
db.session.commit() | ||
response_body = {"task": task.make_dict()} | ||
return make_response(jsonify(response_body), 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.
I like that you organized tasks and goals into two separate files. You can also move them to a directory called
routes
to make it even more organized.