291 lines
6.8 KiB
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"))
|
|
}
|