184 lines
5.5 KiB
Go
184 lines
5.5 KiB
Go
package routes
|
|
|
|
import (
|
|
"bytes"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/emersion/go-ical"
|
|
"github.com/fmartingr/notion2ical/internal/config"
|
|
notionClient "github.com/fmartingr/notion2ical/internal/notion"
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/gofiber/fiber/v2/middleware/cache"
|
|
"github.com/gofiber/fiber/v2/middleware/limiter"
|
|
"github.com/gofiber/fiber/v2/utils"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type CalendarRoutes struct {
|
|
logger *zap.Logger
|
|
router *fiber.App
|
|
notion *notionClient.NotionClient
|
|
publicHostname string
|
|
|
|
limiterHandler fiber.Handler
|
|
cacheHandler fiber.Handler
|
|
}
|
|
|
|
func (r *CalendarRoutes) Setup() *CalendarRoutes {
|
|
r.router.
|
|
Use(r.limiterHandler).
|
|
Post("/wizard", r.wizardHandler).
|
|
Post("/download", r.downloadHandler).
|
|
Get("/", r.indexHandler).
|
|
Use(r.cacheHandler).
|
|
Get("/calendar.ics", r.calendarIcsHandler)
|
|
return r
|
|
}
|
|
|
|
func (r *CalendarRoutes) Router() *fiber.App {
|
|
return r.router
|
|
}
|
|
|
|
func (r *CalendarRoutes) indexHandler(c *fiber.Ctx) error {
|
|
return c.Render("index", fiber.Map{
|
|
"error": c.Query("error"),
|
|
})
|
|
}
|
|
|
|
func (r *CalendarRoutes) wizardHandler(c *fiber.Ctx) error {
|
|
var payload wizardPayload
|
|
if err := c.BodyParser(&payload); err != nil {
|
|
r.logger.Error("error parsing query", zap.String("query", c.Context().QueryArgs().String()))
|
|
return err
|
|
}
|
|
|
|
if err := payload.Validate(); err != nil {
|
|
return c.Redirect("/?error="+err.Error()+"#how-it-works", fiber.StatusTemporaryRedirect)
|
|
}
|
|
|
|
info, err := r.notion.GetDatabaseInfo(c.Context(), payload.GetDatabaseID())
|
|
if err != nil {
|
|
return c.Redirect("/?error=Error getting database information, have you set up the integration properly?#how-it-works", fiber.StatusTemporaryRedirect)
|
|
}
|
|
|
|
if len(info.DateProperties) == 0 {
|
|
return c.Redirect("/?error=Your database does not have any datetime properties, at least one is required#how-it-works", fiber.StatusTemporaryRedirect)
|
|
}
|
|
|
|
if len(info.TextProperties) == 0 {
|
|
return c.Redirect("/?error=Your database does not have any text properties, at least one is required#how-it-works", fiber.StatusTemporaryRedirect)
|
|
}
|
|
|
|
return c.Render("wizard", fiber.Map{
|
|
"textProperties": info.TextProperties,
|
|
"datetimeProperties": info.DateProperties,
|
|
"databaseName": info.Name,
|
|
"databaseID": info.ID,
|
|
})
|
|
}
|
|
|
|
func (r *CalendarRoutes) downloadHandler(c *fiber.Ctx) error {
|
|
var payload calendarDownloadPayload
|
|
if err := c.BodyParser(&payload); err != nil {
|
|
r.logger.Error("error parsing query", zap.String("query", c.Context().QueryArgs().String()))
|
|
return err
|
|
}
|
|
|
|
if err := payload.Validate(); err != nil {
|
|
return c.Redirect("/wizard?error="+err.Error(), fiber.StatusTemporaryRedirect)
|
|
}
|
|
|
|
return c.Render("download", fiber.Map{
|
|
"calendarSubscriptionUrl": r.publicHostname + "/calendar.ics?" + string(c.Request().Body()),
|
|
"calendarICSUrl": r.publicHostname + "/calendar.ics?" + string(c.Request().Body()),
|
|
})
|
|
}
|
|
|
|
func (r *CalendarRoutes) calendarIcsHandler(c *fiber.Ctx) error {
|
|
var payload calendarDownloadPayload
|
|
if err := c.QueryParser(&payload); err != nil {
|
|
r.logger.Error("error parsing query", zap.String("query", c.Context().QueryArgs().String()))
|
|
return err
|
|
}
|
|
|
|
if err := payload.Validate(); err != nil {
|
|
return c.Redirect("/?error="+err.Error(), fiber.StatusTemporaryRedirect)
|
|
}
|
|
|
|
results, err := r.notion.GetDatabaseItems(c.Context(), payload.DatabaseID, payload.NameProperty, payload.DateProperty)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cal := ical.NewCalendar()
|
|
cal.Props.SetText(ical.PropVersion, "2.0")
|
|
cal.Props.SetText(ical.PropProductID, "-//notion2ical//NONSGML PDA Calendar Version 1.0//EN")
|
|
|
|
uri, err := url.Parse(r.publicHostname + c.OriginalURL())
|
|
if err != nil {
|
|
r.logger.Error("error formatting calendar url", zap.Error(err))
|
|
return err
|
|
}
|
|
cal.Props.SetURI(ical.PropURL, uri)
|
|
|
|
for _, item := range results {
|
|
event := ical.NewEvent()
|
|
event.Props.SetText(ical.PropUID, item.ID)
|
|
|
|
if payload.AllDayEvents {
|
|
event.Props.SetDate(ical.PropDateTimeStamp, item.DateStart)
|
|
event.Props.SetDate(ical.PropDateTimeStart, item.DateEnd)
|
|
} else {
|
|
event.Props.SetDateTime(ical.PropDateTimeStamp, item.DateStart)
|
|
event.Props.SetDateTime(ical.PropDateTimeStart, item.DateEnd)
|
|
}
|
|
|
|
event.Props.SetText(ical.PropSummary, item.Name)
|
|
|
|
cal.Children = append(cal.Children, event.Component)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := ical.NewEncoder(&buf).Encode(cal); err != nil {
|
|
r.logger.Error("error encoding calendar", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
c.Set("Content-Type", "text/calendar")
|
|
return c.Send(buf.Bytes())
|
|
}
|
|
|
|
func NewCalendarRoutes(logger *zap.Logger, cfg *config.Config) *CalendarRoutes {
|
|
routes := CalendarRoutes{
|
|
logger: logger,
|
|
notion: cfg.Notion.Client,
|
|
router: fiber.New(),
|
|
publicHostname: cfg.Http.PublicHostname,
|
|
limiterHandler: limiter.New(limiter.Config{
|
|
Max: cfg.Routes.Calendar.LimiterMaxRequest,
|
|
Expiration: cfg.Routes.Calendar.LimiterExpiration,
|
|
}),
|
|
cacheHandler: cache.New(cache.Config{
|
|
Expiration: cfg.Routes.Calendar.CacheExpiration,
|
|
CacheControl: cfg.Routes.Calendar.CacheControl,
|
|
KeyGenerator: func(c *fiber.Ctx) string {
|
|
args := c.Context().QueryArgs()
|
|
params := []string{
|
|
"database_id",
|
|
string(args.Peek("database_id")),
|
|
"all_day_events",
|
|
string(args.Peek("all_day_events")),
|
|
"date_property",
|
|
string(args.Peek("date_property")),
|
|
"name_propety",
|
|
string(args.Peek("name_propety")),
|
|
}
|
|
return utils.CopyString(c.Path() + strings.Join(params, "-"))
|
|
},
|
|
}),
|
|
}
|
|
routes.Setup()
|
|
return &routes
|
|
}
|