""" OpenAPI Schema for GPTs Actions =============================== ChatGPT GPTs Actions에서 사용할 OpenAPI 3.1.0 스키마 생성 """ from starlette.requests import Request from starlette.responses import JSONResponse def generate_openapi_schema(base_url: str = "https://lovelymango-eodi-seats-aero-dev.hf.space") -> dict: """OpenAPI 3.1.0 스키마 생성""" return { "openapi": "3.1.0", "info": { "title": "Award Seat Search API", "description": ( "마일리지 좌석 가용성 검색 API.\n\n" "**왕복 검색**: /api/award/search/roundtrip 사용 (자동으로 양방향 검색)\n" "**편도 검색**: /api/award/search 사용\n\n" "**인증**: Seats.aero Pro 계정이 필요합니다. OAuth로 자동 연결됩니다.\n\n" "**중요**: 캐시 데이터입니다. 예약 전 항공사에서 확인하세요." ), "version": "1.3.0" }, "servers": [ {"url": base_url, "description": "Production"} ], "components": { "securitySchemes": { "oauth2": { "type": "oauth2", "description": "Seats.aero OAuth2 authentication via proxy", "flows": { "authorizationCode": { "authorizationUrl": f"{base_url}/oauth2/authorize", "tokenUrl": f"{base_url}/oauth2/token", "scopes": { "openid": "Access to Seats.aero API" } } } } }, "schemas": { "SearchRequest": { "type": "object", "required": ["origin", "destination"], "properties": { "origin": { "type": "string", "description": "출발 공항 코드", "example": "ICN" }, "destination": { "type": "string", "description": "도착 공항 코드", "example": "NRT" }, "start_date": { "type": "string", "format": "date", "description": "검색 시작일 (YYYY-MM-DD)" }, "end_date": { "type": "string", "format": "date", "description": "검색 종료일 (YYYY-MM-DD)" }, "cabin": { "type": "string", "enum": ["economy", "premium", "business", "first"], "description": "좌석 등급" }, "cabin_explicit": { "type": "boolean", "description": "true일 때만 cabin 필터 적용" }, "programs": { "type": "string", "description": "마일리지 프로그램 필터 (예: united,aeroplan)" }, "direct_only": { "type": "boolean", "default": False, "description": "직항만 검색" }, "carriers": { "type": "string", "description": "항공사 필터 (예: OZ,KE)" }, "limit": { "type": "integer", "default": 20, "maximum": 50, "description": "최대 결과 수" } } }, "RoundtripSearchRequest": { "type": "object", "required": ["origin", "destination"], "properties": { "origin": { "type": "string", "description": "출발지 공항 코드", "example": "ICN" }, "destination": { "type": "string", "description": "도착지 공항 코드", "example": "NRT" }, "outbound_date": { "type": "string", "format": "date", "description": "출발일 (YYYY-MM-DD)" }, "outbound_start_date": { "type": "string", "format": "date", "description": "출발일 검색 시작 (범위 검색 시)" }, "outbound_end_date": { "type": "string", "format": "date", "description": "출발일 검색 종료 (범위 검색 시)" }, "return_date": { "type": "string", "format": "date", "description": "귀국일 (YYYY-MM-DD)" }, "return_start_date": { "type": "string", "format": "date", "description": "귀국일 검색 시작 (범위 검색 시)" }, "return_end_date": { "type": "string", "format": "date", "description": "귀국일 검색 종료 (범위 검색 시)" }, "cabin": { "type": "string", "enum": ["economy", "premium", "business", "first"], "description": "좌석 등급" }, "cabin_explicit": { "type": "boolean", "description": "true일 때만 cabin 필터 적용" }, "programs": { "type": "string", "description": "마일리지 프로그램 필터" }, "direct_only": { "type": "boolean", "default": False, "description": "직항만 검색" }, "carriers": { "type": "string", "description": "항공사 필터" }, "limit": { "type": "integer", "default": 15, "maximum": 30, "description": "각 방향별 최대 결과 수" } } }, "SuccessResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": True }, "count": { "type": "integer" }, "results": { "type": "array", "items": { "type": "object" } } } }, "ErrorResponse": { "type": "object", "properties": { "success": { "type": "boolean", "example": False }, "error": { "type": "string" }, "message": { "type": "string" } } } } }, "security": [ {"oauth2": ["openid"]} ], "paths": { "/api/award/search/roundtrip": { "post": { "operationId": "searchRoundtripAward", "summary": "Search roundtrip award availability (추천)", "description": ( "왕복 마일리지 좌석 검색. 서버에서 자동으로 출발편+귀국편 검색.\n\n" "**사용 시점**: 사용자가 왕복 여행을 언급하거나, 출발일과 귀국일을 모두 제시한 경우\n\n" "**cabin 규칙**: 사용자가 '비즈니스', '퍼스트' 등을 명시한 경우에만 cabin + cabin_explicit: true" ), "x-openai-isConsequential": False, "requestBody": { "required": True, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RoundtripSearchRequest" } } } }, "responses": { "200": { "description": "왕복 검색 성공", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessResponse" } } } }, "400": { "description": "잘못된 요청", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "인증 필요", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "429": { "description": "일일 할당량 초과", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/api/award/search": { "post": { "operationId": "searchOnewayAward", "summary": "Search one-way award availability", "description": ( "편도 마일리지 좌석 검색.\n\n" "**주의**: 왕복 검색은 /api/award/search/roundtrip 사용 권장" ), "x-openai-isConsequential": False, "requestBody": { "required": True, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SearchRequest" } } } }, "responses": { "200": { "description": "검색 성공", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessResponse" } } } }, "400": { "description": "잘못된 요청", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "401": { "description": "인증 필요", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "429": { "description": "일일 할당량 초과", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/api/award/trips/{availability_id}": { "get": { "operationId": "getAwardTripDetails", "summary": "Get trip details", "description": "검색 결과의 특정 가용성에 대한 상세 여정 정보", "x-openai-isConsequential": False, "parameters": [ { "name": "availability_id", "in": "path", "required": True, "schema": {"type": "string"}, "description": "검색 결과의 id 값" }, { "name": "include_filtered", "in": "query", "required": False, "schema": {"type": "boolean", "default": False}, "description": "필터링된 결과 포함 여부" } ], "responses": { "200": { "description": "여정 상세", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessResponse" } } } }, "401": { "description": "인증 필요", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }, "404": { "description": "찾을 수 없음", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/api/award/routes": { "get": { "operationId": "getAvailableRoutes", "summary": "Get available routes", "description": "특정 마일리지 프로그램의 검색 가능 노선", "x-openai-isConsequential": False, "parameters": [ { "name": "source", "in": "query", "required": True, "schema": {"type": "string"}, "description": "프로그램 코드 (예: united, aeroplan, delta)" } ], "responses": { "200": { "description": "노선 목록", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessResponse" } } } }, "401": { "description": "인증 필요", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } } } } }, "/api/programs": { "get": { "operationId": "listMileagePrograms", "summary": "List mileage programs", "description": "지원되는 마일리지 프로그램 목록 (인증 불필요)", "x-openai-isConsequential": False, "security": [], "responses": { "200": { "description": "프로그램 목록", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessResponse" } } } } } } } } } async def openapi_schema(request: Request) -> JSONResponse: """OpenAPI 스키마 엔드포인트""" host = request.headers.get("host", "lovelymango-eodi-seats-aero-dev.hf.space") scheme = request.headers.get("x-forwarded-proto", "https") base_url = f"{scheme}://{host}" schema = generate_openapi_schema(base_url) return JSONResponse(schema)