Files
sm-seo-plugin/classes/manage.go
Jakub Zych aa0c3bcacf 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
2026-10-27 12:17:36 +01:00

65 lines
1.9 KiB
Go

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
}