149 lines
4.0 KiB
Go
149 lines
4.0 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"cmr-backend/internal/apperr"
|
|
"cmr-backend/internal/store/postgres"
|
|
)
|
|
|
|
type DevService struct {
|
|
appEnv string
|
|
store *postgres.Store
|
|
mu sync.Mutex
|
|
logSeq int64
|
|
logs []ClientDebugLogEntry
|
|
}
|
|
|
|
type ClientDebugLogEntry struct {
|
|
ID int64 `json:"id"`
|
|
Source string `json:"source"`
|
|
Level string `json:"level"`
|
|
Category string `json:"category,omitempty"`
|
|
Message string `json:"message"`
|
|
EventID string `json:"eventId,omitempty"`
|
|
ReleaseID string `json:"releaseId,omitempty"`
|
|
SessionID string `json:"sessionId,omitempty"`
|
|
ManifestURL string `json:"manifestUrl,omitempty"`
|
|
Route string `json:"route,omitempty"`
|
|
OccurredAt time.Time `json:"occurredAt"`
|
|
ReceivedAt time.Time `json:"receivedAt"`
|
|
Details map[string]any `json:"details,omitempty"`
|
|
}
|
|
|
|
type CreateClientDebugLogInput struct {
|
|
Source string `json:"source"`
|
|
Level string `json:"level"`
|
|
Category string `json:"category"`
|
|
Message string `json:"message"`
|
|
EventID string `json:"eventId"`
|
|
ReleaseID string `json:"releaseId"`
|
|
SessionID string `json:"sessionId"`
|
|
ManifestURL string `json:"manifestUrl"`
|
|
Route string `json:"route"`
|
|
OccurredAt string `json:"occurredAt"`
|
|
Details map[string]any `json:"details"`
|
|
}
|
|
|
|
func NewDevService(appEnv string, store *postgres.Store) *DevService {
|
|
return &DevService{
|
|
appEnv: appEnv,
|
|
store: store,
|
|
}
|
|
}
|
|
|
|
func (s *DevService) Enabled() bool {
|
|
return s.appEnv != "production"
|
|
}
|
|
|
|
func (s *DevService) BootstrapDemo(ctx context.Context) (*postgres.DemoBootstrapSummary, error) {
|
|
if !s.Enabled() {
|
|
return nil, apperr.New(http.StatusNotFound, "not_found", "dev bootstrap is disabled")
|
|
}
|
|
return s.store.EnsureDemoData(ctx)
|
|
}
|
|
|
|
func (s *DevService) AddClientDebugLog(_ context.Context, input CreateClientDebugLogInput) (*ClientDebugLogEntry, error) {
|
|
if !s.Enabled() {
|
|
return nil, apperr.New(http.StatusNotFound, "not_found", "dev client logs are disabled")
|
|
}
|
|
if input.Message == "" {
|
|
return nil, apperr.New(http.StatusBadRequest, "invalid_request", "message is required")
|
|
}
|
|
if input.Source == "" {
|
|
input.Source = "unknown"
|
|
}
|
|
if input.Level == "" {
|
|
input.Level = "info"
|
|
}
|
|
|
|
occurredAt := time.Now().UTC()
|
|
if input.OccurredAt != "" {
|
|
parsed, err := time.Parse(time.RFC3339, input.OccurredAt)
|
|
if err != nil {
|
|
return nil, apperr.New(http.StatusBadRequest, "invalid_request", "occurredAt must be RFC3339")
|
|
}
|
|
occurredAt = parsed.UTC()
|
|
}
|
|
|
|
entry := ClientDebugLogEntry{
|
|
Source: input.Source,
|
|
Level: input.Level,
|
|
Category: input.Category,
|
|
Message: input.Message,
|
|
EventID: input.EventID,
|
|
ReleaseID: input.ReleaseID,
|
|
SessionID: input.SessionID,
|
|
ManifestURL: input.ManifestURL,
|
|
Route: input.Route,
|
|
OccurredAt: occurredAt,
|
|
ReceivedAt: time.Now().UTC(),
|
|
Details: input.Details,
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.logSeq++
|
|
entry.ID = s.logSeq
|
|
s.logs = append(s.logs, entry)
|
|
if len(s.logs) > 200 {
|
|
s.logs = append([]ClientDebugLogEntry(nil), s.logs[len(s.logs)-200:]...)
|
|
}
|
|
copyEntry := entry
|
|
return ©Entry, nil
|
|
}
|
|
|
|
func (s *DevService) ListClientDebugLogs(_ context.Context, limit int) ([]ClientDebugLogEntry, error) {
|
|
if !s.Enabled() {
|
|
return nil, apperr.New(http.StatusNotFound, "not_found", "dev client logs are disabled")
|
|
}
|
|
if limit <= 0 || limit > 200 {
|
|
limit = 50
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
items := append([]ClientDebugLogEntry(nil), s.logs...)
|
|
sort.Slice(items, func(i, j int) bool {
|
|
return items[i].ID > items[j].ID
|
|
})
|
|
if len(items) > limit {
|
|
items = items[:limit]
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *DevService) ClearClientDebugLogs(_ context.Context) error {
|
|
if !s.Enabled() {
|
|
return apperr.New(http.StatusNotFound, "not_found", "dev client logs are disabled")
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.logs = nil
|
|
return nil
|
|
}
|