func ServeProjectList(w http.ResponseWriter, r *http.Request) {
	ctx := GetContext(r)

	if ctx.Account == nil {
		http.Redirect(w, r, "/login", http.StatusSeeOther)
		return
	}

	vars := mux.Vars(r)
	idStr := vars["id"]
	if !bson.IsObjectIdHex(idStr) {
		ServeNotFound(w, r)
		return
	}
	id := bson.ObjectIdHex(idStr)
	org, err := data.GetOrganization(id)
	catch(r, err)
	if org == nil {
		ServeNotFound(w, r)
		return
	}

	mems, err := data.ListMembersOrganizationAccount(org.ID, ctx.Account.ID, 0, math.MaxInt32)
	catch(r, err)

	if org.OwnerID != ctx.Account.ID && len(mems) == 0 {
		ServeForbidden(w, r)
		return
	}

	prjs := []data.Project{}

	for _, mem := range mems {
		prj, err := mem.Project()
		catch(r, err)

		prjs = append(prjs, *prj)
	}

	w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
	ServeHTMLTemplate(w, r, tplProjectList, struct {
		Context      *Context
		Organization *data.Organization
		Projects     []data.Project
	}{
		Context:      ctx,
		Organization: org,
		Projects:     prjs,
	})
}
func ServeProjectNew(w http.ResponseWriter, r *http.Request) {
	ctx := GetContext(r)

	if ctx.Account == nil {
		http.Redirect(w, r, "/login", http.StatusSeeOther)
		return
	}

	vars := mux.Vars(r)
	idStr := vars["id"]
	if !bson.IsObjectIdHex(idStr) {
		ServeNotFound(w, r)
		return
	}
	id := bson.ObjectIdHex(idStr)
	org, err := data.GetOrganization(id)
	catch(r, err)
	if org == nil {
		ServeNotFound(w, r)
		return
	}

	if org.OwnerID != ctx.Account.ID {
		ServeForbidden(w, r)
		return
	}

	w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
	ServeHTMLTemplate(w, r, tplProjectNew, struct {
		Context      *Context
		Organization *data.Organization
	}{
		Context:      ctx,
		Organization: org,
	})
}
func HandleProjectCreate(w http.ResponseWriter, r *http.Request) {
	ctx := GetContext(r)

	if ctx.Account == nil {
		http.Redirect(w, r, "/login", http.StatusSeeOther)
		return
	}

	err := r.ParseForm()
	catch(r, err)

	vars := mux.Vars(r)
	idStr := vars["id"]
	if !bson.IsObjectIdHex(idStr) {
		ServeNotFound(w, r)
		return
	}
	id := bson.ObjectIdHex(idStr)
	org, err := data.GetOrganization(id)
	catch(r, err)
	if org == nil {
		ServeNotFound(w, r)
		return
	}

	if org.OwnerID != ctx.Account.ID {
		ServeForbidden(w, r)
		return
	}

	body := struct {
		Name string `schema:"name"`
	}{}

	err = schema.NewDecoder().Decode(&body, r.PostForm)
	catch(r, err)

	switch {
	case body.Name == "":
		RedirectBack(w, r)
		return
	}

	prj := data.Project{
		Name:           body.Name,
		OwnerID:        ctx.Account.ID,
		OrganizationID: org.ID,
	}
	err = prj.Put()
	catch(r, err)

	mem := data.Member{
		OrganizationID: prj.OrganizationID,
		ProjectID:      prj.ID,
		AccountID:      prj.OwnerID,
		InviterID:      prj.OwnerID,
		InvitedAt:      time.Now(),
	}
	err = mem.Put()
	catch(r, err)

	prj.MemberIDs = append(prj.MemberIDs, mem.ID)
	err = prj.Put()
	catch(r, err)

	http.Redirect(w, r, "/projects/"+prj.ID.Hex(), http.StatusSeeOther)
}