Class: Api::V1::OembedController

Inherits:
BaseController
  • Object
show all
Defined in:
app/controllers/api/v1/oembed_controller.rb

Overview

oEmbed API endpoint for Redactor 4 embed plugin
Converts URLs (YouTube, Vimeo, WarmlyYours videos, etc.) to embeddable HTML

For external providers: Uses ruby-oembed gem with built-in providers + Noembed fallback
For WY videos: Uses custom Oembed::WyVideoProvider for proper Shaka player support

Providers are registered in config/initializers/oembed.rb
Examples:
GET /api/v1/oembed?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ
=> { html: "<iframe ...>", type: "video", provider_name: "YouTube" }

GET /api/v1/oembed?url=https://www.warmlyyours.com/videos/my-video-slug
=> { html: "...", type: "video", provider_name: "WarmlyYours" }

Instance Method Summary collapse

Methods inherited from BaseController

#catalog_for_request, #error!, #locale_for_request, #logger, #render_bad_request_response, #render_internal_server_error, #render_not_found_response, #render_result, #render_unprocessable_entity_response, #set_locale, #store_for_request, #underscore_params

Instance Method Details

#faqObject

GET /api/v1/oembed/faq
Returns rendered FAQ HTML for embedding in Redactor editor

Required params (one of):
uuid - EmbeddedAsset UUID (new flow, preferred)
ids - Comma-separated FAQ IDs (legacy flow)

Optional params:
title - Custom section title
sort - Sort option: 'popularity' for most helpful first

Usage:
GET /api/v1/oembed/faq?uuid=abc123-...
GET /api/v1/oembed/faq?ids=123,456&title=Custom%20Title&sort=popularity
=> { html: "...", type: "rich", faq_ids: [123, 456] }



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'app/controllers/api/v1/oembed_controller.rb', line 94

def faq
  Rails.logger.debug { "[oEmbed] FAQ params received: #{params.to_unsafe_h.inspect}" }

  # Support UUID-based lookup (new flow) or direct ids (legacy flow)
  return render_faq_by_uuid(params[:uuid]) if params[:uuid].present?

  ids = params[:ids].to_s.split(',').map(&:strip).map(&:to_i).reject(&:zero?)

  return render json: { error: 'ids or uuid parameter is required' }, status: :bad_request if ids.empty?

  provider = Oembed::FaqProvider.new
  result = provider.get(
    ids: ids,
    title: params[:title],
    sort: params[:sort]
  )

  render json: {
    html: result[:html],
    type: result[:type],
    provider_name: result[:provider_name],
    title: result[:title],
    faq_ids: result[:faq_ids],
    faq_count: result[:faq_count]
  }
rescue Oembed::FaqProvider::FaqNotFoundError => e
  Rails.logger.warn("[oEmbed] FAQ not found: #{ids.join(',')} - #{e.message}")
  render json: { error: e.message }, status: :not_found
rescue StandardError => e
  Rails.logger.error("[oEmbed] Error fetching FAQ embed: #{ids.join(',')} - #{e.class}: #{e.message}")
  render json: { error: 'Failed to fetch FAQ embed data' }, status: :internal_server_error
end

#imageObject

GET /api/v1/oembed/image
Returns rendered image HTML for embedding in Redactor editor
Uses image_asset_tag for consistent rendering with the rest of the site

Required params:
image_id - The image ID from the library

Optional params (ImageKit transformations):
width, height - Target dimensions
crop_x, crop_y, crop_w, crop_h - Crop area
crop_mode - Crop mode (pad_resize, force, etc.)
rotate - Rotation in degrees
blur - Blur amount (1-100)
background - Background color for padding
encode_format - Output format (jpeg, png, webp)

Optional params (display):
caption - Image caption text
alt - Alt text override
link - URL to wrap image in link
link_target - Link target (_blank, etc.)
lazyload - Enable lazy loading (default: true)
include_srcset - Include responsive srcset (default: true)
wrapper_tag - Wrapper element: 'figure' (default), 'p', or 'div' (for email compatibility)

Usage:
GET /api/v1/oembed/image?image_id=123&width=800&caption=My%20caption
=> { html: "...", type: "photo", ... }



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'app/controllers/api/v1/oembed_controller.rb', line 155

def image
  Rails.logger.debug { "[oEmbed] Image params received: #{params.to_unsafe_h.inspect}" }

  # Support UUID-based lookup (new flow) or direct image_id (legacy flow)
  return render_image_by_uuid(params[:uuid]) if params[:uuid].present?

  image_id = params[:image_id]

  return render json: { error: 'image_id or uuid parameter is required' }, status: :bad_request if image_id.blank?

  # Build options from permitted params
  options = image_params.merge(image_id: image_id)
  Rails.logger.debug { "[oEmbed] Image options after merge: #{options.inspect}" }

  provider = Oembed::ImageProvider.new
  result = provider.get(options)

  render json: {
    html: result[:html],
    type: result[:type],
    provider_name: result[:provider_name],
    title: result[:title],
    thumbnail_url: result[:thumbnail_url],
    width: result[:width],
    height: result[:height],
    # Custom fields for editing
    image_id: result[:image_id],
    image_slug: result[:image_slug],
    alt: result[:alt],
    caption: result[:caption],
    options: result[:options]
  }
rescue Oembed::ImageProvider::ImageNotFoundError => e
  Rails.logger.warn("[oEmbed] Image not found: #{image_id} - #{e.message}")
  render json: { error: e.message }, status: :not_found
rescue StandardError => e
  Rails.logger.error("[oEmbed] Error fetching image embed: #{image_id} - #{e.class}: #{e.message}")
  Rails.logger.error("[oEmbed] Backtrace: #{e.backtrace.first(10).join("\n")}")
  render json: { error: "Failed to fetch image embed data: #{e.message}" }, status: :internal_server_error
end

#postObject

GET /api/v1/oembed/post
Consumer-facing oEmbed for blog-post pages. Backs the
application/json+oembed discovery links so iframely/Gamma, Slack, Notion,
etc. resolve the canonical title/description/thumbnail instead of scraping
in-article Product JSON-LD.

Required params:
url - The blog-post URL
Optional params:
format - 'json' (default) or 'xml'

Usage:
GET /api/v1/oembed/post?url=https://www.warmlyyours.com/en-US/posts/my-slug
=> { version: "1.0", type: "link", title: "...", description: "...", ... }



72
73
74
75
76
77
# File 'app/controllers/api/v1/oembed_controller.rb', line 72

def post
  url = params[:url]
  return render json: { error: 'URL parameter is required' }, status: :bad_request if url.blank?

  handle_wy_post(url)
end

#productObject

GET /api/v1/oembed/product
Returns rendered product HTML for embedding in Redactor editor
Shows a magazine-style product card with image, title, SKU, price, and add-to-cart

Required params (one of):
uuid - EmbeddedAsset UUID (new flow, preferred)
sku - The product SKU (legacy flow)

Optional params:
locale - Locale for pricing ('en-US' or 'en-CA', defaults to 'en-US')

Usage:
GET /api/v1/oembed/product?uuid=abc123-...
GET /api/v1/oembed/product?sku=TRT512-KIT&locale=en-US
=> { html: "...", type: "rich", sku: "TRT512-KIT" }



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'app/controllers/api/v1/oembed_controller.rb', line 266

def product
  Rails.logger.debug { "[oEmbed] Product params received: #{params.to_unsafe_h.inspect}" }

  # Support UUID-based lookup (new flow) or direct sku (legacy flow)
  return render_product_by_uuid(params[:uuid]) if params[:uuid].present?

  sku = params[:sku].to_s.strip.upcase
  locale = params[:locale].presence || I18n.locale.to_s

  return render json: { error: 'sku or uuid parameter is required' }, status: :bad_request if sku.blank?

  provider = Oembed::ProductProvider.new
  result = provider.get(sku: sku, locale: locale)

  render json: {
    html: result[:html],
    type: result[:type],
    provider_name: result[:provider_name],
    title: result[:title],
    sku: result[:sku],
    image_url: result[:image_url]
  }
rescue Oembed::ProductProvider::ProductNotFoundError => e
  Rails.logger.warn("[oEmbed] Product not found: #{sku} - #{e.message}")
  render json: { error: e.message }, status: :not_found
rescue Oembed::ProductProvider::ProductUnavailableError => e
  Rails.logger.warn("[oEmbed] Product unavailable: #{sku} - #{e.message}")
  render json: { error: e.message }, status: :gone
rescue StandardError => e
  Rails.logger.error("[oEmbed] Error fetching product embed: #{sku} - #{e.class}: #{e.message}")
  Rails.logger.error("[oEmbed] Backtrace: #{e.backtrace.first(10).join("\n")}")
  render json: { error: 'Failed to fetch product embed data' }, status: :internal_server_error
end

#showObject

GET /api/v1/oembed
Required params:
url - The URL to convert to embed HTML
Optional params:
maxwidth - Maximum width for the embed (default: 800)
maxheight - Maximum height for the embed (default: 600)



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'app/controllers/api/v1/oembed_controller.rb', line 32

def show
  url = params[:url]

  return render json: { error: 'URL parameter is required' }, status: :bad_request if url.blank?

  # Request oEmbed data with optional size constraints
  options = {}
  options[:maxwidth] = params[:maxwidth].to_i if params[:maxwidth].present?
  options[:maxheight] = params[:maxheight].to_i if params[:maxheight].present?

  # Set reasonable defaults for embed size
  options[:maxwidth] ||= 800
  options[:maxheight] ||= 450 # 16:9 aspect ratio

  # Check if this is a WarmlyYours blog-post URL first (consumer discovery may
  # hit the generic endpoint rather than the dedicated /oembed/post one).
  return handle_wy_post(url) if Oembed::PostProvider.new.handles?(url)

  # Then WarmlyYours videos.
  wy_provider = Oembed::WyVideoProvider.new
  return handle_wy_video(url, options) if wy_provider.handles?(url)

  # Fall through to standard oEmbed providers for external URLs
  handle_external_oembed(url, options)
end

#videoObject

GET /api/v1/oembed/video
Returns rendered video HTML for embedding in Redactor editor
Uses Shaka player with HLS/captions support

Required params (one of):
uuid - EmbeddedAsset UUID (new flow, preferred)
video_id - Video ID (legacy flow)

Optional params:
player_type - 'html5' (default) or 'iframe'

Usage:
GET /api/v1/oembed/video?uuid=abc123-...
=> { html: "<video ...data-embedded-asset-uuid='...'...", type: "video", ... }



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'app/controllers/api/v1/oembed_controller.rb', line 210

def video
  Rails.logger.debug { "[oEmbed] Video params received: #{params.to_unsafe_h.inspect}" }

  # Support UUID-based lookup (new flow) or direct video_id (legacy flow)
  return render_video_by_uuid(params[:uuid]) if params[:uuid].present?

  video_id = params[:video_id]
  return render json: { error: 'video_id or uuid parameter is required' }, status: :bad_request if video_id.blank?

  video_record = Video.find_by(id: video_id)
  return render json: { error: 'Video not found' }, status: :not_found unless video_record

  # Build video URL and use existing handler
  # include_wrapper: true so the HTML has the ratio-16x9 wrapper + play button overlay
  video_url = "https://www.warmlyyours.com/videos/#{video_record.slug}"
  options = { player_type: params[:player_type] || 'html5', include_wrapper: true }

  provider = Oembed::WyVideoProvider.new
  result = provider.get(video_url, options)

  render_oembed({
    version: '1.0',
    html: result[:html],
    type: result[:type],
    provider_name: result[:provider_name],
    title: result[:title],
    thumbnail_url: result[:thumbnail_url],
    width: result[:width],
    height: result[:height],
    video_id: result[:video_id],
    video_slug: result[:video_slug]
  })
rescue Oembed::WyVideoProvider::VideoNotFoundError => e
  Rails.logger.warn("[oEmbed] Video not found: #{video_id} - #{e.message}")
  render json: { error: e.message }, status: :not_found
rescue StandardError => e
  Rails.logger.error("[oEmbed] Error fetching video embed: #{video_id} - #{e.class}: #{e.message}")
  Rails.logger.error("[oEmbed] Backtrace: #{e.backtrace.first(10).join("\n")}")
  render json: { error: "Failed to fetch video embed data: #{e.message}" }, status: :internal_server_error
end