fix: set_url_seo and record imports refuse a locale the record does not have (WR-18)
A locale given with a URL write overrode the matched one unchecked, so "xx" created an entry nothing ever reads while the answer reported success. SetURLSEO (API, MCP, batch set_seo, records import) now requires one of the record's locales and refuses others with api.locale_unknown.
This commit is contained in:
@@ -156,7 +156,15 @@ func (s *Service) SetURLSEO(ctx context.Context, tx *gorm.DB, in URLSEOWrite) (U
|
||||
}
|
||||
if owner.WrittenTo == WrittenToRecord {
|
||||
locale := owner.Locale
|
||||
if l := strings.TrimSpace(in.Locale); l != "" {
|
||||
if l := strings.TrimSpace(in.Locale); l != "" && l != locale {
|
||||
// Another locale must be one the record describes (WR-18): an
|
||||
// entry in any other would never be read by the resolver or the
|
||||
// SEO tab.
|
||||
if _, known := s.recordLocales(ctx, typ.Key, owner.ID)[l]; !known {
|
||||
return URLSEOResult{}, &ProblemsError{Problems: map[string][]models.Problem{
|
||||
"locale": {{Key: "golem15.seo::lang.api.locale_unknown", Params: map[string]string{"locale": l}}},
|
||||
}}
|
||||
}
|
||||
locale = l
|
||||
}
|
||||
owner.Locale = locale
|
||||
|
||||
@@ -40,7 +40,7 @@ func (v SEOValues) Map() map[string]string {
|
||||
// SetSEOInput is the body of PUT /_seo/api/v1/seo.
|
||||
type SetSEOInput struct {
|
||||
URL string `json:"url" jsonschema:"the page: a path such as /blog/post or an absolute URL of the site"`
|
||||
Locale string `json:"locale,omitempty" jsonschema:"the locale of a record's SEO; the locale the URL matches when omitted"`
|
||||
Locale string `json:"locale,omitempty" jsonschema:"the locale of a record's SEO, one of the record's locales (422 locale_unknown otherwise); the locale the URL matches when omitted"`
|
||||
Values SEOValues `json:"values" jsonschema:"the values to change"`
|
||||
ForceRule bool `json:"force_rule,omitempty" jsonschema:"write a URL rule even when a record is behind the URL"`
|
||||
}
|
||||
|
||||
@@ -315,6 +315,7 @@ api:
|
||||
canonical_invalid: The canonical URL must be a path starting with / or an absolute http(s) URL.
|
||||
bot_unknown: This is not an AI bot of the robots.txt screen.
|
||||
value_invalid: This value is not valid here.
|
||||
locale_unknown: "The record behind this address has no locale “:locale”."
|
||||
type_unknown: No content type with this key is registered.
|
||||
batch_too_large: "A batch takes at most :max changes."
|
||||
change_not_found: The record this change names does not exist.
|
||||
|
||||
@@ -325,6 +325,7 @@ api:
|
||||
canonical_invalid: Adres kanoniczny musi być ścieżką zaczynającą się od / lub pełnym adresem http(s).
|
||||
bot_unknown: To nie jest bot AI z ekranu robots.txt.
|
||||
value_invalid: Ta wartość jest tu nieprawidłowa.
|
||||
locale_unknown: "Rekord pod tym adresem nie ma wersji językowej „:locale”."
|
||||
type_unknown: Nie zarejestrowano typu treści o tym kluczu.
|
||||
batch_too_large: "Partia może mieć najwyżej :max zmian."
|
||||
change_not_found: Rekord wskazany w tej zmianie nie istnieje.
|
||||
|
||||
11
mcp_test.go
11
mcp_test.go
@@ -257,6 +257,17 @@ func TestManagementAPI(t *testing.T) {
|
||||
}
|
||||
env.mcpExpect(t, http.StatusOK, http.MethodPut, "/_seo/api/v1/seo", agent, map[string]any{"url": "/pages/a", "values": map[string]any{"og_image": "https://cdn.example/a.png"}})
|
||||
|
||||
// A locale the record does not describe is refused (WR-18); one it
|
||||
// does is written.
|
||||
for _, bad := range []string{"xx", "EN "} {
|
||||
if rec := env.mcp(t, http.MethodPut, "/_seo/api/v1/seo", agent, map[string]any{"url": "/pages/a", "locale": bad, "values": map[string]any{"title": "Lost"}}); rec.Code != http.StatusUnprocessableEntity || !strings.Contains(rec.Body.String(), `"locale"`) {
|
||||
t.Fatalf("set_url_seo locale %q = %d %s, want 422 on locale", bad, rec.Code, rec.Body.String())
|
||||
}
|
||||
}
|
||||
if got := env.mcpExpect(t, http.StatusOK, http.MethodPut, "/_seo/api/v1/seo", agent, map[string]any{"url": "/pages/a", "locale": "pl", "values": map[string]any{"title": "Po polsku"}}); got["locale"] != "pl" {
|
||||
t.Fatalf("set_url_seo locale pl = %v", got)
|
||||
}
|
||||
|
||||
// URL rules.
|
||||
rule := env.mcpExpect(t, http.StatusCreated, http.MethodPost, "/_seo/api/v1/rules", agent, map[string]any{"url": "/blog?tag=*", "match": "pattern", "robots": "noindex"})
|
||||
data, _ := rule["data"].(map[string]any)
|
||||
|
||||
Reference in New Issue
Block a user