Some checks failed
Build and Release / Create Release (push) Successful in 1s
Build and Release / Unit Tests (push) Successful in 3m42s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 9m25s
Build and Release / Lint (push) Failing after 10m12s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
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 Binary (linux/arm64) (push) Has been skipped
Creates mcp_pages.go with 10 new tools for managing repository landing pages via Claude Code. Supports getting/updating brand, hero, pricing, comparison, features, social proof, SEO, and theme sections. Includes template listing and enable/disable functionality. Integrates with existing pages service and registers tools in handleToolsList and handleToolsCall.
666 lines
22 KiB
Go
666 lines
22 KiB
Go
// Copyright 2026 MarketAlly. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package v2
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
repo_model "code.gitcaddy.com/server/v3/models/repo"
|
|
user_model "code.gitcaddy.com/server/v3/models/user"
|
|
"code.gitcaddy.com/server/v3/modules/json"
|
|
pages_module "code.gitcaddy.com/server/v3/modules/pages"
|
|
"code.gitcaddy.com/server/v3/services/context"
|
|
pages_service "code.gitcaddy.com/server/v3/services/pages"
|
|
)
|
|
|
|
// Landing Pages MCP Tools
|
|
var mcpPagesTools = []MCPTool{
|
|
{
|
|
Name: "get_landing_config",
|
|
Description: "Get the full landing page configuration for a repository. Returns all sections: brand, hero, pricing, comparison, features, social proof, SEO, and more.",
|
|
InputSchema: map[string]any{
|
|
"type": "object",
|
|
"required": []string{"owner", "repo"},
|
|
"properties": map[string]any{
|
|
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
|
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "list_landing_templates",
|
|
Description: "List available landing page templates with display names. Templates include: open-source-hero, saas-conversion, bold-marketing, developer-tool, and more.",
|
|
InputSchema: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{},
|
|
},
|
|
},
|
|
{
|
|
Name: "enable_landing_page",
|
|
Description: "Enable or disable the landing page for a repository. Optionally set a template.",
|
|
InputSchema: map[string]any{
|
|
"type": "object",
|
|
"required": []string{"owner", "repo", "enabled"},
|
|
"properties": map[string]any{
|
|
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
|
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
|
"enabled": map[string]any{"type": "boolean", "description": "Enable or disable"},
|
|
"template": map[string]any{"type": "string", "description": "Template name (e.g., 'saas-conversion', 'open-source-hero')"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "update_landing_brand",
|
|
Description: "Update the brand section of a landing page: name, logo URL, tagline, favicon.",
|
|
InputSchema: map[string]any{
|
|
"type": "object",
|
|
"required": []string{"owner", "repo"},
|
|
"properties": map[string]any{
|
|
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
|
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
|
"name": map[string]any{"type": "string", "description": "Brand name"},
|
|
"logo_url": map[string]any{"type": "string", "description": "Logo image URL"},
|
|
"tagline": map[string]any{"type": "string", "description": "Brand tagline"},
|
|
"favicon_url": map[string]any{"type": "string", "description": "Favicon URL"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "update_landing_hero",
|
|
Description: "Update the hero section: headline, subheadline, CTA buttons, hero image or video URL.",
|
|
InputSchema: map[string]any{
|
|
"type": "object",
|
|
"required": []string{"owner", "repo"},
|
|
"properties": map[string]any{
|
|
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
|
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
|
"headline": map[string]any{"type": "string", "description": "Main headline"},
|
|
"subheadline": map[string]any{"type": "string", "description": "Supporting text"},
|
|
"image_url": map[string]any{"type": "string", "description": "Hero image URL"},
|
|
"video_url": map[string]any{"type": "string", "description": "Hero video URL"},
|
|
"primary_cta_label": map[string]any{"type": "string", "description": "Primary CTA button label"},
|
|
"primary_cta_url": map[string]any{"type": "string", "description": "Primary CTA button URL"},
|
|
"secondary_cta_label": map[string]any{"type": "string", "description": "Secondary CTA button label"},
|
|
"secondary_cta_url": map[string]any{"type": "string", "description": "Secondary CTA button URL"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "update_landing_pricing",
|
|
Description: "Update the pricing section with plans. Each plan has a name, price, period, feature list, CTA, and optional 'featured' flag.",
|
|
InputSchema: map[string]any{
|
|
"type": "object",
|
|
"required": []string{"owner", "repo", "plans"},
|
|
"properties": map[string]any{
|
|
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
|
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
|
"headline": map[string]any{"type": "string", "description": "Pricing section headline"},
|
|
"subheadline": map[string]any{"type": "string", "description": "Pricing section subheadline"},
|
|
"plans": map[string]any{
|
|
"type": "array",
|
|
"items": map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"name": map[string]any{"type": "string"},
|
|
"price": map[string]any{"type": "string"},
|
|
"period": map[string]any{"type": "string"},
|
|
"features": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
|
|
"cta": map[string]any{"type": "string"},
|
|
"featured": map[string]any{"type": "boolean"},
|
|
},
|
|
},
|
|
"description": "Array of pricing plans",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "update_landing_comparison",
|
|
Description: "Update the feature comparison matrix. Define columns (products/tiers) and groups of features with per-column values.",
|
|
InputSchema: map[string]any{
|
|
"type": "object",
|
|
"required": []string{"owner", "repo"},
|
|
"properties": map[string]any{
|
|
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
|
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
|
"enabled": map[string]any{"type": "boolean", "description": "Enable comparison section"},
|
|
"headline": map[string]any{"type": "string", "description": "Section headline"},
|
|
"subheadline": map[string]any{"type": "string", "description": "Section subheadline"},
|
|
"columns": map[string]any{
|
|
"type": "array",
|
|
"items": map[string]any{"type": "string"},
|
|
"description": "Column headers (e.g., ['Free', 'Pro', 'Enterprise'])",
|
|
},
|
|
"groups": map[string]any{
|
|
"type": "array",
|
|
"items": map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"name": map[string]any{"type": "string"},
|
|
"features": map[string]any{
|
|
"type": "array",
|
|
"items": map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"name": map[string]any{"type": "string"},
|
|
"values": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"description": "Feature groups with per-column values",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "update_landing_features",
|
|
Description: "Update the features section with title, description, and optional icon/image for each feature.",
|
|
InputSchema: map[string]any{
|
|
"type": "object",
|
|
"required": []string{"owner", "repo", "features"},
|
|
"properties": map[string]any{
|
|
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
|
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
|
"features": map[string]any{
|
|
"type": "array",
|
|
"items": map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"title": map[string]any{"type": "string"},
|
|
"description": map[string]any{"type": "string"},
|
|
"icon": map[string]any{"type": "string"},
|
|
"image_url": map[string]any{"type": "string"},
|
|
},
|
|
},
|
|
"description": "Array of features",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "update_landing_social_proof",
|
|
Description: "Update testimonials and client logos for social proof.",
|
|
InputSchema: map[string]any{
|
|
"type": "object",
|
|
"required": []string{"owner", "repo"},
|
|
"properties": map[string]any{
|
|
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
|
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
|
"logos": map[string]any{
|
|
"type": "array",
|
|
"items": map[string]any{"type": "string"},
|
|
"description": "Client/partner logo URLs",
|
|
},
|
|
"testimonials": map[string]any{
|
|
"type": "array",
|
|
"items": map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"quote": map[string]any{"type": "string"},
|
|
"author": map[string]any{"type": "string"},
|
|
"role": map[string]any{"type": "string"},
|
|
"avatar": map[string]any{"type": "string"},
|
|
},
|
|
},
|
|
"description": "Testimonials",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "update_landing_seo",
|
|
Description: "Update SEO metadata: title, description, keywords, Open Graph image, Twitter card settings.",
|
|
InputSchema: map[string]any{
|
|
"type": "object",
|
|
"required": []string{"owner", "repo"},
|
|
"properties": map[string]any{
|
|
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
|
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
|
"title": map[string]any{"type": "string", "description": "SEO title"},
|
|
"description": map[string]any{"type": "string", "description": "SEO description"},
|
|
"keywords": map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "SEO keywords"},
|
|
"og_image": map[string]any{"type": "string", "description": "Open Graph image URL"},
|
|
"twitter_card": map[string]any{"type": "string", "description": "Twitter card type (summary, summary_large_image)"},
|
|
"twitter_site": map[string]any{"type": "string", "description": "Twitter @handle"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "update_landing_theme",
|
|
Description: "Update the visual theme: primary color, accent color, light/dark/auto mode.",
|
|
InputSchema: map[string]any{
|
|
"type": "object",
|
|
"required": []string{"owner", "repo"},
|
|
"properties": map[string]any{
|
|
"owner": map[string]any{"type": "string", "description": "Repository owner"},
|
|
"repo": map[string]any{"type": "string", "description": "Repository name"},
|
|
"primary_color": map[string]any{"type": "string", "description": "Primary brand color (hex, e.g., '#512BD4')"},
|
|
"accent_color": map[string]any{"type": "string", "description": "Accent color (hex)"},
|
|
"mode": map[string]any{"type": "string", "enum": []string{"light", "dark", "auto"}, "description": "Color mode"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// ── Tool Implementations ──────────────────────────────────
|
|
|
|
func toolGetLandingConfig(ctx *context.APIContext, args map[string]any) (any, error) {
|
|
owner, repo, err := resolveOwnerRepo(args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
repoObj, err := getRepoByOwnerAndName(ctx, owner, repo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
config, err := pages_service.GetPagesConfig(ctx, repoObj)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get pages config: %w", err)
|
|
}
|
|
if config == nil {
|
|
return map[string]any{"enabled": false, "message": "No landing page configured"}, nil
|
|
}
|
|
|
|
return buildFullResponse(config), nil
|
|
}
|
|
|
|
func toolListLandingTemplates(ctx *context.APIContext, args map[string]any) (any, error) {
|
|
templates := pages_module.ValidTemplates()
|
|
displayNames := pages_module.TemplateDisplayNames()
|
|
|
|
result := make([]map[string]string, 0, len(templates))
|
|
for _, t := range templates {
|
|
result = append(result, map[string]string{
|
|
"id": t,
|
|
"name": displayNames[t],
|
|
})
|
|
}
|
|
return map[string]any{"templates": result}, nil
|
|
}
|
|
|
|
func toolEnableLandingPage(ctx *context.APIContext, args map[string]any) (any, error) {
|
|
owner, repo, err := resolveOwnerRepo(args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
repoObj, err := getRepoByOwnerAndName(ctx, owner, repo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
enabled, _ := args["enabled"].(bool)
|
|
template, _ := args["template"].(string)
|
|
|
|
if enabled {
|
|
if template == "" {
|
|
template = "open-source-hero"
|
|
}
|
|
if !pages_module.IsValidTemplate(template) {
|
|
return nil, fmt.Errorf("invalid template: %s", template)
|
|
}
|
|
if err := pages_service.EnablePages(ctx, repoObj, template); err != nil {
|
|
return nil, fmt.Errorf("enable pages: %w", err)
|
|
}
|
|
return map[string]any{"enabled": true, "template": template}, nil
|
|
}
|
|
|
|
if err := pages_service.DisablePages(ctx, repoObj); err != nil {
|
|
return nil, fmt.Errorf("disable pages: %w", err)
|
|
}
|
|
return map[string]any{"enabled": false}, nil
|
|
}
|
|
|
|
func toolUpdateLandingBrand(ctx *context.APIContext, args map[string]any) (any, error) {
|
|
config, repoObj, err := getConfigForUpdate(ctx, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if v, ok := args["name"].(string); ok {
|
|
config.Brand.Name = v
|
|
}
|
|
if v, ok := args["logo_url"].(string); ok {
|
|
config.Brand.LogoURL = v
|
|
}
|
|
if v, ok := args["tagline"].(string); ok {
|
|
config.Brand.Tagline = v
|
|
}
|
|
if v, ok := args["favicon_url"].(string); ok {
|
|
config.Brand.FaviconURL = v
|
|
}
|
|
|
|
return saveAndReturn(ctx, repoObj, config, "brand")
|
|
}
|
|
|
|
func toolUpdateLandingHero(ctx *context.APIContext, args map[string]any) (any, error) {
|
|
config, repoObj, err := getConfigForUpdate(ctx, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if v, ok := args["headline"].(string); ok {
|
|
config.Hero.Headline = v
|
|
}
|
|
if v, ok := args["subheadline"].(string); ok {
|
|
config.Hero.Subheadline = v
|
|
}
|
|
if v, ok := args["image_url"].(string); ok {
|
|
config.Hero.ImageURL = v
|
|
}
|
|
if v, ok := args["video_url"].(string); ok {
|
|
config.Hero.VideoURL = v
|
|
}
|
|
if v, ok := args["primary_cta_label"].(string); ok {
|
|
config.Hero.PrimaryCTA.Label = v
|
|
}
|
|
if v, ok := args["primary_cta_url"].(string); ok {
|
|
config.Hero.PrimaryCTA.URL = v
|
|
}
|
|
if v, ok := args["secondary_cta_label"].(string); ok {
|
|
config.Hero.SecondaryCTA.Label = v
|
|
}
|
|
if v, ok := args["secondary_cta_url"].(string); ok {
|
|
config.Hero.SecondaryCTA.URL = v
|
|
}
|
|
|
|
return saveAndReturn(ctx, repoObj, config, "hero")
|
|
}
|
|
|
|
func toolUpdateLandingPricing(ctx *context.APIContext, args map[string]any) (any, error) {
|
|
config, repoObj, err := getConfigForUpdate(ctx, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if v, ok := args["headline"].(string); ok {
|
|
config.Pricing.Headline = v
|
|
}
|
|
if v, ok := args["subheadline"].(string); ok {
|
|
config.Pricing.Subheadline = v
|
|
}
|
|
if plans, ok := args["plans"].([]any); ok {
|
|
config.Pricing.Plans = nil
|
|
for _, p := range plans {
|
|
if pm, ok := p.(map[string]any); ok {
|
|
plan := pages_module.PricingPlanConfig{
|
|
Name: strVal(pm, "name"),
|
|
Price: strVal(pm, "price"),
|
|
Period: strVal(pm, "period"),
|
|
CTA: strVal(pm, "cta"),
|
|
Featured: boolVal(pm, "featured"),
|
|
}
|
|
if features, ok := pm["features"].([]any); ok {
|
|
for _, f := range features {
|
|
if s, ok := f.(string); ok {
|
|
plan.Features = append(plan.Features, s)
|
|
}
|
|
}
|
|
}
|
|
config.Pricing.Plans = append(config.Pricing.Plans, plan)
|
|
}
|
|
}
|
|
}
|
|
|
|
return saveAndReturn(ctx, repoObj, config, "pricing")
|
|
}
|
|
|
|
func toolUpdateLandingComparison(ctx *context.APIContext, args map[string]any) (any, error) {
|
|
config, repoObj, err := getConfigForUpdate(ctx, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if v, ok := args["enabled"].(bool); ok {
|
|
config.Comparison.Enabled = v
|
|
}
|
|
if v, ok := args["headline"].(string); ok {
|
|
config.Comparison.Headline = v
|
|
}
|
|
if v, ok := args["subheadline"].(string); ok {
|
|
config.Comparison.Subheadline = v
|
|
}
|
|
if cols, ok := args["columns"].([]any); ok {
|
|
config.Comparison.Columns = nil
|
|
for _, c := range cols {
|
|
if s, ok := c.(string); ok {
|
|
config.Comparison.Columns = append(config.Comparison.Columns, pages_module.ComparisonColumnConfig{Name: s})
|
|
} else if cm, ok := c.(map[string]any); ok {
|
|
config.Comparison.Columns = append(config.Comparison.Columns, pages_module.ComparisonColumnConfig{
|
|
Name: strVal(cm, "name"),
|
|
Highlight: boolVal(cm, "highlight"),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
if groups, ok := args["groups"].([]any); ok {
|
|
config.Comparison.Groups = nil
|
|
for _, g := range groups {
|
|
if gm, ok := g.(map[string]any); ok {
|
|
group := pages_module.ComparisonGroupConfig{Name: strVal(gm, "name")}
|
|
if features, ok := gm["features"].([]any); ok {
|
|
for _, f := range features {
|
|
if fm, ok := f.(map[string]any); ok {
|
|
feature := pages_module.ComparisonFeatureConfig{Name: strVal(fm, "name")}
|
|
if vals, ok := fm["values"].([]any); ok {
|
|
for _, v := range vals {
|
|
if s, ok := v.(string); ok {
|
|
feature.Values = append(feature.Values, s)
|
|
}
|
|
}
|
|
}
|
|
group.Features = append(group.Features, feature)
|
|
}
|
|
}
|
|
}
|
|
config.Comparison.Groups = append(config.Comparison.Groups, group)
|
|
}
|
|
}
|
|
}
|
|
|
|
return saveAndReturn(ctx, repoObj, config, "comparison")
|
|
}
|
|
|
|
func toolUpdateLandingFeatures(ctx *context.APIContext, args map[string]any) (any, error) {
|
|
config, repoObj, err := getConfigForUpdate(ctx, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if features, ok := args["features"].([]any); ok {
|
|
config.Features = nil
|
|
for _, f := range features {
|
|
if fm, ok := f.(map[string]any); ok {
|
|
config.Features = append(config.Features, pages_module.FeatureConfig{
|
|
Title: strVal(fm, "title"),
|
|
Description: strVal(fm, "description"),
|
|
Icon: strVal(fm, "icon"),
|
|
ImageURL: strVal(fm, "image_url"),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return saveAndReturn(ctx, repoObj, config, "features")
|
|
}
|
|
|
|
func toolUpdateLandingSocialProof(ctx *context.APIContext, args map[string]any) (any, error) {
|
|
config, repoObj, err := getConfigForUpdate(ctx, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if logos, ok := args["logos"].([]any); ok {
|
|
config.SocialProof.Logos = nil
|
|
for _, l := range logos {
|
|
if s, ok := l.(string); ok {
|
|
config.SocialProof.Logos = append(config.SocialProof.Logos, s)
|
|
}
|
|
}
|
|
}
|
|
if testimonials, ok := args["testimonials"].([]any); ok {
|
|
config.SocialProof.Testimonials = nil
|
|
for _, t := range testimonials {
|
|
if tm, ok := t.(map[string]any); ok {
|
|
config.SocialProof.Testimonials = append(config.SocialProof.Testimonials, pages_module.TestimonialConfig{
|
|
Quote: strVal(tm, "quote"),
|
|
Author: strVal(tm, "author"),
|
|
Role: strVal(tm, "role"),
|
|
Avatar: strVal(tm, "avatar"),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return saveAndReturn(ctx, repoObj, config, "social_proof")
|
|
}
|
|
|
|
func toolUpdateLandingSEO(ctx *context.APIContext, args map[string]any) (any, error) {
|
|
config, repoObj, err := getConfigForUpdate(ctx, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if v, ok := args["title"].(string); ok {
|
|
config.SEO.Title = v
|
|
}
|
|
if v, ok := args["description"].(string); ok {
|
|
config.SEO.Description = v
|
|
}
|
|
if v, ok := args["og_image"].(string); ok {
|
|
config.SEO.OGImage = v
|
|
}
|
|
if v, ok := args["twitter_card"].(string); ok {
|
|
config.SEO.TwitterCard = v
|
|
}
|
|
if v, ok := args["twitter_site"].(string); ok {
|
|
config.SEO.TwitterSite = v
|
|
}
|
|
if keywords, ok := args["keywords"].([]any); ok {
|
|
config.SEO.Keywords = nil
|
|
for _, k := range keywords {
|
|
if s, ok := k.(string); ok {
|
|
config.SEO.Keywords = append(config.SEO.Keywords, s)
|
|
}
|
|
}
|
|
}
|
|
|
|
return saveAndReturn(ctx, repoObj, config, "seo")
|
|
}
|
|
|
|
func toolUpdateLandingTheme(ctx *context.APIContext, args map[string]any) (any, error) {
|
|
config, repoObj, err := getConfigForUpdate(ctx, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if v, ok := args["primary_color"].(string); ok {
|
|
config.Theme.PrimaryColor = v
|
|
}
|
|
if v, ok := args["accent_color"].(string); ok {
|
|
config.Theme.AccentColor = v
|
|
}
|
|
if v, ok := args["mode"].(string); ok {
|
|
config.Theme.Mode = v
|
|
}
|
|
|
|
return saveAndReturn(ctx, repoObj, config, "theme")
|
|
}
|
|
|
|
// ── Helpers ──────────────────────────────────
|
|
|
|
func getConfigForUpdate(ctx *context.APIContext, args map[string]any) (*pages_module.LandingConfig, *repo_model.Repository, error) {
|
|
owner, repo, err := resolveOwnerRepo(args)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
repoObj, err := getRepoByOwnerAndName(ctx, owner, repo)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
config, err := pages_service.GetPagesConfig(ctx, repoObj)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("get config: %w", err)
|
|
}
|
|
if config == nil {
|
|
config = pages_module.DefaultConfig()
|
|
}
|
|
|
|
return config, repoObj, nil
|
|
}
|
|
|
|
func saveAndReturn(ctx *context.APIContext, repo *repo_model.Repository, config *pages_module.LandingConfig, section string) (any, error) {
|
|
configJSON, _ := json.Marshal(config)
|
|
hash := pages_module.HashConfig(configJSON)
|
|
|
|
existing, _ := repo_model.GetPagesConfigByRepoID(ctx, repo.ID)
|
|
|
|
if existing != nil {
|
|
existing.ConfigJSON = string(configJSON)
|
|
existing.ConfigHash = hash
|
|
existing.Template = repo_model.PagesTemplate(config.Template)
|
|
existing.Enabled = config.Enabled
|
|
if err := repo_model.UpdatePagesConfig(ctx, existing); err != nil {
|
|
return nil, fmt.Errorf("save config: %w", err)
|
|
}
|
|
} else {
|
|
if err := repo_model.CreatePagesConfig(ctx, &repo_model.PagesConfig{
|
|
RepoID: repo.ID,
|
|
Enabled: config.Enabled,
|
|
Template: repo_model.PagesTemplate(config.Template),
|
|
ConfigJSON: string(configJSON),
|
|
ConfigHash: hash,
|
|
}); err != nil {
|
|
return nil, fmt.Errorf("create config: %w", err)
|
|
}
|
|
}
|
|
|
|
return map[string]any{
|
|
"success": true,
|
|
"section": section,
|
|
"message": fmt.Sprintf("Updated %s section", section),
|
|
}, nil
|
|
}
|
|
|
|
func strVal(m map[string]any, key string) string {
|
|
if v, ok := m[key].(string); ok {
|
|
return v
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func boolVal(m map[string]any, key string) bool {
|
|
if v, ok := m[key].(bool); ok {
|
|
return v
|
|
}
|
|
return false
|
|
}
|
|
|
|
func resolveOwnerRepo(args map[string]any) (string, string, error) {
|
|
owner, _ := args["owner"].(string)
|
|
repo, _ := args["repo"].(string)
|
|
if owner == "" || repo == "" {
|
|
return "", "", fmt.Errorf("owner and repo are required")
|
|
}
|
|
return owner, repo, nil
|
|
}
|
|
|
|
func getRepoByOwnerAndName(ctx *context.APIContext, owner, repo string) (*repo_model.Repository, error) {
|
|
ownerObj, err := user_model.GetUserByName(ctx, owner)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("owner not found: %s", owner)
|
|
}
|
|
repoObj, err := repo_model.GetRepositoryByName(ctx, ownerObj.ID, repo)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("repo not found: %s/%s", owner, repo)
|
|
}
|
|
return repoObj, nil
|
|
}
|