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

Kunzite 2 - Angie, Danica, Alejandra #7

Open
wants to merge 52 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
27917fa
sets up files, adds board model
adom2128 Jun 26, 2023
f450ac9
pushes to fix merge conflict
dsarm-edu Jun 26, 2023
7fd194c
Merge branch 'main' of https://github.com/adom2128/back-end-inspirati…
dsarm-edu Jun 26, 2023
dbe51e1
added card model
angiekimtran Jun 26, 2023
e8c5f48
Merge branch 'main' of https://github.com/adom2128/back-end-inspirati…
angiekimtran Jun 26, 2023
8bf346c
Merge branch 'main' of https://github.com/adom2128/back-end-inspirati…
dsarm-edu Jun 26, 2023
b4567de
pushed to resolve merge conflict
civi10 Jun 26, 2023
1aacfbd
Merge branch 'main' of https://github.com/adom2128/back-end-inspirati…
civi10 Jun 26, 2023
18cfa14
Merge branch 'main' of https://github.com/adom2128/back-end-inspirati…
dsarm-edu Jun 26, 2023
4c80fed
pushing to resolve merge conflict
civi10 Jun 26, 2023
d09388c
importing models
civi10 Jun 26, 2023
c007434
pushes to resolve merge conflict when importing models
dsarm-edu Jun 26, 2023
0e55047
Merge branch 'main' of https://github.com/adom2128/back-end-inspirati…
dsarm-edu Jun 26, 2023
0c8b2b3
adds card to Board model
dsarm-edu Jun 26, 2023
355fd4e
changes to board.board_id in Card model
dsarm-edu Jun 26, 2023
9d8f406
finishes setup for Card and Board models
dsarm-edu Jun 26, 2023
2b35970
finishes setup for Card and Board models again
dsarm-edu Jun 26, 2023
dafe8c5
added routes folder
angiekimtran Jun 26, 2023
be7a3cb
registers Board blueprint
dsarm-edu Jun 26, 2023
301e9bd
fixes file path for board_bp route in init file
adom2128 Jun 26, 2023
72432cf
adds from_dict classmethod and post endpoint to Board
adom2128 Jun 26, 2023
23508a1
adds GET method to board_routes and adds to_dict helper function
dsarm-edu Jun 26, 2023
bf2252c
addresses merge conflict, has both from_dict and to_dict methods
adom2128 Jun 26, 2023
5a6b121
merges get and post routes in board_routes
adom2128 Jun 26, 2023
e975fd3
Added PUT/DELETE endpoints to card_routes.py, created routes_helpers.py
civi10 Jun 26, 2023
260ae01
Merge branch 'main' of https://github.com/adom2128/back-end-inspirati…
civi10 Jun 26, 2023
a5fae3f
adds nested GET method
dsarm-edu Jun 26, 2023
1fdaabb
addresses merge conflict
adom2128 Jun 26, 2023
879bf46
Merge branch 'main' of https://github.com/adom2128/back-end-inspirati…
adom2128 Jun 26, 2023
a59ad8e
add period to route_helpers import statement
adom2128 Jun 26, 2023
b1b1429
adds add_card_to_board endpoint
adom2128 Jun 26, 2023
61551bb
changes card to cards in model relationship
adom2128 Jun 27, 2023
8927913
makes small changes while debugging add_card_to_board, still not working
adom2128 Jun 27, 2023
cc13383
deletes and recreates database and deletes migrations folder
dsarm-edu Jun 27, 2023
28f8026
added like count to update_like_count_card endpoint and added delete …
angiekimtran Jun 27, 2023
a114936
adds PUT to board_routes
dsarm-edu Jun 27, 2023
a26580d
changes Board and Card to cls
dsarm-edu Jun 27, 2023
fe545c9
changed id to board_id in Board's to_dict function and GET cards endp…
angiekimtran Jun 28, 2023
07483c1
fixed mispelling in the response body of DELETE card and DELETE board…
angiekimtran Jun 28, 2023
607eb78
added PUT card route to update message and changed update_likes_card …
angiekimtran Jun 28, 2023
1e117b5
handled ValueError in from_dict in Card and Board model
angiekimtran Jun 28, 2023
e8a3280
commented out PUT routes for updating a board/card
angiekimtran Jun 29, 2023
8906eef
updated render database URI in __init__.py
angiekimtran Jun 30, 2023
f2cb975
added get_one_board route and refactored response body of POST, GET, …
angiekimtran Jul 2, 2023
841aeea
changed local db to the deployed db in __init__.py
angiekimtran Jul 2, 2023
8d54545
updated response body of DELETE board and card endpoints
angiekimtran Jul 2, 2023
37f1f74
sorted by ID GET all boards and cards reponse
angiekimtran Jul 17, 2023
0f0eccb
removed comments
angiekimtran Jul 18, 2023
1a54aa2
added tests for Board & Card routes
angiekimtran Jul 19, 2023
f342c28
adds update card endpoint
adom2128 Jul 19, 2023
55f2d69
changes update_card_msg endpoint for debugging purposes
adom2128 Jul 19, 2023
a589073
refactored conftest.py and reorganized test folder
angiekimtran Jul 21, 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
25 changes: 15 additions & 10 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,28 @@
migrate = Migrate()
load_dotenv()


def create_app():
def create_app(test_config=None):
app = Flask(__name__)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_DATABASE_URI")
if test_config is None:
# Deployed Render db
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("RENDER_DATABASE_URI")
else:
app.config["TESTING"] = True
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("SQLALCHEMY_TEST_DATABASE_URI")

# Import models here for Alembic setup
# from app.models.ExampleModel import ExampleModel
from app.models.card import Card
from app.models.board import Board

db.init_app(app)
migrate.init_app(app, db)

# Register Blueprints here
# from .routes import example_bp
# app.register_blueprint(example_bp)
from .routes.card_routes import cards_bp
app.register_blueprint(cards_bp)

from .routes.board_routes import board_bp
app.register_blueprint(board_bp)

CORS(app)
return app
return app
33 changes: 33 additions & 0 deletions app/models/board.py
Original file line number Diff line number Diff line change
@@ -1 +1,34 @@
from app import db
from flask import abort, make_response, jsonify

class Board(db.Model):
board_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String, nullable=False)
owner = db.Column(db.String, nullable=False)
cards = db.relationship("Card", back_populates="board")

@classmethod
def from_dict(cls, board_data):
try:
if board_data["title"] == "":
raise ValueError

if board_data["owner"] == "":
raise ValueError

new_board = cls(
title=board_data["title"],
owner=board_data["owner"]
)
except (ValueError, KeyError):
abort(make_response(jsonify({"details": "Invalid data"}), 400))

Choose a reason for hiding this comment

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

Conceptually, the three flask functions here are at a different "layer" of the backend logic. Models are at a pretty low layer (they're far from the incoming request) while the endpoints are at a higher layer (they are aware of flask and endpoints and all that). We should try to minimize unnecessary dependencies where possible, with special emphasis on avoiding cases where a lower layer depends on a higher layer.

Remember that one of the key benefits of errors is that they allow us to separate the concerns of reporting that something has gone wrong, and resolving that situation. This lets us move the resolution code to places that have more context.

Here, we should avoid importing abort, make_response, and jsonify fromflask and not hanlde the error here. We should let the errors escape the function, and put the error handling closer to the endpoint code. This needn't be directly in and endpoint. Rather, it could be in a helper function that "lives" at the same layer as endpoints.


return new_board

def to_dict(self):
board_as_dict = {}
board_as_dict["board_id"]=self.board_id
board_as_dict["title"]=self.title
board_as_dict["owner"]=self.owner

return board_as_dict
27 changes: 27 additions & 0 deletions app/models/card.py
Original file line number Diff line number Diff line change
@@ -1 +1,28 @@
from app import db
from flask import abort, make_response, jsonify

class Card(db.Model):
card_id = db.Column(db.Integer, primary_key=True)
message = db.Column(db.String, nullable=False)
likes_count = db.Column(db.Integer, default=0)
board_id = db.Column(db.Integer, db.ForeignKey("board.board_id"))
board = db.relationship("Board", back_populates="cards")

@classmethod
def from_dict(cls, card_data):
try:
if card_data["message"] == "" or len(card_data["message"]) > 40:
raise ValueError
new_card = cls(message=card_data["message"])
except (ValueError, TypeError, KeyError):
abort(make_response(jsonify({"details": "Invalid data"}), 400))

return new_card

def to_dict(self):
return dict(
card_id=self.card_id,
message=self.message,
likes_count=self.likes_count,
board_id=self.board_id,
)
4 changes: 0 additions & 4 deletions app/routes.py

This file was deleted.

Empty file added app/routes/__init__.py
Empty file.
89 changes: 89 additions & 0 deletions app/routes/board_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from flask import Blueprint, request, jsonify, make_response
from app import db
from app.models.card import Card
from app.models.board import Board
from .routes_helpers import validate_model

board_bp = Blueprint("boards", __name__, url_prefix="/boards")

@board_bp.route("", methods=["POST"])
def create_board():
request_body = request.get_json()

new_board = Board.from_dict(request_body)

db.session.add(new_board)
db.session.commit()

return make_response(jsonify(new_board.to_dict()), 201)


@board_bp.route("/<board_id>/cards", methods=["POST"])
def add_card_to_board(board_id):
board = validate_model(Board, board_id)

request_body = request.get_json()

new_card = Card.from_dict(request_body)

new_card.board_id = board.board_id

db.session.add(new_card)
db.session.commit()

return make_response(jsonify(new_card.to_dict()), 201)


@board_bp.route("", methods=["GET"])
def get_all_boards():

boards = Board.query.all()

boards_response = [board.to_dict() for board in boards]

def get_id(entry):
return entry['board_id']

boards_response.sort(key=get_id)

Choose a reason for hiding this comment

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

Rather than using sort, we can have the database return the records in sorted order to begin with by using order_by on the Board.query .


return jsonify(boards_response), 200


@board_bp.route("/<board_id>", methods=["GET"])
def get_one_board(board_id):
board = validate_model(Board, board_id)

response_body = {
"board_id": board.board_id,
"title": board.title,
"owner": board.owner,
}

return jsonify(response_body), 200


@board_bp.route("/<board_id>/cards", methods=["GET"])
def get_cards_of_one_board(board_id):
board = validate_model(Board, board_id)

cards_response = []
for card in board.cards:
cards_response.append(card.to_dict())

def get_id(entry):
return entry['card_id']

cards_response.sort(key=get_id)

return jsonify(cards_response), 200


@board_bp.route("/<board_id>", methods=["DELETE"])
def delete_board(board_id):

board = validate_model(Board, board_id)

db.session.delete(board)
db.session.commit()

return make_response(jsonify({"message":f"Board {board_id} successfully deleted"}), 200)
41 changes: 41 additions & 0 deletions app/routes/card_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from flask import Blueprint, jsonify, make_response, request
from app import db
from app.models.card import Card
from .routes_helpers import validate_model

cards_bp = Blueprint("cards", __name__, url_prefix="/cards")


@cards_bp.route("/<card_id>/like", methods=["PUT"])

Choose a reason for hiding this comment

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

This feels more like a PATCH method to me, since we're not replacing the entire resource.

def update_likes_card(card_id):
card = validate_model(Card, card_id)

card.likes_count += 1

db.session.commit()

return make_response(jsonify(card.to_dict()), 200)


@cards_bp.route("/<card_id>", methods=["DELETE"])
def delete_card(card_id):
card = validate_model(Card, card_id)

db.session.delete(card)
db.session.commit()

return make_response(
jsonify({"message": f"Card {card_id} successfully deleted"}), 200
)


@cards_bp.route("/<card_id>", methods=["PUT"])
def update_card_msg(card_id):
card = validate_model(Card, card_id)
request_body = request.get_json()

card.message = request_body["message"]

db.session.commit()

return jsonify({"message": f"Card {card_id} successfully updated"}), 200
14 changes: 14 additions & 0 deletions app/routes/routes_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from flask import abort, make_response

def validate_model(cls, model_id):
try:
model_id = int(model_id)
except:
abort(make_response({"message":f"{cls.__name__} {model_id} invalid"}, 400))

model = cls.query.get(model_id)

if not model:
abort(make_response({"message":f"{cls.__name__} {model_id} not found"}, 404))

return model
1 change: 1 addition & 0 deletions migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generic single-database configuration.
45 changes: 45 additions & 0 deletions migrations/alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# A generic, single database configuration.

[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false


# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
Loading