Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
21b0932
implemented Task model in task.py
c-dubois May 9, 2025
c79e610
implemented validate_model in routes_utilties.py
c-dubois May 9, 2025
b6bc10f
implemented create_model in routes_utilties.py
c-dubois May 9, 2025
67c11ab
impleted routes in task_routes.py
c-dubois May 9, 2025
21c24a5
registered task bp, updated task routes, made completed_at field opti…
c-dubois May 9, 2025
0c1065f
implemented asc and desc sorting endpoints for tasks
c-dubois May 22, 2025
f9e99bd
wave 1 tests passing, refactored create model
c-dubois May 22, 2025
9514d61
wave 2 tests passing
c-dubois May 22, 2025
70a2c60
implemented mark_task_complete and mark_task_incomplete routes
c-dubois May 22, 2025
64c9ec2
wave 3 tests passing
c-dubois May 22, 2025
93a3718
integrated slackbot api with mark_complete endpoint
c-dubois May 27, 2025
a7e5126
implemented Goal model
c-dubois May 27, 2025
170e743
registered goals bp in init.py
c-dubois May 27, 2025
b63a6b9
implemented create goal and get goals
c-dubois May 27, 2025
9306b12
implemented get/put/delete by goal_id
c-dubois May 27, 2025
3ba4fc0
completed wave 5 tests, all wave 5 tests passing
c-dubois May 28, 2025
5a9e2b4
updated goal and task models to a one-to-many relationship
c-dubois May 28, 2025
63b7b9e
updated goal and task models to a one-to-many relationship
c-dubois May 28, 2025
247e3d7
implemented get_all_tasks_under_goal
c-dubois May 28, 2025
eeddee1
all wave 6 tests passing
c-dubois May 28, 2025
0e77f9e
fixed syntax issue in route utils
c-dubois May 28, 2025
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
2 changes: 1 addition & 1 deletion ada-project-docs/wave_04.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Making an app should bring you to a new page that lists "Add features and functi
After seeing the header "Add features and functionality" and expanding it, click "Permissions."

![](assets/slackbot_scroll_to_permissions.png)

k
Scroll down to "Scopes" and "Bot Token Scopes"

![](assets/slackbot_scopes_empty.png)
Expand Down
4 changes: 4 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from flask import Flask
from .db import db, migrate
from .models import task, goal
from .routes.task_routes import bp as tasks_bp
from .routes.goal_routes import bp as goals_bp
import os

def create_app(config=None):
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
18 changes: 17 additions & 1 deletion app/models/goal.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
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")

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

@classmethod
def from_dict(cls, goal_data):
goal = cls(title=goal_data["title"])
return goal
34 changes: 33 additions & 1 deletion app/models/task.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
from sqlalchemy.orm import Mapped, mapped_column
from datetime import datetime
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy import DateTime, ForeignKey
from ..db import db
from typing import Optional, 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[datetime | None] = mapped_column(DateTime, nullable=True)
goal_id: Mapped[Optional[int]] = mapped_column(ForeignKey("goal.id"), nullable=True)
goal: Mapped[Optional["Goal"]] = relationship(back_populates="tasks")

def to_dict(self):
task_dict = {
"id": self.id,
"title": self.title,
"description": self.description,
"is_complete": self.completed_at is not None,
}
if self.goal:
task_dict["goal_id"] = self.goal.id

return task_dict


@classmethod
def from_dict(cls, task_data):
goal_id = task_data.get("goal_id")
task = cls(title=task_data["title"],
description=task_data["description"],
completed_at=task_data.get("completed_at"),
goal_id = goal_id)
return task
98 changes: 97 additions & 1 deletion app/routes/goal_routes.py
Original file line number Diff line number Diff line change
@@ -1 +1,97 @@
from flask import Blueprint
from flask import Blueprint, Response, abort, make_response, request
from app.models.goal import Goal
from app.models.task import Task
from app.routes.routes_utilities import validate_model, create_model
from ..db import db

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


@bp.post("")
def create_goal():
request_body = request.get_json()
return create_model(Goal, request_body)

@bp.get("")
def get_goals():
query = db.select(Goal)

title_param = request.args.get("title")
if title_param:
query = query.where(Goal.title.ilike(f"%{title_param}%"))

sort_param = request.args.get("sort")
if sort_param == "asc":
query = query.order_by(Goal.title.asc())
elif sort_param == "desc":
query = query.order_by(Goal.title.desc())
else:
query = query.order_by(Goal.id)

goals = db.session.scalars(query)

goals_response = []
for goal in goals:
goals_response.append(goal.to_dict())

return goals_response

@bp.get("/<goal_id>")
def get_one_goal(goal_id):
goal = validate_model(Goal, goal_id)

return {"goal": goal.to_dict()}

@bp.put("<goal_id>")
def update_goal(goal_id):
goal = validate_model(Goal, goal_id)
request_body = request.get_json()

if "title" in request_body:
goal.title = request_body["title"]

db.session.commit()
return "", 204

@bp.delete("/<goal_id>")
def delete_task(goal_id):
goal = validate_model(Goal, goal_id)

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

return "", 204

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

if "task_ids" not in request_body:
abort(make_response({"message": "Missing required field: task_ids"}, 400))

for task in goal.tasks:
task.goal_id = None

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

db.session.commit()

return {
"id": goal.id,
"task_ids": request_body["task_ids"]
}

@bp.get("/<goal_id>/tasks")
def get_all_tasks_under_goal(goal_id):
goal = validate_model(Goal, goal_id)

return {
"id": goal.id,
"title": goal.title,
"tasks": [task.to_dict() for task in goal.tasks]
}
39 changes: 39 additions & 0 deletions app/routes/routes_utilities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from flask import abort, make_response
from ..db import db

def validate_model(cls, model_id):
try:
model_id = int(model_id)
except ValueError:
invalid_response = {{"message": f"The {cls.__name__} with ID {model_id} is invalid."}}
abort(make_response(invalid_response, 400))

query = db.select(cls).where(cls.id == model_id)
model = db.session.scalar(query)

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

return model

def create_model(cls, model_data):
required_fields = {
"Task": ["title", "description"],
"Goal": ["title"]
}

missing_fields = [field for field in required_fields[cls.__name__] if field not in model_data]
if missing_fields:
abort(make_response({"details": f"Missing required field(s): {', '.join(missing_fields)}"}, 400))

try:
new_model = cls.from_dict(model_data)
except KeyError as error:
response = {"message": f"Invalid request: missing {error.args[0]}"}
abort(make_response(response, 400))

db.session.add(new_model)
db.session.commit()

return {f"{cls.__name__.lower()}": new_model.to_dict()}, 201
119 changes: 118 additions & 1 deletion app/routes/task_routes.py
Original file line number Diff line number Diff line change
@@ -1 +1,118 @@
from flask import Blueprint
from datetime import datetime
import requests
import os
from dotenv import load_dotenv
from flask import Blueprint, Response, abort, make_response, request
from app.models.task import Task
from app.routes.routes_utilities import validate_model, create_model
from ..db import db

load_dotenv()

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

@bp.post("")
def create_task():
request_body = request.get_json()
return create_model(Task, request_body)

@bp.get("")
def get_tasks():
query = db.select(Task)

title_param = request.args.get("title")
if title_param:
query = query.where(Task.title.ilike(f"%{title_param}%"))

description_param = request.args.get("description")
if description_param:
query = query.where(Task.description.ilike(f"%{description_param}%"))

completed_at_param = request.args.get("completed_at")
if completed_at_param:
query = query.where(Task.completed_at.ilike(f"%{completed_at_param}%"))

sort_param = request.args.get("sort")
if sort_param == "asc":
query = query.order_by(Task.title.asc())
elif sort_param == "desc":
query = query.order_by(Task.title.desc())
else:
query = query.order_by(Task.id)

tasks = db.session.scalars(query)

tasks_response = []
for task in tasks:
tasks_response.append(task.to_dict())

return tasks_response

@bp.get("/<task_id>")
def get_one_task(task_id):
task = validate_model(Task, task_id)

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

@bp.put("<task_id>")
def update_task(task_id):
task = validate_model(Task, task_id)
request_body = request.get_json()

if "title" in request_body:
task.title = request_body["title"]
if "description" in request_body:
task.description = request_body["description"]
if "completed_at" in request_body:
task.completed_at = request_body["completed_at"]

db.session.commit()
return "", 204
# Another option is to return this: Response(status=204, mimetype="application/json")

@bp.delete("/<task_id>")
def delete_task(task_id):
task = validate_model(Task, task_id)

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

return "", 204

@bp.patch("/<task_id>/mark_complete")
def mark_task_complete(task_id):
task = validate_model(Task, task_id)

task.completed_at = datetime.now()
db.session.commit()

slackbot_token = os.environ.get("SLACKBOT_TOKEN")
# if not slackbot_token:
# print("no token found")
# return "", 204

message_text = f"Someone just completed the task \"{task.title}\""
slack_url = "https://slack.com/api/chat.postMessage"
headers = {
"Authorization": f"Bearer {slackbot_token}"
}
payload = {
"channel": "test-slack-api",
"text": message_text
}

try:
response = requests.post(slack_url, headers=headers, json=payload)
except requests.exceptions.RequestException as e:
print(f"Failed to send Slack notification: {str(e)}")

return "", 204

@bp.patch("/<task_id>/mark_incomplete")
def mark_task_incomplete(task_id):
task = validate_model(Task, task_id)

task.completed_at = None
db.session.commit()

return "", 204
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