Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 86cf4cc

Browse files
authored
FIX: automatically bust cache for share ai assets (#942)
* FIX: automatically bust cache for share ai assets CDNs can be configured to strip query params in Discourse hosting. This is generally safe, but in this case we had no way of busting the cache using the path. New design properly caches and properly breaks busts the cache if asset changes so we don't need to worry about versions * one day I will set up conditional lint on save :)
1 parent 2c78961 commit 86cf4cc

File tree

6 files changed

+79
-9
lines changed

6 files changed

+79
-9
lines changed

app/controllers/discourse_ai/ai_bot/shared_ai_conversations_controller.rb

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ class SharedAiConversationsController < ::ApplicationController
77
requires_login only: %i[create update destroy]
88
before_action :require_site_settings!
99

10-
skip_before_action :preload_json, :check_xhr, only: %i[show]
10+
skip_before_action :preload_json, :check_xhr, only: %i[show asset]
11+
skip_before_action :verify_authenticity_token, only: ["asset"]
1112

1213
def create
1314
ensure_allowed_create!
@@ -50,6 +51,30 @@ def show
5051
end
5152
end
5253

54+
def asset
55+
no_cookies
56+
57+
name = params[:name]
58+
path, content_type =
59+
if name == "share"
60+
%w[share.css text/css]
61+
elsif name == "highlight"
62+
%w[highlight.min.js application/javascript]
63+
else
64+
raise Discourse::NotFound
65+
end
66+
67+
content = File.read(DiscourseAi.public_asset_path("ai-share/#{path}"))
68+
69+
# note, path contains a ":version" which automatically busts the cache
70+
# based on file content, so this is safe
71+
response.headers["Last-Modified"] = 10.years.ago.httpdate
72+
response.headers["Content-Length"] = content.bytesize.to_s
73+
immutable_for 1.year
74+
75+
render plain: content, disposition: :nil, content_type: content_type
76+
end
77+
5378
def preview
5479
ensure_allowed_preview!
5580
data = SharedAiConversation.build_conversation_data(@topic, include_usernames: true)

app/helpers/discourse_ai/ai_bot/shared_ai_conversations_helper.rb

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,29 @@
22
module DiscourseAi
33
module AiBot
44
module SharedAiConversationsHelper
5-
# bump up version when assets change
6-
# long term we may want to change this cause it is hard to remember
7-
# to bump versions, but for now this does the job
8-
VERSION = "1"
5+
# keeping it here for caching
6+
def self.share_asset_url(asset_name)
7+
if !%w[share.css highlight.js].include?(asset_name)
8+
raise StandardError, "unknown asset type #{asset_name}"
9+
end
910

10-
def share_asset_url(short_path)
11-
::UrlHelper.absolute("/plugins/discourse-ai/ai-share/#{short_path}?#{VERSION}")
11+
@urls ||= {}
12+
url = @urls[asset_name]
13+
return url if url
14+
15+
path = asset_name
16+
path = "highlight.min.js" if asset_name == "highlight.js"
17+
18+
content = File.read(DiscourseAi.public_asset_path("ai-share/#{path}"))
19+
sha1 = Digest::SHA1.hexdigest(content)
20+
21+
url = "/discourse-ai/ai-bot/shared-ai-conversations/asset/#{sha1}/#{asset_name}"
22+
23+
@urls[asset_name] = GlobalPath.cdn_path(url)
24+
end
25+
26+
def share_asset_url(asset_name)
27+
DiscourseAi::AiBot::SharedAiConversationsHelper.share_asset_url(asset_name)
1228
end
1329
end
1430
end

app/views/discourse_ai/ai_bot/shared_ai_conversations/show.html.erb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<meta name="twitter:title" content="<%= I18n.t("discourse_ai.share_ai.title", title: @shared_conversation.title, site_name: SiteSetting.title) %>">
1111
<meta name="twitter:description" content="<%= @shared_conversation.formatted_excerpt %>">
1212
<meta name="viewport" content="width=device-width">
13-
<link rel="stylesheet" href="<%= ::UrlHelper.absolute("/plugins/discourse-ai/ai-share/share.css?v=1") %>">
13+
<link rel="stylesheet" href="<%= share_asset_url("share.css") %>">
1414
</head>
1515
<body>
1616
<header class="site-header">
@@ -54,7 +54,7 @@
5454
</article>
5555
<% end %>
5656
</section>
57-
<script src="<%= share_asset_url("highlight.min.js") %>" nonce="<%= csp_nonce_placeholder %>" ></script>
57+
<script src="<%= share_asset_url("highlight.js") %>" nonce="<%= csp_nonce_placeholder %>" ></script>
5858
<script nonce="<%= csp_nonce_placeholder %>">
5959
document.querySelectorAll('pre code').forEach((el) => {
6060
hljs.highlightElement(el);

config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
post "/" => "shared_ai_conversations#create"
3131
delete "/:share_key" => "shared_ai_conversations#destroy"
3232
get "/:share_key" => "shared_ai_conversations#show"
33+
get "/asset/:version/:name" => "shared_ai_conversations#asset"
3334
get "/preview/:topic_id" => "shared_ai_conversations#preview"
3435
end
3536

plugin.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343

4444
module ::DiscourseAi
4545
PLUGIN_NAME = "discourse-ai"
46+
47+
def self.public_asset_path(name)
48+
File.expand_path(File.join(__dir__, "public", name))
49+
end
4650
end
4751

4852
Rails.autoloaders.main.push_dir(File.join(__dir__, "lib"), namespace: ::DiscourseAi)

spec/requests/ai_bot/shared_ai_conversations_spec.rb renamed to spec/requests/ai_bot/shared_ai_conversations_controller_spec.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,30 @@ def share_error(key)
294294
end
295295
end
296296

297+
describe "GET asset" do
298+
let(:helper) { Class.new { extend DiscourseAi::AiBot::SharedAiConversationsHelper } }
299+
300+
it "renders highlight js correctly" do
301+
get helper.share_asset_url("highlight.js")
302+
303+
expect(response).to be_successful
304+
expect(response.headers["Content-Type"]).to eq("application/javascript; charset=utf-8")
305+
306+
js = File.read(DiscourseAi.public_asset_path("ai-share/highlight.min.js"))
307+
expect(response.body).to eq(js)
308+
end
309+
310+
it "renders css correctly" do
311+
get helper.share_asset_url("share.css")
312+
313+
expect(response).to be_successful
314+
expect(response.headers["Content-Type"]).to eq("text/css; charset=utf-8")
315+
316+
css = File.read(DiscourseAi.public_asset_path("ai-share/share.css"))
317+
expect(response.body).to eq(css)
318+
end
319+
end
320+
297321
describe "GET preview" do
298322
it "denies preview from logged out users" do
299323
get "#{path}/preview/#{user_pm_share.id}.json"

0 commit comments

Comments
 (0)