Merge branch 'wiki_api' into 'master'

Add API support for wiki pages

Closes #12747

See merge request !13372
This commit is contained in:
Grzegorz Bizon 2017-09-07 10:38:03 +00:00
commit 8ed0a05107
8 changed files with 948 additions and 0 deletions

View File

@ -0,0 +1,5 @@
---
title: Add API support for wiki pages
merge_request: 13372
author: Vitaliy @blackst0ne Klachkov
type: added

View File

@ -58,6 +58,7 @@ following locations:
- [Validate CI configuration](lint.md)
- [V3 to V4](v3_to_v4.md)
- [Version](version.md)
- [Wikis](wikis.md)
## Road to GraphQL

157
doc/api/wikis.md Normal file
View File

@ -0,0 +1,157 @@
# Wikis API
> [Introduced][ce-13372] in GitLab 10.0.
Available only in APIv4.
## List wiki pages
Get all wiki pages for a given project.
```
GET /projects/:id/wikis
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `with_content` | boolean | no | Include pages' content |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/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 project.
```
GET /projects/:id/wikis/:slug
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `slug` | string | yes | The slug (a unique string) of the wiki page |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/wikis/home
```
Example response:
```json
[
{
"content" : "home page",
"format" : "markdown",
"slug" : "home",
"title" : "home"
}
]
```
## Create a new wiki page
Creates a new wiki page for the given repository with the given title, slug, and content.
```
POST /projects/:id/wikis
```
| Attribute | Type | Required | Description |
| ------------- | ------- | -------- | ---------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](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`, and `asciidoc` |
```bash
curl --data "format=rdoc&title=Hello&content=Hello world" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/wikis"
```
Example response:
```json
{
"content" : "Hello world",
"format" : "markdown",
"slug" : "Hello",
"title" : "Hello"
}
```
## Edit an existing wiki page
Updates an existing wiki page. At least one parameter is required to update the wiki page.
```
PUT /projects/:id/wikis/:slug
```
| Attribute | Type | Required | Description |
| --------------- | ------- | --------------------------------- | ------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](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`, and `asciidoc` |
| `slug` | string | yes | The slug (a unique string) of the wiki page |
```bash
curl --request PUT --data "format=rdoc&content=documentation&title=Docs" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/wikis/foo"
```
Example response:
```json
{
"content" : "documentation",
"format" : "markdown",
"slug" : "Docs",
"title" : "Docs"
}
```
## Delete a wiki page
Deletes a wiki page with a given slug.
```
DELETE /projects/:id/wikis/:slug
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `slug` | string | yes | The slug (a unique string) of the wiki page |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/wikis/foo"
```
On success the HTTP status code is `204` and no JSON response is expected.

View File

@ -144,6 +144,7 @@ module API
mount ::API::Variables
mount ::API::GroupVariables
mount ::API::Version
mount ::API::Wikis
route :any, '*path' do
error!('404 Not Found', 404)

View File

@ -1,5 +1,15 @@
module API
module Entities
class WikiPageBasic < Grape::Entity
expose :format
expose :slug
expose :title
end
class WikiPage < WikiPageBasic
expose :content
end
class UserSafe < Grape::Entity
expose :id, :name, :username
end

View File

@ -56,6 +56,12 @@ module API
@project ||= find_project!(params[:id])
end
def wiki_page
page = user_project.wiki.find_page(params[:slug])
page || not_found!('Wiki Page')
end
def available_labels
@available_labels ||= LabelsFinder.new(current_user, project_id: user_project.id).execute
end

89
lib/api/wikis.rb Normal file
View File

@ -0,0 +1,89 @@
module API
class Wikis < Grape::API
helpers do
params :wiki_page_params do
requires :content, type: String, desc: 'Content of a wiki page'
requires :title, type: String, desc: 'Title of a wiki page'
optional :format,
type: String,
values: ProjectWiki::MARKUPS.values.map(&:to_s),
default: 'markdown',
desc: 'Format of a wiki page. Available formats are markdown, rdoc, and asciidoc'
end
end
resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a list of wiki pages' do
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, user_project
entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic
present user_project.wiki.pages, with: entity
end
desc 'Get a wiki page' do
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
end
desc 'Create a wiki page' do
success Entities::WikiPage
end
params do
use :wiki_page_params
end
post ':id/wikis' do
authorize! :create_wiki, user_project
page = WikiPages::CreateService.new(user_project, current_user, params).execute
if page.valid?
present page, with: Entities::WikiPage
else
render_validation_error!(page)
end
end
desc 'Update a wiki page' do
success Entities::WikiPage
end
params do
use :wiki_page_params
end
put ':id/wikis/:slug' do
authorize! :create_wiki, user_project
page = WikiPages::UpdateService.new(user_project, current_user, params).execute(wiki_page)
if page.valid?
present page, with: Entities::WikiPage
else
render_validation_error!(page)
end
end
desc 'Delete a wiki page'
params do
requires :slug, type: String, desc: 'The slug of a wiki page'
end
delete ':id/wikis/:slug' do
authorize! :admin_wiki, user_project
status 204
WikiPages::DestroyService.new(user_project, current_user).execute(wiki_page)
end
end
end
end

View File

@ -0,0 +1,679 @@
require 'spec_helper'
# For every API endpoint we test 3 states of wikis:
# - disabled
# - enabled only for team members
# - enabled for everyone who has access
# Every state is tested for 3 user roles:
# - guest
# - developer
# - master
# because they are 3 edge cases of using wiki pages.
describe API::Wikis do
let(:user) { create(:user) }
let(:payload) { { content: 'content', format: 'rdoc', title: 'title' } }
let(:expected_keys_with_content) { %w(content format slug title) }
let(:expected_keys_without_content) { %w(format slug title) }
shared_examples_for 'returns list of wiki pages' do
context 'when wiki has pages' do
let!(:pages) do
[create(:wiki_page, wiki: project.wiki, attrs: { title: 'page1', content: 'content of page1' }),
create(:wiki_page, wiki: project.wiki, attrs: { title: 'page2', content: 'content of page2' })]
end
it 'returns the list of wiki pages without content' do
get api(url, user)
expect(response).to have_http_status(200)
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), with_content: 1
expect(response).to have_http_status(200)
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_http_status(200)
expect(json_response.size).to eq(0)
end
end
shared_examples_for 'returns wiki page' do
it 'returns the wiki page' do
expect(response).to have_http_status(200)
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
shared_examples_for 'creates wiki page' do
it 'creates the wiki page' do
post(api(url, user), payload)
expect(response).to have_http_status(201)
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), payload)
expect(response).to have_http_status(400)
expect(json_response.size).to eq(1)
expect(json_response['error']).to eq("#{part} is missing")
end
end
end
shared_examples_for 'updates wiki page' do
it 'updates the wiki page' do
expect(response).to have_http_status(200)
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
end
shared_examples_for '403 Forbidden' do
it 'returns 403 Forbidden' do
expect(response).to have_http_status(403)
expect(json_response.size).to eq(1)
expect(json_response['message']).to eq('403 Forbidden')
end
end
shared_examples_for '404 Wiki Page Not Found' do
it 'returns 404 Wiki Page Not Found' do
expect(response).to have_http_status(404)
expect(json_response.size).to eq(1)
expect(json_response['message']).to eq('404 Wiki Page Not Found')
end
end
shared_examples_for '404 Project Not Found' do
it 'returns 404 Project Not Found' do
expect(response).to have_http_status(404)
expect(json_response.size).to eq(1)
expect(json_response['message']).to eq('404 Project Not Found')
end
end
shared_examples_for '204 No Content' do
it 'returns 204 No Content' do
expect(response).to have_http_status(204)
end
end
describe 'GET /projects/:id/wikis' do
let(:url) { "/projects/#{project.id}/wikis" }
context 'when wiki is disabled' do
let(:project) { create(:project, :wiki_disabled) }
context 'when user is guest' do
before do
get api(url)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
get api(url, user)
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
get api(url, user)
end
include_examples '403 Forbidden'
end
end
context 'when wiki is available only for team members' do
let(:project) { create(:project, :wiki_private) }
context 'when user is guest' do
before do
get api(url)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
end
include_examples 'returns list of wiki pages'
end
context 'when user is master' do
before do
project.add_master(user)
end
include_examples 'returns list of wiki pages'
end
end
context 'when wiki is available for everyone with access' do
let(:project) { create(:project) }
context 'when user is guest' do
before do
get api(url)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
end
include_examples 'returns list of wiki pages'
end
context 'when user is master' do
before do
project.add_master(user)
end
include_examples 'returns list of wiki pages'
end
end
end
describe 'GET /projects/:id/wikis/:slug' do
let(:page) { create(:wiki_page, wiki: project.wiki) }
let(:url) { "/projects/#{project.id}/wikis/#{page.slug}" }
context 'when wiki is disabled' do
let(:project) { create(:project, :wiki_disabled) }
context 'when user is guest' do
before do
get api(url)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
get api(url, user)
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
get api(url, user)
end
include_examples '403 Forbidden'
end
end
context 'when wiki is available only for team members' do
let(:project) { create(:project, :wiki_private) }
context 'when user is guest' do
before do
get api(url)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
get api(url, user)
end
include_examples 'returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
context 'when user is master' do
before do
project.add_master(user)
get api(url, user)
end
include_examples 'returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
end
context 'when wiki is available for everyone with access' do
let(:project) { create(:project) }
context 'when user is guest' do
before do
get api(url)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
get api(url, user)
end
include_examples 'returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
context 'when user is master' do
before do
project.add_master(user)
get api(url, user)
end
include_examples 'returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
end
end
describe 'POST /projects/:id/wikis' do
let(:payload) { { title: 'title', content: 'content' } }
let(:url) { "/projects/#{project.id}/wikis" }
context 'when wiki is disabled' do
let(:project) { create(:project, :wiki_disabled) }
context 'when user is guest' do
before do
post(api(url), payload)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
post(api(url, user), payload)
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
post(api(url, user), payload)
end
include_examples '403 Forbidden'
end
end
context 'when wiki is available only for team members' do
let(:project) { create(:project, :wiki_private) }
context 'when user is guest' do
before do
post(api(url), payload)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
end
include_examples 'creates wiki page'
end
context 'when user is master' do
before do
project.add_master(user)
end
include_examples 'creates wiki page'
end
end
context 'when wiki is available for everyone with access' do
let(:project) { create(:project) }
context 'when user is guest' do
before do
post(api(url), payload)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
end
include_examples 'creates wiki page'
end
context 'when user is master' do
before do
project.add_master(user)
end
include_examples 'creates wiki page'
end
end
end
describe 'PUT /projects/:id/wikis/:slug' do
let(:page) { create(:wiki_page, wiki: project.wiki) }
let(:payload) { { title: 'new title', content: 'new content' } }
let(:url) { "/projects/#{project.id}/wikis/#{page.slug}" }
context 'when wiki is disabled' do
let(:project) { create(:project, :wiki_disabled) }
context 'when user is guest' do
before do
put(api(url), payload)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
put(api(url, user), payload)
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
put(api(url, user), payload)
end
include_examples '403 Forbidden'
end
end
context 'when wiki is available only for team members' do
let(:project) { create(:project, :wiki_private) }
context 'when user is guest' do
before do
put(api(url), payload)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
put(api(url, user), payload)
end
include_examples 'updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
context 'when user is master' do
before do
project.add_master(user)
put(api(url, user), payload)
end
include_examples 'updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
end
context 'when wiki is available for everyone with access' do
let(:project) { create(:project) }
context 'when user is guest' do
before do
put(api(url), payload)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
put(api(url, user), payload)
end
include_examples 'updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
context 'when user is master' do
before do
project.add_master(user)
put(api(url, user), payload)
end
include_examples 'updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
end
end
describe 'DELETE /projects/:id/wikis/:slug' do
let(:page) { create(:wiki_page, wiki: project.wiki) }
let(:url) { "/projects/#{project.id}/wikis/#{page.slug}" }
context 'when wiki is disabled' do
let(:project) { create(:project, :wiki_disabled) }
context 'when user is guest' do
before do
delete(api(url))
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
delete(api(url, user))
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
delete(api(url, user))
end
include_examples '403 Forbidden'
end
end
context 'when wiki is available only for team members' do
let(:project) { create(:project, :wiki_private) }
context 'when user is guest' do
before do
delete(api(url))
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
delete(api(url, user))
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
delete(api(url, user))
end
include_examples '204 No Content'
end
end
context 'when wiki is available for everyone with access' do
let(:project) { create(:project) }
context 'when user is guest' do
before do
delete(api(url))
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
delete(api(url, user))
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
delete(api(url, user))
end
include_examples '204 No Content'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
end
end
end