Skip to content

Commit

Permalink
Merge pull request smart-village-solutions#159 from ikuseiGmbH/featur…
Browse files Browse the repository at this point in the history
…e/SVA-226-cms-fileupload

Feature: CMS File upload
  • Loading branch information
Finn Heemeyer authored Mar 22, 2022
2 parents 64cccf1 + 37adf3b commit 12b5edb
Show file tree
Hide file tree
Showing 14 changed files with 283 additions and 10 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ gem "jbuilder", "~> 2.5"
# gem "redis", "~> 4.0"
# Use Active Model has_secure_password
# gem "bcrypt", "~> 3.1.7"
gem "fog-aws"

gem "rake", "13.0.1"

Expand Down
29 changes: 29 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ GEM
regexp_parser (~> 1.5)
xpath (~> 3.2)
childprocess (3.0.0)
coderay (1.1.3)
coffee-rails (5.0.0)
coffee-script (>= 2.2.0)
railties (>= 5.2.0)
Expand All @@ -90,12 +91,29 @@ GEM
debase-ruby_core_source (>= 0.10.2)
debase-ruby_core_source (0.10.10)
erubi (1.8.0)
excon (0.92.0)
execjs (2.7.0)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
ffi (1.11.3)
fog-aws (3.13.0)
fog-core (~> 2.1)
fog-json (~> 1.1)
fog-xml (~> 0.1)
fog-core (2.3.0)
builder
excon (~> 0.71)
formatador (>= 0.2, < 2.0)
mime-types
fog-json (1.2.0)
fog-core
multi_json (~> 1.10)
fog-xml (0.1.4)
fog-core
nokogiri (>= 1.5.11, < 2.0.0)
formatador (1.1.0)
globalid (0.4.2)
activesupport (>= 4.2.0)
graphlient (0.3.7)
Expand Down Expand Up @@ -139,10 +157,14 @@ GEM
mini_mime (>= 0.1.1)
marcel (1.0.2)
method_source (0.9.2)
mime-types (3.4.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2022.0105)
mini_mime (1.1.2)
mini_portile2 (2.5.3)
minitest (5.11.3)
msgpack (1.2.10)
multi_json (1.15.0)
multipart-post (2.1.1)
nested_form_fields (0.8.2)
coffee-rails (>= 3.2.1)
Expand All @@ -155,6 +177,11 @@ GEM
parallel (1.19.2)
parser (3.0.0.0)
ast (~> 2.4.1)
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (3.1.0)
puma (3.12.6)
racc (1.5.2)
Expand Down Expand Up @@ -278,13 +305,15 @@ DEPENDENCIES
byebug
capybara (>= 2.15)
debase
fog-aws
graphlient (~> 0.3.7)
jbuilder (~> 2.5)
jquery-rails
kaminari
listen (>= 3.0.5, < 3.2)
nested_form_fields
nokogiri (>= 1.11.0.rc4)
pry-rails
puma (~> 3.12)
rack (~> 2.1.4)
rails (~> 6.1.4)
Expand Down
31 changes: 31 additions & 0 deletions app/assets/stylesheets/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,34 @@ h1 {
.ck.ck-splitbutton:hover > .ck-file-dialog-button + .ck-splitbutton__arrow::after {
width: 0 !important;
}

/* Fileupload: SVA-226 */
.form-control-file {
margin-bottom: 1em;
}

.upload-progress, .image-preview-wrapper {
display: none;
}

.image-preview-wrapper {
margin-top: 2em;
}

.image-preview-wrapper > label {
text-align: center;
text-decoration: underline;
display: block;
}

.image-preview {
max-width: 100%;
max-height: 200px;
object-fit: contain;
display: block;
margin: 0 auto;
}

.file-upload-collapse {
margin-bottom: 1em;
}
55 changes: 55 additions & 0 deletions app/controllers/minio_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

require "securerandom"

# This controller handles file uploads to minio buckets
# specifically for uploading images for e.g. POIs
class MinioController < ApplicationController

before_action :verify_current_user
before_action :minio_setup

def signed_url
bucket = @minio_config["bucket"]
headers = {}
options = { path_style: true }

url = @storage.put_object_url(
bucket,
generate_filename,
15.minutes.from_now.to_time.to_i,
headers,
options
)

respond_to do |format|
format.json { render json: { signedUrl: url } }
end
end

private

# We overwrite ApplicationController here, because we do not
# need a redirect but just a http response instead
def verify_current_user
head :unauthorized unless session["current_user"]
end

def minio_setup
@minio_config = session["current_user"]["minio"]
@storage = Fog::Storage.new(
provider: "AWS",
endpoint: @minio_config["endpoint"],
aws_access_key_id: @minio_config["access_key_id"],
aws_secret_access_key: @minio_config["secret_access_key"],
region: @minio_config["region"]
)
end

def generate_filename
extension = File.extname(params[:filename])
filename = File.basename(params[:filename], extension).gsub(" ", "-")

"cms_uploads/#{filename}-#{SecureRandom.hex}#{extension}"
end
end
2 changes: 2 additions & 0 deletions app/controllers/session_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ def create
session["current_user"]["applications"] = @current_user.applications
session["current_user"]["roles"] = @current_user.roles
session["current_user"]["permission"] = @current_user.permission
session["current_user"]["minio"] = @current_user.minio
redirect_to root_path
else
flash[:error] = "E-Mail oder Passwort ist falsch"
redirect_to log_in_path
end
end

end

def destroy
Expand Down
2 changes: 2 additions & 0 deletions app/javascript/packs/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ require('../partials/_leaflet_map');
// ckeditor custom build
require('../ckeditor');

require('./fileupload')

// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
Expand Down
61 changes: 61 additions & 0 deletions app/javascript/packs/fileupload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const axios = require('axios')

async function getSignedUrl(filename) {
let response = await axios.get('/minio/signed_url.json?filename=' + filename);
return response.data.signedUrl;
}

async function upload(file, signedUrl, formIndex) {

$('.upload-progress-' + formIndex).show('slow');

await axios.put(signedUrl, file, {
headers: {
'Content-Type': file.type
},
onUploadProgress: (e) => {
let percentCompleted = Math.round((e.loaded * 100) / e.total);
$('.upload-progress-bar-' + formIndex)
.attr('aria-valuenow', percentCompleted)
.css('width', percentCompleted + '%');
},
});
}

async function handleFileChange(e) {
try {
// Get upload URL
const file = e.target.files[0];
// There might be multiple forms on the page
const formIndex = e.target.dataset.index;
const signedUrl = await getSignedUrl(file.name);
const fileUrl = signedUrl.split('?')[0];

// Upload the file
await upload(file, signedUrl, formIndex);

$('.image-preview-' + formIndex).attr('src', fileUrl);
$('.image-preview-wrapper-' + formIndex).show('slow');

// Put the url of the uploaded file into the url input and disable it
$(e.target).closest('.nested-medium-form').find('.media-content-url')
.val(fileUrl)
.attr('disabled', true);
} catch (e) {
console.log(e);
}
}

// Saving this globally, because we need to rebind events
// when a new nested form is added
window.bindFileUploadEvents = () => {
$('.upload-toggle').click((e) => {
const formIndex = e.target.dataset.index;
$('.file-input-' + formIndex).click();
});
$('.file-input').change(handleFileChange);
}

$(() => {
window.bindFileUploadEvents();
});
37 changes: 37 additions & 0 deletions app/javascript/partials/_nested_forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ $(function() {
}
});


// We need to know the amount of forms at DOM load to know which classes we have to fix,
// see below in afterAddForm callback
const nestedMediaFormCount = $('.nested-medium-form').length;

// media not nested in a content block, for example in events.
// everything with classes here, because in content blocks nested-media will appear multiple times
$('.nested-media').nestedForm({
Expand All @@ -83,6 +88,38 @@ $(function() {
...defaultNestedFormsOptions,
beforeAddForm: ($container) => {
$container.children('.nested-medium-form').removeClass('d-none');
},
afterAddForm: function(_, $form) {

// If we only have one nested media form, we don't have to do anything
// becasue the index of 0 is already the right one
if ($('.nested-medium-form').length === 1) {
return;
}

// Increment the index on all elements of the new form by 100
let oldFormIndex = nestedMediaFormCount - 1;
let formIndex = $('.nested-medium-form').length - 1;

$form.find('.upload-toggle').attr('data-target', '.file-upload-collapse-' + formIndex);
$form.find('[data-index]').attr('data-index', formIndex);

const classNamesToFix = [
'file-upload-collapse',
'file-input',
'upload-progress',
'upload-progress-bar',
'image-preview-wrapper',
'image-preview'
]

classNamesToFix.forEach((className) => {
let $el = $form.find('.' + className);
$el.removeClass(className + '-' + oldFormIndex);
$el.addClass(className + '-' + formIndex);
});

window.bindFileUploadEvents();
}
});
});
Expand Down
20 changes: 11 additions & 9 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
class User
attr_accessor :name, :email, :data_provider, :applications,
:authentication_token, :roles, :permission
:authentication_token, :roles, :permission, :minio

def initialize(email:, password: nil, data_provider: nil, applications: nil, authentication_token: nil, roles: nil, permission: nil)
@email = email
@password = password
@authentication_token = authentication_token
@data_provider = data_provider
@roles = roles
@applications = applications
@permission = permission
def initialize(user_data)
@email = user_data[:email]
@password = user_data[:password]
@authentication_token = user_data[:authentication_token]
@data_provider = user_data[:data_provider]
@roles = user_data[:roles]
@applications = user_data[:applications]
@permission = user_data[:permission]
@minio = user_data[:minio]
end

def gravatar_url
Expand Down Expand Up @@ -45,6 +46,7 @@ def sign_in
@data_provider = data["data_provider"]
@roles = data["roles"]
@permission = data["user"]["role"]
@minio = data["minio"]
data
else
result.body
Expand Down
34 changes: 34 additions & 0 deletions app/views/shared/partials/_fileupload.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div class=file-upload-wrapper">
<p>
<button
class="btn btn-primary upload-toggle"
type="button"
data-toggle="collapse"
data-target=".file-upload-collapse-<%= index %>"
data-index="<%= index %>"
aria-expanded="false"
aria-controls="collapseExample">
Bild vom Computer hochladen
</button>
</p>
<div class="collapse file-upload-collapse file-upload-collapse-<%= index %>">
<div class="card card-body">
<input class="form-control-file file-input file-input-<%= index %>" type="file" data-index="<%= index %>">
<div class="upload-progress upload-progress-<%= index %>">
<div class="progress">
<div
class="progress-bar upload-progress-bar upload-progress-bar-<%= index %>"
role="progressbar"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
</div>
<div class="image-preview-wrapper image-preview-wrapper-<%= index %>">
<label>Bildvorschau:</label>
<img src="" class="image-preview image-preview-<%= index %>" />
</div>
</div>
</div>
</div>
Loading

0 comments on commit 12b5edb

Please sign in to comment.