mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-03-06 09:20:14 +00:00
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 :)
This commit is contained in:
parent
2c78961bed
commit
86cf4ccba7
@ -7,7 +7,8 @@ module DiscourseAi
|
|||||||
requires_login only: %i[create update destroy]
|
requires_login only: %i[create update destroy]
|
||||||
before_action :require_site_settings!
|
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
|
def create
|
||||||
ensure_allowed_create!
|
ensure_allowed_create!
|
||||||
@ -50,6 +51,30 @@ module DiscourseAi
|
|||||||
end
|
end
|
||||||
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
|
def preview
|
||||||
ensure_allowed_preview!
|
ensure_allowed_preview!
|
||||||
data = SharedAiConversation.build_conversation_data(@topic, include_usernames: true)
|
data = SharedAiConversation.build_conversation_data(@topic, include_usernames: true)
|
||||||
|
@ -2,13 +2,29 @@
|
|||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module AiBot
|
module AiBot
|
||||||
module SharedAiConversationsHelper
|
module SharedAiConversationsHelper
|
||||||
# bump up version when assets change
|
# keeping it here for caching
|
||||||
# long term we may want to change this cause it is hard to remember
|
def self.share_asset_url(asset_name)
|
||||||
# to bump versions, but for now this does the job
|
if !%w[share.css highlight.js].include?(asset_name)
|
||||||
VERSION = "1"
|
raise StandardError, "unknown asset type #{asset_name}"
|
||||||
|
end
|
||||||
|
|
||||||
def share_asset_url(short_path)
|
@urls ||= {}
|
||||||
::UrlHelper.absolute("/plugins/discourse-ai/ai-share/#{short_path}?#{VERSION}")
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -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: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="twitter:description" content="<%= @shared_conversation.formatted_excerpt %>">
|
||||||
<meta name="viewport" content="width=device-width">
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="site-header">
|
<header class="site-header">
|
||||||
@ -54,7 +54,7 @@
|
|||||||
</article>
|
</article>
|
||||||
<% end %>
|
<% end %>
|
||||||
</section>
|
</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 %>">
|
<script nonce="<%= csp_nonce_placeholder %>">
|
||||||
document.querySelectorAll('pre code').forEach((el) => {
|
document.querySelectorAll('pre code').forEach((el) => {
|
||||||
hljs.highlightElement(el);
|
hljs.highlightElement(el);
|
||||||
|
@ -30,6 +30,7 @@ DiscourseAi::Engine.routes.draw do
|
|||||||
post "/" => "shared_ai_conversations#create"
|
post "/" => "shared_ai_conversations#create"
|
||||||
delete "/:share_key" => "shared_ai_conversations#destroy"
|
delete "/:share_key" => "shared_ai_conversations#destroy"
|
||||||
get "/:share_key" => "shared_ai_conversations#show"
|
get "/:share_key" => "shared_ai_conversations#show"
|
||||||
|
get "/asset/:version/:name" => "shared_ai_conversations#asset"
|
||||||
get "/preview/:topic_id" => "shared_ai_conversations#preview"
|
get "/preview/:topic_id" => "shared_ai_conversations#preview"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -43,6 +43,10 @@ register_asset "stylesheets/modules/ai-bot/common/ai-artifact.scss"
|
|||||||
|
|
||||||
module ::DiscourseAi
|
module ::DiscourseAi
|
||||||
PLUGIN_NAME = "discourse-ai"
|
PLUGIN_NAME = "discourse-ai"
|
||||||
|
|
||||||
|
def self.public_asset_path(name)
|
||||||
|
File.expand_path(File.join(__dir__, "public", name))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Rails.autoloaders.main.push_dir(File.join(__dir__, "lib"), namespace: ::DiscourseAi)
|
Rails.autoloaders.main.push_dir(File.join(__dir__, "lib"), namespace: ::DiscourseAi)
|
||||||
|
@ -294,6 +294,30 @@ RSpec.describe DiscourseAi::AiBot::SharedAiConversationsController do
|
|||||||
end
|
end
|
||||||
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
|
describe "GET preview" do
|
||||||
it "denies preview from logged out users" do
|
it "denies preview from logged out users" do
|
||||||
get "#{path}/preview/#{user_pm_share.id}.json"
|
get "#{path}/preview/#{user_pm_share.id}.json"
|
Loading…
x
Reference in New Issue
Block a user