stratosphere 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +262 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/javascripts/stratosphere/inflection.min.js +22 -0
  6. data/app/assets/javascripts/stratosphere/rsvp.min.js +9 -0
  7. data/app/assets/javascripts/stratosphere/stratosphere.bundled.min.js +31 -0
  8. data/app/assets/javascripts/stratosphere/stratosphere.js +297 -0
  9. data/app/controllers/stratosphere/application_controller.rb +4 -0
  10. data/app/helpers/stratosphere/application_helper.rb +4 -0
  11. data/app/vendor/assets/javascripts/rsvp/README.md +9 -0
  12. data/app/vendor/assets/javascripts/rsvp/bower.json +22 -0
  13. data/app/vendor/assets/javascripts/rsvp/composer.json +18 -0
  14. data/app/vendor/assets/javascripts/rsvp/package.json +59 -0
  15. data/app/vendor/assets/javascripts/rsvp/rsvp.js +1671 -0
  16. data/app/vendor/assets/javascripts/rsvp/rsvp.min.js +9 -0
  17. data/app/views/_attachment_field.html.erb +42 -0
  18. data/config/initializers/action_controller.rb +56 -0
  19. data/config/routes.rb +2 -0
  20. data/lib/generators/stratosphere/attachment/USAGE +8 -0
  21. data/lib/generators/stratosphere/attachment/attachment_generator.rb +18 -0
  22. data/lib/generators/stratosphere/install/USAGE +0 -0
  23. data/lib/generators/stratosphere/install/install_generator.rb +26 -0
  24. data/lib/generators/stratosphere/install/templates/stratosphere.rb.erb +10 -0
  25. data/lib/stratosphere.rb +23 -0
  26. data/lib/stratosphere/attachment.rb +51 -0
  27. data/lib/stratosphere/aws.rb +50 -0
  28. data/lib/stratosphere/config.rb +5 -0
  29. data/lib/stratosphere/engine.rb +5 -0
  30. data/lib/stratosphere/has_attachment.rb +63 -0
  31. data/lib/stratosphere/image.rb +65 -0
  32. data/lib/stratosphere/style.rb +23 -0
  33. data/lib/stratosphere/version.rb +3 -0
  34. data/lib/stratosphere/video.rb +63 -0
  35. data/lib/tasks/stratosphere_tasks.rake +4 -0
  36. data/test/dummy/README.rdoc +28 -0
  37. data/test/dummy/Rakefile +6 -0
  38. data/test/dummy/app/assets/javascripts/application.js +13 -0
  39. data/test/dummy/app/controllers/application_controller.rb +5 -0
  40. data/test/dummy/app/helpers/application_helper.rb +2 -0
  41. data/test/dummy/app/views/layouts/application.html.erb +13 -0
  42. data/test/dummy/bin/bundle +3 -0
  43. data/test/dummy/bin/rails +4 -0
  44. data/test/dummy/bin/rake +4 -0
  45. data/test/dummy/bin/setup +29 -0
  46. data/test/dummy/config.ru +4 -0
  47. data/test/dummy/config/application.rb +26 -0
  48. data/test/dummy/config/boot.rb +5 -0
  49. data/test/dummy/config/database.yml +25 -0
  50. data/test/dummy/config/environment.rb +5 -0
  51. data/test/dummy/config/environments/development.rb +41 -0
  52. data/test/dummy/config/environments/production.rb +79 -0
  53. data/test/dummy/config/environments/test.rb +42 -0
  54. data/test/dummy/config/initializers/assets.rb +11 -0
  55. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  56. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  57. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  58. data/test/dummy/config/initializers/inflections.rb +16 -0
  59. data/test/dummy/config/initializers/mime_types.rb +4 -0
  60. data/test/dummy/config/initializers/session_store.rb +3 -0
  61. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  62. data/test/dummy/config/locales/en.yml +23 -0
  63. data/test/dummy/config/routes.rb +4 -0
  64. data/test/dummy/config/secrets.yml +22 -0
  65. data/test/dummy/log/test.log +0 -0
  66. data/test/dummy/public/404.html +67 -0
  67. data/test/dummy/public/422.html +67 -0
  68. data/test/dummy/public/500.html +66 -0
  69. data/test/dummy/public/favicon.ico +0 -0
  70. data/test/integration/navigation_test.rb +10 -0
  71. data/test/stratosphere_test.rb +7 -0
  72. data/test/test_helper.rb +19 -0
  73. metadata +218 -0
@@ -0,0 +1,9 @@
1
+ /*!
2
+ * @overview RSVP - a tiny implementation of Promises/A+.
3
+ * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors
4
+ * @license Licensed under MIT license
5
+ * See https://raw.githubusercontent.com/tildeio/rsvp.js/master/LICENSE
6
+ * @version 3.0.16
7
+ */
8
+
9
+ (function(){"use strict";function t(t,n){for(var r=0,e=t.length;e>r;r++)if(t[r]===n)return r;return-1}function n(t){var n=t._promiseCallbacks;return (n||(n=t._promiseCallbacks={}), n)}function r(t,n){return"onerror"===t?void mn.on("error",n):2!==arguments.length?mn[t]:void(mn[t]=n)}function e(t){return"function"==typeof t||"object"==typeof t&&null!==t}function o(t){return"function"==typeof t}function i(t){return"object"==typeof t&&null!==t}function u(){}function s(){setTimeout(function(){for(var t,n=0;n<An.length;n++){t=An[n];var r=t.payload;r.guid=r.key+r.id,r.childGuid=r.key+r.childId,r.error&&(r.stack=r.error.stack),mn.trigger(t.name,t.payload)}An.length=0},50)}function a(t,n,r){1===An.push({name:t,payload:{key:n._guidKey,id:n._id,eventName:t,detail:n._result,childId:r&&r._id,label:n._label,timeStamp:bn(),error:mn["instrument-with-stack"]?new Error(n._label):null}})&&s()}function c(){return new TypeError("A promises callback cannot return that same promise.")}function f(){}function l(t){try{return t.then}catch(n){return (Cn.error = n, Cn)}}function h(t,n,r,e){try{t.call(n,r,e)}catch(o){return o}}function p(t,n,r){mn.async(function(t){var e=!1,o=h(r,n,function(r){e||(e=!0,n!==r?d(t,r):m(t,r))},function(n){e||(e=!0,w(t,n))},"Settle: "+(t._label||" unknown promise"));!e&&o&&(e=!0,w(t,o))},t)}function _(t,n){n._state===kn?m(t,n._result):n._state===Sn?(n._onError=null,w(t,n._result)):g(n,void 0,function(r){n!==r?d(t,r):m(t,r)},function(n){w(t,n)})}function v(t,n){if(n.constructor===t.constructor)_(t,n);else{var r=l(n);r===Cn?w(t,Cn.error):void 0===r?m(t,n):o(r)?p(t,n,r):m(t,n)}}function d(t,n){t===n?m(t,n):e(n)?v(t,n):m(t,n)}function y(t){t._onError&&t._onError(t._result),b(t)}function m(t,n){t._state===Tn&&(t._result=n,t._state=kn,0===t._subscribers.length?mn.instrument&&jn("fulfilled",t):mn.async(b,t))}function w(t,n){t._state===Tn&&(t._state=Sn,t._result=n,mn.async(y,t))}function g(t,n,r,e){var o=t._subscribers,i=o.length;t._onError=null,o[i]=n,o[i+kn]=r,o[i+Sn]=e,0===i&&t._state&&mn.async(b,t)}function b(t){var n=t._subscribers,r=t._state;if(mn.instrument&&jn(r===kn?"fulfilled":"rejected",t),0!==n.length){for(var e,o,i=t._result,u=0;u<n.length;u+=3)e=n[u],o=n[u+r],e?j(r,e,o,i):o(i);t._subscribers.length=0}}function E(){this.error=null}function A(t,n){try{return t(n)}catch(r){return (On.error = r, On)}}function j(t,n,r,e){var i,u,s,a,f=o(r);if(f){if(i=A(r,e),i===On?(a=!0,u=i.error,i=null):s=!0,n===i)return void w(n,c())}else i=e,s=!0;n._state!==Tn||(f&&s?d(n,i):a?w(n,u):t===kn?m(n,i):t===Sn&&w(n,i))}function T(t,n){var r=!1;try{n(function(n){r||(r=!0,d(t,n))},function(n){r||(r=!0,w(t,n))})}catch(e){w(t,e)}}function k(t,n,r){return t===kn?{state:"fulfilled",value:r}:{state:"rejected",reason:r}}function S(t,n,r,e){this._instanceConstructor=t,this.promise=new t(f,e),this._abortOnReject=r,this._validateInput(n)?(this._input=n,this.length=n.length,this._remaining=n.length,this._init(),0===this.length?m(this.promise,this._result):(this.length=this.length||0,this._enumerate(),0===this._remaining&&m(this.promise,this._result))):w(this.promise,this._validationError())}function C(t,n){return new In(this,t,!0,n).promise}function O(t,n){function r(t){d(i,t)}function e(t){w(i,t)}var o=this,i=new o(f,n);if(!gn(t))return (w(i,new TypeError("You must pass an array to race.")), i);for(var u=t.length,s=0;i._state===Tn&&u>s;s++)g(o.resolve(t[s]),void 0,r,e);return i}function I(t,n){var r=this;if(t&&"object"==typeof t&&t.constructor===r)return t;var e=new r(f,n);return (d(e,t), e)}function R(t,n){var r=this,e=new r(f,n);return (w(e,t), e)}function x(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function M(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function N(t,n){this._id=Yn++,this._label=n,this._state=void 0,this._result=void 0,this._subscribers=[],mn.instrument&&jn("created",this),f!==t&&(o(t)||x(),this instanceof N||M(),T(this,t))}function P(){this.value=void 0}function Y(t){try{return t.then}catch(n){return (Kn.value = n, Kn)}}function D(t,n,r){try{t.apply(n,r)}catch(e){return (Kn.value = e, Kn)}}function K(t,n){for(var r,e,o={},i=t.length,u=new Array(i),s=0;i>s;s++)u[s]=t[s];for(e=0;e<n.length;e++)r=n[e],o[r]=u[e+1];return o}function U(t){for(var n=t.length,r=new Array(n-1),e=1;n>e;e++)r[e-1]=t[e];return r}function q(t,n){return{then:function(r,e){return t.call(n,r,e)}}}function F(t,n){var r=function(){for(var r,e=this,o=arguments.length,i=new Array(o+1),u=!1,s=0;o>s;++s){if(r=arguments[s],!u){if(u=V(r),u===Un){var a=new Dn(f);return (w(a,Un.value), a)}u&&u!==!0&&(r=q(u,r))}i[s]=r}var c=new Dn(f);return (i[o]=function(t,r){t?w(c,t):void 0===n?d(c,r):n===!0?d(c,U(arguments)):gn(n)?d(c,K(arguments,n)):d(c,r)}, u?L(c,i,t,e):G(c,i,t,e))};return (r.__proto__=t, r)}function G(t,n,r,e){var o=D(r,e,n);return (o===Kn&&w(t,o.value), t)}function L(t,n,r,e){return Dn.all(n).then(function(n){var o=D(r,e,n);return (o===Kn&&w(t,o.value), t)})}function V(t){return t&&"object"==typeof t?t.constructor===Dn?!0:Y(t):!1}function W(t,n){return Dn.all(t,n)}function $(t,n,r){this._superConstructor(t,n,!1,r)}function z(t,n){return new $(Dn,t,n).promise}function B(t,n){return Dn.race(t,n)}function H(t,n,r){this._superConstructor(t,n,!0,r)}function J(t,n){return new Vn(Dn,t,n).promise}function Q(t,n,r){this._superConstructor(t,n,!1,r)}function X(t,n){return new Q(Dn,t,n).promise}function Z(t){throw (setTimeout(function(){throw t}), t)}function tn(t){var n={};return (n.promise=new Dn(function(t,r){n.resolve=t,n.reject=r},t), n)}function nn(t,n,r){return Dn.all(t,r).then(function(t){if(!o(n))throw new TypeError("You must pass a function as map's second argument.");for(var e=t.length,i=new Array(e),u=0;e>u;u++)i[u]=n(t[u]);return Dn.all(i,r)})}function rn(t,n){return Dn.resolve(t,n)}function en(t,n){return Dn.reject(t,n)}function on(t,n,r){return Dn.all(t,r).then(function(t){if(!o(n))throw new TypeError("You must pass a function as filter's second argument.");for(var e=t.length,i=new Array(e),u=0;e>u;u++)i[u]=n(t[u]);return Dn.all(i,r).then(function(n){for(var r=new Array(e),o=0,i=0;e>i;i++)n[i]&&(r[o]=t[i],o++);return (r.length=o, r)})})}function un(t,n){sr[tr]=t,sr[tr+1]=n,tr+=2,2===tr&&$n()}function sn(){var t=process.nextTick,n=process.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/);return (Array.isArray(n)&&"0"===n[1]&&"10"===n[2]&&(t=setImmediate), function(){t(hn)})}function an(){return function(){vertxNext(hn)}}function cn(){var t=0,n=new or(hn),r=document.createTextNode("");return (n.observe(r,{characterData:!0}), function(){r.data=t=++t%2})}function fn(){var t=new MessageChannel;return (t.port1.onmessage=hn, function(){t.port2.postMessage(0)})}function ln(){return function(){setTimeout(hn,1)}}function hn(){for(var t=0;tr>t;t+=2){var n=sr[t],r=sr[t+1];n(r),sr[t]=void 0,sr[t+1]=void 0}tr=0}function pn(){try{{var t=require("vertx");t.runOnLoop||t.runOnContext}return an()}catch(n){return ln()}}function _n(t,n){mn.async(t,n)}function vn(){mn.on.apply(mn,arguments)}function dn(){mn.off.apply(mn,arguments)}var yn={mixin:function(t){return (t.on=this.on, t.off=this.off, t.trigger=this.trigger, t._promiseCallbacks=void 0, t)},on:function(r,e){var o,i=n(this);o=i[r],o||(o=i[r]=[]),-1===t(o,e)&&o.push(e)},off:function(r,e){var o,i,u=n(this);return e?(o=u[r],i=t(o,e),void(-1!==i&&o.splice(i,1))):void(u[r]=[])},trigger:function(t,r){var e,o,i=n(this);if(e=i[t])for(var u=0;u<e.length;u++)(o=e[u])(r)}},mn={instrument:!1};yn.mixin(mn);var wn;wn=Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)};var gn=wn,bn=Date.now||function(){return(new Date).getTime()},En=Object.create||function(t){if(arguments.length>1)throw new Error("Second argument not supported");if("object"!=typeof t)throw new TypeError("Argument must be an object");return (u.prototype=t, new u)},An=[],jn=a,Tn=void 0,kn=1,Sn=2,Cn=new E,On=new E,In=S;S.prototype._validateInput=function(t){return gn(t)},S.prototype._validationError=function(){return new Error("Array Methods must be provided an Array")},S.prototype._init=function(){this._result=new Array(this.length)},S.prototype._enumerate=function(){for(var t=this.length,n=this.promise,r=this._input,e=0;n._state===Tn&&t>e;e++)this._eachEntry(r[e],e)},S.prototype._eachEntry=function(t,n){var r=this._instanceConstructor;i(t)?t.constructor===r&&t._state!==Tn?(t._onError=null,this._settledAt(t._state,n,t._result)):this._willSettleAt(r.resolve(t),n):(this._remaining--,this._result[n]=this._makeResult(kn,n,t))},S.prototype._settledAt=function(t,n,r){var e=this.promise;e._state===Tn&&(this._remaining--,this._abortOnReject&&t===Sn?w(e,r):this._result[n]=this._makeResult(t,n,r)),0===this._remaining&&m(e,this._result)},S.prototype._makeResult=function(t,n,r){return r},S.prototype._willSettleAt=function(t,n){var r=this;g(t,void 0,function(t){r._settledAt(kn,n,t)},function(t){r._settledAt(Sn,n,t)})};var Rn=C,xn=O,Mn=I,Nn=R,Pn="rsvp_"+bn()+"-",Yn=0,Dn=N;N.cast=Mn,N.all=Rn,N.race=xn,N.resolve=Mn,N.reject=Nn,N.prototype={constructor:N,_guidKey:Pn,_onError:function(t){mn.async(function(n){setTimeout(function(){n._onError&&mn.trigger("error",t)},0)},this)},then:function(t,n,r){var e=this,o=e._state;if(o===kn&&!t||o===Sn&&!n)return (mn.instrument&&jn("chained",this,this), this);e._onError=null;var i=new this.constructor(f,r),u=e._result;if(mn.instrument&&jn("chained",e,i),o){var s=arguments[o-1];mn.async(function(){j(o,i,s,u)})}else g(e,i,t,n);return i},"catch":function(t,n){return this.then(null,t,n)},"finally":function(t,n){var r=this.constructor;return this.then(function(n){return r.resolve(t()).then(function(){return n})},function(n){return r.resolve(t()).then(function(){throw n})},n)}};var Kn=new P,Un=new P,qn=F,Fn=W;$.prototype=En(In.prototype),$.prototype._superConstructor=In,$.prototype._makeResult=k,$.prototype._validationError=function(){return new Error("allSettled must be called with an array")};var Gn=z,Ln=B,Vn=H;H.prototype=En(In.prototype),H.prototype._superConstructor=In,H.prototype._init=function(){this._result={}},H.prototype._validateInput=function(t){return t&&"object"==typeof t},H.prototype._validationError=function(){return new Error("Promise.hash must be called with an object")},H.prototype._enumerate=function(){var t=this.promise,n=this._input,r=[];for(var e in n)t._state===Tn&&n.hasOwnProperty(e)&&r.push({position:e,entry:n[e]});var o=r.length;this._remaining=o;for(var i,u=0;t._state===Tn&&o>u;u++)i=r[u],this._eachEntry(i.entry,i.position)};var Wn=J;Q.prototype=En(Vn.prototype),Q.prototype._superConstructor=In,Q.prototype._makeResult=k,Q.prototype._validationError=function(){return new Error("hashSettled must be called with an object")};var $n,zn=X,Bn=Z,Hn=tn,Jn=nn,Qn=rn,Xn=en,Zn=on,tr=0,nr=un,rr="undefined"!=typeof window?window:void 0,er=rr||{},or=er.MutationObserver||er.WebKitMutationObserver,ir="undefined"!=typeof process&&"[object process]"==={}.toString.call(process),ur="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,sr=new Array(1e3);$n=ir?sn():or?cn():ur?fn():void 0===rr&&"function"==typeof require?pn():ln(),mn.async=nr;if("undefined"!=typeof window&&"object"==typeof window.__PROMISE_INSTRUMENTATION__){var ar=window.__PROMISE_INSTRUMENTATION__;r("instrument",!0);for(var cr in ar)ar.hasOwnProperty(cr)&&vn(cr,ar[cr])}var fr={race:Ln,Promise:Dn,allSettled:Gn,hash:Wn,hashSettled:zn,denodeify:qn,on:vn,off:dn,map:Jn,filter:Zn,resolve:Qn,reject:Xn,all:Fn,rethrow:Bn,defer:Hn,EventTarget:yn,configure:r,async:_n};"function"==typeof define&&define.amd?define(function(){return fr}):"undefined"!=typeof module&&module.exports?module.exports=fr:"undefined"!=typeof this&&(this.RSVP=fr)}).call(this);
@@ -0,0 +1,42 @@
1
+ <% attachment = model.send(model.class.attachment_name) %>
2
+ <div class="stratosphere-container" data-stratosphere-enabled="true" data-stratosphere-container-type="field" data-model-name="<%= model.class.to_s.underscore.pluralize %>" data-model-id="<%= model.id %>" data-attachment-name="<%= model.class.attachment_name.to_s %>">
3
+ <label class="statosphere-uploader-label">
4
+ <%= model.class.attachment_name.to_s.capitalize %>
5
+ </label>
6
+ <div class="upload-controls">
7
+ <div class="current-attachment" style="display: <%= (attachment.exists? || attachment.has_default?) ? 'inline-block' : 'none' %>; margin-right: 5px;">
8
+ <% if attachment.type == :image %>
9
+ <%= image_tag attachment.url, width: 32, height: 32, style: 'display: inline-block;' %>
10
+ <%= link_to attachment.file_name, attachment.url, target: '_blank', class: 'file-name', style: 'margin-left: 5px;' %>
11
+ <% elsif attachment.type == :video %>
12
+ <div>
13
+ <video preload="auto" width="320" height="240" controls>
14
+ <source src="<%= attachment.url %>" type="<%= attachment.mime_type %>">
15
+ </video>
16
+ </div>
17
+ <%= link_to attachment.file_name, attachment.url, target: '_blank', class: 'file-name', style: 'margin-left: 5px;' %>
18
+ <% else %>
19
+ <%= link_to attachment.file_name, attachment.url, target: '_blank', class: 'file-name' %>
20
+ <% end %>
21
+ </div>
22
+ <div class="actions" style="display: inline-block;">
23
+ <div class="upload-container" style="position: relative; display: inline-block;">
24
+ <% if attachment.type == :image %>
25
+ <input type="file" accept="image/*" style="position: absolute; opacity: 0; pointer-events: none;">
26
+ <% elsif attachment.type == :video %>
27
+ <input type="file" accept="video/*" style="position: absolute; opacity: 0; pointer-events: none;">
28
+ <% else %>
29
+ <input type="file" accept="*/*" style="position: absolute; opacity: 0; pointer-events: none;">
30
+ <% end %>
31
+ <button class="btn btn-xs btn-primary" data-stratosphere-action="upload">Upload</button>
32
+ </div>
33
+ <button class="btn btn-xs btn-danger" data-stratosphere-action="delete" style="display: <%= attachment.exists? ? 'initial' : 'none' %>; margin-left: 3px;">Delete</button>
34
+ </div>
35
+ </div>
36
+ <div class="upload-progress" style="display: none;">
37
+ <div class="progress">
38
+ <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0;">
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </div>
@@ -0,0 +1,56 @@
1
+ ActionController::Base.class_eval do
2
+ before_action do
3
+ if params[:stratosphere_submitted]
4
+ if stratosphere_model
5
+ case action_name
6
+ when 'edit'
7
+ @upload_params = {
8
+ file_name: params[:file_name],
9
+ content_type: params[:content_type],
10
+ content_length: params[:content_length]
11
+ }
12
+ render_stratosphere_upload_url
13
+ when 'update'
14
+ if params[:crop_params]
15
+ @crop_params = params[:crop_params]
16
+ render_attachment_crop
17
+ else
18
+ @update_params = {}
19
+ attachment_name = stratosphere_model.attachment_name
20
+ %W(#{attachment_name}_file #{attachment_name}_content_type #{attachment_name}_content_length).each do |param|
21
+ @update_params[:"#{param}"] = (param == "#{attachment_name}_content_length") ? params[:"#{param}"].to_i : params[:"#{param}"]
22
+ end
23
+ render_attachment_update
24
+ end
25
+ else
26
+ nil
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ protected
33
+ def stratosphere_model
34
+ model = self.class.to_s.gsub!('Controller', '').singularize.safe_constantize
35
+ model && model.respond_to?(:has_attachment) ? model : nil
36
+ end
37
+
38
+ def stratosphere_upload_url
39
+ stratosphere_model.find(params[:id]).send(:"#{stratosphere_model.attachment_name}").presigned_upload(@upload_params)
40
+ end
41
+
42
+ def render_stratosphere_upload_url
43
+ render json: { url: stratosphere_upload_url }
44
+ end
45
+
46
+ def render_attachment_update
47
+ render json: stratosphere_model.find(params[:id]).update!(@update_params)
48
+ end
49
+
50
+ def render_attachment_crop
51
+ model = stratosphere_model.find(params[:id])
52
+ attachment = model.send(:"#{stratosphere_model.attachment_name}")
53
+ attachment.crop(@crop_params)
54
+ render json: attachment
55
+ end
56
+ end
@@ -0,0 +1,2 @@
1
+ Stratosphere::Engine.routes.draw do
2
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Generate a migration to support a stratosphere attachment on your model. The NAME argument represents the model you would like to add an attachment to and the ATTACHMENT_NAME represents the name of the attachment. i.e. rails generate attachment user avatar.
3
+
4
+ Example:
5
+ rails generate attachment model attachment
6
+
7
+ This will create:
8
+ db/migrate/add_attachment_to_model.rb
@@ -0,0 +1,18 @@
1
+ module Stratosphere
2
+ module Generators
3
+ class AttachmentGenerator < ::Rails::Generators::Base
4
+ source_root File.expand_path('../templates', __FILE__)
5
+
6
+ desc "Generate a migration to support a stratosphere attachment on your model. The #{"\033[32mNAME\033[0m"} " <<
7
+ "argument represents the model you would like to add an attachment to and the #{"\033[36mATTACHMENT_NAME\033[0m"} " <<
8
+ "represents the name of the attachment. i.e. rails generate attachment #{"\033[32muser\033[0m"} #{"\033[36mavatar\033[0m"}."
9
+
10
+ argument :model_name, required: true, type: :string
11
+ argument :attachment_name, required: true, type: :string, desc: 'A name for the attachment you would like to add.'
12
+
13
+ def create_migration
14
+ generate 'migration', "AddAttachmentTo#{model_name.classify.pluralize} #{attachment_name}_file:string #{attachment_name}_content_type:string #{attachment_name}_content_length:int8"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ module Stratosphere
2
+ module Generators
3
+ class InstallGenerator < ::Rails::Generators::Base
4
+ attr_accessor :key, :secret, :bucket
5
+
6
+ source_root File.expand_path('../../../../../', __FILE__)
7
+
8
+ desc 'Add stratosphere views for easy attachment uploads via AJAX.'
9
+
10
+ def set_options
11
+ STDOUT.flush
12
+ print 'What is your S3 bucket name?: '
13
+ @bucket = gets.chomp
14
+ end
15
+
16
+ def create_initializer
17
+ template 'lib/generators/stratosphere/install/templates/stratosphere.rb.erb', 'config/initializers/stratosphere.rb'
18
+ end
19
+
20
+ def create_views
21
+ copy_file 'app/views/_attachment_field.html.erb', 'app/views/stratosphere/_attachment_field.html.erb'
22
+ copy_file 'app/assets/javascripts/stratosphere/stratosphere.bundled.min.js', 'vendor/assets/javascripts/stratosphere.bundled.min.js'
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ Stratosphere.configure do |config|
2
+ config.cloud = :aws
3
+ config.domain = 'http://s3.amazonaws.com/<%= @bucket %>'
4
+ config.aws = {
5
+ access_key: ENV['AWS_ACCESS_KEY_ID'],
6
+ secret: ENV['AWS_SECRET_ACCESS_KEY'],
7
+ region: ENV['AWS_REGION'],
8
+ s3_bucket: '<%= @bucket %>'
9
+ }
10
+ end
@@ -0,0 +1,23 @@
1
+ require 'aws-sdk'
2
+ require 'stratosphere/aws'
3
+ require 'stratosphere/attachment'
4
+ require 'stratosphere/config'
5
+ require 'stratosphere/engine'
6
+ require 'stratosphere/has_attachment'
7
+ require 'stratosphere/image'
8
+ require 'stratosphere/style'
9
+ require 'stratosphere/video'
10
+
11
+ module Stratosphere
12
+ class << self
13
+ attr_writer :config
14
+ end
15
+
16
+ def self.config
17
+ @configuration ||= Stratosphere::Config.new
18
+ end
19
+
20
+ def self.configure
21
+ yield config
22
+ end
23
+ end
@@ -0,0 +1,51 @@
1
+ module Stratosphere
2
+ class Attachment
3
+ attr_accessor :base_path, :config, :file_name, :name, :size, :mime_type, :type, :owner, :file_store
4
+
5
+ def initialize(owner, name, options={})
6
+ @config = Stratosphere.config
7
+ @name = name
8
+ @owner = owner
9
+ @file_name = @owner["#{@name}_file"]
10
+ @file_size = @owner["#{@name}_content_length"]
11
+ @mime_type = @owner["#{@name}_content_type"]
12
+ @type = :attachment
13
+ @file_store = Stratosphere::AWS::S3.new
14
+ set_base_path
15
+ end
16
+
17
+ def set_base_path
18
+ plural_attr = name.to_s.pluralize
19
+ plural_model = owner.class.to_s.downcase.pluralize
20
+
21
+ if config.dir_prefix
22
+ prefix = config.dir_prefix[-1, 1] == '/' ? config.dir_prefix.slice(0, -1) : config.dir_prefix
23
+ @base_path = "#{prefix}/#{plural_model}/#{plural_attr}/#{owner.id}"
24
+ else
25
+ @base_path = "#{plural_model}/#{plural_attr}/#{owner.id}"
26
+ end
27
+ end
28
+
29
+ def exists?
30
+ !file_name.nil?
31
+ end
32
+
33
+ def has_default?
34
+ false
35
+ end
36
+
37
+ def url
38
+ "#{config.domain}/#{base_path}/#{file_name}" if file_name
39
+ end
40
+
41
+ def presigned_upload(options)
42
+ options.merge!(key: "#{base_path}/#{options[:file_name]}")
43
+ options.delete :file_name
44
+ file_store.presigned_upload options
45
+ end
46
+
47
+ def destroy!
48
+ file_store.delete_objects base_path
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,50 @@
1
+ module Stratosphere
2
+ module AWS
3
+ class S3
4
+ attr_accessor :credentials, :resource, :region, :bucket_name, :presigner
5
+
6
+ def initialize
7
+ key = Stratosphere.config.aws[:access_key]
8
+ secret = Stratosphere.config.aws[:secret]
9
+ @region = Stratosphere.config.aws[:region]
10
+ @bucket_name = Stratosphere.config.aws[:s3_bucket]
11
+ @credentials = Aws::Credentials.new(key, secret)
12
+ @resource = Aws::S3::Resource.new(credentials: @credentials, region: @region)
13
+ @presigner = Aws::S3::Presigner.new(region: @region)
14
+ Aws.config[:region] = @region
15
+ Aws.config[:credentials] = @credentials
16
+ end
17
+
18
+ def bucket
19
+ resource.bucket bucket_name
20
+ end
21
+
22
+ def delete_objects(prefix)
23
+ threads = []
24
+ bucket.objects(prefix: prefix).limit(50).each { |object| threads.push Thread.new { object.delete if object } }
25
+ threads.each(&:join)
26
+ end
27
+
28
+ def upload(options={})
29
+ bucket.put_object(options)
30
+ end
31
+
32
+ def presigned_upload(options={})
33
+ params = options.keep_if { |k,v| [:key, :content_type, :content_length].include? k }.merge!(bucket: bucket_name)
34
+ presigner.presigned_url(:put_object, params)
35
+ end
36
+ end
37
+
38
+ class ElasticTranscoder
39
+ def self.encoder
40
+ key = Stratosphere.config.aws[:access_key]
41
+ secret = Stratosphere.config.aws[:secret]
42
+ Aws::ElasticTranscoder::Client.new(credentials: Aws::Credentials.new(key, secret), region: Stratosphere.config.aws[:region])
43
+ end
44
+
45
+ def self.create_job(params)
46
+ encoder.create_job(params)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ module Stratosphere
2
+ class Config
3
+ attr_accessor :aws, :cloud, :domain, :dir_prefix
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Stratosphere
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Stratosphere
4
+ end
5
+ end
@@ -0,0 +1,63 @@
1
+ module Stratosphere
2
+ module HasAttachment
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def has_attachment(name, options={})
7
+ cattr_accessor :attachment_name, :attachment_type
8
+
9
+ self.attachment_name = name.to_sym
10
+ self.attachment_type = options[:type]
11
+
12
+ define_method "#{name}" do
13
+ case options[:type]
14
+ when :image
15
+ Stratosphere::Image.new(self, name, options)
16
+ when :video
17
+ Stratosphere::Video.new(self, name, options)
18
+ else
19
+ Stratosphere::Attachment.new(self, name, options)
20
+ end
21
+ end
22
+
23
+ send(:before_save) do
24
+ if send(:"#{name}_file_changed?")
25
+ attr = self[:"#{name}_file"]
26
+
27
+ if [:image, :video].include? options[:type]
28
+ if options[:type] == :image
29
+ @attachment = Stratosphere::Image.new(self, name, options)
30
+ elsif options[:type] == :video
31
+ @attachment = Stratosphere::Video.new(self, name, options)
32
+ end
33
+ else
34
+ @attachment = Stratosphere::Attachment.new(self, name, options)
35
+ end
36
+
37
+ if attr.nil? || attr.chomp == ''
38
+ self[:"#{name}_file"] = nil
39
+ self[:"#{name}_content_type"] = nil
40
+ self[:"#{name}_content_length"] = nil
41
+ @attachment.destroy!
42
+ else
43
+ @attachment.encode if @attachment.type == :video
44
+ end
45
+ end
46
+ end
47
+
48
+ self.send(:before_destroy) do
49
+ case options[:type]
50
+ when :image
51
+ Stratosphere::Image.new(self, name, options).destroy!
52
+ when :video
53
+ Stratosphere::Video.new(self, name, options).destroy!
54
+ else
55
+ Stratosphere::Attachment.new(self, name, options).destroy!
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+ end
63
+ ActiveRecord::Base.send :include, Stratosphere::HasAttachment
@@ -0,0 +1,65 @@
1
+ require 'open-uri'
2
+ require 'rmagick'
3
+
4
+ module Stratosphere
5
+ class Image < Attachment
6
+ attr_accessor :default, :styles, :crop_params
7
+
8
+ def initialize(owner, name, options={})
9
+ super
10
+ @type = :image
11
+ @styles = []
12
+ @default = options[:default]
13
+ if @file_name && options[:styles] && options[:styles].count > 0
14
+ options[:styles].each { |style| @styles.push Stratosphere::Style.new( style.merge(file_name: @file_name) ) }
15
+ end
16
+ end
17
+
18
+ def has_default?
19
+ !default.nil?
20
+ end
21
+
22
+ def url(style_name=:original)
23
+ url = default ? "#{config.domain}/#{default}" : nil
24
+ if file_name
25
+ url = "#{config.domain}/#{base_path}/#{style_name.to_s}/#{file_name}"
26
+ unless style_name == :original
27
+ style = styles.count > 0 ? self.styles.select { |style| style.name == style_name }.first : nil
28
+ url.gsub!(file_name, style.file_name) unless style.nil?
29
+ end
30
+ end
31
+ url
32
+ end
33
+
34
+ def presigned_upload(options)
35
+ options.merge!(key: "#{base_path}/original/#{options[:file_name]}")
36
+ options.delete :file_name
37
+ file_store.presigned_upload options
38
+ end
39
+
40
+ def crop(x, y, w, h)
41
+ if styles.count > 0
42
+ begin
43
+ io = open(url)
44
+ file = Magick::Image.from_blob(io.read).first.crop(x.to_i, y.to_i, w.to_i, h.to_i)
45
+ threads = []
46
+ io.close
47
+ styles.each do |style|
48
+ if style.dimensions
49
+ t = Thread.new do
50
+ k = "#{base_path}/#{style.name}/#{file_name}"
51
+ r = file.resize(style.dimensions[0], style.dimensions[1])
52
+ Stratosphere.file_store.upload(key: k, content_type: 'image/jpeg', body: r.to_blob)
53
+ end
54
+ threads.push(t)
55
+ end
56
+ end
57
+ threads.each(&:join)
58
+ rescue OpenURI::HTTPError => e
59
+ puts "Error: Original image not found at '#{url}'"
60
+ puts e
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end