223 lines
7.5 KiB
Go
223 lines
7.5 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/of-the-week", JwtOptionalAuthMiddlewareV2([]byte(cfg.JwtSecret)), s.GetRecipeOfTheWeekHandlerV2)
|
|
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/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"})
|
|
})
|
|
|
|
return s
|
|
}
|