feat(api): one API for curl and MCP, REST reads with keys, origin badge with key name
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m16s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m16s
API keys reach tasks and lists over plain REST too (GET /task-lists, GET /tasks with
status/due filters, PATCH/DELETE /thoughts/{id}, GET /lists, list items), guarded by a route
table; notes and settings stay closed. Rights hold for every access alike (webhook:task also
allows create_task over MCP, tasks:write also task delivery).
Tasks, thoughts and list items record which key created them and which one changed them last
(migration 13 replaces agent_mark; origin stays after the user's own edits and survives the
triage into a list item). OriginBadge shows the key name with a webhook or robot icon, details
on hover and tap, and replaces the Webhook and KI badges everywhere, now also on list items.
Settings: category "API" with rights grouped by tasks, lists and inbox and two tabs
(curl / smart home with a Home Assistant sensor, AI agents over MCP). Docs, help, README and
changelog updated; v0.30.0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0ca715ba64
commit
b883315914
36 files changed
+1119
-563
No files matched your search
@@ -1,7 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -70,14 +69,15 @@ func (s *Server) handleWebhookIngest(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Same checks as every other request (revoked/outdated sessions, inactive accounts)
|
||||
user, keyScopes := s.authenticateToken(token)
|
||||
user, key := s.authenticateToken(token)
|
||||
if user == nil {
|
||||
writeError(w, http.StatusUnauthorized, "Ungültiger Webhook-Token oder API-Key")
|
||||
return
|
||||
}
|
||||
if keyScopes != nil {
|
||||
r = r.WithContext(context.WithValue(r.Context(), apiKeyScopesKey{}, keyScopes))
|
||||
if key != nil {
|
||||
r = r.WithContext(withAPIKey(r.Context(), key, storage.OriginAPI))
|
||||
}
|
||||
actor := actorFrom(r.Context())
|
||||
|
||||
var req struct {
|
||||
Title string `json:"title"`
|
||||
@@ -163,11 +163,11 @@ func (s *Server) handleWebhookIngest(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if target == "list_item" {
|
||||
s.ingestListItem(w, user.ID, req.ListID, bodyText, req.Category, req.Quantity)
|
||||
s.ingestListItem(w, user.ID, req.ListID, bodyText, req.Category, req.Quantity, actor)
|
||||
return
|
||||
}
|
||||
if target == "task" {
|
||||
s.ingestTask(w, user.ID, bodyText, req.Priority, source, dueDate, req.TaskListID)
|
||||
s.ingestTask(w, user.ID, bodyText, req.Priority, source, dueDate, req.TaskListID, actor)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -179,6 +179,7 @@ func (s *Server) handleWebhookIngest(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
s.recordThoughtOrigin(thought, actor, true)
|
||||
|
||||
s.wsHub.BroadcastToUsers(s.taskAudience(thought), "TASK_CREATED", thought)
|
||||
|
||||
@@ -188,9 +189,9 @@ func (s *Server) handleWebhookIngest(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// createTask creates a task directly (webhook scope webhook:task, MCP tool create_task), optionally in
|
||||
// a task list the user may write to (otherwise storage.ErrTaskListReadOnly).
|
||||
func (s *Server) createTask(userID, content, priority, source, dueDate, taskListID, notes string) (*domain.Thought, error) {
|
||||
// createTask creates a task directly (POST /webhooks/ingest, MCP tool create_task), optionally in a
|
||||
// task list the user may write to (otherwise storage.ErrTaskListReadOnly). actor: the API key, if any.
|
||||
func (s *Server) createTask(userID, content, priority, source, dueDate, taskListID, notes string, actor *apiActor) (*domain.Thought, error) {
|
||||
if priority == "" {
|
||||
priority = "normal"
|
||||
}
|
||||
@@ -205,12 +206,13 @@ func (s *Server) createTask(userID, content, priority, source, dueDate, taskList
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.recordThoughtOrigin(task, actor, true)
|
||||
s.wsHub.BroadcastToUsers(s.taskAudience(task), "TASK_CREATED", task)
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func (s *Server) ingestTask(w http.ResponseWriter, userID, content, priority, source, dueDate, taskListID string) {
|
||||
task, err := s.createTask(userID, content, priority, source, dueDate, taskListID, "")
|
||||
func (s *Server) ingestTask(w http.ResponseWriter, userID, content, priority, source, dueDate, taskListID string, actor *apiActor) {
|
||||
task, err := s.createTask(userID, content, priority, source, dueDate, taskListID, "", actor)
|
||||
if errors.Is(err, storage.ErrTaskListReadOnly) {
|
||||
writeError(w, http.StatusForbidden, err.Error())
|
||||
return
|
||||
@@ -226,7 +228,7 @@ var errListIDRequired = errors.New("Feld 'listId' ist für Listeneinträge erfor
|
||||
|
||||
// addListItem appends an item to a list the user may write to; the error is storage.ErrListReadOnly for
|
||||
// viewers and "not found" for lists the user cannot see.
|
||||
func (s *Server) addListItem(userID, listID, content, category, quantity string, byAgent bool) (*domain.ListItem, error) {
|
||||
func (s *Server) addListItem(userID, listID, content, category, quantity string, actor *apiActor) (*domain.ListItem, error) {
|
||||
if strings.TrimSpace(listID) == "" {
|
||||
return nil, errListIDRequired
|
||||
}
|
||||
@@ -237,16 +239,14 @@ func (s *Server) addListItem(userID, listID, content, category, quantity string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if byAgent {
|
||||
item.AgentMark = s.store.MarkListItemAgentCreated(item.ID)
|
||||
}
|
||||
s.recordListItemOrigin(item, actor, true)
|
||||
s.wsHub.BroadcastListEvent(listID, "ITEM_CREATED", item)
|
||||
return item, nil
|
||||
}
|
||||
|
||||
// ingestListItem adds an item to a list (scope webhook:list) the owner of the key may write to.
|
||||
func (s *Server) ingestListItem(w http.ResponseWriter, userID, listID, content, category, quantity string) {
|
||||
item, err := s.addListItem(userID, listID, content, category, quantity, false)
|
||||
// ingestListItem adds an item to a list the owner of the key may write to.
|
||||
func (s *Server) ingestListItem(w http.ResponseWriter, userID, listID, content, category, quantity string, actor *apiActor) {
|
||||
item, err := s.addListItem(userID, listID, content, category, quantity, actor)
|
||||
if err != nil {
|
||||
status := http.StatusNotFound
|
||||
switch {
|
||||
|
||||
Reference in new issue
Block a user