diff --git a/backend/alembic/env.py b/backend/alembic/env.py index fdc577c5..36eece60 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -7,8 +7,8 @@ from database import Base from stories import models as story_models from users import models as user_models - from NytLiveCounty import models as nyt_models +from likes import models as like_models # this is the Alembic Config object, which provides # access to the values within the .ini file in use. diff --git a/backend/alembic/versions/edace0efb31c_add_table_for_like_and_dislike.py b/backend/alembic/versions/edace0efb31c_add_table_for_like_and_dislike.py new file mode 100644 index 00000000..686970ce --- /dev/null +++ b/backend/alembic/versions/edace0efb31c_add_table_for_like_and_dislike.py @@ -0,0 +1,41 @@ +"""add table for like and dislike + +Revision ID: edace0efb31c +Revises: 527194b9fb18 +Create Date: 2020-10-14 02:51:38.265440 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "edace0efb31c" +down_revision = "527194b9fb18" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "likes", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("created_at", sa.DateTime(), nullable=True), + sa.Column("updated_at", sa.DateTime(), nullable=True), + sa.Column("like", sa.Boolean(), nullable=True), + sa.Column("my_story_id", sa.Integer(), nullable=True), + sa.Column("liker_story_id", sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(["liker_story_id"], ["stories.id"],), + sa.ForeignKeyConstraint(["my_story_id"], ["my_stories.id"],), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_likes_id"), "likes", ["id"], unique=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f("ix_likes_id"), table_name="likes") + op.drop_table("likes") + # ### end Alembic commands ### diff --git a/backend/likes/__init__.py b/backend/likes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/likes/crud.py b/backend/likes/crud.py new file mode 100644 index 00000000..b69fe44f --- /dev/null +++ b/backend/likes/crud.py @@ -0,0 +1,61 @@ +from sqlalchemy.orm import Session +from . import models, schemas +from sqlalchemy.sql.expression import and_ +from stories.crud import update + + +def get_like_by_story_and_user( + db: Session, my_story_id: int, liker_story_id: int +): + return ( + db.query(models.Like) + .filter( + and_( + models.Like.my_story_id == my_story_id, + models.Like.liker_story_id == liker_story_id, + ) + ) + .first() + ) + + +def update_like(db, like_id, like: schemas.LikeCreate): + return update(like_id, like, models.Like, db) + + +def create_like(db, like: schemas.LikeCreate, liker_story_id: int): + d = like.dict() + d["liker_story_id"] = liker_story_id + db_like = models.Like(**d) + db.add(db_like) + db.commit() + db.refresh(db_like) + return db_like + + +def get_like_count(db, my_story_id): + return ( + db.query(models.Like) + .filter( + and_(models.Like.my_story_id == my_story_id, models.Like.like == 1) + ) + .count() + ) + + +def get_dislike_count(db, my_story_id): + return ( + db.query(models.Like) + .filter( + and_(models.Like.my_story_id == my_story_id, models.Like.like == 0) + ) + .count() + ) + + +def is_like_by(db, my_story_id, liker_story_id): + db_like = get_like_by_story_and_user(db, my_story_id, liker_story_id) + if db_like: + return db_like.like + else: + return None diff --git a/backend/likes/models.py b/backend/likes/models.py new file mode 100644 index 00000000..c2cbfe87 --- /dev/null +++ b/backend/likes/models.py @@ -0,0 +1,13 @@ +from database import Base +from sqlalchemy import Column, ForeignKey, Integer, Boolean +from sqlalchemy.orm import relationship + + +class Like(Base): + __tablename__ = "likes" + + like = Column(Boolean) + my_story_id = Column(Integer, ForeignKey("my_stories.id")) + liker_story_id = Column(Integer, ForeignKey("stories.id")) + + my_story = relationship("MyStory", lazy="select") diff --git a/backend/likes/schemas.py b/backend/likes/schemas.py new file mode 100644 index 00000000..f6cfce53 --- /dev/null +++ b/backend/likes/schemas.py @@ -0,0 +1,13 @@ +from pydantic import BaseModel + + +class LikeCreate(BaseModel): + like: bool = None + my_story_id: int + + +class Like(LikeCreate): + liker_story_id: int + + class Config: + orm_mode = True diff --git a/backend/router/api.py b/backend/router/api.py index 3a549c0b..7adf4894 100644 --- a/backend/router/api.py +++ b/backend/router/api.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from router import auth, stories, users, symptoms, data, nyt_live_county +from router import auth, stories, users, symptoms, data, nyt_live_county, likes router = APIRouter() @@ -17,3 +17,5 @@ router.include_router( nyt_live_county.router, prefix="/nyt_live_county", tags=["nyt_live_county"] ) + +router.include_router(likes.router, prefix="/likes", tags=["likes"]) diff --git a/backend/router/likes.py b/backend/router/likes.py new file mode 100644 index 00000000..3acf77ca --- /dev/null +++ b/backend/router/likes.py @@ -0,0 +1,71 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from starlette.responses import JSONResponse + +from auth import main +from database import get_db +from likes import crud, schemas +from stories.crud import get_my_story +from stories import schemas as stories_schemas + +router = APIRouter() + + +@router.post("/", response_model=schemas.Like) +def create_like( + like: schemas.LikeCreate, + current_story: stories_schemas.Story = Depends(main.get_current_story), + db: Session = Depends(get_db), +): + my_story = get_my_story(db, like.my_story_id) + if not my_story: + raise HTTPException( + status_code=404, + detail="Target my story cannot be found", + headers={"WWW-Authenticate": "Bearer"}, + ) + + if not current_story: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="User haven't shared their story", + headers={"WWW-Authenticate": "Bearer"}, + ) + + like_to_update = crud.get_like_by_story_and_user( + db, my_story_id=like.my_story_id, liker_story_id=current_story.id + ) + + if like_to_update: + db_like = crud.update_like(db, like_to_update.id, like) + else: + if my_story.story_id == current_story.id: + raise HTTPException( + status_code=422, + detail="User cannot like or dislike their own my stories", + headers={"WWW-Authenticate": "Bearer"}, + ) + + db_like = crud.create_like(db, like, current_story.id) + + return db_like + + +@router.get("/{my_story_id}") +def get_like_count( + my_story_id: int, + current_story: stories_schemas.Story = Depends(main.get_current_story), + db: Session = Depends(get_db), +): + like_count = crud.get_like_count(db, my_story_id) + dislike_count = crud.get_dislike_count(db, my_story_id) + is_like_by_me = crud.is_like_by(db, my_story_id, current_story.id) + + return JSONResponse( + { + "like": like_count, + "dislike": dislike_count, + "like_by_me": is_like_by_me, + }, + status_code=200, + ) diff --git a/backend/stories/crud.py b/backend/stories/crud.py index a8b077c9..12e0ade6 100644 --- a/backend/stories/crud.py +++ b/backend/stories/crud.py @@ -146,3 +146,11 @@ def update_latest_my_story(db: Session, story: schemas.Story, my_story): def get_my_story_count(db: Session): return db.query(models.MyStory).count() + + +def get_my_story(db: Session, my_story_id: int): + return ( + db.query(models.MyStory) + .filter(models.MyStory.id == my_story_id) + .first() + )