mirror of
https://github.com/discourse/discourse.git
synced 2026-08-08 12:08:12 -05:00
FEATURE: CRUD access for ai-artifacts (admin only) (#34193)
- GET /admin/plugins/discourse-ai/ai-artifacts.json — list (paginated, per_page capped at 100) - GET /admin/plugins/discourse-ai/ai-artifacts/:id.json — show - POST /admin/plugins/discourse-ai/ai-artifacts.json — create - PUT /admin/plugins/discourse-ai/ai-artifacts/:id.json — update - DELETE /admin/plugins/discourse-ai/ai-artifacts/:id.json — destroy Admin-only, JSON responses.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Admin
|
||||
class AiArtifactsController < ::Admin::AdminController
|
||||
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
||||
|
||||
PER_PAGE_MAX = 100
|
||||
|
||||
before_action :find_artifact, only: %i[show update destroy]
|
||||
|
||||
def index
|
||||
page = params[:page].to_i
|
||||
page = 1 if page < 1
|
||||
per_page = params[:per_page].to_i
|
||||
per_page = 50 if per_page < 1
|
||||
per_page = PER_PAGE_MAX if per_page > PER_PAGE_MAX
|
||||
|
||||
base = AiArtifact.all
|
||||
total = base.count
|
||||
|
||||
artifacts = base.order(created_at: :desc).offset((page - 1) * per_page).limit(per_page)
|
||||
|
||||
render json: {
|
||||
artifacts: serialize_data(artifacts, AiArtifactSerializer),
|
||||
meta: {
|
||||
total: total,
|
||||
page: page,
|
||||
per_page: per_page,
|
||||
has_more: total > page * per_page,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
def show
|
||||
render_serialized(@artifact, AiArtifactSerializer)
|
||||
end
|
||||
|
||||
def create
|
||||
artifact = AiArtifact.new(artifact_params)
|
||||
|
||||
if artifact.save
|
||||
render_serialized(artifact, AiArtifactSerializer, status: :created)
|
||||
else
|
||||
render_json_error artifact
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @artifact.update(artifact_params)
|
||||
render_serialized(@artifact, AiArtifactSerializer)
|
||||
else
|
||||
render_json_error @artifact
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @artifact.destroy
|
||||
head :no_content
|
||||
else
|
||||
render_json_error @artifact
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_artifact
|
||||
@artifact = AiArtifact.find(params[:id])
|
||||
end
|
||||
|
||||
def artifact_params
|
||||
params.require(:ai_artifact).permit(
|
||||
:user_id,
|
||||
:post_id,
|
||||
:name,
|
||||
:html,
|
||||
:css,
|
||||
:js,
|
||||
metadata: {
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AiArtifactSerializer < ApplicationSerializer
|
||||
attributes :id, :user_id, :post_id, :name, :html, :css, :js, :metadata, :created_at, :updated_at
|
||||
|
||||
self.root = "ai_artifact"
|
||||
end
|
||||
@@ -70,6 +70,14 @@ Discourse::Application.routes.draw do
|
||||
:constraints => StaffConstraint.new
|
||||
|
||||
scope "/admin/plugins/discourse-ai", constraints: AdminConstraint.new do
|
||||
resources :ai_artifacts,
|
||||
only: %i[index show create update destroy],
|
||||
path: "ai-artifacts",
|
||||
controller: "discourse_ai/admin/ai_artifacts",
|
||||
defaults: {
|
||||
format: :json,
|
||||
}
|
||||
|
||||
resources :ai_personas,
|
||||
only: %i[index new create edit update destroy],
|
||||
path: "ai-personas",
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe DiscourseAi::Admin::AiArtifactsController, type: :request do
|
||||
fab!(:admin)
|
||||
fab!(:user)
|
||||
fab!(:target_post) { Fabricate(:post) }
|
||||
|
||||
before do
|
||||
enable_current_plugin
|
||||
sign_in(admin)
|
||||
end
|
||||
|
||||
describe "GET #index" do
|
||||
fab!(:artifacts) { Fabricate.times(3, :ai_artifact) }
|
||||
|
||||
it "returns paginated list with meta" do
|
||||
get "/admin/plugins/discourse-ai/ai-artifacts.json", params: { page: 1, per_page: 2 }
|
||||
|
||||
expect(response).to be_successful
|
||||
json = response.parsed_body
|
||||
|
||||
expect(json["artifacts"]).to be_an(Array)
|
||||
expect(json["artifacts"].length).to eq(2)
|
||||
expect(json["meta"]).to include("total", "page", "per_page", "has_more")
|
||||
expect(json["meta"]["total"]).to eq(AiArtifact.count)
|
||||
expect(json["meta"]["page"]).to eq(1)
|
||||
expect(json["meta"]["per_page"]).to eq(2)
|
||||
expect(json["meta"]["has_more"]).to eq(true)
|
||||
end
|
||||
|
||||
it "clamps per_page to max" do
|
||||
get "/admin/plugins/discourse-ai/ai-artifacts.json", params: { per_page: 5000 }
|
||||
expect(response).to be_successful
|
||||
expect(response.parsed_body.dig("meta", "per_page")).to eq(100)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #show" do
|
||||
fab!(:artifact) { Fabricate(:ai_artifact) }
|
||||
|
||||
it "returns a single artifact" do
|
||||
get "/admin/plugins/discourse-ai/ai-artifacts/#{artifact.id}.json"
|
||||
|
||||
expect(response).to be_successful
|
||||
json = response.parsed_body
|
||||
expect(json.dig("ai_artifact", "id")).to eq(artifact.id)
|
||||
expect(json.dig("ai_artifact", "name")).to eq(artifact.name)
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST #create" do
|
||||
it "creates an artifact" do
|
||||
params = {
|
||||
ai_artifact: {
|
||||
user_id: admin.id,
|
||||
post_id: target_post.id,
|
||||
name: "Admin Created",
|
||||
html: "<div>hello</div>",
|
||||
css: ".x { color: red; }",
|
||||
js: "console.log('x')",
|
||||
metadata: {
|
||||
public: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expect {
|
||||
post "/admin/plugins/discourse-ai/ai-artifacts.json",
|
||||
params: params.to_json,
|
||||
headers: {
|
||||
"CONTENT_TYPE" => "application/json",
|
||||
}
|
||||
}.to change(AiArtifact, :count).by(1)
|
||||
|
||||
expect(response).to have_http_status(:created)
|
||||
expect(response.parsed_body.dig("ai_artifact", "name")).to eq("Admin Created")
|
||||
end
|
||||
end
|
||||
|
||||
describe "PUT #update" do
|
||||
fab!(:artifact) { Fabricate(:ai_artifact) }
|
||||
|
||||
it "updates fields" do
|
||||
put "/admin/plugins/discourse-ai/ai-artifacts/#{artifact.id}.json",
|
||||
params: { ai_artifact: { name: "Updated Name", metadata: { public: true } } }.to_json,
|
||||
headers: {
|
||||
"CONTENT_TYPE" => "application/json",
|
||||
}
|
||||
|
||||
expect(response).to be_successful
|
||||
artifact.reload
|
||||
expect(artifact.name).to eq("Updated Name")
|
||||
expect(artifact.metadata["public"]).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE #destroy" do
|
||||
fab!(:artifact) { Fabricate(:ai_artifact) }
|
||||
|
||||
it "removes the artifact" do
|
||||
expect { delete "/admin/plugins/discourse-ai/ai-artifacts/#{artifact.id}.json" }.to change(
|
||||
AiArtifact,
|
||||
:count,
|
||||
).by(-1)
|
||||
|
||||
expect(response).to have_http_status(:no_content)
|
||||
end
|
||||
end
|
||||
|
||||
context "when not admin" do
|
||||
before { sign_in(user) }
|
||||
|
||||
it "blocks access" do
|
||||
get "/admin/plugins/discourse-ai/ai-artifacts.json"
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user