// this method only exists for compatibility with PouchDB and should not be used elsewhere. func BulkGet(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) var ireqs interface{} /* options */ revs := flag(r, "revs") var ok bool if ireqs, ok = ctx.jsonBody["docs"]; !ok { res := responses.BadRequest("You were supposed to request some docs specified by their ids, and you didn't.") w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } reqs := ireqs.([]interface{}) res := responses.BulkGet{ Results: make([]responses.BulkGetResult, len(reqs)), } for i, ireq := range reqs { req := ireq.(map[string]interface{}) res.Results[i] = responses.BulkGetResult{ Docs: make([]responses.DocOrError, 1), } iid, ok := req["id"] if !ok { err := responses.BadRequest("missing id") res.Results[i].Docs[0].Error = &err continue } id := iid.(string) res.Results[i].Id = id path := db.CleanPath(ctx.path) + "/" + db.EscapeKey(id) rev := string(db.GetRev(path)) doc, err := db.GetTreeAt(path) if err != nil { err := responses.NotFound() res.Results[i].Docs[0].Error = &err continue } doc["_id"] = id doc["_rev"] = rev delete(doc, "_val") // pouchdb doesn't like top-level keys starting with _ if revs { // magic method to fetch _revisions // docs["_revisions"] = ... } res.Results[i].Docs[0].Ok = &doc } w.Header().Add("Content-Type", "application/json") w.WriteHeader(200) json.NewEncoder(w).Encode(res) }
func Delete(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) var err error var rev string if !ctx.exists { res := responses.NotFound() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } if ctx.lastKey[0] == '_' { res := responses.BadRequest("you can't delete special keys") w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } if ctx.currentRev != "" && ctx.currentRev != ctx.providedRev { log.WithFields(log.Fields{ "given": ctx.providedRev, "current": ctx.currentRev, }).Debug("rev mismatch.") res := responses.ConflictError() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } rev, err = db.DeleteAt(ctx.path) if err != nil { log.Error("couldn't delete key at ", ctx.path, ": ", err) res := responses.NotFound() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } w.WriteHeader(200) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(responses.Success{true, ctx.lastKey, rev}) }
/* should accept PATCH requests with JSON objects: `curl -X PATCH http://db/path -d '{"to": {"key": "some value"}}' -H 'content-type: application/json'` this will not replace all values under /path, but only modify the values which the JSON object refers to. */ func Patch(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) var err error var rev string if ctx.lastKey[0] == '_' { res := responses.BadRequest("you can't update special keys") w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } if !ctx.exists { res := responses.NotFound() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } if ctx.currentRev != ctx.providedRev { log.WithFields(log.Fields{ "given": ctx.providedRev, "current": ctx.currentRev, }).Debug("rev mismatch.") res := responses.ConflictError() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } // update the tree as the JSON structure demands rev, err = db.SaveTreeAt(ctx.path, ctx.jsonBody) if err != nil { log.Debug("couldn't save value: ", err) res := responses.UnknownError(err.Error()) w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } w.WriteHeader(200) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(responses.Success{true, ctx.lastKey, rev}) }
/* Currently _all_docs does not guarantee key order and should not be used for querying or anything. It is here just to provide PouchDB replication support. */ func AllDocs(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) /* options */ include_docs := flag(r, "include_docs") jsonKeys := param(r, "keys") // startkey := param(r, "startkey") // endkey := param(r, "endkey") // descending := flag(r, "descending") // skip := param(r, "skip") // limit := param(r, "limit") path := db.CleanPath(ctx.path) tree, err := db.GetTreeAt(path) if err != nil { res := responses.UnknownError(err.Error()) w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } res := responses.AllDocs{ TotalRows: 0, Offset: 0, // temporary Rows: make([]responses.Row, 0), } if jsonKeys != "" { // in case "keys" was provided we can just pick the requested keys from our tree var keys []string err = json.Unmarshal([]byte(jsonKeys), &keys) if err != nil { res := responses.BadRequest() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } for _, id := range keys { var doc interface{} var ok bool if doc, ok = tree[id]; !ok || strings.HasPrefix(id, "_") { res.Rows = append(res.Rows, responses.Row{ Key: id, Error: (responses.NotFound()).Reason, }) } rev := string(db.GetRev(path + "/" + db.EscapeKey(id))) row := responses.Row{ Id: id, Key: id, Value: map[string]interface{}{"rev": rev}, } if include_docs { row.Doc = doc.(map[string]interface{}) row.Doc["_id"] = id row.Doc["_rev"] = rev delete(row.Doc, "_val") // pouchdb doesn't like top-level keys starting with _ } res.Rows = append(res.Rows, row) res.TotalRows += 1 } } else { // otherwise iterate through the entire tree for id, doc := range tree { if strings.HasPrefix(id, "_") { continue } rev := string(db.GetRev(path + "/" + db.EscapeKey(id))) row := responses.Row{ Id: id, Key: id, Value: map[string]interface{}{"rev": rev}, } if include_docs { row.Doc = doc.(map[string]interface{}) row.Doc["_id"] = id row.Doc["_rev"] = rev delete(row.Doc, "_val") // pouchdb doesn't like top-level keys starting with _ } res.Rows = append(res.Rows, row) res.TotalRows += 1 } } w.Header().Add("Content-Type", "application/json") w.WriteHeader(200) json.NewEncoder(w).Encode(res) }
func Get(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) /* here we handle special endpoints that, in CouchDB, refer to a database, but here are treated as documents (or better, subtrees) methods */ if ctx.wantsDatabaseInfo { DatabaseInfo(w, r) return } else if ctx.lastKey == "_changes" { Changes(w, r) return } else if ctx.lastKey == "_all_docs" { AllDocs(w, r) return } else if ctx.lastKey == "_security" { ReadSecurity(w, r) return } else if ctx.lastKey == "_missing_revs" { return } var response []byte var err error if !ctx.exists { res := responses.NotFound() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } if ctx.localDoc { jsondoc, err := db.GetLocalDocJsonAt(ctx.path) if err != nil { // ctx.exists doesn't work for _local docs res := responses.NotFound() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } w.WriteHeader(200) w.Header().Add("Content-Type", "application/json") w.Write(jsondoc) return } if ctx.wantsTree { var tree map[string]interface{} tree, err = db.GetTreeAt(ctx.path) if err == nil { tree["_id"] = ctx.lastKey tree["_rev"] = ctx.currentRev if flag(r, "revs") { var revs []string revs, err = db.RevsAt(ctx.path) if err == nil { var start int start, err = strconv.Atoi(strings.Split(revs[0], "-")[0]) revids := make([]string, len(revs)) for i, rev := range revs { revids[i] = strings.Split(rev, "-")[1] } tree["_revisions"] = responses.Revisions{ Start: start, Ids: revids, } } } if flag(r, "revs_info") { var revs []string revs, err = db.RevsAt(ctx.path) if err == nil { revsInfo := make([]responses.RevInfo, len(revs)) i := 0 for r := len(revs) - 1; r >= 0; r-- { revsInfo[i] = responses.RevInfo{ Rev: revs[r], Status: "missing", } i++ } revsInfo[0].Status = "available" tree["_revs_info"] = revsInfo } } response, err = json.Marshal(tree) } } else { if ctx.lastKey == "_rev" { response = []byte(ctx.currentRev) } else { response, err = db.GetValueAt(ctx.path) if err != nil { res := responses.NotFound() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } } } if err != nil { res := responses.UnknownError(err.Error()) w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } w.WriteHeader(200) w.Header().Add("Content-Type", "application/json") w.Write(response) }