package main import ( "context" "crypto/subtle" "embed" "fmt" "html/template" "io" "log" "os" "strings" "github.com/jackc/pgx/v5/pgxpool" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/spf13/cobra" "github.com/spf13/viper" ) // apiCmd represents the api command var apiCmd = &cobra.Command{ Use: "api", Short: "Démarrer le serveur API", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { var cfg Config if err := viper.Unmarshal(&cfg); err != nil { log.Fatal("parse config:", err) } e := echo.New() // Middlewares e.Pre(middleware.AddTrailingSlash()) if cfg.API.Key != "" { e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { return subtle.ConstantTimeCompare([]byte(key), []byte(cfg.API.Key)) == 1, nil })) } // DataClient ctx := context.Background() //prep pool, err := pgxpool.New( ctx, fmt.Sprintf( "user=%s password=%s database=%s host=%s port=%d sslmode=%s ", cfg.DB.User, cfg.DB.Password, cfg.DB.Database, cfg.DB.Host, cfg.DB.Port, cfg.DB.SSLMode, )) if err != nil { log.Fatal("init pgx pool:", err) } defer pool.Close() db := &PostgresClient{ Ctx: ctx, Pool: pool, } if err := db.Pool.Ping(ctx); err != nil { log.Fatal("ping db:", err) } if err := db.CreateOrReplaceSchema(); err != nil { log.Fatal("create or replace schema:", err) } // Routes /* h := handlers.New(client) e.GET("/v7/health/", h.GetHealth) e.POST("/v7/membres/", h.PostMembres) e.GET("/v7/membres/", h.ListMembres) e.GET("/v7/membres/:membre_id/", h.ReadMembre) e.PUT("/v7/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) e.POST("/v7/programmes/", h.PostProgrammes) e.POST("/v7/seed/", h.PostSeed) */ // Execution e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", cfg.API.Port))) }, } func init() { rootCmd.AddCommand(apiCmd) // api.key apiCmd.Flags().String(FlagAPIKey, DefaultAPIKey, DescriptionAPIKey) if err := viper.BindPFlag(ViperAPIKey, apiCmd.Flags().Lookup(FlagAPIKey)); err != nil { log.Fatal(err) } // api.port apiCmd.Flags().Int(FlagAPIPort, DefaultAPIPort, DescriptionAPIPort) if err := viper.BindPFlag(ViperAPIPort, apiCmd.Flags().Lookup(FlagAPIPort)); err != nil { log.Fatal(err) } // db.database apiCmd.Flags().String(FlagDBDatabase, DefaultDBDatabase, DescriptionDBDatabase) if err := viper.BindPFlag(ViperDBDatabase, apiCmd.Flags().Lookup(FlagDBDatabase)); err != nil { log.Fatal(err) } // db.sslmode apiCmd.Flags().String(FlagDBSSLMode, DefaultDBSSLMode, DescriptionDBSSLMode) if err := viper.BindPFlag(ViperDBSSLMode, apiCmd.Flags().Lookup(FlagDBSSLMode)); err != nil { log.Fatal(err) } // db.host apiCmd.Flags().String(FlagDBHost, DefaultDBHost, DescriptionDBHost) if err := viper.BindPFlag(ViperDBHost, apiCmd.Flags().Lookup(FlagDBHost)); err != nil { log.Fatal(err) } // db.password apiCmd.Flags().String(FlagDBPassword, DefaultDBPassword, DescriptionDBPassword) if err := viper.BindPFlag(ViperDBPassword, apiCmd.Flags().Lookup(FlagDBPassword)); err != nil { log.Fatal(err) } // db.port apiCmd.Flags().Int(FlagDBPort, DefaultDBPort, DescriptionDBPort) if err := viper.BindPFlag(ViperDBPort, apiCmd.Flags().Lookup(FlagDBPort)); err != nil { log.Fatal(err) } // db.user apiCmd.Flags().String(FlagDBUser, DefaultDBUser, DescriptionDBUser) if err := viper.BindPFlag(ViperDBUser, apiCmd.Flags().Lookup(FlagDBUser)); err != nil { log.Fatal(err) } } var cfgFile string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "bottin", Short: "Bottin étudiant de l'AGECEM", } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { err := rootCmd.Execute() if err != nil { os.Exit(1) } } func init() { cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)") } // initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile != "" { // Use config file from the flag. viper.SetConfigFile(cfgFile) } else { // Find home directory. home, err := os.UserHomeDir() cobra.CheckErr(err) // Search config in home directory with name ".bottin" (without extension). viper.AddConfigPath(home) viper.SetConfigType("yaml") viper.SetConfigName(".bottin") } viper.SetEnvPrefix("BOTTIN") viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) } } //go:embed templates/* var templatesFS embed.FS type Template struct { templates *template.Template } func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { return t.templates.ExecuteTemplate(w, name, data) } // webCmd represents the web command var webCmd = &cobra.Command{ Use: "web", Short: "Démarrer le client web", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { var cfg Config if err := viper.Unmarshal(&cfg); err != nil { log.Fatal("init config:", err) } // Ping API server /* client := http.DefaultClient defer client.CloseIdleConnections() apiClient := data.NewApiClient(client, webApiKey, webApiHost, webApiProtocol, webApiPort) pingResult, err := apiClient.GetHealth() if err != nil { log.Fatal(err) } log.Println(pingResult) */ e := echo.New() // Middlewares e.Pre(middleware.AddTrailingSlash()) e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) { usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(cfg.Web.User)) == 1 passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.Web.Password)) == 1 return usersMatch && passwordsMatch, nil })) // Template t := &Template{ templates: template.Must(template.ParseFS(templatesFS, "templates/*.html")), } e.Renderer = t // Routes /* handler := webhandlers.Handler{APIClient: apiClient} e.GET("/", handler.GetIndex) e.GET("/membre/", handler.GetMembre) */ // Execution e.Logger.Fatal(e.Start( fmt.Sprintf(":%d", cfg.Web.Port))) }, } func init() { rootCmd.AddCommand(webCmd) // web.api.host webCmd.Flags().String(FlagWebAPIHost, DefaultWebAPIHost, DescriptionWebAPIHost) if err := viper.BindPFlag(ViperWebAPIHost, webCmd.Flags().Lookup(FlagWebAPIHost)); err != nil { log.Fatal(err) } // web.api.key webCmd.Flags().String(FlagWebAPIKey, DefaultWebAPIKey, DescriptionWebAPIKey) if err := viper.BindPFlag(ViperWebAPIKey, webCmd.Flags().Lookup(FlagWebAPIKey)); err != nil { log.Fatal(err) } // web.api.protocol webCmd.Flags().String(FlagWebAPIProtocol, DefaultWebAPIProtocol, DescriptionWebAPIProtocol) if err := viper.BindPFlag(ViperWebAPIProtocol, webCmd.Flags().Lookup(FlagWebAPIProtocol)); err != nil { log.Fatal(err) } // web.api.port webCmd.Flags().Int(FlagWebAPIPort, DefaultWebAPIPort, DescriptionWebAPIPort) if err := viper.BindPFlag(ViperWebAPIPort, webCmd.Flags().Lookup(FlagWebAPIPort)); err != nil { log.Fatal(err) } // web.password webCmd.Flags().String(FlagWebPassword, DefaultWebPassword, DescriptionWebPassword) if err := viper.BindPFlag(ViperWebPassword, webCmd.Flags().Lookup(FlagWebPassword)); err != nil { log.Fatal(err) } // web.port webCmd.Flags().Int(FlagWebPort, DefaultWebPort, DescriptionWebPort) if err := viper.BindPFlag(ViperWebPort, webCmd.Flags().Lookup(FlagWebPort)); err != nil { log.Fatal(err) } // web.user webCmd.Flags().String(FlagWebUser, DefaultWebUser, DescriptionWebUser) if err := viper.BindPFlag(ViperWebUser, webCmd.Flags().Lookup(FlagWebUser)); err != nil { log.Fatal(err) } }