-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
StorageAPI for Images and Files #526
Comments
One feature that I use from Cloudinary is upload a PDF and they generate a thumbnail. This is really useful. |
Thanks @jamlen for sharing your idea! Actually I agree that's pretty useful. Maybe we can create 'middlewares' for File Uploaders, if option |
@grabbou and @JedWatson, does this mean we're dropping support for Cloudinary in 0.3.x? |
@JohnnyEstilles not AFAIK. I am only developing new ImageField that will handle Local & Cloud images (upload/get/resize). A kind of free alternative if you already got hosting. In the future, we will be able to add As I have a deadline for tomorrow with that feature, will try to finish at least Amazon integration. You can check out my fork (branch feature-imagefile) for progress. For now, I've copied AmazonFile files - quite unhappy with the fact that I had to redeclare jQuery and templates as well. Probably in the final case, I will use the same as Amazon/S3 that are almost the same. |
@grabbou thanks for the update. I'll check out your branch. BTW, I love the concept of Types.Image being an extension of a simpler Types.File. The idea is brilliant. |
@JohnnyEstilles thanks! Great to hear that! |
We have a large number of pictures and don't want to pay cloudinary for that. |
Hi @yinear, although my current branch is a bit awkward, all those capabilities are handled now by Amazon (we had to migrate our CMS before today). I will try to finish that module today/tomorrow and in final version, it's going to work right that :) |
Cool, thanks. I haven't heard of them, it's good to close few of them by accident! We can catch up later today. I will start working on that probably around 8-12pm. It seems that we are gonna have busy time with writing new modules, if you can mail me using my email address listed in my profile - that might be speed up our work. Important |
Great spec, @grabbou. My ideas / questions: ImagesHow are you planning to implement the cloudinary-like resizing features? I love the idea, but had planned to do "upload-time" generation of images from the source, based on a definition that would live with the field (e.g. multiple keys w/ sizes, crop methods, etc). Emulating the on-the-fly image resizing from source (when missing) would definitely be cooler, but would also (I imagine) require either middleware to route the request for the image via a generator that integrates with the storage engine while serving the request, or a callback in the underscore methods (as checking for the size, and generating it if necessary, would be async (which the cloudinary methods are not, as all they do is generate a URL). I've got more ideas on this, but first wanted to get a sense of how you're planning for it to work end-to-end. CloudinaryI was originally going to leave the CloudinaryImage and CloudinaryImages field types in place, because I do expect our internal Image API to diverge from Cloudinary's. I'm happy to have one or two exceptions like this; I think "this other service processes your images" vs "keystone processes your image and stores it somewhere else (fs / s3 / etc)" are fundamentally different problems to solve. Especially because CloudinaryImage/s fields are so widely used at the moment, I recommend we build this alongside them, and focus on getting our generic Promises
Generally, without taking either side of the I'd be happy to offer a dual-api (as Mongoose does, where you can fire off async functions by passing a callback argument, but they also return a promise) but don't want to split the API into promises / async. My main reasoning for this is that it's quite easy to promisify async methods anyway, if authors prefer that api, and this keeps Keystone more consistent with core node / express APIs, so there are less things to learn for newcomers. I've found new users have trouble distinguishing the boundaries between what's Keystone, what's Mongoose and what's Express, which has reinforced the importance of a consistent API for me (something I'm a bit guilty of breaking with some early decisions, as concepts have evolved). Dealing with Breaking ChangesGiven the breaking changes in this, we'll need to publish it as a minor version bump (technically anything can break on 0.x.x versions in semver, but I like to go with "patch version bumps fix / add, minor version bumps break". So ideally we can line up these changes with the move to React, which I was going to publish as 0.3.0 (since some people will be running custom builds / hacks that reworking the Admin UI may interfere with). In the meantime, I'd like to keep Alternatively we could focus on adding new I suspect the Finally on breaking changes, with the options that we're replacing (like Refactoring the UpdateHandlerThis has been on my mind for a while, I've opened a new issue for it specifically (#533) because it can / should be done independently of this. I'll jump into it soon, if you don't get to it first ;) Finally, this is something I've been wanting to do to Keystone for quite a while, thanks for tackling it so comprehensively! :D |
Hi! So, basically, in answer to your question: ImagesMy initial idea was to provide users with underscored methods, let's say _resize(400, 500);
I quite don't like the point 4 and the fact that we will have to download high-res photo, but the initial idea is to save them in the cloud in untouched way, so then, we can modify them without uploading them again. They might be prefixed with Upload-time manipulation is better in a way that the first page load will be faster, but in case we are not satisfied with the process, we will have to re-upload it again, which might be confusing if we got dozens of images already uploaded. Fact, I agree that we can leave Cloudinary untouched. This will give us more flexibility in terms of implementing our own image manipulation methods without breaking current images. FilesOk, that's not a problem. We can follow that pattern (at least I am a huge fan of Breaking changesWell, personally, I'd rather prefer the style of doing everything all together. Of course -> we will split that in smaller steps with very small informal deadlines. The initial process will be probably like this:
Not sure if they will be divergent, I am going to use File as a base, as you can see from my spec, Image inherits almost everything from File :) OK, so -> we are changing keystone.set methods, but keeping proccess.env variables the same. We can then add wrapper in Storage Ideally, I would love to have at least kind of 'limited' repo access, so I can create branch here, called Cool, thanks for that issue. I will probably help you on refactoring that. What about (for the future) moving few fields and methods to Field (making them 'abstract') as I pointed before? You're welcome! Thanks for that superb CMS. I felt in love with it from the very beginning and it's fun that I can finally contribute to something I really like. I am going to start writing points 1 & 2 today, so the most important thing is to sort everything out before that :) Probably we will have few issues and suggestions while writing that :) |
I'm having a hard time following through the requirements set forth here that will be addressed via the storage API. Simple question: currently there is support for LocalFile, S3File, and AzureFile. Ideally I would like to be blind about the underlying storage system and just deal with a File. That way, when I'm testing things locally it will use my local file system and when I deploy out there to some cloud it will use whatever file system transparently. Will that be possible with what's being defined here? |
Yes, @webteckie, basically, StorageAPI works like the way you'd like it to work. It's all about configuration. You can use default available provider by just accessing one Storage method that will give you current client instance. If you want to use |
Excellent! And, will there also be an embedded wysiwyg property as part of it so that when you view the file in the Admin UI a preferred wysiwyg editor is used to view its content? |
@grabbou nice to see you back, hope you're better! we're nearly ready for the major new release with React and Express 4 so we'll pick this up after that goes out :) |
@sebmck Yeah, I can see that. I am glad it keeps developing. I think I'll speak with one of contributors on Slack to get an overview what's going on at the moment. I am in for our next Hangouts meeting. @JedWatson Great work on React! Sorry for being away. Let's start contributing once again! :) |
Any further progress on this? Can I help with it? |
@okjake I'm so glad you asked. I've been busily wrapping up the rebuild of the Admin UI and this is the other major blocker to the new version, I'd really love someone to take it on and help with it. Things have moved on quite a bit since @grabbou's work, and are encapsulated in the |
OK great! Just added my email to my profile. |
Great, you're invited! |
Hi @JedWatson Any progress on this? I need to implement something like this:
It is quite difficult to implement it on the current implementation of |
@JedWatson I'd also love to help with this. @okjake If you can take a half hour to bring me up to speed with what you've accomplished, I can dedicate a couple hours a week to help with the integration of this. My email is available in my GitHub profile. |
@mahnunchik @christroutner Happy to help with this effort as well. A little more discussion over on the Google Group post. Let's get on the Google Group Post for continued discussion. |
@jeffreypriebe @christroutner @JedWatson @grabbou |
@jamlen |
@rnjailamba from memory I think the main bit that does it is I use this in my model: Sermon.add({
//...
presentation: { type: Types.CloudinaryImage, collapse: true, allowedTypes:['application/pdf'], autoCleanup : true }
//....
});
Sermon.schema.virtual('thumbnail').get(function() {
if (!this._.presentation.src()) {
return {
img: null,
isPdf: false
};
}
return {
img: this._.presentation.src({ transformation: 'mottram-thumb'}),
isPdf: this.presentation.format.toLowerCase() === 'pdf'
};
}); and then this in the jade: if sermon.presentation.exists
if sermon.thumbnail.isPdf
a(href=sermon.presentation.url).sermonImg
img(src=sermon.thumbnail.img)
else
img(src=sermon.thumbnail.img)
else
img(src='/images/nopdf.gif') |
Hi, I hope this is the right place to add this, I'm curious about whether or not it is possible to setup the S3 storage so that actual file uploads are performed not by keystone in a:
The application I am building performs a lot of file uploads, and in the past I have used this signing strategy in other application frameworks with good success. Below is a more concrete snippet of what this means. In my experience this can quite significantly reduce load at no loss, especially since Keystone only stores the metadata anyway. I'm sure I can tinker my way to this, but maybe there is already some kind of support for it that I am missing? |
@LuganOcode this is something I'd like to look into for all the file-type fields once we've consolidated them, but that's looking like a post-0.4 thing. We need to get the foundations fixed before we build more features on top of them. What will be available though is the ability to pass the metadata directly to Keystone so in the meantime (with 0.4) you could handle the upload to S3 directly with an extension to your app then submit the metadata to Keystone. |
@JedWatson Thanks for the tip. We are already using 0.4 beta - the React UI stuff was just too nice to pass up. So one more hint then, if we go in the direction that you suggest, would this imply essentially not using the 'S3FileType', and instead just just using a custom model object, or you would instead mean using the standard S3FileType and just processing submissions with the metadata but without the actual files? Since this does seem to be the/an appropriate place, I'm going to point out one more small hiccup that we ran into with file uploads and the
In the end we just moved this work into the view controller, and replicated the work otherwise handled by the updatehandler (and I think ExpressJS is also maybe handling the temporary storage), but this is probably not the ultimate recommended way to handle such issues. BTW this is a quite amazing thing! |
See also keystonejs/keystone#3209 (comment) for my take on how Storage Adapters are responsible for handling files, the rest of Keystone should just work with File objects (the bags of data that the Storage Adapter uses to represent the file). |
Maby im in the wrong discussion for this question, but what is the status about using s3 instead of cloudinary in the wysiwyg editor for image uploading? Currently we use "wysiwyg cloudinary images" in the init, but is it possible to use s3 instead? If so, i would be very pleased if someone could tell me how. |
Draft
To store Images, we have to use CloudinaryImage if we are on Heroku and so on or if we like ability to resize or manipulate our image. Not everybody is happy with paying monthly-fee for having only those features. Although Cloudinary is great - not everyone is using all of its features. That's why we need to kick our Image and Files to next level and make it like in other CMS systems.
Storage API
REPOSITORY: https://github.com/grabbou/StorageAPI/
Will be released as stand-alone NPM package and after that, integrated into Keystone.
Deliver
Keystone.StorageAPI
for developers to easily use storage (when available) in their projects. Storage API can be used either in fields or in our own views and modules. It's public. Define once, use multiple times.Directory structure
Storage
obtain(options)
returns current storage provider based onkeystone.set('storage')
specified. We can optionally pass argumentprovider
here in case our custom field allows to set different provider for it (for example - files in S3, images in Cloudinary). Automatically checks whether given provider is available (file with it exists in ourproviders
folder).StorageClient
@constructor
- registers new client based onkeystone.set('storage config')
. This should be an object, specific for different providers. Can accept either single object (then we assume it's config forkeystone.set('storage')
or object array (in case we use multiple storage providers inside our app):upload(options)
,delete(options)
,exists(options)
- easy manipulation_ensureContainer()
- ensures that container exists - based onkeystone.set('storage container')
. We do not have any default value here as container name should be unique across cloud platformPlease note, that async methods are wrapped within promises (
Q
library) to avoid callback chain and provider better functionalityList of supported providers to start with
pkgcloud
- Amazon, Azure, HP, Rackspace, OpenShiftPros:
Storage
checks whetherkeystone.set('storage')
provider is available, if not - throwsnew Error('Unsupported provider')
.Fields
Removing multiple fields that are doing the same job and replacing them with following ones. We are going to delete
ui-azure.js
,ui-s3file.js
and many many more (with CSS3 stylesheets as well! Going simple now!)File
Simple field for handling files. Accepts any storage (uses default one specified in the project). Returns false if uploaded file doesn't match allowedTypes.
Schema:
url {String}
- full_res urlfile_name {String}
provider {String}
container {String}
- might be path in case we used FTP/SFTP.Accepted fields:
required
- if file is required. If yes and no file provided, returns an error.provider
- overwrites provider for that field. Remember to specify configuration. If array specified, multiple actions are performed.size
- maximum size - if wrong, returns an errorallowedTypes
Available hooks:
Image extends Field
Generic ImageField for uploading & handling images. Deletes
CloudinaryImage
as it's now built-in within this field. The same with other image-related fields.Schema:
Accepted fields:
Underscored methods
gm
). Key feature is to mimic Cloudinary functionality (the same method calls, almost the same effects). Our Node API simply checks whether image matching options specified is present on server, if not - we generate it and return a link. Please note that we are keeping ful-res image in the cloud storage just to have the ability to resize them in the future.Available hooks:
Wysiwyg
Removes hard-coded CloudinarySupport. Allows to upload file to default storage provider when available. Allows overwriting that by specifying
keystone.set('wysiwyg storage')
. To enable image upload - callkeystone.set('wysiwyg image upload')
. It means you can now upload your wysiwyg images easily wherever you want. Either to FTP or Amazon S3. Want to resize your images while uploading? Easy! Just setkeystone.set('wysiwyg image options')
and we will adjust your input usingImageField
undescored methods. No more resizing before uploading high-res photos.Breaking changes
keystone.set
configs, like cloudinary and so on.Updates
Update 1
new Error('Method not implemented')
so one can easily overwrite them and implement. Useful when you are writing everything from scratch and want to make sure that you declared everything needed to work.Update 2
updateHandler
needs to be rewritten. For now, we can add new fields to that case, but for the future, we should have in our field method returning value. Based on that value, we can decide what action we should take inActionQueue
. Something like{Field}.getUploadHandlerType
. No need to modify core files after that.The text was updated successfully, but these errors were encountered: