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" "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() loadTemplates(g) holidayService := holiday.Service{DB: client} g.GET("/api/v1/holidays", getHolidays(holidayService)) setupAdminDashboard(g.Group("/admin"), holidayService) g.GET("/docs", func(c *gin.Context) { c.HTML(http.StatusOK, "docs.gohtml", nil) }) g.GET("/", func(c *gin.Context) { c.HTML(http.StatusOK, "index.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, Year: request.Year} holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100}) c.HTML(http.StatusOK, "search.gohtml", mapHolidays(holidays)) }) g.GET("/search/date", 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_date.gohtml", mapHolidays(holidays)) }) 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 }, }) g.LoadHTMLFiles("templates/admin_dashboard.gohtml", "templates/holiday.gohtml", "templates/error.gohtml", "templates/index.gohtml", "templates/search.gohtml", "templates/search_date.gohtml", "templates/docs.gohtml") } func setupAdminDashboard(adminDashboard *gin.RouterGroup, service holiday.Service) { adminDashboard.Use(gin.BasicAuth(loadAuth())) adminDashboard.GET("/holiday", func(c *gin.Context) { id := c.Query("id") response := HolidaySingleResponse{ Id: nil, Country: "", Date: DateResponse{time.Now()}, Name: "", Description: "", IsStateHoliday: true, IsReligiousHoliday: false, } if id != "" { if h, err := service.FindById(uuid.Must(uuid.Parse(id))); err == nil { response = HolidaySingleResponse{ Id: &h.Id, Country: h.Country, Date: DateResponse{h.Date}, Name: h.Name, Description: h.Description, IsStateHoliday: h.IsStateHoliday, IsReligiousHoliday: h.IsReligiousHoliday, } } } c.HTML(http.StatusOK, "holiday.gohtml", response) }) adminDashboard.POST("/holiday", 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:"stateHoliday"` IsReligiousHoliday bool `form:"religiousHoliday"` 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) } 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/holiday?id="+hol.Id.String()) } }) adminDashboard.POST("/holiday/delete", func(c *gin.Context) { request := struct { Id *string `form:"id" binding:"required"` }{} if err := c.ShouldBind(&request); err != nil { c.AbortWithError(http.StatusBadRequest, err) return } err := service.Delete(uuid.MustParse(*request.Id)) if err != nil { log.Printf("Failed deleting holiday: %v", err) c.AbortWithStatus(http.StatusInternalServerError) } else { c.Redirect(http.StatusSeeOther, "/admin") } }) 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) }) } func loadAuth() map[string]string { credentials := envMustExist("AUTH_KEY") values := strings.Split(credentials, ":") return map[string]string{values[0]: values[1]} }