cas-cms 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +43 -0
- data/Rakefile +37 -0
- data/app/assets/config/cas_manifest.js +2 -0
- data/app/assets/images/cas/black-star.svg +4 -0
- data/app/assets/images/cas/check.svg +4 -0
- data/app/assets/images/cas/dropzone-in.svg +4 -0
- data/app/assets/images/cas/loading.gif +0 -0
- data/app/assets/images/cas/white-star.svg +4 -0
- data/app/assets/javascripts/cas/application.js +51 -0
- data/app/assets/javascripts/cas/fileupload_manifest.js +269 -0
- data/app/assets/javascripts/cas/plugins/cas_image_gallery.js +353 -0
- data/app/assets/javascripts/cas/vendor/file_upload/canvas-to-blob.min.js +2 -0
- data/app/assets/javascripts/cas/vendor/file_upload/jquery.fileupload-image.js +326 -0
- data/app/assets/javascripts/cas/vendor/file_upload/jquery.fileupload-process.js +178 -0
- data/app/assets/javascripts/cas/vendor/file_upload/jquery.fileupload.js +1482 -0
- data/app/assets/javascripts/cas/vendor/file_upload/jquery.iframe-transport.js +224 -0
- data/app/assets/javascripts/cas/vendor/file_upload/jquery.ui.widget.js +572 -0
- data/app/assets/javascripts/cas/vendor/file_upload/load-image.all.min.js +2 -0
- data/app/assets/javascripts/cas/vendor/jquery.ui.touch-punch.min.js +11 -0
- data/app/assets/javascripts/cas/vendor/selectize.min.js +3 -0
- data/app/assets/stylesheets/cas/application.sass +17 -0
- data/app/assets/stylesheets/cas/colors.sass +6 -0
- data/app/assets/stylesheets/cas/form.sass +99 -0
- data/app/assets/stylesheets/cas/layout.sass +68 -0
- data/app/assets/stylesheets/cas/mixins.sass +34 -0
- data/app/assets/stylesheets/cas/plugins/attachments_form.sass +16 -0
- data/app/assets/stylesheets/cas/plugins/image_gallery.sass +128 -0
- data/app/assets/stylesheets/cas/tables.sass +13 -0
- data/app/assets/stylesheets/cas/typography.sass +27 -0
- data/app/assets/stylesheets/cas/vendors/selectize.default.css +394 -0
- data/app/assets/stylesheets/cas/vendors/simplegrid.css +298 -0
- data/app/controllers/cas/api/files_controller.rb +88 -0
- data/app/controllers/cas/application_controller.rb +17 -0
- data/app/controllers/cas/devise/sessions_controller.rb +9 -0
- data/app/controllers/cas/file_uploads_controller.rb +14 -0
- data/app/controllers/cas/sections/application_controller.rb +23 -0
- data/app/controllers/cas/sections/categories_controller.rb +47 -0
- data/app/controllers/cas/sections/contents_controller.rb +144 -0
- data/app/controllers/cas/sections_controller.rb +7 -0
- data/app/controllers/cas/users_controller.rb +61 -0
- data/app/helpers/cas/application_helper.rb +4 -0
- data/app/helpers/cas/form_helper.rb +11 -0
- data/app/jobs/cas/application_job.rb +4 -0
- data/app/jobs/cas/images/delete_job.rb +11 -0
- data/app/jobs/cas/images/promote_job.rb +11 -0
- data/app/jobs/cas/increment_pageviews_job.rb +11 -0
- data/app/models/cas/application_record.rb +5 -0
- data/app/models/cas/category.rb +6 -0
- data/app/models/cas/content.rb +59 -0
- data/app/models/cas/media_file.rb +77 -0
- data/app/models/cas/section.rb +10 -0
- data/app/models/cas/site.rb +5 -0
- data/app/models/cas/user.rb +39 -0
- data/app/uploaders/file_uploader.rb +26 -0
- data/app/views/cas/devise/sessions/new.html.erb +24 -0
- data/app/views/cas/sections/categories/_form.html.erb +19 -0
- data/app/views/cas/sections/categories/edit.html.erb +5 -0
- data/app/views/cas/sections/categories/index.html.erb +28 -0
- data/app/views/cas/sections/categories/new.html.erb +5 -0
- data/app/views/cas/sections/contents/_form_attachments.html.erb +42 -0
- data/app/views/cas/sections/contents/_form_attachments_template.html.erb +11 -0
- data/app/views/cas/sections/contents/_form_for_content.html.erb +85 -0
- data/app/views/cas/sections/contents/_form_for_survey.html.erb +36 -0
- data/app/views/cas/sections/contents/_form_images.html.erb +86 -0
- data/app/views/cas/sections/contents/edit.html.erb +6 -0
- data/app/views/cas/sections/contents/index.html.erb +71 -0
- data/app/views/cas/sections/contents/new.html.erb +5 -0
- data/app/views/cas/sections/index.html.erb +21 -0
- data/app/views/cas/shared/_error_messages.html.erb +12 -0
- data/app/views/cas/users/_form.html.erb +11 -0
- data/app/views/cas/users/edit.html.erb +2 -0
- data/app/views/cas/users/index.html.erb +34 -0
- data/app/views/cas/users/new.html.erb +2 -0
- data/app/views/layouts/cas/application.html.erb +71 -0
- data/config/initializers/acts_as_taggable.rb +1 -0
- data/config/initializers/devise.rb +277 -0
- data/config/initializers/shrine.rb +50 -0
- data/config/initializers/simple_form.rb +169 -0
- data/config/locales/devise.en.yml +64 -0
- data/config/locales/pt-BR.yml +242 -0
- data/config/locales/simple_form.en.yml +31 -0
- data/config/routes.rb +31 -0
- data/db/migrate/20170129212144_create_cas_sites.rb +15 -0
- data/db/migrate/20170219172958_create_cas_sections.rb +12 -0
- data/db/migrate/20170219175007_create_cas_contents.rb +17 -0
- data/db/migrate/20170612204500_create_cas_users.rb +14 -0
- data/db/migrate/20170613174724_create_cas_media_files.rb +20 -0
- data/db/migrate/20170613175912_create_cas_categories.rb +14 -0
- data/db/migrate/20170614171928_add_columns_to_cas_contents.rb +12 -0
- data/db/migrate/20170614172904_add_column_to_cas_sections.rb +6 -0
- data/db/migrate/20170615235532_add_devise_to_cas_users.rb +49 -0
- data/db/migrate/20170616011202_remove_password_from_cas_users.rb +5 -0
- data/db/migrate/20170618014204_v3_migration_fields.rb +30 -0
- data/db/migrate/20170623182702_add_column_category_id_to_cas_content.rb +6 -0
- data/db/migrate/20170624024648_rename_url_to_path_in_files.rb +7 -0
- data/db/migrate/20170625192119_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb +36 -0
- data/db/migrate/20170625192120_add_missing_unique_indices.acts_as_taggable_on_engine.rb +26 -0
- data/db/migrate/20170625192121_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb +20 -0
- data/db/migrate/20170625192122_add_missing_taggable_index.acts_as_taggable_on_engine.rb +15 -0
- data/db/migrate/20170625192123_change_collation_for_tag_names.acts_as_taggable_on_engine.rb +15 -0
- data/db/migrate/20170625192124_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb +23 -0
- data/db/migrate/20170713210101_add_location_to_cas_content.rb +5 -0
- data/db/migrate/20170714043036_add_url_to_content.rb +5 -0
- data/db/migrate/20170718201535_add_embedded_to_content.rb +5 -0
- data/db/migrate/20170801171952_categories_with_jsonb_metadata.rb +6 -0
- data/db/migrate/20170801173256_add_published_at_to_cas_contents.rb +6 -0
- data/db/migrate/20170801175407_add_description_to_cas_categories.rb +5 -0
- data/db/migrate/20170830000000_add_data_search_extensions.rb +17 -0
- data/db/migrate/20170830000001_add_search_index_to_cas_contents.rb +20 -0
- data/db/migrate/20170830000002_use_uuid_with_acts_as_taggable_references.rb +17 -0
- data/db/migrate/20170919181809_pageviews_default_to_zero.rb +9 -0
- data/db/migrate/20171201191059_add_domains_to_cas_site.rb +6 -0
- data/lib/cas.rb +22 -0
- data/lib/cas/config.rb +42 -0
- data/lib/cas/engine.rb +33 -0
- data/lib/cas/form_field.rb +54 -0
- data/lib/cas/remote_callbacks.rb +36 -0
- data/lib/cas/section_config.rb +84 -0
- data/lib/cas/setup.rb +43 -0
- data/lib/cas/version.rb +3 -0
- data/lib/devise/custom_failure.rb +16 -0
- data/lib/tasks/cas_tasks.rake +4 -0
- data/lib/templates/erb/scaffold/_form.html.erb +13 -0
- 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,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,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
|