feat: add calendar
parent
82c82b0b42
commit
8f207bf356
|
@ -0,0 +1,117 @@
|
|||
package calendar
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"kumoly.io/kumoly/app/auth"
|
||||
"kumoly.io/kumoly/app/errors"
|
||||
"kumoly.io/kumoly/app/history"
|
||||
"kumoly.io/kumoly/app/server"
|
||||
)
|
||||
|
||||
func ApiCalQuery(c *gin.Context) {
|
||||
id := c.Query("id")
|
||||
if id != "" {
|
||||
cal := &Calendar{}
|
||||
err := HasCalAccess(c, cal, id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
server.OK(c, cal)
|
||||
} else {
|
||||
grp := c.Query("grp")
|
||||
cals := []Calendar{}
|
||||
cl, err := auth.GetContextClaims(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var result *gorm.DB
|
||||
|
||||
if grp != "" && auth.ACHas(c, auth.ADMIN, auth.SYSTEM, grp) {
|
||||
var grp_id uint
|
||||
db.Raw("select id from groups where name = ?", grp).Scan(&grp_id)
|
||||
if grp_id == 0 {
|
||||
panic(errors.ErrorNotFound)
|
||||
}
|
||||
result = db.Find(&cals, "`group_id` = ? ", grp_id)
|
||||
} else if !auth.ACHas(c, auth.ADMIN, auth.SYSTEM) {
|
||||
result = db.
|
||||
Find(&cals, "`group_id` in (?) or group_id = 0",
|
||||
db.Table("groups").Select("id").Where("name in ?", cl.Groups))
|
||||
} else {
|
||||
result = db.Find(&cals)
|
||||
}
|
||||
|
||||
if result.Error != nil {
|
||||
panic(result.Error)
|
||||
}
|
||||
server.OK(c, cals)
|
||||
}
|
||||
}
|
||||
|
||||
func ApiCalNew(c *gin.Context) {
|
||||
cal := &Calendar{}
|
||||
if err := c.ShouldBindJSON(cal); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if cal.ID != "" {
|
||||
panic(errors.ErrorBadRequest)
|
||||
}
|
||||
if !auth.ACHas(c, auth.ADMIN, auth.SYSTEM, cal.GroupName) {
|
||||
panic(errors.ErrorForbidden)
|
||||
}
|
||||
if err := db.Create(cal).Error; err != nil {
|
||||
panic(err)
|
||||
}
|
||||
history.Send(history.Info().
|
||||
Nm("Create").
|
||||
Grp(cal.GroupName).Bd(cal).
|
||||
Iss(c.GetString(auth.GinUserKey)).
|
||||
Msg("Calendar created"))
|
||||
server.OK(c, cal)
|
||||
}
|
||||
|
||||
func ApiCalUpdate(c *gin.Context) {
|
||||
cal := &Calendar{}
|
||||
if err := c.ShouldBindJSON(cal); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if cal.ID == "" {
|
||||
panic(errors.ErrorBadRequest)
|
||||
}
|
||||
if err := HasCalAccess(c, &Calendar{}, cal.ID); err != nil {
|
||||
panic(errors.ErrorForbidden)
|
||||
}
|
||||
if err := db.Save(cal).Error; err != nil {
|
||||
panic(err)
|
||||
}
|
||||
history.Send(history.Info().
|
||||
Nm("Update").
|
||||
Grp(cal.GroupName).Bd(cal).
|
||||
Iss(c.GetString(auth.GinUserKey)).
|
||||
Msg("Calendar Updated"))
|
||||
server.OK(c, cal)
|
||||
}
|
||||
|
||||
func ApiCalDelete(c *gin.Context) {
|
||||
id := c.Query("id")
|
||||
if id == "" {
|
||||
panic(errors.ErrorBadRequest)
|
||||
}
|
||||
cal := &Calendar{}
|
||||
err := HasCalAccess(c, cal, id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = db.Delete(&Calendar{}, "id = ?", id).Error
|
||||
if err != nil {
|
||||
panic(errors.NewError(404, err))
|
||||
}
|
||||
history.Send(history.Info().
|
||||
Nm("Delete").
|
||||
Grp(cal.GroupName).Bd(cal).
|
||||
Iss(c.GetString(auth.GinUserKey)).
|
||||
Msg("Calendar Deleted"))
|
||||
server.OK(c, "ok")
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
package calendar
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"kumoly.io/kumoly/app/auth"
|
||||
"kumoly.io/kumoly/app/errors"
|
||||
"kumoly.io/kumoly/app/history"
|
||||
"kumoly.io/kumoly/app/server"
|
||||
)
|
||||
|
||||
func ApiEventQuery(c *gin.Context) {
|
||||
id := c.Query("id")
|
||||
if id != "" {
|
||||
e := &Event{}
|
||||
err := HasEventAccess(c, e, id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
server.OK(c, e)
|
||||
} else {
|
||||
grp := c.Query("grp")
|
||||
events := []Event{}
|
||||
cl, err := auth.GetContextClaims(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var result *gorm.DB
|
||||
|
||||
if grp != "" && auth.ACHas(c, auth.ADMIN, auth.SYSTEM, grp) {
|
||||
var grp_id uint
|
||||
db.Raw("select id from groups where name = ?", grp).Scan(&grp_id)
|
||||
if grp_id == 0 {
|
||||
panic(errors.ErrorNotFound)
|
||||
}
|
||||
result = db.Find(&events, "`group_id` = ? ", grp_id)
|
||||
} else if !auth.ACHas(c, auth.ADMIN, auth.SYSTEM) {
|
||||
result = db.
|
||||
Find(&events, "`group_id` in (?) or group_id = 0",
|
||||
db.Table("groups").Select("id").Where("name in ?", cl.Groups))
|
||||
} else {
|
||||
result = db.Find(&events)
|
||||
}
|
||||
|
||||
if result.Error != nil {
|
||||
panic(result.Error)
|
||||
}
|
||||
server.OK(c, events)
|
||||
}
|
||||
}
|
||||
|
||||
func ApiEventNew(c *gin.Context) {
|
||||
e := &Event{}
|
||||
if err := c.ShouldBindJSON(e); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if e.ID != "" {
|
||||
panic(errors.ErrorBadRequest)
|
||||
}
|
||||
if e.Start.IsZero() || e.End.IsZero() || e.Start.Before(e.End) {
|
||||
panic(ErrorInvalidTime)
|
||||
}
|
||||
if !auth.ACHas(c, auth.ADMIN, auth.SYSTEM, e.GroupName) {
|
||||
panic(errors.ErrorForbidden)
|
||||
}
|
||||
if err := db.Create(e).Error; err != nil {
|
||||
panic(err)
|
||||
}
|
||||
history.Send(history.Info().
|
||||
Nm("Create").
|
||||
Grp(e.GroupName).Bd(e).
|
||||
Iss(c.GetString(auth.GinUserKey)).
|
||||
Msg("Event created"))
|
||||
server.OK(c, e)
|
||||
}
|
||||
|
||||
func ApiEventUpdate(c *gin.Context) {
|
||||
e := &Event{}
|
||||
if err := c.ShouldBindJSON(e); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if e.ID == "" {
|
||||
panic(errors.ErrorBadRequest)
|
||||
}
|
||||
if err := HasEventAccess(c, &Event{}, e.ID); err != nil {
|
||||
panic(errors.ErrorForbidden)
|
||||
}
|
||||
if err := db.Save(e).Error; err != nil {
|
||||
panic(err)
|
||||
}
|
||||
history.Send(history.Info().
|
||||
Nm("Update").
|
||||
Grp(e.GroupName).Bd(e).
|
||||
Iss(c.GetString(auth.GinUserKey)).
|
||||
Msg("Event Updated"))
|
||||
server.OK(c, e)
|
||||
}
|
||||
|
||||
func ApiEventDelete(c *gin.Context) {
|
||||
id := c.Query("id")
|
||||
if id == "" {
|
||||
panic(errors.ErrorBadRequest)
|
||||
}
|
||||
e := &Event{}
|
||||
err := HasEventAccess(c, e, id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = db.Delete(&Calendar{}, "id = ?", id).Error
|
||||
if err != nil {
|
||||
panic(errors.NewError(404, err))
|
||||
}
|
||||
history.Send(history.Info().
|
||||
Nm("Delete").
|
||||
Grp(e.GroupName).Bd(e).
|
||||
Iss(c.GetString(auth.GinUserKey)).
|
||||
Msg("Event Deleted"))
|
||||
server.OK(c, "ok")
|
||||
}
|
|
@ -6,22 +6,62 @@ import (
|
|||
"github.com/rs/xid"
|
||||
"github.com/rs/zerolog"
|
||||
"gorm.io/gorm"
|
||||
"kumoly.io/kumoly/app/auth"
|
||||
"kumoly.io/kumoly/app/errors"
|
||||
)
|
||||
|
||||
var l zerolog.Logger
|
||||
|
||||
var db *gorm.DB
|
||||
|
||||
type Calendar struct {
|
||||
ID string `gorm:"primaryKey"`
|
||||
|
||||
Name string `gorm:"index:idx_cal,unique"`
|
||||
Description string
|
||||
ExtLink string
|
||||
|
||||
Timezone string
|
||||
|
||||
Events []Event
|
||||
|
||||
Group auth.Group `json:"-"`
|
||||
GroupName string `gorm:"-" json:"Group"`
|
||||
GroupID uint `gorm:"index:idx_cal,unique" json:"-"`
|
||||
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
func (c *Calendar) BeforeSave(tx *gorm.DB) (err error) {
|
||||
if c.GroupName != "" {
|
||||
var grp_id uint
|
||||
db.Raw("select id from groups where name = ?", c.GroupName).Scan(&grp_id)
|
||||
if grp_id == 0 {
|
||||
return errors.ErrorNotFound
|
||||
}
|
||||
c.GroupID = grp_id
|
||||
} else {
|
||||
c.GroupID = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Calendar) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
if c.ID == "" {
|
||||
c.ID = xid.New().String()
|
||||
}
|
||||
if c.Timezone == "" {
|
||||
c.Timezone = "Asia/Taipei"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Calendar) AfterFind(tx *gorm.DB) (err error) {
|
||||
if c.GroupID != 0 {
|
||||
var name string
|
||||
db.Raw("select name from groups where id = ?", c.GroupID).Scan(&name)
|
||||
c.GroupName = name
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package calendar
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestOffsetStr(t *testing.T) {
|
||||
fmt.Println(offsetStr(-28800))
|
||||
fmt.Println(offsetStr(28800))
|
||||
}
|
||||
|
||||
func TestT(t *testing.T) {
|
||||
fmt.Println(icstime(time.Now()))
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package calendar
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"kumoly.io/kumoly/app/errors"
|
||||
)
|
||||
|
||||
var ErrorInvalidTime = errors.Error{
|
||||
Code: http.StatusBadRequest,
|
||||
ID: "ErrorInvalidTime",
|
||||
Message: "time is not valid",
|
||||
}
|
|
@ -5,24 +5,62 @@ import (
|
|||
|
||||
"github.com/rs/xid"
|
||||
"gorm.io/gorm"
|
||||
"kumoly.io/kumoly/app/auth"
|
||||
"kumoly.io/kumoly/app/errors"
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
ID string `gorm:"primaryKey"`
|
||||
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Name string `gorm:"not null"`
|
||||
Hosts string
|
||||
Description string
|
||||
Location string
|
||||
|
||||
// ntu
|
||||
Class string
|
||||
CourseID string
|
||||
Semester string
|
||||
|
||||
CalendarID string
|
||||
EventGroupID string
|
||||
|
||||
Group auth.Group `json:"-"`
|
||||
GroupName string `gorm:"-" json:"Group"`
|
||||
GroupID uint `json:"-"`
|
||||
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
func (e *Event) BeforeSave(tx *gorm.DB) (err error) {
|
||||
if e.GroupName != "" {
|
||||
var grp_id uint
|
||||
db.Raw("select id from groups where name = ?", e.GroupName).Scan(&grp_id)
|
||||
if grp_id == 0 {
|
||||
return errors.ErrorNotFound
|
||||
}
|
||||
e.GroupID = grp_id
|
||||
} else {
|
||||
e.GroupID = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e *Event) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
if e.ID == "" {
|
||||
e.ID = xid.New().String()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (e *Event) AfterFind(tx *gorm.DB) (err error) {
|
||||
if e.GroupID != 0 {
|
||||
var name string
|
||||
db.Raw("select name from groups where id = ?", e.GroupID).Scan(&name)
|
||||
e.GroupName = name
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -5,20 +5,51 @@ import (
|
|||
|
||||
"github.com/rs/xid"
|
||||
"gorm.io/gorm"
|
||||
"kumoly.io/kumoly/app/auth"
|
||||
"kumoly.io/kumoly/app/errors"
|
||||
)
|
||||
|
||||
type EventGroup struct {
|
||||
ID string `gorm:"primaryKey"`
|
||||
|
||||
Name string
|
||||
Rrule string
|
||||
|
||||
Events []Event
|
||||
|
||||
Group auth.Group `json:"-"`
|
||||
GroupName string `gorm:"-" json:"Group"`
|
||||
GroupID uint `json:"-"`
|
||||
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
func (eg *EventGroup) BeforeSave(tx *gorm.DB) (err error) {
|
||||
if eg.GroupName != "" {
|
||||
var grp_id uint
|
||||
db.Raw("select id from groups where name = ?", eg.GroupName).Scan(&grp_id)
|
||||
if grp_id == 0 {
|
||||
return errors.ErrorNotFound
|
||||
}
|
||||
eg.GroupID = grp_id
|
||||
} else {
|
||||
eg.GroupID = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
func (eg *EventGroup) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
if eg.ID == "" {
|
||||
eg.ID = xid.New().String()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (eg *EventGroup) AfterFind(tx *gorm.DB) (err error) {
|
||||
if eg.GroupID != 0 {
|
||||
var name string
|
||||
db.Raw("select name from groups where id = ?", eg.GroupID).Scan(&name)
|
||||
eg.GroupName = name
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package calendar
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"kumoly.io/kumoly/app/store"
|
||||
)
|
||||
|
||||
const ics_base = `BEGIN:VCALENDAR
|
||||
PRODID:-//Kumoly//Kumoly App v0.0.1//EN
|
||||
VERSION:2.0
|
||||
CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
X-WR-CALNAME:{{.Name}}
|
||||
X-WR-TIMEZONE:Asia/Taipei
|
||||
X-WR-CALDESC:{{.Name}}
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:{{.Timezone}}
|
||||
X-LIC-LOCATION:{{.Timezone}}
|
||||
BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0800
|
||||
TZOFFSETTO:+0800
|
||||
TZNAME:CST
|
||||
DTSTART:19700101T000000
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
{{with .Events}}{{ if gt (len .) 0 }}{{ range $event := . }}
|
||||
BEGIN:VEVENT
|
||||
DTSTART:{{$event.Start|icstime}}
|
||||
DTEND:{{$event.End|icstime}}
|
||||
DTSTAMP:{{now|icstime}}
|
||||
UID:{{$event.ID}}
|
||||
CREATED:{{$event.CreatedAt|icstime}}
|
||||
DESCRIPTION:{{$event.ID}}
|
||||
LAST-MODIFIED:{{$event.UpdatedAt|icstime}}
|
||||
LOCATION:{{$event.Location}}
|
||||
SEQUENCE:0
|
||||
STATUS:CONFIRMED
|
||||
SUMMARY:{{$event.Name}}
|
||||
TRANSP:TRANSPARENT
|
||||
END:VEVENT
|
||||
{{end}}{{end}}{{end}}
|
||||
END:VCALENDAR`
|
||||
|
||||
var tmpl *template.Template
|
||||
|
||||
func init() {
|
||||
tmpl = template.Must(template.New("").Funcs(template.FuncMap{
|
||||
"icstime": icstime,
|
||||
"now": func() time.Time { return time.Now() },
|
||||
}).Parse(ics_base))
|
||||
}
|
||||
|
||||
func ICS(cal_id string, from time.Time, to time.Time) (string, error) {
|
||||
cal := &Calendar{}
|
||||
store.DB.Preload("Events", "start > ? and end < ?", from, to).
|
||||
First(cal, "id = ?", cal_id)
|
||||
buf := &bytes.Buffer{}
|
||||
if err := tmpl.Execute(buf, cal); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func icstime(t time.Time) string {
|
||||
return t.UTC().Format("20060102T150405Z")
|
||||
}
|
||||
|
||||
func offsetStr(offset int) string {
|
||||
return fmt.Sprintf("%+05d", offset/36)
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package calendar
|
||||
|
||||
import (
|
||||
"kumoly.io/kumoly/app/server"
|
||||
"kumoly.io/kumoly/app/store"
|
||||
"kumoly.io/kumoly/app/util"
|
||||
)
|
||||
|
@ -15,6 +16,7 @@ func (srv Service) Del() {}
|
|||
func (srv Service) Health() error { return nil }
|
||||
|
||||
func (srv Service) Init() error {
|
||||
db = store.DB
|
||||
|
||||
l = util.Klog.With().Str("mod", "auth").Logger()
|
||||
l.Debug().Msg("Migrating database for calendar.Service ...")
|
||||
|
@ -25,4 +27,18 @@ func (srv Service) Init() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (srv Service) Load() error { return nil }
|
||||
func (srv Service) Load() error {
|
||||
calApi := server.API.Group("cal")
|
||||
calApi.GET("", ApiCalQuery)
|
||||
calApi.POST("", ApiCalNew)
|
||||
calApi.PUT("", ApiCalUpdate)
|
||||
calApi.DELETE("", ApiCalDelete)
|
||||
|
||||
eventApi := server.API.Group("event")
|
||||
eventApi.GET("", ApiEventQuery)
|
||||
eventApi.POST("", ApiEventNew)
|
||||
eventApi.PUT("", ApiEventUpdate)
|
||||
eventApi.DELETE("", ApiEventDelete)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package calendar
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"kumoly.io/kumoly/app/auth"
|
||||
"kumoly.io/kumoly/app/errors"
|
||||
)
|
||||
|
||||
func HasCalAccess(c *gin.Context, cal *Calendar, cid string) error {
|
||||
err := db.First(cal, "id = ?", cid).Error
|
||||
if err != nil {
|
||||
return errors.NewError(404, err)
|
||||
}
|
||||
if cal.GroupName == "" {
|
||||
return nil
|
||||
}
|
||||
if !auth.ACHas(c, auth.ADMIN, auth.SYSTEM, cal.GroupName) {
|
||||
return errors.ErrorForbidden
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func HasEventAccess(c *gin.Context, e *Event, cid string) error {
|
||||
err := db.First(e, "id = ?", cid).Error
|
||||
if err != nil {
|
||||
return errors.NewError(404, err)
|
||||
}
|
||||
if e.GroupName == "" {
|
||||
return nil
|
||||
}
|
||||
if !auth.ACHas(c, auth.ADMIN, auth.SYSTEM, e.GroupName) {
|
||||
return errors.ErrorForbidden
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
"kumoly.io/kumoly/app/attribute"
|
||||
"kumoly.io/kumoly/app/auth"
|
||||
"kumoly.io/kumoly/app/calendar"
|
||||
"kumoly.io/kumoly/app/control"
|
||||
"kumoly.io/kumoly/app/email"
|
||||
"kumoly.io/kumoly/app/history"
|
||||
|
@ -30,6 +31,7 @@ func Default() *system.System {
|
|||
&history.Service{},
|
||||
&email.Service{},
|
||||
&control.Service{},
|
||||
&calendar.Service{},
|
||||
)
|
||||
return sys
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue