Add API to get/edit wiki (#17278)

* Add API to get/edit wiki

* Add swagger docs, various improvements

* fmt

* Fix lint and rm comment

* Add page parameter

* Add pagination to pages

* Add tests

* fmt

* Update func names

* Update error handling

* Update type name

* Fix lint

* Don't delete Home

* Update func name

* Update routers/api/v1/repo/wiki.go

Co-authored-by: delvh <dev.lh@web.de>

* Remove unnecessary check

* Fix lint

* Use English strings

* Update integrations/api_wiki_test.go

Co-authored-by: delvh <dev.lh@web.de>

* Update func and test names

* Remove unsed check and avoid duplicated error reports

* Improve error handling

* Return after error

* Document 404 error

* Update swagger

* Fix lint

* Apply suggestions from code review

Co-authored-by: delvh <dev.lh@web.de>

* Document file encoding

* fmt

* Apply suggestions

* Use convert

* Fix integration test

* simplify permissions

* unify duplicate key Title/Name

* improve types & return UTC timestamps

* improve types pt.2

- add WikiPageMetaData.LastCommit
- add WikiPageMetaData.HTMLURL
- replace WikiPageMetaData.Updated with .LastCommit.Committer.Created

also delete convert.ToWikiPage(), as it received too many arguments and
only had one callsite anyway. sorry for bad advice earlier 🙃

* WikiPage.Content is base64 encoded

* simplify error handling in wikiContentsByName()

* update swagger

* fix & DRY findWikiRepoCommit() error handling

ListWikiPages() previously wrote error twice when repo wiki didn't exist

* rename Content -> ContentBase64

* Fix test

* Fix tests

* Update var name

* suburl -> sub_url

Co-authored-by: delvh <dev.lh@web.de>
Co-authored-by: Norwin <git@nroo.de>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
qwerty287
2021-10-25 05:43:40 +02:00
committed by GitHub
parent 843bc9deeb
commit 3676fafdac
8 changed files with 1336 additions and 1 deletions

View File

@ -0,0 +1,251 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package integrations
import (
"encoding/base64"
"fmt"
"net/http"
"testing"
api "code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
func TestAPIGetWikiPage(t *testing.T) {
defer prepareTestEnv(t)()
username := "user2"
session := loginUser(t, username)
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/Home", username, "repo1")
req := NewRequest(t, "GET", urlStr)
resp := session.MakeRequest(t, req, http.StatusOK)
var page *api.WikiPage
DecodeJSON(t, resp, &page)
assert.Equal(t, &api.WikiPage{
WikiPageMetaData: &api.WikiPageMetaData{
Title: "Home",
HTMLURL: page.HTMLURL,
SubURL: "Home",
LastCommit: &api.WikiCommit{
ID: "2c54faec6c45d31c1abfaecdab471eac6633738a",
Author: &api.CommitUser{
Identity: api.Identity{
Name: "Ethan Koenig",
Email: "ethantkoenig@gmail.com",
},
Date: "2017-11-27T04:31:18Z",
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "Ethan Koenig",
Email: "ethantkoenig@gmail.com",
},
Date: "2017-11-27T04:31:18Z",
},
Message: "Add Home.md\n",
},
},
ContentBase64: base64.RawStdEncoding.EncodeToString(
[]byte("# Home page\n\nThis is the home page!\n"),
),
CommitCount: 1,
Sidebar: "",
Footer: "",
}, page)
}
func TestAPIListWikiPages(t *testing.T) {
defer prepareTestEnv(t)()
username := "user2"
session := loginUser(t, username)
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/pages", username, "repo1")
req := NewRequest(t, "GET", urlStr)
resp := session.MakeRequest(t, req, http.StatusOK)
var meta []*api.WikiPageMetaData
DecodeJSON(t, resp, &meta)
dummymeta := []*api.WikiPageMetaData{
{
Title: "Home",
HTMLURL: meta[0].HTMLURL,
SubURL: "Home",
LastCommit: &api.WikiCommit{
ID: "2c54faec6c45d31c1abfaecdab471eac6633738a",
Author: &api.CommitUser{
Identity: api.Identity{
Name: "Ethan Koenig",
Email: "ethantkoenig@gmail.com",
},
Date: "2017-11-27T04:31:18Z",
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "Ethan Koenig",
Email: "ethantkoenig@gmail.com",
},
Date: "2017-11-27T04:31:18Z",
},
Message: "Add Home.md\n",
},
},
{
Title: "Page With Image",
HTMLURL: meta[1].HTMLURL,
SubURL: "Page-With-Image",
LastCommit: &api.WikiCommit{
ID: "0cf15c3f66ec8384480ed9c3cf87c9e97fbb0ec3",
Author: &api.CommitUser{
Identity: api.Identity{
Name: "Gabriel Silva Simões",
Email: "simoes.sgabriel@gmail.com",
},
Date: "2019-01-25T01:41:55Z",
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "Gabriel Silva Simões",
Email: "simoes.sgabriel@gmail.com",
},
Date: "2019-01-25T01:41:55Z",
},
Message: "Add jpeg.jpg and page with image\n",
},
},
{
Title: "Page With Spaced Name",
HTMLURL: meta[2].HTMLURL,
SubURL: "Page-With-Spaced-Name",
LastCommit: &api.WikiCommit{
ID: "c10d10b7e655b3dab1f53176db57c8219a5488d6",
Author: &api.CommitUser{
Identity: api.Identity{
Name: "Gabriel Silva Simões",
Email: "simoes.sgabriel@gmail.com",
},
Date: "2019-01-25T01:39:51Z",
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "Gabriel Silva Simões",
Email: "simoes.sgabriel@gmail.com",
},
Date: "2019-01-25T01:39:51Z",
},
Message: "Add page with spaced name\n",
},
},
{
Title: "Unescaped File",
HTMLURL: meta[3].HTMLURL,
SubURL: "Unescaped-File",
LastCommit: &api.WikiCommit{
ID: "0dca5bd9b5d7ef937710e056f575e86c0184ba85",
Author: &api.CommitUser{
Identity: api.Identity{
Name: "6543",
Email: "6543@obermui.de",
},
Date: "2021-07-19T16:42:46Z",
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "6543",
Email: "6543@obermui.de",
},
Date: "2021-07-19T16:42:46Z",
},
Message: "add unescaped file\n",
},
},
}
assert.Equal(t, dummymeta, meta)
}
func TestAPINewWikiPage(t *testing.T) {
for _, title := range []string{
"New page",
"&&&&",
} {
defer prepareTestEnv(t)()
username := "user2"
session := loginUser(t, username)
token := getTokenForLoggedInUser(t, session)
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new?token=%s", username, "repo1", token)
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateWikiPageOptions{
Title: title,
ContentBase64: base64.StdEncoding.EncodeToString([]byte("Wiki page content for API unit tests")),
Message: "",
})
session.MakeRequest(t, req, http.StatusCreated)
}
}
func TestAPIEditWikiPage(t *testing.T) {
defer prepareTestEnv(t)()
username := "user2"
session := loginUser(t, username)
token := getTokenForLoggedInUser(t, session)
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/Page-With-Spaced-Name?token=%s", username, "repo1", token)
req := NewRequestWithJSON(t, "PATCH", urlStr, &api.CreateWikiPageOptions{
Title: "edited title",
ContentBase64: base64.StdEncoding.EncodeToString([]byte("Edited wiki page content for API unit tests")),
Message: "",
})
session.MakeRequest(t, req, http.StatusOK)
}
func TestAPIListPageRevisions(t *testing.T) {
defer prepareTestEnv(t)()
username := "user2"
session := loginUser(t, username)
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/revisions/Home", username, "repo1")
req := NewRequest(t, "GET", urlStr)
resp := session.MakeRequest(t, req, http.StatusOK)
var revisions *api.WikiCommitList
DecodeJSON(t, resp, &revisions)
dummyrevisions := &api.WikiCommitList{
WikiCommits: []*api.WikiCommit{
{
ID: "2c54faec6c45d31c1abfaecdab471eac6633738a",
Author: &api.CommitUser{
Identity: api.Identity{
Name: "Ethan Koenig",
Email: "ethantkoenig@gmail.com",
},
Date: "2017-11-27T04:31:18Z",
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "Ethan Koenig",
Email: "ethantkoenig@gmail.com",
},
Date: "2017-11-27T04:31:18Z",
},
Message: "Add Home.md\n",
},
},
Count: 1,
}
assert.Equal(t, dummyrevisions, revisions)
}

60
modules/convert/wiki.go Normal file
View File

@ -0,0 +1,60 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package convert
import (
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
wiki_service "code.gitea.io/gitea/services/wiki"
)
// ToWikiCommit convert a git commit into a WikiCommit
func ToWikiCommit(commit *git.Commit) *api.WikiCommit {
return &api.WikiCommit{
ID: commit.ID.String(),
Author: &api.CommitUser{
Identity: api.Identity{
Name: commit.Author.Name,
Email: commit.Author.Email,
},
Date: commit.Author.When.UTC().Format(time.RFC3339),
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: commit.Committer.Name,
Email: commit.Committer.Email,
},
Date: commit.Committer.When.UTC().Format(time.RFC3339),
},
Message: commit.CommitMessage,
}
}
// ToWikiCommitList convert a list of git commits into a WikiCommitList
func ToWikiCommitList(commits []*git.Commit, total int64) *api.WikiCommitList {
result := make([]*api.WikiCommit, len(commits))
for i := range commits {
result[i] = ToWikiCommit(commits[i])
}
return &api.WikiCommitList{
WikiCommits: result,
Count: total,
}
}
// ToWikiPageMetaData converts meta information to a WikiPageMetaData
func ToWikiPageMetaData(title string, lastCommit *git.Commit, repo *models.Repository) *api.WikiPageMetaData {
suburl := wiki_service.NameToSubURL(title)
return &api.WikiPageMetaData{
Title: title,
HTMLURL: util.URLJoin(repo.HTMLURL(), "wiki", suburl),
SubURL: suburl,
LastCommit: ToWikiCommit(lastCommit),
}
}

View File

@ -0,0 +1,47 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package structs
// WikiCommit page commit/revision
type WikiCommit struct {
ID string `json:"sha"`
Author *CommitUser `json:"author"`
Committer *CommitUser `json:"commiter"`
Message string `json:"message"`
}
// WikiPage a wiki page
type WikiPage struct {
*WikiPageMetaData
// Page content, base64 encoded
ContentBase64 string `json:"content_base64"`
CommitCount int64 `json:"commit_count"`
Sidebar string `json:"sidebar"`
Footer string `json:"footer"`
}
// WikiPageMetaData wiki page meta information
type WikiPageMetaData struct {
Title string `json:"title"`
HTMLURL string `json:"html_url"`
SubURL string `json:"sub_url"`
LastCommit *WikiCommit `json:"last_commit"`
}
// CreateWikiPageOptions form for creating wiki
type CreateWikiPageOptions struct {
// page title. leave empty to keep unchanged
Title string `json:"title"`
// content must be base64 encoded
ContentBase64 string `json:"content_base64"`
// optional commit message summarizing the change
Message string `json:"message"`
}
// WikiCommitList commit/revision list
type WikiCommitList struct {
WikiCommits []*WikiCommit `json:"commits"`
Count int64 `json:"count"`
}

View File

@ -521,6 +521,13 @@ func mustEnableIssuesOrPulls(ctx *context.APIContext) {
}
}
func mustEnableWiki(ctx *context.APIContext) {
if !(ctx.Repo.CanRead(models.UnitTypeWiki)) {
ctx.NotFound()
return
}
}
func mustNotBeArchived(ctx *context.APIContext) {
if ctx.Repo.Repository.IsArchived {
ctx.NotFound()
@ -791,6 +798,15 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
m.Combo("").Get(repo.ListTrackedTimesByRepository)
m.Combo("/{timetrackingusername}").Get(repo.ListTrackedTimesByUser)
}, mustEnableIssues, reqToken())
m.Group("/wiki", func() {
m.Combo("/page/{pageName}").
Get(repo.GetWikiPage).
Patch(mustNotBeArchived, reqRepoWriter(models.UnitTypeWiki), bind(api.CreateWikiPageOptions{}), repo.EditWikiPage).
Delete(mustNotBeArchived, reqRepoWriter(models.UnitTypeWiki), repo.DeleteWikiPage)
m.Get("/revisions/{pageName}", repo.ListPageRevisions)
m.Post("/new", mustNotBeArchived, reqRepoWriter(models.UnitTypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage)
m.Get("/pages", repo.ListWikiPages)
}, mustEnableWiki)
m.Group("/issues", func() {
m.Combo("").Get(repo.ListIssues).
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)

514
routers/api/v1/repo/wiki.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -169,4 +169,7 @@ type swaggerParameterBodies struct {
// in:body
UserSettingsOptions api.UserSettingsOptions
// in:body
CreateWikiPageOptions api.CreateWikiPageOptions
}

View File

@ -323,3 +323,24 @@ type swaggerCombinedStatus struct {
// in: body
Body api.CombinedStatus `json:"body"`
}
// WikiPageList
// swagger:response WikiPageList
type swaggerWikiPageList struct {
// in:body
Body []api.WikiPageMetaData `json:"body"`
}
// WikiPage
// swagger:response WikiPage
type swaggerWikiPage struct {
// in:body
Body api.WikiPage `json:"body"`
}
// WikiCommitList
// swagger:response WikiCommitList
type swaggerWikiCommitList struct {
// in:body
Body api.WikiCommitList `json:"body"`
}

File diff suppressed because it is too large Load Diff