Skip to content

Phoenix-C22-Aksana #23

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 11 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
4 changes: 4 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from flask import Flask
from .routes.task_routes import tasks_bp
from .routes.goal_routes import goals_bp
from .db import db, migrate
from .models import task, goal
import os
Expand All @@ -18,5 +20,7 @@ def create_app(config=None):
migrate.init_app(app, db)

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

return app
8 changes: 7 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm import Mapped, mapped_column,relationship
from ..db import db
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .task import Task

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

41 changes: 40 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db import db
from datetime import datetime
from sqlalchemy import ForeignKey
from typing import Optional
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .goal import Goal


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

def to_dict(self):
task_as_dict = {}
task_as_dict["id"] = self.id
task_as_dict["title"] = self.title
task_as_dict["description"] = self.description
task_as_dict["is_complete"] = bool(self.completed_at)
#task_as_dict["goal_id"] = self.goal.id if self.goal else None

if self.goal:
task_as_dict["goal_id"] = self.goal.id

return task_as_dict

@classmethod
def from_dict(cls, task_data):
# Use get() to fetch values that could be undefined to avoid raising an error
goal_id = task_data.get("goal_id")

new_task = cls(
title=task_data["title"],
description=task_data["description"],
goal_id=goal_id
)

return new_task

Empty file added app/routes/__init__.py
Empty file.
131 changes: 130 additions & 1 deletion app/routes/goal_routes.py
Original file line number Diff line number Diff line change
@@ -1 +1,130 @@
from flask import Blueprint
from flask import Blueprint, abort, make_response, request, Response
from app.models.goal import Goal
from app.models.task import Task
from app.routes.task_routes import validate_task_id
from ..db import db

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

@goals_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

@goals_bp.get("/<goal_id>")
def get_one_goal(goal_id):
goal = validate_goal_id(goal_id)

return {"goal":{
"id": goal.id,
"title": goal.title}
}

def validate_goal_id(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

@goals_bp.post("")
def create_goal():
request_body = request.get_json()

title = request_body.get("title")

if title is None:

response = {"details": "Invalid data"}
return response, 400

new_goal = Goal(title=title)
db.session.add(new_goal)
db.session.commit()

response = {
"goal":{
"id": new_goal.id,
"title": new_goal.title}
}
return response, 201

@goals_bp.put("/<goal_id>")
def update_goal(goal_id):
goal = validate_goal_id(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

@goals_bp.delete("/<goal_id>")
def delete_goal(goal_id):
goal = validate_goal_id(goal_id)

db.session.delete(goal)
db.session.commit()

response_message = f'Goal {goal.id} "{goal.title}" successfully deleted'
response_body = {'details': response_message}

return response_body, 200

@goals_bp.post("/<goal_id>/tasks")
def create_goal_with_tasks(goal_id):
goal = validate_goal_id(goal_id)
request_body = request.get_json()

task_list = request_body["task_ids"]
task_list_in_goal = []
for task_id in task_list:

task = validate_task_id(task_id)
task.goal_id = goal.id
task_list_in_goal.append(task.id)

db.session.commit()
response = {
"id": goal.id,
"task_ids": task_list_in_goal}

return response, 200

@goals_bp.get("/<goal_id>/tasks")
def get_goal_with_tasks(goal_id):
goal = validate_goal_id(goal_id)

tasks = [task.to_dict() for task in goal.tasks]
print(f"TEST{tasks}")

response = {"id": goal.id,
"title": goal.title,
"tasks": tasks}


return response

150 changes: 149 additions & 1 deletion app/routes/task_routes.py
Original file line number Diff line number Diff line change
@@ -1 +1,149 @@
from flask import Blueprint
from flask import Blueprint, abort, make_response, request, Response
from app.models.task import Task
from app.models.goal import Goal
from ..db import db
from sqlalchemy import asc, desc
from datetime import datetime, date
import requests
import os
from dotenv import load_dotenv

load_dotenv()

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

@tasks_bp.get("")
def get_all_tasks():
query = db.select(Task)
sort_order = request.args.get("sort", "asc")
if sort_order == "desc":
query = query.order_by(Task.title.desc())
else:
query = query.order_by(Task.title)
tasks = db.session.scalars(query)
tasks_response = []
for task in tasks:
tasks_response.append(task.to_dict())

return tasks_response, 200

@tasks_bp.get("/<task_id>")
def get_one_task(task_id):
task = validate_task_id(task_id)
task_dict = task.to_dict()

return { "task": task_dict }


def validate_task_id(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

@tasks_bp.post("")
def create_task():
request_body = request.get_json()

title = request_body.get("title")
description = request_body.get("description")
completed_at = request_body.get("completed_at")

if title is None or description is None:

response = {"details": "Invalid data"}
return response, 400

new_task = Task(title=title, description=description, completed_at=completed_at)
db.session.add(new_task)
db.session.commit()

response = {
"task":{
"id": new_task.id,
"title": new_task.title,
"description": new_task.description,
"is_complete": bool(new_task.completed_at)}
}
return response, 201

@tasks_bp.put("/<task_id>")
def update_task(task_id):
task = validate_task_id(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": bool(task.completed_at)}
}

return response, 200

@tasks_bp.delete("/<task_id>")
def delete_task(task_id):
task = validate_task_id(task_id)

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

response_message = f'Task {task.id} "{task.title}" successfully deleted'
response_body = {'details': response_message}

return response_body, 200

@tasks_bp.patch("/<task_id>/mark_complete")
def mark_task_complete(task_id):
task = validate_task_id(task_id)
#now = datetime.now()
#task.competed_at = now.strftime("%m/%d/%Y")
task.completed_at = date.today()
db.session.commit()

url = 'https://slack.com/api/chat.postMessage'
slack_token = os.environ.get('API_token')
headers = {'Authorization': f'Bearer {slack_token}'}
payload = {'channel': 'task-notifications', 'text': f'Someone just completed the task {task.title}'}


response = requests.post(url, headers=headers, data=payload)

response = {
"task":{
"id": task.id,
"title": task.title,
"description": task.description,
"is_complete": bool(task.completed_at)}
}
return response, 200

@tasks_bp.patch("/<task_id>/mark_incomplete")
def mark_task_incomplete(task_id):
task = validate_task_id(task_id)
task.completed_at = None
db.session.commit()
response = {
"task":{
"id": task.id,
"title": task.title,
"description": task.description,
"is_complete": bool(task.completed_at)}
}

return response, 200
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