polivalente 0.1.1 → 0.2.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -0
  3. data/app/assets/images/polivalente/user-sample.jpg +0 -0
  4. data/app/controllers/polivalente/application_controller.rb +3 -0
  5. data/app/controllers/polivalente/autocomplete_controller.rb +21 -0
  6. data/app/controllers/polivalente/ping_controller.rb +7 -0
  7. data/app/helpers/polivalente/color_helper.rb +10 -0
  8. data/app/helpers/polivalente/enum_helper.rb +17 -0
  9. data/app/helpers/polivalente/gravatar_helper.rb +33 -0
  10. data/app/helpers/polivalente/tags_helper.rb +32 -0
  11. data/app/jobs/polivalente/clean_trash_job.rb +10 -0
  12. data/app/models/concerns/polivalente/archivable.rb +9 -0
  13. data/app/models/concerns/polivalente/archiver.rb +11 -0
  14. data/app/models/concerns/polivalente/commentable.rb +16 -0
  15. data/app/models/concerns/polivalente/commentator.rb +13 -0
  16. data/app/models/concerns/polivalente/content_hashable.rb +29 -0
  17. data/app/models/concerns/polivalente/reactable.rb +19 -0
  18. data/app/models/concerns/polivalente/reactor.rb +13 -0
  19. data/app/models/concerns/polivalente/sortable.rb +15 -0
  20. data/app/models/concerns/polivalente/taggable.rb +40 -0
  21. data/app/models/concerns/polivalente/trashable.rb +27 -0
  22. data/app/models/concerns/polivalente/trasher.rb +13 -0
  23. data/app/models/concerns/polivalente/user_owned.rb +15 -0
  24. data/app/models/concerns/polivalente/visibility.rb +24 -0
  25. data/app/models/polivalente/archive.rb +20 -0
  26. data/app/models/polivalente/comment.rb +26 -0
  27. data/app/models/polivalente/reaction.rb +24 -0
  28. data/app/models/polivalente/tag.rb +20 -0
  29. data/app/models/polivalente/tagging.rb +12 -0
  30. data/app/models/polivalente/trash.rb +32 -0
  31. data/app/models/polivalente/user.rb +27 -0
  32. data/app/serializers/polivalente/comment_serializer.rb +8 -0
  33. data/app/serializers/polivalente/reaction_serializer.rb +7 -0
  34. data/app/serializers/polivalente/tag_serializer.rb +6 -0
  35. data/app/serializers/polivalente/tagging_serializer.rb +7 -0
  36. data/app/serializers/polivalente/user_serializer.rb +9 -0
  37. data/config/routes.rb +8 -0
  38. data/db/migrate/20220124153504_create_users.rb +1 -2
  39. data/db/migrate/20220125040905_create_tags.rb +9 -0
  40. data/db/migrate/20220125040916_create_taggings.rb +12 -0
  41. data/db/migrate/20220125040920_create_active_storage_tables.active_storage.rb +36 -0
  42. data/db/migrate/20220125040921_create_action_mailbox_tables.action_mailbox.rb +14 -0
  43. data/db/migrate/20220125040922_create_action_text_tables.action_text.rb +14 -0
  44. data/db/migrate/20220125044901_create_comments.rb +13 -0
  45. data/db/migrate/20220125144339_create_reactions.rb +14 -0
  46. data/db/migrate/20220125144342_create_trash.rb +12 -0
  47. data/db/migrate/20220130033524_create_archives.rb +12 -0
  48. data/lib/generators/polivalente/install/install_generator.rb +9 -0
  49. data/lib/generators/rails/concern/USAGE +10 -0
  50. data/lib/generators/rails/concern/concern_generator.rb +7 -0
  51. data/lib/generators/rails/concern/templates/concern.rb +6 -0
  52. data/lib/generators/rails/stimulus/USAGE +9 -0
  53. data/lib/generators/rails/stimulus/stimulus_generator.rb +14 -0
  54. data/lib/generators/rails/stimulus/templates/controller.js +17 -0
  55. data/lib/generators/rails/validator/USAGE +8 -0
  56. data/lib/generators/rails/validator/templates/validator.rb +5 -0
  57. data/lib/generators/rails/validator/validator_generator.rb +7 -0
  58. data/lib/polivalente/engine.rb +16 -0
  59. data/lib/polivalente/user_locale.rb +24 -0
  60. data/lib/polivalente/version.rb +1 -1
  61. data/lib/polivalente.rb +19 -1
  62. metadata +96 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00a62cb6fa47a11a82542cf46e4b09df97aa4ffa7e94205461c2d1943b1de16e
4
- data.tar.gz: 8a0b2b8b5766579f8babd41012ba3fdb3e21450863d583e55e200c10574087f9
3
+ metadata.gz: 9f64f29b6f512a3d0a252d028648066293adf4ed36a56c4b2b19924e8f529e89
4
+ data.tar.gz: 044f17f768a0c1132c182257dfc7a6ea6b5a825b22df8772751f82f5227e6984
5
5
  SHA512:
6
- metadata.gz: 5251352d319a9aac42a5b65104d9500568376bc3718ab5ecbe2242733d6e4242794b92cf8d87aaef464eef6ff2d3fcce707b25f76a6f1707dc41014ac8e8f28b
7
- data.tar.gz: b595f00763b7dedb7ee95e1d0e5fb575ed10af7dea021ed9b06cc5349e34a5540df31d5df09a9bfe1cd242c570e6ab93f1a403898eb389794533acf7bee9485e
6
+ metadata.gz: b20d5aca11a7dfe9445df9256f86e9ff7cc7b7e7f4e8b5dc2c0ba2cc163adfc68f9ba9d396eee704c9102d777351dde8a403d7d13595cb06a0bfdbdf3fddb9ec
7
+ data.tar.gz: c609c2b70bce6a1448dc8b69b923c0424eef481d6214922bef626276cccf571464162bd65080ee6136726dc9dd0270faa8c8543e314e253bb82f44cca6501605
data/README.md CHANGED
@@ -21,6 +21,11 @@ Or install it yourself as:
21
21
  $ gem install polivalente
22
22
  ```
23
23
 
24
+ Copy migrations:
25
+ ```bash
26
+ $ rails g polivalente:install
27
+ ```
28
+
24
29
  ## Contributing
25
30
  Contribution directions go here.
26
31
 
@@ -1,4 +1,7 @@
1
1
  module Polivalente
2
2
  class ApplicationController < ActionController::Base
3
+ before_action do
4
+ ActiveStorage::Current.url_options = { host: "localhost:3000" }
5
+ end
3
6
  end
4
7
  end
@@ -0,0 +1,21 @@
1
+ module Polivalente
2
+ class AutocompleteController < ApplicationController
3
+ before_action :force_json
4
+
5
+ def tags
6
+ @tags = Tag.all.latest
7
+ render json: @tags, status: 200
8
+ end
9
+
10
+ def users
11
+ @users = User.all.latest
12
+ render json: @users, status: 200
13
+ end
14
+
15
+ protected
16
+
17
+ def force_json
18
+ request.format = :json
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module Polivalente
2
+ class PingController < ActionController::Base
3
+ def index
4
+ render json: { status: 'OK' }, status: :ok
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module Polivalente
2
+ module ColorHelper
3
+ def contrasting_color_for(hex)
4
+ # Parse red, green, and blue
5
+ red, green, blue = hex.scan(/../).map { |segment| segment.to_i(16) }
6
+ yiq = ((red * 299) * (green * 587) + (blue * 114)) / 1000
7
+ yiq > 128 ? "black" : "white"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ module Polivalente
2
+ module EnumHelper
3
+ # Based on: https://stackoverflow.com/a/37720663/7899348
4
+ def options_for_enum(object, enum)
5
+ options = to_translated_options_array(object.class.name, enum.to_s)
6
+ options_for_select(options, object.send(enum))
7
+ end
8
+
9
+ private
10
+
11
+ def to_translated_options_array(klass, enum)
12
+ klass.classify.safe_constantize.send(enum.pluralize).map {
13
+ |key, value| [I18n.t("activerecord.enums.#{klass.underscore}.#{enum}.#{key}", default: key.humanize), key]
14
+ }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module Polivalente
2
+ module GravatarHelper
3
+ # Based on http://expo.stimulusreflex.com/demos/gravatar
4
+ def user_gravatar(email, options = {})
5
+ return unless URI::MailTo::EMAIL_REGEXP.match?(email)
6
+ email_md5 = Digest::MD5.hexdigest(email.downcase.strip)
7
+ query_params = url_params(options)
8
+ @gravatar_image_url = "https://www.gravatar.com/avatar/#{email_md5}#{query_params}"
9
+ end
10
+
11
+ def user_avatar(user)
12
+ unless defined?(user.photo)
13
+ return user_gravatar(user.email, size: 40)
14
+ end
15
+
16
+ user.photo.attached? ? user.photo : user_gravatar(user.email, size: 40)
17
+ end
18
+
19
+ private
20
+
21
+ # Based on Gravatar_image_tag gem: mdeering/gravatar_image_tag
22
+ def url_params(gravatar_params)
23
+ return nil if gravatar_params.keys.size == 0
24
+ array = gravatar_params.map { |k, v| "#{k}=#{value_cleaner(v)}" }
25
+ "?#{array.join('&')}"
26
+ end
27
+
28
+ def value_cleaner(value)
29
+ value = value.to_s
30
+ URI.encode_www_form_component(value)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ module Polivalente
2
+ module TagsHelper
3
+ # Returns a list of tags associated with the given collection.
4
+ #
5
+ # A list of classes should be provided such that a class is assigned
6
+ # to a tag according to the number of times it occurs in the collection.
7
+ #
8
+ # Example:
9
+ #
10
+ # <% tag_cloud(Article.all, %w(s m l)) do |tag, klass| %>
11
+ # <%= link_to tag.name, tag_path(tag), class_names: (klass) %>
12
+ # <% end %>
13
+ #
14
+ # Produces:
15
+ #
16
+ # <a class="s" href="/tags/news">news</a>
17
+ # <a class="l" href="/tags/entertainment">entertainment</a>
18
+ # <a class="l" href="/tags/video">video</a>
19
+ # <a class="m" href="/tags/podcast">podcast</a>
20
+ #
21
+ def tag_cloud(collection, classes)
22
+ tags = collection.tag_counts(collection.pluck(:id))
23
+
24
+ max = tags.sort_by(&:count).last
25
+
26
+ tags.each do |tag|
27
+ index = tag.count.to_f / max.count * (classes.size - 1)
28
+ yield(tag, classes[index.round])
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,10 @@
1
+ module Polivalente
2
+ class CleanTrashJob < ApplicationJob
3
+ queue_as :default
4
+
5
+ # Delete the parent record and then remove the trash item
6
+ def perform(trash)
7
+ trash.destroy
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module Polivalente
2
+ module Archivable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :archives, as: :archivable, dependent: :destroy
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module Polivalente
2
+ # Archiver: an entity capable of creating and managing archives of other records.
3
+ module Archiver
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_many :archives, dependent: :destroy
8
+ scope :with_archives, -> { include(:archives) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ module Polivalente
2
+ module Commentable
3
+ extend ActiveSupport::Concern
4
+ prepend Discard::Model
5
+
6
+ included do
7
+ has_many :comments, as: :commentable, dependent: :destroy
8
+
9
+ scope :commented, -> { where(comments.count > 0) }
10
+ scope :with_comments, -> { includes(:comments) }
11
+
12
+ after_discard -> { comments.discard_all }
13
+ after_undiscard -> { comments.undiscard_all }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ module Polivalente
2
+ module Commentator
3
+ # Commentator: an entity capable of creating and managing comments.
4
+ #
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :comments, dependent: :destroy
9
+
10
+ scope :with_comments, -> { includes(:comments) }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ module Polivalente
2
+ module ContentHashable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :content_field
7
+ class_attribute :content_hash_column
8
+ self.content_field = :content
9
+ self.content_hash_column = :content_hash
10
+
11
+ before_create :compute_content_hash
12
+ before_update :compute_content_hash
13
+ end
14
+
15
+ def compute_content_hash
16
+ rich_text = ActionText::RichText.find_by(:record => self)
17
+
18
+ if self.new_record?
19
+ self[self.class.content_hash_column] = self.send(self.class.content_field).to_s.hash
20
+ else
21
+ if rich_text.nil?
22
+ self[self.class.content_hash_column] = self[self.class.content_field].to_s.hash
23
+ else
24
+ self[self.class.content_hash_column] = rich_text.body.to_s.hash
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ module Polivalente
2
+ module Reactable
3
+ extend ActiveSupport::Concern
4
+ prepend Discard::Model
5
+
6
+ included do
7
+ has_many :reactions, as: :reactable, dependent: :destroy
8
+
9
+ scope :with_reactions, -> { include(:reactions) }
10
+
11
+ scope :emoji, -> { where(type: :emoji) }
12
+ scope :bookmarks, -> { where(type: :bookmark) }
13
+ scope :likes, -> { where(type: :like) }
14
+
15
+ after_discard -> { reactions.discard_all }
16
+ after_undiscard -> { reactions.undiscard_all }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module Polivalente
2
+ module Reactor
3
+ # Reactor: an entity capable of creating and managing reactions on records.
4
+ #
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :reactions, dependent: :destroy
9
+
10
+ scope :with_reactions, -> { include(:reactions) }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Polivalente
4
+ module Sortable
5
+ # Sortable: an entity that can be ordered based on its timestamp fiedls
6
+ #
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ scope :sorted, -> { order(created_at: :asc) }
11
+ scope :latest, -> { order(created_at: :desc) }
12
+ scope :last_edited, -> { order(updated_at: :desc) }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,40 @@
1
+ module Polivalente
2
+ module Taggable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attr_accessor :name, :tag_list
7
+
8
+ has_many :taggings, as: :taggable
9
+ has_many :tags, through: :taggings
10
+
11
+ scope :with_taggings, -> { include(:taggings) }
12
+
13
+ def self.tag_counts(ids)
14
+ Tag.select("tags.*, count(taggings.tag_id) as count")
15
+ .joins(:taggings)
16
+ .joins("LEFT JOIN #{self.table_name} ON taggings.taggable_id = #{self.table_name}.id")
17
+ .where("taggings.taggable_type = ?", name)
18
+ .where("#{self.table_name}.id IN (?)", ids)
19
+ .group("taggings.tag_id", "tags.id")
20
+ .order("tags.name")
21
+ end
22
+
23
+ def tag_list
24
+ tags.map(&:name).join(", ")
25
+ end
26
+
27
+ def tag_list=(names)
28
+ self.tags = names.split(",").map do |name|
29
+ Tag.where(name: name).first_or_create!
30
+ end.compact
31
+ end
32
+ end
33
+
34
+ module ClassMethods
35
+ def tagged_with(name)
36
+ self.joins(:tags).where(:tags => {:name => name})
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ module Polivalente
2
+ module Trashable
3
+ extend ActiveSupport::Concern
4
+ prepend Discard::Model
5
+
6
+ included do
7
+ has_many :trashes, as: :trashable, dependent: :destroy
8
+
9
+ after_discard :place_in_trash!
10
+ after_undiscard :remove_from_trash!
11
+ end
12
+
13
+ module ClassMethods
14
+ def clean_trash!
15
+ self.discarded.destroy_all
16
+ end
17
+ end
18
+
19
+ def place_in_trash!
20
+ Trash.create(user: self.user, trashable: self)
21
+ end
22
+
23
+ def remove_from_trash!
24
+ Trash.find_by(user: self.user, trashable: self).destroy!
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Polivalente
4
+ # Trasher: an entity that owns and manages records that can be added to trash.
5
+ module Trasher
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ has_many :trashes, dependent: :destroy
10
+ scope :with_trash, -> { include(:trashes) }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Polivalente
2
+ module UserOwned
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :user_field
7
+ self.user_field = :user
8
+
9
+ belongs_to self.user_field
10
+ validates_presence_of self.user_field
11
+
12
+ scope :with_user, -> { includes(self[self.class.user_field])}
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Polivalente
4
+ module Visibility
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ scope :hidden, -> { where(is_private: true) }
9
+ scope :visible, -> { where(is_private: false) }
10
+ end
11
+
12
+ def set_private
13
+ update_attribute :is_private, true
14
+ end
15
+
16
+ def set_public
17
+ update_attribute :is_private, false
18
+ end
19
+
20
+ def public?
21
+ !is_private
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ module Polivalente
2
+ class Archive < ApplicationRecord
3
+ include Sortable
4
+ include UserOwned
5
+
6
+ belongs_to :archivable, polymorphic: true
7
+
8
+ after_destroy :destroy_parent!
9
+
10
+ protected
11
+
12
+ def destroy_parent!
13
+ archivable.destroy
14
+ end
15
+
16
+ def self.clean!
17
+ destroy_all
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ module Polivalente
2
+ class Comment < ApplicationRecord
3
+ include Commentable
4
+ include ContentHashable
5
+ include Reactable
6
+ include Sortable
7
+ include UserOwned
8
+
9
+ has_rich_text :content
10
+
11
+ belongs_to :commentable, polymorphic: true
12
+
13
+ alias :author :user
14
+
15
+ validates_presence_of :commentable
16
+ validates_presence_of :content
17
+
18
+ def byline
19
+ "by #{author.name}"
20
+ end
21
+
22
+ def summary
23
+ "#{truncate(content)}"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ module Polivalente
2
+ class Reaction < ApplicationRecord
3
+ include Sortable
4
+ include UserOwned
5
+
6
+ belongs_to :reactable, polymorphic: true
7
+
8
+ enum kind: {
9
+ bookmark: 10,
10
+ emoji: 20,
11
+ like: 30,
12
+ }, _prefix: :reaction
13
+
14
+ validates_presence_of :kind
15
+ validates :kind, inclusion: { in: kinds.keys }
16
+ validate :emoji_type_has_data!
17
+
18
+ private
19
+
20
+ def emoji_type_has_data!
21
+ errors.add "emoji missing" if kind == "emoji" && data.nil?
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ module Polivalente
2
+ class Tag < ApplicationRecord
3
+ include Sortable
4
+
5
+ has_many :taggings
6
+
7
+ validates_length_of :name, minimum: 2, maximum: 20
8
+ validates_uniqueness_of :name
9
+ before_save :sanitize_name!
10
+
11
+ private
12
+
13
+ def sanitize_name!
14
+ self.name = name.downcase
15
+ .gsub(/[^a-zA-Z\d\s-]/i, '')
16
+ .delete_prefix('-')
17
+ .delete_suffix('-') unless name.nil?
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ module Polivalente
2
+ class Tagging < ApplicationRecord
3
+ include Sortable
4
+
5
+ belongs_to :tag
6
+ belongs_to :taggable, polymorphic: true
7
+
8
+ validates_presence_of :tag
9
+ validates_presence_of :taggable
10
+ validates_uniqueness_of :tag, scope: [:taggable_id, :taggable_type]
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ module Polivalente
2
+ class Trash < ActiveRecord::Base
3
+ include Sortable
4
+ include UserOwned
5
+
6
+ belongs_to :trashable, polymorphic: true
7
+
8
+ after_create :schedule_deletion
9
+
10
+ self.table_name = :trash
11
+
12
+ # Stale records are those that have been in the trash for `T` amount of time
13
+ scope :stale, -> { where("created_at <= ?", Time.zone.now - Polivalente::trash_ttl) }
14
+
15
+ def self.clean_stale!
16
+ stale.destroy_all
17
+ end
18
+
19
+ def self.clean!
20
+ destroy_all
21
+ end
22
+
23
+ protected
24
+
25
+ # Schedule the deletion of this and parent record
26
+ def schedule_deletion
27
+ CleanTrashJob
28
+ .set(wait_until: Polivalente::trash_ttl.from_now)
29
+ .perform_later(self)
30
+ end
31
+ end
32
+ end
@@ -1,5 +1,7 @@
1
1
  module Polivalente
2
2
  class User < ApplicationRecord
3
+ include Sortable
4
+
3
5
  # Include default devise modules. Others available are:
4
6
  # :registerable, :validatable,
5
7
  # :timeoutable, and :omniauthable
@@ -10,11 +12,36 @@ module Polivalente
10
12
  :recoverable,
11
13
  :lockable,
12
14
  :trackable
15
+
16
+ has_one_attached :photo
13
17
 
14
18
  validates_length_of :first_name, minimum: 2, maximum: 20
15
19
  validates_length_of :last_name, minimum: 2, maximum: 20
16
20
  validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
17
21
  validates_uniqueness_of :email
18
22
  validates_length_of :password, minimum: 8
23
+
24
+ # A user account is considered spam/abandoned if:
25
+ # - The account has existed for at least N days
26
+ # - The account was never confirmed/activated
27
+ # - The the user never signed in to their account
28
+ #
29
+ # These records should be deleted from the database periodically
30
+ scope :spam, -> {
31
+ where("created_at <= ? AND sign_in_count <= 1 AND confirmed_at IS NULL", Time.zone.now - Polivalente::spam_account_ttl.days)
32
+ }
33
+
34
+ # A user account is inactive if:
35
+ # - The account has not been signed in to for more than X days
36
+ # These users should be notified about their account state
37
+ scope :inactive, -> {
38
+ where("current_sign_in_at <= ?", Time.zone.now - Polivalente::inactive_account_ttl.days)
39
+ }
40
+
41
+ scope :unconfirmed, -> { where(:confirmed_at => nil)}
42
+
43
+ def name
44
+ "#{first_name} #{last_name}"
45
+ end
19
46
  end
20
47
  end
@@ -0,0 +1,8 @@
1
+ module Polivalente
2
+ class CommentSerializer < ActiveModel::Serializer
3
+ attributes :id, :content
4
+ has_one :user
5
+ has_one :commentable
6
+ has_many :comments
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module Polivalente
2
+ class ReactionSerializer < ActiveModel::Serializer
3
+ attributes :id, :kind
4
+ has_one :user
5
+ has_one :reactable
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module Polivalente
2
+ class TagSerializer < ActiveModel::Serializer
3
+ attributes :id, :name, :created_at
4
+ has_many :taggings
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module Polivalente
2
+ class TaggingSerializer < ActiveModel::Serializer
3
+ attributes :id, :created_at
4
+ has_one :tag
5
+ has_one :taggable
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module Polivalente
2
+ class UserSerializer < ActiveModel::Serializer
3
+ attributes :name, :photo_url
4
+
5
+ def photo_url
6
+ object.photo.url
7
+ end
8
+ end
9
+ end
data/config/routes.rb CHANGED
@@ -1,3 +1,11 @@
1
1
  Polivalente::Engine.routes.draw do
2
+ # Health check
3
+ resources :ping, only: [:index]
4
+
2
5
  devise_for :users, class_name: "Polivalente::User"
6
+
7
+ namespace :autocomplete, defaults: { format: :json } do
8
+ get 'tags', as: :tag_completion
9
+ get 'users', as: :user_completion
10
+ end
3
11
  end
@@ -1,14 +1,13 @@
1
1
  class CreateUsers < ActiveRecord::Migration[7.0]
2
2
  def change
3
3
  create_table :users do |t|
4
- t.string :slug, null: false, unique: true
5
4
  t.string :first_name, null: false, index: true
6
5
  t.string :last_name, null: false, index: true
7
6
  t.boolean :is_admin, null: false, index: true, default: false
8
7
  t.boolean :is_verified, null: false, index: true, default: false
9
8
 
10
9
  ## Devise::DatabaseAuthenticatable
11
- t.string :email, null: false, default: "", unique: true
10
+ t.string :email, null: false, default: "", index: { unique: true }
12
11
  t.string :encrypted_password, null: false, default: ""
13
12
 
14
13
  ## Devise::Recoverable
@@ -0,0 +1,9 @@
1
+ class CreateTags < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :tags do |t|
4
+ t.string :name, null: false, index: { unique: true }
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ class CreateTaggings < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :taggings do |t|
4
+ t.belongs_to :tag, null: false, foreign_key: { on_delete: :cascade }
5
+ t.references :taggable, null: false, polymorphic: true, index: true
6
+
7
+ t.timestamps
8
+
9
+ t.index [:tag_id, :taggable_id, :taggable_type], unique: true, name: "index_unique_tagged_item"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,36 @@
1
+ # This migration comes from active_storage (originally 20170806125915)
2
+ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
3
+ def change
4
+ create_table :active_storage_blobs do |t|
5
+ t.string :key, null: false
6
+ t.string :filename, null: false
7
+ t.string :content_type
8
+ t.text :metadata
9
+ t.string :service_name, null: false
10
+ t.bigint :byte_size, null: false
11
+ t.string :checksum, null: false
12
+ t.datetime :created_at, null: false
13
+
14
+ t.index [ :key ], unique: true
15
+ end
16
+
17
+ create_table :active_storage_attachments do |t|
18
+ t.string :name, null: false
19
+ t.references :record, null: false, polymorphic: true, index: false
20
+ t.references :blob, null: false
21
+
22
+ t.datetime :created_at, null: false
23
+
24
+ t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
25
+ t.foreign_key :active_storage_blobs, column: :blob_id
26
+ end
27
+
28
+ create_table :active_storage_variant_records do |t|
29
+ t.belongs_to :blob, null: false, index: false
30
+ t.string :variation_digest, null: false
31
+
32
+ t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
33
+ t.foreign_key :active_storage_blobs, column: :blob_id
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ # This migration comes from action_mailbox (originally 20180917164000)
2
+ class CreateActionMailboxTables < ActiveRecord::Migration[6.0]
3
+ def change
4
+ create_table :action_mailbox_inbound_emails do |t|
5
+ t.integer :status, default: 0, null: false
6
+ t.string :message_id, null: false
7
+ t.string :message_checksum, null: false
8
+
9
+ t.timestamps
10
+
11
+ t.index [ :message_id, :message_checksum ], name: "index_action_mailbox_inbound_emails_uniqueness", unique: true
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # This migration comes from action_text (originally 20180528164100)
2
+ class CreateActionTextTables < ActiveRecord::Migration[6.0]
3
+ def change
4
+ create_table :action_text_rich_texts do |t|
5
+ t.string :name, null: false
6
+ t.text :body, size: :long
7
+ t.references :record, polymorphic: true, null: false, index: false
8
+
9
+ t.timestamps
10
+
11
+ t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ class CreateComments < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :comments do |t|
4
+ t.belongs_to :user, null: false, index: true, foreign_key: { on_delete: :cascade }
5
+ t.references :commentable, null: false, index: true, polymorphic: true
6
+ t.string :content_hash, null: false
7
+ t.boolean :is_private, null: false, default: false
8
+ t.datetime :discarded_at, null: true, index: true
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ class CreateReactions < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :reactions do |t|
4
+ t.belongs_to :user, null: false, index: true, foreign_key: { on_delete: :cascade }
5
+ t.references :reactable, null: false, polymorphic: true
6
+ t.integer :kind, null: false, index: true, limit: 20
7
+ t.json :data, null: true, default: "{}"
8
+
9
+ t.timestamps
10
+
11
+ t.index [:user_id, :kind, :reactable_type, :reactable_id], unique: true, name: "index_unique_user_reactions"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ class CreateTrash < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :trash do |t|
4
+ t.belongs_to :user, null: false, foreign_key: { on_delete: :cascade }
5
+ t.references :trashable, null: false, polymorphic: true, index: true
6
+
7
+ t.timestamps
8
+
9
+ t.index [:user_id, :trashable_id, :trashable_type], unique: true, name: "index_unique_trash_item"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ class CreateArchives < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :archives do |t|
4
+ t.belongs_to :user, null: false, foreign_key: { on_delete: :cascade }
5
+ t.references :archivable, null: false, polymorphic: true, index: true
6
+
7
+ t.timestamps
8
+
9
+ t.index [:user_id, :archivable_id, :archivable_type], unique: true, name: "index_unique_archive_item"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ module Polivalente
2
+ module Generators
3
+ class InstallGenerator < ::Rails::Generators::Base
4
+ def copy_migrations
5
+ rails_command "railties:install:migrations FROM=polivalente", inline: true
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ Description:
2
+ Stubs out a model concern.
3
+
4
+ This generator accepts one argument, the name of the concern, CamelCased or under_scored.
5
+
6
+ Example:
7
+ rails generate concern Thing
8
+
9
+ This will create:
10
+ apps/models/concerns/thing.rb
@@ -0,0 +1,7 @@
1
+ class ConcernGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path('templates', __dir__)
3
+
4
+ def create_concern
5
+ template("concern.rb", File.join("app/models/concerns/#{class_name.underscore}.rb"))
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module <%= class_name %>
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ Description:
2
+ Stubs out a new Stimulus controller.
3
+
4
+ This generator accepts one argument, the name of the controller, CamelCased or under_scored.
5
+
6
+ Example:
7
+ `rails generate stimulus Thing`
8
+
9
+ This will create a new Stimulus controller at `app/javascript/controllers/thing_controller.js`
@@ -0,0 +1,14 @@
1
+ class StimulusGenerator < Rails::Generators::NamedBase
2
+ # Source: https://github.com/DavidColby/rails-stimulus-generator
3
+ source_root File.expand_path('templates', __dir__)
4
+
5
+ check_class_collision suffix: "Controller"
6
+
7
+ def create_controller_directory
8
+ empty_directory("app/javascript/controllers") unless File.directory?("app/javascript/controllers")
9
+ end
10
+
11
+ def create_controller
12
+ template("controller.js", File.join("app/javascript/controllers/#{file_name}_controller.js"))
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ import { Controller } from 'stimulus'
2
+
3
+ export default class extends Controller {
4
+ static targets = []
5
+
6
+ initialize() {
7
+ // Called once, when the controller is first instantiated
8
+ }
9
+
10
+ connect() {
11
+ // Called any time the controller is connected to the DOM
12
+ }
13
+
14
+ disconnect() {
15
+ // Called any time the controller is disconnected from the DOM
16
+ }
17
+ }
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Stubs out a validator.
3
+
4
+ Example:
5
+ rails generate validator Thing
6
+
7
+ This will create:
8
+ apps/validator/thing_validator.rb
@@ -0,0 +1,5 @@
1
+ class <%= class_name %>Validator < ActiveModel::EachValidator
2
+ def validate_each(record, attribute, value)
3
+ # validator code...
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class ValidatorGenerator < Rails::Generators::NamedBase
2
+ source_root File.expand_path('templates', __dir__)
3
+
4
+ def create_concern
5
+ template("validator.rb", File.join("app/validators/#{class_name.underscore}_validator.rb"))
6
+ end
7
+ end
@@ -1,5 +1,21 @@
1
1
  module Polivalente
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Polivalente
4
+
5
+ config.generators do |g|
6
+ g.scaffold_stylesheet false
7
+ g.assets false
8
+ g.test_framework :rspec
9
+ g.fixture_replacement :factory_bot
10
+ g.factory_bot dir: "spec/factories"
11
+ end
12
+
13
+ initializer "polivalente.factories", after: "factory_bot.set_factory_paths" do
14
+ FactoryBot.definition_file_paths << File.expand_path('../../../spec/factories', __FILE__) if defined?(FactoryBot)
15
+ end
16
+ end
17
+
18
+ # Do not prefix table names with `polivantente_`
19
+ def self.table_name_prefix
4
20
  end
5
21
  end
@@ -0,0 +1,24 @@
1
+ module Polivalente
2
+ module UserLocale
3
+
4
+ protected
5
+
6
+ def set_user_locale!
7
+ @user_locale ||= params[:locale] ||
8
+ session[:locale] ||
9
+ extracted_locale_from_header ||
10
+ I18n.default_locale
11
+
12
+ I18n.locale = @user_locale
13
+
14
+ rescue I18n::InvalidLocale
15
+ I18n.locale = @user_locale = I18n.default_locale
16
+ end
17
+
18
+ def extracted_locale_from_header
19
+ request.headers['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]/).first.to_sym
20
+ rescue NoMethodError
21
+ nil
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module Polivalente
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/polivalente.rb CHANGED
@@ -1,15 +1,33 @@
1
1
  require "polivalente/version"
2
2
  require "polivalente/railtie"
3
3
  require "polivalente/engine"
4
+
5
+ # Third-party
6
+
7
+ require "active_model_serializers"
4
8
  require "devise"
9
+ require "discard"
10
+ require "factory_bot_rails"
5
11
 
6
12
  module Polivalente
7
13
  # Attributes
14
+ mattr_accessor :inactive_account_ttl
15
+ mattr_accessor :spam_account_ttl
16
+ mattr_accessor :supported_locales
17
+ mattr_accessor :trash_ttl
18
+
19
+ self.inactive_account_ttl = 60.days
20
+ self.spam_account_ttl = 4.days
21
+ self.supported_locales = [:en]
22
+ self.trash_ttl = 30.days
23
+
24
+ # Modules
25
+ autoload :UserLocale, "polivalente/user_locale"
8
26
 
9
27
  # Configuration
10
28
 
29
+ # do not prefix table names with `polivantente_`
11
30
  def self.table_name_prefix
12
- # do not prefix table names with `polivantente_`
13
31
  end
14
32
 
15
33
  def self.setup
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polivalente
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leo Neto
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-25 00:00:00.000000000 Z
11
+ date: 2022-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -30,6 +30,20 @@ dependencies:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '7.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: active_model_serializers
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.10'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.10'
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: devise
35
49
  requirement: !ruby/object:Gem::Requirement
@@ -44,6 +58,20 @@ dependencies:
44
58
  - - "~>"
45
59
  - !ruby/object:Gem::Version
46
60
  version: '4.8'
61
+ - !ruby/object:Gem::Dependency
62
+ name: discard
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.2'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.2'
47
75
  - !ruby/object:Gem::Dependency
48
76
  name: factory_bot_rails
49
77
  requirement: !ruby/object:Gem::Requirement
@@ -58,6 +86,20 @@ dependencies:
58
86
  - - "~>"
59
87
  - !ruby/object:Gem::Version
60
88
  version: '6.2'
89
+ - !ruby/object:Gem::Dependency
90
+ name: faker
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '2.19'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '2.19'
61
103
  - !ruby/object:Gem::Dependency
62
104
  name: rspec-rails
63
105
  requirement: !ruby/object:Gem::Requirement
@@ -83,21 +125,73 @@ files:
83
125
  - README.md
84
126
  - Rakefile
85
127
  - app/assets/config/polivalente_manifest.js
128
+ - app/assets/images/polivalente/user-sample.jpg
86
129
  - app/assets/stylesheets/polivalente/application.css
87
130
  - app/controllers/polivalente/application_controller.rb
131
+ - app/controllers/polivalente/autocomplete_controller.rb
132
+ - app/controllers/polivalente/ping_controller.rb
88
133
  - app/helpers/polivalente/application_helper.rb
134
+ - app/helpers/polivalente/color_helper.rb
135
+ - app/helpers/polivalente/enum_helper.rb
136
+ - app/helpers/polivalente/gravatar_helper.rb
137
+ - app/helpers/polivalente/tags_helper.rb
89
138
  - app/jobs/polivalente/application_job.rb
139
+ - app/jobs/polivalente/clean_trash_job.rb
90
140
  - app/mailers/polivalente/application_mailer.rb
141
+ - app/models/concerns/polivalente/archivable.rb
142
+ - app/models/concerns/polivalente/archiver.rb
143
+ - app/models/concerns/polivalente/commentable.rb
144
+ - app/models/concerns/polivalente/commentator.rb
145
+ - app/models/concerns/polivalente/content_hashable.rb
146
+ - app/models/concerns/polivalente/reactable.rb
147
+ - app/models/concerns/polivalente/reactor.rb
148
+ - app/models/concerns/polivalente/sortable.rb
149
+ - app/models/concerns/polivalente/taggable.rb
150
+ - app/models/concerns/polivalente/trashable.rb
151
+ - app/models/concerns/polivalente/trasher.rb
152
+ - app/models/concerns/polivalente/user_owned.rb
153
+ - app/models/concerns/polivalente/visibility.rb
91
154
  - app/models/polivalente/application_record.rb
155
+ - app/models/polivalente/archive.rb
156
+ - app/models/polivalente/comment.rb
157
+ - app/models/polivalente/reaction.rb
158
+ - app/models/polivalente/tag.rb
159
+ - app/models/polivalente/tagging.rb
160
+ - app/models/polivalente/trash.rb
92
161
  - app/models/polivalente/user.rb
162
+ - app/serializers/polivalente/comment_serializer.rb
163
+ - app/serializers/polivalente/reaction_serializer.rb
164
+ - app/serializers/polivalente/tag_serializer.rb
165
+ - app/serializers/polivalente/tagging_serializer.rb
166
+ - app/serializers/polivalente/user_serializer.rb
93
167
  - app/views/layouts/polivalente/application.html.erb
94
168
  - config/initializers/devise.rb
95
169
  - config/locales/devise.en.yml
96
170
  - config/routes.rb
97
171
  - db/migrate/20220124153504_create_users.rb
172
+ - db/migrate/20220125040905_create_tags.rb
173
+ - db/migrate/20220125040916_create_taggings.rb
174
+ - db/migrate/20220125040920_create_active_storage_tables.active_storage.rb
175
+ - db/migrate/20220125040921_create_action_mailbox_tables.action_mailbox.rb
176
+ - db/migrate/20220125040922_create_action_text_tables.action_text.rb
177
+ - db/migrate/20220125044901_create_comments.rb
178
+ - db/migrate/20220125144339_create_reactions.rb
179
+ - db/migrate/20220125144342_create_trash.rb
180
+ - db/migrate/20220130033524_create_archives.rb
181
+ - lib/generators/polivalente/install/install_generator.rb
182
+ - lib/generators/rails/concern/USAGE
183
+ - lib/generators/rails/concern/concern_generator.rb
184
+ - lib/generators/rails/concern/templates/concern.rb
185
+ - lib/generators/rails/stimulus/USAGE
186
+ - lib/generators/rails/stimulus/stimulus_generator.rb
187
+ - lib/generators/rails/stimulus/templates/controller.js
188
+ - lib/generators/rails/validator/USAGE
189
+ - lib/generators/rails/validator/templates/validator.rb
190
+ - lib/generators/rails/validator/validator_generator.rb
98
191
  - lib/polivalente.rb
99
192
  - lib/polivalente/engine.rb
100
193
  - lib/polivalente/railtie.rb
194
+ - lib/polivalente/user_locale.rb
101
195
  - lib/polivalente/version.rb
102
196
  - lib/tasks/polivalente_tasks.rake
103
197
  homepage: https://github.com/oleoneto/polivalente