mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 19:11:25 +01:00
services.IsNotFound replaces the hand-rolled 404 checks, convertPage the copied page conversions and lastPathNumber the comment's own number parser. The List wrappers around ListPage of releases, branches, wiki pages, secrets and variables go, pull request filters resolve milestones through Milestones.FindOne like issues, and localFlagsChanged, boolOrder, Cell.Render, stateBadge and dueText replace their copies. Signed-off-by: bircni <bircni@icloud.com>
168 lines
6.3 KiB
Go
168 lines
6.3 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package services
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
gitea "gitea.dev/sdk"
|
|
"gitea.dev/tea/internal/transport"
|
|
)
|
|
|
|
// WikiPage exposes decoded page content and revision metadata.
|
|
type WikiPage struct {
|
|
Title string `json:"title"`
|
|
Body string `json:"body"`
|
|
URL string `json:"url"`
|
|
SHA string `json:"sha"`
|
|
Author string `json:"author"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
CommitCount int64 `json:"commit_count"`
|
|
Sidebar string `json:"sidebar"`
|
|
Footer string `json:"footer"`
|
|
}
|
|
|
|
// WikiRevision represents a wiki commit without exposing author email addresses.
|
|
type WikiRevision struct {
|
|
SHA string `json:"sha"`
|
|
Message string `json:"message"`
|
|
Author string `json:"author"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
func wikiRevisionFrom(commit *gitea.WikiCommit) WikiRevision {
|
|
result := WikiRevision{SHA: commit.ID, Message: commit.Message}
|
|
if commit.Author != nil {
|
|
result.Author = commit.Author.Name
|
|
result.CreatedAt = commit.Author.Date
|
|
}
|
|
return result
|
|
}
|
|
|
|
func wikiPageFrom(title, url string, lastCommit *gitea.WikiCommit) WikiPage {
|
|
result := WikiPage{Title: title, URL: url}
|
|
if lastCommit != nil {
|
|
revision := wikiRevisionFrom(lastCommit)
|
|
result.SHA, result.Author, result.UpdatedAt = revision.SHA, revision.Author, revision.CreatedAt
|
|
}
|
|
return result
|
|
}
|
|
|
|
func wikiContentFrom(page *gitea.WikiPage) (WikiPage, error) {
|
|
body, err := base64.StdEncoding.DecodeString(page.ContentBase64)
|
|
if err != nil {
|
|
return WikiPage{}, fmt.Errorf("decode wiki content: %w", err)
|
|
}
|
|
result := wikiPageFrom(page.Title, page.HTMLURL, page.LastCommit)
|
|
result.Body, result.CommitCount, result.Sidebar, result.Footer = string(body), page.CommitCount, page.Sidebar, page.Footer
|
|
return result, nil
|
|
}
|
|
|
|
// WikiOptions supplies title, decoded content and a commit message.
|
|
type WikiOptions struct{ Title, Body, Message string }
|
|
|
|
// Wiki implements repository wiki operations.
|
|
type Wiki struct {
|
|
Client *transport.Client
|
|
Repository string
|
|
}
|
|
|
|
func (s Wiki) page(ctx context.Context, op repoOperation[*gitea.WikiPage]) (WikiPage, error) {
|
|
page, err := repoCall(ctx, s.Client, s.Repository, op)
|
|
if err != nil {
|
|
return WikiPage{}, err
|
|
}
|
|
return wikiContentFrom(page)
|
|
}
|
|
|
|
// wikiPageSize is the page size for reading the whole wiki; Gitea replaces a limit of 1 with its
|
|
// default page size.
|
|
const wikiPageSize = 50
|
|
|
|
// ListPage lists a page of wiki pages, with whether more follow and how many there are. It reads
|
|
// the whole page list and pages it on the client, because the server pages it inconsistently.
|
|
func (s Wiki) ListPage(ctx context.Context, limit, page int) (Page[WikiPage], error) {
|
|
all, err := listAll(func(page int) ([]*gitea.WikiPageMetaData, error) {
|
|
return repoCall(ctx, s.Client, s.Repository, func(ctx context.Context, c *gitea.Client, owner, repo string) ([]*gitea.WikiPageMetaData, *gitea.Response, error) {
|
|
return c.Wiki.ListPages(ctx, owner, repo, gitea.ListWikiPagesOptions{ListOptions: listOptions(wikiPageSize, page)})
|
|
})
|
|
})
|
|
if err != nil {
|
|
if !IsNotFound(err) {
|
|
return Page[WikiPage]{}, err
|
|
}
|
|
// An uninitialized wiki returns 404. Verify repository visibility and
|
|
// wiki configuration before treating that response as an empty list.
|
|
repository, checkErr := (Repositories{Client: s.Client}).get(ctx, s.Repository)
|
|
if checkErr != nil {
|
|
return Page[WikiPage]{}, checkErr
|
|
}
|
|
if !repository.HasWiki {
|
|
return Page[WikiPage]{}, err
|
|
}
|
|
return Page[WikiPage]{Items: []WikiPage{}, Counted: true}, nil
|
|
}
|
|
items, err := pageOf(convertAll(all, func(item *gitea.WikiPageMetaData) WikiPage {
|
|
return wikiPageFrom(item.Title, item.HTMLURL, item.LastCommit)
|
|
}), limit, page)
|
|
return Page[WikiPage]{Items: items, More: page*limit < len(all), Total: len(all), Counted: true}, err
|
|
}
|
|
|
|
// View retrieves and decodes a wiki page.
|
|
func (s Wiki) View(ctx context.Context, name string) (WikiPage, error) {
|
|
item, err := s.page(ctx, func(ctx context.Context, c *gitea.Client, owner, repo string) (*gitea.WikiPage, *gitea.Response, error) {
|
|
return c.Wiki.GetPage(ctx, owner, repo, name)
|
|
})
|
|
return item, Classify(err, "wiki page", strconv.Quote(name), s.Repository)
|
|
}
|
|
|
|
func wikiOptions(opts WikiOptions) gitea.CreateWikiPageOptions {
|
|
return gitea.CreateWikiPageOptions{Title: opts.Title, ContentBase64: base64.StdEncoding.EncodeToString([]byte(opts.Body)), Message: opts.Message}
|
|
}
|
|
|
|
// Create creates one wiki page, initializing the wiki if necessary.
|
|
func (s Wiki) Create(ctx context.Context, opts WikiOptions) (WikiPage, error) {
|
|
item, err := s.page(ctx, func(ctx context.Context, c *gitea.Client, owner, repo string) (*gitea.WikiPage, *gitea.Response, error) {
|
|
return c.Wiki.CreatePage(ctx, owner, repo, wikiOptions(opts))
|
|
})
|
|
return item, Classify(err, "wiki page", strconv.Quote(opts.Title), s.Repository)
|
|
}
|
|
|
|
// Edit replaces page content and optionally changes its title.
|
|
func (s Wiki) Edit(ctx context.Context, name string, opts WikiOptions) (WikiPage, error) {
|
|
if opts.Title == "" {
|
|
current, err := s.View(ctx, name)
|
|
if err != nil {
|
|
return WikiPage{}, err
|
|
}
|
|
opts.Title = current.Title
|
|
}
|
|
item, err := s.page(ctx, func(ctx context.Context, c *gitea.Client, owner, repo string) (*gitea.WikiPage, *gitea.Response, error) {
|
|
return c.Wiki.EditPage(ctx, owner, repo, name, wikiOptions(opts))
|
|
})
|
|
return item, Classify(err, "wiki page", strconv.Quote(name), s.Repository)
|
|
}
|
|
|
|
// Delete removes a named wiki page.
|
|
func (s Wiki) Delete(ctx context.Context, name string) error {
|
|
err := repoDo(ctx, s.Client, s.Repository, func(ctx context.Context, c *gitea.Client, owner, repo string) (*gitea.Response, error) {
|
|
return c.Wiki.DeletePage(ctx, owner, repo, name)
|
|
})
|
|
return Classify(err, "wiki page", strconv.Quote(name), s.Repository)
|
|
}
|
|
|
|
// Revisions returns one server-paginated page of wiki commits.
|
|
func (s Wiki) Revisions(ctx context.Context, name string, page int) ([]WikiRevision, error) {
|
|
revisions, err := repoCall(ctx, s.Client, s.Repository, func(ctx context.Context, c *gitea.Client, owner, repo string) (*gitea.WikiCommitList, *gitea.Response, error) {
|
|
return c.Wiki.GetPageRevisions(ctx, owner, repo, name, gitea.ListWikiPageRevisionsOptions{Page: page})
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return convertAll(revisions.WikiCommits, wikiRevisionFrom), nil
|
|
}
|