Refactor Snikket code to use a CustomerInstance object

Stephen Paul Weber created

So that we have a model we can save/load and not just the XML element

Change summary

forms/snikket_launched.rb |  4 +-
lib/registration.rb       | 40 ++++++++++++-------
lib/snikket.rb            | 82 ++++++++++++++++++++++++++++++++--------
schemas                   |  2 
sgx_jmp.rb                |  7 +--
test/test_registration.rb | 15 +++++++
test/test_snikket.rb      | 18 +++++++--
7 files changed, 124 insertions(+), 44 deletions(-)

Detailed changes

forms/snikket_launched.rb 🔗

@@ -8,12 +8,12 @@ field(
 	type: "text-single",
 	var: "instance-id",
 	label: "Instance ID",
-	value: @launched.instance_id
+	value: @instance.instance_id
 )
 
 field(
 	type: "text-single",
 	var: "bootstrap-uri",
 	label: "Admin Invite",
-	value: @launched.bootstrap_uri(@domain)
+	value: @instance.bootstrap_uri
 )

lib/registration.rb 🔗

@@ -18,7 +18,7 @@ require_relative "./welcome_message"
 class Registration
 	def self.for(customer, tel_selections)
 		if (reg = customer.registered?)
-			Registered.new(reg.phone)
+			Registered.for(customer, reg.phone)
 		else
 			tel_selections[customer.jid].then(&:choose_tel).then do |tel|
 				BandwidthTnReservationRepo.new.ensure(customer, tel)
@@ -28,6 +28,15 @@ class Registration
 	end
 
 	class Registered
+		def self.for(customer, tel)
+			jid = ProxiedJID.new(customer.jid).unproxied
+			if jid.domain == CONFIG[:onboarding_domain]
+				FinishOnboarding.for(customer, tel)
+			else
+				new(tel)
+			end
+		end
+
 		def initialize(tel)
 			@tel = tel
 		end
@@ -523,14 +532,15 @@ class Registration
 		def self.for(customer, tel)
 			jid = ProxiedJID.new(customer.jid).unproxied
 			if jid.domain == CONFIG[:onboarding_domain]
-				Snikket.new(tel)
+				Snikket.new(customer, tel)
 			else
 				NotOnboarding.new(customer, tel)
 			end
 		end
 
 		class Snikket
-			def initialize(tel, error: nil)
+			def initialize(customer, tel, error: nil)
+				@customer = customer
 				@tel = tel
 				@error = error
 			end
@@ -560,31 +570,31 @@ class Registration
 
 			def launch(domain)
 				IQ_MANAGER.write(::Snikket::Launch.new(
-					nil, CONFIG[:snikket_hosting_api],
-					domain: domain
+					nil, CONFIG[:snikket_hosting_api], domain: domain
 				)).then { |launched|
-					GetInvite.for(domain, launched).then(&:write)
+					GetInvite.for(
+						::Snikket::CustomerInstance.for(@customer, domain, launched)
+					).then(&:write)
 				}.catch { |e|
 					next EMPromise.reject(e) unless e.respond_to?(:text)
 
-					Snikket.new(@tel, error: e.text).write
+					Snikket.new(@customer, @tel, error: e.text).write
 				}
 			end
 
 			class GetInvite
-				def self.for(domain, launched)
-					launched.fetch_invite(domain).then do |xmpp_uri|
+				def self.for(instance)
+					instance.fetch_invite.then do |xmpp_uri|
 						if xmpp_uri
 							GoToInvite.new(xmpp_uri)
 						else
-							new(domain, launched)
+							new(instance)
 						end
 					end
 				end
 
-				def initialize(domain, launched)
-					@domain = domain
-					@launched = launched
+				def initialize(instance)
+					@instance = instance
 				end
 
 				def write
@@ -592,10 +602,10 @@ class Registration
 						reply.allowed_actions = [:next]
 						reply.note_type = :info
 						reply.note_text =
-							"Your instance #{@domain} is starting up. " \
+							"Your instance #{@instance.domain} is starting up. " \
 							"This may take several minutes. " \
 							"Press next to check if it is ready."
-					}.then { GetInvite.for(@domain, @launched).then(&:write) }
+					}.then { GetInvite.for(@instance).then(&:write) }
 				end
 			end
 

lib/snikket.rb 🔗

@@ -47,23 +47,6 @@ module Snikket
 				&.content
 		end
 
-		def bootstrap_uri(instance_domain)
-			"https://#{instance_domain}/invites_bootstrap?token=#{bootstrap_token}"
-		end
-
-		def fetch_invite(instance_domain)
-			url = bootstrap_uri(instance_domain)
-			EM::HttpRequest.new(
-				url, tls: { verify_peer: true }
-			).ahead(redirects: 5).then { |res|
-				LinkHeaderParser.parse(
-					Array(res.response_header["LINK"]), base: url
-				).group_by_relation_type[:alternate]&.find do |header|
-					URI.parse(header.target_uri).scheme == "xmpp"
-				end&.target_uri
-			}.catch { nil }
-		end
-
 		def query
 			at_xpath("./ns:launched", ns: self.class.registered_ns)
 		end
@@ -118,4 +101,69 @@ module Snikket
 			at_xpath("./ns:instance", ns: self.class.registered_ns)
 		end
 	end
+
+	class CustomerInstance
+		def self.for(customer, domain, launched)
+			new(
+				instance_id: launched.instance_id,
+				bootstrap_token: launched.bootstrap_token,
+				customer_id: customer.customer_id,
+				domain: domain
+			)
+		end
+
+		value_semantics do
+			instance_id     String
+			bootstrap_token String
+			customer_id     String
+			domain          String
+		end
+
+		def bootstrap_uri
+			"https://#{domain}/invites_bootstrap?token=#{bootstrap_token}"
+		end
+
+		def fetch_invite
+			url = bootstrap_uri
+			EM::HttpRequest.new(
+				url, tls: { verify_peer: true }
+			).ahead(redirects: 5).then { |res|
+				LinkHeaderParser.parse(
+					Array(res.response_header["LINK"]), base: url
+				).group_by_relation_type[:alternate]&.find do |header|
+					URI.parse(header.target_uri).scheme == "xmpp"
+				end&.target_uri
+			}.catch { nil }
+		end
+	end
+
+	class Repo
+		def initialize(db: DB)
+			@db = db
+		end
+
+		def find_by_customer(customer)
+			promise = @db.query(<<~SQL, [customer.customer_id])
+				SELECT instance_id, bootstrap_token, customer_id, domain
+				FROM snikket_instances
+				WHERE customer_id=$1
+			SQL
+			promise.then do |rows|
+				rows.map { |row| CustomerInstance.new(**row) }
+			end
+		end
+
+		def put(instance)
+			params = [
+				instance.instance_id, instance.bootstrap_token,
+				instance.customer_id, instance.domain
+			]
+			@db.exec(<<~SQL, params)
+				INSERT INTO snikket_instances
+					(instance_id, boostrap_token, customer_id, domain)
+				VALUES
+					($1, $2, $3, $4)
+			SQL
+		end
+	end
 end

schemas 🔗

@@ -1 +1 @@
-Subproject commit a9f8b83487140f91fc8da3a3de99f5f28aa060b9
+Subproject commit dfb88d581ced2cb82cd1bab454de1afafcb9668d

sgx_jmp.rb 🔗

@@ -857,14 +857,13 @@ Command.new(
 				nil, CONFIG[:snikket_hosting_api],
 				domain: domain
 			)).then do |launched|
-				[domain, launched]
+				Snikket::CustomerInstance.for(customer, domain, launched)
 			end
-		}.then { |(domain, launched)|
+		}.then { |instance|
 			Command.finish do |reply|
 				reply.command << FormTemplate.render(
 					"snikket_launched",
-					launched: launched,
-					domain: domain
+					instance: instance
 				)
 			end
 		}

test/test_registration.rb 🔗

@@ -946,7 +946,10 @@ class RegistrationTest < Minitest::Test
 
 		def setup
 			@sgx = Minitest::Mock.new(TrivialBackendSgxRepo.new.get("test"))
-			@snikket = Registration::FinishOnboarding::Snikket.new("+15555550000")
+			@snikket = Registration::FinishOnboarding::Snikket.new(
+				customer,
+				"+15555550000"
+			)
 		end
 
 		def test_write
@@ -961,6 +964,11 @@ class RegistrationTest < Minitest::Test
 			launched << Niceogiri::XML::Node.new(
 				:launched, launched.document, "xmpp:snikket.org/hosting/v1"
 			).tap { |inner|
+				inner << Niceogiri::XML::Node.new(
+					:'instance-id', launched.document, "xmpp:snikket.org/hosting/v1"
+				).tap { |id|
+					id.content = "si-1234"
+				}
 				inner << Niceogiri::XML::Node.new(
 					:bootstrap, launched.document, "xmpp:snikket.org/hosting/v1"
 				).tap { |bootstrap|
@@ -1040,6 +1048,11 @@ class RegistrationTest < Minitest::Test
 			launched << Niceogiri::XML::Node.new(
 				:launched, launched.document, "xmpp:snikket.org/hosting/v1"
 			).tap { |inner|
+				inner << Niceogiri::XML::Node.new(
+					:'instance-id', launched.document, "xmpp:snikket.org/hosting/v1"
+				).tap { |id|
+					id.content = "si-1234"
+				}
 				inner << Niceogiri::XML::Node.new(
 					:bootstrap, launched.document, "xmpp:snikket.org/hosting/v1"
 				).tap { |bootstrap|

test/test_snikket.rb 🔗

@@ -30,10 +30,6 @@ class TestSnikket < Minitest::Test
 		assert_equal :result, launched.type
 		assert_equal NS, launched.query.namespace.href
 		assert_equal "launched", launched.query.node_name
-		assert_equal(
-			"https://example.com/invites_bootstrap?token=fZLy6iTh",
-			launched.bootstrap_uri("example.com")
-		)
 		assert_equal "si-12345", launched.instance_id
 	end
 
@@ -70,4 +66,18 @@ class TestSnikket < Minitest::Test
 		assert_kind_of Nokogiri::XML::Element, instance.operation
 		assert_equal :up, instance.status
 	end
+
+	def test_bootstrap_uri
+		instance = Snikket::CustomerInstance.new(
+			instance_id: "si-1234",
+			bootstrap_token: "fZLy6iTh",
+			customer_id: "test",
+			domain: "example.com"
+		)
+
+		assert_equal(
+			"https://example.com/invites_bootstrap?token=fZLy6iTh",
+			instance.bootstrap_uri
+		)
+	end
 end