""" 競技状態管理API サーバーAPI変更要求書20250904.mdに基づく実装 Author: システム開発チーム Date: 2025-09-04 """ from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework import status from django.db import transaction from django.utils import timezone from rog.models import NewEvent2, Entry, Location2025, GpsLog from django.contrib.gis.geos import Point from django.contrib.gis.measure import D import logging logger = logging.getLogger(__name__) @api_view(['GET']) def competition_status(request): """ 競技状態取得API パラメータ: - event_code: イベントコード - zekken_number: ゼッケン番号 レスポンス: { "status": "OK", "data": { "is_in_rog": true, "rogaining_counted": false, "ready_for_goal": false, "is_at_goal": false, "start_time": "2025-09-04T09:00:00+09:00", "goal_time": null, "last_checkin_time": "2025-09-04T09:30:00+09:00" } } """ logger.info("competition_status API called") event_code = request.query_params.get('event_code') zekken_number = request.query_params.get('zekken_number') logger.debug(f"Parameters: event_code={event_code}, zekken_number={zekken_number}") # パラメータ検証 if not event_code or not zekken_number: logger.warning("Missing required parameters") return Response({ "status": "ERROR", "message": "イベントコードとゼッケン番号が必要です" }, status=status.HTTP_400_BAD_REQUEST) try: # イベントの存在確認 event = NewEvent2.objects.filter(event_name=event_code).first() if not event: logger.warning(f"Event not found: {event_code}") return Response({ "status": "ERROR", "message": "指定されたイベントが見つかりません" }, status=status.HTTP_404_NOT_FOUND) # エントリーの存在確認 entry = Entry.objects.filter( event=event, zekken_number=zekken_number ).first() if not entry: logger.warning(f"Entry not found: zekken_number={zekken_number}, event={event_code}") return Response({ "status": "ERROR", "message": "指定されたゼッケン番号のエントリーが見つかりません" }, status=status.HTTP_404_NOT_FOUND) # 競技状態データを返す data = { "is_in_rog": entry.is_in_rog, "rogaining_counted": entry.rogaining_counted, "ready_for_goal": entry.ready_for_goal, "is_at_goal": entry.is_at_goal, "start_time": entry.start_time.strftime("%Y-%m-%dT%H:%M:%S%z") if entry.start_time else None, "goal_time": entry.goal_time.strftime("%Y-%m-%dT%H:%M:%S%z") if entry.goal_time else None, "last_checkin_time": entry.last_checkin_time.strftime("%Y-%m-%dT%H:%M:%S%z") if entry.last_checkin_time else None } logger.info(f"Competition status retrieved for zekken {zekken_number} in event {event_code}") return Response({ "status": "OK", "data": data }) except Exception as e: logger.error(f"Error in competition_status: {str(e)}") return Response({ "status": "ERROR", "message": "サーバーエラーが発生しました" }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) @api_view(['POST']) def update_competition_status(request): """ 競技状態更新API パラメータ: { "event_code": "EVENT2025", "zekken_number": "001", "is_in_rog": true, "rogaining_counted": false, "ready_for_goal": false, "is_at_goal": false } レスポンス: { "status": "OK", "message": "Competition status updated successfully", "updated_at": "2025-09-04T10:00:00+09:00" } """ logger.info("update_competition_status API called") event_code = request.data.get('event_code') zekken_number = request.data.get('zekken_number') is_in_rog = request.data.get('is_in_rog') rogaining_counted = request.data.get('rogaining_counted') ready_for_goal = request.data.get('ready_for_goal') is_at_goal = request.data.get('is_at_goal') logger.debug(f"Parameters: event_code={event_code}, zekken_number={zekken_number}") # パラメータ検証 if not event_code or not zekken_number: logger.warning("Missing required parameters") return Response({ "status": "ERROR", "message": "イベントコードとゼッケン番号が必要です" }, status=status.HTTP_400_BAD_REQUEST) try: # イベントの存在確認 event = NewEvent2.objects.filter(event_name=event_code).first() if not event: logger.warning(f"Event not found: {event_code}") return Response({ "status": "ERROR", "message": "指定されたイベントが見つかりません" }, status=status.HTTP_404_NOT_FOUND) # エントリーの存在確認 entry = Entry.objects.filter( event=event, zekken_number=zekken_number ).first() if not entry: logger.warning(f"Entry not found: zekken_number={zekken_number}, event={event_code}") return Response({ "status": "ERROR", "message": "指定されたゼッケン番号のエントリーが見つかりません" }, status=status.HTTP_404_NOT_FOUND) # トランザクション内で状態更新 with transaction.atomic(): # 状態の更新(Noneでない値のみ更新) if is_in_rog is not None: entry.is_in_rog = is_in_rog if rogaining_counted is not None: entry.rogaining_counted = rogaining_counted if ready_for_goal is not None: entry.ready_for_goal = ready_for_goal if is_at_goal is not None: entry.is_at_goal = is_at_goal entry.save() update_time = timezone.now() logger.info(f"Competition status updated for zekken {zekken_number} in event {event_code}") return Response({ "status": "OK", "message": "Competition status updated successfully", "updated_at": update_time.strftime("%Y-%m-%dT%H:%M:%S%z") }) except Exception as e: logger.error(f"Error in update_competition_status: {str(e)}") return Response({ "status": "ERROR", "message": "サーバーエラーが発生しました" }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) @api_view(['GET']) def checkpoint_status(request): """ チェックポイント状態取得API パラメータ: - event_code: イベントコード - zekken_number: ゼッケン番号 - cp_number: チェックポイント番号 レスポンス: { "status": "OK", "data": { "cp_number": -2, "is_checked_in": true, "checkin_time": "2025-09-04T09:00:00+09:00", "status": "競技中", // "未", "競技中", "競技終了" "points_earned": 0 } } """ logger.info("checkpoint_status API called") event_code = request.query_params.get('event_code') zekken_number = request.query_params.get('zekken_number') cp_number = request.query_params.get('cp_number') logger.debug(f"Parameters: event_code={event_code}, zekken_number={zekken_number}, cp_number={cp_number}") # パラメータ検証 if not all([event_code, zekken_number, cp_number]): logger.warning("Missing required parameters") return Response({ "status": "ERROR", "message": "イベントコード、ゼッケン番号、チェックポイント番号が必要です" }, status=status.HTTP_400_BAD_REQUEST) try: # イベントの存在確認 event = NewEvent2.objects.filter(event_name=event_code).first() if not event: logger.warning(f"Event not found: {event_code}") return Response({ "status": "ERROR", "message": "指定されたイベントが見つかりません" }, status=status.HTTP_404_NOT_FOUND) # エントリーの存在確認 entry = Entry.objects.filter( event=event, zekken_number=zekken_number ).first() if not entry: logger.warning(f"Entry not found: zekken_number={zekken_number}, event={event_code}") return Response({ "status": "ERROR", "message": "指定されたゼッケン番号のエントリーが見つかりません" }, status=status.HTTP_404_NOT_FOUND) # チェックイン状況の確認 checkin_record = GpsLog.objects.filter( zekken_number=zekken_number, event_code=event_code, cp_number=cp_number ).first() # チェックポイント情報の取得 checkpoint = Location2025.objects.filter( event=event, cp_number=cp_number ).first() # 競技状況の判定 competition_status = "未" if entry.is_at_goal: competition_status = "競技終了" elif entry.is_in_rog: competition_status = "競技中" # レスポンスデータ構築 data = { "cp_number": int(cp_number), "is_checked_in": bool(checkin_record), "checkin_time": checkin_record.checkin_time.strftime("%Y-%m-%dT%H:%M:%S%z") if checkin_record else None, "status": competition_status, "points_earned": checkpoint.point if checkpoint else 0 } logger.info(f"Checkpoint status retrieved for CP {cp_number}, zekken {zekken_number} in event {event_code}") return Response({ "status": "OK", "data": data }) except Exception as e: logger.error(f"Error in checkpoint_status: {str(e)}") return Response({ "status": "ERROR", "message": "サーバーエラーが発生しました" }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)