| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- 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
- }
|