forked from AdaGold/task-list-api
-
Couldn't load subscription status.
- Fork 44
Dragonfly-Sno Ochoa #41
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
ra-choa
wants to merge
12
commits into
Ada-C23:main
Choose a base branch
from
ra-choa:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
5a39a53
task list
ra-choa 0fdd99a
spicy REconnect to Render PostgreSQL and finalize migrations
ra-choa cf3733e
Fixed task completion logic, Slackbot message, and timezone timestamps
ra-choa eee5613
Remove duplicate bp import
ra-choa 19af0d4
Remove unecessary KeyError
ra-choa 83a884d
Removing unecesaary/ verbose SQL Alchemy mapped_column()
ra-choa abf8bcb
feedback updates part 1
ra-choa 8505c10
API feedback completed
ra-choa 6a4f4f3
updated failed wave01 tests
ra-choa ce38466
last update for required task and goal fields
ra-choa 055e03f
update innit py for CORS
ra-choa 161fb97
CORS and app config updated
ra-choa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,27 @@ | ||
| 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): | ||
| __required_fields__ = ["title"] | ||
|
|
||
| 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): | ||
| goal_as_dict = {} | ||
| goal_as_dict["id"] = self.id | ||
| goal_as_dict["title"] = self.title | ||
|
|
||
| return goal_as_dict | ||
|
|
||
|
|
||
| @classmethod | ||
| def from_dict(cls, goal_data): | ||
| new_goal = cls(title=goal_data["title"]) | ||
|
|
||
| return new_goal |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,38 @@ | ||
| from sqlalchemy.orm import Mapped, mapped_column | ||
| from sqlalchemy.orm import Mapped, mapped_column, relationship | ||
| from ..db import db | ||
| from .goal import Goal | ||
| from datetime import datetime | ||
| from typing import Optional | ||
| from sqlalchemy import ForeignKey | ||
|
|
||
| class Task(db.Model): | ||
| __required_fields__ = ["title", "description"] | ||
|
|
||
| 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") | ||
ra-choa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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"] = True if self.completed_at is not None else False | ||
|
|
||
| if self.goal_id: | ||
| task_as_dict["goal_id"] = self.goal_id | ||
ra-choa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| return task_as_dict | ||
|
|
||
| @classmethod | ||
| def from_dict(cls, task_data): | ||
| new_task = cls(title=task_data["title"], | ||
| description=task_data["description"], | ||
| completed_at=task_data.get("completed_at", None) | ||
| ) | ||
|
|
||
| return new_task | ||
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,81 @@ | ||
| from flask import Blueprint | ||
| from flask import abort, Blueprint, make_response, request, Response | ||
| from app.models.task import Task | ||
| from app.models.goal import Goal | ||
| from ..db import db | ||
| from .route_utilities import validate_model, create_model, get_models_with_filters, update_model | ||
|
|
||
|
|
||
| bp = Blueprint("goals_bp", __name__, url_prefix = "/goals") | ||
ra-choa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| @bp.post("") | ||
| def create_goal(): | ||
| request_body = request.get_json() | ||
|
|
||
| return create_model(Goal, request_body) | ||
|
|
||
| @bp.get("/<goal_id>") | ||
| def get_one_goal_by_id(goal_id): | ||
| goal = validate_model(Goal, goal_id) | ||
|
|
||
| return {"goal": goal.to_dict()}, 200 | ||
|
|
||
| @bp.get("") | ||
| def get_all_goals(): | ||
| filters = request.args.to_dict() | ||
| return get_models_with_filters( | ||
| Goal, | ||
| filters, | ||
| sort_attr="title"), 200 | ||
|
|
||
| @bp.put("/<goal_id>") | ||
| def update_goal(goal_id): | ||
| goal = validate_model(Goal, goal_id) | ||
| request_body = request.get_json() | ||
|
|
||
| return update_model(goal, request_body) | ||
|
|
||
| @bp.delete("/<goal_id>") | ||
| def delete_goal(goal_id): | ||
| goal = validate_model(Goal, goal_id) | ||
|
|
||
| db.session.delete(goal) | ||
| db.session.commit() | ||
|
|
||
| return Response(status=204, mimetype="application/json") | ||
|
|
||
|
|
||
| @bp.post("/<goal_id>/tasks") | ||
| def assign_tasks_to_goal(goal_id): | ||
| goal = validate_model(Goal, goal_id) | ||
| request_body = request.get_json() | ||
| task_ids = request_body.get("task_ids", []) | ||
|
|
||
| valid_tasks = [] | ||
| for task_id in task_ids: | ||
| task = validate_model(Task, task_id) | ||
| valid_tasks.append(task) | ||
|
|
||
| goal.tasks = valid_tasks | ||
| db.session.commit() | ||
|
|
||
| return { | ||
| "id": goal.id, | ||
| "task_ids": task_ids | ||
| }, 200 | ||
|
|
||
| @bp.get("/<goal_id>/tasks") | ||
| def get_tasks_for_one_goal(goal_id): | ||
| goal = validate_model(Goal, goal_id) | ||
|
|
||
| tasks_response = [] | ||
| for task in goal.tasks: | ||
ra-choa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| task_dict = task.to_dict() | ||
| task_dict["goal_id"] = goal.id | ||
| tasks_response.append(task_dict) | ||
|
|
||
| return { | ||
| "id": goal.id, | ||
| "title": goal.title, | ||
| "tasks": tasks_response | ||
| }, 200 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| from flask import abort, make_response, Response | ||
| from ..db import db | ||
| import os | ||
| import requests | ||
|
|
||
| def validate_model(cls, model_id): | ||
| try: | ||
| model_id = int(model_id) | ||
| except ValueError: | ||
| response = {"message": f"{cls.__name__} {model_id} invalid"} | ||
| abort(make_response(response , 400)) | ||
|
|
||
| query = db.select(cls).where(cls.id == model_id) | ||
| model = db.session.scalar(query) | ||
|
|
||
| if not model: | ||
| response = {"message": f"{cls.__name__} {model_id} not found"} | ||
| abort(make_response(response, 404)) | ||
|
|
||
| return model | ||
|
|
||
| def create_model(cls, model_data): | ||
| required_fields = getattr(cls, "__required_fields__", []) | ||
|
|
||
| for field in required_fields: | ||
| if not model_data.get(field): | ||
| response = {"details": "Invalid data"} | ||
| abort(make_response(response, 400)) | ||
|
|
||
| try: | ||
| new_model = cls.from_dict(model_data) | ||
| except KeyError as e: | ||
| response = {"message": f"Invalid request: missing {e.args[0]}"} | ||
| abort(make_response(response, 400)) | ||
|
|
||
| db.session.add(new_model) | ||
| db.session.commit() | ||
|
|
||
| return {"task" if cls.__name__ == "Task" else "goal": new_model.to_dict()}, 201 | ||
|
|
||
|
|
||
| def get_models_with_filters(cls, filters=None, sort_attr="id"): | ||
| query = db.select(cls) | ||
|
|
||
| if filters: | ||
| for attribute, value in filters.items(): | ||
| if hasattr(cls, attribute): | ||
| query = query.where(getattr(cls, attribute).ilike(f"%{value}%")) | ||
|
|
||
| sort_order = filters.get("sort", "asc") | ||
| if sort_order == "desc": | ||
| query = query.order_by(getattr(cls, sort_attr).desc()) | ||
| else: | ||
| query = query.order_by(getattr(cls, sort_attr).asc()) | ||
| else: | ||
| query = query.order_by(getattr(cls, sort_attr)) | ||
|
|
||
| models = db.session.scalars(query) | ||
| return [model.to_dict() for model in models] | ||
|
|
||
|
|
||
| def send_slack_notification(message): | ||
| slack_token = os.environ.get("SLACKBOT_API_TOKEN") | ||
| slack_channel = os.environ.get("SLACK_CHANNEL") | ||
|
|
||
| headers = { | ||
| "Authorization": f"Bearer {slack_token}", | ||
| "Content-Type": "application/json" | ||
| } | ||
| data = { | ||
| "channel": slack_channel, | ||
| "text": message | ||
| } | ||
| slack_response = requests.post("https://slack.com/api/chat.postMessage", headers=headers, json=data) | ||
|
|
||
| if not slack_response.ok: | ||
| print("Slack error:", slack_response.text) | ||
|
|
||
| def update_model(obj, data): | ||
| for attr, value in data.items(): | ||
| if hasattr(obj, attr): | ||
| setattr(obj, attr, value) | ||
|
|
||
| db.session.commit() | ||
| return Response(status=204, mimetype="application/json") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,65 @@ | ||
| from flask import Blueprint | ||
| from flask import abort, Blueprint, make_response, request, Response | ||
| from app.models.task import Task | ||
| from ..db import db | ||
| from .route_utilities import validate_model, create_model,get_models_with_filters, send_slack_notification, update_model | ||
| from datetime import datetime, timezone | ||
| import os | ||
| import requests | ||
|
|
||
| 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_all_tasks(): | ||
| filters = request.args.to_dict() | ||
| return get_models_with_filters( | ||
| Task, | ||
| filters, | ||
| sort_attr="title"), 200 | ||
|
|
||
| @bp.get("/<task_id>") | ||
| def get_one_task(task_id): | ||
| task = validate_model(Task, task_id) | ||
|
|
||
| return {"task": task.to_dict()}, 200 | ||
ra-choa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @bp.patch("/<task_id>/mark_complete") | ||
| def mark_task_as_complete(task_id): | ||
| task = validate_model(Task, task_id) | ||
|
|
||
| task.completed_at = datetime.now(timezone.utc) | ||
| db.session.commit() | ||
|
|
||
| message = f"Someone just completed the task: *{task.title}* ! 🎉" | ||
| send_slack_notification(message) | ||
|
|
||
| return make_response(task.to_dict(), 200) | ||
|
|
||
| @bp.patch("/<task_id>/mark_incomplete") | ||
| def mark_task_as_incomplete(task_id): | ||
| task = validate_model(Task, task_id) | ||
|
|
||
| task.completed_at = None | ||
|
|
||
| db.session.commit() | ||
| return Response(status=204, mimetype="application/json") | ||
|
|
||
| @bp.put("/<task_id>") | ||
| def update_task(task_id): | ||
| task = validate_model(Task, task_id) | ||
| request_body = request.get_json() | ||
|
|
||
| return update_model(task, request_body) | ||
|
|
||
| @bp.delete("/<task_id>") | ||
| def delete_task(task_id): | ||
| task = validate_model(Task, task_id) | ||
| db.session.delete(task) | ||
| db.session.commit() | ||
|
|
||
| return Response(status=204, mimetype="application/json") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Single-database configuration for Flask. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.