diff --git a/modules/wikis/app/models/wikis/page_link.rb b/modules/wikis/app/models/wikis/page_link.rb index c9af679a9ce0..e9e9a861471e 100644 --- a/modules/wikis/app/models/wikis/page_link.rb +++ b/modules/wikis/app/models/wikis/page_link.rb @@ -34,5 +34,7 @@ class PageLink < ApplicationRecord belongs_to :provider belongs_to :linkable, polymorphic: true + + def render_author? = false end end diff --git a/modules/wikis/app/models/wikis/relation_page_link.rb b/modules/wikis/app/models/wikis/relation_page_link.rb index e70377261bf4..4f22e3183288 100644 --- a/modules/wikis/app/models/wikis/relation_page_link.rb +++ b/modules/wikis/app/models/wikis/relation_page_link.rb @@ -31,5 +31,7 @@ module Wikis class RelationPageLink < PageLink belongs_to :author, class_name: "User" + + def render_author? = true end end diff --git a/modules/wikis/config/locales/en.yml b/modules/wikis/config/locales/en.yml index e5cea0fedf5a..a5e3a217ccec 100644 --- a/modules/wikis/config/locales/en.yml +++ b/modules/wikis/config/locales/en.yml @@ -22,6 +22,8 @@ en: one: Relation page link other: Relation page links wikis/xwiki_provider: XWiki provider + permission_manage_wiki_page_links: Manage Wiki Page Links + permission_view_wiki_page_links: View Wiki Page Links project_module_wiki_platforms: Wiki providers wikis: buttons: diff --git a/modules/wikis/lib/api/v3/page_links/page_link_representer.rb b/modules/wikis/lib/api/v3/page_links/page_link_representer.rb new file mode 100644 index 000000000000..a0c80d5f2a6a --- /dev/null +++ b/modules/wikis/lib/api/v3/page_links/page_link_representer.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module API + module V3 + module PageLinks + class PageLinkRepresenter < Decorators::Single + include Decorators::LinkedResource + include Decorators::DateProperty + include Caching::CachedRepresenter + + property :id + property :identifier + + date_time_property :created_at + date_time_property :updated_at + + # Title being the identifier is kind of a placeholder until we have actual page names + self_link(path: :wiki_page_link, title_getter: ->(*) { represented.identifier }) + + link :delete, cache_if: ->(*) { user_allowed_to_manage?(represented) } do + { + href: api_v3_paths.wiki_page_link(represented.id), + method: :delete + } + end + + link :author do + next unless represented.render_author? + + { + href: api_v3_paths.user(represented.author_id), + title: represented.author.name + } + end + + associated_resource :provider, v3_path: :wiki_provider + + # TODO: Make this truly polymorphic - @mereghost 2026-04-13 + associated_resource :linkable, + v3_path: :work_package, + representer: ::API::V3::WorkPackages::WorkPackageRepresenter, + skip_render: ->(*) { represented.linkable_id.nil? || represented.linkable_type != "WorkPackage" } + + def _type = represented.class.name.demodulize + + private + + def user_allowed_to_manage?(model) + if model.linkable.present? + current_user.allowed_in_project?(:manage_wiki_page_links, model.linkable.project) + else + current_user == model.author + end + end + end + end + end +end diff --git a/modules/wikis/lib/api/v3/providers/provider_representer.rb b/modules/wikis/lib/api/v3/providers/provider_representer.rb new file mode 100644 index 000000000000..4dfaee7065ba --- /dev/null +++ b/modules/wikis/lib/api/v3/providers/provider_representer.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module API + module V3 + module Providers + class ProviderRepresenter < Decorators::Single + include Decorators::LinkedResource + include Decorators::DateProperty + + property :id + property :name + + date_time_property :created_at + date_time_property :updated_at + + self_link(path: :wiki_provider) + end + end + end +end diff --git a/modules/wikis/lib/open_project/wikis/engine.rb b/modules/wikis/lib/open_project/wikis/engine.rb index c1bffc45166b..386a733b89b6 100644 --- a/modules/wikis/lib/open_project/wikis/engine.rb +++ b/modules/wikis/lib/open_project/wikis/engine.rb @@ -63,6 +63,20 @@ class Engine < ::Rails::Engine replace_principal_references "Wikis::PageLink" => %i[author_id] register "openproject-wikis", author_url: "https://openproject.org" do + project_module :work_package_tracking do + permission :view_wiki_page_links, + {}, + permissible_on: :project, + dependencies: %i[view_work_packages], + contract_actions: { wiki_page_links: %i[view] } + + permission :manage_wiki_page_links, + {}, + permissible_on: :project, + dependencies: %i[view_work_packages], + contract_actions: { wiki_page_links: %i[manage] } + end + menu :work_package_split_view, :wikis, { tab: :wikis }, @@ -81,5 +95,8 @@ class Engine < ::Rails::Engine caption: :project_module_wiki_platforms, icon: "browser" end + + add_api_path(:wiki_page_link) { |page_link_id| "#{root}/wiki_page_links/#{page_link_id}" } + add_api_path(:wiki_provider) { |provider_id| "#{root}/wiki_providers/#{provider_id}" } end end diff --git a/modules/wikis/spec/lib/api/v3/page_links/page_link_representer_spec.rb b/modules/wikis/spec/lib/api/v3/page_links/page_link_representer_spec.rb new file mode 100644 index 000000000000..d915b5247130 --- /dev/null +++ b/modules/wikis/spec/lib/api/v3/page_links/page_link_representer_spec.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" + +module API + module V3 + module PageLinks + RSpec.describe PageLinkRepresenter, :rendering do + include Utilities::PathHelper + + let(:inline_page_link) { build_stubbed(:inline_wiki_page_link) } + let(:relation_page_link) { build_stubbed(:relation_wiki_page_link) } + let(:current_user) { build_stubbed(:user) } + + let(:represented) { relation_page_link } + let(:project) { represented.linkable.project } + + let(:embed_links) { false } + let(:representer) { described_class.new(represented, current_user:, embed_links:) } + + subject(:resulting_json) { representer.to_json } + + describe "_links" do + describe "self" do + it_behaves_like "has a titled link" do + let(:link) { "self" } + let(:href) { "/api/v3/wiki_page_links/#{represented.id}" } + let(:title) { represented.identifier } + end + end + + describe "provider" do + it_behaves_like "has a titled link" do + let(:link) { "provider" } + let(:href) { "/api/v3/wiki_providers/#{represented.provider_id}" } + let(:title) { represented.provider.name } + end + end + + describe "delete" do + let(:permission) { :manage_wiki_page_links } + + let(:link) { "delete" } + let(:href) { "/api/v3/wiki_page_links/#{represented.id}" } + let(:method) { :delete } + + it_behaves_like "has an untitled action link" + + context "when there is no associated linkable" do + before { represented.linkable = nil } + + it_behaves_like "has no link" + + context "and the current user is creator of the file link" do + let(:current_user) { represented.author } + + it { is_expected.to have_json_path("_links/#{link}") } + end + end + end + + describe "author" do + context "when the page link is an InlinePageLink" do + let(:represented) { inline_page_link } + + it "does not render the author link" do + expect(resulting_json).not_to have_json_path("author") + end + end + + it_behaves_like "has a titled link" do + let(:link) { "author" } + let(:href) { "/api/v3/users/#{represented.author_id}" } + let(:title) { represented.author.name } + end + end + + describe "linkable" do + it_behaves_like "has a titled link" do + let(:link) { "linkable" } + let(:href) { "/api/v3/work_packages/#{represented.linkable_id}" } + let(:title) { represented.linkable.name } + end + end + end + + describe "properties" do + describe "_type" do + context "when InlinePageLink" do + let(:represented) { inline_page_link } + + it_behaves_like "property", :_type do + let(:value) { "InlinePageLink" } + end + end + + context "when RelationPageLink" do + it_behaves_like "property", :_type do + let(:value) { "RelationPageLink" } + end + end + end + + it_behaves_like "property", :identifier do + let(:value) { represented.identifier } + end + + it_behaves_like "datetime property", :createdAt do + let(:value) { represented.created_at } + end + + it_behaves_like "datetime property", :updatedAt do + let(:value) { represented.updated_at } + end + end + end + end + end +end diff --git a/modules/wikis/spec/lib/api/v3/providers/provider_representer_spec.rb b/modules/wikis/spec/lib/api/v3/providers/provider_representer_spec.rb new file mode 100644 index 000000000000..602bae7b9635 --- /dev/null +++ b/modules/wikis/spec/lib/api/v3/providers/provider_representer_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" + +module API + module V3 + module Providers + RSpec.describe ProviderRepresenter, :rendering do + let(:xwiki_provider) { build_stubbed(:xwiki_provider) } + let(:internal_provider) { build_stubbed(:internal_wiki_provider) } + let(:embed_links) { false } + let(:current_user) { build_stubbed(:user) } + + let(:represented) { xwiki_provider } + let(:representer) { described_class.new(represented, current_user:, embed_links:) } + + subject(:rendered) { representer.to_json } + + describe "_links" do + describe "self" do + it_behaves_like "has a titled link" do + let(:link) { "self" } + let(:href) { "/api/v3/wiki_providers/#{represented.id}" } + let(:title) { represented.name } + end + end + end + + describe "properties" do + it_behaves_like "datetime property", :createdAt do + let(:value) { represented.created_at } + end + + it_behaves_like "datetime property", :updatedAt do + let(:value) { represented.updated_at } + end + end + end + end + end +end