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 := ¬ifierMain{ 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 }