Hayden Hargreaves b17c5774e9 (UI): Implemented much of the frontend recipe creation wizard.
Most everything is implemented, included a state handler and a pretty
simple (but workable) system for managing state in HTML. Nice and simple
for now.

There is still much work to be done, but the rest is simple backend
creation and error handling. And then input validation...a nightmare.
2025-06-29 22:30:20 -07:00

183 lines
5.0 KiB
Go

package server
import (
"database/sql"
"fmt"
"net/http"
"os"
"github.com/a-h/templ/examples/integration-gin/gintemplrenderer"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/haydenhargreaves/Potion/internal/app/handlers"
"github.com/haydenhargreaves/Potion/internal/app/service"
domain "github.com/haydenhargreaves/Potion/internal/domain/server"
"github.com/haydenhargreaves/Potion/internal/infrastructure/auth"
"github.com/haydenhargreaves/Potion/internal/infrastructure/database/repository"
"github.com/joho/godotenv"
_ "github.com/lib/pq"
)
type Server struct {
port int
Router *gin.Engine
config cors.Config
DB *sql.DB
}
// Init initializes the server with the provided port. CORS settings are defined here.
// A pointer to a server object is returned which allows for method chaining.
func Init(port int) *Server {
// TODO: Set this to release in prod
gin.SetMode(gin.DebugMode)
server := &Server{
Router: gin.Default(),
port: port,
config: cors.DefaultConfig(),
}
// Some stuff for templ rendering
htmlRenderer := server.Router.HTMLRender
server.Router.HTMLRender = &gintemplrenderer.HTMLTemplRenderer{FallbackHtmlRenderer: htmlRenderer}
// Disable proxy warnings
server.Router.SetTrustedProxies(nil)
// Setup the CORS settings and active them
server.config.AllowAllOrigins = true
server.Router.Use(cors.New(server.config))
return server
}
func (s *Server) ConfigureAuth() *Server {
err := godotenv.Load(".env")
if err != nil {
panic("Could not load env file")
}
redirect_domain := os.Getenv("DOMAIN")
var (
redirectUrl string = fmt.Sprintf("%s%s", redirect_domain, domain.API_AUTH_CALLBACK)
clientId string = os.Getenv("GOOGLE_CLIENT_ID")
clientSecret string = os.Getenv("GOOGLE_CLIENT_SECRET")
scope []string = []string{
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
}
)
// Setup Google OAuth
auth.NewGoogleConfig(redirectUrl, clientId, clientSecret, scope)
return s
}
func (s *Server) ConnectDatabase() *Server {
err := godotenv.Load(".env")
if err != nil {
panic("Could not load env file")
}
var connUrl string = os.Getenv("DATABASE_URL")
db, err := sql.Open("postgres", connUrl)
if err != nil {
panic("Could not connect to database: " + err.Error())
}
if err = db.Ping(); err != nil {
panic("Error pinging database: " + err.Error())
}
s.DB = db
return s
}
// 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 {
panic("Could not load env file")
}
jwtSecret := []byte(os.Getenv("JWT_SECRET"))
// Initialize and inject dependencies
userRepo := repository.NewUserRepository(s.DB)
recipeRepo := repository.NewRecipeRepository(s.DB)
userService := service.NewUserService(userRepo)
authService := service.NewAuthService(userRepo, jwtSecret)
recipeService := service.NewRecipeService(recipeRepo)
deps := &domain.InjectedDependencies{
UserService: userService,
AuthService: authService,
RecipeService: recipeService,
}
// Apply middleware
s.Router.Use(DepedencyInjectionMiddleware(deps))
s.Router.Use(JwtAuthMiddleWare(jwtSecret))
// Redirect index to home page: Update this as needed
s.Router.GET("/", func(ctx *gin.Context) { ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) })
// Wrap all routes with a version
router_v1 := s.Router.Group(domain.VERSION)
// Domain specific routers
router_web := router_v1.Group(domain.WEB)
router_api := router_v1.Group(domain.API)
router_state := router_web.Group("state")
// Static routes
router_web.Static("/static", "./web/static")
// API router endpoints
router_api.GET("/", func(ctx *gin.Context) { ctx.JSON(200, gin.H{"message": "Server is active."}) })
router_api.GET("/tmp", func(ctx *gin.Context) {
if !domain.IsLoggedIn(ctx) {
ctx.JSON(200, gin.H{"error": "User is not logged in. Please login to continue"})
return
}
userId := ctx.MustGet("userId").(int)
userEmail := ctx.MustGet("userEmail").(string)
ctx.JSON(200, gin.H{"id": userId, "email": userEmail})
})
// WEB router endpoints
router_web.GET("/", func(ctx *gin.Context) { ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) })
router_web.GET("/login", handlers.LoginPage)
router_web.GET("/home", handlers.HomePage)
router_web.GET("/favorites", handlers.FavoritesPage)
router_web.GET("/create", handlers.CreatePage)
router_web.GET("/profile", handlers.ProfilePage)
router_web.GET("/list", handlers.ListPage)
// WEB state endpoints
router_state.POST("/tags", handlers.NewTag)
router_state.POST("/tags/delete", handlers.DeleteTag)
// Authentication
router_api.GET("/auth/login", handlers.GoogleLogin)
router_api.GET("/auth/callback", handlers.GoogleCallback)
router_api.GET("/auth/logout", handlers.Logout)
// Recipe endpoints
// TODO: This should be post. Temp!
router_api.POST("/recipe", handlers.CreateRecipe)
return s
}