github.com/oinume/lekcije@v0.0.0-20231017100347-5b4c5eb6ab24/backend/cmd/server/main.go (about) 1 package main 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "net/http" 8 "os" 9 "time" 10 11 "cloud.google.com/go/profiler" 12 "github.com/99designs/gqlgen/graphql/handler" 13 "github.com/99designs/gqlgen/graphql/playground" 14 "github.com/GoogleCloudPlatform/berglas/pkg/berglas" 15 "github.com/rollbar/rollbar-go" 16 "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" 17 "go.opentelemetry.io/otel" 18 "goji.io/v3" 19 "goji.io/v3/pat" 20 21 "github.com/oinume/lekcije/backend/domain/config" 22 "github.com/oinume/lekcije/backend/event_logger" 23 "github.com/oinume/lekcije/backend/infrastructure/ga_measurement" 24 "github.com/oinume/lekcije/backend/infrastructure/gcp" 25 "github.com/oinume/lekcije/backend/infrastructure/mysql" 26 "github.com/oinume/lekcije/backend/infrastructure/open_telemetry" 27 interfaces "github.com/oinume/lekcije/backend/interface" 28 "github.com/oinume/lekcije/backend/interface/graphql" 29 "github.com/oinume/lekcije/backend/interface/graphql/generated" 30 "github.com/oinume/lekcije/backend/interface/graphql/resolver" 31 interfaces_http "github.com/oinume/lekcije/backend/interface/http" 32 "github.com/oinume/lekcije/backend/logger" 33 "github.com/oinume/lekcije/backend/model" 34 "github.com/oinume/lekcije/backend/registry" 35 ) 36 37 const ( 38 maxDBConnections = 10 39 serviceName = "lekcije" 40 ) 41 42 func main() { 43 ctx := context.Background() 44 gae := os.Getenv("GAE_APPLICATION") 45 if gae != "" { 46 for _, env := range []string{"MYSQL_USER", "MYSQL_PASSWORD", "MYSQL_HOST"} { 47 if os.Getenv(env) == "" { 48 log.Fatalf("Environment variable %q is required", env) 49 } 50 if err := berglas.Replace(ctx, env); err != nil { 51 log.Fatalf("berglas.Replace failed: %v", err) 52 } 53 } 54 } 55 config.MustProcessDefault() 56 configVars := config.DefaultVars 57 port := configVars.HTTPPort 58 59 tracerProvider, err := open_telemetry.NewTracerProvider("server", config.DefaultVars) 60 if err != nil { 61 log.Fatalf("NewTraceProvider failed: %v", err) 62 } 63 defer func() { 64 if err := tracerProvider.Shutdown(context.Background()); err != nil { 65 log.Fatal(err) 66 } 67 }() 68 otel.SetTracerProvider(tracerProvider) 69 70 if config.DefaultVars.EnableStackdriverProfiler { 71 credential, err := gcp.WithCredentialsJSONFromBase64String(configVars.GCPServiceAccountKey) 72 if err != nil { 73 log.Fatalf("WithCredentialsJSONFromBase64String failed: %v", err) 74 } 75 76 // TODO: Move to gcp package 77 if err := profiler.Start(profiler.Config{ 78 ProjectID: configVars.GCPProjectID, 79 Service: serviceName, 80 ServiceVersion: "1.0.0", // TODO: release version? 81 DebugLogging: false, 82 }, credential); err != nil { 83 log.Fatalf("Stackdriver profiler.Start failed: %v", err) 84 } 85 } 86 87 gormDB, err := model.OpenDB( 88 configVars.DBURL(), 89 maxDBConnections, 90 configVars.DebugSQL, 91 ) 92 if err != nil { 93 log.Fatalf("model.OpenDB failed: %v", err) 94 } 95 defer gormDB.Close() 96 97 accessLogger := logger.NewAccessLogger(os.Stdout) 98 appLogger := logger.NewAppLogger(os.Stderr, logger.NewLevel("info")) // TODO: flag 99 rollbarClient := rollbar.New( 100 configVars.RollbarAccessToken, 101 configVars.ServiceEnv, 102 configVars.VersionHash, 103 "", "/", 104 ) 105 defer rollbarClient.Close() 106 107 args := &interfaces.ServerArgs{ 108 AccessLogger: accessLogger, 109 AppLogger: appLogger, 110 DB: gormDB.DB(), 111 GAMeasurementClient: ga_measurement.NewClient(nil, event_logger.New(accessLogger)), 112 GormDB: gormDB, 113 RollbarClient: rollbarClient, 114 SenderHTTPClient: &http.Client{ 115 Timeout: 5 * time.Second, 116 }, 117 } 118 119 errors := make(chan error) 120 go func() { 121 errors <- startHTTPServer(port, args) 122 }() 123 124 for err := range errors { 125 log.Fatal(err) 126 } 127 } 128 129 func startHTTPServer(port int, args *interfaces.ServerArgs) error { 130 mux := goji.NewMux() 131 132 // TODO: graceful shutdown 133 errorRecorder := registry.NewErrorRecorderUsecase(args.AppLogger, args.RollbarClient) 134 userAPIToken := registry.NewUserAPITokenUsecase(args.DB) 135 server := interfaces_http.NewServer(args, errorRecorder, userAPIToken) 136 oauthServer := registry.NewOAuthServer(args.AppLogger, args.DB, args.GAMeasurementClient, args.RollbarClient, args.SenderHTTPClient) 137 mCountryList := registry.MustNewMCountryList(context.Background(), args.DB) 138 server.Setup(mux) 139 oauthServer.Setup(mux) 140 141 gqlResolver := resolver.NewResolver( 142 mysql.NewFollowingTeacherRepository(args.DB), 143 registry.NewFollowingTeacherUsecase(args.AppLogger, args.DB, mCountryList), 144 registry.NewGAMeasurementUsecase(args.GAMeasurementClient), 145 mysql.NewNotificationTimeSpanRepository(args.DB), 146 registry.NewNotificationTimeSpanUsecase(args.DB), 147 mysql.NewTeacherRepository(args.DB), 148 mysql.NewUserRepository(args.DB), 149 registry.NewUserUsecase(args.DB), 150 ) 151 gqlSchema := generated.NewExecutableSchema(generated.Config{ 152 Resolvers: gqlResolver, 153 }) 154 gqlServer := handler.NewDefaultServer(gqlSchema) 155 gqlMiddleware := graphql.NewMiddleware(args.AppLogger) 156 gqlServer.AroundOperations(gqlMiddleware.AroundOperations) 157 gqlServer.SetErrorPresenter(gqlMiddleware.ErrorPresenter) 158 159 const gqlPath = "/graphql" 160 mux.Handle(pat.Get(gqlPath), gqlServer) 161 mux.Handle(pat.Post(gqlPath), gqlServer) 162 if !config.DefaultVars.IsProductionEnv() { 163 mux.Handle(pat.Get("/playground"), playground.Handler("GraphQL playground", gqlPath)) 164 } 165 166 otelHandler := otelhttp.NewHandler( 167 mux, 168 "server", 169 otelhttp.WithMessageEvents(otelhttp.ReadEvents, otelhttp.WriteEvents), 170 ) 171 fmt.Printf("Starting HTTP server on %v\n", port) 172 return http.ListenAndServe(fmt.Sprintf(":%d", port), otelHandler) 173 }