So much progress! Yay!! Whats missing is the global storage of the filters. That is the final touch for searching.
232 lines
8.3 KiB
Go
232 lines
8.3 KiB
Go
package server
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"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/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/lib/pq"
|
|
)
|
|
|
|
type Server struct {
|
|
port int
|
|
Router *gin.Engine
|
|
config cors.Config
|
|
DB *sql.DB
|
|
deps domain.InjectedDependencies
|
|
}
|
|
|
|
// 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 {
|
|
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.config.AllowOrigins = []string{"http://localhost:5173"}
|
|
server.config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE"}
|
|
server.config.AllowHeaders = []string{"Origin", "Content-Type", "Authorization"}
|
|
server.config.AllowCredentials = true
|
|
server.Router.Use(cors.New(server.config))
|
|
|
|
return server
|
|
}
|
|
|
|
// 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))
|
|
}
|
|
|
|
// TODO: (9/4/2025) Abstract these functions and cleanup. This is fucking messy...
|
|
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.")
|
|
}
|
|
|
|
if cfg.Environment == "dev" {
|
|
gin.SetMode(gin.DebugMode)
|
|
} else if cfg.Environment == "prod" {
|
|
gin.SetMode(gin.ReleaseMode)
|
|
} else {
|
|
gin.SetMode(gin.TestMode)
|
|
}
|
|
|
|
// SETUP GOOGLE AUTH
|
|
var (
|
|
// NOTE: USING V2 NOW
|
|
redirectUrl string = fmt.Sprintf("%s%s", cfg.Domain, domain.API_AUTH_CALLBACK_V2)
|
|
clientId string = cfg.GoogleClientId
|
|
clientSecret string = cfg.GoogleClientSecret
|
|
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)
|
|
|
|
// SETUP DATABASE
|
|
db, err := sql.Open("postgres", cfg.DatabaseUrl)
|
|
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
|
|
|
|
// SETUP JWT
|
|
jwtSecret := []byte(cfg.JwtSecret)
|
|
|
|
// Initialize and inject dependencies
|
|
userRepo := repository.NewUserRepository(s.DB)
|
|
recipeRepo := repository.NewRecipeRepository(s.DB)
|
|
engagementRepo := repository.NewEngagementRepository(s.DB)
|
|
userService := service.NewUserService(userRepo)
|
|
authService := service.NewAuthService(userRepo, jwtSecret)
|
|
recipeService := service.NewRecipeService(recipeRepo, engagementRepo)
|
|
engagementService := service.NewEngagementService(engagementRepo, recipeRepo)
|
|
|
|
s.deps = domain.InjectedDependencies{
|
|
UserService: userService,
|
|
AuthService: authService,
|
|
RecipeService: recipeService,
|
|
EngagementService: engagementService,
|
|
EnvironmentConfig: *cfg,
|
|
}
|
|
|
|
// Apply middleware
|
|
s.Router.Use(RecoveryMiddleware())
|
|
// NOTE: No longer running on every connection!
|
|
// 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_1)
|
|
router_v2 := s.Router.Group(domain.VERSION_2)
|
|
|
|
// 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."}) })
|
|
|
|
// WEB router endpoints
|
|
router_web.GET("/", func(ctx *gin.Context) { ctx.Redirect(http.StatusSeeOther, domain.WEB_HOME) })
|
|
router_web.GET("/login", s.LoginPageHandler)
|
|
router_web.GET("/home", s.HomePageHandler)
|
|
router_web.GET("/favorites", s.FavoritesPageHandler)
|
|
router_web.GET("/create", s.CreatePageHandler)
|
|
router_web.GET("/profile", s.ProfilePageHandler)
|
|
router_web.GET("/list", s.ListPageHandler)
|
|
router_web.GET("/recipe/:id", s.RecipePageHandler)
|
|
router_web.GET("/search", s.SearchPageHandler)
|
|
router_web.GET("/404", s.NotFoundPageHandler)
|
|
|
|
// WEB state endpoints
|
|
router_state.POST("/tags", s.NewTagHandler)
|
|
router_state.POST("/tags/delete", s.DeleteTagHandler)
|
|
|
|
// Authentication
|
|
router_api.GET("/auth/login", s.GoogleLoginHandler)
|
|
router_api.GET("/auth/callback", s.GoogleCallbackHandler)
|
|
router_api.GET("/auth/logout", s.LogoutHandler)
|
|
|
|
// Recipe endpoints
|
|
router_api.POST("/recipe", s.CreateRecipeHandler)
|
|
router_api.POST("/recipe/search", s.SearchRecipesHandler)
|
|
router_api.POST("/recipe/search/favorites", s.SearchRecipesFavoritesHandler)
|
|
|
|
router_api.GET("/user/recipes", s.GetUserFavoriteRecipesHandler)
|
|
router_api.GET("/user/favorites", s.GetUserFavoriteRecipesHandler)
|
|
|
|
// Engagement endpoints
|
|
router_api.POST("/engagement/view/:id", s.EngagementViewRecipeHandler)
|
|
router_api.POST("/engagement/share/:id", s.EngagementShareRecipeHandler)
|
|
router_api.POST("/engagement/favorite/:id", s.EngagementFavoriteRecipeHandler)
|
|
router_api.POST("/engagement/make/:id", s.EngagementMakeRecipeHandler)
|
|
|
|
// Catch un-routed URLS
|
|
s.Router.NoRoute(func(ctx *gin.Context) {
|
|
path := ctx.Request.URL.Path
|
|
|
|
// TODO: Use constants for errors?
|
|
if strings.HasPrefix(path, domain.VERSION_1+domain.API) {
|
|
ctx.JSON(http.StatusNotFound, gin.H{
|
|
"status": http.StatusNotFound,
|
|
"error": "API_NOT_FOUND",
|
|
"message": "The request endpoint does not exist.",
|
|
"path": path,
|
|
})
|
|
return
|
|
}
|
|
|
|
ctx.Redirect(http.StatusSeeOther, domain.WEB_NOT_FOUND)
|
|
})
|
|
|
|
// ---- VERSION 2 ROUTES ---- //
|
|
router_api_v2 := router_v2.Group(domain.API)
|
|
router_api_v2.GET("/recipe/:id", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetRecipeHandlerV2)
|
|
router_api_v2.GET("/recipe/of-the-week", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetRecipeOfTheWeekHandlerV2)
|
|
router_api_v2.POST("/recipe/search", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.SearchRecipeHandlerV2)
|
|
|
|
router_api_v2.GET("/auth/login", s.GetGoogleAuthUrlHandlerV2)
|
|
router_api_v2.GET("/auth/callback", s.GoogleCallbackHandlerV2)
|
|
router_api_v2.GET("/auth/logout", s.LogoutHandlerV2)
|
|
|
|
router_api_v2.GET("/user", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenticatedUserHandlerV2)
|
|
router_api_v2.GET("/user/:id", s.GetUserV2)
|
|
router_api_v2.GET("/user/recipes", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserRecipesV2)
|
|
router_api_v2.GET("/user/favorites", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserFavoritesV2)
|
|
router_api_v2.GET("/user/engagement", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserEngagementV2)
|
|
|
|
router_api_v2.GET("/user/recipes/made", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserMadeRecipesV2)
|
|
router_api_v2.GET("/user/recipes/viewed", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetAuthenicatedUserViewedRecipesV2)
|
|
|
|
router_api_v2.GET("/protected", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), func(ctx *gin.Context) {
|
|
ctx.JSON(http.StatusOK, gin.H{"msg": "YAY"})
|
|
})
|
|
|
|
router_api_v2.POST("/engagement/view/:id", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementViewRecipeHandlerV2)
|
|
router_api_v2.POST("/engagement/share/:id", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementShareRecipeHandlerV2)
|
|
router_api_v2.POST("/engagement/favorite/:id", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementFavoriteRecipeHandlerV2)
|
|
router_api_v2.POST("/engagement/make/:id", JwtAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.EngagementMakeRecipeHandlerV2)
|
|
|
|
return s
|
|
}
|