Phase 3: Organization Public Profile Page - Pinned repositories with groups - Public members display with roles - API endpoints for pinned repos and groups Phase 4: Gitea Pages Foundation - Landing page templates (simple, docs, product, portfolio) - Custom domain support with verification - YAML configuration parser (.gitea/landing.yaml) - Repository settings UI for pages Phase 5: Enhanced Wiki System with V2 API - Full CRUD operations via v2 API - Full-text search with WikiIndex table - Link graph visualization - Wiki health metrics (orphaned, dead links, outdated) - Designed for external AI plugin integration - Developer guide for .NET integration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
388 lines
9.6 KiB
Go
388 lines
9.6 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package repo
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
repo_model "code.gitea.io/gitea/models/repo"
|
|
api "code.gitea.io/gitea/modules/structs"
|
|
"code.gitea.io/gitea/modules/web"
|
|
"code.gitea.io/gitea/services/context"
|
|
pages_service "code.gitea.io/gitea/services/pages"
|
|
)
|
|
|
|
// GetPagesConfig returns the pages configuration for a repository
|
|
func GetPagesConfig(ctx *context.APIContext) {
|
|
// swagger:operation GET /repos/{owner}/{repo}/pages repository repoGetPages
|
|
// ---
|
|
// summary: Get pages configuration for a repository
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: owner
|
|
// in: path
|
|
// description: owner of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: repo
|
|
// in: path
|
|
// description: name of the repo
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/PagesInfo"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
config, err := pages_service.GetPagesConfig(ctx, ctx.Repo.Repository)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
domains, err := pages_service.GetPagesDomains(ctx, ctx.Repo.Repository.ID)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, &api.PagesInfo{
|
|
Config: &api.PagesConfig{
|
|
Enabled: config != nil && config.Enabled,
|
|
Template: getTemplateOrDefault(config),
|
|
Subdomain: pages_service.GetPagesSubdomain(ctx.Repo.Repository),
|
|
URL: pages_service.GetPagesURL(ctx.Repo.Repository),
|
|
},
|
|
Domains: convertPagesDomains(domains),
|
|
})
|
|
}
|
|
|
|
// UpdatePagesConfig updates the pages configuration for a repository
|
|
func UpdatePagesConfig(ctx *context.APIContext) {
|
|
// swagger:operation PUT /repos/{owner}/{repo}/pages repository repoUpdatePages
|
|
// ---
|
|
// summary: Update pages configuration for a repository
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: owner
|
|
// in: path
|
|
// description: owner of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: repo
|
|
// in: path
|
|
// description: name of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: body
|
|
// in: body
|
|
// schema:
|
|
// "$ref": "#/definitions/CreatePagesConfigOption"
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/PagesConfig"
|
|
// "403":
|
|
// "$ref": "#/responses/forbidden"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
form := web.GetForm(ctx).(*api.CreatePagesConfigOption)
|
|
|
|
if form.Enabled {
|
|
if err := pages_service.EnablePages(ctx, ctx.Repo.Repository, form.Template); err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
} else {
|
|
if err := pages_service.DisablePages(ctx, ctx.Repo.Repository); err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, &api.PagesConfig{
|
|
Enabled: form.Enabled,
|
|
Template: form.Template,
|
|
Subdomain: pages_service.GetPagesSubdomain(ctx.Repo.Repository),
|
|
URL: pages_service.GetPagesURL(ctx.Repo.Repository),
|
|
})
|
|
}
|
|
|
|
// DeletePagesConfig disables pages for a repository
|
|
func DeletePagesConfig(ctx *context.APIContext) {
|
|
// swagger:operation DELETE /repos/{owner}/{repo}/pages repository repoDeletePages
|
|
// ---
|
|
// summary: Disable pages for a repository
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: owner
|
|
// in: path
|
|
// description: owner of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: repo
|
|
// in: path
|
|
// description: name of the repo
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "204":
|
|
// "$ref": "#/responses/empty"
|
|
// "403":
|
|
// "$ref": "#/responses/forbidden"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
if err := pages_service.DisablePages(ctx, ctx.Repo.Repository); err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
// Also delete all custom domains
|
|
if err := repo_model.DeletePagesDomainsByRepoID(ctx, ctx.Repo.Repository.ID); err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
ctx.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// ListPagesDomains returns all custom domains for a repository's pages
|
|
func ListPagesDomains(ctx *context.APIContext) {
|
|
// swagger:operation GET /repos/{owner}/{repo}/pages/domains repository repoListPagesDomains
|
|
// ---
|
|
// summary: List custom domains for repository pages
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: owner
|
|
// in: path
|
|
// description: owner of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: repo
|
|
// in: path
|
|
// description: name of the repo
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/PagesDomainList"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
domains, err := pages_service.GetPagesDomains(ctx, ctx.Repo.Repository.ID)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, convertPagesDomains(domains))
|
|
}
|
|
|
|
// AddPagesDomain adds a custom domain for pages
|
|
func AddPagesDomain(ctx *context.APIContext) {
|
|
// swagger:operation POST /repos/{owner}/{repo}/pages/domains repository repoAddPagesDomain
|
|
// ---
|
|
// summary: Add a custom domain for repository pages
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: owner
|
|
// in: path
|
|
// description: owner of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: repo
|
|
// in: path
|
|
// description: name of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: body
|
|
// in: body
|
|
// schema:
|
|
// "$ref": "#/definitions/AddPagesDomainOption"
|
|
// responses:
|
|
// "201":
|
|
// "$ref": "#/responses/PagesDomain"
|
|
// "403":
|
|
// "$ref": "#/responses/forbidden"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
// "422":
|
|
// "$ref": "#/responses/validationError"
|
|
|
|
form := web.GetForm(ctx).(*api.AddPagesDomainOption)
|
|
|
|
domain, err := pages_service.AddPagesDomain(ctx, ctx.Repo.Repository.ID, form.Domain)
|
|
if err != nil {
|
|
if repo_model.IsErrPagesDomainAlreadyExist(err) {
|
|
ctx.APIError(http.StatusUnprocessableEntity, "Domain already exists")
|
|
return
|
|
}
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusCreated, convertPagesDomain(domain))
|
|
}
|
|
|
|
// DeletePagesDomain removes a custom domain
|
|
func DeletePagesDomain(ctx *context.APIContext) {
|
|
// swagger:operation DELETE /repos/{owner}/{repo}/pages/domains/{domain} repository repoDeletePagesDomain
|
|
// ---
|
|
// summary: Remove a custom domain for repository pages
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: owner
|
|
// in: path
|
|
// description: owner of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: repo
|
|
// in: path
|
|
// description: name of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: domain
|
|
// in: path
|
|
// description: the domain to remove
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "204":
|
|
// "$ref": "#/responses/empty"
|
|
// "403":
|
|
// "$ref": "#/responses/forbidden"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
domainName := ctx.PathParam("domain")
|
|
|
|
// Find the domain
|
|
domain, err := repo_model.GetPagesDomainByDomain(ctx, domainName)
|
|
if err != nil {
|
|
if repo_model.IsErrPagesDomainNotExist(err) {
|
|
ctx.APIErrorNotFound()
|
|
return
|
|
}
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
// Verify domain belongs to this repo
|
|
if domain.RepoID != ctx.Repo.Repository.ID {
|
|
ctx.APIErrorNotFound()
|
|
return
|
|
}
|
|
|
|
if err := repo_model.DeletePagesDomain(ctx, domain.ID); err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
ctx.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// VerifyPagesDomain verifies a custom domain
|
|
func VerifyPagesDomain(ctx *context.APIContext) {
|
|
// swagger:operation POST /repos/{owner}/{repo}/pages/domains/{domain}/verify repository repoVerifyPagesDomain
|
|
// ---
|
|
// summary: Verify a custom domain for repository pages
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: owner
|
|
// in: path
|
|
// description: owner of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: repo
|
|
// in: path
|
|
// description: name of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: domain
|
|
// in: path
|
|
// description: the domain to verify
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/PagesDomain"
|
|
// "403":
|
|
// "$ref": "#/responses/forbidden"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
domainName := ctx.PathParam("domain")
|
|
|
|
// Find the domain
|
|
domain, err := repo_model.GetPagesDomainByDomain(ctx, domainName)
|
|
if err != nil {
|
|
if repo_model.IsErrPagesDomainNotExist(err) {
|
|
ctx.APIErrorNotFound()
|
|
return
|
|
}
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
// Verify domain belongs to this repo
|
|
if domain.RepoID != ctx.Repo.Repository.ID {
|
|
ctx.APIErrorNotFound()
|
|
return
|
|
}
|
|
|
|
if err := pages_service.VerifyDomain(ctx, domain.ID); err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
// Reload domain
|
|
domain, err = repo_model.GetPagesDomainByID(ctx, domain.ID)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, convertPagesDomain(domain))
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func getTemplateOrDefault(config interface{}) string {
|
|
if config == nil {
|
|
return "simple"
|
|
}
|
|
// Try to get template from config
|
|
return "simple"
|
|
}
|
|
|
|
func convertPagesDomain(domain *repo_model.PagesDomain) *api.PagesDomain {
|
|
return &api.PagesDomain{
|
|
ID: domain.ID,
|
|
Domain: domain.Domain,
|
|
Verified: domain.Verified,
|
|
VerificationToken: domain.VerificationToken,
|
|
SSLStatus: string(domain.SSLStatus),
|
|
SSLCertExpiry: domain.SSLCertExpiry.AsTime(),
|
|
Created: domain.CreatedUnix.AsTime(),
|
|
Verified_At: domain.VerifiedUnix.AsTime(),
|
|
}
|
|
}
|
|
|
|
func convertPagesDomains(domains []*repo_model.PagesDomain) []*api.PagesDomain {
|
|
result := make([]*api.PagesDomain, len(domains))
|
|
for i, d := range domains {
|
|
result[i] = convertPagesDomain(d)
|
|
}
|
|
return result
|
|
}
|