minireader/internal/minireader/app.go
2024-08-10 11:01:50 +07:00

153 lines
3.4 KiB
Go

package minireader
import (
"context"
"database/sql"
"fmt"
"log/slog"
"os"
"os/signal"
"syscall"
"time"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/pressly/goose/v3"
"github.com/sethvargo/go-envconfig"
"golang.org/x/sync/errgroup"
"borodyadka.dev/borodyadka/minireader/internal/minireader/config"
"borodyadka.dev/borodyadka/minireader/internal/minireader/dto"
"borodyadka.dev/borodyadka/minireader/internal/minireader/fetcher"
"borodyadka.dev/borodyadka/minireader/internal/minireader/fetcher/rss"
"borodyadka.dev/borodyadka/minireader/internal/minireader/reader"
"borodyadka.dev/borodyadka/minireader/internal/minireader/repositories"
"borodyadka.dev/borodyadka/minireader/internal/minireader/transport/http"
"borodyadka.dev/borodyadka/minireader/migrations"
)
type App struct {
logger *slog.Logger
config config.Config
}
func New() (*App, error) {
config := config.Config{}
if err := envconfig.Process(context.Background(), &config); err != nil {
return nil, err
}
logger, err := newLogger(config.Log)
if err != nil {
return nil, err
}
return &App{
config: config,
logger: logger,
}, nil
}
func (app *App) Start(ctx context.Context) error {
err := app.migrate(
ctx,
app.logger.With("module", "migrator"),
app.config.Postgres.DSN,
)
if err != nil {
return fmt.Errorf("failed to apply migrations: %w", err)
}
db, err := repositories.NewDatabase(ctx, app.config.Postgres.DSN)
if err != nil {
return err
}
fetcher := fetcher.New(
fetcher.WithConfig(fetcher.Config{
CheckInterval: app.config.Fetcher.Interval,
Fetchers: map[dto.Provider]fetcher.Fetcher{
dto.ProviderRSS: rss.New(rss.Config{
HTTPTimeout: 5 * time.Minute,
}),
},
}),
fetcher.WithRepositories(fetcher.Repositories{
Feed: repositories.NewFeedRepository(),
Item: repositories.NewItemRepository(),
}),
fetcher.WithLogger(app.logger.With("module", "fetcher")),
)
reader := reader.New(
reader.WithRepositories(reader.Repositories{
Feed: repositories.NewFeedRepository(),
Subscription: repositories.NewSubscriptionRepository(),
Item: repositories.NewItemRepository(),
User: repositories.NewUserRepository(),
}),
reader.WithFetcher(fetcher),
reader.WithLogger(app.logger.With("module", "reader")),
)
server := http.New(
http.WithConfig(app.config.HTTP),
http.WithServices(http.Services{
Reader: reader,
}),
http.WithSecurity(app.config.Security),
http.WithLogger(app.logger.With("module", "http")),
)
server.Inject(repositories.ConnKey, db)
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
return server.Start()
})
eg.Go(func() error {
fctx := context.WithValue(ctx, repositories.ConnKey, db)
return fetcher.Start(fctx)
})
eg.Go(func() error {
sigHandler := make(chan os.Signal, 1)
signal.Notify(sigHandler, syscall.SIGTERM, syscall.SIGINT)
select {
case <-ctx.Done():
case <-sigHandler:
}
_ = server.Shutdown()
return nil
})
return eg.Wait()
}
func (app *App) migrate(ctx context.Context, logger *slog.Logger, dsn string) error {
goose.SetBaseFS(migrations.Migrations)
goose.SetLogger(&gooseWrapper{logger: logger})
db, err := sql.Open("pgx", dsn)
if err != nil {
return err
}
defer db.Close()
goose.SetTableName("migrations")
if err := goose.SetDialect("postgres"); err != nil {
return err
}
if err := goose.UpContext(ctx, db, "."); err != nil {
return err
}
return nil
}
func (app *App) Logger() *slog.Logger {
return app.logger
}