holiday-api/api/api.go

351 lines
10 KiB
Go

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 "-"
}