Some checks failed
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 1m34s
Build and Release / Lint (push) Failing after 1m53s
Build and Release / Build Binaries (amd64, darwin) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux) (push) Has been skipped
Build and Release / Build Binaries (amd64, windows) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin) (push) Has been skipped
Build and Release / Build Binaries (arm64, linux) (push) Has been skipped
Build and Release / Unit Tests (push) Failing after 2m5s
- Add public_landing option to allow private repos to have public landing pages
- Add public_releases option to allow private repos to serve releases publicly
- Add /api/v2/repos/{owner}/{repo}/releases/update endpoint for Electron/Squirrel compatible app updates
- Add /api/v2/repos/{owner}/{repo}/pages/config and /content endpoints
- Add repoAssignmentWithPublicAccess middleware to bypass auth for public landing/releases
- Update README with documentation for new features
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
161 lines
3.8 KiB
Go
161 lines
3.8 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package v2
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
repo_model "code.gitea.io/gitea/models/repo"
|
|
"code.gitea.io/gitea/modules/git"
|
|
pages_module "code.gitea.io/gitea/modules/pages"
|
|
"code.gitea.io/gitea/services/context"
|
|
pages_service "code.gitea.io/gitea/services/pages"
|
|
)
|
|
|
|
// PagesConfigResponse represents the pages configuration for a repository
|
|
type PagesConfigResponse struct {
|
|
Enabled bool `json:"enabled"`
|
|
PublicLanding bool `json:"public_landing"`
|
|
Template string `json:"template"`
|
|
Domain string `json:"domain,omitempty"`
|
|
Branding pages_module.BrandingConfig `json:"branding,omitempty"`
|
|
Hero pages_module.HeroConfig `json:"hero,omitempty"`
|
|
SEO pages_module.SEOConfig `json:"seo,omitempty"`
|
|
Footer pages_module.FooterConfig `json:"footer,omitempty"`
|
|
}
|
|
|
|
// PagesContentResponse represents the rendered content for a landing page
|
|
type PagesContentResponse struct {
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
Readme string `json:"readme,omitempty"`
|
|
}
|
|
|
|
// GetPagesConfig returns the pages configuration for a repository
|
|
// GET /api/v2/repos/{owner}/{repo}/pages/config
|
|
func GetPagesConfig(ctx *context.APIContext) {
|
|
repo := ctx.Repo.Repository
|
|
if repo == nil {
|
|
ctx.APIErrorNotFound("Repository not found")
|
|
return
|
|
}
|
|
|
|
config, err := pages_service.GetPagesConfig(ctx, repo)
|
|
if err != nil {
|
|
ctx.APIErrorNotFound("Pages not configured")
|
|
return
|
|
}
|
|
|
|
response := &PagesConfigResponse{
|
|
Enabled: config.Enabled,
|
|
PublicLanding: config.PublicLanding,
|
|
Template: config.Template,
|
|
Domain: config.Domain,
|
|
Branding: config.Branding,
|
|
Hero: config.Hero,
|
|
SEO: config.SEO,
|
|
Footer: config.Footer,
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// GetPagesContent returns the rendered content for a repository's landing page
|
|
// GET /api/v2/repos/{owner}/{repo}/pages/content
|
|
func GetPagesContent(ctx *context.APIContext) {
|
|
repo := ctx.Repo.Repository
|
|
if repo == nil {
|
|
ctx.APIErrorNotFound("Repository not found")
|
|
return
|
|
}
|
|
|
|
config, err := pages_service.GetPagesConfig(ctx, repo)
|
|
if err != nil || !config.Enabled {
|
|
ctx.APIErrorNotFound("Pages not enabled")
|
|
return
|
|
}
|
|
|
|
// Load README content
|
|
readme := loadReadmeContent(ctx, repo)
|
|
|
|
// Build title
|
|
title := config.SEO.Title
|
|
if title == "" {
|
|
title = config.Hero.Title
|
|
}
|
|
if title == "" {
|
|
title = repo.Name
|
|
}
|
|
|
|
// Build description
|
|
description := config.SEO.Description
|
|
if description == "" {
|
|
description = config.Hero.Tagline
|
|
}
|
|
if description == "" {
|
|
description = repo.Description
|
|
}
|
|
|
|
response := &PagesContentResponse{
|
|
Title: title,
|
|
Description: description,
|
|
Readme: readme,
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// loadReadmeContent loads the README content from the repository
|
|
func loadReadmeContent(ctx *context.APIContext, repo *repo_model.Repository) string {
|
|
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
defer gitRepo.Close()
|
|
|
|
branch := repo.DefaultBranch
|
|
if branch == "" {
|
|
branch = "main"
|
|
}
|
|
|
|
commit, err := gitRepo.GetBranchCommit(branch)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
// Try common README paths
|
|
readmePaths := []string{
|
|
"README.md",
|
|
"readme.md",
|
|
"Readme.md",
|
|
"README.markdown",
|
|
"README.txt",
|
|
"README",
|
|
}
|
|
|
|
for _, path := range readmePaths {
|
|
entry, err := commit.GetTreeEntryByPath(path)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
reader, err := entry.Blob().DataAsync()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
content := make([]byte, entry.Blob().Size())
|
|
_, err = reader.Read(content)
|
|
reader.Close()
|
|
|
|
if err != nil && err.Error() != "EOF" {
|
|
continue
|
|
}
|
|
|
|
return string(content)
|
|
}
|
|
|
|
return ""
|
|
}
|