~singpolyma/sgx-jmp

b8aa8ed3d1fa81afd574803cd95a5a9391a8c7a3 โ€” Stephen Paul Weber 2 days ago c3579cc + 303e839 master
Merge branch 'feature_flags'

* feature_flags:
  Put CDRs behind a feature flag
  Store feature flags on user for limiting commands, etc
  Added a New Command to Display CDRs to Customers
M .rubocop.yml => .rubocop.yml +1 -1
@@ 24,7 24,7 @@ Metrics/AbcSize:
    - test/*

Metrics/ParameterLists:
  Max: 6
  Max: 7

Naming/MethodParameterName:
  AllowNamesEndingInNumbers: false

A forms/customer_cdr.rb => forms/customer_cdr.rb +13 -0
@@ 0,0 1,13 @@
result!
title "Call History"

table(
	@cdrs,
	start: "Start",
	direction: "Direction",
	tel: "Number",
	disposition: "Status",
	duration: "Duration",
	formatted_rate: "Rate",
	formatted_charge: "Charge"
)

M lib/cdr.rb => lib/cdr.rb +19 -9
@@ 30,7 30,25 @@ class CDR
		billsec Integer
		disposition Disposition
		tel(/\A\+\d+\Z/)
		direction Either(:inbound, :outbound)
		direction Either(:inbound, :outbound), coerce: :to_sym.to_proc
		rate Either(nil, BigDecimal), default: nil
		charge Either(nil, BigDecimal), default: nil
	end

	def formatted_rate
		"$%.4f" % rate
	end

	def formatted_charge
		"$%.4f" % charge
	end

	def duration
		"%02d:%02d:%02d" % [
			billsec / (60 * 60),
			billsec % (60 * 60) / 60,
			billsec % 60
		]
	end

	def self.for(event, **kwargs)


@@ 61,12 79,4 @@ class CDR
			direction: :outbound
		)
	end

	def save
		columns, values = to_h.to_a.transpose
		DB.query_defer(<<~SQL, values)
			INSERT INTO cdr (#{columns.join(',')})
			VALUES ($1, $2, $3, $4, $5, $6, $7)
		SQL
	end
end

A lib/cdr_repo.rb => lib/cdr_repo.rb +47 -0
@@ 0,0 1,47 @@
# frozen_string_literal: true

require_relative "cdr"

class CDRRepo
	def initialize(db: DB)
		@db = db
	end

	def put(cdr)
		data = cdr.to_h
		data.delete(:rate)
		data.delete(:charge)
		columns, values = data.to_a.transpose
		DB.query_defer(<<~SQL, values)
			INSERT INTO cdr (#{columns.join(',')})
			VALUES ($1, $2, $3, $4, $5, $6, $7)
		SQL
	end

	def find_range(customer, range)
		customer_id = customer.customer_id
		cdrs = @db.query_defer(CDR_SQL, [customer_id, range.first, range.last])

		cdrs.then do |rows|
			rows.map { |row|
				CDR.new(**row.transform_keys(&:to_sym))
			}
		end
	end

	CDR_SQL = <<~SQL
		SELECT
			cdr_id,
			customer_id,
			start,
			billsec,
			disposition,
			tel,
			direction,
			rate,
			charge
		FROM cdr_with_charge
		WHERE customer_id = $1 AND start >= $2 AND DATE_TRUNC('day', start) <= $3
		ORDER BY start DESC
	SQL
end

M lib/command_list.rb => lib/command_list.rb +1 -1
@@ 18,7 18,7 @@ class CommandList
		args = {
			from_jid: from_jid, customer: customer,
			tel: customer&.registered? ? customer&.registered?&.phone : nil,
			fwd: customer&.fwd,
			fwd: customer&.fwd, feature_flags: customer&.feature_flags || [],
			payment_methods: []
		}
		return EMPromise.resolve(args) unless customer&.plan_name

M lib/customer.rb => lib/customer.rb +4 -2
@@ 18,7 18,7 @@ require_relative "./trivial_backend_sgx_repo"
class Customer
	extend Forwardable

	attr_reader :customer_id, :balance, :jid, :tndetails
	attr_reader :customer_id, :balance, :jid, :tndetails, :feature_flags
	alias billing_customer_id customer_id

	def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan,


@@ 43,7 43,7 @@ class Customer
		klass.new(
			customer_id, jid,
			plan: CustomerPlan.extract(customer_id, **kwargs),
			**kwargs.slice(:balance, :sgx, :tndetails, *keys)
			**kwargs.slice(:balance, :sgx, :tndetails, :feature_flags, *keys)
		)
	end



@@ 53,6 53,7 @@ class Customer
		plan: CustomerPlan.new(customer_id),
		balance: BigDecimal(0),
		tndetails: {},
		feature_flags: [],
		sgx: TrivialBackendSgxRepo.new.get(customer_id)
	)
		@plan = plan


@@ 62,6 63,7 @@ class Customer
		@jid = jid
		@balance = balance
		@tndetails = tndetails
		@feature_flags = feature_flags
		@sgx = sgx
	end


M lib/customer_repo.rb => lib/customer_repo.rb +8 -4
@@ 164,11 164,15 @@ protected
	end

	def fetch_redis(customer_id)
		mget(
			"jmp_customer_auto_top_up_amount-#{customer_id}",
			"jmp_customer_monthly_overage_limit-#{customer_id}"
		).then { |r|
		EMPromise.all([
			mget(
				"jmp_customer_auto_top_up_amount-#{customer_id}",
				"jmp_customer_monthly_overage_limit-#{customer_id}"
			),
			@redis.smembers("jmp_customer_feature_flags-#{customer_id}")
		]).then { |r, flags|
			r.transform_keys { |k| k.match(/^jmp_customer_([^-]+)/)[1].to_sym }
			 .merge(feature_flags: flags.map(&:to_sym))
		}
	end


M sgx_jmp.rb => sgx_jmp.rb +16 -0
@@ 527,6 527,22 @@ Command.new(
}.register(self).then(&CommandList.method(:register))

Command.new(
	"cdrs",
	"๐Ÿ“ฒ Show Call Logs",
	list_for: ->(feature_flags:, **) { feature_flags.include?(:cdrs) }
) {
	report_for = ((Date.today << 1)..Date.today)

	Command.customer.then { |customer|
		CDRRepo.new.find_range(customer, report_for)
	}.then do |cdrs|
		Command.finish do |reply|
			reply.command << FormTemplate.render("customer_cdr", cdrs: cdrs)
		end
	end
}.register(self).then(&CommandList.method(:register))

Command.new(
	"transactions",
	"๐Ÿงพ Show Transactions",
	list_for: ->(customer:, **) { !!customer&.currency }

M test/test_helper.rb => test/test_helper.rb +4 -0
@@ 258,6 258,10 @@ class FakeRedis
		@values[key]&.size || 0
	end

	def smembers(key)
		@values[key]&.to_a || []
	end

	def expire(_, _); end

	def exists(*keys)

M web.rb => web.rb +7 -2
@@ 10,6 10,7 @@ require "sentry-ruby"

require_relative "lib/call_attempt_repo"
require_relative "lib/cdr"
require_relative "lib/cdr_repo"
require_relative "lib/oob"
require_relative "lib/rev_ai"
require_relative "lib/roda_capture"


@@ 121,6 122,10 @@ class Web < Roda
		opts[:call_attempt_repo] || CallAttemptRepo.new
	end

	def cdr_repo
		opts[:cdr_repo] || CDRRepo.new
	end

	def rev_ai
		RevAi.new(logger: log.child(loggable_params))
	end


@@ 200,7 205,7 @@ class Web < Roda
						end

						customer_repo.find_by_tel(params["to"]).then do |customer|
							CDR.for_inbound(customer.customer_id, params).save
							cdr_repo.put(CDR.for_inbound(customer.customer_id, params))
						end
					end
					"OK"


@@ 359,7 364,7 @@ class Web < Roda
					log.info "#{params['eventType']} #{params['callId']}", loggable_params
					if params["eventType"] == "disconnect"
						call_attempt_repo.ending_call(c, params["callId"])
						CDR.for_outbound(params).save.catch(&method(:log_error))
						cdr_repo.put(CDR.for_outbound(params)).catch(&method(:log_error))
					end
					"OK"
				end