feat: management API and an MCP tool per SEO admin operation
- /_seo/api/v1 routes for the effective SEO of a URL with sources and owner, URL rules, redirects (loops refused with redirect_loop), the 404 log actions, robots.txt and its tester, settings and history - the CMS-data issue report: missing and duplicate titles and descriptions, rules over records, redirect chains, canonicals to noindex or redirected addresses, noindex addresses robots.txt blocks - 24 MCP tools on those routes (backend guard, golem15.seo.manage) - AdminOperations maps every admin operation to its tool; TestEveryAdminOperationHasTool and TestMCPReadToolsAreGET guard drift - robots.txt writes go through classes.SaveRobots from the admin too
This commit is contained in:
@@ -4,9 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/sm-seo-plugin/classes"
|
||||
"git.golem15.com/golem15/sm-seo-plugin/controllers"
|
||||
@@ -116,43 +114,24 @@ func (p *Plugin) robotsForm() *pact.AdminFormExtension {
|
||||
if svc == nil {
|
||||
return pact.AdminFormSaveResult{}, classes.ErrNotBooted
|
||||
}
|
||||
row, ok := in.Record.(*models.Robots)
|
||||
if !ok || row == nil {
|
||||
if row, ok := in.Record.(*models.Robots); !ok || row == nil {
|
||||
return pact.AdminFormSaveResult{}, nil
|
||||
}
|
||||
before := robotsValues(*row)
|
||||
var w classes.RobotsWrite
|
||||
if raw, ok := in.Values["content"]; ok {
|
||||
if text, ok := raw.(string); ok {
|
||||
row.Content = text
|
||||
w.Content = &text
|
||||
}
|
||||
}
|
||||
bots := models.BotSwitches{}
|
||||
for token, on := range row.Bots {
|
||||
bots[token] = on
|
||||
}
|
||||
for _, b := range classes.Bots {
|
||||
if raw, ok := in.Values[classes.BotField(b.Token)]; ok {
|
||||
bots[b.Token] = truthy(raw)
|
||||
if w.Bots == nil {
|
||||
w.Bots = map[string]bool{}
|
||||
}
|
||||
w.Bots[b.Token] = truthy(raw)
|
||||
}
|
||||
}
|
||||
row.Bots = bots
|
||||
now := time.Now().UTC()
|
||||
row.UpdatedAt = &now
|
||||
if err := in.DB.Model(&models.Robots{}).Where("id = ?", row.ID).Updates(map[string]any{
|
||||
"content": row.Content, "bots": row.Bots, "updated_at": now,
|
||||
}).Error; err != nil {
|
||||
return pact.AdminFormSaveResult{}, err
|
||||
}
|
||||
if err := svc.Record(ctx, in.DB, classes.Change{
|
||||
Kind: models.SubjectRobots,
|
||||
SubjectID: strconv.FormatUint(uint64(row.ID), 10),
|
||||
What: svc.Phrase("golem15.seo::lang.history.what_robots", nil),
|
||||
Before: before,
|
||||
After: robotsValues(*row),
|
||||
}); err != nil {
|
||||
return pact.AdminFormSaveResult{}, err
|
||||
}
|
||||
blocked, err := svc.BlockedNoindex(in.DB, row.Content, row.Bots)
|
||||
_, blocked, err := svc.SaveRobots(ctx, in.DB, w)
|
||||
if err != nil || len(blocked) == 0 {
|
||||
return pact.AdminFormSaveResult{}, err
|
||||
}
|
||||
@@ -169,15 +148,6 @@ func (p *Plugin) robotsForm() *pact.AdminFormExtension {
|
||||
}
|
||||
}
|
||||
|
||||
// robotsValues are the robots row's values for history.
|
||||
func robotsValues(r models.Robots) map[string]any {
|
||||
bots := map[string]any{}
|
||||
for _, b := range classes.Bots {
|
||||
bots[b.Token] = r.Bots.Allowed(b.Token)
|
||||
}
|
||||
return map[string]any{"content": r.Content, "bots": bots}
|
||||
}
|
||||
|
||||
// robotsTestPayload is the tester's request: op context answers the
|
||||
// crawler list and the environment; op test answers whether agent may
|
||||
// fetch url under the editor's unsaved text and switches.
|
||||
|
||||
Reference in New Issue
Block a user