schedule/main.go

291 lines
6.8 KiB
Go

package main
import (
"embed"
"log"
"net/http"
"time"
"github.com/gofiber/fiber/v2/middleware/filesystem"
"github.com/glebarez/sqlite"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"gorm.io/gorm"
)
//go:embed public/*
var f embed.FS
type Model struct {
ID uint `gorm:"primaryKey;not null" json:"id"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
type ScheduleItem struct {
Model
Name string `gorm:"not null" json:"name"`
Description *string `json:"description"`
Duration Duration `gorm:"not null;default:\"1h\"" json:"duration"`
Date time.Time `gorm:"not null" json:"date"`
Tags []ScheduleTag `gorm:"many2many:schedule_item_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"tags"`
}
type ScheduleTag struct {
Model
Name string `gorm:"not null;unique;index" json:"name"`
}
type NewScheduleItem struct {
Name *string `json:"name" form:"name"`
Description *string `json:"description" form:"description"`
Duration *Duration `json:"duration" form:"duration"`
Date *time.Time `json:"date" form:"date"`
Tags []string `json:"tags"`
}
type NewScheduleTag struct {
Name *string `json:"name" form:"name"`
}
func getDateValues(c *fiber.Ctx) (time.Time, time.Time, error) {
var err error
from := time.Now()
to := time.Now().Add(time.Hour * 24 * time.Duration(c.QueryInt("days", 7)))
if rawFromDate := c.Query("from"); rawFromDate != "" {
from, err = time.Parse("2006-01-02", rawFromDate)
if err != nil {
return from, to, err
}
}
if rawToDate := c.Query("to"); rawToDate != "" {
to, err = time.Parse("2006-01-02", rawToDate)
if err != nil {
return from, to, err
}
}
return from, to, nil
}
func dateRange(from time.Time, to time.Time) []time.Time {
var dates []time.Time
for d := from; !d.After(to); d = d.AddDate(0, 0, 1) {
dates = append(dates, d)
}
return dates[:len(dates)-1]
}
func main() {
db, err := gorm.Open(sqlite.Open("schedule.db"), &gorm.Config{})
if err != nil {
log.Fatalf("failed to open database: %v", err)
}
err = db.AutoMigrate(&ScheduleItem{}, &ScheduleTag{})
if err != nil {
log.Fatalf("failed to migrate database: %v", err)
}
app := fiber.New()
app.Use(cors.New())
app.Get("/api/items", func(c *fiber.Ctx) error {
fromDate, toDate, err := getDateValues(c)
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString("Invalid date query values")
}
items, err := GetScheduleItems(db, fromDate, toDate)
if err != nil {
log.Printf("failed to find schedule items: %v", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(items)
})
app.Post("/api/items", func(c *fiber.Ctx) error {
input := new(NewScheduleItem)
if err := c.BodyParser(input); err != nil {
return err
}
if input.Name == nil || len(*input.Name) == 0 {
return c.Status(fiber.StatusBadRequest).SendString("Name is required")
}
if input.Duration == nil {
input.Duration = &Duration{Duration: time.Hour}
}
if input.Date == nil {
return c.Status(fiber.StatusBadRequest).SendString("Date is required")
}
item, err := CreateScheduleItem(db, *input.Name, input.Description, *input.Duration, *input.Date, input.Tags)
if err != nil {
log.Printf("failed to create schedule item: %v", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(item)
})
app.Get("/api/schedule", func(c *fiber.Ctx) error {
fromDate, toDate, err := getDateValues(c)
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString("Invalid date query values")
}
items, err := GetScheduleItems(db, fromDate, toDate)
if err != nil {
log.Printf("failed to find schedule items: %v", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
schedule := map[string][]ScheduleItem{}
for _, date := range dateRange(fromDate, toDate) {
schedule[date.Format(time.DateOnly)] = []ScheduleItem{}
}
for _, item := range items {
schedule[item.Date.Format(time.DateOnly)] = append(schedule[item.Date.Format(time.DateOnly)], item)
}
return c.JSON(schedule)
})
app.Get("/api/items/:id<int>", func(c *fiber.Ctx) error {
id, err := c.ParamsInt("id")
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString("Invalid id")
}
item, err := GetScheduleItem(db, id)
if err != nil {
log.Printf("failed to find schedule item: %v", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
if item == nil {
return c.SendStatus(fiber.StatusNotFound)
}
return c.JSON(*item)
})
app.Delete("/api/items/:id<int>", func(c *fiber.Ctx) error {
id, err := c.ParamsInt("id")
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString("Invalid id")
}
ok, err := DeleteScheduleItem(db, id)
if err != nil {
log.Printf("failed to delete schedule item: %v", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
if !ok {
return c.SendStatus(fiber.StatusNotFound)
}
return c.SendStatus(http.StatusOK)
})
app.Get("/api/tags", func(c *fiber.Ctx) error {
tags, err := GetScheduleTags(db)
if err != nil {
log.Printf("failed to find schedule tags: %v", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(tags)
})
app.Post("/api/tags", func(c *fiber.Ctx) error {
input := new(NewScheduleTag)
if err := c.BodyParser(input); err != nil {
return err
}
if input.Name == nil || len(*input.Name) == 0 {
return c.Status(fiber.StatusBadRequest).SendString("Name is required")
}
tag, err := CreateScheduleTag(db, *input.Name)
if err != nil {
log.Printf("failed to create schedule tag: %v", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
return c.JSON(tag)
})
app.Get("/api/tags/:id<int>", func(c *fiber.Ctx) error {
id, err := c.ParamsInt("id")
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString("Invalid id")
}
tag, err := GetScheduleTag(db, id)
if err != nil {
log.Printf("failed to find schedule tag: %v", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
if tag == nil {
return c.SendStatus(fiber.StatusNotFound)
}
return c.JSON(*tag)
})
app.Delete("/api/tags/:id<int>", func(c *fiber.Ctx) error {
id, err := c.ParamsInt("id")
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString("Invalid id")
}
ok, err := DeleteScheduleTag(db, id)
if err != nil {
log.Printf("failed to delete schedule item: %v", err)
return c.SendStatus(fiber.StatusInternalServerError)
}
if !ok {
return c.SendStatus(fiber.StatusNotFound)
}
return c.SendStatus(http.StatusOK)
})
app.Use(filesystem.New(filesystem.Config{
Root: http.FS(f),
PathPrefix: "public",
}))
log.Fatal(app.Listen(":3000"))
}