구현된 기능
User
- 회원가입
- 클라이언트로부터 아이디(username), 비밀번호, 닉네임을 입력받아 데이터베이스에 새로운 사용자를 생성
- 로그인
- 사용자가 입력한 아이디와 비밀번호가 일치하면 접속을 허가하고
- 이를 증명하는 수단으로 '액세스 토큰'과 '리프레시 토큰' 두 가지 종류의 출입증을 발급
- 로그아웃
- 클라이언트가 제출한 리프레시 토큰을 서버의 블랙리스트에 등록하여
- 더 이상 인증 수단으로 사용할 수 없도록 만듬
- 회원탈퇴
- 회원의 데이터를 데이터베이스에서 완전히 지워버리지 않고
- 계정의 비활성화 상태(is_active)를 거짓(False)으로 변경하는 '논리적 삭제' 방식을 사용
- 또한 개인정보 보호를 위해
- 기존 사용자의 아이디와 닉네임을 삭제된 시간과 고유 번호를 활용한 임의의 문자열로 덮어씌움
Event(약속 관리 기능)
- 사용자들이 모임을 기획하고 확인할 수 있는 이 앱의 핵심 비즈니스 로직
- 약속 생성
- 제목과 비즈니스 목적 여부를 입력하여 새로운 약속 방을 만듬
- 이때 약속을 만든 사용자는 자동으로 해당 방의 '방장(host)'으로 지정되며
- 첫 번째 멤버로 등록됨과 동시에 다른 사람을 초대할 수 있는 권한을 얻게 됨
- 단일 약속 조회
- 특정 약속의 고유 아이디를 통해 해당 약속의 상세 정보(제목, 비즈니스 여부, 생성일 등)를 불러옴
- 전체 약속 목록 조회
- 현재 접속한 사용자가 방장으로 있거나 일반 멤버로 속해 있는 모든 약속들의 목록을 한 번에 조회
EventMember(참여자 관리 기능)
- 생성된 약속 방 내부에서 참여자들 사이의 상호작용과 권한을 제어하는 세부 로직
- 멤버 초대
- 방장이거나, 초대 권한(can_invite)을 가진 멤버라면 다른 사용자를 해당 약속 방의 새로운 멤버로 등록 가능
- 초대 권한 부여
- 오직 방장만이 자신이 가진 초대 권한을 다른 일반 멤버에게도 나누어 줄 수 있음
- 방장이 아닌 사람이 이 행동을 시도하면 시스템이 차단
- 멤버 강퇴
- 약속 방에서 특정 인원을 내보내는 기능, 이 역시 방장만이 수행할 수 있는 고유 권한으로 설정
메인기능
일정 저장
model
- 일대다(1:N) 관계
- 한 명의 참여자가 여러 개의 가능한 날짜와 시간을 가질 수 있기 때문
class Availability(models.Model):
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()
available_times = serializers.ListField(
child=serializers.TimeField()
)
service
class AvailabilityService:
"""참여자가 선택한 날짜와 시간 목록을 받아 일괄적으로 저장하는 함수"""
@staticmethod
def save_member_schedule(event_id, user, date_data):
member = EventMember.objects.get(event_id=event_id, user=user)
with transaction.atomic():
Availability.objects.filter(
member=member,
available_date=date_data["available_date"]
).delete()
new_availabilities = [
Availability(
member=member,
available_date=date_data["available_date"],
available_time=time_val
)
for time_val in date_data["available_times"]
]
Availability.objects.bulk_create(new_availabilities)
return True
일정 조율
model
- 일대다(1:N) 관계
- 한 명의 참여자가 여러 개의 가능한 날짜와 시간을 가질 수 있기 때문
class MemberAvailability(models.Model):
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):
total_members = EventMember.objects.filter(event=event_id).count()
if total_members == 0:
return []
common_slots = (MemberAvailability.objects.filter(member__event_id=event_id)
.values("available_date","available_time")
.annotate(member_count=Count("member"))
.filter(member_count=total_members))
return [{"date": slot["available_date"], "time": slot["available_time"]} for slot in common_slots]
view
def get(self, request, event_id):
common_times = ScheduleService.find_common_schedule(event_id)
return Response({"message": "교집합 일정 조회 성공", "common_times": common_times}, status=status.HTTP_200_OK)