diff --git a/.github/workflows/deploy-op.yml b/.github/workflows/deploy-op.yml new file mode 100644 index 0000000..50f2ca1 --- /dev/null +++ b/.github/workflows/deploy-op.yml @@ -0,0 +1,60 @@ +name: deploy-openapi + +on: + push: + branches: + - main + paths: + - 'openapi/**' + workflow_dispatch: + pull_request: + paths: + - "openapi/**" + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + shell: bash + working-directory: ./openapi + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 18.x + + - name: Install redocly + run: npm i -g @redocly/cli@latest + - name: Build a static file. + run: redocly build-docs -o ./docs-openapi/index.html openapi.yml + + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + # 注: top からの相対パスになるので注意。 + path: ./openapi/docs-openapi/ + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to gh-pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/openapi/Makefile b/openapi/Makefile new file mode 100644 index 0000000..0d280bd --- /dev/null +++ b/openapi/Makefile @@ -0,0 +1,18 @@ +.DEFAULT_GOAL := help + +.PHONY: help +help: ## https://postd.cc/auto-documented-makefile/ + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: bootstrap +bootstrap: ## 外部ツールをインストールする。 + npm i -g @redocly/cli@latest + +.PHONY: preview +preview: ## openapi をプレビューする。 + redocly preview-docs openapi.yml --port 9988 + +.PHONY: gen +gen: ## openapi.yml からコードを生成する。 + redocly codegen generate openapi.yml --output ./generated diff --git a/openapi/README.md b/openapi/README.md new file mode 100644 index 0000000..8d82f83 --- /dev/null +++ b/openapi/README.md @@ -0,0 +1,6 @@ +## ローカルで openapi の確認 + +``` sh +make bootstrap +make preview +``` diff --git a/openapi/examples/get-timeline.json b/openapi/examples/get-timeline.json new file mode 100644 index 0000000..16bef31 --- /dev/null +++ b/openapi/examples/get-timeline.json @@ -0,0 +1,22 @@ +{ + "tweets": [ + { + "id": "9747be01-cc1b-4e2b-824c-dcad1b7a70c7", + "user_id": "55a980aa-f5bb-4ae5-aa9f-572d56a8a0a7", + "body": "I'm a tweet!", + "timestamp": "1706888873152334145" + }, + { + "id": "f3e3e3e3-cc1b-4e2b-824c-dcad1b7a70c7", + "user_id": "55a980aa-f5bb-4ae5-aa9f-572d56a8a0a7", + "body": "I'm another tweet!", + "timestamp": "1706888873124334145" + }, + { + "id": "f3e3e3e3-cc1b-4e2b-824c-dcad1b7a70c7", + "user_id": "55a980aa-f5bb-4ae5-aa9f-572d56a8a0a7", + "body": "I'm another tweet!", + "timestamp": "1706888872124334110" + } + ] +} diff --git a/openapi/examples/get-users.json b/openapi/examples/get-users.json new file mode 100644 index 0000000..a9e72c0 --- /dev/null +++ b/openapi/examples/get-users.json @@ -0,0 +1,14 @@ +{ + "users": [ + { + "id": "b3493c7d-71be-48d0-918d-465788dd4cec", + "name": "John Doe", + "is_follower": false + }, + { + "id": "bffe2bf6-7c0f-4fd3-ab84-f9299738d279", + "name": "名無しの権兵衛", + "is_follower": true + } + ] +} diff --git a/openapi/examples/post-follows-request.json b/openapi/examples/post-follows-request.json new file mode 100644 index 0000000..07af6cd --- /dev/null +++ b/openapi/examples/post-follows-request.json @@ -0,0 +1,3 @@ +{ + "followed_id": "b3493c7d-71be-48d0-918d-465788dd4cec" +} diff --git a/openapi/examples/post-tweet-request.json b/openapi/examples/post-tweet-request.json new file mode 100644 index 0000000..52e6a43 --- /dev/null +++ b/openapi/examples/post-tweet-request.json @@ -0,0 +1,3 @@ +{ + "body": "今日は寒すぎる。" +} diff --git a/openapi/openapi.yml b/openapi/openapi.yml new file mode 100644 index 0000000..66bdeba --- /dev/null +++ b/openapi/openapi.yml @@ -0,0 +1,366 @@ +openapi: 3.0.0 +info: + version: 0.1.0 + title: SNS-app API + description: | + SNS サービスの API 仕様書です。 + +servers: + - url: "http://localhost:9876" + description: 開発環境 + +security: + - cookieAuth: [] + +paths: + /health: + get: + summary: ヘルスチェック用エンドポイント。 + operationId: healthCheck + security: [] # no authentication + responses: + '200': + description: サーバーの状態を返す。 + content: + application/json: + schema: + $ref: '#/components/schemas/HealthResult' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + + /login: + post: + summary: ログイン処理。 + operationId: postLogin + tags: + - Users + security: [] # no authentication + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginRequest' + responses: + '200': + description: 成功時、Cookie にセッション ID を設定する。 + headers: + Set-Cookie: + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Me' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + + /logout: + post: + summary: ログアウト処理。 + operationId: postLogout + tags: + - Users + security: [] # no authentication + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginRequest' + responses: + '204': + description: 成功時 Cookie のセッション情報を削除する。 + headers: + Set-Cookie: + schema: + type: string + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + + /me: + get: + summary: セッションをもとにログインしているユーザーの情報を取得する。 + operationId: getMe + tags: + - Users + responses: + '200': + description: 成功時、Cookie にセッション ID を設定する。 + content: + application/json: + schema: + $ref: '#/components/schemas/Me' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + + /users: + get: + summary: フォローするためのユーザー一覧を取得する。 + tags: + - Users + parameters: + - in: query + name: user_id + required: true + schema: + type: string + description: | + TODO: 認証の内容からユーザー ID を取得するようにし、query (や path) からは削除する。 + responses: + '200': + description: ユーザー一覧 + content: + application/json: + schema: + $ref: '#/components/schemas/Users' + example: + $ref: './examples/get-users.json' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + + /follows: + post: + summary: ユーザーをフォローする。 + tags: + - Users + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/FollowUserRequest' + example: + $ref: './examples/post-follows-request.json' + responses: + '201': + description: User followed successfully + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + + /tweets: + post: + summary: 新規ツイートを投稿する。 + tags: + - Tweets + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PostTweetRequest' + example: + $ref: './examples/post-tweet-request.json' + responses: + '201': + description: Tweet posted successfully + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + + /timeline: + get: + summary: ユーザーのタイムライン用の情報を取得する。 + tags: + - Tweets + parameters: + - in: query + name: user_id + required: true + schema: + type: string + description: | + TODO: 認証の内容からユーザー ID を取得するようにし、query (や path) からは削除する。 + responses: + '200': + description: タイムラインに表示する情報一覧 + content: + application/json: + schema: + $ref: '#/components/schemas/Timeline' + example: + $ref: './examples/get-timeline.json' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '500': + $ref: '#/components/responses/InternalServerError' + +components: + securitySchemes: + cookieAuth: + type: apiKey + in: cookie + name: sns-app-session + + responses: + BadRequest: + description: 不正なリクエスト。 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + Unauthorized: + description: 認証エラー。 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + InternalServerError: + description: インターナルサーバーエラー。 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + schemas: + LoginRequest: + type: object + required: + - token + properties: + token: + type: string + description: Firebase Auth API から受け取った ID トークン。 + PostTweetRequest: + type: object + required: + - body + properties: + body: + type: string + description: ツイートの本文。文字数は 140 文字まで。 + FollowUserRequest: + type: object + required: + - followed_id + properties: + followed_id: + type: string + description: フォローされるユーザーの ID。 + + HealthResult: + type: object + required: + - status + properties: + status: + type: string + description: サーバーの状態。 + example: + status: "OK" + + Me: + type: object + required: + - id + - name + properties: + id: + type: string + description: ユーザーの ID。 + name: + type: string + description: ユーザー名。 + provider: + type: string + description: どのプロバイダーでログインしているか。 + + Users: + type: object + required: + - users + properties: + users: + type: array + items: + type: object + $ref: '#/components/schemas/User' + User: + type: object + required: + - id + - name + - is_follower + properties: + id: + type: string + description: ユーザーの ID。 + name: + type: string + description: ユーザー名。 + is_follower: + type: boolean + description: ログインユーザーがフォローしているかどうか。 + + Timeline: + type: object + required: + - tweet + properties: + tweets: + type: array + items: + type: object + $ref: '#/components/schemas/Tweet' + Tweet: + type: object + required: + - id + - user_id + - user_name + - body + - timestamp + properties: + id: + type: string + description: ツイートの ID。 + user_id: + type: string + description: ツイートしたユーザーの ID。 + user_name: + type: string + description: ツイートしたユーザーの名前。 + body: + type: string + description: ツイートの本文。 + timestamp: + type: string + format: int64 + description: ツイートした日時(ナノ秒タイムスタンプ)。 + + ErrorResponse: + type: object + properties: + error: + type: string + message: + type: string