package schema import ( "fmt" "net/url" "sort" "strings" "time" "github.com/Luzifer/go_helpers/str" "github.com/cnf/structhash" couch "github.com/lancecarlson/couchgo" ) const ( TagIll = "ill" TagVacation = "vacation" TagWeekend = "weekend" TagHoliday = "holiday" TagEvent = "event" TagHomeoffice = "homeoffice" TagBreak = "break" TagAutotrack = "autotrack" TagOnCall = "on-call" ) const schemaVersion = 1 func evalTags(tags []string, tag string) []string { rawTag := strings.TrimLeft(tag, "+-") out := tags switch tag[0] { default: fallthrough case '+': if !str.StringInSlice(rawTag, tags) { out = append(tags, rawTag) } case '-': if str.StringInSlice(rawTag, tags) { out = []string{} for _, t := range tags { if t != rawTag { out = append(out, t) } } } } sort.Strings(out) return out } type Day struct { DayID string `json:"_id"` Revision string `json:"_rev,omitempty"` Times []*Time `json:"times"` Tags []string `json:"tags,omitempty"` // deprecated tags, have auto-migration IsIll bool `json:"is_ill,omitempty"` IsVacation bool `json:"is_vacation,omitempty"` IsWeekend bool `json:"is_weekend,omitempty"` IsHoliday bool `json:"is_holiday,omitempty"` IsEvent bool `json:"is_event,omitempty"` Homeoffice bool `json:"homeoffice,omitempty"` initialHash string `hash:"-"` } func (d *Day) Tag(tag string) { d.Tags = evalTags(d.Tags, tag) } func (d *Day) migrate() { if d.IsIll { d.Tag(TagIll) } if d.IsVacation { d.Tag(TagVacation) } if d.IsWeekend { d.Tag(TagWeekend) } if d.IsHoliday { d.Tag(TagHoliday) } if d.IsEvent { d.Tag(TagEvent) } if d.Homeoffice { d.Tag(TagHomeoffice) } d.IsIll = false d.IsEvent = false d.IsVacation = false d.IsWeekend = false d.IsHoliday = false d.Homeoffice = false for _, t := range d.Times { t.migrate() } } func (d *Day) validate() error { for _, t := range d.Times { if err := t.validate(); err != nil { return err } } return nil } func LoadDay(db *couch.Client, date time.Time, mayCreate bool) (*Day, error) { id := date.Format("2006-01-02") doc := &Day{} if err := db.Get(id, doc); err != nil { if strings.Contains(err.Error(), "not_found") && mayCreate { doc = &Day{DayID: id, Times: []*Time{}} } else { return nil, err } } doc.migrate() doc.initialHash = fmt.Sprintf("%x", structhash.Sha1(doc, schemaVersion)) return doc, nil } func (d *Day) Save(db *couch.Client) error { if err := d.validate(); err != nil { return err } if !d.hasChanged() { return nil } res, err := db.Save(d) if err != nil { return err } d.Revision = res.Rev return nil } func (d *Day) hasChanged() bool { return d.initialHash != fmt.Sprintf("%x", structhash.Sha1(d, schemaVersion)) } type Time struct { ID string `json:"id"` Start string `json:"start"` End string `json:"end"` Tags []string `json:"tags,omitempty"` // deprecated tags, have auto-migration IsBreak bool `json:"is_break,omitempty"` IsAutotrack bool `json:"is_autotrack,omitempty"` IsOnCall bool `json:"is_on_call,omitempty"` } func (w *Time) Tag(tag string) { w.Tags = evalTags(w.Tags, tag) } func (w *Time) migrate() { if w.IsBreak { w.Tag(TagBreak) } if w.IsAutotrack { w.Tag(TagAutotrack) } if w.IsOnCall { w.Tag(TagOnCall) } w.IsBreak = false w.IsAutotrack = false w.IsOnCall = false } func (t *Time) validate() error { var err error if t.Start, err = t.sanitizeTime(t.Start); err != nil { return fmt.Errorf("Time %.7s has invalid start date: %s", t.ID, err) } if t.End, err = t.sanitizeTime(t.End); err != nil { return fmt.Errorf("Time %.7s has invalid end date: %s", t.ID, err) } return nil } func (t *Time) sanitizeTime(in string) (string, error) { if in == "now" { return time.Now().Format("15:04:05"), nil } if t, err := time.Parse("15:04", in); err == nil { return t.Format("15:04:05"), nil } if _, err := time.Parse("15:04:05", in); err == nil { return in, nil } return "", fmt.Errorf("Date does not comply format 15:04 or 15:04:05") } type Overtime struct { Value float64 `json:"value"` } func GetOvertime(db *couch.Client, day time.Time) (Overtime, error) { var opts *url.Values if !day.IsZero() { opts = &url.Values{} opts.Set("reduce", "false") opts.Set("startkey", fmt.Sprintf("\"%s\"", day.Format("2006-01-02"))) opts.Set("endkey", fmt.Sprintf("\"%s\"", day.Format("2006-01-02"))) } mdr, err := db.View("analysis", "overtime", opts, nil) if err != nil { return Overtime{}, err } if len(mdr.Rows) == 0 { return Overtime{}, fmt.Errorf("Did not find any results in view") } result := Overtime{} return result, couch.Remarshal(mdr.Rows[0], &result) }