1
0

CalDAV support (#15)

This commit is contained in:
konrad
2018-11-03 15:05:45 +00:00
committed by Gitea
parent 31a4a1dd00
commit d03fca801b
59 changed files with 2192 additions and 998 deletions

95
pkg/caldav/caldav.go Normal file
View 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
}

View File

@ -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
}

View 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))
}

View File

@ -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
View 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]
}