# 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 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 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) redis.get("jmp_customer_jid-#{customer_id}").then do |jid| raise NotFound, "No jid" unless jid [customer_id, jid] 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) redis.get("jmp_customer_id-#{jid}").then do |customer_id| raise NotFound, "No customer" unless customer_id [customer_id, @jid] 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) end end end end def find(customer_id) QueryKey::ID.new(customer_id).keys(redis).then { |k| find_inner(*k) } end def find_by_jid(jid) QueryKey::JID.for(jid).keys(redis).then { |k| find_inner(*k) } end def find_by_tel(tel) QueryKey::Tel.new(tel).keys(redis).then { |k| find_inner(*k) } end def find_by_format(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 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 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 FROM customer_plans LEFT JOIN balances USING (customer_id) WHERE customer_id=$1 LIMIT 1 SQL def fetch_sql(customer_id) @db.query_defer(SQL, [customer_id]).then do |rows| rows.first&.transform_keys(&:to_sym) || {} end end def fetch_all(customer_id) EMPromise.all([ @sgx_repo.get(customer_id), fetch_sql(customer_id), fetch_redis(customer_id) ]).then { |sgx, sql, redis| [sgx, sql.merge(redis)] } end def tndetails(sgx) return unless sgx.registered? LazyObject.new { @bandwidth_tn_repo.find(sgx.registered?.phone) } end def find_inner(customer_id, jid) fetch_all(customer_id).then do |(sgx, data)| Customer.new( customer_id, Blather::JID.new(jid), sgx: sgx, tndetails: tndetails(sgx), plan: CustomerPlan.for( customer_id, **data.reject { |(k, _)| k == :balance } ), **data.slice(:balance) ) end end end