virtuatable-core 1.0.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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/lib/core/models/account.rb +102 -0
  3. data/lib/core/models/authentication/session.rb +30 -0
  4. data/lib/core/models/authentication.rb +9 -0
  5. data/lib/core/models/campaign.rb +110 -0
  6. data/lib/core/models/campaigns/invitation.rb +31 -0
  7. data/lib/core/models/campaigns/tag.rb +23 -0
  8. data/lib/core/models/campaigns.rb +10 -0
  9. data/lib/core/models/chatrooms/base.rb +16 -0
  10. data/lib/core/models/chatrooms/campaign.rb +13 -0
  11. data/lib/core/models/chatrooms/conversation.rb +9 -0
  12. data/lib/core/models/chatrooms/membership.rb +17 -0
  13. data/lib/core/models/chatrooms/message.rb +33 -0
  14. data/lib/core/models/chatrooms.rb +13 -0
  15. data/lib/core/models/concerns/activable.rb +20 -0
  16. data/lib/core/models/concerns/diagnosticable.rb +24 -0
  17. data/lib/core/models/concerns/enumerable.rb +50 -0
  18. data/lib/core/models/concerns/historizable.rb +64 -0
  19. data/lib/core/models/concerns/mime_typable.rb +44 -0
  20. data/lib/core/models/concerns/premiumable.rb +17 -0
  21. data/lib/core/models/concerns/sluggable.rb +29 -0
  22. data/lib/core/models/concerns/typable.rb +19 -0
  23. data/lib/core/models/concerns.rb +16 -0
  24. data/lib/core/models/decorators/errors/env_variable_missing.rb +16 -0
  25. data/lib/core/models/decorators/errors.rb +11 -0
  26. data/lib/core/models/decorators/gateway.rb +111 -0
  27. data/lib/core/models/event.rb +31 -0
  28. data/lib/core/models/factories/errors/gateway_not_found.rb +16 -0
  29. data/lib/core/models/factories/errors.rb +11 -0
  30. data/lib/core/models/factories.rb +10 -0
  31. data/lib/core/models/files/document.rb +52 -0
  32. data/lib/core/models/files/permission.rb +24 -0
  33. data/lib/core/models/files.rb +8 -0
  34. data/lib/core/models/monitoring/route.rb +44 -0
  35. data/lib/core/models/monitoring/service.rb +33 -0
  36. data/lib/core/models/monitoring.rb +10 -0
  37. data/lib/core/models/notification.rb +24 -0
  38. data/lib/core/models/oauth/access_token.rb +34 -0
  39. data/lib/core/models/oauth/application.rb +58 -0
  40. data/lib/core/models/oauth/authorization.rb +33 -0
  41. data/lib/core/models/oauth/refresh_token.rb +20 -0
  42. data/lib/core/models/oauth.rb +12 -0
  43. data/lib/core/models/permissions/category.rb +17 -0
  44. data/lib/core/models/permissions/group.rb +32 -0
  45. data/lib/core/models/permissions/right.rb +21 -0
  46. data/lib/core/models/permissions.rb +12 -0
  47. data/lib/core/models/ruleset.rb +32 -0
  48. data/lib/core/models.rb +27 -0
  49. data/lib/core/version.rb +3 -0
  50. data/lib/core.rb +10 -0
  51. metadata +329 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: eaef58986378462da6ff78e3aeac595cbed418f3351254e3e99b303059fe21d1
4
+ data.tar.gz: 1ed97f4ff856ecfbe5ccf969547ee0141f03f1e9c59304661880f6d4e87bfaf7
5
+ SHA512:
6
+ metadata.gz: ffc760f437db7e376de09c5e88fa794e440729600819f8cce7474d4db6a122185fc20472020e131ae889d6371f1cf95f0fb7389506ac4ec1f5a9840a3ed2dc98
7
+ data.tar.gz: '08addb4e9612082536359a2a624f5eded215ba382a8f9b16aebf016cfc35b9c9dbc12a503a3160780dc81896bdabf0434f1966062d5eb339e999eadef92cbec1'
@@ -0,0 +1,102 @@
1
+ module Core
2
+ module Models
3
+ # A user account with all related attributes. It holds credentials and informations about a designated user.
4
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
5
+ class Account
6
+ include Mongoid::Document
7
+ include Mongoid::Timestamps
8
+ include ActiveModel::SecurePassword
9
+ include Core::Models::Concerns::Enumerable
10
+
11
+ # @!attribute [rw] username
12
+ # @return [String] the nickname the user chose at subscription, must be given, unique, and 6 or more characters long.
13
+ field :username, type: String
14
+ # @!attribute [r] password_digest
15
+ # @return [String] the password of the user, encrypted with the Blowfish algorithm.
16
+ field :password_digest, type: String
17
+ # @!attribute [rw] lastname
18
+ # @return [String] the last name (family name) of the user.
19
+ field :lastname, type: String, default: ''
20
+ # @!attribute [rw] firstname
21
+ # @return [String] the first name of the user.
22
+ field :firstname, type: String, default: ''
23
+ # @!attribute [rw] email
24
+ # @return [String] the email address of the user, useful to contact them ; it must be given, unique, and have an email format.
25
+ field :email, type: String
26
+ # @!attribute [rw] language
27
+ # @return [Symbol] the language preferred by this user.
28
+ enum_field :language, [:en_GB, :fr_FR], default: :fr_FR
29
+ # @!attribute [rw] gender
30
+ # @return [Symbol] the way you prefer the application to gender you.
31
+ enum_field :gender, [:female, :male, :neutral], default: :neutral
32
+
33
+ # @!attribute [w] password
34
+ # @return [String] password, in clear, of the user ; do not attempt to get the value, just set it when changing the password.
35
+ # @!attribute [w] password_confirmation
36
+ # @return [String] the confirmation of the password, do not get, just set it ; it must be the same as the password.
37
+ has_secure_password validations: false
38
+
39
+ # @!attribute [rw] groups
40
+ # @return [Array<Core::Models::Permissions::Group>] the groups giving their corresponding rights to the current account.
41
+ has_and_belongs_to_many :groups, class_name: 'Core::Models::Permissions::Group', inverse_of: :accounts
42
+
43
+ # @!attribute [rw] applications
44
+ # @return [Array<Core::Models::OAuth::Application] the applications this user has created and owns.
45
+ has_many :applications, class_name: 'Core::Models::OAuth::Application', inverse_of: :creator
46
+ # @!attribute [rw] authorizations
47
+ # @return [Array<Core::Models::OAuth::Authorization>] the authorization issued by this account to third-party applications to access its data.
48
+ has_many :authorizations, class_name: 'Core::Models::OAuth::Authorization', inverse_of: :account
49
+ # @!attribute [rw] services
50
+ # @return [Array<Core::Models::Monitoring::Service>] the services created by this user.
51
+ has_many :services, class_name: 'Core::Models::Monitoring::Service', inverse_of: :creator
52
+ # @!attribute [rw] sessions
53
+ # @return [Array<Core::Models::Authentication::Session>] the sessions on which this account is, or has been logged in.
54
+ has_many :sessions, class_name: 'Core::Models::Authentication::Session', inverse_of: :account
55
+ # @!attribute [rw] invitations
56
+ # @return [Array<Core::Models::Campaigns::Invitation>] the invitations in campaigns you have been issued.
57
+ has_many :invitations, class_name: 'Core::Models::Campaigns::Invitation', inverse_of: :account
58
+ # @!attribute [rw] invitations
59
+ # @return [Array<Core::Models::Campaigns::Invitation>] the invitations you've issued yourself to other players.
60
+ has_many :created_invitations, class_name: 'Core::Models::Campaigns::Invitation', inverse_of: :creator
61
+ # @!attribute [rw] permissions
62
+ # @return [Array<Core::Models::Files::Permission>] the file access permissions granted to this account.
63
+ has_many :permissions, class_name: 'Core::Models::Files::Permission', inverse_of: :account
64
+ # @!attribute [rw] messages
65
+ # @return [Array<Core::Models::Chatrooms::Messages>] all the messages ever sent by the user.
66
+ has_many :messages, class_name: 'Core::Models::Chatrooms::Message', inverse_of: :account
67
+
68
+ has_many :memberships, class_name: 'Core::Models::Chatrooms::Membership', inverse_of: :account
69
+
70
+ # @!attribute [rw] notifications
71
+ # @return [Array<Core::Models::Notification>] the notifications linked to this user.
72
+ embeds_many :notifications, class_name: 'Core::Models::Notification', inverse_of: :account
73
+
74
+ # @return [Array<Core::Models::Notification>] the unread notifications that should be displayed first for the user.
75
+ def unread_notifications
76
+ notifications.where(read: false)
77
+ end
78
+
79
+ # @return [Array<Core::Models::Notification>] the notifications already read, less important to display than the unread ones.
80
+ def read_notifications
81
+ notifications.where(read: true)
82
+ end
83
+
84
+ validates :username,
85
+ presence: {message: 'required'},
86
+ length: {minimum: 6, message: 'minlength', if: :username?},
87
+ uniqueness: {message: 'uniq', if: :username?}
88
+
89
+ validates :email,
90
+ presence: {message: 'required'},
91
+ format: {with: /\A[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\z/, message: 'pattern', if: :email?},
92
+ uniqueness: {message: 'uniq', if: :email?}
93
+
94
+ validates :password,
95
+ presence: {message: 'required', if: ->{ !persisted? || password_digest_changed? }},
96
+ confirmation: {message: 'confirmation', if: :password_digest_changed?}
97
+
98
+ validates :password_confirmation,
99
+ presence: {message: 'required', if: :password_digest_changed?}
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,30 @@
1
+ module Core
2
+ module Models
3
+ module Authentication
4
+ # A session represents the connection of the user on our frontend application. Nobody else than our frontend should
5
+ # have access to the session or it's content (in particular to the token), instead they shall use the OAuth2.0 protocol.
6
+ # A session shall ONLY be created by a premium application (only our frontend applications are premium).
7
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
8
+ class Session
9
+ include Mongoid::Document
10
+ include Mongoid::Timestamps
11
+
12
+ # @!attribute [rw] token
13
+ # @return [String] the unique token for this session, used to identify it and be sure the user is connected on this application.
14
+ field :token, type: String
15
+ # @!attribute [rw] websocket_id
16
+ # @return [String] the ID of the websocket on which the session is connected. It's not an association because instances are embedded.
17
+ field :websocket_id, type: String, default: ''
18
+
19
+ # @!attribute [rw] account
20
+ # @return [Core::Models::Account] the account connected to the application.
21
+ belongs_to :account, class_name: 'Core::Models::Account', inverse_of: :sessions
22
+
23
+ validates :token,
24
+ presence: {message: 'required'},
25
+ uniqueness: {message: 'uniq', if: :token?},
26
+ length: {minimum: 10, message: 'minlength', if: :token?}
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ module Core
2
+ module Models
3
+ # This module holds the logic for user authentication to our frontend.
4
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
5
+ module Authentication
6
+ autoload :Session, 'core/models/authentication/session'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,110 @@
1
+ module Core
2
+ module Models
3
+ # A campaign is a gathering of accounts playing on the same interface, and interacting in a common game.
4
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
5
+ class Campaign
6
+ include Mongoid::Document
7
+ include Mongoid::Timestamps
8
+
9
+ # @!attribute [rw] title
10
+ # @return [String] the title, or name, of the campaign, used to identify it in the list.
11
+ field :title, type: String
12
+ # @!attribute [rw] description
13
+ # @return [String] a more detailed description, used to give further information about the campaign in general.
14
+ field :description, type: String
15
+ # @!attribute [rw] is_private
16
+ # @return [Boolean] TRUE if the campaign can be joined only by being invited by the creator, FALSE if it's publicly displayed and accessible.
17
+ field :is_private, type: Boolean, default: true
18
+ # @!attribute [rw] tags
19
+ # @return [Array<String>] an array of tags describing characteristics of this campaign.
20
+ field :tags, type: Array, default: []
21
+ # @!attributes [rw] max_players
22
+ # @return [Integer] the maximum number of players allowed in this campaign.
23
+ field :max_players, type: Integer, default: 5
24
+
25
+ # @!attribute [rw] invitations
26
+ # @return [Array<Core::Models::Campaigns::Invitation>] the invitations to players that have been made for this campaign.
27
+ has_many :invitations, class_name: 'Core::Models::Campaigns::Invitation', inverse_of: :campaign
28
+ # @!attribute [rw] files
29
+ # @return [Array<Core::Models::Files::Document>] the files uploaded in this campaign.
30
+ has_many :files, class_name: 'Core::Models::Files::Document'
31
+
32
+ # @!attribute [rw] chatroom
33
+ # @return [Core::Models::Chatrooms::Campaign] the chatroom linked to this campaign.
34
+ embeds_one :chatroom, class_name: 'Core::Models::Chatrooms::Campaign', inverse_of: :campaign
35
+
36
+ # @!attribute [rw] ruleset
37
+ # @return [Core::Models::Ruleset] the set of rules this campaign is based upon.
38
+ belongs_to :ruleset, class_name: 'Core::Models::Ruleset', inverse_of: :campaigns, optional: true
39
+
40
+ validates :title,
41
+ presence: {message: 'required'},
42
+ length: {minimum: 4, message: 'minlength', if: :title?}
43
+
44
+ validates :max_players,
45
+ numericality: {less_than: 21, message: 'maximum'}
46
+
47
+ validate :title_unicity
48
+
49
+ validate :max_players_minimum
50
+
51
+ # Sets the creator of the campaign. This method is mainly used for backward-compatibility needs.
52
+ # @param account [Core::Models::Account] the account of the creator for this campaign.
53
+ def creator=(account)
54
+ if !invitations.where(account: account).exists?
55
+ invitation = Core::Models::Campaigns::Invitation.create(
56
+ campaign: self,
57
+ account: account,
58
+ status: :creator
59
+ )
60
+ end
61
+ end
62
+
63
+ # Getter for the creator account of this campaign.
64
+ # @return [Core::Models::Account] the account of the player creating this campaign.
65
+ def creator
66
+ invitations.to_a.find(&:status_creator?).account
67
+ end
68
+
69
+ # Adds an error message if the account creating this campaign already has a campaign with the very same name.
70
+ def title_unicity
71
+ # First we take all the other campaign ids of the user.
72
+ campaign_ids = creator.invitations.where(:campaign_id.ne => _id).pluck(:campaign_id)
73
+ # With this list of campaign IDs, we look for a campaign with the same title.
74
+ same_title_campaign = Core::Models::Campaign.where(:_id.in => campaign_ids, title: title)
75
+ if !creator.nil? && title? && same_title_campaign.exists?
76
+ errors.add(:title, 'uniq')
77
+ end
78
+ end
79
+
80
+ # Validation for the max number of players for a campaign.
81
+ # If there is a max number of players, and the current number of
82
+ # players is above it, or the max number of players is 0, raises an error.
83
+ def max_players_minimum
84
+ if max_players? && (max_players < players_count || max_players < 1)
85
+ errors.add(:max_players, 'minimum')
86
+ end
87
+ end
88
+
89
+ # @return [Array<Core::Models::Campaigns::Invitation>] the players in this campaign.
90
+ def players
91
+ invitations.to_a.select do |invitation|
92
+ [:creator, :accepted].include? invitation.enum_status
93
+ end
94
+ end
95
+
96
+ # @return [Integer] the number of players in this campaign.
97
+ def players_count
98
+ players.count
99
+ end
100
+
101
+ def messages
102
+ chatroom.messages
103
+ end
104
+
105
+ after_initialize do
106
+ self.chatroom = Core::Models::Chatrooms::Campaign.new(campaign: self)
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ module Models
5
+ module Campaigns
6
+ # An invitation is the linked between a player and a campaign.
7
+ # It keeps the history of the interaction between the player and the campaign.
8
+ #
9
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
10
+ class Invitation
11
+ include Mongoid::Document
12
+ include Mongoid::Timestamps
13
+ include Core::Models::Concerns::Enumerable
14
+ include Core::Models::Concerns::Historizable
15
+
16
+ # @!attribute [rw] account
17
+ # @return [Core::Models::Account] the account the invitation has been issued to.
18
+ belongs_to :account, class_name: 'Core::Models::Account', inverse_of: :invitations
19
+ # @!attribute [rw] campaign
20
+ # @return [Core::Models::Campaign] the campaign the invitation has been made in.
21
+ belongs_to :campaign, class_name: 'Core::Models::Campaign', inverse_of: :invitations
22
+
23
+ # @!attribute [rw] status
24
+ # @return [Symbol] the current status of the invitation.
25
+ historize enum_field :status,
26
+ [:pending, :request, :accepted, :refused, :expelled, :left, :master, :creator],
27
+ default: :pending
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ module Models
5
+ module Campaigns
6
+ # A campaign tag is a string describing a characteristic of the campaign it's in.
7
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
8
+ class Tag
9
+ include Mongoid::Document
10
+ include Mongoid::Timestamps
11
+
12
+ # @!attribute [rw] content
13
+ # @return [String] the string content of the tag, describing a characteristic.
14
+ field :content, type: String
15
+ # @!attribute [rw] count
16
+ # @return [Integer] the number of campaigns this tag is in, avoiding a join.
17
+ field :count, type: Integer, default: 1
18
+
19
+ validates :content, presence: { message: 'required' }, uniqueness: { message: 'uniq' }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,10 @@
1
+ module Core
2
+ module Models
3
+ # The campaigns module is holding the logic for some objects related to campaigns.
4
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
5
+ module Campaigns
6
+ autoload :Invitation, 'core/models/campaigns/invitation'
7
+ autoload :Tag , 'core/models/campaigns/tag'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ module Core
2
+ module Models
3
+ module Chatrooms
4
+ # The base chatroom class, made to be subclassed in campaign and personal chatrooms.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ class Base
7
+ include Mongoid::Document
8
+ include Mongoid::Timestamps
9
+
10
+ # @!attribute [rw] messages
11
+ # @return [Array<Core::Models::Chatrooms::Message>] the messages sent in this chatroom.
12
+ has_many :messages, class_name: 'Core::Models::Chatrooms::Message', inverse_of: :chatroom
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ module Core
2
+ module Models
3
+ module Chatrooms
4
+ # Represents the chatroom embedded in a campaign.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ class Campaign < Core::Models::Chatrooms::Base
7
+ # @!attribute [rw] campaign
8
+ # @return [Core::Models::Campaign] the campaign the chatroom is linked to.
9
+ embedded_in :campaign, class_name: 'Core::Models::Campaign', inverse_of: :chatroom
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ module Core
2
+ module Models
3
+ module Chatrooms
4
+ class Conversation < Core::Models::Chatrooms::Base
5
+ has_many :memberships, class_name: 'Core::Models::Chatrooms::Membership', inverse_of: :chatroom
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ module Core
2
+ module Models
3
+ module Chatrooms
4
+ class Membership
5
+ include Mongoid::Document
6
+ include Mongoid::Timestamps
7
+ include Core::Models::Concerns::Enumerable
8
+
9
+ enum_field :status, [:shown, :hidden], default: :shown
10
+
11
+ belongs_to :chatroom, class_name: 'Core::Models::Chatrooms::Private', inverse_of: :memberships
12
+
13
+ belongs_to :account, class_name: 'Core::Models::Account', inverse_of: :memberships
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module Core
2
+ module Models
3
+ module Chatrooms
4
+ # This model represents an in-game tchat message sent in the tchat of a campaign.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ class Message
7
+ include Mongoid::Document
8
+ include Mongoid::Timestamps
9
+ include Core::Models::Concerns::Enumerable
10
+
11
+ # @!attribute [rw] type
12
+ # @return [Symbol] the type of message (plain text or command) contained in the data, used to parse and display it.
13
+ enum_field :type, [:text, :command], default: :text
14
+ # @!attribute [rw] data
15
+ # @return [Hash] the additional data passed to the message (arguments of the command, or content of the text)
16
+ field :data, type: Hash, default: {}
17
+ # @!attribute {rw} raw
18
+ # @return [String] the content as typed by the user, without any parsing or transformation.
19
+ field :raw, type: String, default: ''
20
+ # @!attribute [rw] deleted
21
+ # @return [Boolean] TRUE if the message has been marked as deleted by its user, FALSE otherwise.
22
+ field :deleted, type: Boolean, default: false
23
+
24
+ # @!attribute [rw] campaign
25
+ # @return [Core::Models::Chatrooms::Campaign] the chatroom in which the message has been emitted.
26
+ belongs_to :chatroom, class_name: 'Core::Models::Chatrooms::Campaign', inverse_of: :messages
27
+ # @!attribute [rw] player
28
+ # @return [Core::Models::Account] the account that has emitted the message in the campaign.
29
+ belongs_to :account, class_name: 'Core::Models::Account', inverse_of: :messages
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ module Core
2
+ module Models
3
+ # The chatrooms modules regroup all classes concerning messages between players.
4
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
5
+ module Chatrooms
6
+ autoload :Base , 'core/models/chatrooms/base'
7
+ autoload :Campaign , 'core/models/chatrooms/campaign'
8
+ autoload :Conversation, 'core/models/chatrooms/conversation'
9
+ autoload :Message , 'core/models/chatrooms/message'
10
+ autoload :Membership , 'core/models/chatrooms/membership'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ module Core
2
+ module Models
3
+ module Concerns
4
+ # Concerns for the objects that can be activated or deactivated, included the corresponding scopes.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ module Activable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ # @!attribute [rw] active
11
+ # @return [Boolean] the active status of the instance, indicating if someone has deactivated it or not.
12
+ field :active, type: Boolean, default: true
13
+
14
+ scope :active , ->{ where(active: true) }
15
+ scope :inactive, ->{ where(active: false) }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ module Core
2
+ module Models
3
+ module Concerns
4
+ # Includes the diagnostic URL field, and the related validations.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ module Diagnosticable
7
+ extend ActiveSupport::Concern
8
+
9
+ # Module holding the class methods for the classes including this concern.
10
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
11
+ included do
12
+ # @!attribute [rw] diagnostic
13
+ # @return [String] the diagnostic URL to know the status of an entity (usually a gateway, or an instance of a service).
14
+ field :diagnostic, type: String, default: '/status'
15
+
16
+ validates :diagnostic,
17
+ presence: {message: "required"},
18
+ length: {minimum: 4, message: "minlength"},
19
+ format: {with: /\A(\/[a-z]+)+\z/, message: "pattern"}
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,50 @@
1
+ module Core
2
+ module Models
3
+ module Concerns
4
+ # Defines enumerations for the Mongoid models. An enumeration is a field that can only use a given set of values.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ module Enumerable
7
+ extend ActiveSupport::Concern
8
+
9
+ # Submodule holding all the static methods add to the current subclass.
10
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
11
+ module ClassMethods
12
+
13
+ # Creates the field with the given name, set of possible values, and options.
14
+ # @param field_name [String] the name of the enumerated field.
15
+ # @param values [Array<Symbol>] the possible values of the enumerated field.
16
+ # @param options [Hash<Symbol, Any>] the possible options for the field.
17
+ def enum_field(field_name, values, options = {})
18
+ returned = field :"enum_#{field_name}", type: Symbol, default: options[:default]
19
+
20
+ validates :"enum_#{field_name}", inclusion: {in: values.map(&:to_sym), message: 'inclusion'}
21
+
22
+ define_method field_name do
23
+ return self["enum_#{field_name}"]
24
+ end
25
+
26
+ define_method "#{field_name}=" do |value|
27
+ if values.include? value.to_sym
28
+ self["enum_#{field_name}"] = value.to_sym
29
+ end
30
+ end
31
+
32
+ values.map(&:to_sym).each do |value|
33
+ define_method "#{field_name}_#{value}!" do
34
+ self["enum_#{field_name}"] = value
35
+ end
36
+
37
+ define_method "#{field_name}_#{value}?" do
38
+ self["enum_#{field_name}"] == value
39
+ end
40
+ end
41
+
42
+ # This is to make enumerations historizable by
43
+ # returning the field object created by Mongoid.
44
+ returned
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Core
4
+ module Models
5
+ module Concerns
6
+ # This module is what I call "a beautiful piece of Ruby engineering"
7
+ # It takes any mongoid field that you may have declared, and historizes
8
+ # it in a dedicated relation if this relation does not already exists.
9
+ #
10
+ # What it does exactly :
11
+ # - Creates the :history relation if it does not already exists.
12
+ # - Creates a method to check for changes of a specific attribute.
13
+ # - Check for changes at initialization to insert the first value.
14
+ # - Check for changes at update/save to store the history.
15
+ #
16
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
17
+ module Historizable
18
+ extend ActiveSupport::Concern
19
+
20
+ # Adds an entry in the history table for the given field.
21
+ # It checks several things to make the history entry valid :
22
+ # - the new value is different from the old value
23
+ # - the old value is identical to the last recorded new value.
24
+ #
25
+ # @param field [String] the name of the field to historize
26
+ # @param from [Any] the old value before update
27
+ # @param to [Any] the new value after update.
28
+ def add_history(field:, from:, to:)
29
+ return if from == to
30
+ return if !history.empty? && history.order_by(:created_at.desc).first.to != from
31
+
32
+ event = Core::Models::Event.create(field: field, from: from, to: to, document: self)
33
+ event.save
34
+ end
35
+
36
+ # Submodule holding all the static methods add to the current subclass.
37
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
38
+ module ClassMethods
39
+
40
+ # Takes the Mongoid declared field and creates the callbacks
41
+ # to intercept any value change and add it to the history.
42
+ # @field field [Mongoid::Fields::Standard] the Mongoid field to historize.
43
+ def historize(field)
44
+
45
+ unless relations.key?('history')
46
+ embeds_many :history, class_name: 'Core::Models::Event'
47
+ end
48
+
49
+ after_initialize do |doc|
50
+ add_history(field: field.name, from: nil, to: doc[field.name])
51
+ end
52
+
53
+ after_save do |doc|
54
+ if doc.changed_attributes.key?(field.name)
55
+ from = doc.changed_attributes[field.name]
56
+ add_history(field: field.name, from: from, to: doc[field.name])
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,44 @@
1
+ module Core
2
+ module Models
3
+ module Concerns
4
+ # Includes the MIME type field to files to ensure only supported types are included.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ module MimeTypable
7
+ extend ActiveSupport::Concern
8
+
9
+ # Submodule holding all the static methods add to the current subclass.
10
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
11
+ module ClassMethods
12
+ # Defines the MIME type attribute with the given possible MIME types.
13
+ # @param values [Array<String>] the possible MIME types, * can be used as wild cards.
14
+ def mime_type(values)
15
+
16
+ # @!attribute [rw] mime_type
17
+ # @return [String] the MIME type of the file, obtained from the uploaded file.
18
+ field :mime_type, type: String
19
+
20
+ validates :mime_type, presence: {message: 'required'}
21
+
22
+ validate :mime_type_validity, if: :mime_type?
23
+
24
+ # Validates the validity of the MIME type by checking if it respects any of the given mime types.
25
+ # If it does not respect any MIME types possible, it adds an error to the mime_type field and invalidates.
26
+ define_method :mime_type_validity do
27
+ checked_types = if values.is_a? Symbol
28
+ self.send(values) rescue []
29
+ else
30
+ values
31
+ end
32
+ return true if checked_types.empty?
33
+ checked_types.each do |type|
34
+ type_regex = ::Regexp.new("^#{type.sub(/\*/, '(.+)')}$")
35
+ return true if !type_regex.match(mime_type).nil?
36
+ end
37
+ errors.add(:mime_type, 'pattern')
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,17 @@
1
+ module Core
2
+ module Models
3
+ module Concerns
4
+ # Includes the premium field to make the entity including it accessible only to premium applications or not.
5
+ # @author Vincent Courtois <courtois.vincent@outlook.com>
6
+ module Premiumable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ # @!attribute [rw] premium
11
+ # @return [Boolean] TRUE if the entity is made to be accessible only to premiuma pplications, FALSE otherwise.
12
+ field :premium, type: Boolean, default: false
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end