package main import ( "context" "errors" "fmt" "log/slog" "net/http" "os" "os/signal" "time" "git.loyso.art/frx/kurious/internal/common/client/sravni" "git.loyso.art/frx/kurious/internal/common/config" "git.loyso.art/frx/kurious/internal/common/generator" "git.loyso.art/frx/kurious/internal/common/xcontext" "git.loyso.art/frx/kurious/internal/kurious/adapters" xhttp "git.loyso.art/frx/kurious/internal/kurious/ports/http" "git.loyso.art/frx/kurious/internal/kurious/service" "golang.org/x/sync/errgroup" "github.com/gorilla/mux" ) func main() { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() err := app(ctx) if err != nil { println(err.Error()) os.Exit(1) } } func app(ctx context.Context) error { var cfgpath string if len(os.Args) > 1 { cfgpath = os.Args[1] } else { cfgpath = "config.json" } cfg, err := readFromFile(cfgpath, defaultConfig) if err != nil { return fmt.Errorf("reading config from file: %w", err) } log := config.NewSLogger(cfg.Log) sravniClient, err := sravni.NewClient(ctx, log, false) if err != nil { return fmt.Errorf("unable to make new sravni client: %w", err) } mainPageState, err := sravniClient.GetMainPageState() if err != nil { return fmt.Errorf("getting main page state: %w", err) } dictionaries := mainPageState.Props.InitialReduxState.Dictionaries.Data dictFieldsAsMap := func(fields ...sravni.Field) map[string]string { out := make(map[string]string, len(fields)) for _, field := range fields { out[field.Value] = field.Name } return out } courseThematcisMapped := dictFieldsAsMap(dictionaries.CourseThematics.Fields...) learningTypeMapped := dictFieldsAsMap(dictionaries.LearningType.Fields...) mapper := adapters.NewMemoryMapper(courseThematcisMapped, learningTypeMapped) app, err := service.NewApplication(ctx, service.ApplicationConfig{ LogConfig: cfg.Log, YDB: cfg.YDB, }, mapper) if err != nil { return fmt.Errorf("making new application: %w", err) } httpAPI := xhttp.NewServer(app, log.With(slog.String("component", "http"))) httpServer := setupHTTP(cfg.HTTP, httpAPI, log) eg, egctx := errgroup.WithContext(ctx) eg.Go(func() error { xcontext.LogInfo( ctx, log, "serving http", slog.String("addr", httpServer.Addr), ) err := httpServer.ListenAndServe() if err != nil { if !errors.Is(err, http.ErrServerClosed) { return fmt.Errorf("listening http: %w", err) } } return nil }) eg.Go(func() error { <-egctx.Done() xcontext.LogInfo(ctx, log, "trying to shutdown http") sdctx, sdcancel := context.WithTimeout(context.Background(), time.Second*10) defer sdcancel() err := httpServer.Shutdown(sdctx) if err != nil { return fmt.Errorf("shutting down the server: %w", err) } xcontext.LogInfo(ctx, log, "server closed successfuly") return nil }) return eg.Wait() } func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server { router := mux.NewRouter() coursesAPI := srv.Courses() router.Use(mux.CORSMethodMiddleware(router)) router.Use(middlewareLogger(log)) router.HandleFunc("/updatedesc", coursesAPI.UdpateDescription).Methods(http.MethodPost) coursesRouter := router.PathPrefix("/courses").Subrouter() coursesRouter.HandleFunc("/", coursesAPI.List).Methods(http.MethodGet) courseRouter := coursesRouter.PathPrefix("/{course_id}").Subrouter() courseRouter.HandleFunc("/", coursesAPI.Get).Methods(http.MethodGet) courseRouter.HandleFunc("/short", coursesAPI.GetShort).Methods(http.MethodGet) courseRouter.HandleFunc("/editdesc", coursesAPI.RenderEditDescription).Methods(http.MethodGet) courseRouter.HandleFunc("/description", coursesAPI.UpdateCourseDescription).Methods(http.MethodPut) if cfg.MountLive { fs := http.FileServer(http.Dir("./assets/kurious/static/")) router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fs)) } return &http.Server{ Addr: cfg.ListenAddr, Handler: router, } } func middlewareLogger(log *slog.Logger) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() requestID := r.Header.Get("x-request-id") if requestID == "" { requestID = generator.RandomInt64ID() } ctx = xcontext.WithLogFields(ctx, slog.String("request_id", requestID)) xcontext.LogInfo( ctx, log, "incoming request", slog.String("method", r.Method), slog.String("path", r.URL.Path), ) start := time.Now() next.ServeHTTP(w, r.WithContext(ctx)) elapsed := time.Since(start) xcontext.LogInfo( ctx, log, "request processed", slog.Duration("elapsed", elapsed), ) }) } }