feat: links settings and outbound rules in resolve
- migration 202610280003: settings links_new_tab (default on),
nofollow_domains and sponsored_domains; robots llms_enabled and
llms_intro (used by the llms.txt commit)
- SEO settings Links tab: the new-tab switch and the two domain lists,
one domain per line, lowercased, refused with settings.domain_invalid
naming the line; writable through seo_update_settings
- the resolve answer of an ok page carries outbound {new_tab,
nofollow_domains, sponsored_domains}; no existing key changes
This commit is contained in:
@@ -57,11 +57,14 @@ type Result struct {
|
||||
Status int `json:"status"`
|
||||
// Location is the absolute address a redirect answer sends the visitor
|
||||
// to; absent on every other action.
|
||||
Location string `json:"location,omitempty"`
|
||||
URL string `json:"url"`
|
||||
Locale string `json:"locale"`
|
||||
Staging bool `json:"staging"`
|
||||
SEO *SEO `json:"seo"`
|
||||
Location string `json:"location,omitempty"`
|
||||
URL string `json:"url"`
|
||||
Locale string `json:"locale"`
|
||||
Staging bool `json:"staging"`
|
||||
SEO *SEO `json:"seo"`
|
||||
// Outbound is how the layer renders links to other sites (P33-16),
|
||||
// present on ok answers.
|
||||
Outbound *Outbound `json:"outbound,omitempty"`
|
||||
Sources map[string]string `json:"-"`
|
||||
// TypeKey and RecordID name the record behind the URL, "" when none.
|
||||
TypeKey string `json:"-"`
|
||||
@@ -80,6 +83,35 @@ type SEO struct {
|
||||
Verification []MetaTag `json:"verification"`
|
||||
}
|
||||
|
||||
// Outbound holds the settings' Links rules for links to other sites
|
||||
// (D-83): open them in a new tab, and the domains (subdomains included)
|
||||
// whose links always get nofollow or sponsored.
|
||||
type Outbound struct {
|
||||
NewTab bool `json:"new_tab"`
|
||||
NofollowDomains []string `json:"nofollow_domains"`
|
||||
SponsoredDomains []string `json:"sponsored_domains"`
|
||||
}
|
||||
|
||||
// OutboundOf is the Links rules of the settings.
|
||||
func OutboundOf(s models.Settings) *Outbound {
|
||||
return &Outbound{
|
||||
NewTab: s.LinksNewTab,
|
||||
NofollowDomains: validDomains(s.NofollowDomains),
|
||||
SponsoredDomains: validDomains(s.SponsoredDomains),
|
||||
}
|
||||
}
|
||||
|
||||
// validDomains lists the valid domains of a stored list.
|
||||
func validDomains(list string) []string {
|
||||
out := []string{}
|
||||
for _, d := range models.DomainLines(list) {
|
||||
if models.ValidDomain(d) {
|
||||
out = append(out, d)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// OpenGraph holds the og: values.
|
||||
type OpenGraph struct {
|
||||
Title string `json:"title"`
|
||||
@@ -646,6 +678,7 @@ func (r *Resolver) resolveAt(ctx context.Context, db *gorm.DB, settings models.S
|
||||
}
|
||||
|
||||
res.Action, res.Status, res.Locale, res.SEO = ActionOK, 200, locale, seo
|
||||
res.Outbound = OutboundOf(settings)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ var SettingsColumns = []string{
|
||||
"site_name", "twitter_site", "title_template", "home_template",
|
||||
"default_description", "default_og_image", "query_params",
|
||||
"noindex_paths", "search_paths", "url_host", "url_slash",
|
||||
"url_lowercase", "verify_google", "verify_bing", "verify_yandex",
|
||||
"url_lowercase", "links_new_tab", "nofollow_domains",
|
||||
"sponsored_domains", "verify_google", "verify_bing", "verify_yandex",
|
||||
"verify_other", "verify_files", "csv_delimiter",
|
||||
}
|
||||
|
||||
@@ -57,6 +58,9 @@ func SettingsValues(st models.Settings) map[string]any {
|
||||
"url_host": st.URLHost,
|
||||
"url_slash": st.URLSlash,
|
||||
"url_lowercase": st.URLLowercase,
|
||||
"links_new_tab": st.LinksNewTab,
|
||||
"nofollow_domains": st.NofollowDomains,
|
||||
"sponsored_domains": st.SponsoredDomains,
|
||||
"verify_google": st.VerifyGoogle,
|
||||
"verify_bing": st.VerifyBing,
|
||||
"verify_yandex": st.VerifyYandex,
|
||||
@@ -119,12 +123,22 @@ func SetSettingsValue(st *models.Settings, column string, v any) error {
|
||||
st.VerifyYandex, err = text()
|
||||
case "csv_delimiter":
|
||||
st.CSVDelimiter, err = text()
|
||||
case "nofollow_domains":
|
||||
st.NofollowDomains, err = text()
|
||||
case "sponsored_domains":
|
||||
st.SponsoredDomains, err = text()
|
||||
case "url_lowercase":
|
||||
b, ok := v.(bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("golem15.seo: settings %s is not a bool", column)
|
||||
}
|
||||
st.URLLowercase = b
|
||||
case "links_new_tab":
|
||||
b, ok := v.(bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("golem15.seo: settings %s is not a bool", column)
|
||||
}
|
||||
st.LinksNewTab = b
|
||||
case "query_params":
|
||||
var out []models.QueryParam
|
||||
if err = list(&out); err == nil {
|
||||
@@ -256,6 +270,8 @@ func (s *Service) SaveSettings(ctx context.Context, tx *gorm.DB, in SettingsWrit
|
||||
if len(problems) > 0 {
|
||||
return models.Settings{}, &ProblemsError{Problems: problems}
|
||||
}
|
||||
row.NofollowDomains = models.NormalizeDomains(row.NofollowDomains)
|
||||
row.SponsoredDomains = models.NormalizeDomains(row.SponsoredDomains)
|
||||
now := time.Now().UTC()
|
||||
row.UpdatedAt = &now
|
||||
if err := tx.Save(&row).Error; err != nil {
|
||||
|
||||
Reference in New Issue
Block a user