2
0
Files
gitcaddy-server/routers/api/v2/pages_api.go
logikonline c5786aab2b
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m48s
Build and Release / Lint (push) Failing after 8m37s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binary (linux/arm64) (push) Has been skipped
Build and Release / Unit Tests (push) Successful in 8m53s
fix(api): use internal json module in pages API
Replace stdlib encoding/json with internal json module for consistency with rest of codebase. Ensures proper handling of edge cases and custom marshaling behavior.
2026-03-17 01:15:01 -04:00

839 lines
22 KiB
Go

// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT
package v2
import (
"code.gitcaddy.com/server/v3/modules/json"
"net/http"
repo_model "code.gitcaddy.com/server/v3/models/repo"
apierrors "code.gitcaddy.com/server/v3/modules/errors"
"code.gitcaddy.com/server/v3/modules/git"
pages_module "code.gitcaddy.com/server/v3/modules/pages"
api "code.gitcaddy.com/server/v3/modules/structs"
"code.gitcaddy.com/server/v3/modules/web"
"code.gitcaddy.com/server/v3/services/context"
pages_service "code.gitcaddy.com/server/v3/services/pages"
)
// PagesFullConfigResponse represents the complete landing page configuration
type PagesFullConfigResponse struct {
Enabled bool `json:"enabled"`
PublicLanding bool `json:"public_landing"`
Template string `json:"template"`
Domain string `json:"domain,omitempty"`
Brand pages_module.BrandConfig `json:"brand"`
Hero pages_module.HeroConfig `json:"hero"`
Stats []pages_module.StatConfig `json:"stats"`
ValueProps []pages_module.ValuePropConfig `json:"value_props"`
Features []pages_module.FeatureConfig `json:"features"`
SocialProof pages_module.SocialProofConfig `json:"social_proof"`
Pricing pages_module.PricingConfig `json:"pricing"`
CTASection pages_module.CTASectionConfig `json:"cta_section"`
Blog pages_module.BlogSectionConfig `json:"blog"`
Gallery pages_module.GallerySectionConfig `json:"gallery"`
Comparison pages_module.ComparisonSectionConfig `json:"comparison"`
Navigation pages_module.NavigationConfig `json:"navigation"`
Footer pages_module.FooterConfig `json:"footer"`
Theme pages_module.ThemeConfig `json:"theme"`
SEO pages_module.SEOConfig `json:"seo"`
Analytics pages_module.AnalyticsConfig `json:"analytics"`
Advanced pages_module.AdvancedConfig `json:"advanced"`
}
// 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"`
}
// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------
func buildFullResponse(config *pages_module.LandingConfig) *PagesFullConfigResponse {
return &PagesFullConfigResponse{
Enabled: config.Enabled,
PublicLanding: config.PublicLanding,
Template: config.Template,
Domain: config.Domain,
Brand: config.Brand,
Hero: config.Hero,
Stats: config.Stats,
ValueProps: config.ValueProps,
Features: config.Features,
SocialProof: config.SocialProof,
Pricing: config.Pricing,
CTASection: config.CTASection,
Blog: config.Blog,
Gallery: config.Gallery,
Comparison: config.Comparison,
Navigation: config.Navigation,
Footer: config.Footer,
Theme: config.Theme,
SEO: config.SEO,
Analytics: config.Analytics,
Advanced: config.Advanced,
}
}
func getPagesConfigAPI(ctx *context.APIContext) (*pages_module.LandingConfig, bool) {
config, err := pages_service.GetPagesConfig(ctx, ctx.Repo.Repository)
if err != nil {
ctx.APIErrorWithCode(apierrors.PagesNotConfigured)
return nil, false
}
return config, true
}
func savePagesConfigAPI(ctx *context.APIContext, config *pages_module.LandingConfig) bool {
configJSON, err := json.Marshal(config)
if err != nil {
ctx.APIErrorInternal(err)
return false
}
dbConfig, err := repo_model.GetPagesConfigByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil {
if repo_model.IsErrPagesConfigNotExist(err) {
dbConfig = &repo_model.PagesConfig{
RepoID: ctx.Repo.Repository.ID,
Enabled: config.Enabled,
Template: repo_model.PagesTemplate(config.Template),
ConfigJSON: string(configJSON),
}
if err := repo_model.CreatePagesConfig(ctx, dbConfig); err != nil {
ctx.APIErrorInternal(err)
return false
}
return true
}
ctx.APIErrorInternal(err)
return false
}
dbConfig.Enabled = config.Enabled
dbConfig.Template = repo_model.PagesTemplate(config.Template)
dbConfig.ConfigJSON = string(configJSON)
if err := repo_model.UpdatePagesConfig(ctx, dbConfig); err != nil {
ctx.APIErrorInternal(err)
return false
}
return true
}
func requirePagesAdmin(ctx *context.APIContext) bool {
if !ctx.Repo.Permission.IsAdmin() && !ctx.IsUserSiteAdmin() {
ctx.APIErrorWithCode(apierrors.PermRepoAdminRequired)
return false
}
return true
}
// ---------------------------------------------------------------------------
// Apply helpers — map API option structs to config structs
// ---------------------------------------------------------------------------
func applyCTAButton(dst *pages_module.CTAButton, src *api.PagesCTAButtonOption) {
if src == nil {
return
}
if src.Label != nil {
dst.Label = *src.Label
}
if src.URL != nil {
dst.URL = *src.URL
}
if src.Variant != nil {
dst.Variant = *src.Variant
}
}
func applyBrand(dst *pages_module.BrandConfig, src *api.UpdatePagesBrandOption) {
if src.Name != nil {
dst.Name = *src.Name
}
if src.LogoURL != nil {
dst.LogoURL = *src.LogoURL
}
if src.Tagline != nil {
dst.Tagline = *src.Tagline
}
if src.FaviconURL != nil {
dst.FaviconURL = *src.FaviconURL
}
}
func applyHero(dst *pages_module.HeroConfig, src *api.UpdatePagesHeroOption) {
if src.Headline != nil {
dst.Headline = *src.Headline
}
if src.Subheadline != nil {
dst.Subheadline = *src.Subheadline
}
if src.ImageURL != nil {
dst.ImageURL = *src.ImageURL
}
if src.VideoURL != nil {
dst.VideoURL = *src.VideoURL
}
if src.CodeExample != nil {
dst.CodeExample = *src.CodeExample
}
applyCTAButton(&dst.PrimaryCTA, src.PrimaryCTA)
applyCTAButton(&dst.SecondaryCTA, src.SecondaryCTA)
}
func applyStats(config *pages_module.LandingConfig, src []api.PagesStatOption) {
config.Stats = make([]pages_module.StatConfig, len(src))
for i, s := range src {
config.Stats[i] = pages_module.StatConfig{Value: s.Value, Label: s.Label}
}
}
func applyValueProps(config *pages_module.LandingConfig, src []api.PagesValuePropOption) {
config.ValueProps = make([]pages_module.ValuePropConfig, len(src))
for i, v := range src {
config.ValueProps[i] = pages_module.ValuePropConfig{Title: v.Title, Description: v.Description, Icon: v.Icon}
}
}
func applyFeatures(config *pages_module.LandingConfig, src []api.PagesFeatureOption) {
config.Features = make([]pages_module.FeatureConfig, len(src))
for i, f := range src {
config.Features[i] = pages_module.FeatureConfig{Title: f.Title, Description: f.Description, Icon: f.Icon, ImageURL: f.ImageURL}
}
}
func applySocial(dst *pages_module.SocialProofConfig, src *api.UpdatePagesSocialOption) {
if src.Logos != nil {
dst.Logos = *src.Logos
}
if src.Testimonials != nil {
dst.Testimonials = make([]pages_module.TestimonialConfig, len(*src.Testimonials))
for i, t := range *src.Testimonials {
dst.Testimonials[i] = pages_module.TestimonialConfig{Quote: t.Quote, Author: t.Author, Role: t.Role, Avatar: t.Avatar}
}
}
}
func applyPricing(dst *pages_module.PricingConfig, src *api.UpdatePagesPricingOption) {
if src.Headline != nil {
dst.Headline = *src.Headline
}
if src.Subheadline != nil {
dst.Subheadline = *src.Subheadline
}
if src.Plans != nil {
dst.Plans = make([]pages_module.PricingPlanConfig, len(*src.Plans))
for i, p := range *src.Plans {
dst.Plans[i] = pages_module.PricingPlanConfig{
Name: p.Name, Price: p.Price, Period: p.Period,
Features: p.Features, CTA: p.CTA, Featured: p.Featured,
}
}
}
}
func applyCTASection(dst *pages_module.CTASectionConfig, src *api.UpdatePagesCTAOption) {
if src.Headline != nil {
dst.Headline = *src.Headline
}
if src.Subheadline != nil {
dst.Subheadline = *src.Subheadline
}
applyCTAButton(&dst.Button, src.Button)
}
func applyBlog(dst *pages_module.BlogSectionConfig, src *api.UpdatePagesBlogOption) {
if src.Enabled != nil {
dst.Enabled = *src.Enabled
}
if src.Headline != nil {
dst.Headline = *src.Headline
}
if src.Subheadline != nil {
dst.Subheadline = *src.Subheadline
}
if src.MaxPosts != nil {
dst.MaxPosts = *src.MaxPosts
}
}
func applyGallery(dst *pages_module.GallerySectionConfig, src *api.UpdatePagesGalleryOption) {
if src.Enabled != nil {
dst.Enabled = *src.Enabled
}
if src.Headline != nil {
dst.Headline = *src.Headline
}
if src.Subheadline != nil {
dst.Subheadline = *src.Subheadline
}
if src.MaxImages != nil {
dst.MaxImages = *src.MaxImages
}
if src.Columns != nil {
dst.Columns = *src.Columns
}
}
func applyComparison(dst *pages_module.ComparisonSectionConfig, src *api.UpdatePagesComparisonOption) {
if src.Enabled != nil {
dst.Enabled = *src.Enabled
}
if src.Headline != nil {
dst.Headline = *src.Headline
}
if src.Subheadline != nil {
dst.Subheadline = *src.Subheadline
}
if src.Columns != nil {
dst.Columns = make([]pages_module.ComparisonColumnConfig, len(*src.Columns))
for i, c := range *src.Columns {
dst.Columns[i] = pages_module.ComparisonColumnConfig{Name: c.Name, Highlight: c.Highlight}
}
}
if src.Groups != nil {
dst.Groups = make([]pages_module.ComparisonGroupConfig, len(*src.Groups))
for i, g := range *src.Groups {
features := make([]pages_module.ComparisonFeatureConfig, len(g.Features))
for j, f := range g.Features {
features[j] = pages_module.ComparisonFeatureConfig{Name: f.Name, Values: f.Values}
}
dst.Groups[i] = pages_module.ComparisonGroupConfig{Name: g.Name, Features: features}
}
}
}
func applyNavigation(dst *pages_module.NavigationConfig, src *api.UpdatePagesNavOption) {
if src.ShowDocs != nil {
dst.ShowDocs = *src.ShowDocs
}
if src.ShowAPI != nil {
dst.ShowAPI = *src.ShowAPI
}
if src.ShowRepository != nil {
dst.ShowRepository = *src.ShowRepository
}
if src.ShowReleases != nil {
dst.ShowReleases = *src.ShowReleases
}
if src.ShowIssues != nil {
dst.ShowIssues = *src.ShowIssues
}
}
func applyFooter(dst *pages_module.FooterConfig, ctaDst *pages_module.CTASectionConfig, src *api.UpdatePagesFooterOption) {
if src.Copyright != nil {
dst.Copyright = *src.Copyright
}
if src.ShowPoweredBy != nil {
dst.ShowPoweredBy = *src.ShowPoweredBy
}
if src.Links != nil {
dst.Links = make([]pages_module.FooterLink, len(*src.Links))
for i, l := range *src.Links {
dst.Links[i] = pages_module.FooterLink{Label: l.Label, URL: l.URL}
}
}
if src.Social != nil {
dst.Social = make([]pages_module.SocialLink, len(*src.Social))
for i, s := range *src.Social {
dst.Social[i] = pages_module.SocialLink{Platform: s.Platform, URL: s.URL}
}
}
if src.CTASection != nil {
applyCTASection(ctaDst, src.CTASection)
}
}
func applyTheme(dst *pages_module.ThemeConfig, src *api.UpdatePagesThemeOption) {
if src.PrimaryColor != nil {
dst.PrimaryColor = *src.PrimaryColor
}
if src.AccentColor != nil {
dst.AccentColor = *src.AccentColor
}
if src.Mode != nil {
dst.Mode = *src.Mode
}
}
func applySEO(dst *pages_module.SEOConfig, src *api.UpdatePagesSEOOption) {
if src.Title != nil {
dst.Title = *src.Title
}
if src.Description != nil {
dst.Description = *src.Description
}
if src.Keywords != nil {
dst.Keywords = *src.Keywords
}
if src.OGImage != nil {
dst.OGImage = *src.OGImage
}
if src.UseMediaKitOG != nil {
dst.UseMediaKitOG = *src.UseMediaKitOG
}
if src.TwitterCard != nil {
dst.TwitterCard = *src.TwitterCard
}
if src.TwitterSite != nil {
dst.TwitterSite = *src.TwitterSite
}
}
func applyAdvanced(dst *pages_module.AdvancedConfig, src *api.UpdatePagesAdvancedOption) {
if src.CustomCSS != nil {
dst.CustomCSS = *src.CustomCSS
}
if src.CustomHead != nil {
dst.CustomHead = *src.CustomHead
}
if src.PublicReleases != nil {
dst.PublicReleases = *src.PublicReleases
}
if src.HideMobileReleases != nil {
dst.HideMobileReleases = *src.HideMobileReleases
}
if src.GooglePlayID != nil {
dst.GooglePlayID = *src.GooglePlayID
}
if src.AppStoreID != nil {
dst.AppStoreID = *src.AppStoreID
}
}
// applyFullConfig applies all non-nil sections from the update option to the config
func applyFullConfig(config *pages_module.LandingConfig, form *api.UpdatePagesConfigOption) {
if form.Enabled != nil {
config.Enabled = *form.Enabled
}
if form.PublicLanding != nil {
config.PublicLanding = *form.PublicLanding
}
if form.Template != nil {
config.Template = *form.Template
}
if form.Brand != nil {
applyBrand(&config.Brand, form.Brand)
}
if form.Hero != nil {
applyHero(&config.Hero, form.Hero)
}
if form.Stats != nil {
applyStats(config, *form.Stats)
}
if form.ValueProps != nil {
applyValueProps(config, *form.ValueProps)
}
if form.Features != nil {
applyFeatures(config, *form.Features)
}
if form.SocialProof != nil {
applySocial(&config.SocialProof, form.SocialProof)
}
if form.Pricing != nil {
applyPricing(&config.Pricing, form.Pricing)
}
if form.CTASection != nil {
applyCTASection(&config.CTASection, form.CTASection)
}
if form.Blog != nil {
applyBlog(&config.Blog, form.Blog)
}
if form.Gallery != nil {
applyGallery(&config.Gallery, form.Gallery)
}
if form.Comparison != nil {
applyComparison(&config.Comparison, form.Comparison)
}
if form.Navigation != nil {
applyNavigation(&config.Navigation, form.Navigation)
}
if form.Footer != nil {
applyFooter(&config.Footer, &config.CTASection, form.Footer)
}
if form.Theme != nil {
applyTheme(&config.Theme, form.Theme)
}
if form.SEO != nil {
applySEO(&config.SEO, form.SEO)
}
if form.Advanced != nil {
applyAdvanced(&config.Advanced, form.Advanced)
}
}
// ---------------------------------------------------------------------------
// GET endpoints
// ---------------------------------------------------------------------------
// GetPagesConfig returns the full 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.APIErrorWithCode(apierrors.PagesNotConfigured)
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// 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.APIErrorWithCode(apierrors.PagesNotEnabled)
return
}
readme := loadReadmeContent(ctx, repo)
title := config.SEO.Title
if title == "" {
title = config.Hero.Headline
}
if title == "" {
title = config.Brand.Name
}
if title == "" {
title = repo.Name
}
description := config.SEO.Description
if description == "" {
description = config.Hero.Subheadline
}
if description == "" {
description = repo.Description
}
ctx.JSON(http.StatusOK, &PagesContentResponse{
Title: title,
Description: description,
Readme: readme,
})
}
// ---------------------------------------------------------------------------
// PUT /config — replace full config
// ---------------------------------------------------------------------------
// UpdatePagesConfig replaces the entire landing page configuration
// PUT /api/v2/repos/{owner}/{repo}/pages/config
func UpdatePagesConfig(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesConfigOption)
if form.Template != nil && !pages_module.IsValidTemplate(*form.Template) {
ctx.APIErrorWithCode(apierrors.PagesInvalidTemplate)
return
}
applyFullConfig(config, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// ---------------------------------------------------------------------------
// PATCH /config — partial merge
// ---------------------------------------------------------------------------
// PatchPagesConfig partially updates the landing page configuration
// PATCH /api/v2/repos/{owner}/{repo}/pages/config
func PatchPagesConfig(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesConfigOption)
if form.Template != nil && !pages_module.IsValidTemplate(*form.Template) {
ctx.APIErrorWithCode(apierrors.PagesInvalidTemplate)
return
}
applyFullConfig(config, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// ---------------------------------------------------------------------------
// Section PUT endpoints
// ---------------------------------------------------------------------------
// UpdatePagesBrand updates the brand section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/brand
func UpdatePagesBrand(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesBrandOption)
applyBrand(&config.Brand, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesHero updates the hero section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/hero
func UpdatePagesHero(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesHeroOption)
applyHero(&config.Hero, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesContentSection updates the content section (blog, gallery, stats, features, nav, etc.)
// PUT /api/v2/repos/{owner}/{repo}/pages/config/content
func UpdatePagesContentSection(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesContentOption)
if form.Blog != nil {
applyBlog(&config.Blog, form.Blog)
}
if form.Gallery != nil {
applyGallery(&config.Gallery, form.Gallery)
}
if form.ComparisonEnabled != nil {
config.Comparison.Enabled = *form.ComparisonEnabled
}
if form.Stats != nil {
applyStats(config, *form.Stats)
}
if form.ValueProps != nil {
applyValueProps(config, *form.ValueProps)
}
if form.Features != nil {
applyFeatures(config, *form.Features)
}
if form.Navigation != nil {
applyNavigation(&config.Navigation, form.Navigation)
}
if form.Advanced != nil {
applyAdvanced(&config.Advanced, form.Advanced)
}
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesComparison updates the comparison section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/comparison
func UpdatePagesComparison(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesComparisonOption)
applyComparison(&config.Comparison, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesSocial updates the social proof section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/social
func UpdatePagesSocial(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesSocialOption)
applySocial(&config.SocialProof, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesPricing updates the pricing section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/pricing
func UpdatePagesPricing(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesPricingOption)
applyPricing(&config.Pricing, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesFooter updates the footer and CTA section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/footer
func UpdatePagesFooter(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesFooterOption)
applyFooter(&config.Footer, &config.CTASection, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesTheme updates the theme and SEO section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/theme
func UpdatePagesTheme(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesThemeOption)
applyTheme(&config.Theme, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// ---------------------------------------------------------------------------
// Utility
// ---------------------------------------------------------------------------
// 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 ""
}
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 ""
}