This includes backend updates as well as frontend changes! The backend also includes a new repository to get a list of jobs via their IDs, which does respect order! The list displays for users that are logged in and a small message when the user is not logged in. The last piece is the recipe of the week segment, which can be as simple as a DB cron-job. But that will require stored procedures. Need to learn those next. Though I want to deploy the application soon, so I need to begin working on that.
233 lines
6.6 KiB
Go
233 lines
6.6 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, 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
|
|
}
|