Files
tutti/services/tuttid/api/daemon_user_presence.go
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

106 lines
5.0 KiB
Go

package api
import (
"context"
"errors"
"log/slog"
"strings"
tuttigenerated "github.com/tutti-os/tutti/services/tuttid/api/generated"
"github.com/tutti-os/tutti/services/tuttid/apierrors"
userpresenceservice "github.com/tutti-os/tutti/services/tuttid/service/userpresence"
)
type UserPresenceService interface {
VisitRoom(context.Context, userpresenceservice.VisitRoomInput) (userpresenceservice.RoomPresenceSnapshot, error)
RoomSnapshot(string) userpresenceservice.RoomPresenceSnapshot
SetForeground(bool)
}
func (api DaemonAPI) PutAccountUserPresenceCurrentRoom(ctx context.Context, request tuttigenerated.PutAccountUserPresenceCurrentRoomRequestObject) (tuttigenerated.PutAccountUserPresenceCurrentRoomResponseObject, error) {
if api.UserPresenceService == nil {
return tuttigenerated.PutAccountUserPresenceCurrentRoom503JSONResponse{
ServiceUnavailableErrorJSONResponse: userPresenceUnavailable("user_presence_service_unavailable", nil),
}, nil
}
if request.Body == nil || strings.TrimSpace(request.Body.RoomId) == "" {
return tuttigenerated.PutAccountUserPresenceCurrentRoom400JSONResponse{
InvalidRequestErrorJSONResponse: invalidRequestError(apierrors.InvalidRequest("user_presence_room_required")),
}, nil
}
members := make([]userpresenceservice.RoomMemberProjection, 0, len(request.Body.Members))
for _, member := range request.Body.Members {
members = append(members, userpresenceservice.RoomMemberProjection{
UserID: member.UserId, MembershipActive: member.MembershipActive,
AccountPresenceCapable: member.AccountPresenceCapable,
})
}
snapshot, err := api.UserPresenceService.VisitRoom(ctx, userpresenceservice.VisitRoomInput{
RoomID: request.Body.RoomId, Members: members,
})
if err != nil {
if errors.Is(err, userpresenceservice.ErrPresenceUserLimit) || errors.Is(err, userpresenceservice.ErrPresenceFrameLimit) {
return tuttigenerated.PutAccountUserPresenceCurrentRoom400JSONResponse{
InvalidRequestErrorJSONResponse: invalidRequestError(apierrors.InvalidRequest("user_presence_interest_quota_exceeded", apierrors.WithCause(err))),
}, nil
}
// The page is allowed to open with a non-authoritative offline
// projection. AccountRealtime retains the desired set for reconnection,
// so transport/snapshot failures are diagnostics rather than API failure.
slog.Warn("user presence current room remains degraded", "event", "tutti.user_presence.degraded", "roomId", request.Body.RoomId, "error", err)
}
return tuttigenerated.PutAccountUserPresenceCurrentRoom200JSONResponse(generatedRoomPresence(snapshot)), nil
}
func (api DaemonAPI) GetAccountUserPresenceRoom(_ context.Context, request tuttigenerated.GetAccountUserPresenceRoomRequestObject) (tuttigenerated.GetAccountUserPresenceRoomResponseObject, error) {
if api.UserPresenceService == nil {
return tuttigenerated.GetAccountUserPresenceRoom503JSONResponse{
ServiceUnavailableErrorJSONResponse: userPresenceUnavailable("user_presence_service_unavailable", nil),
}, nil
}
return tuttigenerated.GetAccountUserPresenceRoom200JSONResponse(generatedRoomPresence(api.UserPresenceService.RoomSnapshot(request.RoomID))), nil
}
func (api DaemonAPI) PutAccountUserPresenceForeground(_ context.Context, request tuttigenerated.PutAccountUserPresenceForegroundRequestObject) (tuttigenerated.PutAccountUserPresenceForegroundResponseObject, error) {
if api.UserPresenceService == nil {
return tuttigenerated.PutAccountUserPresenceForeground503JSONResponse{
ServiceUnavailableErrorJSONResponse: userPresenceUnavailable("user_presence_service_unavailable", nil),
}, nil
}
if request.Body == nil {
return tuttigenerated.PutAccountUserPresenceForeground400JSONResponse{
InvalidRequestErrorJSONResponse: invalidRequestError(apierrors.EmptyBody()),
}, nil
}
api.UserPresenceService.SetForeground(request.Body.Foreground)
return tuttigenerated.PutAccountUserPresenceForeground204Response{}, nil
}
func generatedRoomPresence(snapshot userpresenceservice.RoomPresenceSnapshot) tuttigenerated.AccountUserPresenceRoomResponse {
result := tuttigenerated.AccountUserPresenceRoomResponse{
RoomId: snapshot.RoomID, Members: make([]tuttigenerated.AccountUserPresenceUser, 0, len(snapshot.Members)),
}
for _, member := range snapshot.Members {
generated := tuttigenerated.AccountUserPresenceUser{
UserId: member.UserID, Status: tuttigenerated.AccountUserPresenceUserStatus(member.Status),
Availability: tuttigenerated.AccountUserPresenceUserAvailability(member.Availability),
Authoritative: member.Authoritative, AuthorityGeneration: member.AuthorityGeneration,
PresenceRevision: member.PresenceRevision,
}
if !member.ObservedAt.IsZero() {
observedAt := member.ObservedAt
generated.ObservedAt = &observedAt
}
result.Members = append(result.Members, generated)
}
return result
}
func userPresenceUnavailable(reason string, err error) tuttigenerated.ServiceUnavailableErrorJSONResponse {
options := []apierrors.Option{}
if err != nil {
options = append(options, apierrors.WithCause(err))
}
return serviceUnavailableError(apierrors.ServiceUnavailable(reason, options...))
}