CalDAV support (#15)
This commit is contained in:
95
pkg/caldav/caldav.go
Normal file
95
pkg/caldav/caldav.go
Normal file
@ -0,0 +1,95 @@
|
||||
package caldav
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Event holds a single caldav event
|
||||
type Event struct {
|
||||
Summary string
|
||||
Description string
|
||||
UID string
|
||||
Alarms []Alarm
|
||||
|
||||
TimestampUnix int64
|
||||
StartUnix int64
|
||||
EndUnix int64
|
||||
}
|
||||
|
||||
// Alarm holds infos about an alarm from a caldav event
|
||||
type Alarm struct {
|
||||
TimeUnix int64
|
||||
Description string
|
||||
}
|
||||
|
||||
// Config is the caldav calendar config
|
||||
type Config struct {
|
||||
Name string
|
||||
ProdID string
|
||||
}
|
||||
|
||||
// ParseEvents parses an array of caldav events and gives them back as string
|
||||
func ParseEvents(config *Config, events []*Event) (caldavevents string) {
|
||||
caldavevents += `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:` + config.Name + `
|
||||
PRODID:-//` + config.ProdID + `//EN`
|
||||
|
||||
for _, e := range events {
|
||||
|
||||
if e.UID == "" {
|
||||
e.UID = makeCalDavTimeFromUnixTime(e.TimestampUnix) + utils.Sha256(e.Summary)
|
||||
}
|
||||
|
||||
caldavevents += `
|
||||
BEGIN:VEVENT
|
||||
UID:` + e.UID + `
|
||||
SUMMARY:` + e.Summary + `
|
||||
DESCRIPTION:` + e.Description + `
|
||||
DTSTAMP:` + makeCalDavTimeFromUnixTime(e.TimestampUnix) + `
|
||||
DTSTART:` + makeCalDavTimeFromUnixTime(e.StartUnix) + `
|
||||
DTEND:` + makeCalDavTimeFromUnixTime(e.EndUnix)
|
||||
|
||||
for _, a := range e.Alarms {
|
||||
if a.Description == "" {
|
||||
a.Description = e.Summary
|
||||
}
|
||||
|
||||
caldavevents += `
|
||||
BEGIN:VALARM
|
||||
TRIGGER:` + calcAlarmDateFromReminder(e.StartUnix, a.TimeUnix) + `
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:` + a.Description + `
|
||||
END:VALARM`
|
||||
}
|
||||
caldavevents += `
|
||||
END:VEVENT`
|
||||
}
|
||||
|
||||
caldavevents += `
|
||||
END:VCALENDAR` // Need a line break
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func makeCalDavTimeFromUnixTime(unixtime int64) (caldavtime string) {
|
||||
tm := time.Unix(unixtime, 0)
|
||||
return tm.Format("20060102T150405")
|
||||
}
|
||||
|
||||
func calcAlarmDateFromReminder(eventStartUnix, reminderUnix int64) (alarmTime string) {
|
||||
if eventStartUnix > reminderUnix {
|
||||
alarmTime += `-`
|
||||
}
|
||||
alarmTime += `PT`
|
||||
diff := eventStartUnix - reminderUnix
|
||||
if diff < 0 { // Make it positive
|
||||
diff = diff * -1
|
||||
}
|
||||
alarmTime += strconv.Itoa(int(diff/60)) + "M"
|
||||
return
|
||||
}
|
@ -161,7 +161,11 @@ type ListTasksDummy struct {
|
||||
|
||||
// ReadAll gets all tasks for a user
|
||||
func (lt *ListTasksDummy) ReadAll(u *User) (interface{}, error) {
|
||||
return GetTasksByUser(u)
|
||||
}
|
||||
|
||||
//GetTasksByUser returns all tasks for a user
|
||||
func GetTasksByUser(u *User) (tasks []*ListTask, err error) {
|
||||
// Get all lists
|
||||
lists, err := getRawListsForUser(u)
|
||||
if err != nil {
|
||||
@ -175,7 +179,6 @@ func (lt *ListTasksDummy) ReadAll(u *User) (interface{}, error) {
|
||||
}
|
||||
|
||||
// Then return all tasks for that lists
|
||||
var tasks []*ListTask
|
||||
if err := x.In("list_id", listIDs).Find(&tasks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
74
pkg/routes/api/v1/caldav.go
Normal file
74
pkg/routes/api/v1/caldav.go
Normal file
@ -0,0 +1,74 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/caldav"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/routes/crud"
|
||||
"github.com/labstack/echo"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Caldav returns a caldav-readable format with all tasks
|
||||
func Caldav(c echo.Context) error {
|
||||
|
||||
// swagger:operation GET /tasks/caldav list caldavTasks
|
||||
// ---
|
||||
// summary: Get all tasks as caldav
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Message"
|
||||
// "400":
|
||||
// "$ref": "#/responses/Message"
|
||||
// "500":
|
||||
// "$ref": "#/responses/Message"
|
||||
|
||||
// Request basic auth
|
||||
user, pass, ok := c.Request().BasicAuth()
|
||||
|
||||
// Check credentials
|
||||
creds := &models.UserLogin{
|
||||
Username: user,
|
||||
Password: pass,
|
||||
}
|
||||
u, err := models.CheckUserCredentials(creds)
|
||||
|
||||
if !ok || err != nil {
|
||||
c.Response().Header().Set("WWW-Authenticate", `Basic realm="Vikunja cal"`)
|
||||
return c.String(http.StatusUnauthorized, "Unauthorized.")
|
||||
}
|
||||
|
||||
// Get all tasks for that user
|
||||
tasks, err := models.GetTasksByUser(&u)
|
||||
if err != nil {
|
||||
return crud.HandleHTTPError(err)
|
||||
}
|
||||
|
||||
hour := int64(time.Hour.Seconds())
|
||||
var caldavTasks []*caldav.Event
|
||||
for _, t := range tasks {
|
||||
if t.DueDateUnix != 0 {
|
||||
event := &caldav.Event{
|
||||
Summary: t.Text,
|
||||
Description: t.Description,
|
||||
UID: "",
|
||||
TimestampUnix: t.Updated,
|
||||
StartUnix: t.DueDateUnix,
|
||||
EndUnix: t.DueDateUnix + hour,
|
||||
}
|
||||
|
||||
if t.ReminderUnix != 0 {
|
||||
event.Alarms = append(event.Alarms, caldav.Alarm{TimeUnix: t.ReminderUnix})
|
||||
}
|
||||
|
||||
caldavTasks = append(caldavTasks, event)
|
||||
}
|
||||
}
|
||||
|
||||
caldavConfig := &caldav.Config{
|
||||
Name: "Vikunja Calendar for " + u.Username,
|
||||
ProdID: "Vikunja Todo App",
|
||||
}
|
||||
|
||||
return c.String(http.StatusOK, caldav.ParseEvents(caldavConfig, caldavTasks))
|
||||
}
|
@ -41,6 +41,8 @@ import (
|
||||
func NewEcho() *echo.Echo {
|
||||
e := echo.New()
|
||||
|
||||
e.HideBanner = true
|
||||
|
||||
// Logger
|
||||
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
||||
Format: "${time_rfc3339_nano}: ${remote_ip} ${method} ${status} ${uri} ${latency_human} - ${user_agent}\n",
|
||||
@ -69,6 +71,9 @@ func RegisterRoutes(e *echo.Echo) {
|
||||
a.POST("/user/password/reset", apiv1.UserResetPassword)
|
||||
a.POST("/user/confirm", apiv1.UserConfirmEmail)
|
||||
|
||||
// Caldav, with auth
|
||||
a.GET("/tasks/caldav", apiv1.Caldav)
|
||||
|
||||
// ===== Routes with Authetification =====
|
||||
// Authetification
|
||||
a.Use(middleware.JWT([]byte(viper.GetString("service.JWTSecret"))))
|
||||
|
11
pkg/utils/sha256.go
Normal file
11
pkg/utils/sha256.go
Normal file
@ -0,0 +1,11 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Sha256 calculates a sha256 hash from a string
|
||||
func Sha256(cleartext string) string {
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(cleartext)))[:45]
|
||||
}
|
Reference in New Issue
Block a user