go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/apputil/db_entrypoint.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package apputil 9 10 import ( 11 "context" 12 "flag" 13 "strings" 14 15 "go.charczuk.com/sdk/cliutil" 16 "go.charczuk.com/sdk/configutil" 17 "go.charczuk.com/sdk/db" 18 "go.charczuk.com/sdk/logutil" 19 ) 20 21 // DBEntryPointConfigProvider is a type that can provide 22 // an entrypoint config. 23 type DBEntryPointConfigProvider interface { 24 LoggerProvider 25 DBProvider 26 MetaProvider 27 } 28 29 // EntryPoint is a top level handler for a database backed app. 30 // 31 // It handles reading the config, setting up the logger, 32 // opening the database connection, and based on comandline 33 // flags handling calling specific handlers for initializing the 34 // database and applying migrations on start. 35 type DBEntryPoint[T DBEntryPointConfigProvider] struct { 36 Setup func(context.Context, T) error 37 Migrate func(context.Context, T, *db.Connection) error 38 Start func(context.Context, T, *db.Connection) error 39 40 config T 41 flagDatabaseSetup bool 42 flagDatabaseMigrate bool 43 flagStart bool 44 } 45 46 // Init should be run before `Run` and registers flags and the like. 47 func (e *DBEntryPoint[T]) Init() { 48 flag.BoolVar(&e.flagDatabaseSetup, "db-setup", false, "if we should run the first time setup for the database") 49 flag.BoolVar(&e.flagDatabaseMigrate, "db-migrate", false, "if we should apply database migrations") 50 flag.BoolVar(&e.flagStart, "start", true, "if we should start the server (false will exit after other steps complete)") 51 flag.Parse() 52 } 53 54 // Main is the actual function that needs to be called in Main. 55 func (e *DBEntryPoint[T]) Main() { 56 ctx := context.Background() 57 configPaths := configutil.MustRead(&e.config) 58 log := logutil.New( 59 logutil.OptConfig(e.config.GetLogger()), 60 ) 61 ctx = logutil.WithLogger(ctx, log) 62 if len(configPaths) > 0 { 63 logutil.Infof(log, "using config path(s): %s", strings.Join(configPaths, ", ")) 64 } else { 65 logutil.Infof(log, "using environment resolved config") 66 } 67 if e.flagDatabaseSetup && e.Setup != nil { 68 logutil.Infof(log, "running first time database setup: %s", e.config.GetDB().Database) 69 if err := e.Setup(ctx, e.config); err != nil { 70 cliutil.Fatal(err) 71 } 72 logutil.Infof(log, "running first time database setup complete") 73 } else { 74 logutil.Debug(log, "skipping running first time database setup") 75 } 76 conn, err := db.New( 77 db.OptConfig(e.config.GetDB()), 78 db.OptLog(log), 79 ) 80 if err != nil { 81 cliutil.Fatal(err) 82 } 83 if err = conn.Open(); err != nil { 84 cliutil.Fatal(err) 85 } 86 defer conn.Close() 87 88 if e.flagDatabaseMigrate && e.Migrate != nil { 89 logutil.Info(log, "applying database migrations") 90 if err := e.Migrate(ctx, e.config, conn); err != nil { 91 cliutil.Fatal(err) 92 } 93 logutil.Info(log, "applying database migrations complete") 94 } else { 95 logutil.Debug(log, "skipping database migrations") 96 } 97 98 if e.config.GetMeta().IsProdlike() { 99 logutil.Debugf(log, "using database dsn: %s", conn.CreateLoggingDSN()) 100 } 101 102 if e.flagStart && e.Start != nil { 103 if err := e.Start(ctx, e.config, conn); err != nil { 104 cliutil.Fatal(err) 105 } 106 } 107 }