Beispiel #1
0
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
}
Beispiel #2
0
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
}
Beispiel #3
0
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
}
Beispiel #4
0
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
}
Beispiel #5
0
// 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
	}
}
Beispiel #6
0
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
}