Gemfile 🔗
@@ -14,6 +14,7 @@ gem "eventmachine"
gem "money-open-exchange-rates"
gem "ruby-bandwidth-iris"
gem "sentry-ruby"
+gem "value_semantics", git: "https://github.com/singpolyma/value_semantics"
group(:development) do
gem "pry-reload"
Stephen Paul Weber created
* create-reset-sip-account:
Create or reset SIP account
Factor out Catapult connection
Gemfile | 1
config.dhall.sample | 4
lib/bandwidth_tn_order.rb | 31 --
lib/catapult.rb | 97 ++++++++++
lib/customer.rb | 11 +
lib/mn_words.rb | 277 +++++++++++++++++++++++++++++++
lib/sip_account.rb | 101 +++++++++++
sgx_jmp.rb | 20 ++
test/data/catapult_create_sip.json | 1
test/data/catapult_import_body.json | 2
test/test_customer.rb | 77 ++++++++
test/test_helper.rb | 2
test/test_sip_account.rb | 121 +++++++++++++
13 files changed, 717 insertions(+), 28 deletions(-)
@@ -14,6 +14,7 @@ gem "eventmachine"
gem "money-open-exchange-rates"
gem "ruby-bandwidth-iris"
gem "sentry-ruby"
+gem "value_semantics", git: "https://github.com/singpolyma/value_semantics"
group(:development) do
gem "pry-reload"
@@ -17,7 +17,9 @@
user = "",
token = "",
secret = "",
- application_id = ""
+ application_id = "",
+ domain = "",
+ sip_host = ""
},
web_register = {
to = "cheogram",
@@ -4,6 +4,8 @@ require "forwardable"
require "ruby-bandwidth-iris"
Faraday.default_adapter = :em_synchrony
+require_relative "./catapult"
+
class BandwidthTNOrder
def self.get(id)
EM.promise_fiber do
@@ -86,35 +88,12 @@ class BandwidthTNOrder
# After buying, import to catapult and set v1 voice app
def catapult_import
- catapult_request.apost(
- head: catapult_headers,
- body: {
- number: tel,
- applicationId: catapult[:application_id],
- provider: dashboard_provider
- }.to_json
- )
- end
-
- def catapult
- CONFIG[:catapult]
- end
-
- def catapult_request
- EM::HttpRequest.new(
- "https://api.catapult.inetwork.com/v1/users/" \
- "#{catapult[:user]}/phoneNumbers",
- tls: { verify_peer: true }
+ CATAPULT.import(
+ number: tel,
+ provider: dashboard_provider
)
end
- def catapult_headers
- {
- "Authorization" => [catapult[:token], catapult[:secret]],
- "Content-Type" => "application/json"
- }
- end
-
def dashboard_provider
{
providerName: "bandwidth-dashboard",
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require "value_semantics/monkey_patched"
+
+class Catapult
+ value_semantics do
+ user String
+ token String
+ secret String
+ application_id String
+ domain String
+ sip_host String
+ end
+
+ def import(body)
+ post(
+ "phoneNumbers",
+ body: { applicationId: application_id }.merge(body)
+ )
+ end
+
+ def create_endpoint(body)
+ post(
+ "domains/#{@domain}/endpoints",
+ body: { applicationId: @application_id }.merge(body)
+ ).then do |http|
+ unless http.response_header.status == 201
+ raise "Create new SIP account failed"
+ end
+ http.response_header["location"]
+ end
+ end
+
+ def endpoint_list(page=0)
+ get(
+ "domains/#{@domain}/endpoints",
+ query: { size: 1000, page: page }
+ ).then do |http|
+ next [] if http.response_header.status == 404
+ raise "Could not list endpoints" if http.response_header.status != 200
+
+ JSON.parse(http.response)
+ end
+ end
+
+ def endpoint_find(name, page=0)
+ endpoint_list(page).then do |list|
+ next if list.empty?
+
+ if (found = list.find { |e| e["name"] == name })
+ found.merge("url" => CATAPULT.mkurl(
+ "domains/#{found['domainId']}/endpoints/#{found['id']}"
+ ))
+ else
+ endpoint_find(name, page + 1)
+ end
+ end
+ end
+
+ def post(path, body:, head: {})
+ EM::HttpRequest.new(
+ mkurl(path), tls: { verify_peer: true }
+ ).apost(
+ head: catapult_headers.merge(head),
+ body: body.to_json
+ )
+ end
+
+ def delete(path, head: {})
+ EM::HttpRequest.new(
+ mkurl(path), tls: { verify_peer: true }
+ ).adelete(head: catapult_headers.merge(head))
+ end
+
+ def get(path, head: {}, **kwargs)
+ EM::HttpRequest.new(
+ mkurl(path), tls: { verify_peer: true }
+ ).aget(head: catapult_headers.merge(head), **kwargs)
+ end
+
+ def mkurl(path)
+ base = "https://api.catapult.inetwork.com/v1/users/#{@user}/"
+ return path if path.start_with?(base)
+ "#{base}#{path}"
+ end
+
+protected
+
+ def catapult_headers
+ {
+ "Authorization" => [@token, @secret],
+ "Content-Type" => "application/json"
+ }
+ end
+end
+
+CATAPULT = Catapult.new(**CONFIG[:catapult])
@@ -9,6 +9,7 @@ require_relative "./backend_sgx"
require_relative "./ibr"
require_relative "./payment_methods"
require_relative "./plan"
+require_relative "./sip_account"
class Customer
def self.for_jid(jid)
@@ -103,5 +104,15 @@ class Customer
BLATHER << @sgx.stanza(stanza)
end
+ def sip_account
+ SipAccount.find(customer_id)
+ end
+
+ def reset_sip_account
+ SipAccount::New.new(username: customer_id).put.catch do
+ sip_account.then { |acct| acct.with_random_password.put }
+ end
+ end
+
protected def_delegator :@plan, :expires_at
end
@@ -0,0 +1,277 @@
+# frozen_string_literal: true
+
+MN_WORDS = [
+ "academy", "acrobat", "active", "actor", "adam", "admiral",
+ "adrian", "africa", "agenda", "agent", "airline", "airport",
+ "aladdin", "alarm", "alaska", "albert", "albino", "album",
+ "alcohol", "alex", "algebra", "alibi", "alice", "alien",
+ "alpha", "alpine", "amadeus", "amanda", "amazon", "amber",
+ "america", "amigo", "analog", "anatomy", "angel", "animal",
+ "antenna", "antonio", "apollo", "april", "archive", "arctic",
+ "arizona", "arnold", "aroma", "arthur", "artist", "asia",
+ "aspect", "aspirin", "athena", "athlete", "atlas", "audio",
+ "august", "austria", "axiom", "aztec", "balance", "ballad",
+ "banana", "bandit", "banjo", "barcode", "baron", "basic",
+ "battery", "belgium", "berlin", "bermuda", "bernard", "bikini",
+ "binary", "bingo", "biology", "block", "blonde", "bonus",
+ "boris", "boston", "boxer", "brandy", "bravo", "brazil",
+ "bronze", "brown", "bruce", "bruno", "burger", "burma",
+ "cabinet", "cactus", "cafe", "cairo", "cake", "calypso",
+ "camel", "camera", "campus", "canada", "canal", "cannon",
+ "canoe", "cantina", "canvas", "canyon", "capital", "caramel",
+ "caravan", "carbon", "cargo", "carlo", "carol", "carpet",
+ "cartel", "casino", "castle", "castro", "catalog", "caviar",
+ "cecilia", "cement", "center", "century", "ceramic", "chamber",
+ "chance", "change", "chaos", "charlie", "charm", "charter",
+ "chef", "chemist", "cherry", "chess", "chicago", "chicken",
+ "chief", "china", "cigar", "cinema", "circus", "citizen",
+ "city", "clara", "classic", "claudia", "clean", "client",
+ "climax", "clinic", "clock", "club", "cobra", "coconut",
+ "cola", "collect", "colombo", "colony", "color", "combat",
+ "comedy", "comet", "command", "compact", "company", "complex",
+ "concept", "concert", "connect", "consul", "contact", "context",
+ "contour", "control", "convert", "copy", "corner", "corona",
+ "correct", "cosmos", "couple", "courage", "cowboy", "craft",
+ "crash", "credit", "cricket", "critic", "crown", "crystal",
+ "cuba", "culture", "dallas", "dance", "daniel", "david",
+ "decade", "decimal", "deliver", "delta", "deluxe", "demand",
+ "demo", "denmark", "derby", "design", "detect", "develop",
+ "diagram", "dialog", "diamond", "diana", "diego", "diesel",
+ "diet", "digital", "dilemma", "diploma", "direct", "disco",
+ "disney", "distant", "doctor", "dollar", "dominic", "domino",
+ "donald", "dragon", "drama", "dublin", "duet", "dynamic",
+ "east", "ecology", "economy", "edgar", "egypt", "elastic",
+ "elegant", "element", "elite", "elvis", "email", "energy",
+ "engine", "english", "episode", "equator", "escort", "ethnic",
+ "europe", "everest", "evident", "exact", "example", "exit",
+ "exotic", "export", "express", "extra", "fabric", "factor",
+ "falcon", "family", "fantasy", "fashion", "fiber", "fiction",
+ "fidel", "fiesta", "figure", "film", "filter", "final",
+ "finance", "finish", "finland", "flash", "florida", "flower",
+ "fluid", "flute", "focus", "ford", "forest", "formal",
+ "format", "formula", "fortune", "forum", "fragile", "france",
+ "frank", "friend", "frozen", "future", "gabriel", "galaxy",
+ "gallery", "gamma", "garage", "garden", "garlic", "gemini",
+ "general", "genetic", "genius", "germany", "global", "gloria",
+ "golf", "gondola", "gong", "good", "gordon", "gorilla",
+ "grand", "granite", "graph", "green", "group", "guide",
+ "guitar", "guru", "hand", "happy", "harbor", "harmony",
+ "harvard", "havana", "hawaii", "helena", "hello", "henry",
+ "hilton", "history", "horizon", "hotel", "human", "humor",
+ "icon", "idea", "igloo", "igor", "image", "impact",
+ "import", "index", "india", "indigo", "input", "insect",
+ "instant", "iris", "italian", "jacket", "jacob", "jaguar",
+ "janet", "japan", "jargon", "jazz", "jeep", "john",
+ "joker", "jordan", "jumbo", "june", "jungle", "junior",
+ "jupiter", "karate", "karma", "kayak", "kermit", "kilo",
+ "king", "koala", "korea", "labor", "lady", "lagoon",
+ "laptop", "laser", "latin", "lava", "lecture", "left",
+ "legal", "lemon", "level", "lexicon", "liberal", "libra",
+ "limbo", "limit", "linda", "linear", "lion", "liquid",
+ "liter", "little", "llama", "lobby", "lobster", "local",
+ "logic", "logo", "lola", "london", "lotus", "lucas",
+ "lunar", "machine", "macro", "madam", "madonna", "madrid",
+ "maestro", "magic", "magnet", "magnum", "major", "mama",
+ "mambo", "manager", "mango", "manila", "marco", "marina",
+ "market", "mars", "martin", "marvin", "master", "matrix",
+ "maximum", "media", "medical", "mega", "melody", "melon",
+ "memo", "mental", "mentor", "menu", "mercury", "message",
+ "metal", "meteor", "meter", "method", "metro", "mexico",
+ "miami", "micro", "million", "mineral", "minimum", "minus",
+ "minute", "miracle", "mirage", "miranda", "mister", "mixer",
+ "mobile", "model", "modem", "modern", "modular", "moment",
+ "monaco", "monica", "monitor", "mono", "monster", "montana",
+ "morgan", "motel", "motif", "motor", "mozart", "multi",
+ "museum", "music", "mustang", "natural", "neon", "nepal",
+ "neptune", "nerve", "neutral", "nevada", "news", "ninja",
+ "nirvana", "normal", "nova", "novel", "nuclear", "numeric",
+ "nylon", "oasis", "object", "observe", "ocean", "octopus",
+ "olivia", "olympic", "omega", "opera", "optic", "optimal",
+ "orange", "orbit", "organic", "orient", "origin", "orlando",
+ "oscar", "oxford", "oxygen", "ozone", "pablo", "pacific",
+ "pagoda", "palace", "pamela", "panama", "panda", "panel",
+ "panic", "paradox", "pardon", "paris", "parker", "parking",
+ "parody", "partner", "passage", "passive", "pasta", "pastel",
+ "patent", "patriot", "patrol", "patron", "pegasus", "pelican",
+ "penguin", "pepper", "percent", "perfect", "perfume", "period",
+ "permit", "person", "peru", "phone", "photo", "piano",
+ "picasso", "picnic", "picture", "pigment", "pilgrim", "pilot",
+ "pirate", "pixel", "pizza", "planet", "plasma", "plaster",
+ "plastic", "plaza", "pocket", "poem", "poetic", "poker",
+ "polaris", "police", "politic", "polo", "polygon", "pony",
+ "popcorn", "popular", "postage", "postal", "precise", "prefix",
+ "premium", "present", "price", "prince", "printer", "prism",
+ "private", "product", "profile", "program", "project", "protect",
+ "proton", "public", "pulse", "puma", "pyramid", "queen",
+ "radar", "radio", "random", "rapid", "rebel", "record",
+ "recycle", "reflex", "reform", "regard", "regular", "relax",
+ "report", "reptile", "reverse", "ricardo", "ringo", "ritual",
+ "robert", "robot", "rocket", "rodeo", "romeo", "royal",
+ "russian", "safari", "salad", "salami", "salmon", "salon",
+ "salute", "samba", "sandra", "santana", "sardine", "school",
+ "screen", "script", "second", "secret", "section", "segment",
+ "select", "seminar", "senator", "senior", "sensor", "serial",
+ "service", "sheriff", "shock", "sierra", "signal", "silicon",
+ "silver", "similar", "simon", "single", "siren", "slogan",
+ "social", "soda", "solar", "solid", "solo", "sonic",
+ "soviet", "special", "speed", "spiral", "spirit", "sport",
+ "static", "station", "status", "stereo", "stone", "stop",
+ "street", "strong", "student", "studio", "style", "subject",
+ "sultan", "super", "susan", "sushi", "suzuki", "switch",
+ "symbol", "system", "tactic", "tahiti", "talent", "tango",
+ "tarzan", "taxi", "telex", "tempo", "tennis", "texas",
+ "textile", "theory", "thermos", "tiger", "titanic", "tokyo",
+ "tomato", "topic", "tornado", "toronto", "torpedo", "total",
+ "totem", "tourist", "tractor", "traffic", "transit", "trapeze",
+ "travel", "tribal", "trick", "trident", "trilogy", "tripod",
+ "tropic", "trumpet", "tulip", "tuna", "turbo", "twist",
+ "ultra", "uniform", "union", "uranium", "vacuum", "valid",
+ "vampire", "vanilla", "vatican", "velvet", "ventura", "venus",
+ "vertigo", "veteran", "victor", "video", "vienna", "viking",
+ "village", "vincent", "violet", "violin", "virtual", "virus",
+ "visa", "vision", "visitor", "visual", "vitamin", "viva",
+ "vocal", "vodka", "volcano", "voltage", "volume", "voyage",
+ "water", "weekend", "welcome", "western", "window", "winter",
+ "wizard", "wolf", "world", "xray", "yankee", "yoga",
+ "yogurt", "yoyo", "zebra", "zero", "zigzag", "zipper",
+ "zodiac", "zoom", "abraham", "action", "address", "alabama",
+ "alfred", "almond", "ammonia", "analyze", "annual", "answer",
+ "apple", "arena", "armada", "arsenal", "atlanta", "atomic",
+ "avenue", "average", "bagel", "baker", "ballet", "bambino",
+ "bamboo", "barbara", "basket", "bazaar", "benefit", "bicycle",
+ "bishop", "blitz", "bonjour", "bottle", "bridge", "british",
+ "brother", "brush", "budget", "cabaret", "cadet", "candle",
+ "capitan", "capsule", "career", "cartoon", "channel", "chapter",
+ "cheese", "circle", "cobalt", "cockpit", "college", "compass",
+ "comrade", "condor", "crimson", "cyclone", "darwin", "declare",
+ "degree", "delete", "delphi", "denver", "desert", "divide",
+ "dolby", "domain", "domingo", "double", "drink", "driver",
+ "eagle", "earth", "echo", "eclipse", "editor", "educate",
+ "edward", "effect", "electra", "emerald", "emotion", "empire",
+ "empty", "escape", "eternal", "evening", "exhibit", "expand",
+ "explore", "extreme", "ferrari", "first", "flag", "folio",
+ "forget", "forward", "freedom", "fresh", "friday", "fuji",
+ "galileo", "garcia", "genesis", "gold", "gravity", "habitat",
+ "hamlet", "harlem", "helium", "holiday", "house", "hunter",
+ "ibiza", "iceberg", "imagine", "infant", "isotope", "jackson",
+ "jamaica", "jasmine", "java", "jessica", "judo", "kitchen",
+ "lazarus", "letter", "license", "lithium", "loyal", "lucky",
+ "magenta", "mailbox", "manual", "marble", "mary", "maxwell",
+ "mayor", "milk", "monarch", "monday", "money", "morning",
+ "mother", "mystery", "native", "nectar", "nelson", "network",
+ "next", "nikita", "nobel", "nobody", "nominal", "norway",
+ "nothing", "number", "october", "office", "oliver", "opinion",
+ "option", "order", "outside", "package", "pancake", "pandora",
+ "panther", "papa", "patient", "pattern", "pedro", "pencil",
+ "people", "phantom", "philips", "pioneer", "pluto", "podium",
+ "portal", "potato", "prize", "process", "protein", "proxy",
+ "pump", "pupil", "python", "quality", "quarter", "quiet",
+ "rabbit", "radical", "radius", "rainbow", "ralph", "ramirez",
+ "ravioli", "raymond", "respect", "respond", "result", "resume",
+ "retro", "richard", "right", "risk", "river", "roger",
+ "roman", "rondo", "sabrina", "salary", "salsa", "sample",
+ "samuel", "saturn", "savage", "scarlet", "scoop", "scorpio",
+ "scratch", "scroll", "sector", "serpent", "shadow", "shampoo",
+ "sharon", "sharp", "short", "shrink", "silence", "silk",
+ "simple", "slang", "smart", "smoke", "snake", "society",
+ "sonar", "sonata", "soprano", "source", "sparta", "sphere",
+ "spider", "sponsor", "spring", "acid", "adios", "agatha",
+ "alamo", "alert", "almanac", "aloha", "andrea", "anita",
+ "arcade", "aurora", "avalon", "baby", "baggage", "balloon",
+ "bank", "basil", "begin", "biscuit", "blue", "bombay",
+ "brain", "brenda", "brigade", "cable", "carmen", "cello",
+ "celtic", "chariot", "chrome", "citrus", "civil", "cloud",
+ "common", "compare", "cool", "copper", "coral", "crater",
+ "cubic", "cupid", "cycle", "depend", "door", "dream",
+ "dynasty", "edison", "edition", "enigma", "equal", "eric",
+ "event", "evita", "exodus", "extend", "famous", "farmer",
+ "food", "fossil", "frog", "fruit", "geneva", "gentle",
+ "george", "giant", "gilbert", "gossip", "gram", "greek",
+ "grille", "hammer", "harvest", "hazard", "heaven", "herbert",
+ "heroic", "hexagon", "husband", "immune", "inca", "inch",
+ "initial", "isabel", "ivory", "jason", "jerome", "joel",
+ "joshua", "journal", "judge", "juliet", "jump", "justice",
+ "kimono", "kinetic", "leonid", "lima", "maze", "medusa",
+ "member", "memphis", "michael", "miguel", "milan", "mile",
+ "miller", "mimic", "mimosa", "mission", "monkey", "moral",
+ "moses", "mouse", "nancy", "natasha", "nebula", "nickel",
+ "nina", "noise", "orchid", "oregano", "origami", "orinoco",
+ "orion", "othello", "paper", "paprika", "prelude", "prepare",
+ "pretend", "profit", "promise", "provide", "puzzle", "remote",
+ "repair", "reply", "rival", "riviera", "robin", "rose",
+ "rover", "rudolf", "saga", "sahara", "scholar", "shelter",
+ "ship", "shoe", "sigma", "sister", "sleep", "smile",
+ "spain", "spark", "split", "spray", "square", "stadium",
+ "star", "storm", "story", "strange", "stretch", "stuart",
+ "subway", "sugar", "sulfur", "summer", "survive", "sweet",
+ "swim", "table", "taboo", "target", "teacher", "telecom",
+ "temple", "tibet", "ticket", "tina", "today", "toga",
+ "tommy", "tower", "trivial", "tunnel", "turtle", "twin",
+ "uncle", "unicorn", "unique", "update", "valery", "vega",
+ "version", "voodoo", "warning", "william", "wonder", "year",
+ "yellow", "young", "absent", "absorb", "accent", "alfonso",
+ "alias", "ambient", "andy", "anvil", "appear", "apropos",
+ "archer", "ariel", "armor", "arrow", "austin", "avatar",
+ "axis", "baboon", "bahama", "bali", "balsa", "bazooka",
+ "beach", "beast", "beatles", "beauty", "before", "benny",
+ "betty", "between", "beyond", "billy", "bison", "blast",
+ "bless", "bogart", "bonanza", "book", "border", "brave",
+ "bread", "break", "broken", "bucket", "buenos", "buffalo",
+ "bundle", "button", "buzzer", "byte", "caesar", "camilla",
+ "canary", "candid", "carrot", "cave", "chant", "child",
+ "choice", "chris", "cipher", "clarion", "clark", "clever",
+ "cliff", "clone", "conan", "conduct", "congo", "content",
+ "costume", "cotton", "cover", "crack", "current", "danube",
+ "data", "decide", "desire", "detail", "dexter", "dinner",
+ "dispute", "donor", "druid", "drum", "easy", "eddie",
+ "enjoy", "enrico", "epoxy", "erosion", "except", "exile",
+ "explain", "fame", "fast", "father", "felix", "field",
+ "fiona", "fire", "fish", "flame", "flex", "flipper",
+ "float", "flood", "floor", "forbid", "forever", "fractal",
+ "frame", "freddie", "front", "fuel", "gallop", "game",
+ "garbo", "gate", "gibson", "ginger", "giraffe", "gizmo",
+ "glass", "goblin", "gopher", "grace", "gray", "gregory",
+ "grid", "griffin", "ground", "guest", "gustav", "gyro",
+ "hair", "halt", "harris", "heart", "heavy", "herman",
+ "hippie", "hobby", "honey", "hope", "horse", "hostel",
+ "hydro", "imitate", "info", "ingrid", "inside", "invent",
+ "invest", "invite", "iron", "ivan", "james", "jester",
+ "jimmy", "join", "joseph", "juice", "julius", "july",
+ "justin", "kansas", "karl", "kevin", "kiwi", "ladder",
+ "lake", "laura", "learn", "legacy", "legend", "lesson",
+ "life", "light", "list", "locate", "lopez", "lorenzo",
+ "love", "lunch", "malta", "mammal", "margo", "marion",
+ "mask", "match", "mayday", "meaning", "mercy", "middle",
+ "mike", "mirror", "modest", "morph", "morris", "nadia",
+ "nato", "navy", "needle", "neuron", "never", "newton",
+ "nice", "night", "nissan", "nitro", "nixon", "north",
+ "oberon", "octavia", "ohio", "olga", "open", "opus",
+ "orca", "oval", "owner", "page", "paint", "palma",
+ "parade", "parent", "parole", "paul", "peace", "pearl",
+ "perform", "phoenix", "phrase", "pierre", "pinball", "place",
+ "plate", "plato", "plume", "pogo", "point", "polite",
+ "polka", "poncho", "powder", "prague", "press", "presto",
+ "pretty", "prime", "promo", "quasi", "quest", "quick",
+ "quiz", "quota", "race", "rachel", "raja", "ranger",
+ "region", "remark", "rent", "reward", "rhino", "ribbon",
+ "rider", "road", "rodent", "round", "rubber", "ruby",
+ "rufus", "sabine", "saddle", "sailor", "saint", "salt",
+ "satire", "scale", "scuba", "season", "secure", "shake",
+ "shallow", "shannon", "shave", "shelf", "sherman", "shine",
+ "shirt", "side", "sinatra", "sincere", "size", "slalom",
+ "slow", "small", "snow", "sofia", "song", "sound",
+ "south", "speech", "spell", "spend", "spoon", "stage",
+ "stamp", "stand", "state", "stella", "stick", "sting",
+ "stock", "store", "sunday", "sunset", "support", "sweden",
+ "swing", "tape", "think", "thomas", "tictac", "time",
+ "toast", "tobacco", "tonight", "torch", "torso", "touch",
+ "toyota", "trade", "tribune", "trinity", "triton", "truck",
+ "trust", "type", "under", "unit", "urban", "urgent",
+ "user", "value", "vendor", "venice", "verona", "vibrate",
+ "virgo", "visible", "vista", "vital", "voice", "vortex",
+ "waiter", "watch", "wave", "weather", "wedding", "wheel",
+ "whiskey", "wisdom", "deal", "null", "nurse", "quebec",
+ "reserve", "reunion", "roof", "singer", "verbal", "amen",
+ "ego", "fax", "jet", "job", "rio", "ski",
+ "yes"
+].freeze
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require "securerandom"
+require "value_semantics/monkey_patched"
+
+require_relative "./catapult"
+require_relative "./mn_words"
+
+class SipAccount
+ def self.find(name)
+ CATAPULT.endpoint_find(name).then do |found|
+ next New.new(username: name) unless found
+
+ new(username: found["name"], url: found["url"])
+ end
+ end
+
+ module Common
+ def with_random_password
+ with(password: MN_WORDS.sample(3).join(" "))
+ end
+
+ protected
+
+ def create
+ CATAPULT.create_endpoint(
+ name: username,
+ credentials: { password: password }
+ ).then do |url|
+ with(url: url)
+ end
+ end
+ end
+
+ include Common
+
+ value_semantics do
+ url String
+ username String
+ password Either(String, nil), default: nil
+ end
+
+ def form
+ form = Blather::Stanza::X.new(:result)
+ form.title = "Sip Account Reset!"
+ form.instructions = "These are your new SIP credentials"
+
+ form.fields = [
+ { var: "username", value: username, label: "Username" },
+ { var: "password", value: password, label: "Password" },
+ { var: "server", value: server, label: "Server" }
+ ]
+
+ form
+ end
+
+ def put
+ delete.then { create }
+ end
+
+ def delete
+ CATAPULT.delete(url).then do |http|
+ unless http.response_header.status == 200
+ raise "Delete old SIP account failed"
+ end
+
+ self
+ end
+ end
+
+protected
+
+ protected :url, :username, :password
+
+ def server
+ CATAPULT.sip_host
+ end
+
+ class New
+ include Common
+
+ value_semantics do
+ username String
+ password String, default_generator: -> { MN_WORDS.sample(3).join(" ") }
+ end
+
+ def put
+ create
+ end
+
+ def with(**kwargs)
+ if kwargs.key?(:url)
+ SipAccount.new(internal_to_h.merge(kwargs))
+ else
+ super
+ end
+ end
+
+ protected :username, :password
+ end
+end
@@ -258,6 +258,11 @@ disco_items node: "http://jabber.org/protocol/commands" do |iq|
iq.to,
"usage",
"Show Monthly Usage"
+ ),
+ Blather::Stanza::DiscoItems::Item.new(
+ iq.to,
+ "reset sip account",
+ "Create or Reset SIP Account"
)
]
self << reply
@@ -321,6 +326,21 @@ command :execute?, node: "buy-credit", sessionid: nil do |iq|
}.catch { |e| panic(e, sentry_hub) }
end
+command :execute?, node: "reset sip account", sessionid: nil do |iq|
+ sentry_hub = new_sentry_hub(iq, name: iq.node)
+ Customer.for_jid(iq.from.stripped).then { |customer|
+ sentry_hub.current_scope.set_user(
+ id: customer.customer_id,
+ jid: iq.from.stripped.to_s
+ )
+ customer.reset_sip_account
+ }.then { |sip_account|
+ reply = iq.reply
+ reply.command << sip_account.form
+ BLATHER << reply
+ }.catch { |e| panic(e, sentry_hub) }
+end
+
command :execute?, node: "usage", sessionid: nil do |iq|
sentry_hub = new_sentry_hub(iq, name: iq.node)
report_for = (Date.today..(Date.today << 1))
@@ -0,0 +1 @@
+{"applicationId":"catapult_app","name":"12345","credentials":{"password":"old password"}}
@@ -1 +1 @@
-{"number":"+15555550000","applicationId":"catapult_app","provider":{"providerName":"bandwidth-dashboard","properties":{"accountId":"test_bw_account","userName":"test_bw_user","password":"test_bw_password"}}}
+{"applicationId":"catapult_app","number":"+15555550000","provider":{"providerName":"bandwidth-dashboard","properties":{"accountId":"test_bw_account","userName":"test_bw_user","password":"test_bw_password"}}}
@@ -11,6 +11,14 @@ CustomerPlan::DB = Minitest::Mock.new
CustomerUsage::REDIS = Minitest::Mock.new
CustomerUsage::DB = Minitest::Mock.new
+class SipAccount
+ public :username, :url
+
+ class New
+ public :username
+ end
+end
+
class CustomerTest < Minitest::Test
def test_for_jid
Customer::REDIS.expect(
@@ -205,4 +213,73 @@ class CustomerTest < Minitest::Test
)
end
em :test_customer_usage_report
+
+ def test_sip_account_new
+ req = stub_request(
+ :get,
+ "https://api.catapult.inetwork.com/v1/users/" \
+ "catapult_user/domains/catapult_domain/endpoints?page=0&size=1000"
+ ).with(
+ headers: {
+ "Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0"
+ }
+ ).to_return(status: 404)
+ sip = Customer.new("test").sip_account.sync
+ assert_kind_of SipAccount::New, sip
+ assert_equal "test", sip.username
+ assert_requested req
+ end
+ em :test_sip_account_new
+
+ def test_sip_account_existing
+ req1 = stub_request(
+ :get,
+ "https://api.catapult.inetwork.com/v1/users/" \
+ "catapult_user/domains/catapult_domain/endpoints?page=0&size=1000"
+ ).with(
+ headers: {
+ "Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0"
+ }
+ ).to_return(status: 200, body: [
+ { name: "NOTtest", domainId: "domain", id: "endpoint" }
+ ].to_json)
+
+ req2 = stub_request(
+ :get,
+ "https://api.catapult.inetwork.com/v1/users/" \
+ "catapult_user/domains/catapult_domain/endpoints?page=1&size=1000"
+ ).with(
+ headers: {
+ "Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0"
+ }
+ ).to_return(status: 200, body: [
+ { name: "test", domainId: "domain", id: "endpoint" }
+ ].to_json)
+
+ sip = Customer.new("test").sip_account.sync
+ assert_kind_of SipAccount, sip
+ assert_equal "test", sip.username
+ assert_equal(
+ "https://api.catapult.inetwork.com/v1/users/" \
+ "catapult_user/domains/domain/endpoints/endpoint",
+ sip.url
+ )
+
+ assert_requested req1
+ assert_requested req2
+ end
+ em :test_sip_account_existing
+
+ def test_sip_account_error
+ stub_request(
+ :get,
+ "https://api.catapult.inetwork.com/v1/users/" \
+ "catapult_user/domains/catapult_domain/endpoints?page=0&size=1000"
+ ).to_return(status: 400)
+
+ assert_raises(RuntimeError) do
+ Customer.new("test").sip_account.sync
+ end
+ end
+ em :test_sip_account_error
end
@@ -48,6 +48,8 @@ CONFIG = {
user: "catapult_user",
token: "catapult_token",
secret: "catapult_secret",
+ domain: "catapult_domain",
+ sip_host: "host.bwapp.io.example.com",
application_id: "catapult_app"
},
activation_amount: 1,
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "sip_account"
+
+class SipAccount
+ public :password, :url
+
+ class New
+ public :password
+ end
+end
+
+class SipAccountTest < Minitest::Test
+ def setup
+ @sip = SipAccount.new(
+ url: "https://api.catapult.inetwork.com/v1/" \
+ "users/catapult_user/domains/catapult_domain/endpoints/test",
+ username: "12345",
+ password: "old password"
+ )
+ end
+
+ def test_with_random_password
+ new_sip = @sip.with_random_password
+ refute_equal @sip.password, new_sip.password
+ refute_empty new_sip.password
+ assert_kind_of String, new_sip.password
+ end
+
+ def test_form
+ form = @sip.form
+ assert_equal "12345", form.field("username").value
+ assert_equal "old password", form.field("password").value
+ assert_equal "host.bwapp.io.example.com", form.field("server").value
+ end
+
+ def test_put
+ delete = stub_request(:delete, @sip.url).with(
+ headers: {
+ "Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0"
+ }
+ ).to_return(status: 200)
+
+ post = stub_request(
+ :post,
+ "https://api.catapult.inetwork.com/v1/users/" \
+ "catapult_user/domains/catapult_domain/endpoints"
+ ).with(
+ body: open(__dir__ + "/data/catapult_create_sip.json").read.chomp,
+ headers: {
+ "Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0",
+ "Content-Type" => "application/json"
+ }
+ ).to_return(
+ status: 201,
+ headers: { "Location" => "http://example.com/endpoint" }
+ )
+
+ new_sip = @sip.put.sync
+ assert_equal "http://example.com/endpoint", new_sip.url
+ assert_requested delete
+ assert_requested post
+ end
+ em :test_put
+
+ def test_put_delete_fail
+ stub_request(:delete, @sip.url).to_return(status: 400)
+ assert_raises(RuntimeError) { @sip.put.sync }
+ end
+ em :test_put_delete_fail
+
+ def test_put_post_fail
+ stub_request(:delete, @sip.url).to_return(status: 200)
+ stub_request(
+ :post,
+ "https://api.catapult.inetwork.com/v1/users/" \
+ "catapult_user/domains/catapult_domain/endpoints"
+ ).to_return(status: 400)
+ assert_raises(RuntimeError) { @sip.put.sync }
+ end
+ em :test_put_post_fail
+
+ class NewTest < Minitest::Test
+ def setup
+ @sip = SipAccount::New.new(
+ username: "12345",
+ password: "old password"
+ )
+ end
+
+ def test_with_random_password
+ new_sip = @sip.with_random_password
+ refute_equal @sip.password, new_sip.password
+ refute_empty new_sip.password
+ assert_kind_of String, new_sip.password
+ end
+
+ def test_put
+ post = stub_request(
+ :post,
+ "https://api.catapult.inetwork.com/v1/users/" \
+ "catapult_user/domains/catapult_domain/endpoints"
+ ).with(
+ body: open(__dir__ + "/data/catapult_create_sip.json").read.chomp,
+ headers: {
+ "Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0",
+ "Content-Type" => "application/json"
+ }
+ ).to_return(
+ status: 201,
+ headers: { "Location" => "http://example.com/endpoint" }
+ )
+
+ new_sip = @sip.put.sync
+ assert_equal "http://example.com/endpoint", new_sip.url
+ assert_requested post
+ end
+ em :test_put
+ end
+end