package api import ( "github.com/gin-gonic/gin" "github.com/google/uuid" "holiday-api/domain/holiday" "log/slog" "net/http" "strconv" "time" ) func NoMethod() gin.HandlerFunc { return func(c *gin.Context) { c.AbortWithError(http.StatusNotFound, nil) } } func NoRoute() gin.HandlerFunc { return func(c *gin.Context) { c.AbortWithError(http.StatusNotFound, nil) } } func GetIndex(holidayService holiday.HolidayService, countryService holiday.CountryService, yearService holiday.YearService) gin.HandlerFunc { return func(c *gin.Context) { search := holiday.Search{Country: "HR", Year: nil} if err := c.ShouldBindQuery(&search); err != nil { c.AbortWithError(http.StatusBadRequest, err) return } years, _ := yearService.Find() if search.Year == nil { c.HTML(http.StatusOK, "index.gohtml", gin.H{"Year": time.Now().Year(), "Years": years}) return } countries, _ := countryService.Find() holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100}) c.HTML(http.StatusOK, "results.gohtml", gin.H{"Years": years, "Countries": countries, "Search": search, "Holidays": holiday.ToResponse(holidays).Holidays}) } } func GetDocumentation(countryService holiday.CountryService, yearService holiday.YearService) gin.HandlerFunc { return func(c *gin.Context) { countries, _ := countryService.Find() years, _ := yearService.Find() c.HTML(http.StatusOK, "documentation.gohtml", gin.H{"Year": time.Now().Year(), "Years": years, "Countries": countries}) } } func GetHolidays(service holiday.HolidayService) gin.HandlerFunc { return func(c *gin.Context) { paging := holiday.Paging{PageSize: 50} if err := c.ShouldBindQuery(&paging); err != nil { c.AbortWithError(http.StatusBadRequest, err) return } search := holiday.Search{} if err := c.ShouldBindQuery(&search); err != nil { c.AbortWithError(http.StatusBadRequest, err) return } holidays, err := service.Find(search, paging) if err != nil { render(c, http.StatusNotFound, holiday.ErrorResponse{Created: time.Now(), Message: "failed fetching holidays"}, nil) } else { render(c, http.StatusOK, holiday.ToResponse(holidays), search.Type) } } } func DeleteCountry(countryService holiday.CountryService) gin.HandlerFunc { return func(c *gin.Context) { id := uuid.MustParse(c.Param("id")) _, err := countryService.FindById(id) if err != nil { Abort(c, err, http.StatusNotFound, "couldn't find country") return } if err := countryService.Delete(id); err != nil { Abort(c, err, http.StatusInternalServerError, "couldn't delete country") return } c.Redirect(http.StatusSeeOther, "/admin/countries") } } func CreateOrUpdateCountry(countryService holiday.CountryService) gin.HandlerFunc { return func(c *gin.Context) { request := struct { Id *string `form:"id"` IsoName string `form:"iso_name" binding:"required,min=2,max=2"` Name string `form:"name" binding:"required,min=1,max=45"` }{} if err := c.ShouldBind(&request); err != nil { Abort(c, err, http.StatusInternalServerError, "invalid request when creating or updating country") return } country := holiday.Country{ IsoName: request.IsoName, Name: request.Name, } var err error if request.Id != nil { country.Id = uuid.MustParse(*request.Id) country, err = countryService.Update(country) } else { country, err = countryService.Create(country) } if err != nil { Abort(c, err, http.StatusInternalServerError, "couldn't create or update country") } else { c.Redirect(http.StatusSeeOther, "/admin/countries") } } } func GetCountries(countryService holiday.CountryService) gin.HandlerFunc { return func(c *gin.Context) { countries, _ := countryService.Find() c.HTML(http.StatusOK, "countries.gohtml", gin.H{"Countries": countries}) } } func DeleteHoliday(holidayService holiday.HolidayService) gin.HandlerFunc { return func(c *gin.Context) { id := uuid.MustParse(c.Param("id")) hol, err := holidayService.FindById(id) if err != nil { Abort(c, err, http.StatusNotFound, "couldn't find holiday to delete") return } if err := holidayService.Delete(id); err != nil { Abort(c, err, http.StatusInternalServerError, "couldn't delete holiday") return } c.Redirect(http.StatusSeeOther, "/admin?country="+hol.Country+"&year="+strconv.FormatInt(int64(hol.Date.Year()), 10)) } } func CopyYear(holidayService holiday.HolidayService) gin.HandlerFunc { return func(c *gin.Context) { request := struct { From int `form:"from"` To int `form:"to"` Country string `form:"country" binding:"len=2"` }{} if err := c.ShouldBind(&request); err != nil { Abort(c, err, http.StatusBadRequest, "invalid request when copying holiday") return } err := holidayService.Copy(request.Country, request.From, request.To) if err != nil { Abort(c, err, http.StatusInternalServerError, "couldn't copy holidays") } else { c.Redirect(http.StatusSeeOther, "/admin?country="+request.Country+"&year="+strconv.FormatInt(int64(request.To), 10)) } } } func CreateOrUpdateHoliday(holidayService holiday.HolidayService) gin.HandlerFunc { return 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 { Abort(c, err, http.StatusBadRequest, "invalid request when creating holiday") 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 = holidayService.Update(hol) } else { hol, err = holidayService.Create(hol) } if err != nil { Abort(c, err, 500, "couldn't create or update holiday") } else { c.Redirect(http.StatusSeeOther, "/admin?country="+request.Country+"&year="+strconv.FormatInt(int64(request.Date.Year()), 10)) } } } func GetAdminHome(holidayService holiday.HolidayService, countryService holiday.CountryService, yearService holiday.YearService) gin.HandlerFunc { return func(c *gin.Context) { search := holiday.Search{Country: "HR", Year: new(int)} *search.Year = time.Now().Year() if err := c.ShouldBindQuery(&search); err != nil { Abort(c, err, http.StatusBadRequest, "invalid search parameters") return } holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100}) holidayResponse := holiday.ToResponse(holidays) countries, _ := countryService.Find() years, _ := yearService.Find() response := map[string]any{} response["Holidays"] = holidayResponse response["Search"] = search response["Countries"] = countries response["Years"] = years c.HTML(http.StatusOK, "admin_dashboard.gohtml", response) } } func AddHolidayDialog(countryService holiday.CountryService) gin.HandlerFunc { return func(c *gin.Context) { countries, _ := countryService.Find() c.HTML(http.StatusOK, "add-holiday.gohtml", gin.H{"Countries": countries}) } } func EditHolidayDialog(holidayService holiday.HolidayService, countryService holiday.CountryService) gin.HandlerFunc { return func(c *gin.Context) { id := uuid.MustParse(c.Query("id")) hol, err := holidayService.FindById(id) if err != nil { Abort(c, err, http.StatusNotFound, "couldn't find holiday to edit") return } countries, _ := countryService.Find() c.HTML(http.StatusOK, "edit-holiday.gohtml", gin.H{"Countries": countries, "Holiday": hol}) } } func DeleteHolidayDialog(holidayService holiday.HolidayService) gin.HandlerFunc { return func(c *gin.Context) { id := uuid.MustParse(c.Query("id")) hol, err := holidayService.FindById(id) if err != nil { Abort(c, err, http.StatusNotFound, "couldn't find dialog") return } c.HTML(http.StatusOK, "delete-holiday.gohtml", gin.H{"Holiday": hol}) } } func CopyYearDialog() gin.HandlerFunc { return func(c *gin.Context) { country := c.Query("country") year, err := strconv.ParseInt(c.Query("year"), 10, 32) if err != nil { Abort(c, err, http.StatusNotFound, "couldn't parse year") return } c.HTML(http.StatusOK, "copy-year.gohtml", gin.H{"Country": country, "Year": year}) } } func AddCountryDialog() gin.HandlerFunc { return func(c *gin.Context) { c.HTML(http.StatusOK, "add-country.gohtml", gin.H{}) } } func EditCountryDialog(countryService holiday.CountryService) gin.HandlerFunc { return func(c *gin.Context) { id := uuid.MustParse(c.Query("id")) country, err := countryService.FindById(id) if err != nil { Abort(c, err, http.StatusNotFound, "couldn't find country") return } c.HTML(http.StatusOK, "edit-country.gohtml", gin.H{"Country": country}) } } func DeleteCountryDialog(countryService holiday.CountryService) gin.HandlerFunc { return func(c *gin.Context) { id := uuid.MustParse(c.Query("id")) country, err := countryService.FindById(id) if err != nil { Abort(c, err, http.StatusNotFound, "couldn't find dialog") return } c.HTML(http.StatusOK, "delete-country.gohtml", gin.H{"Country": country}) } } type CSV interface { CSV() []byte } func render(c *gin.Context, status int, response any, contentType *string) { value := c.GetHeader("accept") if contentType != nil { switch *contentType { case "xml": value = "text/xml" case "json": value = "application/json" case "csv": value = "text/csv" } } switch value { case "text/csv": if csvResponse, ok := response.(CSV); ok { c.Data(200, value+"; charset=utf-8", csvResponse.CSV()) } else { c.Header("content-type", "application/json; charset=utf-8") c.JSON(status, response) } case "application/xml": fallthrough case "text/xml": c.Header("content-type", value+"; charset=utf-8; header=present;") c.XML(status, response) case "application/json": fallthrough default: c.Header("content-type", "application/json; charset=utf-8") c.JSON(status, response) } } func Abort(c *gin.Context, err error, statusCode int, message string) { slog.Error(message, slog.String("err", errorMessage(err))) c.AbortWithError(statusCode, err) } func errorMessage(err error) string { if err != nil { return err.Error() } return "-" }