cas-cms 0.1.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 (126) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +43 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/config/cas_manifest.js +2 -0
  6. data/app/assets/images/cas/black-star.svg +4 -0
  7. data/app/assets/images/cas/check.svg +4 -0
  8. data/app/assets/images/cas/dropzone-in.svg +4 -0
  9. data/app/assets/images/cas/loading.gif +0 -0
  10. data/app/assets/images/cas/white-star.svg +4 -0
  11. data/app/assets/javascripts/cas/application.js +51 -0
  12. data/app/assets/javascripts/cas/fileupload_manifest.js +269 -0
  13. data/app/assets/javascripts/cas/plugins/cas_image_gallery.js +353 -0
  14. data/app/assets/javascripts/cas/vendor/file_upload/canvas-to-blob.min.js +2 -0
  15. data/app/assets/javascripts/cas/vendor/file_upload/jquery.fileupload-image.js +326 -0
  16. data/app/assets/javascripts/cas/vendor/file_upload/jquery.fileupload-process.js +178 -0
  17. data/app/assets/javascripts/cas/vendor/file_upload/jquery.fileupload.js +1482 -0
  18. data/app/assets/javascripts/cas/vendor/file_upload/jquery.iframe-transport.js +224 -0
  19. data/app/assets/javascripts/cas/vendor/file_upload/jquery.ui.widget.js +572 -0
  20. data/app/assets/javascripts/cas/vendor/file_upload/load-image.all.min.js +2 -0
  21. data/app/assets/javascripts/cas/vendor/jquery.ui.touch-punch.min.js +11 -0
  22. data/app/assets/javascripts/cas/vendor/selectize.min.js +3 -0
  23. data/app/assets/stylesheets/cas/application.sass +17 -0
  24. data/app/assets/stylesheets/cas/colors.sass +6 -0
  25. data/app/assets/stylesheets/cas/form.sass +99 -0
  26. data/app/assets/stylesheets/cas/layout.sass +68 -0
  27. data/app/assets/stylesheets/cas/mixins.sass +34 -0
  28. data/app/assets/stylesheets/cas/plugins/attachments_form.sass +16 -0
  29. data/app/assets/stylesheets/cas/plugins/image_gallery.sass +128 -0
  30. data/app/assets/stylesheets/cas/tables.sass +13 -0
  31. data/app/assets/stylesheets/cas/typography.sass +27 -0
  32. data/app/assets/stylesheets/cas/vendors/selectize.default.css +394 -0
  33. data/app/assets/stylesheets/cas/vendors/simplegrid.css +298 -0
  34. data/app/controllers/cas/api/files_controller.rb +88 -0
  35. data/app/controllers/cas/application_controller.rb +17 -0
  36. data/app/controllers/cas/devise/sessions_controller.rb +9 -0
  37. data/app/controllers/cas/file_uploads_controller.rb +14 -0
  38. data/app/controllers/cas/sections/application_controller.rb +23 -0
  39. data/app/controllers/cas/sections/categories_controller.rb +47 -0
  40. data/app/controllers/cas/sections/contents_controller.rb +144 -0
  41. data/app/controllers/cas/sections_controller.rb +7 -0
  42. data/app/controllers/cas/users_controller.rb +61 -0
  43. data/app/helpers/cas/application_helper.rb +4 -0
  44. data/app/helpers/cas/form_helper.rb +11 -0
  45. data/app/jobs/cas/application_job.rb +4 -0
  46. data/app/jobs/cas/images/delete_job.rb +11 -0
  47. data/app/jobs/cas/images/promote_job.rb +11 -0
  48. data/app/jobs/cas/increment_pageviews_job.rb +11 -0
  49. data/app/models/cas/application_record.rb +5 -0
  50. data/app/models/cas/category.rb +6 -0
  51. data/app/models/cas/content.rb +59 -0
  52. data/app/models/cas/media_file.rb +77 -0
  53. data/app/models/cas/section.rb +10 -0
  54. data/app/models/cas/site.rb +5 -0
  55. data/app/models/cas/user.rb +39 -0
  56. data/app/uploaders/file_uploader.rb +26 -0
  57. data/app/views/cas/devise/sessions/new.html.erb +24 -0
  58. data/app/views/cas/sections/categories/_form.html.erb +19 -0
  59. data/app/views/cas/sections/categories/edit.html.erb +5 -0
  60. data/app/views/cas/sections/categories/index.html.erb +28 -0
  61. data/app/views/cas/sections/categories/new.html.erb +5 -0
  62. data/app/views/cas/sections/contents/_form_attachments.html.erb +42 -0
  63. data/app/views/cas/sections/contents/_form_attachments_template.html.erb +11 -0
  64. data/app/views/cas/sections/contents/_form_for_content.html.erb +85 -0
  65. data/app/views/cas/sections/contents/_form_for_survey.html.erb +36 -0
  66. data/app/views/cas/sections/contents/_form_images.html.erb +86 -0
  67. data/app/views/cas/sections/contents/edit.html.erb +6 -0
  68. data/app/views/cas/sections/contents/index.html.erb +71 -0
  69. data/app/views/cas/sections/contents/new.html.erb +5 -0
  70. data/app/views/cas/sections/index.html.erb +21 -0
  71. data/app/views/cas/shared/_error_messages.html.erb +12 -0
  72. data/app/views/cas/users/_form.html.erb +11 -0
  73. data/app/views/cas/users/edit.html.erb +2 -0
  74. data/app/views/cas/users/index.html.erb +34 -0
  75. data/app/views/cas/users/new.html.erb +2 -0
  76. data/app/views/layouts/cas/application.html.erb +71 -0
  77. data/config/initializers/acts_as_taggable.rb +1 -0
  78. data/config/initializers/devise.rb +277 -0
  79. data/config/initializers/shrine.rb +50 -0
  80. data/config/initializers/simple_form.rb +169 -0
  81. data/config/locales/devise.en.yml +64 -0
  82. data/config/locales/pt-BR.yml +242 -0
  83. data/config/locales/simple_form.en.yml +31 -0
  84. data/config/routes.rb +31 -0
  85. data/db/migrate/20170129212144_create_cas_sites.rb +15 -0
  86. data/db/migrate/20170219172958_create_cas_sections.rb +12 -0
  87. data/db/migrate/20170219175007_create_cas_contents.rb +17 -0
  88. data/db/migrate/20170612204500_create_cas_users.rb +14 -0
  89. data/db/migrate/20170613174724_create_cas_media_files.rb +20 -0
  90. data/db/migrate/20170613175912_create_cas_categories.rb +14 -0
  91. data/db/migrate/20170614171928_add_columns_to_cas_contents.rb +12 -0
  92. data/db/migrate/20170614172904_add_column_to_cas_sections.rb +6 -0
  93. data/db/migrate/20170615235532_add_devise_to_cas_users.rb +49 -0
  94. data/db/migrate/20170616011202_remove_password_from_cas_users.rb +5 -0
  95. data/db/migrate/20170618014204_v3_migration_fields.rb +30 -0
  96. data/db/migrate/20170623182702_add_column_category_id_to_cas_content.rb +6 -0
  97. data/db/migrate/20170624024648_rename_url_to_path_in_files.rb +7 -0
  98. data/db/migrate/20170625192119_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb +36 -0
  99. data/db/migrate/20170625192120_add_missing_unique_indices.acts_as_taggable_on_engine.rb +26 -0
  100. data/db/migrate/20170625192121_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb +20 -0
  101. data/db/migrate/20170625192122_add_missing_taggable_index.acts_as_taggable_on_engine.rb +15 -0
  102. data/db/migrate/20170625192123_change_collation_for_tag_names.acts_as_taggable_on_engine.rb +15 -0
  103. data/db/migrate/20170625192124_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb +23 -0
  104. data/db/migrate/20170713210101_add_location_to_cas_content.rb +5 -0
  105. data/db/migrate/20170714043036_add_url_to_content.rb +5 -0
  106. data/db/migrate/20170718201535_add_embedded_to_content.rb +5 -0
  107. data/db/migrate/20170801171952_categories_with_jsonb_metadata.rb +6 -0
  108. data/db/migrate/20170801173256_add_published_at_to_cas_contents.rb +6 -0
  109. data/db/migrate/20170801175407_add_description_to_cas_categories.rb +5 -0
  110. data/db/migrate/20170830000000_add_data_search_extensions.rb +17 -0
  111. data/db/migrate/20170830000001_add_search_index_to_cas_contents.rb +20 -0
  112. data/db/migrate/20170830000002_use_uuid_with_acts_as_taggable_references.rb +17 -0
  113. data/db/migrate/20170919181809_pageviews_default_to_zero.rb +9 -0
  114. data/db/migrate/20171201191059_add_domains_to_cas_site.rb +6 -0
  115. data/lib/cas.rb +22 -0
  116. data/lib/cas/config.rb +42 -0
  117. data/lib/cas/engine.rb +33 -0
  118. data/lib/cas/form_field.rb +54 -0
  119. data/lib/cas/remote_callbacks.rb +36 -0
  120. data/lib/cas/section_config.rb +84 -0
  121. data/lib/cas/setup.rb +43 -0
  122. data/lib/cas/version.rb +3 -0
  123. data/lib/devise/custom_failure.rb +16 -0
  124. data/lib/tasks/cas_tasks.rake +4 -0
  125. data/lib/templates/erb/scaffold/_form.html.erb +13 -0
  126. metadata +560 -0
@@ -0,0 +1,47 @@
1
+ require_dependency "cas/application_controller"
2
+
3
+ module Cas
4
+ class Sections::CategoriesController < Sections::ApplicationController
5
+ before_action :set_category, only: [:edit, :update, :destroy]
6
+
7
+ def index
8
+ @categories = @section.categories
9
+ end
10
+
11
+ def new
12
+ @category = ::Cas::Category.new
13
+ end
14
+
15
+ def edit
16
+ end
17
+
18
+ def create
19
+ @category = ::Cas::Category.new(category_params)
20
+ @category.section = @section
21
+
22
+ if @category.save
23
+ redirect_to section_categories_url(@section), notice: 'Categoria salva com sucesso.'
24
+ else
25
+ render :new
26
+ end
27
+ end
28
+
29
+ def update
30
+ if @category.update(category_params)
31
+ redirect_to section_categories_url(@section), notice: 'Categoria salva com sucesso.'
32
+ else
33
+ render :edit
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def set_category
40
+ @category = ::Cas::Category.find(params[:id])
41
+ end
42
+
43
+ def category_params
44
+ params.require(:category).permit(:name)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,144 @@
1
+ require_dependency "cas/application_controller"
2
+
3
+ module Cas
4
+ class Sections::ContentsController < Sections::ApplicationController
5
+ class FileBelongsToAnotherAttachable < StandardError; end
6
+ before_action :set_content_type
7
+
8
+ def index
9
+ @contents = scope_content_by_role(@section.contents)
10
+ @contents = @contents.order('created_at desc').page(params[:page]).per(25)
11
+ end
12
+
13
+ def new
14
+ @content = ::Cas::Content.new
15
+ load_categories
16
+ end
17
+
18
+ def create
19
+ @content = ::Cas::Content.new(content_params)
20
+
21
+ success = nil
22
+ begin
23
+ ActiveRecord::Base.transaction do
24
+ @content.author_id = current_user.id
25
+ @content.section_id = @section.id
26
+ @content.tag_list = content_params[:tag_list] if content_params[:tag_list]
27
+ success = @content.save!
28
+ associate_files(@content, :images)
29
+ associate_files(@content, :attachments)
30
+ end
31
+ rescue ActiveRecord::RecordInvalid
32
+ success = nil
33
+ end
34
+
35
+ if success
36
+ redirect_to section_contents_url(@section), notice: 'Noticia salva com sucesso.'
37
+ else
38
+ load_categories
39
+ render :new
40
+ end
41
+ end
42
+
43
+ def edit
44
+ @content = scope_content_by_role.friendly.find(params[:id])
45
+
46
+ if @content.created_at.present? && @content.date.blank?
47
+ @default_date = @content.created_at
48
+ else
49
+ @default_date = @content.date
50
+ end
51
+
52
+ load_categories
53
+ end
54
+
55
+ def update
56
+ @content = scope_content_by_role.friendly.find(params[:id])
57
+
58
+ success = nil
59
+ begin
60
+ ActiveRecord::Base.transaction do
61
+ @content.tag_list = content_params[:tag_list]
62
+ success = @content.update!(content_params)
63
+ associate_files(@content, :images)
64
+ associate_files(@content, :attachments)
65
+ end
66
+ rescue ActiveRecord::RecordInvalid
67
+ success = nil
68
+ end
69
+
70
+ if success
71
+ redirect_to section_contents_path
72
+ else
73
+ load_categories
74
+ render :edit
75
+ end
76
+ end
77
+
78
+ def destroy
79
+ @content = scope_content_by_role.friendly.find(params[:id])
80
+ @content.destroy
81
+
82
+ redirect_to section_contents_path
83
+ end
84
+
85
+ private
86
+
87
+ def set_content_type
88
+ @content_type = @section.section_type
89
+ end
90
+
91
+ def content_params
92
+ @content_params ||= begin
93
+ result = params.require(:content).permit(
94
+ :category_id,
95
+ :title,
96
+ :location,
97
+ :summary,
98
+ :published,
99
+ :date,
100
+ :text,
101
+ :url,
102
+ :embedded,
103
+ :tag_list
104
+ )
105
+
106
+ unless result.keys.map(&:to_sym).include?(:published)
107
+ result[:published] = true
108
+ end
109
+
110
+ if params.dig(:content, :metadata).present?
111
+ # TODO the survey is sending empty questions and these
112
+ # are being saved anyway. We need to filter these.
113
+ result[:metadata] = params.dig(:content, :metadata).to_unsafe_h
114
+ end
115
+
116
+ result
117
+ end
118
+ end
119
+
120
+ # form_key_name could be :images or :attachments
121
+ def associate_files(item, form_key_name)
122
+ return if params[form_key_name].blank?
123
+
124
+ ActiveRecord::Base.transaction do
125
+ params[form_key_name][:files].to_unsafe_h.each do |key, value|
126
+ next if value["id"].blank?
127
+ begin
128
+ new_order = key.to_i + 1
129
+ is_cover = params[form_key_name][:cover_id] == value["id"]
130
+ image = ::Cas::MediaFile.find(value["id"])
131
+ if image.attachable.blank?
132
+ image.update!(cover: is_cover, order: new_order, attachable: item)
133
+ elsif image.order.to_i != new_order || image.cover != is_cover
134
+ image.update!(cover: is_cover, order: new_order)
135
+ elsif image.attachable != item
136
+ raise FileBelongsToAnotherAttachable, "File already belongs to #{item.attachable.id}"
137
+ end
138
+ rescue ActiveRecord::RecordNotFound
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,7 @@
1
+ module Cas
2
+ class SectionsController < ApplicationController
3
+ def index
4
+ @sections = Section.all
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,61 @@
1
+ require_dependency "cas/application_controller"
2
+
3
+ module Cas
4
+ class UsersController < ApplicationController
5
+ def index
6
+ @users = ::Cas::User.order('name ASC')
7
+ end
8
+
9
+ def new
10
+ @user = ::Cas::User.new
11
+ end
12
+
13
+ def create
14
+ @user = ::Cas::User.new(user_params)
15
+ @user.roles = user_params[:roles]
16
+ if @user.save
17
+ redirect_to users_path
18
+ else
19
+ render :new
20
+ end
21
+ end
22
+
23
+ def edit
24
+ @user = ::Cas::User.find(params[:id])
25
+ end
26
+
27
+ def update
28
+ @user = ::Cas::User.find(params[:id])
29
+ success = nil
30
+ if user_params[:password].blank? && user_params[:password_confirmation].blank?
31
+ without_password = user_params.except(:password, :password_confirmation)
32
+ success = @user.update_without_password(without_password)
33
+ else
34
+ success = @user.update_attributes(user_params)
35
+ end
36
+
37
+ if success
38
+ redirect_to users_path
39
+ else
40
+ render 'edit'
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def user_params
47
+ params
48
+ .require(:user)
49
+ .permit(
50
+ :name,
51
+ :email,
52
+ :password,
53
+ :password_confirmation,
54
+ :roles
55
+ )
56
+ .merge!(
57
+ roles: [params[:user][:roles]]
58
+ )
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,4 @@
1
+ module Cas
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,11 @@
1
+ module Cas
2
+ module FormHelper
3
+ def display_field?(name)
4
+ ::Cas::SectionConfig.new(@section).form_has_field?(name)
5
+ end
6
+
7
+ def field_properties(name)
8
+ ::Cas::FormField.new(@section, name)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ module Cas
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,11 @@
1
+ module Cas
2
+ module Images
3
+ class DeleteJob
4
+ include ::Sidekiq::Worker
5
+
6
+ def perform(data)
7
+ ::Shrine::Attacher.delete(data)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Cas
2
+ module Images
3
+ class PromoteJob
4
+ include ::Sidekiq::Worker
5
+
6
+ def perform(data)
7
+ ::Shrine::Attacher.promote(data)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Cas
2
+ class IncrementPageviewsJob
3
+ include ::Sidekiq::Worker
4
+
5
+ def perform(id, type)
6
+ if type == "content"
7
+ Cas::Content.find(id).increment!(:pageviews)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module Cas
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ module Cas
2
+ class Category < ApplicationRecord
3
+ belongs_to :section
4
+ has_many :contents
5
+ end
6
+ end
@@ -0,0 +1,59 @@
1
+ module Cas
2
+ class Content < ApplicationRecord
3
+ include ::PgSearch
4
+ extend ::FriendlyId
5
+
6
+ friendly_id :title, use: :slugged
7
+ acts_as_taggable
8
+
9
+ serialize :metadata
10
+
11
+ belongs_to :section
12
+ belongs_to :category
13
+ belongs_to :author, class_name: Cas::User
14
+ has_many :images, ->{ where(media_type: :image).order("cas_media_files.order ASC") }, class_name: Cas::MediaFile, as: :attachable, dependent: :destroy
15
+ has_many :attachments, ->{ where(media_type: :attachment).order("cas_media_files.order ASC") }, class_name: Cas::MediaFile, as: :attachable, dependent: :destroy
16
+ has_one :cover_image, ->{ where(media_type: :image, cover: true) }, class_name: Cas::MediaFile, as: :attachable
17
+
18
+ validates :title, presence: true
19
+
20
+ before_validation :set_published_at
21
+ before_save :cache_tags
22
+
23
+ scope :published, ->{ where(published: true) }
24
+
25
+ pg_search_scope :search, ->(query) do
26
+ {
27
+ query: query,
28
+ against: [:title, :text, :location, :tags_cache],
29
+ order_within_rank: "cas_contents.published_at DESC"
30
+ }
31
+ end
32
+
33
+ def date_year
34
+ date.year
35
+ end
36
+
37
+ def metadata
38
+ if self[:metadata].is_a?(String)
39
+ JSON.parse(self[:metadata])
40
+ else
41
+ super
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def set_published_at
48
+ if published_at.blank? && published
49
+ self.published_at = Time.now
50
+ end
51
+ end
52
+
53
+ # so we can fulltext search appropriatelly with pg_search
54
+ def cache_tags
55
+ category_name = "#{category.name if category.present?}"
56
+ self.tags_cache = (tag_list + category_name).flatten.join(", ")
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,77 @@
1
+ module Cas
2
+ class MediaFile < ApplicationRecord
3
+ include FileUploader::Attachment.new(:file)
4
+
5
+ class UnknownPath < StandardError; end
6
+ class UnknownFileService < StandardError; end
7
+
8
+ belongs_to :attachable, polymorphic: true
9
+ belongs_to :author, class_name: "Cas::User"
10
+
11
+ before_validation :set_media_type
12
+ before_save :set_image_as_unique_cover
13
+
14
+ scope :cover, ->{ where(cover: true) }
15
+ scope :non_cover, ->{ where(cover: false) }
16
+ scope :usable, -> { where.not(attachable_id: nil) }
17
+
18
+ def url(version:, use_cdn: true)
19
+ cdn = ENV.fetch("CDN_HOST", nil) if use_cdn
20
+
21
+ # When `path` is present, it means no gem was used for uploads, therefore
22
+ # the image has to be treat as raw URL.
23
+ if path.present?
24
+ if service.downcase == "s3"
25
+ bucket = ENV.fetch('S3_BUCKET')
26
+ region = ENV.fetch('S3_REGION', "s3")
27
+ if cdn
28
+ host = cdn
29
+ else
30
+ host = "https://s3-#{region}.amazonaws.com/#{bucket}"
31
+ end
32
+
33
+ [host, path].join("/").gsub(/([^:])\/\//, '\1/')
34
+ else
35
+ raise UnknownFileService
36
+ end
37
+
38
+ # Shrine gem uses `file_data`
39
+ elsif JSON.parse(file_data).present?
40
+ if cdn.present?
41
+ file_url(version.to_sym, host: cdn, public: true)
42
+ else
43
+ file_url(version.to_sym, public: true).gsub(/\?.*/, "")
44
+ end
45
+ else
46
+ raise UnknownPath
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def set_media_type
53
+ if self.media_type.blank?
54
+ nature = mime_type.scan(/([a-z]*)\/.*/)[0][0]
55
+ if nature == 'image'
56
+ self.media_type = 'image'
57
+ else
58
+ self.media_type = 'attachment'
59
+ end
60
+ end
61
+ end
62
+
63
+ # Only images can have a `cover`
64
+ def set_image_as_unique_cover
65
+ return unless media_type == 'image'
66
+ cover_file = MediaFile
67
+ .where(attachable: self.attachable, cover: true)
68
+ .where.not(id: id)
69
+
70
+ if cover?
71
+ cover_file.update_all(cover: false)
72
+ elsif cover_file.blank?
73
+ self.cover = true
74
+ end
75
+ end
76
+ end
77
+ end