package lkgateway import ( "context" "errors" "log" "net/http" "time" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2mcore" "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter/mock" ) // ServerConfig — конфигурация HTTP-сервера lk-gateway. type ServerConfig struct { Addr string DefaultSender m2m.DeponentCode DefaultReceiver m2m.DeponentCode CheckOptions func() CheckOptions MockDecisionDelay time.Duration // 0 = дефолт 3 секунды } // Server — обвязка HTTP + сервис + workers. type Server struct { cfg ServerConfig svc *Service mock *mock.Sender store *SeedStore mux *http.ServeMux server *http.Server } // NewServer собирает Server с in-memory репозиторием, mock NSDSender, // SeedStore и REST + Admin маршрутами. func NewServer(cfg ServerConfig) (*Server, error) { store := NewSeedStore() mockCfg := mock.DefaultConfig() mockCfg.NSDSenderCode = "MC0010300000" if cfg.MockDecisionDelay > 0 { mockCfg.DecisionDelay = cfg.MockDecisionDelay } sender := mock.NewSender(mockCfg) svc := NewService(Config{ Repository: m2mcore.NewMemoryRepository(), Sender: sender, Store: store, Recorder: m2mcore.NewMemoryRecorder(), DefaultSender: cfg.DefaultSender, DefaultReceiver: cfg.DefaultReceiver, }) mux := http.NewServeMux() RegisterAPI(mux, svc) if cfg.CheckOptions == nil { cfg.CheckOptions = func() CheckOptions { return CheckOptions{Profile: "demo (mock NSD)", CryptoProvider: "stub"} } } if err := RegisterAdmin(mux, svc, cfg.CheckOptions); err != nil { return nil, err } registerHealth(mux) registerSetCallback(mux, svc) registerSeedListing(mux, store) return &Server{ cfg: cfg, svc: svc, mock: sender, store: store, mux: mux, server: &http.Server{ Addr: cfg.Addr, Handler: mux, ReadHeaderTimeout: 5 * time.Second, }, }, nil } // SetCallbackURL обновляет адрес, куда отправлять PATCH callback'и в ЛК. func (s *Server) SetCallbackURL(url string) { s.svc.callbackURL = url } // Service возвращает Service для тестов. func (s *Server) Service() *Service { return s.svc } // Mock возвращает mock-сендер. func (s *Server) Mock() *mock.Sender { return s.mock } // Store возвращает SeedStore. func (s *Server) Store() *SeedStore { return s.store } // Mux возвращает обработчик (для httptest). func (s *Server) Mux() http.Handler { return s.mux } // Run поднимает HTTP-сервер и фоновый Decisions-consumer. // Блокируется до ctx.Done(). func (s *Server) Run(ctx context.Context) error { go s.consumeDecisions(ctx) errCh := make(chan error, 1) go func() { log.Printf("lk-gateway: listen %s", s.cfg.Addr) errCh <- s.server.ListenAndServe() }() select { case <-ctx.Done(): shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _ = s.server.Shutdown(shutdownCtx) return nil case err := <-errCh: if errors.Is(err, http.ErrServerClosed) { return nil } return err } } // consumeDecisions слушает Decisions от mock и обновляет соответствующие сделки. func (s *Server) consumeDecisions(ctx context.Context) { for { select { case <-ctx.Done(): return case d := <-s.mock.Decisions(): if d == nil { continue } if err := s.svc.ApplyDecision(ctx, d); err != nil { log.Printf("lk-gateway: ApplyDecision GUID=%s: %v", d.Header.GUID, err) } else { log.Printf("lk-gateway: Decision применён GUID=%s, callback в %s", d.Header.GUID, s.svc.callbackURL) } } } } func registerHealth(mux *http.ServeMux) { mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("ok\n")) }) } // registerSetCallback — служебный POST /admin/api/callback-url для // эмулятора ЛК, чтобы сообщить gateway свой URL. func registerSetCallback(mux *http.ServeMux, svc *Service) { mux.HandleFunc("/admin/api/callback-url", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method", http.StatusMethodNotAllowed) return } url := r.URL.Query().Get("url") if url == "" { http.Error(w, "url required", http.StatusBadRequest) return } svc.callbackURL = url w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("ok")) }) } func registerSeedListing(mux *http.ServeMux, store *SeedStore) { mux.HandleFunc("/admin/api/clients", func(w http.ResponseWriter, _ *http.Request) { type c struct { ID, LastName, FirstName, MiddleName string } out := make([]c, 0) for _, cl := range store.Clients() { out = append(out, c{ID: cl.ID, LastName: cl.LastName, FirstName: cl.FirstName, MiddleName: cl.MiddleName}) } writeJSON(w, http.StatusOK, out) }) }