1#!/usr/bin/ruby
2# frozen_string_literal: true
3
4require "date"
5require "dhall"
6require "em_promise"
7require "pg"
8require "ruby-bandwidth-iris"
9require "set"
10
11require_relative "../lib/blather_notify"
12require_relative "../lib/to_form"
13
14CONFIG = Dhall.load(<<-DHALL).sync
15 (#{ARGV[0]}) : {
16 sgx_jmp: Text,
17 creds: {
18 account: Text,
19 username: Text,
20 password: Text
21 },
22 notify_using: {
23 jid: Text,
24 password: Text,
25 target: Text -> Text,
26 body: Text -> Text -> Text
27 }
28 }
29DHALL
30
31Faraday.default_adapter = :em_synchrony
32BandwidthIris::Client.global_options = {
33 account_id: CONFIG[:creds][:account],
34 username: CONFIG[:creds][:username],
35 password: CONFIG[:creds][:password]
36}
37
38using ToForm
39
40db = PG.connect(dbname: "jmp")
41db.type_map_for_results = PG::BasicTypeMapForResults.new(db)
42db.type_map_for_queries = PG::BasicTypeMapForQueries.new(db)
43
44BlatherNotify.start(
45 CONFIG[:notify_using][:jid],
46 CONFIG[:notify_using][:password]
47)
48
49def format(item)
50 if item.respond_to?(:note) && item.note && item.note.text != ""
51 item.note.text
52 elsif item.respond_to?(:to_xml)
53 item.to_xml
54 else
55 item.inspect
56 end
57end
58
59ported_in_promise = Promise.new
60
61EM.schedule do
62 Fiber.new {
63 begin
64 tns = Set.new
65 page = BandwidthIris::PortIn.list(
66 page: 1,
67 size: 1000,
68 status: :complete
69 )
70 while page
71 page.each_slice(250) do |orders|
72 EMPromise.all(
73 orders.map { |order|
74 EMPromise.resolve(nil).then { order.tns }
75 }
76 ).sync.each { |chunk| tns += chunk.map { |tn| "+1#{tn}" } }
77 end
78 page = page.next
79 end
80 raise "ported_in looks wrong" if tns.length < 250
81
82 ported_in_promise.fulfill(tns)
83 rescue StandardError
84 ported_in_promise.reject($!)
85 end
86 }.resume
87end
88
89class ExpiringCustomer
90 def initialize(customer_id)
91 @customer_id = customer_id
92 end
93
94 def info
95 BlatherNotify.execute(
96 "customer info",
97 { q: @customer_id }.to_form(:submit)
98 ).then do |iq|
99 @sessionid = iq.sessionid
100 unless iq.form.field("customer_id")
101 raise "#{@customer_id} not found"
102 end
103
104 iq
105 end
106 end
107
108 def next
109 raise "Call info first" unless @sessionid
110
111 BlatherNotify.write_with_promise(BlatherNotify.command(
112 "customer info",
113 @sessionid
114 ))
115 end
116
117 def cancel_account
118 raise "Call info first" unless @sessionid
119
120 BlatherNotify.write_with_promise(BlatherNotify.command(
121 "customer info",
122 @sessionid,
123 action: :complete,
124 form: { action: "cancel_account" }.to_form(:submit)
125 ))
126 end
127end
128
129one = Queue.new
130
131ported_in_promise.then { |ported_in|
132 EM::Iterator.new(db.exec(
133 <<-SQL
134 SELECT customer_id, expires_at FROM customer_plans
135 WHERE expires_at < LOCALTIMESTAMP - INTERVAL '1 month'
136 SQL
137 ), 3).each(nil, -> { one << :done }) do |row, iter|
138 customer = ExpiringCustomer.new(row["customer_id"])
139 customer.info.then { |iq|
140 if ported_in.include?(iq.form.field("tel")&.value&.to_s) &&
141 row["expires_at"] > (Date.today << 12)
142 puts "#{row['customer_id']} ported in, skipping"
143 EMPromise.reject(:skip)
144 else
145 customer.next
146 end
147 }.then {
148 customer.cancel_account
149 }.then { |result|
150 puts format(result)
151 iter.next
152 }.catch do |err|
153 next iter.next if err == :skip
154
155 one << (err.is_a?(Exception) ? err : RuntimeError.new(format(err)))
156 end
157 end
158}.catch do |err|
159 one << (err.is_a?(Exception) ? err : RuntimeError.new(format(err)))
160end
161
162result = one.pop
163raise result if result.is_a?(Exception)