I'm looking to build a photo management app and I've decided to use Filepicker.io with Amazon s3 to manage the uploads/hosting of static files. I plan on having Filepicker handle the upload of images to s3, and then I will store the url of the image in a database -- these urls will be embedded in a template. For example,
HTML:
<input type="file" name="datafile">
{{#if src}}
<img src='{{src}}'>
{{/if}}
Javascript :
'change input' : function (e, t) {
var file = e.currentTarget.files[0];
if (file) {
filepicker.store(file, function(fp){
// Set URL to fpURL
}, function(err){
console.log('error', err);
}, function(progress){
console.log('loading', progress);
});
}
}
My question: Is it better to store the filepicker url in the database? Or should I be saving the key url, which can link directly to s3?
My filePicker success object looks like this:
{url: "https://www.filepicker.io/api/file/wppeyWAUQaaX0HPgXQ",
size: 76511, type: "image/png",
key: "EdqmSpbDQziIvSfI4g_logo.png",
filename: "logo.png"}
We recommend storing the URL directly, as that way you can take advantage of the conversion features and other functionality we provide on top of the URLs. Plus, you don't have to mess with the S3 APIs directly and can perform GETs and POSTs on the url instead
Related
How can I make the CKFinder ASP.net S3 integration load content from a dynamic key prefix rather than just a root location?
I'm using CKEditor 5 and CKFinder 3 with the ASP.net Connector to allow image upload directly to an S3 bucket. The web application we are connecting this all to is not an ASP.net application.
Setting is up was simple enough by following the documentation.
However, our product is SaaS, so each time the CKFinder is launched, I need it to target a different key prefix in our bucket. Multiple websites run off the same app and each should be able to have their own gallery of images loaded via the CKFinder without being able to see the images belonging to other apps.
Our CKFinder Web.config:
<backend name="s3Bucket" adapter="s3">
<option name="bucket" value="myBucket" />
<option name="key" value="KEYHERE" />
<option name="secret" value="SECRETHERE" />
<option name="region" value="us-east-1" />
<option name="root" value="images" />
</backend>
This config gets content into the /images/ common key prefix "folder" just great, but for each app that uses the CKFinder, I want it to read from a different "root":
/images/app1Id/
/images/app2Id/
/images/app3Id/
Ideally, I want to set this when invoking the Editor/Finder instance; something like:
ClassicEditor.create( document.querySelector( '#textareaId' ), {
ckfinder: {
uploadUrl: '/ckfinder/connector?command=QuickUpload&type=Images&responseType=json',
connectorRoot: '/images/app1Id/'
},
toolbar: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'ckfinder' ],
heading: {
options: [
{ model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
{ model: 'heading1', view: 'h1', title: 'Heading 1', class: 'ck-heading_heading1' },
{ model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' }
]
}
});
Here I added connectorRoot: '/images/app1Id/' as an example of what I would like to pass.
Is there some way to do something like this? I've read through the ASP.net Connector docs and see that you can build your own connector and use pass to send it data, but having to compile and maintain a custom connector does not sound very fun. The S3 connectivity here is so great and easy... if only it let me be a little more specific.
The solution we came to was to modify and customize the CKFinder ASP Connector. Big thanks to the CKSource team for helping us to get this running.
ConnectorConfig.cs
namespace CKSource.CKFinder.Connector.WebApp
{
using System.Configuration;
using System.Linq;
using CKSource.CKFinder.Connector.Config;
using CKSource.CKFinder.Connector.Core.Acl;
using CKSource.CKFinder.Connector.Core.Builders;
using CKSource.CKFinder.Connector.Host.Owin;
using CKSource.CKFinder.Connector.KeyValue.FileSystem;
using CKSource.FileSystem.Amazon;
//using CKSource.FileSystem.Azure;
//using CKSource.FileSystem.Dropbox;
//using CKSource.FileSystem.Ftp;
using CKSource.FileSystem.Local;
using Owin;
public class ConnectorConfig
{
public static void RegisterFileSystems()
{
FileSystemFactory.RegisterFileSystem<LocalStorage>();
//FileSystemFactory.RegisterFileSystem<DropboxStorage>();
FileSystemFactory.RegisterFileSystem<AmazonStorage>();
//FileSystemFactory.RegisterFileSystem<AzureStorage>();
//FileSystemFactory.RegisterFileSystem<FtpStorage>();
}
public static void SetupConnector(IAppBuilder builder)
{
var allowedRoleMatcherTemplate = ConfigurationManager.AppSettings["ckfinderAllowedRole"];
var authenticator = new RoleBasedAuthenticator(allowedRoleMatcherTemplate);
var connectorFactory = new OwinConnectorFactory();
var connectorBuilder = new ConnectorBuilder();
var connector = connectorBuilder
.LoadConfig()
.SetAuthenticator(authenticator)
.SetRequestConfiguration(
(request, config) =>
{
config.LoadConfig();
var defaultBackend = config.GetBackend("default");
var keyValueStoreProvider = new FileSystemKeyValueStoreProvider(defaultBackend);
config.SetKeyValueStoreProvider(keyValueStoreProvider);
// Remove dummy resource type
config.RemoveResourceType("dummy");
var queryParameters = request.QueryParameters;
// This code lacks some input validation - make sure the user is allowed to access passed appId
string appId = queryParameters.ContainsKey("appId") ? Enumerable.FirstOrDefault(queryParameters["appId"]) : string.Empty;
// set up an array of StringMatchers for folder to hide!
StringMatcher[] hideFoldersMatcher = new StringMatcher[] { new StringMatcher(".*"), new StringMatcher("CVS"), new StringMatcher("thumbs"), new StringMatcher("__thumbs") };
// image type resource setup
var fileSystem_Images = new AmazonStorage(secret: "SECRET-HERE",
key: "KEY-HERE",
bucket: "BUCKET-HERE",
region: "us-east-1",
root: string.Format("images/{0}/userimages/", appId),
signatureVersion: "4");
string[] allowedExtentions_Images = new string[] {"gif","jpeg","jpg","png"};
config.AddBackend("s3Images", fileSystem_Images, string.Format("CDNURL-HERE/images/{0}/userimages/", appId), false);
config.AddResourceType("Images", resourceBuilder => {
resourceBuilder.SetBackend("s3Images", "/")
.SetAllowedExtensions(allowedExtentions_Images)
.SetHideFoldersMatchers(hideFoldersMatcher)
.SetMaxFileSize( 5242880 );
});
// file type resource setup
var fileSystem_Files = new AmazonStorage(secret: "SECRET-HERE",
key: "KEY-HERE",
bucket: "BUCKET-HERE",
region: "us-east-1",
root: string.Format("docs/{0}/userfiles/", appId),
signatureVersion: "4");
string[] allowedExtentions_Files = new string[] {"csv","doc","docx","gif","jpeg","jpg","ods","odt","pdf","png","ppt","pptx","rtf","txt","xls","xlsx"};
config.AddBackend("s3Files", fileSystem_Files, string.Format("CDNURL-HERE/docs/{0}/userfiles/", appId), false);
config.AddResourceType("Files", resourceBuilder => {
resourceBuilder.SetBackend("s3Files", "/")
.SetAllowedExtensions(allowedExtentions_Files)
.SetHideFoldersMatchers(hideFoldersMatcher)
.SetMaxFileSize( 10485760 );
});
})
.Build(connectorFactory);
builder.UseConnector(connector);
}
}
}
Items of note:
Added using System.Linq; so that FirstOrDefault works when getting the appId
We removed some of the fileSystems (Azure,Dropbox,Ftp) because we do not use those in our integration
In the CKFinder web.config file, we create a 'dummy' resource type because the Finder requires at least one to be present, but we then remove it during connector config and replace it with our desired resource types <resourceTypes><resourceType name="dummy" backend="default"></resourceType>resourceTypes>
Please note and take care that you're placing some sensitive information in this file. Please consider how you version control this (or not) and you may want to take additional actions to make this more secure
Initializing a CKEditor4/CKFinder3 instance
<script src="/js/ckeditor/ckeditor.js"></script>
<script src="/js/ckfinder3/ckfinder.js"></script>
<script type="text/javascript">
var myEditor = CKEDITOR.replace( 'bodyContent', {
toolbar: 'Default',
width: '100%',
startupMode: 'wysiwyg',
filebrowserBrowseUrl: '/js/ckfinder3/ckfinder.html?type=Files&appId=12345',
filebrowserUploadUrl: '/js/ckfinder3/connector?command=QuickUpload&type=Files&appId=12345',
filebrowserImageBrowseUrl: '/js/ckfinder3/ckfinder.html?type=Images&appId=12345',
filebrowserImageUploadUrl: '/js/ckfinder3/connector?command=QuickUpload&type=Images&appId=12345',
uploadUrl: '/js/ckfinder3/connector?command=QuickUpload&type=Images&responseType=json&appId=12345'
});
</script>
Items of note:
Due to other integration requirements, are using the Manual Integration method here, which requires us to manually define our filebrowserUrls
Currently, adding &pass=appId to your filebrowserUrls or adding config.pass = 'appId'; to your config.js file does not properly pass the desired value through to the editor
I believe this only fails when using the Manual Integration method (it should work correctly if you're using CKFinder.setupCKEditor())
ckfinder.html
<!DOCTYPE html>
<!--
Copyright (c) 2007-2019, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or https://ckeditor.com/sales/license/ckfinder
-->
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>CKFinder 3 - File Browser</title>
</head>
<body>
<script src="ckfinder.js"></script>
<script>
var urlParams = new URLSearchParams( window.location.search );
var myAppId = ( urlParams.has( 'appId' ) ) ? urlParams.get( 'appId' ) : '';
if ( myAppId !== '' ) {
CKFinder.start( { pass: 'appId', appId: myAppId } );
} else {
document.write( 'Error loading configuration.' );
}
</script>
</body>
</html>
Items of note:
This all seems to work much more smoothly when integrating into CKEditor5, but when integrating into CKEditor4, we experience a lot of issues getting the appId value to pass properly into the editor when utilizing the Manual Integration method for CKFinder
We modify the ckfinder.html file here to look for the desired url params and pass them into the CKFinder instance as it's started. This ensures they are passed through the entirety of the Finder instance
Check out this question for some great further details about this process as well as a more generic method of passing n params into your Finder instances: How do I pass custom values to CKFinder3 when instantiating a CKEditor4 instance?
Is there anyway to use JSON-LD without including the script inline in the HTML, but still get Google (& other) spiders to find it? Looking around I've seen some conflicting information.
If this was the JSON-LD file:
<script type="application/ld+json">
{
"#context" : "http://schema.org",
"#type" : "WebSite",
"name" : "Example Site",
"alternateName" : "example",
"description" : "Welcome to this WebSite",
"headline" : "Welcome to Website",
"logo" : "https://example.com/public/images/logo.png",
"url" : "https://example.com/"
}
</script>
And I have this in the head of the HTML:
<script src="/public/json-ld.json" type="application/ld+json"></script>
EDIT: I've also tried:
<link href="/public/json-ld.json" rel="alternate" type="application/ld+" />
Google Spiders seem to miss it and so does the testing tool unless I point it directly at the file. I'm trying to work around unsafe-inline in the CSP. And the only thing I can find is this, which would work in Chrome but don't want to be firing console errors on every other browser. Plus, I just like the idea of Schema.org data being abstracted out of the page structure. Would adding the JSON-LD to the sitemap for Google Webmaster Tools help?
Apologies, total noob to JSON-lD and keep ending up in email documentation (this would be for a site) or old documentation.
True, it can not be made external and it is not supported inline, but you can still achieve what you want by injecting it into the DOM via a JavaScript file.
Note: I am using an array for neatness so I can segment all the structured data elements and add an infinite amount of them. I have a more complicated version of this code on my websites and actually have an external server side rendered file masquerading as a JavaScript file.
An yes Google search bot does understand it. May take days for it to register and using Webmaster tools to force a re-crawl does not seem to force a refresh of JSON-LD data - seems like you just have to wait.
var structuredData = {
schema: {
corporation: {
'#context': 'http://schema.org',
'#type': 'Corporation',
'name': 'Acme',
'url': 'https://acme.com',
'contactPoint':
{
'#type': 'ContactPoint',
'telephone': '+1-1234-567-890',
'contactType': 'customer service',
'areaServed': 'US'
}
},
service: {
'#context': 'http://schema.org/',
'#type': 'Service',
'name': 'Code optimization',
'serviceOutput' : 'Externalized json',
'description': 'Inline json to externalized json'
},
},
init: function () {
var g = [];
var sd = structuredData;
g.push(sd.schema.corporation);
g.push(sd.schema.service);
//etc.
var o = document.createElement('script');
o.type = 'application/ld+json';
o.innerHTML = JSON.stringify(g);
var d = document; (d.head || d.body).appendChild(o);
}
}
structuredData.init();
Is there any function/hook for showing a loader while an Amazon S3 image downloads from Amazon S3 (or any image from anywhere for that matter)? I'm not currently using any CDNs or CloudFront, so my downloads can sometimes be slow. I'd like to just show a loader while the image is downloading. In my code I have:
{{#if uploadedCustomLogo}}
{{#with customLogo}}
{{#if isUploaded}}
<div class="img-wrapper">
<img src="{{this.url store='logos'}}" alt=""/>
</div>
{{else}}
{{> loading}}
{{/if}}
{{/with}}
{{/if}}
The issue is the uploading {{> loading }} loader-template runs fine, but it only lasts a fraction of a second because the actual upload is really fast. It's the download that can then take several seconds (sometimes up to twenty or so even on a small image). Is there any way to test/check if an image has been downloaded?
I used FF Inspector to see if there was a delay in the src getting set on the img tag but it gets set immediately. So the wait is really on S3... nothing changes in the DOM once it finally loads.
I'm using CollectionFS and the S3 adapter (Meteor-cfs-s3).
I figured it out. I was searching for the wrong thing on Google. The question is really how to use JQuery to listen for when an image has loaded. So you can just add your loader in your template, then hide it once the load event fires on the image. This simple code works great in Meteor:
Template.myTemplate.events({
'load #whateverImage': function(event) {
event.preventDefault();
// Hide your loader DIV (for example)
hideLoader();
},
The solution is to use has stored helper, I also had a problem figuring this one out isUploaded is to the meteor server but if you want to wait to for it to be uploaded to amazon s3 {{#if this.hasStored 'thumbs'}}
edit:
Here is my custom helper to check if it's upload & stored to amazon s3 (So I can safely create an amazon s3 url and display it to the user).
Which in my case looks like this:
uploadDoc: function () {
var fileId = Template.instance().posterData.get('fileId'); // You can ignore this, It's just how I get the file document id.
if (fileId)
return Images.findOne(fileId); // We need the file document
}
isUploadedAndStored: function (storage) {
if (this && this.copies && this.copies[storage])
return true;
}
sUrl: function () {
if (this && this.copies && this.copies.thumbs)
return 'https://s3-us-west-2.amazonaws.com/NAME/' + this.copies.thumbs.key;
}
using it like this:
{{#with uploadDoc}}
{{#if isUploadedAndStored 'thumbs'}}
<img class="attachment-thumbnail" src="{{sUrl}}">
{{else}}
{{>loading}}
{{/if}}
{{/with}}
How it works? When we subscribe to the uploaded file collection the document will not have copies and when it comes from the server it means it's actually saved on amazon s3, the this.hasStored does similar check but I found it to re-run too many times maybe need to report it to github so they can fix it.
I am implementing Cloudinary Jquery Upload. From my file upload webpage, if I surf to another website ( google.com, or any external website), and then click on the back button on the browser into this same file upload page, the upload fails.
The error message I gotten back is (from Firebug):
400 Bad Request
{"error":{"message":"Upload preset Must specify upload preset when using unsigned upload”}}
I did not enable unsigned upload on the Cloudinary management console
because my intention is a signed upload
This is the JSON data that is created at the backend for data-form-data:
{"timestamp":1409146953,"callback":"http://newappsure.herokuapp.com/vendor/cloudinary/cloudinary_cors.html","signature":"19071a3e822eed51238454e359589f52cccca042","api_key":"224456847515364”}
Below is the javascript and input HTML:
<script type="text/javascript”>
$.cloudinary.config({cloud_name:'dashy', api_key:’XXXXXXXXXXXXXXX'});
</script>
<input name="file" type="file" id="uploadinput" class="cloudinary-fileupload" data-cloudinary-field="image_upload"
data-form-data="" ></input>
<script>
$.ajax({
url: '/filer',
type: 'POST',
success: function(response){
$('#uploadinput').attr('data-form-data', response);
}
});
</script>
This is the Ruby backend that generates JSON:
post '/filer' do
ts = Time.now.getutc.to_time.to_i.to_s
secret="XXXXXXXXXXXXXXXXXXXXXX"
altogether="callback=http://newappsure.herokuapp.com/vendor/cloudinary/cloudinary_cors.html×tamp="+ts+secret
sig=Digest::SHA1.hexdigest altogether
ts = Time.now.getutc.to_time.to_i
{:timestamp => ts, :callback => "http://newappsure.herokuapp.com/vendor/cloudinary/cloudinary_cors.html", :signature => sig, :api_key =>"XXXXXXXXXXXXXXXX"}.to_json
end
Please help me understand what did I do wrong?
While your solution may work, the more optimal way is to update the upload parameters to call $(...).fileupload({formData: data}) where data is the parameters hash (not JSON serialized).
For more information:
http://support.cloudinary.com/entries/24950218-Why-is-updating-a-cloudinary-fileupload-field-dynamically-not-working-
Got it working by forcing the page to reload with the following snippets (ref: https://stackoverflow.com/a/9217531/3781343 and http://www.webdeveloper.com/forum/showthread.php?137518-How-to-refresh-page-after-clicking-quot-Back-quot-button)
<input type="hidden" id="refreshed" value="no">
<script type="text/javascript">
onload=function(){
var e=document.getElementById("refreshed");
if(e.value=="no")e.value="yes";
else{e.value="no";location.reload();}
}
</script>
I have an ExtJS4 site www.mysite.com where I serve index.html when a user enter the site. I want the user to be able to access the site with some param data redirected from another site. For example, www.mysite.com?q=10
How do I capture q=10 which I will use to retrieve some data from the database?
How do I send index.html so that browser retrieves javascript and css files. Once all the javascript and css files are loaded, I need to render a page displaying the result from the database?
Thanks
To get the url parameters I've done this :
var getParams = document.URL.split("?");
var params = Ext.urlDecode(getParams[getParams.length - 1]);
console.log(params.q) // you should see 10 being printed
If index.html is gonna come with some param in the url you can use the launch method to do an ajax request and bassed on that response render something
Ext.application({
name : 'MyAppWithDynamicFirstPage',
launch : function() {
var getParams = document.URL.split("?");
var params = Ext.urlDecode(getParams[getParams.length - 1]);
var q = params.q;
Ext.Ajax.request({
url: 'someServlet/getViewToRender',
params: {
'q': q
},
success: function(response, opts) {
//bassed on this you would do something else like render some specific panel on your viewport
},
failure: function(response, opts) {
console.log('server-side failure with status code ' + response.status);
}
});
}
});
I hope this was of some help.
Best regards.
Depends of your web server, programming language and architecture
Usually first ExtJs is loading with all js/css. After it loaded, data loads asynchronously from the server. But if you exactly know what are you doing, you can render your data into a global variable inside a script tag and then use it in the code.