186 lines
6.9 KiB
Go
186 lines
6.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"git.cin.su/m/Assistant/contracts/continuity"
|
|
"git.cin.su/m/Assistant/contracts/identity"
|
|
)
|
|
|
|
type attentionRepository interface {
|
|
AttentionByOwner(context.Context, identity.PrincipalID, int) ([]continuity.AttentionItem, error)
|
|
CreateAttentionItem(context.Context, continuity.AttentionItem) (continuity.AttentionItem, error)
|
|
}
|
|
|
|
func registerCreatorAttentionRoutes(mux *http.ServeMux, identityClient creatorIdentityClient, repository attentionRepository) {
|
|
mux.HandleFunc("GET /v1/creator/attention", func(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := creatorSessionForAttention(w, r, identityClient)
|
|
if !ok {
|
|
return
|
|
}
|
|
limit, err := parseListLimit(r.URL.Query().Get("limit"))
|
|
if err != nil {
|
|
http.Error(w, "invalid attention limit", http.StatusBadRequest)
|
|
return
|
|
}
|
|
items, err := repository.AttentionByOwner(r.Context(), session.PrincipalID, limit)
|
|
if err != nil {
|
|
http.Error(w, "attention unavailable", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
_ = json.NewEncoder(w).Encode(continuity.AttentionListResult{Items: items})
|
|
})
|
|
mux.HandleFunc("GET /v1/creator/today", func(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := creatorSessionForAttention(w, r, identityClient)
|
|
if !ok {
|
|
return
|
|
}
|
|
items, err := repository.AttentionByOwner(r.Context(), session.PrincipalID, 50)
|
|
if err != nil {
|
|
http.Error(w, "today unavailable", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
result := continuity.TodayResult{Items: make([]continuity.TodayItem, 0, len(items))}
|
|
for _, item := range items {
|
|
result.Items = append(result.Items, continuity.TodayItem{
|
|
Title: item.Summary,
|
|
WhenUnixMillis: item.DueAtUnixMillis,
|
|
AttentionLevel: item.Level,
|
|
AttentionState: item.State,
|
|
HumanState: humanAttentionState(item.State),
|
|
DeliveryState: humanDeliveryState(item),
|
|
SourceDescription: humanAttentionSource(item.SourceKind),
|
|
ResponseHint: humanResponseHint(item),
|
|
})
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
_ = json.NewEncoder(w).Encode(result)
|
|
})
|
|
mux.HandleFunc("POST /v1/creator/attention", func(w http.ResponseWriter, r *http.Request) {
|
|
session, ok := creatorSessionForAttention(w, r, identityClient)
|
|
if !ok {
|
|
return
|
|
}
|
|
var request continuity.CreateAttentionRequest
|
|
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 32<<10))
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(&request); err != nil {
|
|
http.Error(w, "invalid attention request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if request.OwnerPrincipalID == "" {
|
|
request.OwnerPrincipalID = session.PrincipalID
|
|
}
|
|
if request.OwnerPrincipalID != session.PrincipalID || request.ScopeID == "" || request.SourceKind == "" || request.Level == "" || request.Summary == "" || request.CreatedAtUnixMillis < 1 || request.DueAtUnixMillis < 1 {
|
|
http.Error(w, "invalid attention request", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if request.SourceKind == continuity.AttentionSourceAgentDelegation && request.SourceExternalRef == "" {
|
|
http.Error(w, "delegation attention requires source external ref", http.StatusBadRequest)
|
|
return
|
|
}
|
|
item := continuity.AttentionItem{
|
|
ID: continuity.AttentionItemID(request.SourceExternalRef),
|
|
OwnerPrincipalID: request.OwnerPrincipalID,
|
|
ScopeID: request.ScopeID,
|
|
SourceKind: request.SourceKind,
|
|
SourceProspectiveID: request.SourceProspectiveID,
|
|
SourceExternalRef: request.SourceExternalRef,
|
|
Level: request.Level,
|
|
State: continuity.AttentionPending,
|
|
Summary: request.Summary,
|
|
DueAtUnixMillis: request.DueAtUnixMillis,
|
|
CreatedAtUnixMillis: request.CreatedAtUnixMillis,
|
|
Revision: 1,
|
|
}
|
|
saved, err := repository.CreateAttentionItem(r.Context(), item)
|
|
if err != nil {
|
|
http.Error(w, "attention unavailable", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
w.WriteHeader(http.StatusCreated)
|
|
_ = json.NewEncoder(w).Encode(saved)
|
|
})
|
|
}
|
|
|
|
func creatorSessionForAttention(w http.ResponseWriter, r *http.Request, identityClient creatorIdentityClient) (identity.CreatorSession, bool) {
|
|
session, err := identityClient.Session(r.Context(), r.Header.Get("Authorization"))
|
|
if errors.Is(err, errCreatorSessionDenied) {
|
|
http.Error(w, "creator session denied", http.StatusUnauthorized)
|
|
return identity.CreatorSession{}, false
|
|
}
|
|
if err != nil {
|
|
http.Error(w, "identity service unavailable", http.StatusServiceUnavailable)
|
|
return identity.CreatorSession{}, false
|
|
}
|
|
return session, true
|
|
}
|
|
|
|
func humanAttentionState(state continuity.AttentionState) string {
|
|
switch state {
|
|
case continuity.AttentionPending:
|
|
return "ожидает внимания"
|
|
case continuity.AttentionDelivering:
|
|
return "доставляется"
|
|
case continuity.AttentionDelivered:
|
|
return "доставлено"
|
|
case continuity.AttentionResolved:
|
|
return "закрыто"
|
|
case continuity.AttentionSuppressed:
|
|
return "скрыто"
|
|
case continuity.AttentionExpired:
|
|
return "истекло"
|
|
case continuity.AttentionFailed:
|
|
return "не доставлено"
|
|
default:
|
|
return "состояние уточняется"
|
|
}
|
|
}
|
|
|
|
func humanResponseHint(item continuity.AttentionItem) string {
|
|
if item.SourceKind == continuity.AttentionSourceAgentDelegation {
|
|
if item.State == continuity.AttentionDelivered || item.State == continuity.AttentionPending || item.State == continuity.AttentionFailed {
|
|
return "Можно ответить: «Продолжай» или «Нет, нужен другой вариант»."
|
|
}
|
|
if item.ResponseOutcome != "" {
|
|
return "Ответ записан: " + string(item.ResponseOutcome)
|
|
}
|
|
return ""
|
|
}
|
|
if item.State == continuity.AttentionDelivered || item.State == continuity.AttentionPending || item.State == continuity.AttentionFailed {
|
|
return "Можно ответить: «Готово», «Отложи на 10 минут» или «Отмени»."
|
|
}
|
|
if item.ResponseOutcome != "" {
|
|
return "Ответ записан: " + string(item.ResponseOutcome)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func humanDeliveryState(item continuity.AttentionItem) string {
|
|
if item.State == continuity.AttentionDelivered {
|
|
return "уведомление отправлено"
|
|
}
|
|
if item.State == continuity.AttentionDelivering {
|
|
return "уведомление отправляется"
|
|
}
|
|
return "уведомление ещё не отправлено"
|
|
}
|
|
|
|
func humanAttentionSource(kind continuity.AttentionSourceKind) string {
|
|
if kind == continuity.AttentionSourceProspectiveReminder {
|
|
return "напоминание"
|
|
}
|
|
if kind == continuity.AttentionSourceAgentDelegation {
|
|
return "задача coding-agent"
|
|
}
|
|
return "событие Домового"
|
|
}
|