evematic 0.1.0 → 0.1.1

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/evematic/sessions_controller.rb +21 -0
  3. data/app/controllers/evematic/sso_callbacks_controller.rb +30 -0
  4. data/app/models/evematic/access_rule.rb +17 -0
  5. data/app/models/evematic/account.rb +12 -0
  6. data/app/models/evematic/application_record.rb +2 -4
  7. data/app/models/evematic/esi/alliance.rb +25 -0
  8. data/app/models/evematic/esi/character.rb +35 -0
  9. data/app/models/evematic/esi/corporation.rb +37 -0
  10. data/app/models/evematic/identity.rb +24 -0
  11. data/config/i18n-tasks.yml +5 -4
  12. data/config/locales/en.yml +13 -0
  13. data/lib/evematic/configuration/builder.rb +74 -0
  14. data/lib/evematic/configuration.rb +20 -0
  15. data/lib/evematic/controllers/helper.rb +7 -0
  16. data/lib/evematic/controllers/helpers/authentication.rb +90 -0
  17. data/lib/evematic/controllers.rb +2 -0
  18. data/lib/evematic/engine.rb +82 -8
  19. data/lib/evematic/esi/client.rb +89 -0
  20. data/lib/evematic/esi/helper.rb +43 -0
  21. data/lib/evematic/esi.rb +2 -0
  22. data/lib/evematic/jobs/helper.rb +3 -0
  23. data/lib/evematic/models/helper.rb +31 -0
  24. data/lib/evematic/models/mixins/access_rule.rb +43 -0
  25. data/lib/evematic/models/mixins/account.rb +38 -0
  26. data/lib/evematic/models/mixins/esi/alliance.rb +34 -0
  27. data/lib/evematic/models/mixins/esi/character.rb +41 -0
  28. data/lib/evematic/models/mixins/esi/corporation.rb +41 -0
  29. data/lib/evematic/models/mixins/esi/entity.rb +55 -0
  30. data/lib/evematic/models/mixins/esi.rb +2 -0
  31. data/lib/evematic/models/mixins/identity.rb +37 -0
  32. data/lib/evematic/models/mixins.rb +2 -0
  33. data/lib/evematic/models.rb +2 -0
  34. data/lib/evematic/routes/helper.rb +5 -0
  35. data/lib/evematic/routes/router.rb +10 -0
  36. data/lib/evematic/routes/routers/authentication.rb +14 -0
  37. data/lib/evematic/routes.rb +2 -0
  38. data/lib/evematic/version.rb +1 -1
  39. data/lib/evematic/views/helper.rb +2 -0
  40. data/lib/evematic/views.rb +2 -0
  41. data/lib/evematic.rb +37 -1
  42. metadata +182 -9
  43. data/config/locales/evematic.en.yml +0 -5
  44. data/config/routes.rb +0 -2
@@ -0,0 +1,43 @@
1
+ require "evematic/esi/client"
2
+
3
+ module Evematic::ESI::Helper
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def esi_delete(*args, **kwargs)
8
+ esi_client.delete(*args, **kwargs)
9
+ end
10
+
11
+ def esi_get(*args, **kwargs)
12
+ esi_client.get(*args, **kwargs)
13
+ end
14
+
15
+ def esi_post(*args, **kwargs)
16
+ esi_client.post(*args, **kwargs)
17
+ end
18
+
19
+ def esi_put(*args, **kwargs)
20
+ esi_client.put(*args, **kwargs)
21
+ end
22
+
23
+ def esi_client
24
+ @esi_client ||= Evematic::ESI::Client.new
25
+ end
26
+ end
27
+
28
+ def esi_delete(*args, **kwargs)
29
+ self.class.esi_delete(*args, **kwargs)
30
+ end
31
+
32
+ def esi_get(*args, **kwargs)
33
+ self.class.esi_get(*args, **kwargs)
34
+ end
35
+
36
+ def esi_post(*args, **kwargs)
37
+ self.class.esi_post(*args, **kwargs)
38
+ end
39
+
40
+ def esi_put(*args, **kwargs)
41
+ self.class.esi_put(*args, **kwargs)
42
+ end
43
+ end
@@ -0,0 +1,2 @@
1
+ module Evematic::ESI
2
+ end
@@ -0,0 +1,3 @@
1
+ module Evematic::Jobs::Helper
2
+ extend ActiveSupport::Concern
3
+ end
@@ -0,0 +1,31 @@
1
+ module Evematic::Models::Helper
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ [
6
+ :access_rule, :account, :identity,
7
+ :esi_alliance, :esi_character, :esi_corporation
8
+ ].each do |name|
9
+ define_method("#{name}_class") do
10
+ Evematic.config.send(:"#{name}_class")
11
+ end
12
+
13
+ define_method("#{name}_model") do
14
+ Evematic.config.send(:"#{name}_model")
15
+ end
16
+ end
17
+ end
18
+
19
+ [
20
+ :access_rule, :account, :identity,
21
+ :esi_alliance, :esi_character, :esi_corporation
22
+ ].each do |name|
23
+ define_method("#{name}_class") do
24
+ self.class.send(:"#{name}_class")
25
+ end
26
+
27
+ define_method("#{name}_model") do
28
+ self.class.send(:"#{name}_model")
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ module Evematic::Models::Mixins::AccessRule
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ self.table_name = Evematic.config.access_rules_table
6
+
7
+ belongs_to :principal, polymorphic: true
8
+
9
+ validates :action, inclusion: {in: ["allow", "deny"]}
10
+ validates :principal_type,
11
+ inclusion: {
12
+ in: [
13
+ esi_alliance_class,
14
+ esi_character_class,
15
+ esi_corporation_class
16
+ ]
17
+ }
18
+
19
+ scope :allowed, -> { where(action: "allow") }
20
+ scope :denied, -> { where(action: "deny") }
21
+ end
22
+
23
+ module ClassMethods
24
+ def allows?(character)
25
+ return false if denied.exists? { |rule| rule.matches?(character) }
26
+
27
+ return true if allowed.exists? { |rule| rule.matches?(character) }
28
+
29
+ false
30
+ end
31
+ end
32
+
33
+ def matches?(character)
34
+ case principal_type
35
+ when esi_character_class
36
+ principal_id == character.id
37
+ when esi_corporation_class
38
+ principal_id == character.corporation_id
39
+ when esi_alliance_class
40
+ principal_id == character.alliance_id
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,38 @@
1
+ module Evematic::Models::Mixins::Account
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ self.table_name = Evematic.config.accounts_table
6
+
7
+ has_one :main_identity,
8
+ -> { where(main: true) },
9
+ class_name: identity_class
10
+
11
+ has_one :main_character,
12
+ class_name: esi_character_class,
13
+ through: :main_identity,
14
+ source: :character
15
+ has_one :main_alliance,
16
+ class_name: esi_alliance_class,
17
+ through: :main_character,
18
+ source: :alliance
19
+ has_one :main_corporation,
20
+ class_name: esi_corporation_class,
21
+ through: :main_character,
22
+ source: :corporation
23
+
24
+ has_many :identities, dependent: :destroy
25
+
26
+ has_many :characters,
27
+ class_name: esi_character_class,
28
+ through: :identities
29
+ has_many :alliances,
30
+ class_name: esi_alliance_class,
31
+ through: :characters
32
+ has_many :corporations,
33
+ class_name: esi_corporation_class,
34
+ through: :characters
35
+
36
+ delegate :name, to: :main_character
37
+ end
38
+ end
@@ -0,0 +1,34 @@
1
+ module Evematic::Models::Mixins::ESI::Alliance
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ include Evematic::Models::Mixins::ESI::Entity
6
+
7
+ self.table_name = Evematic.config.esi_alliances_table
8
+
9
+ has_one :access_rule,
10
+ as: :principal,
11
+ dependent: :restrict_with_exception
12
+
13
+ has_many :corporations,
14
+ class_name: esi_corporation_class,
15
+ dependent: :restrict_with_exception
16
+ has_many :characters,
17
+ class_name: esi_character_class,
18
+ dependent: :restrict_with_exception
19
+
20
+ has_many :identities,
21
+ class_name: identity_class,
22
+ through: :characters
23
+
24
+ has_many :accounts,
25
+ class_name: account_class,
26
+ through: :identities
27
+ end
28
+
29
+ private
30
+
31
+ def fetch_esi_attributes
32
+ esi_get("/latest/alliances/#{id}/")
33
+ end
34
+ end
@@ -0,0 +1,41 @@
1
+ module Evematic::Models::Mixins::ESI::Character
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ include Evematic::Models::Mixins::ESI::Entity
6
+
7
+ self.table_name = Evematic.config.esi_characters_table
8
+
9
+ belongs_to :alliance, class_name: esi_alliance_class, optional: true
10
+ belongs_to :corporation, class_name: esi_corporation_class
11
+
12
+ has_one :access_rule,
13
+ as: :principal,
14
+ dependent: :restrict_with_exception
15
+
16
+ has_one :identity,
17
+ class_name: identity_class,
18
+ dependent: :restrict_with_exception
19
+
20
+ has_one :account,
21
+ class_name: account_class,
22
+ through: :identity
23
+
24
+ before_validation :ensure_alliance
25
+ before_validation :ensure_corporation
26
+ end
27
+
28
+ private
29
+
30
+ def fetch_esi_attributes
31
+ esi_get("/latest/characters/#{id}/")
32
+ end
33
+
34
+ def ensure_alliance
35
+ self.alliance = esi_alliance_model.find_or_create_by(id: alliance_id) if alliance_id.present?
36
+ end
37
+
38
+ def ensure_corporation
39
+ self.corporation = esi_corporation_model.find_or_create_by(id: corporation_id)
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ module Evematic::Models::Mixins::ESI::Corporation
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ include Evematic::Models::Mixins::ESI::Entity
6
+
7
+ self.table_name = Evematic.config.esi_corporations_table
8
+
9
+ belongs_to :alliance,
10
+ class_name: esi_alliance_class,
11
+ optional: true
12
+
13
+ has_one :access_rule,
14
+ as: :principal,
15
+ dependent: :restrict_with_exception
16
+
17
+ has_many :characters,
18
+ class_name: esi_character_class,
19
+ dependent: :restrict_with_exception
20
+
21
+ has_many :identities,
22
+ class_name: identity_class,
23
+ through: :characters
24
+
25
+ has_many :accounts,
26
+ class_name: account_class,
27
+ through: :identities
28
+
29
+ before_validation :ensure_alliance
30
+ end
31
+
32
+ private
33
+
34
+ def fetch_esi_attributes
35
+ esi_get("/latest/corporations/#{id}/")
36
+ end
37
+
38
+ def ensure_alliance
39
+ self.alliance = esi_alliance_model.find_or_create_by(id: alliance_id) if alliance_id.present?
40
+ end
41
+ end
@@ -0,0 +1,55 @@
1
+ module Evematic::Models::Mixins::ESI::Entity
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ include Evematic::ESI::Helper
6
+
7
+ before_validation :assign_attributes_from_esi
8
+
9
+ define_model_callbacks :esi_sync
10
+ end
11
+
12
+ def assign_attributes_from_esi
13
+ return if fresh?
14
+
15
+ res = fetch_esi_attributes
16
+
17
+ attributes = parse_json(res.body).merge(
18
+ esi_etag: res.headers["etag"].delete('"'),
19
+ esi_expires_at: DateTime.parse(res.headers["expires"]),
20
+ esi_last_modified_at: DateTime.parse(res.headers["last-modified"]).change(usec: 0)
21
+ )
22
+ transform_esi_attributes(attributes) if respond_to?(:transform_esi_attributes)
23
+ assign_attributes(attributes)
24
+ end
25
+
26
+ def fresh?
27
+ return false unless persisted?
28
+
29
+ return false if esi_expires_at.blank?
30
+
31
+ esi_expires_at >= Time.zone.now
32
+ end
33
+
34
+ def sync
35
+ run_callbacks :esi_sync do
36
+ assign_attributes_from_esi
37
+ changed? ? save : true
38
+ end
39
+ end
40
+
41
+ def sync!
42
+ run_callbacks :esi_sync do
43
+ assign_attributes_from_esi
44
+ changed? ? save! : true
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def parse_json(str)
51
+ return Oj.load(str) if defined?(Oj)
52
+
53
+ JSON.parse(str)
54
+ end
55
+ end
@@ -0,0 +1,2 @@
1
+ module Evematic::Models::Mixins::ESI
2
+ end
@@ -0,0 +1,37 @@
1
+ module Evematic::Models::Mixins::Identity
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ self.table_name = Evematic.config.identities_table
6
+
7
+ belongs_to :account, class_name: account_class
8
+ belongs_to :character, class_name: esi_character_class
9
+
10
+ has_one :alliance,
11
+ class_name: esi_alliance_class,
12
+ through: :character
13
+ has_one :corporation,
14
+ class_name: esi_corporation_class,
15
+ through: :character
16
+
17
+ delegate :name, to: :character
18
+ end
19
+
20
+ module ClassMethods
21
+ def sso_authenticate_by(character)
22
+ character.sync
23
+ character.alliance.sync if character.alliance.present?
24
+ character.corporation.sync
25
+
26
+ return unless access_rule_model.allows?(character)
27
+
28
+ identity = identity_model.find_by(character:)
29
+ return identity if identity.present?
30
+
31
+ identity = identity_model.new(character:, main: true)
32
+ identity.build_account
33
+ identity.save!
34
+ identity
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,2 @@
1
+ module Evematic::Models::Mixins
2
+ end
@@ -0,0 +1,2 @@
1
+ module Evematic::Models
2
+ end
@@ -0,0 +1,5 @@
1
+ module Evematic::Routes::Helper
2
+ def evematic_authentication(**opts)
3
+ Evematic::Routes::Routers::Authentication.new(self, **opts).draw!
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require "dry-initializer"
2
+
3
+ class Evematic::Routes::Router
4
+ extend Dry::Initializer
5
+
6
+ param :routes
7
+ option :controllers
8
+ option :path_names
9
+ option :scope, default: -> { "/" }
10
+ end
@@ -0,0 +1,14 @@
1
+ class Evematic::Routes::Routers::Authentication < Evematic::Routes::Router
2
+ option :controllers, default: -> { {sessions: "evematic/sessions", sso_callbacks: "evematic/sso_callbacks"} }
3
+ option :path_names, default: -> { {login: "login", logout: "logout", callback: "auth/eve/callback"} }
4
+
5
+ def draw!
6
+ return unless Evematic.config.authentication_enabled
7
+
8
+ routes.scope(scope) do
9
+ routes.get(path_names[:callback], to: "#{controllers[:sso_callbacks]}#eve", as: :sso_callback)
10
+ routes.get(path_names[:login], to: "#{controllers[:sessions]}#new")
11
+ routes.delete(path_names[:logout], to: "#{controllers[:sessions]}#destroy")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,2 @@
1
+ module Evematic::Routes
2
+ end
@@ -1,3 +1,3 @@
1
1
  module Evematic
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -0,0 +1,2 @@
1
+ module Evematic::Views::Helper
2
+ end
@@ -0,0 +1,2 @@
1
+ module Evematic::Views
2
+ end
data/lib/evematic.rb CHANGED
@@ -1,6 +1,42 @@
1
1
  require "evematic/version"
2
+ require "evematic/configuration"
2
3
  require "evematic/engine"
3
4
 
4
5
  module Evematic
5
- # Your code goes here...
6
+ def self.setup
7
+ configuration_builder.clear_cache!
8
+
9
+ if defined?(Config)
10
+ settings = Config.const_name.safe_constantize
11
+
12
+ configure do |config|
13
+ config.esi_client_id = settings.esi.client_id
14
+ config.esi_client_secret = settings.esi.client_secret
15
+ end
16
+ end
17
+ end
18
+
19
+ module ESI
20
+ end
21
+
22
+ module Models
23
+ module ESI
24
+ end
25
+ end
6
26
  end
27
+
28
+ require "zeitwerk"
29
+
30
+ loader = Zeitwerk::Loader.for_gem
31
+ loader.inflector.inflect(
32
+ "api" => "API",
33
+ "eve" => "EVE",
34
+ "esi" => "ESI",
35
+ "oauth" => "OAuth",
36
+ "omniauth" => "OmniAuth",
37
+ "sso" => "SSO"
38
+ )
39
+ loader.ignore("#{__dir__}/generators")
40
+ loader.enable_reloading
41
+ loader.setup
42
+ loader.eager_load