blob: fa1cc9da9d84fbe9814a8a033695037e92c417a1 [file]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package handlers
import (
"fmt"
"infra/appengine/rotang"
"net/http"
"strconv"
"strings"
"time"
"go.chromium.org/luci/common/logging"
"go.chromium.org/luci/server/auth"
"go.chromium.org/luci/server/router"
"go.chromium.org/luci/server/templates"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func cleanOwners(email, ownStr string) ([]string, error) {
owners := strings.Split(ownStr, ",")
ownerFound := false
for i := range owners {
owners[i] = strings.Join(strings.Fields(owners[i]), "")
if owners[i] == email {
ownerFound = true
}
}
if !ownerFound {
return nil, status.Errorf(codes.NotFound, "current user not in owners")
}
return owners, nil
}
func fillIntegers(ctx *router.Context, cfg *rotang.Config) error {
var err error
if cfg.Email.DaysBeforeNotify, err = strconv.Atoi(ctx.Request.FormValue("EmailNotify")); err != nil {
return err
}
if cfg.Expiration, err = strconv.Atoi(ctx.Request.FormValue("Expiration")); err != nil {
return err
}
if cfg.DaysToSchedule, err = strconv.Atoi(ctx.Request.FormValue("ShiftsToSchedule")); err != nil {
return err
}
if cfg.Shifts.Length, err = strconv.Atoi(ctx.Request.FormValue("shiftLength")); err != nil {
return err
}
if cfg.Shifts.Skip, err = strconv.Atoi(ctx.Request.FormValue("shiftSkip")); err != nil {
return err
}
if cfg.Shifts.ShiftMembers, err = strconv.Atoi(ctx.Request.FormValue("shiftMembers")); err != nil {
return err
}
if cfg.Shifts.StartTime, err = time.Parse("15:04", ctx.Request.FormValue("shiftStart")); err != nil {
return err
}
return nil
}
func fillMembers(ctx *router.Context, memberStore rotang.MemberStorer) ([]rotang.ShiftMember, error) {
if len(ctx.Request.Form["addEmail"]) != len(ctx.Request.Form["addTZ"]) &&
len(ctx.Request.Form["addEmail"]) != len(ctx.Request.Form["addName"]) {
return nil, status.Errorf(codes.InvalidArgument, "Email, TimeZone and Name must all have a value")
}
var members []rotang.ShiftMember
for i, v := range ctx.Request.Form["addEmail"] {
if v == "" {
logging.Warningf(ctx.Context, "skipping user with empty email, name: %q", ctx.Request.Form["addName"][i])
continue
}
loc, err := time.LoadLocation(ctx.Request.Form["addTZ"][i])
if err != nil {
loc = time.UTC
}
member := rotang.Member{
Email: v,
Name: ctx.Request.Form["addName"][i],
TZ: *loc,
}
if err := memberStore.CreateMember(ctx.Context, &member); err != nil && status.Code(err) != codes.AlreadyExists {
return nil, err
}
members = append(members, rotang.ShiftMember{
Email: v,
})
}
for _, v := range ctx.Request.Form["members"] {
m, err := memberStore.Member(ctx.Context, v)
if err != nil {
return nil, err
}
members = append(members, rotang.ShiftMember{
Email: m.Email,
})
}
return members, nil
}
func fillShifts(ctx *router.Context) ([]rotang.Shift, error) {
var shifts []rotang.Shift
for i, v := range ctx.Request.Form["addShiftName"] {
h, err := strconv.Atoi(ctx.Request.Form["addShiftDuration"][i])
if err != nil {
return nil, err
}
shifts = append(shifts, rotang.Shift{
Name: v,
Duration: time.Duration(h) * time.Hour,
})
}
return shifts, nil
}
// HandleCreateRota creates a new rotation.
func (h *State) HandleCreateRota(ctx *router.Context) {
if err := ctx.Context.Err(); err != nil {
http.Error(ctx.Writer, err.Error(), http.StatusInternalServerError)
return
}
if ctx.Request.Method == "GET" {
ms, err := h.memberStore(ctx.Context).AllMembers(ctx.Context)
if err != nil {
http.Error(ctx.Writer, err.Error(), http.StatusInternalServerError)
return
}
usr := auth.CurrentUser(ctx.Context)
if usr == nil {
http.Error(ctx.Writer, "login required", http.StatusForbidden)
return
}
templates.MustRender(ctx.Context, ctx.Writer, "pages/createrota.html",
templates.Args{"Members": ms, "User": usr})
return
}
if ctx.Request.Method != "POST" {
http.Error(ctx.Writer, "HandleCreateRota handles GET and POST requests only", http.StatusBadRequest)
return
}
if err := ctx.Request.ParseForm(); err != nil {
http.Error(ctx.Writer, err.Error(), http.StatusBadRequest)
return
}
members, err := fillMembers(ctx, h.memberStore(ctx.Context))
if err != nil {
http.Error(ctx.Writer, err.Error(), http.StatusBadRequest)
return
}
shifts, err := fillShifts(ctx)
if err != nil {
http.Error(ctx.Writer, err.Error(), http.StatusBadRequest)
return
}
usr := auth.CurrentUser(ctx.Context)
if usr == nil {
http.Error(ctx.Writer, "Login required", http.StatusForbidden)
return
}
owners, err := cleanOwners(usr.Email, ctx.Request.FormValue("Owners"))
if err != nil {
http.Error(ctx.Writer, err.Error(), http.StatusBadRequest)
return
}
cfg := rotang.Configuration{
Config: rotang.Config{
Name: ctx.Request.FormValue("Name"),
Description: ctx.Request.FormValue("Description"),
Calendar: ctx.Request.FormValue("Calendar"),
Owners: owners,
Email: rotang.Email{
Subject: ctx.Request.FormValue("EmailSubjectTemplate"),
Body: ctx.Request.FormValue("EmailBodyTemplate"),
},
Shifts: rotang.ShiftConfig{
Shifts: shifts,
Generator: ctx.Request.FormValue("generator"),
},
},
Members: members,
}
if err := fillIntegers(ctx, &cfg.Config); err != nil {
http.Error(ctx.Writer, err.Error(), http.StatusBadRequest)
return
}
if err := h.configStore(ctx.Context).CreateRotaConfig(ctx.Context, &cfg); err != nil {
http.Error(ctx.Writer, err.Error(), http.StatusBadRequest)
return
}
fmt.Fprintf(ctx.Writer, "Rotation %q added!", cfg.Config.Name)
}