153 lines
3.4 KiB
Go
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
|
|
}
|