diff --git a/routers/hat/hat.go b/routers/hat/hat.go index 50703e9..49ff9e7 100644 --- a/routers/hat/hat.go +++ b/routers/hat/hat.go @@ -1,9 +1,14 @@ package hat import ( + "fmt" "net/http" + "reflect" "strings" + hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs" + "gitea.com/go-chi/binding" + access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -54,7 +59,8 @@ func Routers() *web.Route { m.Get("/tag_name_set", context.ReferencesGitRepo(), repo.TagNameSet) m.Group("/wikies", func() { - m.Combo("").Get(repo.ListWikiPages) + m.Combo("").Get(repo.ListWikiPages). + Post(bind(hat_api.WikiOption{}), repo.CreateWiki) }) }, repoAssignment()) }) @@ -153,3 +159,20 @@ func repoAssignment() func(ctx *context.APIContext) { } } } + +// bind binding an obj to a func(ctx *context.APIContext) +func bind(obj interface{}) http.HandlerFunc { + tp := reflect.TypeOf(obj) + for tp.Kind() == reflect.Ptr { + tp = tp.Elem() + } + return web.Wrap(func(ctx *context.APIContext) { + theObj := reflect.New(tp).Interface() // create a new form obj for every request but not use obj directly + errs := binding.Bind(ctx.Req, theObj) + if len(errs) > 0 { + ctx.Error(http.StatusUnprocessableEntity, "validationError", fmt.Sprintf("%s: %s", errs[0].FieldNames, errs[0].Error())) + return + } + web.SetForm(ctx, theObj) + }) +} diff --git a/routers/hat/repo/wiki.go b/routers/hat/repo/wiki.go index abcfaf8..3fa9425 100644 --- a/routers/hat/repo/wiki.go +++ b/routers/hat/repo/wiki.go @@ -1,12 +1,19 @@ package repo import ( + "bytes" + "io" "net/http" + "net/url" "sort" + "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" + "code.gitea.io/gitea/modules/web" wiki_service "code.gitea.io/gitea/services/wiki" hat_git "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/git" hat_api "code.gitlink.org.cn/Gitlink/gitea_hat.git/modules/structs" @@ -112,3 +119,122 @@ func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, err } return wikiRepo, commit, nil } + +func CreateWiki(ctx *context.Context) { + form := web.GetForm(ctx).(*hat_api.WikiOption) + + wikiName := wiki_service.NormalizeWikiName(form.Name) + wikiCloneLink := ctx.Repo.Repository.WikiCloneLink() + + if err := wiki_service.AddWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName, form.Content, form.CommitMessage); err != nil { + if models.IsErrWikiReservedName(err) { + ctx.Error(http.StatusInternalServerError, "WikiNameIsReservedPage", "wiki名称是被保留的.") + } else if models.IsErrWikiAlreadyExist(err) { + ctx.Error(http.StatusConflict, "WikiNameAlreadyExist", "wiki名称已存在") + } else { + ctx.Error(http.StatusInternalServerError, "AddWikiPage", err.Error()) + } + return + } + + wikiRepo, commit, _ := findWikiRepoCommit(ctx) + data, entry, pageFilename := wikiContentsByName(ctx, commit, wikiName) + metas := ctx.Repo.Repository.ComposeDocumentMetas() + + var rctx = &markup.RenderContext{ + URLPrefix: ctx.Repo.RepoLink, + Metas: metas, + IsWiki: true, + } + + var buf strings.Builder + if err := markdown.Render(rctx, bytes.NewReader(data), &buf); err != nil { + if wikiRepo != nil { + wikiRepo.Close() + } + ctx.ServerError("Render", err) + return + } + + commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename) + c, err := wikiRepo.GetCommitByPath(entry.Name()) + if err != nil { + if models.IsErrWikiInvalidFileName(err) { + return + } + } + wiki := hat_api.WikiResponse{ + WikiCloneLink: hat_api.CloneLink{ + HTTPS: wikiCloneLink.HTTPS, + SSH: wikiCloneLink.SSH, + }, + WikiMeta: hat_api.WikiMeta{ + Name: form.Name, + Commit: hat_api.WikiCommit{ + Author: hat_api.WikiUser{ + Name: c.Author.Name, + Email: c.Author.Email, + When: c.Author.When.Unix(), + }, + Commiter: hat_api.WikiUser{ + Name: c.Committer.Name, + Email: c.Committer.Email, + When: c.Author.When.Unix(), + }, + ID: c.ID.String(), + Message: c.Message(), + }, + }, + CommitCounts: commitsCount, + MdContent: string(data), + SimpleContent: buf.String(), + } + ctx.JSON(http.StatusOK, wiki) +} + +func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte { + reader, err := entry.Blob().DataAsync() + if err != nil { + ctx.ServerError("Blob.Data", err) + return nil + } + defer reader.Close() + content, err := io.ReadAll(reader) + if err != nil { + ctx.ServerError("ReadAll", err) + return nil + } + return content +} + +func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) { + entry, err := commit.GetTreeEntryByPath(target) + if err != nil { + return nil, err + } + if entry != nil { + return entry, nil + } + + // Then the unescaped, shortest alternative + var unescapedTarget string + if unescapedTarget, err = url.QueryUnescape(target); err != nil { + return nil, err + } + return commit.GetTreeEntryByPath(unescapedTarget) +} + +// wikiContentsByName returns the contents of a wiki page, along with a boolean +// indicating whether the page exists. Writes to ctx if an error occurs. +func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName string) ([]byte, *git.TreeEntry, string) { + pageFilename := wiki_service.NameToFilename(wikiName) + entry, err := findEntryForFile(commit, pageFilename) + if err != nil { + if git.IsErrNotExist(err) { + } else { + ctx.ServerError("findEntryForFile", err) + } + return nil, nil, "" + } + return wikiContentsByEntry(ctx, entry), entry, pageFilename +}