Skip to content

Commit 16a4657

Browse files
authored
Merge pull request #1770 from codidact/0valt/1769/uploads
File upload improvements
2 parents 10fbb1c + ee48e0f commit 16a4657

13 files changed

Lines changed: 146 additions & 58 deletions

File tree

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ gem 'counter_culture', '~> 3.2'
77
gem 'fastimage', '~> 2.2'
88
gem 'image_processing', '~> 1.12'
99
gem 'jquery-rails', '~> 4.5.0'
10+
gem 'mime-types', '~> 3.7'
1011
gem 'mysql2', '~> 0.5.4'
1112
gem 'puma', '~> 5.6'
1213
gem 'rails', '~> 7.2'

Gemfile.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ DEPENDENCIES
493493
listen (~> 3.7)
494494
maintenance_tasks (~> 2.2)
495495
memory_profiler (~> 1.0)
496+
mime-types (~> 3.7)
496497
minitest (~> 5.16.0)
497498
minitest-ci (~> 3.4.0)
498499
msgpack (~> 1.8)

app/assets/javascripts/site_settings.js

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
$(() => {
2+
/**
3+
* @type {Record<string, JQuery<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>>}
4+
*/
25
const settingEditFields = {
6+
'array': $(`<select class="form-element js-setting-edit" multiple></select>`),
37
'string': $(`<input type="text" class="form-element js-setting-edit" />`),
48
'integer': $('<input type="number" class="form-element js-setting-edit" />'),
59
'float': $('<input type="number" step="0.0001" class="form-element js-setting-edit" />'),
6-
'boolean': $(`<select class="form-element js-setting-edit"><option value></option><option value="true">true</option><option value="false">false</option></select>`),
10+
'boolean': $(`<select class="form-element js-setting-edit">
11+
<option value></option>
12+
<option value="true">true</option>
13+
<option value="false">false</option>
14+
</select>`),
715
'json': $(`<textarea rows="5" cols="100" class="form-element js-setting-edit"></textarea>`),
816
'text': $(`<textarea rows="5" cols="100" class="form-element js-setting-edit"></textarea>`)
917
};
@@ -26,9 +34,34 @@ $(() => {
2634
const data = await resp.json();
2735
const value = data.typed;
2836

29-
const form = settingEditFields[valueType].clone().val(!!value ? value.toString() : '')
30-
.attr('data-name', name).attr('data-community-id', communityId);
31-
$tgt.addClass('editing').html(form).append(`<button class="button is-primary is-filled js-setting-submit">Update</button>`);
37+
const field = settingEditFields[valueType].clone()
38+
.attr('data-name', name)
39+
.attr('data-community-id', communityId)
40+
.get(0);
41+
42+
if (valueType === 'array' && field instanceof HTMLSelectElement) {
43+
for (const opt of data.options ?? []) {
44+
const option = document.createElement('option');
45+
option.textContent = opt;
46+
option.value = opt;
47+
option.selected = value.includes(opt);
48+
field.add(option);
49+
}
50+
}
51+
else if (valueType === 'boolean') {
52+
field.value = value.toString();
53+
}
54+
else {
55+
field.value = !!value ? value.toString() : '';
56+
}
57+
58+
$tgt.addClass('editing')
59+
.html(field)
60+
.append(`<button class="button is-primary is-filled js-setting-submit has-display-block">Update</button>`);
61+
62+
if (valueType === 'array') {
63+
$(field).select2();
64+
}
3265
});
3366

3467
$(document).on('click', '.js-setting-submit', async (evt) => {
@@ -39,9 +72,14 @@ $(() => {
3972
const communityId = $input.data('community-id');
4073
const value = $input.val();
4174

42-
let body = {site_setting: {value}};
75+
const body = {
76+
site_setting: {
77+
value: Array.isArray(value) ? value.join(' ') : value
78+
}
79+
};
80+
4381
if (!!communityId) {
44-
body = Object.assign(body, {community_id: communityId});
82+
body.community_id = communityId;
4583
}
4684

4785
const resp = await QPixel.fetchJSON(`/admin/settings/${name}`, body);

app/controllers/posts_controller.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -509,12 +509,12 @@ def document
509509
end
510510

511511
def upload
512-
content_types = Rails.application.config.active_storage.web_image_content_types
513-
extensions = content_types.map { |ct| ct.gsub('image/', '') }
514-
unless helpers.valid_image?(params[:file])
515-
render json: { error: "Images must be one of #{extensions.join(', ')}" }, status: :bad_request
512+
unless helpers.valid_upload?(params[:file])
513+
render json: { error: "Images must be one of #{helpers.allowed_upload_extensions.join(', ')}" },
514+
status: :bad_request
516515
return
517516
end
517+
518518
@blob = ActiveStorage::Blob.create_and_upload!(io: params[:file], filename: params[:file].original_filename,
519519
content_type: params[:file].content_type)
520520
render json: { link: uploaded_url(@blob.key) }

app/controllers/users_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ def update_profile
398398
@user = current_user
399399

400400
if params[:user][:avatar].present?
401-
if helpers.valid_image?(params[:user][:avatar])
401+
if helpers.valid_upload?(params[:user][:avatar])
402402
@user.avatar.attach(params[:user][:avatar])
403403
else
404404
@user.errors.add(:avatar, 'must be a valid image')

app/helpers/uploads_helper.rb

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'mime/types'
2+
13
module UploadsHelper
24
def upload_remote_url(blob)
35
bucket = Rails.cache.fetch 'active_storage/s3/bucket' do
@@ -6,15 +8,23 @@ def upload_remote_url(blob)
68
"https://s3.amazonaws.com/#{bucket}/#{blob.is_a?(String) ? blob : blob.key}"
79
end
810

9-
##
10-
# Test if the given IO object is a valid image file by content type, extension, and content test.
11-
# @param io [File] The file to test.
11+
# Gets a list of MIME types allowed to be uploaded
12+
# @return [Array<String>]
13+
def allowed_upload_mime_types
14+
fallback_types = Rails.application.config.active_storage.web_image_content_types
15+
SiteSetting['AllowedUploadTypes'].presence || fallback_types
16+
end
17+
18+
# Gets a list of file extensions allowed to be uploaded
19+
# @return [Array<String>]
20+
def allowed_upload_extensions
21+
allowed_upload_mime_types.map { |mime| MIME::Types[mime].first.preferred_extension }
22+
end
23+
24+
# Is a given file a valid upload by content type?
25+
# @param io [File] file to check
1226
# @return [Boolean]
13-
def valid_image?(io)
14-
content_types = Rails.application.config.active_storage.web_image_content_types
15-
extensions = content_types.map { |ct| ct.gsub('image/', '') }
16-
submitted_extension = io.original_filename.split('.')[-1].downcase
17-
content_types.include?(io.content_type) && extensions.include?(submitted_extension) &&
18-
extensions.map(&:to_sym).include?(FastImage.type(io))
27+
def valid_upload?(io)
28+
allowed_upload_mime_types.include?(io.content_type)
1929
end
2030
end

app/models/site_setting.rb

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class SiteSetting < ApplicationRecord
99
scope :global, -> { for_community_id(nil) }
1010
scope :priority_order, -> { order(Arel.sql('IF(site_settings.community_id IS NULL, 1, 0)')) }
1111

12+
serialize :options, coder: YAML, type: Array
13+
1214
def self.[](name, community: nil)
1315
key = "SiteSettings/#{community.present? ? community.id : RequestContext.community_id}/#{name}"
1416
cached = Rails.cache.fetch key, include_community: false do
@@ -48,20 +50,26 @@ def global?
4850
community_id.nil?
4951
end
5052

53+
# Is the setting array-valued?
54+
# @return [Boolean] check result
55+
def array?
56+
value_type.downcase == 'array'
57+
end
58+
5159
# Is the setting boolean-valued?
52-
# @return [Boolena] check result
60+
# @return [Boolean] check result
5361
def boolean?
5462
value_type.downcase == 'boolean'
5563
end
5664

5765
# Is the setting floating point number-valued?
58-
# @return [Boolena] check result
66+
# @return [Boolean] check result
5967
def float?
6068
value_type.downcase == 'float'
6169
end
6270

6371
# Is the setting integer-valued?
64-
# @return [Boolena] check result
72+
# @return [Boolean] check result
6573
def integer?
6674
value_type.downcase == 'integer'
6775
end
@@ -118,33 +126,3 @@ def self.all_communities(name)
118126
end
119127
end
120128
end
121-
122-
class SettingConverter
123-
def initialize(value)
124-
@value = value
125-
end
126-
127-
def as_string
128-
@value&.to_s
129-
end
130-
131-
def as_text
132-
@value&.to_s
133-
end
134-
135-
def as_integer
136-
@value&.to_i
137-
end
138-
139-
def as_float
140-
@value&.to_f
141-
end
142-
143-
def as_boolean
144-
ActiveModel::Type::Boolean.new.cast(@value)
145-
end
146-
147-
def as_json
148-
JSON.parse(@value)
149-
end
150-
end

app/views/site_settings/edit.html.erb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,16 @@
1515
<% end %>
1616
<div class="field">
1717
<%= f.label :value %>
18-
<%= f.text_field :value, class: "form-control" %>
18+
<% if @setting.array? %>
19+
<%= f.select :value,
20+
options_for_select(@setting.typed, selected: @setting.typed),
21+
{},
22+
class: 'form-element' %>
23+
<% else %>
24+
<%= f.text_field :value, class: "form-control" %>
25+
<% end %>
1926
</div>
2027
<div class="field">
21-
<%= f.submit "Update", class: "btn btn-info" %>
28+
<%= f.submit "Update", class: "btn btn-info has-display-block" %>
2229
</div>
2330
<% end %>

app/views/site_settings/index.html.erb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<% content_for :title, "Site Settings" %>
1+
<% content_for :title, 'Site Settings' %>
22

33
<h1><%= current_page?(global_settings_path) ? 'Global' : '' %> Site Settings</h1>
44
<p>
@@ -26,7 +26,8 @@
2626
<h4><%= setting.name %></h4>
2727
<div class="form-caption"><%= setting.description %></div>
2828
</td>
29-
<td class="site-setting--value js-setting-value<%= setting.text? ? '' : ' nowrap' %>" data-type="<%= setting.value_type %>" data-name="<%= setting.name %>"
29+
<td class="site-setting--value js-setting-value<%= setting.text? ? '' : ' nowrap' %>"
30+
data-type="<%= setting.value_type %>" data-name="<%= setting.name %>"
3031
data-community-id="<%= current_page?(global_settings_path) ? nil : RequestContext.community_id %>">
3132
<%= setting.typed %>
3233
</td>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class AddOptionsToSiteSettings < ActiveRecord::Migration[7.2]
2+
def change
3+
add_column :site_settings, :options, :string
4+
end
5+
end

0 commit comments

Comments
 (0)