From 09b1cd74a4cb8fd3123aaf67e2ab4bc372f7017d Mon Sep 17 00:00:00 2001 From: Amru Rosyada Date: Thu, 19 Dec 2019 16:24:56 +0700 Subject: [PATCH 1/4] Fix error when create database and query This will happend if passing database name to the flurl with special char for example with /, . , + etc. Will raise exception when requesting. So we add Uri.EscapeDataString(database) when request using flurl with AppendPathSegment(Uri.EscapeDataString(database)) --- .gitignore | 1 + src/CouchDB.Driver/CouchClient.cs | 19 ++++++++++--------- src/CouchDB.Driver/CouchDatabase.cs | 4 ++-- src/CouchDB.Driver/CouchQueryProvider.cs | 4 ++-- src/CouchDB.Driver/QueryTranslator.cs | 4 ++-- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 3e759b7..2d903fd 100644 --- a/.gitignore +++ b/.gitignore @@ -328,3 +328,4 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ +.vscode diff --git a/src/CouchDB.Driver/CouchClient.cs b/src/CouchDB.Driver/CouchClient.cs index 1af54b9..ce6c5a2 100644 --- a/src/CouchDB.Driver/CouchClient.cs +++ b/src/CouchDB.Driver/CouchClient.cs @@ -66,7 +66,7 @@ public CouchClient(string connectionString, Action couchSettingsF #region CRUD /// - /// Returns an instance of the CouchDB database with the given name. + /// Returns an instance of the CouchDB database with the given name. /// If EnsureDatabaseExists is configured, it creates the database if it doesn't exists. /// /// The type of database documents. @@ -113,7 +113,7 @@ public async Task> CreateDatabaseAsync(string da } IFlurlRequest request = NewRequest() - .AppendPathSegment(database); + .AppendPathSegment(Uri.EscapeDataString(database)); if (shards.HasValue) { @@ -152,13 +152,14 @@ public async Task DeleteDatabaseAsync(string database) where TSource : } OperationResult result = await NewRequest() - .AppendPathSegment(database) + .AppendPathSegment(Uri.EscapeDataString(database)) .DeleteAsync() .ReceiveJson() .SendRequestAsync() .ConfigureAwait(false); - if (!result.Ok) { + if (!result.Ok) + { throw new CouchException("Something went wrong during the delete.", "S"); } } @@ -168,7 +169,7 @@ public async Task DeleteDatabaseAsync(string database) where TSource : #region CRUD reflection /// - /// Returns an instance of the CouchDB database of the given type. + /// Returns an instance of the CouchDB database of the given type. /// If EnsureDatabaseExists is configured, it creates the database if it doesn't exists. /// /// The type of database documents. @@ -179,7 +180,7 @@ public CouchDatabase GetDatabase() where TSource : CouchDocume } /// - /// Creates a new database of the given type in the server. + /// Creates a new database of the given type in the server. /// The name must begin with a lowercase letter and can contains only lowercase characters, digits or _, $, (, ), +, - and /.s /// /// The type of database documents. @@ -235,7 +236,7 @@ public CouchDatabase GetUsersDatabase() where TUser : CouchUser #region Utils /// - /// Determines whether the server is up, running, and ready to respond to requests. + /// Determines whether the server is up, running, and ready to respond to requests. /// /// true is the server is not in maintenance_mode; otherwise, false. public async Task IsUpAsync() @@ -249,7 +250,7 @@ public async Task IsUpAsync() .ConfigureAwait(false); return result.Status == "ok"; } - catch(CouchNotFoundException) + catch (CouchNotFoundException) { return false; } @@ -305,7 +306,7 @@ protected virtual void Dispose(bool disposing) { if (_settings.AuthenticationType == AuthenticationType.Cookie && _settings.LogOutOnDispose) { - AsyncContext.Run(() => LogoutAsync().ConfigureAwait(false)); + _ = AsyncContext.Run(() => LogoutAsync().ConfigureAwait(false)); } _flurlClient.Dispose(); } diff --git a/src/CouchDB.Driver/CouchDatabase.cs b/src/CouchDB.Driver/CouchDatabase.cs index b6342b6..ce87ca2 100644 --- a/src/CouchDB.Driver/CouchDatabase.cs +++ b/src/CouchDB.Driver/CouchDatabase.cs @@ -274,7 +274,7 @@ private async Task> SendQueryAsync(Func /// The collection of documents IDs. @@ -487,7 +487,7 @@ public override string ToString() private IFlurlRequest NewRequest() { - return _flurlClient.Request(_connectionString).AppendPathSegment(Database); + return _flurlClient.Request(_connectionString).AppendPathSegment(Uri.EscapeDataString(Database)); } #endregion diff --git a/src/CouchDB.Driver/CouchQueryProvider.cs b/src/CouchDB.Driver/CouchQueryProvider.cs index e08c4d5..a341b5b 100644 --- a/src/CouchDB.Driver/CouchQueryProvider.cs +++ b/src/CouchDB.Driver/CouchQueryProvider.cs @@ -85,7 +85,7 @@ public object GetCouchList(string body) { FindResult result = _flurlClient .Request(_connectionString) - .AppendPathSegments(_db, "_find") + .AppendPathSegments(Uri.EscapeDataString(_db), "_find") .WithHeader("Content-Type", "application/json") .PostStringAsync(body).ReceiveJson>() .SendRequest(); @@ -142,7 +142,7 @@ private object InvokeUnsupportedMethodCallExpression(object result, MethodCallEx MethodInfo queryableMethodInfo = methodCallExpression.Method; Expression[] queryableMethodArguments = methodCallExpression.Arguments.ToArray(); - // Since Max and Min are not map 1 to 1 from Queryable to Enumerable + // Since Max and Min are not map 1 to 1 from Queryable to Enumerable // they need to be handled differently MethodInfo FindEnumerableMethod() { diff --git a/src/CouchDB.Driver/QueryTranslator.cs b/src/CouchDB.Driver/QueryTranslator.cs index 3c197ca..06e8993 100644 --- a/src/CouchDB.Driver/QueryTranslator.cs +++ b/src/CouchDB.Driver/QueryTranslator.cs @@ -6,7 +6,7 @@ #pragma warning disable IDE0058 // Expression value is never used namespace CouchDB.Driver -{ +{ internal partial class QueryTranslator : ExpressionVisitor { private readonly CouchSettings _settings; @@ -22,7 +22,7 @@ internal string Translate(Expression expression) _sb = new StringBuilder(); _sb.Append("{"); Visit(expression); - + // If no Where() calls if (!_isSelectorSet) { From d5897b4e8daf048637f2ad92df064a0de7e9054f Mon Sep 17 00:00:00 2001 From: Amru Rosyada Date: Fri, 20 Dec 2019 05:24:52 +0700 Subject: [PATCH 2/4] Remove dabatasename validation --- src/CouchDB.Driver/CouchClient.cs | 11 ++++++++--- src/CouchDB.Driver/CouchDatabase.cs | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/CouchDB.Driver/CouchClient.cs b/src/CouchDB.Driver/CouchClient.cs index ce6c5a2..273cfcd 100644 --- a/src/CouchDB.Driver/CouchClient.cs +++ b/src/CouchDB.Driver/CouchClient.cs @@ -107,13 +107,18 @@ public async Task> CreateDatabaseAsync(string da throw new ArgumentNullException(nameof(database)); } - if (!_systemDatabases.Contains(database) && !new Regex(@"^[a-z][a-z0-9_$()+/-]*$").IsMatch(database)) + //if (!_systemDatabases.Contains(database) && !new Regex(@"^[a-z][a-z0-9_$()+/-]*$").IsMatch(database)) + //{ + // throw new ArgumentException($"Name {database} contains invalid characters. Please visit: https://docs.couchdb.org/en/stable/api/database/common.html#put--db", nameof(database)); + //} + + if (_systemDatabases.Contains(database)) { throw new ArgumentException($"Name {database} contains invalid characters. Please visit: https://docs.couchdb.org/en/stable/api/database/common.html#put--db", nameof(database)); } IFlurlRequest request = NewRequest() - .AppendPathSegment(Uri.EscapeDataString(database)); + .AppendPathSegment(database); if (shards.HasValue) { @@ -152,7 +157,7 @@ public async Task DeleteDatabaseAsync(string database) where TSource : } OperationResult result = await NewRequest() - .AppendPathSegment(Uri.EscapeDataString(database)) + .AppendPathSegment(database) .DeleteAsync() .ReceiveJson() .SendRequestAsync() diff --git a/src/CouchDB.Driver/CouchDatabase.cs b/src/CouchDB.Driver/CouchDatabase.cs index ce87ca2..d1d865c 100644 --- a/src/CouchDB.Driver/CouchDatabase.cs +++ b/src/CouchDB.Driver/CouchDatabase.cs @@ -487,7 +487,7 @@ public override string ToString() private IFlurlRequest NewRequest() { - return _flurlClient.Request(_connectionString).AppendPathSegment(Uri.EscapeDataString(Database)); + return _flurlClient.Request(_connectionString).AppendPathSegment(Database); } #endregion From daae03bf372987d3cbb4cdfdf0ed598f472f6bb2 Mon Sep 17 00:00:00 2001 From: Amru Rosyada Date: Mon, 30 Dec 2019 21:46:32 +0700 Subject: [PATCH 3/4] Add Couch Document attachment Add couch document attachment --- src/CouchDB.Driver/CouchClient.cs | 2 +- src/CouchDB.Driver/CouchDatabase.cs | 123 +++++++++++++++++- .../Translators/MemberExpressionTranslator.cs | 6 +- .../Types/CouchDocumentAttachment.cs | 40 ++++++ 4 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 src/CouchDB.Driver/Types/CouchDocumentAttachment.cs diff --git a/src/CouchDB.Driver/CouchClient.cs b/src/CouchDB.Driver/CouchClient.cs index 7792901..7e0bc28 100644 --- a/src/CouchDB.Driver/CouchClient.cs +++ b/src/CouchDB.Driver/CouchClient.cs @@ -145,7 +145,7 @@ public async Task DeleteDatabaseAsync(string database) where TSource : .SendRequestAsync() .ConfigureAwait(false); - if (!result.Ok) + if (!result.Ok) { throw new CouchException("Something went wrong during the delete.", "S"); } diff --git a/src/CouchDB.Driver/CouchDatabase.cs b/src/CouchDB.Driver/CouchDatabase.cs index 7852e34..6564331 100644 --- a/src/CouchDB.Driver/CouchDatabase.cs +++ b/src/CouchDB.Driver/CouchDatabase.cs @@ -1,4 +1,6 @@ -using CouchDB.Driver.DTOs; +using System.Net.Http.Headers; +using System.IO; +using CouchDB.Driver.DTOs; using CouchDB.Driver.Exceptions; using CouchDB.Driver.Extensions; using CouchDB.Driver.Helpers; @@ -361,6 +363,125 @@ public async Task CreateOrUpdateAsync(TSource document, bool batch = fa return document; } + /// + /// Create a new document with attachment and returns it. + /// + /// The document to create. The document type should be CouchDocumentAttachment. + /// Stores document in batch mode. + /// A task that represents the asynchronous operation. The task result contains the element created. + public async Task CreateWithAttachmentAsync(TSource document, bool batch = false) + { + var documentAttachment = document as CouchDocumentAttachment; + + if (string.IsNullOrEmpty(documentAttachment.Id)) + { + throw new InvalidOperationException("Cannot add or update a document without an ID."); + } + + foreach (var attachments in documentAttachment.Attachments) + { + var fileTobeAttach = attachments.Value.FileTobeAttach; + var contentType = attachments.Value.ContentType; + + // do not apply the attachment if don't have file to be attach + if (string.IsNullOrEmpty(fileTobeAttach)) + continue; + + if (!File.Exists(fileTobeAttach)) + { + throw new InvalidOperationException($"File {fileTobeAttach} not found."); + } + + var contentStream = new StreamContent( + new FileStream(attachments.Value.FileTobeAttach, FileMode.Open)); + + // apply the content type if defined + if (!string.IsNullOrEmpty(contentType)) + { + contentStream.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + } + + IFlurlRequest request = + NewRequest() + .AppendPathSegment(documentAttachment.Id) + .AppendPathSegment(attachments.Key, + true); + + if (!string.IsNullOrEmpty(documentAttachment.Rev)) + { + request.SetQueryParam("rev", documentAttachment.Rev); + } + + if (batch) + { + request = request.SetQueryParam("batch", "ok"); + } + + DocumentSaveResponse response = await request + .PutAsync(contentStream) + .ReceiveJson() + .SendRequestAsync() + .ConfigureAwait(false); + + document.ProcessSaveResponse(response); + } + + return documentAttachment as TSource; + } + + /// + /// Deletes attachment the document with the given attachment ID. + /// + /// The document to delete. + /// Stores document in batch mode. + /// A task that represents the asynchronous operation. + public async Task DeleteAttachmentAsync(TSource document, bool batch = false) + { + var documentAttachment = document as CouchDocumentAttachment; + + if (string.IsNullOrEmpty(documentAttachment.Id)) + { + throw new InvalidOperationException("Cannot add or update a document without an ID."); + } + + List errorMsg = new List(); + + foreach (var attachments in documentAttachment.Attachments) + { + IFlurlRequest request = + NewRequest() + .AppendPathSegment(documentAttachment.Id) + .AppendPathSegment(attachments.Key, + true); + + if (!string.IsNullOrEmpty(documentAttachment.Rev)) + { + request.SetQueryParam("rev", documentAttachment.Rev); + } + + if (batch) + { + request = request.SetQueryParam("batch", "ok"); + } + + OperationResult result = await request + .DeleteAsync() + .SendRequestAsync() + .ReceiveJson() + .ConfigureAwait(false); + + if (!result.Ok) + { + errorMsg.Add(attachments.Key); + } + } + + if (errorMsg.Any()) + { + throw new CouchDeleteException($"Went wrong when delete this files: {string.Join(", ", errorMsg.ToArray())}"); + } + } + /// /// Deletes the document with the given ID. /// diff --git a/src/CouchDB.Driver/Translators/MemberExpressionTranslator.cs b/src/CouchDB.Driver/Translators/MemberExpressionTranslator.cs index 64e873f..73e128d 100644 --- a/src/CouchDB.Driver/Translators/MemberExpressionTranslator.cs +++ b/src/CouchDB.Driver/Translators/MemberExpressionTranslator.cs @@ -15,8 +15,8 @@ string GetPropertyName(MemberInfo memberInfo) var jsonPropertyAttributes = memberInfo.GetCustomAttributes(typeof(JsonPropertyAttribute), true); JsonPropertyAttribute jsonProperty = jsonPropertyAttributes.Length > 0 ? jsonPropertyAttributes[0] as JsonPropertyAttribute : null; - return jsonProperty != null ? - jsonProperty.PropertyName : + return jsonProperty != null ? + jsonProperty.PropertyName : _settings.PropertiesCase.Convert(memberInfo.Name); } @@ -38,4 +38,4 @@ string GetPropertyName(MemberInfo memberInfo) } } } -#pragma warning restore IDE0058 // Expression value is never used \ No newline at end of file +#pragma warning restore IDE0058 // Expression value is never used diff --git a/src/CouchDB.Driver/Types/CouchDocumentAttachment.cs b/src/CouchDB.Driver/Types/CouchDocumentAttachment.cs new file mode 100644 index 0000000..8fc59e1 --- /dev/null +++ b/src/CouchDB.Driver/Types/CouchDocumentAttachment.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.Serialization; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace CouchDB.Driver.Types +{ + public abstract class CouchDocumentAttachment : CouchDocument + { + [DataMember] + [JsonProperty("_attachments", NullValueHandling = NullValueHandling.Ignore)] + public Dictionary Attachments { get; set; } + } + + public class CouchDocumentAttachmentItem + { + [DataMember] + [JsonProperty("content_type", NullValueHandling = NullValueHandling.Ignore)] + public string ContentType { get; set; } + + [DataMember] + [JsonProperty("revpos", NullValueHandling = NullValueHandling.Ignore)] + public int RevPos { get; set; } + + [DataMember] + [JsonProperty("digest", NullValueHandling = NullValueHandling.Ignore)] + public string Digest { get; set; } + + [DataMember] + [JsonProperty("length", NullValueHandling = NullValueHandling.Ignore)] + public int Length { get; set; } + + [DataMember] + [JsonProperty("stub", NullValueHandling = NullValueHandling.Ignore)] + public bool Stub { get; set; } + + [DataMember] + public string FileTobeAttach { get; set; } + } +} From 8642f4d9e7b1f7ad4a25420c33df23117a7875a0 Mon Sep 17 00:00:00 2001 From: Amru Rosyada Date: Mon, 30 Dec 2019 22:24:47 +0700 Subject: [PATCH 4/4] Fix Add Document attachment --- src/CouchDB.Driver/CouchQueryProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CouchDB.Driver/CouchQueryProvider.cs b/src/CouchDB.Driver/CouchQueryProvider.cs index a341b5b..c38213b 100644 --- a/src/CouchDB.Driver/CouchQueryProvider.cs +++ b/src/CouchDB.Driver/CouchQueryProvider.cs @@ -85,7 +85,7 @@ public object GetCouchList(string body) { FindResult result = _flurlClient .Request(_connectionString) - .AppendPathSegments(Uri.EscapeDataString(_db), "_find") + .AppendPathSegments(_db, "_find") .WithHeader("Content-Type", "application/json") .PostStringAsync(body).ReceiveJson>() .SendRequest();