iachat/config/routes.rb
Gabriel Jablonski e032fc7774
feat(whatsapp): convert inbox between WhatsApp providers (#268)
* feat(whatsapp): allow converting inbox between WhatsApp providers

Adds a Convert flow to switch a WhatsApp inbox between the four
supported providers (default/360dialog, whatsapp_cloud, baileys, zapi)
without losing conversations, agents, or history.

- Channel::Whatsapp#convert_provider! runs inside a transaction:
  disconnects the old provider, clears provider_connection and
  message_templates, assigns the new provider/config, and triggers
  webhook setup plus template resync on the new service.
- New POST /api/v1/accounts/:id/inboxes/:id/convert_provider endpoint
  guarded by InboxPolicy#convert_provider? (admin only).
- UI adds a Convert button on the inbox Settings page with a
  type-to-confirm ConvertInboxModal that lists the effects before
  redirecting to a dedicated route reusing the WhatsApp provider
  wizard in convert mode (phone number locked, current provider
  hidden from the picker).

* chore(whatsapp): polish convert UI colors and expand specs

- Settings: use slate for the Convert trigger and ruby for the modal
  confirm to mirror the delete gate instead of the less conventional
  amber variant.
- Drop the redundant "current provider is hidden from the list"
  sentence from the convert wizard description.
- Add specs for the post-conversion webhook setup path (triggered and
  skipped branches) and the sync_templates error-rescue behaviour.

* fix: address CodeRabbit review on convert-provider flow

- Whitelist provider_config keys in the convert endpoint via permit
  rather than permit!, and default to an empty hash when omitted so
  the request no longer crashes.
- Pre-validate the new provider config before disconnecting the old
  session so a bad target config no longer terminates the existing
  provider; also keep the disconnect bound to the old provider_url.
- Guard ConvertInboxModal's submit handler so pressing Enter cannot
  bypass the type-to-confirm gate, and migrate it to <script setup>.
- Reject invalid ?provider= query values in convert mode so hidden
  providers (Twilio, the current provider) cannot be reached via URL.
- Await the inbox fetch in InboxConvert before running the route guard
  so directly opening the route for a non-WhatsApp inbox redirects.
- Remove the unreachable second CloudWhatsapp branch in Whatsapp.vue.

* fix: address second CodeRabbit round on convert-provider flow

- Unify provider picker validation so create mode also rejects
  unknown ?provider= values, with a single helper that accepts
  available providers plus the whatsapp_manual fallback.
- Simplify the pre-validation rollback in convert_provider!: the
  errors snapshot/merge dance was redundant because assign_attributes
  does not clear errors.
- Follow the repo convention of asserting on error.class.name so the
  rollback spec stays stable under reloading/parallel environments.
- Strengthen the controller success spec with provider_connection and
  message_templates cleanup invariants, and set Content-Type on the
  templates stub so HTTParty parses the empty data array correctly.

* fix: address third CodeRabbit round on convert-provider flow

- Add 360Dialog entry to the Whatsapp provider catalog, keep it hidden
  from the create picker (preserving the existing fork behavior) but
  expose it in the convert picker where it is a valid target. Restore
  URL reachability for ?provider=360dialog in create mode.
- Scope the WHATSAPP_MANUAL allowance to create mode only: the manual
  fallback flow is not reachable in convert mode.
- Redirect to the inboxes list in InboxConvert when the inbox is still
  absent after the store fetch, so the page no longer stays blank.
- Use an explicit allowlist of WhatsApp providers to gate the Convert
  button instead of negating Twilio, so adding a new WhatsApp channel
  type will not silently expose the flow.
- Bind the disabled provider display field with :value instead of
  v-model, since the underlying computed is getter-only.
- Add Content-Type: application/json to the templates stub in the
  model spec so HTTParty parses the empty data array.

* fix: address fourth CodeRabbit round on convert-provider flow

- Reject no-op conversions that target the same provider as the one
  already configured, so the endpoint no longer wipes provider
  connection and message templates on a request that changes nothing.
- Call the provider service's disconnect directly so failures abort
  the conversion instead of being silently swallowed; otherwise the
  old external session could remain live while the inbox flips to
  the new provider.
- Cover both behaviors with specs.

* fix: address fifth CodeRabbit round on convert-provider flow

- Reset the Vuelidate state when closing ConvertInboxModal so reopening
  the gate does not surface stale validation errors.
- Call teardown_webhooks before converting away from whatsapp_cloud so
  the Meta webhook subscription is removed for embedded_signup channels,
  mirroring the destroy-time cleanup (manual-setup channels keep the
  existing no-op behavior). Swallow teardown failures so a flaky Meta
  call does not abort the swap.
- Switch the rollback specs to compare message_templates counts instead
  of the boolean be_present matcher so they remain meaningful if the
  fixture happens to have an empty templates list.

* fix: address sixth CodeRabbit round on convert-provider flow

- Derive the convert header's current-provider label from the shared
  PROVIDER_CATALOG so the picker and header stay in sync.
- Assert the full Cloud provider_config payload and the absence of the
  Baileys-only provider_url key on both the controller success spec
  and the model atomic-swap spec.
- In the sync-error spec, reload and assert that the record was
  actually flipped to the new provider before the sync rescue fires,
  so the test can't pass on a pre-save failure.

* test: pin 422 error payload on convert_provider negative paths

The unsupported-conversion and invalid-config specs only checked the
status code, so they would have stayed green if the 422 started coming
from a different branch. Pin the response body so each example actually
covers the failure case it names.

* fix(baileys): save custom host as provider_url, not url

The Baileys form was writing the custom endpoint to
provider_config['url'] while the backend reads
provider_config['provider_url']. That silently broke the custom-host
feature for newly created or converted Baileys inboxes: they always
fell back to BAILEYS_PROVIDER_DEFAULT_URL. Align the key on both ends.

* fix(whatsapp): skip second validation pass in convert_provider!

The transaction's save! was re-running validate_provider_config after
the old provider's session had already been disconnected, so a transient
Graph API failure on the second check could roll back the swap while
leaving the external session terminated — the exact inconsistency the
pre-flight valid? was meant to rule out.

Capture the validated provider_config snapshot after valid? (so fields
populated by before_validation callbacks like webhook_verify_token are
preserved) and switch the final persist to save!(validate: false) so the
earlier check stays authoritative.

* fix: normalize provider-conversion failures and pass accountId

- The convert_provider action only rescued ActiveRecord::RecordInvalid,
  so disconnect/teardown failures bubbled up as 500 with no stable
  payload. Catch StandardError, log the class + message, and return a
  422 with a generic user-facing message so the dashboard can surface
  the error consistently.
- Nested settings routes live under /accounts/:accountId, so the
  router push from Settings.vue must include accountId alongside
  inboxId. Mirrors how sibling pages navigate to settings_inbox_show.

* fix: report missing :provider as 400 and sync modal v-model

- The generic rescue StandardError on convert_provider was masking
  ActionController::ParameterMissing behind a misleading
  provider-conversion error message. Catch it explicitly before the
  generic rescue and return 400 with the parameter-missing message.
- ConvertInboxModal's closeModal now drives localShow to false so
  parents using v-model:show stay in sync on every close path,
  not only when the explicit onClose listener flips the flag.

* fix(whatsapp): serialize concurrent convert_provider calls with_lock

Without a per-record lock, two admin requests against the same inbox
could both pass the pre-flight validation, race the disconnect/save,
and then run setup_webhooks/sync_templates in arbitrary order, leaving
the persisted provider out of sync with the external configuration.

Wrap the whole convert flow in with_lock so the loser blocks until the
winner commits; the subsequent no-op guard then rejects a second
conversion request targeting the provider the first one just set.

* test: harden convert_provider policy + controller failure specs

- Pass accountId explicitly in InboxConvert redirects so the route
  navigation mirrors how Settings.vue reaches settings_inbox_convert.
- Add a spec that assigns the agent to the inbox and still expects 401,
  so a future regression in InboxPolicy#convert_provider? can no longer
  slip past on the show policy alone.
- Add a spec that stubs convert_provider! to raise StandardError and
  asserts the controller's generic-failure 422 payload, pinning the
  dashboard contract for provider-side failures.

* test: pin convert_provider success response payload

Parse the rendered body and assert provider + provider_config so the
spec catches regressions where the DB is updated correctly but the
serialized response drifts (dashboard store commits response.data).

* fix(whatsapp): reset teardown guard after pre-conversion webhook cleanup

teardown_webhooks memoizes @webhook_teardown_initiated = true to prevent
double execution during destroy. Calling it from convert_provider!
leaves that flag set, so a subsequent destroy! or follow-up conversion
on the same instance would skip webhook removal silently. Reset the
flag in an ensure block so the destroy-time guard stays scoped to
destroy only.

* fix: include accountId in post-conversion redirect params

* test: pin same-provider convert returns 422

* fix(whatsapp): reset template columns when post-conversion sync fails

* fix(convert): enforce provider allowlist in InboxConvert route guard

* test: broaden Cloud templates stub to match account-scoped path

* test(whatsapp): cover cloud to baileys conversion branch
2026-04-18 20:57:27 -03:00

733 lines
27 KiB
Ruby

Rails.application.routes.draw do
# AUTH STARTS
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
confirmations: 'devise_overrides/confirmations',
passwords: 'devise_overrides/passwords',
sessions: 'devise_overrides/sessions',
token_validations: 'devise_overrides/token_validations',
omniauth_callbacks: 'devise_overrides/omniauth_callbacks'
}, via: [:get, :post]
post 'resend_confirmation', to: 'auth/resend_confirmations#create'
## renders the frontend paths only if its not an api only server
if ActiveModel::Type::Boolean.new.cast(ENV.fetch('CW_API_ONLY_SERVER', false))
root to: 'api#index'
else
root to: 'dashboard#index'
get '/app', to: 'dashboard#index'
get '/app/*params', to: 'dashboard#index'
get '/app/accounts/:account_id/settings/inboxes/new/twitter', to: 'dashboard#index', as: 'app_new_twitter_inbox'
get '/app/accounts/:account_id/settings/inboxes/new/microsoft', to: 'dashboard#index', as: 'app_new_microsoft_inbox'
get '/app/accounts/:account_id/settings/inboxes/new/instagram', to: 'dashboard#index', as: 'app_new_instagram_inbox'
get '/app/accounts/:account_id/settings/inboxes/new/tiktok', to: 'dashboard#index', as: 'app_new_tiktok_inbox'
get '/app/accounts/:account_id/settings/inboxes/new/:inbox_id/agents', to: 'dashboard#index', as: 'app_twitter_inbox_agents'
get '/app/accounts/:account_id/settings/inboxes/new/:inbox_id/agents', to: 'dashboard#index', as: 'app_email_inbox_agents'
get '/app/accounts/:account_id/settings/inboxes/new/:inbox_id/agents', to: 'dashboard#index', as: 'app_instagram_inbox_agents'
get '/app/accounts/:account_id/settings/inboxes/new/:inbox_id/agents', to: 'dashboard#index', as: 'app_tiktok_inbox_agents'
get '/app/accounts/:account_id/settings/inboxes/:inbox_id', to: 'dashboard#index', as: 'app_instagram_inbox_settings'
get '/app/accounts/:account_id/settings/inboxes/:inbox_id', to: 'dashboard#index', as: 'app_tiktok_inbox_settings'
get '/app/accounts/:account_id/settings/inboxes/:inbox_id', to: 'dashboard#index', as: 'app_email_inbox_settings'
resource :widget, only: [:show]
namespace :survey do
resources :responses, only: [:show]
end
resource :slack_uploads, only: [:show]
end
get '/health', to: 'health#show'
get '/api', to: 'api#index'
namespace :api, defaults: { format: 'json' } do
namespace :v1 do
# ----------------------------------
# start of account scoped api routes
resources :accounts, only: [:create, :show, :update] do
member do
post :update_active_at
get :cache_keys
end
scope module: :accounts do
namespace :actions do
resource :contact_merge, only: [:create]
end
resource :bulk_actions, only: [:create]
resources :agents, only: [:index, :create, :update, :destroy] do
post :bulk_create, on: :collection
end
namespace :captain do
resource :preferences, only: [:show, :update]
resources :assistants do
member do
post :playground
end
collection do
get :tools
end
resources :inboxes, only: [:index, :create, :destroy], param: :inbox_id
resources :scenarios
end
resources :assistant_responses
resources :bulk_actions, only: [:create]
resources :copilot_threads, only: [:index, :create] do
resources :copilot_messages, only: [:index, :create]
end
resources :custom_tools do
post :test, on: :collection
end
resources :documents, only: [:index, :show, :create, :destroy]
resource :tasks, only: [], controller: 'tasks' do
post :rewrite
post :summarize
post :reply_suggestion
post :label_suggestion
post :follow_up
end
end
resource :saml_settings, only: [:show, :create, :update, :destroy]
resources :agent_bots, only: [:index, :create, :show, :update, :destroy] do
delete :avatar, on: :member
post :reset_access_token, on: :member
post :reset_secret, on: :member
end
resources :contact_inboxes, only: [] do
collection do
post :filter
end
end
resources :assignable_agents, only: [:index]
resource :audit_logs, only: [:show]
resources :callbacks, only: [] do
collection do
post :register_facebook_page
get :register_facebook_page
post :facebook_pages
post :reauthorize_page
end
end
resources :canned_responses, only: [:index, :create, :update, :destroy]
resources :automation_rules, only: [:index, :create, :show, :update, :destroy] do
post :clone
end
resources :macros, only: [:index, :create, :show, :update, :destroy] do
post :execute, on: :member
end
resources :sla_policies, only: [:index, :create, :show, :update, :destroy]
resources :custom_roles, only: [:index, :create, :show, :update, :destroy]
resources :agent_capacity_policies, only: [:index, :create, :show, :update, :destroy] do
scope module: :agent_capacity_policies do
resources :users, only: [:index, :create, :destroy]
resources :inbox_limits, only: [:create, :update, :destroy]
end
end
resources :campaigns, only: [:index, :create, :show, :update, :destroy]
resources :dashboard_apps, only: [:index, :show, :create, :update, :destroy]
namespace :channels do
resource :twilio_channel, only: [:create]
end
resources :conversations, only: [:index, :create, :show, :update, :destroy] do
collection do
get :meta
get :search
post :filter
post :presence_subscribe_bulk
end
scope module: :conversations do
resources :messages, only: [:index, :create, :destroy, :update] do
member do
post :translate
post :retry
patch :edit_content
end
resources :attachments, only: [:update]
end
resources :scheduled_messages, only: [:index, :create, :update, :destroy]
resources :recurring_scheduled_messages, only: [:index, :create, :update, :destroy]
resources :assignments, only: [:create]
resources :labels, only: [:create, :index]
resource :participants, only: [:show, :create, :update, :destroy]
resource :direct_uploads, only: [:create]
resource :draft_messages, only: [:show, :update, :destroy]
end
member do
post :mute
post :unmute
post :transcript
post :toggle_status
post :toggle_priority
post :toggle_typing_status
post :presence_subscribe
post :update_last_seen
post :unread
post :custom_attributes
get :attachments
get :inbox_assistant
get :reporting_events if ChatwootApp.enterprise?
end
end
namespace :internal_chat do
resource :search, only: [:show], controller: 'search'
resources :categories, only: [:index, :create, :update, :destroy]
resources :channels, only: [:index, :create, :show, :update, :destroy] do
member do
post :archive
post :unarchive
post :toggle_typing_status
post :mark_read
post :mark_unread
end
resources :members, controller: 'channel_members', only: [:index, :create, :update, :destroy]
resources :messages, only: [:index, :create, :update, :destroy] do
member do
post :pin
delete :unpin
get :thread
end
end
resource :draft, only: [:update, :destroy]
end
resources :messages, only: [] do
resources :reactions, only: [:create, :destroy]
end
resources :polls, only: [:create] do
member do
post :vote
delete :vote, action: :unvote
end
end
resources :drafts, only: [:index]
end
resources :search, only: [:index] do
collection do
get :conversations
get :messages
get :contacts
get :articles
end
end
resources :companies, only: [:index, :show, :create, :update, :destroy] do
collection do
get :search
end
end
resources :groups, only: [:create]
resources :contacts, only: [:index, :show, :update, :create, :destroy] do
collection do
get :active
get :search
post :filter
post :import
post :export
end
member do
get :contactable_inboxes
post :destroy_custom_attributes
post :sync_group
delete :avatar
end
scope module: :contacts do
resources :conversations, only: [:index]
resources :contact_inboxes, only: [:create]
resources :group_members, only: [:index, :create, :destroy] do
patch ':member_id', to: 'group_members#update', on: :collection
end
resource :group_metadata, only: [:update]
resource :group_invite, only: [:show] do
post :revoke, on: :member
end
resources :group_join_requests, only: [:index] do
post :handle, on: :collection
end
resource :group_admin, only: [:update], controller: 'group_admin' do
post :leave, on: :member
end
resources :labels, only: [:create, :index]
resources :notes
post :call, on: :member, to: 'calls#create' if ChatwootApp.enterprise?
end
end
resources :csat_survey_responses, only: [:index] do
collection do
get :metrics
get :download
end
member do
patch :update if ChatwootApp.enterprise?
end
end
resources :applied_slas, only: [:index] do
collection do
get :metrics
get :download
end
end
resources :reporting_events, only: [:index] if ChatwootApp.enterprise?
resources :custom_attribute_definitions, only: [:index, :show, :create, :update, :destroy]
resources :custom_filters, only: [:index, :show, :create, :update, :destroy]
resources :inboxes, only: [:index, :show, :create, :update, :destroy] do
get :assignable_agents, on: :member
get :campaigns, on: :member
get :agent_bot, on: :member
post :set_agent_bot, on: :member
post :setup_channel_provider, on: :member
post :disconnect_channel_provider, on: :member
post :convert_provider, on: :member
delete :avatar, on: :member
post :sync_templates, on: :member
get :health, on: :member
post :register_webhook, on: :member
post :reset_secret, on: :member
post :on_whatsapp, on: :member
if ChatwootApp.enterprise?
resource :conference, only: %i[create destroy], controller: 'conference' do
get :token, on: :member
end
end
resource :csat_template, only: [:show, :create], controller: 'inbox_csat_templates' do
post :analyze, on: :collection
post :link, on: :member
get :available_templates, on: :member
end
end
resources :inbox_members, only: [:create, :show], param: :inbox_id do
collection do
delete :destroy
patch :update
end
end
resources :labels, only: [:index, :show, :create, :update, :destroy]
resources :notifications, only: [:index, :update, :destroy] do
collection do
post :read_all
get :unread_count
post :destroy_all
end
member do
post :snooze
post :unread
end
end
resource :notification_settings, only: [:show, :update]
resources :teams do
resources :team_members, only: [:index, :create] do
collection do
delete :destroy
patch :update
end
end
end
# Assignment V2 Routes
resources :assignment_policies do
resources :inboxes, only: [:index, :create, :destroy], module: :assignment_policies
end
resources :inboxes, only: [] do
resource :assignment_policy, only: [:show, :create, :destroy], module: :inboxes
end
namespace :twitter do
resource :authorization, only: [:create]
end
namespace :microsoft do
resource :authorization, only: [:create]
end
namespace :google do
resource :authorization, only: [:create]
end
namespace :instagram do
resource :authorization, only: [:create]
end
namespace :tiktok do
resource :authorization, only: [:create]
end
namespace :notion do
resource :authorization, only: [:create]
end
namespace :whatsapp do
resource :authorization, only: [:create]
end
resources :webhooks, only: [:index, :create, :update, :destroy]
namespace :integrations do
resources :apps, only: [:index, :show]
resources :hooks, only: [:show, :create, :update, :destroy] do
member do
post :process_event
end
end
resource :slack, only: [:create, :update, :destroy], controller: 'slack' do
member do
get :list_all_channels
end
end
resource :dyte, controller: 'dyte', only: [] do
collection do
post :create_a_meeting
post :add_participant_to_meeting
end
end
resource :shopify, controller: 'shopify', only: [:destroy] do
collection do
post :auth
get :orders
end
end
resource :linear, controller: 'linear', only: [] do
collection do
delete :destroy
get :teams
get :team_entities
post :create_issue
post :link_issue
post :unlink_issue
get :search_issue
get :linked_issues
end
end
resource :notion, controller: 'notion', only: [] do
collection do
delete :destroy
end
end
end
resources :working_hours, only: [:update]
resources :portals do
member do
patch :archive
delete :logo
post :send_instructions
get :ssl_status
end
resources :categories do
post :reorder, on: :collection
end
resources :articles do
post :reorder, on: :collection
end
end
resources :upload, only: [:create]
end
end
# end of account scoped api routes
# ----------------------------------
namespace :integrations do
resources :webhooks, only: [:create]
end
# Frontend API endpoint to trigger SAML authentication flow
post 'auth/saml_login', to: 'auth#saml_login'
resource :profile, only: [:show, :update] do
delete :avatar, on: :collection
member do
post :availability
post :auto_offline
put :set_active_account
post :resend_confirmation
post :reset_access_token
end
# MFA routes
scope module: 'profile' do
resource :mfa, controller: 'mfa', only: [:show, :create, :destroy] do
post :verify
post :backup_codes
end
resources :inbox_signatures, only: %i[index show update destroy], param: :inbox_id
end
end
resource :notification_subscriptions, only: [:create, :destroy]
namespace :widget do
resource :direct_uploads, only: [:create]
resource :config, only: [:create]
resources :campaigns, only: [:index]
resources :events, only: [:create]
resources :messages, only: [:index, :create, :update]
resources :conversations, only: [:index, :create] do
collection do
post :destroy_custom_attributes
post :set_custom_attributes
post :update_last_seen
post :toggle_typing
post :transcript
get :toggle_status
end
end
resource :contact, only: [:show, :update] do
collection do
post :destroy_custom_attributes
patch :set_user
end
end
resources :inbox_members, only: [:index]
resources :labels, only: [:create, :destroy]
namespace :integrations do
resource :dyte, controller: 'dyte', only: [] do
collection do
post :add_participant_to_meeting
end
end
end
end
end
namespace :v2 do
resources :accounts, only: [:create] do
scope module: :accounts do
resources :summary_reports, only: [] do
collection do
get :agent
get :team
get :inbox
get :label
get :channel
end
end
resources :reports, only: [:index] do
collection do
get :summary
get :bot_summary
get :agents
get :inboxes
get :labels
get :teams
get :conversations
get :conversations_summary
get :conversation_traffic
get :bot_metrics
get :inbox_label_matrix
get :first_response_time_distribution
get :outgoing_messages_count
end
end
resource :year_in_review, only: [:show]
resources :live_reports, only: [] do
collection do
get :conversation_metrics
get :grouped_conversation_metrics
end
end
end
end
end
end
if ChatwootApp.enterprise?
namespace :enterprise, defaults: { format: 'json' } do
namespace :api do
namespace :v1 do
resources :accounts do
member do
post :checkout
post :subscription
get :limits
post :toggle_deletion
post :topup_checkout
end
end
end
end
post 'webhooks/stripe', to: 'webhooks/stripe#process_payload'
post 'webhooks/firecrawl', to: 'webhooks/firecrawl#process_payload'
end
end
# ----------------------------------------------------------------------
# Routes for platform APIs
namespace :platform, defaults: { format: 'json' } do
namespace :api do
namespace :v1 do
resources :users, only: [:create, :show, :update, :destroy] do
member do
get :login
post :token
end
end
resources :agent_bots, only: [:index, :create, :show, :update, :destroy] do
delete :avatar, on: :member
end
resources :accounts, only: [:index, :create, :show, :update, :destroy] do
resources :account_users, only: [:index, :create] do
collection do
delete :destroy
end
end
resources :email_channel_migrations, only: [:create]
end
end
end
end
# ----------------------------------------------------------------------
# Routes for inbox APIs Exposed to contacts
namespace :public, defaults: { format: 'json' } do
namespace :api do
namespace :v1 do
resources :inboxes do
scope module: :inboxes do
resources :contacts, only: [:create, :show, :update] do
resources :conversations, only: [:index, :create, :show] do
member do
post :toggle_status
post :toggle_typing
post :update_last_seen
end
resources :messages, only: [:index, :create, :update]
end
end
end
end
resources :csat_survey, only: [:show, :update]
end
end
end
get 'hc/:slug', to: 'public/api/v1/portals#show'
get 'hc/:slug/sitemap.xml', to: 'public/api/v1/portals#sitemap'
get 'hc/:slug/:locale', to: 'public/api/v1/portals#show'
get 'hc/:slug/:locale/articles', to: 'public/api/v1/portals/articles#index'
get 'hc/:slug/:locale/categories', to: 'public/api/v1/portals/categories#index'
get 'hc/:slug/:locale/categories/:category_slug', to: 'public/api/v1/portals/categories#show'
get 'hc/:slug/:locale/categories/:category_slug/articles', to: 'public/api/v1/portals/articles#index'
get 'hc/:slug/articles/:article_slug.png', to: 'public/api/v1/portals/articles#tracking_pixel'
get 'hc/:slug/articles/:article_slug', to: 'public/api/v1/portals/articles#show'
# ----------------------------------------------------------------------
# Used in mailer templates
resource :app, only: [:index] do
resources :accounts do
resources :conversations, only: [:show]
end
end
# ----------------------------------------------------------------------
# Routes for channel integrations
mount Facebook::Messenger::Server, at: 'bot'
get 'webhooks/twitter', to: 'api/v1/webhooks#twitter_crc'
post 'webhooks/twitter', to: 'api/v1/webhooks#twitter_events'
post 'webhooks/line/:line_channel_id', to: 'webhooks/line#process_payload'
post 'webhooks/telegram/:bot_token', to: 'webhooks/telegram#process_payload'
post 'webhooks/sms/:phone_number', to: 'webhooks/sms#process_payload'
get 'webhooks/whatsapp/:phone_number', to: 'webhooks/whatsapp#verify'
post 'webhooks/whatsapp/:phone_number', to: 'webhooks/whatsapp#process_payload'
get 'webhooks/instagram', to: 'webhooks/instagram#verify'
post 'webhooks/instagram', to: 'webhooks/instagram#events'
post 'webhooks/tiktok', to: 'webhooks/tiktok#events'
post 'webhooks/shopify', to: 'webhooks/shopify#events'
namespace :twitter do
resource :callback, only: [:show]
end
namespace :linear do
resource :callback, only: [:show]
end
namespace :shopify do
resource :callback, only: [:show]
end
namespace :twilio do
resources :callback, only: [:create]
resources :delivery_status, only: [:create]
if ChatwootApp.enterprise?
post 'voice/call/:phone', to: 'voice#call_twiml', as: :voice_call
post 'voice/status/:phone', to: 'voice#status', as: :voice_status
post 'voice/conference_status/:phone', to: 'voice#conference_status', as: :voice_conference_status
end
end
get 'microsoft/callback', to: 'microsoft/callbacks#show'
get 'google/callback', to: 'google/callbacks#show'
get 'instagram/callback', to: 'instagram/callbacks#show'
get 'tiktok/callback', to: 'tiktok/callbacks#show'
get 'notion/callback', to: 'notion/callbacks#show'
# ----------------------------------------------------------------------
# Routes for external service verifications
get '.well-known/assetlinks.json' => 'android_app#assetlinks'
get '.well-known/apple-app-site-association' => 'apple_app#site_association'
get '.well-known/microsoft-identity-association.json' => 'microsoft#identity_association'
get '.well-known/cf-custom-hostname-challenge/:id', to: 'custom_domains#verify'
# ----------------------------------------------------------------------
# Internal Monitoring Routes
require 'sidekiq/web'
require 'sidekiq/cron/web'
devise_for :super_admins, path: 'super_admin', controllers: { sessions: 'super_admin/devise/sessions' }
devise_scope :super_admin do
get 'super_admin/logout', to: 'super_admin/devise/sessions#destroy'
namespace :super_admin do
root to: 'dashboard#index'
resource :app_config, only: [:show, :create]
# order of resources affect the order of sidebar navigation in super admin
resources :accounts, only: [:index, :new, :create, :show, :edit, :update, :destroy] do
post :seed, on: :member
post :reset_cache, on: :member
end
resources :users, only: [:index, :new, :create, :show, :edit, :update, :destroy] do
delete :avatar, on: :member, action: :destroy_avatar
end
resources :access_tokens, only: [:index, :show]
resources :installation_configs, only: [:index, :new, :create, :show, :edit, :update]
resources :agent_bots, only: [:index, :new, :create, :show, :edit, :update, :destroy] do
delete :avatar, on: :member, action: :destroy_avatar
end
resources :platform_apps, only: [:index, :new, :create, :show, :edit, :update, :destroy]
resource :instance_status, only: [:show]
resource :settings, only: [:show] do
get :refresh, on: :collection
end
# resources that doesn't appear in primary navigation in super admin
resources :account_users, only: [:new, :create, :show, :destroy]
end
authenticated :super_admin do
mount Sidekiq::Web => '/monitoring/sidekiq'
end
end
namespace :installation do
get 'onboarding', to: 'onboarding#index'
post 'onboarding', to: 'onboarding#create'
end
# ---------------------------------------------------------------------
# Routes for swagger docs
get '/swagger/*path', to: 'swagger#respond'
get '/swagger', to: 'swagger#respond'
# ----------------------------------------------------------------------
# Routes for testing
resources :widget_tests, only: [:index] unless Rails.env.production?
end