diff --git a/cmd/gophermart/main.go b/cmd/gophermart/main.go index 38dd16da6..3301bd861 100644 --- a/cmd/gophermart/main.go +++ b/cmd/gophermart/main.go @@ -1,3 +1,34 @@ package main -func main() {} +import ( + "context" + "github.com/StarkovPO/go-musthave-diploma-tpl.git/internal/app" + "github.com/StarkovPO/go-musthave-diploma-tpl.git/internal/config" + "github.com/StarkovPO/go-musthave-diploma-tpl.git/internal/service" + "github.com/StarkovPO/go-musthave-diploma-tpl.git/internal/store" + "log" + "time" +) + +func main() { + + c, err := config.Init() + + if err != nil { + log.Fatalf("init configuration: %s", err) + } + + db := store.NewPostgres(store.MustPostgresConnection(c)) + s := service.NewService(db, c) + application := app.NewApplication(db, c, s) + router := app.SetupAPI(application.Service) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + err = app.Run(ctx, application.Config, router) + if err != nil { + log.Fatalf("run application: %s", err) + } + +} diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 000000000..d11a271dd --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,76 @@ +package app + +import ( + "context" + "fmt" + "github.com/StarkovPO/go-musthave-diploma-tpl.git/internal/config" + "github.com/StarkovPO/go-musthave-diploma-tpl.git/internal/handler" + "github.com/StarkovPO/go-musthave-diploma-tpl.git/internal/service" + "github.com/StarkovPO/go-musthave-diploma-tpl.git/internal/store" + "github.com/gorilla/mux" + "log" + "net" + "net/http" + "os" + "os/signal" + "syscall" + "time" +) + +type Application struct { + Store store.Store + Config config.Config + Service service.Service +} + +func NewApplication(s store.Store, c config.Config, o service.Service) Application { + return Application{ + Store: s, + Config: c, + Service: o, + } +} + +func SetupAPI(s service.Service) *mux.Router { + router := mux.NewRouter() + router.HandleFunc("/ping", handler.Test(s)).Methods(http.MethodGet) + + return router + +} + +func Run(ctx context.Context, c config.Config, router *mux.Router) error { + + cancelChan := make(chan os.Signal, 1) + signal.Notify(cancelChan, syscall.SIGTERM, syscall.SIGINT) + + listener, err := net.Listen("tcp", c.RunAddressValue) + if err != nil { + return fmt.Errorf("failed to listen on address %s: %v", c.RunAddressValue, err) + } + + server := &http.Server{ + Handler: router, + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + ReadHeaderTimeout: 15 * time.Second, + } + + go func() { + if err = server.Serve(listener); err != nil && err != http.ErrServerClosed { + log.Fatalf("Server error: %v", err) + } + }() + + log.Printf("Server started and listening on address and port %s", c.RunAddressValue) + + sig := <-cancelChan + + log.Printf("Caught signal %v", sig) + if err = server.Shutdown(ctx); err != nil { + return fmt.Errorf("failed to shutdown server: %v", err) + } + + log.Println("Server shutdown successfully") + return nil +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 000000000..88dbd54b6 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,58 @@ +package config + +import ( + "fmt" + "github.com/spf13/cobra" + "os" +) + +const ( + RUN_ADDRESS_KEY = "RUN_ADDRESS" + DATABASE_URI_KEY = "DATABASE_URI" + ACCRUAL_SYSTEM_ADDRESS_KEY = "ACCRUAL_SYSTEM_ADDRESS" +) + +type Config struct { + RunAddressValue string + DatabaseURIValue string + AccrualSystemAddressValue string +} + +func NewConfig() *Config { + return &Config{} +} + +func ReadFlags(c Config) error { + rootCmd := &cobra.Command{ + Use: "go-shop", + Short: "[-a address], [-d database], [-r accrual system]", + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("address: %s, database: %s, accrual system: %s\n", c.RunAddressValue, c.DatabaseURIValue, c.AccrualSystemAddressValue) + }, + } + rootCmd.Flags().StringVarP(&c.RunAddressValue, "Port for service", "a", ":8080", "Server address") + rootCmd.Flags().StringVarP(&c.DatabaseURIValue, "URI for Postgres DB", "d", "postgres://admin:admin@localhost/go-shop?sslmode=disable", "Postgres URI") + rootCmd.Flags().StringVarP(&c.AccrualSystemAddressValue, "ACCRUAL SYSTEM ADDRESS", "r", ":8000", "ACCRUAL_SYSTEM_ADDRESS") + + err := rootCmd.Execute() + if err != nil { + return err + } + + return nil +} + +func Init() (Config, error) { + + c := NewConfig() + c.RunAddressValue = os.Getenv(RUN_ADDRESS_KEY) + c.DatabaseURIValue = os.Getenv(DATABASE_URI_KEY) + c.AccrualSystemAddressValue = os.Getenv(ACCRUAL_SYSTEM_ADDRESS_KEY) + + if c.RunAddressValue == "" || c.DatabaseURIValue == "" || c.AccrualSystemAddressValue == "" { + if err := ReadFlags(*c); err != nil { + return Config{}, err + } + } + return *c, nil +} diff --git a/internal/handler/handler.go b/internal/handler/handler.go new file mode 100644 index 000000000..a2055a7e9 --- /dev/null +++ b/internal/handler/handler.go @@ -0,0 +1,14 @@ +package handler + +import ( + "net/http" +) + +type ServiceInterface interface { +} + +func Test(s ServiceInterface) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + } +} diff --git a/internal/service/service.go b/internal/service/service.go new file mode 100644 index 000000000..271d9b984 --- /dev/null +++ b/internal/service/service.go @@ -0,0 +1,18 @@ +package service + +import ( + "github.com/StarkovPO/go-musthave-diploma-tpl.git/internal/config" + "github.com/StarkovPO/go-musthave-diploma-tpl.git/internal/store" +) + +type Service struct { + store store.Store + config config.Config +} + +func NewService(s store.Store, c config.Config) Service { + return Service{ + store: s, + config: c, + } +} diff --git a/internal/store/postgre_querry.go b/internal/store/postgre_querry.go new file mode 100644 index 000000000..ba0f7a9f0 --- /dev/null +++ b/internal/store/postgre_querry.go @@ -0,0 +1,11 @@ +package store + +const createURLTable = ` + CREATE TABLE IF NOT EXISTS urls ( + primary_id integer PRIMARY KEY, + id varchar36 UNIQUE, + login varchar255 UNIQUE, + password_hash varchar60, + created_at datetime NOT NULL + ) + ` diff --git a/internal/store/store.go b/internal/store/store.go new file mode 100644 index 000000000..8f05a7398 --- /dev/null +++ b/internal/store/store.go @@ -0,0 +1,4 @@ +package store + +type Store interface { +} diff --git a/internal/store/store_postgre.go b/internal/store/store_postgre.go new file mode 100644 index 000000000..d0c7cbc32 --- /dev/null +++ b/internal/store/store_postgre.go @@ -0,0 +1,56 @@ +package store + +import ( + "fmt" + "github.com/StarkovPO/go-musthave-diploma-tpl.git/internal/config" + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" + "time" +) + +const ( + DBMaxOpenConnection = 25 + DBMaxIdleConnection = 25 + DBMaxConnectionLifeTime = 10 * time.Minute +) + +type Postgres struct { + db *sqlx.DB +} + +func NewPostgres(db *sqlx.DB) *Postgres { + return &Postgres{ + db: db, + } +} + +func MustPostgresConnection(c config.Config) *sqlx.DB { + db, err := sqlx.Open("postgres", c.DatabaseURIValue) + if err != nil { + panic(err) + } + + // Test the connection + err = db.Ping() + if err != nil { + defer db.Close() + return nil + } + + db.SetMaxOpenConns(DBMaxOpenConnection) + db.SetMaxIdleConns(DBMaxIdleConnection) + db.SetConnMaxLifetime(DBMaxConnectionLifeTime) + + if err = MakeDB(*db); err != nil { + panic(err) + } + + return db +} + +func MakeDB(db sqlx.DB) error { + if _, err := db.Exec(createURLTable); err != nil { + return fmt.Errorf("error while run migrations %v", err) + } + return nil +}