Some checks failed
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 3m19s
Build and Release / Create Release (push) Successful in 0s
Build and Release / Lint (push) Failing after 11m49s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, windows, windows-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
Build and Release / Unit Tests (push) Successful in 12m52s
Replaces fmt.Errorf with errors.New for static error message in resolveOwnerRepo. Marks unused context and args parameters with underscore in toolListLandingTemplates.
667 lines
22 KiB
Go
667 lines
22 KiB
Go
// Copyright 2026 MarketAlly. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package v2
|
|
|
|
import (
|
|
"errors"
|
|
"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(_ *context.APIContext, _ 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 "", "", errors.New("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
|
|
}
|