fileboost 0.1.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +227 -0
- data/Rakefile +8 -0
- data/lib/fileboost/config.rb +29 -0
- data/lib/fileboost/engine.rb +17 -0
- data/lib/fileboost/error_handler.rb +57 -0
- data/lib/fileboost/helpers.rb +107 -0
- data/lib/fileboost/signature_generator.rb +44 -0
- data/lib/fileboost/url_builder.rb +155 -0
- data/lib/fileboost/version.rb +3 -0
- data/lib/fileboost.rb +12 -0
- data/lib/generators/fileboost/install_generator.rb +27 -0
- data/lib/generators/fileboost/templates/INSTALL.md +41 -0
- data/lib/generators/fileboost/templates/fileboost.rb +19 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fd85f01129f867dfe1627a2fd20f22ac47e873f03cdecf5973ffa1cbc0f3ff40
|
4
|
+
data.tar.gz: e2f625239f1e930f3d08b10f8fd67a62b882c553291e112bcd34ce0b1b92598e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c206e01371679f502dac87f79309933e38560e76eba829f01016458e6cf2588ef6167df9c737ca4caee636406795647536428e96ba8899b12a2c07f6221e57df
|
7
|
+
data.tar.gz: cfd0ad1902c05576a38e34d4fe5aec67ce0c1a5349f26bd044fd840ae83ba7cac7ad71d693b083b9581cc35c68a2386eec39114c0a1b05f0fb5de06ac7b7e7c4
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright bilal
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,227 @@
|
|
1
|
+
# Fileboost
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/fileboost)
|
4
|
+
|
5
|
+
Fileboost is a Rails gem that provides seamless integration with the Fileboost.dev image optimization service. It offers drop-in replacement helpers for Rails' native image helpers with automatic optimization, HMAC authentication, and comprehensive transformation support for ActiveStorage objects.
|
6
|
+
|
7
|
+
## Features
|
8
|
+
|
9
|
+
- 🚀 **Drop-in replacement** for Rails `image_tag` and `url_for` helpers
|
10
|
+
- 🔒 **Secure HMAC authentication** with Fileboost.dev service
|
11
|
+
- 📱 **ActiveStorage only** - works exclusively with ActiveStorage attachments
|
12
|
+
- 🎛️ **Comprehensive transformations** - resize, quality, format conversion, and more
|
13
|
+
- 🔧 **Simple configuration** - just project ID and token required
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Register an account at [Fileboost.dev](https://fileboost.dev) and obtain your project ID and token.
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem "fileboost"
|
23
|
+
```
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
```bash
|
28
|
+
$ bundle install
|
29
|
+
```
|
30
|
+
|
31
|
+
Generate the initializer:
|
32
|
+
|
33
|
+
```bash
|
34
|
+
$ rails generate fileboost:install
|
35
|
+
```
|
36
|
+
|
37
|
+
## Configuration
|
38
|
+
|
39
|
+
Set your environment variables:
|
40
|
+
|
41
|
+
```bash
|
42
|
+
export FILEBOOST_PROJECT_ID="your-project-id"
|
43
|
+
export FILEBOOST_TOKEN="your-secret-token"
|
44
|
+
```
|
45
|
+
|
46
|
+
## Usage
|
47
|
+
|
48
|
+
### Basic Image Tag
|
49
|
+
|
50
|
+
Replace `image_tag` with `fileboost_image_tag` for ActiveStorage objects:
|
51
|
+
|
52
|
+
```erb
|
53
|
+
<!-- Before (Rails) -->
|
54
|
+
<%= image_tag user.avatar, width: 300, height: 300, alt: "Avatar" %>
|
55
|
+
|
56
|
+
<!-- After (Fileboost) -->
|
57
|
+
<%= fileboost_image_tag user.avatar, resize: { w: 300, h: 300 }, alt: "Avatar" %>
|
58
|
+
```
|
59
|
+
|
60
|
+
**Note:** Fileboost only works with ActiveStorage objects. String paths and external URLs are not supported.
|
61
|
+
|
62
|
+
### URL Generation
|
63
|
+
|
64
|
+
Generate optimized URLs directly:
|
65
|
+
|
66
|
+
```erb
|
67
|
+
<div style="background-image: url(<%= fileboost_url_for(banner.image, resize: { w: 1200, h: 400 }) %>)">
|
68
|
+
<!-- content -->
|
69
|
+
</div>
|
70
|
+
```
|
71
|
+
|
72
|
+
### Transformation Options
|
73
|
+
|
74
|
+
Fileboost supports comprehensive image transformations:
|
75
|
+
|
76
|
+
```erb
|
77
|
+
<%= fileboost_image_tag post.image,
|
78
|
+
resize: {
|
79
|
+
width: 800, # Resize width
|
80
|
+
height: 600, # Resize height
|
81
|
+
quality: 85, # JPEG/WebP quality (1-100)
|
82
|
+
blur: 5, # Blur effect (0-100)
|
83
|
+
brightness: 110, # Brightness adjustment (0-200, 100 = normal)
|
84
|
+
contrast: 120, # Contrast adjustment (0-200, 100 = normal)
|
85
|
+
rotation: 90, # Rotation in degrees (0-359)
|
86
|
+
fit: :cover # Resize behavior (cover, contain, fill, scale-down, crop, pad)
|
87
|
+
},
|
88
|
+
class: "hero-image", # Standard Rails options work too
|
89
|
+
alt: "Hero image" %>
|
90
|
+
|
91
|
+
<!-- Short parameter names also work -->
|
92
|
+
<%= fileboost_image_tag post.image,
|
93
|
+
resize: { w: 800, h: 600, q: 85 },
|
94
|
+
class: "hero-image" %>
|
95
|
+
```
|
96
|
+
|
97
|
+
### Parameter Aliases
|
98
|
+
|
99
|
+
Use short or long parameter names within the resize parameter:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
# These are equivalent:
|
103
|
+
fileboost_image_tag(image, resize: { w: 400, h: 300, q: 85 })
|
104
|
+
fileboost_image_tag(image, resize: { width: 400, height: 300, quality: 85 })
|
105
|
+
```
|
106
|
+
|
107
|
+
**Note:** Avoid using the `format` parameter. Fileboost automatically selects the optimal image format (WebP, AVIF, JPEG, etc.) based on browser headers and capabilities for the best performance and compatibility.
|
108
|
+
|
109
|
+
### ActiveStorage Support
|
110
|
+
|
111
|
+
Works seamlessly with all ActiveStorage attachment types:
|
112
|
+
|
113
|
+
```erb
|
114
|
+
<!-- has_one_attached -->
|
115
|
+
<%= fileboost_image_tag user.avatar, resize: { w: 150, h: 150, fit: :cover } %>
|
116
|
+
|
117
|
+
<!-- has_many_attached -->
|
118
|
+
<% post.images.each do |image| %>
|
119
|
+
<%= fileboost_image_tag image, resize: { width: 400, quality: 90 } %>
|
120
|
+
<% end %>
|
121
|
+
|
122
|
+
<!-- Direct blob access -->
|
123
|
+
<%= fileboost_image_tag post.featured_image.blob, resize: { w: 800 } %>
|
124
|
+
```
|
125
|
+
|
126
|
+
### Responsive Images
|
127
|
+
|
128
|
+
Generate multiple sizes for responsive designs:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
# In your controller or helper
|
132
|
+
@responsive_urls = fileboost_responsive_urls(hero.image, [
|
133
|
+
{ width: 400, suffix: "sm" },
|
134
|
+
{ width: 800, suffix: "md" },
|
135
|
+
{ width: 1200, suffix: "lg" }
|
136
|
+
], resize: { quality: 85 })
|
137
|
+
|
138
|
+
# Returns: { "sm" => "url1", "md" => "url2", "lg" => "url3" }
|
139
|
+
```
|
140
|
+
|
141
|
+
```erb
|
142
|
+
<!-- In your view -->
|
143
|
+
<img src="<%= @responsive_urls['md'] %>"
|
144
|
+
srcset="<%= @responsive_urls['sm'] %> 400w,
|
145
|
+
<%= @responsive_urls['md'] %> 800w,
|
146
|
+
<%= @responsive_urls['lg'] %> 1200w"
|
147
|
+
sizes="(max-width: 400px) 400px, (max-width: 800px) 800px, 1200px"
|
148
|
+
alt="Responsive image">
|
149
|
+
```
|
150
|
+
|
151
|
+
## Error Handling
|
152
|
+
|
153
|
+
Fileboost handles errors gracefully:
|
154
|
+
|
155
|
+
- **Configuration errors**: Logs warnings about missing configuration and returns empty strings/nil
|
156
|
+
- **Invalid assets**: Logs errors when non-ActiveStorage objects are passed and returns empty strings/nil
|
157
|
+
- **Signature errors**: Returns nil when HMAC generation fails
|
158
|
+
|
159
|
+
## Security
|
160
|
+
|
161
|
+
Fileboost uses HMAC-SHA256 signatures to secure your image transformations:
|
162
|
+
|
163
|
+
- URLs are signed with your secret token
|
164
|
+
- Prevents unauthorized image manipulation
|
165
|
+
- Signatures include all transformation parameters
|
166
|
+
- Uses secure comparison to prevent timing attacks
|
167
|
+
|
168
|
+
## Development
|
169
|
+
|
170
|
+
After checking out the repo, run:
|
171
|
+
|
172
|
+
```bash
|
173
|
+
$ bundle install
|
174
|
+
$ rake test
|
175
|
+
```
|
176
|
+
|
177
|
+
To test against the dummy Rails application:
|
178
|
+
|
179
|
+
```bash
|
180
|
+
$ cd test/dummy
|
181
|
+
$ rails server
|
182
|
+
```
|
183
|
+
|
184
|
+
## Testing
|
185
|
+
|
186
|
+
Run the test suite:
|
187
|
+
|
188
|
+
```bash
|
189
|
+
$ rake test
|
190
|
+
```
|
191
|
+
|
192
|
+
Run RuboCop:
|
193
|
+
|
194
|
+
```bash
|
195
|
+
$ bundle exec rubocop
|
196
|
+
```
|
197
|
+
|
198
|
+
## Contributing
|
199
|
+
|
200
|
+
1. Fork it
|
201
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
202
|
+
3. Write tests for your changes
|
203
|
+
4. Ensure all tests pass (`rake test`)
|
204
|
+
5. Commit your changes (`git commit -am 'Add some feature'`)
|
205
|
+
6. Push to the branch (`git push origin my-new-feature`)
|
206
|
+
7. Create new Pull Request
|
207
|
+
|
208
|
+
## Fileboost.dev Service
|
209
|
+
|
210
|
+
To use Fileboost, you'll need access to the Fileboost.dev image optimization service at `cdn.fileboost.dev`. The service:
|
211
|
+
|
212
|
+
1. Receives requests at `https://cdn.fileboost.dev/{project_id}/path/to/activestorage/blob`
|
213
|
+
2. Verifies HMAC signatures using your secret token
|
214
|
+
3. Applies transformations based on query parameters
|
215
|
+
4. Returns optimized images
|
216
|
+
|
217
|
+
The service handles ActiveStorage blob URLs and applies image transformations on-the-fly.
|
218
|
+
|
219
|
+
## License
|
220
|
+
|
221
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
222
|
+
|
223
|
+
## Support
|
224
|
+
|
225
|
+
- [GitHub Issues](https://github.com/bilalbudhani/fileboost/issues)
|
226
|
+
- [Documentation](https://github.com/bilalbudhani/fileboost/wiki)
|
227
|
+
- [Cloudflare Worker Setup Guide](https://github.com/bilalbudhani/fileboost-worker)
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Fileboost
|
2
|
+
class Config
|
3
|
+
attr_accessor :project_id, :token
|
4
|
+
|
5
|
+
CDN_DOMAIN = "cdn.fileboost.dev"
|
6
|
+
BASE_URL = "https://#{CDN_DOMAIN}"
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@project_id = ENV["FILEBOOST_PROJECT_ID"]
|
10
|
+
@token = ENV["FILEBOOST_TOKEN"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
project_id.present? && token.present?
|
15
|
+
end
|
16
|
+
|
17
|
+
def base_url
|
18
|
+
BASE_URL
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.config
|
23
|
+
@config ||= Config.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.configure
|
27
|
+
yield(config) if block_given?
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Fileboost
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Fileboost
|
4
|
+
|
5
|
+
initializer "fileboost.action_view" do
|
6
|
+
ActiveSupport.on_load :action_view do
|
7
|
+
include Fileboost::Helpers
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
initializer "fileboost.active_storage" do
|
12
|
+
ActiveSupport.on_load :active_storage_blob do
|
13
|
+
# Extend ActiveStorage::Blob with fileboost-specific methods if needed
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Fileboost
|
2
|
+
class ErrorHandler
|
3
|
+
class << self
|
4
|
+
def handle_with_fallback(error_context, &block)
|
5
|
+
begin
|
6
|
+
yield
|
7
|
+
rescue StandardError => e
|
8
|
+
log_error(error_context, e)
|
9
|
+
|
10
|
+
if Fileboost.config.fallback_to_rails
|
11
|
+
yield_fallback if block_given?
|
12
|
+
else
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def handle_gracefully(error_context, default_value = nil, &block)
|
19
|
+
begin
|
20
|
+
yield
|
21
|
+
rescue StandardError => e
|
22
|
+
log_error(error_context, e)
|
23
|
+
default_value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def log_error(context, error)
|
30
|
+
return unless defined?(Rails) && Rails.logger
|
31
|
+
|
32
|
+
Rails.logger.warn(
|
33
|
+
"[Fileboost] Error in #{context}: #{error.class}: #{error.message}"
|
34
|
+
)
|
35
|
+
|
36
|
+
# Log backtrace in development for debugging
|
37
|
+
if Rails.env.development?
|
38
|
+
Rails.logger.debug(
|
39
|
+
"[Fileboost] Backtrace:\n#{error.backtrace.take(5).join("\n")}"
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def yield_fallback
|
45
|
+
# This would be implemented by the calling code
|
46
|
+
# The pattern is to pass a fallback block when needed
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Specific exception classes for better error handling
|
53
|
+
class ConfigurationError < StandardError; end
|
54
|
+
class SignatureGenerationError < StandardError; end
|
55
|
+
class UrlBuildError < StandardError; end
|
56
|
+
class AssetPathExtractionError < StandardError; end
|
57
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require "active_storage"
|
2
|
+
|
3
|
+
module Fileboost
|
4
|
+
module Helpers
|
5
|
+
# Generate an optimized image tag using Fileboost
|
6
|
+
#
|
7
|
+
# @param asset [ActiveStorage::Blob, ActiveStorage::Attached, ActiveStorage::VariantWithRecord] The ActiveStorage image asset
|
8
|
+
# @param options [Hash] Image transformation and HTML options
|
9
|
+
# @return [String] HTML image tag
|
10
|
+
#
|
11
|
+
# Examples:
|
12
|
+
# fileboost_image_tag(user.avatar, resize: { w: 300, h: 200 }, alt: "Avatar")
|
13
|
+
# fileboost_image_tag(post.featured_image.blob, resize: { width: 1200, quality: 90 }, class: "hero-image")
|
14
|
+
def fileboost_image_tag(asset, **options)
|
15
|
+
# Extract resize options for transformation
|
16
|
+
resize_options = options.delete(:resize) || {}
|
17
|
+
|
18
|
+
# Generate the optimized URL
|
19
|
+
optimized_url = fileboost_url_for(asset, resize: resize_options)
|
20
|
+
|
21
|
+
# Return empty string if no URL could be generated
|
22
|
+
return "" if optimized_url.blank?
|
23
|
+
|
24
|
+
# Use the optimized URL with Rails image_tag for consistency
|
25
|
+
image_tag(optimized_url, **options)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Generate an optimized URL using Fileboost
|
29
|
+
#
|
30
|
+
# @param asset [ActiveStorage::Blob, ActiveStorage::Attached, ActiveStorage::VariantWithRecord] The ActiveStorage image asset
|
31
|
+
# @param options [Hash] Image transformation options
|
32
|
+
# @return [String, nil] The optimized URL or nil if generation failed
|
33
|
+
#
|
34
|
+
# Examples:
|
35
|
+
# fileboost_url_for(post.image, resize: { width: 500, format: :webp })
|
36
|
+
# fileboost_url_for(user.avatar.blob, resize: { w: 1200, h: 400, q: 85 })
|
37
|
+
def fileboost_url_for(asset, **options)
|
38
|
+
# Validate that asset is an ActiveStorage object
|
39
|
+
unless valid_activestorage_asset?(asset)
|
40
|
+
Rails.logger.error("[Fileboost] Invalid asset type #{asset.class}. Only ActiveStorage objects are supported.") if defined?(Rails)
|
41
|
+
return nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Validate configuration
|
45
|
+
unless Fileboost.config.valid?
|
46
|
+
log_configuration_warning
|
47
|
+
return nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Build the optimized URL
|
51
|
+
Fileboost::UrlBuilder.build_url(asset, **options)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Generate multiple image URLs for responsive images
|
55
|
+
#
|
56
|
+
# @param asset [ActiveStorage::Blob, ActiveStorage::Attached, ActiveStorage::VariantWithRecord] The ActiveStorage image asset
|
57
|
+
# @param sizes [Array<Hash>] Array of size configurations
|
58
|
+
# @param base_options [Hash] Base transformation options applied to all sizes
|
59
|
+
# @return [Hash] Hash with size keys and URL values
|
60
|
+
#
|
61
|
+
# Example:
|
62
|
+
# fileboost_responsive_urls(hero.image, [
|
63
|
+
# { width: 400, suffix: "sm" },
|
64
|
+
# { width: 800, suffix: "md" },
|
65
|
+
# { width: 1200, suffix: "lg" }
|
66
|
+
# ], resize: { quality: 85, format: :webp })
|
67
|
+
# # Returns: { "sm" => "url1", "md" => "url2", "lg" => "url3" }
|
68
|
+
def fileboost_responsive_urls(asset, sizes, **base_options)
|
69
|
+
urls = {}
|
70
|
+
|
71
|
+
sizes.each do |size_config|
|
72
|
+
suffix = size_config[:suffix] || size_config["suffix"]
|
73
|
+
size_options = size_config.except(:suffix, "suffix")
|
74
|
+
combined_options = base_options.merge(size_options)
|
75
|
+
|
76
|
+
url = fileboost_url_for(asset, **combined_options)
|
77
|
+
urls[suffix] = url if url.present?
|
78
|
+
end
|
79
|
+
|
80
|
+
urls
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
|
86
|
+
# Validate that the asset is a supported ActiveStorage object
|
87
|
+
def valid_activestorage_asset?(asset)
|
88
|
+
return true if asset.is_a?(ActiveStorage::Blob)
|
89
|
+
return true if asset.is_a?(ActiveStorage::Attached)
|
90
|
+
return true if asset.is_a?(ActiveStorage::VariantWithRecord)
|
91
|
+
|
92
|
+
false
|
93
|
+
end
|
94
|
+
|
95
|
+
# Log configuration warnings
|
96
|
+
def log_configuration_warning
|
97
|
+
missing_configs = []
|
98
|
+
missing_configs << "project_id" if Fileboost.config.project_id.blank?
|
99
|
+
missing_configs << "token" if Fileboost.config.token.blank?
|
100
|
+
|
101
|
+
Rails.logger.warn(
|
102
|
+
"[Fileboost] Configuration incomplete. Missing: #{missing_configs.join(', ')}. " \
|
103
|
+
"Set FILEBOOST_PROJECT_ID and FILEBOOST_TOKEN environment variables or configure them in your initializer."
|
104
|
+
) if defined?(Rails)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "openssl"
|
2
|
+
require "base64"
|
3
|
+
|
4
|
+
module Fileboost
|
5
|
+
class SignatureGenerator
|
6
|
+
def self.generate(project_id:, asset_path:, params: {})
|
7
|
+
return nil unless project_id.present? && asset_path.present? && Fileboost.config.token.present?
|
8
|
+
|
9
|
+
# Sort parameters for consistent signature generation
|
10
|
+
sorted_params = params.sort.to_h
|
11
|
+
query_string = sorted_params.map { |k, v| "#{k}=#{v}" }.join("&")
|
12
|
+
|
13
|
+
# Create the signing string: project_id:asset_path:sorted_query_params
|
14
|
+
signing_string = [project_id, asset_path, query_string].join(":")
|
15
|
+
Rails.logger.debug("signature payload #{project_id}, #{asset_path}, #{query_string}, #{Fileboost.config.token}")
|
16
|
+
# Generate HMAC-SHA256 signature for secure authentication with Fileboost.dev
|
17
|
+
digest = OpenSSL::HMAC.digest("SHA256", Fileboost.config.token, signing_string)
|
18
|
+
# Use URL-safe base64 encoding and remove padding for maximum URL compatibility
|
19
|
+
Base64.urlsafe_encode64(digest, padding: false)
|
20
|
+
rescue StandardError => e
|
21
|
+
if defined?(Rails) && Rails.env.development?
|
22
|
+
raise e
|
23
|
+
else
|
24
|
+
Rails.logger.warn("[Fileboost] Failed to generate signature: #{e.message}") if defined?(Rails)
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.verify_signature(project_id:, asset_path:, params: {}, signature:)
|
30
|
+
expected_signature = generate(project_id: project_id, asset_path: asset_path, params: params)
|
31
|
+
return false if expected_signature.nil? || signature.nil?
|
32
|
+
|
33
|
+
# Use secure comparison to prevent timing attacks
|
34
|
+
ActiveSupport::SecurityUtils.secure_compare(signature, expected_signature)
|
35
|
+
rescue StandardError => e
|
36
|
+
if defined?(Rails) && Rails.env.development?
|
37
|
+
raise e
|
38
|
+
else
|
39
|
+
Rails.logger.warn("[Fileboost] Failed to verify signature: #{e.message}") if defined?(Rails)
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
module Fileboost
|
4
|
+
class UrlBuilder
|
5
|
+
# Supported transformation parameters for Fileboost.dev service
|
6
|
+
TRANSFORMATION_PARAMS = %w[
|
7
|
+
w width
|
8
|
+
h height
|
9
|
+
q quality
|
10
|
+
f format
|
11
|
+
b blur
|
12
|
+
br brightness
|
13
|
+
c contrast
|
14
|
+
r rotation
|
15
|
+
fit
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
# Parameter aliases for convenience
|
19
|
+
PARAM_ALIASES = {
|
20
|
+
"width" => "w",
|
21
|
+
"height" => "h",
|
22
|
+
"quality" => "q",
|
23
|
+
"format" => "f",
|
24
|
+
"blur" => "b",
|
25
|
+
"brightness" => "br",
|
26
|
+
"contrast" => "c",
|
27
|
+
"rotation" => "r"
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
# Valid resize parameter keys
|
31
|
+
RESIZE_PARAMS = %w[w width h height q quality f format b blur br brightness c contrast r rotation fit].freeze
|
32
|
+
|
33
|
+
def self.build_url(asset, **options)
|
34
|
+
return nil unless Fileboost.config.valid?
|
35
|
+
|
36
|
+
asset_path = extract_asset_path(asset)
|
37
|
+
return nil unless asset_path.present?
|
38
|
+
|
39
|
+
project_id = Fileboost.config.project_id
|
40
|
+
base_url = Fileboost.config.base_url
|
41
|
+
|
42
|
+
# Build the full asset URL path for Fileboost.dev service
|
43
|
+
full_path = "/#{project_id}#{asset_path}"
|
44
|
+
|
45
|
+
# Extract and normalize transformation parameters
|
46
|
+
transformation_params = extract_transformation_params(options)
|
47
|
+
|
48
|
+
# Generate HMAC signature for secure authentication
|
49
|
+
signature = Fileboost::SignatureGenerator.generate(
|
50
|
+
project_id: project_id,
|
51
|
+
asset_path: asset_path,
|
52
|
+
params: transformation_params
|
53
|
+
)
|
54
|
+
|
55
|
+
return nil unless signature
|
56
|
+
|
57
|
+
# Add signature to parameters
|
58
|
+
all_params = transformation_params.merge("sig" => signature)
|
59
|
+
|
60
|
+
# Build final URL
|
61
|
+
uri = URI.join(base_url, full_path)
|
62
|
+
uri.query = URI.encode_www_form(all_params) unless all_params.empty?
|
63
|
+
|
64
|
+
uri.to_s
|
65
|
+
rescue StandardError => e
|
66
|
+
if defined?(Rails) && Rails.env.development?
|
67
|
+
raise e
|
68
|
+
else
|
69
|
+
Rails.logger.warn("[Fileboost] Failed to build URL: #{e.message}") if defined?(Rails)
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def self.extract_asset_path(asset)
|
77
|
+
case asset
|
78
|
+
when ActiveStorage::Blob
|
79
|
+
# ActiveStorage Blob
|
80
|
+
Rails.application.routes.url_helpers.rails_blob_path(asset, only_path: true)
|
81
|
+
|
82
|
+
when ActiveStorage::Attached
|
83
|
+
# ActiveStorage Attachment (has_one_attached, has_many_attached)
|
84
|
+
if asset.respond_to?(:blob) && asset.blob.present?
|
85
|
+
Rails.application.routes.url_helpers.rails_blob_path(asset.blob, only_path: true)
|
86
|
+
else
|
87
|
+
Rails.logger.warn("[Fileboost] ActiveStorage attachment has no blob") if defined?(Rails)
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
when ActiveStorage::VariantWithRecord
|
92
|
+
# ActiveStorage Variant - use blob URL to avoid triggering variant generation
|
93
|
+
Rails.application.routes.url_helpers.rails_blob_path(asset.blob, only_path: true)
|
94
|
+
|
95
|
+
else
|
96
|
+
# Only ActiveStorage objects are supported
|
97
|
+
Rails.logger.warn("[Fileboost] Unsupported asset type: #{asset.class}. Only ActiveStorage objects are supported.") if defined?(Rails)
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
rescue StandardError => e
|
101
|
+
if defined?(Rails) && Rails.env.development?
|
102
|
+
raise e
|
103
|
+
else
|
104
|
+
Rails.logger.warn("[Fileboost] Failed to extract asset path: #{e.message}") if defined?(Rails)
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.extract_transformation_params(options)
|
110
|
+
params = {}
|
111
|
+
|
112
|
+
# Only handle nested resize parameter
|
113
|
+
if options[:resize].is_a?(Hash)
|
114
|
+
resize_options = options[:resize]
|
115
|
+
resize_options.each do |key, value|
|
116
|
+
key_str = key.to_s
|
117
|
+
|
118
|
+
# Only process valid resize parameters
|
119
|
+
next unless RESIZE_PARAMS.include?(key_str)
|
120
|
+
|
121
|
+
# Use alias if available
|
122
|
+
param_key = PARAM_ALIASES[key_str] || key_str
|
123
|
+
|
124
|
+
# Convert value to string and validate
|
125
|
+
param_value = normalize_param_value(param_key, value)
|
126
|
+
next if param_value.blank?
|
127
|
+
|
128
|
+
params[param_key] = param_value
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
params
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.normalize_param_value(key, value)
|
136
|
+
case key
|
137
|
+
when "w", "h", "q", "b", "br", "c", "r"
|
138
|
+
# Numeric parameters
|
139
|
+
value.to_i.to_s if value.to_i > 0
|
140
|
+
when "f"
|
141
|
+
# Format parameter - validate against common formats
|
142
|
+
valid_formats = %w[webp jpeg jpg png gif avif]
|
143
|
+
normalized = value.to_s.downcase
|
144
|
+
valid_formats.include?(normalized) ? normalized : nil
|
145
|
+
when "fit"
|
146
|
+
# Fit parameter - validate against supported values
|
147
|
+
valid_fits = %w[cover contain fill scale-down crop pad]
|
148
|
+
normalized = value.to_s.downcase.gsub("_", "-")
|
149
|
+
valid_fits.include?(normalized) ? normalized : nil
|
150
|
+
else
|
151
|
+
value.to_s
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
data/lib/fileboost.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "fileboost/version"
|
2
|
+
require "fileboost/config"
|
3
|
+
require "fileboost/error_handler"
|
4
|
+
require "fileboost/signature_generator"
|
5
|
+
require "fileboost/url_builder"
|
6
|
+
require "fileboost/helpers"
|
7
|
+
require "fileboost/engine"
|
8
|
+
|
9
|
+
module Fileboost
|
10
|
+
# Fileboost provides seamless integration with the Fileboost.dev
|
11
|
+
# image optimization service for Rails applications
|
12
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "rails/generators/base"
|
2
|
+
|
3
|
+
module Fileboost
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
desc "Create Fileboost initializer file"
|
7
|
+
|
8
|
+
def self.source_root
|
9
|
+
@source_root ||= File.expand_path("templates", __dir__)
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_initializer_file
|
13
|
+
template "fileboost.rb", "config/initializers/fileboost.rb"
|
14
|
+
end
|
15
|
+
|
16
|
+
def show_readme
|
17
|
+
readme "INSTALL" if behavior == :invoke
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def readme(path)
|
23
|
+
say File.read(File.join(self.class.source_root, "#{path}.md"))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
===============================================================================
|
2
|
+
|
3
|
+
⚡ Fileboost Installation Complete!
|
4
|
+
|
5
|
+
===============================================================================
|
6
|
+
|
7
|
+
The Fileboost initializer has been created at:
|
8
|
+
config/initializers/fileboost.rb
|
9
|
+
|
10
|
+
Next steps:
|
11
|
+
|
12
|
+
1. Set your environment variables:
|
13
|
+
export FILEBOOST_PROJECT_ID="your-project-id"
|
14
|
+
export FILEBOOST_TOKEN="your-secret-token"
|
15
|
+
|
16
|
+
2. Or configure directly in the initializer file:
|
17
|
+
Edit config/initializers/fileboost.rb with your credentials
|
18
|
+
|
19
|
+
3. Start using Fileboost helpers in your views:
|
20
|
+
|
21
|
+
<!-- Replace image_tag with fileboost_image_tag -->
|
22
|
+
|
23
|
+
<%= fileboost_image_tag user.avatar, alt: "Avatar", resize: {width: 100, height: 100, fit: "cover"} %>
|
24
|
+
|
25
|
+
<!-- Generate optimized URLs -->
|
26
|
+
|
27
|
+
<%= fileboost_url_for post.image %>
|
28
|
+
|
29
|
+
4. Supported transformation options:
|
30
|
+
- width, height (or w, h)
|
31
|
+
- quality (or q): 1-100
|
32
|
+
- format (or f): webp, jpeg, png, gif, avif
|
33
|
+
- blur (or b): 0-100
|
34
|
+
- brightness (or br): 0-200
|
35
|
+
- contrast (or c): 0-200
|
36
|
+
- rotation (or r): 0-359
|
37
|
+
- fit: cover, contain, fill, scale-down, crop, pad
|
38
|
+
|
39
|
+
For more information, visit: https://fileboost.dev
|
40
|
+
|
41
|
+
===============================================================================
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Fileboost Configuration
|
2
|
+
#
|
3
|
+
# Configure your Fileboost integration for seamless image optimization
|
4
|
+
# through the Fileboost.dev service. Set up your environment variables or
|
5
|
+
# configure the values directly below.
|
6
|
+
#
|
7
|
+
# Fileboost uses cdn.fileboost.dev as the CDN domain and only supports
|
8
|
+
# ActiveStorage objects.
|
9
|
+
|
10
|
+
Fileboost.configure do |config|
|
11
|
+
# Your unique Fileboost project identifier
|
12
|
+
# You can also set this via the FILEBOOST_PROJECT_ID environment variable
|
13
|
+
config.project_id = ENV["FILEBOOST_PROJECT_ID"] # || "your-project-id"
|
14
|
+
|
15
|
+
# HMAC signing secret for secure authentication with Fileboost.dev service
|
16
|
+
# You can also set this via the FILEBOOST_TOKEN environment variable
|
17
|
+
# IMPORTANT: Keep this secret secure and never commit it to version control
|
18
|
+
config.token = ENV["FILEBOOST_TOKEN"] # || "your-secret-token"
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fileboost
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- bilal
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: activestorage
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '6.0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '6.0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: bundler
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.15'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.15'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: combustion
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.1'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.1'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: rake
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '13.0'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '13.0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: rails
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 8.0.0
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 8.0.0
|
82
|
+
description: Fileboost provides drop-in replacement Rails image helpers with automatic
|
83
|
+
optimization through the Fileboost.dev service. Works exclusively with ActiveStorage
|
84
|
+
objects, features HMAC authentication, and comprehensive transformation support.
|
85
|
+
email:
|
86
|
+
- bilal@bilalbudhani.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- MIT-LICENSE
|
92
|
+
- README.md
|
93
|
+
- Rakefile
|
94
|
+
- lib/fileboost.rb
|
95
|
+
- lib/fileboost/config.rb
|
96
|
+
- lib/fileboost/engine.rb
|
97
|
+
- lib/fileboost/error_handler.rb
|
98
|
+
- lib/fileboost/helpers.rb
|
99
|
+
- lib/fileboost/signature_generator.rb
|
100
|
+
- lib/fileboost/url_builder.rb
|
101
|
+
- lib/fileboost/version.rb
|
102
|
+
- lib/generators/fileboost/install_generator.rb
|
103
|
+
- lib/generators/fileboost/templates/INSTALL.md
|
104
|
+
- lib/generators/fileboost/templates/fileboost.rb
|
105
|
+
homepage: https://github.com/bilalbudhani/fileboost-ruby
|
106
|
+
licenses:
|
107
|
+
- MIT
|
108
|
+
metadata:
|
109
|
+
homepage_uri: https://github.com/bilalbudhani/fileboost-ruby
|
110
|
+
source_code_uri: https://github.com/bilalbudhani/fileboost-ruby
|
111
|
+
changelog_uri: https://github.com/bilalbudhani/fileboost-ruby/blob/main/CHANGELOG.md
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '3.0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubygems_version: 3.6.9
|
127
|
+
specification_version: 4
|
128
|
+
summary: Rails gem for Fileboost.dev image optimization with ActiveStorage
|
129
|
+
test_files: []
|