~singpolyma/sgx-jmp

b9c4c2cb0cfdb321c351c826c2b8ed5655088e12 — Stephen Paul Weber 1 year, 1 month ago b1cee59
Notify admin if a user goes over 500 messages in 30 days

Notify only once per day (using expiring redis key).
Can notify MUC or user, always sends directed presence first so it will join MUC
if not joined.
Ignore all messages direct to the component, mostly to throw out live messages
from MUC if we join that to notify.
4 files changed, 52 insertions(+), 14 deletions(-)

M config.dhall.sample
M lib/customer.rb
M lib/customer_usage.rb
M sgx_jmp.rb
M config.dhall.sample => config.dhall.sample +1 -0
@@ 39,6 39,7 @@
	xep0157 = [
		{ var = "support-addresses", value = "xmpp:+14169938000@cheogram.com" }
	],
	notify_admin = "muc_or_user@example.com",
	sip_host = "sip.jmp.chat",
	plans = [
		{

M lib/customer.rb => lib/customer.rb +1 -1
@@ 49,7 49,7 @@ class Customer
	def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan,
	               :currency, :merchant_account, :plan_name
	def_delegators :@sgx, :register!, :registered?
	def_delegator :@usage, :report, :usage_report
	def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage

	def initialize(
		customer_id,

M lib/customer_usage.rb => lib/customer_usage.rb +29 -1
@@ 7,7 7,7 @@ class CustomerUsage
		@customer_id = customer_id
	end

	def report(range)
	def usage_report(range)
		EMPromise.all([
			messages_by_day(range),
			minutes_by_day(range)


@@ 16,6 16,34 @@ class CustomerUsage
		end
	end

	def expire_message_usage
		today = Time.now.utc.to_date
		REDIS.zremrangebylex(
			"jmp_customer_outbound_messages-#{@customer_id}",
			"-",
			# Store message counts per day for 1 year
			"[#{(today << 12).strftime('%Y%m%d')}"
		)
	end

	def incr_message_usage(amount=1)
		today = Time.now.utc.to_date
		EMPromise.all([
			expire_message_usage,
			REDIS.zincrby(
				"jmp_customer_outbound_messages-#{@customer_id}",
				amount,
				today.strftime("%Y%m%d")
			)
		])
	end

	def message_usage(range)
		messages_by_day(range).then do |by_day|
			by_day.values.sum
		end
	end

	def messages_by_day(range)
		EMPromise.all(range.first.downto(range.last).map { |day|
			REDIS.zscore(

M sgx_jmp.rb => sgx_jmp.rb +21 -12
@@ 163,28 163,37 @@ before nil, to: /\Acustomer_/, from: /(\A|@)#{CONFIG[:sgx]}(\/|\Z)/ do |s|
	halt
end

# Ignore messages to component
# Especially if we have the component join MUC for notifications
message(to: /\A#{CONFIG[:component][:jid]}\Z/) {}

message do |m|
	sentry_hub = new_sentry_hub(m, name: "message")
	today = Time.now.utc.to_date
	Customer.for_jid(m.from.stripped).then { |customer|
		sentry_hub.current_scope.set_user(
			id: customer.customer_id,
			jid: m.from.stripped.to_s
		)
		today = Time.now.utc.to_date
		EMPromise.all([
			REDIS.zremrangebylex(
				"jmp_customer_outbound_messages-#{customer.customer_id}",
				"-",
				# Store message counts per day for 1 year
				"[#{(today << 12).strftime('%Y%m%d')}"
			),
			REDIS.zincrby(
				"jmp_customer_outbound_messages-#{customer.customer_id}",
				1,
				today.strftime("%Y%m%d")
			),
			customer,
			customer.incr_message_usage,
			REDIS.exists("jmp_usage_notify-#{customer.customer_id}"),
			customer.stanza_from(m)
		])
	}.then { |(customer, _, already, _)|
		next if already == 1

		customer.message_usage((today..(today - 30))).then do |usage|
			next unless usage > 500

			BLATHER.join(CONFIG[:notify_admin], "sgx-jmp")
			BLATHER.say(
				CONFIG[:notify_admin],
				"#{customer.customer_id} has used #{usage} messages since #{today - 30}"
			)
			REDIS.set("jmp_usage_notify-#{customer.customer_id}", ex: 60 * 60 * 24)
		end
	}.catch { |e| panic(e, sentry_hub) }
end