conduit-ussd 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +426 -0
- data/Rakefile +2 -0
- data/app/assets/stylesheets/conduit/application.css +15 -0
- data/app/controllers/conduit/application_controller.rb +4 -0
- data/app/helpers/conduit/application_helper.rb +4 -0
- data/app/jobs/conduit/application_job.rb +4 -0
- data/app/jobs/conduit/save_session_job.rb +11 -0
- data/app/models/conduit/application_record.rb +5 -0
- data/app/models/conduit/session_record.rb +28 -0
- data/config/routes.rb +2 -0
- data/lib/conduit/configuration.rb +25 -0
- data/lib/conduit/display_builder.rb +58 -0
- data/lib/conduit/engine.rb +21 -0
- data/lib/conduit/flow.rb +54 -0
- data/lib/conduit/middleware/logging.rb +23 -0
- data/lib/conduit/middleware/session_tracking.rb +30 -0
- data/lib/conduit/middleware/throttling.rb +41 -0
- data/lib/conduit/middleware.rb +36 -0
- data/lib/conduit/providers/africas_talking.rb +39 -0
- data/lib/conduit/request_handler.rb +126 -0
- data/lib/conduit/response.rb +23 -0
- data/lib/conduit/router.rb +30 -0
- data/lib/conduit/session.rb +73 -0
- data/lib/conduit/session_store.rb +41 -0
- data/lib/conduit/state.rb +189 -0
- data/lib/conduit/validator.rb +55 -0
- data/lib/conduit/version.rb +3 -0
- data/lib/conduit.rb +31 -0
- data/lib/generators/conduit/install/install_generator.rb +41 -0
- data/lib/generators/conduit/install/templates/conduit.rb +26 -0
- data/lib/generators/conduit/install/templates/example_flow.rb +72 -0
- data/lib/generators/conduit/install/templates/ussd_controller.rb +11 -0
- data/lib/generators/conduit/migration/migration_generator.rb +21 -0
- data/lib/generators/conduit/migration/templates/create_conduit_sessions.rb +19 -0
- data/lib/tasks/conduit_tasks.rake +4 -0
- metadata +191 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Conduit
|
|
2
|
+
class Validator
|
|
3
|
+
def self.numeric
|
|
4
|
+
lambda do |input, _session|
|
|
5
|
+
return true if input.match?(/^\d+(\.\d+)?$/)
|
|
6
|
+
"Please enter a valid number"
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.greater_than(value)
|
|
11
|
+
lambda do |input, _session|
|
|
12
|
+
num = input.to_f
|
|
13
|
+
return true if num > value
|
|
14
|
+
"Must be greater than #{value}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.less_than(value)
|
|
19
|
+
lambda do |input, _session|
|
|
20
|
+
num = input.to_f
|
|
21
|
+
return true if num < value
|
|
22
|
+
"Must be less than #{value}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.min_length(length)
|
|
27
|
+
lambda do |input, _session|
|
|
28
|
+
return true if input.length >= length
|
|
29
|
+
"Must be at least #{length} characters"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.max_length(length)
|
|
34
|
+
lambda do |input, _session|
|
|
35
|
+
return true if input.length <= length
|
|
36
|
+
"Must be at most #{length} characters"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.matches(pattern, message = "Invalid format")
|
|
41
|
+
lambda do |input, _session|
|
|
42
|
+
return true if input.match?(pattern)
|
|
43
|
+
message
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.in_range(min, max)
|
|
48
|
+
lambda do |input, _session|
|
|
49
|
+
num = input.to_f
|
|
50
|
+
return true if num.between?(min, max)
|
|
51
|
+
"Must be between #{min} and #{max}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/lib/conduit.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "conduit/version"
|
|
2
|
+
require "conduit/engine"
|
|
3
|
+
require "conduit/configuration"
|
|
4
|
+
require "conduit/router"
|
|
5
|
+
require "conduit/middleware"
|
|
6
|
+
require "conduit/middleware/logging"
|
|
7
|
+
require "conduit/middleware/throttling"
|
|
8
|
+
require "conduit/middleware/session_tracking"
|
|
9
|
+
require "conduit/display_builder"
|
|
10
|
+
require "conduit/validator"
|
|
11
|
+
|
|
12
|
+
module Conduit
|
|
13
|
+
class Error < StandardError; end
|
|
14
|
+
|
|
15
|
+
class SessionTimeout < Error; end
|
|
16
|
+
|
|
17
|
+
class InvalidTransition < Error; end
|
|
18
|
+
|
|
19
|
+
mattr_accessor :configuration
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
def configure
|
|
23
|
+
self.configuration ||= Configuration.new
|
|
24
|
+
yield(configuration) if block_given?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def process(params)
|
|
28
|
+
RequestHandler.new.process(params)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
|
|
3
|
+
module Conduit
|
|
4
|
+
module Generators
|
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
|
7
|
+
|
|
8
|
+
def create_initializer
|
|
9
|
+
template "conduit.rb", "config/initializers/conduit.rb"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create_controller
|
|
13
|
+
template "ussd_controller.rb", "app/controllers/ussd_controller.rb"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def add_routes
|
|
17
|
+
route 'post "/ussd", to: "ussd#handle"'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create_flow_directory
|
|
21
|
+
empty_directory "app/flows"
|
|
22
|
+
template "example_flow.rb", "app/flows/example_flow.rb"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def display_post_install
|
|
26
|
+
say "\n✅ Conduit installed successfully!", :green
|
|
27
|
+
say "\nNext steps:"
|
|
28
|
+
say " 1. Configure Redis in config/initializers/conduit.rb"
|
|
29
|
+
say " 2. Run 'rails generate conduit:migration' to create database table"
|
|
30
|
+
say " 3. Run 'rails db:migrate'"
|
|
31
|
+
say " 4. Create your flows in app/flows/"
|
|
32
|
+
say " 5. Map service codes to flows in the initializer"
|
|
33
|
+
say " 6. Test your USSD endpoint: POST /ussd"
|
|
34
|
+
say "\nExample AfricasTalking request:"
|
|
35
|
+
say " curl -X POST http://localhost:3000/ussd \\"
|
|
36
|
+
say ' -H "Content-Type: application/x-www-form-urlencoded" \\'
|
|
37
|
+
say ' -d "sessionId=test123&phoneNumber=254712345678&serviceCode=*123%23&text="'
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Conduit.configure do |config|
|
|
2
|
+
# Redis configuration
|
|
3
|
+
config.redis_url = ENV.fetch("REDIS_URL", "redis://localhost:6379/1")
|
|
4
|
+
config.session_ttl = 90.seconds
|
|
5
|
+
|
|
6
|
+
# Logging
|
|
7
|
+
config.logger = Rails.logger
|
|
8
|
+
|
|
9
|
+
# Session settings
|
|
10
|
+
config.max_navigation_depth = 10
|
|
11
|
+
config.save_sessions = true
|
|
12
|
+
|
|
13
|
+
# Middleware (order matters - first added is outermost)
|
|
14
|
+
config.middleware.use Conduit::Middleware::Logging
|
|
15
|
+
config.middleware.use Conduit::Middleware::Throttling, max_requests: 20, window: 60
|
|
16
|
+
config.middleware.use Conduit::Middleware::SessionTracking
|
|
17
|
+
|
|
18
|
+
# Add custom middleware
|
|
19
|
+
# config.middleware.use MyCustomMiddleware
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Route service codes to flows
|
|
23
|
+
Conduit::Router.draw do
|
|
24
|
+
route "*123#", to: ExampleFlow
|
|
25
|
+
# route "*456#", to: AnotherFlow
|
|
26
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
class ExampleFlow < Conduit::Flow
|
|
2
|
+
initial_state :welcome
|
|
3
|
+
|
|
4
|
+
state :welcome do
|
|
5
|
+
display <<~TEXT
|
|
6
|
+
Welcome to Conduit USSD Demo!
|
|
7
|
+
|
|
8
|
+
1. Get Started
|
|
9
|
+
2. About
|
|
10
|
+
3. Exit
|
|
11
|
+
TEXT
|
|
12
|
+
|
|
13
|
+
on "1", to: :get_name
|
|
14
|
+
on "2", to: :about
|
|
15
|
+
on "3" do
|
|
16
|
+
Conduit::Response.new(text: "Thank you for using Conduit! Goodbye.", action: :end)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
state :get_name do
|
|
21
|
+
display "What's your name?"
|
|
22
|
+
|
|
23
|
+
on_any do |input, session|
|
|
24
|
+
session.data[:name] = input
|
|
25
|
+
session.navigate_to(:greet)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
state :greet do
|
|
30
|
+
display do |session|
|
|
31
|
+
<<~TEXT
|
|
32
|
+
Hello #{session.data[:name]}!
|
|
33
|
+
|
|
34
|
+
You have successfully set up Conduit.
|
|
35
|
+
|
|
36
|
+
1. Try something else
|
|
37
|
+
0. Back
|
|
38
|
+
00. Home
|
|
39
|
+
TEXT
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
on "1", to: :demo_feature
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
state :about do
|
|
46
|
+
display <<~TEXT
|
|
47
|
+
Conduit v#{Conduit::VERSION}
|
|
48
|
+
|
|
49
|
+
Lightning-fast USSD framework for Rails
|
|
50
|
+
Built with ❤️ for Africa
|
|
51
|
+
|
|
52
|
+
0. Back
|
|
53
|
+
00. Home
|
|
54
|
+
TEXT
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
state :demo_feature do
|
|
58
|
+
display do |session|
|
|
59
|
+
<<~TEXT
|
|
60
|
+
#{session.data[:name]}, this is where you'd add your features!
|
|
61
|
+
|
|
62
|
+
Current session info:
|
|
63
|
+
- ID: #{session.session_id}
|
|
64
|
+
- Phone: #{session.msisdn}
|
|
65
|
+
- Duration: #{session.duration.to_i}s
|
|
66
|
+
|
|
67
|
+
0. Back
|
|
68
|
+
00. Home
|
|
69
|
+
TEXT
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class UssdController < ApplicationController
|
|
2
|
+
skip_before_action :verify_authenticity_token
|
|
3
|
+
|
|
4
|
+
def handle
|
|
5
|
+
# Process AfricasTalking USSD request
|
|
6
|
+
response = Conduit.process(params)
|
|
7
|
+
|
|
8
|
+
# Return plain text response for AfricasTalking
|
|
9
|
+
render plain: response, content_type: "text/plain"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
require "rails/generators/active_record"
|
|
3
|
+
|
|
4
|
+
module Conduit
|
|
5
|
+
module Generators
|
|
6
|
+
class MigrationGenerator < Rails::Generators::Base
|
|
7
|
+
include ActiveRecord::Generators::Migration
|
|
8
|
+
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
def create_migration_file
|
|
12
|
+
migration_template "create_conduit_sessions.rb", "db/migrate/create_conduit_sessions.rb"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def display_post_install
|
|
16
|
+
say "\n✅ Migration created!", :green
|
|
17
|
+
say "\nRun 'rails db:migrate' to create the conduit_sessions table"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class CreateConduitSessions < ActiveRecord::Migration[8.0] # this needs fixing to make it dynamic?
|
|
2
|
+
def change
|
|
3
|
+
create_table :conduit_sessions do |t|
|
|
4
|
+
t.string :session_id, null: false, index: {unique: true}
|
|
5
|
+
t.string :msisdn, null: false, index: true
|
|
6
|
+
t.string :service_code
|
|
7
|
+
t.string :final_state
|
|
8
|
+
t.jsonb :data, default: {}
|
|
9
|
+
t.integer :duration_seconds
|
|
10
|
+
t.boolean :completed, default: false
|
|
11
|
+
t.datetime :started_at, null: false
|
|
12
|
+
t.datetime :completed_at
|
|
13
|
+
t.timestamps null: false
|
|
14
|
+
|
|
15
|
+
t.index [:msisdn, :created_at]
|
|
16
|
+
t.index :created_at
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: conduit-ussd
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Charles Chuck
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 8.0.2
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 8.0.2
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: redis
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '5.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '5.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: connection_pool
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.4'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.4'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: standard
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rubocop-rails-omakase
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rspec-rails
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '8.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '8.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: fakeredis
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: pry-rails
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0'
|
|
124
|
+
description: Build USSD applications with an expressive DSL. Redis-backed sessions,
|
|
125
|
+
AfricasTalking integration, built for the 60-120 second constraint.
|
|
126
|
+
email:
|
|
127
|
+
- chalcchuck@email.com
|
|
128
|
+
executables: []
|
|
129
|
+
extensions: []
|
|
130
|
+
extra_rdoc_files: []
|
|
131
|
+
files:
|
|
132
|
+
- MIT-LICENSE
|
|
133
|
+
- README.md
|
|
134
|
+
- Rakefile
|
|
135
|
+
- app/assets/stylesheets/conduit/application.css
|
|
136
|
+
- app/controllers/conduit/application_controller.rb
|
|
137
|
+
- app/helpers/conduit/application_helper.rb
|
|
138
|
+
- app/jobs/conduit/application_job.rb
|
|
139
|
+
- app/jobs/conduit/save_session_job.rb
|
|
140
|
+
- app/models/conduit/application_record.rb
|
|
141
|
+
- app/models/conduit/session_record.rb
|
|
142
|
+
- config/routes.rb
|
|
143
|
+
- lib/conduit.rb
|
|
144
|
+
- lib/conduit/configuration.rb
|
|
145
|
+
- lib/conduit/display_builder.rb
|
|
146
|
+
- lib/conduit/engine.rb
|
|
147
|
+
- lib/conduit/flow.rb
|
|
148
|
+
- lib/conduit/middleware.rb
|
|
149
|
+
- lib/conduit/middleware/logging.rb
|
|
150
|
+
- lib/conduit/middleware/session_tracking.rb
|
|
151
|
+
- lib/conduit/middleware/throttling.rb
|
|
152
|
+
- lib/conduit/providers/africas_talking.rb
|
|
153
|
+
- lib/conduit/request_handler.rb
|
|
154
|
+
- lib/conduit/response.rb
|
|
155
|
+
- lib/conduit/router.rb
|
|
156
|
+
- lib/conduit/session.rb
|
|
157
|
+
- lib/conduit/session_store.rb
|
|
158
|
+
- lib/conduit/state.rb
|
|
159
|
+
- lib/conduit/validator.rb
|
|
160
|
+
- lib/conduit/version.rb
|
|
161
|
+
- lib/generators/conduit/install/install_generator.rb
|
|
162
|
+
- lib/generators/conduit/install/templates/conduit.rb
|
|
163
|
+
- lib/generators/conduit/install/templates/example_flow.rb
|
|
164
|
+
- lib/generators/conduit/install/templates/ussd_controller.rb
|
|
165
|
+
- lib/generators/conduit/migration/migration_generator.rb
|
|
166
|
+
- lib/generators/conduit/migration/templates/create_conduit_sessions.rb
|
|
167
|
+
- lib/tasks/conduit_tasks.rake
|
|
168
|
+
homepage: https://github.com/chalchuck/conduit
|
|
169
|
+
licenses:
|
|
170
|
+
- MIT
|
|
171
|
+
metadata:
|
|
172
|
+
homepage_uri: https://github.com/chalchuck/conduit
|
|
173
|
+
source_code_uri: https://github.com/chalchuck/conduit
|
|
174
|
+
rdoc_options: []
|
|
175
|
+
require_paths:
|
|
176
|
+
- lib
|
|
177
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
178
|
+
requirements:
|
|
179
|
+
- - ">="
|
|
180
|
+
- !ruby/object:Gem::Version
|
|
181
|
+
version: 3.4.1
|
|
182
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
183
|
+
requirements:
|
|
184
|
+
- - ">="
|
|
185
|
+
- !ruby/object:Gem::Version
|
|
186
|
+
version: '0'
|
|
187
|
+
requirements: []
|
|
188
|
+
rubygems_version: 3.6.9
|
|
189
|
+
specification_version: 4
|
|
190
|
+
summary: Lightning-fast USSD flow engine for Rails
|
|
191
|
+
test_files: []
|