2026/05/13

김기훈·5일 전

TIL

목록 보기
194/194

구현된 기능

User

  • 회원가입
    • 클라이언트로부터 아이디(username), 비밀번호, 닉네임을 입력받아 데이터베이스에 새로운 사용자를 생성
  • 로그인
    • 사용자가 입력한 아이디와 비밀번호가 일치하면 접속을 허가하고
    • 이를 증명하는 수단으로 '액세스 토큰'과 '리프레시 토큰' 두 가지 종류의 출입증을 발급
  • 로그아웃
    • 클라이언트가 제출한 리프레시 토큰을 서버의 블랙리스트에 등록하여
    • 더 이상 인증 수단으로 사용할 수 없도록 만듬
  • 회원탈퇴
    • 회원의 데이터를 데이터베이스에서 완전히 지워버리지 않고
    • 계정의 비활성화 상태(is_active)를 거짓(False)으로 변경하는 '논리적 삭제' 방식을 사용
    • 또한 개인정보 보호를 위해
      • 기존 사용자의 아이디와 닉네임을 삭제된 시간과 고유 번호를 활용한 임의의 문자열로 덮어씌움

Event(약속 관리 기능)

  • 사용자들이 모임을 기획하고 확인할 수 있는 이 앱의 핵심 비즈니스 로직
    • 약속 생성
      • 제목과 비즈니스 목적 여부를 입력하여 새로운 약속 방을 만듬
      • 이때 약속을 만든 사용자는 자동으로 해당 방의 '방장(host)'으로 지정되며
        • 첫 번째 멤버로 등록됨과 동시에 다른 사람을 초대할 수 있는 권한을 얻게 됨
    • 단일 약속 조회
      • 특정 약속의 고유 아이디를 통해 해당 약속의 상세 정보(제목, 비즈니스 여부, 생성일 등)를 불러옴
    • 전체 약속 목록 조회
      • 현재 접속한 사용자가 방장으로 있거나 일반 멤버로 속해 있는 모든 약속들의 목록을 한 번에 조회

EventMember(참여자 관리 기능)

  • 생성된 약속 방 내부에서 참여자들 사이의 상호작용과 권한을 제어하는 세부 로직
  • 멤버 초대
    • 방장이거나, 초대 권한(can_invite)을 가진 멤버라면 다른 사용자를 해당 약속 방의 새로운 멤버로 등록 가능
  • 초대 권한 부여
    • 오직 방장만이 자신이 가진 초대 권한을 다른 일반 멤버에게도 나누어 줄 수 있음
    • 방장이 아닌 사람이 이 행동을 시도하면 시스템이 차단
  • 멤버 강퇴
    • 약속 방에서 특정 인원을 내보내는 기능, 이 역시 방장만이 수행할 수 있는 고유 권한으로 설정

메인기능

일정 저장

model

  • 일대다(1:N) 관계
    • 한 명의 참여자가 여러 개의 가능한 날짜와 시간을 가질 수 있기 때문
class Availability(models.Model):
    # 어떤 참여자의 일정인지 연결하기 위해 EventMember 모델을 외래키로 참조
    member = models.ForeignKey(EventMember, on_delete=models.CASCADE, related_name="availabilities")
    # 사용자가 선택한 가능한 날짜(년, 월, 일)를 데이터베이스에 기록
    available_date = models.DateField()
    # 해당 날짜에 가능한 특정 시간(시, 분)을 데이터베이스에 기록
    available_time = models.TimeField()

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["member", "available_date", "available_time"],
                name="unique_member_availability",
            )
        ]

serializer

  • ListField를 활용하면 하나의 날짜에 묶인 여러 시간 데이터를 한 번에 검증하고 처리할 수 있음
from rest_framework import serializers

class AvailabilityCreateSerializer(serializers.Serializer):
    """클라이언트가 보낼 날짜 값이 올바른 날짜 형식인지 검증"""
    available_date = serializers.DateField()
    # 1. 하나의 날짜에 대해 여러 시간을 보낼 수 있도록 시간 리스트 형식을 정의
    available_times = serializers.ListField(
        child=serializers.TimeField()
    )

service

class AvailabilityService:
    """참여자가 선택한 날짜와 시간 목록을 받아 일괄적으로 저장하는 함수"""
    @staticmethod
    def save_member_schedule(event_id, user, date_data):
        # 1. 해당 약속에 참여 중인 유저의 멤버 정보를 찾아옴
        member = EventMember.objects.get(event_id=event_id, user=user)

        # 2. 데이터 저장 중 오류 발생 시 모든 작업을 취소하기 위해 트랜잭션을 사용
        with transaction.atomic():
            # 3. 새로운 일정을 등록하기 위해 해당 날짜의 기존 설정 시간을 먼저 삭제
            Availability.objects.filter(
                member=member,
                available_date=date_data["available_date"]
            ).delete()

            # 4. 사용자가 보낸 시간 리스트를 하나씩 꺼내어 데이터베이스에 저장
            new_availabilities = [
                Availability(
                    member=member,
                    available_date=date_data["available_date"],
                    available_time=time_val
                )
                for time_val in date_data["available_times"]
            ]
            # 5. 여러 개의 객체를 한 번의 쿼리로 빠르게 저장하기위해 bulk_create를 사용
            Availability.objects.bulk_create(new_availabilities)

        return True

일정 조율

model

  • 일대다(1:N) 관계
    • 한 명의 참여자가 여러 개의 가능한 날짜와 시간을 가질 수 있기 때문
class MemberAvailability(models.Model):
    # 어떤 참여자의 일정인지 연결하기 위해 EventMember 모델을 외래키로 참조
    member = models.ForeignKey(EventMember, on_delete=models.CASCADE, related_name="availabilities")
    # 사용자가 선택한 가능한 날짜(년, 월, 일)를 데이터베이스에 기록
    available_date = models.DateField()
    # 해당 날짜에 가능한 특정 시간(시, 분)을 데이터베이스에 기록
    available_time = models.TimeField()

service

  • 교집합 일정을 찾는 서비스 로직
class ScheduleService:
    """특정 약속(event_id)에서 모든 인원이 겹치는 시간을 찾아내는 클래스"""
    def find_common_schedule(event_id):
        # 1. 해당 약속 방에 참여 중인 전체 멤버의 숫자를 카운트
        total_members = EventMember.objects.filter(event=event_id).count()

        # 2. 방에 참여자가 한 명도 없다면 빈 결과를 돌려보냄(오류 방지)
        if total_members == 0:
            return []

        # 3. 약속 방에 속한 모든 멤버의 일정 데이터를 데이터베이스에서 가져옴
        common_slots = (MemberAvailability.objects.filter(member__event_id=event_id)
                        # values 메서드를 사용하여 날짜와 시간 정보만 그룹으로 묶어줌
                        .values("available_date","available_time")
                        # annotate 메서드로 동일한 날짜와 시간을 선택한 사람이 몇 명인지(member_count) 계산
                        .annotate(member_count=Count("member"))
                        # filter를 사용해 그 인원수가 전체 참여자 수와 똑같은 경우만 남김
                        .filter(member_count=total_members))

        # 4. 찾아낸 교집합 데이터들을 클라이언트가 보기 편한 딕셔너리 형태로 가공
        return [{"date": slot["available_date"], "time": slot["available_time"]} for slot in common_slots]

view

    def get(self, request, event_id):
        # 1. 서비스 함수 호출
        common_times = ScheduleService.find_common_schedule(event_id)

        return Response({"message": "교집합 일정 조회 성공", "common_times": common_times}, status=status.HTTP_200_OK)

profile
안녕하세요.

0개의 댓글