polivalente 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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