MVP
- 사용자 등록/로그인 : 카카오 로그인만 지원
- 채팅 기능 :
- 1:1 실시간 채팅가능
- 초대된 유저 혹은 친구관계인 유저와 채팅가능
- 이스터에그 등록 : 최대 3개의 이스터에그 생성가능. ( 수정 및 삭제도 가능 )
- 이스터에그 힌트 기능 : 유저가 힌트를 받을 수 있는 기능.
- 대화 중 이스터에그 트리거 : 채팅 도중 특정 단어가 입력되면 이스터에그가 트리거됨.
ERD 다이어그램
Users 테이블
- 카카오 로그인 외에도 다른 소셜 로그인 및 자체 로그인을 지원할 수 있도록 설계
ChatroomUsers 테이블
- 채팅방과 유저를 다대다로 매칭하기 위해 필요한 관계테이블
MessageReadStatus 테이블
- 다대일 채팅 설계를 대비하여, 읽은 사람을 추적하는 로직에 필요한 테이블 설계
Friend 테이블
- 채티방 초대로 가입하게 된 유저가 초대자와 친구가 될 수 있도록 설계
EasterEggHistory & EasterEggHintHistory
- 추후 이스터에그 관련한 대시보드 조회할 수 있도록 설계
ORM 모델 정의
CommonFields 클래스 ( models/base.py )
모든 테이블에 created_at과 updated_at 필드를 포함하고 있어, 다른 테이블 모델을 정의할 때 이를 상속하기 위해 만든 클래스이다.
from datetime import datetime
from pydantic import Base
from sqlalchemy import Column, DateTime, func
class CommonFields(Base):
"""공통 필드 클래스"""
__abstract__ = True
created_at = Column(DateTime, default=func.current_timestamp(), nullable=False)
updated_at = Column(
DateTime,
default=func.current_timestamp(),
onupdate=func.current_timestamp(),
nullable=False,
)
class Config:
orm_mode = True
해당 클래스는 상속을 목적으로만 만든 클래스 때문에 __abstract__ = True 를 이용해 추상클래스로 정의했다.
Users ( models/user.py ), ChatRoomsUsers ChatRoos ( models/chats.py )
# models/users.py
class Users(CommonFields):
"""유저 테이블"""
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
email = Column(String(128), unique=True, nullable=False, comment="회원 이메일")
social_id = Column(String(64), nullable=True, comment="소셜로그인 key")
login_type = Column(
String(40), nullable=False, comment="로그인 타입 (KAKAO, EMAIL, ...)"
)
password = Column(String(64), nullable=True, comment="비밀번호")
profile = Column(String(64), nullable=True, comment="프로필 이미지")
nickname = Column(String(40), nullable=False, comment="닉네임")
is_admin = Column(Integer, default=0, nullable=False, comment="어드민 여부")
chatrooms = relationship("ChatRooms", secondary="chat_rooms_users")
# models/chats.py
class ChatRooms(CommonFields):
__tablename__ = "chat_rooms"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(64), nullable=False)
status = Column(
String(4),
nullable=False,
comment="I : 초대는 했는데, 초대수락 안됨, O : 대화중, L: 대화 후 상대방이 나감.",
)
users = relationship(
"Users", secondary="chat_rooms_users", back_populates="chatrooms"
)
class ChatRoomsUsers(CommonFields):
__tablename__ = "chat_rooms_users"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
chatroom_id = Column(Integer, ForeignKey("chat_rooms.id"), primary_key=True)
user = relationship("Users", back_populates="chatrooms")
chatroom = relationship("ChatRooms", back_populates="users")
secondary는 SqlAlchemy에서 다대다 관계를 정의하는 옵션이다.
다대다 관계를 맽기 위해 중간에서 처리해줄 ChatroomsUsers 정의하기 위해 Users와 ChatRooms 모델에 이 옵션을 이용하여 중간테이블을 명시해줬다.
back_populates는 SqlAlchemy에서 양방향으로 접근가능하게 해주는 옵션이다.
양쪽 모델에 모두 정의해줄 필요없이 한쪽 모델에만 정의해도 SQLAlchemy에서 알아서 매핑해준다.
나는 ChatRooms 모델에만 명시해줬다.
Messages ( models/chats.py )
class ChatRoomsUsers(CommonFields):
"""채팅방-유저 관계 테이블"""
__tablename__ = "chat_rooms_users"
user_id = Column(BigInteger, ForeignKey("users.id"), primary_key=True)
chatroom_id = Column(BigInteger, ForeignKey("chat_rooms.id"), primary_key=True)
user = relationship("Users", back_populates="chatrooms")
chatroom = relationship("ChatRooms", back_populates="users")
class Messages(CommonFields):
"""메세지 테이블"""
__tablename__ = "messages"
id = Column(BigInteger, primary_key=True, autoincrement=True)
chatroom_id = Column(BigInteger, ForeignKey("chatrooms.id"), nullable=False)
sender_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
content = Column(Text, nullable=False)
class MessageReadStatus(CommonFields):
"""메세지읽음 상태 테이블"""
__tablename__ = "message_read_status"
message_id = Column(BigInteger, ForeignKey("messages.id"), primary_key=True)
reader_id = Column(BigInteger, ForeignKey("users.id"), primary_key=True)
read_at = Column(DateTime, nullable=False, default=func.current_timestamp())
Friend, Invitation ( models/friends.py )
from sqlalchemy import BigInteger, Column, ForeignKey, Integer, String
from app.models.base import CommonFields
class Invitation(CommonFields):
"""초대 테이블"""
__tablename__ = "invitations"
id = Column(BigInteger, primary_key=True, autoincrement=True)
link = Column(String(256), nullable=False)
chatroom_id = Column(BigInteger, ForeignKey("chatrooms.id"), nullable=False)
inviter_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
invitee_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
is_valid = Column(Integer, default=1)
class Friend(CommonFields):
"""친구 테이블"""
__tablename__ = "friends"
invitation_id = Column(BigInteger, ForeignKey("invitations.id"), primary_key=True)
request_from = Column(BigInteger, ForeignKey("users.id"), nullable=False)
request_to = Column(BigInteger, ForeignKey("users.id"), nullable=False)
status = Column(String(4), nullable=False, comment="A : 친구, I : 친구삭제, B : 차단")
EasterEggs, EasterEggHintHistory, EasterEggHistory ( models/easter_eggs.py )
from sqlalchemy import BigInteger, Column, DateTime, ForeignKey, Integer, String, func
from app.models.base import CommonFields
class EasterEgg(CommonFields):
"""이스터에그 테이블"""
__tablename__ = "easter_eggs"
id = Column(BigInteger, primary_key=True, autoincrement=True)
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
trigger_word = Column(String(16), nullable=False)
gif_url = Column(String(256), nullable=False)
hint = Column(String(16), nullable=False)
class EasterEggHintHistory(CommonFields):
"""이스터에그 힌트 히스토리"""
__tablename__ = "easter_egg_hint_history"
id = Column(BigInteger, primary_key=True, autoincrement=True)
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
chatroom_id = Column(BigInteger, ForeignKey("chatrooms.id"), nullable=False)
easter_egg_id = Column(BigInteger, ForeignKey("easter_eggs.id"), nullable=False)
is_used = Column(Integer, default=0)
used_at = Column(DateTime, nullable=False, default=func.current_timestamp())
class EasterEggHistory(CommonFields):
"""이스터에그 히스토리 테이블"""
__tablename__ = "easter_egg_history"
id = Column(BigInteger, primary_key=True, autoincrement=True)
trigger_user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
chatroom_id = Column(BigInteger, ForeignKey("chatrooms.id"), nullable=False)
easter_egg_id = Column(BigInteger, ForeignKey("easter_eggs.id"), nullable=False)
is_used = Column(Integer, default=0)
triggered_at = Column(DateTime, nullable=False, default=func.current_timestamp())
'사이드프로젝트' 카테고리의 다른 글
[EggChatter] 테스트코드 실행문 스크립트 파일로 간소화하기 (0) | 2025.03.07 |
---|---|
[EggChatter] pytest, pytest-asyncio와 Faker로 테스트 환경 구축하기 (0) | 2025.03.06 |
[ 기록의 정원 ] Error:Field validation for *** failed on the 'required' (0) | 2024.06.12 |
[ 포텐데이 X Ncloud ] 기록의 정원과 NCP 사용 후기 (2) | 2024.06.05 |
[ 기록의 정원 ] 게이트웨이 서버에 인증・인가 적용기 ( 2편 ) (0) | 2024.06.04 |