# frozen_string_literal: true require "lazy_object" require "value_semantics/monkey_patched" require_relative "bandwidth_tn_repo" require_relative "customer" require_relative "polyfill" class CustomerRepo class NotFound < RuntimeError; end value_semantics do set_user Proc, default: ->(**) {}, coerce: :to_proc.to_proc redis Anything(), default: LazyObject.new { REDIS } db Anything(), default: LazyObject.new { DB } braintree Anything(), default: LazyObject.new { BRAINTREE } sgx_repo Anything(), default: TrivialBackendSgxRepo.new bandwidth_tn_repo Anything(), default: BandwidthTnRepo.new end module QueryKey def self.for(s) case s when Blather::JID, ProxiedJID JID.for(s) when /\Axmpp:(.*)/ JID.for($1) when /\A(?:tel:)?(\+\d+)\Z/ Tel.new($1) else ID.new(s) end end ID = Struct.new(:customer_id) do def keys(redis, tel: nil) redis.get("jmp_customer_jid-#{customer_id}").then do |jid| raise NotFound, "No jid" unless jid [customer_id, Blather::JID.new(jid), *tel] end end end JID = Struct.new(:jid) do def self.for(jid) if jid.to_s =~ /\Acustomer_(.+)@#{CONFIG[:component][:jid]}\Z/ ID.new($1) else new(jid) end end def keys(redis, tel: nil) redis.get("jmp_customer_id-#{jid}").then do |customer_id| raise NotFound, "No customer" unless customer_id [customer_id, Blather::JID.new(jid), *tel] end end end Tel = Struct.new(:tel) do def keys(redis) redis.get("catapult_jid-#{tel}").then do |jid| raise NotFound, "No jid" unless jid JID.for(jid).keys(redis, tel: tel) end end end end def find(customer_id) set_user.call(customer_id: customer_id) QueryKey::ID.new(customer_id).keys(redis).then { |k| find_inner(*k) } end def find_by_jid(jid) set_user.call(jid: jid) QueryKey::JID.for(jid).keys(redis).then { |k| find_inner(*k) } end def find_by_tel(tel) set_user.call(tel: tel) QueryKey::Tel.new(tel).keys(redis).then { |k| find_inner(*k) } end def find_by_format(s) set_user.call(q: s) QueryKey.for(s).keys(redis).then { |k| find_inner(*k) } end def create(jid) @braintree.customer.create.then do |result| raise "Braintree customer create failed" unless result.success? cid = result.customer.id @redis.msetnx( "jmp_customer_id-#{jid}", cid, "jmp_customer_jid-#{cid}", jid ).then do |redis_result| raise "Saving new customer to redis failed" unless redis_result == 1 Customer.new(cid, Blather::JID.new(jid), sgx: new_sgx(cid)) end end end def disconnect_tel(customer) tel = customer.registered?.phone @bandwidth_tn_repo.disconnect(tel, customer.customer_id) end def put_lidb_name(customer, lidb_name) @bandwidth_tn_repo.put_lidb_name(customer.registered?.phone, lidb_name) end def put_transcription_enabled(customer, enabled) @sgx_repo.put_transcription_enabled(customer.customer_id, enabled) end def put_fwd(customer, customer_fwd) tel = customer.registered?.phone @sgx_repo.put_fwd(customer.customer_id, tel, customer_fwd) end def put_monthly_overage_limit(customer, limit) k = "jmp_customer_monthly_overage_limit-#{customer.customer_id}" @redis.set(k, limit) end def change_jid(customer, new_jid) @redis.set("jmp_customer_id-#{new_jid}", customer.customer_id).then { @redis.set("jmp_customer_jid-#{customer.customer_id}", new_jid) }.then { SwapDefaultFwd.new.do(self, customer, new_jid) }.then do @redis.del("jmp_customer_id-#{customer.jid}") end end # I've put this here to hide the lines from rubocop # After we sort out where call routing should live, this whole process will # no longer be necessary class SwapDefaultFwd def do(repo, customer, new_jid) unless customer.fwd.uri == "xmpp:#{customer.jid}" return EMPromise.resolve(nil) end repo.put_fwd(customer, customer.fwd.with(uri: "xmpp:#{new_jid}")) end end protected def new_sgx(customer_id) TrivialBackendSgxRepo.new.get(customer_id).with(registered?: false) end def mget(*keys) @redis.mget(*keys).then { |values| Hash[keys.zip(values.map(&:to_i))] } end def fetch_redis(customer_id) mget( "jmp_customer_auto_top_up_amount-#{customer_id}", "jmp_customer_monthly_overage_limit-#{customer_id}" ).then { |r| r.transform_keys { |k| k.match(/^jmp_customer_([^-]+)/)[1].to_sym } } end SQL = <<~SQL SELECT COALESCE(balance,0) AS balance, plan_name, expires_at, parent_customer_id FROM customer_plans LEFT JOIN balances USING (customer_id) WHERE customer_id=$1 LIMIT 1 SQL def tndetails(sgx) return {} unless sgx.registered? LazyObject.new { @bandwidth_tn_repo.find(sgx.registered?.phone) || {} } end def find_inner(cid, jid, tel=nil) set_user.call(customer_id: cid, jid: jid) EMPromise.all([ @sgx_repo.get(cid, tel: tel).then { |sgx| { sgx: sgx } }, @db.query_one(SQL, cid, default: {}), fetch_redis(cid) ]).then { |all| all.reduce(&:merge) }.then do |data| Customer.extract(cid, jid, tndetails: tndetails(data[:sgx]), **data) end end end