Sfoglia il codice sorgente

fix(edit_page): preserve publish/privacy state via read-before-write

Paul Klumpp 1 giorno fa
parent
commit
139e2e6e07

+ 57 - 5
internal/server/tools.go

@@ -182,17 +182,56 @@ func (h *handlers) editPage(ctx context.Context, _ *mcp.CallToolRequest, in edit
 		return nil, mutationOutput{}, err
 	}
 
+	// Read the current page so that fields the caller did not explicitly set
+	// (publish state, privacy, locale, tags) are preserved instead of being
+	// overwritten with defaults. The pages.update mutation requires all of
+	// them, so "leave unchanged" is only possible by re-sending the existing
+	// values.
+	existing, err := h.wiki.PageByID(ctx, in.ID)
+	if err != nil {
+		return nil, mutationOutput{}, fmt.Errorf("failed to load page: %w", err)
+	}
+	if existing == nil {
+		return nil, mutationOutput{}, fmt.Errorf("failed to update page: Page not found")
+	}
+
+	path := in.Path
+	if strings.TrimSpace(path) == "" {
+		path = existing.Path
+	}
+	locale := in.Locale
+	if strings.TrimSpace(locale) == "" {
+		locale = existing.Locale
+	}
+	if strings.TrimSpace(locale) == "" {
+		locale = "en"
+	}
+
+	isPublished := existing.IsPublished
+	if in.IsPublished != nil {
+		isPublished = *in.IsPublished
+	}
+	isPrivate := existing.IsPrivate
+	if in.IsPrivate != nil {
+		isPrivate = *in.IsPrivate
+	}
+
+	tags := existingTags(existing.Tags)
+	if in.Tags != nil {
+		tags = nonNilStrings(in.Tags)
+	}
+
 	result, err := h.wiki.UpdatePage(ctx, wikijs.UpdatePageInput{
 		ID:          in.ID,
 		Title:       in.Title,
 		Content:     in.Content,
 		Description: in.Description,
-		Path:        in.Path,
+		Path:        path,
 		Editor:      defaultValue(in.Editor, "markdown"),
-		IsPublished: boolValue(in.IsPublished, true),
-		IsPrivate:   boolValue(in.IsPrivate, false),
-		Locale:      defaultValue(in.Locale, "en"),
-		Tags:        nonNilStrings(in.Tags),
+		IsPublished: isPublished,
+		IsPrivate:   isPrivate,
+		Locale:      locale,
+		Tags:        tags,
 	})
 	if err != nil {
 		return nil, mutationOutput{}, err
@@ -265,6 +304,19 @@ func nonNilStrings(values []string) []string {
 	return values
 }
 
+// existingTags converts the tags of an existing page (Tag structs) into the
+// string slice expected by UpdatePageInput, preserving them when the caller
+// did not pass tags.
+func existingTags(tags []wikijs.Tag) []string {
+	out := make([]string, 0, len(tags))
+	for _, t := range tags {
+		if t.Tag != "" {
+			out = append(out, t.Tag)
+		}
+	}
+	return out
+}
+
 // nullResult preserves the original server's "null" response for a page that
 // does not exist. A typed nil page would be coerced to an empty object by the
 // MCP SDK's output schema handling.

+ 2 - 1
internal/server/tools_test.go

@@ -71,7 +71,8 @@ func (f *fakeWiki) handle(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprintf(w, `{"data":{"pages":{"single":{
 			"id":%d,"path":"/dev/go","title":"Go","description":"desc",
 			"tags":[{"tag":"go","title":"Go"}],"content":"body",
-			"createdAt":"c","updatedAt":"u","locale":"de"}}}}`, int(id))
+			"createdAt":"c","updatedAt":"u","locale":"de",
+			"isPublished":true,"isPrivate":false}}}}`, int(id))
 
 	case strings.Contains(req.Query, "query GetPageByPath"):
 		path, _ := req.Variables["path"].(string)

+ 4 - 0
internal/wikijs/queries.go

@@ -30,6 +30,8 @@ const pageByIDQuery = `query GetPageById($id: Int!) {
       createdAt
       updatedAt
       locale
+      isPublished
+      isPrivate
     }
   }
 }`
@@ -45,6 +47,8 @@ const pageByPathQuery = `query GetPageByPath($path: String!, $locale: String!) {
       createdAt
       updatedAt
       locale
+      isPublished
+      isPrivate
       tags {
         tag
         title

+ 2 - 0
internal/wikijs/types.go

@@ -18,6 +18,8 @@ type Page struct {
 	CreatedAt   string `json:"createdAt"`
 	UpdatedAt   string `json:"updatedAt"`
 	Locale      string `json:"locale"`
+	IsPublished bool   `json:"isPublished"`
+	IsPrivate   bool   `json:"isPrivate"`
 	URL         string `json:"url"`
 }