func CreateUser(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) if name, ok := ctx.jsonBody["name"]; ok { if password, ok := ctx.jsonBody["password"]; ok { err := db.SaveUser(name.(string), password.(string)) if err != nil { res := responses.UnknownError(err.Error()) w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } else { res := responses.Success{Ok: true} w.Header().Add("Content-Type", "application/json") w.WriteHeader(201) json.NewEncoder(w).Encode(res) return } } } res := responses.BadRequest(`You must send a JSON body with "name" and "password".`) w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) }
/* requests with raw string bodies: - curl -X PUT http://db/path/to/key/_val -d 'some value' and complete JSON objects - curl -X PUT http://db/path -d '{"to": {"_val": "nothing here", "key": "some value"}}' -H 'content-type: application/json' While setting the raw string body of a path will only update that path and do not change others, a full JSON request will replace all keys under the specified path. PUT is idempotent. The "_val" key is optional when setting, but can be used to set values right to the key to which they refer. It is sometimes needed, like in this example, here "path/to" had some children values to be set, but also needed a value of its own. Other use of the "_val" key is to set a value to null strictly, because setting the nude key to null will delete it instead of setting it. */ func Put(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) if ctx.lastKey == "_security" { WriteSecurity(w, r) return } var err error var rev string if ctx.exists && 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 } if ctx.lastKey == "_val" || ctx.jsonBody == nil { // updating the value at only this specific path, as raw bytes rev, err = db.SaveValueAt(ctx.path, db.ToLevel(ctx.body)) } else if ctx.jsonBody != nil { // saving a JSON tree, as its structure demands, expanding to subpaths and so on if ctx.lastKey[0] == '_' { log.WithFields(log.Fields{ "path": ctx.path, "lastkey": ctx.lastKey, }).Debug("couldn't update special key.") res := responses.BadRequest("you can't update special keys") w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } if ctx.localDoc { rev, err = db.SaveLocalDocAt(ctx.path, ctx.jsonBody) } else { rev, err = db.ReplaceTreeAt(ctx.path, ctx.jsonBody, false) } } else { err = errors.New("value received at " + ctx.path + " is not JSON: " + string(ctx.body)) } 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(201) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(responses.Success{true, ctx.lastKey, rev}) }
func Post(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) var err error var rev string if ctx.jsonBody == nil { res := responses.BadRequest("You must send a JSON body for this request.") w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } if ctx.lastKey == "_bulk_get" { BulkGet(w, r) return } else if ctx.lastKey == "_bulk_docs" { BulkDocs(w, r) return } else if ctx.lastKey == "_revs_diff" { RevsDiff(w, r) return } if ctx.lastKey[0] == '_' { res := responses.BadRequest("you can't post to special keys.") w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } // POST is supposed to generate a new doc, with a new random _id: id := db.Random(7) path := ctx.path + "/" + id if ctx.jsonBody != nil { // if it is JSON we must save it as its structure demands rev, err = db.SaveTreeAt(path, ctx.jsonBody) } else { // otherwise it's an error res := responses.BadRequest("you need to send a JSON body for this request.") w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } if err != nil { log.Error("couldn't save value: ", err) res := responses.UnknownError(err.Error()) w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } w.WriteHeader(201) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(responses.Success{true, id, 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}) }
func DatabaseInfo(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) lastSeq, err := db.LastSeqAt(ctx.path) if err != nil { res := responses.UnknownError(err.Error()) w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } res := responses.DatabaseInfo{ DBName: ctx.path, UpdateSeq: lastSeq, InstanceStartTime: settings.STARTTIME.Unix(), } w.Header().Add("Content-Type", "application/json") w.WriteHeader(200) json.NewEncoder(w).Encode(res) }
func Changes(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) /* options */ // always true temporarily: all_docs := flag(r, "style", "all_docs") sincep := param(r, "since") var since uint64 if sincep == "now" { since = db.GlobalUpdateSeq() } else { var err error since, err = strconv.ParseUint(sincep, 10, 64) if err != nil { since = 0 } } path := db.CleanPath(ctx.path) changes, err := db.ListChangesAt(path, since) if err != nil { res := responses.UnknownError(err.Error()) w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } var lastSeq uint64 = 0 nchanges := len(changes) if nchanges > 0 { lastSeq = changes[nchanges-1].Seq } res := responses.Changes{ LastSeq: lastSeq, Results: changes, } w.Header().Add("Content-Type", "application/json") w.WriteHeader(200) json.NewEncoder(w).Encode(res) }
// HTTP handler for writing security metadata func WriteSecurity(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) path := db.CleanPath(ctx.path) if !db.AdminAllowedAt(path, ctx.user) { res := responses.Unauthorized() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } err := db.SetRulesAt(path, ctx.jsonBody) if err != nil { res := responses.UnknownError() w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } res := responses.Success{Ok: true} w.Header().Add("Content-Type", "application/json") w.WriteHeader(200) json.NewEncoder(w).Encode(res) }
/* 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) }
// this method only exists for compatibility with PouchDB and should not be used elsewhere. func BulkDocs(w http.ResponseWriter, r *http.Request) { ctx := getContext(r) var idocs interface{} var iid interface{} var irev interface{} var id string var rev string var ok bool if idocs, ok = ctx.jsonBody["docs"]; !ok { res := responses.BadRequest("You're supposed to send an array of docs to input on the database, and you didn't.") w.WriteHeader(res.Code) json.NewEncoder(w).Encode(res) return } /* options */ new_edits := true if inew_edits, ok := ctx.jsonBody["new_edits"]; ok { new_edits = inew_edits.(bool) } path := db.CleanPath(ctx.path) docs := idocs.([]interface{}) res := make([]responses.BulkDocsResult, len(docs)) var err error if new_edits { /* procedure for normal document saving */ for i, idoc := range docs { var newrev string localDoc := false doc := idoc.(map[string]interface{}) if iid, ok = doc["_id"]; ok { id = iid.(string) if strings.HasPrefix(id, "_local/") { localDoc = true } if irev, ok = doc["_rev"]; ok { rev = irev.(string) } else { rev = "" } } else { id = db.Random(5) } if !localDoc { // procedure for normal documents // check rev matching: if iid != nil /* when iid is nil that means the doc had no _id, so we don't have to check. */ { currentRev := db.GetRev(path + "/" + db.EscapeKey(id)) if string(currentRev) != rev && !bytes.HasPrefix(currentRev, []byte{'0', '-'}) { // rev is not matching, don't input this document e := responses.ConflictError() res[i] = responses.BulkDocsResult{ Id: id, Error: e.Error, Reason: e.Reason, } continue } } // proceed to write the document. newrev, err = db.ReplaceTreeAt(path+"/"+db.EscapeKey(id), doc, false) if err != nil { e := responses.UnknownError() res[i] = responses.BulkDocsResult{ Id: id, Error: e.Error, Reason: e.Reason, } continue } } else { // procedure for local documents // check rev matching: currentRev := db.GetLocalDocRev(path + "/" + id) if currentRev != "0-0" && currentRev != rev { // rev is not matching, don't input this document e := responses.ConflictError() res[i] = responses.BulkDocsResult{ Id: id, Error: e.Error, Reason: e.Reason, } continue } // proceed to write the document. newrev, err = db.SaveLocalDocAt(path+"/"+id, doc) if err != nil { e := responses.UnknownError() res[i] = responses.BulkDocsResult{ Id: id, Error: e.Error, Reason: e.Reason, } continue } } res[i] = responses.BulkDocsResult{ Id: id, Ok: true, Rev: newrev, } } } else { /* procedure for replication-type _bulk_docs (new_edits=false)*/ for i, idoc := range docs { id = "" rev = "" doc := idoc.(map[string]interface{}) if iid, ok = doc["_id"]; ok { id = iid.(string) if irev, ok = doc["_rev"]; ok { rev = irev.(string) } } if id == "" || rev == "" { e := responses.UnknownError() res[i] = responses.BulkDocsResult{ Id: id, Error: e.Error, Reason: e.Reason, } continue } // proceed to write currentRev := db.GetRev(path + "/" + db.EscapeKey(id)) if rev < string(currentRev) { // will write only the rev if it is not the winning rev db.AcknowledgeRevFor(path+"/"+db.EscapeKey(id), rev) } else { // otherwise will write the whole doc normally (replacing the current) rev, err = db.ReplaceTreeAt(path+"/"+db.EscapeKey(id), doc, true) } // handle write error if err != nil { e := responses.UnknownError() res[i] = responses.BulkDocsResult{ Id: id, Error: e.Error, Reason: e.Reason, } continue } res[i] = responses.BulkDocsResult{ Id: id, Ok: true, Rev: rev, } } } w.WriteHeader(201) 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) }