diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml new file mode 100644 index 00000000..173cccd1 --- /dev/null +++ b/.github/workflows/openapi.yml @@ -0,0 +1,71 @@ +name: Generate OpenAPI Documentation + +on: + workflow_dispatch: + push: + branches: + - main + +jobs: + generate_openapi: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5 + with: + python-version: "3.12" + + - name: Install Poetry + run: | + curl -sSL https://install.python-poetry.org | python3 - + + - name: Configure Poetry + run: | + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Install dependencies + run: | + poetry install + + - name: Generate OpenAPI JSON + run: | + poetry run generate-openapi > api/openapi.json + + - name: Check if OpenAPI changed + id: check-openapi + run: | + if ! git diff --quiet api/openapi.json ; then + echo "changed=true" >> "$GITHUB_OUTPUT" + else + echo "changed=false" >> "$GITHUB_OUTPUT" + fi + + - name: Set git config + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + - name: Generate PR if needed + if: steps.check-openapi.outputs.changed == 'true' + run: | + git checkout -b update-openapi-$GITHUB_SHA + + git add api/openapi.json + git commit -m "Update OpenAPI to version generated from ref $GITHUB_SHA" + + echo "Pushing branch so we can create a PR..." + git push --set-upstream origin update-openapi-$GITHUB_SHA + + gh pr create --title "Update OpenAPI" \ + --body "This PR updates the OpenAPI definition to the version generated from ref $GITHUB_SHA" \ + --repo "$GITHUB_REPOSITORY" \ + --base main \ + --head update-openapi-$GITHUB_SHA + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/api/openapi.json b/api/openapi.json new file mode 100644 index 00000000..4c54f59f --- /dev/null +++ b/api/openapi.json @@ -0,0 +1,290 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/dashboard/messages": { + "get": { + "tags": [ + "Dashboard" + ], + "summary": "Get Messages", + "description": "Get all the messages from the database and return them as a list of conversations.", + "operationId": "get_messages_dashboard_messages_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Conversation" + }, + "type": "array", + "title": "Response Get Messages Dashboard Messages Get" + } + } + } + } + } + } + }, + "/dashboard/alerts": { + "get": { + "tags": [ + "Dashboard" + ], + "summary": "Get Alerts", + "description": "Get all the messages from the database and return them as a list of conversations.", + "operationId": "get_alerts_dashboard_alerts_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/AlertConversation" + }, + "type": "array", + "title": "Response Get Alerts Dashboard Alerts Get" + } + } + } + } + } + } + }, + "/dashboard/alerts_notification": { + "get": { + "tags": [ + "Dashboard" + ], + "summary": "Stream Sse", + "description": "Send alerts event", + "operationId": "stream_sse_dashboard_alerts_notification_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AlertConversation": { + "properties": { + "conversation": { + "$ref": "#/components/schemas/Conversation" + }, + "alert_id": { + "type": "string", + "title": "Alert Id" + }, + "code_snippet": { + "anyOf": [ + { + "$ref": "#/components/schemas/CodeSnippet" + }, + { + "type": "null" + } + ] + }, + "trigger_string": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Trigger String" + }, + "trigger_type": { + "type": "string", + "title": "Trigger Type" + }, + "trigger_category": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Trigger Category" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp" + } + }, + "type": "object", + "required": [ + "conversation", + "alert_id", + "code_snippet", + "trigger_string", + "trigger_type", + "trigger_category", + "timestamp" + ], + "title": "AlertConversation", + "description": "Represents an alert with it's respective conversation." + }, + "ChatMessage": { + "properties": { + "message": { + "type": "string", + "title": "Message" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Timestamp" + }, + "message_id": { + "type": "string", + "title": "Message Id" + } + }, + "type": "object", + "required": [ + "message", + "timestamp", + "message_id" + ], + "title": "ChatMessage", + "description": "Represents a chat message." + }, + "CodeSnippet": { + "properties": { + "code": { + "type": "string", + "title": "Code" + }, + "language": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Language" + }, + "filepath": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Filepath" + }, + "libraries": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Libraries" + } + }, + "type": "object", + "required": [ + "code", + "language", + "filepath" + ], + "title": "CodeSnippet" + }, + "Conversation": { + "properties": { + "question_answers": { + "items": { + "$ref": "#/components/schemas/QuestionAnswer" + }, + "type": "array", + "title": "Question Answers" + }, + "provider": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Provider" + }, + "type": { + "type": "string", + "title": "Type" + }, + "chat_id": { + "type": "string", + "title": "Chat Id" + }, + "conversation_timestamp": { + "type": "string", + "format": "date-time", + "title": "Conversation Timestamp" + } + }, + "type": "object", + "required": [ + "question_answers", + "provider", + "type", + "chat_id", + "conversation_timestamp" + ], + "title": "Conversation", + "description": "Represents a conversation." + }, + "QuestionAnswer": { + "properties": { + "question": { + "$ref": "#/components/schemas/ChatMessage" + }, + "answer": { + "anyOf": [ + { + "$ref": "#/components/schemas/ChatMessage" + }, + { + "type": "null" + } + ] + } + }, + "type": "object", + "required": [ + "question", + "answer" + ], + "title": "QuestionAnswer", + "description": "Represents a question and answer pair." + } + } + } +} diff --git a/pyproject.toml b/pyproject.toml index 68e18a61..38c903c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] codegate = "codegate.cli:main" +generate-openapi = "src.codegate.dashboard.dashboard:generate_openapi" [tool.black] line-length = 100 diff --git a/src/codegate/dashboard/dashboard.py b/src/codegate/dashboard/dashboard.py index 19352b51..35d1efd5 100644 --- a/src/codegate/dashboard/dashboard.py +++ b/src/codegate/dashboard/dashboard.py @@ -1,8 +1,9 @@ import asyncio +import json from typing import AsyncGenerator, List, Optional import structlog -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, FastAPI from fastapi.responses import StreamingResponse from codegate.dashboard.post_processing import ( @@ -59,3 +60,18 @@ async def stream_sse(): Send alerts event """ return StreamingResponse(generate_sse_events(), media_type="text/event-stream") + + +def generate_openapi(): + # Create a temporary FastAPI app instance + app = FastAPI() + + # Include your defined router + app.include_router(dashboard_router) + + # Generate OpenAPI JSON + openapi_schema = app.openapi() + + # Convert the schema to JSON string for easier handling or storage + openapi_json = json.dumps(openapi_schema, indent=2) + print(openapi_json)