Files
gitea/routers/api/v2/pages_api.go
logikonline 18bb922839
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
feat(api): Add v2 API for public releases and app updates
- 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>
2026-01-10 08:10:41 -05:00

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 ""
}