Restructure omni services and add Chatwoot research snapshot

This commit is contained in:
Ruslan Bakiev
2026-02-21 11:11:27 +07:00
parent edea7a0034
commit b73babbbf6
7732 changed files with 978203 additions and 32 deletions

View File

@@ -0,0 +1,129 @@
class Integrations::App
include Linear::IntegrationHelper
attr_accessor :params
def initialize(params)
@params = params
end
def id
params[:id]
end
def name
I18n.t("integration_apps.#{params[:i18n_key]}.name")
end
def description
I18n.t("integration_apps.#{params[:i18n_key]}.description")
end
def short_description
I18n.t("integration_apps.#{params[:i18n_key]}.short_description")
end
def logo
params[:logo]
end
def fields
params[:fields]
end
# There is no way to get the account_id from the linear callback
# so we are using the generate_linear_token method to generate a token and encode it in the state parameter
def encode_state
generate_linear_token(Current.account.id)
end
def action
case params[:id]
when 'slack'
client_id = GlobalConfigService.load('SLACK_CLIENT_ID', nil)
"#{params[:action]}&client_id=#{client_id}&redirect_uri=#{self.class.slack_integration_url}"
when 'linear'
build_linear_action
else
params[:action]
end
end
def active?(account)
case params[:id]
when 'slack'
GlobalConfigService.load('SLACK_CLIENT_SECRET', nil).present?
when 'linear'
account.feature_enabled?('linear_integration') && GlobalConfigService.load('LINEAR_CLIENT_ID', nil).present?
when 'shopify'
shopify_enabled?(account)
when 'leadsquared'
account.feature_enabled?('crm_integration')
when 'notion'
notion_enabled?(account)
else
true
end
end
def build_linear_action
app_id = GlobalConfigService.load('LINEAR_CLIENT_ID', nil)
[
"#{params[:action]}?response_type=code",
"client_id=#{app_id}",
"redirect_uri=#{self.class.linear_integration_url}",
"state=#{encode_state}",
'scope=read,write',
'prompt=consent',
'actor=app'
].join('&')
end
def enabled?(account)
case params[:id]
when 'webhook'
account.webhooks.exists?
when 'dashboard_apps'
account.dashboard_apps.exists?
else
account.hooks.exists?(app_id: id)
end
end
def hooks
Current.account.hooks.where(app_id: id)
end
def self.slack_integration_url
"#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{Current.account.id}/settings/integrations/slack"
end
def self.linear_integration_url
"#{ENV.fetch('FRONTEND_URL', nil)}/linear/callback"
end
class << self
def apps
Hashie::Mash.new(APPS_CONFIG)
end
def all
apps.values.each_with_object([]) do |app, result|
result << new(app)
end
end
def find(params)
all.detect { |app| app.id == params[:id] }
end
end
private
def shopify_enabled?(account)
account.feature_enabled?('shopify_integration') && GlobalConfigService.load('SHOPIFY_CLIENT_ID', nil).present?
end
def notion_enabled?(account)
account.feature_enabled?('notion_integration') && GlobalConfigService.load('NOTION_CLIENT_ID', nil).present?
end
end

View File

@@ -0,0 +1,110 @@
# == Schema Information
#
# Table name: integrations_hooks
#
# id :bigint not null, primary key
# access_token :string
# hook_type :integer default("account")
# settings :jsonb
# status :integer default("enabled")
# created_at :datetime not null
# updated_at :datetime not null
# account_id :integer
# app_id :string
# inbox_id :integer
# reference_id :string
#
class Integrations::Hook < ApplicationRecord
include Reauthorizable
attr_readonly :app_id, :account_id, :inbox_id, :hook_type
before_validation :ensure_hook_type
after_create :trigger_setup_if_crm
# TODO: Remove guard once encryption keys become mandatory (target 3-4 releases out).
encrypts :access_token, deterministic: true if Chatwoot.encryption_configured?
validates :account_id, presence: true
validates :app_id, presence: true
validates :inbox_id, presence: true, if: -> { hook_type == 'inbox' }
validate :validate_settings_json_schema
validate :ensure_feature_enabled
validates :app_id, uniqueness: { scope: [:account_id], unless: -> { app.present? && app.params[:allow_multiple_hooks].present? } }
# TODO: This seems to be only used for slack at the moment
# We can add a validator when storing the integration settings and toggle this in future
enum status: { disabled: 0, enabled: 1 }
belongs_to :account
belongs_to :inbox, optional: true
has_secure_token :access_token
enum hook_type: { account: 0, inbox: 1 }
scope :account_hooks, -> { where(hook_type: 'account') }
scope :inbox_hooks, -> { where(hook_type: 'inbox') }
def app
@app ||= Integrations::App.find(id: app_id)
end
def slack?
app_id == 'slack'
end
def dialogflow?
app_id == 'dialogflow'
end
def notion?
app_id == 'notion'
end
def disable
update(status: 'disabled')
end
def process_event(_event)
# OpenAI integration migrated to Captain::EditorService
# Other integrations (slack, dialogflow, etc.) handled via HookJob
{ error: 'No processor found' }
end
def feature_allowed?
return true if app.blank?
flag = app.params[:feature_flag]
return true unless flag
account.feature_enabled?(flag)
end
private
def ensure_feature_enabled
errors.add(:feature_flag, 'Feature not enabled') unless feature_allowed?
end
def ensure_hook_type
self.hook_type = app.params[:hook_type] if app.present?
end
def validate_settings_json_schema
return if app.blank? || app.params[:settings_json_schema].blank?
errors.add(:settings, ': Invalid settings data') unless JSONSchemer.schema(app.params[:settings_json_schema]).valid?(settings)
end
def trigger_setup_if_crm
# we need setup services to create data prerequisite to functioning of the integration
# in case of Leadsquared, we need to create a custom activity type for capturing conversations and transcripts
# https://apidocs.leadsquared.com/create-new-activity-type-api/
return unless crm_integration?
::Crm::SetupJob.perform_later(id)
end
def crm_integration?
%w[leadsquared].include?(app_id)
end
end