Class: Assistant::BlogMediaToolBuilder

Inherits:
Object
  • Object
show all
Defined in:
app/services/assistant/blog_media_tool_builder.rb

Overview

Service object: blog media tool builder.

Defined Under Namespace

Classes: EmbedOpGuardError

Constant Summary collapse

CRM_POST_URL =

URL for crm post.

BlogToolBuilder::CRM_POST_URL

Class Method Summary collapse

Class Method Details

.find_embed_figure(html, uuid) ⇒ Object

Note:

do NOT interpolate uuid into a CSS selector string. embed UUIDs
come straight from tool input, so quotes/CSS metacharacters (e.g. a
value like "]/script>) can turn a stale or malicious value into a
selector parse error — or worse, match the wrong node before we
mutate the fragment. Iterate the candidate nodes and compare the
attribute by string equality instead.

Locate the embed wrapper by its data-embedded-asset-uuid in the post
solution. Most embeds are , but video embeds are sometimes
rendered as (see
BlogContentValidator#detect_dropped_embeds for the full list). Match
any element carrying the attribute to stay aligned with the validator.
Returns [node, fragment] or [nil, nil].



552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
# File 'app/services/assistant/blog_media_tool_builder.rb', line 552

def find_embed_figure(html, uuid)
  return [nil, nil] if html.nil? || html.to_s.empty? || uuid.to_s.empty?

  fragment = if defined?(Nokogiri::HTML5)
               Nokogiri::HTML5.fragment(html.to_s)
             else
               Nokogiri::HTML.fragment(html.to_s)
             end

  uuid_str = uuid.to_s
  node = fragment.css('[data-embedded-asset-uuid]').find do |el|
    el['data-embedded-asset-uuid'] == uuid_str
  end
  [node, fragment]
end

.save_post_with_embed_op!(post, new_html, change_note:, audit_context:) ⇒ Object

Raises:



572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
# File 'app/services/assistant/blog_media_tool_builder.rb', line 572

def save_post_with_embed_op!(post, new_html, change_note:, audit_context:)
  # These atomic embed ops (remove_embed / replace_embed / move_embed)
  # write post.solution directly, so without this guard a hallucinated
  # <figure data-embedded-asset-uuid="..."> from replace_embed could
  # land in the database without going through the same body / link /
  # asset-reference checks update_blog_post and edit_blog_post rely on.
  # We pass skip_embed_check: true because the user's intent to drop or
  # rearrange the targeted UUID is explicit (that's the whole point of
  # these tools), but every other content rule must still pass.
  guard_err = Assistant::BlogToolBuilder.guard_content(
    body_args: {
      old_html: post.solution.to_s,
      new_html: new_html.to_s,
      post_id: post.id,
      skip_embed_check: true
    }
  )
  raise EmbedOpGuardError, guard_err if guard_err

  author = Employee.find_by(id: audit_context[:user_id])
  post.solution = new_html
  post.auto_update_revised_at = true if post.respond_to?(:auto_update_revised_at=)
  post.updater_id = author&.id
  post.revision_change_notes = change_note
  post.save!
  post.reload
  Assistant::BlogToolBuilder.stabilize_post_solution_after_save!(post)
  Assistant::BlogContentValidator.upsert_link_graph(post)
  # remove/replace/move all rewrite published body HTML, so the edge copy
  # is stale the moment this commits — same gap update_blog_post had.
  Assistant::BlogToolBuilder.purge_edge_cache_after_edit!(post)
  post
end

.serialize_fragment(fragment) ⇒ Object



568
569
570
# File 'app/services/assistant/blog_media_tool_builder.rb', line 568

def serialize_fragment(fragment)
  fragment.to_html
end

.tools(audit_context: {}) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
# File 'app/services/assistant/blog_media_tool_builder.rb', line 24

def tools(audit_context: {})
  [
    build_insert_image_tool,
    build_insert_video_tool,
    build_insert_faqs_tool,
    build_insert_product_tool,
    build_remove_embed_tool(audit_context),
    build_replace_embed_tool(audit_context),
    build_move_embed_tool(audit_context),
    build_refresh_blog_oembeds_tool(audit_context)
  ]
end