cas-cms 0.1.0

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