The application can now be built, tailwind is built, AND templ components are generated! Which means, we can no longer push the tailwind generated CSS file OR the templ generated go files! Plus, this is the first step towards deployment and CI/CD!
233 lines
6.9 KiB
Go
233 lines
6.9 KiB
Go
package server
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"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/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 {
|
|
fmt.Printf("No .env file found or error loading .env: %v. Relying on system environment variables.", err)
|
|
}
|
|
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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)
|
|
|
|
deps := &domain.InjectedDependencies{
|
|
UserService: userService,
|
|
AuthService: authService,
|
|
RecipeService: recipeService,
|
|
EngagementService: engagementService,
|
|
}
|
|
|
|
// 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)
|
|
router_web.GET("/recipe/:id", handlers.RecipePage)
|
|
router_web.GET("/search", handlers.SearchPage)
|
|
router_web.GET("/404", handlers.NotFoundPage)
|
|
|
|
// 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
|
|
router_api.POST("/recipe", handlers.CreateRecipe)
|
|
router_api.POST("/recipe/search", handlers.SearchRecipes)
|
|
router_api.POST("/recipe/search/favorites", handlers.SearchRecipesFavorites)
|
|
router_api.GET("/user/recipes", handlers.GetUserRecipes)
|
|
router_api.GET("/user/favorites", handlers.GetUserFavoriteRecipes)
|
|
|
|
router_api.GET("/user/temp", func(ctx *gin.Context) {
|
|
recipes, err := recipeService.GetUserMadeRecipes(3, 6)
|
|
|
|
if err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
|
"recipes": recipes,
|
|
"error": err.Error(),
|
|
})
|
|
} else {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{
|
|
"recipes": recipes,
|
|
"error": "",
|
|
})
|
|
}
|
|
})
|
|
|
|
// Engagement endpoints
|
|
router_api.POST("/engagement/view/:id", handlers.EngagementViewRecipe)
|
|
router_api.POST("/engagement/share/:id", handlers.EngagementShareRecipe)
|
|
router_api.POST("/engagement/favorite/:id", handlers.EngagementFavoriteRecipe)
|
|
router_api.POST("/engagement/make/:id", handlers.EngagementMakeRecipe)
|
|
|
|
// 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+domain.API) {
|
|
ctx.JSON(http.StatusNotFound, gin.H{
|
|
"status": 404,
|
|
"error": "API_NOT_FOUND",
|
|
"message": "The request endpoint does not exist.",
|
|
"path": path,
|
|
})
|
|
return
|
|
}
|
|
|
|
ctx.Redirect(http.StatusSeeOther, domain.WEB_NOT_FOUND)
|
|
})
|
|
|
|
return s
|
|
}
|