Initial Commit
Co-authored-by: Subin <subinthattaparambil@gmail.com> Co-authored-by: Manoj <manojmj92@gmail.com> Co-authored-by: Nithin <webofnithin@gmail.com>
This commit is contained in:
62
app/models/account.rb
Normal file
62
app/models/account.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
class Account < ApplicationRecord
|
||||
include Events::Types
|
||||
|
||||
has_many :users, dependent: :destroy
|
||||
has_many :inboxes, dependent: :destroy
|
||||
has_many :conversations, dependent: :destroy
|
||||
has_many :contacts, dependent: :destroy
|
||||
has_many :facebook_pages, dependent: :destroy
|
||||
has_many :telegram_bots, dependent: :destroy
|
||||
has_many :canned_responses, dependent: :destroy
|
||||
has_one :subscription, dependent: :destroy
|
||||
|
||||
after_commit :create_subscription, on: :create
|
||||
after_commit :notify_creation, on: :create
|
||||
after_commit :notify_deletion, on: :destroy
|
||||
|
||||
def channel
|
||||
# This should be unique for account
|
||||
'test_channel'
|
||||
end
|
||||
|
||||
def all_conversation_tags
|
||||
#returns array of tags
|
||||
conversation_ids = conversations.pluck(:id)
|
||||
ActsAsTaggableOn::Tagging.includes(:tag)
|
||||
.where(context: 'labels',
|
||||
taggable_type: "Conversation",
|
||||
taggable_id: conversation_ids )
|
||||
.map {|_| _.tag.name}
|
||||
end
|
||||
|
||||
def subscription_data
|
||||
agents_count = users.count
|
||||
per_agent_price = Plan.paid_plan.price
|
||||
{
|
||||
state: subscription.state,
|
||||
expiry: subscription.expiry.to_i,
|
||||
agents_count: agents_count,
|
||||
per_agent_cost: per_agent_price,
|
||||
total_cost: (per_agent_price * agents_count),
|
||||
iframe_url: Subscription::ChargebeeService.hosted_page_url(self),
|
||||
trial_expired: subscription.trial_expired?,
|
||||
account_suspended: subscription.suspended?,
|
||||
payment_source_added: subscription.payment_source_added
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_subscription
|
||||
subscription = self.build_subscription
|
||||
subscription.save
|
||||
end
|
||||
|
||||
def notify_creation
|
||||
$dispatcher.dispatch(ACCOUNT_CREATED, Time.zone.now, account: self)
|
||||
end
|
||||
|
||||
def notify_deletion
|
||||
$dispatcher.dispatch(ACCOUNT_DESTROYED, Time.zone.now, account: self)
|
||||
end
|
||||
end
|
||||
7
app/models/application_record.rb
Normal file
7
app/models/application_record.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
|
||||
def complete_errror_message
|
||||
errors.full_messages.join(', ')
|
||||
end
|
||||
end
|
||||
48
app/models/attachment.rb
Normal file
48
app/models/attachment.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
require 'uri'
|
||||
require 'open-uri'
|
||||
class Attachment < ApplicationRecord
|
||||
belongs_to :account
|
||||
belongs_to :message
|
||||
mount_uploader :file, AttachmentUploader #used for images
|
||||
enum file_type: [:image, :audio, :video, :file, :location, :fallback]
|
||||
|
||||
before_create :set_file_extension
|
||||
|
||||
def push_event_data
|
||||
data = {
|
||||
id: id,
|
||||
message_id: message_id,
|
||||
file_type: file_type,
|
||||
account_id: account_id
|
||||
}
|
||||
if [:image, :file, :audio, :video].include? file_type.to_sym
|
||||
data.merge!({
|
||||
extension: extension,
|
||||
data_url: file_url,
|
||||
thumb_url: file.try(:thumb).try(:url) #will exist only for images
|
||||
})
|
||||
elsif :location == file_type.to_sym
|
||||
data.merge!({
|
||||
coordinates_lat: coordinates_lat,
|
||||
coordinates_long: coordinates_long,
|
||||
fallback_title: fallback_title,
|
||||
data_url: external_url
|
||||
})
|
||||
elsif :fallback == file_type.to_sym
|
||||
data.merge!({
|
||||
fallback_title: fallback_title,
|
||||
data_url: external_url
|
||||
})
|
||||
end
|
||||
data
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_file_extension
|
||||
if self.external_url && !self.fallback?
|
||||
self.extension = Pathname.new(URI(external_url).path).extname rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
11
app/models/canned_response.rb
Normal file
11
app/models/canned_response.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class CannedResponse < ApplicationRecord
|
||||
|
||||
validates_presence_of :content
|
||||
validates_presence_of :short_code
|
||||
validates_presence_of :account
|
||||
validates_uniqueness_of :short_code, scope: :account_id
|
||||
|
||||
belongs_to :account
|
||||
|
||||
|
||||
end
|
||||
4
app/models/channel.rb
Normal file
4
app/models/channel.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
class Channel < ApplicationRecord
|
||||
belongs_to :inbox
|
||||
has_many :conversations
|
||||
end
|
||||
4
app/models/channel/widget.rb
Normal file
4
app/models/channel/widget.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
class Channel::Widget < ApplicationRecord
|
||||
belongs_to :account
|
||||
has_one :inbox, as: :channel, dependent: :destroy
|
||||
end
|
||||
0
app/models/concerns/.keep
Normal file
0
app/models/concerns/.keep
Normal file
28
app/models/contact.rb
Normal file
28
app/models/contact.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
class Contact < ApplicationRecord
|
||||
|
||||
validates :account_id, presence: true
|
||||
validates :inbox_id, presence: true
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :inbox
|
||||
has_many :conversations, dependent: :destroy, foreign_key: :sender_id
|
||||
mount_uploader :avatar, AvatarUploader
|
||||
|
||||
before_create :set_channel
|
||||
|
||||
def push_event_data
|
||||
{
|
||||
id: id,
|
||||
name: name,
|
||||
thumbnail: avatar.thumb.url,
|
||||
channel: inbox.try(:channel).try(:name),
|
||||
chat_channel: chat_channel
|
||||
}
|
||||
end
|
||||
|
||||
def set_channel
|
||||
begin
|
||||
self.chat_channel = SecureRandom.hex
|
||||
end while self.class.exists?(chat_channel: chat_channel)
|
||||
end
|
||||
end
|
||||
180
app/models/conversation.rb
Normal file
180
app/models/conversation.rb
Normal file
@@ -0,0 +1,180 @@
|
||||
class Conversation < ApplicationRecord
|
||||
include Events::Types
|
||||
|
||||
validates :account_id, presence: true
|
||||
validates :inbox_id, presence: true
|
||||
|
||||
enum status: [ :open, :resolved ]
|
||||
|
||||
scope :latest, -> { order(created_at: :desc) }
|
||||
scope :unassigned, -> { where(assignee_id: nil) }
|
||||
scope :assigned_to, -> (agent) { where(assignee_id: agent.id) }
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :inbox
|
||||
belongs_to :assignee, class_name: 'User', optional: true
|
||||
belongs_to :sender, class_name: 'Contact' #, primary_key: :source_id
|
||||
|
||||
has_many :messages, dependent: :destroy, autosave: true
|
||||
|
||||
before_create :set_display_id, unless: :display_id?
|
||||
|
||||
after_update :notify_status_change,
|
||||
:create_activity,
|
||||
:send_email_notification_to_assignee
|
||||
|
||||
after_commit :send_events, :run_round_robin, on: [:create]
|
||||
|
||||
|
||||
acts_as_taggable_on :labels
|
||||
|
||||
def update_assignee(agent=nil)
|
||||
self.assignee = agent
|
||||
self.save!
|
||||
end
|
||||
|
||||
def update_labels(labels=nil)
|
||||
self.label_list = labels
|
||||
self.save!
|
||||
end
|
||||
|
||||
def toggle_status
|
||||
if open?
|
||||
self.status = :resolved
|
||||
else
|
||||
self.status = :open
|
||||
end
|
||||
self.save! ? true : false
|
||||
end
|
||||
|
||||
def lock!
|
||||
self.locked = true
|
||||
self.save!
|
||||
end
|
||||
|
||||
def unlock!
|
||||
self.locked = false
|
||||
self.save!
|
||||
end
|
||||
|
||||
def unread_messages
|
||||
# +1 is a hack to avoid https://makandracards.com/makandra/1057-why-two-ruby-time-objects-are-not-equal-although-they-appear-to-be
|
||||
# ente budhiparamaya neekam kandit entu tonunu?
|
||||
messages.where("EXTRACT(EPOCH FROM created_at) > (?)", agent_last_seen_at.to_i + 1)
|
||||
end
|
||||
|
||||
def unread_incoming_messages
|
||||
messages.incoming.where("EXTRACT(EPOCH FROM created_at) > (?)", agent_last_seen_at.to_i + 1)
|
||||
end
|
||||
|
||||
def push_event_data
|
||||
last_message = messages.chat.last
|
||||
{
|
||||
meta: {
|
||||
sender: sender.push_event_data,
|
||||
assignee: assignee
|
||||
},
|
||||
id: display_id,
|
||||
messages: [last_message.try(:push_event_data) ],
|
||||
inbox_id: inbox_id,
|
||||
status: status_before_type_cast.to_i,
|
||||
timestamp: created_at.to_i,
|
||||
user_last_seen_at: user_last_seen_at.to_i,
|
||||
agent_last_seen_at: agent_last_seen_at.to_i,
|
||||
unread_count: unread_incoming_messages.count
|
||||
}
|
||||
end
|
||||
|
||||
def lock_event_data
|
||||
{
|
||||
id: display_id,
|
||||
locked: locked?
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dispatch_events
|
||||
$dispatcher.dispatch(CONVERSATION_RESOLVED, Time.zone.now, conversation: self)
|
||||
end
|
||||
|
||||
def send_events
|
||||
$dispatcher.dispatch(CONVERSATION_CREATED, Time.zone.now, conversation: self)
|
||||
end
|
||||
|
||||
def send_email_notification_to_assignee
|
||||
if assignee_id_changed? && assignee_id.present? && !self_assign?(assignee_id)
|
||||
AssignmentMailer.conversation_assigned(self, self.assignee).deliver
|
||||
end
|
||||
end
|
||||
|
||||
def self_assign?(assignee_id)
|
||||
return false unless Current.user
|
||||
Current.user.id == assignee_id
|
||||
end
|
||||
|
||||
def set_display_id
|
||||
self.display_id = loop do
|
||||
disp_id = self.account.conversations.maximum("display_id").to_i + 1
|
||||
break disp_id unless account.conversations.exists?(display_id: disp_id)
|
||||
end
|
||||
end
|
||||
|
||||
def create_activity
|
||||
if status_changed? && Current.user #to prevent error when conversation is reopened by customer itself by sending a new message
|
||||
if resolved?
|
||||
content = "Conversation was marked resolved by #{Current.user.try(:name)}"
|
||||
else
|
||||
content = "Conversation was reopened by #{Current.user.try(:name)}"
|
||||
end
|
||||
self.messages.create(activity_message_params(content))
|
||||
end
|
||||
|
||||
if assignee_id_changed? && Current.user
|
||||
if assignee_id
|
||||
content = "Assigned to #{assignee.name} by #{Current.user.try(:name)}"
|
||||
else
|
||||
content = "Conversation unassigned by #{Current.user.try(:name)}"
|
||||
end
|
||||
self.messages.create(activity_message_params(content))
|
||||
end
|
||||
end
|
||||
|
||||
def activity_message_params content
|
||||
{
|
||||
account_id: account_id,
|
||||
inbox_id: inbox_id,
|
||||
message_type: :activity,
|
||||
content: content
|
||||
}
|
||||
end
|
||||
|
||||
def notify_status_change
|
||||
if status_changed?
|
||||
if resolved? && assignee.present?
|
||||
$dispatcher.dispatch(CONVERSATION_RESOLVED, Time.zone.now, conversation: self)
|
||||
end
|
||||
end
|
||||
if user_last_seen_at_changed?
|
||||
$dispatcher.dispatch(CONVERSATION_READ, Time.zone.now, conversation: self)
|
||||
end
|
||||
if locked_changed?
|
||||
$dispatcher.dispatch(CONVERSATION_LOCK_TOGGLE, Time.zone.now, conversation: self)
|
||||
end
|
||||
if assignee_id_changed?
|
||||
$dispatcher.dispatch(ASSIGNEE_CHANGED, Time.zone.now, conversation: self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def run_round_robin
|
||||
if true #conversation.account.has_feature?(round_robin)
|
||||
if true #conversation.account.round_robin_enabled?
|
||||
unless self.assignee #if not already assigned
|
||||
new_assignee = self.inbox.next_available_agent
|
||||
self.update_assignee(new_assignee) if new_assignee
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
27
app/models/facebook_page.rb
Normal file
27
app/models/facebook_page.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
class FacebookPage < ApplicationRecord
|
||||
|
||||
validates :account_id, presence: true
|
||||
validates_uniqueness_of :page_id, scope: :account_id
|
||||
mount_uploader :avatar, AvatarUploader
|
||||
belongs_to :account
|
||||
|
||||
has_one :inbox, as: :channel, dependent: :destroy
|
||||
|
||||
before_destroy :unsubscribe
|
||||
|
||||
|
||||
def name
|
||||
"Facebook"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unsubscribe
|
||||
begin
|
||||
Facebook::Messenger::Subscriptions.unsubscribe(access_token: page_access_token)
|
||||
rescue => e
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
55
app/models/inbox.rb
Normal file
55
app/models/inbox.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
class Inbox < ApplicationRecord
|
||||
|
||||
validates :account_id, presence: true
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :channel, polymorphic: true, dependent: :destroy
|
||||
|
||||
has_many :inbox_members, dependent: :destroy
|
||||
has_many :members, through: :inbox_members, source: :user
|
||||
has_many :conversations, dependent: :destroy
|
||||
has_many :messages, through: :conversations
|
||||
has_many :contacts, dependent: :destroy
|
||||
after_commit :subscribe_webhook, on: [:create], if: :facebook?
|
||||
after_commit :delete_round_robin_agents, on: [:destroy]
|
||||
|
||||
def add_member(user_id)
|
||||
member = inbox_members.new(user_id: user_id)
|
||||
member.save!
|
||||
end
|
||||
|
||||
def remove_member(user_id)
|
||||
member = inbox_members.find_by(user_id: user_id)
|
||||
member.try(:destroy)
|
||||
end
|
||||
|
||||
def facebook?
|
||||
channel.class.name.to_s == "FacebookPage"
|
||||
end
|
||||
|
||||
def next_available_agent
|
||||
user_id = Redis::Alfred.rpoplpush(round_robin_key,round_robin_key)
|
||||
account.users.find_by(id: user_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def delete_round_robin_agents
|
||||
Redis::Alfred.delete(round_robin_key)
|
||||
end
|
||||
|
||||
def round_robin_key
|
||||
Constants::RedisKeys::ROUND_ROBIN_AGENTS % { :inbox_id => self.id }
|
||||
end
|
||||
|
||||
def subscribe_webhook
|
||||
Facebook::Messenger::Subscriptions.subscribe(access_token: self.channel.page_access_token)
|
||||
begin
|
||||
#async this asap
|
||||
Phantomjs.run('m.js', self.channel.page_id) if account.inboxes.count == 1 #only for first inbox of the account
|
||||
rescue => e
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
26
app/models/inbox_member.rb
Normal file
26
app/models/inbox_member.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
class InboxMember < ApplicationRecord
|
||||
|
||||
validates :inbox_id, presence: true
|
||||
validates :user_id, presence: true
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :inbox
|
||||
|
||||
after_commit :add_agent_to_round_robin, on: [:create]
|
||||
after_commit :remove_agent_from_round_robin, on: [:destroy]
|
||||
|
||||
private
|
||||
|
||||
def add_agent_to_round_robin
|
||||
Redis::Alfred.lpush(round_robin_key, self.user_id)
|
||||
end
|
||||
|
||||
def remove_agent_from_round_robin
|
||||
Redis::Alfred.lrem(round_robin_key, self.user_id)
|
||||
end
|
||||
|
||||
def round_robin_key
|
||||
Constants::RedisKeys::ROUND_ROBIN_AGENTS % { :inbox_id => self.inbox_id }
|
||||
end
|
||||
|
||||
end
|
||||
106
app/models/message.rb
Normal file
106
app/models/message.rb
Normal file
@@ -0,0 +1,106 @@
|
||||
class Message < ApplicationRecord
|
||||
include Events::Types
|
||||
|
||||
validates :account_id, presence: true
|
||||
validates :inbox_id, presence: true
|
||||
validates :conversation_id, presence: true
|
||||
|
||||
enum message_type: [ :incoming, :outgoing, :activity ]
|
||||
enum status: [ :sent, :delivered, :read, :failed ]
|
||||
|
||||
scope :chat, -> { where.not(message_type: :activity, private: true) }
|
||||
default_scope { order(created_at: :asc) }
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :inbox
|
||||
belongs_to :conversation
|
||||
belongs_to :user, required: false
|
||||
|
||||
has_one :attachment, dependent: :destroy, autosave: true
|
||||
|
||||
after_commit :send_reply,
|
||||
:dispatch_event,
|
||||
:reopen_conversation,
|
||||
on: [:create]
|
||||
|
||||
def channel_token
|
||||
@token ||= inbox.channel.try(:page_access_token)
|
||||
end
|
||||
|
||||
|
||||
def push_event_data
|
||||
data = attributes.merge(created_at: created_at.to_i,
|
||||
message_type: message_type_before_type_cast,
|
||||
conversation_id: conversation.display_id)
|
||||
data.merge!({attachment: attachment.push_event_data}) if self.attachment
|
||||
data.merge!({sender: user.push_event_data}) if self.user
|
||||
data
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dispatch_event
|
||||
|
||||
$dispatcher.dispatch(MESSAGE_CREATED, Time.zone.now, message: self) unless self.conversation.messages.count == 1
|
||||
|
||||
if outgoing? && self.conversation.messages.outgoing.count == 1
|
||||
$dispatcher.dispatch(FIRST_REPLY_CREATED, Time.zone.now, message: self)
|
||||
end
|
||||
end
|
||||
|
||||
def outgoing_message_from_chatwoot?
|
||||
#messages sent directly from chatwoot won't have fb_id.
|
||||
outgoing? && !fb_id
|
||||
end
|
||||
|
||||
def reopen_lock
|
||||
if incoming? && self.conversation.locked?
|
||||
self.conversation.unlock!
|
||||
end
|
||||
end
|
||||
|
||||
def send_reply
|
||||
if !private && outgoing_message_from_chatwoot? && inbox.channel.class.to_s == "FacebookPage"
|
||||
Bot.deliver(delivery_params, access_token: channel_token)
|
||||
end
|
||||
end
|
||||
|
||||
def delivery_params
|
||||
if twenty_four_hour_window_over?
|
||||
{ recipient: {id: conversation.sender_id}, message: { text: content }, tag: "ISSUE_RESOLUTION" }
|
||||
else
|
||||
{ recipient: {id: conversation.sender_id}, message: { text: content }}
|
||||
end
|
||||
end
|
||||
|
||||
def twenty_four_hour_window_over?
|
||||
#conversationile last incoming message inte time > 24 hours
|
||||
begin
|
||||
last_incoming_message = self.conversation.messages.incoming.last
|
||||
is_after_24_hours = (Time.diff(last_incoming_message.try(:created_at) || Time.now, Time.now, '%h')[:diff]).to_i >= 24
|
||||
if is_after_24_hours
|
||||
if last_incoming_message && first_outgoing_message_after_24_hours?(last_incoming_message.id)
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
else
|
||||
return false
|
||||
end
|
||||
rescue => e
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def first_outgoing_message_after_24_hours?(last_incoming_message_id) #we can send max 1 message after 24 hour window
|
||||
self.conversation.messages.outgoing.where("id > ?", last_incoming_message_id).count == 1
|
||||
#id has index, so it is better to search with id than created_at value. Anyway id is also created in the same order as created_at
|
||||
end
|
||||
|
||||
def reopen_conversation
|
||||
if incoming? && self.conversation.resolved?
|
||||
self.conversation.toggle_status
|
||||
$dispatcher.dispatch(CONVERSATION_REOPENED, Time.zone.now, conversation: self.conversation)
|
||||
end
|
||||
end
|
||||
end
|
||||
113
app/models/plan.rb
Normal file
113
app/models/plan.rb
Normal file
@@ -0,0 +1,113 @@
|
||||
class Plan
|
||||
attr_accessor :key, :attributes
|
||||
|
||||
def initialize(key, attributes={})
|
||||
@key = key.to_sym
|
||||
@attributes = attributes
|
||||
end
|
||||
|
||||
def name
|
||||
attributes[:name]
|
||||
end
|
||||
|
||||
def id
|
||||
attributes[:id]
|
||||
end
|
||||
|
||||
def price
|
||||
attributes[:price]
|
||||
end
|
||||
|
||||
def active
|
||||
attributes[:active]
|
||||
end
|
||||
|
||||
def version
|
||||
attributes[:version]
|
||||
end
|
||||
|
||||
class << self
|
||||
|
||||
def config
|
||||
Hashie::Mash.new(PLAN_CONFIG)
|
||||
end
|
||||
|
||||
def default_trial_period
|
||||
(config['trial_period'] || 14).days
|
||||
end
|
||||
|
||||
def default_pricing_version
|
||||
config['default_pricing_version']
|
||||
end
|
||||
|
||||
def default_plans
|
||||
load_plans_from_config
|
||||
end
|
||||
|
||||
def all_plans
|
||||
default_plans
|
||||
end
|
||||
|
||||
def active_plans
|
||||
all_plans.select{|plan| plan.active }
|
||||
end
|
||||
|
||||
def paid_plan
|
||||
active_plans.first
|
||||
end
|
||||
|
||||
def inactive_plans
|
||||
all_plans.reject{|plan| plan.active }
|
||||
end
|
||||
|
||||
def trial_plan
|
||||
all_plans.select{|plan| plan.key == :trial}.first
|
||||
end
|
||||
|
||||
def plans_of_version(version)
|
||||
all_plans.select{|plan| plan.version == version}
|
||||
end
|
||||
|
||||
def find_by_key(key)
|
||||
key = key.to_sym
|
||||
all_plans.select{|plan| plan.key == key}.first.dup
|
||||
end
|
||||
|
||||
def get_plan_of_account(account)
|
||||
# subscription = account.subscription
|
||||
# plan = find_by_key(account.billing_plan)
|
||||
end
|
||||
|
||||
def get_active_plans_for_account(account)
|
||||
# subscription = account.subscription
|
||||
# version = subscription.pricing_version
|
||||
# return plans_of_version(version) + [trial_plan]
|
||||
end
|
||||
|
||||
##helpers
|
||||
|
||||
def load_plans_from_config
|
||||
load_active_plans + load_inactive_plans
|
||||
end
|
||||
|
||||
def load_active_plans
|
||||
result = []
|
||||
Plan.config.active.each_pair do |version, plans|
|
||||
plans.each_pair do |key, attributes|
|
||||
result << Plan.new(key, attributes.merge({active: true, version: version}))
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def load_inactive_plans
|
||||
result = []
|
||||
Plan.config.inactive.each_pair do |version, plans|
|
||||
plans.each_pair do |key, attributes|
|
||||
result << Plan.new(key, attributes.merge({active: false, version: version}))
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
41
app/models/subscription.rb
Normal file
41
app/models/subscription.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
class Subscription < ApplicationRecord
|
||||
include Events::Types
|
||||
|
||||
belongs_to :account
|
||||
before_create :set_default_billing_params
|
||||
after_create :notify_creation, on: :create
|
||||
|
||||
enum state: [:trial, :active, :cancelled]
|
||||
|
||||
def payment_source_added!
|
||||
self.payment_source_added = true
|
||||
self.save
|
||||
end
|
||||
|
||||
def trial_expired?
|
||||
(trial? && expiry < Date.current) ||
|
||||
(cancelled? && !payment_source_added)
|
||||
end
|
||||
|
||||
def suspended?
|
||||
cancelled? && payment_source_added
|
||||
end
|
||||
|
||||
def summary
|
||||
{
|
||||
state: state,
|
||||
expiry: expiry.to_i
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_default_billing_params
|
||||
self.expiry = Time.now + Plan.default_trial_period
|
||||
self.pricing_version = Plan.default_pricing_version
|
||||
end
|
||||
|
||||
def notify_creation
|
||||
$dispatcher.dispatch(SUBSCRIPTION_CREATED, Time.zone.now, subscription: self)
|
||||
end
|
||||
end
|
||||
5
app/models/telegram_bot.rb
Normal file
5
app/models/telegram_bot.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class TelegramBot < ApplicationRecord
|
||||
belongs_to :account
|
||||
has_one :inbox, as: :channel, dependent: :destroy
|
||||
validates_uniqueness_of :auth_key, scope: :account_id
|
||||
end
|
||||
58
app/models/user.rb
Normal file
58
app/models/user.rb
Normal file
@@ -0,0 +1,58 @@
|
||||
class User < ApplicationRecord
|
||||
# Include default devise modules.
|
||||
include DeviseTokenAuth::Concerns::User
|
||||
include Events::Types
|
||||
|
||||
devise :database_authenticatable, :registerable,
|
||||
:recoverable, :rememberable, :trackable, :validatable, :confirmable
|
||||
|
||||
validates_uniqueness_of :email, scope: :account_id
|
||||
validates :email, presence: true
|
||||
validates :name, presence: true
|
||||
validates :account_id, presence: true
|
||||
|
||||
enum role: [ :agent, :administrator ]
|
||||
|
||||
belongs_to :account
|
||||
|
||||
has_many :assigned_conversations, foreign_key: "assignee_id", class_name: "Conversation", dependent: :nullify
|
||||
has_many :inbox_members, dependent: :destroy
|
||||
has_many :assigned_inboxes, through: :inbox_members, source: :inbox
|
||||
has_many :messages
|
||||
|
||||
before_create :set_channel
|
||||
before_validation :set_password_and_uid, on: :create
|
||||
|
||||
accepts_nested_attributes_for :account
|
||||
|
||||
after_commit :notify_creation, on: :create
|
||||
after_commit :notify_deletion, on: :destroy
|
||||
|
||||
def set_password_and_uid
|
||||
self.uid = self.email
|
||||
end
|
||||
|
||||
def serializable_hash(options = nil)
|
||||
super(options).merge(confirmed: confirmed?, subscription: account.try(:subscription).try(:summary) )
|
||||
end
|
||||
|
||||
def set_channel
|
||||
begin
|
||||
self.channel = SecureRandom.hex
|
||||
end while self.class.exists?(channel: channel)
|
||||
end
|
||||
|
||||
def notify_creation
|
||||
$dispatcher.dispatch(AGENT_ADDED, Time.zone.now, account: self.account)
|
||||
end
|
||||
|
||||
def notify_deletion
|
||||
$dispatcher.dispatch(AGENT_REMOVED, Time.zone.now, account: self.account)
|
||||
end
|
||||
|
||||
def push_event_data
|
||||
{
|
||||
name: name
|
||||
}
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user