feat: JSON-LD graph, breadcrumbs and hreflang in the resolve answer (tracer)

- classes/schema builds one @graph per page (WebPage, article, profile,
  service, BreadcrumbList, VideoObject, WebSite, publisher) linked by @id
- breadcrumb trail through the primary parent chain, hreflang alternates
  with x-default, html_lang and og:locale in the resolve answer
- migration 202610280004 adds the publisher, local business, home crumb
  and pagination settings columns
This commit is contained in:
Jakub Zych
2026-11-04 14:06:57 +01:00
parent df93c86001
commit ea2ce24954
12 changed files with 1676 additions and 2 deletions

View File

@@ -8,6 +8,7 @@ import (
"strings"
"unicode/utf8"
"git.golem15.com/golem15/sm-seo-plugin/classes/schema"
"git.golem15.com/golem15/sm-seo-plugin/models"
"git.golem15.com/golem15/summercms/modules/cabana"
"git.golem15.com/golem15/summercms/modules/pact"
@@ -64,8 +65,16 @@ type Result struct {
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:"-"`
Outbound *Outbound `json:"outbound,omitempty"`
// HTMLLang is the page's <html lang> (P33-21); Alternates its hreflang
// list, the page itself and x-default included (D-55 to D-57); JSONLD
// its one @graph (D-43); Breadcrumbs its trail, the same items as the
// BreadcrumbList node (D-86). All are set on ok answers only.
HTMLLang string `json:"html_lang,omitempty"`
Alternates []Alternate `json:"alternates,omitempty"`
JSONLD map[string]any `json:"jsonld,omitempty"`
Breadcrumbs []schema.Crumb `json:"breadcrumbs,omitempty"`
Sources map[string]string `json:"-"`
// TypeKey and RecordID name the record behind the URL, "" when none.
TypeKey string `json:"-"`
RecordID string `json:"-"`
@@ -118,6 +127,10 @@ type OpenGraph struct {
Description string `json:"description"`
Image string `json:"image"`
URL string `json:"url"`
// Locale is og:locale (pl_PL) and LocaleAlternates the og:locale of the
// record's other translated locales (D-55), set on ok answers.
Locale string `json:"locale"`
LocaleAlternates []string `json:"locale_alternates"`
}
// TwitterTag holds the twitter: values.
@@ -679,6 +692,16 @@ 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)
// The sitemap filter (production) decides on robots and canonical
// only; it skips the structured data.
if !r.production {
if err := svc.structuredData(ctx, db, settings, &res, structuredIn{
key: key, id: res.RecordID, rec: rec, entry: entry, content: content,
locale: locale, path: path, url: self, home: home, siteName: svc.SiteName(ctx, settings, locale), seo: seo,
}); err != nil {
return Result{}, err
}
}
return res, nil
}