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
108 lines
3.2 KiB
Go
108 lines
3.2 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
|
|
tuttigenerated "github.com/tutti-os/tutti/services/tuttid/api/generated"
|
|
"github.com/tutti-os/tutti/services/tuttid/apierrors"
|
|
reporterservice "github.com/tutti-os/tutti/services/tuttid/service/reporter"
|
|
)
|
|
|
|
const (
|
|
maxTrackEventsPerRequest = 100
|
|
maxTrackEventNameLength = 128
|
|
)
|
|
|
|
var trackEventNamePattern = regexp.MustCompile(`^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$`)
|
|
|
|
func (api DaemonAPI) TrackEvents(ctx context.Context, request tuttigenerated.TrackEventsRequestObject) (tuttigenerated.TrackEventsResponseObject, error) {
|
|
if response := validateTrackEventsRequest(request); response != nil {
|
|
return response, nil
|
|
}
|
|
if api.AnalyticsReporter == nil {
|
|
return tuttigenerated.TrackEvents503JSONResponse{
|
|
ServiceUnavailableErrorJSONResponse: serviceUnavailableError(
|
|
apierrors.ServiceUnavailable(
|
|
"analytics_reporter_unavailable",
|
|
apierrors.WithDeveloperMessage("analytics reporter is not configured"),
|
|
),
|
|
),
|
|
}, nil
|
|
}
|
|
events := make([]reporterservice.Event, 0, len(request.Body.Events))
|
|
for _, event := range request.Body.Events {
|
|
events = append(events, reporterservice.Event{
|
|
Name: event.Name,
|
|
ClientTS: event.ClientTs,
|
|
Params: copyTrackEventParams(event.Params),
|
|
})
|
|
}
|
|
api.AnalyticsReporter.Track(ctx, events...)
|
|
|
|
return tuttigenerated.TrackEvents202Response{}, nil
|
|
}
|
|
|
|
func validateTrackEventsRequest(request tuttigenerated.TrackEventsRequestObject) tuttigenerated.TrackEventsResponseObject {
|
|
if request.Body == nil || len(request.Body.Events) == 0 {
|
|
return tuttigenerated.TrackEvents400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(
|
|
apierrors.InvalidRequest(
|
|
"analytics_events_required",
|
|
apierrors.WithDeveloperMessage("analytics events are required"),
|
|
),
|
|
),
|
|
}
|
|
}
|
|
if len(request.Body.Events) > maxTrackEventsPerRequest {
|
|
return tuttigenerated.TrackEvents400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(
|
|
apierrors.InvalidRequest(
|
|
"analytics_events_limit_exceeded",
|
|
apierrors.WithDeveloperMessage("analytics events must not exceed 100"),
|
|
),
|
|
),
|
|
}
|
|
}
|
|
for _, event := range request.Body.Events {
|
|
if !isValidTrackEventName(event.Name) {
|
|
return tuttigenerated.TrackEvents400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(
|
|
apierrors.InvalidRequest(
|
|
"analytics_event_name_invalid",
|
|
apierrors.WithDeveloperMessage("analytics event name is invalid"),
|
|
),
|
|
),
|
|
}
|
|
}
|
|
if event.ClientTs < 1 {
|
|
return tuttigenerated.TrackEvents400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(
|
|
apierrors.InvalidRequest(
|
|
"analytics_event_client_ts_invalid",
|
|
apierrors.WithDeveloperMessage("analytics event client_ts is invalid"),
|
|
),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isValidTrackEventName(name string) bool {
|
|
return len(name) > 0 &&
|
|
len(name) <= maxTrackEventNameLength &&
|
|
trackEventNamePattern.MatchString(name)
|
|
}
|
|
|
|
func copyTrackEventParams(params *map[string]interface{}) map[string]any {
|
|
if params == nil {
|
|
return nil
|
|
}
|
|
result := make(map[string]any, len(*params))
|
|
for key, value := range *params {
|
|
result[key] = value
|
|
}
|
|
return result
|
|
}
|