/// <summary> /// Attempt to update a document based on the information in the HTTP request /// </summary> /// <returns>The resulting status of the operation</returns> /// <param name="context">The request context</param> /// <param name="db">The database in which the document exists</param> /// <param name="docId">The ID of the document being updated</param> /// <param name="body">The new document body</param> /// <param name="deleting">Whether or not the document is being deleted</param> /// <param name="allowConflict">Whether or not to allow a conflict to be inserted</param> /// <param name="outRev">The resulting revision of the document</param> public static StatusCode UpdateDocument(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting, bool allowConflict, out RevisionInternal outRev) { outRev = null; if (body != null && !body.IsValidJSON()) { return StatusCode.BadJson; } string prevRevId; if (!deleting) { var properties = body.GetProperties(); deleting = properties.GetCast<bool>("_deleted"); if (docId == null) { // POST's doc ID may come from the _id field of the JSON body. docId = properties.GetCast<string>("_id"); if (docId == null && deleting) { return StatusCode.BadId; } } // PUT's revision ID comes from the JSON body. prevRevId = properties.GetCast<string>("_rev"); } else { // DELETE's revision ID comes from the ?rev= query param prevRevId = context.GetQueryParam("rev"); } // A backup source of revision ID is an If-Match header: if (prevRevId == null) { prevRevId = context.IfMatch(); } if (docId == null && deleting) { return StatusCode.BadId; } RevisionInternal rev = new RevisionInternal(docId, null, deleting); rev.SetBody(body); StatusCode status = StatusCode.Created; try { if (docId != null && docId.StartsWith("_local")) { outRev = db.Storage.PutLocalRevision(rev, prevRevId, true); //TODO: Doesn't match iOS } else { Status retStatus = new Status(); outRev = db.PutRevision(rev, prevRevId, allowConflict, retStatus); status = retStatus.Code; } } catch(CouchbaseLiteException e) { status = e.Code; } return status; }
/// <summary> /// Creates (and executes) a temporary view based on the view function supplied in the JSON request. /// </summary> /// <returns>The response state for further HTTP processing</returns> /// <param name="context">The context of the Couchbase Lite HTTP request</param> /// <remarks> /// http://docs.couchdb.org/en/latest/api/database/temp-views.html#post--db-_temp_view /// </remarks> public static ICouchbaseResponseState ExecuteTemporaryViewFunction(ICouchbaseListenerContext context) { var response = context.CreateResponse(); if (context.RequestHeaders["Content-Type"] == null || !context.RequestHeaders["Content-Type"].StartsWith("application/json")) { response.InternalStatus = StatusCode.UnsupportedType; return response.AsDefaultState(); } IEnumerable<byte> json = context.BodyStream.ReadAllBytes(); var requestBody = new Body(json); if (!requestBody.IsValidJSON()) { response.InternalStatus = StatusCode.BadJson; return response.AsDefaultState(); } var props = requestBody.GetProperties(); if (props == null) { response.InternalStatus = StatusCode.BadJson; return response.AsDefaultState(); } var options = context.QueryOptions; if (options == null) { response.InternalStatus = StatusCode.BadRequest; return response.AsDefaultState(); } return PerformLogicWithDatabase(context, true, db => { if (context.CacheWithEtag(db.GetLastSequenceNumber().ToString())) { response.InternalStatus = StatusCode.NotModified; return response; } var view = db.GetView("@@TEMPVIEW@@"); var status = view.Compile(props, "javascript"); if(status.IsError) { response.InternalStatus = status.Code; return response; } try { view.UpdateIndex_Internal(); return QueryView(context, null, view, options); } catch(CouchbaseLiteException e) { response.InternalStatus = e.CBLStatus.Code; } return response; }).AsDefaultState(); }
/// <summary> /// Attempt to update a document based on the information in the HTTP request /// </summary> /// <returns>The resulting status of the operation</returns> /// <param name="context">The request context</param> /// <param name="db">The database in which the document exists</param> /// <param name="docId">The ID of the document being updated</param> /// <param name="body">The new document body</param> /// <param name="deleting">Whether or not the document is being deleted</param> /// <param name="allowConflict">Whether or not to allow a conflict to be inserted</param> /// <param name="outRev">The resulting revision of the document</param> public static StatusCode UpdateDocument(ICouchbaseListenerContext context, Database db, string docId, Body body, bool deleting, bool allowConflict, out RevisionInternal outRev) { outRev = null; if (body != null && !body.IsValidJSON()) { return StatusCode.BadJson; } string prevRevId; if (!deleting) { var properties = body.GetProperties(); deleting = properties.GetCast<bool>("_deleted"); if (docId == null) { // POST's doc ID may come from the _id field of the JSON body. docId = properties.CblID(); if (docId == null && deleting) { return StatusCode.BadId; } } // PUT's revision ID comes from the JSON body. prevRevId = properties.GetCast<string>("_rev"); } else { // DELETE's revision ID comes from the ?rev= query param prevRevId = context.GetQueryParam("rev"); } // A backup source of revision ID is an If-Match header: if (prevRevId == null) { prevRevId = context.IfMatch(); } if (docId == null && deleting) { return StatusCode.BadId; } RevisionInternal rev = new RevisionInternal(docId, null, deleting); rev.SetBody(body); // Check for doc expiration var expirationTime = default(DateTime?); var tmp = default(object); var props = rev.GetProperties(); var hasValue = false; if(props != null && props.TryGetValue("_exp", out tmp)) { hasValue = true; if(tmp != null) { try { expirationTime = Convert.ToDateTime(tmp); } catch(Exception) { try { var num = Convert.ToInt64(tmp); expirationTime = Misc.OffsetFromEpoch(TimeSpan.FromSeconds(num)); } catch(Exception) { Log.To.Router.E(TAG, "Invalid value for _exp: {0}", tmp); return StatusCode.BadRequest; } } } props.Remove("_exp"); rev.SetProperties(props); } var castContext = context as ICouchbaseListenerContext2; var source = castContext != null && !castContext.IsLoopbackRequest ? castContext.Sender : null; StatusCode status = deleting ? StatusCode.Ok : StatusCode.Created; try { if(docId != null && docId.StartsWith("_local")) { if(expirationTime.HasValue) { return StatusCode.BadRequest; } outRev = db.Storage.PutLocalRevision(rev, prevRevId.AsRevID(), true); //TODO: Doesn't match iOS } else { outRev = db.PutRevision(rev, prevRevId.AsRevID(), allowConflict, source); if(hasValue) { db.Storage?.SetDocumentExpiration(rev.DocID, expirationTime); } } } catch(CouchbaseLiteException e) { status = e.Code; } return status; }