Commit 470c68c9 authored by Ash McKenzie's avatar Ash McKenzie

Merge branch 'ajk-group-wiki-attachments' into 'master'

[Group Wiki] Update attachment API to support Group Level Wiki

Closes #212199

See merge request gitlab-org/gitlab!34232
parents ce38822c 2a742324
# Wikis API
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/212199) in GitLab 13.2.
Available only in APIv4.
## List wiki pages
List all wiki pages for a given group.
```plaintext
GET /groups/:id/wikis
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `with_content` | boolean | no | Include pages' content |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/wikis?with_content=1"
```
Example response:
```json
[
{
"content" : "Here is an instruction how to deploy this project.",
"format" : "markdown",
"slug" : "deploy",
"title" : "deploy"
},
{
"content" : "Our development process is described here.",
"format" : "markdown",
"slug" : "development",
"title" : "development"
},{
"content" : "* [Deploy](deploy)\n* [Development](development)",
"format" : "markdown",
"slug" : "home",
"title" : "home"
}
]
```
## Get a wiki page
Get a wiki page for a given group.
```plaintext
GET /groups/:id/wikis/:slug
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `slug` | string | yes | The slug (a unique string) of the wiki page |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/wikis/home"
```
Example response:
```json
{
"content" : "home page",
"format" : "markdown",
"slug" : "home",
"title" : "home"
}
```
## Create a new wiki page
Create a new wiki page for the given repository with the given title, slug, and content.
```plaintext
POST /projects/:id/wikis
```
| Attribute | Type | Required | Description |
| ------------- | ------- | -------- | ---------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `content` | string | yes | The content of the wiki page |
| `title` | string | yes | The title of the wiki page |
| `format` | string | no | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, `asciidoc` and `org` |
```shell
curl --data "format=rdoc&title=Hello&content=Hello world" \
--header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/groups/1/wikis"
```
Example response:
```json
{
"content" : "Hello world",
"format" : "markdown",
"slug" : "Hello",
"title" : "Hello"
}
```
## Edit an existing wiki page
Update an existing wiki page. At least one parameter is required to update the wiki page.
```plaintext
PUT /groups/:id/wikis/:slug
```
| Attribute | Type | Required | Description |
| --------------- | ------- | --------------------------------- | ------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `content` | string | yes if `title` is not provided | The content of the wiki page |
| `title` | string | yes if `content` is not provided | The title of the wiki page |
| `format` | string | no | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, `asciidoc` and `org` |
| `slug` | string | yes | The slug (a unique identifier) of the wiki page |
```shell
curl --request PUT --data "format=rdoc&content=documentation&title=Docs" \
--header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/groups/1/wikis/foo"
```
Example response:
```json
{
"content" : "documentation",
"format" : "markdown",
"slug" : "Docs",
"title" : "Docs"
}
```
## Delete a wiki page
Delete a wiki page with a given slug.
```plaintext
DELETE /groups/:id/wikis/:slug
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `slug` | string | yes | The slug (a unique identifier) of the wiki page |
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/wikis/foo"
```
On success the HTTP status code is `204` and no JSON response is expected.
## Upload an attachment to the wiki repository
Upload a file to the attachment folder inside the wiki's repository. The
attachment folder is the `uploads` folder.
```plaintext
POST /groups/:id/wikis/attachments
```
| Attribute | Type | Required | Description |
| ------------- | ------- | -------- | ---------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `file` | string | yes | The attachment to be uploaded |
| `branch` | string | no | The name of the branch. Defaults to the wiki repository default branch |
To upload a file from your filesystem, use the `--form` argument. This causes
cURL to post data using the header `Content-Type: multipart/form-data`.
The `file=` parameter must point to a file on your filesystem and be preceded
by `@`. For example:
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --form "file=@dk.png" "https://gitlab.example.com/api/v4/groups/1/wikis/attachments"
```
Example response:
```json
{
"file_name" : "dk.png",
"file_path" : "uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png",
"branch" : "master",
"link" : {
"url" : "uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png",
"markdown" : "![dk](uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png)"
}
}
```
---
title: Add group wiki REST API
merge_request: 34232
author:
type: added
# frozen_string_literal: true
module EE
module API
module Helpers
module WikisHelpers
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
class_methods do
def wiki_resource_kinds
[:groups, *super]
end
end
override :find_container
def find_container(kind)
return user_group if kind == :groups
super
end
end
end
end
end
This diff is collapsed.
...@@ -79,12 +79,6 @@ module API ...@@ -79,12 +79,6 @@ module API
@project ||= find_project!(params[:id]) @project ||= find_project!(params[:id])
end end
def wiki_page
page = ProjectWiki.new(user_project, current_user).find_page(params[:slug])
page || not_found!('Wiki Page')
end
def available_labels_for(label_parent, include_ancestor_groups: true) def available_labels_for(label_parent, include_ancestor_groups: true)
search_params = { include_ancestor_groups: include_ancestor_groups } search_params = { include_ancestor_groups: include_ancestor_groups }
......
# frozen_string_literal: true
module API
module Helpers
module WikisHelpers
def self.wiki_resource_kinds
[:projects]
end
def find_container(kind)
return user_project if kind == :projects
raise "Unknown wiki container #{kind}"
end
def wiki_page
Wiki.for_container(container, current_user).find_page(params[:slug]) || not_found!('Wiki Page')
end
def commit_params(attrs)
base_params = { branch_name: attrs[:branch] }
file_details = case attrs[:file]
when Hash # legacy format: TODO remove when we drop support for non accelerated uploads
{ file_name: attrs[:file][:filename], file_content: attrs[:file][:tempfile].read }
else
{ file_name: attrs[:file].original_filename, file_content: attrs[:file].read }
end
base_params.merge(file_details)
end
end
end
end
API::Helpers::WikisHelpers.prepend_if_ee('EE::API::Helpers::WikisHelpers')
...@@ -2,24 +2,10 @@ ...@@ -2,24 +2,10 @@
module API module API
class Wikis < Grape::API class Wikis < Grape::API
helpers ::API::Helpers::WikisHelpers
helpers do helpers do
def commit_params(attrs) attr_reader :container
# In order to avoid service disruption this can work with an old workhorse without the acceleration
# the first branch of this if must be removed when we drop support for non accelerated uploads
if attrs[:file].is_a?(Hash)
{
file_name: attrs[:file][:filename],
file_content: attrs[:file][:tempfile].read,
branch_name: attrs[:branch]
}
else
{
file_name: attrs[:file].original_filename,
file_content: attrs[:file].read,
branch_name: attrs[:branch]
}
end
end
params :common_wiki_page_params do params :common_wiki_page_params do
optional :format, optional :format,
...@@ -32,108 +18,118 @@ module API ...@@ -32,108 +18,118 @@ module API
WIKI_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(slug: API::NO_SLASH_URL_PART_REGEX) WIKI_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(slug: API::NO_SLASH_URL_PART_REGEX)
resource :projects, requirements: WIKI_ENDPOINT_REQUIREMENTS do ::API::Helpers::WikisHelpers.wiki_resource_kinds.each do |container_resource|
desc 'Get a list of wiki pages' do resource container_resource, requirements: WIKI_ENDPOINT_REQUIREMENTS do
success Entities::WikiPageBasic after_validation do
end @container = Gitlab::Lazy.new { find_container(container_resource) }
params do end
optional :with_content, type: Boolean, default: false, desc: "Include pages' content"
end
get ':id/wikis' do
authorize! :read_wiki, user_project
entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic
present user_project.wiki.list_pages(load_content: params[:with_content]), with: entity desc 'Get a list of wiki pages' do
end success Entities::WikiPageBasic
end
params do
optional :with_content, type: Boolean, default: false, desc: "Include pages' content"
end
get ':id/wikis' do
authorize! :read_wiki, container
desc 'Get a wiki page' do entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic
success Entities::WikiPage
end
params do
requires :slug, type: String, desc: 'The slug of a wiki page'
end
get ':id/wikis/:slug' do
authorize! :read_wiki, user_project
present wiki_page, with: Entities::WikiPage present container.wiki.list_pages(load_content: params[:with_content]), with: entity
end end
desc 'Create a wiki page' do desc 'Get a wiki page' do
success Entities::WikiPage success Entities::WikiPage
end end
params do params do
requires :title, type: String, desc: 'Title of a wiki page' requires :slug, type: String, desc: 'The slug of a wiki page'
requires :content, type: String, desc: 'Content of a wiki page' end
use :common_wiki_page_params get ':id/wikis/:slug' do
end authorize! :read_wiki, container
post ':id/wikis' do
authorize! :create_wiki, user_project
page = WikiPages::CreateService.new(container: user_project, current_user: current_user, params: params).execute present wiki_page, with: Entities::WikiPage
end
if page.valid? desc 'Create a wiki page' do
present page, with: Entities::WikiPage success Entities::WikiPage
else
render_validation_error!(page)
end end
end params do
requires :title, type: String, desc: 'Title of a wiki page'
requires :content, type: String, desc: 'Content of a wiki page'
use :common_wiki_page_params
end
post ':id/wikis' do
authorize! :create_wiki, container
desc 'Update a wiki page' do page = WikiPages::CreateService.new(container: container, current_user: current_user, params: params).execute
success Entities::WikiPage
end
params do
optional :title, type: String, desc: 'Title of a wiki page'
optional :content, type: String, desc: 'Content of a wiki page'
use :common_wiki_page_params
at_least_one_of :content, :title, :format
end
put ':id/wikis/:slug' do
authorize! :create_wiki, user_project
page = WikiPages::UpdateService.new(container: user_project, current_user: current_user, params: params).execute(wiki_page) if page.valid?
present page, with: Entities::WikiPage
else
render_validation_error!(page)
end
end
if page.valid? desc 'Update a wiki page' do
present page, with: Entities::WikiPage success Entities::WikiPage
else end
render_validation_error!(page) params do
optional :title, type: String, desc: 'Title of a wiki page'
optional :content, type: String, desc: 'Content of a wiki page'
use :common_wiki_page_params
at_least_one_of :content, :title, :format
end
put ':id/wikis/:slug' do
authorize! :create_wiki, container
page = WikiPages::UpdateService
.new(container: container, current_user: current_user, params: params)
.execute(wiki_page)
if page.valid?
present page, with: Entities::WikiPage
else
render_validation_error!(page)
end
end end
end
desc 'Delete a wiki page' desc 'Delete a wiki page'
params do params do
requires :slug, type: String, desc: 'The slug of a wiki page' requires :slug, type: String, desc: 'The slug of a wiki page'
end end
delete ':id/wikis/:slug' do delete ':id/wikis/:slug' do
authorize! :admin_wiki, user_project authorize! :admin_wiki, container
WikiPages::DestroyService.new(container: user_project, current_user: current_user).execute(wiki_page) WikiPages::DestroyService
.new(container: container, current_user: current_user)
.execute(wiki_page)
no_content! no_content!
end end
desc 'Upload an attachment to the wiki repository' do desc 'Upload an attachment to the wiki repository' do
detail 'This feature was introduced in GitLab 11.3.' detail 'This feature was introduced in GitLab 11.3.'
success Entities::WikiAttachment success Entities::WikiAttachment
end end
params do params do
requires :file, types: [::API::Validations::Types::SafeFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded' requires :file, types: [::API::Validations::Types::SafeFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded'
optional :branch, type: String, desc: 'The name of the branch' optional :branch, type: String, desc: 'The name of the branch'
end end
post ":id/wikis/attachments" do post ":id/wikis/attachments" do
authorize! :create_wiki, user_project authorize! :create_wiki, container
result = ::Wikis::CreateAttachmentService.new( result = ::Wikis::CreateAttachmentService.new(
container: user_project, container: container,
current_user: current_user, current_user: current_user,
params: commit_params(declared_params(include_missing: false)) params: commit_params(declared_params(include_missing: false))
).execute ).execute
if result[:status] == :success if result[:status] == :success
status(201) status(201)
present OpenStruct.new(result[:result]), with: Entities::WikiAttachment present OpenStruct.new(result[:result]), with: Entities::WikiAttachment
else else
render_api_error!(result[:message], 400) render_api_error!(result[:message], 400)
end
end end
end end
end end
......
This diff is collapsed.
# frozen_string_literal: true
RSpec.shared_examples_for 'wikis API returns list of wiki pages' do
context 'when wiki has pages' do
let!(:pages) do
[create(:wiki_page, wiki: wiki, title: 'page1', content: 'content of page1'),
create(:wiki_page, wiki: wiki, title: 'page2.with.dot', content: 'content of page2')]
end
it 'returns the list of wiki pages without content' do
get api(url, user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.size).to eq(2)
json_response.each_with_index do |page, index|
expect(page.keys).to match_array(expected_keys_without_content)
expect(page['slug']).to eq(pages[index].slug)
expect(page['title']).to eq(pages[index].title)
end
end
it 'returns the list of wiki pages with content' do
get api(url, user), params: { with_content: 1 }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.size).to eq(2)
json_response.each_with_index do |page, index|
expect(page.keys).to match_array(expected_keys_with_content)
expect(page['content']).to eq(pages[index].content)
expect(page['slug']).to eq(pages[index].slug)
expect(page['title']).to eq(pages[index].title)
end
end
end
it 'return the empty list of wiki pages' do
get api(url, user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.size).to eq(0)
end
end
RSpec.shared_examples_for 'wikis API returns wiki page' do
it 'returns the wiki page' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.size).to eq(4)
expect(json_response.keys).to match_array(expected_keys_with_content)
expect(json_response['content']).to eq(page.content)
expect(json_response['slug']).to eq(page.slug)
expect(json_response['title']).to eq(page.title)
end
end
RSpec.shared_examples_for 'wikis API creates wiki page' do
it 'creates the wiki page' do
post(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(:created)
expect(json_response.size).to eq(4)
expect(json_response.keys).to match_array(expected_keys_with_content)
expect(json_response['content']).to eq(payload[:content])
expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
expect(json_response['title']).to eq(payload[:title])
expect(json_response['rdoc']).to eq(payload[:rdoc])
end
[:title, :content].each do |part|
it "responds with validation error on empty #{part}" do
payload.delete(part)
post(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response.size).to eq(1)
expect(json_response['error']).to eq("#{part} is missing")
end
end
end
RSpec.shared_examples_for 'wikis API updates wiki page' do
it 'updates the wiki page' do
put(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.size).to eq(4)
expect(json_response.keys).to match_array(expected_keys_with_content)
expect(json_response['content']).to eq(payload[:content])
expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
expect(json_response['title']).to eq(payload[:title])
end
[:title, :content, :format].each do |part|
it "updates with wiki with missing #{part}" do
payload.delete(part)
put(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(:ok)
end
end
end
RSpec.shared_examples_for 'wiki API 403 Forbidden' do
it 'returns 403 Forbidden' do
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response.size).to eq(1)
expect(json_response['message']).to eq('403 Forbidden')
end
end
RSpec.shared_examples_for 'wiki API 404 Wiki Page Not Found' do
it 'returns 404 Wiki Page Not Found' do
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response.size).to eq(1)
expect(json_response['message']).to eq('404 Wiki Page Not Found')
end
end
RSpec.shared_examples_for 'wiki API 404 Not Found' do |what|
it "returns 404 #{what} Not Found" do
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response.size).to eq(1)
expect(json_response['message']).to eq("404 #{what} Not Found")
end
end
RSpec.shared_examples_for 'wiki API 204 No Content' do
it 'returns 204 No Content' do
expect(response).to have_gitlab_http_status(:no_content)
end
end
RSpec.shared_examples_for 'wiki API uploads wiki attachment' do
it 'pushes attachment to the wiki repository' do
allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
workhorse_post_with_file(api(url, user), file_key: :file, params: payload)
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to eq result_hash.deep_stringify_keys
end
it 'responds with validation error on empty file' do
payload.delete(:file)
post(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response.size).to eq(1)
expect(json_response['error']).to eq('file is missing')
end
it 'responds with validation error on invalid temp file' do
payload[:file] = { tempfile: '/etc/hosts' }
post(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response.size).to eq(1)
expect(json_response['error']).to eq('file is invalid')
end
it 'is backward compatible with regular multipart uploads' do
allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
post(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(:created)
expect(json_response).to eq result_hash.deep_stringify_keys
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment