diff --git a/.gitignore b/.gitignore
index 7192ba9d9..629e1676e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,6 +37,10 @@ public/packs*
*.swo
*.un~
.jest-cache
+
+#VS Code files
+.vscode
+
# ignore jetbrains IDE files
.idea
diff --git a/Gemfile b/Gemfile
index d2f3465e2..49a8c0729 100644
--- a/Gemfile
+++ b/Gemfile
@@ -83,6 +83,9 @@ group :development do
gem 'bullet'
gem 'letter_opener'
gem 'web-console'
+
+ # used in swagger build
+ gem 'json_refs', git: 'https://github.com/sony-mathew/json_refs', ref: 'b6c142a'
end
group :development, :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index 9bb07458f..5d309e59a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -5,6 +5,14 @@ GIT
twitty (0.1.0)
oauth
+GIT
+ remote: https://github.com/sony-mathew/json_refs
+ revision: b6c142ae486e399d00fbc758e21a7ed63a934f61
+ ref: b6c142a
+ specs:
+ json_refs (0.1.2)
+ hana
+
GEM
remote: https://rubygems.org/
specs:
@@ -201,6 +209,7 @@ GEM
os (>= 0.9, < 2.0)
signet (~> 0.12)
haikunator (1.1.0)
+ hana (1.3.5)
hashie (4.1.0)
http-accept (1.7.0)
http-cookie (1.0.3)
@@ -498,6 +507,7 @@ DEPENDENCIES
haikunator
hashie
jbuilder
+ json_refs!
jwt
kaminari
koala
diff --git a/app/controllers/swagger_controller.rb b/app/controllers/swagger_controller.rb
new file mode 100644
index 000000000..c5f8c0f5b
--- /dev/null
+++ b/app/controllers/swagger_controller.rb
@@ -0,0 +1,18 @@
+class SwaggerController < ApplicationController
+ def respond
+ if Rails.env.development? || Rails.env.test?
+ render inline: File.read(Rails.root.join('swagger', derived_path))
+ else
+ head 404
+ end
+ end
+
+ private
+
+ def derived_path
+ params[:path] ||= 'index.html'
+ path = params[:path]
+ path << ".#{params[:format]}" unless path.ends_with?(params[:format].to_s)
+ path
+ end
+end
diff --git a/config/routes.rb b/config/routes.rb
index b1d718576..a067eb30f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -144,4 +144,8 @@ Rails.application.routes.draw do
mount Sidekiq::Web, at: '/sidekiq'
end
# ----------------------------------------------------------------------
+
+ # Routes for swagger docs
+ get '/swagger/*path', to: 'swagger#respond'
+ get '/swagger', to: 'swagger#respond'
end
diff --git a/lib/tasks/swagger.rake b/lib/tasks/swagger.rake
new file mode 100644
index 000000000..c13147aee
--- /dev/null
+++ b/lib/tasks/swagger.rake
@@ -0,0 +1,22 @@
+namespace :swagger do
+ desc 'build combined swagger.json file from all the fragmented definitions and paths inside swagger folder'
+ task build: :environment do
+ require 'json_refs'
+
+ base_path = Rails.root.join('swagger')
+ Dir.chdir(base_path) do
+ swagger_index = YAML.safe_load(File.open('index.yml'))
+
+ final_build = JsonRefs.call(
+ swagger_index,
+ resolve_local_ref: false,
+ resolve_file_ref: true,
+ logging: true
+ )
+ File.write('swagger.json', JSON.pretty_generate(final_build))
+ puts 'Swagger build was succesful.'
+ puts "Generated #{base_path}/swagger.json"
+ puts 'Go to http://localhost:3000/swagger see the changes.'
+ end
+ end
+end
diff --git a/spec/controllers/swagger_controller_spec.rb b/spec/controllers/swagger_controller_spec.rb
new file mode 100644
index 000000000..f68bd6e9e
--- /dev/null
+++ b/spec/controllers/swagger_controller_spec.rb
@@ -0,0 +1,12 @@
+require 'rails_helper'
+
+describe '/swagger', type: :request do
+ describe 'GET /swagger' do
+ it 'renders swagger index.html' do
+ get '/swagger'
+ expect(response).to have_http_status(:success)
+ expect(response.body).to include('redoc')
+ expect(response.body).to include('/swagger.json')
+ end
+ end
+end
diff --git a/swagger/definitions/error/bad_request.yml b/swagger/definitions/error/bad_request.yml
new file mode 100644
index 000000000..b6facb361
--- /dev/null
+++ b/swagger/definitions/error/bad_request.yml
@@ -0,0 +1,9 @@
+title: data
+type: object
+properties:
+ description:
+ type: string
+ errors:
+ type: array
+ items:
+ $ref: '#/definitions/request_error'
diff --git a/swagger/definitions/error/request.yml b/swagger/definitions/error/request.yml
new file mode 100644
index 000000000..79b2ca4d4
--- /dev/null
+++ b/swagger/definitions/error/request.yml
@@ -0,0 +1,8 @@
+type: object
+properties:
+ field:
+ type: string
+ message:
+ type: string
+ code:
+ type: string
diff --git a/swagger/definitions/index.yml b/swagger/definitions/index.yml
new file mode 100644
index 000000000..3ef07961d
--- /dev/null
+++ b/swagger/definitions/index.yml
@@ -0,0 +1,39 @@
+# ERROR
+bad_request_error:
+ $ref: ./error/bad_request.yml
+request_error:
+ $ref: ./error/request.yml
+
+# RESOURCE
+contact:
+ $ref: ./resource/contact.yml
+conversation:
+ $ref: ./resource/conversation.yml
+
+# RESPONSE
+extended_contact:
+ allOf:
+ - $ref: '#/definitions/contact'
+ - $ref: ./resource/extension/contact/show.yml
+contact_base:
+ allOf:
+ - $ref: '#/definitions/contact'
+ - $ref: ./resource/extension/generic.yml
+contact_list:
+ type: array
+ items:
+ allOf:
+ - $ref: '#/definitions/contact'
+ - $ref: ./resource/extension/generic.yml
+contact_conversations:
+ type: array
+ items:
+ allOf:
+ - $ref: '#/definitions/conversation'
+ - $ref: ./resource/extension/contact/conversation.yml
+
+# REQUEST
+contact_create:
+ $ref: ./request/contact/create.yml
+contact_update:
+ $ref: ./request/contact/update.yml
diff --git a/swagger/definitions/request/contact/create.yml b/swagger/definitions/request/contact/create.yml
new file mode 100644
index 000000000..6e83a12fa
--- /dev/null
+++ b/swagger/definitions/request/contact/create.yml
@@ -0,0 +1,6 @@
+type: object
+properties:
+ account_id:
+ type: number
+ inbox_id:
+ type: number
diff --git a/swagger/definitions/request/contact/update.yml b/swagger/definitions/request/contact/update.yml
new file mode 100644
index 000000000..cafa35169
--- /dev/null
+++ b/swagger/definitions/request/contact/update.yml
@@ -0,0 +1,8 @@
+type: object
+properties:
+ name:
+ type: string
+ email:
+ type: string
+ phone_number:
+ type: string
diff --git a/swagger/definitions/resource/contact.yml b/swagger/definitions/resource/contact.yml
new file mode 100644
index 000000000..01261239a
--- /dev/null
+++ b/swagger/definitions/resource/contact.yml
@@ -0,0 +1,12 @@
+type: object
+properties:
+ email:
+ type: string
+ name:
+ type: string
+ phone_number:
+ type: string
+ thumbnail:
+ type: string
+ additional_attributes:
+ type: object
diff --git a/swagger/definitions/resource/conversation.yml b/swagger/definitions/resource/conversation.yml
new file mode 100644
index 000000000..a6c8f18d6
--- /dev/null
+++ b/swagger/definitions/resource/conversation.yml
@@ -0,0 +1,21 @@
+type: object
+properties:
+ display_id:
+ type: number
+ messages:
+ type: array
+ items:
+ type: object
+ inbox_id:
+ type: number
+ status:
+ type: string
+ enum: ['open', 'resolved']
+ timestamp:
+ type: string
+ user_last_seen_at:
+ type: string
+ agent_last_seen_at:
+ type: agent_last_seen_at
+ unread_count:
+ type: number
diff --git a/swagger/definitions/resource/extension/contact/conversation.yml b/swagger/definitions/resource/extension/contact/conversation.yml
new file mode 100644
index 000000000..e54cb178a
--- /dev/null
+++ b/swagger/definitions/resource/extension/contact/conversation.yml
@@ -0,0 +1,18 @@
+type: object
+properties:
+ meta:
+ type: object
+ properties:
+ sender:
+ type: object
+ properties:
+ id:
+ type: number
+ name:
+ type: string
+ thumbnail:
+ type: string
+ channel:
+ type: string
+ assignee:
+ type: object
diff --git a/swagger/definitions/resource/extension/contact/list.yml b/swagger/definitions/resource/extension/contact/list.yml
new file mode 100644
index 000000000..0378da06e
--- /dev/null
+++ b/swagger/definitions/resource/extension/contact/list.yml
@@ -0,0 +1,5 @@
+type: object
+properties:
+ allOf:
+ - $ref: '#/definitions/contact'
+ - $ref: ./resource/extension/contact.yaml
diff --git a/swagger/definitions/resource/extension/contact/show.yml b/swagger/definitions/resource/extension/contact/show.yml
new file mode 100644
index 000000000..a4c26f306
--- /dev/null
+++ b/swagger/definitions/resource/extension/contact/show.yml
@@ -0,0 +1,7 @@
+type: object
+properties:
+ id:
+ type: number
+ availability_status:
+ type: string
+ enum: ['online', 'offline']
\ No newline at end of file
diff --git a/swagger/definitions/resource/extension/generic.yml b/swagger/definitions/resource/extension/generic.yml
new file mode 100644
index 000000000..1817d821d
--- /dev/null
+++ b/swagger/definitions/resource/extension/generic.yml
@@ -0,0 +1,4 @@
+type: object
+properties:
+ id:
+ type: number
\ No newline at end of file
diff --git a/swagger/index.html b/swagger/index.html
new file mode 100644
index 000000000..65ec1b81a
--- /dev/null
+++ b/swagger/index.html
@@ -0,0 +1,23 @@
+
+
+
+ ReDoc
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/swagger/index.yml b/swagger/index.yml
new file mode 100644
index 000000000..44b75ca20
--- /dev/null
+++ b/swagger/index.yml
@@ -0,0 +1,17 @@
+swagger: 2.0
+info:
+ description: This is the api documentation for Chatwoot server.
+ version: 1.0.0
+ title: Chatwoot
+ termsOfService: https://www.chatwoot.com/terms-of-service/
+ contact:
+ email: support@chatwoot.com
+ license:
+ name: MIT License
+ url: https://opensource.org/licenses/MIT
+ host: app.chatwoot.com
+ basePath: /api/v1/
+paths:
+ $ref: ./paths/index.yml
+definitions:
+ $ref: ./definitions/index.yml
diff --git a/swagger/paths/contact/conversations.yml b/swagger/paths/contact/conversations.yml
new file mode 100644
index 000000000..39279a449
--- /dev/null
+++ b/swagger/paths/contact/conversations.yml
@@ -0,0 +1,18 @@
+get:
+ tags: [Contact]
+ summary: Conversations
+ parameters:
+ - name: id
+ in: path
+ type: number
+ description: ID of contact
+ required: true
+ responses:
+ 200:
+ description: Success
+ schema:
+ $ref: '#/definitions/contact_conversations'
+ 404:
+ description: Contact not found
+ 403:
+ description: Access denied
diff --git a/swagger/paths/contact/crud.yml b/swagger/paths/contact/crud.yml
new file mode 100644
index 000000000..c15911cb4
--- /dev/null
+++ b/swagger/paths/contact/crud.yml
@@ -0,0 +1,42 @@
+get:
+ tags: [Contact]
+ summary: Show Contact
+ parameters:
+ - name: id
+ in: path
+ type: number
+ description: ID of contact
+ required: true
+ responses:
+ 200:
+ description: Success
+ schema:
+ $ref: '#/definitions/extended_contact'
+ 404:
+ description: Contact not found
+ 403:
+ description: Access denied
+
+put:
+ tags: [Contact]
+ summary: Update Contact
+ parameters:
+ - name: id
+ in: path
+ type: number
+ description: ID of the contact
+ required: true
+ - name: data
+ in: body
+ required: true
+ schema:
+ $ref: '#/definitions/contact_update'
+ responses:
+ 204:
+ description: Success
+ schema:
+ $ref: '#/definitions/contact_base'
+ 404:
+ description: Contact not found
+ 403:
+ description: Access denied
diff --git a/swagger/paths/contact/list_create.yml b/swagger/paths/contact/list_create.yml
new file mode 100644
index 000000000..4aad9d7ac
--- /dev/null
+++ b/swagger/paths/contact/list_create.yml
@@ -0,0 +1,36 @@
+get:
+ tags: [Contact]
+ description: Listing all contacts with pagination
+ summary: List contacts
+ parameters:
+ - name: query_hash
+ in: query
+ type: string
+ responses:
+ 200:
+ description: Success
+ schema:
+ $ref: '#/definitions/contact_list'
+ 400:
+ description: Bad Request Error
+ schema:
+ $ref: '#/definitions/bad_request_error'
+
+post:
+ tags: [Contact]
+ description: Create a contact
+ parameters:
+ - name: data
+ in: body
+ required: true
+ schema:
+ $ref: '#/definitions/contact_create'
+ responses:
+ 200:
+ description: Success
+ schema:
+ $ref: '#/definitions/extended_contact'
+ 400:
+ description: Bad Request Error
+ schema:
+ $ref: '#/definitions/bad_request_error'
diff --git a/swagger/paths/index.yml b/swagger/paths/index.yml
new file mode 100644
index 000000000..54509e2e5
--- /dev/null
+++ b/swagger/paths/index.yml
@@ -0,0 +1,7 @@
+# Contacts
+/contacts:
+ $ref: ./contact/list_create.yml
+/contacts/{id}:
+ $ref: ./contact/crud.yml
+/contacts/{id}/conversations:
+ $ref: ./contact/conversations.yml
diff --git a/swagger/swagger.json b/swagger/swagger.json
new file mode 100644
index 000000000..1228004b3
--- /dev/null
+++ b/swagger/swagger.json
@@ -0,0 +1,386 @@
+{
+ "swagger": 2.0,
+ "info": {
+ "description": "This is the api documentation for Chatwoot server.",
+ "version": "1.0.0",
+ "title": "Chatwoot",
+ "termsOfService": "https://www.chatwoot.com/terms-of-service/",
+ "contact": {
+ "email": "support@chatwoot.com"
+ },
+ "license": {
+ "name": "MIT License",
+ "url": "https://opensource.org/licenses/MIT"
+ },
+ "host": "app.chatwoot.com",
+ "basePath": "/api/v1/"
+ },
+ "paths": {
+ "/contacts": {
+ "get": {
+ "tags": [
+ "Contact"
+ ],
+ "description": "Listing all contacts with pagination",
+ "summary": "List contacts",
+ "parameters": [
+ {
+ "name": "query_hash",
+ "in": "query",
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "schema": {
+ "$ref": "#/definitions/contact_list"
+ }
+ },
+ "400": {
+ "description": "Bad Request Error",
+ "schema": {
+ "$ref": "#/definitions/bad_request_error"
+ }
+ }
+ }
+ },
+ "post": {
+ "tags": [
+ "Contact"
+ ],
+ "description": "Create a contact",
+ "parameters": [
+ {
+ "name": "data",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/contact_create"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "schema": {
+ "$ref": "#/definitions/extended_contact"
+ }
+ },
+ "400": {
+ "description": "Bad Request Error",
+ "schema": {
+ "$ref": "#/definitions/bad_request_error"
+ }
+ }
+ }
+ }
+ },
+ "/contacts/{id}": {
+ "get": {
+ "tags": [
+ "Contact"
+ ],
+ "summary": "Show Contact",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "type": "number",
+ "description": "ID of contact",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "schema": {
+ "$ref": "#/definitions/extended_contact"
+ }
+ },
+ "404": {
+ "description": "Contact not found"
+ },
+ "403": {
+ "description": "Access denied"
+ }
+ }
+ },
+ "put": {
+ "tags": [
+ "Contact"
+ ],
+ "summary": "Update Contact",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "type": "number",
+ "description": "ID of the contact",
+ "required": true
+ },
+ {
+ "name": "data",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/contact_update"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Success",
+ "schema": {
+ "$ref": "#/definitions/contact_base"
+ }
+ },
+ "404": {
+ "description": "Contact not found"
+ },
+ "403": {
+ "description": "Access denied"
+ }
+ }
+ }
+ },
+ "/contacts/{id}/conversations": {
+ "get": {
+ "tags": [
+ "Contact"
+ ],
+ "summary": "Conversations",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "type": "number",
+ "description": "ID of contact",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "schema": {
+ "$ref": "#/definitions/contact_conversations"
+ }
+ },
+ "404": {
+ "description": "Contact not found"
+ },
+ "403": {
+ "description": "Access denied"
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "bad_request_error": {
+ "title": "data",
+ "type": "object",
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "errors": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/request_error"
+ }
+ }
+ }
+ },
+ "request_error": {
+ "type": "object",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ },
+ "code": {
+ "type": "string"
+ }
+ }
+ },
+ "contact": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "phone_number": {
+ "type": "string"
+ },
+ "thumbnail": {
+ "type": "string"
+ },
+ "additional_attributes": {
+ "type": "object"
+ }
+ }
+ },
+ "conversation": {
+ "type": "object",
+ "properties": {
+ "display_id": {
+ "type": "number"
+ },
+ "messages": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ }
+ },
+ "inbox_id": {
+ "type": "number"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "open",
+ "resolved"
+ ]
+ },
+ "timestamp": {
+ "type": "string"
+ },
+ "user_last_seen_at": {
+ "type": "string"
+ },
+ "agent_last_seen_at": {
+ "type": "agent_last_seen_at"
+ },
+ "unread_count": {
+ "type": "number"
+ }
+ }
+ },
+ "extended_contact": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/contact"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "number"
+ },
+ "availability_status": {
+ "type": "string",
+ "enum": [
+ "online",
+ "offline"
+ ]
+ }
+ }
+ }
+ ]
+ },
+ "contact_base": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/contact"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "number"
+ }
+ }
+ }
+ ]
+ },
+ "contact_list": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/contact"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "number"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "contact_conversations": {
+ "type": "array",
+ "items": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/conversation"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "meta": {
+ "type": "object",
+ "properties": {
+ "sender": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "number"
+ },
+ "name": {
+ "type": "string"
+ },
+ "thumbnail": {
+ "type": "string"
+ },
+ "channel": {
+ "type": "string"
+ }
+ }
+ },
+ "assignee": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "contact_create": {
+ "type": "object",
+ "properties": {
+ "account_id": {
+ "type": "number"
+ },
+ "inbox_id": {
+ "type": "number"
+ }
+ }
+ },
+ "contact_update": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "phone_number": {
+ "type": "string"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file