Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: gunicorn 'app:create_app()'
12 changes: 11 additions & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
migrate = Migrate()
load_dotenv()

SLACK_TOKEN = os.environ.get("SLACK_TOKEN")


def create_app(test_config=None):
app = Flask(__name__)
Expand All @@ -28,7 +30,15 @@ def create_app(test_config=None):

db.init_app(app)
migrate.init_app(app, db)

from .routes.goal_routes import goal_bp
app.register_blueprint(goal_bp)

# Register Blueprints here

# Register Blueprints here
from .routes.task_routes import task_bp
app.register_blueprint(task_bp)

return app


9 changes: 9 additions & 0 deletions app/models/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,12 @@

class Goal(db.Model):
goal_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
tasks = db.relationship("Task", backref='goal', lazy=True)

def to_dict(self):
return {
"id": self.goal_id,
"title": self.title,

}
24 changes: 23 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,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)
description = db.Column(db.String)
completed_at = db.Column(db.DateTime, nullable=True)
is_complete = db.Column(db.Boolean)
# completed_at can be empty/null which means that a task is not completed
goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id'), nullable = True)

def to_dict(self):
if not self.completed_at:
self.is_complete = False
else:
self.is_complete = True
dictionary = {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": self.is_complete
}
if self.goal_id:
dictionary["goal_id"] = self.goal_id

return dictionary
2 changes: 0 additions & 2 deletions app/routes.py

This file was deleted.

Empty file added app/routes/__init__.py
Empty file.
95 changes: 95 additions & 0 deletions app/routes/goal_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from app.models.goal import Goal
from flask import jsonify
from flask import Blueprint, make_response, request, jsonify, abort
from app import db
from app.models.task import Task


#helper functions
goal_bp = Blueprint("goal", __name__,url_prefix="/goals")
def valid_int(number, parameter_type):
try:
int(number)
except:
abort(make_response({"error": f"{parameter_type} must be an int"})), 400

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that you included a parameter name in the message!


def get_goal_from_id(goal_id):
valid_int(goal_id, "goal_id")
return Goal.query.get_or_404(goal_id, description="{goal not found}")
# get all goal

@goal_bp.route("", methods=["GET", "POST"])
def handle_goals():
if request.method == "GET":
goals = Goal.query.all()
goals_response = []
for goal in goals:
goal = goal.to_dict()
goals_response.append(goal)
return jsonify(goals_response), 200

#write query to fetch all goals


elif request.method == "POST":
request_body = request.get_json()
if not "title" in request_body:
return jsonify({"details":"Invalid data"}), 400
new_goal = Goal(title=request_body["title"])

db.session.add(new_goal)
db.session.commit()
return jsonify({"goal": new_goal.to_dict()}), 201



@goal_bp.route("/<goal_id>", methods=["GET", "PUT", "DELETE"])
def handle_goal(goal_id):
goal_id = int(goal_id)
goal = Goal.query.get(goal_id)
if goal is None:
return make_response("", 404)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's helpful to provide detailed error messages for debugging:

Suggested change
return make_response("", 404)
return make_response({"error": f"Could not find goal #{goal_id}"}, 404)

if request.method == "GET":

return jsonify({"goal": goal.to_dict()}), 200

elif request.method == "PUT":
request_body = request.get_json()

goal.title = request_body["title"]

db.session.commit()
return jsonify({"goal": goal.to_dict()}), 200

elif request.method == "DELETE":
db.session.delete(goal)
db.session.commit()
return jsonify({"details":f'Goal {goal.goal_id} "{goal.title}" successfully deleted'}), 200

###WAVE 6 routes###
@goal_bp.route("/<goal_id>/tasks", methods=["POST"])
def create_one_to_many(goal_id):
request_body = request.get_json()
goal = Goal.query.get(goal_id)
task_ids = request_body["task_ids"]
for task_id in task_ids:
task=Task.query.get(task_id)
goal.tasks.append(task) #list of task objects

db.session.commit()
return jsonify({"id": goal.goal_id,
"task_ids":[task.task_id for task in goal.tasks]}), 200

@goal_bp.route("/<goal_id>/tasks", methods=["GET"])
def get_task_for_goal(goal_id):
# request_body = request.get_json()
goal = Goal.query.get(goal_id)
if goal is None:
return make_response("", 404)

db.session.commit()
return jsonify({
"id": goal.goal_id,
"title": goal.title,
"tasks": [task.to_dict() for task in goal.tasks]
}), 200
121 changes: 121 additions & 0 deletions app/routes/task_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from app.models.task import Task
from flask import jsonify
from flask import Blueprint, make_response, request, jsonify, abort
from app import db, SLACK_TOKEN
from datetime import datetime
import requests


#helper functions
task_bp = Blueprint("task", __name__,url_prefix="/tasks")
def valid_int(number, parameter_type):
try:
int(number)
except:
abort(make_response({"error": f"{parameter_type} must be an int"})), 400

def get_task_from_id(task_id):
valid_int(task_id, "task_id")
return Task.query.get_or_404(task_id, description="{task not found}")
# get all tasks

@task_bp.route("", methods=["GET", "POST"])
def handle_tasks():
if request.method == "GET":

#write query to fetch all tasks
sort_query = request.args.get("sort") ###WAVE 2###

if sort_query == "asc":
tasks = Task.query.order_by(Task.title.asc())
elif sort_query == "desc":
tasks = Task.query.order_by(Task.title.desc())
else:
tasks = Task.query.all()
tasks_response = [task.to_dict() for task in tasks]
return jsonify(tasks_response), 200

elif request.method == "POST":
request_body = request.get_json()
if not "title" in request_body or not "description" in request_body or not "completed_at" in request_body:
return jsonify({"details":"Invalid data"}), 400
new_task = Task(title=request_body["title"],
description=request_body["description"],
completed_at=request_body["completed_at"],

)

db.session.add(new_task)
db.session.commit()
return jsonify({"task": new_task.to_dict()}), 201



@task_bp.route("/<task_id>", methods=["GET", "PUT", "DELETE"])
def handle_task(task_id):
task_id = int(task_id)
task = Task.query.get(task_id)
if task is None:
return make_response("", 404)
if request.method == "GET":

return jsonify({"task": task.to_dict()}), 200

elif request.method == "PUT":
request_body = request.get_json()

task.title = request_body["title"]
task.description = request_body["description"]

db.session.commit()
return jsonify({"task": task.to_dict()}), 200

elif request.method == "DELETE":
db.session.delete(task)
db.session.commit()
return jsonify({"details":f'Task {task.task_id} "{task.title}" successfully deleted'}), 200

##WAVE 4 Slack Helper Function###
def post_complete_task_to_slack(task):
url = "https://slack.com/api/chat.postMessage"
message = f"Someone just completed the task {task.title}"
query_params = {
"token": SLACK_TOKEN,
"channel": 'task-list-api',
"text" : message
}
return requests.post(url, data=query_params).json()

##wave 3 complete/incomplete##
@task_bp.route("/<task_id>/mark_complete", methods=["PATCH"])
def update_task_completion(task_id):
task= get_task_from_id(task_id)
task.is_complete=True
task.completed_at = datetime.now()
db.session.commit()
post_complete_task_to_slack(task)
return jsonify({"task": task.to_dict()}), 200


@task_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"])
def update_task_incomplete(task_id):
task= get_task_from_id(task_id)
task.is_complete=False
task.completed_at = None
db.session.commit()
return jsonify({"task": task.to_dict()}), 200

# @task_bp.route("/<task_id>", methods=["GET"])
# def handle_task(task_id):
# task_id = int(task_id)
# task = Task.query.get(task_id)
# if not task:
# return make_response(f"Task {task_id} Bad data", 400)

# if request.method == GET

# for task in tasks:
# if task.id == task_id:
# return vars(task)

# return "Not found", 404
Comment on lines +108 to +121

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style: Clean up commented out code.

1 change: 1 addition & 0 deletions migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generic single-database configuration.
45 changes: 45 additions & 0 deletions migrations/alembic.ini
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
Loading