lovelymango's picture
Upload 31 files
11c324f verified
"""
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)