From 079839ae81859a23ba9f3d82e726d3cef8c2de37 Mon Sep 17 00:00:00 2001 From: Rainacam Date: Mon, 26 Jun 2023 13:28:57 -0400 Subject: [PATCH 01/35] migrations --- migrations/README | 1 + migrations/alembic.ini | 45 +++++++++ migrations/env.py | 96 +++++++++++++++++++ migrations/script.py.mako | 24 +++++ ...19008e0195f2_adds_board_and_card_models.py | 40 ++++++++ 5 files changed, 206 insertions(+) create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/19008e0195f2_adds_board_and_card_models.py diff --git a/migrations/README b/migrations/README new file mode 100644 index 00000000..98e4f9c4 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 00000000..f8ed4801 --- /dev/null +++ b/migrations/alembic.ini @@ -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 diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 00000000..8b3fb335 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,96 @@ +from __future__ import with_statement + +import logging +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option( + 'sqlalchemy.url', + str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) +target_metadata = current_app.extensions['migrate'].db.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 00000000..2c015630 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/19008e0195f2_adds_board_and_card_models.py b/migrations/versions/19008e0195f2_adds_board_and_card_models.py new file mode 100644 index 00000000..b4e10dbd --- /dev/null +++ b/migrations/versions/19008e0195f2_adds_board_and_card_models.py @@ -0,0 +1,40 @@ +"""adds Board and Card models + +Revision ID: 19008e0195f2 +Revises: +Create Date: 2023-06-26 12:46:06.484561 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '19008e0195f2' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('board') + op.drop_table('card') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('card', + sa.Column('card_id', sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column('message', sa.VARCHAR(length=500), autoincrement=False, nullable=True), + sa.Column('likes_count', sa.INTEGER(), autoincrement=False, nullable=True), + sa.PrimaryKeyConstraint('card_id', name='card_pkey') + ) + op.create_table('board', + sa.Column('board_id', sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column('title', sa.VARCHAR(length=100), autoincrement=False, nullable=True), + sa.Column('owner', sa.VARCHAR(length=100), autoincrement=False, nullable=True), + sa.PrimaryKeyConstraint('board_id', name='board_pkey') + ) + # ### end Alembic commands ### From 4e04f6709fb4e5044c5e506b5dc18124c155775a Mon Sep 17 00:00:00 2001 From: Rainacam Date: Mon, 26 Jun 2023 13:30:57 -0400 Subject: [PATCH 02/35] model import statements added into init.py --- app/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index 1c821436..e94f018a 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -27,5 +27,7 @@ def create_app(): # from .routes import example_bp # app.register_blueprint(example_bp) + from app.models.card import card + from app.models.board import board CORS(app) return app From 2ce2c1b761c08b68efbbe4f07d4d23ba5b338744 Mon Sep 17 00:00:00 2001 From: an2084 Date: Mon, 26 Jun 2023 13:34:19 -0400 Subject: [PATCH 03/35] Created class Card model --- app/models/card.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/card.py b/app/models/card.py index 147eb748..83f5ea8f 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -1 +1,6 @@ from app import db + +class Card(db.Model): + card_id = db.Column(db.Integer, primary_key=True, autoincreme=True) + message = db.Column(db.String) + liked_count = db.Column(db.Integer) From f475c4e94dd7ada35e450d4fd9cf4eec29afc6c0 Mon Sep 17 00:00:00 2001 From: Rainacam Date: Mon, 26 Jun 2023 13:35:54 -0400 Subject: [PATCH 04/35] board model added --- app/models/board.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/models/board.py b/app/models/board.py index 147eb748..ebdc1909 100644 --- a/app/models/board.py +++ b/app/models/board.py @@ -1 +1,7 @@ from app import db +from app import db + +class Board(db.board): + board_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + title = db.Column(db.String) + owner = db.Column(db.String) \ No newline at end of file From eb23010d22bf3a4b131dc4ad0ce92b4e2cfa4085 Mon Sep 17 00:00:00 2001 From: an2084 Date: Mon, 26 Jun 2023 13:36:52 -0400 Subject: [PATCH 05/35] Fixed mistake on autoincrement --- app/models/card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/card.py b/app/models/card.py index 83f5ea8f..11070f07 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -1,6 +1,6 @@ from app import db class Card(db.Model): - card_id = db.Column(db.Integer, primary_key=True, autoincreme=True) + card_id = db.Column(db.Integer, primary_key=True, autoincrement=True) message = db.Column(db.String) liked_count = db.Column(db.Integer) From d3cb87f8a8d485fe5bd37c032ecb8f13c70cbd7d Mon Sep 17 00:00:00 2001 From: Rainacam Date: Mon, 26 Jun 2023 13:43:42 -0400 Subject: [PATCH 06/35] corrected import statments --- app/__init__.py | 4 ++-- app/models/board.py | 2 +- app/models/card.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index e94f018a..a0d17598 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -27,7 +27,7 @@ def create_app(): # from .routes import example_bp # app.register_blueprint(example_bp) - from app.models.card import card - from app.models.board import board + from app.models.card import Card + from app.models.board import Board CORS(app) return app diff --git a/app/models/board.py b/app/models/board.py index ebdc1909..51439e37 100644 --- a/app/models/board.py +++ b/app/models/board.py @@ -1,7 +1,7 @@ from app import db from app import db -class Board(db.board): +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) \ No newline at end of file diff --git a/app/models/card.py b/app/models/card.py index 83f5ea8f..11070f07 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -1,6 +1,6 @@ from app import db class Card(db.Model): - card_id = db.Column(db.Integer, primary_key=True, autoincreme=True) + card_id = db.Column(db.Integer, primary_key=True, autoincrement=True) message = db.Column(db.String) liked_count = db.Column(db.Integer) From c4d6592a0cf88328f84856160ded77ae167b3660 Mon Sep 17 00:00:00 2001 From: Rainacam Date: Mon, 26 Jun 2023 14:30:26 -0400 Subject: [PATCH 07/35] est. relationship between card and board in board --- app/models/board.py | 3 ++- app/models/card.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/board.py b/app/models/board.py index 51439e37..c8dfa29b 100644 --- a/app/models/board.py +++ b/app/models/board.py @@ -4,4 +4,5 @@ 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) \ No newline at end of file + owner = db.Column(db.String) + card = db.relationship("Card", back_populates="board") \ No newline at end of file diff --git a/app/models/card.py b/app/models/card.py index 11070f07..c56ba195 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -4,3 +4,4 @@ class Card(db.Model): card_id = db.Column(db.Integer, primary_key=True, autoincrement=True) message = db.Column(db.String) liked_count = db.Column(db.Integer) + From e87501f3a70e23aeaab51575a833fd72779782ea Mon Sep 17 00:00:00 2001 From: an2084 Date: Mon, 26 Jun 2023 14:31:44 -0400 Subject: [PATCH 08/35] Set up relationship with Board in class Card --- app/models/card.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/card.py b/app/models/card.py index 11070f07..7b3f3556 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -4,3 +4,5 @@ class Card(db.Model): card_id = db.Column(db.Integer, primary_key=True, autoincrement=True) message = db.Column(db.String) liked_count = db.Column(db.Integer) + board = db.relationship("Board", back_populates="cards") + board_fk = db.Column(db.Integer, db.ForeignKey('board.board_id')) From 502c4777d5da3648e1dac8c68a04f69cc8920357 Mon Sep 17 00:00:00 2001 From: Rainacam Date: Mon, 26 Jun 2023 14:32:35 -0400 Subject: [PATCH 09/35] connected card and board w/ foreignkey --- app/models/board.py | 2 +- app/models/card.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/board.py b/app/models/board.py index c8dfa29b..5e3c1a2d 100644 --- a/app/models/board.py +++ b/app/models/board.py @@ -5,4 +5,4 @@ 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) - card = db.relationship("Card", back_populates="board") \ No newline at end of file + cards = db.relationship("Card", back_populates="board") \ No newline at end of file diff --git a/app/models/card.py b/app/models/card.py index c56ba195..7b3f3556 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -4,4 +4,5 @@ class Card(db.Model): card_id = db.Column(db.Integer, primary_key=True, autoincrement=True) message = db.Column(db.String) liked_count = db.Column(db.Integer) - + board = db.relationship("Board", back_populates="cards") + board_fk = db.Column(db.Integer, db.ForeignKey('board.board_id')) From 26dffc0c88f5f3d3d6fcd297aaeab6bcbb315c6f Mon Sep 17 00:00:00 2001 From: an2084 Date: Tue, 27 Jun 2023 09:05:16 -0400 Subject: [PATCH 10/35] Create from_dict and to_dict for CARD --- app/models/card.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/models/card.py b/app/models/card.py index 7b3f3556..cf0144da 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -6,3 +6,21 @@ class Card(db.Model): liked_count = db.Column(db.Integer) board = db.relationship("Board", back_populates="cards") board_fk = db.Column(db.Integer, db.ForeignKey('board.board_id')) + + +@classmethod +def from_dict(cls, card_data): + new_card = Card( + message = card_data["message"], + liked_count = card_data["liked_count"] + ) + + return new_card + + +def to_dict(self): + return { + "card_id": self.card_id, + "message": self.message, + "liked_count": self.liked_count + } \ No newline at end of file From 3d853cac209520761c53622be9b2851f1c4c48ea Mon Sep 17 00:00:00 2001 From: an2084 Date: Tue, 27 Jun 2023 09:05:49 -0400 Subject: [PATCH 11/35] Post method created for card - not working on Postman yet --- app/routes.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/routes.py b/app/routes.py index 480b8c4b..54110259 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,4 +1,18 @@ from flask import Blueprint, request, jsonify, make_response from app import db +from app.models.card import Card # example_bp = Blueprint('example_bp', __name__) +cards_bp = Blueprint("cards", __name__, url_prefix="/cards") + +@cards_bp.route("", methods=["POST"]) + +def create_card(): + request_body = request.get_json() + + new_card = Card.from_dict(request_body) + + db.session.add(new_card) + db.session.commit() + + return jsonify(new_card.to_dict()), 201 \ No newline at end of file From 550fcba78ef874b0c144e40438031620588a349a Mon Sep 17 00:00:00 2001 From: an2084 Date: Tue, 27 Jun 2023 09:23:49 -0400 Subject: [PATCH 12/35] Added GET and DELETE routes for CARD --- app/routes.py | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/app/routes.py b/app/routes.py index 54110259..46e28c2d 100644 --- a/app/routes.py +++ b/app/routes.py @@ -3,10 +3,23 @@ from app.models.card import Card # example_bp = Blueprint('example_bp', __name__) -cards_bp = Blueprint("cards", __name__, url_prefix="/cards") +card_bp = Blueprint("cards", __name__, url_prefix="/cards") -@cards_bp.route("", methods=["POST"]) +def validate_model(cls, model_id): + try: + model_id = int(model_id) + except: + abort(make_response({"message": f"{model_id} is not a vlid type ({type(model_id)})"})) + model = cls.query.get(model_id) + + if not model: + abort(make_response({"message":f"{cls.__name__} {model_id} does not exist"}, 404)) + + return model + + +@card_bp.route("", methods=["POST"]) def create_card(): request_body = request.get_json() @@ -15,4 +28,29 @@ def create_card(): db.session.add(new_card) db.session.commit() - return jsonify(new_card.to_dict()), 201 \ No newline at end of file + return jsonify(new_card.to_dict()), 201 + + +@card_bp.route("", methods=["GET"]) +def read_all_cards(): + board_query = request.args.get("board") + + if board_query: + cards = Card.query.filter_by(board=board_query) + + cards_response = [] + + for card in cards: + cards_response.append(card.to_dict()) + + return jsonify(cards_response) + + +@card_bp.route("/", methods=["DELETE"]) +def delete_card(card_id): + card = validate_model(Card, card_id) + + db.session.delete(card) + db.session.commit() + + return make_response(f"Card #{card_id} successfully deleted") \ No newline at end of file From 659579c475d3180a1664bb7b99228ac9b54bbee4 Mon Sep 17 00:00:00 2001 From: Rainacam Date: Tue, 27 Jun 2023 10:12:41 -0400 Subject: [PATCH 13/35] added board routes --- app/routes.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/app/routes.py b/app/routes.py index 46e28c2d..3a649efb 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,6 +1,7 @@ from flask import Blueprint, request, jsonify, make_response from app import db from app.models.card import Card +from app.models.board import Board # example_bp = Blueprint('example_bp', __name__) card_bp = Blueprint("cards", __name__, url_prefix="/cards") @@ -53,4 +54,65 @@ def delete_card(card_id): db.session.delete(card) db.session.commit() - return make_response(f"Card #{card_id} successfully deleted") \ No newline at end of file + return make_response(f"Card #{card_id} successfully deleted") + +from app.models.board import Board + +# example_bp = Blueprint('example_bp', __name__) +board_bp =Blueprint("boards", __name__, url_prefix="/boards" ) + + +def validate_model(cls, model_id): + try: + model_id = int(model_id) + except: + abort(make_response({"message": f"{model_id} invalid type ({type(model_id)})"}, 400)) + + model = cls.query.get(model_id) + + if not model: + abort(make_response({"message":f"{model_id} invalid"}, 400)) + + return model + + +# CREATE new board +@board_bp.route("", methods=["POST"]) +def create_new_board(): + request_body = request.get_json() + + # pass in request_body + new_board = Board.from_dict(request_body) + + db.session.add(new_board) + db.session.commit() + + return jsonify(f"Board {new_board.title} successfully created"), 201 + + +# READ all boards +@board_bp.route("", methods=["GET"]) +def read_all_boards(): + boards_response = [] + + board_title_query = request.args.get("title") + board_owner_query = request.args.get("owner") + + if board_title_query: + boards = Board.query.filter_by(title=board_title_query) + elif board_owner_query: + boards = Board.query.filter_by(owner=board_owner_query) + else: + boards = Board.query.all() + + for board in boards: + boards_response.append(board.to_dict()) + return jsonify(boards_response) + + +# READ a specific board +@board_bp.route("/", methods=["GET"]) +def read_one_board(board_id): + board = validate_model(Board, board_id) + + return board.to_dict(), 200 \ No newline at end of file From d312aade3a3b2ff35c27d24e7def80eae7a1cf9f Mon Sep 17 00:00:00 2001 From: Rainacam Date: Tue, 27 Jun 2023 10:20:01 -0400 Subject: [PATCH 14/35] created separate routes in folder --- app/{routes.py => routes/board-routes.py} | 58 ----------------------- app/routes/card-routes.py | 58 +++++++++++++++++++++++ 2 files changed, 58 insertions(+), 58 deletions(-) rename app/{routes.py => routes/board-routes.py} (53%) create mode 100644 app/routes/card-routes.py diff --git a/app/routes.py b/app/routes/board-routes.py similarity index 53% rename from app/routes.py rename to app/routes/board-routes.py index 3a649efb..dfb3ff57 100644 --- a/app/routes.py +++ b/app/routes/board-routes.py @@ -1,67 +1,9 @@ from flask import Blueprint, request, jsonify, make_response from app import db -from app.models.card import Card from app.models.board import Board -# example_bp = Blueprint('example_bp', __name__) -card_bp = Blueprint("cards", __name__, url_prefix="/cards") - -def validate_model(cls, model_id): - try: - model_id = int(model_id) - except: - abort(make_response({"message": f"{model_id} is not a vlid type ({type(model_id)})"})) - - model = cls.query.get(model_id) - - if not model: - abort(make_response({"message":f"{cls.__name__} {model_id} does not exist"}, 404)) - - return model - - -@card_bp.route("", methods=["POST"]) -def create_card(): - request_body = request.get_json() - - new_card = Card.from_dict(request_body) - - db.session.add(new_card) - db.session.commit() - - return jsonify(new_card.to_dict()), 201 - - -@card_bp.route("", methods=["GET"]) -def read_all_cards(): - board_query = request.args.get("board") - - if board_query: - cards = Card.query.filter_by(board=board_query) - - cards_response = [] - - for card in cards: - cards_response.append(card.to_dict()) - - return jsonify(cards_response) - - -@card_bp.route("/", methods=["DELETE"]) -def delete_card(card_id): - card = validate_model(Card, card_id) - - db.session.delete(card) - db.session.commit() - - return make_response(f"Card #{card_id} successfully deleted") - -from app.models.board import Board - -# example_bp = Blueprint('example_bp', __name__) board_bp =Blueprint("boards", __name__, url_prefix="/boards" ) - def validate_model(cls, model_id): try: model_id = int(model_id) diff --git a/app/routes/card-routes.py b/app/routes/card-routes.py new file mode 100644 index 00000000..b033e789 --- /dev/null +++ b/app/routes/card-routes.py @@ -0,0 +1,58 @@ +from flask import Blueprint, request, jsonify, make_response +from app import db +from app.models.card import Card + +# example_bp = Blueprint('example_bp', __name__) +card_bp = Blueprint("cards", __name__, url_prefix="/cards") + +def validate_model(cls, model_id): + try: + model_id = int(model_id) + except: + abort(make_response({"message": f"{model_id} is not a vlid type ({type(model_id)})"})) + + model = cls.query.get(model_id) + + if not model: + abort(make_response({"message":f"{cls.__name__} {model_id} does not exist"}, 404)) + + return model + + +@card_bp.route("", methods=["POST"]) +def create_card(): + request_body = request.get_json() + + new_card = Card.from_dict(request_body) + + db.session.add(new_card) + db.session.commit() + + return jsonify(new_card.to_dict()), 201 + + +@card_bp.route("", methods=["GET"]) +def read_all_cards(): + board_query = request.args.get("board") + + if board_query: + cards = Card.query.filter_by(board=board_query) + + cards_response = [] + + for card in cards: + cards_response.append(card.to_dict()) + + return jsonify(cards_response) + + +@card_bp.route("/", methods=["DELETE"]) +def delete_card(card_id): + card = validate_model(Card, card_id) + + db.session.delete(card) + db.session.commit() + + return make_response(f"Card #{card_id} successfully deleted") + + From 7e74ba391bf06df023720eeb1b3e753d96a300eb Mon Sep 17 00:00:00 2001 From: Rainacam Date: Tue, 27 Jun 2023 10:28:01 -0400 Subject: [PATCH 15/35] to_dict and from_dict funcs restored in board.py --- app/models/board.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/app/models/board.py b/app/models/board.py index 5e3c1a2d..11ab5124 100644 --- a/app/models/board.py +++ b/app/models/board.py @@ -5,4 +5,22 @@ 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") \ No newline at end of file + cards = db.relationship("Card", back_populates="board") + + @classmethod + # data to give that will be organized into a dict + def from_dict(cls, board_data): + new_board = Board( + title=board_data["title"], + owner=board_data["owner"] + ) + + return new_board + + # turn response into dict, using this on an object + def to_dict(self): + return { + "board_id": self.board_id, + "title": self.title, + "owner": self.owner + } \ No newline at end of file From 55e8ac8f9cea472f0bb8f962a50b765804eb9309 Mon Sep 17 00:00:00 2001 From: Rainacam Date: Tue, 27 Jun 2023 10:36:28 -0400 Subject: [PATCH 16/35] registered bps --- app/__init__.py | 5 +++++ app/routes/{board-routes.py => board_routes.py} | 0 app/routes/{card-routes.py => card_routes.py} | 0 3 files changed, 5 insertions(+) rename app/routes/{board-routes.py => board_routes.py} (100%) rename app/routes/{card-routes.py => card_routes.py} (100%) diff --git a/app/__init__.py b/app/__init__.py index a0d17598..6c1fe772 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -25,6 +25,11 @@ def create_app(): # Register Blueprints here # from .routes import example_bp + from app.routes.board_routes import board_bp + from app.routes.card_routes import card_bp + + app.register_blueprint(board_bp) + app.register_blueprint(card_bp) # app.register_blueprint(example_bp) from app.models.card import Card diff --git a/app/routes/board-routes.py b/app/routes/board_routes.py similarity index 100% rename from app/routes/board-routes.py rename to app/routes/board_routes.py diff --git a/app/routes/card-routes.py b/app/routes/card_routes.py similarity index 100% rename from app/routes/card-routes.py rename to app/routes/card_routes.py From 884504b68bcbe9658e68b78ab563f2f4d2026da7 Mon Sep 17 00:00:00 2001 From: an2084 Date: Tue, 27 Jun 2023 11:23:27 -0400 Subject: [PATCH 17/35] Indentation fixed for from_dict and to_dict --- app/models/card.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/models/card.py b/app/models/card.py index cf0144da..8bb9373b 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -8,19 +8,19 @@ class Card(db.Model): board_fk = db.Column(db.Integer, db.ForeignKey('board.board_id')) -@classmethod -def from_dict(cls, card_data): - new_card = Card( - message = card_data["message"], - liked_count = card_data["liked_count"] - ) + @classmethod + def from_dict(cls, card_data): + new_card = Card( + message = card_data["message"], + liked_count = card_data["liked_count"] + ) - return new_card + return new_card -def to_dict(self): - return { - "card_id": self.card_id, - "message": self.message, - "liked_count": self.liked_count - } \ No newline at end of file + def to_dict(self): + return { + "card_id": self.card_id, + "message": self.message, + "liked_count": self.liked_count + } \ No newline at end of file From 8453573e8722755aa191f836bb7863fe558e5544 Mon Sep 17 00:00:00 2001 From: an2084 Date: Tue, 27 Jun 2023 11:24:10 -0400 Subject: [PATCH 18/35] Get all cards route in progress, instructions added --- app/routes/card_routes.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/routes/card_routes.py b/app/routes/card_routes.py index b033e789..be8bf5de 100644 --- a/app/routes/card_routes.py +++ b/app/routes/card_routes.py @@ -45,6 +45,12 @@ def read_all_cards(): return jsonify(cards_response) + # 1. Alter route to expect a board id +# 2. validate board and return respective board +# 3. Loop through board.cards +# 4. Append dict representation of card to card response +# 5. Send card response in response body + @card_bp.route("/", methods=["DELETE"]) def delete_card(card_id): From 496f4e9f018f396615c459814e6c59e7f8f8f18b Mon Sep 17 00:00:00 2001 From: an2084 Date: Tue, 27 Jun 2023 11:25:11 -0400 Subject: [PATCH 19/35] Migrations folder updated --- migrations/versions/136e5f4b9890_.py | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 migrations/versions/136e5f4b9890_.py diff --git a/migrations/versions/136e5f4b9890_.py b/migrations/versions/136e5f4b9890_.py new file mode 100644 index 00000000..fa8ad2c7 --- /dev/null +++ b/migrations/versions/136e5f4b9890_.py @@ -0,0 +1,42 @@ +"""empty message + +Revision ID: 136e5f4b9890 +Revises: +Create Date: 2023-06-27 10:58:49.174623 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '136e5f4b9890' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('board', + sa.Column('board_id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('title', sa.String(), nullable=True), + sa.Column('owner', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('board_id') + ) + op.create_table('card', + sa.Column('card_id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('message', sa.String(), nullable=True), + sa.Column('liked_count', sa.Integer(), nullable=True), + sa.Column('board_fk', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['board_fk'], ['board.board_id'], ), + sa.PrimaryKeyConstraint('card_id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('card') + op.drop_table('board') + # ### end Alembic commands ### From e2dd4825b2af76aca9ea7d9c644f7ebc4a906e5f Mon Sep 17 00:00:00 2001 From: an2084 Date: Tue, 27 Jun 2023 11:34:14 -0400 Subject: [PATCH 20/35] Created entire new migrations folder --- ...19008e0195f2_adds_board_and_card_models.py | 40 ------------------- .../{136e5f4b9890_.py => b244007a8da6_.py} | 6 +-- 2 files changed, 3 insertions(+), 43 deletions(-) delete mode 100644 migrations/versions/19008e0195f2_adds_board_and_card_models.py rename migrations/versions/{136e5f4b9890_.py => b244007a8da6_.py} (92%) diff --git a/migrations/versions/19008e0195f2_adds_board_and_card_models.py b/migrations/versions/19008e0195f2_adds_board_and_card_models.py deleted file mode 100644 index b4e10dbd..00000000 --- a/migrations/versions/19008e0195f2_adds_board_and_card_models.py +++ /dev/null @@ -1,40 +0,0 @@ -"""adds Board and Card models - -Revision ID: 19008e0195f2 -Revises: -Create Date: 2023-06-26 12:46:06.484561 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '19008e0195f2' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('board') - op.drop_table('card') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('card', - sa.Column('card_id', sa.INTEGER(), autoincrement=False, nullable=False), - sa.Column('message', sa.VARCHAR(length=500), autoincrement=False, nullable=True), - sa.Column('likes_count', sa.INTEGER(), autoincrement=False, nullable=True), - sa.PrimaryKeyConstraint('card_id', name='card_pkey') - ) - op.create_table('board', - sa.Column('board_id', sa.INTEGER(), autoincrement=False, nullable=False), - sa.Column('title', sa.VARCHAR(length=100), autoincrement=False, nullable=True), - sa.Column('owner', sa.VARCHAR(length=100), autoincrement=False, nullable=True), - sa.PrimaryKeyConstraint('board_id', name='board_pkey') - ) - # ### end Alembic commands ### diff --git a/migrations/versions/136e5f4b9890_.py b/migrations/versions/b244007a8da6_.py similarity index 92% rename from migrations/versions/136e5f4b9890_.py rename to migrations/versions/b244007a8da6_.py index fa8ad2c7..454ebe3b 100644 --- a/migrations/versions/136e5f4b9890_.py +++ b/migrations/versions/b244007a8da6_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 136e5f4b9890 +Revision ID: b244007a8da6 Revises: -Create Date: 2023-06-27 10:58:49.174623 +Create Date: 2023-06-27 11:33:22.662215 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '136e5f4b9890' +revision = 'b244007a8da6' down_revision = None branch_labels = None depends_on = None From b74891852c26cd28d2086a4bb17aa26bf30d39b2 Mon Sep 17 00:00:00 2001 From: maggie weir Date: Tue, 27 Jun 2023 11:45:25 -0400 Subject: [PATCH 21/35] added deletions --- migrations/README | 1 - migrations/alembic.ini | 45 ------------- migrations/env.py | 96 ---------------------------- migrations/script.py.mako | 24 ------- migrations/versions/b244007a8da6_.py | 42 ------------ 5 files changed, 208 deletions(-) delete mode 100644 migrations/README delete mode 100644 migrations/alembic.ini delete mode 100644 migrations/env.py delete mode 100644 migrations/script.py.mako delete mode 100644 migrations/versions/b244007a8da6_.py diff --git a/migrations/README b/migrations/README deleted file mode 100644 index 98e4f9c4..00000000 --- a/migrations/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini deleted file mode 100644 index f8ed4801..00000000 --- a/migrations/alembic.ini +++ /dev/null @@ -1,45 +0,0 @@ -# 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 diff --git a/migrations/env.py b/migrations/env.py deleted file mode 100644 index 8b3fb335..00000000 --- a/migrations/env.py +++ /dev/null @@ -1,96 +0,0 @@ -from __future__ import with_statement - -import logging -from logging.config import fileConfig - -from sqlalchemy import engine_from_config -from sqlalchemy import pool -from flask import current_app - -from alembic import context - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config - -# Interpret the config file for Python logging. -# This line sets up loggers basically. -fileConfig(config.config_file_name) -logger = logging.getLogger('alembic.env') - -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -config.set_main_option( - 'sqlalchemy.url', - str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) -target_metadata = current_app.extensions['migrate'].db.metadata - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. - - -def run_migrations_offline(): - """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - - """ - url = config.get_main_option("sqlalchemy.url") - context.configure( - url=url, target_metadata=target_metadata, literal_binds=True - ) - - with context.begin_transaction(): - context.run_migrations() - - -def run_migrations_online(): - """Run migrations in 'online' mode. - - In this scenario we need to create an Engine - and associate a connection with the context. - - """ - - # this callback is used to prevent an auto-migration from being generated - # when there are no changes to the schema - # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html - def process_revision_directives(context, revision, directives): - if getattr(config.cmd_opts, 'autogenerate', False): - script = directives[0] - if script.upgrade_ops.is_empty(): - directives[:] = [] - logger.info('No changes in schema detected.') - - connectable = engine_from_config( - config.get_section(config.config_ini_section), - prefix='sqlalchemy.', - poolclass=pool.NullPool, - ) - - with connectable.connect() as connection: - context.configure( - connection=connection, - target_metadata=target_metadata, - process_revision_directives=process_revision_directives, - **current_app.extensions['migrate'].configure_args - ) - - with context.begin_transaction(): - context.run_migrations() - - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako deleted file mode 100644 index 2c015630..00000000 --- a/migrations/script.py.mako +++ /dev/null @@ -1,24 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -# revision identifiers, used by Alembic. -revision = ${repr(up_revision)} -down_revision = ${repr(down_revision)} -branch_labels = ${repr(branch_labels)} -depends_on = ${repr(depends_on)} - - -def upgrade(): - ${upgrades if upgrades else "pass"} - - -def downgrade(): - ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/b244007a8da6_.py b/migrations/versions/b244007a8da6_.py deleted file mode 100644 index 454ebe3b..00000000 --- a/migrations/versions/b244007a8da6_.py +++ /dev/null @@ -1,42 +0,0 @@ -"""empty message - -Revision ID: b244007a8da6 -Revises: -Create Date: 2023-06-27 11:33:22.662215 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'b244007a8da6' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('board', - sa.Column('board_id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('title', sa.String(), nullable=True), - sa.Column('owner', sa.String(), nullable=True), - sa.PrimaryKeyConstraint('board_id') - ) - op.create_table('card', - sa.Column('card_id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('message', sa.String(), nullable=True), - sa.Column('liked_count', sa.Integer(), nullable=True), - sa.Column('board_fk', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['board_fk'], ['board.board_id'], ), - sa.PrimaryKeyConstraint('card_id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('card') - op.drop_table('board') - # ### end Alembic commands ### From 4a8a6d9bdad5cad0b241ee1fb9cc4dcd0565858f Mon Sep 17 00:00:00 2001 From: maggie weir Date: Tue, 27 Jun 2023 11:56:30 -0400 Subject: [PATCH 22/35] added migrations --- migrations/README | 1 + migrations/alembic.ini | 45 +++++++++++++ migrations/env.py | 96 ++++++++++++++++++++++++++++ migrations/script.py.mako | 24 +++++++ migrations/versions/a380272d886e_.py | 42 ++++++++++++ 5 files changed, 208 insertions(+) create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/a380272d886e_.py diff --git a/migrations/README b/migrations/README new file mode 100644 index 00000000..98e4f9c4 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 00000000..f8ed4801 --- /dev/null +++ b/migrations/alembic.ini @@ -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 diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 00000000..8b3fb335 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,96 @@ +from __future__ import with_statement + +import logging +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option( + 'sqlalchemy.url', + str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) +target_metadata = current_app.extensions['migrate'].db.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 00000000..2c015630 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/a380272d886e_.py b/migrations/versions/a380272d886e_.py new file mode 100644 index 00000000..669c2ae2 --- /dev/null +++ b/migrations/versions/a380272d886e_.py @@ -0,0 +1,42 @@ +"""empty message + +Revision ID: a380272d886e +Revises: +Create Date: 2023-06-27 11:54:50.682456 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a380272d886e' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('board', + sa.Column('board_id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('title', sa.String(), nullable=True), + sa.Column('owner', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('board_id') + ) + op.create_table('card', + sa.Column('card_id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('message', sa.String(), nullable=True), + sa.Column('liked_count', sa.Integer(), nullable=True), + sa.Column('board_fk', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['board_fk'], ['board.board_id'], ), + sa.PrimaryKeyConstraint('card_id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('card') + op.drop_table('board') + # ### end Alembic commands ### From 5eed4a2929a67560375e327ae25777500164ce87 Mon Sep 17 00:00:00 2001 From: an2084 Date: Tue, 27 Jun 2023 14:21:15 -0400 Subject: [PATCH 23/35] Patch endpoint created to update liked_count, POST by board_id, GET cards by board_id, all working --- app/routes/card_routes.py | 58 +++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/app/routes/card_routes.py b/app/routes/card_routes.py index be8bf5de..3a68c6ca 100644 --- a/app/routes/card_routes.py +++ b/app/routes/card_routes.py @@ -1,10 +1,12 @@ 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 app.routes.board_routes import board_bp -# example_bp = Blueprint('example_bp', __name__) -card_bp = Blueprint("cards", __name__, url_prefix="/cards") +card_bp = Blueprint("cards", __name__, url_prefix="/cards") +### Validate model ### def validate_model(cls, model_id): try: model_id = int(model_id) @@ -19,39 +21,55 @@ def validate_model(cls, model_id): return model -@card_bp.route("", methods=["POST"]) -def create_card(): +### Post a new card under a board ### +@board_bp.route("//cards", methods = ["POST"]) +def create_card_by_board_id(board_id): + + board = validate_model(Board, board_id) + request_body = request.get_json() - new_card = Card.from_dict(request_body) + new_card = Card( + message=request_body["message"], + liked_count=request_body["liked_count"], + board=board + ) db.session.add(new_card) db.session.commit() - return jsonify(new_card.to_dict()), 201 + return jsonify(f"Card {new_card.card_id} under {new_card.board.title} was successfully created."), 201 + +### Get all cards from a board ### +@board_bp.route("//cards", methods=["GET"]) +def get_all_cards_with_board_id(board_id): + board = validate_model(Board, board_id) -@card_bp.route("", methods=["GET"]) -def read_all_cards(): - board_query = request.args.get("board") + card_response = [] - if board_query: - cards = Card.query.filter_by(board=board_query) + for card in board.cards: + card_response.append(card.to_dict()) - cards_response = [] + return jsonify(card_response),200 - for card in cards: - cards_response.append(card.to_dict()) - return jsonify(cards_response) +### Update liked_count by card ### +@card_bp.route('/', methods=['PATCH']) +def update_liked_count(card_id): + card = validate_model(Card, card_id) + + if not card.liked_count: + card.liked_count = 0 + + card.liked_count = card.liked_count + 1 + + db.session.commit() - # 1. Alter route to expect a board id -# 2. validate board and return respective board -# 3. Loop through board.cards -# 4. Append dict representation of card to card response -# 5. Send card response in response body + return card.to_dict(), 200 +### Delete card ### @card_bp.route("/", methods=["DELETE"]) def delete_card(card_id): card = validate_model(Card, card_id) From 24a38c059d02ff8cc6b1e0e5d3d9218d1087c668 Mon Sep 17 00:00:00 2001 From: maggie weir Date: Wed, 28 Jun 2023 11:07:35 -0400 Subject: [PATCH 24/35] updated routes to make functionality work --- app/models/card.py | 3 ++- app/routes/board_routes.py | 2 +- app/routes/card_routes.py | 7 +++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/models/card.py b/app/models/card.py index 8bb9373b..868deb62 100644 --- a/app/models/card.py +++ b/app/models/card.py @@ -22,5 +22,6 @@ def to_dict(self): return { "card_id": self.card_id, "message": self.message, - "liked_count": self.liked_count + "liked_count": self.liked_count, + "board_id": self.board_fk } \ No newline at end of file diff --git a/app/routes/board_routes.py b/app/routes/board_routes.py index dfb3ff57..e147c265 100644 --- a/app/routes/board_routes.py +++ b/app/routes/board_routes.py @@ -29,7 +29,7 @@ def create_new_board(): db.session.add(new_board) db.session.commit() - return jsonify(f"Board {new_board.title} successfully created"), 201 + return jsonify(new_board.to_dict()), 201 # READ all boards diff --git a/app/routes/card_routes.py b/app/routes/card_routes.py index 3a68c6ca..3ded1e75 100644 --- a/app/routes/card_routes.py +++ b/app/routes/card_routes.py @@ -24,22 +24,21 @@ def validate_model(cls, model_id): ### Post a new card under a board ### @board_bp.route("//cards", methods = ["POST"]) def create_card_by_board_id(board_id): - board = validate_model(Board, board_id) request_body = request.get_json() new_card = Card( message=request_body["message"], - liked_count=request_body["liked_count"], + liked_count=0, board=board ) db.session.add(new_card) db.session.commit() - return jsonify(f"Card {new_card.card_id} under {new_card.board.title} was successfully created."), 201 - + return jsonify(new_card.to_dict()), 201 + # we want response to be new_card object with id, message, board, like count ### Get all cards from a board ### @board_bp.route("//cards", methods=["GET"]) From 2dd52c095e0741c30055918b4055c4445a8dda6f Mon Sep 17 00:00:00 2001 From: Rainacam Date: Wed, 28 Jun 2023 11:34:51 -0400 Subject: [PATCH 25/35] delete board route created --- app/routes/board_routes.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/routes/board_routes.py b/app/routes/board_routes.py index e147c265..719439c5 100644 --- a/app/routes/board_routes.py +++ b/app/routes/board_routes.py @@ -1,4 +1,4 @@ -from flask import Blueprint, request, jsonify, make_response +from flask import Blueprint, request, jsonify, make_response, abort from app import db from app.models.board import Board @@ -57,4 +57,14 @@ def read_all_boards(): def read_one_board(board_id): board = validate_model(Board, board_id) - return board.to_dict(), 200 \ No newline at end of file + return board.to_dict(), 200 + +# DELETE one board +@board_bp.route("/", methods=["DELETE"]) +def delete_one_board(board_id): + board = validate_model(Board, board_id) + + db.session.delete(board) + db.session.commit() + + return make_response(f"Board {board.board_id} successfully deleted") \ No newline at end of file From df93adb8e474f9709a98d696abef6dff882e4040 Mon Sep 17 00:00:00 2001 From: maggie weir Date: Wed, 28 Jun 2023 18:36:15 -0400 Subject: [PATCH 26/35] updated board model to_dict method to include cards --- app/models/board.py | 3 ++- app/routes/board_routes.py | 2 +- app/routes/card_routes.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/board.py b/app/models/board.py index 11ab5124..89a1c63f 100644 --- a/app/models/board.py +++ b/app/models/board.py @@ -22,5 +22,6 @@ def to_dict(self): return { "board_id": self.board_id, "title": self.title, - "owner": self.owner + "owner": self.owner, + "cards": self.cards } \ No newline at end of file diff --git a/app/routes/board_routes.py b/app/routes/board_routes.py index 719439c5..d6154be8 100644 --- a/app/routes/board_routes.py +++ b/app/routes/board_routes.py @@ -67,4 +67,4 @@ def delete_one_board(board_id): db.session.delete(board) db.session.commit() - return make_response(f"Board {board.board_id} successfully deleted") \ No newline at end of file + return make_response(board.to_dict()) \ No newline at end of file diff --git a/app/routes/card_routes.py b/app/routes/card_routes.py index 3ded1e75..0a1b50b7 100644 --- a/app/routes/card_routes.py +++ b/app/routes/card_routes.py @@ -1,4 +1,4 @@ -from flask import Blueprint, request, jsonify, make_response +from flask import Blueprint, request, jsonify, make_response, abort from app import db from app.models.card import Card from app.models.board import Board From e4d2a4cb518e576aeb6d677bfb23ed124fcaf45a Mon Sep 17 00:00:00 2001 From: Elaine Watkins Date: Thu, 29 Jun 2023 11:06:32 -0400 Subject: [PATCH 27/35] updated the board component, cards attribute to serialize --- app/models/board.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/board.py b/app/models/board.py index 89a1c63f..0d363037 100644 --- a/app/models/board.py +++ b/app/models/board.py @@ -14,7 +14,6 @@ def from_dict(cls, board_data): title=board_data["title"], owner=board_data["owner"] ) - return new_board # turn response into dict, using this on an object From 0a71256889f5782325721cdaf715a456aba32636 Mon Sep 17 00:00:00 2001 From: maggie weir Date: Thu, 29 Jun 2023 11:36:36 -0400 Subject: [PATCH 28/35] updated cors headers to fix error --- app/__init__.py | 5 +++-- app/models/board.py | 3 +-- requirements.txt | 17 ++++++++++++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 6c1fe772..f4c7df74 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -13,7 +13,8 @@ def create_app(): app = Flask(__name__) app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False - + CORS(app) + app.config["CORS_HEADERS"] = "Content-Type" app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get( "SQLALCHEMY_DATABASE_URI") @@ -34,5 +35,5 @@ def create_app(): from app.models.card import Card from app.models.board import Board - CORS(app) + return app diff --git a/app/models/board.py b/app/models/board.py index 0d363037..7ef011f5 100644 --- a/app/models/board.py +++ b/app/models/board.py @@ -21,6 +21,5 @@ def to_dict(self): return { "board_id": self.board_id, "title": self.title, - "owner": self.owner, - "cards": self.cards + "owner": self.owner } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 76f1f000..3b321ee0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,22 @@ alembic==1.5.4 -attrs==21.2.0 +attrs==20.3.0 autopep8==1.5.5 +big-O-calculator==0.0.9.8.4 +blinker==1.4 certifi==2020.12.5 chardet==4.0.0 +charset-normalizer==2.0.12 click==7.1.2 +coverage==7.2.5 +exceptiongroup==1.1.1 Flask==1.1.2 -Flask-Cors==3.0.10 +Flask-Cors==4.0.0 Flask-Migrate==2.6.0 Flask-SQLAlchemy==2.4.4 +greenlet==1.1.3 gunicorn==20.1.0 idna==2.10 +importlib-metadata==4.11.3 iniconfig==1.1.1 itsdangerous==1.1.0 Jinja2==2.11.3 @@ -22,6 +29,7 @@ py==1.11.0 pycodestyle==2.6.0 pyparsing==2.4.7 pytest==7.1.1 +pytest-cov==2.12.1 python-dateutil==2.8.1 python-dotenv==0.15.0 python-editor==1.0.4 @@ -29,5 +37,8 @@ requests==2.25.1 six==1.15.0 SQLAlchemy==1.3.23 toml==0.10.2 -urllib3==1.26.4 +tomli==2.0.1 +urllib3==1.26.5 Werkzeug==1.0.1 +wonderwords==2.2.0 +zipp==3.7.0 From c206a64965d341620d9c587ddd5d4019e65c4c89 Mon Sep 17 00:00:00 2001 From: Hannah Date: Mon, 17 Jul 2023 23:03:28 -0400 Subject: [PATCH 29/35] added user login routes --- app/__init__.py | 3 +++ app/models/user.py | 16 +++++++++++++++ app/routes/user_routes.py | 41 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 app/models/user.py create mode 100644 app/routes/user_routes.py diff --git a/app/__init__.py b/app/__init__.py index f4c7df74..1358173f 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -4,6 +4,7 @@ from dotenv import load_dotenv import os from flask_cors import CORS +from app.routes.user_routes import user_bp db = SQLAlchemy() migrate = Migrate() @@ -31,6 +32,8 @@ def create_app(): app.register_blueprint(board_bp) app.register_blueprint(card_bp) + app.register_blueprint(user_bp) + # app.register_blueprint(example_bp) from app.models.card import Card diff --git a/app/models/user.py b/app/models/user.py new file mode 100644 index 00000000..4760ef2c --- /dev/null +++ b/app/models/user.py @@ -0,0 +1,16 @@ +from app import db + +class User(db.Model): + user_id = db.Column(db.Integer, primary_key=True, autoincrement=True) + username = db.Column(db.String, unique=True, nullable=False) + password = db.Column(db.String, nullable=False) + + def __init__(self, username, password): + self.username = username + self.password = password + + def to_dict(self): + return { + "user_id": self.user_id, + "username": self.username + } \ No newline at end of file diff --git a/app/routes/user_routes.py b/app/routes/user_routes.py new file mode 100644 index 00000000..8f96ccf4 --- /dev/null +++ b/app/routes/user_routes.py @@ -0,0 +1,41 @@ +from flask import Blueprint, request, jsonify, make_response +from app import db +from app.models.user import User + +user_bp = Blueprint("user", __name__, url_prefix="/user") + +@user_bp.route("/login", methods=["POST"]) +def login_user(): + data = request.get_json() + username = data.get("username") + password = data.get("password") + + if not username or not password: + return make_response(jsonify({"message": "Username and password are required."}), 400) + + user = User.query.filter_by(username=username).first() + + if not user or user.password != password: + return make_response(jsonify({"message": "Invalid username or password."}), 401) + + return jsonify({"message": "Login successful.", "user": user.to_dict()}), 200 + +@user_bp.route("/register", methods=["POST"]) +def register_user(): + data = request.get_json() + username = data.get("username") + password = data.get("password") + + if not username or not password: + return make_response(jsonify({"message": "Username and password are required."}), 400) + + existing_user = User.query.filter_by(username=username).first() + if existing_user: + return make_response(jsonify({"message": "Username already taken."}), 409) + + new_user = User(username=username, password=password) + db.session.add(new_user) + db.session.commit() + + return jsonify({"message": "User registered successfully.", "user": new_user.to_dict()}), 201 + From fba7c365b055d999c20f84dc6f818ee52a68e2c3 Mon Sep 17 00:00:00 2001 From: Rainacam Date: Tue, 18 Jul 2023 17:10:57 -0400 Subject: [PATCH 30/35] backend reconnect --- app/__init__.py | 3 ++- app/models/board.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 1358173f..d1aae3ce 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -4,7 +4,6 @@ from dotenv import load_dotenv import os from flask_cors import CORS -from app.routes.user_routes import user_bp db = SQLAlchemy() migrate = Migrate() @@ -29,6 +28,7 @@ def create_app(): # from .routes import example_bp from app.routes.board_routes import board_bp from app.routes.card_routes import card_bp + from app.routes.user_routes import user_bp app.register_blueprint(board_bp) app.register_blueprint(card_bp) @@ -38,5 +38,6 @@ def create_app(): from app.models.card import Card from app.models.board import Board + from app.models.user import User return app diff --git a/app/models/board.py b/app/models/board.py index 7ef011f5..9ac0af21 100644 --- a/app/models/board.py +++ b/app/models/board.py @@ -1,5 +1,4 @@ from app import db -from app import db class Board(db.Model): board_id = db.Column(db.Integer, primary_key=True, autoincrement=True) From 6f0eb3430e759390d1a8e58e4e9d8d86a8e41c9d Mon Sep 17 00:00:00 2001 From: Hannah Date: Wed, 19 Jul 2023 10:54:24 -0400 Subject: [PATCH 31/35] changed user.py file to include first name & last for registration --- app/models/user.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/models/user.py b/app/models/user.py index 4760ef2c..c51195ff 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -4,13 +4,19 @@ class User(db.Model): user_id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String, unique=True, nullable=False) password = db.Column(db.String, nullable=False) + first_name = db.Column(db.String, nullable=False) + last_name = db.Column(db.String, nullable=False) - def __init__(self, username, password): + def __init__(self, username, password, first_name, last_name): self.username = username self.password = password + self.first_name = first_name + self.last_name = last_name def to_dict(self): return { "user_id": self.user_id, - "username": self.username - } \ No newline at end of file + "username": self.username, + "first_name": self.first_name, + "last_name": self.last_name + } From 2c6f0a0a1dcd2a3701da65fd5bb7268eaa8487bd Mon Sep 17 00:00:00 2001 From: Hannah Date: Wed, 19 Jul 2023 10:59:11 -0400 Subject: [PATCH 32/35] changed user_routes.py file to include first name & last for registration --- app/routes/user_routes.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/routes/user_routes.py b/app/routes/user_routes.py index 8f96ccf4..7090fc63 100644 --- a/app/routes/user_routes.py +++ b/app/routes/user_routes.py @@ -25,17 +25,20 @@ def register_user(): data = request.get_json() username = data.get("username") password = data.get("password") + first_name = data.get("first_name") + last_name = data.get("last_name") - if not username or not password: - return make_response(jsonify({"message": "Username and password are required."}), 400) + if not username or not password or not first_name or not last_name: + return make_response(jsonify({"message": "Username, password, first name, and last name are required."}), 400) existing_user = User.query.filter_by(username=username).first() if existing_user: return make_response(jsonify({"message": "Username already taken."}), 409) - new_user = User(username=username, password=password) + new_user = User(username=username, password=password, first_name=first_name, last_name=last_name) db.session.add(new_user) db.session.commit() return jsonify({"message": "User registered successfully.", "user": new_user.to_dict()}), 201 + From 1e3f16f79aac67b6d0a87c9414b9442a6f088366 Mon Sep 17 00:00:00 2001 From: maggie weir Date: Thu, 20 Jul 2023 10:42:12 -0400 Subject: [PATCH 33/35] commit --- app/routes/card_routes.py | 2 +- app/routes/user_routes.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/routes/card_routes.py b/app/routes/card_routes.py index 0a1b50b7..a351deec 100644 --- a/app/routes/card_routes.py +++ b/app/routes/card_routes.py @@ -11,7 +11,7 @@ def validate_model(cls, model_id): try: model_id = int(model_id) except: - abort(make_response({"message": f"{model_id} is not a vlid type ({type(model_id)})"})) + abort(make_response({"message": f"{model_id} is not a valid type ({type(model_id)})"})) model = cls.query.get(model_id) diff --git a/app/routes/user_routes.py b/app/routes/user_routes.py index 7090fc63..e0b8a316 100644 --- a/app/routes/user_routes.py +++ b/app/routes/user_routes.py @@ -9,7 +9,7 @@ def login_user(): data = request.get_json() username = data.get("username") password = data.get("password") - + if not username or not password: return make_response(jsonify({"message": "Username and password are required."}), 400) @@ -20,13 +20,14 @@ def login_user(): return jsonify({"message": "Login successful.", "user": user.to_dict()}), 200 + @user_bp.route("/register", methods=["POST"]) def register_user(): data = request.get_json() username = data.get("username") password = data.get("password") - first_name = data.get("first_name") - last_name = data.get("last_name") + first_name = data.get("firstName") + last_name = data.get("lastName") if not username or not password or not first_name or not last_name: return make_response(jsonify({"message": "Username, password, first name, and last name are required."}), 400) @@ -39,6 +40,6 @@ def register_user(): db.session.add(new_user) db.session.commit() - return jsonify({"message": "User registered successfully.", "user": new_user.to_dict()}), 201 + return new_user.to_dict(), 201 From 770057e7e69a04ac3912ddb94c74725add36b066 Mon Sep 17 00:00:00 2001 From: an2084 Date: Fri, 21 Jul 2023 08:09:11 -0400 Subject: [PATCH 34/35] Updated render link to init file --- app/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index d1aae3ce..693beb0b 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -16,7 +16,7 @@ def create_app(): CORS(app) app.config["CORS_HEADERS"] = "Content-Type" app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get( - "SQLALCHEMY_DATABASE_URI") + "RENDER_DATABASE_URI") # Import models here for Alembic setup # from app.models.ExampleModel import ExampleModel From fe63fdc577d56f64e89b5aecfeb80d3cee689f89 Mon Sep 17 00:00:00 2001 From: an2084 Date: Fri, 21 Jul 2023 08:09:29 -0400 Subject: [PATCH 35/35] New migrations created after render connection built --- migrations/versions/046b9e416046_.py | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 migrations/versions/046b9e416046_.py diff --git a/migrations/versions/046b9e416046_.py b/migrations/versions/046b9e416046_.py new file mode 100644 index 00000000..2d7d3b99 --- /dev/null +++ b/migrations/versions/046b9e416046_.py @@ -0,0 +1,36 @@ +"""empty message + +Revision ID: 046b9e416046 +Revises: a380272d886e +Create Date: 2023-07-20 13:36:11.406732 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '046b9e416046' +down_revision = 'a380272d886e' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('user', + sa.Column('user_id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('username', sa.String(), nullable=False), + sa.Column('password', sa.String(), nullable=False), + sa.Column('first_name', sa.String(), nullable=False), + sa.Column('last_name', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('user_id'), + sa.UniqueConstraint('username') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('user') + # ### end Alembic commands ###