feat: auto-links screen, keyphrase feed and MCP tools

- SEO › Auto-links (golem15.seo.autolinks, after Pages): list by phrase
  with locale, origin and active filters, the disabled row state, the
  form with its refusals, history through a field-less extension
- toolbar Add focus keyphrases (from_keyphrases) and
  POST /autolinks/from-keyphrases
- GET/POST /autolinks, PUT /autolinks/{id}, DELETE /autolinks and the
  tools seo_list_autolinks, seo_create_autolink, seo_update_autolink,
  seo_delete_autolinks, seo_autolinks_from_keyphrases
- the settings tile description and the permission comment name
  auto-links and the site audit
This commit is contained in:
Jakub Zych
2026-11-04 16:30:56 +01:00
parent 6203aec2bc
commit ab4e83416d
19 changed files with 852 additions and 13 deletions

View File

@@ -294,3 +294,40 @@ func (s *Service) ForPage(ctx context.Context, db *gorm.DB, locale, selfPath str
}
return out, nil
}
// FromKeyphrases adds one auto-link per record whose focus keyphrase in a
// locale is not yet a phrase in that locale (UI-SPEC C22): the record's
// address in that locale, origin keyphrase, active. A keyphrase another
// row already has, one under 3 characters or one whose address is not a
// path is skipped. It runs inside tx and returns the rows added; a second
// run adds nothing.
func (s *Service) FromKeyphrases(ctx context.Context, tx *gorm.DB) (int, error) {
var rows []struct {
Keyphrase string
Locale string
URL string
}
err := tx.Raw(`SELECT keyphrase, locale, url FROM golem15_seo_entries e
WHERE e.keyphrase <> '' AND e.url <> ''
AND NOT EXISTS (SELECT 1 FROM golem15_seo_autolinks a WHERE lower(a.phrase) = lower(e.keyphrase) AND (a.locale = e.locale OR a.locale = ''))
ORDER BY e.locale ASC, e.updated_at DESC, e.id DESC`).Scan(&rows).Error
if err != nil {
return 0, err
}
added := 0
for _, row := range rows {
a := models.Autolink{Phrase: row.Keyphrase, URL: row.URL, Locale: row.Locale, Enabled: true, Origin: models.AutolinkOriginKeyphrase}
problems, err := s.AutolinkProblems(ctx, tx, &a)
if err != nil {
return added, err
}
if len(problems) > 0 {
continue
}
if err := s.SaveAutolink(ctx, tx, &a); err != nil {
return added, err
}
added++
}
return added, nil
}