func classAndUser(w http.ResponseWriter, r *http.Request) (*account.Account, *classes.Class, *webapp.Error) { c := appengine.NewContext(r) u := user.Current(c) if u == nil { return nil, nil, badRequest(w, "Must be logged in.") } a, err := account.ForUser(c, u) if err != nil { return nil, nil, badRequest(w, "Must be registered.") } id, err := strconv.ParseInt(r.FormValue("class"), 10, 64) if err != nil { return nil, nil, invalidData(w, "Couldn't parse class ID") } class, err := classes.ClassWithID(c, id) switch err { case nil: break case classes.ErrClassNotFound: return nil, nil, invalidData(w, "No such class") default: return nil, nil, webapp.InternalError(fmt.Errorf("failed to look up class %d: %s", id, err)) } return a, class, nil }
func roster(w http.ResponseWriter, r *http.Request) *webapp.Error { id, err := strconv.ParseInt(r.FormValue("class"), 10, 64) if err != nil { return invalidData(w, "Invalid class ID") } c := appengine.NewContext(r) class, err := classes.ClassWithID(c, id) if err != nil { return invalidData(w, "No such class.") } acct, ok := userContext(r) if !ok { return badRequest(w, "Must be logged in.") } staff, _ := staff.WithID(c, acct.ID) if !canViewRoster(staff, acct, class.TeacherEntity(c)) { return webapp.UnauthorizedError(fmt.Errorf("only staff or teachers can view rosters")) } classStudents := students.In(c, class, time.Now()) sort.Sort(students.ByName(classStudents)) token, err := storeNewToken(c, acct.ID, "/register/paper") if err != nil { return webapp.InternalError(fmt.Errorf("Failed to store token: %s", err)) } data := map[string]interface{}{ "Class": class, "Students": classStudents, "Token": token.Encode(), } if err := rosterPage.Execute(w, data); err != nil { return webapp.InternalError(err) } return nil }
func deleteClass(w http.ResponseWriter, r *http.Request) *webapp.Error { idString := r.FormValue("class") if idString == "" { return missingFields(w) } id, err := strconv.ParseInt(idString, 10, 64) if err != nil { return invalidData(w, fmt.Sprintf("Invalid class ID")) } c := appengine.NewContext(r) class, err := classes.ClassWithID(c, id) switch err { case nil: break case classes.ErrClassNotFound: return invalidData(w, "No such class.") default: return webapp.InternalError(fmt.Errorf("failed to look up class %d: %s", id, err)) } staffAccount, ok := staffContext(r) if !ok { return webapp.UnauthorizedError(fmt.Errorf("only staff may delete classes")) } if r.Method == "POST" { c.Infof("updating class %d", class.ID) token, err := auth.TokenForRequest(c, staffAccount.ID, r.URL.Path) if err != nil { return webapp.UnauthorizedError(fmt.Errorf("didn't find an auth token")) } if !token.IsValid(r.FormValue(auth.TokenFieldName), time.Now()) { return webapp.UnauthorizedError(fmt.Errorf("invalid auth token")) } if err := class.Delete(c); err != nil { return webapp.InternalError(fmt.Errorf("failed to delete class %d: %s", class.ID, err)) } token.Delete(c) http.Redirect(w, r, "/staff", http.StatusSeeOther) return nil } token, err := auth.NewToken(staffAccount.ID, r.URL.Path, time.Now()) if err != nil { return webapp.InternalError(err) } if err := token.Store(c); err != nil { return webapp.InternalError(err) } data := map[string]interface{}{ "Token": token.Encode(), "Class": class, "Teacher": class.TeacherEntity(c), } if err := deleteClassPage.Execute(w, data); err != nil { return webapp.InternalError(err) } return nil }
func class(w http.ResponseWriter, r *http.Request) *webapp.Error { id, err := strconv.ParseInt(r.FormValue("id"), 10, 64) if err != nil { return invalidData(w, "Couldn't parse class ID") } c := appengine.NewContext(r) class, err := classes.ClassWithID(c, id) switch err { case nil: break case classes.ErrClassNotFound: return invalidData(w, "No such class") default: return webapp.InternalError(fmt.Errorf("failed to find class %d: %s", id, err)) } teacher := class.TeacherEntity(c) data := map[string]interface{}{ "Class": class, "Teacher": teacher, } if u := user.Current(c); u != nil { switch a, err := maybeOldAccount(c, u); err { case nil: data["User"] = a staffer, _ := maybeOldStaff(c, a, u) data["CanViewRoster"] = canViewRoster(staffer, a, teacher) sessionToken, err := storeNewToken(c, a.ID, "/register/session") if err != nil { return webapp.InternalError(fmt.Errorf("failed to store token: %s")) } data["SessionToken"] = sessionToken.Encode() oneDayToken, err := storeNewToken(c, a.ID, "/register/oneday") if err != nil { return webapp.InternalError(fmt.Errorf("failed to store token: %s")) } data["OneDayToken"] = oneDayToken.Encode() switch student, err := maybeOldStudent(c, a, u, class); err { case nil: data["Student"] = student case students.ErrStudentNotFound: break default: c.Errorf("failed to find student %q in %d: %s", a.ID, class.ID, err) } case account.ErrUserNotFound: break default: c.Errorf("Failed to find user account: %s", err) } } if err := classPage.Execute(w, data); err != nil { return webapp.InternalError(err) } return nil }
// Add attempts to write a new Student entity; it will not overwrite // any existing Students. Returns ErrClassFull if the class is full as // of the given date. The number of students "currently registered" // for a class is the number of session-registered students plus any // future drop ins. This may be smaller than the number of students // registered on a particular day, and so may prevent drop-ins which // may otherwise have succeeded. In other words, a student can only // drop in if we can prove that there is room for them to register for // the rest of the session. func (s *Student) Add(c appengine.Context, asOf time.Time) error { key := s.key(c) var txnErr error for i := 0; i < 25; i++ { txnErr = datastore.RunInTransaction(c, func(c appengine.Context) error { old := &Student{} switch err := datastore.Get(c, key, old); err { case datastore.ErrNoSuchEntity: break case nil: if old.DropIn && old.Date.Before(asOf) { // Old registration is an expired drop-in. Allow re-registering. break } // Old registration is still active; do nothing. c.Warningf("Attempted duplicate registration of %q in %d", s.ID, s.ClassID) return nil default: return fmt.Errorf("students: failed to look up existing student: %s", err) } class, err := classes.ClassWithID(c, s.ClassID) if err != nil { return err } in := In(c, class, asOf) if int32(len(in)) >= class.Capacity { return ErrClassIsFull } if err := class.Update(c); err != nil { return fmt.Errorf("students: failed to update class: %s", err) } if _, err := datastore.Put(c, key, s); err != nil { return fmt.Errorf("students: failed to write student: %s", err) } return nil }, nil) if txnErr != datastore.ErrConcurrentTransaction { break } } switch txnErr { case nil: return nil case datastore.ErrConcurrentTransaction: return fmt.Errorf("students: too many concurrent updates to class %d", s.ClassID) default: return txnErr } }
func editClass(w http.ResponseWriter, r *http.Request) *webapp.Error { idString := r.FormValue("class") if idString == "" { return missingFields(w) } id, err := strconv.ParseInt(idString, 10, 64) if err != nil { return invalidData(w, fmt.Sprintf("Invalid class ID")) } c := appengine.NewContext(r) class, err := classes.ClassWithID(c, id) switch err { case nil: break case classes.ErrClassNotFound: return invalidData(w, "No such class.") default: return webapp.InternalError(fmt.Errorf("failed to look up class %d: %s", id, err)) } staffAccount, ok := staffContext(r) if !ok { return webapp.UnauthorizedError(fmt.Errorf("only staff may edit classes")) } if r.Method == "POST" { c.Infof("updating class %d", class.ID) token, err := auth.TokenForRequest(c, staffAccount.ID, r.URL.Path) if err != nil { return webapp.UnauthorizedError(fmt.Errorf("didn't find an auth token")) } if !token.IsValid(r.FormValue(auth.TokenFieldName), time.Now()) { return webapp.UnauthorizedError(fmt.Errorf("invalid auth token")) } fields, err := webapp.ParseRequiredValues(r, "name", "description", "maxstudents", "dayofweek", "starttime", "length", "dropinonly") if err != nil { return missingFields(w) } class.Title = fields["name"] class.LongDescription = []byte(fields["description"]) class.DropInOnly = fields["dropinonly"] == "yes" weekday, err := parseWeekday(fields["dayofweek"]) if err != nil { return invalidData(w, "Invalid weekday") } class.Weekday = weekday maxStudents, err := strconv.ParseInt(fields["maxstudents"], 10, 32) if err != nil || maxStudents <= 0 { return invalidData(w, "Invalid student capacity") } class.Capacity = int32(maxStudents) length, err := parseMinutes(fields["length"]) if err != nil { return invalidData(w, "Invalid length") } class.Length = length start, err := parseLocalTime(fields["starttime"]) if err != nil { return invalidData(w, "Invalid start time; please use HH:MMpm format (e.g., 3:04pm)") } class.StartTime = start if email := r.FormValue("teacher"); email == "" { class.Teacher = nil } else { teacher, err := classes.TeacherWithEmail(c, email) if err != nil { return invalidData(w, "Invalid teacher selected") } class.Teacher = teacher.Key(c) } if err := class.Update(c); err != nil { return webapp.InternalError(fmt.Errorf("failed to update class %d: %s", class.ID, err)) } token.Delete(c) http.Redirect(w, r, "/staff", http.StatusSeeOther) return nil } token, err := auth.NewToken(staffAccount.ID, r.URL.Path, time.Now()) if err != nil { return webapp.InternalError(err) } if err := token.Store(c); err != nil { return webapp.InternalError(err) } data := map[string]interface{}{ "Token": token.Encode(), "Class": class, "Teacher": class.TeacherEntity(c), "Teachers": classes.Teachers(c), "DaysInOrder": daysInOrder, } if err := editClassPage.Execute(w, data); err != nil { return webapp.InternalError(err) } return nil }