DEV: Working on environments and the recovery from nil pointers. #29

Merged
azpect merged 4 commits from dev into master 2025-07-22 22:45:34 -07:00
4 changed files with 110 additions and 49 deletions
Showing only changes of commit e0e1230660 - Show all commits

View File

@ -5,7 +5,7 @@ import "github.com/haydenhargreaves/Potion/internal/app/server"
const PORT = 3000 const PORT = 3000
func main() { func main() {
s := server.Init(PORT).ConfigureAuth().ConnectDatabase().Setup() s := server.Init(PORT).Setup()
defer s.DB.Close() defer s.DB.Close()
s.Start() s.Start()

View File

@ -4,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"os"
"strconv" "strconv"
"github.com/a-h/templ" "github.com/a-h/templ"
@ -189,7 +188,7 @@ func RecipePage(ctx *gin.Context) {
} }
title := "Potion - View Recipe" title := "Potion - View Recipe"
page := pages.RecipePage(*recipe, *user, loggedIn, os.Getenv("DOMAIN")) page := pages.RecipePage(*recipe, *user, loggedIn, deps.EnvironmentConfig.Domain)
ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page)) ctx.HTML(http.StatusOK, "", layouts.AppLayout(title, page))
} }

View File

@ -4,7 +4,6 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"net/http" "net/http"
"os"
"strings" "strings"
"github.com/a-h/templ/examples/integration-gin/gintemplrenderer" "github.com/a-h/templ/examples/integration-gin/gintemplrenderer"
@ -15,7 +14,6 @@ import (
domain "github.com/haydenhargreaves/Potion/internal/domain/server" domain "github.com/haydenhargreaves/Potion/internal/domain/server"
"github.com/haydenhargreaves/Potion/internal/infrastructure/auth" "github.com/haydenhargreaves/Potion/internal/infrastructure/auth"
"github.com/haydenhargreaves/Potion/internal/infrastructure/database/repository" "github.com/haydenhargreaves/Potion/internal/infrastructure/database/repository"
"github.com/joho/godotenv"
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
@ -53,18 +51,26 @@ func Init(port int) *Server {
return server return server
} }
func (s *Server) ConfigureAuth() *Server { // Start starts the server on the port provided when the server was initialized
err := godotenv.Load(".env") func (s *Server) Start() {
if err != nil { s.Router.Run(fmt.Sprintf(":%d", s.port))
fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err)
} }
redirect_domain := os.Getenv("DOMAIN") func (s *Server) Setup() *Server {
// SETUP THE ENVIRONMENT CONFIGURATION
cfg, err := domain.LoadEnvironment()
if err != nil {
panic(err.Error())
}
if cfg == nil {
panic("Environment configuration is nil, crashing.")
}
// SETUP GOOGLE AUTH
var ( var (
redirectUrl string = fmt.Sprintf("%s%s", redirect_domain, domain.API_AUTH_CALLBACK) redirectUrl string = fmt.Sprintf("%s%s", cfg.Domain, domain.API_AUTH_CALLBACK)
clientId string = os.Getenv("GOOGLE_CLIENT_ID") clientId string = cfg.GoogleClientId
clientSecret string = os.Getenv("GOOGLE_CLIENT_SECRET") clientSecret string = cfg.GoogleClientSecret
scope []string = []string{ scope []string = []string{
"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.profile",
@ -74,18 +80,8 @@ func (s *Server) ConfigureAuth() *Server {
// Setup Google OAuth // Setup Google OAuth
auth.NewGoogleConfig(redirectUrl, clientId, clientSecret, scope) auth.NewGoogleConfig(redirectUrl, clientId, clientSecret, scope)
return s // SETUP DATABASE
} db, err := sql.Open("postgres", cfg.DatabaseUrl)
func (s *Server) ConnectDatabase() *Server {
err := godotenv.Load(".env")
if err != nil {
fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err)
}
var connUrl string = os.Getenv("DATABASE_URL")
db, err := sql.Open("postgres", connUrl)
if err != nil { if err != nil {
panic("Could not connect to database: " + err.Error()) panic("Could not connect to database: " + err.Error())
} }
@ -96,21 +92,8 @@ func (s *Server) ConnectDatabase() *Server {
s.DB = db s.DB = db
return s // SETUP JWT
} jwtSecret := []byte(cfg.JwtSecret)
// Start starts the server on the port provided when the server was initialized
func (s *Server) Start() {
s.Router.Run(fmt.Sprintf(":%d", s.port))
}
func (s *Server) Setup() *Server {
err := godotenv.Load(".env")
if err != nil {
fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err)
}
jwtSecret := []byte(os.Getenv("JWT_SECRET"))
// Initialize and inject dependencies // Initialize and inject dependencies
userRepo := repository.NewUserRepository(s.DB) userRepo := repository.NewUserRepository(s.DB)
@ -126,6 +109,7 @@ func (s *Server) Setup() *Server {
AuthService: authService, AuthService: authService,
RecipeService: recipeService, RecipeService: recipeService,
EngagementService: engagementService, EngagementService: engagementService,
EnvironmentConfig: *cfg,
} }
// Apply middleware // Apply middleware
@ -150,15 +134,8 @@ func (s *Server) Setup() *Server {
// API router endpoints // API router endpoints
router_api.GET("/", func(ctx *gin.Context) { ctx.JSON(200, gin.H{"message": "Server is active."}) }) router_api.GET("/", func(ctx *gin.Context) { ctx.JSON(200, gin.H{"message": "Server is active."}) })
router_api.GET("/tmp", func(ctx *gin.Context) { router_api.GET("/tmp", func(ctx *gin.Context) {
if !domain.IsLoggedIn(ctx) { deps := ctx.MustGet("deps").(*domain.InjectedDependencies)
ctx.JSON(200, gin.H{"error": "User is not logged in. Please login to continue"}) ctx.JSON(200, gin.H{"config": deps.EnvironmentConfig})
return
}
userId := ctx.MustGet("userId").(int)
userEmail := ctx.MustGet("userEmail").(string)
ctx.JSON(200, gin.H{"id": userId, "email": userEmail})
}) })
// WEB router endpoints // WEB router endpoints

View File

@ -1,14 +1,30 @@
package domain package domain
import ( import (
"fmt"
"os"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
domainAuth "github.com/haydenhargreaves/Potion/internal/domain/auth" domainAuth "github.com/haydenhargreaves/Potion/internal/domain/auth"
domainEngagement "github.com/haydenhargreaves/Potion/internal/domain/engagement" domainEngagement "github.com/haydenhargreaves/Potion/internal/domain/engagement"
domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe" domainRecipe "github.com/haydenhargreaves/Potion/internal/domain/recipe"
domainUser "github.com/haydenhargreaves/Potion/internal/domain/user" domainUser "github.com/haydenhargreaves/Potion/internal/domain/user"
"github.com/joho/godotenv"
) )
// EnvironmentConfig stores the configuration of the environment. Anything loaded from the .env
// or docker environment will be stored here and can be accessed from the InjectedDependencies
// struct, which this is attached to.
type EnvironmentConfig struct {
GoogleClientId string
GoogleClientSecret string
JwtSecret string
DatabaseUrl string
Environment string
Domain string
}
// InjectedDependencies is a collection of dependencies that are injected into the application. They // InjectedDependencies is a collection of dependencies that are injected into the application. They
// are stored in the context and can be accessed by handlers via the context. // are stored in the context and can be accessed by handlers via the context.
type InjectedDependencies struct { type InjectedDependencies struct {
@ -16,6 +32,7 @@ type InjectedDependencies struct {
AuthService domainAuth.AuthService AuthService domainAuth.AuthService
RecipeService domainRecipe.RecipeService RecipeService domainRecipe.RecipeService
EngagementService domainEngagement.EngagementService EngagementService domainEngagement.EngagementService
EnvironmentConfig EnvironmentConfig
} }
// JwtClaims is the data stored in the JSON web token. All that is needed is the users ID and their // JwtClaims is the data stored in the JSON web token. All that is needed is the users ID and their
@ -32,3 +49,71 @@ func IsLoggedIn(ctx *gin.Context) bool {
_, email := ctx.Get("userEmail") _, email := ctx.Get("userEmail")
return id && email return id && email
} }
func LoadEnvironment() (*EnvironmentConfig, error) {
err := godotenv.Load(".env")
if err != nil {
fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err)
}
env := os.Getenv("ENVIRONMENT")
if env == "" {
return nil, fmt.Errorf("ENVIRONMENT environment variable is required.")
}
googleClientId := os.Getenv("GOOGLE_CLIENT_ID")
if googleClientId == "" {
return nil, fmt.Errorf("GOOGLE_CLIENT_ID environment variable is required.")
}
googleClientSecret := os.Getenv("GOOGLE_CLIENT_SECRET")
if googleClientSecret == "" {
return nil, fmt.Errorf("GOOGLE_CLIENT_SECRET environment variable is required.")
}
jwtSecret := os.Getenv("JWT_SECRET")
if jwtSecret == "" {
return nil, fmt.Errorf("JWT_SECRET environment variable is required.")
}
var domain string
if env == "dev" {
domain = os.Getenv("DOMAIN_DEV")
if domain == "" {
return nil, fmt.Errorf("DOMAIN_DEV environment variable is required when ENVIRONMENT is 'dev'.")
}
} else if env == "prod" {
domain = os.Getenv("DOMAIN_PROD")
if domain == "" {
return nil, fmt.Errorf("DOMAIN_PROD environment variable is required when ENVIRONMENT is 'prod'.")
}
} else {
return nil, fmt.Errorf("ENVIRONMENT environment variable is required and must be 'dev' or 'prod'.")
}
var dbUrl string
if env == "dev" {
dbUrl = os.Getenv("DATABASE_URL_DEV")
if dbUrl == "" {
return nil, fmt.Errorf("DATABASE_URL_DEV environment variable is required when ENVIRONMENT is 'dev'.")
}
} else if env == "prod" {
dbUrl = os.Getenv("DATABASE_URL_PROD")
if dbUrl == "" {
return nil, fmt.Errorf("DATABASE_URL_PROD environment variable is required when ENVIRONMENT is 'prod'.")
}
} else {
return nil, fmt.Errorf("ENVIRONMENT environment variable is required and must be 'dev' or 'prod'.")
}
cfg := &EnvironmentConfig{
GoogleClientId: googleClientId,
GoogleClientSecret: googleClientSecret,
JwtSecret: jwtSecret,
DatabaseUrl: dbUrl,
Environment: env,
Domain: domain,
}
return cfg, nil
}