├── LoveSeat.IntegrationTest ├── Files │ ├── test_upload.txt │ └── martin.jpg ├── App.config ├── Properties │ └── AssemblyInfo.cs ├── PagingHelperTest.cs ├── NewtonSoftTests.cs ├── LoveSeat.IntegrationTest.csproj └── CouchClientTest.cs ├── Libraries ├── Moq │ ├── Moq.chm │ └── Moq.dll ├── LibCurlNet.dll ├── Newtonsoft.Json.dll └── nunit.framework.dll ├── LoveSeat ├── bin │ └── Debug │ │ ├── libcurl.dll │ │ └── LibCurlShim.dll ├── CouchResponse.cs ├── CouchUser.cs ├── Interfaces │ ├── IKeyOptions.cs │ ├── IListResult.cs │ ├── IViewOptions.cs │ ├── IViewResult.cs │ └── IDocumentDatabase.cs ├── CouchException.cs ├── Support │ ├── NullableDictionary.cs │ ├── ResponseExtensionMethods.cs │ ├── ReplicationOptions.cs │ ├── TtlDictionary.cs │ ├── CouchBase.cs │ ├── KeyOptions.cs │ └── CouchRequest.cs ├── CouchConfiguration.cs ├── ObjectSerializer.cs ├── Properties │ └── AssemblyInfo.cs ├── ListResult.cs ├── SecurityUtility.cs ├── Document.cs ├── ViewOptions.cs ├── PagingHelper.cs ├── ViewResult.cs ├── LoveSeat.csproj ├── CouchClient.cs └── CouchDatabase.cs ├── LoveSeat.Interfaces ├── IRepository.cs ├── IBaseObject.cs ├── IAuditableRecord.cs ├── Properties │ └── AssemblyInfo.cs └── LoveSeat.Interfaces.csproj ├── .gitignore ├── LoveSeat.Repository ├── AuditableRepository.cs ├── CouchRepository.cs ├── Properties │ └── AssemblyInfo.cs └── LoveSeat.Repositories.csproj ├── LICENSE ├── LoveSeat.sln └── README.md /LoveSeat.IntegrationTest/Files/test_upload.txt: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /Libraries/Moq/Moq.chm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orenmazor/LoveSeat/master/Libraries/Moq/Moq.chm -------------------------------------------------------------------------------- /Libraries/Moq/Moq.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orenmazor/LoveSeat/master/Libraries/Moq/Moq.dll -------------------------------------------------------------------------------- /Libraries/LibCurlNet.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orenmazor/LoveSeat/master/Libraries/LibCurlNet.dll -------------------------------------------------------------------------------- /Libraries/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orenmazor/LoveSeat/master/Libraries/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /Libraries/nunit.framework.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orenmazor/LoveSeat/master/Libraries/nunit.framework.dll -------------------------------------------------------------------------------- /LoveSeat/bin/Debug/libcurl.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orenmazor/LoveSeat/master/LoveSeat/bin/Debug/libcurl.dll -------------------------------------------------------------------------------- /LoveSeat/bin/Debug/LibCurlShim.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orenmazor/LoveSeat/master/LoveSeat/bin/Debug/LibCurlShim.dll -------------------------------------------------------------------------------- /LoveSeat.IntegrationTest/Files/martin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orenmazor/LoveSeat/master/LoveSeat.IntegrationTest/Files/martin.jpg -------------------------------------------------------------------------------- /LoveSeat.Interfaces/IRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LoveSeat.Interfaces; 3 | 4 | namespace LoveSeat.Interfaces 5 | { 6 | public interface IRepository where T : IBaseObject 7 | { 8 | void Save(T item); 9 | T Find(Guid id); 10 | } 11 | } -------------------------------------------------------------------------------- /LoveSeat.IntegrationTest/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LoveSeat.Interfaces/IBaseObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace LoveSeat.Interfaces 5 | { 6 | public interface IBaseObject 7 | { 8 | [JsonProperty("_id")] 9 | string Id { get; set; } 10 | [JsonProperty("_rev")] 11 | string Rev { get; set; } 12 | string Type { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LoveSeat/CouchResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Newtonsoft.Json.Linq; 6 | 7 | namespace LoveSeat { 8 | public class CouchResponse : JObject { 9 | public CouchResponse(JObject obj) : base(obj) 10 | { 11 | } 12 | public int StatusCode { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LoveSeat.Interfaces/IAuditableRecord.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LoveSeat.Interfaces; 3 | 4 | namespace Accounting.Domain 5 | { 6 | public interface IAuditableRecord : IBaseObject 7 | { 8 | DateTime CreatedAt { get; set; } 9 | DateTime LastModifiedAt { get; set; } 10 | string CreatedBy { get; set; } 11 | string LastModifiedBy { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #ignore thumbnails created by windows 3 | Thumbs.db 4 | #Ignore files build by Visual Studio 5 | *.obj 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.cache 20 | *.ilk 21 | *.log 22 | [Bb]in 23 | [Dd]ebug*/ 24 | *.lib 25 | *.sbr 26 | obj/ 27 | [Rr]elease*/ 28 | _ReSharper*/ 29 | [Tt]est[Rr]esult* 30 | *.orig 31 | LoveSeat.IntegrationTest/LoveSeat.IntegrationTest.pidb -------------------------------------------------------------------------------- /LoveSeat/CouchUser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json.Linq; 3 | 4 | namespace LoveSeat 5 | { 6 | public class CouchUser : Document 7 | { 8 | public CouchUser(JObject jobj) 9 | : base(jobj) 10 | { 11 | } 12 | 13 | public IEnumerable Roles 14 | { 15 | get 16 | { 17 | if (!this["roles"].HasValues) 18 | { 19 | yield return null; 20 | } 21 | foreach (var role in this["roles"].Values()) 22 | { 23 | yield return role.Value(); 24 | } 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /LoveSeat/Interfaces/IKeyOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Newtonsoft.Json.Linq; 6 | 7 | namespace LoveSeat.Interfaces 8 | { 9 | public interface IKeyOptions 10 | { 11 | string ToString(); 12 | void Insert(int index, JToken item); 13 | void RemoveAt(int index); 14 | void Add(JToken item); 15 | bool Remove(JToken item); 16 | int Count { get; } 17 | bool HasValues { get; } 18 | void Add(object content); 19 | void Add(params object[] items); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LoveSeat/CouchException.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace LoveSeat 4 | { 5 | public class CouchException : System.Exception 6 | { 7 | private readonly HttpWebRequest request; 8 | private readonly HttpWebResponse response; 9 | 10 | public CouchException(HttpWebRequest request, HttpWebResponse response, string mesg) : base(mesg) 11 | { 12 | this.request = request; 13 | this.response = response; 14 | } 15 | 16 | public HttpWebRequest Request { get { return request; } } 17 | public HttpWebResponse Response { get { return response; } } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LoveSeat/Interfaces/IListResult.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace LoveSeat.Interfaces 4 | { 5 | public interface IListResult: System.IEquatable 6 | { 7 | /// 8 | /// Typically won't be needed Provided for debuging assitance 9 | /// 10 | HttpWebRequest Request { get; } 11 | /// 12 | /// Typically won't be needed Provided for debuging assitance 13 | /// 14 | HttpWebResponse Response { get; } 15 | HttpStatusCode StatusCode { get; } 16 | string Etag { get; } 17 | string RawString { get; } 18 | 19 | } 20 | } -------------------------------------------------------------------------------- /LoveSeat.Repository/AuditableRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Accounting.Domain; 3 | using LoveSeat.Interfaces; 4 | 5 | namespace LoveSeat.Repositories 6 | { 7 | public abstract class AuditableRepository : CouchRepository where T : IAuditableRecord 8 | { 9 | protected AuditableRepository(CouchDatabase db) : base(db) 10 | { 11 | } 12 | public override void Save(T item) 13 | { 14 | if (item.Rev == null) 15 | item.CreatedAt = DateTime.Now; 16 | item.LastModifiedAt = DateTime.Now; 17 | if (item.Id == string.Empty) 18 | item.Id = Guid.NewGuid().ToString(); 19 | base.Save(item); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /LoveSeat/Interfaces/IViewOptions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace LoveSeat.Interfaces 4 | { 5 | public interface IViewOptions 6 | { 7 | IKeyOptions Key { get; set; } 8 | IKeyOptions StartKey { get; set; } 9 | IKeyOptions EndKey { get; set; } 10 | int? Limit { get; set; } 11 | int? Skip { get; set; } 12 | bool? Reduce { get; set; } 13 | bool? Group { get; set; } 14 | bool? IncludeDocs { get; set; } 15 | bool? InclusiveEnd { get; set; } 16 | int? GroupLevel { get; set; } 17 | bool? Descending { get; set; } 18 | bool? Stale { get; set; } 19 | string StaleOption { get; set; } 20 | string Etag { get; set; } 21 | string StartKeyDocId { get; set; } 22 | string EndKeyDocId { get; set; } 23 | string ToString(); 24 | } 25 | } -------------------------------------------------------------------------------- /LoveSeat/Support/NullableDictionary.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace LoveSeat.Support 4 | { 5 | public class CouchDictionary : NullableDictionary 6 | { 7 | } 8 | 9 | public class NullableDictionary 10 | { 11 | private readonly Dictionary dictionary = new Dictionary(); 12 | public NullableDictionary() 13 | { 14 | } 15 | public void Add(X key, Y value) 16 | { 17 | dictionary.Add(key, value); 18 | } 19 | public Y this[X key] 20 | { 21 | get 22 | { 23 | if (dictionary.ContainsKey(key)) 24 | { 25 | return dictionary[key]; 26 | } 27 | else 28 | { 29 | return default(Y); 30 | } 31 | 32 | } 33 | } 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /LoveSeat/CouchConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Newtonsoft.Json.Linq; 4 | 5 | namespace LoveSeat 6 | { 7 | public class CouchConfiguration 8 | { 9 | public CouchConfiguration() 10 | { 11 | } 12 | public CouchConfiguration(string pathToCouchAppRc) 13 | { 14 | var fileContents = File.ReadAllText(pathToCouchAppRc); 15 | var jboj = JObject.Parse(fileContents); 16 | var tmp= jboj["env"]["default"].Value("db"); 17 | var uri = new Uri(tmp); 18 | Host = uri.Host; 19 | Port = uri.Port; 20 | var userInfo = uri.UserInfo.Split(':'); 21 | User = userInfo[0]; 22 | Password = userInfo[1]; 23 | Database = uri.PathAndQuery; 24 | } 25 | 26 | public string Database { get; set; } 27 | public string User { get; set; } 28 | public string Password { get; set; } 29 | public int Port { get; set; } 30 | public string Host { get; set; } 31 | } 32 | } -------------------------------------------------------------------------------- /LoveSeat/Support/ResponseExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Net; 3 | using Newtonsoft.Json.Linq; 4 | 5 | namespace LoveSeat.Support 6 | { 7 | public static class ResponseExtensionMethods 8 | { 9 | public static Document GetCouchDocument(this HttpWebResponse response) 10 | { 11 | var jobj = JObject.Parse(response.GetResponseString()); 12 | return new Document(jobj); 13 | } 14 | 15 | public static string GetResponseString(this HttpWebResponse response) 16 | { 17 | using (var stream = response.GetResponseStream()) 18 | { 19 | using (var streamReader = new StreamReader(stream)) 20 | { 21 | var result = streamReader.ReadToEnd(); 22 | if (string.IsNullOrEmpty(result)) return null; 23 | return result; 24 | } 25 | } 26 | } 27 | public static CouchResponse GetJObject(this HttpWebResponse response) 28 | { 29 | var resp = new CouchResponse(JObject.Parse(response.GetResponseString())); 30 | resp.StatusCode = (int)response.StatusCode; 31 | return resp; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /LoveSeat.Repository/CouchRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LoveSeat.Interfaces; 3 | 4 | namespace LoveSeat.Repositories 5 | { 6 | public class CouchRepository : IRepository where T : IBaseObject 7 | { 8 | protected readonly CouchDatabase db = null; 9 | public CouchRepository(CouchDatabase db) 10 | { 11 | this.db = db; 12 | } 13 | 14 | public virtual void Save(T item) 15 | { 16 | if (item.Id == "") 17 | item.Id = Guid.NewGuid().ToString(); 18 | var doc = new Document(item); 19 | db.SaveDocument(doc); 20 | } 21 | 22 | public virtual T Find(Guid id) 23 | { 24 | return db.GetDocument(id.ToString()); 25 | } 26 | 27 | /// 28 | /// Repository methods don't have the business validation. Use the service methods to enforce. 29 | /// 30 | /// 31 | public virtual void Delete(T obj) 32 | { 33 | db.DeleteDocument(obj.Id.ToString(), obj.Rev); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /LoveSeat/ObjectSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Converters; 5 | using Newtonsoft.Json.Serialization; 6 | 7 | namespace LoveSeat 8 | { 9 | public interface IObjectSerializer 10 | { 11 | T Deserialize(string json); 12 | string Serialize(T obj); 13 | } 14 | public class DefaultSerializer : IObjectSerializer 15 | { 16 | protected readonly JsonSerializerSettings settings; 17 | public DefaultSerializer() { 18 | settings = new JsonSerializerSettings(); 19 | var converters = new List { new IsoDateTimeConverter() }; 20 | settings.Converters = converters; 21 | settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); 22 | settings.NullValueHandling = NullValueHandling.Ignore; 23 | } 24 | 25 | public T Deserialize(string json) 26 | { 27 | return JsonConvert.DeserializeObject(json, settings); 28 | } 29 | 30 | public string Serialize(T obj) 31 | { 32 | return JsonConvert.SerializeObject(obj, Formatting.Indented, settings); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /LoveSeat/Support/ReplicationOptions.cs: -------------------------------------------------------------------------------- 1 | namespace LoveSeat.Support 2 | { 3 | public class ReplicationOptions 4 | { 5 | private readonly string source; 6 | private readonly string target; 7 | private readonly bool continuous; 8 | private readonly string query_params; 9 | 10 | public ReplicationOptions(string source, string target, bool continuous, string query_params) 11 | { 12 | this.source = source; 13 | this.target = target; 14 | this.continuous = continuous; 15 | this.query_params = query_params; 16 | } 17 | public ReplicationOptions(string source, string target, bool continuous) : this(source, target, continuous, null) 18 | { 19 | } 20 | public ReplicationOptions(string source, string target) : this(source, target, false) 21 | { 22 | } 23 | 24 | public override string ToString() 25 | { 26 | string result = @"{ ""source"": ""%source%"", ""target"" : ""%target%"", ""continuous"" : %continuous% " + 27 | (string.IsNullOrEmpty(query_params) ? "" : @", ""query_params"" : %query_params%") + 28 | " }"; 29 | result = result.Replace("%source%", source).Replace("%target%", target).Replace("%query_params%", query_params).Replace("%continuous%", continuous.ToString().ToLower()); 30 | return result; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT X11 License covers libraries in the LoveSeat project. 2 | Some third party libraries come from different projects, and 3 | are licensed under their own terms. 4 | 5 | Copyright (c) 2010 Martin Murphy. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | "Software"), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LoveSeat/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("LoveSeat")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("LoveSeat")] 13 | [assembly: AssemblyCopyright("Copyright © 2010")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6f735d6b-f3e8-4be3-95b0-8659ba3c8178")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LoveSeat.IntegrationTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("LoveSeat.IntegrationTest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("LoveSeat.IntegrationTest")] 13 | [assembly: AssemblyCopyright("Copyright © 2010")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("9919df81-a6c1-4637-8a8c-f8c694f9543f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LoveSeat.Interfaces/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("LoveSeat.Interfaces")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("LoveSeat.Interfaces")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2011")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("07ab2e6c-e8e5-490f-943a-3c8f568031cd")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LoveSeat.Repository/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("LoveSeat.Repository")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("LoveSeat.Repository")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2011")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("c366c847-c326-451b-9e15-c5e2bc82620d")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LoveSeat/Interfaces/IViewResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using Newtonsoft.Json.Linq; 4 | 5 | namespace LoveSeat.Interfaces 6 | { 7 | public interface IViewResult : System.IEquatable 8 | { 9 | JObject Json { get; } 10 | 11 | /// 12 | /// Typically won't be needed. Provided for debuging assistance 13 | /// 14 | HttpWebRequest Request { get; } 15 | 16 | /// 17 | /// Typically won't be needed. Provided for debugging assistance 18 | /// 19 | HttpWebResponse Response { get; } 20 | 21 | HttpStatusCode StatusCode { get; } 22 | string Etag { get; } 23 | int TotalRows { get; } 24 | int OffSet { get; } 25 | IEnumerable Rows { get; } 26 | 27 | /// 28 | /// Only populated when IncludeDocs is true 29 | /// 30 | IEnumerable Docs { get; } 31 | 32 | /// 33 | /// An IEnumerable of strings insteda of the IEnumerable of JTokens 34 | /// 35 | IEnumerable RawRows { get; } 36 | 37 | IEnumerable RawValues { get; } 38 | IEnumerable RawDocs { get; } 39 | string RawString { get; } 40 | 41 | /// 42 | /// Provides a formatted version of the json returned from this Result. (Avoid this method in favor of RawString as it's much more performant) 43 | /// 44 | string FormattedResponse { get; } 45 | 46 | string ToString(); 47 | } 48 | } -------------------------------------------------------------------------------- /LoveSeat.IntegrationTest/PagingHelperTest.cs: -------------------------------------------------------------------------------- 1 | using LoveSeat.Interfaces; 2 | using LoveSeat.Support; 3 | using Moq; 4 | using NUnit.Framework; 5 | 6 | namespace LoveSeat.IntegrationTest 7 | { 8 | [TestFixture] 9 | public class PagingHelperTest 10 | { 11 | [Test] 12 | public void Should_Show_Previous_If_OffSet_Not_Equal_Zero() 13 | { 14 | var result = new Mock(); 15 | result.ExpectGet(x => x.OffSet).Returns(1); 16 | var options = new ViewOptions(); 17 | var model = new PageableModel(); 18 | model.UpdatePaging(options, result.Object); 19 | Assert.IsTrue(model.ShowPrev); 20 | } 21 | [Test] 22 | public void Should_Not_Show_Previous_If_Offset_Equal_Zero() 23 | { 24 | var result = new Mock(); 25 | result.ExpectGet(x => x.OffSet).Returns(0); 26 | var options = new ViewOptions(); 27 | var model = new PageableModel(); 28 | model.UpdatePaging(options, result.Object); 29 | Assert.IsFalse(model.ShowPrev); 30 | } 31 | [Test] 32 | public void Should_Show_Next_Unless_Offset_Plus_Limit_Gte_Count() 33 | { 34 | var result = new Mock(); 35 | result.ExpectGet(x => x.OffSet).Returns(5); 36 | var options = new ViewOptions(); 37 | options.Limit = 5; 38 | result.ExpectGet(x => x.TotalRows).Returns(10); 39 | var model = new PageableModel(); 40 | model.UpdatePaging(options, result.Object); 41 | Assert.IsFalse(model.ShowNext); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /LoveSeat/Support/TtlDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LoveSeat.Support 7 | { 8 | internal class TtlDictionary 9 | { 10 | private readonly Dictionary items; 11 | private readonly Dictionary expiration; 12 | 13 | public TtlDictionary() 14 | { 15 | items = new Dictionary(); 16 | expiration = new Dictionary(); 17 | } 18 | 19 | public void Add(X key, Y value, TimeSpan ttl) 20 | { 21 | if (items.ContainsKey(key)) 22 | { 23 | items.Remove(key); 24 | expiration.Remove(key); 25 | } 26 | items.Add(key, value); 27 | expiration.Add(key, DateTime.Now.Add(ttl)); 28 | RemoveExpiredKeys(); 29 | } 30 | 31 | private void RemoveExpiredKeys() 32 | { 33 | foreach (var key in expiration.Keys) 34 | { 35 | if (expiration[key] < DateTime.Now) 36 | { 37 | expiration.Remove(key); 38 | items.Remove(key); 39 | } 40 | } 41 | } 42 | public Y this[X key] 43 | { 44 | get 45 | { 46 | if (expiration.ContainsKey(key) && expiration[key] > DateTime.Now) 47 | { 48 | return items[key]; 49 | } 50 | else 51 | { 52 | return default(Y); 53 | } 54 | } 55 | } 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /LoveSeat/ListResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using LoveSeat.Interfaces; 4 | using LoveSeat.Support; 5 | 6 | namespace LoveSeat 7 | { 8 | public class ListResult : IListResult 9 | { 10 | private readonly HttpWebRequest request; 11 | private readonly HttpWebResponse response; 12 | 13 | public ListResult(HttpWebRequest request , HttpWebResponse response) 14 | { 15 | this.request = request; 16 | this.response = response; 17 | } 18 | 19 | public HttpWebRequest Request 20 | { 21 | get { return request; } 22 | } 23 | 24 | public HttpWebResponse Response 25 | { 26 | get { return response; } 27 | } 28 | 29 | public HttpStatusCode StatusCode 30 | { 31 | get { return Response.StatusCode; } 32 | } 33 | 34 | public string Etag 35 | { 36 | get { return Response.Headers["ETag"]; } 37 | } 38 | 39 | public string RawString 40 | { 41 | get { return Response.GetResponseString(); } 42 | } 43 | 44 | public bool Equals(IListResult other) 45 | { 46 | if (other == null) 47 | return false; 48 | 49 | if (string.IsNullOrEmpty(other.Etag)) 50 | return false; 51 | 52 | return other.Etag == Etag; 53 | } 54 | 55 | public override bool Equals(object obj) 56 | { 57 | return Equals(obj as IListResult); 58 | } 59 | 60 | public override int GetHashCode() 61 | { 62 | if (string.IsNullOrEmpty(Etag)) 63 | return base.GetHashCode(); 64 | 65 | return Etag.GetHashCode(); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /LoveSeat.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoveSeat", "LoveSeat\LoveSeat.csproj", "{AF987164-0100-4A90-A767-D473F882EA8B}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoveSeat.IntegrationTest", "LoveSeat.IntegrationTest\LoveSeat.IntegrationTest.csproj", "{7496CEA9-442C-4468-8350-54F34693F64A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoveSeat.Repositories", "LoveSeat.Repository\LoveSeat.Repositories.csproj", "{95A18B36-B97A-4A39-8920-B245ACD12AAD}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoveSeat.Interfaces", "LoveSeat.Interfaces\LoveSeat.Interfaces.csproj", "{18833FC5-9145-451D-82D4-41A1886B0BE7}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {AF987164-0100-4A90-A767-D473F882EA8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {AF987164-0100-4A90-A767-D473F882EA8B}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {AF987164-0100-4A90-A767-D473F882EA8B}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {AF987164-0100-4A90-A767-D473F882EA8B}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {7496CEA9-442C-4468-8350-54F34693F64A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {7496CEA9-442C-4468-8350-54F34693F64A}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {7496CEA9-442C-4468-8350-54F34693F64A}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {7496CEA9-442C-4468-8350-54F34693F64A}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {95A18B36-B97A-4A39-8920-B245ACD12AAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {95A18B36-B97A-4A39-8920-B245ACD12AAD}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {95A18B36-B97A-4A39-8920-B245ACD12AAD}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {95A18B36-B97A-4A39-8920-B245ACD12AAD}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {18833FC5-9145-451D-82D4-41A1886B0BE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {18833FC5-9145-451D-82D4-41A1886B0BE7}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {18833FC5-9145-451D-82D4-41A1886B0BE7}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {18833FC5-9145-451D-82D4-41A1886B0BE7}.Release|Any CPU.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /LoveSeat.IntegrationTest/NewtonSoftTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using NUnit.Framework; 5 | 6 | namespace LoveSeat.IntegrationTest 7 | { 8 | [TestFixture] 9 | public class NewtonSoftTests 10 | { 11 | [Test] 12 | public void JArray_Should_Support_Complex_Types() 13 | { 14 | var arry = new JArray(); 15 | arry.Add(1); 16 | arry.Add("abc"); 17 | var result = arry.ToString(Formatting.None); 18 | Assert.AreEqual("[1,\"abc\"]", result); 19 | } 20 | [Test] 21 | public void KeyOptions_Should_Produce_Single_Value_For_A_Single_Array() 22 | { 23 | var arry = new KeyOptions(); 24 | arry.Add(1); 25 | var result = arry.ToString(); 26 | Assert.AreEqual("1", result); 27 | 28 | } 29 | [Test] 30 | public void KeyOptions_Should_Produce_A_Complex_Array_For_Multiple_Values() 31 | { 32 | var arry = new KeyOptions(); 33 | arry.Add(1); 34 | arry.Add(new DateTime(2011,1,1)); 35 | var result = arry.ToString(); 36 | Assert.AreEqual("[1,%222011-01-01T00%3a00%3a00%22]", result); 37 | } 38 | 39 | [Test] 40 | public void KeyOptions_Should_Produce_Squirley_Brackets_for_CouchValueMax() 41 | { 42 | var arry = new KeyOptions(); 43 | arry.Add(CouchValue.MaxValue); 44 | arry.Add(1); 45 | var result = arry.ToString(); 46 | Assert.AreEqual("[{},1]", result); 47 | } 48 | [Test] 49 | public void KeyOptions_Should_Produce_Squirley_Brackets_for_CouchValueMax2() 50 | { 51 | var arry = new KeyOptions(CouchValue.MaxValue, 1); 52 | var result = arry.ToString(); 53 | Assert.AreEqual("[{},1]", result); 54 | } 55 | 56 | 57 | [Test] 58 | public void KeyOptions_Should_Produce_IsoTime() 59 | { 60 | var arry = new KeyOptions(); 61 | arry.Add(CouchValue.MinValue); 62 | arry.Add(new DateTime(2011,1,1)); 63 | var result = arry.ToString(); 64 | Assert.AreEqual("[null,%222011-01-01T00%3a00%3a00%22]", result); 65 | 66 | } 67 | 68 | [Test] 69 | public void KeyOptions_Constructor_Fails() 70 | { 71 | var arry = new KeyOptions(CouchValue.MinValue); 72 | var result = arry.ToString(); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /LoveSeat.Interfaces/LoveSeat.Interfaces.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {18833FC5-9145-451D-82D4-41A1886B0BE7} 9 | Library 10 | Properties 11 | LoveSeat.Interfaces 12 | LoveSeat.Interfaces 13 | v3.5 14 | 512 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | False 43 | ..\Libraries\Newtonsoft.Json.dll 44 | 45 | 46 | 47 | 54 | -------------------------------------------------------------------------------- /LoveSeat.Repository/LoveSeat.Repositories.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {95A18B36-B97A-4A39-8920-B245ACD12AAD} 9 | Library 10 | Properties 11 | LoveSeat.Repositories 12 | LoveSeat.Repositories 13 | v3.5 14 | 512 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\Libraries\Newtonsoft.Json.dll 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | {18833FC5-9145-451D-82D4-41A1886B0BE7} 50 | LoveSeat.Interfaces 51 | 52 | 53 | {AF987164-0100-4A90-A767-D473F882EA8B} 54 | LoveSeat 55 | 56 | 57 | 58 | 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LoveSeat 2 | ======== 3 | 4 | Love Seat is a simply architected [CouchDB](http://couchdb.apache.org/) C# client with the 5 | intent to abstract away just enough so that it's easy to use, but not enough so that you 6 | don't know what's going on. 7 | 8 | 9 | Tested compatibility 10 | ==================== 11 | 12 | * CouchDB 1.0.1 13 | * .NET Framework 4.0 or Mono 2.9 (compiled master branch from Nov 20 2010) 14 | 15 | 16 | LoveSeat usage 17 | ============== 18 | 19 | **Basics** 20 | 21 | // assumes localhost:5984 with no credentials if constructor is left blank 22 | var client = new CouchClient(); 23 | var db= client.GetDatabase("Northwind"); 24 | 25 | // set default design doc (not required) 26 | db.SetDefaultDesignDoc("docs"); 27 | 28 | // get document by ID 29 | Document myDoc = db.GetDocument("12345"); 30 | 31 | // get document by ID (strongly typed POCO version) 32 | MyObject myObj = db.GetDocument("12345"); 33 | 34 | **Simple view results** 35 | 36 | // get view results 37 | var results = db.View("view_name"); 38 | 39 | // get view results with parameters 40 | var options = new ViewOptions(); 41 | options.StartKey.Add("Atlanta"); 42 | options.EndKey.Add("Washington"); 43 | 44 | var results = db.View("view_name", options); 45 | 46 | // loop through strongly typed results 47 | foreach (var item in results.Items){ 48 | // do something 49 | 50 | } 51 | 52 | **Generating more complex view parameters** 53 | 54 | var options = new ViewOptions(); 55 | // generate ["foo"] startkey parameter 56 | options.StartKey.Add("foo"); 57 | // generate ["foo",{},{}] endkey parameter 58 | options.EndKey.Add("foo", CouchValue.Empty, CouchValue.Empty); 59 | 60 | var results = db.View("view_name", options); 61 | 62 | // loop through strongly typed results 63 | foreach (var item in results.Items){ 64 | // do something 65 | 66 | } 67 | 68 | **Customized view key parameters** 69 | 70 | Assuming that view keys have complex structure, for example: 71 | 72 | ["johny", ["work", "programming"]] 73 | 74 | ["joe", ["programming"]] 75 | 76 | ["johny", ["work"]] 77 | 78 | using Newtonsoft.Json.Linq; 79 | 80 | ... 81 | 82 | var options = new ViewOptions(); 83 | options.StartKey.Add(new JRaw("[\"johny\",[\"work\"]")); 84 | options.EndKey.Add(new JRaw("[\"johny\",[\"work\",{}]]")); 85 | 86 | var results = db.View("view_name", options); 87 | 88 | foreach (var item in results.Items){ 89 | // do something 90 | 91 | } 92 | 93 | This example will return only rows where first key contains "johny" and second key 94 | contains "work". 95 | 96 | **Get the results of a List** 97 | 98 | var results = db.List("list_name") 99 | 100 | LoveSeat supports replication and user management off of the CouchClient as well. Enjoy! 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /LoveSeat/Support/CouchBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace LoveSeat.Support 5 | { 6 | public abstract class CouchBase 7 | { 8 | protected readonly string username; 9 | protected readonly string password; 10 | protected readonly AuthenticationType authType; 11 | protected string baseUri; 12 | private TtlDictionary cookiestore = new TtlDictionary(); 13 | 14 | protected CouchBase() 15 | { 16 | throw new Exception("Should not be used."); 17 | } 18 | protected CouchBase(string username, string password, AuthenticationType aT) 19 | { 20 | this.username = username; 21 | this.password = password; 22 | this.authType = aT; 23 | } 24 | public static bool Authenticate(string baseUri, string userName, string password) 25 | { 26 | if (!baseUri.Contains("http://")) 27 | baseUri = "http://" + baseUri; 28 | var request = new CouchRequest(baseUri + "/_session"); 29 | request.Timeout = 3000; 30 | var response = request.Post() 31 | .ContentType("application/x-www-form-urlencoded") 32 | .Data("name=" + userName + "&password=" + password) 33 | .GetResponse(); 34 | return response.StatusCode == HttpStatusCode.OK; 35 | } 36 | public Cookie GetSession() { 37 | var authCookie = cookiestore["authcookie"]; 38 | 39 | if (authCookie != null) 40 | return authCookie; 41 | 42 | if (string.IsNullOrEmpty(username)) return null; 43 | var request = new CouchRequest(baseUri + "_session"); 44 | request.GetRequest().Headers.Add("Authorization:Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(username + ":" + password))); 45 | var response = request.Post() 46 | .Form() 47 | .Data("name=" + username + "&password=" + password) 48 | .GetResponse(); 49 | 50 | var header = response.Headers.Get("Set-Cookie"); 51 | if (header != null) { 52 | var parts = header.Split(';')[0].Split('='); 53 | authCookie = new Cookie(parts[0], parts[1]); 54 | authCookie.Domain = response.Server; 55 | cookiestore.Add("authcookie", authCookie, TimeSpan.FromMinutes(9)); 56 | } 57 | return authCookie; 58 | } 59 | protected CouchRequest GetRequest(string uri) 60 | { 61 | return GetRequest(uri, null); 62 | } 63 | 64 | protected CouchRequest GetRequest(string uri, string etag) 65 | { 66 | if (AuthenticationType.Cookie == this.authType) 67 | { 68 | return new CouchRequest(uri, GetSession(), etag); 69 | } 70 | else if (AuthenticationType.Basic == this.authType) //Basic Authentication 71 | { 72 | return new CouchRequest(uri, username, password); 73 | } 74 | else //default Cookie 75 | { 76 | return new CouchRequest(uri, GetSession(), etag); 77 | } 78 | } 79 | 80 | 81 | 82 | 83 | } 84 | } -------------------------------------------------------------------------------- /LoveSeat/Support/KeyOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Web; 6 | using LoveSeat.Interfaces; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Converters; 9 | using Newtonsoft.Json.Linq; 10 | 11 | namespace LoveSeat 12 | { 13 | public class KeyOptions : IKeyOptions 14 | { 15 | private JArray objects = new JArray(); 16 | 17 | public KeyOptions(params object[] objects) 18 | { 19 | foreach (var obj in objects) 20 | { 21 | this.Add(obj); 22 | } 23 | } 24 | 25 | public KeyOptions(JArray jArray) 26 | { 27 | this.objects = jArray; 28 | } 29 | 30 | public override string ToString() 31 | { 32 | if (objects.Count == 0) 33 | { 34 | return ""; 35 | } 36 | if (objects.Count == 1) 37 | { 38 | return HttpUtility.UrlEncode(objects[0].ToString(Formatting.None, new IsoDateTimeConverter())); 39 | } 40 | 41 | string result = "["; 42 | bool first = true; 43 | foreach (var item in objects) 44 | { 45 | if (!first) 46 | result += ","; 47 | first = false; 48 | if(item.ToString().Equals("{}")) 49 | result += item.ToString(Formatting.None, new IsoDateTimeConverter()); 50 | else 51 | result += HttpUtility.UrlEncode(item.ToString(Formatting.None, new IsoDateTimeConverter())); 52 | } 53 | result += "]"; 54 | return result; 55 | } 56 | 57 | public void Insert(int index, JToken item) 58 | { 59 | objects.Insert(index, item); 60 | } 61 | 62 | public void RemoveAt(int index) 63 | { 64 | objects.RemoveAt(index); 65 | } 66 | 67 | public bool Remove(JToken item) 68 | { 69 | return objects.Remove(item); 70 | } 71 | 72 | public int Count 73 | { 74 | get { return objects.Count; } 75 | } 76 | 77 | public bool HasValues 78 | { 79 | get { return objects.Count > 0; } 80 | } 81 | 82 | public void Add(JToken item) 83 | { 84 | objects.Add(item); 85 | } 86 | 87 | public void Add(object item) 88 | { 89 | if (item == CouchValue.MaxValue) 90 | { 91 | objects.Add(new JRaw("{}")); 92 | return; 93 | } 94 | objects.Add(item); 95 | } 96 | 97 | public void Add(params object[] items) 98 | { 99 | foreach (var item in items) 100 | { 101 | Add(item); 102 | } 103 | } 104 | } 105 | public static class CouchValue 106 | { 107 | static object value = new object(); 108 | public static object MaxValue 109 | { 110 | get 111 | { 112 | return value; 113 | } 114 | } 115 | 116 | public static object MinValue 117 | { 118 | get { return null; } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /LoveSeat/SecurityUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Security.Cryptography; 4 | 5 | /// 6 | /// Class generates hashes for SHA1 hashing algorithm. 7 | /// 8 | 9 | 10 | namespace LoveSeat 11 | { 12 | public class HashIt 13 | { 14 | /// 15 | /// Computes SHA1 hash for plain text and returns a 16 | /// base64-encoded result. Before the hash is computed, a random salt of 25 characters. 17 | /// is generated and appended to the plain text. The salt is passed by 18 | /// reference. 19 | /// 20 | public static string ComputeHash(string plainText, ref string salt) 21 | { 22 | // If salt is not specified, generate it on the fly. 23 | byte[] saltBytes = new byte[0]; 24 | 25 | // Convert plain text into a byte array. 26 | byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText); 27 | 28 | // Generate salt of 25 characters 29 | salt = RandomString(25); 30 | 31 | // Encode with UTF8 32 | saltBytes = Encoding.UTF8.GetBytes(salt); 33 | 34 | // Allocate array, which will hold plain text and salt. 35 | byte[] plainTextWithSaltBytes = 36 | new byte[plainTextBytes.Length + saltBytes.Length]; 37 | 38 | // Copy plain text bytes into resulting array. 39 | for (int i = 0; i < plainTextBytes.Length; i++) 40 | plainTextWithSaltBytes[i] = plainTextBytes[i]; 41 | 42 | // Append salt bytes to the resulting array. 43 | for (int i = 0; i < saltBytes.Length; i++) 44 | plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i]; 45 | 46 | // Current versions are SHA1 for hashed password 47 | HashAlgorithm hash; 48 | hash = new SHA1Managed(); 49 | 50 | // Compute hash value of our plain text with appended salt. 51 | byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes); 52 | 53 | // Convert result into a hexadecimal 54 | string hashValue = ByteArrayToString(hashBytes); 55 | 56 | // Return the result. 57 | return hashValue; 58 | } 59 | 60 | /// 61 | /// Build random string 62 | /// 63 | /// 64 | /// 65 | public static string RandomString(int size) 66 | { 67 | 68 | Random rnd = new Random(); 69 | StringBuilder builder = new StringBuilder(); 70 | char ch; 71 | for (int i = 0; i < size; i++) 72 | { 73 | ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * rnd.NextDouble() + 65))); 74 | builder.Append(ch); 75 | } 76 | 77 | return builder.ToString(); 78 | } 79 | 80 | /// 81 | /// Convert ByteArray to String 82 | /// 83 | /// 84 | /// 85 | public static string ByteArrayToString(byte[] ba) 86 | { 87 | StringBuilder hex = new StringBuilder(ba.Length * 2); 88 | foreach (byte b in ba) 89 | hex.AppendFormat("{0:x2}", b); 90 | return hex.ToString(); 91 | } 92 | 93 | } 94 | } -------------------------------------------------------------------------------- /LoveSeat.IntegrationTest/LoveSeat.IntegrationTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {7496CEA9-442C-4468-8350-54F34693F64A} 9 | Library 10 | Properties 11 | LoveSeat.IntegrationTest 12 | LoveSeat.IntegrationTest 13 | v3.5 14 | 512 15 | 16 | 17 | 3.5 18 | 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | x86 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | ..\Libraries\Moq\Moq.dll 41 | 42 | 43 | False 44 | ..\Libraries\Newtonsoft.Json.dll 45 | 46 | 47 | False 48 | ..\Libraries\nunit.framework.dll 49 | 50 | 51 | 52 | 53 | 3.5 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | {18833FC5-9145-451D-82D4-41A1886B0BE7} 65 | LoveSeat.Interfaces 66 | 67 | 68 | {AF987164-0100-4A90-A767-D473F882EA8B} 69 | LoveSeat 70 | 71 | 72 | 73 | 74 | Designer 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 89 | -------------------------------------------------------------------------------- /LoveSeat/Document.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using LoveSeat.Interfaces; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | 8 | namespace LoveSeat 9 | { 10 | public class Document : Document 11 | { 12 | private static IObjectSerializer objectSerializer = new DefaultSerializer(); 13 | 14 | public Document(T obj) : base(objectSerializer.Serialize(obj)) { 15 | } 16 | public Document(T obj, IObjectSerializer objectSerializer) : base(objectSerializer.Serialize(obj)) 17 | { 18 | } 19 | } 20 | 21 | #region Bulk documents 22 | 23 | /// 24 | /// Class containing list of documents for bulk loading multiple documents with /all_docs 25 | /// 26 | public class Documents 27 | { 28 | public Documents() 29 | { 30 | Values = new List(); 31 | } 32 | 33 | [JsonProperty("docs")] 34 | public List Values { get; set; } 35 | } 36 | 37 | /// 38 | /// Class containing list of keys for fetching multiple documents with /all_docs 39 | /// 40 | public class Keys 41 | { 42 | public Keys() 43 | { 44 | Values = new List(); 45 | } 46 | 47 | [JsonProperty("keys")] 48 | public List Values { get; set; } 49 | 50 | } 51 | 52 | public class BulkDocumentResponses : List 53 | { } 54 | 55 | public class BulkDocumentResponse 56 | { 57 | [JsonProperty("id")] 58 | public string Id { get; set; } 59 | 60 | [JsonProperty("rev")] 61 | public string Rev { get; set; } 62 | 63 | [JsonProperty("error")] 64 | public string Error {get;set;} 65 | 66 | [JsonProperty("reason")] 67 | public string Reason { get; set; } 68 | } 69 | 70 | #endregion 71 | 72 | 73 | 74 | public class Document : JObject, IBaseObject 75 | { 76 | [JsonIgnore] 77 | public string Id 78 | { 79 | get { 80 | JToken id; 81 | return this.TryGetValue("_id", out id) ? id.ToString() : null; 82 | } 83 | set { this["_id"] = value; } 84 | } 85 | 86 | [JsonIgnore] 87 | public string Rev { 88 | get { 89 | JToken rev; 90 | return this.TryGetValue("_rev", out rev) ? rev.ToString() : null; 91 | } 92 | set { this["_rev"] = value; } 93 | } 94 | 95 | public string Type { get; private set; } 96 | 97 | protected Document() 98 | { 99 | } 100 | public Document(JObject jobj) 101 | : base(jobj) 102 | { 103 | JToken tmp; 104 | if (jobj.TryGetValue("id", out tmp)) 105 | this.Id = tmp.Value(); 106 | if (jobj.TryGetValue("rev", out tmp)) 107 | this.Rev = tmp.Value(); 108 | if (jobj.TryGetValue("_id", out tmp)) 109 | this.Id = tmp.Value(); 110 | if (jobj.TryGetValue("_rev", out tmp)) 111 | this.Rev = tmp.Value(); 112 | } 113 | public Document(string json) 114 | : base(JObject.Parse(json)) 115 | { 116 | } 117 | public bool HasAttachment 118 | { 119 | get { return this["_attachments"] != null; } 120 | } 121 | 122 | public IEnumerable GetAttachmentNames() 123 | { 124 | var attachment = this["_attachments"]; 125 | if (attachment == null) return null; 126 | return attachment.Select(x => x.Value().Name); 127 | } 128 | 129 | } 130 | } -------------------------------------------------------------------------------- /LoveSeat/ViewOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using LoveSeat.Interfaces; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Linq; 8 | using Newtonsoft.Json.Serialization; 9 | 10 | namespace LoveSeat 11 | { 12 | public class ViewOptions : IViewOptions 13 | { 14 | public ViewOptions() 15 | { 16 | Key = new KeyOptions(); 17 | StartKey = new KeyOptions(); 18 | EndKey = new KeyOptions(); 19 | } 20 | /// 21 | /// If you have a complex object as a string set this using a JRaw object() 22 | /// 23 | public IKeyOptions Key { get; set; } 24 | public IEnumerable Keys { get; set; } 25 | /// 26 | /// If you have a complex object as a string set this using a JRaw object() 27 | /// 28 | public IKeyOptions StartKey { get; set; } 29 | public string StartKeyDocId { get; set; } 30 | /// 31 | /// If you have a complex object as a string set this using a JRaw object() 32 | /// 33 | public IKeyOptions EndKey { get; set; } 34 | public string EndKeyDocId { get; set; } 35 | public int? Limit { get; set; } 36 | public int? Skip { get; set; } 37 | public bool? Reduce { get; set; } 38 | public bool? Group { get; set; } 39 | public bool? IncludeDocs { get; set; } 40 | public bool? InclusiveEnd { get; set; } 41 | public int? GroupLevel { get; set; } 42 | public bool? Descending { get; set; } 43 | public bool? Stale { get; set; } 44 | public string StaleOption { get; set; } 45 | public string Etag { get; set; } 46 | 47 | 48 | public override string ToString() 49 | { 50 | string result = ""; 51 | if ((Key != null) && (Key.Count > 0)) 52 | result += "&key=" + Key.ToString(); 53 | if (Keys != null) 54 | result += "&keys=[" + String.Join(",", Keys.Select(k => k.ToString()).ToArray()) + "]"; 55 | if ((StartKey != null) && (StartKey.Count > 0)) 56 | if((StartKey.Count == 1) && (EndKey.Count > 1)) 57 | result += "&startkey=[" + StartKey.ToString() + "]"; 58 | else 59 | result += "&startkey=" + StartKey.ToString(); 60 | if ((EndKey != null) && (EndKey.Count > 0)) 61 | result += "&endkey=" + EndKey.ToString(); 62 | if (Limit.HasValue) 63 | result += "&limit=" + Limit.Value.ToString(); 64 | if (Skip.HasValue) 65 | result += "&skip=" + Skip.Value.ToString(); 66 | if (Reduce.HasValue) 67 | result += "&reduce=" + Reduce.Value.ToString().ToLower(); 68 | if (Group.HasValue) 69 | result += "&group=" + Group.Value.ToString().ToLower(); 70 | if (IncludeDocs.HasValue) 71 | result += "&include_docs=" + IncludeDocs.Value.ToString().ToLower(); 72 | if (InclusiveEnd.HasValue) 73 | result += "&inclusive_end=" + InclusiveEnd.Value.ToString().ToLower(); 74 | if (GroupLevel.HasValue) 75 | result += "&group_level=" + GroupLevel.Value.ToString(); 76 | if (Descending.HasValue) 77 | result += "&descending=" + Descending.Value.ToString().ToLower(); 78 | if (Stale.HasValue && Stale.Value) 79 | { 80 | if(!string.IsNullOrEmpty(StaleOption)) 81 | { 82 | if (StaleOption.ToLower() == "ok") 83 | result += "&stale=ok"; 84 | else if (StaleOption.ToLower() == "update_after") 85 | result += "&stale=update_after"; 86 | else 87 | throw new ArgumentException("Invalid StaleOption provided as a CouchDB ViewOption - as of v 1.1.0, valid options include 'ok' and 'update_after'."); 88 | } 89 | else 90 | { 91 | result += "&stale=ok"; 92 | } 93 | } 94 | if (!string.IsNullOrEmpty(StartKeyDocId)) 95 | result += "&startkey_docid=" + StartKeyDocId; 96 | if (!string.IsNullOrEmpty(EndKeyDocId)) 97 | result += "&endkey_docid=" + EndKeyDocId; 98 | return result.Length < 1 ? "" : "?" + result.Substring(1); 99 | } 100 | } 101 | 102 | 103 | } -------------------------------------------------------------------------------- /LoveSeat/PagingHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Web; 6 | using LoveSeat.Interfaces; 7 | using Newtonsoft.Json.Linq; 8 | 9 | namespace LoveSeat 10 | { 11 | public interface IPageableModel 12 | { 13 | bool ShowNext { get; set; } 14 | bool ShowPrev { get; set; } 15 | string NextUrlParameters { get; set; } 16 | string PrevUrlParameters { get; set; } 17 | int? Limit { get; set; } 18 | bool Descending { get; set; } 19 | string StartKey { get; set; } 20 | int? Skip { get; set; } 21 | string StaleOption { get; set; } 22 | bool? Stale { get; set; } 23 | int StartIndex { get; set; } 24 | int EndIndex { get; set; } 25 | int TotalRows { get; set; } 26 | string StartKeyDocId { get; set; } 27 | string EndKeyDocId { get; set; } 28 | } 29 | 30 | public class PageableModel : IPageableModel 31 | { 32 | public bool ShowNext { get; set; } 33 | public bool ShowPrev { get; set; } 34 | public string NextUrlParameters { get; set; } 35 | public string PrevUrlParameters { get; set; } 36 | public int StartIndex { get; set; } 37 | public int EndIndex { get; set; } 38 | public int TotalRows { get; set; } 39 | public int? Limit { get; set; } 40 | public bool Descending { get; set; } 41 | public string StartKey { get; set; } 42 | public string StartKeyDocId { get; set; } 43 | public string EndKeyDocId { get; set; } 44 | public int? Skip { get; set; } 45 | public string StaleOption { get; set; } 46 | public bool? Stale { get; set; } 47 | } 48 | public static class PagingHelper 49 | { 50 | public static ViewOptions GetOptions(this IPageableModel model) 51 | { 52 | var options = new ViewOptions(); 53 | options.Descending = model.Descending; 54 | options.StartKey.Add(model.StartKey); 55 | options.Skip = model.Skip; 56 | options.Stale = model.Stale; 57 | options.StartKeyDocId = model.StartKeyDocId; 58 | options.EndKeyDocId = model.EndKeyDocId; 59 | options.Limit = model.Limit.HasValue ? model.Limit : 10; 60 | return options; 61 | } 62 | 63 | public static void UpdatePaging(this IPageableModel model, IViewOptions options, IViewResult result) 64 | { 65 | int count = result.Rows.Count(); 66 | var limit = options.Limit.HasValue ? options.Limit.Value : 10 ; 67 | model.Limit = limit; 68 | int rowsMinusOffset = (result.TotalRows - result.OffSet); 69 | model.ShowPrev = result.OffSet != 0 && !(model.Descending && (rowsMinusOffset <= count)); 70 | model.ShowNext = (result.TotalRows > options.Limit + result.OffSet) || options.Descending.GetValueOrDefault(); 71 | string skipPrev = result.OffSet < limit ? "" : "&skip=1"; 72 | string skipNext = rowsMinusOffset < limit ? "" : "&skip=1"; 73 | JToken lastRow; 74 | JToken firstRow; 75 | if (options.Descending.HasValue && options.Descending.Value) 76 | { //descending 77 | lastRow = result.Rows.FirstOrDefault(); 78 | firstRow = result.Rows.LastOrDefault(); 79 | model.StartIndex = (rowsMinusOffset - limit) < 1 ? 1 : (rowsMinusOffset - limit+1); 80 | }else 81 | { //ascending 82 | lastRow = result.Rows.LastOrDefault(); 83 | firstRow = result.Rows.FirstOrDefault(); 84 | model.StartIndex = result.OffSet + 1; 85 | } 86 | 87 | var startIndexPlusCount = model.StartIndex + count-1; 88 | model.EndIndex =result.TotalRows == 0 ? 0 : startIndexPlusCount; 89 | if (count == 0 ) model.EndIndex = model.StartIndex = 0; 90 | 91 | model.TotalRows = result.TotalRows; 92 | string prevStartKey = firstRow != null ? "&startkey=" + HttpUtility.UrlEncode(firstRow.Value("key")) + "&StartKeyDocId=" + firstRow.Value("id") : ""; 93 | string nextStartKey = lastRow != null ? "&startkey=" + HttpUtility.UrlEncode(lastRow.Value("key") ) + "&StartKeyDocId="+ lastRow.Value("id") : ""; 94 | model.NextUrlParameters = "?limit=" + model.Limit + nextStartKey + skipNext ; 95 | model.PrevUrlParameters = "?limit=" + model.Limit + prevStartKey 96 | + skipPrev + 97 | "&descending=true"; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /LoveSeat/ViewResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Web; 6 | using LoveSeat.Interfaces; 7 | using LoveSeat.Support; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Linq; 10 | 11 | namespace LoveSeat 12 | { 13 | public class ViewResult : ViewResult 14 | { 15 | private readonly IObjectSerializer objectSerializer = null; 16 | private CouchDictionary dict = null; 17 | public ViewResult(HttpWebResponse response, HttpWebRequest request, IObjectSerializer objectSerializer) 18 | : base(response, request) 19 | { 20 | this.objectSerializer = objectSerializer; 21 | 22 | } 23 | 24 | public CouchDictionary Dictionary 25 | { 26 | get 27 | { 28 | if (dict != null) return dict; 29 | dict = new CouchDictionary(); 30 | foreach (var row in this.Rows) 31 | { 32 | dict.Add(row.Value("key").ToString(Formatting.None), objectSerializer.Deserialize(row.Value("value"))); 33 | } 34 | return dict; 35 | } 36 | } 37 | 38 | public IEnumerable Items 39 | { 40 | get 41 | { 42 | if (objectSerializer == null) 43 | { 44 | throw new InvalidOperationException("ObjectSerializer must be set in order to use the generic view."); 45 | } 46 | return this.RawValues.Select(item => objectSerializer.Deserialize(item)); 47 | } 48 | } 49 | } 50 | 51 | public class ViewResult : IViewResult 52 | { 53 | private readonly HttpWebResponse response; 54 | private readonly HttpWebRequest request; 55 | private JObject json = null; 56 | private readonly string responseString; 57 | 58 | public JObject Json { get { return json ?? (json = JObject.Parse(responseString)); } } 59 | public ViewResult(HttpWebResponse response, HttpWebRequest request) 60 | { 61 | this.response = response; 62 | this.request = request; 63 | this.responseString = response.GetResponseString(); 64 | } 65 | /// 66 | /// Typically won't be needed. Provided for debuging assistance 67 | /// 68 | public HttpWebRequest Request { get { return request; } } 69 | /// 70 | /// Typically won't be needed. Provided for debugging assistance 71 | /// 72 | public HttpWebResponse Response { get { return response; } } 73 | public HttpStatusCode StatusCode { get { return response.StatusCode; } } 74 | 75 | public string Etag { get { return response.Headers["ETag"]; } } 76 | public int TotalRows { get 77 | { 78 | if (Json["total_rows"] == null) throw new CouchException(request, response, Json["reason"].Value()); 79 | return Json["total_rows"].Value(); 80 | } } 81 | public int OffSet { get 82 | { 83 | if (Json["offset"] == null) throw new CouchException(request, response, Json["reason"].Value()); 84 | return Json["offset"].Value(); 85 | } } 86 | public IEnumerable Rows { get 87 | { 88 | if (Json["rows"] == null) throw new CouchException(request, response, Json["reason"].Value()); 89 | return (JArray)Json["rows"]; 90 | } } 91 | /// 92 | /// Only populated when IncludeDocs is true 93 | /// 94 | public IEnumerable Docs 95 | { 96 | get 97 | { 98 | return (JArray)Json["doc"]; 99 | } 100 | } 101 | 102 | public JToken[] Keys 103 | { 104 | get 105 | { 106 | var arry = (JArray)Json["rows"]; 107 | return arry.Select(item => item["key"]).ToArray(); 108 | } 109 | } 110 | /// 111 | /// An IEnumerable of strings insteda of the IEnumerable of JTokens 112 | /// 113 | public IEnumerable RawRows 114 | { 115 | get 116 | { 117 | var arry = (JArray)Json["rows"]; 118 | return arry.Select(item => item.ToString()); 119 | } 120 | } 121 | 122 | public IEnumerable RawValues 123 | { 124 | get 125 | { 126 | var arry = (JArray)Json["rows"]; 127 | return arry.Select(item => item["value"].ToString()); 128 | } 129 | } 130 | public IEnumerable RawDocs 131 | { 132 | get 133 | { 134 | var arry = (JArray)Json["rows"]; 135 | return arry.Select(item => item["doc"].ToString()); 136 | } 137 | } 138 | public string RawString 139 | { 140 | get { return responseString; } 141 | } 142 | 143 | public bool Equals(IListResult other) 144 | { 145 | if (string.IsNullOrEmpty(Etag) || string.IsNullOrEmpty(other.Etag)) return false; 146 | return Etag == other.Etag; 147 | } 148 | 149 | public override string ToString() 150 | { 151 | return responseString; 152 | } 153 | /// 154 | /// Provides a formatted version of the json returned from this Result. (Avoid this method in favor of RawString as it's much more performant) 155 | /// 156 | public string FormattedResponse { get { return Json.ToString(Formatting.Indented); } } 157 | } 158 | } -------------------------------------------------------------------------------- /LoveSeat/Interfaces/IDocumentDatabase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Newtonsoft.Json.Linq; 4 | 5 | namespace LoveSeat.Interfaces 6 | { 7 | public interface IDocumentDatabase 8 | { 9 | /// 10 | /// Creates a document using the json provided. 11 | /// No validation or smarts attempted here by design for simplicities sake 12 | /// 13 | /// Id of Document 14 | /// 15 | /// The status from CouchDb as a JObject 16 | CouchResponse CreateDocument(string id, string jsonForDocument); 17 | 18 | CouchResponse CreateDocument(IBaseObject doc); 19 | 20 | /// 21 | /// Creates a document when you intend for Couch to generate the id for you. 22 | /// 23 | /// Json for creating the document 24 | /// Returns the status from Couchdb as a JObject 25 | CouchResponse CreateDocument(string jsonForDocument); 26 | 27 | CouchResponse DeleteDocument(string id, string rev); 28 | 29 | /// 30 | /// Returns null if document is not found 31 | /// 32 | /// 33 | /// 34 | Document GetDocument(string id); 35 | 36 | T GetDocument(string id); 37 | /// 38 | /// Adds an attachment to a document. If revision is not specified then the most recent will be fetched and used. Warning: if you need document update conflicts to occur please use the method that specifies the revision 39 | /// 40 | /// id of the couch Document 41 | /// byte[] of of the attachment. Use File.ReadAllBytes() 42 | /// Content Type must be specifed 43 | CouchResponse AddAttachment(string id, byte[] attachment, string filename, string contentType); 44 | 45 | /// 46 | /// Adds an attachment to the documnet. Rev must be specified on this signature. If you want to attach no matter what then use the method without the rev param 47 | /// 48 | /// id of the couch Document 49 | /// revision _rev of the Couch Document 50 | /// byte[] of of the attachment. Use File.ReadAllBytes() 51 | /// filename of the attachment 52 | /// Content Type must be specifed 53 | /// 54 | CouchResponse AddAttachment(string id, string rev, byte[] attachment, string filename, string contentType); 55 | 56 | Stream GetAttachmentStream(Document doc, string attachmentName); 57 | Stream GetAttachmentStream(string docId, string rev, string attachmentName); 58 | Stream GetAttachmentStream(string docId, string attachmentName); 59 | CouchResponse DeleteAttachment(string id, string rev, string attachmentName); 60 | CouchResponse DeleteAttachment(string id, string attachmentName); 61 | CouchResponse SaveDocument(Document document); 62 | BulkDocumentResponses SaveDocuments(Documents docs, bool all_or_nothing); 63 | 64 | /// 65 | /// Gets the results of a view with no view parameters. Use the overload to pass parameters 66 | /// 67 | /// The name of the view 68 | /// The design doc on which the view resides 69 | /// 70 | ViewResult View(string viewName, string designDoc); 71 | 72 | /// 73 | /// Gets the results of the view using any and all parameters 74 | /// 75 | /// The name of the view 76 | /// Options such as startkey etc. 77 | /// The design doc on which the view resides 78 | /// 79 | ViewResult View(string viewName, ViewOptions options, string designDoc); 80 | 81 | /// 82 | /// Gets all the documents in the database using the _all_docs uri 83 | /// 84 | /// 85 | ViewResult GetAllDocuments(); 86 | 87 | ViewResult GetAllDocuments(ViewOptions options); 88 | 89 | /// 90 | /// Gets the results of the view using the defaultDesignDoc and no view parameters. Use the overloads to specify options. 91 | /// 92 | /// 93 | /// 94 | /// 95 | ViewResult View(string viewName); 96 | 97 | /// 98 | /// Allows you to specify options and uses the defaultDesignDoc Specified. 99 | /// 100 | /// 101 | /// 102 | /// 103 | /// 104 | ViewResult View(string viewName, ViewOptions options); 105 | 106 | T GetDocument(Guid id , IObjectSerializer objectSerializer); 107 | T GetDocument(Guid id); 108 | string Show (string showName, string docId); 109 | IListResult List(string listName, string viewName, ViewOptions options, string designDoc); 110 | IListResult List(string listName, string viewName, ViewOptions options); 111 | 112 | ViewResult View(string viewName, ViewOptions options, string designDoc); 113 | ViewResult View(string viewName, ViewOptions options); 114 | ViewResult View(string viewName); 115 | } 116 | } -------------------------------------------------------------------------------- /LoveSeat/LoveSeat.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {AF987164-0100-4A90-A767-D473F882EA8B} 9 | Library 10 | Properties 11 | LoveSeat 12 | LoveSeat 13 | v3.5 14 | 512 15 | 16 | 17 | 3.5 18 | 19 | 20 | publish\ 21 | true 22 | Disk 23 | false 24 | Foreground 25 | 7 26 | Days 27 | false 28 | false 29 | true 30 | 0 31 | 1.0.0.%2a 32 | false 33 | false 34 | true 35 | 36 | 37 | true 38 | full 39 | false 40 | bin\Debug\ 41 | DEBUG;TRACE 42 | prompt 43 | 4 44 | 45 | 46 | pdbonly 47 | true 48 | bin\Release\ 49 | TRACE 50 | prompt 51 | 4 52 | 53 | 54 | 55 | False 56 | ..\Libraries\Newtonsoft.Json.dll 57 | 58 | 59 | 60 | 3.5 61 | 62 | 63 | 3.5 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | False 101 | .NET Framework 3.5 SP1 Client Profile 102 | false 103 | 104 | 105 | False 106 | .NET Framework 3.5 SP1 107 | true 108 | 109 | 110 | False 111 | Windows Installer 3.1 112 | true 113 | 114 | 115 | 116 | 117 | {18833FC5-9145-451D-82D4-41A1886B0BE7} 118 | LoveSeat.Interfaces 119 | 120 | 121 | 122 | 129 | -------------------------------------------------------------------------------- /LoveSeat/Support/CouchRequest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Net; 3 | using System.Text; 4 | using System.Web; 5 | using Newtonsoft.Json.Linq; 6 | 7 | namespace LoveSeat.Support { 8 | public class CouchRequest { 9 | private const string INVALID_USERNAME_OR_PASSWORD = "reason=Name or password is incorrect"; 10 | private const string NOT_AUTHORIZED = "reason=You are not authorized to access this db."; 11 | private const int STREAM_BUFFER_SIZE = 4096; 12 | 13 | private readonly HttpWebRequest request; 14 | public CouchRequest(string uri) 15 | : this(uri, new Cookie(), null) { 16 | } 17 | 18 | /// 19 | /// Request with Cookie authentication 20 | /// 21 | /// 22 | /// 23 | /// 24 | public CouchRequest(string uri, Cookie authCookie, string eTag) { 25 | request = (HttpWebRequest)WebRequest.Create(uri); 26 | request.Headers.Clear(); //important 27 | if (!string.IsNullOrEmpty(eTag)) 28 | request.Headers.Add("If-None-Match", eTag); 29 | request.Headers.Add("Accept-Charset", "utf-8"); 30 | request.Headers.Add("Accept-Language", "en-us"); 31 | request.Accept = "application/json"; 32 | request.Referer = uri; 33 | request.ContentType = "application/json"; 34 | request.KeepAlive = true; 35 | if (authCookie != null) 36 | request.Headers.Add("Cookie", "AuthSession=" + authCookie.Value); 37 | request.Timeout = Timeout.HasValue ? Timeout.Value : 10000; 38 | } 39 | 40 | /// 41 | /// Basic Authorization Header 42 | /// 43 | /// 44 | /// 45 | /// 46 | public CouchRequest(string uri, string username, string password) { 47 | 48 | request = (HttpWebRequest)WebRequest.Create(uri); 49 | request.Headers.Clear(); //important 50 | 51 | // Deal with Authorization Header 52 | string authValue = "Basic "; 53 | string userNAndPassword = username + ":" + password; 54 | 55 | // Base64 encode 56 | string b64 = System.Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(userNAndPassword)); 57 | 58 | authValue = authValue + b64; 59 | 60 | request.Headers.Add("Authorization", authValue); 61 | request.Headers.Add("Accept-Charset", "utf-8"); 62 | request.Headers.Add("Accept-Language", "en-us"); 63 | request.Referer = uri; 64 | request.ContentType = "application/json"; 65 | request.KeepAlive = true; 66 | } 67 | 68 | 69 | public int? Timeout { get; set; } 70 | public CouchRequest Put() { 71 | request.Method = "PUT"; 72 | return this; 73 | } 74 | 75 | public CouchRequest Get() { 76 | request.Method = "GET"; 77 | return this; 78 | } 79 | public CouchRequest Post() { 80 | request.Method = "POST"; 81 | return this; 82 | } 83 | public CouchRequest Delete() { 84 | request.Method = "DELETE"; 85 | return this; 86 | } 87 | public CouchRequest Data(Stream data) 88 | { 89 | using (var body = request.GetRequestStream()) 90 | { 91 | var buffer = new byte[STREAM_BUFFER_SIZE]; 92 | var bytesRead = 0; 93 | while (0 != (bytesRead = data.Read(buffer, 0, buffer.Length))) 94 | { 95 | body.Write(buffer, 0, bytesRead); 96 | } 97 | } 98 | return this; 99 | } 100 | public CouchRequest Data(string data) { 101 | using (var body = request.GetRequestStream()) { 102 | var encodedData = Encoding.UTF8.GetBytes(data); 103 | body.Write(encodedData, 0, encodedData.Length); 104 | } 105 | return this; 106 | } 107 | public CouchRequest Data(byte[] attachment) { 108 | using (var body = request.GetRequestStream()) { 109 | body.Write(attachment, 0, attachment.Length); 110 | } 111 | return this; 112 | } 113 | public CouchRequest Data(JObject obj) { 114 | return Data(obj.ToString()); 115 | } 116 | 117 | public CouchRequest ContentType(string contentType) { 118 | request.ContentType = contentType; 119 | return this; 120 | } 121 | 122 | public CouchRequest Form() { 123 | request.ContentType = "application/x-www-form-urlencoded"; 124 | return this; 125 | } 126 | 127 | public CouchRequest Json() { 128 | request.ContentType = "application/json"; 129 | return this; 130 | } 131 | 132 | public HttpWebRequest GetRequest() { 133 | return request; 134 | } 135 | 136 | public HttpWebResponse GetResponse() { 137 | bool failedAuth = false; 138 | try { 139 | var response = (HttpWebResponse)request.GetResponse(); 140 | string msg = ""; 141 | if (isAuthenticateOrAuthorized(response, ref msg) == false) { 142 | failedAuth = true; 143 | throw new WebException(msg, new System.Exception(msg)); 144 | } 145 | 146 | return response; 147 | } catch (WebException webEx) { 148 | if (failedAuth == true) { 149 | throw webEx; 150 | } 151 | var response = (HttpWebResponse)webEx.Response; 152 | if (response == null) 153 | throw new HttpException("Request failed to receive a response"); 154 | return response; 155 | } 156 | throw new HttpException("Request failed to receive a response"); 157 | } 158 | 159 | 160 | /// 161 | /// Checks response if username and password was valid 162 | /// 163 | /// 164 | private bool isAuthenticateOrAuthorized(HttpWebResponse response, ref string message) { 165 | //"reason=Name or password is incorrect" 166 | // Check if query string is okay 167 | string[] split = response.ResponseUri.Query.Split('&'); 168 | 169 | if (split.Length > 0) { 170 | for (int i = 0; i < split.Length; i++) { 171 | string temp = System.Web.HttpUtility.UrlDecode(split[i]); 172 | 173 | if (temp.Contains(INVALID_USERNAME_OR_PASSWORD) == true) { 174 | message = "Invalid username or password"; 175 | return false; 176 | } else if (temp.Contains(NOT_AUTHORIZED) == true) { 177 | message = "Not Authorized to access database"; 178 | return false; 179 | } 180 | } 181 | } 182 | return true; 183 | }// end private void checkResponse(... 184 | 185 | 186 | } 187 | } -------------------------------------------------------------------------------- /LoveSeat.IntegrationTest/CouchClientTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using LoveSeat.Interfaces; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Converters; 10 | using Newtonsoft.Json.Serialization; 11 | using NUnit.Framework; 12 | using LoveSeat; 13 | 14 | namespace LoveSeat.IntegrationTest 15 | { 16 | [TestFixture] 17 | public class CouchClientTest 18 | { 19 | private CouchClient client; 20 | private const string baseDatabase = "love-seat-test-base"; 21 | private const string replicateDatabase = "love-seat-test-repli"; 22 | 23 | private readonly string host = ConfigurationManager.AppSettings["Host"].ToString(); 24 | private readonly int port = int.Parse(ConfigurationManager.AppSettings["Port"].ToString()); 25 | private readonly string username = ConfigurationManager.AppSettings["UserName"].ToString(); 26 | private readonly string password = ConfigurationManager.AppSettings["Password"].ToString(); 27 | 28 | [TestFixtureSetUp] 29 | public void Setup() 30 | { 31 | client = new CouchClient(host, port, username, password, false,AuthenticationType.Cookie); 32 | if (!client.HasDatabase(baseDatabase)) 33 | { 34 | client.CreateDatabase(baseDatabase); 35 | } 36 | if (!client.HasDatabase(replicateDatabase)) 37 | { 38 | client.CreateDatabase(replicateDatabase); 39 | } 40 | } 41 | [TestFixtureTearDown] 42 | public void TearDown() 43 | { 44 | //delete the test database 45 | if (client.HasDatabase(baseDatabase)) { 46 | client.DeleteDatabase(baseDatabase); 47 | } 48 | if (client.HasDatabase(replicateDatabase)) { 49 | client.DeleteDatabase(replicateDatabase); 50 | } 51 | if (client.HasUser("Leela")) { 52 | client.DeleteAdminUser("Leela"); 53 | } 54 | } 55 | 56 | [Test] 57 | public void Should_Trigger_Replication() 58 | { 59 | var obj = client.TriggerReplication("http://Professor:Farnsworth@"+ host+":5984/" +replicateDatabase, baseDatabase); 60 | Assert.IsTrue(obj != null); 61 | } 62 | public class Bunny { 63 | public Bunny() { } 64 | public string Name { get; set; } 65 | } 66 | [Test] 67 | public void Creating_A_Document_Should_Keep_Id_If_Supplied() 68 | { 69 | var doc = new Document(new Bunny()); 70 | doc.Id = "myid"; 71 | var db = client.GetDatabase(baseDatabase); 72 | db.CreateDocument(doc); 73 | var savedDoc = db.GetDocument("myid"); 74 | Assert.IsNotNull(savedDoc, "Saved doc should be able to be retrieved by the same id"); 75 | } 76 | 77 | [Test] 78 | public void Should_Create_Document_From_String() 79 | { 80 | string obj = @"{""test"": ""prop""}"; 81 | var db = client.GetDatabase(baseDatabase); 82 | var result = db.CreateDocument("fdas", obj); 83 | var document = db.GetDocument("fdas"); 84 | Assert.IsNotNull(document); 85 | } 86 | [Test] 87 | public void Should_Save_Existing_Document() 88 | { 89 | string obj = @"{""test"": ""prop""}"; 90 | var db = client.GetDatabase(baseDatabase); 91 | var result = db.CreateDocument("fdas", obj); 92 | var doc = db.GetDocument("fdas"); 93 | doc["test"] = "newprop"; 94 | db.SaveDocument(doc); 95 | var newresult= db.GetDocument("fdas"); 96 | Assert.AreEqual(newresult.Value("test"), "newprop"); 97 | } 98 | 99 | [Test] 100 | public void Should_Delete_Document() 101 | { 102 | var db = client.GetDatabase(baseDatabase); 103 | db.CreateDocument("asdf", "{}"); 104 | var doc = db.GetDocument("asdf"); 105 | var result = db.DeleteDocument(doc.Id, doc.Rev); 106 | Assert.IsNull(db.GetDocument("asdf")); 107 | } 108 | 109 | 110 | [Test] 111 | public void Should_Determine_If_Doc_Has_Attachment() 112 | { 113 | var db = client.GetDatabase(baseDatabase); 114 | db.CreateDocument(@"{""_id"":""fdsa""}"); 115 | byte[] attachment = File.ReadAllBytes("../../Files/martin.jpg"); 116 | db.AddAttachment("fdsa" , attachment,"martin.jpg", "image/jpeg"); 117 | var doc = db.GetDocument("fdsa"); 118 | Assert.IsTrue(doc.HasAttachment); 119 | } 120 | [Test] 121 | public void Should_Return_Attachment_Names() 122 | { 123 | var db = client.GetDatabase(baseDatabase); 124 | db.CreateDocument(@"{""_id"":""upload""}"); 125 | var attachment = File.ReadAllBytes("../../Files/martin.jpg"); 126 | db.AddAttachment("upload", attachment, "martin.jpg", "image/jpeg"); 127 | var doc = db.GetDocument("upload"); 128 | Assert.IsTrue(doc.GetAttachmentNames().Contains("martin.jpg")); 129 | } 130 | 131 | [Test] 132 | public void Should_Create_Admin_User() 133 | { 134 | client.CreateAdminUser("Leela", "Turanga"); 135 | } 136 | 137 | [Test] 138 | public void Should_Delete_Admin_User() 139 | { 140 | client.DeleteAdminUser("Leela"); 141 | } 142 | 143 | [Test] 144 | public void Should_Get_Attachment() 145 | { 146 | var db = client.GetDatabase(baseDatabase); 147 | db.CreateDocument(@"{""_id"":""test_upload""}"); 148 | var doc = db.GetDocument("test_upload"); 149 | var attachment = File.ReadAllBytes("../../Files/test_upload.txt"); 150 | db.AddAttachment("test_upload", attachment, "test_upload.txt", "text/html"); 151 | var stream = db.GetAttachmentStream(doc, "test_upload.txt"); 152 | using (StreamReader sr = new StreamReader(stream)) 153 | { 154 | string result = sr.ReadToEnd(); 155 | Assert.IsTrue(result == "test"); 156 | } 157 | } 158 | [Test] 159 | public void Should_Delete_Attachment() 160 | { 161 | var db = client.GetDatabase(baseDatabase); 162 | db.CreateDocument(@"{""_id"":""test_delete""}"); 163 | var doc = db.GetDocument("test_delete"); 164 | var attachment = File.ReadAllBytes("../../Files/test_upload.txt"); 165 | db.AddAttachment("test_delete", attachment, "test_upload.txt", "text/html"); 166 | db.DeleteAttachment("test_delete", "test_upload.txt"); 167 | var retrieved = db.GetDocument("test_delete"); 168 | Assert.IsFalse(retrieved.HasAttachment); 169 | } 170 | [Test] 171 | public void Should_Return_Etag_In_ViewResults() 172 | { 173 | var db = client.GetDatabase(baseDatabase); 174 | db.CreateDocument(@"{""_id"":""test_eTag""}"); 175 | ViewResult result = db.GetAllDocuments(); 176 | Assert.IsTrue(!string.IsNullOrEmpty(result.Etag)); 177 | } 178 | 179 | [Test] 180 | public void Should_Persist_Property() 181 | { 182 | var db = client.GetDatabase(baseDatabase); 183 | var company = new Company(); 184 | company.Id = "1234"; 185 | company.Name = "Whiteboard-IT"; 186 | db.CreateDocument(company); 187 | var doc = db.GetDocument("1234"); 188 | Assert.AreEqual(company.Name, doc.Name); 189 | } 190 | [Test] 191 | public void JsonConvert_Should_Serialize_Properly() 192 | { 193 | var company = new Company(); 194 | company.Name = "Whiteboard-it"; 195 | var settings = new JsonSerializerSettings(); 196 | var converters = new List { new IsoDateTimeConverter() }; 197 | settings.Converters = converters; 198 | settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); 199 | settings.NullValueHandling = NullValueHandling.Ignore; 200 | var result = JsonConvert.SerializeObject(company, Formatting.Indented, settings); 201 | Console.Write(result); 202 | Assert.IsTrue(result.Contains("Whiteboard-it")); 203 | } 204 | [Test] 205 | public void Should_Get_304_If_ETag_Matches() 206 | { 207 | var db = client.GetDatabase(baseDatabase); 208 | db.CreateDocument(@"{""_id"":""test_eTag_exception""}"); 209 | ViewResult result = db.GetAllDocuments(); 210 | ViewResult cachedResult = db.GetAllDocuments(new ViewOptions {Etag = result.Etag}); 211 | Assert.AreEqual(cachedResult.StatusCode, HttpStatusCode.NotModified); 212 | } 213 | [Test] 214 | public void Should_Get_Id_From_Existing_Document() 215 | { 216 | var db = client.GetDatabase(baseDatabase); 217 | string id = "test_should_get_id"; 218 | db.CreateDocument("{\"_id\":\""+ id+"\"}"); 219 | Document doc= db.GetDocument(id); 220 | Assert.AreEqual(id, doc.Id); 221 | } 222 | } 223 | public class Company : IBaseObject 224 | { 225 | public string Name { get; set; } 226 | public string Id { get; set; } 227 | public string Rev { get; set; } 228 | public string Type { get { return "company"; } } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /LoveSeat/CouchClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Web; 4 | using LoveSeat.Support; 5 | using Newtonsoft.Json.Linq; 6 | 7 | namespace LoveSeat 8 | { 9 | // type of authentication used in request to CouchDB 10 | public enum AuthenticationType 11 | { Basic, Cookie }; 12 | 13 | /// 14 | /// Used as the starting point for any communication with CouchDB 15 | /// 16 | public class CouchClient : CouchBase 17 | { 18 | // Authentication type used in request to CouchDB 19 | protected readonly AuthenticationType authType; 20 | 21 | /// 22 | /// This is only intended for use if your CouchDb is in Admin Party 23 | /// 24 | public CouchClient() 25 | : this("localhost", 5984, null, null, false, AuthenticationType.Basic) 26 | { 27 | } 28 | 29 | /// 30 | /// CouchClient constructor 31 | /// 32 | /// 33 | /// 34 | public CouchClient(string username, string password) 35 | : this("localhost", 5984, username, password, false, AuthenticationType.Basic) 36 | { 37 | } 38 | 39 | /// 40 | /// CouchClient constructor using URI 41 | /// 42 | /// the full uri for parsing 43 | public CouchClient(string uri) 44 | { 45 | 46 | } 47 | /// 48 | /// Constructs the CouchClient and gets an authentication cookie (10 min) 49 | /// 50 | /// The hostname of the CouchDB instance 51 | /// The port of the CouchDB instance 52 | /// The username of the CouchDB instanceCou 53 | /// The password of the CouchDB instance 54 | public CouchClient(string host, int port, string username, string password, bool isHttps, AuthenticationType aT) 55 | : base(username, password, aT) 56 | { 57 | if (isHttps == false) 58 | { 59 | baseUri = "http://" + host + ":" + port + "/"; 60 | } 61 | else 62 | { 63 | baseUri = "https://" + host + ":" + port + "/"; 64 | } 65 | 66 | authType = aT; 67 | 68 | } 69 | 70 | 71 | /// 72 | /// CouchClient constructor 73 | /// 74 | /// 75 | public CouchClient(CouchConfiguration config) 76 | : this(config.Host, config.Port, config.User, config.Password, false, AuthenticationType.Basic) 77 | { 78 | } 79 | 80 | /// 81 | /// Triggers one way replication from the source to target. If bidirection is needed call this method twice with the source and target args reversed. 82 | /// 83 | /// Uri or database name of database to replicate from 84 | /// Uri or database name of database to replicate to 85 | /// Whether or not CouchDB should continue to replicate going forward on it's own 86 | /// 87 | public CouchResponse TriggerReplication(string source, string target, bool continuous) 88 | { 89 | var request = GetRequest(baseUri + "_replicate"); 90 | 91 | var options = new ReplicationOptions(source, target, continuous); 92 | var response = request.Post() 93 | .Data(options.ToString()) 94 | .GetResponse(); 95 | 96 | return response.GetJObject(); 97 | } 98 | 99 | public CouchResponse TriggerReplication(string source, string target) 100 | { 101 | return TriggerReplication(source, target, false); 102 | } 103 | 104 | /// 105 | /// Creates a database 106 | /// 107 | /// Name of new database 108 | /// 109 | public CouchResponse CreateDatabase(string databaseName) 110 | { 111 | return GetRequest(baseUri + databaseName).Put().GetResponse().GetJObject(); 112 | } 113 | /// 114 | /// Deletes the specified database 115 | /// 116 | /// Database to delete 117 | /// 118 | public CouchResponse DeleteDatabase(string databaseName) 119 | { 120 | return GetRequest(baseUri + databaseName).Delete().GetResponse().GetJObject(); 121 | } 122 | 123 | /// 124 | /// Gets a Database object 125 | /// 126 | /// Name of database to fetch 127 | /// 128 | public CouchDatabase GetDatabase(string databaseName) 129 | { 130 | return new CouchDatabase(baseUri, databaseName, username, password, this.authType); 131 | } 132 | 133 | /// 134 | /// Creates an admin user 135 | /// 136 | /// 137 | /// 138 | /// 139 | public CouchResponse CreateAdminUser(string usernameToCreate, string passwordToCreate) 140 | { 141 | //Creates the user in the local.ini 142 | var iniResult = GetRequest(baseUri + "_config/admins/" + HttpUtility.UrlEncode(usernameToCreate)) 143 | .Put().Json().Data("\"" + passwordToCreate + "\"").GetResponse(); 144 | 145 | var user = @"{ ""name"": ""%name%"", 146 | ""_id"": ""org.couchdb.user:%name%"", ""type"": ""user"", ""roles"": [], 147 | }".Replace("%name%", usernameToCreate).Replace("\r\n", ""); 148 | var docResult = GetRequest(baseUri + "_users/org.couchdb.user:" + HttpUtility.UrlEncode(usernameToCreate)) 149 | .Put().Json().Data(user).GetResponse().GetJObject(); 150 | return docResult; 151 | 152 | } 153 | 154 | /// 155 | /// Deletes admin user (if you have permission) 156 | /// 157 | /// 158 | public void DeleteAdminUser(string userToDelete) 159 | { 160 | var iniResult = GetRequest(baseUri + "_config/admins/" + HttpUtility.UrlEncode(userToDelete)) 161 | .Delete().Json().GetResponse(); 162 | 163 | var userDb = this.GetDatabase("_users"); 164 | var userId = "org.couchdb.user:" + HttpUtility.UrlEncode(userToDelete); 165 | var userDoc = userDb.GetDocument(userId); 166 | if (userDoc != null) 167 | { 168 | userDb.DeleteDocument(userDoc.Id, userDoc.Rev); 169 | } 170 | } 171 | 172 | /// 173 | /// Returns a bool indicating whether or not the database exists. 174 | /// 175 | /// 176 | /// 177 | public bool HasDatabase(string databaseName) { 178 | var request = GetRequest(baseUri + databaseName); 179 | request.Timeout = -1; 180 | 181 | var response = request.GetResponse(); 182 | var pDocResult = new Document(response.GetResponseString()); 183 | 184 | if (pDocResult["error"] == null) { 185 | return (true); 186 | } 187 | if (pDocResult["error"].Value() == "not_found") { 188 | return (false); 189 | } 190 | throw new Exception(pDocResult["error"].Value()); 191 | } 192 | 193 | /// 194 | /// Returns true/false depending on whether or not the user is contained in the _users database 195 | /// 196 | /// 197 | /// 198 | public bool HasUser(string userId) 199 | { 200 | return GetUser(userId) != null; 201 | } 202 | 203 | /// 204 | /// Get's the user. 205 | /// 206 | /// 207 | /// 208 | public Document GetUser(string userId) 209 | { 210 | var db = new CouchDatabase(baseUri, "_users", username, password, this.authType); 211 | userId = "org.couchdb.user:" + HttpUtility.UrlEncode(userId); 212 | return db.GetDocument(userId); 213 | } 214 | 215 | 216 | /// 217 | /// Create a user in the _user database 218 | /// 219 | /// 220 | /// 221 | /// 222 | public CouchResponse CreateUser(string usernameToCreate, string passwordToCreate) 223 | { 224 | 225 | var user = ""; 226 | //check if user exists already 227 | Document cUser = GetUser(usernameToCreate); 228 | 229 | if (cUser != null) // add revision number to update existing document 230 | { 231 | user = @"{ ""name"": ""%name%"",""_id"": ""org.couchdb.user:%name%"",""_rev"": ""%_rev%"", ""type"": ""user"", ""roles"": [],""password_sha"": ""%password%"",""salt"": ""%salt%""}" 232 | .Replace("%_rev%", cUser.Rev); 233 | } 234 | else // new user to add 235 | { 236 | user = @"{ ""name"": ""%name%"",""_id"": ""org.couchdb.user:%name%"", ""type"": ""user"", ""roles"": [],""password_sha"": ""%password%"",""salt"": ""%salt%""}"; 237 | } 238 | 239 | //Add/Update user to _user 240 | string salt = new string(new char[0]); // Empty string 241 | 242 | string hashedPassword = HashIt.ComputeHash(passwordToCreate, ref salt); 243 | 244 | user = user.Replace("%name%", usernameToCreate) 245 | .Replace("%password%", hashedPassword) 246 | .Replace("%salt%", salt) 247 | .Replace("\r\n", ""); 248 | 249 | var docResult = GetRequest(baseUri + "_users/org.couchdb.user:" + HttpUtility.UrlEncode(usernameToCreate)) 250 | .Put().Json().Data(user).GetResponse(); 251 | 252 | if (docResult.StatusCode == HttpStatusCode.Created) 253 | { 254 | return docResult.GetJObject(); 255 | } 256 | else 257 | { 258 | throw new WebException("An error occurred when creating a user. Status code: " + docResult.StatusDescription); 259 | } 260 | } 261 | 262 | /// 263 | /// Deletes user (if you have permission) 264 | /// 265 | /// 266 | public void DeleteUser(string userToDelete) 267 | { 268 | var userDb = this.GetDatabase("_users"); 269 | var userId = "org.couchdb.user:" + HttpUtility.UrlEncode(userToDelete); 270 | var userDoc = userDb.GetDocument(userId); 271 | if (userDoc != null) 272 | { 273 | userDb.DeleteDocument(userDoc.Id, userDoc.Rev); 274 | } 275 | else 276 | { 277 | throw new WebException("An error occurred while deleting the user " + userToDelete + ". Username does not exist."); 278 | } 279 | 280 | } 281 | 282 | /// 283 | /// Get's UUID from CouchDB. Limit 50 uuid requests. 284 | /// 285 | /// 286 | /// 287 | public UniqueIdentifiers GetUUID(int count) 288 | { 289 | 290 | string request = baseUri + "_uuids"; 291 | 292 | if (count == 1) 293 | { 294 | if (count > 50)//limit 50 295 | { 296 | count = 50; 297 | } 298 | 299 | request = request + "?Count=" + Convert.ToString(count); 300 | } 301 | 302 | var x = GetRequest(request); 303 | String str = x.Get().Json().GetResponse().GetJObject().ToString(); 304 | UniqueIdentifiers y = Newtonsoft.Json.JsonConvert.DeserializeObject(str); 305 | 306 | return y; 307 | 308 | } 309 | } 310 | 311 | /// 312 | /// Unique Identifier 313 | /// 314 | public class UniqueIdentifiers 315 | { 316 | public System.Collections.Generic.List uuids { get; set; } 317 | } 318 | } 319 | 320 | 321 | 322 | -------------------------------------------------------------------------------- /LoveSeat/CouchDatabase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Web; 6 | using LoveSeat.Interfaces; 7 | using LoveSeat.Support; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Linq; 10 | 11 | namespace LoveSeat 12 | { 13 | public class CouchDatabase : CouchBase, IDocumentDatabase 14 | { 15 | public IObjectSerializer ObjectSerializer = new DefaultSerializer(); 16 | 17 | private readonly string databaseBaseUri; 18 | private string defaultDesignDoc = null; 19 | internal CouchDatabase(string baseUri, string databaseName, string username, string password, AuthenticationType aT) 20 | : base(username, password, aT) 21 | { 22 | this.baseUri = baseUri; 23 | this.databaseBaseUri = baseUri + databaseName; 24 | } 25 | 26 | /// 27 | /// Creates a document using the json provided. 28 | /// No validation or smarts attempted here by design for simplicities sake 29 | /// 30 | /// Id of Document 31 | /// 32 | /// 33 | public CouchResponse CreateDocument(string id, string jsonForDocument) 34 | { 35 | var jobj = JObject.Parse(jsonForDocument); 36 | if (jobj.Value("_rev") == null) 37 | jobj.Remove("_rev"); 38 | var resp = GetRequest(databaseBaseUri + "/" + id) 39 | .Put().Form() 40 | .Data(jobj.ToString(Formatting.None)) 41 | .GetResponse(); 42 | return 43 | resp.GetJObject(); 44 | } 45 | 46 | public CouchResponse CreateDocument(IBaseObject doc) 47 | { 48 | var serialized = ObjectSerializer.Serialize(doc); 49 | if (doc.Id != null) 50 | { 51 | return CreateDocument(doc.Id, serialized); 52 | } 53 | return CreateDocument(serialized); 54 | } 55 | /// 56 | /// Creates a document when you intend for Couch to generate the id for you. 57 | /// 58 | /// Json for creating the document 59 | /// The response as a JObject 60 | public CouchResponse CreateDocument(string jsonForDocument) 61 | { 62 | var json = JObject.Parse(jsonForDocument); //to make sure it's valid json 63 | var jobj = 64 | GetRequest(databaseBaseUri + "/").Post().Json().Data(jsonForDocument).GetResponse().GetJObject(); 65 | return jobj; 66 | } 67 | public CouchResponse DeleteDocument(string id, string rev) 68 | { 69 | if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(rev)) 70 | throw new Exception("Both id and rev must have a value that is not empty"); 71 | return GetRequest(databaseBaseUri + "/" + id + "?rev=" + rev).Delete().Form().GetResponse().GetJObject(); 72 | } 73 | /// 74 | /// Returns null if document is not found 75 | /// 76 | /// 77 | /// 78 | public Document GetDocument(string id) 79 | { 80 | var resp = GetRequest(databaseBaseUri + "/" + id).Get().Json().GetResponse(); 81 | if (resp.StatusCode==HttpStatusCode.NotFound) return null; 82 | return resp.GetCouchDocument(); 83 | } 84 | public T GetDocument(Guid id , IObjectSerializer objectSerializer) 85 | { 86 | return GetDocument(id.ToString(), objectSerializer); 87 | } 88 | public T GetDocument(Guid id) 89 | { 90 | return GetDocument(id.ToString()); 91 | } 92 | public T GetDocument(string id) 93 | { 94 | return GetDocument(id, ObjectSerializer); 95 | } 96 | public T GetDocument(string id, IObjectSerializer objectSerializer) 97 | { 98 | var resp = GetRequest(databaseBaseUri + "/" + id).Get().Json().GetResponse(); 99 | if (resp.StatusCode == HttpStatusCode.NotFound) return default(T); 100 | return objectSerializer.Deserialize(resp.GetResponseString()); 101 | } 102 | 103 | /// 104 | /// Request multiple documents 105 | /// in a single request. 106 | /// 107 | /// 108 | /// 109 | public ViewResult GetDocuments(Keys keyLst) 110 | { 111 | // serialize list of keys to json 112 | string data = Newtonsoft.Json.JsonConvert.SerializeObject(keyLst); 113 | 114 | var resp = GetRequest(databaseBaseUri + "/_all_docs").Post().Json().Data(data).GetResponse(); 115 | 116 | if (resp == null) return null; 117 | 118 | if (resp.StatusCode == HttpStatusCode.NotFound) return null; 119 | 120 | ViewResult vw = new ViewResult(resp, null); 121 | 122 | return vw; 123 | } 124 | 125 | /// 126 | /// Using the bulk API for the loading of documents. 127 | /// 128 | /// 129 | /// Here we assume you have either added the correct rev, id, or _deleted attribute to each document. The response will indicate if there were any errors. 130 | /// Please note that the max_document_size configuration variable in CouchDB limits the maximum request size to CouchDB. 131 | /// JSON of updated documents in the BulkDocumentResponse class. 132 | public BulkDocumentResponses SaveDocuments(Documents docs, bool all_or_nothing) 133 | { 134 | string uri = databaseBaseUri + "/_bulk_docs"; 135 | 136 | string data = Newtonsoft.Json.JsonConvert.SerializeObject(docs); 137 | 138 | if (all_or_nothing == true) 139 | { 140 | uri = uri + "?all_or_nothing=true"; 141 | } 142 | 143 | HttpWebResponse resp = GetRequest(uri).Post().Json().Data(data).GetResponse(); 144 | 145 | if (resp == null) 146 | { 147 | throw new System.Exception("Response returned null."); 148 | } 149 | 150 | if (resp.StatusCode != HttpStatusCode.Created) 151 | { 152 | throw new System.Exception("Response returned with a HTTP status code of " + resp.StatusCode + " - " + resp.StatusDescription); 153 | } 154 | 155 | // Get response 156 | string x = resp.GetResponseString(); 157 | 158 | // Convert to Bulk response 159 | BulkDocumentResponses bulk = Newtonsoft.Json.JsonConvert.DeserializeObject(x); 160 | 161 | return bulk; 162 | } 163 | 164 | 165 | /// 166 | /// Adds an attachment to a document. If revision is not specified then the most recent will be fetched and used. Warning: if you need document update conflicts to occur please use the method that specifies the revision 167 | /// 168 | /// id of the couch Document 169 | /// byte[] of of the attachment. Use File.ReadAllBytes() 170 | /// filename of the attachment 171 | /// Content Type must be specifed 172 | public CouchResponse AddAttachment(string id, byte[] attachment, string filename, string contentType) 173 | { 174 | var doc = GetDocument(id); 175 | return AddAttachment(id, doc.Rev, attachment, filename, contentType); 176 | } 177 | /// 178 | /// Adds an attachment to the documnet. Rev must be specified on this signature. If you want to attach no matter what then use the method without the rev param 179 | /// 180 | /// id of the couch Document 181 | /// revision _rev of the Couch Document 182 | /// byte[] of of the attachment. Use File.ReadAllBytes() 183 | /// filename of the attachment 184 | /// Content Type must be specifed 185 | /// 186 | public CouchResponse AddAttachment(string id, string rev, byte[] attachment, string filename, string contentType) 187 | { 188 | return 189 | GetRequest(databaseBaseUri + "/" + id + "/" + filename + "?rev=" + rev).Put().ContentType(contentType).Data(attachment).GetResponse().GetJObject(); 190 | } 191 | /// 192 | /// Adds an attachment to a document. If revision is not specified then the most recent will be fetched and used. Warning: if you need document update conflicts to occur please use the method that specifies the revision 193 | /// 194 | /// id of the couch Document 195 | /// Stream of the attachment. 196 | /// Content Type must be specifed 197 | public CouchResponse AddAttachment(string id, Stream attachmentStream, string filename, string contentType) 198 | { 199 | var doc = GetDocument(id); 200 | return AddAttachment(id, doc.Rev, attachmentStream, filename, contentType); 201 | } 202 | /// 203 | /// Adds an attachment to the documnet. Rev must be specified on this signature. If you want to attach no matter what then use the method without the rev param 204 | /// 205 | /// id of the couch Document 206 | /// revision _rev of the Couch Document 207 | /// Stream of of the attachment. Use File.ReadAllBytes() 208 | /// filename of the attachment 209 | /// Content Type must be specifed 210 | /// 211 | public CouchResponse AddAttachment(string id, string rev, Stream attachmentStream, string filename, string contentType) 212 | { 213 | return 214 | GetRequest(databaseBaseUri + "/" + id + "/" + filename + "?rev=" + rev).Put().ContentType(contentType).Data(attachmentStream).GetResponse().GetJObject(); 215 | } 216 | 217 | public Stream GetAttachmentStream(Document doc, string attachmentName) 218 | { 219 | return GetAttachmentStream(doc.Id, doc.Rev, attachmentName); 220 | } 221 | public Stream GetAttachmentStream(string docId, string rev, string attachmentName) 222 | { 223 | return GetRequest(databaseBaseUri + "/" + docId + "/" + HttpUtility.UrlEncode(attachmentName)).Get().GetResponse().GetResponseStream(); 224 | } 225 | public Stream GetAttachmentStream(string docId, string attachmentName) 226 | { 227 | var doc = GetDocument(docId); 228 | if (doc == null) return null; 229 | return GetAttachmentStream(docId, doc.Rev, attachmentName); 230 | } 231 | public CouchResponse DeleteAttachment(string id, string rev, string attachmentName) 232 | { 233 | return GetRequest(databaseBaseUri + "/" + id + "/" + attachmentName + "?rev=" + rev).Json().Delete().GetResponse().GetJObject(); 234 | } 235 | public CouchResponse DeleteAttachment(string id, string attachmentName) 236 | { 237 | var doc = GetDocument(id); 238 | return DeleteAttachment(doc.Id, doc.Rev, attachmentName); 239 | } 240 | 241 | public CouchResponse SaveDocument(Document document) 242 | { 243 | if (document.Rev == null) 244 | return CreateDocument(document); 245 | 246 | var resp = GetRequest(databaseBaseUri + "/" + document.Id + "?rev=" + document.Rev).Put().Form().Data(document).GetResponse(); 247 | return resp.GetJObject(); 248 | } 249 | 250 | /// 251 | /// Gets the results of a view with no view parameters. Use the overload to pass parameters 252 | /// 253 | /// The name of the view 254 | /// The design doc on which the view resides 255 | /// 256 | public ViewResult View(string viewName, string designDoc) 257 | { 258 | return View(viewName, null, designDoc); 259 | } 260 | 261 | /// 262 | /// Gets the results of the view using the defaultDesignDoc and no view parameters. Use the overloads to specify options. 263 | /// 264 | /// 265 | /// 266 | /// 267 | public ViewResult View(string viewName) 268 | { 269 | ThrowDesignDocException(); 270 | return View(viewName, defaultDesignDoc); 271 | } 272 | public ViewResult View(string viewName) 273 | { 274 | ThrowDesignDocException(); 275 | return View(viewName, new ViewOptions()); 276 | } 277 | 278 | /// 279 | /// Call view cleanup for a database 280 | /// 281 | /// JSON success statement if the response code is Accepted 282 | public JObject ViewCleanup() 283 | { 284 | return CheckAccepted(GetRequest(databaseBaseUri + "/_view_cleanup").Post().Json().GetResponse()); 285 | } 286 | 287 | /// 288 | /// Compact the current database 289 | /// 290 | /// 291 | public JObject Compact() 292 | { 293 | return CheckAccepted(GetRequest(databaseBaseUri + "/_compact").Post().Json().GetResponse()); 294 | } 295 | 296 | /// 297 | /// Compact a view. 298 | /// 299 | /// The view to compact 300 | /// 301 | /// Requires admin permissions. 302 | public JObject Compact(string designDoc) 303 | { 304 | return CheckAccepted(GetRequest(databaseBaseUri + "/_compact/" + designDoc).Post().Json().GetResponse()); 305 | } 306 | 307 | private static JObject CheckAccepted(HttpWebResponse resp) 308 | { 309 | if (resp == null) { 310 | throw new System.Exception("Response returned null."); 311 | } 312 | 313 | if (resp.StatusCode != HttpStatusCode.Accepted) { 314 | throw new System.Exception("Response return with a HTTP Code of " + resp.StatusCode + " - " + resp.StatusDescription); 315 | } 316 | 317 | return resp.GetJObject(); 318 | 319 | } 320 | 321 | 322 | public string Show (string showName, string docId) 323 | { 324 | ThrowDesignDocException(); 325 | return Show(showName, docId, defaultDesignDoc); 326 | } 327 | 328 | private void ThrowDesignDocException() 329 | { 330 | if (string.IsNullOrEmpty(defaultDesignDoc)) 331 | throw new Exception("You must use SetDefaultDesignDoc prior to using this signature. Otherwise explicitly specify the design doc in the other overloads."); 332 | } 333 | 334 | public string Show(string showName, string docId, string designDoc) 335 | { 336 | //TODO: add in Etag support for Shows 337 | var uri = databaseBaseUri + "/_design/" + designDoc + "/_show/" + showName + "/" + docId; 338 | var req = GetRequest(uri); 339 | return req.GetResponse().GetResponseString(); 340 | } 341 | public IListResult List(string listName, string viewName, ViewOptions options, string designDoc) 342 | { 343 | var uri = databaseBaseUri + "/_design/" + designDoc + "/_list/" + listName + "/" + viewName + options.ToString(); 344 | var req = GetRequest(uri); 345 | return new ListResult(req.GetRequest(), req.GetResponse()); 346 | } 347 | 348 | public IListResult List(string listName, string viewName, ViewOptions options) 349 | { 350 | ThrowDesignDocException(); 351 | return List(listName, viewName, options, defaultDesignDoc); 352 | } 353 | 354 | public void SetDefaultDesignDoc(string designDoc) 355 | { 356 | this.defaultDesignDoc = designDoc; 357 | } 358 | 359 | private ViewResult ProcessGenericResults(string uri, ViewOptions options) { 360 | CouchRequest req = GetRequest(options, uri); 361 | var resp = req.GetResponse(); 362 | if (resp.StatusCode == HttpStatusCode.BadRequest) { 363 | throw new CouchException(req.GetRequest(), resp, resp.GetResponseString() + "\n" + req.GetRequest().RequestUri); 364 | } 365 | return new ViewResult(resp, req.GetRequest(), ObjectSerializer); 366 | } 367 | /// 368 | /// Gets the results of the view using any and all parameters 369 | /// 370 | /// The name of the view 371 | /// Options such as startkey etc. 372 | /// The design doc on which the view resides 373 | /// 374 | public ViewResult View(string viewName, ViewOptions options, string designDoc) 375 | { 376 | var uri = databaseBaseUri + "/_design/" + designDoc + "/_view/" + viewName; 377 | return ProcessGenericResults(uri, options); 378 | } 379 | /// 380 | /// Allows you to specify options and uses the defaultDesignDoc Specified. 381 | /// 382 | /// 383 | /// 384 | /// 385 | /// 386 | public ViewResult View(string viewName, ViewOptions options) 387 | { 388 | ThrowDesignDocException(); 389 | return View(viewName, options, defaultDesignDoc); 390 | } 391 | 392 | public ViewResult View(string viewName, ViewOptions options, string designDoc) 393 | { 394 | var uri = databaseBaseUri + "/_design/" + designDoc + "/_view/" + viewName; 395 | return ProcessResults(uri, options); 396 | } 397 | 398 | public ViewResult View(string viewName, ViewOptions options) 399 | { 400 | ThrowDesignDocException(); 401 | var uri = databaseBaseUri + "/_design/" + this.defaultDesignDoc + "/_view/" + viewName; 402 | return ProcessResults(uri, options); 403 | } 404 | private ViewResult ProcessResults(string uri, ViewOptions options) 405 | { 406 | CouchRequest req = GetRequest(options, uri); 407 | var resp = req.GetResponse(); 408 | return new ViewResult(resp, req.GetRequest()); 409 | } 410 | 411 | private CouchRequest GetRequest(ViewOptions options, string uri) 412 | { 413 | if (options != null) 414 | uri += options.ToString(); 415 | return GetRequest(uri, options == null ? null : options.Etag).Get().Json(); 416 | } 417 | 418 | 419 | /// 420 | /// Gets all the documents in the database using the _all_docs uri 421 | /// 422 | /// 423 | public ViewResult GetAllDocuments() 424 | { 425 | var uri = databaseBaseUri + "/_all_docs"; 426 | return ProcessResults(uri, null); 427 | } 428 | public ViewResult GetAllDocuments(ViewOptions options) 429 | { 430 | var uri = databaseBaseUri + "/_all_docs"; 431 | return ProcessResults(uri, options); 432 | } 433 | 434 | 435 | 436 | 437 | #region Security 438 | public SecurityDocument getSecurityConfiguration() 439 | { 440 | string request = databaseBaseUri + "/_security"; 441 | 442 | var docResult = GetRequest(request).Get().Json().GetResponse().GetJObject(); 443 | 444 | SecurityDocument sDoc = Newtonsoft.Json.JsonConvert.DeserializeObject(docResult.ToString()); 445 | 446 | return sDoc; 447 | } 448 | 449 | /// 450 | /// Updates security configuration for the database 451 | /// 452 | /// 453 | public void UpdateSecurityDocument(SecurityDocument sDoc) 454 | { 455 | string request = databaseBaseUri + "/_security"; 456 | 457 | // serialize SecurityDocument to json 458 | string data = Newtonsoft.Json.JsonConvert.SerializeObject(sDoc); 459 | 460 | var result = GetRequest(request).Put().Json().Data(data).GetResponse(); 461 | 462 | if (result.StatusCode != HttpStatusCode.OK) //Check if okay 463 | { 464 | throw new WebException("An error occurred while trying to update the security document. StatusDescription: " + result.StatusDescription); 465 | } 466 | } 467 | 468 | #endregion 469 | } 470 | 471 | #region Security Configuration 472 | 473 | // Example: {"admins":{},"readers":{"names":["dave"],"roles":[]}} 474 | /// 475 | /// Security configuration for the database 476 | /// 477 | public class SecurityDocument 478 | { 479 | public SecurityDocument() 480 | { 481 | admins = new UserType(); 482 | readers = new UserType(); 483 | } 484 | 485 | 486 | public UserType admins; 487 | public UserType readers; 488 | } 489 | 490 | public class UserType 491 | { 492 | public UserType() 493 | { 494 | names = new List(); 495 | roles = new List(); 496 | } 497 | 498 | public List names { get; set; } 499 | public List roles { get; set; } 500 | } 501 | #endregion 502 | 503 | } --------------------------------------------------------------------------------