package api import ( "context" "encoding/json" "net/http" "git.golem15.com/golem15/sm-seo-plugin/classes" "git.golem15.com/golem15/sm-seo-plugin/models" "gorm.io/gorm" ) // UpdateSettingsInput is the body of PUT /_seo/api/v1/settings: only the // fields sent change. type UpdateSettingsInput struct { SiteName *string `json:"site_name,omitempty" jsonschema:"the site name (%site% in templates)"` TwitterSite *string `json:"twitter_site,omitempty" jsonschema:"the site's X/Twitter handle"` TitleTemplate *string `json:"title_template,omitempty" jsonschema:"the title template: %title%, %description% and %site%"` HomeTemplate *string `json:"home_template,omitempty" jsonschema:"the home page title template"` DefaultDescription *string `json:"default_description,omitempty" jsonschema:"the description of pages without one"` DefaultOGImage *string `json:"default_og_image,omitempty" jsonschema:"the social image of pages without one"` QueryParams *[]models.QueryParam `json:"query_params,omitempty" jsonschema:"the query parameter list: name (a trailing * matches a prefix), kind content, filter or tracking, and an optional path pattern"` NoindexPaths *string `json:"noindex_paths,omitempty" jsonschema:"path patterns never indexed, one per line"` SearchPaths *string `json:"search_paths,omitempty" jsonschema:"search result path patterns (noindex,follow), one per line"` URLHost *string `json:"url_host,omitempty" jsonschema:"keep, www or bare"` URLSlash *string `json:"url_slash,omitempty" jsonschema:"keep, add or remove a trailing slash"` URLLowercase *bool `json:"url_lowercase,omitempty" jsonschema:"lowercase the ASCII letters of addresses"` VerifyGoogle *string `json:"verify_google,omitempty" jsonschema:"the Google Search Console verification code"` VerifyBing *string `json:"verify_bing,omitempty" jsonschema:"the Bing verification code"` VerifyYandex *string `json:"verify_yandex,omitempty" jsonschema:"the Yandex verification code"` VerifyOther *[]models.VerifyMeta `json:"verify_other,omitempty" jsonschema:"other verification meta tags (name, content)"` VerifyFiles *[]models.VerifyFile `json:"verify_files,omitempty" jsonschema:"verification files served from the site root (filename, body)"` CSVDelimiter *string `json:"csv_delimiter,omitempty" jsonschema:"the import and export delimiter: , or ;"` Types map[string]classes.TypeSettingWrite `json:"types,omitempty" jsonschema:"per content type key: inject (show the SEO tab), title_template, description_template, robots"` } // settingsJSON is the settings in an answer: the values and, per content // type, its label and settings. func settingsJSON(ctx context.Context, svc *classes.Service, db *gorm.DB, row models.Settings) (map[string]any, error) { stored, err := classes.LoadTypeSettings(db) if err != nil { return nil, err } types := map[string]any{} if reg := svc.Registry(); reg != nil { for _, typ := range reg.ContentTypes() { ts, ok := stored[typ.Key] if !ok { ts = models.DefaultTypeSetting(typ.Key) } v := classes.TypeSettingValues(ts) v["label"] = svc.PhraseCtx(ctx, typ.Label, nil) types[typ.Key] = v } } return map[string]any{"settings": classes.SettingsValues(row), "types": types}, nil } // ShowSettings serves GET /_seo/api/v1/settings. func ShowSettings(service func() *classes.Service) http.HandlerFunc { return manage(service, func(ctx context.Context, svc *classes.Service, r *http.Request) (int, any, error) { db, err := read(ctx, svc) if err != nil { return 0, nil, err } row, err := classes.LoadSettings(db) if err != nil { return 0, nil, err } body, err := settingsJSON(ctx, svc, db, row) if err != nil { return 0, nil, err } return http.StatusOK, map[string]any{"data": body}, nil }) } // UpdateSettings serves PUT /_seo/api/v1/settings: the fields sent change, // with the checks of the settings screen; 422 validation_failed names the // refused fields. func UpdateSettings(service func() *classes.Service) http.HandlerFunc { return manage(service, func(ctx context.Context, svc *classes.Service, r *http.Request) (int, any, error) { raw := map[string]json.RawMessage{} if err := decode(r, maxBody, &raw); err != nil { return 0, nil, err } in := classes.SettingsWrite{Values: map[string]any{}} for key, value := range raw { if key == "types" { if err := json.Unmarshal(value, &in.Types); err != nil { return 0, nil, Fail(http.StatusUnprocessableEntity, errInvalidBody) } continue } var v any if err := json.Unmarshal(value, &v); err != nil { return 0, nil, Fail(http.StatusUnprocessableEntity, errInvalidBody) } in.Values[key] = v } var row models.Settings err := write(ctx, svc, func(ctx context.Context, tx *gorm.DB) error { var err error row, err = svc.SaveSettings(ctx, tx, in) return err }) if err != nil { return 0, nil, err } db, err := read(ctx, svc) if err != nil { return 0, nil, err } body, err := settingsJSON(ctx, svc, db, row) if err != nil { return 0, nil, err } return http.StatusOK, map[string]any{"data": body}, nil }) }