Skip to content
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
10 changes: 8 additions & 2 deletions backend/app/models/appointment_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@ class Appointments(Base):
name = Column(String(255), nullable=False)
creator_id = Column(String(255))
max_participants = Column(Integer, nullable=False)
status = Column(Enum("VOTING", "CONFIRMED", "CANCELED"), nullable=False)
status = Column(Enum("VOTING", "CONFIRMED"), nullable=False)
invite_link = Column(String(255), unique=True)
confirmed_date = Column(Date, nullable=True)
confirmed_start_time = Column(String(5), nullable=True)
confirmed_end_time = Column(String(5), nullable=True)
confirmed_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, default=datetime.now)

appointment_dates = relationship(
Expand All @@ -37,7 +41,9 @@ class AppointmentDates(Base):
__tablename__ = "appointment_dates"

id = Column(Integer, primary_key=True, autoincrement=True)
appointment_id = Column(Integer, ForeignKey("appointments.id"), nullable=False)
appointment_id = Column(
Integer, ForeignKey("appointments.id", ondelete="CASCADE"), nullable=False
)
candidate_date = Column(Date, nullable=False)
__table_args__ = (UniqueConstraint("appointment_id", "candidate_date"),)

Expand Down
1 change: 1 addition & 0 deletions backend/app/models/user_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from sqlalchemy import Column, String, TEXT, DateTime
from app.db.base import Base


class User(Base):
__tablename__ = "users"

Expand Down
36 changes: 36 additions & 0 deletions backend/app/routes/appointment_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
AppointmentDetailResponse,
AppointmentListResponse,
SyncMySchedulesResponse,
ConfirmAppointmentRequest,
ConfirmAppointmentResponse,
)
from app.utils.jwt import get_current_user

Expand Down Expand Up @@ -246,3 +248,37 @@ async def sync_my_schedules(
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"일정 동기화 실패: {str(e)}")


@router.post("/{invite_code}/confirm", response_model=ConfirmAppointmentResponse)
async def confirm_appointment(
invite_code: str,
request: ConfirmAppointmentRequest,
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
):
# 약속 확정
try:
appointment = await AppointmentService.confirm_appointment(
invite_code=invite_code,
confirmed_date=request.confirmed_date,
confirmed_start_time=request.confirmed_start_time,
confirmed_end_time=request.confirmed_end_time,
user_id=current_user["sub"],
db=db,
)

return ConfirmAppointmentResponse(
id=appointment.id,
name=appointment.name,
status=appointment.status,
confirmed_date=appointment.confirmed_date,
confirmed_start_time=appointment.confirmed_start_time,
confirmed_end_time=appointment.confirmed_end_time,
confirmed_at=appointment.confirmed_at,
)

except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"약속 확정 실패: {str(e)}")
27 changes: 27 additions & 0 deletions backend/app/schema/appointment_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,30 @@ class SyncMySchedulesResponse(BaseModel):
total_appointments: int
updated_count: int
failed_count: int


class ConfirmAppointmentRequest(BaseModel):
confirmed_date: date
confirmed_start_time: str
confirmed_end_time: str

@validator("confirmed_start_time", "confirmed_end_time")
def validate_time_format(cls, v):
try:
datetime.strptime(v, "%H:%M")
except ValueError:
raise ValueError("시간 형식은 HH:MM이어야 합니다 (예: 09:00)")
return v


class ConfirmAppointmentResponse(BaseModel):
id: int
name: str
status: str
confirmed_date: Optional[date]
confirmed_start_time: Optional[str]
confirmed_end_time: Optional[str]
confirmed_at: Optional[datetime]

class Config:
from_attributes = True
41 changes: 40 additions & 1 deletion backend/app/services/appointment_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ async def calculate_optimal_times(
def _filter_slots_by_time_range(
slots: List[dict], time_range_start: str = None, time_range_end: str = None
) -> List[dict]:
#저장된 가용 시간 슬롯을 시간대 필터로 제한
# 저장된 가용 시간 슬롯을 시간대 필터로 제한
if not time_range_start and not time_range_end:
return slots

Expand Down Expand Up @@ -390,6 +390,45 @@ def _filter_slots_by_time_range(

return filtered_slots

@staticmethod
async def confirm_appointment(
invite_code: str,
confirmed_date: str,
confirmed_start_time: str,
confirmed_end_time: str,
user_id: str,
db: AsyncSession,
) -> Appointments:
from datetime import datetime

# 약속 조회
appointment = await AppointmentService.get_appointment_by_invite_code(
invite_code, db
)
if not appointment:
raise ValueError("존재하지 않는 약속입니다")

# 생성자 확인
if appointment.creator_id != user_id:
raise ValueError("약속 생성자만 확정할 수 있습니다")

# 이미 확정된 약속인지 확인
if appointment.status == "CONFIRMED":
raise ValueError("이미 확정된 약속입니다")


# 약속 확정
appointment.status = "CONFIRMED"
appointment.confirmed_date = confirmed_date
appointment.confirmed_start_time = confirmed_start_time
appointment.confirmed_end_time = confirmed_end_time
appointment.confirmed_at = datetime.now()

await db.commit()
await db.refresh(appointment)

return appointment

@staticmethod
async def get_my_appointments(user_id: str, db: AsyncSession) -> List[Appointments]:
# 내가 참여한 약속 목록 조회
Expand Down
2 changes: 2 additions & 0 deletions backend/app/utils/jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt


def verify_token(token: str):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except JWTError:
return None


def get_current_user(token=Depends(security)):
# JWT 토큰에서 현재 사용자 정보 추출
credentials_exception = HTTPException(
Expand Down
Loading