Skip to content
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

Draft: Implement new API routes to fetch blog posts by authorIds and to update a post #2

Open
wants to merge 79 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
9c54e06
Start route that handles GET request for posts having at least one of…
huaszu Apr 11, 2023
604f094
Update api/posts.py
huaszu Apr 13, 2023
6904073
Update URL of route and update return statement to have proper jsonif…
huaszu Apr 14, 2023
6589caf
Update URL of route and update return statement to have proper jsonif…
huaszu Apr 14, 2023
3774363
Correct URL to match specification. Shorten docstring.
huaszu Apr 15, 2023
68326a5
Make list of author_ids where each author_id is an integer
huaszu Apr 15, 2023
470cbf2
Make list of Post objects to represent in response
huaszu Apr 15, 2023
44f5f61
Make dictionary containing information to include in response. Forma…
huaszu Apr 15, 2023
d660db7
Handle case of user entering duplicate authors.
huaszu Apr 15, 2023
3a6f17b
Comment to explain use of dictionary.
huaszu Apr 15, 2023
9612dc6
Add file to ignore
huaszu Apr 15, 2023
76e5d03
Add key-value pair to inner dictionary of posts_data dictionary to he…
huaszu Apr 15, 2023
2e26c4b
Implement two directions for sorting blog posts.
huaszu Apr 15, 2023
cb60657
Suggest potential refactoring.
huaszu Apr 15, 2023
04b70b7
Handle error when required query parameter is missing. Handle error …
huaszu Apr 15, 2023
c5b84d0
Change error checking of direction by which to sort posts to help wit…
huaszu Apr 15, 2023
648c0f8
Initial commit
huaszu Apr 16, 2023
3a1e6b6
Implement type hints.
huaszu Apr 16, 2023
c208ca4
Update comment on alternative solution.
huaszu Apr 16, 2023
5529ef6
Refactor so as not to make unnecessary list of Post objects.
huaszu Apr 16, 2023
9a4d4c4
Use sets to consider unique author ids and unique posts. Handle case…
huaszu Apr 16, 2023
dbf2ea6
Update variable names for clarity. Make code more concise by removin…
huaszu Apr 17, 2023
24f1be2
Move file for better organization.
huaszu Apr 17, 2023
732a030
Use set comprehension. Save line of code that initialized an empty set.
huaszu Apr 17, 2023
b01a21f
Incorporate set comprehension. Initialize a set intended to be a sup…
huaszu Apr 17, 2023
6773aa4
Use list comprehension.
huaszu Apr 17, 2023
3ac2e08
Update error to warning.
huaszu Apr 22, 2023
dd9e9a9
Ensure that only a logged in can use this route.
huaszu Apr 22, 2023
c3f5764
Start route to update a blog post. Validate that user is logged in. …
huaszu Apr 23, 2023
7f3a782
Add instance method to get a post by post id.
huaszu Apr 23, 2023
c88810a
Enable logged in user to modify authorIds, tags, or text of post. Wh…
huaszu Apr 26, 2023
1da081a
Test use of row_to_dict(row) function.
huaszu Apr 26, 2023
8674425
Prepare format for Response Body.
huaszu Apr 27, 2023
1855fc0
Update response.
huaszu Apr 27, 2023
58917da
Attempt to get response to match exact format of specification, speci…
huaszu Apr 27, 2023
c4aeed8
Attempt differently to create specified order within response.
huaszu Apr 27, 2023
7107958
Set sorting of keys of JSON objects alphabetically to FALSE to achiev…
huaszu Apr 27, 2023
f630b16
Ensure only an author of a post can update the post.
huaszu Apr 27, 2023
9fcc11f
Give useful error message to user.
huaszu Apr 27, 2023
0f103f9
Give warning when database has no post with requested postId. Give e…
huaszu Apr 27, 2023
99c75f5
Handle error when user does not provide authorIds in the format of an…
huaszu Apr 27, 2023
a9fa08b
Handle error when user enters tags not in the format of an array. Ha…
huaszu Apr 27, 2023
03c8bd5
Handle error when user provides text that is not a string.
huaszu Apr 27, 2023
3c0a5f0
Sort tags alphabetically in response based on looking at example in s…
huaszu Apr 27, 2023
24fa5d1
Undo sort of tags in response because that sort made test_posts.py::t…
huaszu Apr 27, 2023
1321643
Remove unnecessary code because, for the Post class, the use of the @…
huaszu Apr 27, 2023
7b9cb9c
Test using db.utils.rows_to_list(rows) helper function to get toward …
huaszu Apr 27, 2023
d90805e
Improve error handling to handle additional error. Refactor implemen…
huaszu Apr 27, 2023
67ca0ef
Give up opportunity to deduplicate tags because, otherwise, the imple…
huaszu Apr 27, 2023
f4e0b34
Add static method for Post class that builds a query joining the post…
huaszu Apr 30, 2023
52bc8b4
Rename file
huaszu Apr 30, 2023
dd7ca19
Move constant to different file. Write helper functions.
huaszu May 1, 2023
68bb986
Write helper function to format results of database query per specifi…
huaszu May 1, 2023
3c3abdc
Eliminate unnecessary code to handle case when there are no posts to …
huaszu May 1, 2023
e13bc1c
Fix typograhical error
huaszu May 1, 2023
1d7f5ca
Remove unnecessary comment.
huaszu May 1, 2023
d160ce1
Rename file for clarity. Update comment for clarity.
huaszu May 1, 2023
f47e531
Use helper function to validate post_id and, if valid, return post.
huaszu May 1, 2023
e15c332
Make route more concise by factoring portions of code into helper fun…
huaszu May 1, 2023
dd272e8
Write helper function to get post and format information about post f…
huaszu May 1, 2023
06088b0
Refactor for extensibility. Map which kind of error handling message…
huaszu May 1, 2023
1d058c0
Rewrite for consistency and understanding.
huaszu May 2, 2023
5437c1e
Check whether user included data in request and give helpful error me…
huaszu May 2, 2023
c712f5a
Remove unnecessary commented out code.
huaszu May 2, 2023
c52b43c
Make repository layer for functions that make queries to the database…
huaszu Jun 25, 2023
d0a273e
Move function that queries database to repository layer. api/util/he…
huaszu Jun 25, 2023
1f55a4f
Pull out another three functions to repository layer that interact wi…
huaszu Jun 25, 2023
65ce818
Remove unnecessary imports. Fix code to refer to updated file name
huaszu Jun 25, 2023
7a69b51
Fix database operation to delete a UserPost record
huaszu Jun 26, 2023
5dc1b3d
Using Controller-Service-Repository pattern, refactor to make reposit…
huaszu Jul 2, 2023
39f41c7
Revise code to use consistent style across API routes
huaszu Jul 2, 2023
56a7666
Initial commit
huaszu Jul 3, 2023
c628c18
Views of SQLite database for reference, with the help of https://inlo…
huaszu Jul 3, 2023
088d1cd
Include prompt for easy reference. Introduce formatting for readabil…
huaszu Jul 3, 2023
1e15e1a
Improve technical correctness, style, organization, and readability
huaszu Jul 5, 2023
327719b
Update formatting so that all footnotes show
huaszu Jul 5, 2023
26dfb89
Edit list indentation for readability
huaszu Jul 5, 2023
9067923
Edit for clarity
huaszu Jul 5, 2023
cb0a60c
Fix spelling
huaszu Jul 5, 2023
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
Prev Previous commit
Next Next commit
Using Controller-Service-Repository pattern, refactor to make reposit…
…ory layer that contains functions that interact with the database. Improve factoring of code into functions for extensibility, maintability, and readability. Remove print statements from debugging. Make code more concise. Decrease repetitive code. Write comments where helpful. Pass tests.
  • Loading branch information
huaszu committed Jul 2, 2023
commit 5dc1b3d6cd9ddf89b646a674c411755057bc3c51
74 changes: 18 additions & 56 deletions api/posts.py
Original file line number Diff line number Diff line change
@@ -3,12 +3,11 @@
from api import api
from db.shared import db
from db.models.user_post import UserPost
from db.models.post import Post, User

from db.utils import row_to_dict, rows_to_list
from middlewares import auth_required
from db.models.post import Post

from db.utils import row_to_dict
from api.util import helpers_to_fetch_posts, helpers_to_update_post
from middlewares import auth_required


@api.post("/posts")
@@ -47,79 +46,42 @@ def fetch_posts():
"""
Fetch blog posts that have at least one of the authors specified.
"""
# validation
# Validation
user = g.get("user")
if user is None:
return abort(401)

parameters = request.args

# Handle errors in query parameter inputs from user
result_of_check = helpers_to_fetch_posts.validate_parameters_to_fetch_posts(parameters)
if not result_of_check["success"]:
return jsonify(result_of_check["message"]), result_of_check["status_code"]
else:
parsed_author_ids = result_of_check["parsed_author_ids"]

parsed_author_ids: set[int] = helpers_to_fetch_posts.create_author_ids_response(parameters=parameters)
sort_by: str = parameters.get("sortBy", "id")

direction: str = parameters.get("direction", "asc")

# Fetch posts
result = helpers_to_fetch_posts.display_posts(parsed_author_ids=parsed_author_ids,
sort_by=sort_by,
direction=direction)
sort_by=sort_by,
direction=direction)

return jsonify({"posts": result}), 200
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend returning a flask response object here instead of 2 parameters: https://tedboy.github.io/flask/generated/generated/flask.Response.html



@api.route("/posts/<postId>", methods=["PATCH"])
@auth_required
def update_post(postId):
def update_post(postId: str):
"""
Update blog post, if it exists in the database. Return updated blog post.
"""
# validation
result_of_post_check = helpers_to_update_post.validate_post_id(post_id=postId)
if not result_of_post_check["success"]:
return jsonify(result_of_post_check["message"]), result_of_post_check["status_code"]
else:
post = result_of_post_check["post"]
existing_post = helpers_to_update_post.validate_post_id(post_id=postId)

user = g.get("user")

result_of_user_check = helpers_to_update_post.validate_user_for_post_update(user, post)
if not result_of_user_check["success"]:
return jsonify(result_of_user_check["message"]), result_of_user_check["status_code"]

raw_data = request.data

result_of_raw_data_check = helpers_to_update_post.validate_data_present(raw_data=raw_data)
if not result_of_raw_data_check["success"]:
return jsonify(result_of_raw_data_check["message"]), result_of_raw_data_check["status_code"]
# Validation
user = g.get("user")
helpers_to_update_post.validate_user_for_post_update(user=user,
post=existing_post)

# Update post

print(post.users)
print(post.tags)
print(post.text)
# Check that request contains information about updates to make
helpers_to_update_post.validate_data_present(raw_data=request.data)

parsed_json = request.get_json(force=True)
if "authorIds" in parsed_json:
result_of_update_authors = helpers_to_update_post.update_author_ids_of_post(post=post, parsed_json=parsed_json)
if not result_of_update_authors["success"]:
return jsonify(result_of_update_authors["message"]), result_of_update_authors["status_code"]

if "tags" in parsed_json:
result_of_update_tags = helpers_to_update_post.update_tags_of_post(post=post, parsed_json=parsed_json)
if not result_of_update_tags["success"]:
return jsonify(result_of_update_tags["message"]), result_of_update_tags["status_code"]

if "text" in parsed_json:
result_of_update_text = helpers_to_update_post.update_text_of_post(post=post, parsed_json=parsed_json)
if not result_of_update_text["success"]:
return jsonify(result_of_update_text["message"]), result_of_update_text["status_code"]

db.session.commit()

return jsonify({"post": helpers_to_update_post.format_post_for_response(post_id=postId)}), 200
# Update post and return specified response
return jsonify({"post": helpers_to_update_post.generate_updated_post_response(existing_post=existing_post,
parsed_json=parsed_json)}), 200
7 changes: 6 additions & 1 deletion api/util/constants.py
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
MESSAGE_TYPE_AND_STATUS_CODE = {"error": 400, "warning": 200}
MESSAGE_TYPE_AND_STATUS_CODE = {"error": 400,
"unauthorized": 401,
"warning": 200}

PARAMETERS_ACCEPTED_VALUES = {"sortBy": ["id", "reads", "likes", "popularity"],
"direction": ["asc", "desc"]}
74 changes: 41 additions & 33 deletions api/util/helpers_to_fetch_posts.py
Original file line number Diff line number Diff line change
@@ -1,74 +1,82 @@
from db.shared import db
from db.models.post import Post
from db.utils import rows_to_list
from constants import MESSAGE_TYPE_AND_STATUS_CODE
from repository import database_operations


PARAMETERS_ACCEPTED_VALUES = {"sortBy": ["id", "reads", "likes", "popularity"],
"direction": ["asc", "desc"]}
from db.utils import rows_to_list
from api.util.constants import MESSAGE_TYPE_AND_STATUS_CODE, PARAMETERS_ACCEPTED_VALUES
from repository_layer import database_operations


def check_user_exists(user_id):
def check_user_exists(user_id: int) -> bool:
"""Check by user id whether or not a user exists."""

return database_operations.get_user_by_id(user_id=user_id) is not None


def parse_author_ids(author_ids):
def parse_author_ids(author_ids: str):
"""Parse author ids. If not possible, give user error messaging."""
try:
parsed_author_ids: set[int] = set(int(author_id) for author_id in author_ids.split(",") if check_user_exists(int(author_id)))
parsed_author_ids: set = set(int(author_id) for author_id in author_ids.split(",") if check_user_exists(int(author_id)))
if not parsed_author_ids:
error_or_warning = "warning"
error_or_warning: str = "warning"
return {"success": False,
"message": {error_or_warning: "None of the author id(s) you requested exist in the database."},
"status_code": MESSAGE_TYPE_AND_STATUS_CODE[error_or_warning]}
return {"success": True,
"parsed_author_ids": parsed_author_ids}
return parsed_author_ids
except:
error_or_warning = "error"
error_or_warning: str = "error"
return {"success": False,
"message": {error_or_warning: "Please provide a query parameter value for `authorIds` as a number or as numbers separated by commas, such as '1,5'."},
"status_code": MESSAGE_TYPE_AND_STATUS_CODE[error_or_warning]}


def validate_parameters_to_fetch_posts(parameters):
# Check for 400 errors
def validate_parameters_accepted_values(parameters: dict):
"""Validate that parameters have acceptable values."""
for parameter, value in parameters.items():
if parameter in PARAMETERS_ACCEPTED_VALUES:
acceptable = PARAMETERS_ACCEPTED_VALUES[parameter]
acceptable: list[str] = PARAMETERS_ACCEPTED_VALUES[parameter]
if value not in acceptable:
error_or_warning = "error"
error_or_warning: str = "error"
return {"success": False,
"message": {error_or_warning: f"Unacceptable value for {parameter} query parameter. We only accept one of {acceptable}."},
"status_code": MESSAGE_TYPE_AND_STATUS_CODE[error_or_warning]}
# Note: If user includes query parameter keys not among the
# expected ones, i.e., not "sortBy" or "direction", process
# request ignoring irrelevant parameter keys.


def validate_authorIds_exist_in_request(parameters: dict):
"""Validate that request provided authorIds."""
author_ids: str = parameters.get("authorIds", None)
if author_ids is None:
error_or_warning = "error"
error_or_warning: str = "error"
return {"success": False,
"message": {error_or_warning: "Please identify author(s) using the query parameter key `authorIds`."},
"status_code": MESSAGE_TYPE_AND_STATUS_CODE[error_or_warning]}

# Either 400 with error message, 200 with warning message, or no problem
else:
return parse_author_ids(author_ids)
return author_ids


def create_author_ids_response(parameters: dict):
"""If parameters valid, return information containing parsed ids. If not, give error messaging."""
validate_parameters_accepted_values(parameters=parameters)
author_ids: str = validate_authorIds_exist_in_request(parameters=parameters)
return parse_author_ids(author_ids=author_ids)


def display_posts(parsed_author_ids, sort_by, direction):
posts_of_authors: list[Post] = Post.get_sorted_posts_by_user_ids(user_ids = parsed_author_ids,
sort_by=sort_by,
direction=direction)
def display_posts(parsed_author_ids, sort_by, direction) -> list[dict]:
"""Create response to user showing posts with applicable sorting."""
posts_of_authors: list[Post] = Post.get_sorted_posts_by_user_ids(user_ids=parsed_author_ids,
sort_by=sort_by,
direction=direction)

result = []
result: list = []

listed_posts_of_authors: list[dict] = rows_to_list(posts_of_authors)

post_properties = [property.name for property in Post.__table__.columns]
post_properties.sort() # Example in specification indicates that response
# shows post properties in alphabetical order
post_properties: list = [property.name for property in Post.__table__.columns]
post_properties.sort() # Example in specification indicates that
# response shows post properties in alphabetical order

for post in listed_posts_of_authors:
post_response = {post_property: post[post_property] for post_property in post_properties}
post_response: dict = {post_property: post[post_property] for post_property in post_properties}
result.append(post_response)

return result
return result
Loading