Invites Repo

Christopher Vollick created

In preparation for another command I'd like to make I've first got to
make a place where Invites live.

There's probably other parts of the code that interact with Invites that
I've missed, but this is a good start at least.

Change summary

lib/customer.rb           |  6 ++----
lib/invites_repo.rb       | 30 ++++++++++++++++++++++++++++++
lib/registration.rb       | 19 +++++--------------
test/test_registration.rb |  2 +-
4 files changed, 38 insertions(+), 19 deletions(-)

Detailed changes

lib/customer.rb 🔗

@@ -10,6 +10,7 @@ require_relative "./customer_ogm"
 require_relative "./customer_info"
 require_relative "./customer_finacials"
 require_relative "./backend_sgx"
+require_relative "./invites_repo"
 require_relative "./payment_methods"
 require_relative "./plan"
 require_relative "./proxied_jid"
@@ -75,10 +76,7 @@ class Customer
 	end
 
 	def unused_invites
-		promise = DB.query_defer(<<~SQL, [customer_id])
-			SELECT code FROM unused_invites WHERE creator_id=$1
-		SQL
-		promise.then { |result| result.map { |row| row["code"] } }
+		InvitesRepo.new(DB).unused_invites(customer_id)
 	end
 
 	def stanza_to(stanza)

lib/invites_repo.rb 🔗

@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class InvitesRepo
+	class Invalid < StandardError; end
+
+	def initialize(db=DB)
+		@db = db
+	end
+
+	def unused_invites(customer_id)
+		promise = @db.query_defer(<<~SQL, [customer_id])
+			SELECT code FROM unused_invites WHERE creator_id=$1
+		SQL
+		promise.then { |result| result.map { |row| row["code"] } }
+	end
+
+	def claim_code(customer_id, code, &blk)
+		EMPromise.resolve(nil).then do
+			@db.transaction do
+				valid = @db.exec(<<~SQL, [customer_id, code]).cmd_tuples.positive?
+					UPDATE invites SET used_by_id=$1, used_at=LOCALTIMESTAMP
+					WHERE code=$2 AND used_by_id IS NULL
+				SQL
+				raise Invalid, "Not a valid invite code: #{code}" unless valid
+
+				blk.call
+			end
+		end
+	end
+end

lib/registration.rb 🔗

@@ -8,6 +8,7 @@ require_relative "./alt_top_up_form"
 require_relative "./bandwidth_tn_order"
 require_relative "./command"
 require_relative "./em"
+require_relative "./invites_repo"
 require_relative "./oob"
 require_relative "./proxied_jid"
 require_relative "./tel_selections"
@@ -318,8 +319,6 @@ class Registration
 		class InviteCode
 			Payment.kinds[:code] = method(:new)
 
-			class Invalid < StandardError; end
-
 			FIELDS = [{
 				var: "code",
 				type: "text-single",
@@ -353,14 +352,14 @@ class Registration
 					verify(iq.form.field("code")&.value&.to_s)
 				}.then {
 					Finish.new(@customer, @tel)
-				}.catch_only(Invalid, &method(:invalid_code)).then(&:write)
+				}.catch_only(InvitesRepo::Invalid, &method(:invalid_code)).then(&:write)
 			end
 
 		protected
 
 			def guard_too_many_tries
 				REDIS.get("jmp_invite_tries-#{customer_id}").then do |t|
-					raise Invalid, "Too many wrong attempts" if t.to_i > 10
+					raise InvitesRepo::Invalid, "Too many wrong attempts" if t.to_i > 10
 				end
 			end
 
@@ -378,16 +377,8 @@ class Registration
 			end
 
 			def verify(code)
-				EMPromise.resolve(nil).then do
-					DB.transaction do
-						valid = DB.exec(<<~SQL, [customer_id, code]).cmd_tuples.positive?
-							UPDATE invites SET used_by_id=$1, used_at=LOCALTIMESTAMP
-							WHERE code=$2 AND used_by_id IS NULL
-						SQL
-						raise Invalid, "Not a valid invite code: #{code}" unless valid
-
-						@customer.activate_plan_starting_now
-					end
+				InvitesRepo.new(DB).claim_code(customer_id, code) do
+					@customer.activate_plan_starting_now
 				end
 			end
 		end

test/test_registration.rb 🔗

@@ -551,7 +551,7 @@ class RegistrationTest < Minitest::Test
 						["jmp_invite_tries-test"]
 					)
 					Registration::Payment::InviteCode::DB.expect(:transaction, []) do
-						raise Registration::Payment::InviteCode::Invalid, "wut"
+						raise InvitesRepo::Invalid, "wut"
 					end
 					Registration::Payment::InviteCode::REDIS.expect(
 						:incr,