studio-engine 0.5.2 → 0.5.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f351e5f2cf75e05dfc8befdd34cb525310ab4c4415bb084d15c57a1e3713b782
4
- data.tar.gz: f84aad6087299527b4cdb330e644ee707f695b6a70597c753039c8b7af81ee93
3
+ metadata.gz: a8a6751151b77737dbf25bd04a59d9ad3f801ec8f66c214dc5a97ef5f006b1f6
4
+ data.tar.gz: f0283ba8e4e38b8e2375b563b6f93917c3ffbc3a0880353c62ec2c5f9441b6f4
5
5
  SHA512:
6
- metadata.gz: fc184764831f825771d76f9cc93ae51f0103823c571ad886431beeffeb1ba55c15907004dc1796d6901fdafc560571c02cbde8fdd02141f2629c5cd60071f5a0
7
- data.tar.gz: 20a76451338e9aea84191a1c5f3efb995c46514117e18e80d014bda75524ff50e6288abb88da26b84ce463fd296f601e63580de9bfb729458c31cfaf54fa96dc
6
+ metadata.gz: a911d76518bba342d65e2e5cb1d32c80f28a484057deb8ea630a2aae34867a530b51f0814abedee21267ff66a03df2c71e4c5610b24248e8cb5174b289f92da5
7
+ data.tar.gz: '09a3b96acf18c3cd876463445eb02f704b25292f048006be18c44aeaffe2e9e152d1f42977bad66cde5834c87a0eda05489ebbf1b53cdb8c7ab6ca1f5a1d3d7b'
data/CHANGELOG.md CHANGED
@@ -6,6 +6,16 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
6
6
 
7
7
  No entries yet.
8
8
 
9
+ ## v0.5.3 (2026-06-14)
10
+
11
+ ### Added
12
+ - **`Studio::Email.deliver`** — shared ActionMailer delivery entry point that uses an app-level `EmailDelivery` when present, the engine's namespaced durable outbox when installed, and raw `deliver_later` as a fallback.
13
+ - **`Studio::EmailDelivery` / `Studio::EmailDeliveryJob`** — namespaced durable delivery rows for apps that want shared audit, retry, and resend recovery without colliding with an existing top-level `EmailDelivery` model.
14
+ - **`studio_email_deliveries` migration template** — installable shared outbox table for new or migrating consumer apps.
15
+
16
+ ### Changed
17
+ - Engine magic-link and passwordless signup controllers now send through `Studio::Email.deliver` instead of calling `deliver_later` directly.
18
+
9
19
  ## v0.5.2 (2026-06-13)
10
20
 
11
21
  ### Added
@@ -22,7 +22,7 @@ class MagicLinksController < ApplicationController
22
22
  email = params[:email].to_s.strip.downcase
23
23
  if email.match?(URI::MailTo::EMAIL_REGEXP)
24
24
  token = MagicLink.generate(email: email, return_to: safe_path(params[:return_to]))
25
- UserMailer.magic_link(email, token).deliver_later
25
+ Studio::Email.deliver(UserMailer, :magic_link, email, token, to: email)
26
26
  end
27
27
  respond_to do |format|
28
28
  format.json { render json: { success: true } }
@@ -14,7 +14,7 @@ class RegistrationsController < ApplicationController
14
14
  email = (params.dig(:user, :email) || params[:email]).to_s.strip.downcase
15
15
  if email.match?(URI::MailTo::EMAIL_REGEXP)
16
16
  token = MagicLink.generate(email: email)
17
- UserMailer.magic_link(email, token).deliver_later
17
+ Studio::Email.deliver(UserMailer, :magic_link, email, token, to: email)
18
18
  end
19
19
  return redirect_to login_path, notice: "Check your inbox — we just emailed you a sign-in link."
20
20
  end
@@ -0,0 +1,9 @@
1
+ module Studio
2
+ class EmailDeliveryJob < (defined?(::ApplicationJob) ? ::ApplicationJob : ActiveJob::Base)
3
+ queue_as :mailers
4
+
5
+ def perform(id)
6
+ Studio::EmailDelivery.find_by(id: id)&.deliver_now!
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ module Studio
2
+ class EmailDelivery < ApplicationRecord
3
+ self.table_name = "studio_email_deliveries"
4
+
5
+ belongs_to :user, optional: true
6
+
7
+ scope :unsent, -> { where(sent: false) }
8
+ scope :recent, -> { order(created_at: :desc) }
9
+
10
+ def self.available?
11
+ connection.data_source_exists?(table_name)
12
+ rescue ActiveRecord::ActiveRecordError, NoMethodError
13
+ false
14
+ end
15
+
16
+ def self.deliver(mailer, action, *args, to:, user: nil, **kwargs)
17
+ record = create!(
18
+ mailer: mailer.to_s,
19
+ action: action.to_s,
20
+ email_key: "#{mailer}##{action}",
21
+ to: to.to_s,
22
+ user: user,
23
+ args: ActiveJob::Arguments.serialize(args),
24
+ kwargs: ActiveJob::Arguments.serialize([kwargs]).first
25
+ )
26
+ Studio::EmailDeliveryJob.perform_later(record.id)
27
+ record
28
+ end
29
+
30
+ def deliver_now!
31
+ return if sent?
32
+
33
+ pos = ActiveJob::Arguments.deserialize(args)
34
+ kw = ActiveJob::Arguments.deserialize([kwargs]).first.symbolize_keys
35
+ mailer.constantize.public_send(action, *pos, **kw).deliver_now
36
+ update!(sent: true, sent_at: Time.current, error: nil)
37
+ rescue StandardError => e
38
+ update(error: e.message.to_s.first(500))
39
+ raise
40
+ end
41
+
42
+ def self.resend_unsent!
43
+ unsent.find_each { |delivery| Studio::EmailDeliveryJob.perform_later(delivery.id) }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,22 @@
1
+ class CreateStudioEmailDeliveries < ActiveRecord::Migration[7.2]
2
+ def change
3
+ create_table :studio_email_deliveries do |t|
4
+ t.string :email_key, null: false
5
+ t.string :to
6
+ t.string :mailer, null: false
7
+ t.string :action, null: false
8
+ t.jsonb :args, null: false, default: []
9
+ t.jsonb :kwargs, null: false, default: {}
10
+ t.boolean :sent, null: false, default: false
11
+ t.datetime :sent_at
12
+ t.text :error
13
+ t.references :user, foreign_key: true
14
+
15
+ t.timestamps
16
+ end
17
+
18
+ add_index :studio_email_deliveries, :sent
19
+ add_index :studio_email_deliveries, :email_key
20
+ add_index :studio_email_deliveries, :created_at
21
+ end
22
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Studio
4
+ module Email
5
+ class << self
6
+ # Shared email send entry point for Studio apps.
7
+ #
8
+ # Apps with an existing top-level EmailDelivery model keep using it through
9
+ # this facade. Apps that have installed the engine outbox migration use the
10
+ # namespaced Studio::EmailDelivery model. Apps without either still send via
11
+ # ActionMailer's normal deliver_later path.
12
+ def deliver(mailer, action, *args, to:, user: nil, **kwargs)
13
+ if (adapter = app_delivery_adapter)
14
+ return adapter.deliver(mailer, action, *args, to: to, user: user, **kwargs)
15
+ end
16
+
17
+ if (adapter = studio_delivery_adapter)
18
+ return adapter.deliver(mailer, action, *args, to: to, user: user, **kwargs)
19
+ end
20
+
21
+ mailer.public_send(action, *args, **kwargs).deliver_later
22
+ end
23
+
24
+ private
25
+
26
+ def app_delivery_adapter
27
+ return unless Object.const_defined?(:EmailDelivery, false)
28
+
29
+ adapter = Object.const_get(:EmailDelivery, false)
30
+ adapter if adapter.respond_to?(:deliver)
31
+ rescue NameError
32
+ nil
33
+ end
34
+
35
+ def studio_delivery_adapter
36
+ return unless Studio.const_defined?(:EmailDelivery, false)
37
+
38
+ adapter = Studio.const_get(:EmailDelivery, false)
39
+ adapter if adapter.respond_to?(:available?) && adapter.available?
40
+ rescue NameError
41
+ nil
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.5.2"
2
+ VERSION = "0.5.3"
3
3
  end
data/lib/studio.rb CHANGED
@@ -5,6 +5,7 @@ require "studio/theme_resolver"
5
5
  require "studio/username_generator"
6
6
  require "studio/s3"
7
7
  require "studio/image_cache"
8
+ require "studio/email"
8
9
  require "studio/mail_transport"
9
10
 
10
11
  module Studio
@@ -37,7 +38,7 @@ module Studio
37
38
  mattr_accessor :draw_auth_routes, default: true
38
39
 
39
40
  # Default From: for engine-sent mail (magic links). Apps set this to their
40
- # verified Resend sending address in config/initializers/studio.rb.
41
+ # verified sending address in config/initializers/studio.rb.
41
42
  mattr_accessor :mailer_from, default: nil
42
43
 
43
44
  # Theme role colors (7 roles)
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  "changelog_uri" => "https://github.com/amcritchie/studio-engine/blob/main/CHANGELOG.md"
19
19
  }
20
20
 
21
- spec.files = Dir["lib/**/*", "app/**/*", "config/**/*", "tailwind/**/*", "Gemfile", "studio-engine.gemspec", "README.md", "CHANGELOG.md", "LICENSE"]
21
+ spec.files = Dir["lib/**/*", "app/**/*", "config/**/*", "db/**/*", "tailwind/**/*", "Gemfile", "studio-engine.gemspec", "README.md", "CHANGELOG.md", "LICENSE"]
22
22
  spec.require_paths = ["lib"]
23
23
 
24
24
  spec.add_dependency "rails", ">= 7.0", "< 8.0"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie
@@ -153,6 +153,7 @@ files:
153
153
  - app/controllers/theme_settings_controller.rb
154
154
  - app/helpers/studio_theme_helper.rb
155
155
  - app/jobs/error_log_cleanup_job.rb
156
+ - app/jobs/studio/email_delivery_job.rb
156
157
  - app/mailers/application_mailer.rb
157
158
  - app/mailers/user_mailer.rb
158
159
  - app/models/concerns/sluggable.rb
@@ -160,6 +161,7 @@ files:
160
161
  - app/models/error_log.rb
161
162
  - app/models/image_cache.rb
162
163
  - app/models/session_context.rb
164
+ - app/models/studio/email_delivery.rb
163
165
  - app/models/theme_setting.rb
164
166
  - app/services/google_oauth_validator.rb
165
167
  - app/services/magic_link.rb
@@ -199,9 +201,11 @@ files:
199
201
  - app/views/theme_settings/edit.html.erb
200
202
  - app/views/user_mailer/magic_link.html.erb
201
203
  - app/views/user_mailer/magic_link.text.erb
204
+ - db/migrate/20260614000000_create_studio_email_deliveries.rb
202
205
  - lib/studio-engine.rb
203
206
  - lib/studio.rb
204
207
  - lib/studio/color_scale.rb
208
+ - lib/studio/email.rb
205
209
  - lib/studio/engine.rb
206
210
  - lib/studio/image_cache.rb
207
211
  - lib/studio/mail_transport.rb