289 lines
7.6 KiB
Go
289 lines
7.6 KiB
Go
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]}
|
|
}
|