package main import ( "embed" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/joho/godotenv" _ "github.com/lib/pq" "holiday-api/holiday" "holiday-api/migration" "html/template" "log" "net/http" "os" "strconv" "strings" "time" ) //go:embed db/dev/*.sql var devMigrations embed.FS //go:embed db/prod/*.sql var prodMigrations embed.FS var isDev = false func init() { godotenv.Load() if strings.Contains(os.Getenv("PROFILE"), "dev") { isDev = true } log.SetPrefix("") log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) } func main() { client, err := connectToDb() if err != nil { log.Fatalf("couldn't connect to db: %v", err) } migrationFolder := prodMigrations if isDev { migrationFolder = devMigrations } if err := migration.InitializeMigrations(client, migrationFolder); err != nil { log.Fatalf("couldn't execute migrations: %v", err) } g := gin.Default() g.Static("assets", "assets") loadTemplates(g) holidayService := holiday.Service{DB: client} g.GET("/api/v1/holidays", getHolidays(holidayService)) setupAdminDashboard(g.Group("/admin"), holidayService) g.GET("/", func(c *gin.Context) { year := time.Now().Year() search := holiday.Search{Country: "HR", Year: &year} if err := c.ShouldBindQuery(&search); err != nil { c.AbortWithError(http.StatusBadRequest, err) return } holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100}) c.HTML(http.StatusOK, "index.gohtml", gin.H{"Search": search, "Holidays": mapHolidays(holidays).Holidays}) }) g.GET("/documentation", func(c *gin.Context) { c.HTML(http.StatusOK, "documentation.gohtml", nil) }) g.GET("/search", func(c *gin.Context) { request := holiday.Search{} if err := c.ShouldBindQuery(&request); err != nil { c.AbortWithError(http.StatusBadRequest, err) return } search := holiday.Search{Country: request.Country, Date: request.Date} holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100}) c.HTML(http.StatusOK, "search.gohtml", gin.H{"Search": search, "Holidays": mapHolidays(holidays).Holidays}) }) g.GET("/dialogs/check-is-a-holiday", func(c *gin.Context) { c.HTML(http.StatusOK, "check-is-a-holiday.gohtml", gin.H{}) }) log.Fatal(http.ListenAndServe(":5281", g)) } func loadTemplates(g *gin.Engine) { g.SetFuncMap(template.FuncMap{ "boolcmp": func(value *bool, expected string) bool { if value == nil { return expected == "nil" } else { return (*value && expected == "true") || (!(*value) && expected == "false") } }, "deferint": func(value *int) int { return *value }, "intpeq": func(selected *int, value int) bool { if selected != nil { return *selected == value } return false }, }) g.LoadHTMLFiles( "templates/index.gohtml", "templates/search.gohtml", "templates/documentation.gohtml", "templates/admin_dashboard.gohtml", "templates/dialogs/add-holiday.gohtml", "templates/dialogs/edit-holiday.gohtml", "templates/dialogs/delete-holiday.gohtml", "templates/dialogs/check-is-a-holiday.gohtml", ) } func setupAdminDashboard(adminDashboard *gin.RouterGroup, service holiday.Service) { adminDashboard.Use(gin.BasicAuth(loadAuth())) adminDashboard.GET("/", func(c *gin.Context) { search := holiday.Search{Country: "HR", Year: new(int)} *search.Year = time.Now().Year() if err := c.ShouldBindQuery(&search); err != nil { c.AbortWithError(http.StatusBadRequest, err) return } holidays, _ := service.Find(search, holiday.Paging{PageSize: 100}) holidayResponse := mapHolidays(holidays) response := map[string]any{} response["holidays"] = holidayResponse response["search"] = search c.HTML(http.StatusOK, "admin_dashboard.gohtml", response) }) adminDashboard.POST("/holidays", func(c *gin.Context) { request := struct { Id *string `form:"id"` Name string `form:"name" binding:"required,min=1"` Description string `form:"description"` IsStateHoliday bool `form:"state_holiday"` IsReligiousHoliday bool `form:"religious_holiday"` Country string `form:"country" binding:"len=2"` Date time.Time `form:"date" time_format:"2006-01-02"` }{} if err := c.ShouldBind(&request); err != nil { c.AbortWithError(http.StatusBadRequest, err) return } hol := holiday.Holiday{ Country: request.Country, Date: request.Date, Name: request.Name, Description: request.Description, IsStateHoliday: request.IsStateHoliday, IsReligiousHoliday: request.IsReligiousHoliday, } var err error if request.Id != nil { hol.Id = uuid.MustParse(*request.Id) hol, err = service.Update(hol) } else { hol, err = service.Create(hol) } if err != nil { c.AbortWithError(http.StatusInternalServerError, err) } else { c.Redirect(http.StatusSeeOther, "/admin?country="+request.Country+"&year="+strconv.FormatInt(int64(request.Date.Year()), 10)) } }) adminDashboard.POST("/holidays/:id/delete", func(c *gin.Context) { id := uuid.MustParse(c.Param("id")) hol, err := service.FindById(id) if err != nil { c.AbortWithError(http.StatusNotFound, err) return } if err := service.Delete(id); err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } c.Redirect(http.StatusSeeOther, "/admin?country="+hol.Country+"&year="+strconv.FormatInt(int64(hol.Date.Year()), 10)) }) adminDashboard.GET("/dialogs/add-holiday", func(c *gin.Context) { c.HTML(http.StatusOK, "add-holiday.gohtml", gin.H{}) }) adminDashboard.GET("/dialogs/edit-holiday", func(c *gin.Context) { id := uuid.MustParse(c.Query("id")) hol, err := service.FindById(id) if err != nil { c.AbortWithError(http.StatusNotFound, err) return } c.HTML(http.StatusOK, "edit-holiday.gohtml", gin.H{"Holiday": hol}) }) adminDashboard.GET("/dialogs/delete-holiday", func(c *gin.Context) { id := uuid.MustParse(c.Query("id")) hol, err := service.FindById(id) if err != nil { c.AbortWithError(http.StatusNotFound, err) return } c.HTML(http.StatusOK, "delete-holiday.gohtml", gin.H{"Holiday": hol}) }) } func loadAuth() map[string]string { credentials := envMustExist("AUTH_KEY") values := strings.Split(credentials, ":") return map[string]string{values[0]: values[1]} }