flagsmith 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +4 -1
  4. data/.rubocop_todo.yml +0 -1
  5. data/.ruby-version +1 -0
  6. data/Gemfile.lock +35 -6
  7. data/LICENCE +4 -5
  8. data/README.md +8 -64
  9. data/Rakefile +5 -1
  10. data/example/.env.development +5 -0
  11. data/example/.env.test +4 -0
  12. data/example/.gitignore +5 -0
  13. data/example/.hanamirc +3 -0
  14. data/example/.rspec +2 -0
  15. data/example/Gemfile +25 -0
  16. data/example/Gemfile.lock +269 -0
  17. data/example/README.md +29 -0
  18. data/example/Rakefile +9 -0
  19. data/example/apps/web/application.rb +162 -0
  20. data/example/apps/web/config/routes.rb +1 -0
  21. data/example/apps/web/controllers/.gitkeep +0 -0
  22. data/example/apps/web/controllers/home/index.rb +32 -0
  23. data/example/apps/web/templates/application.html.slim +7 -0
  24. data/example/apps/web/templates/home/index.html.slim +10 -0
  25. data/example/apps/web/views/application_layout.rb +7 -0
  26. data/example/apps/web/views/home/index.rb +29 -0
  27. data/example/config/boot.rb +2 -0
  28. data/example/config/environment.rb +17 -0
  29. data/example/config/initializers/.gitkeep +0 -0
  30. data/example/config/initializers/flagsmith.rb +9 -0
  31. data/example/config/puma.rb +15 -0
  32. data/example/config.ru +3 -0
  33. data/example/spec/example/entities/.gitkeep +0 -0
  34. data/example/spec/example/mailers/.gitkeep +0 -0
  35. data/example/spec/example/repositories/.gitkeep +0 -0
  36. data/example/spec/features_helper.rb +12 -0
  37. data/example/spec/spec_helper.rb +103 -0
  38. data/example/spec/support/.gitkeep +0 -0
  39. data/example/spec/support/capybara.rb +8 -0
  40. data/example/spec/web/controllers/.gitkeep +0 -0
  41. data/example/spec/web/controllers/home/index_spec.rb +9 -0
  42. data/example/spec/web/features/.gitkeep +0 -0
  43. data/example/spec/web/views/application_layout_spec.rb +10 -0
  44. data/example/spec/web/views/home/index_spec.rb +10 -0
  45. data/lib/flagsmith/engine/core.rb +88 -0
  46. data/lib/flagsmith/engine/environments/models.rb +61 -0
  47. data/lib/flagsmith/engine/features/models.rb +173 -0
  48. data/lib/flagsmith/engine/identities/models.rb +115 -0
  49. data/lib/flagsmith/engine/organisations/models.rb +28 -0
  50. data/lib/flagsmith/engine/projects/models.rb +31 -0
  51. data/lib/flagsmith/engine/segments/constants.rb +41 -0
  52. data/lib/flagsmith/engine/segments/evaluator.rb +68 -0
  53. data/lib/flagsmith/engine/segments/models.rb +121 -0
  54. data/lib/flagsmith/engine/utils/hash_func.rb +34 -0
  55. data/lib/flagsmith/hash_slice.rb +12 -0
  56. data/lib/flagsmith/sdk/analytics_processor.rb +39 -0
  57. data/lib/flagsmith/sdk/api_client.rb +47 -0
  58. data/lib/flagsmith/sdk/config.rb +91 -0
  59. data/lib/flagsmith/sdk/errors.rb +9 -0
  60. data/lib/flagsmith/sdk/instance_methods.rb +137 -0
  61. data/lib/flagsmith/sdk/intervals.rb +24 -0
  62. data/lib/flagsmith/sdk/models/flag.rb +62 -0
  63. data/lib/flagsmith/sdk/models/flags/collection.rb +105 -0
  64. data/lib/flagsmith/sdk/pooling_manager.rb +31 -0
  65. data/lib/flagsmith/version.rb +5 -0
  66. data/lib/flagsmith.rb +79 -101
  67. metadata +104 -6
  68. data/.gitignore +0 -57
  69. data/flagsmith.gemspec +0 -22
@@ -0,0 +1,162 @@
1
+ require 'hanami/helpers'
2
+ require 'hanami/assets'
3
+
4
+ require_relative '../../../lib/flagsmith'
5
+
6
+ module Web
7
+ class Application < Hanami::Application
8
+ configure do
9
+ root __dir__
10
+
11
+ load_paths << %w[
12
+ controllers
13
+ views
14
+ ]
15
+
16
+ routes 'config/routes'
17
+
18
+ layout :application # It will load Web::Views::ApplicationLayout
19
+
20
+ templates 'templates'
21
+
22
+ ##
23
+ # ASSETS
24
+ #
25
+ assets do
26
+ # In order to skip JavaScript compression comment the following line
27
+ javascript_compressor :builtin
28
+
29
+ # In order to skip stylesheet compression comment the following line
30
+ stylesheet_compressor :builtin
31
+ end
32
+
33
+ ##
34
+ # SECURITY
35
+ #
36
+
37
+ # X-Frame-Options is a HTTP header supported by modern browsers.
38
+ # It determines if a web page can or cannot be included via <frame> and
39
+ # <iframe> tags by untrusted domains.
40
+ #
41
+ # Web applications can send this header to prevent Clickjacking attacks.
42
+ #
43
+ # Read more at:
44
+ #
45
+ # * https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options
46
+ # * https://www.owasp.org/index.php/Clickjacking
47
+ #
48
+ security.x_frame_options 'DENY'
49
+
50
+ # X-Content-Type-Options prevents browsers from interpreting files as
51
+ # something else than declared by the content type in the HTTP headers.
52
+ #
53
+ # Read more at:
54
+ #
55
+ # * https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#X-Content-Type-Options
56
+ # * https://msdn.microsoft.com/en-us/library/gg622941%28v=vs.85%29.aspx
57
+ # * https://blogs.msdn.microsoft.com/ie/2008/09/02/ie8-security-part-vi-beta-2-update
58
+ #
59
+ security.x_content_type_options 'nosniff'
60
+
61
+ # X-XSS-Protection is a HTTP header to determine the behavior of the
62
+ # browser in case an XSS attack is detected.
63
+ #
64
+ # Read more at:
65
+ #
66
+ # * https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
67
+ # * https://www.owasp.org/index.php/OWASP_Secure_Headers_Project#X-XSS-Protection
68
+ #
69
+ security.x_xss_protection '1; mode=block'
70
+
71
+ # Content-Security-Policy (CSP) is a HTTP header supported by modern
72
+ # browsers. It determines trusted sources of execution for dynamic
73
+ # contents (JavaScript) or other web related assets: stylesheets, images,
74
+ # fonts, plugins, etc.
75
+ #
76
+ # Web applications can send this header to mitigate Cross Site Scripting
77
+ # (XSS) attacks.
78
+ #
79
+ # The default value allows images, scripts, AJAX, fonts and CSS from the
80
+ # same origin, and does not allow any other resources to load (eg object,
81
+ # frame, media, etc).
82
+ #
83
+ # Inline JavaScript is NOT allowed. To enable it, please use:
84
+ # "script-src 'unsafe-inline'".
85
+ #
86
+ # Content Security Policy introduction:
87
+ #
88
+ # * http://www.html5rocks.com/en/tutorials/security/content-security-policy/
89
+ # * https://www.owasp.org/index.php/Content_Security_Policy
90
+ # * https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29
91
+ #
92
+ # Inline and eval JavaScript risks:
93
+ #
94
+ # * http://www.html5rocks.com/en/tutorials/security/content-security-policy/#inline-code-considered-harmful
95
+ # * http://www.html5rocks.com/en/tutorials/security/content-security-policy/#eval-too
96
+ #
97
+ # Content Security Policy usage:
98
+ #
99
+ # * http://content-security-policy.com/
100
+ # * https://developer.mozilla.org/en-US/docs/Web/Security/CSP/Using_Content_Security_Policy
101
+ #
102
+ # Content Security Policy references:
103
+ #
104
+ # * https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives
105
+ #
106
+ security.content_security_policy %(
107
+ form-action 'self';
108
+ frame-ancestors 'self';
109
+ base-uri 'self';
110
+ default-src 'none';
111
+ script-src 'self';
112
+ connect-src 'self';
113
+ img-src 'self' https: data:;
114
+ style-src 'self' 'unsafe-inline' https:;
115
+ font-src 'self';
116
+ object-src 'none';
117
+ plugin-types application/pdf;
118
+ child-src 'self';
119
+ frame-src 'self';
120
+ media-src 'self'
121
+ )
122
+
123
+ ##
124
+ # FRAMEWORKS
125
+ #
126
+
127
+ # Configure the code that will yield each time Web::Action is included
128
+ # This is useful for sharing common functionality
129
+ #
130
+ # See: http://www.rubydoc.info/gems/hanami-controller#Configuration
131
+ controller.prepare do
132
+ # include MyAuthentication # included in all the actions
133
+ # before :authenticate! # run an authentication before callback
134
+ end
135
+
136
+ # Configure the code that will yield each time Web::View is included
137
+ # This is useful for sharing common functionality
138
+ #
139
+ # See: http://www.rubydoc.info/gems/hanami-view#Configuration
140
+ view.prepare do
141
+ include Hanami::Helpers
142
+ include Web::Assets::Helpers
143
+ end
144
+ end
145
+
146
+ ##
147
+ # DEVELOPMENT
148
+ #
149
+ configure :development do
150
+ # Don't handle exceptions, render the stack trace
151
+ handle_exceptions false
152
+ end
153
+
154
+ ##
155
+ # TEST
156
+ #
157
+ configure :test do
158
+ # Don't handle exceptions, render the stack trace
159
+ handle_exceptions false
160
+ end
161
+ end
162
+ end
@@ -0,0 +1 @@
1
+ get '/', to: 'home#index'
File without changes
@@ -0,0 +1,32 @@
1
+ module Web
2
+ module Controllers
3
+ module Home
4
+ class Index
5
+ include Web::Action
6
+
7
+ expose :identifier, :show_button, :button_color
8
+
9
+ def call(params)
10
+ @identifier = params.get(:flagsmith, :identifier)
11
+
12
+ if @identifier.nil? || @identifier.blank?
13
+ # Get the default flags for the current environment
14
+ flags = $flagsmith.get_environment_flags
15
+ @show_button = flags.is_feature_enabled("secret_button")
16
+ @button_data = JSON.parse(flags.get_feature_value("secret_button"))["colour"]
17
+ else
18
+ trait_key = params.get(:flagsmith, :trait_key)
19
+ trait_value = params.get(:flagsmith, :trait_value)
20
+ traits = trait_key.nil? ? nil : { trait_key: trait_value }
21
+
22
+ # Get the flags for an identity, including the provided trait which will be
23
+ # persisted to the API for future requests.
24
+ identity_flags = $flagsmith.get_identity_flags(identifier, traits)
25
+ @show_button = identity_flags.is_feature_enabled('secret_button')
26
+ @button_color = JSON.parse(identity_flags.get_feature_value('secret_button'))['colour']
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ doctype html
2
+ html
3
+ head
4
+ title Flagsmith Example
5
+ body
6
+ main[style="max-width: 768px; margin: auto; text-align: center"]
7
+ = yield
@@ -0,0 +1,10 @@
1
+ p
2
+ ' Hello,
3
+ = (identifier.nil? || identifier.blank?) ? 'World' : identifier
4
+
5
+ = if show_button
6
+ button[style="background-color: #{button_color}"] A secret button
7
+
8
+ p
9
+
10
+ = form
@@ -0,0 +1,7 @@
1
+ module Web
2
+ module Views
3
+ class ApplicationLayout
4
+ include Web::Layout
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ module Web
2
+ module Views
3
+ module Home
4
+ class Index
5
+ include Web::View
6
+
7
+ def form
8
+ form_for :flagsmith, '/', method: :get do
9
+ h3 'Identify as a User'
10
+ div style: 'margin-bottom: 1em;' do
11
+ label :identifier
12
+ text_field :identifier
13
+ end
14
+ p '... with an optional user trait'
15
+ div style: 'margin-bottom: 1em;' do
16
+ label :trait_key
17
+ text_field :trait_key
18
+ end
19
+ div style: 'margin-bottom: 1em;' do
20
+ label :trait_value
21
+ text_field :trait_value
22
+ end
23
+ div { submit 'Identify!' }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,2 @@
1
+ require_relative './environment'
2
+ Hanami.boot
@@ -0,0 +1,17 @@
1
+ require 'bundler/setup'
2
+ require 'hanami/setup'
3
+ require 'hanami/model'
4
+ require_relative '../apps/web/application'
5
+
6
+ Hanami.configure do
7
+ mount Web::Application, at: '/'
8
+
9
+ model do
10
+ adapter :sql, ENV.fetch('DATABASE_URL')
11
+ end
12
+
13
+ environment :development do
14
+ # See: https://guides.hanamirb.org/projects/logging
15
+ logger level: :debug
16
+ end
17
+ end
File without changes
@@ -0,0 +1,9 @@
1
+ $flagsmith = Flagsmith::Client.new(
2
+ enable_local_evaluation: true,
3
+ environment_refresh_interval_seconds: 60,
4
+ default_flag_handler: lambda { |feature_name|
5
+ Flagsmith::Flag.new(
6
+ feature_name: feature_name, enabled: false, value: {}.to_json, feature_id: nil
7
+ )
8
+ }
9
+ )
@@ -0,0 +1,15 @@
1
+ require_relative './environment'
2
+ workers 1
3
+
4
+ threads_count = 1
5
+ threads threads_count, threads_count
6
+
7
+ preload_app!
8
+
9
+ rackup DefaultRackup
10
+ port 2300
11
+ environment 'development'
12
+
13
+ on_worker_boot do
14
+ Hanami.boot
15
+ end
data/example/config.ru ADDED
@@ -0,0 +1,3 @@
1
+ require_relative 'config/environment'
2
+
3
+ run Hanami.app
File without changes
File without changes
File without changes
@@ -0,0 +1,12 @@
1
+ # Require this file for feature tests
2
+ require_relative './spec_helper'
3
+
4
+ require 'capybara'
5
+ require 'capybara/rspec'
6
+
7
+ RSpec.configure do |config|
8
+ config.include RSpec::FeatureExampleGroup
9
+
10
+ config.include Capybara::DSL, feature: true
11
+ config.include Capybara::RSpecMatchers, feature: true
12
+ end
@@ -0,0 +1,103 @@
1
+ # Require this file for unit tests
2
+ ENV['HANAMI_ENV'] ||= 'test'
3
+
4
+ require_relative '../config/environment'
5
+ Hanami.boot
6
+ Hanami::Utils.require!("#{__dir__}/support")
7
+
8
+ # This file was generated by the `rspec --init` command. Conventionally, all
9
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
10
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
11
+ # this file to always be loaded, without a need to explicitly require it in any
12
+ # files.
13
+ #
14
+ # Given that it is always loaded, you are encouraged to keep this file as
15
+ # light-weight as possible. Requiring heavyweight dependencies from this file
16
+ # will add to the boot time of your test suite on EVERY test run, even for an
17
+ # individual file that may not need all of that loaded. Instead, consider making
18
+ # a separate helper file that requires the additional dependencies and performs
19
+ # the additional setup, and require it from the spec files that actually need
20
+ # it.
21
+ #
22
+ # The `.rspec` file also contains a few flags that are not defaults but that
23
+ # users commonly want.
24
+ #
25
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
26
+ RSpec.configure do |config|
27
+ # rspec-expectations config goes here. You can use an alternate
28
+ # assertion/expectation library such as wrong or the stdlib/minitest
29
+ # assertions if you prefer.
30
+ config.expect_with :rspec do |expectations|
31
+ # This option will default to `true` in RSpec 4. It makes the `description`
32
+ # and `failure_message` of custom matchers include text for helper methods
33
+ # defined using `chain`, e.g.:
34
+ # be_bigger_than(2).and_smaller_than(4).description
35
+ # # => "be bigger than 2 and smaller than 4"
36
+ # ...rather than:
37
+ # # => "be bigger than 2"
38
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
39
+ end
40
+
41
+ # rspec-mocks config goes here. You can use an alternate test double
42
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
43
+ config.mock_with :rspec do |mocks|
44
+ # Prevents you from mocking or stubbing a method that does not exist on
45
+ # a real object. This is generally recommended, and will default to
46
+ # `true` in RSpec 4.
47
+ mocks.verify_partial_doubles = true
48
+ end
49
+
50
+ # The settings below are suggested to provide a good initial experience
51
+ # with RSpec, but feel free to customize to your heart's content.
52
+ =begin
53
+ # These two settings work together to allow you to limit a spec run
54
+ # to individual examples or groups you care about by tagging them with
55
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
56
+ # get run.
57
+ config.filter_run :focus
58
+ config.run_all_when_everything_filtered = true
59
+
60
+ # Allows RSpec to persist some state between runs in order to support
61
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
62
+ # you configure your source control system to ignore this file.
63
+ config.example_status_persistence_file_path = "spec/examples.txt"
64
+
65
+ # Limits the available syntax to the non-monkey patched syntax that is
66
+ # recommended. For more details, see:
67
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
68
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
69
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
70
+ config.disable_monkey_patching!
71
+
72
+ # This setting enables warnings. It's recommended, but in many cases may
73
+ # be too noisy due to issues in dependencies.
74
+ config.warnings = false
75
+
76
+ # Many RSpec users commonly either run the entire suite or an individual
77
+ # file, and it's useful to allow more verbose output when running an
78
+ # individual spec file.
79
+ if config.files_to_run.one?
80
+ # Use the documentation formatter for detailed output,
81
+ # unless a formatter has already been configured
82
+ # (e.g. via a command-line flag).
83
+ config.default_formatter = 'doc'
84
+ end
85
+
86
+ # Print the 10 slowest examples and example groups at the
87
+ # end of the spec run, to help surface which specs are running
88
+ # particularly slow.
89
+ config.profile_examples = 10
90
+
91
+ # Run specs in random order to surface order dependencies. If you find an
92
+ # order dependency and want to debug it, you can fix the order by providing
93
+ # the seed, which is printed after each run.
94
+ # --seed 1234
95
+ config.order = :random
96
+
97
+ # Seed global randomization in this process using the `--seed` CLI option.
98
+ # Setting this allows you to use `--seed` to deterministically reproduce
99
+ # test failures related to randomization by passing the same `--seed` value
100
+ # as the one that triggered the failure.
101
+ Kernel.srand config.seed
102
+ =end
103
+ end
File without changes
@@ -0,0 +1,8 @@
1
+ module RSpec
2
+ module FeatureExampleGroup
3
+ def self.included(group)
4
+ group.metadata[:type] = :feature
5
+ Capybara.app = Hanami.app
6
+ end
7
+ end
8
+ end
File without changes
@@ -0,0 +1,9 @@
1
+ RSpec.describe Web::Controllers::Home::Index, type: :action do
2
+ let(:action) { described_class.new }
3
+ let(:params) { Hash[] }
4
+
5
+ it 'is successful' do
6
+ response = action.call(params)
7
+ expect(response[0]).to eq 200
8
+ end
9
+ end
File without changes
@@ -0,0 +1,10 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Web::Views::ApplicationLayout, type: :view do
4
+ let(:layout) { Web::Views::ApplicationLayout.new({ format: :html }, "contents") }
5
+ let(:rendered) { layout.render }
6
+
7
+ it 'contains application name' do
8
+ expect(rendered).to include('Web')
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ RSpec.describe Web::Views::Home::Index, type: :view do
2
+ let(:exposures) { Hash[format: :html] }
3
+ let(:template) { Hanami::View::Template.new('apps/web/templates/home/index.html.slim') }
4
+ let(:view) { described_class.new(template, exposures) }
5
+ let(:rendered) { view.render }
6
+
7
+ it 'exposes #format' do
8
+ expect(view.format).to eq exposures.fetch(:format)
9
+ end
10
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'semantic'
4
+ require 'securerandom'
5
+
6
+ require_relative 'environments/models'
7
+ require_relative 'features/models'
8
+ require_relative 'identities/models'
9
+ require_relative 'organisations/models'
10
+ require_relative 'projects/models'
11
+ require_relative 'segments/evaluator'
12
+ require_relative 'segments/models'
13
+ require_relative 'utils/hash_func'
14
+
15
+ module Flagsmith
16
+ module Engine
17
+ # Flags engine methods
18
+ module Core
19
+ include Flagsmith::Engine::Segments::Evaluator
20
+
21
+ def get_identity_feature_state(environment, identity, feature_name, override_traits = nil)
22
+ feature_states = get_identity_feature_states_dict(environment, identity, override_traits).values
23
+
24
+ feature_state = feature_states.find { |f| f.feature.name == feature_name }
25
+
26
+ raise Flagsmith::FeatureStateNotFound, 'Feature State Not Found' if feature_state.nil?
27
+
28
+ feature_state
29
+ end
30
+
31
+ def get_identity_feature_states(environment, identity, override_traits = nil)
32
+ feature_states = get_identity_feature_states_dict(environment, identity, override_traits).values
33
+
34
+ return feature_states.select(&:enabled?) if environment.project.hide_disabled_flags
35
+
36
+ feature_states
37
+ end
38
+
39
+ def get_environment_feature_state(environment, feature_name)
40
+ features_state = environment.feature_states.find { |f| f.feature.name == feature_name }
41
+
42
+ raise Flagsmith::FeatureStateNotFound, 'Feature State Not Found' if features_state.nil?
43
+
44
+ features_state
45
+ end
46
+
47
+ def get_environment_feature_states(environment)
48
+ return environment.feature_states.select(&:enabled?) if environment.project.hide_disabled_flags
49
+
50
+ environment.feature_states
51
+ end
52
+
53
+ private
54
+
55
+ def get_identity_feature_states_dict(environment, identity, override_traits = nil)
56
+ # Get feature states from the environment
57
+ feature_states = {}
58
+ override = ->(fs) { feature_states[fs.feature.id] = fs }
59
+ environment.feature_states.each(&override)
60
+
61
+ override_by_matching_segments(environment, identity, override_traits) do |fs|
62
+ override.call(fs) unless higher_segment_priority?(feature_states, fs)
63
+ end
64
+
65
+ # Override with any feature states defined directly the identity
66
+ identity.identity_features.each(&override)
67
+ feature_states
68
+ end
69
+
70
+ # Override with any feature states defined by matching segments
71
+ def override_by_matching_segments(environment, identity, override_traits)
72
+ identity_segments = get_identity_segments(environment, identity, override_traits)
73
+ identity_segments.each do |matching_segment|
74
+ matching_segment.feature_states.each do |feature_state|
75
+ yield feature_state if block_given?
76
+ end
77
+ end
78
+ end
79
+
80
+ def higher_segment_priority?(collection, feature_state)
81
+ collection.key?(feature_state.feature.id) &&
82
+ collection[feature_state.feature.id].higher_segment_priority?(
83
+ feature_state
84
+ )
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flagsmith
4
+ module Engine
5
+ # EnvironmentModel
6
+ class Environment
7
+ attr_reader :id, :api_key
8
+ attr_accessor :project, :feature_states, :amplitude_config, :segment_config,
9
+ :mixpanel_config, :heap_config
10
+
11
+ def initialize(id:, api_key:, project:, feature_states: [])
12
+ @id = id
13
+ @api_key = api_key
14
+ @project = project
15
+ @feature_states = feature_states
16
+ end
17
+
18
+ class << self
19
+ def build(json)
20
+ project = Flagsmith::Engine::Project.build(json[:project])
21
+ feature_states = json[:feature_states].map do |fs|
22
+ Flagsmith::Engine::FeatureState.build(fs)
23
+ end
24
+
25
+ new(**json.slice(:id, :api_key).merge(project: project, feature_states: feature_states))
26
+ end
27
+ end
28
+ end
29
+
30
+ module Environments
31
+ # EnvironmentApiKeyModel
32
+ class ApiKey
33
+ attr_reader :id, :key, :created_at, :name, :client_api_key
34
+ attr_accessor :expires_at, :active
35
+
36
+ def initialize(params = {})
37
+ @id = params.fetch(:id)
38
+ @key = params.fetch(:key)
39
+ @name = params.fetch(:name)
40
+ @client_api_key = params.fetch(:client_api_key)
41
+ @created_at = params.fetch(:created_at, Time.now)
42
+ @expires_at = params.fetch(:expires_at, nil)
43
+ @active = params.fetch(:active, true)
44
+ end
45
+
46
+ def valid?
47
+ active && (!expires_at || expires_at > Time.now)
48
+ end
49
+
50
+ class << self
51
+ def build(json)
52
+ attributes = json.slice(:id, :key, :name, :client_api_key, :active)
53
+ attributes = attributes.merge(expires_at: Date.parse(json[:created_at])) unless json[:created_at].nil?
54
+ attributes = attributes.merge(expires_at: Date.parse(json[:expires_at])) unless json[:expires_at].nil?
55
+ new(**attributes)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end