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

Relationships between users #20

Merged
merged 4 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
71 changes: 71 additions & 0 deletions identity_socializer/db/dao/relationship_dao.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from typing import List

from fastapi import Depends
from sqlalchemy import delete, select
from sqlalchemy.ext.asyncio import AsyncSession

from identity_socializer.db.dependencies import get_db_session
from identity_socializer.db.models.relationship_model import RelationshipModel
from identity_socializer.db.models.user_model import UserModel


class RelationshipDAO:
"""Class for accessing relationships table."""

def __init__(self, session: AsyncSession = Depends(get_db_session)):
self.session = session

async def create_relationship_model(
self,
follower_id: str,
following_id: str,
) -> None:
"""Add single relationship to session."""
relationship_model = RelationshipModel(
follower_id=follower_id,
following_id=following_id,
)

self.session.add(relationship_model)

async def delete_relationship_model(
self,
follower_id: str,
following_id: str,
) -> None:
"""Delete single relationship from session."""
query = delete(RelationshipModel).where(
RelationshipModel.follower_id == follower_id,
RelationshipModel.following_id == following_id,
)
await self.session.execute(query)

async def get_following_by_id(self, user_id: str) -> List[UserModel]:
"""Get following of user_id."""
query = (
select(UserModel)
.join(
RelationshipModel.following,
)
.where(
RelationshipModel.follower_id == user_id,
)
)
rows = await self.session.execute(query)

return list(rows.scalars().fetchall())

async def get_followers_by_id(self, user_id: str) -> List[UserModel]:
"""Get followers of user_id."""
query = (
select(UserModel)
.join(
RelationshipModel.follower,
)
.where(
RelationshipModel.following_id == user_id,
)
)
rows = await self.session.execute(query)

return list(rows.scalars().fetchall())
6 changes: 6 additions & 0 deletions identity_socializer/db/dao/user_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,9 @@ async def filter(
query = query.where(UserModel.first_name == first_name)
rows = await self.session.execute(query)
return list(rows.scalars().fetchall())

async def get_user_by_id(self, user_id: str) -> Optional[UserModel]:
"""Get specific user model."""
query = select(UserModel).where(UserModel.id == user_id)
rows = await self.session.execute(query)
return rows.scalars().first()
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""create relationship table

Revision ID: 6a777ccf275f
Revises: 847eee4ea866
Create Date: 2023-10-15 23:56:33.161484

"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "6a777ccf275f"
down_revision = "847eee4ea866"
branch_labels = None
depends_on = None


def upgrade() -> None:
op.create_table(
"relationships",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("follower_id", sa.String(), nullable=False),
sa.Column("following_id", sa.String(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(
["following_id"],
["users.id"],
name=op.f("fk_relationships_following_id_users"),
),
sa.ForeignKeyConstraint(
["follower_id"],
["users.id"],
name=op.f("fk_relationships_follower_id_users"),
),
sa.PrimaryKeyConstraint("id"),
)


def downgrade() -> None:
op.drop_table("relationships")
31 changes: 31 additions & 0 deletions identity_socializer/db/models/relationship_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import datetime
import uuid

from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql.sqltypes import DateTime, String, Uuid

from identity_socializer.db.base import Base


class RelationshipModel(Base):
"""Model for Relationship."""

__tablename__ = "relationships"

length = 200

id: Mapped[uuid.UUID] = mapped_column(
Uuid,
default=uuid.uuid4,
primary_key=True,
)
follower_id: Mapped[str] = mapped_column(String(length), ForeignKey("users.id"))
following_id: Mapped[str] = mapped_column(String(length), ForeignKey("users.id"))
created_at: Mapped[DateTime] = mapped_column(
DateTime,
default=datetime.datetime.utcnow(),
)

follower = relationship("UserModel", foreign_keys=[follower_id])
following = relationship("UserModel", foreign_keys=[following_id])
60 changes: 59 additions & 1 deletion identity_socializer/web/api/auth/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from typing import List
from typing import List, Optional

from fastapi import APIRouter, Depends, HTTPException
from firebase_admin import auth

from identity_socializer.db.dao.relationship_dao import RelationshipDAO
from identity_socializer.db.dao.user_dao import UserDAO
from identity_socializer.db.models.user_model import UserModel
from identity_socializer.web.api.auth.schema import (
Expand Down Expand Up @@ -64,3 +65,60 @@ async def get_user_models(
:return: list of users objects from database.
"""
return await user_dao.get_all_users(limit=limit, offset=offset)


@router.get("/users/{user_id}", response_model=None)
async def get_user_model(
user_id: str,
user_dao: UserDAO = Depends(),
) -> Optional[UserModel]:
"""Retrieve a user object from the database."""
return await user_dao.get_user_by_id(user_id)


@router.post("/{user_id}/follow/{followed_user_id}", response_model=None)
async def follow_user(
user_id: str,
followed_user_id: str,
relationship_dao: RelationshipDAO = Depends(),
) -> None:
"""
Follow a user.

If user_id or followed_user_id does not exist, the relationship will not be created.
"""
await relationship_dao.create_relationship_model(user_id, followed_user_id)


@router.delete("/{user_id}/unfollow/{followed_user_id}", response_model=None)
async def unfollow_user(
user_id: str,
followed_user_id: str,
relationship_dao: RelationshipDAO = Depends(),
) -> None:
"""
Unfollow a user.

Delete the relationship model between user_id and followed_user_id.
If the relationship between user_id and followed_user_id does not exist,
anything will happen.
"""
await relationship_dao.delete_relationship_model(user_id, followed_user_id)


@router.get("/{user_id}/following", response_model=None)
async def get_following(
user_id: str,
relationship_dao: RelationshipDAO = Depends(),
) -> List[UserModel]:
"""Get following of user_id."""
return await relationship_dao.get_following_by_id(user_id)


@router.get("/{user_id}/followers", response_model=None)
async def get_followers(
user_id: str,
relationship_dao: RelationshipDAO = Depends(),
) -> List[UserModel]:
"""Get followers of user_id."""
return await relationship_dao.get_followers_by_id(user_id)
Loading