Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

104 řádky
3.2KB

  1. package app
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "time"
  8. "github.com/go-chi/chi/v5"
  9. "qctextbuilder/internal/buildsvc"
  10. "qctextbuilder/internal/config"
  11. "qctextbuilder/internal/httpserver"
  12. "qctextbuilder/internal/httpserver/handlers"
  13. "qctextbuilder/internal/httpserver/views"
  14. "qctextbuilder/internal/logging"
  15. "qctextbuilder/internal/mapping"
  16. "qctextbuilder/internal/onboarding"
  17. "qctextbuilder/internal/polling"
  18. "qctextbuilder/internal/qcclient"
  19. "qctextbuilder/internal/store/memory"
  20. "qctextbuilder/internal/templatesvc"
  21. )
  22. type App struct {
  23. server *httpserver.Server
  24. pollingSvc *polling.Service
  25. }
  26. func New(cfg config.Config) (*App, error) {
  27. logger := logging.New()
  28. memStore := memory.New()
  29. qc := qcclient.New(cfg.QCBaseURL, cfg.QCToken, 15*time.Second, logger)
  30. templateSvc := templatesvc.New(qc, memStore, memStore)
  31. onboardSvc := onboarding.New(qc, memStore, memStore)
  32. mappingSvc := mapping.New()
  33. buildSvc := buildsvc.New(qc, memStore, memStore, memStore, mappingSvc, time.Duration(cfg.PollTimeoutSeconds)*time.Second)
  34. pollingSvc := polling.New(buildSvc, memStore, time.Duration(cfg.PollIntervalSeconds)*time.Second, cfg.PollMaxConcurrent, logger)
  35. api := handlers.NewAPI(templateSvc, onboardSvc, buildSvc)
  36. renderer, err := views.NewRenderer("web/templates/*.gohtml")
  37. if err != nil {
  38. return nil, fmt.Errorf("init renderer: %w", err)
  39. }
  40. ui := handlers.NewUI(templateSvc, onboardSvc, buildSvc, cfg, renderer)
  41. server := httpserver.New(cfg.HTTPAddr, logger, func(r chi.Router) {
  42. r.Get("/", ui.Home)
  43. r.Get("/settings", ui.Settings)
  44. r.Get("/templates", ui.Templates)
  45. r.Post("/templates/sync", ui.SyncTemplates)
  46. r.Get("/templates/{id}", ui.TemplateDetail)
  47. r.Post("/templates/{id}/onboard", ui.OnboardTemplate)
  48. r.Post("/templates/{id}/fields", ui.UpdateTemplateFields)
  49. r.Get("/builds/new", ui.BuildNew)
  50. r.Post("/builds", ui.CreateBuild)
  51. r.Get("/builds/{id}", ui.BuildDetail)
  52. r.Post("/builds/{id}/poll", ui.PollBuild)
  53. r.Post("/builds/{id}/fetch-editor-url", ui.FetchEditorURL)
  54. r.Get("/healthz", api.Health)
  55. r.Route("/api", func(r chi.Router) {
  56. r.Post("/templates/sync", api.SyncTemplates)
  57. r.Get("/templates", api.ListTemplates)
  58. r.Get("/templates/{id}", api.GetTemplateDetail)
  59. r.Post("/templates/{id}/onboard", api.OnboardTemplate)
  60. r.Put("/templates/{id}/fields", api.UpdateTemplateFields)
  61. r.Post("/site-builds", api.StartBuild)
  62. r.Get("/site-builds/{id}", api.GetBuild)
  63. r.Post("/site-builds/{id}/poll", api.PollBuildOnce)
  64. r.Post("/site-builds/{id}/fetch-editor-url", api.FetchBuildEditorURL)
  65. })
  66. })
  67. return &App{server: server, pollingSvc: pollingSvc}, nil
  68. }
  69. func (a *App) Run(ctx context.Context) error {
  70. go func() {
  71. if err := a.pollingSvc.Start(ctx); err != nil {
  72. // polling is best-effort in milestone-2; request flow works without supervisor
  73. }
  74. }()
  75. errCh := make(chan error, 1)
  76. go func() {
  77. if err := a.server.Run(); err != nil && !errors.Is(err, http.ErrServerClosed) {
  78. errCh <- fmt.Errorf("http run: %w", err)
  79. }
  80. close(errCh)
  81. }()
  82. select {
  83. case <-ctx.Done():
  84. shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  85. defer cancel()
  86. return a.server.Shutdown(shutdownCtx)
  87. case err := <-errCh:
  88. return err
  89. }
  90. }