github.com/oinume/lekcije@v0.0.0-20231017100347-5b4c5eb6ab24/backend/cmd/notifier/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"os"
    10  	"time"
    11  
    12  	"github.com/rollbar/rollbar-go"
    13  	"go.opentelemetry.io/otel"
    14  	"go.uber.org/zap"
    15  
    16  	"github.com/oinume/lekcije/backend/cli"
    17  	"github.com/oinume/lekcije/backend/domain/config"
    18  	"github.com/oinume/lekcije/backend/domain/repository"
    19  	"github.com/oinume/lekcije/backend/infrastructure/dmm_eikaiwa"
    20  	"github.com/oinume/lekcije/backend/infrastructure/mysql"
    21  	"github.com/oinume/lekcije/backend/infrastructure/open_telemetry"
    22  	irollbar "github.com/oinume/lekcije/backend/infrastructure/rollbar"
    23  	"github.com/oinume/lekcije/backend/infrastructure/send_grid"
    24  	"github.com/oinume/lekcije/backend/logger"
    25  	"github.com/oinume/lekcije/backend/model"
    26  	"github.com/oinume/lekcije/backend/model2"
    27  	"github.com/oinume/lekcije/backend/registry"
    28  	"github.com/oinume/lekcije/backend/usecase"
    29  )
    30  
    31  func main() {
    32  	m := &notifierMain{
    33  		outStream: os.Stdout,
    34  		errStream: os.Stderr,
    35  	}
    36  	if err := m.run(os.Args); err != nil {
    37  		cli.WriteError(m.errStream, err)
    38  		os.Exit(cli.ExitError)
    39  	}
    40  	os.Exit(cli.ExitOK)
    41  }
    42  
    43  type notifierMain struct {
    44  	outStream io.Writer
    45  	errStream io.Writer
    46  }
    47  
    48  func (m *notifierMain) run(args []string) error {
    49  	flagSet := flag.NewFlagSet("notifier", flag.ContinueOnError)
    50  	flagSet.SetOutput(m.errStream)
    51  
    52  	var (
    53  		concurrency          = flagSet.Int("concurrency", 1, "Concurrency of fetcher")
    54  		dryRun               = flagSet.Bool("dry-run", false, "Don't update database with fetched lessons")
    55  		fetcherCache         = flagSet.Bool("fetcher-cache", false, "Cache teacher and lesson data in Fetcher")
    56  		notificationInterval = flagSet.Int("notification-interval", 0, "Notification interval")
    57  		sendEmail            = flagSet.Bool("send-email", true, "Flag to send email")
    58  		logLevel             = flagSet.String("log-level", "info", "Log level")
    59  	)
    60  
    61  	if err := flagSet.Parse(args[1:]); err != nil {
    62  		return err
    63  	}
    64  
    65  	config.MustProcessDefault()
    66  	startedAt := time.Now().UTC()
    67  	appLogger := logger.NewAppLogger(os.Stderr, logger.NewLevel(*logLevel))
    68  	appLogger.Info(fmt.Sprintf("notifier started (interval=%d)", *notificationInterval))
    69  
    70  	const serviceName = "notifier"
    71  	tracerProvider, err := open_telemetry.NewTracerProvider(serviceName, config.DefaultVars)
    72  	if err != nil {
    73  		log.Fatalf("NewTraceProvider failed: %v", err)
    74  	}
    75  	defer func() {
    76  		if err := tracerProvider.Shutdown(context.Background()); err != nil {
    77  			log.Fatal(err)
    78  		}
    79  	}()
    80  	otel.SetTracerProvider(tracerProvider)
    81  
    82  	ctx, span := otel.Tracer(config.DefaultTracerName).Start(context.Background(), "main")
    83  	defer span.End()
    84  	db, err := model.OpenDB(config.DefaultVars.DBURL(), 1, config.DefaultVars.DebugSQL)
    85  	if err != nil {
    86  		return err
    87  	}
    88  	defer func() { _ = db.Close() }()
    89  
    90  	if *notificationInterval == 0 {
    91  		return fmt.Errorf("-notification-interval is required")
    92  	}
    93  	users, err := mysql.NewUserRepository(db.DB()).FindAllByEmailVerifiedIsTrue(ctx, *notificationInterval)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	mCountryList := registry.MustNewMCountryList(ctx, db.DB())
    98  	lessonFetcher := dmm_eikaiwa.NewLessonFetcher(nil, *concurrency, *fetcherCache, mCountryList, appLogger)
    99  
   100  	var sender repository.EmailSender
   101  	if *sendEmail {
   102  		sender = send_grid.NewEmailSender(nil, appLogger)
   103  	} else {
   104  		sender = repository.NewNopEmailSender()
   105  	}
   106  
   107  	rollbarClient := rollbar.New(
   108  		config.DefaultVars.RollbarAccessToken,
   109  		config.DefaultVars.ServiceEnv,
   110  		config.DefaultVars.VersionHash,
   111  		"", "/",
   112  	)
   113  	errorRecorder := usecase.NewErrorRecorder(
   114  		appLogger,
   115  		irollbar.NewErrorRecorderRepository(rollbarClient),
   116  	)
   117  	lessonUsecase := registry.NewLessonUsecase(db.DB())
   118  	notificationTimeSpanUsecase := registry.NewNotificationTimeSpanUsecase(db.DB())
   119  	statNotifierUsecase := registry.NewStatNotifierUsecase(db.DB())
   120  	teacherUsecase := registry.NewTeacherUsecase(db.DB())
   121  	notifier := usecase.NewNotifier(
   122  		appLogger, db, errorRecorder, lessonFetcher, *dryRun, lessonUsecase, notificationTimeSpanUsecase,
   123  		statNotifierUsecase, teacherUsecase, sender, mysql.NewFollowingTeacherRepository(db.DB()),
   124  	)
   125  	defer notifier.Close(ctx, &model2.StatNotifier{
   126  		Datetime:             startedAt,
   127  		Interval:             uint8(*notificationInterval),
   128  		Elapsed:              0,
   129  		UserCount:            uint(len(users)),
   130  		FollowedTeacherCount: 0,
   131  	})
   132  	for _, user := range users {
   133  		if err := notifier.SendNotification(ctx, user); err != nil {
   134  			return err
   135  		}
   136  	}
   137  
   138  	elapsed := time.Now().UTC().Sub(startedAt) / time.Millisecond
   139  	appLogger.Info(
   140  		fmt.Sprintf("notifier finished (interval=%d)", *notificationInterval),
   141  		zap.Int("elapsed", int(elapsed)),
   142  		zap.Int("usersCount", len(users)),
   143  	)
   144  
   145  	return nil
   146  }