Files
sm-bm-plugin/mcp_files.go
Jakub Zych 924e6aa0dc feat: signed one-time uploads for material files
- classes/uploads.go: tickets signed with HMAC-SHA256 under the
  jz.bm.upload.v1: label (app.key), 10-minute expiry, the nonce pulled
  from the cache once (duplicated from golem15.presentations on purpose)
- POST /api/v1/bm/materials/{id}/files/upload-url issues the URL with a
  curl example; POST /_bm/api/v1/uploads/{ticket} takes one file part,
  no bearer, throttled per IP and per ticket, user and permission
  re-checked, stored through storeMaterialFile
- bm_manage_create_material_upload; README documents the file routes
2026-10-28 15:10:32 +01:00

47 lines
3.3 KiB
Go

package bm
import (
"net/http"
"git.golem15.com/golem15/summercms/modules/pact"
"git.golem15.com/jakub/sm-bm-plugin/controllers/api"
)
// attachMaterialFileInput is the input of bm_manage_attach_material_file.
type attachMaterialFileInput struct {
ID uint `json:"id" path:"id" jsonschema:"the material id"`
Name string `json:"name" jsonschema:"file name with extension, for example notatki.pdf"`
Mime string `json:"mime,omitempty" jsonschema:"optional MIME type; the stored type must match"`
ContentBase64 string `json:"content_base64" jsonschema:"standard base64, at most 3000000 bytes decoded; use bm_manage_create_material_upload for larger files"`
}
// detachMaterialFileInput is the input of bm_manage_detach_material_file.
type detachMaterialFileInput struct {
ID uint `json:"id" path:"id" jsonschema:"the material id"`
File uint `json:"file" path:"file" jsonschema:"the file id, as listed in the files of bm_manage_show_material"`
}
// fileTools are the material file tools over the management API (D-10,
// D-11, D-17): attach a small file as base64, get a signed one-time upload
// URL for a larger one, and detach a file. Every description carries the
// public-file warning and says no file contents are returned.
func fileTools() []pact.MCPTool {
tool := func(name, title, desc, method, path string, input any) pact.MCPTool {
return pact.MCPTool{Name: name, Title: title, Description: desc, Guard: "user", Method: method, Path: ManagePrefix + path, Input: input}
}
return []pact.MCPTool{
tool("manage_attach_material_file", "Attach a file to a material",
"Attaches one file sent as standard base64 (at most 3000000 bytes decoded; for larger files use bm_manage_create_material_upload) to a material, after its files; a material holds at most 20. The extension and the content are checked; a sent mime must match the content. Returns the material with its files (id, absolute link, name, size and type) and a warning; 404 when the material is unknown, 422 when the file is refused. "+
api.MaterialFileWarning+" "+MCPMaterialPrivacy+writeMaterials,
http.MethodPost, "/materials/{id}/files/inline", attachMaterialFileInput{}),
tool("manage_create_material_upload", "Get an upload URL for a material file",
"Returns a one-time upload URL for one file of this material, valid for 10 minutes, bound to this material and to you, and usable once: send the file with the returned curl_example (curl -F 'file=@<path>' '<upload_url>') from a shell, so the bytes never pass through MCP. At most 50 MB, one of the listed accept extensions; the answer also has expires_at, max_bytes and a warning. A used, expired or forged URL gets 403. 404 when the material is unknown, 503 when uploads are not configured. "+
api.MaterialFileWarning+" "+MCPMaterialPrivacy+writeMaterials,
http.MethodPost, "/materials/{id}/files/upload-url", idInput{}),
tool("manage_detach_material_file", "Detach a file from a material",
"Deletes one file of a material, its row and its stored blob, so its link stops working; 404 when the material or the file is unknown or the file belongs to another material."+deleteForever+" "+
api.MaterialFileWarning+" "+MCPMaterialPrivacy+writeMaterials,
http.MethodDelete, "/materials/{id}/files/{file}", detachMaterialFileInput{}),
}
}