The make button is pretty much done, just need to finish up by rate limiting it. That will take place in the engagement repository. Sharing works as well, just a UI change, there is no backend yet, maybe there should be an engagement type for sharing recipes. But not totally sure. The viewing is also in a semi-working state. It does not create requests for users that aren't signed in, which needs to come. With that update there is a need to update HOW the requests come in, we don't need it every time we load the page. Maybe just when we click to it, from somewhere else? Finally, the favoriting does not totally work. The entry into the engagement table is complete, but the actual favorites table, favorite creation, button toggling AND button rendering is not implemented yet.
214 lines
6.1 KiB
Go
214 lines
6.1 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 {
|
|
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)
|
|
engagementRepo := repository.NewEngagementRepository(s.DB)
|
|
userService := service.NewUserService(userRepo)
|
|
authService := service.NewAuthService(userRepo, jwtSecret)
|
|
recipeService := service.NewRecipeService(recipeRepo)
|
|
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.GET("/user/recipes", handlers.GetUserRecipes)
|
|
|
|
// Engagement endpoints
|
|
router_api.POST("/engagement/view/:id", handlers.EngagementViewRecipe)
|
|
router_api.POST("/engagement/like/:id", handlers.EngagementLikeRecipe)
|
|
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
|
|
}
|