Class: Studio::EmailsController

Inherits:
ApplicationController
  • Object
show all
Defined in:
app/controllers/studio/emails_controller.rb

Overview

/admin/emails — the standard transactional-email page every Studio app gets, modelled on the living style guide (/admin/style): a plain host-inherited controller whose view is a bare content wrapper, so it renders inside each host's application layout and picks up that app's navbar and theme.

It lists Studio::EmailCatalog's registry — one row per registered email, each showing its live banner and whether that banner is the INHERITED engine default or an APP-OWNED override — and writes an override through the shared crop modal. Replaces /admin/email_images, which now redirects here.

An app whose host never set Studio.s3_bucket_prefix cannot store an override. That is a read-only page, not an error: uploads_available? gates the write actions and the view explains why, so the page still shows what each email is currently sending.

Constant Summary collapse

MAX_BYTES =
8.megabytes

Instance Method Summary collapse

Instance Method Details

#copyObject

PATCH /admin/emails/:key/copy — the banner's words and logo.

Separate from #settings rather than one big form: the tint is a slider you nudge while looking at the picture, the copy is a sentence you write. They save independently so tuning one never risks clobbering an unsaved edit to the other. Saves EVERYTHING the page edits, in one write. The page has a single Save button, so it must: three buttons that each saved a third of the form meant an operator who changed the subject and the tint had to notice there were two places to press.



126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'app/controllers/studio/emails_controller.rb', line 126

def copy
  rescue_and_log do
    Studio::EmailSetting.set_copy(@key, copy_params)
    if params.key?(:scrim_percent)
      percent = params[:scrim_percent]
      percent = nil unless percent.present? && Studio::EmailSetting::SCRIM_RANGE.cover?(percent.to_i)
      Studio::EmailSetting.set_scrim(@key, percent)
    end
    redirect_to admin_email_path(@key), status: :see_other, notice: "Saved."
  end
rescue StandardError
  redirect_to admin_email_path(@key), status: :see_other,
              alert: "Couldn't save the banner text. Please try again."
end

#destroyObject

DELETE /admin/emails/:key — drop this app's override and fall back to the inherited default.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'app/controllers/studio/emails_controller.rb', line 169

def destroy
  # rescue_and_log, like #update: destroy is a WRITE path (it drops an
  # ImageCache row and deletes the S3 object behind it), and the bare rescue
  # below turns any failure into a friendly alert. Without the log that
  # failure is invisible — the admin sees "try again" and nothing reaches
  # ErrorLog to say why.
  rescue_and_log do
    reverted = Studio::EmailCatalog.revert(@key)
    # label() falls back to the humanised key, so an ORPHAN (no registry
    # entry at all) still gets a readable message rather than a blank one.
    label = Studio::EmailCatalog.label(@key).presence || @key.humanize
    notice = if reverted
               "#{label} reverted to the inherited default."
             else
               "#{label} was already using the inherited default."
             end
    redirect_to admin_emails_path, notice: notice, status: :see_other
  end
rescue StandardError
  redirect_to admin_emails_path, alert: "Couldn't revert the image. Please try again.", status: :see_other
end

#indexObject



23
24
25
26
27
28
29
30
31
32
# File 'app/controllers/studio/emails_controller.rb', line 23

def index
  @entries = Studio::EmailCatalog.entries
  @uploads_available = Studio::EmailCatalog.uploads_available?

  # The list renders every banner and subject AS THEY WOULD ARRIVE, which has
  # no answer until someone is receiving them.
  @targets = Studio::EmailPreviewTarget.all
  @target = Studio::EmailPreviewTarget.resolve(params[:target])
  @preview_name = @target&.name
end

#logoObject

PATCH /admin/emails/:key/logo — upload a logo for THIS email, or drop it.

Its own action and its own ImageCache purpose: a logo is a small transparent mark, a banner is 3:1 artwork that may be animated, and reverting one must not touch the other.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'app/controllers/studio/emails_controller.rb', line 146

def 
  if params[:remove].present?
    rescue_and_log { Studio::EmailCatalog.(@key) }
    return redirect_to admin_email_path(@key), status: :see_other,
                       notice: "Back to the standard logo."
  end

  file = params[:image]
  unless valid_image?(file)
    message = file.blank? ? "Choose an image to upload." : "Use a PNG, JPG, WebP or GIF under 8 MB."
    return redirect_to admin_email_path(@key), alert: message, status: :see_other
  end

  rescue_and_log do
    Studio::EmailCatalog.(@key, io: file, content_type: file.content_type)
    redirect_to admin_email_path(@key), notice: "Logo updated.", status: :see_other
  end
rescue StandardError
  redirect_to admin_email_path(@key), alert: "Couldn't save the logo. Please try again.", status: :see_other
end

#rawObject

GET /admin/emails/:key/raw — the rendered email itself, as the iframe source on #show. Layout-less on purpose: this response IS the email.

A preview builder is host code run against whatever sample data this environment happens to hold, so it is expected to fail sometimes. It renders the failure as a readable page inside the iframe rather than 500ing, so one broken builder costs one preview, not the manager.



69
70
71
72
73
74
# File 'app/controllers/studio/emails_controller.rb', line 69

def raw
  html = Studio::EmailCatalog.preview_html(@key)
  return render(html: preview_unavailable_html.html_safe, layout: false) if html.nil?

  render html: html.html_safe, layout: false
end

#settingsObject

PATCH /admin/emails/:key/settings — operator-tunable knobs.

The scrim is the dial between a readable header and a visible picture, and the right value depends on artwork that changes without a deploy. Blank clears the override so the email falls back to the registry default rather than being pinned to whatever that default was on the day.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'app/controllers/studio/emails_controller.rb', line 98

def settings
  percent = params[:scrim_percent]

  if percent.present? && !Studio::EmailSetting::SCRIM_RANGE.cover?(percent.to_i)
    return redirect_to admin_email_path(@key), status: :see_other,
                       alert: "Tint must be between 0 and 100."
  end

  rescue_and_log do
    Studio::EmailSetting.set_scrim(@key, percent)
    redirect_to admin_email_path(@key), status: :see_other,
                notice: percent.present? ? "Tint set to #{percent.to_i}%." : "Tint reset to the default."
  end
rescue StandardError
  redirect_to admin_email_path(@key), status: :see_other,
              alert: "Couldn't save the tint. Please try again."
end

#showObject

GET /admin/emails/:key — one email: its banner, its type, and a live preview built from the host's sample data.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'app/controllers/studio/emails_controller.rb', line 36

def show
  # nil for an ORPHAN — an email that left the registry while this app still
  # holds an upload for it. The view renders a minimal page whose only real
  # affordance is Revert, which is the whole reason the route stays open.
  @entry = Studio::EmailCatalog.entry(@key)
  @orphan = @entry.nil?
  return render(:orphan) if @orphan

  @subject = Studio::EmailCatalog.preview_subject(@key)
  @preview_error = Studio::EmailCatalog.preview_error(@key)
  @uploads_available = Studio::EmailCatalog.uploads_available?

  # WHO this is previewed as. The banner greets by name, so "what does this
  # email look like" has no answer until someone is receiving it — and the
  # two people who break email differently (an admin with a full name on
  # file, a member with none) are both offered.
  @targets = Studio::EmailPreviewTarget.all
  @target = Studio::EmailPreviewTarget.resolve(params[:target])
  @preview_name = @target&.name

  # The banner AS IT ARRIVES, built the same way a mailer builds it so the
  # page cannot show something the inbox never gets.
  @banner = Studio::Banner.for(@key, name: @preview_name)
  @subject = Studio::EmailCatalog.subject_for(@key, name: @preview_name) || @subject
end

#updateObject

PATCH /admin/emails/:key — upload/replace this app's own banner.



77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'app/controllers/studio/emails_controller.rb', line 77

def update
  file = params[:image]
  unless valid_image?(file)
    message = file.blank? ? "Choose an image to upload." : "Use a PNG, JPG, WebP or GIF under 8 MB."
    return redirect_to admin_emails_path, alert: message, status: :see_other
  end

  rescue_and_log do
    Studio::EmailCatalog.store(@key, io: file, content_type: file.content_type)
    redirect_to admin_emails_path, notice: "#{Studio::EmailCatalog.label(@key)} banner updated.", status: :see_other
  end
rescue StandardError
  redirect_to admin_emails_path, alert: "Couldn't save the image. Please try again.", status: :see_other
end