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
Replace stdlib encoding/json with internal json module for consistency with rest of codebase. Ensures proper handling of edge cases and custom marshaling behavior.
839 lines
22 KiB
Go
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 ""
|
|
}
|