- golem15_seo_pages (migration 202610280002): the site's own frontend pages
with an address and a name per locale, kind, origin, parent, content date
and note; settings routes_reported_at
- SEO > Pages (golem15.seo.pages) after URL rules: list with kind, origin
and missing filters and the missing row state, form with the P9 partial;
the plugin's form extension saves the address and name per locale and
refuses url_required, url_invalid, url_taken and url_owned
- menu item type seo-page with a content descriptor, so each page gets
the SEO tab, resolves ok and is listed in the sitemaps grouped by kind
(pages, offers, services, landing-pages, titled per locale)
- management API GET/POST /_seo/api/v1/pages, PUT/DELETE /pages/{id} and
the MCP tools seo_list_pages, seo_create_page, seo_update_page,
seo_delete_page (in this commit because the operations parity test maps
every Pages operation to its tool)
387 lines
16 KiB
Go
387 lines
16 KiB
Go
package seo
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
apicontrollers "git.golem15.com/golem15/sm-seo-plugin/controllers/api"
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
)
|
|
|
|
var _ pact.HasMCP = (*Plugin)(nil)
|
|
|
|
// mcpGuard is the guard every SEO tool runs as: the backend account linked
|
|
// to the MCP user. The routes resolve that administrator themselves.
|
|
const mcpGuard = "backend"
|
|
|
|
// mcpPermission is the one SEO permission (D-15) every tool is listed with;
|
|
// the routes check it on every call.
|
|
const mcpPermission = apicontrollers.PermissionManage
|
|
|
|
// MCP declares the SEO tools (D-10, P-24): one per operation of the SEO
|
|
// admin (operations.go maps each admin operation to its tool), each bound
|
|
// to a /_seo/api/v1 management route, run as the backend account linked to
|
|
// the MCP user and listed only when that account has golem15.seo.manage.
|
|
// pier prefixes them with seo_. GET tools are read-only, so a read grant
|
|
// sees only those; deletes and "mark as gone" are destructive.
|
|
func (p *Plugin) MCP() pact.MCP {
|
|
destructive := true
|
|
return pact.MCP{Tools: []pact.MCPTool{
|
|
// Pages addressed by URL (D-11, D-13).
|
|
{
|
|
Name: "get_url_seo",
|
|
Title: "Get the SEO of a URL",
|
|
Description: "Shows the effective SEO of a page addressed by URL: every value with its source (URL rule, record, template, global settings, content or default), the redirect decision, the owner a change would be written to (the record or a URL rule) and the warnings of the SEO tab.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/seo",
|
|
Input: apicontrollers.GetSEOInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "set_url_seo",
|
|
Title: "Set the SEO of a URL",
|
|
Description: "Changes the SEO values of a page addressed by URL. A record behind the URL gets the change in its SEO tab in the URL's locale; otherwise, or with force_rule, an exact URL rule is written. The answer says where it was written.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPut,
|
|
Path: "/_seo/api/v1/seo",
|
|
Input: apicontrollers.SetSEOInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
|
|
// URL rules (D-02).
|
|
{
|
|
Name: "list_rules",
|
|
Title: "List URL rules",
|
|
Description: "Lists the URL rules (SEO for an address with or without a record behind it) by address, with a search text and the match kind.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/rules",
|
|
Input: apicontrollers.ListRulesInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "show_rule",
|
|
Title: "Show a URL rule",
|
|
Description: "Shows one URL rule by id.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/rules/{id}",
|
|
Input: apicontrollers.RuleIDInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "create_rule",
|
|
Title: "Create a URL rule",
|
|
Description: "Creates a URL rule: an exact address or a pattern where * matches any text, with the values it sets. A rule wins over the record's SEO tab.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPost,
|
|
Path: "/_seo/api/v1/rules",
|
|
Input: apicontrollers.CreateRuleInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "update_rule",
|
|
Title: "Update a URL rule",
|
|
Description: "Changes the fields sent of one URL rule by id.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPut,
|
|
Path: "/_seo/api/v1/rules/{id}",
|
|
Input: apicontrollers.UpdateRuleInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "delete_rule",
|
|
Title: "Delete a URL rule",
|
|
Description: "Deletes one URL rule by id; history keeps its values.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodDelete,
|
|
Path: "/_seo/api/v1/rules/{id}",
|
|
Input: apicontrollers.RuleIDInput{},
|
|
Permissions: []string{mcpPermission},
|
|
Destructive: &destructive,
|
|
},
|
|
|
|
// Redirects (D-16 to D-19).
|
|
{
|
|
Name: "list_redirects",
|
|
Title: "List redirects",
|
|
Description: "Lists redirects, newest change first, with a search text and the status (301, 302, 410) and origin filters.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/redirects",
|
|
Input: apicontrollers.ListRedirectsInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "show_redirect",
|
|
Title: "Show a redirect",
|
|
Description: "Shows one redirect by id.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/redirects/{id}",
|
|
Input: apicontrollers.RedirectIDInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "create_redirect",
|
|
Title: "Create a redirect",
|
|
Description: "Creates a 301, 302 or 410 redirect from a path, a pattern or a regular expression. A redirect that would create a loop is refused with redirect_loop and the loop's path; a chain is saved with a warning.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPost,
|
|
Path: "/_seo/api/v1/redirects",
|
|
Input: apicontrollers.CreateRedirectInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "update_redirect",
|
|
Title: "Update a redirect",
|
|
Description: "Changes the fields sent of one redirect by id; a loop is refused like a create.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPut,
|
|
Path: "/_seo/api/v1/redirects/{id}",
|
|
Input: apicontrollers.UpdateRedirectInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "delete_redirect",
|
|
Title: "Delete a redirect",
|
|
Description: "Deletes one redirect by id; history keeps its values.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodDelete,
|
|
Path: "/_seo/api/v1/redirects/{id}",
|
|
Input: apicontrollers.RedirectIDInput{},
|
|
Permissions: []string{mcpPermission},
|
|
Destructive: &destructive,
|
|
},
|
|
|
|
// The page registry (D-33).
|
|
{
|
|
Name: "list_pages",
|
|
Title: "List registry pages",
|
|
Description: "Lists the page registry (the site's own frontend pages such as the home page, contact or offer pages) by address, with every locale's address and name, the kind, the origin (manual, reported by the site, MCP) and whether the site's newest route report missed the page. Filters: search text, id, kind, origin, missing.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/pages",
|
|
Input: apicontrollers.ListPagesInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "create_page",
|
|
Title: "Create a registry page",
|
|
Description: "Adds a frontend page to the registry, so it gets SEO values (set them with set_url_seo on its address) and a place in the sitemaps: its path per locale, name, kind (page, offer, service, landing), parent and content date. Refused for an address another page or another content type (a post, a category) already has.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPost,
|
|
Path: "/_seo/api/v1/pages",
|
|
Input: apicontrollers.CreatePageInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "update_page",
|
|
Title: "Update a registry page",
|
|
Description: "Changes the fields sent of one registry page by id; in url and title only the locales sent. A changed address gets a 301 from the old one.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPut,
|
|
Path: "/_seo/api/v1/pages/{id}",
|
|
Input: apicontrollers.UpdatePageInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "delete_page",
|
|
Title: "Delete a registry page",
|
|
Description: "Deletes one registry page by id with its SEO values; its address leaves the sitemaps on the next rebuild. A page the site reports comes back with its next route report.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodDelete,
|
|
Path: "/_seo/api/v1/pages/{id}",
|
|
Input: apicontrollers.PageIDInput{},
|
|
Permissions: []string{mcpPermission},
|
|
Destructive: &destructive,
|
|
},
|
|
|
|
// 404 log (D-20).
|
|
{
|
|
Name: "list_not_found",
|
|
Title: "List the 404 log",
|
|
Description: "Lists the addresses that answered 404, most hits first, with whether a redirect answers them now (status open or resolved).",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/notfound",
|
|
Input: apicontrollers.ListNotFoundInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "redirect_not_found",
|
|
Title: "Redirect a 404 address",
|
|
Description: "Creates a 301 from a logged 404 address to a target. Refused when a rule already answers the address.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPost,
|
|
Path: "/_seo/api/v1/notfound/{id}/redirect",
|
|
Input: apicontrollers.RedirectNotFoundInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "gone_not_found",
|
|
Title: "Mark a 404 address as gone",
|
|
Description: "Creates a 410 for a logged 404 address, which tells search engines to drop it. Refused when a rule already answers the address.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPost,
|
|
Path: "/_seo/api/v1/notfound/{id}/gone",
|
|
Input: apicontrollers.NotFoundIDInput{},
|
|
Permissions: []string{mcpPermission},
|
|
Destructive: &destructive,
|
|
},
|
|
{
|
|
Name: "delete_not_found",
|
|
Title: "Remove a 404 log entry",
|
|
Description: "Removes one entry from the 404 log; it comes back on its next 404.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodDelete,
|
|
Path: "/_seo/api/v1/notfound/{id}",
|
|
Input: apicontrollers.NotFoundIDInput{},
|
|
Permissions: []string{mcpPermission},
|
|
Destructive: &destructive,
|
|
},
|
|
|
|
// robots.txt and AI bots (D-22, D-24, D-25, D-99).
|
|
{
|
|
Name: "show_robots",
|
|
Title: "Show robots.txt",
|
|
Description: "Shows the robots.txt rules, the AI bot switches, the file crawlers get in production and whether the site runs outside production (crawlers then get Disallow: /).",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/robots",
|
|
Input: struct{}{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "update_robots",
|
|
Title: "Update robots.txt",
|
|
Description: "Changes the robots.txt rules and the AI bot switches sent. The answer lists noindex addresses the new rules keep Googlebot from.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPut,
|
|
Path: "/_seo/api/v1/robots",
|
|
Input: apicontrollers.UpdateRobotsInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "test_robots",
|
|
Title: "Test an address against robots.txt",
|
|
Description: "Answers whether a crawler may fetch an address under the saved production robots.txt, with Google's matcher and the deciding line.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/robots/test",
|
|
Input: apicontrollers.TestRobotsInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
|
|
// Settings (D-03, D-05, D-07, D-20b, D-21, D-23, D-104).
|
|
{
|
|
Name: "show_settings",
|
|
Title: "Show the SEO settings",
|
|
Description: "Shows the SEO settings (templates, defaults, the query parameter list, indexing paths, the address form, verification) and the settings of every content type.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/settings",
|
|
Input: struct{}{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "update_settings",
|
|
Title: "Update the SEO settings",
|
|
Description: "Changes the SEO settings sent and, under types, the settings of content types by key, with the checks of the settings screen.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPut,
|
|
Path: "/_seo/api/v1/settings",
|
|
Input: apicontrollers.UpdateSettingsInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
|
|
// History (D-13).
|
|
{
|
|
Name: "list_history",
|
|
Title: "List SEO history",
|
|
Description: "Lists SEO changes, newest first, with author, time, via (admin, mcp, import, auto, undo), before and after values, filtered by via, kind or batch.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/history",
|
|
Input: apicontrollers.ListHistoryInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "show_history",
|
|
Title: "Show an SEO change",
|
|
Description: "Shows one SEO change by id with its before and after values.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/history/{id}",
|
|
Input: apicontrollers.HistoryIDInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
|
|
// Undo (D-13).
|
|
{
|
|
Name: "undo_change",
|
|
Title: "Undo an SEO change",
|
|
Description: "Restores the values before one SEO change from history; the undo is itself recorded. Refused with undo_stale when a later change touched the same values (naming who and when), undo_loop when the restore would create a redirect loop, undo_gone when the record is gone.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPost,
|
|
Path: "/_seo/api/v1/history/{id}/undo",
|
|
Input: apicontrollers.HistoryIDInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "undo_batch",
|
|
Title: "Undo a batch",
|
|
Description: "Undoes every change of a batch or an import (by batch id) as a whole, or none of them, with the refusals of undo_change.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPost,
|
|
Path: "/_seo/api/v1/history/batches/{batch}/undo",
|
|
Input: apicontrollers.HistoryBatchInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
|
|
// Bulk changes and CSV (D-12, D-13).
|
|
{
|
|
Name: "apply_batch",
|
|
Title: "Apply a batch of SEO changes",
|
|
Description: "Applies a list of changes (set_seo by URL, redirect and rule creates, updates and deletes) all or nothing. With dry_run it answers the diff and the conflicts (redirect loops and chains, noindex on a published page, a canonical pointing to a redirected page) and writes nothing; a loop refuses the whole batch.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPost,
|
|
Path: "/_seo/api/v1/batch",
|
|
Input: apicontrollers.ApplyBatchInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "export_csv",
|
|
Title: "Export SEO data as CSV",
|
|
Description: "Exports redirects, URL rules or the SEO of records (by URL) as CSV, UTF-8 with a BOM, 1000 rows per page; cells that start a formula are prefixed with an apostrophe.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/export",
|
|
Input: apicontrollers.ExportInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
{
|
|
Name: "import_csv",
|
|
Title: "Import SEO data from CSV",
|
|
Description: "Imports redirects, URL rules or the SEO of records (rows address pages by URL; no record means a URL rule) from CSV of at most 900 KB. With dry_run it previews every row as new, changed, unchanged or refused with notes; otherwise every row is applied as one batch, or nothing while a row is refused (a loop, an invalid value).",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodPost,
|
|
Path: "/_seo/api/v1/import",
|
|
Input: apicontrollers.ImportInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
|
|
// Audit (D-13, D-14 CMS part).
|
|
{
|
|
Name: "list_issues",
|
|
Title: "List SEO issues",
|
|
Description: "Lists the CMS-data SEO issues: missing and duplicate titles and descriptions, URL rules overriding a record, redirect chains, canonicals pointing to noindex or redirected addresses, and noindex addresses robots.txt blocks; each names the tool that fixes it.",
|
|
Guard: mcpGuard,
|
|
Method: http.MethodGet,
|
|
Path: "/_seo/api/v1/issues",
|
|
Input: apicontrollers.ListIssuesInput{},
|
|
Permissions: []string{mcpPermission},
|
|
},
|
|
}}
|
|
}
|