// EventPOST implements gaia's response to a POST request to the '/event/' endpoint. // // Assumptions: The user has been authenticated. // // Proceedings: Parses the url parameters. func EventPOST(ctx context.Context, w http.ResponseWriter, r *http.Request, db data.DB, logger services.Logger) { l := logger.WithPrefix("EventPOST: ") // Parse the form if err := r.ParseForm(); err != nil { l.Printf("error parsing form: %s", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } // Retrieve the tags parameter tagNames, ok := r.Form[tagsParam] if !ok { l.Print("no tags param") tagNames = []string{} } // if any tag names have commas, split those tagNames = flatten(mapSplit(tagNames, ",")) // Retrieve our user u, ok := user.FromContext(ctx) if !ok { l.Print("failed to retrieve user from context") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } e := new(models.Event) tags := make([]*models.Tag, len(tagNames)) for i, n := range tagNames { t, err := tag.ForName(db, u, tag.Name(n)) if err != nil { l.Printf("tag.ForName(%q) error: %s", n, err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } tags[i] = t } defer r.Body.Close() if requestBody, err := ioutil.ReadAll(r.Body); err != nil { l.Printf("ioutil.ReadAll(r.Body) error: %s", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } else if err := json.Unmarshal(requestBody, e); err != nil { l.Printf("info: request body:\n%s", string(requestBody)) l.Printf("error: while unmarshalling request body, %s", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if e.CreatedAt.IsZero() { e.CreatedAt = time.Now() } e.UpdatedAt = time.Now() e.SetOwner(u) for _, t := range tags { e.IncludeTag(t) } if allowed, err := access.CanCreate(db, u, e); err != nil { l.Printf("access.CanCreate(db, u, e) error: %s", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } else if !allowed { l.Print("access.CanCreate(db, u, e) rejected authorization") http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } if err := db.Save(u); err != nil { l.Printf("error saving record: %s", err) switch err { case data.ErrAccessDenial: http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) // These are all equally distressing case data.ErrNotFound: // TODO shouldn't a not found not be fing impossible for a Save? fallthrough case data.ErrNoConnection: fallthrough case data.ErrInvalidID: default: http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } return } // Now we shall write our response b, err := json.MarshalIndent(e, "", " ") if err != nil { l.Printf("json.MarshalIndent(m, \"\", \" \") error: %s", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) w.Header().Set("Content-Type", "application/json") w.Write(b) }
// RecordPOST implements gaia's response to a POST request to the '/record/' endpoint. // // Assumptions: The user has been authenticated. // // Proceedings: Parses the url parameters, and retrieves the kind parameter (required). // Then it checks whether the payload record's id is declared, generating one if not. // Finally, it saves or updates the record, returning the record with corresponding status. // // Success: // * StatusOK with the record as JSON, meaning the record was _updated_ // * StatusCreated with the record as JSON, meaning the record was _created_ // // Errors: // * InternalServerError: failure to parse the parameters, database connections, json marshalling // * BadRequest: no kind param, unrecognized kind // * NotFound: unauthorized, record actually doesn't exist // * Unauthorized: not authorized to create/update that record, database access denial func RecordPOST(ctx context.Context, w http.ResponseWriter, r *http.Request, logger services.Logger, db services.DB) { l := logger.WithPrefix("RecordPOST: ") // Parse the form if err := r.ParseForm(); err != nil { l.Printf("error parsing form: %s", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } // l.Printf("%+v", r) // Retrieve the kind parameter k := r.FormValue(kindParam) if k == "" { l.Printf("no kind specified") http.Error(w, fmt.Sprintf("You must specify a %q parameter", kindParam), http.StatusBadRequest) return } kind := data.Kind(k) // Verify it is a recognized kind if _, ok := models.Kinds[kind]; !ok { http.Error(w, fmt.Sprintf("The kind %q is not recognized", kind), http.StatusBadRequest) return } m := models.ModelFor(kind) var requestBody []byte var err error // Now we must read the body of the request defer r.Body.Close() // don't forget to close it if requestBody, err = ioutil.ReadAll(r.Body); err != nil { l.Printf("error while reading request body: %s", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } // Now we unmarshal that into the record if err = json.Unmarshal(requestBody, m); err != nil { l.Printf("info: request body:\n%s", string(requestBody)) l.Printf("error: while unmarshalling request body, %s", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } // Now we can determine whether we are creating a new record // or updating an existing one. We expect to be updating... creation := false // ...unless the id is empty, in which case we are creating if m.ID().String() == "" { m.SetID(db.NewID()) // Be sure to assign it an ID creation = true } // Retrieve our user u, ok := user.FromContext(ctx) if !ok { l.Print("failed to retrieve user from context") http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } var allowed bool // We need to check either [creation] we can create the record or [!creation] // we can update the record we are trying to update if creation { prop, ok := m.(access.Property) if !ok { l.Printf("tried to create record that isn't property") http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } allowed, err = access.CanCreate(db, u, prop) } else { allowed, err = access.CanWrite(db, u, m) } if err != nil { l.Printf("access.{CanCreate | CanWrite} error: %s", err) switch err { // This indicates that no, you have no access case data.ErrAccessDenial: http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) // All of these are bad, and considered an internal error case data.ErrNotFound: fallthrough case data.ErrNoConnection: fallthrough case data.ErrInvalidID: fallthrough default: http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } return } else if !allowed { l.Printf("access denied at create/update stage") http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } // If we have made it this far, it only remains to commit the record if err = db.Save(m); err != nil { l.Printf("error saving record: %s", err) switch err { case data.ErrAccessDenial: http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) // These are all equally distressing case data.ErrNotFound: // TODO shouldn't a not found not be fing impossible for a Save? fallthrough case data.ErrNoConnection: fallthrough case data.ErrInvalidID: default: http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } return } // Now we shall write our response bytes, err := json.MarshalIndent(m, "", " ") if err != nil { l.Printf("error marshalling model: %s", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if creation { w.WriteHeader(http.StatusCreated) } else { w.WriteHeader(http.StatusOK) } w.Header().Set("Content-Type", "application/json") w.Write(bytes) }