package main import ( "embed" "fmt" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "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) } r := chi.NewRouter() r.Use(middleware.RequestID) r.Use(middleware.RealIP) r.Use(middleware.Logger) r.Use(middleware.Recoverer) holidayService := holiday.Service{client} r.Get("/api/v1/holidays", getHolidays(holidayService)) templates, err := template.New("").ParseFiles("templates/index.gohtml", "templates/search.gohtml", "templates/search_date.gohtml", "templates/docs.gohtml") if err != nil { log.Fatalf("couldn't load templates: %v", err) } r.Get("/docs", func(w http.ResponseWriter, r *http.Request) { templates, err := template.New("").ParseFiles("templates/docs.gohtml") if err != nil { log.Fatalf("couldn't load templates: %v", err) } renderTemplate(w, r, 200, templates, "docs.gohtml", 0) }) r.Get("/", func(w http.ResponseWriter, r *http.Request) { renderTemplate(w, r, 200, templates, "index.gohtml", 0) }) r.Get("/search", func(w http.ResponseWriter, r *http.Request) { r.ParseForm() country := r.FormValue("country") year := time.Now().Year() if y := r.FormValue("year"); y != "" { if yr, err := strconv.ParseInt(y, 10, 64); err == nil { year = int(yr) } } search := holiday.Search{Year: &year, Country: country} holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100}) holidayResponse := mapHolidays(holidays) renderTemplate(w, r, 200, templates, "search.gohtml", holidayResponse) }) r.Get("/search/date", func(w http.ResponseWriter, r *http.Request) { r.ParseForm() country := r.FormValue("country") date := time.Now() if y := r.FormValue("date"); y != "" { date = parseDate(y) } search := holiday.Search{Date: &date, Country: country} holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100}) holidayResponse := mapHolidays(holidays) renderTemplate(w, r, 200, templates, "search_date.gohtml", holidayResponse) }) r.Mount("/admin", adminDashboard(holidayService)) log.Fatal(http.ListenAndServe(":5281", r)) } func adminDashboard(service holiday.Service) http.Handler { r := chi.NewRouter() credentials := loadCredentials() templates, err := template.New("").Funcs(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 }, }).ParseFiles("templates/admin_dashboard.gohtml", "templates/holiday.gohtml", "templates/error.gohtml") if err != nil { log.Fatalf("couldn't load templates: %v", err) } r.Use(middleware.BasicAuth("/admin", credentials)) r.Get("/holiday", func(w http.ResponseWriter, r *http.Request) { r.ParseForm() id := r.FormValue("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, } } } renderTemplate(w, r, 200, templates, "holiday.gohtml", response) }) r.Post("/holiday/delete", func(w http.ResponseWriter, r *http.Request) { r.ParseForm() var id uuid.UUID if i := r.FormValue("id"); i != "" { id = uuid.MustParse(i) } else { log.Printf("Failed deleting holiday: missing id", err) w.WriteHeader(500) w.Write([]byte("There was an error")) } err := service.Delete(id) if err != nil { log.Printf("Failed deleting holiday: %v", err) w.WriteHeader(500) w.Write([]byte("There was an error")) } else { w.Header().Add("Location", "/admin") w.WriteHeader(http.StatusSeeOther) w.Write([]byte{}) } }) r.Post("/holiday", func(w http.ResponseWriter, r *http.Request) { r.ParseForm() var id *uuid.UUID = nil if i := r.FormValue("id"); i != "" { temp := uuid.MustParse(i) id = &temp } name := strings.TrimSpace(r.FormValue("name")) description := strings.TrimSpace(r.FormValue("description")) isStateHoliday := parseCheckbox(r.FormValue("stateHoliday")) isReligiousHoliday := parseCheckbox(r.FormValue("religiousHoliday")) date := parseDate(r.FormValue("date")) country := r.FormValue("country") hol := holiday.Holiday{ Country: country, Date: date, Name: name, Description: description, IsStateHoliday: isStateHoliday, IsReligiousHoliday: isReligiousHoliday, } var err error if id != nil { hol.Id = *id hol, err = service.Update(hol) } else { hol, err = service.Create(hol) } if err != nil { log.Printf("Failed updating or creating holiday: %v", err) w.WriteHeader(500) w.Write([]byte("There was an error")) } else { w.Header().Add("Location", "/admin/holiday?id="+hol.Id.String()) w.WriteHeader(http.StatusSeeOther) w.Write([]byte{}) } }) r.Get("/", func(w http.ResponseWriter, r *http.Request) { r.ParseForm() country := r.FormValue("country") if country == "" { country = "HR" } var stateHoliday *bool = nil if h := r.FormValue("stateHoliday"); h != "" { if hol, err := strconv.ParseBool(h); err == nil { stateHoliday = &hol } } var religiousHoliday *bool = nil if h := r.FormValue("religiousHoliday"); h != "" { if hol, err := strconv.ParseBool(h); err == nil { religiousHoliday = &hol } } year := time.Now().Year() if y := r.FormValue("year"); y != "" { if yr, err := strconv.ParseInt(y, 10, 64); err == nil { year = int(yr) } } search := holiday.Search{Year: &year, Country: country, StateHoliday: stateHoliday, ReligiousHoliday: religiousHoliday} holidays, _ := service.Find(search, holiday.Paging{PageSize: 100}) holidayResponse := mapHolidays(holidays) response := map[string]any{} response["holidays"] = holidayResponse response["search"] = search renderTemplate(w, r, 200, templates, "admin_dashboard.gohtml", response) }) return r } func parseDate(date string) time.Time { var year, month, day int fmt.Sscanf(date, "%d-%d-%d", &year, &month, &day) return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) } func parseCheckbox(value string) bool { if value != "" { return true } return false } func loadCredentials() map[string]string { credentials := envMustExist("AUTH_KEY") values := strings.Split(credentials, ":") return map[string]string{values[0]: values[1]} }