Skip to content
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

Panthers - Ghameerah #139

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
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()'
6 changes: 6 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,11 @@ def create_app(test_config=None):
migrate.init_app(app, db)

# Register Blueprints here
from app.routes.task_routes import tasks_bp, root_bp
from app.routes.goal_routes import goals_bp

app.register_blueprint(tasks_bp)
app.register_blueprint(goals_bp)
app.register_blueprint(root_bp)

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

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")

def to_json(self):
return {
"id": self.goal_id,
"title": self.title,
}
36 changes: 35 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
from app import db

from flask import abort, make_response

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_json(self):
task_dict = {
"id": self.task_id,
"title": self.title,
"description": self.description,
"is_complete": True if self.completed_at else False
}

if self.goal_id:
task_dict["goal_id"] = self.goal_id

return task_dict

@classmethod
def from_dict(cls, req_body):
return cls(
title=req_body["title"],
description=req_body["description"],
completed_at=req_body.get("completed_at")
)

def update(self, req_body):
try:
self.title = req_body["title"]
self.description = req_body["description"]
self.completed_at = req_body.get("completed_at")
except KeyError as error:
abort(make_response({'message': f"Missing attribute: {error}"}))
1 change: 0 additions & 1 deletion app/routes.py

This file was deleted.

130 changes: 130 additions & 0 deletions app/routes/goal_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import os
from app import db
import requests
from datetime import date
from app.models.goal import Goal
from app.models.task import Task
from app.routes.routes_helpers import *
from flask import Blueprint, jsonify, make_response, request

goals_bp = Blueprint('goals_bp', __name__, url_prefix='/goals')

# Index
@goals_bp.route("", methods=["GET", "POST"])
def handle_goals():
# Get all goals
if request.method == "GET":
goals = Goal.query.all()
goals_response = [goal.to_json() for goal in goals]

return jsonify(goals_response), 200

# Create a new goal
elif request.method == "POST":
request_body = request.get_json()
new_goal = {}

if not "title" in request_body:
return jsonify({
"details": "Invalid data"
}), 400

try:
new_goal = Goal(title=request_body["title"])
except KeyError:
return (f"Invalid data", 400)

# Add this new instance of goal to the database
db.session.add(new_goal)
db.session.commit()

# Successful response
return {
"goal": new_goal.to_json()
}, 201

# Path/Endpoint to get a single goal
# Include the id of the record to retrieve as a part of the endpoint
@goals_bp.route("/<goal_id>", methods=["GET", "PUT", "DELETE"])

# GET /goal/id
def handle_goal(goal_id):
# Query our db to grab the goal that has the id we want:
goal = Goal.query.get(goal_id)

if not goal:
return {"message": f"Goal {goal_id} not found"}, 404

# Show a single goal
if request.method == "GET":
goal = get_record_by_id(Goal, goal_id)

return {
"goal": goal.to_json()
}, 200

# Update a goal
elif request.method == "PUT":
request_body = request.get_json()

goal.title = request_body["title"]

# Update this goal in the database
db.session.commit()

# Successful response
return {
"goal": goal.to_json()
}, 200

# Delete a goal
elif request.method == "DELETE":
db.session.delete(goal)
db.session.commit()

return {
"details": f'Goal {goal.goal_id} \"{goal.title}\" successfully deleted',
}, 202

# Connects a goal to a task
# /goals/<goal_id>/tasks

@goals_bp.route("/<goal_id>/tasks", methods=["GET", "POST"])
def handle_goals_tasks(goal_id):
if request.method == "GET":
goal = Goal.query.get(goal_id)

if not goal:
return {"message": f"Goal {goal_id} not found"}, 404

task_list = [task.to_json() for task in goal.tasks]

goal_dict = goal.to_json()
goal_dict["tasks"] = task_list

print(goal_id)
return jsonify(goal_dict)

elif request.method == "POST":
goal = get_record_by_id(Goal, goal_id)

if not goal:
return {"message": f"Goal {goal_id} not found"}, 404

request_body = request.get_json()

for task_id in request_body["task_ids"]:
task = get_record_by_id(Task, task_id)
task.goal_id = goal_id
task.goal = goal

db.session.commit()

task_ids = []
for task in goal.tasks:
task_ids.append(task.task_id)

return {
'id': goal.goal_id,
"task_ids": task_ids
}, 200
16 changes: 16 additions & 0 deletions app/routes/routes_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from flask import jsonify, abort, make_response

def error_message(message, status_code):
abort(make_response(jsonify(dict(details=message)), status_code))

def get_record_by_id(cls, task_id):
try:
task_id = int(task_id)
except ValueError:
error_message(f"Invalid id {task_id}", 404)

model = cls.query.get(task_id)
if model:
return model

error_message(f"No model of type {cls.__name__} with id {task_id} found", 404)
141 changes: 141 additions & 0 deletions app/routes/task_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import os
from app import db
import requests
from datetime import date
from app.models.task import Task
from app.routes.routes_helpers import *
from flask import Blueprint, jsonify, make_response, request

tasks_bp = Blueprint('tasks_bp', __name__, url_prefix='/tasks')
root_bp = Blueprint("root_bp", __name__)

SLACK_API_URL = "https://slack.com/api/chat.postMessage"
SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"]

# Home page
@root_bp.route("/", methods=["GET"])
def root():
return {
"name": "Ghameerah's Task List API",
"message": "Fun with Flask",
}

# Index
@tasks_bp.route("", methods=["GET", "POST"])
def handle_tasks():
# Get all tasks
if request.method == "GET":
task_query = Task.query

sort = request.args.get("sort")
if sort == "desc":
task_query = task_query.order_by(Task.title.desc())
elif sort == "asc":
task_query = task_query.order_by(Task.title.asc())

tasks = task_query.all()
tasks_response = []
tasks_response = [task.to_json() for task in tasks]

return jsonify(tasks_response), 200

# Create a new task
elif request.method == "POST":
request_body = request.get_json()

if "title" not in request_body or "description" not in request_body:
return make_response({"details": "Invalid data"}), 400
else:
new_task = Task(
title=request_body["title"],
description=request_body["description"])

db.session.add(new_task)
db.session.commit()

return {"task": new_task.to_json()}, 201

# Path/Endpoint to get a single task
# Include the id of the record to retrieve as a part of the endpoint
@tasks_bp.route("/<task_id>", methods=["GET", "PUT", "DELETE"])

# GET /task/id
def handle_task(task_id):
# Query our db to grab the task that has the id we want:
task = Task.query.get(task_id)

# Show a single task
if request.method == "GET":
task = get_record_by_id(Task, task_id)
return {
"task": task.to_json()
}, 200

# Update a task
elif request.method == "PUT":
task = get_record_by_id(Task, task_id)

request_body = request.get_json()

task.update(request_body)

# Update this task in the database
db.session.commit()

# Successful response
return {
"task": task.to_json()
}, 200

# Delete a task
elif request.method == "DELETE":
task = get_record_by_id(Task, task_id)

db.session.delete(task)
db.session.commit()

return {
"details": f'Task {task.task_id} \"{task.title}\" successfully deleted',
}, 200

# PATCH /task/id/mark_complete
@tasks_bp.route("/<task_id>/mark_complete", methods=["PATCH"])

def mark_complete_task(task_id):
task = get_record_by_id(Task, task_id)
task.completed_at = date.today()

headers = {
"Authorization": f"Bearer {SLACK_BOT_TOKEN}",
}

if task.completed_at:
data = {
"channel": "task-notifications",
"text": f"Task {task.title} has been marked complete",
}
else:
data = {
"channel": "task-notifications",
"text": f"Task {task.title} has been marked incomplete",
}

requests.post(SLACK_API_URL, headers=headers, data=data)

db.session.commit()

return {
"task": task.to_json()
}

# PATCH /task/id/mark_incomplete
@tasks_bp.route("/<task_id>/mark_incomplete", methods=["PATCH"])
def mark_incomplete_task(task_id):
task = get_record_by_id(Task, task_id)
task.completed_at = None

db.session.commit()

return {
"task": task.to_json()
}
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.
Loading