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:
64
classes/manage.go
Normal file
64
classes/manage.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package classes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"git.golem15.com/golem15/sm-seo-plugin/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// LoadRedirect returns redirect id; ErrNoRecord when there is none.
|
||||
func LoadRedirect(db *gorm.DB, id uint) (models.Redirect, error) {
|
||||
var rows []models.Redirect
|
||||
if err := db.Where("id = ?", id).Limit(1).Find(&rows).Error; err != nil {
|
||||
return models.Redirect{}, err
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return models.Redirect{}, ErrNoRecord
|
||||
}
|
||||
return rows[0], nil
|
||||
}
|
||||
|
||||
// LoadNotFound returns 404 log entry id; ErrNoRecord when there is none.
|
||||
func LoadNotFound(db *gorm.DB, id uint) (models.NotFound, error) {
|
||||
var rows []models.NotFound
|
||||
if err := db.Where("id = ?", id).Limit(1).Find(&rows).Error; err != nil {
|
||||
return models.NotFound{}, err
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return models.NotFound{}, ErrNoRecord
|
||||
}
|
||||
return rows[0], nil
|
||||
}
|
||||
|
||||
// NotFoundValues are a 404 log entry's values for history.
|
||||
func NotFoundValues(n models.NotFound) map[string]any {
|
||||
return map[string]any{"url": n.URL, "hits": n.Hits, "referer": n.Referer}
|
||||
}
|
||||
|
||||
// DeleteNotFound removes a 404 log entry inside tx and records it in
|
||||
// history; the address is logged again on its next 404.
|
||||
func (s *Service) DeleteNotFound(ctx context.Context, tx *gorm.DB, n models.NotFound) error {
|
||||
if err := tx.Delete(&models.NotFound{}, n.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Record(ctx, tx, Change{
|
||||
Kind: models.SubjectNotFound,
|
||||
SubjectID: strconv.FormatUint(uint64(n.ID), 10),
|
||||
What: s.Phrase("golem15.seo::lang.history.what_notfound", map[string]string{"url": n.URL}),
|
||||
Before: NotFoundValues(n),
|
||||
})
|
||||
}
|
||||
|
||||
// RedirectOrigin is the origin of a redirect created through ctx: mcp
|
||||
// through MCP, import through an import, else manual.
|
||||
func RedirectOrigin(ctx context.Context) string {
|
||||
switch ViaOf(ctx) {
|
||||
case models.ViaMCP:
|
||||
return models.OriginMCP
|
||||
case models.ViaImport:
|
||||
return models.OriginImport
|
||||
}
|
||||
return models.OriginManual
|
||||
}
|
||||
Reference in New Issue
Block a user