diff --git a/starter/.gitignore b/starter/.gitignore new file mode 100644 index 0000000..b8f8017 --- /dev/null +++ b/starter/.gitignore @@ -0,0 +1,3 @@ +.env +.idea/ +tmp/ diff --git a/starter/cli/generate_env.go b/starter/cli/generate_env.go deleted file mode 100644 index 189a127..0000000 --- a/starter/cli/generate_env.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "errors" - "ersteller-lib/starter/env" - "fmt" - "os" - "path/filepath" - "strings" -) - -// findRepoRoot walks up from the current working directory until it finds a go.mod file. -// It returns the directory containing go.mod, or an error if not found. -func findRepoRoot() (string, error) { - wd, err := os.Getwd() - if err != nil { - return "", err - } - curr := wd - for { - if _, err := os.Stat(filepath.Join(curr, "go.mod")); err == nil { - return curr, nil - } - parent := filepath.Dir(curr) - if parent == curr { - break - } - curr = parent - } - return "", errors.New("could not locate repository root (go.mod not found in any parent directory)") -} - -func main() { - root, err := findRepoRoot() - if err != nil { - fmt.Println("Error:", err) - os.Exit(1) - } - - envPath := filepath.Join(root, ".env") - if _, err := os.Stat(envPath); err == nil { - fmt.Println(".env already exists at:", envPath) - fmt.Println("No changes made. Delete or move the existing .env to regenerate.") - return - } - - // Build .env content dynamically from keys exposed by env package - // so we don't duplicate the list here. - keys := env.EnvKeys() - - builder := &strings.Builder{} - fmt.Fprintln(builder, "# Environment configuration for Salezenify") - fmt.Fprintln(builder, "# Generated by cli/generate_env.go") - fmt.Fprintln(builder, "# Fill in the appropriate values for your environment.") - fmt.Fprintln(builder) - - // Optional defaults can be set here; leave blank for most keys to avoid assumptions. - defaults := map[string]string{ - "REPOSITORY_TYPE": "postgres", - } - - for _, k := range keys { - v := defaults[k] - if v == "" { - fmt.Fprintf(builder, "%s=\n", k) - } else { - fmt.Fprintf(builder, "%s=%s\n", k, v) - } - } - - content := builder.String() - - if err := os.WriteFile(envPath, []byte(content), 0600); err != nil { - fmt.Println("Failed to write .env:", err) - os.Exit(1) - } - - fmt.Println(".env file created at:", envPath) - fmt.Println("Review and update the values as needed.") -} diff --git a/starter/create/create.go b/starter/create/create.go index 5714971..ec55e05 100644 --- a/starter/create/create.go +++ b/starter/create/create.go @@ -2,6 +2,8 @@ package create import ( . "ersteller-lib" + "ersteller-lib/starter/env" + "fmt" "log" "os" "os/exec" @@ -18,10 +20,11 @@ const ( ) type Params struct { - DbType DatabaseType - ProjectDir string - ModuleName string - GoPath string + DbType DatabaseType + ProjectDir string + ModuleName string + GoPath string + OverwriteEnv bool } type StarterCreator struct { @@ -71,6 +74,7 @@ func (s StarterCreator) Create() { // Write to /main.go targetPath := filepath.Join(s.params.ProjectDir, "main.go") + content = s.replaceImports(content) must(os.WriteFile(targetPath, content, 0o644)) log.Printf("StarterCreator.Create: wrote starter main.go to %s", targetPath) @@ -91,6 +95,24 @@ func (s StarterCreator) Create() { if s.params.DbType == DataBaseTypeSqlite { s.executeDbPush() } + + s.copyFile("../.gitignore", ".gitignore") + + s.createEnvironment() +} + +func (s StarterCreator) createEnvironment() { + if err := os.MkdirAll(s.params.ProjectDir+"/env", 0o755); err != nil { + log.Printf("StarterCreator.Create: failed to create env target directory%v", err) + return + } + //s.copyFile("../env/environment.go", "env/environment.go") + content, err := os.ReadFile(filepath.Join(s.thisDir, "../env/environment.go")) + must(err) + must(os.WriteFile(filepath.Join(s.params.ProjectDir, "env/environment.go"), content, 0o644)) + + must(env.GenerateEnvFile(s.params.ProjectDir, s.params.OverwriteEnv)) + } func (s StarterCreator) executeDbPush() { @@ -119,17 +141,6 @@ func (s StarterCreator) executeDbPush() { func (s StarterCreator) copyFile(src string, dst string) { content, err := os.ReadFile(filepath.Join(s.thisDir, src)) must(err) - - // If we are copying the Prisma schema and the target DB is sqlite, - // replace the datasource block accordingly. - if dst == "schema.prisma" && s.params.DbType == DataBaseTypeSqlite { - pgBlock := "datasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n" - sqliteBlock := "datasource db {\n provider = \"sqlite\"\n url = \"file:./db/sqlite.db\"\n}\n" - contentStr := string(content) - contentStr = strings.Replace(contentStr, pgBlock, sqliteBlock, 1) - content = []byte(contentStr) - } - must(os.WriteFile(filepath.Join(s.params.ProjectDir, dst), content, 0o644)) } @@ -187,6 +198,12 @@ func (s StarterCreator) executeGetPrismaClient() { must(cmd.Run()) } +func (s StarterCreator) replaceImports(content []byte) []byte { + contentString := string(content) + contentString = strings.ReplaceAll(contentString, "\"ersteller-lib/starter/env\"", fmt.Sprint("\"", s.params.ModuleName, "/env\"")) + return []byte(contentString) +} + func must(err error) { if err != nil { panic(err) diff --git a/starter/env/environment.go b/starter/env/environment.go index 918c6ae..aa76782 100644 --- a/starter/env/environment.go +++ b/starter/env/environment.go @@ -2,7 +2,9 @@ package env import ( . "ersteller-lib" + "fmt" "os" + "path/filepath" "github.com/joho/godotenv" ) @@ -100,3 +102,80 @@ func EnvKeys() []string { EnvKeyKeycloakClientSecret, } } + +// GenerateEnvFile creates a .env file template with all required environment variables +// from the EnvKeys function. The file will contain comments and default values where appropriate. +func GenerateEnvFile(rootPath string, overwrite bool) error { + envPath := filepath.Join(rootPath, ".env") + Debug("envPath:", envPath) + if !overwrite { + if _, err := os.Stat(envPath); err == nil { + fmt.Println(".env already exists at:", envPath) + fmt.Println("No changes made. Delete or move the existing .env to regenerate.") + return fmt.Errorf(".env already exists at: %s", envPath) + } + } + file, err := os.Create(envPath) + if err != nil { + return fmt.Errorf("failed to create .env file: %w", err) + } + defer file.Close() + + // Write header comment + _, err = file.WriteString("# Environment Variables Configuration\n") + if err != nil { + return fmt.Errorf("failed to write to .env file: %w", err) + } + _, err = file.WriteString("# Generated from EnvKeys function\n\n") + if err != nil { + return fmt.Errorf("failed to write to .env file: %w", err) + } + + // Get all environment keys + keys := EnvKeys() + + // Define default values and comments for specific keys + defaults := map[string]string{ + EnvKeyRepositoryType: "postgres", + EnvKeyIsLocal: "false", + EnvKeyIsDev: "false", + } + + comments := map[string]string{ + EnvKeyRepositoryType: "# Repository type (postgres, sqlite, etc.)", + EnvKeyDatabaseURL: "# Database connection URL", + EnvKeySessionSecret: "# Secret key for session management", + EnvKeyBaseURL: "# Base URL of the application", + EnvKeyIsLocal: "# Set to true for local development", + EnvKeyIsDev: "# Set to true for development mode", + EnvKeyGoogleClientID: "# Google OAuth client ID", + EnvKeyGoogleClientSecret: "# Google OAuth client secret", + EnvKeyGoogleRedirectURL: "# Google OAuth redirect URL", + EnvKeyKeycloakDiscoveryURL: "# Keycloak discovery URL", + EnvKeyKeycloakClientID: "# Keycloak client ID", + EnvKeyKeycloakClientSecret: "# Keycloak client secret", + } + + // Write each environment variable + for _, key := range keys { + // Write comment if available + if comment, exists := comments[key]; exists { + _, err = file.WriteString(comment + "\n") + if err != nil { + return fmt.Errorf("failed to write to .env file: %w", err) + } + } + + // Write the environment variable with default value if available + if defaultValue, exists := defaults[key]; exists { + _, err = file.WriteString(fmt.Sprintf("%s=%s\n", key, defaultValue)) + } else { + _, err = file.WriteString(fmt.Sprintf("%s=\n", key)) + } + if err != nil { + return fmt.Errorf("failed to write to .env file: %w", err) + } + } + + return nil +} diff --git a/starter/main.go b/starter/main.go index 6354afb..c98b749 100644 --- a/starter/main.go +++ b/starter/main.go @@ -2,6 +2,7 @@ package main import ( . "ersteller-lib" + "ersteller-lib/starter/env" "log" "net/http" ) @@ -10,10 +11,9 @@ func main() { GlobalI18n = GlobalI18nImplementation{} server := NewHtmxServer() - // Path to the SQLite database - dbPath := "db/sqlite.db" - // Create database connection - db, err := CreateSQLiteConnpool(dbPath) + environment := env.LoadEnvironment() + + db, err := CreatePostgresConnpool(environment.DatabaseUrl) if err != nil { Error("Failed to create database connection:", err) panic(err)