Skip to content

C22-Phoenix_Ajar D_Task_List_API #45

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

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
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
6 changes: 5 additions & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from flask import Flask
from .routes.task_routes import bp as tasks_bp
from .routes.goal_routes import bp as goals_bp
from .db import db, migrate
from .models import task, goal
import os

def create_app(config=None):
app = Flask(__name__)


app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('SQLALCHEMY_DATABASE_URI')
Expand All @@ -18,5 +20,7 @@ def create_app(config=None):
migrate.init_app(app, db)

# Register Blueprints here
app.register_blueprint(goals_bp)
app.register_blueprint(tasks_bp)

return app
17 changes: 16 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db import db

class Goal(db.Model):
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
title: Mapped[str]
tasks: Mapped[list['Task']] = relationship(back_populates='goal')

def to_dict(self):
return dict(
id=self.id,
title=self.title,
)

@classmethod
def from_dict(cls, goal_data):
new_goal = cls(
title=goal_data['title'])

return new_goal
29 changes: 28 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm import Mapped, mapped_column,relationship
from sqlalchemy import ForeignKey
from typing import Optional
from ..db import db
from datetime import datetime

class Task(db.Model):
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
title: Mapped[str]
description: Mapped[str]
completed_at: Mapped[str] = mapped_column(nullable=True)
goal_id: Mapped[Optional[int]] = mapped_column(ForeignKey('goal.id'))
goal: Mapped[Optional['Goal']] = relationship(back_populates='tasks')


def to_dict(self):
return dict(
id=self.id,
title=self.title,
description=self.description,
is_complete=bool(self.completed_at),
goal_id=self.goal.id if self.goal else None
)

@classmethod
def from_dict(cls, task_data):
new_task = cls(
title=task_data['title'],
description=task_data['description'],
goal_id=task_data.get('goal_id',None)
)
return new_task
130 changes: 129 additions & 1 deletion app/routes/goal_routes.py
Original file line number Diff line number Diff line change
@@ -1 +1,129 @@
from flask import Blueprint

from flask import Blueprint, abort, make_response, request
from app.models.goal import Goal
from app.models.task import Task
from .route_utilities import validate_model
from ..db import db

bp = Blueprint("goals_bp", __name__, url_prefix="/goals")
@bp.post("")
def create_goal():
request_body = request.get_json()
if "title" not in request_body:
response_body = {
"details": "Invalid data"
}
return response_body, 400

new_goal = Goal.from_dict(request_body)
db.session.add(new_goal)
db.session.commit()
response = {"goal": new_goal.to_dict()}
return response, 201


@bp.get("")
def get_all_goals():
query = db.select(Goal)
goals = db.session.scalars(query)
goals_response = []
for goal in goals:
goals_response.append(
{
"id": goal.id,
"title": goal.title,
}
)
return goals_response, 200

@bp.get("/<goal_id>")
def get_one_goal(goal_id):
goal = validate_goal(goal_id)
response_body = {"goal": goal.to_dict()}
return response_body

@bp.put("/<goal_id>")
def update_goal(goal_id):
goal = validate_goal(goal_id)
request_body = request.get_json()
goal.title = request_body["title"]

db.session.commit()
response = {
"goal": {
"id": goal.id,
"title": goal.title,

}
}
return response, 200

@bp.delete("/<goal_id>")
def delete_goal(goal_id):
goal = validate_goal(goal_id)
db.session.delete(goal)
db.session.commit()
response = {
"details": "Goal 1 \"Build a habit of going outside daily\" successfully deleted"
}

return response, 200

@bp.post("/<goal_id>/tasks")
def create_task_with_goal_id(goal_id):
goal = validate_model(Goal,goal_id)
request_body = request.get_json()

if 'title' in request_body and 'description' in request_body:
request_body['goal_id'] = goal.id
new_task = Task.from_dict(request_body)
db.session.add(new_task)
db.session.commit()
tasks = [task.id for task in goal.tasks]
response = {
"id": goal.id,
"task_ids": tasks,
}
return response


task_ids = []
for task_id in request_body['task_ids']:
task = validate_model(Task,task_id)
task_ids.append(task.id)
goal.tasks.append(task)

db.session.commit()

response = {
"id": goal.id,
"task_ids": task_ids,
}
return response,200

@bp.get("/<goal_id>/tasks")
def get_tasks_by_goal(goal_id):
goal = validate_model(Goal,goal_id)
tasks = [task.to_dict() for task in goal.tasks]
goal_dict = goal.to_dict()
response = {
"id": goal_dict["id"],
"title": goal_dict["title"],
"tasks": tasks
}
return response

def validate_goal(goal_id):
try:
goal_id = int(goal_id)
except:
response = {"message": f"Goal {goal_id} invalid"}
abort(make_response(response , 400))

query = db.select(Goal).where(Goal.id == goal_id)
goal = db.session.scalar(query)

if not goal:
response = {"message": f"goal {goal_id} not found"}
abort(make_response(response, 404))
return goal
17 changes: 17 additions & 0 deletions app/routes/route_utilities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from flask import abort, make_response
from ..db import db

def validate_model(cls,model_id):
try:
model_id = int(model_id)
except:
response = {"message": f"{cls.__name__} id {(model_id)} invalid"}
abort(make_response(response , 404))
query = db.select(cls).where(cls.id == model_id)
model = db.session.scalar(query)

if not model:
response = {"message": f"{cls.__name__} id {(model_id)} not found"}
abort(make_response(response , 404))

return model
141 changes: 140 additions & 1 deletion app/routes/task_routes.py
Original file line number Diff line number Diff line change
@@ -1 +1,140 @@
from flask import Blueprint
from flask import Blueprint, abort, make_response, request
from app.models.task import Task
from ..db import db
from datetime import datetime
from sqlalchemy import asc, desc
import os
import requests

bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks")

@bp.post("")
def create_task():
request_body = request.get_json()
if "title" not in request_body or "description" not in request_body:
response_body = {"details": "Invalid data"}
return make_response(response_body, 400)

new_task = Task.from_dict(request_body)
db.session.add(new_task)
db.session.commit()
task = normalize_task_response(new_task.to_dict())
response = {"task": task}
return response, 201

@bp.get("")
def get_all_tasks():
query = db.select(Task)
sort_param = request.args.get("sort")
if sort_param == "asc":
query = db.select(Task).order_by(asc((Task.title)))
if sort_param == "desc":
query = db.select(Task).order_by(desc((Task.title)))

tasks = db.session.scalars(query)
tasks_response = []
for task in tasks:
tasks_response.append(
{
"id": task.id,
"title": task.title,
"description": task.description,
"is_complete": is_complete(task.completed_at)
}
)
return tasks_response, 200

@bp.get("/<task_id>")
def get_one_task(task_id):
task = validate_task(task_id)
response_task = normalize_task_response(task.to_dict())
response_body = {"task": response_task}
return response_body

@bp.put("/<task_id>")
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 = {
"task": {
"id": task.id,
"title": task.title,
"description": task.description,
"is_complete": False
}
}
return response, 200

@bp.delete("/<task_id>")
def delete_task(task_id):
task = validate_task(task_id)
db.session.delete(task)
db.session.commit()
response = {
"details": "Task 1 \"Go on my daily walk 🏞\" successfully deleted"
}

return response, 200

@bp.patch("/<task_id>/mark_complete")
def complete_task(task_id):
task = validate_task(task_id)
task.completed_at = str(datetime.now())
db.session.commit()
key = os.environ.get('SLACK_TOKEN')
url = "https://slack.com/api/chat.postMessage"
data= {
"channel": "api-test-channel",
"text": "My beautiful Task"
}
headers = {
"Authorization": key
}
requests.post(url, data=data, headers=headers)

response = {
"task": {
"id": task.id,
"title": task.title,
"description": task.description,
"is_complete": True
}
}
return response, 200

@bp.patch("/<task_id>/mark_incomplete")
def update_not_completed_task(task_id):
task = validate_task(task_id)
task.completed_at = None
db.session.commit()
response_task = normalize_task_response(task.to_dict())
response_body = {"task": response_task}
return make_response(response_body, 200)


def is_complete(completed_at):
return False if completed_at is None else True

def normalize_task_response(task):
if task['goal_id']==None:
del task['goal_id']
return task

def validate_task(task_id):
try:
task_id = int(task_id)
except:
response = {"message": f"Task {task_id} invalid"}
abort(make_response(response , 400))

query = db.select(Task).where(Task.id == task_id)
task = db.session.scalar(query)

if not task:
response = {"message": f"task {task_id} not found"}
abort(make_response(response, 404))
return task
1 change: 1 addition & 0 deletions migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Single-database configuration for Flask.
Loading