Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ class SharedAiConversationsController < ::ApplicationController
requires_login only: %i[create update destroy]
before_action :require_site_settings!

skip_before_action :preload_json, :check_xhr, only: %i[show]
skip_before_action :preload_json, :check_xhr, only: %i[show asset]
skip_before_action :verify_authenticity_token, only: ["asset"]

def create
ensure_allowed_create!
Expand Down Expand Up @@ -50,6 +51,30 @@ def show
end
end

def asset
no_cookies

name = params[:name]
path, content_type =
if name == "share"
%w[share.css text/css]
elsif name == "highlight"
%w[highlight.min.js application/javascript]
else
raise Discourse::NotFound
end

content = File.read(DiscourseAi.public_asset_path("ai-share/#{path}"))

# note, path contains a ":version" which automatically busts the cache
# based on file content, so this is safe
response.headers["Last-Modified"] = 10.years.ago.httpdate
response.headers["Content-Length"] = content.bytesize.to_s
immutable_for 1.year

render plain: content, disposition: :nil, content_type: content_type
end

def preview
ensure_allowed_preview!
data = SharedAiConversation.build_conversation_data(@topic, include_usernames: true)
Expand Down
28 changes: 22 additions & 6 deletions app/helpers/discourse_ai/ai_bot/shared_ai_conversations_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,29 @@
module DiscourseAi
module AiBot
module SharedAiConversationsHelper
# bump up version when assets change
# long term we may want to change this cause it is hard to remember
# to bump versions, but for now this does the job
VERSION = "1"
# keeping it here for caching
def self.share_asset_url(asset_name)
if !%w[share.css highlight.js].include?(asset_name)
raise StandardError, "unknown asset type #{asset_name}"
end

def share_asset_url(short_path)
::UrlHelper.absolute("/plugins/discourse-ai/ai-share/#{short_path}?#{VERSION}")
@urls ||= {}
url = @urls[asset_name]
return url if url

path = asset_name
path = "highlight.min.js" if asset_name == "highlight.js"

content = File.read(DiscourseAi.public_asset_path("ai-share/#{path}"))
sha1 = Digest::SHA1.hexdigest(content)

url = "/discourse-ai/ai-bot/shared-ai-conversations/asset/#{sha1}/#{asset_name}"

@urls[asset_name] = GlobalPath.cdn_path(url)
end

def share_asset_url(asset_name)
DiscourseAi::AiBot::SharedAiConversationsHelper.share_asset_url(asset_name)
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<meta name="twitter:title" content="<%= I18n.t("discourse_ai.share_ai.title", title: @shared_conversation.title, site_name: SiteSetting.title) %>">
<meta name="twitter:description" content="<%= @shared_conversation.formatted_excerpt %>">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="<%= ::UrlHelper.absolute("/plugins/discourse-ai/ai-share/share.css?v=1") %>">
<link rel="stylesheet" href="<%= share_asset_url("share.css") %>">
</head>
<body>
<header class="site-header">
Expand Down Expand Up @@ -54,7 +54,7 @@
</article>
<% end %>
</section>
<script src="<%= share_asset_url("highlight.min.js") %>" nonce="<%= csp_nonce_placeholder %>" ></script>
<script src="<%= share_asset_url("highlight.js") %>" nonce="<%= csp_nonce_placeholder %>" ></script>
<script nonce="<%= csp_nonce_placeholder %>">
document.querySelectorAll('pre code').forEach((el) => {
hljs.highlightElement(el);
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
post "/" => "shared_ai_conversations#create"
delete "/:share_key" => "shared_ai_conversations#destroy"
get "/:share_key" => "shared_ai_conversations#show"
get "/asset/:version/:name" => "shared_ai_conversations#asset"
get "/preview/:topic_id" => "shared_ai_conversations#preview"
end

Expand Down
4 changes: 4 additions & 0 deletions plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@

module ::DiscourseAi
PLUGIN_NAME = "discourse-ai"

def self.public_asset_path(name)
File.expand_path(File.join(__dir__, "public", name))
end
end

Rails.autoloaders.main.push_dir(File.join(__dir__, "lib"), namespace: ::DiscourseAi)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,30 @@ def share_error(key)
end
end

describe "GET asset" do
let(:helper) { Class.new { extend DiscourseAi::AiBot::SharedAiConversationsHelper } }

it "renders highlight js correctly" do
get helper.share_asset_url("highlight.js")

expect(response).to be_successful
expect(response.headers["Content-Type"]).to eq("application/javascript; charset=utf-8")

js = File.read(DiscourseAi.public_asset_path("ai-share/highlight.min.js"))
expect(response.body).to eq(js)
end

it "renders css correctly" do
get helper.share_asset_url("share.css")

expect(response).to be_successful
expect(response.headers["Content-Type"]).to eq("text/css; charset=utf-8")

css = File.read(DiscourseAi.public_asset_path("ai-share/share.css"))
expect(response.body).to eq(css)
end
end

describe "GET preview" do
it "denies preview from logged out users" do
get "#{path}/preview/#{user_pm_share.id}.json"
Expand Down