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

Panthers - Group 4: Betts, Mejia, Tamang, Xu #27

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6841f78
set-up
lbbetts Jan 3, 2023
f9ca570
cleaned up requirements
lbbetts Jan 3, 2023
b0aec3a
completed create a board and get all boards
silviax123 Jan 5, 2023
fac547d
complete get all boards and create a board
silviax123 Jan 5, 2023
04bfc1c
fixed requirements.txt
n1colemejia Jan 5, 2023
62c8b32
Merge branch 'main' of https://github.com/n1colemejia/back-end-inspir…
n1colemejia Jan 5, 2023
9457941
separated routes
lbbetts Jan 5, 2023
027eaee
fixed route paths
lbbetts Jan 5, 2023
d564288
basic outline of card routes
lbbetts Jan 5, 2023
9472cb7
added another card route
lbbetts Jan 5, 2023
a033ba1
GET/DELETE/PATCH a board by Id done
silviax123 Jan 6, 2023
08d2387
typo corrected
silviax123 Jan 6, 2023
fa93f42
database upgrade
lbbetts Jan 6, 2023
14fdb41
updated cards url
lbbetts Jan 6, 2023
bda9ffd
fixed typo
lbbetts Jan 6, 2023
8396b0b
cors update ??
lbbetts Jan 6, 2023
453ee14
cors ??
lbbetts Jan 6, 2023
3f213f9
minimum routes met
lbbetts Jan 17, 2023
606a5f4
routes functional
lbbetts Jan 17, 2023
8b9909e
tests created and passing
lbbetts Jan 17, 2023
f23655d
updated requirements
lbbetts Jan 17, 2023
2d94cbc
added deployment setup
lbbetts Jan 17, 2023
2841847
restructure board body
lbbetts Jan 17, 2023
68884ca
changed get_all_cards_per_board response
n1colemejia Jan 17, 2023
4501644
Merge branch 'main' of https://github.com/n1colemejia/back-end-inspir…
n1colemejia Jan 17, 2023
d1113f7
tweaks for front end
lbbetts Jan 18, 2023
37c1bce
cleaning up final version
lbbetts Jan 19, 2023
c7a768c
final, error eliminated
lbbetts Jan 19, 2023
d7e7f7d
fixed patch route to return json
n1colemejia Jan 20, 2023
127b3ba
Update README.md
n1colemejia Jun 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
48 changes: 4 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,7 @@
# Inspiration Board: Back-end Layer
## Inspo Board - Backend

This scaffold includes the following:
Inspo Board is an digital bulletin board for inspirational content! Users can create new boards, select their board, view all the cards associated with the selected board, add new cards, and +1 cards that they like.

## `app/__init__.py`
- Link to Frontend Repo: https://github.com/n1colemejia/front-end-inspiration-board

This file configures the app. It's where:

We expect developers to modify this file by:

- Replacing the database connection string
- Importing all models
- Registering all blueprints

Note that `create_app` also uses CORS. There is no extra action needed to be done with CORS.

## `app/routes.py`

We expect endpoints to be defined here.

The file already imports:

- `Blueprint`
- `request`
- `jsonify`
- `make_response`
- `db`

Feel free to alter these import statements.

This file also has a comment to define a Blueprint. Feel free to delete it.

## `app/models` Directory

This project already includes `app/models/board.py` and `app/models/card.py`, to anticipate the models `Board` and `Card`.

Both files already import `db`, for convenience!

## `requirements.txt`

This file lists the dependencies we anticipate are needed for the project.

## `Procfile`

This file already has the contents needed for a Heroku deployment.

If the `create_app` function in `app/__init__.py` is renamed or moved, the contents of this file need to change. Otherwise, we don't anticipate this file to change.
<img width="auto" alt="" src="https://github.com/n1colemejia/inspo-board/assets/100858764/0a89ba77-566c-460b-8161-29d848af5b18">
15 changes: 13 additions & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,33 @@
load_dotenv()


def create_app():
def create_app(test_config=None):
app = Flask(__name__)


CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'

app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get(
"SQLALCHEMY_DATABASE_URI")

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

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

# Register Blueprints here
# from .routes import example_bp
# app.register_blueprint(example_bp)
from .board_routes import board_bp
app.register_blueprint(board_bp)

# from .card_routes import cards_bp
# app.register_blueprint(cards_bp)

CORS(app)
return app
183 changes: 183 additions & 0 deletions app/board_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
from flask import Blueprint, request, jsonify, make_response, abort
from app import db
from app.models.board import Board
from app.models.card import Card

# example_bp = Blueprint('example_bp', __name__)
board_bp = Blueprint('board_bp', __name__, url_prefix='/board')

# non-route functions
def validate_board(board_id):
try:
board_id = int(board_id)
except:
abort(make_response({"error message":f"Board {board_id} invalid"}, 400))

board = Board.query.get(board_id)

if not board:
abort(make_response({"error message":f"Board {board_id} not found"}, 404))

def validate_card(card_id):
# used to determine correct type for card search
try:
card_id = int(card_id)
except:
abort(make_response({"error message":f"{card_id} is an invalid input."}, 400))

card = Card.query.get(card_id)

# used to determine if searched card is within database
if not card:
abort(make_response({"error message":f"Card ID {card_id} not found."}, 404))

return card

# get all boards
@board_bp.route("", methods=["GET"])
def get_boards():
boards = Board.query.all()
boards_response = []
for board in boards:
boards_response.append({
"board_id": board.board_id,
"title": board.title,
"owner": board.owner,
"cards": [card.to_dict() for card in board.cards]
})
return make_response(jsonify(boards_response), 200)


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

if "title" not in request_body:
return make_response({ "error message" :
"Invalid data. Must include title"
}, 400)
if "owner" not in request_body:
return make_response({"error message" :
"Invalid data. Must include owner"
}, 400)

new_board = Board(
title=request_body["title"],
owner=request_body["owner"]
)

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

return make_response({"board":new_board.to_dict()}, 201)

# Get a board by ID
@board_bp.route("/<board_id>", methods=["GET"])
def get_one_board(board_id):
board = validate_board(board_id)
board = Board.query.get(board_id)

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

# Delete a board by ID
@board_bp.route("/<board_id>", methods=["PUT"])
def update_one_board(board_id):
request_body = request.get_json()

if "title" not in request_body:
return make_response({"error message":
"Invalid data. Must include title"
}, 400)
if "owner" not in request_body:
return make_response({"error message":
"Invalid data. Must include owner"
}, 400)

board = validate_board(board_id)
board = Board.query.get(board_id)

board.title = request_body["title"]
board.ownder = request_body["owner"]

db.session.commit()
return make_response({"board":board.to_dict()}, 200)


# Patch a board by ID
@board_bp.route("/<board_id>", methods=["DELETE"])
def delete_one_board(board_id):
board = validate_board(board_id)
board = Board.query.get(board_id)

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

return make_response({"message": "Board successfully deleted"}, 200)

# post new card to board
@board_bp.route("/<board_id>/cards", methods=["POST"])
def add_card_to_board(board_id):
board = validate_board(board_id)
board = Board.query.get(board_id)

request_body = request.get_json()

# ensures minimum input for card; its message
if "message" not in request_body:
return make_response({"error message": "Invalid input"}, 400)

# managing the character limit
if len(request_body["message"]) < 1:
return make_response({"error message": "Input cannot be empty."}, 400)
if len(request_body["message"]) > 40:
return make_response({"error message": "Input exceeds character limit."}, 400)

# id and likes count should be automatically added
new_card = Card(
message = request_body["message"])

new_card.board_id = board.board_id

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

return make_response(new_card.to_dict(), 200)

# get all cards of board
@board_bp.route("/<board_id>/cards", methods=["GET"])
def get_all_cards_per_board(board_id):
board = validate_board(board_id)
board = Board.query.get(board_id)

return_body = board.to_dict()
return_body["cards"] = [card.to_dict() for card in board.cards]

return make_response(jsonify(return_body["cards"]), 200)

# delete specific card
@board_bp.route("/<board_id>/cards/<card_id>", methods=["DELETE"])
def delete_card_from_board(board_id, card_id):
board = validate_board(board_id)

card = validate_card(card_id)
card = Card.query.get(card_id)

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

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

# increase like via api
@board_bp.route("/<board_id>/cards/<card_id>", methods=["PATCH"])
def increase_card_likes(board_id, card_id):
board = validate_board(board_id)

card = validate_card(card_id)
card = Card.query.get(card_id)

card.likes_count += 1

db.session.commit()

return make_response(jsonify(card.likes_count), 200)
11 changes: 11 additions & 0 deletions app/models/board.py
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
from app import db
class Board(db.Model):
board_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String)
owner = db.Column(db.String)
cards = db.relationship("Card", back_populates="board")

def to_dict(self):
return {
"title": self.title,
"owner": self.owner
}
13 changes: 13 additions & 0 deletions app/models/card.py
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
from app import db
class Card(db.Model):
card_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
message = db.Column(db.String)
likes_count = db.Column(db.Integer, default=0)
board_id = db.Column(db.Integer, db.ForeignKey('board.board_id'), nullable=True)
board = db.relationship("Board", back_populates="cards")

def to_dict(self):
return {
"card_id": self.card_id,
"message": self.message,
"likes_count": self.likes_count
}
4 changes: 0 additions & 4 deletions app/routes.py

This file was deleted.

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