-
Notifications
You must be signed in to change notification settings - Fork 44
C22 Sphinx - Weixi He #36
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
base: main
Are you sure you want to change the base?
Changes from all commits
f091e53
cf1b4d1
4b9687e
9f58475
83fa854
fac5994
ed8f9ba
0839c41
12e18e7
d1ef313
62841f1
03c2d6f
37d4156
55cefa3
32c9518
35f9714
9a66616
f1d7d17
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,23 @@ | ||
| 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("Task", back_populates="goal") | ||
|
|
||
| def to_dict(self): | ||
| goal_as_dict = { | ||
| "id": self.id, | ||
| "title": self.title, | ||
| } | ||
|
|
||
| if self.tasks: | ||
| goal_as_dict = { | ||
| "id": self.id, | ||
| "title": self.title, | ||
| "tasks": [task.id for task in self.tasks] | ||
| } | ||
|
|
||
| return goal_as_dict |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,34 @@ | ||
| from sqlalchemy.orm import Mapped, mapped_column | ||
| from sqlalchemy.orm import Mapped, mapped_column, relationship | ||
| from sqlalchemy import ForeignKey | ||
| from datetime import datetime | ||
| from typing import Optional | ||
| from ..db import db | ||
|
|
||
| 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("Goal", back_populates="tasks") | ||
|
|
||
| def to_dict(self): | ||
| task_as_dict ={ | ||
| "id": self.id, | ||
| "title": self.title, | ||
| "description": self.description, | ||
| "is_complete": bool(self.completed_at), | ||
| } | ||
|
|
||
| if self.goal: | ||
| task_as_dict ={ | ||
| "id": self.id, | ||
| "title": self.title, | ||
| "description": self.description, | ||
| "is_complete": bool(self.completed_at), | ||
| "goal_id": self.goal_id # just for wave 6 | ||
| } | ||
|
|
||
| return task_as_dict | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,113 @@ | ||
| from flask import Blueprint | ||
| from flask import Blueprint, request, make_response, abort, jsonify | ||
| from app.models.goal import Goal | ||
| from app.routes.task_routes import validate_task | ||
| from ..db import db | ||
|
|
||
| goals_bp = Blueprint("goals_bp", __name__, url_prefix="/goals") | ||
|
|
||
| @goals_bp.post("") | ||
| def create_goal(): | ||
| request_body = request.get_json() | ||
|
|
||
| if "title" not in request_body: | ||
| response = {"details": "Invalid data"} | ||
| return make_response(response, 400) | ||
|
|
||
| title = request_body["title"] | ||
|
|
||
| new_goal= Goal(title=title) | ||
| db.session.add(new_goal) | ||
| db.session.commit() | ||
|
|
||
| return {"goal": new_goal.to_dict()}, 201 | ||
|
|
||
| @goals_bp.get("") | ||
| def get_all_goals(): | ||
| query = db.select(Goal) | ||
| goals = db.session.scalars(query) | ||
|
|
||
| goals_response = [] | ||
| goals_response = [goal.to_dict() for goal in goals] | ||
| return goals_response | ||
|
|
||
| @goals_bp.get("/<goal_id>") | ||
| def get_one_goal(goal_id): | ||
| goal = validate_goal(goal_id) | ||
|
|
||
| return { | ||
| "goal":goal.to_dict() | ||
| }, 200 | ||
|
|
||
| def validate_goal(goal_id): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is very similar to our
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know I can create validate_models in bass.py then impor it in goal_routes and task_routes! |
||
| try: | ||
| goal_id = int(goal_id) | ||
| except ValueError: | ||
| 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.put("/<goal_id>") | ||
| def update_goal(goal_id): | ||
| goal = validate_goal(goal_id) | ||
| request_body = request.get_json() | ||
|
|
||
| goal.title = request_body.get("title", goal.title) | ||
| db.session.commit() | ||
|
|
||
| return { | ||
| "goal":goal.to_dict() | ||
| }, 200 | ||
|
|
||
| @goals_bp.delete("/<goal_id>") | ||
| def delete_goal(goal_id): | ||
| goal = validate_goal(goal_id) | ||
|
|
||
| db.session.delete(goal) | ||
| db.session.commit() | ||
|
|
||
| return jsonify({ | ||
| "details": f'Goal {goal.id} "{goal.title}" successfully deleted' | ||
| }), 200 | ||
|
|
||
| @goals_bp.post("/<goal_id>/tasks") | ||
| def add_tasks_to_goal(goal_id): | ||
| goal=validate_goal(goal_id) | ||
| request_body=request.get_json() | ||
|
|
||
| task_list= request_body["task_ids"] | ||
|
|
||
| for task in task_list: | ||
| task = validate_task(task) | ||
| task.goal_id = goal_id | ||
| db.session.commit() | ||
|
|
||
| task_ids =[] | ||
| for task in goal.tasks: | ||
| task_ids.append(task.id) | ||
|
|
||
| response = { | ||
| "id": goal.id, | ||
| "task_ids": task_ids | ||
| } | ||
| return response, 200 | ||
|
|
||
| @goals_bp.get("/<goal_id>/tasks") | ||
| def get_tasks_for_goal(goal_id): | ||
| goal = validate_goal(goal_id) | ||
| response = { | ||
| "id": goal.id, | ||
| "title": goal.title, | ||
| "tasks":[task.to_dict() for task in goal.tasks] | ||
| } | ||
| return response | ||
|
|
||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,176 @@ | ||
| from flask import Blueprint | ||
| from flask import Blueprint, abort, make_response, request, Response, jsonify | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| from app.models.task import Task | ||
| from sqlalchemy import desc | ||
| from datetime import datetime | ||
| from ..db import db | ||
| import os | ||
| import requests | ||
|
|
||
| tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") | ||
|
|
||
| @tasks_bp.post("") | ||
| def create_task(): | ||
| request_body = request.get_json() | ||
|
|
||
| if "title" not in request_body or "description" not in request_body: | ||
| response = {"details": "Invalid data"} | ||
| return make_response(response, 400) | ||
|
|
||
| title = request_body["title"] | ||
| description = request_body["description"] | ||
| completed_at = request_body.get("completed_at", None) | ||
|
|
||
|
|
||
|
Comment on lines
+22
to
+23
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would remove one of these blank lines; two blank lines is a convention used to separate top level functions, so many readers would see that spacing, assume the function is ending, and have to do some extra thought about what the code is actually doing. |
||
| 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) | ||
| } | ||
| } | ||
|
Comment on lines
+28
to
+35
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we have a response = {"task": new_task.to_dict()} |
||
|
|
||
| return response, 201 | ||
|
|
||
| @tasks_bp.get("") | ||
| def get_all_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 == "true": | ||
| query = query.where(Task.completed_at.isnot(None)) | ||
| elif completed_at_param == "false": | ||
| query = query.where(Task.completed_at.is_(None)) | ||
|
|
||
| sort_param = request.args.get("sort") | ||
| if sort_param == "asc": | ||
| query = query.order_by(Task.title) | ||
| if sort_param == "desc": | ||
| query = query.order_by(desc(Task.title)) | ||
| else: | ||
| query = query.order_by(Task.id) | ||
|
|
||
| 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": bool(task.completed_at) | ||
| }) | ||
|
Comment on lines
+67
to
+74
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be a great place to use a list comprehension in conjunction with our Task to_dict function: tasks_response = [task.to_dict() for task in tasks] |
||
|
|
||
| return tasks_response, 200 | ||
|
|
||
| @tasks_bp.get("/<task_id>") | ||
| def get_one_task(task_id): | ||
| task = validate_task(task_id) | ||
|
|
||
| return { | ||
| "task":task.to_dict() | ||
| }, 200 | ||
|
|
||
| def validate_task(task_id): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would consider moving this to the bottom of the file as a helper function that's used by multiple routes, it's a little out of place in between a couple routes. |
||
| 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.put("/<task_id>") | ||
| def update_task(task_id): | ||
| task = validate_task(task_id) | ||
| request_body = request.get_json() | ||
|
|
||
| task.title = request_body.get("title", task.title) | ||
| task.description = request_body.get("description", task.description) | ||
| task.completed_at = request_body.get("completed_at", task.completed_at) | ||
|
Comment on lines
+106
to
+108
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Neat use of the original values as defaults if a value is missing. |
||
| db.session.commit() | ||
|
|
||
| return { | ||
| "task":task.to_dict() | ||
| }, 200 | ||
|
|
||
| @tasks_bp.delete("/<task_id>") | ||
| def delete_task(task_id): | ||
| task = validate_task(task_id) | ||
| if not task: | ||
| return jsonify({"message": f"task {task_id} not found"}), 404 | ||
|
Comment on lines
+118
to
+119
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We make this same check inside the |
||
| db.session.delete(task) | ||
| db.session.commit() | ||
|
|
||
| return jsonify({ | ||
| "details": f'Task {task.id} "{task.title}" successfully deleted' | ||
| }), 200 | ||
|
|
||
|
|
||
| def mark_task_complete(task_id): | ||
| task = validate_task(task_id) | ||
| if not task: | ||
| return abort(404, description="Task not found") | ||
|
|
||
| task.completed_at = datetime.now() | ||
| db.session.commit() | ||
|
|
||
| return { | ||
| "task":task.to_dict() | ||
| }, 200 | ||
|
|
||
| @tasks_bp.patch("/<task_id>/mark_complete") | ||
| def patch_complete(task_id): | ||
| task = validate_task(task_id) | ||
| task.completed_at = datetime.now().isoformat() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Were you running into issues with the |
||
| db.session.commit() | ||
|
|
||
| url = "https://slack.com/api/chat.postMessage" | ||
| headers = { | ||
| "Authorization": f"Bearer {os.environ.get('SLACK_API_KEY')}", | ||
| "Content-Type": "application/json" | ||
| } | ||
| data = { | ||
| "channel" : "#api-test-channel", | ||
| "text": f"Someone just completed the task {task.title}" | ||
| } | ||
|
|
||
| response = requests.post(url, headers=headers, json=data) | ||
|
|
||
| print("Slack API Response:", response.status_code, response.json()) | ||
|
|
||
| response_data = {"task": task.to_dict()} | ||
|
|
||
| return response_data, 200 | ||
|
|
||
| @tasks_bp.patch("/<task_id>/mark_incomplete") | ||
| def mark_task_incomplete(task_id): | ||
| task = validate_task(task_id) | ||
| if not task: | ||
| return abort(404, description="Task not found") | ||
|
|
||
| task.completed_at = None | ||
| db.session.commit() | ||
|
|
||
| return { | ||
| "task":task.to_dict() | ||
| }, 200 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Single-database configuration for Flask. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We create the task dictionary and then we return it, could we add a step in between creation and returning where we conditionally add the
goal_idkey only if agoal_idactually exists? What could that look like?