activestorage_saas 5.2.5.1 → 5.2.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/activestorage_saas.gemspec +1 -1
- data/app/controller/active_storage_saas/direct_uploads_controller.rb +4 -2
- data/app/javascript/active_storage_saas/direct_upload_controller/blob_record.js +73 -0
- data/app/javascript/active_storage_saas/direct_upload_controller/blob_upload.js +45 -0
- data/app/javascript/active_storage_saas/direct_upload_controller/direct_upload.js +48 -0
- data/app/javascript/active_storage_saas/direct_upload_controller/file_checksum.js +53 -0
- data/app/javascript/active_storage_saas/direct_upload_controller/helpers.js +51 -0
- data/app/javascript/active_storage_saas/direct_upload_controller.js +78 -0
- data/app/models/tenant_storage_service.rb +5 -0
- data/lib/active_storage/service/saas_service.rb +12 -4
- data/lib/{actives_torage_saas → active_storage_saas}/blob_patch.rb +10 -0
- data/lib/{actives_torage_saas → active_storage_saas}/engine.rb +0 -0
- data/lib/{actives_torage_saas → active_storage_saas}/routes.rb +3 -3
- metadata +13 -10
- data/app/controller/active_storage_saas/base_controller.rb +0 -7
- data/app/controller/active_storage_saas/blobs_controller.rb +0 -8
- data/app/controller/active_storage_saas/disk_controller.rb +0 -63
- data/app/controller/active_storage_saas/representations_controller.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14d926492586f0ab3058a8ff4a48a7cec2824747366c677f9c91f5c46a658535
|
4
|
+
data.tar.gz: 1c57c47f19302d6edc4953e6dcf94cd7871f0e22dda6670955c99639f2e3fffb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b9101363a32b4d5fbe122d0c7e4953fd10c0a7b2dce02f3d83c4ba42e80745a88d3a50111a4ad100417b6b4e2f3a6244cf5c16f8f07539afbaedea2ea8adde67
|
7
|
+
data.tar.gz: b5cb1539cbc4129abcee5c5f7b1df6a3b2773a9a87cd4426c223be3bd6ca41ab82632d93deff93b9a59fd4d90a97394c3ae320afc397f4ef0ea16bfd75aa0738
|
data/activestorage_saas.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
class ActiveStorageSaas::DirectUploadsController <
|
1
|
+
class ActiveStorageSaas::DirectUploadsController < ActiveStorage::BaseController
|
2
2
|
def create
|
3
3
|
blob = ActiveStorage::Blob.create!(blob_args)
|
4
4
|
render json: direct_upload_json(blob)
|
@@ -19,8 +19,10 @@ class ActiveStorageSaas::DirectUploadsController < ActiveStorageSaas::BaseContro
|
|
19
19
|
blob.as_json(root: false, methods: :signed_id, only: :signed_id)
|
20
20
|
.merge(direct_upload: {
|
21
21
|
url: blob.service_url_for_direct_upload,
|
22
|
+
method: blob.service_http_method_for_direct_upload,
|
23
|
+
responseType: blob.service_http_response_type_for_direct_upload,
|
22
24
|
headers: blob.service_headers_for_direct_upload,
|
23
|
-
formData: blob.service_form_data_for_direct_upload
|
25
|
+
formData: blob.service_form_data_for_direct_upload.presence
|
24
26
|
})
|
25
27
|
end
|
26
28
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import { getMetaValue } from "./helpers"
|
2
|
+
|
3
|
+
export class BlobRecord {
|
4
|
+
constructor(file, checksum, url) {
|
5
|
+
this.file = file
|
6
|
+
|
7
|
+
this.attributes = {
|
8
|
+
filename: file.name,
|
9
|
+
content_type: file.type || "application/octet-stream",
|
10
|
+
byte_size: file.size,
|
11
|
+
checksum: checksum
|
12
|
+
}
|
13
|
+
|
14
|
+
this.xhr = new XMLHttpRequest
|
15
|
+
this.xhr.open("POST", url, true)
|
16
|
+
this.xhr.responseType = "json"
|
17
|
+
this.xhr.setRequestHeader("Content-Type", "application/json")
|
18
|
+
this.xhr.setRequestHeader("Accept", "application/json")
|
19
|
+
this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
|
20
|
+
|
21
|
+
const csrfToken = getMetaValue("csrf-token")
|
22
|
+
if (csrfToken != undefined) {
|
23
|
+
this.xhr.setRequestHeader("X-CSRF-Token", csrfToken)
|
24
|
+
}
|
25
|
+
|
26
|
+
this.xhr.addEventListener("load", event => this.requestDidLoad(event))
|
27
|
+
this.xhr.addEventListener("error", event => this.requestDidError(event))
|
28
|
+
}
|
29
|
+
|
30
|
+
get status() {
|
31
|
+
return this.xhr.status
|
32
|
+
}
|
33
|
+
|
34
|
+
get response() {
|
35
|
+
const { responseType, response } = this.xhr
|
36
|
+
if (responseType == "json") {
|
37
|
+
return response
|
38
|
+
} else {
|
39
|
+
// Shim for IE 11: https://connect.microsoft.com/IE/feedback/details/794808
|
40
|
+
return JSON.parse(response)
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
create(callback) {
|
45
|
+
this.callback = callback
|
46
|
+
this.xhr.send(JSON.stringify({ blob: this.attributes }))
|
47
|
+
}
|
48
|
+
|
49
|
+
requestDidLoad(event) {
|
50
|
+
if (this.status >= 200 && this.status < 300) {
|
51
|
+
const { response } = this
|
52
|
+
const { direct_upload } = response
|
53
|
+
delete response.direct_upload
|
54
|
+
this.attributes = response
|
55
|
+
this.directUploadData = direct_upload
|
56
|
+
this.callback(null, this.toJSON())
|
57
|
+
} else {
|
58
|
+
this.requestDidError(event)
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
requestDidError(event) {
|
63
|
+
this.callback(event, this)
|
64
|
+
}
|
65
|
+
|
66
|
+
toJSON() {
|
67
|
+
const result = {}
|
68
|
+
for (const key in this.attributes) {
|
69
|
+
result[key] = this.attributes[key]
|
70
|
+
}
|
71
|
+
return result
|
72
|
+
}
|
73
|
+
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
export class BlobUpload {
|
2
|
+
constructor(blob) {
|
3
|
+
this.blob = blob
|
4
|
+
this.file = blob.file
|
5
|
+
|
6
|
+
const { url, headers, method, responseType } = blob.directUploadData
|
7
|
+
|
8
|
+
this.xhr = new XMLHttpRequest
|
9
|
+
this.xhr.open(method || "PUT", url, true)
|
10
|
+
this.xhr.responseType = responseType || "text"
|
11
|
+
for (const key in headers) {
|
12
|
+
this.xhr.setRequestHeader(key, headers[key])
|
13
|
+
}
|
14
|
+
this.xhr.addEventListener("load", event => this.requestDidLoad(event))
|
15
|
+
this.xhr.addEventListener("error", event => this.requestDidError(event))
|
16
|
+
}
|
17
|
+
|
18
|
+
create(callback) {
|
19
|
+
this.callback = callback
|
20
|
+
if(this.blob.directUploadData.formData){
|
21
|
+
var formData
|
22
|
+
formData = new FormData()
|
23
|
+
for(const key in this.blob.directUploadData.formData){
|
24
|
+
formData.append(key, this.blob.directUploadData.formData[key])
|
25
|
+
}
|
26
|
+
formData.append('file', this.file)
|
27
|
+
this.xhr.send(formData)
|
28
|
+
}else{
|
29
|
+
this.xhr.send(this.file.slice())
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
requestDidLoad(event) {
|
34
|
+
const { status, response } = this.xhr
|
35
|
+
if (status >= 200 && status < 300) {
|
36
|
+
this.callback(null, response)
|
37
|
+
} else {
|
38
|
+
this.requestDidError(event)
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
requestDidError(event) {
|
43
|
+
this.callback(event, this)
|
44
|
+
}
|
45
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import { FileChecksum } from "./file_checksum"
|
2
|
+
import { BlobRecord } from "./blob_record"
|
3
|
+
import { BlobUpload } from "./blob_upload"
|
4
|
+
|
5
|
+
let id = 0
|
6
|
+
|
7
|
+
export class DirectUpload {
|
8
|
+
constructor(file, url, delegate) {
|
9
|
+
this.id = ++id
|
10
|
+
this.file = file
|
11
|
+
this.url = url
|
12
|
+
this.delegate = delegate
|
13
|
+
}
|
14
|
+
|
15
|
+
create(callback) {
|
16
|
+
FileChecksum.create(this.file, (error, checksum) => {
|
17
|
+
if (error) {
|
18
|
+
callback(error)
|
19
|
+
return
|
20
|
+
}
|
21
|
+
|
22
|
+
const blob = new BlobRecord(this.file, checksum, this.url)
|
23
|
+
notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr)
|
24
|
+
|
25
|
+
blob.create(error => {
|
26
|
+
if (error) {
|
27
|
+
callback(error)
|
28
|
+
} else {
|
29
|
+
const upload = new BlobUpload(blob)
|
30
|
+
notify(this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr)
|
31
|
+
upload.create(error => {
|
32
|
+
if (error) {
|
33
|
+
callback(error)
|
34
|
+
} else {
|
35
|
+
callback(null, blob.toJSON())
|
36
|
+
}
|
37
|
+
})
|
38
|
+
}
|
39
|
+
})
|
40
|
+
})
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
function notify(object, methodName, ...messages) {
|
45
|
+
if (object && typeof object[methodName] == "function") {
|
46
|
+
return object[methodName](...messages)
|
47
|
+
}
|
48
|
+
}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import SparkMD5 from "spark-md5"
|
2
|
+
|
3
|
+
const fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
|
4
|
+
|
5
|
+
export class FileChecksum {
|
6
|
+
static create(file, callback) {
|
7
|
+
const instance = new FileChecksum(file)
|
8
|
+
instance.create(callback)
|
9
|
+
}
|
10
|
+
|
11
|
+
constructor(file) {
|
12
|
+
this.file = file
|
13
|
+
this.chunkSize = 2097152 // 2MB
|
14
|
+
this.chunkCount = Math.ceil(this.file.size / this.chunkSize)
|
15
|
+
this.chunkIndex = 0
|
16
|
+
}
|
17
|
+
|
18
|
+
create(callback) {
|
19
|
+
this.callback = callback
|
20
|
+
this.md5Buffer = new SparkMD5.ArrayBuffer
|
21
|
+
this.fileReader = new FileReader
|
22
|
+
this.fileReader.addEventListener("load", event => this.fileReaderDidLoad(event))
|
23
|
+
this.fileReader.addEventListener("error", event => this.fileReaderDidError(event))
|
24
|
+
this.readNextChunk()
|
25
|
+
}
|
26
|
+
|
27
|
+
fileReaderDidLoad(event) {
|
28
|
+
this.md5Buffer.append(event.target.result)
|
29
|
+
|
30
|
+
if (!this.readNextChunk()) {
|
31
|
+
const binaryDigest = this.md5Buffer.end(true)
|
32
|
+
const base64digest = btoa(binaryDigest)
|
33
|
+
this.callback(null, base64digest)
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
fileReaderDidError(event) {
|
38
|
+
this.callback(`Error reading ${this.file.name}`)
|
39
|
+
}
|
40
|
+
|
41
|
+
readNextChunk() {
|
42
|
+
if (this.chunkIndex < this.chunkCount || (this.chunkIndex == 0 && this.chunkCount == 0)) {
|
43
|
+
const start = this.chunkIndex * this.chunkSize
|
44
|
+
const end = Math.min(start + this.chunkSize, this.file.size)
|
45
|
+
const bytes = fileSlice.call(this.file, start, end)
|
46
|
+
this.fileReader.readAsArrayBuffer(bytes)
|
47
|
+
this.chunkIndex++
|
48
|
+
return true
|
49
|
+
} else {
|
50
|
+
return false
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
@@ -0,0 +1,51 @@
|
|
1
|
+
export function getMetaValue(name) {
|
2
|
+
const element = findElement(document.head, `meta[name="${name}"]`)
|
3
|
+
if (element) {
|
4
|
+
return element.getAttribute("content")
|
5
|
+
}
|
6
|
+
}
|
7
|
+
|
8
|
+
export function findElements(root, selector) {
|
9
|
+
if (typeof root == "string") {
|
10
|
+
selector = root
|
11
|
+
root = document
|
12
|
+
}
|
13
|
+
const elements = root.querySelectorAll(selector)
|
14
|
+
return toArray(elements)
|
15
|
+
}
|
16
|
+
|
17
|
+
export function findElement(root, selector) {
|
18
|
+
if (typeof root == "string") {
|
19
|
+
selector = root
|
20
|
+
root = document
|
21
|
+
}
|
22
|
+
return root.querySelector(selector)
|
23
|
+
}
|
24
|
+
|
25
|
+
export function dispatchEvent(element, type, eventInit = {}) {
|
26
|
+
const { disabled } = element
|
27
|
+
const { bubbles, cancelable, detail } = eventInit
|
28
|
+
const event = document.createEvent("Event")
|
29
|
+
|
30
|
+
event.initEvent(type, bubbles || true, cancelable || true)
|
31
|
+
event.detail = detail || {}
|
32
|
+
|
33
|
+
try {
|
34
|
+
element.disabled = false
|
35
|
+
element.dispatchEvent(event)
|
36
|
+
} finally {
|
37
|
+
element.disabled = disabled
|
38
|
+
}
|
39
|
+
|
40
|
+
return event
|
41
|
+
}
|
42
|
+
|
43
|
+
export function toArray(value) {
|
44
|
+
if (Array.isArray(value)) {
|
45
|
+
return value
|
46
|
+
} else if (Array.from) {
|
47
|
+
return Array.from(value)
|
48
|
+
} else {
|
49
|
+
return [].slice.call(value)
|
50
|
+
}
|
51
|
+
}
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
import { DirectUpload } from "./direct_upload_controller/direct_upload";
|
3
|
+
|
4
|
+
const URL = window.URL || window.webkitURL
|
5
|
+
|
6
|
+
function setupFilePreview(target, file){
|
7
|
+
const onLoaded = function(){
|
8
|
+
URL.revokeObjectURL(target.src)
|
9
|
+
target.removeEventListener('load', onLoaded)
|
10
|
+
}
|
11
|
+
target.addEventListener('load', onLoaded)
|
12
|
+
target.src = URL.createObjectURL(file)
|
13
|
+
}
|
14
|
+
export default class extends Controller {
|
15
|
+
static targets = ['file', 'preview'];
|
16
|
+
static values = {
|
17
|
+
url: String,
|
18
|
+
}
|
19
|
+
|
20
|
+
initialize(){
|
21
|
+
this.onFileChange = this.onFileChange.bind(this)
|
22
|
+
}
|
23
|
+
|
24
|
+
fileTargetConnected(target){
|
25
|
+
if(!target.hiddenInput){
|
26
|
+
const hiddenInput = document.createElement("input")
|
27
|
+
hiddenInput.type = "hidden"
|
28
|
+
hiddenInput.name = target.name
|
29
|
+
target.insertAdjacentElement("beforebegin", hiddenInput)
|
30
|
+
target.removeAttribute('name')
|
31
|
+
target.hiddenInput = hiddenInput
|
32
|
+
}
|
33
|
+
target.addEventListener('change', this.onFileChange)
|
34
|
+
}
|
35
|
+
|
36
|
+
fileTargetDisconnected(target){
|
37
|
+
target.removeEventListener('change', this.onFileChange)
|
38
|
+
}
|
39
|
+
|
40
|
+
onFileChange(event){
|
41
|
+
const { target } = event
|
42
|
+
const { hiddenInput, files } = target
|
43
|
+
const file = files[0]
|
44
|
+
if(!file) return
|
45
|
+
|
46
|
+
const directUpload = new DirectUpload(file, this.urlValue, this)
|
47
|
+
|
48
|
+
if(this.hasPreviewTarget){
|
49
|
+
setupFilePreview(this.previewTarget, file)
|
50
|
+
}
|
51
|
+
|
52
|
+
directUpload.create((error, attributes) => {
|
53
|
+
if(error){
|
54
|
+
hiddenInput.removeAttribute('value')
|
55
|
+
}else{
|
56
|
+
hiddenInput.setAttribute('value', attributes.signed_id)
|
57
|
+
}
|
58
|
+
})
|
59
|
+
}
|
60
|
+
|
61
|
+
directUploadWillCreateBlobWithXHR(xhr) {
|
62
|
+
this.dispatch("before-blob-request", { detail: xhr })
|
63
|
+
}
|
64
|
+
|
65
|
+
directUploadWillStoreFileWithXHR(xhr) {
|
66
|
+
this.dispatch('started', { detail: xhr })
|
67
|
+
this.dispatch('progress', { detail: { percent: 0 } })
|
68
|
+
xhr.upload.addEventListener("progress", event => this.uploadRequestDidProgress(event))
|
69
|
+
}
|
70
|
+
|
71
|
+
uploadRequestDidProgress(event){
|
72
|
+
const percent = event.loaded / event.total
|
73
|
+
this.dispatch('progress', { detail: { percent } })
|
74
|
+
if(percent == 1){
|
75
|
+
this.dispatch('uploaded')
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
@@ -39,6 +39,14 @@ module ActiveStorage
|
|
39
39
|
service.respond_to?(method)
|
40
40
|
end
|
41
41
|
|
42
|
+
def http_method_for_direct_upload
|
43
|
+
service.service_http_method_for_direct_upload if service.respond_to?(:http_method_for_direct_upload)
|
44
|
+
end
|
45
|
+
|
46
|
+
def http_response_type_for_direct_upload
|
47
|
+
service.http_response_type_for_direct_upload if service.respond_to?(:http_response_type_for_direct_upload)
|
48
|
+
end
|
49
|
+
|
42
50
|
def form_data_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
|
43
51
|
if service.respond_to?(:form_data_for_direct_upload)
|
44
52
|
service.form_data_for_direct_upload key,
|
@@ -62,10 +70,10 @@ module ActiveStorage
|
|
62
70
|
|
63
71
|
private
|
64
72
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
73
|
+
def default_service
|
74
|
+
@default_service ||= ActiveStorage::Service.configure @options[:default_service],
|
75
|
+
Rails.configuration.active_storage.service_configurations
|
76
|
+
end
|
69
77
|
end
|
70
78
|
end
|
71
79
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'active_storage/service/saas_service'
|
2
|
+
|
1
3
|
module ActiveStorageSaas
|
2
4
|
module BlobPatch
|
3
5
|
extend ActiveSupport::Concern
|
@@ -16,6 +18,14 @@ module ActiveStorageSaas
|
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
21
|
+
def service_http_method_for_direct_upload
|
22
|
+
service.respond_to?(:http_method_for_direct_upload) ? service.http_method_for_direct_upload : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def service_http_response_type_for_direct_upload
|
26
|
+
service.respond_to?(:http_response_type_for_direct_upload) ? service.http_response_type_for_direct_upload : nil
|
27
|
+
end
|
28
|
+
|
19
29
|
# support sending data from form instead of headers
|
20
30
|
def service_form_data_for_direct_upload(expires_in: service.url_expires_in)
|
21
31
|
return {} unless service.respond_to?(:form_data_for_direct_upload)
|
File without changes
|
@@ -3,9 +3,9 @@ module ActiveStorageSaas
|
|
3
3
|
# rubocop: disable Layout/LineLength
|
4
4
|
def draw_active_storage_saas_routes(
|
5
5
|
prefix: '/rails/active_storage',
|
6
|
-
blobs_controller: '
|
7
|
-
representations_controller: '
|
8
|
-
disk_controller: '
|
6
|
+
blobs_controller: 'active_storage/blobs',
|
7
|
+
representations_controller: 'active_storage/representations',
|
8
|
+
disk_controller: 'active_storage/disk',
|
9
9
|
direct_uploads_controller: 'active_storage_saas/direct_uploads',
|
10
10
|
**option_overrides
|
11
11
|
)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activestorage_saas
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.2.5.
|
4
|
+
version: 5.2.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- xiaohui
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-08-
|
11
|
+
date: 2022-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activestorage
|
@@ -41,17 +41,20 @@ files:
|
|
41
41
|
- README.md
|
42
42
|
- Rakefile
|
43
43
|
- activestorage_saas.gemspec
|
44
|
-
- app/controller/active_storage_saas/base_controller.rb
|
45
|
-
- app/controller/active_storage_saas/blobs_controller.rb
|
46
44
|
- app/controller/active_storage_saas/direct_uploads_controller.rb
|
47
|
-
- app/
|
48
|
-
- app/
|
45
|
+
- app/javascript/active_storage_saas/direct_upload_controller.js
|
46
|
+
- app/javascript/active_storage_saas/direct_upload_controller/blob_record.js
|
47
|
+
- app/javascript/active_storage_saas/direct_upload_controller/blob_upload.js
|
48
|
+
- app/javascript/active_storage_saas/direct_upload_controller/direct_upload.js
|
49
|
+
- app/javascript/active_storage_saas/direct_upload_controller/file_checksum.js
|
50
|
+
- app/javascript/active_storage_saas/direct_upload_controller/helpers.js
|
51
|
+
- app/models/tenant_storage_service.rb
|
49
52
|
- db/migrate/001_activestorage_saas_tables.rb
|
50
53
|
- lib/active_storage/service/saas_service.rb
|
51
54
|
- lib/active_storage_saas.rb
|
52
|
-
- lib/
|
53
|
-
- lib/
|
54
|
-
- lib/
|
55
|
+
- lib/active_storage_saas/blob_patch.rb
|
56
|
+
- lib/active_storage_saas/engine.rb
|
57
|
+
- lib/active_storage_saas/routes.rb
|
55
58
|
- lib/activestorage_saas.rb
|
56
59
|
- sig/activestorage_saas.rbs
|
57
60
|
homepage: https://github.com/xiaohui-zhangxh/activestorage_saas
|
@@ -76,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
79
|
- !ruby/object:Gem::Version
|
77
80
|
version: '0'
|
78
81
|
requirements: []
|
79
|
-
rubygems_version: 3.
|
82
|
+
rubygems_version: 3.3.7
|
80
83
|
signing_key:
|
81
84
|
specification_version: 4
|
82
85
|
summary: Wraps multi-tenant storage services as ActiveStorage service
|
@@ -1,63 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Serves files stored with the disk service in the same way that the cloud services do.
|
4
|
-
# This means using expiring, signed URLs that are meant for immediate access, not permanent linking.
|
5
|
-
# Always go through the BlobsController, or your own authenticated controller, rather than directly
|
6
|
-
# to the service url.
|
7
|
-
class ActiveStorageSaas::DiskController < ActiveStorage::BaseController
|
8
|
-
skip_forgery_protection
|
9
|
-
|
10
|
-
def show
|
11
|
-
if key = decode_verified_key
|
12
|
-
serve_file disk_service.path_for(key[:key]), content_type: key[:content_type], disposition: key[:disposition]
|
13
|
-
else
|
14
|
-
head :not_found
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def update
|
19
|
-
if token = decode_verified_token
|
20
|
-
if acceptable_content?(token)
|
21
|
-
disk_service.upload token[:key], request.body, checksum: token[:checksum]
|
22
|
-
head :no_content
|
23
|
-
else
|
24
|
-
head :unprocessable_entity
|
25
|
-
end
|
26
|
-
end
|
27
|
-
rescue ActiveStorage::IntegrityError
|
28
|
-
head :unprocessable_entity
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
def disk_service
|
33
|
-
ActiveStorage::Blob.service
|
34
|
-
end
|
35
|
-
|
36
|
-
|
37
|
-
def decode_verified_key
|
38
|
-
ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
|
39
|
-
end
|
40
|
-
|
41
|
-
def serve_file(path, content_type:, disposition:)
|
42
|
-
Rack::File.new(nil).serving(request, path).tap do |(status, headers, body)|
|
43
|
-
self.status = status
|
44
|
-
self.response_body = body
|
45
|
-
|
46
|
-
headers.each do |name, value|
|
47
|
-
response.headers[name] = value
|
48
|
-
end
|
49
|
-
|
50
|
-
response.headers["Content-Type"] = content_type || DEFAULT_SEND_FILE_TYPE
|
51
|
-
response.headers["Content-Disposition"] = disposition || DEFAULT_SEND_FILE_DISPOSITION
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
|
56
|
-
def decode_verified_token
|
57
|
-
ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
|
58
|
-
end
|
59
|
-
|
60
|
-
def acceptable_content?(token)
|
61
|
-
token[:content_type] == request.content_mime_type && token[:content_length] == request.content_length
|
62
|
-
end
|
63
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Take a signed permanent reference for a blob representation and turn it into an expiring service URL for download.
|
4
|
-
# Note: These URLs are publicly accessible. If you need to enforce access protection beyond the
|
5
|
-
# security-through-obscurity factor of the signed blob and variation reference, you'll need to implement your own
|
6
|
-
# authenticated redirection controller.
|
7
|
-
class ActiveStorageSaas::RepresentationsController < ActiveStorage::BaseController
|
8
|
-
include ActiveStorage::SetBlob
|
9
|
-
|
10
|
-
def show
|
11
|
-
expires_in ActiveStorage::Blob.service.url_expires_in
|
12
|
-
redirect_to @blob.representation(params[:variation_key]).processed.service_url(disposition: params[:disposition])
|
13
|
-
end
|
14
|
-
end
|