From c3455e20b585b1e461bb309f0a6c11359403c7ce Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Wed, 4 Dec 2019 13:26:40 +0300 Subject: [PATCH 01/14] Add sharepoint synchronization service --- ...stant.ExternalStorages.Abstractions.csproj | 13 + .../BaseCondition.cs | 20 ++ .../EqualCondition.cs | 13 + .../ICondition.cs | 6 + .../IExternalStorage.cs | 18 ++ .../PropertyNameParser.cs | 29 +++ .../StorageItem.cs | 23 ++ ...t.ExternalStorages.SharepointOnline.csproj | 17 ++ .../Contracts/ISharepointAuthTokenService.cs | 11 + .../ISharepointConditionsCompiler.cs | 11 + .../Contracts/ISharepointFieldsMapper.cs | 12 + .../ISharepointOnlineConfiguration.cs | 11 + .../Contracts/ISharepointRequestExecutor.cs | 14 ++ .../Contracts/SharepointRequest.cs | 120 ++++++++++ .../SharepointApiModels/SharepointListItem.cs | 33 +++ .../SharepointListItemRequest.cs | 42 ++++ .../SharepointListItemsResponse.cs | 9 + .../SharepointListResponse.cs | 7 + .../SharepointAuthTokenService.cs | 210 +++++++++++++++++ .../SharepointConditionsCompiler.cs | 58 +++++ .../SharepointFieldsMapper.cs | 78 ++++++ .../SharepointOnlineConfiguration.cs | 13 + .../SharepointRequestExecutor.cs | 62 +++++ .../SharepointStorage.cs | 223 ++++++++++++++++++ .../Arcadia.Assistant.Sharepoint.csproj | 16 ++ .../PackageRoot/Config/Settings.xml | 11 + .../PackageRoot/ServiceManifest.xml | 35 +++ .../Arcadia.Assistant.Sharepoint/Program.cs | 38 +++ .../ServiceEventSource.cs | 175 ++++++++++++++ .../Sharepoint.cs | 52 ++++ .../Arcadia.Assistant/Arcadia.Assistant.sln | 152 ++++++++++++ 31 files changed, 1532 insertions(+) create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/Arcadia.Assistant.ExternalStorages.Abstractions.csproj create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/BaseCondition.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/EqualCondition.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/ICondition.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/IExternalStorage.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/PropertyNameParser.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/StorageItem.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Arcadia.Assistant.ExternalStorages.SharepointOnline.csproj create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointAuthTokenService.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointConditionsCompiler.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointFieldsMapper.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointOnlineConfiguration.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointRequestExecutor.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItem.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemRequest.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemsResponse.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListResponse.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointConditionsCompiler.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointOnlineConfiguration.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointRequestExecutor.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/ServiceManifest.xml create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ServiceEventSource.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/Arcadia.Assistant.ExternalStorages.Abstractions.csproj b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/Arcadia.Assistant.ExternalStorages.Abstractions.csproj new file mode 100644 index 000000000..3059692d3 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/Arcadia.Assistant.ExternalStorages.Abstractions.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.1 + AnyCPU;x64 + enable + + + + + + + \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/BaseCondition.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/BaseCondition.cs new file mode 100644 index 000000000..60aecc71f --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/BaseCondition.cs @@ -0,0 +1,20 @@ +namespace Arcadia.Assistant.ExternalStorages.Abstractions +{ + using System; + using System.Linq.Expressions; + + public abstract class BaseCondition : ICondition + { + protected BaseCondition(Expression> property, object value) + { + new PropertyNameParser().EnsureExpressionIsProperty(property); + + this.Property = property; + this.Value = value; + } + + public Expression> Property { get; } + + public object Value { get; } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/EqualCondition.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/EqualCondition.cs new file mode 100644 index 000000000..64fcc9100 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/EqualCondition.cs @@ -0,0 +1,13 @@ +namespace Arcadia.Assistant.ExternalStorages.Abstractions +{ + using System; + using System.Linq.Expressions; + + public class EqualCondition : BaseCondition + { + public EqualCondition(Expression> property, object value) + : base(property, value) + { + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/ICondition.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/ICondition.cs new file mode 100644 index 000000000..630b4e76a --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/ICondition.cs @@ -0,0 +1,6 @@ +namespace Arcadia.Assistant.ExternalStorages.Abstractions +{ + public interface ICondition + { + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/IExternalStorage.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/IExternalStorage.cs new file mode 100644 index 000000000..92256a8ca --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/IExternalStorage.cs @@ -0,0 +1,18 @@ +namespace Arcadia.Assistant.ExternalStorages.Abstractions +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + public interface IExternalStorage : IDisposable + { + Task> GetItems(string list, IEnumerable? conditions = null, CancellationToken cancellationToken = default); + + Task AddItem(string list, StorageItem item, CancellationToken cancellationToken = default); + + Task UpdateItem(string list, StorageItem item, CancellationToken cancellationToken = default); + + Task DeleteItem(string list, string itemId, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/PropertyNameParser.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/PropertyNameParser.cs new file mode 100644 index 000000000..a76a6389e --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/PropertyNameParser.cs @@ -0,0 +1,29 @@ +namespace Arcadia.Assistant.ExternalStorages.Abstractions +{ + using System; + using System.Linq.Expressions; + using System.Reflection; + + public class PropertyNameParser + { + public void EnsureExpressionIsProperty(Expression> expression) + { + this.GetName(expression); + } + + public string GetName(Expression> expression) + { + switch (expression.Body) + { + case MemberExpression member when member.Member.MemberType == MemberTypes.Property: + return member.Member.Name; + + case UnaryExpression unary when unary.Operand is MemberExpression operand && operand.Member.MemberType == MemberTypes.Property: + return operand.Member.Name; + + default: + throw new ArgumentException("Expression is not a property", nameof(expression)); + } + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/StorageItem.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/StorageItem.cs new file mode 100644 index 000000000..30dace379 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/StorageItem.cs @@ -0,0 +1,23 @@ +namespace Arcadia.Assistant.ExternalStorages.Abstractions +{ + using System; + + public class StorageItem + { + public string Id { get; set; } = string.Empty; + + public string Title { get; set; } = string.Empty; + + public string Description { get; set; } = string.Empty; + + public DateTime StartDate { get; set; } + + public DateTime EndDate { get; set; } + + public string Category { get; set; } = string.Empty; + + public bool AllDayEvent { get; set; } + + public string CalendarEventId { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Arcadia.Assistant.ExternalStorages.SharepointOnline.csproj b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Arcadia.Assistant.ExternalStorages.SharepointOnline.csproj new file mode 100644 index 000000000..63ca4a47c --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Arcadia.Assistant.ExternalStorages.SharepointOnline.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.1 + AnyCPU;x64 + enable + + + + + + + + + + + \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointAuthTokenService.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointAuthTokenService.cs new file mode 100644 index 000000000..ccccd3721 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointAuthTokenService.cs @@ -0,0 +1,11 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline.Contracts +{ + using System; + using System.Threading; + using System.Threading.Tasks; + + public interface ISharepointAuthTokenService : IDisposable + { + Task GetAccessToken(string sharepointUrl, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointConditionsCompiler.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointConditionsCompiler.cs new file mode 100644 index 000000000..6087be6e3 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointConditionsCompiler.cs @@ -0,0 +1,11 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline.Contracts +{ + using System.Collections.Generic; + + using Abstractions; + + public interface ISharepointConditionsCompiler + { + string? CompileConditions(IEnumerable? conditions = null); + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointFieldsMapper.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointFieldsMapper.cs new file mode 100644 index 000000000..f27d2ffb8 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointFieldsMapper.cs @@ -0,0 +1,12 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline.Contracts +{ + using System; + using System.Linq.Expressions; + + using Abstractions; + + public interface ISharepointFieldsMapper + { + string GetSharepointField(Expression> property); + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointOnlineConfiguration.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointOnlineConfiguration.cs new file mode 100644 index 000000000..6a3d028c1 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointOnlineConfiguration.cs @@ -0,0 +1,11 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline.Contracts +{ + public interface ISharepointOnlineConfiguration + { + string ServerUrl { get; } + + string ClientId { get; } + + string ClientSecret { get; } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointRequestExecutor.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointRequestExecutor.cs new file mode 100644 index 000000000..c5be0a253 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointRequestExecutor.cs @@ -0,0 +1,14 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline.Contracts +{ + using System; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + public interface ISharepointRequestExecutor : IDisposable + { + Task ExecuteSharepointRequest(SharepointRequest request, CancellationToken cancellationToken = default); + + Task ExecuteSharepointRequest(SharepointRequest request, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs new file mode 100644 index 000000000..abbae1b1e --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs @@ -0,0 +1,120 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline.Contracts +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Net.Http.Headers; + + using Newtonsoft.Json; + + public class SharepointRequest + { + private const string AcceptHeaderName = "Accept"; + private const string AuthorizationHeaderName = "Authorization"; + private const string ContentTypeHeaderName = "Content-Type"; + private const string ContentLengthHeaderName = "Content-Length"; + private const string IfMatchHeaderName = "IF-MATCH"; + private const string XHttpMethodHeaderName = "X-HTTP-Method"; + + private readonly List> headersInternal = new List>(); + + private SharepointRequest(HttpMethod httpMethod, string url) + { + this.HttpMethod = httpMethod; + this.Url = url; + } + + public HttpMethod HttpMethod { get; } + + public string Url { get; } + + public HttpContent? Content { get; private set; } + + public IReadOnlyList> Headers => this.headersInternal; + + public static SharepointRequest Create(HttpMethod httpMethod, string url) + { + return new SharepointRequest(httpMethod, url); + } + + public SharepointRequest WithAcceptHeader(string value) + { + this.AddHeader(AcceptHeaderName, value); + return this; + } + + public SharepointRequest WithBearerAuthorizationHeader(string value) + { + this.AddHeader(AuthorizationHeaderName, value); + return this; + } + + public SharepointRequest WithContent(object content) + { + var contentString = JsonConvert.SerializeObject(content); + this.Content = new StringContent(contentString); + + this.AddHeader(ContentTypeHeaderName, "application/json;odata=verbose"); + this.AddHeader(ContentLengthHeaderName, contentString.Length.ToString()); + + return this; + } + + public SharepointRequest WithIfMatchHeader() + { + this.AddHeader(IfMatchHeaderName, "*"); + return this; + } + + public SharepointRequest WithXHttpMethodHeader(string value) + { + this.AddHeader(XHttpMethodHeaderName, value); + return this; + } + + public HttpRequestMessage GetHttpRequest() + { + var httpRequest = new HttpRequestMessage(this.HttpMethod, this.Url); + + if (this.Content != null) + { + httpRequest.Content = this.Content; + } + + foreach (var (headerName, headerValue) in this.headersInternal) + { + switch (headerName) + { + case AcceptHeaderName: + httpRequest.Headers.Accept.Clear(); + httpRequest.Headers.Accept.TryParseAdd(headerValue); + break; + + case AuthorizationHeaderName: + httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", headerValue); + break; + + case ContentTypeHeaderName: + httpRequest.Content.Headers.Remove(headerName); + httpRequest.Content.Headers.TryAddWithoutValidation(headerName, headerValue); + break; + + case ContentLengthHeaderName: + httpRequest.Content.Headers.ContentLength = long.Parse(headerValue); + break; + + default: + httpRequest.Headers.TryAddWithoutValidation(headerName, headerValue); + break; + } + } + + return httpRequest; + } + + private void AddHeader(string name, string value) + { + this.headersInternal.Add(Tuple.Create(name, value)); + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItem.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItem.cs new file mode 100644 index 000000000..dc82209f6 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItem.cs @@ -0,0 +1,33 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline.SharepointApiModels +{ + using System; + using System.Runtime.Serialization; + + [DataContract] + public class SharepointListItem + { + [DataMember] + public int Id { get; set; } + + [DataMember] + public string Title { get; set; } = string.Empty; + + [DataMember] + public string Description { get; set; } = string.Empty; + + [DataMember] + public DateTime EventDate { get; set; } + + [DataMember] + public DateTime EndDate { get; set; } + + [DataMember] + public string Category { get; set; } = string.Empty; + + [DataMember(Name = "fAllDayEvent")] + public bool AllDayEvent { get; set; } + + [DataMember] + public string CalendarEventId { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemRequest.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemRequest.cs new file mode 100644 index 000000000..07f956911 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemRequest.cs @@ -0,0 +1,42 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline.SharepointApiModels +{ + using System.Runtime.Serialization; + + [DataContract] + public class SharepointListItemRequest + { + [DataMember(Name = "__metadata")] + public MetadataRequest Metadata { get; set; } = new MetadataRequest(); + + [DataMember] + public int Id { get; set; } + + [DataMember] + public string Title { get; set; } = string.Empty; + + [DataMember] + public string Description { get; set; } = string.Empty; + + [DataMember] + public string EventDate { get; set; } = string.Empty; + + [DataMember] + public string EndDate { get; set; } = string.Empty; + + [DataMember] + public string Category { get; set; } = string.Empty; + + [DataMember(Name = "fAllDayEvent")] + public bool AllDayEvent { get; set; } + + [DataMember] + public string CalendarEventId { get; set; } = string.Empty; + + [DataContract] + public class MetadataRequest + { + [DataMember(Name = "type")] + public string? Type { get; set; } + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemsResponse.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemsResponse.cs new file mode 100644 index 000000000..2ea6c9070 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemsResponse.cs @@ -0,0 +1,9 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline.SharepointApiModels +{ + using System.Collections.Generic; + + public class SharepointListItemsResponse + { + public IEnumerable? Value { get; set; } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListResponse.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListResponse.cs new file mode 100644 index 000000000..568e92e5b --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListResponse.cs @@ -0,0 +1,7 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline.SharepointApiModels +{ + public class SharepointListResponse + { + public string? ListItemEntityTypeFullName { get; set; } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs new file mode 100644 index 000000000..7beea5d0a --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs @@ -0,0 +1,210 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Runtime.Serialization; + using System.Threading; + using System.Threading.Tasks; + using System.Web; + + using Contracts; + + using Newtonsoft.Json; + + public class SharepointAuthTokenService : ISharepointAuthTokenService + { + private const string SharePointPrincipal = "00000003-0000-0ff1-ce00-000000000000"; + + private const string AcsMetadataEndpoint = "https://accounts.accesscontrol.windows.net/metadata/json/1"; + private const string OAuth2GrantType = "client_credentials"; + private const string OAuth2Protocol = "OAuth2"; + + private readonly ISharepointOnlineConfiguration configuration; + private readonly HttpClient httpClient; + + public SharepointAuthTokenService(ISharepointOnlineConfiguration configuration, IHttpClientFactory httpClientFactory) + { + this.configuration = configuration; + this.httpClient = httpClientFactory.CreateClient(); + } + + public async Task GetAccessToken(string sharepointUrl, CancellationToken cancellationToken) + { + var realm = await this.GetRealm(sharepointUrl, cancellationToken); + var hostName = new Uri(sharepointUrl).Authority; + + var oauthClientId = this.GetFormattedPrincipal(this.configuration.ClientId, null, realm); + var oauthResource = this.GetFormattedPrincipal(SharePointPrincipal, hostName, realm); + + var accessToken = await this.IssueToken( + realm, + oauthClientId, + this.configuration.ClientSecret, + oauthResource, + cancellationToken); + + if (accessToken == null) + { + throw new Exception("Access token is null"); + } + + return accessToken; + } + + public void Dispose() + { + this.httpClient.Dispose(); + } + + private async Task GetRealm(string urlSharePoint, CancellationToken cancellationToken) + { + var request = new HttpRequestMessage(HttpMethod.Get, urlSharePoint + "/_vti_bin/client.svc"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", string.Empty); + + var response = await this.httpClient.SendAsync(request, cancellationToken); + + if (response.StatusCode != HttpStatusCode.Unauthorized || response.Headers.WwwAuthenticate == null) + { + throw new HttpRequestException("Not supported response to a realm request"); + } + + var wwwAuthenticateHeaderValue = response.Headers.WwwAuthenticate.ToString(); + + var realm = this.GetRealmFromHeader(wwwAuthenticateHeaderValue); + + if (realm == null) + { + throw new HttpRequestException("Not supported response to a realm request"); + } + + return realm; + } + + private string? GetRealmFromHeader(string headerValue) + { + const string bearer = "Bearer realm=\""; + const int realmLength = 36; + + var bearerIndex = headerValue.IndexOf(bearer, StringComparison.Ordinal); + + if (bearerIndex < 0) + { + return null; + } + + var realmStartIndex = bearerIndex + bearer.Length; + + if (headerValue.Length < realmStartIndex + realmLength) + { + return null; + } + + var realm = headerValue.Substring(realmStartIndex, realmLength); + + return !Guid.TryParse(realm, out _) ? null : realm; + } + + private async Task IssueToken( + string realm, + string tokenClientId, + string tokenClientSecret, + string tokenResource, + CancellationToken cancellationToken) + { + var tokenUrl = await this.GetOAuth2Url(realm, cancellationToken); + + var requestData = + "grant_type=" + HttpUtility.UrlEncode(OAuth2GrantType) + + "&client_id=" + HttpUtility.UrlEncode(tokenClientId) + + "&client_secret=" + HttpUtility.UrlEncode(tokenClientSecret) + + "&resource=" + HttpUtility.UrlEncode(tokenResource); + + var request = new HttpRequestMessage(HttpMethod.Post, tokenUrl) + { + Content = new StringContent(requestData) + }; + + request.Content.Headers.Remove("Content-Type"); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); + request.Content.Headers.ContentLength = requestData.Length; + + var response = await this.httpClient.SendAsync(request, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException("Access token response has failed"); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + + var oauthResult = JsonConvert.DeserializeObject(responseContent); + return oauthResult.AccessToken; + } + + private async Task GetOAuth2Url(string realm, CancellationToken cancellationToken) + { + var metadata = await this.GetMetadataDocument(realm, cancellationToken); + + var s2SEndpoint = metadata.Endpoints?.SingleOrDefault(e => e.Protocol == OAuth2Protocol); + + if (s2SEndpoint != null) + { + return s2SEndpoint.Location; + } + + throw new Exception("Metadata document does not contain OAuth2 endpoint url"); + } + + private async Task GetMetadataDocument(string realm, CancellationToken cancellationToken) + { + var metadataEndpointUrl = $"{AcsMetadataEndpoint}?realm={realm}"; + + var response = await this.httpClient.GetAsync(metadataEndpointUrl, cancellationToken); + var metadataString = await response.Content.ReadAsStringAsync(); + var metadata = JsonConvert.DeserializeObject(metadataString); + + if (metadata == null) + { + throw new Exception($"No metadata document found at the global endpoint {metadataEndpointUrl}"); + } + + return metadata; + } + + private string GetFormattedPrincipal(string principalName, string? hostName, string realm) + { + return string.IsNullOrEmpty(hostName) + ? string.Format(CultureInfo.InvariantCulture, "{0}@{1}", principalName, realm) + : string.Format(CultureInfo.InvariantCulture, "{0}/{1}@{2}", principalName, hostName, realm); + } + + [DataContract] + private class JsonMetadataDocument + { + [DataMember(Name = "endpoints")] + public IEnumerable? Endpoints { get; set; } + } + + [DataContract] + private class JsonEndpoint + { + [DataMember(Name = "location")] + public string? Location { get; set; } + + [DataMember(Name = "protocol")] + public string? Protocol { get; set; } + } + + [DataContract] + private class OAuth2AccessTokenResponse + { + [DataMember(Name = "access_token")] + public string? AccessToken { get; set; } + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointConditionsCompiler.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointConditionsCompiler.cs new file mode 100644 index 000000000..8af12f335 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointConditionsCompiler.cs @@ -0,0 +1,58 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + + using Abstractions; + + using Contracts; + + public class SharepointConditionsCompiler : ISharepointConditionsCompiler + { + private readonly ISharepointFieldsMapper fieldsMapper; + + public SharepointConditionsCompiler(ISharepointFieldsMapper fieldsMapper) + { + this.fieldsMapper = fieldsMapper; + } + + public string? CompileConditions(IEnumerable? conditions = null) + { + if (conditions == null) + { + return null; + } + + var compiledConditions = conditions + .Select(this.CompileCondition); + + return $"$filter={HttpUtility.UrlEncode(string.Join(" and ", compiledConditions))}"; + } + + private string CompileCondition(ICondition condition) + { + return condition switch + { + EqualCondition equalCondition => this.GetEqualCompileCondition(equalCondition), + + _ => throw new ArgumentException($"Not supported condition type: {condition.GetType()}", nameof(condition)), + }; + } + + private string GetEqualCompileCondition(BaseCondition equalCondition) + { + var sharepointField = this.fieldsMapper.GetSharepointField(equalCondition.Property); + + if (equalCondition.Value is DateTime dateTimeValue) + { + throw new NotImplementedException("Filter by datetime is not supported yet."); + } + + var fieldValue = equalCondition.Value; + + return $"{sharepointField} eq '{fieldValue}'"; + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs new file mode 100644 index 000000000..a9241e03f --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs @@ -0,0 +1,78 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + + using Abstractions; + + using Contracts; + + public class SharepointFieldsMapper : ISharepointFieldsMapper + { + private static readonly SharepointFieldMapping CalendarEventIdMapping = + CreateMapping(item => item.CalendarEventId, "CalendarEventId"); + + private readonly Dictionary fieldsByPropertyName; + + public SharepointFieldsMapper() + : this(DefaultMappingWithCalendarEventId) + { + } + + public SharepointFieldsMapper(params SharepointFieldMapping[] fields) + { + this.fieldsByPropertyName = fields + .ToDictionary(f => this.GetPropertyName(f.Property), f => f.Field); + } + + public static IEnumerable DefaultMapping { get; } = new[] + { + CreateMapping(item => item.Id, "ID"), + CreateMapping(item => item.Title, "Title"), + CreateMapping(item => item.Description, "Description"), + CreateMapping(item => item.StartDate, "EventDate"), + CreateMapping(item => item.EndDate, "EndDate"), + CreateMapping(item => item.Category, "Category"), + CreateMapping(item => item.AllDayEvent, "fAllDayEvent") + }; + + private static SharepointFieldMapping[] DefaultMappingWithCalendarEventId { get; } = DefaultMapping.Union(new[] { CalendarEventIdMapping }).ToArray(); + + public string GetSharepointField(Expression> property) + { + var propertyName = this.GetPropertyName(property); + + if (this.fieldsByPropertyName.TryGetValue(propertyName, out var field)) + { + return field; + } + + throw new ArgumentException($"Mapping for property '{propertyName}' doesn't exist"); + } + + public static SharepointFieldMapping CreateMapping(Expression> property, string fieldName) + { + return new SharepointFieldMapping(property, fieldName); + } + + private string GetPropertyName(Expression> property) + { + return new PropertyNameParser().GetName(property); + } + + public struct SharepointFieldMapping + { + public SharepointFieldMapping(Expression> property, string field) + { + this.Property = property; + this.Field = field; + } + + public Expression> Property { get; } + + public string Field { get; } + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointOnlineConfiguration.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointOnlineConfiguration.cs new file mode 100644 index 000000000..6558169b7 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointOnlineConfiguration.cs @@ -0,0 +1,13 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline +{ + using Contracts; + + public class SharepointOnlineConfiguration : ISharepointOnlineConfiguration + { + public string ServerUrl { get; set; } = string.Empty; + + public string ClientId { get; set; } = string.Empty; + + public string ClientSecret { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointRequestExecutor.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointRequestExecutor.cs new file mode 100644 index 000000000..b62f1e703 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointRequestExecutor.cs @@ -0,0 +1,62 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline +{ + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + using Contracts; + + using Newtonsoft.Json; + + public class SharepointRequestExecutor : ISharepointRequestExecutor + { + private readonly ISharepointAuthTokenService authTokenService; + private readonly ISharepointOnlineConfiguration configuration; + private readonly HttpClient httpClient; + + private string? accessToken; + + public SharepointRequestExecutor( + ISharepointOnlineConfiguration configuration, + ISharepointAuthTokenService authTokenService, + IHttpClientFactory httpClientFactory) + { + this.configuration = configuration; + this.authTokenService = authTokenService; + this.httpClient = httpClientFactory.CreateClient(); + } + + public async Task ExecuteSharepointRequest(SharepointRequest request, CancellationToken cancellationToken = default) + { + var response = await this.ExecuteSharepointRequest(request, cancellationToken); + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Request failed with {response.StatusCode} status code"); + } + + var content = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(content); + } + + public async Task ExecuteSharepointRequest(SharepointRequest request, CancellationToken cancellationToken = default) + { + // To cache access token for several Sharepoint requests in bounds of one request to storage + if (this.accessToken == null) + { + this.accessToken = await this.authTokenService.GetAccessToken(this.configuration.ServerUrl, cancellationToken); + } + + request = request + .WithAcceptHeader("application/json;odata=nometadata") + .WithBearerAuthorizationHeader(this.accessToken); + + return await this.httpClient.SendAsync(request.GetHttpRequest(), cancellationToken); + } + + public void Dispose() + { + this.httpClient.Dispose(); + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs new file mode 100644 index 000000000..bf8c7d502 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs @@ -0,0 +1,223 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + using Abstractions; + + using Contracts; + + using SharepointApiModels; + + public class SharepointStorage : IExternalStorage + { + private readonly ISharepointConditionsCompiler conditionsCompiler; + private readonly ISharepointOnlineConfiguration configuration; + private readonly ISharepointFieldsMapper fieldsMapper; + private readonly ISharepointRequestExecutor requestExecutor; + + public SharepointStorage( + ISharepointOnlineConfiguration configuration, + ISharepointRequestExecutor requestExecutor, + ISharepointFieldsMapper sharePointFieldsMapper, + ISharepointConditionsCompiler sharePointConditionsCompiler) + { + this.configuration = configuration; + this.requestExecutor = requestExecutor; + this.fieldsMapper = sharePointFieldsMapper; + this.conditionsCompiler = sharePointConditionsCompiler; + } + + public async Task> GetItems(string list, IEnumerable? conditions, CancellationToken cancellationToken) + { + var listItems = await this.GetListItems(list, conditions, cancellationToken); + + return listItems + .Select(this.ListItemToStorageItem) + .ToArray(); + } + + public async Task AddItem(string list, StorageItem item, CancellationToken cancellationToken) + { + var listItemType = await this.GetListItemType(list, cancellationToken); + var requestData = this.StorageItemToListItemRequest(item, listItemType); + + var request = SharepointRequest + .Create(HttpMethod.Post, this.GetListItemsUrl(list)) + .WithContent(requestData); + + var addedItem = await this.requestExecutor.ExecuteSharepointRequest(request, cancellationToken); + + return this.ListItemToStorageItem(addedItem); + } + + public async Task UpdateItem(string list, StorageItem item, CancellationToken cancellationToken) + { + var idConditions = new[] { new EqualCondition(x => x.Id, item.Id) }; + var listItems = (await this.GetListItems(list, idConditions, cancellationToken)).ToArray(); + + this.EnsureSingleItemReturned(listItems); + + var existingListItem = listItems.First(); + + var updateItemUrl = this.GetListItemsUrl(list, false); + updateItemUrl += $"({existingListItem.Id})"; + + var listItemType = await this.GetListItemType(list, cancellationToken); + + var requestData = this.StorageItemToListItemRequest(item, listItemType); + + var request = SharepointRequest + .Create(HttpMethod.Post, updateItemUrl) + .WithContent(requestData) + .WithIfMatchHeader() + .WithXHttpMethodHeader("MERGE"); + + await this.requestExecutor.ExecuteSharepointRequest(request, cancellationToken); + } + + public async Task DeleteItem(string list, string itemId, CancellationToken cancellationToken) + { + var idConditions = new[] { new EqualCondition(x => x.Id, itemId) }; + var listItems = (await this.GetListItems(list, idConditions, cancellationToken)).ToArray(); + + this.EnsureSingleItemReturned(listItems); + + var existingListItem = listItems.First(); + + var deleteItemUrl = this.GetListItemsUrl(list, false); + deleteItemUrl += $"({existingListItem.Id})"; + + var request = SharepointRequest + .Create(HttpMethod.Post, deleteItemUrl) + .WithIfMatchHeader() + .WithXHttpMethodHeader("DELETE"); + + await this.requestExecutor.ExecuteSharepointRequest(request, cancellationToken); + } + + public void Dispose() + { + } + + private async Task?> GetListItems( + string list, + IEnumerable? conditions, + CancellationToken cancellationToken) + { + var itemsUrl = this.GetListItemsUrl(list); + + var conditionsUrlPart = this.conditionsCompiler.CompileConditions(conditions); + + if (conditionsUrlPart != null) + { + itemsUrl += $"&{conditionsUrlPart}"; + } + + var request = SharepointRequest.Create(HttpMethod.Get, itemsUrl); + var response = await this.requestExecutor.ExecuteSharepointRequest(request, cancellationToken); + + return response.Value; + } + + private async Task GetListItemType(string list, CancellationToken cancellationToken) + { + var listUrl = $"{this.GetListUrl(list)}?$select=ListItemEntityTypeFullName"; + var request = SharepointRequest.Create(HttpMethod.Get, listUrl); + var response = await this.requestExecutor.ExecuteSharepointRequest(request, cancellationToken); + return response.ListItemEntityTypeFullName; + } + + private string GetListUrl(string list) + { + return $"{this.configuration.ServerUrl}/_api/lists/GetByTitle('{list}')"; + } + + private string GetListItemsUrl(string list, bool includeSelectPart = true) + { + var baseUrl = $"{this.GetListUrl(list)}/items"; + + var fieldNames = this.GetFieldNames(); + + var selectUrlPart = + $"$select={fieldNames.Id},{fieldNames.Title},{fieldNames.Description},{fieldNames.StartDate}," + + $"{fieldNames.EndDate},{fieldNames.Category},{fieldNames.AllDayEvent},{fieldNames.CalendarEventId}"; + + return !includeSelectPart + ? baseUrl + : $"{baseUrl}?{selectUrlPart}"; + } + + private StorageItem ListItemToStorageItem(SharepointListItem item) + { + return new StorageItem + { + Id = item.Id.ToString(), + Title = item.Title, + Description = item.Description, + StartDate = item.EventDate, + EndDate = item.EndDate, + Category = item.Category, + AllDayEvent = item.AllDayEvent, + CalendarEventId = item.CalendarEventId + }; + } + + private SharepointListItemRequest StorageItemToListItemRequest(StorageItem item, string? listItemType) + { + return new SharepointListItemRequest + { + Metadata = new SharepointListItemRequest.MetadataRequest + { + Type = listItemType + }, + Title = item.Title, + Description = item.Description, + EventDate = item.StartDate.ToString("d"), + EndDate = item.EndDate.ToString("d"), + Category = item.Category, + AllDayEvent = item.AllDayEvent, + CalendarEventId = item.CalendarEventId + }; + } + + private void EnsureSingleItemReturned(SharepointListItem[] listItems) + { + if (listItems.Length == 0) + { + throw new ArgumentException("No items were found by specified conditions"); + } + + if (listItems.Length > 1) + { + throw new ArgumentException("More than one item was found by specified conditions"); + } + } + + private ( + string Id, + string Title, + string Description, + string StartDate, + string EndDate, + string Category, + string AllDayEvent, + string CalendarEventId + ) GetFieldNames() + { + var idField = this.fieldsMapper.GetSharepointField(si => si.Id); + var titleField = this.fieldsMapper.GetSharepointField(si => si.Title); + var descriptionField = this.fieldsMapper.GetSharepointField(si => si.Description); + var startDateField = this.fieldsMapper.GetSharepointField(si => si.StartDate); + var endDateField = this.fieldsMapper.GetSharepointField(si => si.EndDate); + var categoryField = this.fieldsMapper.GetSharepointField(si => si.Category); + var allDayEvent = this.fieldsMapper.GetSharepointField(si => si.AllDayEvent); + var calendarEventIdField = this.fieldsMapper.GetSharepointField(si => si.CalendarEventId); + return (idField, titleField, descriptionField, startDateField, endDateField, categoryField, allDayEvent, calendarEventIdField); + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj new file mode 100644 index 000000000..1725162b7 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj @@ -0,0 +1,16 @@ + + + + Exe + netcoreapp3.0 + True + True + win7-x64 + False + + + + + + + diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml new file mode 100644 index 000000000..aea0ce165 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/ServiceManifest.xml b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/ServiceManifest.xml new file mode 100644 index 000000000..76632f8c7 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/ServiceManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + Arcadia.Assistant.Sharepoint.exe + + + + + + + + + + + + + + \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs new file mode 100644 index 000000000..e0ce85202 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs @@ -0,0 +1,38 @@ +namespace Arcadia.Assistant.Sharepoint +{ + using System; + using System.Diagnostics; + using System.Threading; + + using Microsoft.ServiceFabric.Services.Runtime; + + internal static class Program + { + /// + /// This is the entry point of the service host process. + /// + private static void Main() + { + try + { + // The ServiceManifest.XML file defines one or more service type names. + // Registering a service maps a service type name to a .NET type. + // When Service Fabric creates an instance of this service type, + // an instance of the class is created in this host process. + + ServiceRuntime.RegisterServiceAsync("Arcadia.Assistant.SharepointType", + context => new Sharepoint(context)).GetAwaiter().GetResult(); + + ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(Sharepoint).Name); + + // Prevents this host process from terminating so services keep running. + Thread.Sleep(Timeout.Infinite); + } + catch (Exception e) + { + ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString()); + throw; + } + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ServiceEventSource.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ServiceEventSource.cs new file mode 100644 index 000000000..642073ea7 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ServiceEventSource.cs @@ -0,0 +1,175 @@ +namespace Arcadia.Assistant.Sharepoint +{ + using System; + using System.Diagnostics.Tracing; + using System.Fabric; + + [EventSource(Name = "MyCompany-Arcadia.Assistant.SF-Arcadia.Assistant.Sharepoint")] + internal sealed class ServiceEventSource : EventSource + { + public static readonly ServiceEventSource Current = new ServiceEventSource(); + + // Instance constructor is private to enforce singleton semantics + private ServiceEventSource() + { + } + + #region Keywords + + // Event keywords can be used to categorize events. + // Each keyword is a bit flag. A single event can be associated with multiple keywords (via EventAttribute.Keywords property). + // Keywords must be defined as a public class named 'Keywords' inside EventSource that uses them. + public static class Keywords + { + public const EventKeywords Requests = (EventKeywords)0x1L; + public const EventKeywords ServiceInitialization = (EventKeywords)0x2L; + } + + #endregion + + #region Events + + // Define an instance method for each event you want to record and apply an [Event] attribute to it. + // The method name is the name of the event. + // Pass any parameters you want to record with the event (only primitive integer types, DateTime, Guid & string are allowed). + // Each event method implementation should check whether the event source is enabled, and if it is, call WriteEvent() method to raise the event. + // The number and types of arguments passed to every event method must exactly match what is passed to WriteEvent(). + // Put [NonEvent] attribute on all methods that do not define an event. + // For more information see https://msdn.microsoft.com/en-us/library/system.diagnostics.tracing.eventsource.aspx + + [NonEvent] + public void Message(string message, params object[] args) + { + if (this.IsEnabled()) + { + var finalMessage = string.Format(message, args); + this.Message(finalMessage); + } + } + + private const int MessageEventId = 1; + + [Event(MessageEventId, Level = EventLevel.Informational, Message = "{0}")] + public void Message(string message) + { + if (this.IsEnabled()) + { + this.WriteEvent(MessageEventId, message); + } + } + + [NonEvent] + public void ServiceMessage(StatelessServiceContext serviceContext, string message, params object[] args) + { + if (this.IsEnabled()) + { + var finalMessage = string.Format(message, args); + this.ServiceMessage( + serviceContext.ServiceName.ToString(), + serviceContext.ServiceTypeName, + serviceContext.InstanceId, + serviceContext.PartitionId, + serviceContext.CodePackageActivationContext.ApplicationName, + serviceContext.CodePackageActivationContext.ApplicationTypeName, + serviceContext.NodeContext.NodeName, + finalMessage); + } + } + + // For very high-frequency events it might be advantageous to raise events using WriteEventCore API. + // This results in more efficient parameter handling, but requires explicit allocation of EventData structure and unsafe code. + // To enable this code path, define UNSAFE conditional compilation symbol and turn on unsafe code support in project properties. + private const int ServiceMessageEventId = 2; + + [Event(ServiceMessageEventId, Level = EventLevel.Informational, Message = "{7}")] + private +#if UNSAFE + unsafe +#endif + void ServiceMessage( + string serviceName, + string serviceTypeName, + long replicaOrInstanceId, + Guid partitionId, + string applicationName, + string applicationTypeName, + string nodeName, + string message) + { +#if !UNSAFE + this.WriteEvent(ServiceMessageEventId, serviceName, serviceTypeName, replicaOrInstanceId, partitionId, applicationName, applicationTypeName, nodeName, message); +#else + const int numArgs = 8; + fixed (char* pServiceName = serviceName, pServiceTypeName = serviceTypeName, pApplicationName = applicationName, pApplicationTypeName = applicationTypeName, pNodeName = nodeName, pMessage = message) + { + EventData* eventData = stackalloc EventData[numArgs]; + eventData[0] = new EventData { DataPointer = (IntPtr) pServiceName, Size = SizeInBytes(serviceName) }; + eventData[1] = new EventData { DataPointer = (IntPtr) pServiceTypeName, Size = SizeInBytes(serviceTypeName) }; + eventData[2] = new EventData { DataPointer = (IntPtr) (&replicaOrInstanceId), Size = sizeof(long) }; + eventData[3] = new EventData { DataPointer = (IntPtr) (&partitionId), Size = sizeof(Guid) }; + eventData[4] = new EventData { DataPointer = (IntPtr) pApplicationName, Size = SizeInBytes(applicationName) }; + eventData[5] = new EventData { DataPointer = (IntPtr) pApplicationTypeName, Size = SizeInBytes(applicationTypeName) }; + eventData[6] = new EventData { DataPointer = (IntPtr) pNodeName, Size = SizeInBytes(nodeName) }; + eventData[7] = new EventData { DataPointer = (IntPtr) pMessage, Size = SizeInBytes(message) }; + + WriteEventCore(ServiceMessageEventId, numArgs, eventData); + } +#endif + } + + private const int ServiceTypeRegisteredEventId = 3; + + [Event(ServiceTypeRegisteredEventId, Level = EventLevel.Informational, Message = "Service host process {0} registered service type {1}", Keywords = Keywords.ServiceInitialization)] + public void ServiceTypeRegistered(int hostProcessId, string serviceType) + { + this.WriteEvent(ServiceTypeRegisteredEventId, hostProcessId, serviceType); + } + + private const int ServiceHostInitializationFailedEventId = 4; + + [Event(ServiceHostInitializationFailedEventId, Level = EventLevel.Error, Message = "Service host initialization failed", Keywords = Keywords.ServiceInitialization)] + public void ServiceHostInitializationFailed(string exception) + { + this.WriteEvent(ServiceHostInitializationFailedEventId, exception); + } + + // A pair of events sharing the same name prefix with a "Start"/"Stop" suffix implicitly marks boundaries of an event tracing activity. + // These activities can be automatically picked up by debugging and profiling tools, which can compute their execution time, child activities, + // and other statistics. + private const int ServiceRequestStartEventId = 5; + + [Event(ServiceRequestStartEventId, Level = EventLevel.Informational, Message = "Service request '{0}' started", Keywords = Keywords.Requests)] + public void ServiceRequestStart(string requestTypeName) + { + this.WriteEvent(ServiceRequestStartEventId, requestTypeName); + } + + private const int ServiceRequestStopEventId = 6; + + [Event(ServiceRequestStopEventId, Level = EventLevel.Informational, Message = "Service request '{0}' finished", Keywords = Keywords.Requests)] + public void ServiceRequestStop(string requestTypeName, string exception = "") + { + this.WriteEvent(ServiceRequestStopEventId, requestTypeName, exception); + } + + #endregion + + #region Private methods + +#if UNSAFE + private int SizeInBytes(string s) + { + if (s == null) + { + return 0; + } + else + { + return (s.Length + 1) * sizeof(char); + } + } +#endif + + #endregion + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs new file mode 100644 index 000000000..94e2ed709 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs @@ -0,0 +1,52 @@ +namespace Arcadia.Assistant.Sharepoint +{ + using System; + using System.Collections.Generic; + using System.Fabric; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.ServiceFabric.Services.Communication.Runtime; + using Microsoft.ServiceFabric.Services.Runtime; + + /// + /// An instance of this class is created for each service instance by the Service Fabric runtime. + /// + internal sealed class Sharepoint : StatelessService + { + public Sharepoint(StatelessServiceContext context) + : base(context) + { + } + + /// + /// Optional override to create listeners (e.g., TCP, HTTP) for this service replica to handle client or user requests. + /// + /// A collection of listeners. + protected override IEnumerable CreateServiceInstanceListeners() + { + return new ServiceInstanceListener[0]; + } + + /// + /// This is the main entry point for your service instance. + /// + /// Canceled when Service Fabric needs to shut down this service instance. + protected override async Task RunAsync(CancellationToken cancellationToken) + { + // TODO: Replace the following sample code with your own logic + // or remove this RunAsync override if it's not needed in your service. + + long iterations = 0; + + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + + ServiceEventSource.Current.ServiceMessage(this.Context, "Working-{0}", ++iterations); + + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + } + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.sln b/server2/Arcadia.Assistant/Arcadia.Assistant.sln index bf3a7bff8..ba512d951 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.sln +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.sln @@ -67,130 +67,278 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcadia.Assistant.MobileBui EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MobileBuilds", "MobileBuilds", "{13E39FC7-D22B-4521-B65A-48C62593821F}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sharepoint", "Sharepoint", "{EEF565D4-4A3D-4784-AE0F-AF39C4A27E90}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcadia.Assistant.ExternalStorages.Abstractions", "Arcadia.Assistant.ExternalStorages.Abstractions\Arcadia.Assistant.ExternalStorages.Abstractions.csproj", "{9507586E-6D63-432E-99E2-59B73907ACA0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcadia.Assistant.ExternalStorages.SharepointOnline", "Arcadia.Assistant.ExternalStorages.SharepointOnline\Arcadia.Assistant.ExternalStorages.SharepointOnline.csproj", "{085366A3-3D08-45B5-9A24-DDF3E1EEB2FA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcadia.Assistant.Sharepoint", "Arcadia.Assistant.Sharepoint\Arcadia.Assistant.Sharepoint.csproj", "{C5818540-DECF-409D-82CE-D34E72C34D7D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {80714C33-E9A0-4D12-A77F-A86574998240}.Debug|Any CPU.ActiveCfg = Debug|x64 {80714C33-E9A0-4D12-A77F-A86574998240}.Debug|x64.ActiveCfg = Debug|x64 {80714C33-E9A0-4D12-A77F-A86574998240}.Debug|x64.Build.0 = Debug|x64 {80714C33-E9A0-4D12-A77F-A86574998240}.Debug|x64.Deploy.0 = Debug|x64 + {80714C33-E9A0-4D12-A77F-A86574998240}.Release|Any CPU.ActiveCfg = Release|x64 {80714C33-E9A0-4D12-A77F-A86574998240}.Release|x64.ActiveCfg = Release|x64 {80714C33-E9A0-4D12-A77F-A86574998240}.Release|x64.Build.0 = Release|x64 {80714C33-E9A0-4D12-A77F-A86574998240}.Release|x64.Deploy.0 = Release|x64 + {7A1EA298-4475-4086-9952-6D35B5BEB944}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A1EA298-4475-4086-9952-6D35B5BEB944}.Debug|Any CPU.Build.0 = Debug|Any CPU {7A1EA298-4475-4086-9952-6D35B5BEB944}.Debug|x64.ActiveCfg = Debug|Any CPU {7A1EA298-4475-4086-9952-6D35B5BEB944}.Debug|x64.Build.0 = Debug|Any CPU + {7A1EA298-4475-4086-9952-6D35B5BEB944}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A1EA298-4475-4086-9952-6D35B5BEB944}.Release|Any CPU.Build.0 = Release|Any CPU {7A1EA298-4475-4086-9952-6D35B5BEB944}.Release|x64.ActiveCfg = Release|Any CPU {7A1EA298-4475-4086-9952-6D35B5BEB944}.Release|x64.Build.0 = Release|Any CPU + {7C909B53-A5FE-4F3B-BCFC-E707FD89955B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C909B53-A5FE-4F3B-BCFC-E707FD89955B}.Debug|Any CPU.Build.0 = Debug|Any CPU {7C909B53-A5FE-4F3B-BCFC-E707FD89955B}.Debug|x64.ActiveCfg = Debug|x64 {7C909B53-A5FE-4F3B-BCFC-E707FD89955B}.Debug|x64.Build.0 = Debug|x64 + {7C909B53-A5FE-4F3B-BCFC-E707FD89955B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C909B53-A5FE-4F3B-BCFC-E707FD89955B}.Release|Any CPU.Build.0 = Release|Any CPU {7C909B53-A5FE-4F3B-BCFC-E707FD89955B}.Release|x64.ActiveCfg = Release|x64 {7C909B53-A5FE-4F3B-BCFC-E707FD89955B}.Release|x64.Build.0 = Release|x64 + {2D76AE9B-BCEE-40A3-BB5C-61E34624EBC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D76AE9B-BCEE-40A3-BB5C-61E34624EBC2}.Debug|Any CPU.Build.0 = Debug|Any CPU {2D76AE9B-BCEE-40A3-BB5C-61E34624EBC2}.Debug|x64.ActiveCfg = Debug|Any CPU {2D76AE9B-BCEE-40A3-BB5C-61E34624EBC2}.Debug|x64.Build.0 = Debug|Any CPU + {2D76AE9B-BCEE-40A3-BB5C-61E34624EBC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D76AE9B-BCEE-40A3-BB5C-61E34624EBC2}.Release|Any CPU.Build.0 = Release|Any CPU {2D76AE9B-BCEE-40A3-BB5C-61E34624EBC2}.Release|x64.ActiveCfg = Release|Any CPU {2D76AE9B-BCEE-40A3-BB5C-61E34624EBC2}.Release|x64.Build.0 = Release|Any CPU + {493E9E0F-4D9F-4283-A00D-937E9CD23C13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {493E9E0F-4D9F-4283-A00D-937E9CD23C13}.Debug|Any CPU.Build.0 = Debug|Any CPU {493E9E0F-4D9F-4283-A00D-937E9CD23C13}.Debug|x64.ActiveCfg = Debug|Any CPU {493E9E0F-4D9F-4283-A00D-937E9CD23C13}.Debug|x64.Build.0 = Debug|Any CPU + {493E9E0F-4D9F-4283-A00D-937E9CD23C13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {493E9E0F-4D9F-4283-A00D-937E9CD23C13}.Release|Any CPU.Build.0 = Release|Any CPU {493E9E0F-4D9F-4283-A00D-937E9CD23C13}.Release|x64.ActiveCfg = Release|Any CPU {493E9E0F-4D9F-4283-A00D-937E9CD23C13}.Release|x64.Build.0 = Release|Any CPU + {137B9BB9-CFB5-4448-B930-DBE5DFE8D198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {137B9BB9-CFB5-4448-B930-DBE5DFE8D198}.Debug|Any CPU.Build.0 = Debug|Any CPU {137B9BB9-CFB5-4448-B930-DBE5DFE8D198}.Debug|x64.ActiveCfg = Debug|Any CPU {137B9BB9-CFB5-4448-B930-DBE5DFE8D198}.Debug|x64.Build.0 = Debug|Any CPU + {137B9BB9-CFB5-4448-B930-DBE5DFE8D198}.Release|Any CPU.ActiveCfg = Release|Any CPU + {137B9BB9-CFB5-4448-B930-DBE5DFE8D198}.Release|Any CPU.Build.0 = Release|Any CPU {137B9BB9-CFB5-4448-B930-DBE5DFE8D198}.Release|x64.ActiveCfg = Release|Any CPU {137B9BB9-CFB5-4448-B930-DBE5DFE8D198}.Release|x64.Build.0 = Release|Any CPU + {BF24FE61-7804-4415-913B-B8714633B8DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF24FE61-7804-4415-913B-B8714633B8DF}.Debug|Any CPU.Build.0 = Debug|Any CPU {BF24FE61-7804-4415-913B-B8714633B8DF}.Debug|x64.ActiveCfg = Debug|x64 {BF24FE61-7804-4415-913B-B8714633B8DF}.Debug|x64.Build.0 = Debug|x64 + {BF24FE61-7804-4415-913B-B8714633B8DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF24FE61-7804-4415-913B-B8714633B8DF}.Release|Any CPU.Build.0 = Release|Any CPU {BF24FE61-7804-4415-913B-B8714633B8DF}.Release|x64.ActiveCfg = Release|Any CPU {BF24FE61-7804-4415-913B-B8714633B8DF}.Release|x64.Build.0 = Release|Any CPU + {5D2D5D76-743D-4CE4-BA71-B5F0793FB330}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D2D5D76-743D-4CE4-BA71-B5F0793FB330}.Debug|Any CPU.Build.0 = Debug|Any CPU {5D2D5D76-743D-4CE4-BA71-B5F0793FB330}.Debug|x64.ActiveCfg = Debug|Any CPU {5D2D5D76-743D-4CE4-BA71-B5F0793FB330}.Debug|x64.Build.0 = Debug|Any CPU + {5D2D5D76-743D-4CE4-BA71-B5F0793FB330}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D2D5D76-743D-4CE4-BA71-B5F0793FB330}.Release|Any CPU.Build.0 = Release|Any CPU {5D2D5D76-743D-4CE4-BA71-B5F0793FB330}.Release|x64.ActiveCfg = Release|Any CPU {5D2D5D76-743D-4CE4-BA71-B5F0793FB330}.Release|x64.Build.0 = Release|Any CPU + {287D4A21-F212-4C65-A2A0-8806DA0846A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {287D4A21-F212-4C65-A2A0-8806DA0846A9}.Debug|Any CPU.Build.0 = Debug|Any CPU {287D4A21-F212-4C65-A2A0-8806DA0846A9}.Debug|x64.ActiveCfg = Debug|Any CPU {287D4A21-F212-4C65-A2A0-8806DA0846A9}.Debug|x64.Build.0 = Debug|Any CPU + {287D4A21-F212-4C65-A2A0-8806DA0846A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {287D4A21-F212-4C65-A2A0-8806DA0846A9}.Release|Any CPU.Build.0 = Release|Any CPU {287D4A21-F212-4C65-A2A0-8806DA0846A9}.Release|x64.ActiveCfg = Release|Any CPU {287D4A21-F212-4C65-A2A0-8806DA0846A9}.Release|x64.Build.0 = Release|Any CPU + {D773B729-E46C-432D-92B5-3EA7F352FBFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D773B729-E46C-432D-92B5-3EA7F352FBFD}.Debug|Any CPU.Build.0 = Debug|Any CPU {D773B729-E46C-432D-92B5-3EA7F352FBFD}.Debug|x64.ActiveCfg = Debug|x64 {D773B729-E46C-432D-92B5-3EA7F352FBFD}.Debug|x64.Build.0 = Debug|x64 + {D773B729-E46C-432D-92B5-3EA7F352FBFD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D773B729-E46C-432D-92B5-3EA7F352FBFD}.Release|Any CPU.Build.0 = Release|Any CPU {D773B729-E46C-432D-92B5-3EA7F352FBFD}.Release|x64.ActiveCfg = Release|Any CPU {D773B729-E46C-432D-92B5-3EA7F352FBFD}.Release|x64.Build.0 = Release|Any CPU + {F4578998-0B4F-49BF-A485-6A11A3D7D837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4578998-0B4F-49BF-A485-6A11A3D7D837}.Debug|Any CPU.Build.0 = Debug|Any CPU {F4578998-0B4F-49BF-A485-6A11A3D7D837}.Debug|x64.ActiveCfg = Debug|Any CPU {F4578998-0B4F-49BF-A485-6A11A3D7D837}.Debug|x64.Build.0 = Debug|Any CPU + {F4578998-0B4F-49BF-A485-6A11A3D7D837}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4578998-0B4F-49BF-A485-6A11A3D7D837}.Release|Any CPU.Build.0 = Release|Any CPU {F4578998-0B4F-49BF-A485-6A11A3D7D837}.Release|x64.ActiveCfg = Release|Any CPU {F4578998-0B4F-49BF-A485-6A11A3D7D837}.Release|x64.Build.0 = Release|Any CPU + {900D57EF-6192-4825-9DED-6A81840BB8DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {900D57EF-6192-4825-9DED-6A81840BB8DA}.Debug|Any CPU.Build.0 = Debug|Any CPU {900D57EF-6192-4825-9DED-6A81840BB8DA}.Debug|x64.ActiveCfg = Debug|x64 {900D57EF-6192-4825-9DED-6A81840BB8DA}.Debug|x64.Build.0 = Debug|x64 + {900D57EF-6192-4825-9DED-6A81840BB8DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {900D57EF-6192-4825-9DED-6A81840BB8DA}.Release|Any CPU.Build.0 = Release|Any CPU {900D57EF-6192-4825-9DED-6A81840BB8DA}.Release|x64.ActiveCfg = Release|Any CPU {900D57EF-6192-4825-9DED-6A81840BB8DA}.Release|x64.Build.0 = Release|Any CPU + {C9DF5143-8E11-4731-B76E-B187B0A95444}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9DF5143-8E11-4731-B76E-B187B0A95444}.Debug|Any CPU.Build.0 = Debug|Any CPU {C9DF5143-8E11-4731-B76E-B187B0A95444}.Debug|x64.ActiveCfg = Debug|Any CPU {C9DF5143-8E11-4731-B76E-B187B0A95444}.Debug|x64.Build.0 = Debug|Any CPU + {C9DF5143-8E11-4731-B76E-B187B0A95444}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9DF5143-8E11-4731-B76E-B187B0A95444}.Release|Any CPU.Build.0 = Release|Any CPU {C9DF5143-8E11-4731-B76E-B187B0A95444}.Release|x64.ActiveCfg = Release|Any CPU {C9DF5143-8E11-4731-B76E-B187B0A95444}.Release|x64.Build.0 = Release|Any CPU + {1877FF11-485D-490B-9409-1AA57C8EBB3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1877FF11-485D-490B-9409-1AA57C8EBB3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {1877FF11-485D-490B-9409-1AA57C8EBB3D}.Debug|x64.ActiveCfg = Debug|x64 {1877FF11-485D-490B-9409-1AA57C8EBB3D}.Debug|x64.Build.0 = Debug|x64 + {1877FF11-485D-490B-9409-1AA57C8EBB3D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1877FF11-485D-490B-9409-1AA57C8EBB3D}.Release|Any CPU.Build.0 = Release|Any CPU {1877FF11-485D-490B-9409-1AA57C8EBB3D}.Release|x64.ActiveCfg = Release|Any CPU {1877FF11-485D-490B-9409-1AA57C8EBB3D}.Release|x64.Build.0 = Release|Any CPU + {C228C471-6C35-4630-B475-3161A800C72A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C228C471-6C35-4630-B475-3161A800C72A}.Debug|Any CPU.Build.0 = Debug|Any CPU {C228C471-6C35-4630-B475-3161A800C72A}.Debug|x64.ActiveCfg = Debug|Any CPU {C228C471-6C35-4630-B475-3161A800C72A}.Debug|x64.Build.0 = Debug|Any CPU + {C228C471-6C35-4630-B475-3161A800C72A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C228C471-6C35-4630-B475-3161A800C72A}.Release|Any CPU.Build.0 = Release|Any CPU {C228C471-6C35-4630-B475-3161A800C72A}.Release|x64.ActiveCfg = Release|Any CPU {C228C471-6C35-4630-B475-3161A800C72A}.Release|x64.Build.0 = Release|Any CPU + {A911FABD-DDEA-4B78-BAFB-6CE60BD9D772}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A911FABD-DDEA-4B78-BAFB-6CE60BD9D772}.Debug|Any CPU.Build.0 = Debug|Any CPU {A911FABD-DDEA-4B78-BAFB-6CE60BD9D772}.Debug|x64.ActiveCfg = Debug|x64 {A911FABD-DDEA-4B78-BAFB-6CE60BD9D772}.Debug|x64.Build.0 = Debug|x64 + {A911FABD-DDEA-4B78-BAFB-6CE60BD9D772}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A911FABD-DDEA-4B78-BAFB-6CE60BD9D772}.Release|Any CPU.Build.0 = Release|Any CPU {A911FABD-DDEA-4B78-BAFB-6CE60BD9D772}.Release|x64.ActiveCfg = Release|Any CPU {A911FABD-DDEA-4B78-BAFB-6CE60BD9D772}.Release|x64.Build.0 = Release|Any CPU + {E3B5A256-2924-4E8C-A798-0D74615C2BB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E3B5A256-2924-4E8C-A798-0D74615C2BB6}.Debug|Any CPU.Build.0 = Debug|Any CPU {E3B5A256-2924-4E8C-A798-0D74615C2BB6}.Debug|x64.ActiveCfg = Debug|Any CPU {E3B5A256-2924-4E8C-A798-0D74615C2BB6}.Debug|x64.Build.0 = Debug|Any CPU + {E3B5A256-2924-4E8C-A798-0D74615C2BB6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E3B5A256-2924-4E8C-A798-0D74615C2BB6}.Release|Any CPU.Build.0 = Release|Any CPU {E3B5A256-2924-4E8C-A798-0D74615C2BB6}.Release|x64.ActiveCfg = Release|Any CPU {E3B5A256-2924-4E8C-A798-0D74615C2BB6}.Release|x64.Build.0 = Release|Any CPU + {7925064E-3C64-4865-B603-DEAF82E6A4AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7925064E-3C64-4865-B603-DEAF82E6A4AC}.Debug|Any CPU.Build.0 = Debug|Any CPU {7925064E-3C64-4865-B603-DEAF82E6A4AC}.Debug|x64.ActiveCfg = Debug|x64 {7925064E-3C64-4865-B603-DEAF82E6A4AC}.Debug|x64.Build.0 = Debug|x64 + {7925064E-3C64-4865-B603-DEAF82E6A4AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7925064E-3C64-4865-B603-DEAF82E6A4AC}.Release|Any CPU.Build.0 = Release|Any CPU {7925064E-3C64-4865-B603-DEAF82E6A4AC}.Release|x64.ActiveCfg = Release|Any CPU {7925064E-3C64-4865-B603-DEAF82E6A4AC}.Release|x64.Build.0 = Release|Any CPU + {B78E8874-6AA6-47A2-81BC-7CD9D110CF10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B78E8874-6AA6-47A2-81BC-7CD9D110CF10}.Debug|Any CPU.Build.0 = Debug|Any CPU {B78E8874-6AA6-47A2-81BC-7CD9D110CF10}.Debug|x64.ActiveCfg = Debug|Any CPU {B78E8874-6AA6-47A2-81BC-7CD9D110CF10}.Debug|x64.Build.0 = Debug|Any CPU + {B78E8874-6AA6-47A2-81BC-7CD9D110CF10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B78E8874-6AA6-47A2-81BC-7CD9D110CF10}.Release|Any CPU.Build.0 = Release|Any CPU {B78E8874-6AA6-47A2-81BC-7CD9D110CF10}.Release|x64.ActiveCfg = Release|Any CPU {B78E8874-6AA6-47A2-81BC-7CD9D110CF10}.Release|x64.Build.0 = Release|Any CPU + {B66BE3BE-43A5-47D4-989C-0B905BED8415}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B66BE3BE-43A5-47D4-989C-0B905BED8415}.Debug|Any CPU.Build.0 = Debug|Any CPU {B66BE3BE-43A5-47D4-989C-0B905BED8415}.Debug|x64.ActiveCfg = Debug|x64 {B66BE3BE-43A5-47D4-989C-0B905BED8415}.Debug|x64.Build.0 = Debug|x64 + {B66BE3BE-43A5-47D4-989C-0B905BED8415}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B66BE3BE-43A5-47D4-989C-0B905BED8415}.Release|Any CPU.Build.0 = Release|Any CPU {B66BE3BE-43A5-47D4-989C-0B905BED8415}.Release|x64.ActiveCfg = Release|Any CPU {B66BE3BE-43A5-47D4-989C-0B905BED8415}.Release|x64.Build.0 = Release|Any CPU + {04C572F0-0075-4A03-9201-247E610B5BB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04C572F0-0075-4A03-9201-247E610B5BB5}.Debug|Any CPU.Build.0 = Debug|Any CPU {04C572F0-0075-4A03-9201-247E610B5BB5}.Debug|x64.ActiveCfg = Debug|Any CPU {04C572F0-0075-4A03-9201-247E610B5BB5}.Debug|x64.Build.0 = Debug|Any CPU + {04C572F0-0075-4A03-9201-247E610B5BB5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04C572F0-0075-4A03-9201-247E610B5BB5}.Release|Any CPU.Build.0 = Release|Any CPU {04C572F0-0075-4A03-9201-247E610B5BB5}.Release|x64.ActiveCfg = Release|Any CPU {04C572F0-0075-4A03-9201-247E610B5BB5}.Release|x64.Build.0 = Release|Any CPU + {0FE483A6-B5C9-40D4-94A5-13F9006F420C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0FE483A6-B5C9-40D4-94A5-13F9006F420C}.Debug|Any CPU.Build.0 = Debug|Any CPU {0FE483A6-B5C9-40D4-94A5-13F9006F420C}.Debug|x64.ActiveCfg = Debug|x64 {0FE483A6-B5C9-40D4-94A5-13F9006F420C}.Debug|x64.Build.0 = Debug|x64 + {0FE483A6-B5C9-40D4-94A5-13F9006F420C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0FE483A6-B5C9-40D4-94A5-13F9006F420C}.Release|Any CPU.Build.0 = Release|Any CPU {0FE483A6-B5C9-40D4-94A5-13F9006F420C}.Release|x64.ActiveCfg = Release|Any CPU {0FE483A6-B5C9-40D4-94A5-13F9006F420C}.Release|x64.Build.0 = Release|Any CPU + {88DA2A02-B4E5-46AF-AE5D-E2405E3AFB5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88DA2A02-B4E5-46AF-AE5D-E2405E3AFB5F}.Debug|Any CPU.Build.0 = Debug|Any CPU {88DA2A02-B4E5-46AF-AE5D-E2405E3AFB5F}.Debug|x64.ActiveCfg = Debug|Any CPU {88DA2A02-B4E5-46AF-AE5D-E2405E3AFB5F}.Debug|x64.Build.0 = Debug|Any CPU + {88DA2A02-B4E5-46AF-AE5D-E2405E3AFB5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88DA2A02-B4E5-46AF-AE5D-E2405E3AFB5F}.Release|Any CPU.Build.0 = Release|Any CPU {88DA2A02-B4E5-46AF-AE5D-E2405E3AFB5F}.Release|x64.ActiveCfg = Release|Any CPU {88DA2A02-B4E5-46AF-AE5D-E2405E3AFB5F}.Release|x64.Build.0 = Release|Any CPU + {BBA7563D-DFD2-4964-8FD0-CD340B272662}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BBA7563D-DFD2-4964-8FD0-CD340B272662}.Debug|Any CPU.Build.0 = Debug|Any CPU {BBA7563D-DFD2-4964-8FD0-CD340B272662}.Debug|x64.ActiveCfg = Debug|x64 {BBA7563D-DFD2-4964-8FD0-CD340B272662}.Debug|x64.Build.0 = Debug|x64 + {BBA7563D-DFD2-4964-8FD0-CD340B272662}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BBA7563D-DFD2-4964-8FD0-CD340B272662}.Release|Any CPU.Build.0 = Release|Any CPU {BBA7563D-DFD2-4964-8FD0-CD340B272662}.Release|x64.ActiveCfg = Release|Any CPU {BBA7563D-DFD2-4964-8FD0-CD340B272662}.Release|x64.Build.0 = Release|Any CPU + {62740673-4B3F-4093-A7E5-94251B7C74EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62740673-4B3F-4093-A7E5-94251B7C74EE}.Debug|Any CPU.Build.0 = Debug|Any CPU {62740673-4B3F-4093-A7E5-94251B7C74EE}.Debug|x64.ActiveCfg = Debug|Any CPU {62740673-4B3F-4093-A7E5-94251B7C74EE}.Debug|x64.Build.0 = Debug|Any CPU + {62740673-4B3F-4093-A7E5-94251B7C74EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62740673-4B3F-4093-A7E5-94251B7C74EE}.Release|Any CPU.Build.0 = Release|Any CPU {62740673-4B3F-4093-A7E5-94251B7C74EE}.Release|x64.ActiveCfg = Release|Any CPU {62740673-4B3F-4093-A7E5-94251B7C74EE}.Release|x64.Build.0 = Release|Any CPU + {F402EF81-854B-483C-A795-0FA2796CF153}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F402EF81-854B-483C-A795-0FA2796CF153}.Debug|Any CPU.Build.0 = Debug|Any CPU {F402EF81-854B-483C-A795-0FA2796CF153}.Debug|x64.ActiveCfg = Debug|x64 {F402EF81-854B-483C-A795-0FA2796CF153}.Debug|x64.Build.0 = Debug|x64 + {F402EF81-854B-483C-A795-0FA2796CF153}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F402EF81-854B-483C-A795-0FA2796CF153}.Release|Any CPU.Build.0 = Release|Any CPU {F402EF81-854B-483C-A795-0FA2796CF153}.Release|x64.ActiveCfg = Release|Any CPU {F402EF81-854B-483C-A795-0FA2796CF153}.Release|x64.Build.0 = Release|Any CPU + {BA73D362-4E99-42F2-9966-E6692B0CBF7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA73D362-4E99-42F2-9966-E6692B0CBF7D}.Debug|Any CPU.Build.0 = Debug|Any CPU {BA73D362-4E99-42F2-9966-E6692B0CBF7D}.Debug|x64.ActiveCfg = Debug|Any CPU {BA73D362-4E99-42F2-9966-E6692B0CBF7D}.Debug|x64.Build.0 = Debug|Any CPU + {BA73D362-4E99-42F2-9966-E6692B0CBF7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA73D362-4E99-42F2-9966-E6692B0CBF7D}.Release|Any CPU.Build.0 = Release|Any CPU {BA73D362-4E99-42F2-9966-E6692B0CBF7D}.Release|x64.ActiveCfg = Release|Any CPU {BA73D362-4E99-42F2-9966-E6692B0CBF7D}.Release|x64.Build.0 = Release|Any CPU + {BC44E3D2-B3B0-49F8-965E-25AAB4589BAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC44E3D2-B3B0-49F8-965E-25AAB4589BAA}.Debug|Any CPU.Build.0 = Debug|Any CPU {BC44E3D2-B3B0-49F8-965E-25AAB4589BAA}.Debug|x64.ActiveCfg = Debug|Any CPU {BC44E3D2-B3B0-49F8-965E-25AAB4589BAA}.Debug|x64.Build.0 = Debug|Any CPU + {BC44E3D2-B3B0-49F8-965E-25AAB4589BAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC44E3D2-B3B0-49F8-965E-25AAB4589BAA}.Release|Any CPU.Build.0 = Release|Any CPU {BC44E3D2-B3B0-49F8-965E-25AAB4589BAA}.Release|x64.ActiveCfg = Release|Any CPU {BC44E3D2-B3B0-49F8-965E-25AAB4589BAA}.Release|x64.Build.0 = Release|Any CPU + {1C3C2A57-B9EE-45E6-8639-7295AFDEC518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C3C2A57-B9EE-45E6-8639-7295AFDEC518}.Debug|Any CPU.Build.0 = Debug|Any CPU {1C3C2A57-B9EE-45E6-8639-7295AFDEC518}.Debug|x64.ActiveCfg = Debug|x64 {1C3C2A57-B9EE-45E6-8639-7295AFDEC518}.Debug|x64.Build.0 = Debug|x64 + {1C3C2A57-B9EE-45E6-8639-7295AFDEC518}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C3C2A57-B9EE-45E6-8639-7295AFDEC518}.Release|Any CPU.Build.0 = Release|Any CPU {1C3C2A57-B9EE-45E6-8639-7295AFDEC518}.Release|x64.ActiveCfg = Release|Any CPU {1C3C2A57-B9EE-45E6-8639-7295AFDEC518}.Release|x64.Build.0 = Release|Any CPU + {9507586E-6D63-432E-99E2-59B73907ACA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9507586E-6D63-432E-99E2-59B73907ACA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9507586E-6D63-432E-99E2-59B73907ACA0}.Debug|x64.ActiveCfg = Debug|x64 + {9507586E-6D63-432E-99E2-59B73907ACA0}.Debug|x64.Build.0 = Debug|x64 + {9507586E-6D63-432E-99E2-59B73907ACA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9507586E-6D63-432E-99E2-59B73907ACA0}.Release|Any CPU.Build.0 = Release|Any CPU + {9507586E-6D63-432E-99E2-59B73907ACA0}.Release|x64.ActiveCfg = Release|Any CPU + {9507586E-6D63-432E-99E2-59B73907ACA0}.Release|x64.Build.0 = Release|Any CPU + {085366A3-3D08-45B5-9A24-DDF3E1EEB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {085366A3-3D08-45B5-9A24-DDF3E1EEB2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {085366A3-3D08-45B5-9A24-DDF3E1EEB2FA}.Debug|x64.ActiveCfg = Debug|x64 + {085366A3-3D08-45B5-9A24-DDF3E1EEB2FA}.Debug|x64.Build.0 = Debug|x64 + {085366A3-3D08-45B5-9A24-DDF3E1EEB2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {085366A3-3D08-45B5-9A24-DDF3E1EEB2FA}.Release|Any CPU.Build.0 = Release|Any CPU + {085366A3-3D08-45B5-9A24-DDF3E1EEB2FA}.Release|x64.ActiveCfg = Release|Any CPU + {085366A3-3D08-45B5-9A24-DDF3E1EEB2FA}.Release|x64.Build.0 = Release|Any CPU + {C5818540-DECF-409D-82CE-D34E72C34D7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5818540-DECF-409D-82CE-D34E72C34D7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5818540-DECF-409D-82CE-D34E72C34D7D}.Debug|x64.ActiveCfg = Debug|Any CPU + {C5818540-DECF-409D-82CE-D34E72C34D7D}.Debug|x64.Build.0 = Debug|Any CPU + {C5818540-DECF-409D-82CE-D34E72C34D7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5818540-DECF-409D-82CE-D34E72C34D7D}.Release|Any CPU.Build.0 = Release|Any CPU + {C5818540-DECF-409D-82CE-D34E72C34D7D}.Release|x64.ActiveCfg = Release|Any CPU + {C5818540-DECF-409D-82CE-D34E72C34D7D}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -212,6 +360,10 @@ Global {BA73D362-4E99-42F2-9966-E6692B0CBF7D} = {13E39FC7-D22B-4521-B65A-48C62593821F} {BC44E3D2-B3B0-49F8-965E-25AAB4589BAA} = {13E39FC7-D22B-4521-B65A-48C62593821F} {1C3C2A57-B9EE-45E6-8639-7295AFDEC518} = {13E39FC7-D22B-4521-B65A-48C62593821F} + {EEF565D4-4A3D-4784-AE0F-AF39C4A27E90} = {EC5FA305-0A2E-4985-A977-B9C6F2ADEFFF} + {9507586E-6D63-432E-99E2-59B73907ACA0} = {EEF565D4-4A3D-4784-AE0F-AF39C4A27E90} + {085366A3-3D08-45B5-9A24-DDF3E1EEB2FA} = {EEF565D4-4A3D-4784-AE0F-AF39C4A27E90} + {C5818540-DECF-409D-82CE-D34E72C34D7D} = {EEF565D4-4A3D-4784-AE0F-AF39C4A27E90} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C376673D-F593-42B1-8093-D1F667D586B1} From 5268d6df7e16c2fa1c4efa56856a6c09b767b775 Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Thu, 5 Dec 2019 18:27:12 +0300 Subject: [PATCH 02/14] Sharepoint syncronization service stage 1 --- ...t.ExternalStorages.SharepointOnline.csproj | 5 +-- .../Contracts/SharepointRequest.cs | 5 ++- .../SharepointAuthTokenService.cs | 7 ++-- .../SharepointOnlineConfiguration.cs | 15 ++++++-- .../SharepointRequestExecutor.cs | 5 ++- .../ApplicationManifest.xml | 27 ++++++++++++++ .../Arcadia.Assistant.SF.sfproj | 1 + .../Arcadia.Assistant.Sharepoint.csproj | 13 +++++-- ...ISharepointDepartmentsCalendarsSettings.cs | 9 +++++ .../PackageRoot/Config/Settings.xml | 12 ++++--- .../Arcadia.Assistant.Sharepoint/Program.cs | 35 +++++++++++++------ .../Sharepoint.cs | 11 ++++-- .../SharepointDepartmentCalendarMapping.cs | 13 +++++++ .../SharepointDepartmentsCalendarsSettings.cs | 29 +++++++++++++++ 14 files changed, 153 insertions(+), 34 deletions(-) create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ISharepointDepartmentsCalendarsSettings.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentCalendarMapping.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentsCalendarsSettings.cs diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Arcadia.Assistant.ExternalStorages.SharepointOnline.csproj b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Arcadia.Assistant.ExternalStorages.SharepointOnline.csproj index 63ca4a47c..6ede2f97b 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Arcadia.Assistant.ExternalStorages.SharepointOnline.csproj +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Arcadia.Assistant.ExternalStorages.SharepointOnline.csproj @@ -8,9 +8,10 @@ - + + - + diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs index abbae1b1e..46980fdb1 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs @@ -4,8 +4,7 @@ using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; - - using Newtonsoft.Json; + using System.Text.Json; public class SharepointRequest { @@ -51,7 +50,7 @@ public SharepointRequest WithBearerAuthorizationHeader(string value) public SharepointRequest WithContent(object content) { - var contentString = JsonConvert.SerializeObject(content); + var contentString = JsonSerializer.Serialize(content); this.Content = new StringContent(contentString); this.AddHeader(ContentTypeHeaderName, "application/json;odata=verbose"); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs index 7beea5d0a..22d317554 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs @@ -8,14 +8,13 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Runtime.Serialization; + using System.Text.Json; using System.Threading; using System.Threading.Tasks; using System.Web; using Contracts; - using Newtonsoft.Json; - public class SharepointAuthTokenService : ISharepointAuthTokenService { private const string SharePointPrincipal = "00000003-0000-0ff1-ce00-000000000000"; @@ -142,7 +141,7 @@ private async Task GetRealm(string urlSharePoint, CancellationToken canc var responseContent = await response.Content.ReadAsStringAsync(); - var oauthResult = JsonConvert.DeserializeObject(responseContent); + var oauthResult = JsonSerializer.Deserialize(responseContent); return oauthResult.AccessToken; } @@ -166,7 +165,7 @@ private async Task GetMetadataDocument(string realm, Cance var response = await this.httpClient.GetAsync(metadataEndpointUrl, cancellationToken); var metadataString = await response.Content.ReadAsStringAsync(); - var metadata = JsonConvert.DeserializeObject(metadataString); + var metadata = JsonSerializer.Deserialize(metadataString); if (metadata == null) { diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointOnlineConfiguration.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointOnlineConfiguration.cs index 6558169b7..c16780132 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointOnlineConfiguration.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointOnlineConfiguration.cs @@ -1,13 +1,22 @@ namespace Arcadia.Assistant.ExternalStorages.SharepointOnline { + using System.Fabric.Description; + using Contracts; public class SharepointOnlineConfiguration : ISharepointOnlineConfiguration { - public string ServerUrl { get; set; } = string.Empty; + public SharepointOnlineConfiguration(ConfigurationSection configurationSection) + { + this.ServerUrl = configurationSection.Parameters["ServerUrl"].Value; + this.ClientId = configurationSection.Parameters["ClientId"].Value; + this.ClientSecret = configurationSection.Parameters["ClientSecret"].Value; + } + + public string ServerUrl { get; set; } - public string ClientId { get; set; } = string.Empty; + public string ClientId { get; set; } - public string ClientSecret { get; set; } = string.Empty; + public string ClientSecret { get; set; } } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointRequestExecutor.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointRequestExecutor.cs index b62f1e703..7af733b1e 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointRequestExecutor.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointRequestExecutor.cs @@ -1,13 +1,12 @@ namespace Arcadia.Assistant.ExternalStorages.SharepointOnline { using System.Net.Http; + using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Contracts; - using Newtonsoft.Json; - public class SharepointRequestExecutor : ISharepointRequestExecutor { private readonly ISharepointAuthTokenService authTokenService; @@ -36,7 +35,7 @@ public async Task ExecuteSharepointRequest(SharepointRequest request, Canc } var content = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(content); + return JsonSerializer.Deserialize(content); } public async Task ExecuteSharepointRequest(SharepointRequest request, CancellationToken cancellationToken = default) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.SF/ApplicationPackageRoot/ApplicationManifest.xml b/server2/Arcadia.Assistant/Arcadia.Assistant.SF/ApplicationPackageRoot/ApplicationManifest.xml index 75261691d..4d2162eb4 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.SF/ApplicationPackageRoot/ApplicationManifest.xml +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.SF/ApplicationPackageRoot/ApplicationManifest.xml @@ -1,6 +1,7 @@  + @@ -28,6 +29,10 @@ + + + + @@ -51,6 +56,23 @@ + + + + + +
+ + + +
+
+ +
+
+
+
+
@@ -214,6 +236,11 @@ ServiceFabric PowerShell module. The attribute ServiceTypeName below must match the name defined in the imported ServiceManifest.xml file. --> + + + + + diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.SF/Arcadia.Assistant.SF.sfproj b/server2/Arcadia.Assistant/Arcadia.Assistant.SF/Arcadia.Assistant.SF.sfproj index 8c8a2e614..bb08eb38d 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.SF/Arcadia.Assistant.SF.sfproj +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.SF/Arcadia.Assistant.SF.sfproj @@ -36,6 +36,7 @@ + diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj index 1725162b7..7534eeb15 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,10 +7,19 @@ True win7-x64 False + enable - + + + + + + + + + diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ISharepointDepartmentsCalendarsSettings.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ISharepointDepartmentsCalendarsSettings.cs new file mode 100644 index 000000000..acf7a057a --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ISharepointDepartmentsCalendarsSettings.cs @@ -0,0 +1,9 @@ +namespace Arcadia.Assistant.Sharepoint +{ + using System.Collections.Generic; + + public interface ISharepointDepartmentsCalendarsSettings + { + IEnumerable DepartmentsCalendars { get; } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml index aea0ce165..cae3cb17e 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml @@ -2,10 +2,12 @@ - - \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs index e0ce85202..fbd57528b 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs @@ -2,9 +2,17 @@ namespace Arcadia.Assistant.Sharepoint { using System; using System.Diagnostics; + using System.Fabric; using System.Threading; - using Microsoft.ServiceFabric.Services.Runtime; + using Autofac; + using Autofac.Extensions.DependencyInjection; + using Autofac.Integration.ServiceFabric; + + using ExternalStorages.SharepointOnline; + using ExternalStorages.SharepointOnline.Contracts; + + using Microsoft.Extensions.DependencyInjection; internal static class Program { @@ -15,18 +23,25 @@ private static void Main() { try { - // The ServiceManifest.XML file defines one or more service type names. - // Registering a service maps a service type name to a .NET type. - // When Service Fabric creates an instance of this service type, - // an instance of the class is created in this host process. + var configurationPackage = FabricRuntime.GetActivationContext().GetConfigurationPackageObject("Config"); + + var services = new ServiceCollection(); + services.AddHttpClient(); - ServiceRuntime.RegisterServiceAsync("Arcadia.Assistant.SharepointType", - context => new Sharepoint(context)).GetAwaiter().GetResult(); + var builder = new ContainerBuilder(); + builder.RegisterServiceFabricSupport(); + builder.Register(x => new SharepointOnlineConfiguration(configurationPackage.Settings.Sections["Sharepoint"])).As().SingleInstance(); + builder.Register(x => new SharepointDepartmentsCalendarsSettings(configurationPackage.Settings.Sections["DepartmentsCalendars"])).As().SingleInstance(); + builder.RegisterStatelessService("Arcadia.Assistant.SharepointType"); + builder.Populate(services); - ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(Sharepoint).Name); + using (builder.Build()) + { + ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(Sharepoint).Name); - // Prevents this host process from terminating so services keep running. - Thread.Sleep(Timeout.Infinite); + // Prevents this host process from terminating so services keep running. + Thread.Sleep(Timeout.Infinite); + } } catch (Exception e) { diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs index 94e2ed709..f6fbe5dfa 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs @@ -6,17 +6,24 @@ namespace Arcadia.Assistant.Sharepoint using System.Threading; using System.Threading.Tasks; + using ExternalStorages.SharepointOnline.Contracts; + using Microsoft.ServiceFabric.Services.Communication.Runtime; using Microsoft.ServiceFabric.Services.Runtime; /// /// An instance of this class is created for each service instance by the Service Fabric runtime. /// - internal sealed class Sharepoint : StatelessService + public class Sharepoint : StatelessService { - public Sharepoint(StatelessServiceContext context) + private readonly ISharepointOnlineConfiguration sharepointConnectSettings; + private readonly ISharepointDepartmentsCalendarsSettings departmentsSettings; + + public Sharepoint(StatelessServiceContext context, ISharepointOnlineConfiguration sharepointConnectSettings, ISharepointDepartmentsCalendarsSettings departmentsSettings) : base(context) { + this.sharepointConnectSettings = sharepointConnectSettings; + this.departmentsSettings = departmentsSettings; } /// diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentCalendarMapping.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentCalendarMapping.cs new file mode 100644 index 000000000..ffb1e8289 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentCalendarMapping.cs @@ -0,0 +1,13 @@ +namespace Arcadia.Assistant.Sharepoint +{ + using System.Text.Json.Serialization; + + public class SharepointDepartmentCalendarMapping + { + [JsonPropertyName("id")] + public string DepartmentId { get; set; } = string.Empty; + + [JsonPropertyName("name")] + public string Calendar { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentsCalendarsSettings.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentsCalendarsSettings.cs new file mode 100644 index 000000000..8c2450b3e --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentsCalendarsSettings.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json; + +namespace Arcadia.Assistant.Sharepoint +{ + using System.Fabric.Description; + using System.Text.Json.Serialization; + + public class SharepointDepartmentsCalendarsSettings : ISharepointDepartmentsCalendarsSettings + { + public SharepointDepartmentsCalendarsSettings(ConfigurationSection configurationSection) + { + try + { + var val = configurationSection.Parameters["Value"].Value; + this.DepartmentsCalendars = JsonSerializer.Deserialize>(val); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + public IEnumerable DepartmentsCalendars { get; set; } + } +} From 9beb70769dd1680f7ed2253e8aa4eb0dad952ab3 Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Thu, 12 Dec 2019 15:28:41 +0300 Subject: [PATCH 03/14] Complete synchronization --- ...dia.Assistant.Calendar.Abstractions.csproj | 12 + .../CalendarEvent.cs | 38 +++ .../CalendarEventAdditionalDataEntry.cs | 15 + .../CalendarEventStatuses.cs | 113 ++++++++ .../CalendarEventTypes.cs | 23 ++ .../CspCalendarEventIdParser.cs | 29 ++ .../DatesPeriod.cs | 104 +++++++ .../SickLeaveStatuses.cs | 17 ++ .../VacationStatuses.cs | 23 ++ .../WorkHoursChangeStatuses.cs | 19 ++ .../EmployeeMetadata.cs | 2 + .../SharepointOnlineConfiguration.cs | 6 +- .../ApplicationManifest.xml | 6 + .../Arcadia.Assistant.Sharepoint.csproj | 8 +- .../ISharepointSynchronizationSettings.cs | 7 + .../PackageRoot/Config/Settings.xml | 4 + .../Arcadia.Assistant.Sharepoint/Program.cs | 45 ++- .../RemoveCalendarEventFromSharepoint.cs | 19 ++ .../Sharepoint.cs | 274 +++++++++++++++++- .../SharepointDepartmentsCalendarsSettings.cs | 13 +- .../SharepointStorageItemComparer.cs | 58 ++++ .../SharepointSynchronizationSettings.cs | 16 + .../StoreCalendarEventToSharepoint.cs | 19 ++ .../ISickLeaves.cs | 3 + .../SickLeaveDescription.cs | 5 + .../SickLeaveModelConverter.cs | 3 + .../SickLeaves.cs | 12 + .../IWorkHoursCredit.cs | 2 + .../WorkHoursCredit.cs | 9 + .../Arcadia.Assistant/Arcadia.Assistant.sln | 11 + 30 files changed, 889 insertions(+), 26 deletions(-) create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/Arcadia.Assistant.Calendar.Abstractions.csproj create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEvent.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventAdditionalDataEntry.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventStatuses.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventTypes.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CspCalendarEventIdParser.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/DatesPeriod.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/SickLeaveStatuses.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/VacationStatuses.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/WorkHoursChangeStatuses.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ISharepointSynchronizationSettings.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/RemoveCalendarEventFromSharepoint.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointStorageItemComparer.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizationSettings.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/StoreCalendarEventToSharepoint.cs diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/Arcadia.Assistant.Calendar.Abstractions.csproj b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/Arcadia.Assistant.Calendar.Abstractions.csproj new file mode 100644 index 000000000..c9840b768 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/Arcadia.Assistant.Calendar.Abstractions.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.1 + AnyCPU;x64 + enable + + + + + + \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEvent.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEvent.cs new file mode 100644 index 000000000..111787f32 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEvent.cs @@ -0,0 +1,38 @@ +namespace Arcadia.Assistant.Calendar.Abstractions +{ + using System.Linq; + + public class CalendarEvent + { + public CalendarEvent( + string eventId, + string type, + DatesPeriod dates, + string status, + string employeeId, + CalendarEventAdditionalDataEntry[]? additionalData = null) + { + this.EventId = eventId; + this.Dates = dates; + this.Status = status; + this.Type = type; + this.EmployeeId = employeeId; + this.AdditionalData = additionalData ?? new CalendarEventAdditionalDataEntry[0]; + this.IsPending = new CalendarEventStatuses().PendingForType(type).Contains(status); + } + + public string EventId { get; } + + public DatesPeriod Dates { get; } + + public string Status { get; } + + public string Type { get; } + + public bool IsPending { get; } + + public string EmployeeId { get; } + + public CalendarEventAdditionalDataEntry[] AdditionalData { get; } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventAdditionalDataEntry.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventAdditionalDataEntry.cs new file mode 100644 index 000000000..932429b27 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventAdditionalDataEntry.cs @@ -0,0 +1,15 @@ +namespace Arcadia.Assistant.Calendar.Abstractions +{ + public class CalendarEventAdditionalDataEntry + { + public CalendarEventAdditionalDataEntry(string key, string value) + { + this.Key = key; + this.Value = value; + } + + public string Key { get; } + + public string Value { get; } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventStatuses.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventStatuses.cs new file mode 100644 index 000000000..e61e5b356 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventStatuses.cs @@ -0,0 +1,113 @@ +namespace Arcadia.Assistant.Calendar.Abstractions +{ + using System.Collections.Generic; + + public class CalendarEventStatuses + { + private static readonly IReadOnlyDictionary StatusesByType = new Dictionary + { + { CalendarEventTypes.Dayoff, WorkHoursChangeStatuses.All }, + { CalendarEventTypes.Workout, WorkHoursChangeStatuses.All }, + { CalendarEventTypes.Sickleave, SickLeaveStatuses.All }, + { CalendarEventTypes.Vacation, VacationStatuses.All } + }; + + private static readonly IReadOnlyDictionary PendingStatusesByType = new Dictionary + { + { CalendarEventTypes.Dayoff, WorkHoursChangeStatuses.Pending }, + { CalendarEventTypes.Workout, WorkHoursChangeStatuses.Pending }, + { CalendarEventTypes.Sickleave, SickLeaveStatuses.Pending }, + { CalendarEventTypes.Vacation, VacationStatuses.Pending } + }; + + private static readonly IReadOnlyDictionary ActualStatusesByType = new Dictionary + { + { CalendarEventTypes.Dayoff, WorkHoursChangeStatuses.Actual }, + { CalendarEventTypes.Workout, WorkHoursChangeStatuses.Actual }, + { CalendarEventTypes.Sickleave, SickLeaveStatuses.Actual }, + { CalendarEventTypes.Vacation, VacationStatuses.Actual } + }; + + private static readonly IReadOnlyDictionary ApprovedStatusByType = new Dictionary + { + { CalendarEventTypes.Dayoff, WorkHoursChangeStatuses.Approved }, + { CalendarEventTypes.Workout, WorkHoursChangeStatuses.Approved }, + { CalendarEventTypes.Vacation, VacationStatuses.Approved } + }; + + private static readonly IReadOnlyDictionary RejectedStatusByType = new Dictionary + { + { CalendarEventTypes.Dayoff, WorkHoursChangeStatuses.Rejected }, + { CalendarEventTypes.Workout, WorkHoursChangeStatuses.Rejected }, + { CalendarEventTypes.Vacation, VacationStatuses.Rejected } + }; + + private static readonly IReadOnlyDictionary CancelledStatusByType = new Dictionary + { + { CalendarEventTypes.Dayoff, WorkHoursChangeStatuses.Cancelled }, + { CalendarEventTypes.Workout, WorkHoursChangeStatuses.Cancelled }, + { CalendarEventTypes.Sickleave, SickLeaveStatuses.Cancelled }, + { CalendarEventTypes.Vacation, VacationStatuses.Cancelled } + }; + + public string[] AllForType(string type) + { + if (StatusesByType.TryGetValue(type, out var statuses)) + { + return statuses; + } + + return new string[0]; + } + + public string[] PendingForType(string type) + { + if (PendingStatusesByType.TryGetValue(type, out var statuses)) + { + return statuses; + } + + return new string[0]; + } + + public string[] ActualForType(string type) + { + if (ActualStatusesByType.TryGetValue(type, out var statuses)) + { + return statuses; + } + + return new string[0]; + } + + public string? ApprovedForType(string type) + { + if (ApprovedStatusByType.TryGetValue(type, out var status)) + { + return status; + } + + return null; + } + + public string? RejectedForType(string type) + { + if (RejectedStatusByType.TryGetValue(type, out var status)) + { + return status; + } + + return null; + } + + public string? CancelledForType(string type) + { + if (CancelledStatusByType.TryGetValue(type, out var status)) + { + return status; + } + + return null; + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventTypes.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventTypes.cs new file mode 100644 index 000000000..066fb7ce3 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventTypes.cs @@ -0,0 +1,23 @@ +namespace Arcadia.Assistant.Calendar.Abstractions +{ + using System; + using System.Linq; + + public static class CalendarEventTypes + { + public const string Vacation = "Vacation"; + + public const string Dayoff = "Dayoff"; + + public const string Workout = "Workout"; + + public const string Sickleave = "Sickleave"; + + public static readonly string[] All = { Vacation, Dayoff, Workout, Sickleave }; + + public static bool IsKnownType(string x) + { + return All.Contains(x, StringComparer.InvariantCultureIgnoreCase); + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CspCalendarEventIdParser.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CspCalendarEventIdParser.cs new file mode 100644 index 000000000..7107a2dec --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CspCalendarEventIdParser.cs @@ -0,0 +1,29 @@ +namespace Arcadia.Assistant.Calendar.Abstractions +{ + using System; + + public static class CspCalendarEventIdParser + { + public static int GetCspIdFromCalendarEvent(string calendarEventId, string calendarEventType) + { + var parts = calendarEventId.Split('_'); + + if (parts.Length != 2 || parts[0] != calendarEventType || !int.TryParse(parts[1], out var cspId)) + { + throw new ArgumentException("Calendar event id has wrong format"); + } + + return cspId; + } + + public static string GetCalendarEventIdFromCspId(int cspId, string calendarEventType) + { + return $"{calendarEventType}_{cspId}"; + } + + public static string GetCalendarEventIdFromCspId(Guid cspId, string calendarEventType) + { + return $"{calendarEventType}_{cspId}"; + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/DatesPeriod.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/DatesPeriod.cs new file mode 100644 index 000000000..4dbcaaf43 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/DatesPeriod.cs @@ -0,0 +1,104 @@ +namespace Arcadia.Assistant.Calendar.Abstractions +{ + using System; + + public sealed class DatesPeriod + { + public DatesPeriod(DateTime startDate, DateTime endDate, int startWorkingHour = 0, int finishWorkingHour = 8) + { + this.StartDate = MinDate(startDate, endDate); + this.EndDate = MaxDate(startDate, endDate); + this.StartWorkingHour = Math.Min(startWorkingHour, finishWorkingHour); + this.FinishWorkingHour = Math.Max(startWorkingHour, finishWorkingHour); + } + + public DateTime StartDate { get; } + + public DateTime EndDate { get; } + + /// + /// Starting working hour index. Typically, 0 or 4. + /// + public int StartWorkingHour { get; } + + /// + /// Finish working hour index. Typically, 4 or 8 + /// + public int FinishWorkingHour { get; } + + private bool Equals(DatesPeriod other) + { + return this.StartDate.Equals(other.StartDate) + && this.EndDate.Equals(other.EndDate) + && this.StartWorkingHour == other.StartWorkingHour + && this.FinishWorkingHour == other.FinishWorkingHour; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + + return this.Equals((DatesPeriod)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = this.StartDate.GetHashCode(); + hashCode = (hashCode * 397) ^ this.EndDate.GetHashCode(); + hashCode = (hashCode * 397) ^ this.StartWorkingHour; + hashCode = (hashCode * 397) ^ this.FinishWorkingHour; + return hashCode; + } + } + + public static bool operator ==(DatesPeriod left, DatesPeriod right) + { + return Equals(left, right); + } + + public static bool operator !=(DatesPeriod left, DatesPeriod right) + { + return !Equals(left, right); + } + + public bool DatesIntersectsWith(DatesPeriod? period) + { + if (period == null) + { + return false; + } + + if (this.EndDate < period.StartDate || period.EndDate < this.StartDate) + { + return false; + } + + return true; + } + + private static DateTime MinDate(DateTime first, DateTime second) + { + return first <= second ? first : second; + } + + private static DateTime MaxDate(DateTime first, DateTime second) + { + return first >= second ? first : second; + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/SickLeaveStatuses.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/SickLeaveStatuses.cs new file mode 100644 index 000000000..e7596fa40 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/SickLeaveStatuses.cs @@ -0,0 +1,17 @@ +namespace Arcadia.Assistant.Calendar.Abstractions +{ + public static class SickLeaveStatuses + { + public const string Requested = "Requested"; + + public const string Cancelled = "Cancelled"; + + public const string Completed = "Completed"; + + public static readonly string[] All = { Requested, Completed, Cancelled }; + + public static readonly string[] Pending = { Requested }; + + public static readonly string[] Actual = { Requested, Completed }; + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/VacationStatuses.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/VacationStatuses.cs new file mode 100644 index 000000000..a55c1e6ee --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/VacationStatuses.cs @@ -0,0 +1,23 @@ +namespace Arcadia.Assistant.Calendar.Abstractions +{ + public static class VacationStatuses + { + public const string Requested = "Requested"; + + public const string Cancelled = "Cancelled"; + + public const string Approved = "Approved"; + + public const string Rejected = "Rejected"; + + public const string AccountingReady = "AccountingReady"; + + public const string Processed = "Processed"; + + public static readonly string[] All = { Requested, Approved, Cancelled, Rejected, AccountingReady, Processed }; + + public static readonly string[] Pending = { Requested }; + + public static readonly string[] Actual = { Requested, Approved, AccountingReady, AccountingReady, Processed }; + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/WorkHoursChangeStatuses.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/WorkHoursChangeStatuses.cs new file mode 100644 index 000000000..f0076ecce --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/WorkHoursChangeStatuses.cs @@ -0,0 +1,19 @@ +namespace Arcadia.Assistant.Calendar.Abstractions +{ + public static class WorkHoursChangeStatuses + { + public const string Requested = "Requested"; + + public const string Cancelled = "Cancelled"; + + public const string Approved = "Approved"; + + public const string Rejected = "Rejected"; + + public static readonly string[] All = { Requested, Approved, Cancelled, Rejected }; + + public static readonly string[] Pending = { Requested }; + + public static readonly string[] Actual = { Requested, Approved }; + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Employees.Contracts/EmployeeMetadata.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Employees.Contracts/EmployeeMetadata.cs index 223df6491..d1facefa8 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Employees.Contracts/EmployeeMetadata.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Employees.Contracts/EmployeeMetadata.cs @@ -59,6 +59,8 @@ public EmployeeMetadata(EmployeeId employeeId, string email) return CalculateYearsFromDate(this.BirthDate, date); } + public string Name => $"{this.FirstName ?? string.Empty} {this.LastName ?? string.Empty}"; + public int? YearsServedAt(DateTime date) { DateTime toDate; diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointOnlineConfiguration.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointOnlineConfiguration.cs index c16780132..99b6e080f 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointOnlineConfiguration.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointOnlineConfiguration.cs @@ -13,10 +13,10 @@ public SharepointOnlineConfiguration(ConfigurationSection configurationSection) this.ClientSecret = configurationSection.Parameters["ClientSecret"].Value; } - public string ServerUrl { get; set; } + public string ServerUrl { get; } - public string ClientId { get; set; } + public string ClientId { get; } - public string ClientSecret { get; set; } + public string ClientSecret { get; } } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.SF/ApplicationPackageRoot/ApplicationManifest.xml b/server2/Arcadia.Assistant/Arcadia.Assistant.SF/ApplicationPackageRoot/ApplicationManifest.xml index 4d2162eb4..7d8d57ab8 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.SF/ApplicationPackageRoot/ApplicationManifest.xml +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.SF/ApplicationPackageRoot/ApplicationManifest.xml @@ -32,6 +32,8 @@ + + @@ -65,6 +67,10 @@ + + +
+
diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj index 7534eeb15..321eebb44 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj @@ -19,7 +19,13 @@ + + + + + + - + \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ISharepointSynchronizationSettings.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ISharepointSynchronizationSettings.cs new file mode 100644 index 000000000..939c4b11a --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ISharepointSynchronizationSettings.cs @@ -0,0 +1,7 @@ +namespace Arcadia.Assistant.Sharepoint +{ + public interface ISharepointSynchronizationSettings + { + int SynchronizationIntervalMinutes { get; } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml index cae3cb17e..7fbecade1 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml @@ -6,6 +6,10 @@ + +
+
+
diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs index fbd57528b..f386300e1 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs @@ -3,16 +3,29 @@ namespace Arcadia.Assistant.Sharepoint using System; using System.Diagnostics; using System.Fabric; + using System.Linq; using System.Threading; using Autofac; using Autofac.Extensions.DependencyInjection; using Autofac.Integration.ServiceFabric; + using Employees.Contracts; + + using ExternalStorages.Abstractions; using ExternalStorages.SharepointOnline; using ExternalStorages.SharepointOnline.Contracts; using Microsoft.Extensions.DependencyInjection; + using Microsoft.ServiceFabric.Services.Remoting.Client; + + using Organization.Contracts; + + using SickLeaves.Contracts; + + using Vacations.Contracts; + + using WorkHoursCredit.Contracts; internal static class Program { @@ -24,15 +37,45 @@ private static void Main() try { var configurationPackage = FabricRuntime.GetActivationContext().GetConfigurationPackageObject("Config"); + var sharepointSection = configurationPackage.Settings.Sections["Sharepoint"]; + var calendarEventIdField = sharepointSection.Parameters.TryGetValue("CalendarEventIdField", out var val) ? val.Value : null; var services = new ServiceCollection(); services.AddHttpClient(); var builder = new ContainerBuilder(); builder.RegisterServiceFabricSupport(); - builder.Register(x => new SharepointOnlineConfiguration(configurationPackage.Settings.Sections["Sharepoint"])).As().SingleInstance(); + builder.Register(x => new SharepointOnlineConfiguration(sharepointSection)).As().SingleInstance(); + builder.Register(x => new SharepointSynchronizationSettings(configurationPackage.Settings.Sections["Service"])).As().SingleInstance(); builder.Register(x => new SharepointDepartmentsCalendarsSettings(configurationPackage.Settings.Sections["DepartmentsCalendars"])).As().SingleInstance(); + builder + .Register(ctx => + { + if (string.IsNullOrEmpty(calendarEventIdField)) + { + return new SharepointFieldsMapper(); + } + + var mapping = SharepointFieldsMapper.DefaultMapping + .Union(new[] + { + SharepointFieldsMapper.CreateMapping(x => x.CalendarEventId, calendarEventIdField) + }); + return new SharepointFieldsMapper(mapping.ToArray()); + }) + .As(); + + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterInstance(new ServiceProxyFactory()); builder.RegisterStatelessService("Arcadia.Assistant.SharepointType"); + builder.RegisterModule(); + builder.RegisterModule(); + builder.RegisterModule(); + builder.RegisterModule(); + builder.RegisterModule(); builder.Populate(services); using (builder.Build()) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/RemoveCalendarEventFromSharepoint.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/RemoveCalendarEventFromSharepoint.cs new file mode 100644 index 000000000..dbea220c9 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/RemoveCalendarEventFromSharepoint.cs @@ -0,0 +1,19 @@ +namespace Arcadia.Assistant.Sharepoint +{ + using Calendar.Abstractions; + + using Employees.Contracts; + + public class RemoveCalendarEventFromSharepoint + { + public RemoveCalendarEventFromSharepoint(CalendarEvent @event, EmployeeMetadata employeeMetadata) + { + this.Event = @event; + this.EmployeeMetadata = employeeMetadata; + } + + public CalendarEvent Event { get; } + + public EmployeeMetadata EmployeeMetadata { get; } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs index f6fbe5dfa..8aa1b0a25 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs @@ -3,27 +3,62 @@ namespace Arcadia.Assistant.Sharepoint using System; using System.Collections.Generic; using System.Fabric; + using System.Linq; using System.Threading; using System.Threading.Tasks; - using ExternalStorages.SharepointOnline.Contracts; + using Calendar.Abstractions; + + using Employees.Contracts; + + using ExternalStorages.Abstractions; using Microsoft.ServiceFabric.Services.Communication.Runtime; using Microsoft.ServiceFabric.Services.Runtime; + using Organization.Contracts; + + using SickLeaves.Contracts; + + using Vacations.Contracts; + + using WorkHoursCredit.Contracts; + /// /// An instance of this class is created for each service instance by the Service Fabric runtime. /// public class Sharepoint : StatelessService { - private readonly ISharepointOnlineConfiguration sharepointConnectSettings; - private readonly ISharepointDepartmentsCalendarsSettings departmentsSettings; + private readonly ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings; + private readonly IEmployees employees; + private readonly Func externalStorageProvider; + private readonly IOrganization organizations; + private readonly ISharepointSynchronizationSettings serviceSettings; + private readonly IEqualityComparer sharepointStorageItemComparer = new SharepointStorageItemComparer(); + private readonly ISickLeaves sickLeaves; + private readonly IVacations vacations; + private readonly IWorkHoursCredit workouts; - public Sharepoint(StatelessServiceContext context, ISharepointOnlineConfiguration sharepointConnectSettings, ISharepointDepartmentsCalendarsSettings departmentsSettings) + public Sharepoint( + StatelessServiceContext context, + IVacations vacations, + IWorkHoursCredit workouts, + ISickLeaves sickLeaves, + IEmployees employees, + IOrganization organizations, + Func externalStorageProvider, + ISharepointSynchronizationSettings serviceSettings, + ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings) : base(context) { - this.sharepointConnectSettings = sharepointConnectSettings; - this.departmentsSettings = departmentsSettings; + this.externalStorageProvider = externalStorageProvider; + this.vacations = vacations; + this.workouts = workouts; + this.sickLeaves = sickLeaves; + this.employees = employees; + this.organizations = organizations; + this.serviceSettings = serviceSettings; + this.departmentsCalendarsSettings = departmentsCalendarsSettings; } /// @@ -41,19 +76,232 @@ protected override IEnumerable CreateServiceInstanceLis /// Canceled when Service Fabric needs to shut down this service instance. protected override async Task RunAsync(CancellationToken cancellationToken) { - // TODO: Replace the following sample code with your own logic - // or remove this RunAsync override if it's not needed in your service. - - long iterations = 0; - while (true) { cancellationToken.ThrowIfCancellationRequested(); - ServiceEventSource.Current.ServiceMessage(this.Context, "Working-{0}", ++iterations); + //ServiceEventSource.Current.ServiceMessage(this.Context, "Working-{0}", ++iterations); + + // request Sharepoint calendars + var externalStorage = this.externalStorageProvider(); + var departments = await this.GetDepartmentsList(cancellationToken); + var sharepointCalendars = departments.Distinct().ToDictionary(x => x, + dId => this.GetSharepointCalendarsByDepartment(dId).ToDictionary(x => x, + async cal => await this.GetAllSharepointItemsForCalendar(externalStorage, cal))); + + foreach (var departmentId in departments) + { + var departmentEmployes = await this.employees.FindEmployeesAsync(EmployeesQuery.Create().ForDepartment(departmentId), cancellationToken); + var employeeIds = departmentEmployes.Select(x => x.EmployeeId).ToArray(); + + var employeeVacations = await this.vacations.GetCalendarEventsByEmployeeAsync(employeeIds, cancellationToken); + var employeeVacationValues = employeeVacations.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.VacationId, CalendarEventTypes.Vacation), x => x); + + var employeeWorkouts = await this.workouts.GetCalendarEventsCollectionAsync(employeeIds, cancellationToken); + var employeeWorkoutsValues = employeeWorkouts.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.ChangeId, CalendarEventTypes.Workout), x => x); + + var employeeSickLeaves = await this.sickLeaves.GetCalendarEventsCollectionAsync(employeeIds, cancellationToken); + var employeeSickLeavesValues = employeeSickLeaves.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.SickLeaveId, CalendarEventTypes.Sickleave), x => x); + + foreach (var calendar in this.GetSharepointCalendarsByDepartment(departmentId)) + { + var storageItems = await sharepointCalendars[departmentId][calendar]; + + var employeeVacationsStorageItems = storageItems.Where(x => x.Category == CalendarEventTypes.Vacation).ToList(); + var employeeWorkoutsStorageItems = storageItems.Where(x => x.Category == CalendarEventTypes.Workout).ToList(); + var employeeSickLeavesStorageItems = storageItems.Where(x => x.Category == CalendarEventTypes.Sickleave).ToList(); + + #region vacation synchronization + + // synchronize vacations for selected department + try + { + var removeStorageItems = employeeVacationsStorageItems.Where(x => !employeeVacationValues.Keys.Contains(x.CalendarEventId)).ToArray(); + + // insert or update items + foreach (var vacationEventId in employeeVacationValues.Keys) + { + var employeeMetadata = departmentEmployes.Single(x => x.EmployeeId == employeeVacationValues[vacationEventId].EmployeeId); + await this.UpsertVacation(vacationEventId, calendar, employeeVacationValues[vacationEventId], employeeMetadata, externalStorage, cancellationToken); + } + + // remove redundant items + foreach (var item in removeStorageItems) + { + await externalStorage.DeleteItem( + calendar, + item.Id); + } + } + catch (Exception e) + { + ServiceEventSource.Current.ServiceMessage(this.Context, e.ToString()); + } + + #endregion + + #region workhours synchronization + + // synchronize workouts for selected department + try + { + var removeStorageItems = employeeWorkoutsStorageItems.Where(x => !employeeWorkoutsValues.Keys.Contains(x.CalendarEventId)).ToArray(); + + // insert or update items + foreach (var workHourEventId in employeeWorkoutsValues.Keys) + { + var employeeMetadata = departmentEmployes.Single(x => x.EmployeeId == employeeWorkoutsValues[workHourEventId].EmployeeId); + await this.UpsertWorkHour(workHourEventId, calendar, employeeWorkoutsValues[workHourEventId], employeeMetadata, externalStorage, cancellationToken); + } + + // remove redundant items + foreach (var item in removeStorageItems) + { + await externalStorage.DeleteItem( + calendar, + item.Id); + } + } + catch (Exception e) + { + ServiceEventSource.Current.ServiceMessage(this.Context, e.ToString()); + } + + #endregion + + #region sick lives synchronization + + // synchronize vacations for selected department + try + { + var removeStorageItems = employeeSickLeavesStorageItems.Where(x => !employeeSickLeavesValues.Keys.Contains(x.CalendarEventId)).ToArray(); + + // insert or update items + foreach (var sickLeaveEventId in employeeSickLeavesValues.Keys) + { + var employeeMetadata = departmentEmployes.Single(x => x.EmployeeId == employeeSickLeavesValues[sickLeaveEventId].EmployeeId); + await this.UpsertSickLeave(sickLeaveEventId, calendar, employeeSickLeavesValues[sickLeaveEventId], employeeMetadata, externalStorage, cancellationToken); + } + + // remove redundant items + foreach (var item in removeStorageItems) + { + await externalStorage.DeleteItem( + calendar, + item.Id); + } + } + catch (Exception e) + { + ServiceEventSource.Current.ServiceMessage(this.Context, e.ToString()); + } + + #endregion + } + } + +#if DEBUG + await Task.Delay(TimeSpan.FromMinutes(15), cancellationToken); +#else + await Task.Delay(TimeSpan.FromMinutes(serviceSettings.SynchronizationIntervalMinutes), cancellationToken); +#endif + } + } + + private async Task> GetDepartmentsList(CancellationToken cancellationToken) + { + if (this.departmentsCalendarsSettings.DepartmentsCalendars != null && this.departmentsCalendarsSettings.DepartmentsCalendars.Any()) + { + return this.departmentsCalendarsSettings.DepartmentsCalendars.Select(x => x.DepartmentId); + } + + return (await this.organizations.GetDepartmentsAsync(cancellationToken)).Select(x => x.DepartmentId.Value.ToString()); + } + + private IEnumerable GetSharepointCalendarsByDepartment(string departmentId) + { + return this.departmentsCalendarsSettings.DepartmentsCalendars + .Where(x => x.DepartmentId == departmentId) + .Select(x => x.Calendar); + } + + private async Task> GetAllSharepointItemsForCalendar(IExternalStorage externalStorage, string calendar) + { + return await externalStorage.GetItems(calendar); + } + + private async Task GetSharepointItemForCalendarEvent(IExternalStorage externalStorage, string calendar, string eventId) + { + var existingItems = await externalStorage.GetItems( + calendar, + new[] { new EqualCondition(x => x.CalendarEventId, eventId) }); + return existingItems.SingleOrDefault(); + } - await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + private async Task UpsertVacation(string eventId, string calendar, VacationDescription vacation, EmployeeMetadata employeeMetadata, IExternalStorage externalStorage, CancellationToken cancellationToken) + { + var datesPeriod = new DatesPeriod(vacation.StartDate.Date, vacation.EndDate.Date); + var upsertItem = this.CalendarEventToStorageItem(eventId, CalendarEventTypes.Vacation, datesPeriod, employeeMetadata); + await this.UpsertStorageItem(eventId, calendar, datesPeriod, upsertItem, externalStorage, cancellationToken); + } + + private async Task UpsertWorkHour(string eventId, string calendar, WorkHoursChange workHours, EmployeeMetadata employeeMetadata, IExternalStorage externalStorage, CancellationToken cancellationToken) + { + var datesPeriod = new DatesPeriod(workHours.Date, workHours.Date); + var upsertItem = this.CalendarEventToStorageItem(eventId, CalendarEventTypes.Workout, datesPeriod, employeeMetadata); + await this.UpsertStorageItem(eventId, calendar, datesPeriod, upsertItem, externalStorage, cancellationToken); + } + + private async Task UpsertSickLeave(string eventId, string calendar, SickLeaveDescription sickLeave, EmployeeMetadata employeeMetadata, IExternalStorage externalStorage, CancellationToken cancellationToken) + { + var datesPeriod = new DatesPeriod(sickLeave.StartDate.Date, sickLeave.EndDate.Date); + var upsertItem = this.CalendarEventToStorageItem(eventId, CalendarEventTypes.Sickleave, datesPeriod, employeeMetadata); + await this.UpsertStorageItem(eventId, calendar, datesPeriod, upsertItem, externalStorage, cancellationToken); + } + + private async Task UpsertStorageItem(string eventId, string calendar, DatesPeriod datesPeriod, StorageItem upsertItem, IExternalStorage externalStorage, CancellationToken cancellationToken) + { + var storageItem = await this.GetSharepointItemForCalendarEvent(externalStorage, calendar, eventId); + + if (storageItem == null) + { + await externalStorage.AddItem( + calendar, + upsertItem, + cancellationToken); + } + else if (!this.sharepointStorageItemComparer.Equals(upsertItem, storageItem)) + { + upsertItem.Id = storageItem.Id; + await externalStorage.UpdateItem( + calendar, + upsertItem, + cancellationToken); } } + + private StorageItem CalendarEventToStorageItem(string eventId, string calendarEventType, DatesPeriod period, EmployeeMetadata employeeMetadata) + { + var totalHours = period.FinishWorkingHour - period.StartWorkingHour; + + var longEventsTitle = $"{employeeMetadata.Name} ({calendarEventType})"; + var shortEventsTitle = $"{employeeMetadata.Name} ({calendarEventType}: {totalHours} hours)"; + + var title = calendarEventType == CalendarEventTypes.Vacation || calendarEventType == CalendarEventTypes.Sickleave + ? longEventsTitle + : shortEventsTitle; + + var storageItem = new StorageItem + { + Title = title, + StartDate = period.StartDate, + EndDate = period.EndDate, + Category = calendarEventType, + AllDayEvent = true, + CalendarEventId = eventId + }; + + return storageItem; + } } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentsCalendarsSettings.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentsCalendarsSettings.cs index 8c2450b3e..f9e194889 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentsCalendarsSettings.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentsCalendarsSettings.cs @@ -1,12 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Text.Json; - -namespace Arcadia.Assistant.Sharepoint +namespace Arcadia.Assistant.Sharepoint { + using System; + using System.Collections.Generic; using System.Fabric.Description; - using System.Text.Json.Serialization; + using System.Text.Json; public class SharepointDepartmentsCalendarsSettings : ISharepointDepartmentsCalendarsSettings { @@ -26,4 +23,4 @@ public SharepointDepartmentsCalendarsSettings(ConfigurationSection configuration public IEnumerable DepartmentsCalendars { get; set; } } -} +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointStorageItemComparer.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointStorageItemComparer.cs new file mode 100644 index 000000000..162a3ca2d --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointStorageItemComparer.cs @@ -0,0 +1,58 @@ +namespace Arcadia.Assistant.Sharepoint +{ + using System.Collections.Generic; + + using ExternalStorages.Abstractions; + + public sealed class SharepointStorageItemComparer : IEqualityComparer + { + public bool Equals(StorageItem x, StorageItem y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + if (ReferenceEquals(x, null)) + { + return false; + } + + if (ReferenceEquals(y, null)) + { + return false; + } + + if (x.GetType() != y.GetType()) + { + return false; + } + + return + string.Equals(x.Id, y.Id) && + string.Equals(x.Title, y.Title) && + string.Equals(x.Description, y.Description) && + x.StartDate.Date.Equals(y.StartDate.Date) && + x.EndDate.Date.Equals(y.EndDate.Date) && + string.Equals(x.Category, y.Category) && + x.AllDayEvent == y.AllDayEvent && + string.Equals(x.CalendarEventId, y.CalendarEventId); + } + + public int GetHashCode(StorageItem obj) + { + unchecked + { + var hashCode = obj.Id.GetHashCode(); + hashCode = (hashCode * 397) ^ obj.Title.GetHashCode(); + hashCode = (hashCode * 397) ^ obj.Description.GetHashCode(); + hashCode = (hashCode * 397) ^ obj.StartDate.Date.GetHashCode(); + hashCode = (hashCode * 397) ^ obj.EndDate.Date.GetHashCode(); + hashCode = (hashCode * 397) ^ obj.Category.GetHashCode(); + hashCode = (hashCode * 397) ^ obj.AllDayEvent.GetHashCode(); + hashCode = (hashCode * 397) ^ obj.CalendarEventId.GetHashCode(); + return hashCode; + } + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizationSettings.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizationSettings.cs new file mode 100644 index 000000000..8711dad4f --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizationSettings.cs @@ -0,0 +1,16 @@ +namespace Arcadia.Assistant.Sharepoint +{ + using System.Fabric.Description; + + public class SharepointSynchronizationSettings : ISharepointSynchronizationSettings + { + private readonly int DefaultSynchronizationIntervalMinutes = 240; + + public SharepointSynchronizationSettings(ConfigurationSection configurationSection) + { + this.SynchronizationIntervalMinutes = int.TryParse(configurationSection.Parameters["SynchronizationIntervalMinutes"].Value, out var interval) ? interval : this.DefaultSynchronizationIntervalMinutes; + } + + public int SynchronizationIntervalMinutes { get; } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/StoreCalendarEventToSharepoint.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/StoreCalendarEventToSharepoint.cs new file mode 100644 index 000000000..380591bcc --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/StoreCalendarEventToSharepoint.cs @@ -0,0 +1,19 @@ +namespace Arcadia.Assistant.Sharepoint +{ + using Calendar.Abstractions; + + using Employees.Contracts; + + public class StoreCalendarEventToSharepoint + { + public StoreCalendarEventToSharepoint(CalendarEvent @event, EmployeeMetadata employeeMetadata) + { + this.Event = @event; + this.EmployeeMetadata = employeeMetadata; + } + + public CalendarEvent Event { get; } + + public EmployeeMetadata EmployeeMetadata { get; } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves.Contracts/ISickLeaves.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves.Contracts/ISickLeaves.cs index 0aeba094f..915b1f1bf 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves.Contracts/ISickLeaves.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves.Contracts/ISickLeaves.cs @@ -6,6 +6,7 @@ namespace Arcadia.Assistant.SickLeaves.Contracts { using System; + using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -17,6 +18,8 @@ public interface ISickLeaves : IService { Task GetCalendarEventsAsync(EmployeeId employeeId, CancellationToken cancellationToken); + Task> GetCalendarEventsCollectionAsync(EmployeeId[] employeeIds, CancellationToken cancellationToken); + Task GetCalendarEventAsync(EmployeeId employeeId, int eventId, CancellationToken cancellationToken); Task CreateSickLeaveAsync(EmployeeId employeeId, DateTime startDate, DateTime endDate); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves.Contracts/SickLeaveDescription.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves.Contracts/SickLeaveDescription.cs index 163cf3612..2e8dacd65 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves.Contracts/SickLeaveDescription.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves.Contracts/SickLeaveDescription.cs @@ -3,12 +3,17 @@ using System; using System.Runtime.Serialization; + using Employees.Contracts; + [DataContract] public class SickLeaveDescription { [DataMember] public int SickLeaveId { get; set; } + [DataMember] + public EmployeeId EmployeeId { get; set; } + [DataMember] public DateTime StartDate { get; set; } diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/SickLeaveModelConverter.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/SickLeaveModelConverter.cs index b41eee54a..ea16324f9 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/SickLeaveModelConverter.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/SickLeaveModelConverter.cs @@ -6,12 +6,15 @@ using Contracts; + using Employees.Contracts; + public class SickLeaveModelConverter { public Expression> ToDescription { get; } = model => new SickLeaveDescription() { SickLeaveId = model.Id, + EmployeeId = new EmployeeId(model.EmployeeId), StartDate = model.Start, EndDate = model.End, Status = model.SickLeaveCancellations.Any() ? SickLeaveStatus.Cancelled diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/SickLeaves.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/SickLeaves.cs index 7653cd969..baa621881 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/SickLeaves.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/SickLeaves.cs @@ -80,6 +80,18 @@ public async Task GetCalendarEventsAsync(EmployeeId empl return sickLeaves; } + public async Task> GetCalendarEventsCollectionAsync(EmployeeId[] employeeIds, CancellationToken cancellationToken) + { + using var db = this.dbFactory(); + var sickLeaves = await db.Value + .SickLeaves + .Where(x => employeeIds.Any(id => x.EmployeeId == id.Value)) + .Select(this.modelConverter.ToDescription) + .ToArrayAsync(cancellationToken); + + return sickLeaves.GroupBy(x => x.EmployeeId).ToDictionary(x => x.Key, x => x.ToArray()); + } + public async Task GetCalendarEventAsync(EmployeeId employeeId, int eventId, CancellationToken cancellationToken) { using var db = this.dbFactory(); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit.Contracts/IWorkHoursCredit.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit.Contracts/IWorkHoursCredit.cs index 5768e3f43..4d58f9fc3 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit.Contracts/IWorkHoursCredit.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit.Contracts/IWorkHoursCredit.cs @@ -24,6 +24,8 @@ public interface IWorkHoursCredit : IService Task GetCalendarEventsAsync(EmployeeId employeeId, CancellationToken cancellationToken); + Task> GetCalendarEventsCollectionAsync(EmployeeId[] employeeIds, CancellationToken cancellationToken); + Task GetCalendarEventAsync(EmployeeId employeeId, Guid eventId, CancellationToken cancellationToken); Task> GetActiveRequestsAsync(EmployeeId[] employeeIds, CancellationToken cancellationToken); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs index df16d5e64..59e5056a0 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs @@ -77,6 +77,15 @@ public async Task GetCalendarEventsAsync(EmployeeId employeeI return events; } + public async Task> GetCalendarEventsCollectionAsync(EmployeeId[] employeeIds, CancellationToken cancellationToken) + { + using var ctx = this.dbFactory(); + var events = await this.QueryCalendarEvents(ctx.Value.ChangeRequests, x => employeeIds.Any(id => id.Value == x.EmployeeId)) + .ToArrayAsync(cancellationToken); + + return events.GroupBy(x=> x.EmployeeId).ToDictionary(x => x.Key, x=> x.ToArray()); + } + public async Task GetCalendarEventAsync(EmployeeId employeeId, Guid eventId, CancellationToken cancellationToken) { using var ctx = this.dbFactory(); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.sln b/server2/Arcadia.Assistant/Arcadia.Assistant.sln index ba512d951..f01d7caf9 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.sln +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.sln @@ -75,6 +75,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcadia.Assistant.ExternalS EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcadia.Assistant.Sharepoint", "Arcadia.Assistant.Sharepoint\Arcadia.Assistant.Sharepoint.csproj", "{C5818540-DECF-409D-82CE-D34E72C34D7D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcadia.Assistant.Calendar.Abstractions", "Arcadia.Assistant.Calendar.Abstractions\Arcadia.Assistant.Calendar.Abstractions.csproj", "{2D9BD0AE-9420-4743-96EF-C6F39F46C39F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -339,6 +341,14 @@ Global {C5818540-DECF-409D-82CE-D34E72C34D7D}.Release|Any CPU.Build.0 = Release|Any CPU {C5818540-DECF-409D-82CE-D34E72C34D7D}.Release|x64.ActiveCfg = Release|Any CPU {C5818540-DECF-409D-82CE-D34E72C34D7D}.Release|x64.Build.0 = Release|Any CPU + {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Debug|x64.ActiveCfg = Debug|x64 + {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Debug|x64.Build.0 = Debug|x64 + {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Release|Any CPU.Build.0 = Release|Any CPU + {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Release|x64.ActiveCfg = Release|x64 + {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -364,6 +374,7 @@ Global {9507586E-6D63-432E-99E2-59B73907ACA0} = {EEF565D4-4A3D-4784-AE0F-AF39C4A27E90} {085366A3-3D08-45B5-9A24-DDF3E1EEB2FA} = {EEF565D4-4A3D-4784-AE0F-AF39C4A27E90} {C5818540-DECF-409D-82CE-D34E72C34D7D} = {EEF565D4-4A3D-4784-AE0F-AF39C4A27E90} + {2D9BD0AE-9420-4743-96EF-C6F39F46C39F} = {EEF565D4-4A3D-4784-AE0F-AF39C4A27E90} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C376673D-F593-42B1-8093-D1F667D586B1} From efc96b8da86ef9655ab41514938b21c5c26e8492 Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Wed, 25 Dec 2019 13:20:00 +0300 Subject: [PATCH 04/14] Fix PR comments --- .../EmployeeMetadata.cs | 2 +- .../SharepointStorageItemComparer.cs | 4 +- .../PublishProfiles/Azure.xml | 2 +- .../Arcadia.Assistant.Sharepoint.csproj | 1 - .../CalendarEvent/CalendarEventTypes.cs | 11 + .../CalendarEvent/CspCalendarEventIdParser.cs | 29 +++ .../CalendarEvent/DatesPeriod.cs | 104 +++++++++ ...ISharepointDepartmentsCalendarsSettings.cs | 2 + .../SharepointDepartmentCalendarMapping.cs | 2 +- .../SharepointDepartmentsCalendarsSettings.cs | 2 +- .../SharepointSynchronizationSettings.cs | 2 +- .../Arcadia.Assistant.Sharepoint/Program.cs | 2 + .../RemoveCalendarEventFromSharepoint.cs | 19 -- .../ServiceEventSource.cs | 2 +- .../Sharepoint.cs | 215 +----------------- .../SharepointItemSynchronization.cs | 143 ++++++++++++ .../SharepointSynchronizator.cs | 171 ++++++++++++++ .../StoreCalendarEventToSharepoint.cs | 19 -- .../Arcadia.Assistant/Arcadia.Assistant.sln | 11 - 19 files changed, 479 insertions(+), 264 deletions(-) rename server2/Arcadia.Assistant/{Arcadia.Assistant.Sharepoint => Arcadia.Assistant.ExternalStorages.Abstractions}/SharepointStorageItemComparer.cs (95%) create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/CalendarEvent/CalendarEventTypes.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/CalendarEvent/CspCalendarEventIdParser.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/CalendarEvent/DatesPeriod.cs rename server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/{ => Models}/SharepointDepartmentCalendarMapping.cs (86%) rename server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/{ => Models}/SharepointDepartmentsCalendarsSettings.cs (94%) rename server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/{ => Models}/SharepointSynchronizationSettings.cs (92%) delete mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/RemoveCalendarEventFromSharepoint.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointItemSynchronization.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs delete mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/StoreCalendarEventToSharepoint.cs diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Employees.Contracts/EmployeeMetadata.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Employees.Contracts/EmployeeMetadata.cs index d1facefa8..04aaac07e 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Employees.Contracts/EmployeeMetadata.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Employees.Contracts/EmployeeMetadata.cs @@ -59,7 +59,7 @@ public EmployeeMetadata(EmployeeId employeeId, string email) return CalculateYearsFromDate(this.BirthDate, date); } - public string Name => $"{this.FirstName ?? string.Empty} {this.LastName ?? string.Empty}"; + public string Name => $"{this.FirstName ?? string.Empty} {this.LastName ?? string.Empty}".Trim(); public int? YearsServedAt(DateTime date) { diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointStorageItemComparer.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/SharepointStorageItemComparer.cs similarity index 95% rename from server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointStorageItemComparer.cs rename to server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/SharepointStorageItemComparer.cs index 162a3ca2d..2710c3aa0 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointStorageItemComparer.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/SharepointStorageItemComparer.cs @@ -1,9 +1,7 @@ -namespace Arcadia.Assistant.Sharepoint +namespace Arcadia.Assistant.ExternalStorages.Abstractions { using System.Collections.Generic; - using ExternalStorages.Abstractions; - public sealed class SharepointStorageItemComparer : IEqualityComparer { public bool Equals(StorageItem x, StorageItem y) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.SF/PublishProfiles/Azure.xml b/server2/Arcadia.Assistant/Arcadia.Assistant.SF/PublishProfiles/Azure.xml index d57ba57bd..c2e63a7fb 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.SF/PublishProfiles/Azure.xml +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.SF/PublishProfiles/Azure.xml @@ -20,7 +20,7 @@ AzureActiveDirectory="true" ServerCertThumbprint="0123456789012345678901234567890123456789" /> --> - + diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj index 321eebb44..986c111a4 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj @@ -19,7 +19,6 @@ - diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/CalendarEvent/CalendarEventTypes.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/CalendarEvent/CalendarEventTypes.cs new file mode 100644 index 000000000..f1928af76 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/CalendarEvent/CalendarEventTypes.cs @@ -0,0 +1,11 @@ +namespace Arcadia.Assistant.Sharepoint.CalendarEvent +{ + public static class CalendarEventTypes + { + public const string Vacation = "Vacation"; + + public const string Workout = "Workout"; + + public const string Sickleave = "Sickleave"; + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/CalendarEvent/CspCalendarEventIdParser.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/CalendarEvent/CspCalendarEventIdParser.cs new file mode 100644 index 000000000..72027daaa --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/CalendarEvent/CspCalendarEventIdParser.cs @@ -0,0 +1,29 @@ +namespace Arcadia.Assistant.Sharepoint.CalendarEvent +{ + using System; + + public static class CspCalendarEventIdParser + { + public static int GetCspIdFromCalendarEvent(string calendarEventId, string calendarEventType) + { + var parts = calendarEventId.Split('_'); + + if (parts.Length != 2 || parts[0] != calendarEventType || !int.TryParse(parts[1], out var cspId)) + { + throw new ArgumentException("Calendar event id has wrong format"); + } + + return cspId; + } + + public static string GetCalendarEventIdFromCspId(int cspId, string calendarEventType) + { + return $"{calendarEventType}_{cspId}"; + } + + public static string GetCalendarEventIdFromCspId(Guid cspId, string calendarEventType) + { + return $"{calendarEventType}_{cspId}"; + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/CalendarEvent/DatesPeriod.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/CalendarEvent/DatesPeriod.cs new file mode 100644 index 000000000..a1a2bfd59 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/CalendarEvent/DatesPeriod.cs @@ -0,0 +1,104 @@ +namespace Arcadia.Assistant.Sharepoint.CalendarEvent +{ + using System; + + public sealed class DatesPeriod + { + public DatesPeriod(DateTime startDate, DateTime endDate, int startWorkingHour = 0, int finishWorkingHour = 8) + { + this.StartDate = MinDate(startDate, endDate); + this.EndDate = MaxDate(startDate, endDate); + this.StartWorkingHour = Math.Min(startWorkingHour, finishWorkingHour); + this.FinishWorkingHour = Math.Max(startWorkingHour, finishWorkingHour); + } + + public DateTime StartDate { get; } + + public DateTime EndDate { get; } + + /// + /// Starting working hour index. Typically, 0 or 4. + /// + public int StartWorkingHour { get; } + + /// + /// Finish working hour index. Typically, 4 or 8 + /// + public int FinishWorkingHour { get; } + + private bool Equals(DatesPeriod other) + { + return this.StartDate.Equals(other.StartDate) + && this.EndDate.Equals(other.EndDate) + && this.StartWorkingHour == other.StartWorkingHour + && this.FinishWorkingHour == other.FinishWorkingHour; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + + return this.Equals((DatesPeriod)obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = this.StartDate.GetHashCode(); + hashCode = (hashCode * 397) ^ this.EndDate.GetHashCode(); + hashCode = (hashCode * 397) ^ this.StartWorkingHour; + hashCode = (hashCode * 397) ^ this.FinishWorkingHour; + return hashCode; + } + } + + public static bool operator ==(DatesPeriod left, DatesPeriod right) + { + return Equals(left, right); + } + + public static bool operator !=(DatesPeriod left, DatesPeriod right) + { + return !Equals(left, right); + } + + public bool DatesIntersectsWith(DatesPeriod? period) + { + if (period == null) + { + return false; + } + + if (this.EndDate < period.StartDate || period.EndDate < this.StartDate) + { + return false; + } + + return true; + } + + private static DateTime MinDate(DateTime first, DateTime second) + { + return first <= second ? first : second; + } + + private static DateTime MaxDate(DateTime first, DateTime second) + { + return first >= second ? first : second; + } + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ISharepointDepartmentsCalendarsSettings.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ISharepointDepartmentsCalendarsSettings.cs index acf7a057a..1b4df8aae 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ISharepointDepartmentsCalendarsSettings.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ISharepointDepartmentsCalendarsSettings.cs @@ -2,6 +2,8 @@ { using System.Collections.Generic; + using Models; + public interface ISharepointDepartmentsCalendarsSettings { IEnumerable DepartmentsCalendars { get; } diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentCalendarMapping.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointDepartmentCalendarMapping.cs similarity index 86% rename from server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentCalendarMapping.cs rename to server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointDepartmentCalendarMapping.cs index ffb1e8289..8e297d81f 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentCalendarMapping.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointDepartmentCalendarMapping.cs @@ -1,4 +1,4 @@ -namespace Arcadia.Assistant.Sharepoint +namespace Arcadia.Assistant.Sharepoint.Models { using System.Text.Json.Serialization; diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentsCalendarsSettings.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointDepartmentsCalendarsSettings.cs similarity index 94% rename from server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentsCalendarsSettings.cs rename to server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointDepartmentsCalendarsSettings.cs index f9e194889..ee53f0169 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointDepartmentsCalendarsSettings.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointDepartmentsCalendarsSettings.cs @@ -1,4 +1,4 @@ -namespace Arcadia.Assistant.Sharepoint +namespace Arcadia.Assistant.Sharepoint.Models { using System; using System.Collections.Generic; diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizationSettings.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointSynchronizationSettings.cs similarity index 92% rename from server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizationSettings.cs rename to server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointSynchronizationSettings.cs index 8711dad4f..f3f022a3a 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizationSettings.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointSynchronizationSettings.cs @@ -1,4 +1,4 @@ -namespace Arcadia.Assistant.Sharepoint +namespace Arcadia.Assistant.Sharepoint.Models { using System.Fabric.Description; diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs index f386300e1..cb090842b 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs @@ -19,6 +19,8 @@ namespace Arcadia.Assistant.Sharepoint using Microsoft.Extensions.DependencyInjection; using Microsoft.ServiceFabric.Services.Remoting.Client; + using Models; + using Organization.Contracts; using SickLeaves.Contracts; diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/RemoveCalendarEventFromSharepoint.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/RemoveCalendarEventFromSharepoint.cs deleted file mode 100644 index dbea220c9..000000000 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/RemoveCalendarEventFromSharepoint.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Arcadia.Assistant.Sharepoint -{ - using Calendar.Abstractions; - - using Employees.Contracts; - - public class RemoveCalendarEventFromSharepoint - { - public RemoveCalendarEventFromSharepoint(CalendarEvent @event, EmployeeMetadata employeeMetadata) - { - this.Event = @event; - this.EmployeeMetadata = employeeMetadata; - } - - public CalendarEvent Event { get; } - - public EmployeeMetadata EmployeeMetadata { get; } - } -} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ServiceEventSource.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ServiceEventSource.cs index 642073ea7..bd30ab49d 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ServiceEventSource.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/ServiceEventSource.cs @@ -4,7 +4,7 @@ namespace Arcadia.Assistant.Sharepoint using System.Diagnostics.Tracing; using System.Fabric; - [EventSource(Name = "MyCompany-Arcadia.Assistant.SF-Arcadia.Assistant.Sharepoint")] + [EventSource(Name = "Arcadia.Assistant.SF-Arcadia.Assistant.Sharepoint")] internal sealed class ServiceEventSource : EventSource { public static readonly ServiceEventSource Current = new ServiceEventSource(); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs index 8aa1b0a25..55ca6e628 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs @@ -7,12 +7,11 @@ namespace Arcadia.Assistant.Sharepoint using System.Threading; using System.Threading.Tasks; - using Calendar.Abstractions; - using Employees.Contracts; using ExternalStorages.Abstractions; + using Microsoft.Extensions.Logging; using Microsoft.ServiceFabric.Services.Communication.Runtime; using Microsoft.ServiceFabric.Services.Runtime; @@ -32,9 +31,9 @@ public class Sharepoint : StatelessService private readonly ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings; private readonly IEmployees employees; private readonly Func externalStorageProvider; + private readonly ILogger? logger = null; // TO DO - initialize variable private readonly IOrganization organizations; private readonly ISharepointSynchronizationSettings serviceSettings; - private readonly IEqualityComparer sharepointStorageItemComparer = new SharepointStorageItemComparer(); private readonly ISickLeaves sickLeaves; private readonly IVacations vacations; private readonly IWorkHoursCredit workouts; @@ -85,126 +84,18 @@ protected override async Task RunAsync(CancellationToken cancellationToken) // request Sharepoint calendars var externalStorage = this.externalStorageProvider(); var departments = await this.GetDepartmentsList(cancellationToken); - var sharepointCalendars = departments.Distinct().ToDictionary(x => x, - dId => this.GetSharepointCalendarsByDepartment(dId).ToDictionary(x => x, - async cal => await this.GetAllSharepointItemsForCalendar(externalStorage, cal))); + var synchronizator = new SharepointSynchronizator(this.sickLeaves, this.vacations, this.workouts, this.departmentsCalendarsSettings, this.logger); - foreach (var departmentId in departments) + try { - var departmentEmployes = await this.employees.FindEmployeesAsync(EmployeesQuery.Create().ForDepartment(departmentId), cancellationToken); - var employeeIds = departmentEmployes.Select(x => x.EmployeeId).ToArray(); - - var employeeVacations = await this.vacations.GetCalendarEventsByEmployeeAsync(employeeIds, cancellationToken); - var employeeVacationValues = employeeVacations.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.VacationId, CalendarEventTypes.Vacation), x => x); - - var employeeWorkouts = await this.workouts.GetCalendarEventsCollectionAsync(employeeIds, cancellationToken); - var employeeWorkoutsValues = employeeWorkouts.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.ChangeId, CalendarEventTypes.Workout), x => x); - - var employeeSickLeaves = await this.sickLeaves.GetCalendarEventsCollectionAsync(employeeIds, cancellationToken); - var employeeSickLeavesValues = employeeSickLeaves.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.SickLeaveId, CalendarEventTypes.Sickleave), x => x); - - foreach (var calendar in this.GetSharepointCalendarsByDepartment(departmentId)) - { - var storageItems = await sharepointCalendars[departmentId][calendar]; - - var employeeVacationsStorageItems = storageItems.Where(x => x.Category == CalendarEventTypes.Vacation).ToList(); - var employeeWorkoutsStorageItems = storageItems.Where(x => x.Category == CalendarEventTypes.Workout).ToList(); - var employeeSickLeavesStorageItems = storageItems.Where(x => x.Category == CalendarEventTypes.Sickleave).ToList(); - - #region vacation synchronization - - // synchronize vacations for selected department - try - { - var removeStorageItems = employeeVacationsStorageItems.Where(x => !employeeVacationValues.Keys.Contains(x.CalendarEventId)).ToArray(); - - // insert or update items - foreach (var vacationEventId in employeeVacationValues.Keys) - { - var employeeMetadata = departmentEmployes.Single(x => x.EmployeeId == employeeVacationValues[vacationEventId].EmployeeId); - await this.UpsertVacation(vacationEventId, calendar, employeeVacationValues[vacationEventId], employeeMetadata, externalStorage, cancellationToken); - } - - // remove redundant items - foreach (var item in removeStorageItems) - { - await externalStorage.DeleteItem( - calendar, - item.Id); - } - } - catch (Exception e) - { - ServiceEventSource.Current.ServiceMessage(this.Context, e.ToString()); - } - - #endregion - - #region workhours synchronization - - // synchronize workouts for selected department - try - { - var removeStorageItems = employeeWorkoutsStorageItems.Where(x => !employeeWorkoutsValues.Keys.Contains(x.CalendarEventId)).ToArray(); - - // insert or update items - foreach (var workHourEventId in employeeWorkoutsValues.Keys) - { - var employeeMetadata = departmentEmployes.Single(x => x.EmployeeId == employeeWorkoutsValues[workHourEventId].EmployeeId); - await this.UpsertWorkHour(workHourEventId, calendar, employeeWorkoutsValues[workHourEventId], employeeMetadata, externalStorage, cancellationToken); - } - - // remove redundant items - foreach (var item in removeStorageItems) - { - await externalStorage.DeleteItem( - calendar, - item.Id); - } - } - catch (Exception e) - { - ServiceEventSource.Current.ServiceMessage(this.Context, e.ToString()); - } - - #endregion - - #region sick lives synchronization - - // synchronize vacations for selected department - try - { - var removeStorageItems = employeeSickLeavesStorageItems.Where(x => !employeeSickLeavesValues.Keys.Contains(x.CalendarEventId)).ToArray(); - - // insert or update items - foreach (var sickLeaveEventId in employeeSickLeavesValues.Keys) - { - var employeeMetadata = departmentEmployes.Single(x => x.EmployeeId == employeeSickLeavesValues[sickLeaveEventId].EmployeeId); - await this.UpsertSickLeave(sickLeaveEventId, calendar, employeeSickLeavesValues[sickLeaveEventId], employeeMetadata, externalStorage, cancellationToken); - } - - // remove redundant items - foreach (var item in removeStorageItems) - { - await externalStorage.DeleteItem( - calendar, - item.Id); - } - } - catch (Exception e) - { - ServiceEventSource.Current.ServiceMessage(this.Context, e.ToString()); - } - - #endregion - } + await synchronizator.Synchronize(this.employees, departments, externalStorage, cancellationToken); + } + catch (Exception e) + { + this.logger?.LogError(e, "Sharepoint items synchronization fail"); } -#if DEBUG - await Task.Delay(TimeSpan.FromMinutes(15), cancellationToken); -#else - await Task.Delay(TimeSpan.FromMinutes(serviceSettings.SynchronizationIntervalMinutes), cancellationToken); -#endif + await Task.Delay(TimeSpan.FromMinutes(this.serviceSettings.SynchronizationIntervalMinutes), cancellationToken); } } @@ -217,91 +108,5 @@ private async Task> GetDepartmentsList(CancellationToken can return (await this.organizations.GetDepartmentsAsync(cancellationToken)).Select(x => x.DepartmentId.Value.ToString()); } - - private IEnumerable GetSharepointCalendarsByDepartment(string departmentId) - { - return this.departmentsCalendarsSettings.DepartmentsCalendars - .Where(x => x.DepartmentId == departmentId) - .Select(x => x.Calendar); - } - - private async Task> GetAllSharepointItemsForCalendar(IExternalStorage externalStorage, string calendar) - { - return await externalStorage.GetItems(calendar); - } - - private async Task GetSharepointItemForCalendarEvent(IExternalStorage externalStorage, string calendar, string eventId) - { - var existingItems = await externalStorage.GetItems( - calendar, - new[] { new EqualCondition(x => x.CalendarEventId, eventId) }); - return existingItems.SingleOrDefault(); - } - - private async Task UpsertVacation(string eventId, string calendar, VacationDescription vacation, EmployeeMetadata employeeMetadata, IExternalStorage externalStorage, CancellationToken cancellationToken) - { - var datesPeriod = new DatesPeriod(vacation.StartDate.Date, vacation.EndDate.Date); - var upsertItem = this.CalendarEventToStorageItem(eventId, CalendarEventTypes.Vacation, datesPeriod, employeeMetadata); - await this.UpsertStorageItem(eventId, calendar, datesPeriod, upsertItem, externalStorage, cancellationToken); - } - - private async Task UpsertWorkHour(string eventId, string calendar, WorkHoursChange workHours, EmployeeMetadata employeeMetadata, IExternalStorage externalStorage, CancellationToken cancellationToken) - { - var datesPeriod = new DatesPeriod(workHours.Date, workHours.Date); - var upsertItem = this.CalendarEventToStorageItem(eventId, CalendarEventTypes.Workout, datesPeriod, employeeMetadata); - await this.UpsertStorageItem(eventId, calendar, datesPeriod, upsertItem, externalStorage, cancellationToken); - } - - private async Task UpsertSickLeave(string eventId, string calendar, SickLeaveDescription sickLeave, EmployeeMetadata employeeMetadata, IExternalStorage externalStorage, CancellationToken cancellationToken) - { - var datesPeriod = new DatesPeriod(sickLeave.StartDate.Date, sickLeave.EndDate.Date); - var upsertItem = this.CalendarEventToStorageItem(eventId, CalendarEventTypes.Sickleave, datesPeriod, employeeMetadata); - await this.UpsertStorageItem(eventId, calendar, datesPeriod, upsertItem, externalStorage, cancellationToken); - } - - private async Task UpsertStorageItem(string eventId, string calendar, DatesPeriod datesPeriod, StorageItem upsertItem, IExternalStorage externalStorage, CancellationToken cancellationToken) - { - var storageItem = await this.GetSharepointItemForCalendarEvent(externalStorage, calendar, eventId); - - if (storageItem == null) - { - await externalStorage.AddItem( - calendar, - upsertItem, - cancellationToken); - } - else if (!this.sharepointStorageItemComparer.Equals(upsertItem, storageItem)) - { - upsertItem.Id = storageItem.Id; - await externalStorage.UpdateItem( - calendar, - upsertItem, - cancellationToken); - } - } - - private StorageItem CalendarEventToStorageItem(string eventId, string calendarEventType, DatesPeriod period, EmployeeMetadata employeeMetadata) - { - var totalHours = period.FinishWorkingHour - period.StartWorkingHour; - - var longEventsTitle = $"{employeeMetadata.Name} ({calendarEventType})"; - var shortEventsTitle = $"{employeeMetadata.Name} ({calendarEventType}: {totalHours} hours)"; - - var title = calendarEventType == CalendarEventTypes.Vacation || calendarEventType == CalendarEventTypes.Sickleave - ? longEventsTitle - : shortEventsTitle; - - var storageItem = new StorageItem - { - Title = title, - StartDate = period.StartDate, - EndDate = period.EndDate, - Category = calendarEventType, - AllDayEvent = true, - CalendarEventId = eventId - }; - - return storageItem; - } } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointItemSynchronization.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointItemSynchronization.cs new file mode 100644 index 000000000..26c880b5b --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointItemSynchronization.cs @@ -0,0 +1,143 @@ +namespace Arcadia.Assistant.Sharepoint +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using CalendarEvent; + + using Employees.Contracts; + + using ExternalStorages.Abstractions; + + using Microsoft.Extensions.Logging; + + public abstract class SharepointItemSynchronization + { + #region ctor + + protected SharepointItemSynchronization(IExternalStorage externalStorage, ILogger? logger = null) + { + this.ExternalStorage = externalStorage; + this.Logger = logger; + } + + #endregion + + #region public interface + + public async Task SynchronizeItem(string calendar, EmployeeMetadata[] departmentEmployes, Dictionary values, IEnumerable storageItemsList, CancellationToken cancellationToken) + { + try + { + var storageItems = storageItemsList.Where(x => x.Category == this.ItemEventType).ToList(); + var removeStorageItems = storageItems.Where(x => !values.Keys.Contains(x.CalendarEventId)).ToArray(); + + // insert or update items + foreach (var workHourEventId in values.Keys) + { + var employeeMetadata = departmentEmployes.Single(x => x.EmployeeId == this.GetItemEmployeeId(values[workHourEventId])); + await this.UpsertItem(workHourEventId, calendar, values[workHourEventId], employeeMetadata, this.ExternalStorage, cancellationToken); + } + + // remove redundant items + foreach (var item in removeStorageItems) + { + await this.ExternalStorage.DeleteItem( + calendar, + item.Id); + } + } + catch (Exception e) + { + this.Logger?.LogError(e, $"'{typeof(T)}' synchronization error"); + } + } + + #endregion + + #region variables + + private readonly IEqualityComparer sharepointStorageItemComparer = new SharepointStorageItemComparer(); + protected readonly IExternalStorage ExternalStorage; + protected readonly ILogger? Logger; + + #endregion + + #region virtual interface + + protected abstract string ItemEventType { get; } + + protected abstract DatesPeriod GetItemDatePeriod(T item); + + protected abstract EmployeeId GetItemEmployeeId(T item); + + #endregion + + #region private + + private async Task UpsertItem(string eventId, string calendar, T item, EmployeeMetadata employeeMetadata, IExternalStorage externalStorage, CancellationToken cancellationToken) + { + var datesPeriod = this.GetItemDatePeriod(item); + var upsertItem = this.CalendarEventToStorageItem(eventId, CalendarEventTypes.Sickleave, datesPeriod, employeeMetadata); + await this.UpsertStorageItem(eventId, calendar, datesPeriod, upsertItem, externalStorage, cancellationToken); + } + + private async Task GetSharepointItemForCalendarEvent(IExternalStorage externalStorage, string calendar, string eventId) + { + var existingItems = await externalStorage.GetItems( + calendar, + new[] { new EqualCondition(x => x.CalendarEventId, eventId) }); + return existingItems.SingleOrDefault(); + } + + private async Task UpsertStorageItem(string eventId, string calendar, DatesPeriod datesPeriod, StorageItem upsertItem, IExternalStorage externalStorage, CancellationToken cancellationToken) + { + var storageItem = await this.GetSharepointItemForCalendarEvent(externalStorage, calendar, eventId); + + if (storageItem == null) + { + await externalStorage.AddItem( + calendar, + upsertItem, + cancellationToken); + } + else if (!this.sharepointStorageItemComparer.Equals(upsertItem, storageItem)) + { + upsertItem.Id = storageItem.Id; + await externalStorage.UpdateItem( + calendar, + upsertItem, + cancellationToken); + } + } + + private StorageItem CalendarEventToStorageItem(string eventId, string calendarEventType, DatesPeriod period, EmployeeMetadata employeeMetadata) + { + var totalHours = period.FinishWorkingHour - period.StartWorkingHour; + + var longEventsTitle = $"{employeeMetadata.Name} ({calendarEventType})"; + var shortEventsTitle = $"{employeeMetadata.Name} ({calendarEventType}: {totalHours} hours)"; + + var title = calendarEventType == CalendarEventTypes.Vacation || calendarEventType == CalendarEventTypes.Sickleave + ? longEventsTitle + : shortEventsTitle; + + var storageItem = new StorageItem + { + Title = title, + StartDate = period.StartDate, + EndDate = period.EndDate, + Category = calendarEventType, + AllDayEvent = true, + CalendarEventId = eventId + }; + + return storageItem; + } + + #endregion + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs new file mode 100644 index 000000000..2c28ee474 --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs @@ -0,0 +1,171 @@ +namespace Arcadia.Assistant.Sharepoint +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using CalendarEvent; + + using Employees.Contracts; + + using ExternalStorages.Abstractions; + + using Microsoft.Extensions.Logging; + + using SickLeaves.Contracts; + + using Vacations.Contracts; + + using WorkHoursCredit.Contracts; + + public sealed class SharepointSynchronizator + { + #region ctor + + public SharepointSynchronizator(ISickLeaves sickLeaves, IVacations vacations, IWorkHoursCredit workouts, ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings, ILogger? logger) + { + this.sickLeaves = sickLeaves; + this.vacations = vacations; + this.workouts = workouts; + this.departmentsCalendarsSettings = departmentsCalendarsSettings; + this.logger = logger; + } + + #endregion + + #region publci interface + + public async Task Synchronize(IEmployees employees, IEnumerable departments, IExternalStorage storage, CancellationToken cancellationToken) + { + var vacationsSync = new EmployeeVacationsSynchronization(storage, this.logger); + var workoutsSync = new EmployeeWorkoutsSynchronization(storage, this.logger); + var sickLeavesSync = new EmployeeSickLeavesSynchronization(storage, this.logger); + + var sharepointCalendars = departments.Distinct().ToDictionary(x => x, + dId => this.GetSharepointCalendarsByDepartment(dId).ToDictionary(x => x, + async cal => await this.GetAllSharepointItemsForCalendar(storage, cal))); + + foreach (var departmentId in departments) + { + var departmentEmployes = await employees.FindEmployeesAsync(EmployeesQuery.Create().ForDepartment(departmentId), cancellationToken); + var employeeIds = departmentEmployes.Select(x => x.EmployeeId).ToArray(); + + var employeeVacations = await this.vacations.GetCalendarEventsByEmployeeAsync(employeeIds, cancellationToken); + var employeeVacationValues = employeeVacations.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.VacationId, CalendarEventTypes.Vacation), x => x); + + var employeeWorkouts = await this.workouts.GetCalendarEventsCollectionAsync(employeeIds, cancellationToken); + var employeeWorkoutsValues = employeeWorkouts.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.ChangeId, CalendarEventTypes.Workout), x => x); + + var employeeSickLeaves = await this.sickLeaves.GetCalendarEventsCollectionAsync(employeeIds, cancellationToken); + var employeeSickLeavesValues = employeeSickLeaves.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.SickLeaveId, CalendarEventTypes.Sickleave), x => x); + + foreach (var calendar in this.GetSharepointCalendarsByDepartment(departmentId)) + { + var storageItems = await sharepointCalendars[departmentId][calendar]; + + // synchronize vacations for selected department + await vacationsSync.SynchronizeItem(calendar, departmentEmployes, employeeVacationValues, storageItems, cancellationToken); + + // synchronize workouts for selected department + await workoutsSync.SynchronizeItem(calendar, departmentEmployes, employeeWorkoutsValues, storageItems, cancellationToken); + + // synchronize vacations for selected department + await sickLeavesSync.SynchronizeItem(calendar, departmentEmployes, employeeSickLeavesValues, storageItems, cancellationToken); + } + } + } + + #endregion + + #region internal class + + private class EmployeeVacationsSynchronization : SharepointItemSynchronization + { + public EmployeeVacationsSynchronization(IExternalStorage externalStorage, ILogger? logger = null) + : base(externalStorage, logger) + { + } + + protected override string ItemEventType { get; } = CalendarEventTypes.Vacation; + + protected override DatesPeriod GetItemDatePeriod(VacationDescription item) + { + return new DatesPeriod(item.StartDate.Date, item.EndDate.Date); + } + + protected override EmployeeId GetItemEmployeeId(VacationDescription item) + { + return item.EmployeeId; + } + } + + private class EmployeeWorkoutsSynchronization : SharepointItemSynchronization + { + public EmployeeWorkoutsSynchronization(IExternalStorage externalStorage, ILogger? logger = null) + : base(externalStorage, logger) + { + } + + protected override string ItemEventType { get; } = CalendarEventTypes.Workout; + + protected override DatesPeriod GetItemDatePeriod(WorkHoursChange item) + { + return new DatesPeriod(item.Date, item.Date); + } + + protected override EmployeeId GetItemEmployeeId(WorkHoursChange item) + { + return item.EmployeeId; + } + } + + private class EmployeeSickLeavesSynchronization : SharepointItemSynchronization + { + public EmployeeSickLeavesSynchronization(IExternalStorage externalStorage, ILogger? logger = null) + : base(externalStorage, logger) + { + } + + protected override string ItemEventType { get; } = CalendarEventTypes.Sickleave; + + protected override DatesPeriod GetItemDatePeriod(SickLeaveDescription item) + { + return new DatesPeriod(item.StartDate.Date, item.EndDate.Date); + } + + protected override EmployeeId GetItemEmployeeId(SickLeaveDescription item) + { + return item.EmployeeId; + } + } + + #endregion + + #region variables + + private readonly ISickLeaves sickLeaves; + private readonly IVacations vacations; + private readonly IWorkHoursCredit workouts; + private readonly ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings; + private readonly ILogger? logger; + + #endregion + + #region private + + private async Task> GetAllSharepointItemsForCalendar(IExternalStorage externalStorage, string calendar) + { + return await externalStorage.GetItems(calendar); + } + + private IEnumerable GetSharepointCalendarsByDepartment(string departmentId) + { + return this.departmentsCalendarsSettings.DepartmentsCalendars + .Where(x => x.DepartmentId == departmentId) + .Select(x => x.Calendar); + } + + #endregion + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/StoreCalendarEventToSharepoint.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/StoreCalendarEventToSharepoint.cs deleted file mode 100644 index 380591bcc..000000000 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/StoreCalendarEventToSharepoint.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Arcadia.Assistant.Sharepoint -{ - using Calendar.Abstractions; - - using Employees.Contracts; - - public class StoreCalendarEventToSharepoint - { - public StoreCalendarEventToSharepoint(CalendarEvent @event, EmployeeMetadata employeeMetadata) - { - this.Event = @event; - this.EmployeeMetadata = employeeMetadata; - } - - public CalendarEvent Event { get; } - - public EmployeeMetadata EmployeeMetadata { get; } - } -} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.sln b/server2/Arcadia.Assistant/Arcadia.Assistant.sln index f01d7caf9..ba512d951 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.sln +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.sln @@ -75,8 +75,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcadia.Assistant.ExternalS EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcadia.Assistant.Sharepoint", "Arcadia.Assistant.Sharepoint\Arcadia.Assistant.Sharepoint.csproj", "{C5818540-DECF-409D-82CE-D34E72C34D7D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcadia.Assistant.Calendar.Abstractions", "Arcadia.Assistant.Calendar.Abstractions\Arcadia.Assistant.Calendar.Abstractions.csproj", "{2D9BD0AE-9420-4743-96EF-C6F39F46C39F}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -341,14 +339,6 @@ Global {C5818540-DECF-409D-82CE-D34E72C34D7D}.Release|Any CPU.Build.0 = Release|Any CPU {C5818540-DECF-409D-82CE-D34E72C34D7D}.Release|x64.ActiveCfg = Release|Any CPU {C5818540-DECF-409D-82CE-D34E72C34D7D}.Release|x64.Build.0 = Release|Any CPU - {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Debug|x64.ActiveCfg = Debug|x64 - {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Debug|x64.Build.0 = Debug|x64 - {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Release|Any CPU.Build.0 = Release|Any CPU - {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Release|x64.ActiveCfg = Release|x64 - {2D9BD0AE-9420-4743-96EF-C6F39F46C39F}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -374,7 +364,6 @@ Global {9507586E-6D63-432E-99E2-59B73907ACA0} = {EEF565D4-4A3D-4784-AE0F-AF39C4A27E90} {085366A3-3D08-45B5-9A24-DDF3E1EEB2FA} = {EEF565D4-4A3D-4784-AE0F-AF39C4A27E90} {C5818540-DECF-409D-82CE-D34E72C34D7D} = {EEF565D4-4A3D-4784-AE0F-AF39C4A27E90} - {2D9BD0AE-9420-4743-96EF-C6F39F46C39F} = {EEF565D4-4A3D-4784-AE0F-AF39C4A27E90} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C376673D-F593-42B1-8093-D1F667D586B1} From 832b65dc966981bf714399a806a8f15526191de6 Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Wed, 25 Dec 2019 14:25:42 +0300 Subject: [PATCH 05/14] Remove redundant code --- ...dia.Assistant.Calendar.Abstractions.csproj | 12 -- .../CalendarEvent.cs | 38 ------ .../CalendarEventAdditionalDataEntry.cs | 15 --- .../CalendarEventStatuses.cs | 113 ------------------ .../CalendarEventTypes.cs | 23 ---- .../CspCalendarEventIdParser.cs | 29 ----- .../DatesPeriod.cs | 104 ---------------- .../SickLeaveStatuses.cs | 17 --- .../VacationStatuses.cs | 23 ---- .../WorkHoursChangeStatuses.cs | 19 --- 10 files changed, 393 deletions(-) delete mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/Arcadia.Assistant.Calendar.Abstractions.csproj delete mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEvent.cs delete mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventAdditionalDataEntry.cs delete mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventStatuses.cs delete mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventTypes.cs delete mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CspCalendarEventIdParser.cs delete mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/DatesPeriod.cs delete mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/SickLeaveStatuses.cs delete mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/VacationStatuses.cs delete mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/WorkHoursChangeStatuses.cs diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/Arcadia.Assistant.Calendar.Abstractions.csproj b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/Arcadia.Assistant.Calendar.Abstractions.csproj deleted file mode 100644 index c9840b768..000000000 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/Arcadia.Assistant.Calendar.Abstractions.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - netstandard2.1 - AnyCPU;x64 - enable - - - - - - \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEvent.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEvent.cs deleted file mode 100644 index 111787f32..000000000 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEvent.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Arcadia.Assistant.Calendar.Abstractions -{ - using System.Linq; - - public class CalendarEvent - { - public CalendarEvent( - string eventId, - string type, - DatesPeriod dates, - string status, - string employeeId, - CalendarEventAdditionalDataEntry[]? additionalData = null) - { - this.EventId = eventId; - this.Dates = dates; - this.Status = status; - this.Type = type; - this.EmployeeId = employeeId; - this.AdditionalData = additionalData ?? new CalendarEventAdditionalDataEntry[0]; - this.IsPending = new CalendarEventStatuses().PendingForType(type).Contains(status); - } - - public string EventId { get; } - - public DatesPeriod Dates { get; } - - public string Status { get; } - - public string Type { get; } - - public bool IsPending { get; } - - public string EmployeeId { get; } - - public CalendarEventAdditionalDataEntry[] AdditionalData { get; } - } -} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventAdditionalDataEntry.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventAdditionalDataEntry.cs deleted file mode 100644 index 932429b27..000000000 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventAdditionalDataEntry.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Arcadia.Assistant.Calendar.Abstractions -{ - public class CalendarEventAdditionalDataEntry - { - public CalendarEventAdditionalDataEntry(string key, string value) - { - this.Key = key; - this.Value = value; - } - - public string Key { get; } - - public string Value { get; } - } -} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventStatuses.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventStatuses.cs deleted file mode 100644 index e61e5b356..000000000 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventStatuses.cs +++ /dev/null @@ -1,113 +0,0 @@ -namespace Arcadia.Assistant.Calendar.Abstractions -{ - using System.Collections.Generic; - - public class CalendarEventStatuses - { - private static readonly IReadOnlyDictionary StatusesByType = new Dictionary - { - { CalendarEventTypes.Dayoff, WorkHoursChangeStatuses.All }, - { CalendarEventTypes.Workout, WorkHoursChangeStatuses.All }, - { CalendarEventTypes.Sickleave, SickLeaveStatuses.All }, - { CalendarEventTypes.Vacation, VacationStatuses.All } - }; - - private static readonly IReadOnlyDictionary PendingStatusesByType = new Dictionary - { - { CalendarEventTypes.Dayoff, WorkHoursChangeStatuses.Pending }, - { CalendarEventTypes.Workout, WorkHoursChangeStatuses.Pending }, - { CalendarEventTypes.Sickleave, SickLeaveStatuses.Pending }, - { CalendarEventTypes.Vacation, VacationStatuses.Pending } - }; - - private static readonly IReadOnlyDictionary ActualStatusesByType = new Dictionary - { - { CalendarEventTypes.Dayoff, WorkHoursChangeStatuses.Actual }, - { CalendarEventTypes.Workout, WorkHoursChangeStatuses.Actual }, - { CalendarEventTypes.Sickleave, SickLeaveStatuses.Actual }, - { CalendarEventTypes.Vacation, VacationStatuses.Actual } - }; - - private static readonly IReadOnlyDictionary ApprovedStatusByType = new Dictionary - { - { CalendarEventTypes.Dayoff, WorkHoursChangeStatuses.Approved }, - { CalendarEventTypes.Workout, WorkHoursChangeStatuses.Approved }, - { CalendarEventTypes.Vacation, VacationStatuses.Approved } - }; - - private static readonly IReadOnlyDictionary RejectedStatusByType = new Dictionary - { - { CalendarEventTypes.Dayoff, WorkHoursChangeStatuses.Rejected }, - { CalendarEventTypes.Workout, WorkHoursChangeStatuses.Rejected }, - { CalendarEventTypes.Vacation, VacationStatuses.Rejected } - }; - - private static readonly IReadOnlyDictionary CancelledStatusByType = new Dictionary - { - { CalendarEventTypes.Dayoff, WorkHoursChangeStatuses.Cancelled }, - { CalendarEventTypes.Workout, WorkHoursChangeStatuses.Cancelled }, - { CalendarEventTypes.Sickleave, SickLeaveStatuses.Cancelled }, - { CalendarEventTypes.Vacation, VacationStatuses.Cancelled } - }; - - public string[] AllForType(string type) - { - if (StatusesByType.TryGetValue(type, out var statuses)) - { - return statuses; - } - - return new string[0]; - } - - public string[] PendingForType(string type) - { - if (PendingStatusesByType.TryGetValue(type, out var statuses)) - { - return statuses; - } - - return new string[0]; - } - - public string[] ActualForType(string type) - { - if (ActualStatusesByType.TryGetValue(type, out var statuses)) - { - return statuses; - } - - return new string[0]; - } - - public string? ApprovedForType(string type) - { - if (ApprovedStatusByType.TryGetValue(type, out var status)) - { - return status; - } - - return null; - } - - public string? RejectedForType(string type) - { - if (RejectedStatusByType.TryGetValue(type, out var status)) - { - return status; - } - - return null; - } - - public string? CancelledForType(string type) - { - if (CancelledStatusByType.TryGetValue(type, out var status)) - { - return status; - } - - return null; - } - } -} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventTypes.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventTypes.cs deleted file mode 100644 index 066fb7ce3..000000000 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CalendarEventTypes.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Arcadia.Assistant.Calendar.Abstractions -{ - using System; - using System.Linq; - - public static class CalendarEventTypes - { - public const string Vacation = "Vacation"; - - public const string Dayoff = "Dayoff"; - - public const string Workout = "Workout"; - - public const string Sickleave = "Sickleave"; - - public static readonly string[] All = { Vacation, Dayoff, Workout, Sickleave }; - - public static bool IsKnownType(string x) - { - return All.Contains(x, StringComparer.InvariantCultureIgnoreCase); - } - } -} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CspCalendarEventIdParser.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CspCalendarEventIdParser.cs deleted file mode 100644 index 7107a2dec..000000000 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/CspCalendarEventIdParser.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Arcadia.Assistant.Calendar.Abstractions -{ - using System; - - public static class CspCalendarEventIdParser - { - public static int GetCspIdFromCalendarEvent(string calendarEventId, string calendarEventType) - { - var parts = calendarEventId.Split('_'); - - if (parts.Length != 2 || parts[0] != calendarEventType || !int.TryParse(parts[1], out var cspId)) - { - throw new ArgumentException("Calendar event id has wrong format"); - } - - return cspId; - } - - public static string GetCalendarEventIdFromCspId(int cspId, string calendarEventType) - { - return $"{calendarEventType}_{cspId}"; - } - - public static string GetCalendarEventIdFromCspId(Guid cspId, string calendarEventType) - { - return $"{calendarEventType}_{cspId}"; - } - } -} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/DatesPeriod.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/DatesPeriod.cs deleted file mode 100644 index 4dbcaaf43..000000000 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/DatesPeriod.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace Arcadia.Assistant.Calendar.Abstractions -{ - using System; - - public sealed class DatesPeriod - { - public DatesPeriod(DateTime startDate, DateTime endDate, int startWorkingHour = 0, int finishWorkingHour = 8) - { - this.StartDate = MinDate(startDate, endDate); - this.EndDate = MaxDate(startDate, endDate); - this.StartWorkingHour = Math.Min(startWorkingHour, finishWorkingHour); - this.FinishWorkingHour = Math.Max(startWorkingHour, finishWorkingHour); - } - - public DateTime StartDate { get; } - - public DateTime EndDate { get; } - - /// - /// Starting working hour index. Typically, 0 or 4. - /// - public int StartWorkingHour { get; } - - /// - /// Finish working hour index. Typically, 4 or 8 - /// - public int FinishWorkingHour { get; } - - private bool Equals(DatesPeriod other) - { - return this.StartDate.Equals(other.StartDate) - && this.EndDate.Equals(other.EndDate) - && this.StartWorkingHour == other.StartWorkingHour - && this.FinishWorkingHour == other.FinishWorkingHour; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != this.GetType()) - { - return false; - } - - return this.Equals((DatesPeriod)obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = this.StartDate.GetHashCode(); - hashCode = (hashCode * 397) ^ this.EndDate.GetHashCode(); - hashCode = (hashCode * 397) ^ this.StartWorkingHour; - hashCode = (hashCode * 397) ^ this.FinishWorkingHour; - return hashCode; - } - } - - public static bool operator ==(DatesPeriod left, DatesPeriod right) - { - return Equals(left, right); - } - - public static bool operator !=(DatesPeriod left, DatesPeriod right) - { - return !Equals(left, right); - } - - public bool DatesIntersectsWith(DatesPeriod? period) - { - if (period == null) - { - return false; - } - - if (this.EndDate < period.StartDate || period.EndDate < this.StartDate) - { - return false; - } - - return true; - } - - private static DateTime MinDate(DateTime first, DateTime second) - { - return first <= second ? first : second; - } - - private static DateTime MaxDate(DateTime first, DateTime second) - { - return first >= second ? first : second; - } - } -} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/SickLeaveStatuses.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/SickLeaveStatuses.cs deleted file mode 100644 index e7596fa40..000000000 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/SickLeaveStatuses.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Arcadia.Assistant.Calendar.Abstractions -{ - public static class SickLeaveStatuses - { - public const string Requested = "Requested"; - - public const string Cancelled = "Cancelled"; - - public const string Completed = "Completed"; - - public static readonly string[] All = { Requested, Completed, Cancelled }; - - public static readonly string[] Pending = { Requested }; - - public static readonly string[] Actual = { Requested, Completed }; - } -} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/VacationStatuses.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/VacationStatuses.cs deleted file mode 100644 index a55c1e6ee..000000000 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/VacationStatuses.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Arcadia.Assistant.Calendar.Abstractions -{ - public static class VacationStatuses - { - public const string Requested = "Requested"; - - public const string Cancelled = "Cancelled"; - - public const string Approved = "Approved"; - - public const string Rejected = "Rejected"; - - public const string AccountingReady = "AccountingReady"; - - public const string Processed = "Processed"; - - public static readonly string[] All = { Requested, Approved, Cancelled, Rejected, AccountingReady, Processed }; - - public static readonly string[] Pending = { Requested }; - - public static readonly string[] Actual = { Requested, Approved, AccountingReady, AccountingReady, Processed }; - } -} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/WorkHoursChangeStatuses.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/WorkHoursChangeStatuses.cs deleted file mode 100644 index f0076ecce..000000000 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Calendar.Abstractions/WorkHoursChangeStatuses.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Arcadia.Assistant.Calendar.Abstractions -{ - public static class WorkHoursChangeStatuses - { - public const string Requested = "Requested"; - - public const string Cancelled = "Cancelled"; - - public const string Approved = "Approved"; - - public const string Rejected = "Rejected"; - - public static readonly string[] All = { Requested, Approved, Cancelled, Rejected }; - - public static readonly string[] Pending = { Requested }; - - public static readonly string[] Actual = { Requested, Approved }; - } -} \ No newline at end of file From 4e85f4f42f1fa516e33b4c77acd7f932c0bc5c10 Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Wed, 25 Dec 2019 14:41:15 +0300 Subject: [PATCH 06/14] Fix method name --- .../Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs | 4 ++-- .../Arcadia.Assistant.SickLeaves.Contracts/ISickLeaves.cs | 2 +- .../Arcadia.Assistant.SickLeaves/SickLeaves.cs | 2 +- .../IWorkHoursCredit.cs | 2 +- .../Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs index 2c28ee474..d3293c4e8 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs @@ -54,10 +54,10 @@ public async Task Synchronize(IEmployees employees, IEnumerable departme var employeeVacations = await this.vacations.GetCalendarEventsByEmployeeAsync(employeeIds, cancellationToken); var employeeVacationValues = employeeVacations.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.VacationId, CalendarEventTypes.Vacation), x => x); - var employeeWorkouts = await this.workouts.GetCalendarEventsCollectionAsync(employeeIds, cancellationToken); + var employeeWorkouts = await this.workouts.GetCalendarEventsByEmployeeMapAsync(employeeIds, cancellationToken); var employeeWorkoutsValues = employeeWorkouts.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.ChangeId, CalendarEventTypes.Workout), x => x); - var employeeSickLeaves = await this.sickLeaves.GetCalendarEventsCollectionAsync(employeeIds, cancellationToken); + var employeeSickLeaves = await this.sickLeaves.GetCalendarEventsByEmployeeMapAsync(employeeIds, cancellationToken); var employeeSickLeavesValues = employeeSickLeaves.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.SickLeaveId, CalendarEventTypes.Sickleave), x => x); foreach (var calendar in this.GetSharepointCalendarsByDepartment(departmentId)) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves.Contracts/ISickLeaves.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves.Contracts/ISickLeaves.cs index 915b1f1bf..f94ecbc94 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves.Contracts/ISickLeaves.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves.Contracts/ISickLeaves.cs @@ -18,7 +18,7 @@ public interface ISickLeaves : IService { Task GetCalendarEventsAsync(EmployeeId employeeId, CancellationToken cancellationToken); - Task> GetCalendarEventsCollectionAsync(EmployeeId[] employeeIds, CancellationToken cancellationToken); + Task> GetCalendarEventsByEmployeeMapAsync(EmployeeId[] employeeIds, CancellationToken cancellationToken); Task GetCalendarEventAsync(EmployeeId employeeId, int eventId, CancellationToken cancellationToken); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/SickLeaves.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/SickLeaves.cs index baa621881..f73928dee 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/SickLeaves.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/SickLeaves.cs @@ -80,7 +80,7 @@ public async Task GetCalendarEventsAsync(EmployeeId empl return sickLeaves; } - public async Task> GetCalendarEventsCollectionAsync(EmployeeId[] employeeIds, CancellationToken cancellationToken) + public async Task> GetCalendarEventsByEmployeeMapAsync(EmployeeId[] employeeIds, CancellationToken cancellationToken) { using var db = this.dbFactory(); var sickLeaves = await db.Value diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit.Contracts/IWorkHoursCredit.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit.Contracts/IWorkHoursCredit.cs index 4d58f9fc3..7c539f356 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit.Contracts/IWorkHoursCredit.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit.Contracts/IWorkHoursCredit.cs @@ -24,7 +24,7 @@ public interface IWorkHoursCredit : IService Task GetCalendarEventsAsync(EmployeeId employeeId, CancellationToken cancellationToken); - Task> GetCalendarEventsCollectionAsync(EmployeeId[] employeeIds, CancellationToken cancellationToken); + Task> GetCalendarEventsByEmployeeMapAsync(EmployeeId[] employeeIds, CancellationToken cancellationToken); Task GetCalendarEventAsync(EmployeeId employeeId, Guid eventId, CancellationToken cancellationToken); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs index 59e5056a0..7a7571889 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs @@ -77,7 +77,7 @@ public async Task GetCalendarEventsAsync(EmployeeId employeeI return events; } - public async Task> GetCalendarEventsCollectionAsync(EmployeeId[] employeeIds, CancellationToken cancellationToken) + public async Task> GetCalendarEventsByEmployeeMapAsync(EmployeeId[] employeeIds, CancellationToken cancellationToken) { using var ctx = this.dbFactory(); var events = await this.QueryCalendarEvents(ctx.Value.ChangeRequests, x => employeeIds.Any(id => id.Value == x.EmployeeId)) From 48cd463bcc13d75b8ff7db6027b795d2ab6ed1d2 Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Tue, 21 Jan 2020 18:41:16 +0300 Subject: [PATCH 07/14] Fix list item to Json converting --- ...stant.ExternalStorages.Abstractions.csproj | 1 + .../PropertyNameParser.cs | 4 +- .../SharepointStorageItemComparer.cs | 10 +- .../StorageItem.cs | 11 +- .../Contracts/ISharepointFieldsMapper.cs | 2 + .../Contracts/SharepointRequest.cs | 28 ++++- .../SharepointApiModels/SharepointListItem.cs | 33 ----- .../SharepointListItemRequest.cs | 53 ++++---- .../SharepointListItemsResponse.cs | 5 +- .../SharepointAuthTokenService.cs | 5 + .../SharepointFieldsMapper.cs | 12 +- .../SharepointStorage.cs | 97 ++++---------- .../StorageItemJsonConvertor.cs | 119 ++++++++++++++++++ .../SharepointItemSynchronization.cs | 30 ++--- 14 files changed, 246 insertions(+), 164 deletions(-) delete mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItem.cs create mode 100644 server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/StorageItemJsonConvertor.cs diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/Arcadia.Assistant.ExternalStorages.Abstractions.csproj b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/Arcadia.Assistant.ExternalStorages.Abstractions.csproj index 3059692d3..1a41ab789 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/Arcadia.Assistant.ExternalStorages.Abstractions.csproj +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/Arcadia.Assistant.ExternalStorages.Abstractions.csproj @@ -8,6 +8,7 @@ + \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/PropertyNameParser.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/PropertyNameParser.cs index a76a6389e..5527202c1 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/PropertyNameParser.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/PropertyNameParser.cs @@ -6,12 +6,12 @@ public class PropertyNameParser { - public void EnsureExpressionIsProperty(Expression> expression) + public void EnsureExpressionIsProperty(Expression> expression) { this.GetName(expression); } - public string GetName(Expression> expression) + public string GetName(Expression> expression) { switch (expression.Body) { diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/SharepointStorageItemComparer.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/SharepointStorageItemComparer.cs index 2710c3aa0..1380a9a05 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/SharepointStorageItemComparer.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/SharepointStorageItemComparer.cs @@ -27,14 +27,14 @@ public bool Equals(StorageItem x, StorageItem y) } return - string.Equals(x.Id, y.Id) && - string.Equals(x.Title, y.Title) && - string.Equals(x.Description, y.Description) && + x.Id.Equals(y.Id) && + x.Title.Equals(y.Title) && + x.Description.Equals(y.Description) && x.StartDate.Date.Equals(y.StartDate.Date) && x.EndDate.Date.Equals(y.EndDate.Date) && - string.Equals(x.Category, y.Category) && + x.Category.Equals(y.Category) && x.AllDayEvent == y.AllDayEvent && - string.Equals(x.CalendarEventId, y.CalendarEventId); + x.CalendarEventId.Equals(y.CalendarEventId); } public int GetHashCode(StorageItem obj) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/StorageItem.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/StorageItem.cs index 30dace379..37a0932a4 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/StorageItem.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.Abstractions/StorageItem.cs @@ -1,23 +1,32 @@ namespace Arcadia.Assistant.ExternalStorages.Abstractions { using System; + using System.Text.Json.Serialization; public class StorageItem { - public string Id { get; set; } = string.Empty; + [JsonPropertyName("Id")] + public int Id { get; set; } + [JsonPropertyName("Title")] public string Title { get; set; } = string.Empty; + [JsonPropertyName("Description")] public string Description { get; set; } = string.Empty; + [JsonPropertyName("StartDate")] public DateTime StartDate { get; set; } + [JsonPropertyName("EndDate")] public DateTime EndDate { get; set; } + [JsonPropertyName("Category")] public string Category { get; set; } = string.Empty; + [JsonPropertyName("AllDayEvent")] public bool AllDayEvent { get; set; } + [JsonPropertyName("CalendarEventId")] public string CalendarEventId { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointFieldsMapper.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointFieldsMapper.cs index f27d2ffb8..b937bd8cb 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointFieldsMapper.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointFieldsMapper.cs @@ -8,5 +8,7 @@ public interface ISharepointFieldsMapper { string GetSharepointField(Expression> property); + + string? GetSharepointField(string propertyName); } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs index 46980fdb1..5e667e080 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text.Json; @@ -36,9 +37,18 @@ public static SharepointRequest Create(HttpMethod httpMethod, string url) return new SharepointRequest(httpMethod, url); } - public SharepointRequest WithAcceptHeader(string value) + public SharepointRequest WithAcceptHeader(string value, bool update = true) { - this.AddHeader(AcceptHeaderName, value); + if (update) + { + this.RemoveHeader(AcceptHeaderName); + } + + if (update || !this.headersInternal.Any(x => x.Item1 == AcceptHeaderName)) + { + this.AddHeader(AcceptHeaderName, value); + } + return this; } @@ -48,9 +58,8 @@ public SharepointRequest WithBearerAuthorizationHeader(string value) return this; } - public SharepointRequest WithContent(object content) + public SharepointRequest WithContentString(string contentString) { - var contentString = JsonSerializer.Serialize(content); this.Content = new StringContent(contentString); this.AddHeader(ContentTypeHeaderName, "application/json;odata=verbose"); @@ -59,6 +68,12 @@ public SharepointRequest WithContent(object content) return this; } + public SharepointRequest WithContent(object content) + { + var contentString = JsonSerializer.Serialize(content); + return this.WithContentString(contentString); + } + public SharepointRequest WithIfMatchHeader() { this.AddHeader(IfMatchHeaderName, "*"); @@ -115,5 +130,10 @@ private void AddHeader(string name, string value) { this.headersInternal.Add(Tuple.Create(name, value)); } + + private void RemoveHeader(string name) + { + this.headersInternal.Where(x => x.Item1 == name).ToList().ForEach(x => this.headersInternal.Remove(x)); + } } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItem.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItem.cs deleted file mode 100644 index dc82209f6..000000000 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItem.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Arcadia.Assistant.ExternalStorages.SharepointOnline.SharepointApiModels -{ - using System; - using System.Runtime.Serialization; - - [DataContract] - public class SharepointListItem - { - [DataMember] - public int Id { get; set; } - - [DataMember] - public string Title { get; set; } = string.Empty; - - [DataMember] - public string Description { get; set; } = string.Empty; - - [DataMember] - public DateTime EventDate { get; set; } - - [DataMember] - public DateTime EndDate { get; set; } - - [DataMember] - public string Category { get; set; } = string.Empty; - - [DataMember(Name = "fAllDayEvent")] - public bool AllDayEvent { get; set; } - - [DataMember] - public string CalendarEventId { get; set; } = string.Empty; - } -} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemRequest.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemRequest.cs index 07f956911..64b5c6d80 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemRequest.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemRequest.cs @@ -2,39 +2,42 @@ { using System.Runtime.Serialization; + using Abstractions; + [DataContract] - public class SharepointListItemRequest + public class SharepointListItemRequest : StorageItem { + public SharepointListItemRequest(StorageItem storage, string? type = null) + { + this.Id = storage.Id; + this.Title = storage.Title; + this.Description = storage.Description; + this.StartDate = storage.StartDate; + this.EndDate = storage.EndDate; + this.Category = storage.Category; + this.AllDayEvent = storage.AllDayEvent; + this.CalendarEventId = storage.CalendarEventId; + if (type != null) + { + this.Metadata = new MetadataRequest(type); + } + } + [DataMember(Name = "__metadata")] public MetadataRequest Metadata { get; set; } = new MetadataRequest(); - [DataMember] - public int Id { get; set; } - - [DataMember] - public string Title { get; set; } = string.Empty; - - [DataMember] - public string Description { get; set; } = string.Empty; - - [DataMember] - public string EventDate { get; set; } = string.Empty; - - [DataMember] - public string EndDate { get; set; } = string.Empty; - - [DataMember] - public string Category { get; set; } = string.Empty; - - [DataMember(Name = "fAllDayEvent")] - public bool AllDayEvent { get; set; } - - [DataMember] - public string CalendarEventId { get; set; } = string.Empty; - [DataContract] public class MetadataRequest { + public MetadataRequest() + { + } + + public MetadataRequest(string type) + { + this.Type = type; + } + [DataMember(Name = "type")] public string? Type { get; set; } } diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemsResponse.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemsResponse.cs index 2ea6c9070..0c93701b3 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemsResponse.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemsResponse.cs @@ -1,9 +1,12 @@ namespace Arcadia.Assistant.ExternalStorages.SharepointOnline.SharepointApiModels { using System.Collections.Generic; + using System.Text.Json; + using System.Text.Json.Serialization; public class SharepointListItemsResponse { - public IEnumerable? Value { get; set; } + [JsonPropertyName("value")] + public IEnumerable? Value { get; set; } } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs index 22d317554..8e8d2e188 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs @@ -9,6 +9,7 @@ using System.Net.Http.Headers; using System.Runtime.Serialization; using System.Text.Json; + using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using System.Web; @@ -185,6 +186,7 @@ private string GetFormattedPrincipal(string principalName, string? hostName, str [DataContract] private class JsonMetadataDocument { + [JsonPropertyName("endpoints")] [DataMember(Name = "endpoints")] public IEnumerable? Endpoints { get; set; } } @@ -192,9 +194,11 @@ private class JsonMetadataDocument [DataContract] private class JsonEndpoint { + [JsonPropertyName("location")] [DataMember(Name = "location")] public string? Location { get; set; } + [JsonPropertyName("protocol")] [DataMember(Name = "protocol")] public string? Protocol { get; set; } } @@ -202,6 +206,7 @@ private class JsonEndpoint [DataContract] private class OAuth2AccessTokenResponse { + [JsonPropertyName("access_token")] [DataMember(Name = "access_token")] public string? AccessToken { get; set; } } diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs index a9241e03f..a11f0aefb 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs @@ -43,13 +43,19 @@ public SharepointFieldsMapper(params SharepointFieldMapping[] fields) public string GetSharepointField(Expression> property) { var propertyName = this.GetPropertyName(property); + var fieldName = this.GetSharepointField(propertyName); - if (this.fieldsByPropertyName.TryGetValue(propertyName, out var field)) + if (fieldName == null) { - return field; + throw new ArgumentException($"Mapping for property '{propertyName}' doesn't exist"); } - throw new ArgumentException($"Mapping for property '{propertyName}' doesn't exist"); + return fieldName; + } + + public string? GetSharepointField(string propertyName) + { + return this.fieldsByPropertyName.TryGetValue(propertyName, out var field) ? field : null; } public static SharepointFieldMapping CreateMapping(Expression> property, string fieldName) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs index bf8c7d502..251bb0385 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs @@ -17,7 +17,6 @@ public class SharepointStorage : IExternalStorage { private readonly ISharepointConditionsCompiler conditionsCompiler; private readonly ISharepointOnlineConfiguration configuration; - private readonly ISharepointFieldsMapper fieldsMapper; private readonly ISharepointRequestExecutor requestExecutor; public SharepointStorage( @@ -28,31 +27,33 @@ public SharepointStorage( { this.configuration = configuration; this.requestExecutor = requestExecutor; - this.fieldsMapper = sharePointFieldsMapper; + this.FieldsMapper = sharePointFieldsMapper; this.conditionsCompiler = sharePointConditionsCompiler; + this.StorageItemJsonConvertor = new StorageItemJsonConvertor(this.FieldsMapper); } + private ISharepointFieldsMapper FieldsMapper { get; } + + private StorageItemJsonConvertor StorageItemJsonConvertor { get; } + public async Task> GetItems(string list, IEnumerable? conditions, CancellationToken cancellationToken) { var listItems = await this.GetListItems(list, conditions, cancellationToken); - return listItems - .Select(this.ListItemToStorageItem) - .ToArray(); + return listItems?.ToArray() ?? new StorageItem[0]; } public async Task AddItem(string list, StorageItem item, CancellationToken cancellationToken) { var listItemType = await this.GetListItemType(list, cancellationToken); - var requestData = this.StorageItemToListItemRequest(item, listItemType); + var requestData = this.StorageItemJsonConvertor.StorageItemToRequestJson(item, listItemType); var request = SharepointRequest - .Create(HttpMethod.Post, this.GetListItemsUrl(list)) - .WithContent(requestData); + .Create(HttpMethod.Post, this.GetListItemsUrl(list, false)) + .WithAcceptHeader("application/json;odata=verbose") + .WithContentString(requestData); - var addedItem = await this.requestExecutor.ExecuteSharepointRequest(request, cancellationToken); - - return this.ListItemToStorageItem(addedItem); + return await this.requestExecutor.ExecuteSharepointRequest(request, cancellationToken); } public async Task UpdateItem(string list, StorageItem item, CancellationToken cancellationToken) @@ -69,15 +70,16 @@ public async Task UpdateItem(string list, StorageItem item, CancellationToken ca var listItemType = await this.GetListItemType(list, cancellationToken); - var requestData = this.StorageItemToListItemRequest(item, listItemType); + var requestData = this.StorageItemJsonConvertor.StorageItemToRequestJson(item, listItemType); var request = SharepointRequest .Create(HttpMethod.Post, updateItemUrl) - .WithContent(requestData) + .WithAcceptHeader("application/json;odata=verbose") + .WithContentString(requestData) .WithIfMatchHeader() .WithXHttpMethodHeader("MERGE"); - await this.requestExecutor.ExecuteSharepointRequest(request, cancellationToken); + await this.requestExecutor.ExecuteSharepointRequest(request, cancellationToken); } public async Task DeleteItem(string list, string itemId, CancellationToken cancellationToken) @@ -94,6 +96,7 @@ public async Task DeleteItem(string list, string itemId, CancellationToken cance var request = SharepointRequest .Create(HttpMethod.Post, deleteItemUrl) + .WithAcceptHeader("application/json;odata=verbose") .WithIfMatchHeader() .WithXHttpMethodHeader("DELETE"); @@ -104,7 +107,7 @@ public void Dispose() { } - private async Task?> GetListItems( + private async Task?> GetListItems( string list, IEnumerable? conditions, CancellationToken cancellationToken) @@ -120,8 +123,7 @@ public void Dispose() var request = SharepointRequest.Create(HttpMethod.Get, itemsUrl); var response = await this.requestExecutor.ExecuteSharepointRequest(request, cancellationToken); - - return response.Value; + return response.Value.Select(this.StorageItemJsonConvertor.JsonToStorageItem); } private async Task GetListItemType(string list, CancellationToken cancellationToken) @@ -134,14 +136,14 @@ public void Dispose() private string GetListUrl(string list) { - return $"{this.configuration.ServerUrl}/_api/lists/GetByTitle('{list}')"; + return $"{this.configuration.ServerUrl}/_api/web/lists/getByTitle('{list}')"; } private string GetListItemsUrl(string list, bool includeSelectPart = true) { var baseUrl = $"{this.GetListUrl(list)}/items"; - var fieldNames = this.GetFieldNames(); + var fieldNames = this.StorageItemJsonConvertor.GetFieldNames(); var selectUrlPart = $"$select={fieldNames.Id},{fieldNames.Title},{fieldNames.Description},{fieldNames.StartDate}," + @@ -152,40 +154,7 @@ private string GetListItemsUrl(string list, bool includeSelectPart = true) : $"{baseUrl}?{selectUrlPart}"; } - private StorageItem ListItemToStorageItem(SharepointListItem item) - { - return new StorageItem - { - Id = item.Id.ToString(), - Title = item.Title, - Description = item.Description, - StartDate = item.EventDate, - EndDate = item.EndDate, - Category = item.Category, - AllDayEvent = item.AllDayEvent, - CalendarEventId = item.CalendarEventId - }; - } - - private SharepointListItemRequest StorageItemToListItemRequest(StorageItem item, string? listItemType) - { - return new SharepointListItemRequest - { - Metadata = new SharepointListItemRequest.MetadataRequest - { - Type = listItemType - }, - Title = item.Title, - Description = item.Description, - EventDate = item.StartDate.ToString("d"), - EndDate = item.EndDate.ToString("d"), - Category = item.Category, - AllDayEvent = item.AllDayEvent, - CalendarEventId = item.CalendarEventId - }; - } - - private void EnsureSingleItemReturned(SharepointListItem[] listItems) + private void EnsureSingleItemReturned(StorageItem[] listItems) { if (listItems.Length == 0) { @@ -197,27 +166,5 @@ private void EnsureSingleItemReturned(SharepointListItem[] listItems) throw new ArgumentException("More than one item was found by specified conditions"); } } - - private ( - string Id, - string Title, - string Description, - string StartDate, - string EndDate, - string Category, - string AllDayEvent, - string CalendarEventId - ) GetFieldNames() - { - var idField = this.fieldsMapper.GetSharepointField(si => si.Id); - var titleField = this.fieldsMapper.GetSharepointField(si => si.Title); - var descriptionField = this.fieldsMapper.GetSharepointField(si => si.Description); - var startDateField = this.fieldsMapper.GetSharepointField(si => si.StartDate); - var endDateField = this.fieldsMapper.GetSharepointField(si => si.EndDate); - var categoryField = this.fieldsMapper.GetSharepointField(si => si.Category); - var allDayEvent = this.fieldsMapper.GetSharepointField(si => si.AllDayEvent); - var calendarEventIdField = this.fieldsMapper.GetSharepointField(si => si.CalendarEventId); - return (idField, titleField, descriptionField, startDateField, endDateField, categoryField, allDayEvent, calendarEventIdField); - } } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/StorageItemJsonConvertor.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/StorageItemJsonConvertor.cs new file mode 100644 index 000000000..33389cf3c --- /dev/null +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/StorageItemJsonConvertor.cs @@ -0,0 +1,119 @@ +namespace Arcadia.Assistant.ExternalStorages.SharepointOnline +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.Json; + + using Abstractions; + + using Contracts; + + public class StorageItemJsonConvertor + { + public StorageItemJsonConvertor(ISharepointFieldsMapper fieldsMapper) + { + this.FieldsMapper = fieldsMapper; + } + + private ISharepointFieldsMapper FieldsMapper { get; } + + #region public interface + + public ( + string Id, + string Title, + string Description, + string StartDate, + string EndDate, + string Category, + string AllDayEvent, + string CalendarEventId + ) GetFieldNames() + { + var idField = this.FieldsMapper.GetSharepointField(si => si.Id); + var titleField = this.FieldsMapper.GetSharepointField(si => si.Title); + var descriptionField = this.FieldsMapper.GetSharepointField(si => si.Description); + var startDateField = this.FieldsMapper.GetSharepointField(si => si.StartDate); + var endDateField = this.FieldsMapper.GetSharepointField(si => si.EndDate); + var categoryField = this.FieldsMapper.GetSharepointField(si => si.Category); + var allDayEvent = this.FieldsMapper.GetSharepointField(si => si.AllDayEvent); + var calendarEventIdField = this.FieldsMapper.GetSharepointField(si => si.CalendarEventId); + return (idField, titleField, descriptionField, startDateField, endDateField, categoryField, allDayEvent, calendarEventIdField); + } + + public string StorageItemToRequestJson(StorageItem item, string? listItemType) + { + var properties = new Dictionary + { + [$"{this.FieldsMapper.GetSharepointField(x => x.Title)}"] = $"\"{item.Title}\"", + [$"{this.FieldsMapper.GetSharepointField(x => x.Description)}"] = $"\"{item.Description}\"", + [$"{this.FieldsMapper.GetSharepointField(x => x.StartDate)}"] = $"\"{item.StartDate.ToString("d")}\"", + [$"{this.FieldsMapper.GetSharepointField(x => x.EndDate)}"] = $"\"{item.EndDate.ToString("d")}\"", + [$"{this.FieldsMapper.GetSharepointField(x => x.Category)}"] = $"\"{item.Category}\"", + [$"{this.FieldsMapper.GetSharepointField(x => x.AllDayEvent)}"] = item.AllDayEvent ? "true" : "false", + [$"{this.FieldsMapper.GetSharepointField(x => x.CalendarEventId)}"] = $"\"{item.CalendarEventId}\"" + }; + + if (item.Id != 0) + { + properties.Add($"{this.FieldsMapper.GetSharepointField(x => x.Id)}", item.Id.ToString()); + } + + if (listItemType != null) + { + properties.Add("__metadata", $"{{ \"type\":\"{listItemType}\" }}"); + } + + return $"{{ {string.Join(',', properties.Keys.Select(k => $"\"{k}\":{properties[k]}"))} }}"; + } + + public StorageItem? JsonToStorageItem(JsonElement item) + { + var allDayEvent = this.GetJsonBool(item, this.FieldsMapper.GetSharepointField(x => x.AllDayEvent)); + return new StorageItem + { + Id = this.GetJsonInt(item, this.FieldsMapper.GetSharepointField(x => x.Id)), + Title = this.GetJsonString(item, this.FieldsMapper.GetSharepointField(x => x.Title)), + Description = this.GetJsonString(item, this.FieldsMapper.GetSharepointField(x => x.Description)), + StartDate = this.GetJsonDate(item, this.FieldsMapper.GetSharepointField(x => x.StartDate), allDayEvent, DateTime.MinValue), + EndDate = this.GetJsonDate(item, this.FieldsMapper.GetSharepointField(x => x.EndDate), allDayEvent, DateTime.UtcNow), + Category = this.GetJsonString(item, this.FieldsMapper.GetSharepointField(x => x.Category)), + AllDayEvent = allDayEvent, + CalendarEventId = this.GetJsonString(item, this.FieldsMapper.GetSharepointField(x => x.CalendarEventId)) + }; + } + + #endregion + + #region private members + + private string GetJsonString(JsonElement item, string propName, string defaultValue = "") + { + return item.TryGetProperty(propName, out var prop) && prop.ValueKind != JsonValueKind.Null ? prop.GetString() : defaultValue; + } + + private int GetJsonInt(JsonElement item, string propName, int defaultValue = 0) + { + return item.TryGetProperty(propName, out var prop) && prop.ValueKind != JsonValueKind.Null && prop.TryGetInt32(out var val) ? val : defaultValue; + } + + private bool GetJsonBool(JsonElement item, string propName, bool defaultValue = false) + { + return item.TryGetProperty(propName, out var prop) && prop.ValueKind != JsonValueKind.Null ? prop.GetBoolean() : defaultValue; + } + + private DateTime GetJsonDate(JsonElement item, string propName, bool dateOnly, DateTime defaultValue) + { + var date = item.TryGetProperty(propName, out var prop) && prop.ValueKind != JsonValueKind.Null && prop.TryGetDateTime(out var val) ? val : defaultValue; + return dateOnly ? new DateTime(date.Year, date.Month, date.Day) : date; + } + + private DateTime GetJsonDate(JsonElement item, string propName, DateTime defaultValue) + { + return item.TryGetProperty(propName, out var prop) && prop.ValueKind != JsonValueKind.Null && prop.TryGetDateTime(out var val) ? val : defaultValue; + } + + #endregion + } +} \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointItemSynchronization.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointItemSynchronization.cs index 26c880b5b..89e35f846 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointItemSynchronization.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointItemSynchronization.cs @@ -47,7 +47,7 @@ public async Task SynchronizeItem(string calendar, EmployeeMetadata[] department { await this.ExternalStorage.DeleteItem( calendar, - item.Id); + item.Id.ToString()); } } catch (Exception e) @@ -81,7 +81,7 @@ await this.ExternalStorage.DeleteItem( private async Task UpsertItem(string eventId, string calendar, T item, EmployeeMetadata employeeMetadata, IExternalStorage externalStorage, CancellationToken cancellationToken) { var datesPeriod = this.GetItemDatePeriod(item); - var upsertItem = this.CalendarEventToStorageItem(eventId, CalendarEventTypes.Sickleave, datesPeriod, employeeMetadata); + var upsertItem = this.CalendarEventToStorageItem(eventId, this.ItemEventType, datesPeriod, employeeMetadata); await this.UpsertStorageItem(eventId, calendar, datesPeriod, upsertItem, externalStorage, cancellationToken); } @@ -104,26 +104,26 @@ await externalStorage.AddItem( upsertItem, cancellationToken); } - else if (!this.sharepointStorageItemComparer.Equals(upsertItem, storageItem)) + else { upsertItem.Id = storageItem.Id; - await externalStorage.UpdateItem( - calendar, - upsertItem, - cancellationToken); + if (!this.sharepointStorageItemComparer.Equals(upsertItem, storageItem)) + { + await externalStorage.UpdateItem( + calendar, + upsertItem, + cancellationToken); + } } } private StorageItem CalendarEventToStorageItem(string eventId, string calendarEventType, DatesPeriod period, EmployeeMetadata employeeMetadata) { var totalHours = period.FinishWorkingHour - period.StartWorkingHour; - - var longEventsTitle = $"{employeeMetadata.Name} ({calendarEventType})"; - var shortEventsTitle = $"{employeeMetadata.Name} ({calendarEventType}: {totalHours} hours)"; - - var title = calendarEventType == CalendarEventTypes.Vacation || calendarEventType == CalendarEventTypes.Sickleave - ? longEventsTitle - : shortEventsTitle; + var longEvent = calendarEventType == CalendarEventTypes.Vacation || calendarEventType == CalendarEventTypes.Sickleave; + var title = longEvent + ? $"{employeeMetadata.Name} ({calendarEventType})" + : $"{employeeMetadata.Name} ({calendarEventType}: {totalHours} hours)"; var storageItem = new StorageItem { @@ -131,7 +131,7 @@ private StorageItem CalendarEventToStorageItem(string eventId, string calendarEv StartDate = period.StartDate, EndDate = period.EndDate, Category = calendarEventType, - AllDayEvent = true, + AllDayEvent = longEvent, CalendarEventId = eventId }; From 87ece52ebb8ecdcf89e94a83f857d0bcfa8ce5a7 Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Tue, 11 Feb 2020 17:13:41 +0300 Subject: [PATCH 08/14] Fixed merge result --- .../Program.cs | 2 +- .../Program.cs | 2 +- .../Arcadia.Assistant.Avatars/Program.cs | 2 +- .../Arcadia.Assistant.Employees/Program.cs | 2 +- .../Arcadia.Assistant.Inbox/Program.cs | 2 +- .../LoggerRegistrationExtensions.cs | 21 ++++++++----- .../LoggerSettings.cs | 4 +-- .../Arcadia.Assistant.MobileBuild/Program.cs | 2 +- .../Arcadia.Assistant.Organization/Program.cs | 2 +- .../Program.cs | 2 +- .../Arcadia.Assistant.Permissions/Program.cs | 2 +- .../ApplicationManifest.xml | 31 ++++++++++--------- .../Arcadia.Assistant.Sharepoint.csproj | 1 + .../Arcadia.Assistant.Sharepoint/Program.cs | 18 ++++++----- .../Arcadia.Assistant.SickLeaves/Program.cs | 2 +- .../Program.cs | 2 +- .../Arcadia.Assistant.Vacations/Program.cs | 2 +- .../Program.cs | 2 +- .../Program.cs | 2 +- 19 files changed, 58 insertions(+), 45 deletions(-) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.AppCenterBuilds/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.AppCenterBuilds/Program.cs index fab8db426..0c876f5e0 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.AppCenterBuilds/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.AppCenterBuilds/Program.cs @@ -46,7 +46,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.TryResolve(out ILogger val) ? val : null; logger?.LogInformation($"Service type '{typeof(AppCenterBuilds).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars.Manager/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars.Manager/Program.cs index 3be7adea2..d58e96e31 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars.Manager/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars.Manager/Program.cs @@ -48,7 +48,7 @@ private static void Main() builder.RegisterStatelessService("Arcadia.Assistant.Avatars.ManagerType"); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.TryResolve(out ILogger val) ? val : null; logger?.LogInformation($"Service type '{typeof(Manager).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars/Program.cs index 9ef60f9a0..4b08e0505 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars/Program.cs @@ -30,7 +30,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.Resolve(); + logger = container.TryResolve(out ILogger val) ? val : null; Thread.Sleep(Timeout.Infinite); } catch (Exception e) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Employees/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Employees/Program.cs index 3752428e2..3b8e4085b 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Employees/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Employees/Program.cs @@ -39,7 +39,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.TryResolve(out ILogger val) ? val : null; logger?.LogInformation($"Service type '{typeof(Employees).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Inbox/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Inbox/Program.cs index 011d18996..26fa18689 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Inbox/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Inbox/Program.cs @@ -30,7 +30,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.TryResolve(out ILogger val) ? val : null; logger?.LogInformation($"Service type '{typeof(Inbox).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); Thread.Sleep(Timeout.Infinite); } diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Logging/LoggerRegistrationExtensions.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Logging/LoggerRegistrationExtensions.cs index 6531b42cf..22f7a06ad 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Logging/LoggerRegistrationExtensions.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Logging/LoggerRegistrationExtensions.cs @@ -1,11 +1,14 @@ -using Autofac; -using Microsoft.Extensions.Logging; -using ServiceFabric.Logging; -using System; -using System.Fabric; - -namespace Arcadia.Assistant.Logging +namespace Arcadia.Assistant.Logging { + using System; + using System.Fabric; + + using Autofac; + + using Microsoft.Extensions.Logging; + + using ServiceFabric.Logging; + public static class LoggerRegistrationExtensions { public static void RegisterServiceLogging( @@ -13,7 +16,9 @@ public static void RegisterServiceLogging( LoggerSettings loggerSettings) { if (builder == null) + { throw new ArgumentNullException(nameof(builder)); + } builder.Register((x, p) => { @@ -45,7 +50,9 @@ public static void RegisterServiceLogging( ServiceContext context) { if (builder == null) + { throw new ArgumentNullException(nameof(builder)); + } var loggerFactoryBuilder = new LoggerFactoryBuilder(context); var loggerFactory = loggerFactoryBuilder.CreateLoggerFactory(loggerSettings.ApplicationInsightsKey); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Logging/LoggerSettings.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Logging/LoggerSettings.cs index 28bd7770c..bc9017516 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Logging/LoggerSettings.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Logging/LoggerSettings.cs @@ -20,11 +20,11 @@ public LoggerSettings(string appKey) this.ApplicationInsightsKey = appKey; } + public string ApplicationInsightsKey { get; set; } = string.Empty; + public static LoggerSettings FromInsightsKey(string applicationInsightsKey) { return new LoggerSettings(applicationInsightsKey); } - - public string ApplicationInsightsKey { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.MobileBuild/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.MobileBuild/Program.cs index 0ce34b730..e9d92afed 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.MobileBuild/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.MobileBuild/Program.cs @@ -29,7 +29,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.TryResolve(out ILogger val) ? val : null; Thread.Sleep(Timeout.Infinite); } catch (Exception e) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Organization/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Organization/Program.cs index 53ed5c59c..5220f120f 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Organization/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Organization/Program.cs @@ -43,7 +43,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.TryResolve(out ILogger val) ? val : null; logger?.LogInformation($"Service type '{typeof(Organization).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.PendingActions/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.PendingActions/Program.cs index f0c24dab8..a95b21bde 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.PendingActions/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.PendingActions/Program.cs @@ -46,7 +46,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.TryResolve(out ILogger val) ? val : null; logger?.LogInformation($"Service type '{typeof(PendingActions).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Permissions/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Permissions/Program.cs index 22effe7f8..9f29ed86a 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Permissions/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Permissions/Program.cs @@ -39,7 +39,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.TryResolve(out ILogger val) ? val : null; logger?.LogInformation($"Service type '{typeof(Permissions).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.SF/ApplicationPackageRoot/ApplicationManifest.xml b/server2/Arcadia.Assistant/Arcadia.Assistant.SF/ApplicationPackageRoot/ApplicationManifest.xml index 33cc93761..b8b34e82d 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.SF/ApplicationPackageRoot/ApplicationManifest.xml +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.SF/ApplicationPackageRoot/ApplicationManifest.xml @@ -30,14 +30,12 @@ - - + - @@ -78,13 +76,16 @@
+
+ +
- +
@@ -107,7 +108,7 @@
-
+
@@ -134,7 +135,7 @@
-
+
@@ -149,7 +150,7 @@
-
+
@@ -167,7 +168,7 @@
-
+
@@ -185,7 +186,7 @@
-
+
@@ -206,7 +207,7 @@ - +
@@ -236,7 +237,7 @@
-
+
@@ -251,7 +252,7 @@
-
+
@@ -260,7 +261,7 @@ - +
@@ -278,7 +279,7 @@
-
+
@@ -302,7 +303,7 @@
-
+
diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj index 986c111a4..1c2fc030c 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Arcadia.Assistant.Sharepoint.csproj @@ -21,6 +21,7 @@ + diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs index cb090842b..9203a41fb 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs @@ -16,7 +16,10 @@ namespace Arcadia.Assistant.Sharepoint using ExternalStorages.SharepointOnline; using ExternalStorages.SharepointOnline.Contracts; + using Logging; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; using Microsoft.ServiceFabric.Services.Remoting.Client; using Models; @@ -36,6 +39,7 @@ internal static class Program ///
private static void Main() { + ILogger? logger = null; try { var configurationPackage = FabricRuntime.GetActivationContext().GetConfigurationPackageObject("Config"); @@ -78,19 +82,19 @@ private static void Main() builder.RegisterModule(); builder.RegisterModule(); builder.RegisterModule(); + builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); builder.Populate(services); - using (builder.Build()) - { - ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(Sharepoint).Name); - - // Prevents this host process from terminating so services keep running. - Thread.Sleep(Timeout.Infinite); - } + using var container = builder.Build(); + logger = container.TryResolve(out ILogger log) ? log : null; + logger?.LogInformation($"Service type '{typeof(Sharepoint).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); + // Prevents this host process from terminating so services keep running. + Thread.Sleep(Timeout.Infinite); } catch (Exception e) { ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString()); + logger?.LogCritical(e, e.Message); throw; } } diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/Program.cs index fdbc04123..3d5feb164 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/Program.cs @@ -45,7 +45,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.TryResolve(out ILogger val) ? val : null; logger?.LogInformation($"Service type '{typeof(SickLeaves).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.UserPreferences/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.UserPreferences/Program.cs index f7a9b4832..0c040375e 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.UserPreferences/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.UserPreferences/Program.cs @@ -28,7 +28,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.TryResolve(out ILogger val) ? val : null; Thread.Sleep(Timeout.Infinite); } catch (Exception e) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Vacations/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Vacations/Program.cs index 5820e0854..0a1142dec 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Vacations/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Vacations/Program.cs @@ -45,7 +45,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.TryResolve(out ILogger val) ? val : null; logger?.LogInformation($"Service type '{typeof(Vacations).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.VacationsCredit/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.VacationsCredit/Program.cs index 1f7d28cbb..e0bdd492b 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.VacationsCredit/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.VacationsCredit/Program.cs @@ -46,7 +46,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.TryResolve(out ILogger val) ? val : null; logger?.LogInformation($"Service type '{typeof(VacationsCredit).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/Program.cs index 9d9483dda..ff0561e0c 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/Program.cs @@ -46,7 +46,7 @@ private static void Main() builder.Register(c => new WorkHoursCreditContext(c.Resolve>())).AsSelf(); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.TryResolve(out ILogger val) ? val : null; logger?.LogInformation($"Service type '{typeof(WorkHoursCredit).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); From 5c87961d4b12500cf1ad306a30b8a0665312589a Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Tue, 11 Feb 2020 20:41:13 +0300 Subject: [PATCH 09/14] Add missed configuration --- .../PackageRoot/Config/Settings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml index 7fbecade1..be0876c59 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/PackageRoot/Config/Settings.xml @@ -14,4 +14,7 @@
+
+ +
\ No newline at end of file From 2a9789e6c351fa6162a9b78cf418bc371997ff58 Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Wed, 12 Feb 2020 22:38:58 +0300 Subject: [PATCH 10/14] Fix PR comments --- .../Program.cs | 2 +- .../Program.cs | 2 +- .../Arcadia.Assistant.Avatars/Program.cs | 2 +- .../Arcadia.Assistant.Employees/Program.cs | 2 +- .../Contracts/ISharepointFieldsMapper.cs | 2 - .../Contracts/SharepointRequest.cs | 2 +- .../SharepointListItemRequest.cs | 8 ++-- .../SharepointAuthTokenService.cs | 8 ---- .../SharepointFieldsMapper.cs | 2 +- .../Arcadia.Assistant.Inbox/Program.cs | 2 +- .../Arcadia.Assistant.MobileBuild/Program.cs | 2 +- .../Arcadia.Assistant.Organization/Program.cs | 2 +- .../Program.cs | 2 +- .../Arcadia.Assistant.Permissions/Program.cs | 2 +- .../Arcadia.Assistant.Sharepoint/Program.cs | 2 +- .../SharepointItemSynchronization.cs | 4 +- .../SharepointSynchronizator.cs | 43 +++++++++---------- .../Arcadia.Assistant.SickLeaves/Program.cs | 2 +- .../Program.cs | 2 +- .../Arcadia.Assistant.Vacations/Program.cs | 2 +- .../Program.cs | 2 +- .../Program.cs | 2 +- .../WorkHoursCredit.cs | 3 +- 23 files changed, 45 insertions(+), 57 deletions(-) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.AppCenterBuilds/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.AppCenterBuilds/Program.cs index 0c876f5e0..19cd66d7a 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.AppCenterBuilds/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.AppCenterBuilds/Program.cs @@ -46,7 +46,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); logger?.LogInformation($"Service type '{typeof(AppCenterBuilds).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars.Manager/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars.Manager/Program.cs index d58e96e31..dde91aeb9 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars.Manager/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars.Manager/Program.cs @@ -48,7 +48,7 @@ private static void Main() builder.RegisterStatelessService("Arcadia.Assistant.Avatars.ManagerType"); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); logger?.LogInformation($"Service type '{typeof(Manager).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars/Program.cs index 4b08e0505..c3597f9a3 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Avatars/Program.cs @@ -30,7 +30,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); Thread.Sleep(Timeout.Infinite); } catch (Exception e) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Employees/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Employees/Program.cs index 3b8e4085b..e121305dd 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Employees/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Employees/Program.cs @@ -39,7 +39,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); logger?.LogInformation($"Service type '{typeof(Employees).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointFieldsMapper.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointFieldsMapper.cs index b937bd8cb..f27d2ffb8 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointFieldsMapper.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/ISharepointFieldsMapper.cs @@ -8,7 +8,5 @@ public interface ISharepointFieldsMapper { string GetSharepointField(Expression> property); - - string? GetSharepointField(string propertyName); } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs index 5e667e080..ba8703a4c 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs @@ -133,7 +133,7 @@ private void AddHeader(string name, string value) private void RemoveHeader(string name) { - this.headersInternal.Where(x => x.Item1 == name).ToList().ForEach(x => this.headersInternal.Remove(x)); + this.headersInternal.RemoveAll(x => x.Item1 == name); } } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemRequest.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemRequest.cs index 64b5c6d80..7ec79a63f 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemRequest.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointApiModels/SharepointListItemRequest.cs @@ -1,10 +1,9 @@ namespace Arcadia.Assistant.ExternalStorages.SharepointOnline.SharepointApiModels { - using System.Runtime.Serialization; + using System.Text.Json.Serialization; using Abstractions; - [DataContract] public class SharepointListItemRequest : StorageItem { public SharepointListItemRequest(StorageItem storage, string? type = null) @@ -23,10 +22,9 @@ public SharepointListItemRequest(StorageItem storage, string? type = null) } } - [DataMember(Name = "__metadata")] + [JsonPropertyName("__metadata")] public MetadataRequest Metadata { get; set; } = new MetadataRequest(); - [DataContract] public class MetadataRequest { public MetadataRequest() @@ -38,7 +36,7 @@ public MetadataRequest(string type) this.Type = type; } - [DataMember(Name = "type")] + [JsonPropertyName("type")] public string? Type { get; set; } } } diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs index 8e8d2e188..be630412b 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointAuthTokenService.cs @@ -7,7 +7,6 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; - using System.Runtime.Serialization; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; @@ -183,31 +182,24 @@ private string GetFormattedPrincipal(string principalName, string? hostName, str : string.Format(CultureInfo.InvariantCulture, "{0}/{1}@{2}", principalName, hostName, realm); } - [DataContract] private class JsonMetadataDocument { [JsonPropertyName("endpoints")] - [DataMember(Name = "endpoints")] public IEnumerable? Endpoints { get; set; } } - [DataContract] private class JsonEndpoint { [JsonPropertyName("location")] - [DataMember(Name = "location")] public string? Location { get; set; } [JsonPropertyName("protocol")] - [DataMember(Name = "protocol")] public string? Protocol { get; set; } } - [DataContract] private class OAuth2AccessTokenResponse { [JsonPropertyName("access_token")] - [DataMember(Name = "access_token")] public string? AccessToken { get; set; } } } diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs index a11f0aefb..306b3478a 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs @@ -53,7 +53,7 @@ public string GetSharepointField(Expression> property) return fieldName; } - public string? GetSharepointField(string propertyName) + private string? GetSharepointField(string propertyName) { return this.fieldsByPropertyName.TryGetValue(propertyName, out var field) ? field : null; } diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Inbox/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Inbox/Program.cs index 26fa18689..89bfef348 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Inbox/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Inbox/Program.cs @@ -30,7 +30,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); logger?.LogInformation($"Service type '{typeof(Inbox).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); Thread.Sleep(Timeout.Infinite); } diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.MobileBuild/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.MobileBuild/Program.cs index e9d92afed..ddc6239e7 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.MobileBuild/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.MobileBuild/Program.cs @@ -29,7 +29,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); Thread.Sleep(Timeout.Infinite); } catch (Exception e) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Organization/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Organization/Program.cs index 5220f120f..664066fa4 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Organization/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Organization/Program.cs @@ -43,7 +43,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); logger?.LogInformation($"Service type '{typeof(Organization).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.PendingActions/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.PendingActions/Program.cs index a95b21bde..690abdc26 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.PendingActions/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.PendingActions/Program.cs @@ -46,7 +46,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); logger?.LogInformation($"Service type '{typeof(PendingActions).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Permissions/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Permissions/Program.cs index 9f29ed86a..f91001a2f 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Permissions/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Permissions/Program.cs @@ -39,7 +39,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); logger?.LogInformation($"Service type '{typeof(Permissions).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs index 9203a41fb..cd87dfc4e 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Program.cs @@ -86,7 +86,7 @@ private static void Main() builder.Populate(services); using var container = builder.Build(); - logger = container.TryResolve(out ILogger log) ? log : null; + logger = container.ResolveOptional(); logger?.LogInformation($"Service type '{typeof(Sharepoint).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointItemSynchronization.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointItemSynchronization.cs index 89e35f846..aa51b9d88 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointItemSynchronization.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointItemSynchronization.cs @@ -28,7 +28,7 @@ protected SharepointItemSynchronization(IExternalStorage externalStorage, ILogge #region public interface - public async Task SynchronizeItem(string calendar, EmployeeMetadata[] departmentEmployes, Dictionary values, IEnumerable storageItemsList, CancellationToken cancellationToken) + public async Task SynchronizeItems(string calendar, EmployeeMetadata[] departmentEmployees, Dictionary values, IEnumerable storageItemsList, CancellationToken cancellationToken) { try { @@ -38,7 +38,7 @@ public async Task SynchronizeItem(string calendar, EmployeeMetadata[] department // insert or update items foreach (var workHourEventId in values.Keys) { - var employeeMetadata = departmentEmployes.Single(x => x.EmployeeId == this.GetItemEmployeeId(values[workHourEventId])); + var employeeMetadata = departmentEmployees.Single(x => x.EmployeeId == this.GetItemEmployeeId(values[workHourEventId])); await this.UpsertItem(workHourEventId, calendar, values[workHourEventId], employeeMetadata, this.ExternalStorage, cancellationToken); } diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs index d3293c4e8..b46deb01b 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs @@ -34,7 +34,7 @@ public SharepointSynchronizator(ISickLeaves sickLeaves, IVacations vacations, IW #endregion - #region publci interface + #region public interface public async Task Synchronize(IEmployees employees, IEnumerable departments, IExternalStorage storage, CancellationToken cancellationToken) { @@ -42,36 +42,35 @@ public async Task Synchronize(IEmployees employees, IEnumerable departme var workoutsSync = new EmployeeWorkoutsSynchronization(storage, this.logger); var sickLeavesSync = new EmployeeSickLeavesSynchronization(storage, this.logger); - var sharepointCalendars = departments.Distinct().ToDictionary(x => x, + var sharepointItemsByCalendars = departments.Distinct().ToDictionary(x => x, dId => this.GetSharepointCalendarsByDepartment(dId).ToDictionary(x => x, async cal => await this.GetAllSharepointItemsForCalendar(storage, cal))); foreach (var departmentId in departments) { - var departmentEmployes = await employees.FindEmployeesAsync(EmployeesQuery.Create().ForDepartment(departmentId), cancellationToken); - var employeeIds = departmentEmployes.Select(x => x.EmployeeId).ToArray(); + var departmentEmployees = await employees.FindEmployeesAsync(EmployeesQuery.Create().ForDepartment(departmentId), cancellationToken); + var employeeIds = departmentEmployees.Select(x => x.EmployeeId).ToArray(); - var employeeVacations = await this.vacations.GetCalendarEventsByEmployeeAsync(employeeIds, cancellationToken); - var employeeVacationValues = employeeVacations.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.VacationId, CalendarEventTypes.Vacation), x => x); + var employeeVacationsTask = this.vacations.GetCalendarEventsByEmployeeAsync(employeeIds, cancellationToken); + var employeeWorkoutsTask = this.workouts.GetCalendarEventsByEmployeeMapAsync(employeeIds, cancellationToken); + var employeeSickLeavesTask = this.sickLeaves.GetCalendarEventsByEmployeeMapAsync(employeeIds, cancellationToken); + await Task.WhenAll(employeeVacationsTask, employeeWorkoutsTask, employeeSickLeavesTask); - var employeeWorkouts = await this.workouts.GetCalendarEventsByEmployeeMapAsync(employeeIds, cancellationToken); - var employeeWorkoutsValues = employeeWorkouts.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.ChangeId, CalendarEventTypes.Workout), x => x); + var employeeVacationValues = employeeVacationsTask.Result.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.VacationId, CalendarEventTypes.Vacation), x => x); + var employeeWorkoutsValues = employeeWorkoutsTask.Result.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.ChangeId, CalendarEventTypes.Workout), x => x); + var employeeSickLeavesValues = employeeSickLeavesTask.Result.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.SickLeaveId, CalendarEventTypes.Sickleave), x => x); - var employeeSickLeaves = await this.sickLeaves.GetCalendarEventsByEmployeeMapAsync(employeeIds, cancellationToken); - var employeeSickLeavesValues = employeeSickLeaves.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.SickLeaveId, CalendarEventTypes.Sickleave), x => x); - - foreach (var calendar in this.GetSharepointCalendarsByDepartment(departmentId)) + foreach (var calendar in sharepointItemsByCalendars[departmentId].Keys) { - var storageItems = await sharepointCalendars[departmentId][calendar]; - - // synchronize vacations for selected department - await vacationsSync.SynchronizeItem(calendar, departmentEmployes, employeeVacationValues, storageItems, cancellationToken); - - // synchronize workouts for selected department - await workoutsSync.SynchronizeItem(calendar, departmentEmployes, employeeWorkoutsValues, storageItems, cancellationToken); - - // synchronize vacations for selected department - await sickLeavesSync.SynchronizeItem(calendar, departmentEmployes, employeeSickLeavesValues, storageItems, cancellationToken); + var storageItems = await sharepointItemsByCalendars[departmentId][calendar]; + + await Task.WhenAll( + // synchronize vacations for selected department + vacationsSync.SynchronizeItems(calendar, departmentEmployees, employeeVacationValues, storageItems, cancellationToken), + // synchronize workouts for selected department + workoutsSync.SynchronizeItems(calendar, departmentEmployees, employeeWorkoutsValues, storageItems, cancellationToken), + // synchronize vacations for selected department + sickLeavesSync.SynchronizeItems(calendar, departmentEmployees, employeeSickLeavesValues, storageItems, cancellationToken)); } } } diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/Program.cs index 3d5feb164..1e807fc8b 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.SickLeaves/Program.cs @@ -45,7 +45,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); logger?.LogInformation($"Service type '{typeof(SickLeaves).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.UserPreferences/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.UserPreferences/Program.cs index 0c040375e..8d12743f0 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.UserPreferences/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.UserPreferences/Program.cs @@ -28,7 +28,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); Thread.Sleep(Timeout.Infinite); } catch (Exception e) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Vacations/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Vacations/Program.cs index 0a1142dec..71498d1e8 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Vacations/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Vacations/Program.cs @@ -45,7 +45,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); logger?.LogInformation($"Service type '{typeof(Vacations).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.VacationsCredit/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.VacationsCredit/Program.cs index e0bdd492b..41b38e9f2 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.VacationsCredit/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.VacationsCredit/Program.cs @@ -46,7 +46,7 @@ private static void Main() builder.RegisterServiceLogging(new LoggerSettings(configurationPackage.Settings.Sections["Logging"])); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); logger?.LogInformation($"Service type '{typeof(VacationsCredit).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/Program.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/Program.cs index ff0561e0c..9967642ad 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/Program.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/Program.cs @@ -46,7 +46,7 @@ private static void Main() builder.Register(c => new WorkHoursCreditContext(c.Resolve>())).AsSelf(); using var container = builder.Build(); - logger = container.TryResolve(out ILogger val) ? val : null; + logger = container.ResolveOptional(); logger?.LogInformation($"Service type '{typeof(WorkHoursCredit).Name}' registered. Process: {Process.GetCurrentProcess().Id}."); // Prevents this host process from terminating so services keep running. Thread.Sleep(Timeout.Infinite); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs index 476e848d4..315d2f03b 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs @@ -86,7 +86,8 @@ public async Task> GetCalendarEventsBy var events = await this.QueryCalendarEvents(ctx.Value.ChangeRequests, x => employeeIds.Any(id => id.Value == x.EmployeeId)) .ToArrayAsync(cancellationToken); - return events.GroupBy(x=> x.EmployeeId).ToDictionary(x => x.Key, x=> x.ToArray()); + return events.GroupBy(x=> x.EmployeeId) + .ToDictionary(x => x.Key, x=> x.ToArray()); } public async Task GetCalendarEventAsync(EmployeeId employeeId, Guid eventId, CancellationToken cancellationToken) From 768af1c5312f021795ad742dbcf1a1cb92d333d2 Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Thu, 13 Feb 2020 09:46:26 +0300 Subject: [PATCH 11/14] Fixed PR comments --- .../Sharepoint.cs | 2 +- .../SharepointSynchronizator.cs | 81 ++++++++++--------- .../WorkHoursCredit.cs | 4 +- 3 files changed, 46 insertions(+), 41 deletions(-) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs index 55ca6e628..2e38d6d46 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs @@ -103,7 +103,7 @@ private async Task> GetDepartmentsList(CancellationToken can { if (this.departmentsCalendarsSettings.DepartmentsCalendars != null && this.departmentsCalendarsSettings.DepartmentsCalendars.Any()) { - return this.departmentsCalendarsSettings.DepartmentsCalendars.Select(x => x.DepartmentId); + return this.departmentsCalendarsSettings.DepartmentsCalendars.Select(x => x.DepartmentId).Distinct(); } return (await this.organizations.GetDepartmentsAsync(cancellationToken)).Select(x => x.DepartmentId.Value.ToString()); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs index b46deb01b..dbafcf75d 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs @@ -21,6 +21,16 @@ public sealed class SharepointSynchronizator { + #region variables + + private readonly ISickLeaves sickLeaves; + private readonly IVacations vacations; + private readonly IWorkHoursCredit workouts; + private readonly ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings; + private readonly ILogger? logger; + + #endregion + #region ctor public SharepointSynchronizator(ISickLeaves sickLeaves, IVacations vacations, IWorkHoursCredit workouts, ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings, ILogger? logger) @@ -42,11 +52,8 @@ public async Task Synchronize(IEmployees employees, IEnumerable departme var workoutsSync = new EmployeeWorkoutsSynchronization(storage, this.logger); var sickLeavesSync = new EmployeeSickLeavesSynchronization(storage, this.logger); - var sharepointItemsByCalendars = departments.Distinct().ToDictionary(x => x, - dId => this.GetSharepointCalendarsByDepartment(dId).ToDictionary(x => x, - async cal => await this.GetAllSharepointItemsForCalendar(storage, cal))); - - foreach (var departmentId in departments) + var storageItemsCache = new Dictionary>(); + foreach (var departmentId in departments.Distinct()) { var departmentEmployees = await employees.FindEmployeesAsync(EmployeesQuery.Create().ForDepartment(departmentId), cancellationToken); var employeeIds = departmentEmployees.Select(x => x.EmployeeId).ToArray(); @@ -56,27 +63,50 @@ public async Task Synchronize(IEmployees employees, IEnumerable departme var employeeSickLeavesTask = this.sickLeaves.GetCalendarEventsByEmployeeMapAsync(employeeIds, cancellationToken); await Task.WhenAll(employeeVacationsTask, employeeWorkoutsTask, employeeSickLeavesTask); - var employeeVacationValues = employeeVacationsTask.Result.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.VacationId, CalendarEventTypes.Vacation), x => x); - var employeeWorkoutsValues = employeeWorkoutsTask.Result.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.ChangeId, CalendarEventTypes.Workout), x => x); - var employeeSickLeavesValues = employeeSickLeavesTask.Result.Values.SelectMany(x => x).ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.SickLeaveId, CalendarEventTypes.Sickleave), x => x); + var employeeVacationValues = employeeVacationsTask.Result.Values.SelectMany(x => x) + .ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.VacationId, CalendarEventTypes.Vacation), x => x); + var employeeWorkoutsValues = employeeWorkoutsTask.Result.Values.SelectMany(x => x) + .ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.ChangeId, CalendarEventTypes.Workout), x => x); + var employeeSickLeavesValues = employeeSickLeavesTask.Result.Values.SelectMany(x => x) + .ToDictionary(x => CspCalendarEventIdParser.GetCalendarEventIdFromCspId(x.SickLeaveId, CalendarEventTypes.Sickleave), x => x); - foreach (var calendar in sharepointItemsByCalendars[departmentId].Keys) + foreach (var calendar in this.GetSharepointCalendarsByDepartment(departmentId)) { - var storageItems = await sharepointItemsByCalendars[departmentId][calendar]; + if (!storageItemsCache.ContainsKey(calendar)) + { + storageItemsCache.Add(calendar, await this.GetAllSharepointItemsForCalendar(storage, calendar)); + } await Task.WhenAll( // synchronize vacations for selected department - vacationsSync.SynchronizeItems(calendar, departmentEmployees, employeeVacationValues, storageItems, cancellationToken), + vacationsSync.SynchronizeItems(calendar, departmentEmployees, employeeVacationValues, storageItemsCache[calendar], cancellationToken), // synchronize workouts for selected department - workoutsSync.SynchronizeItems(calendar, departmentEmployees, employeeWorkoutsValues, storageItems, cancellationToken), + workoutsSync.SynchronizeItems(calendar, departmentEmployees, employeeWorkoutsValues, storageItemsCache[calendar], cancellationToken), // synchronize vacations for selected department - sickLeavesSync.SynchronizeItems(calendar, departmentEmployees, employeeSickLeavesValues, storageItems, cancellationToken)); + sickLeavesSync.SynchronizeItems(calendar, departmentEmployees, employeeSickLeavesValues, storageItemsCache[calendar], cancellationToken)); } } } #endregion + #region private + + private async Task> GetAllSharepointItemsForCalendar(IExternalStorage externalStorage, string calendar) + { + return await externalStorage.GetItems(calendar); + } + + private IEnumerable GetSharepointCalendarsByDepartment(string departmentId) + { + return this.departmentsCalendarsSettings.DepartmentsCalendars + .Where(x => x.DepartmentId == departmentId) + .Select(x => x.Calendar) + .Distinct(); + } + + #endregion + #region internal class private class EmployeeVacationsSynchronization : SharepointItemSynchronization @@ -141,30 +171,5 @@ protected override EmployeeId GetItemEmployeeId(SickLeaveDescription item) #endregion - #region variables - - private readonly ISickLeaves sickLeaves; - private readonly IVacations vacations; - private readonly IWorkHoursCredit workouts; - private readonly ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings; - private readonly ILogger? logger; - - #endregion - - #region private - - private async Task> GetAllSharepointItemsForCalendar(IExternalStorage externalStorage, string calendar) - { - return await externalStorage.GetItems(calendar); - } - - private IEnumerable GetSharepointCalendarsByDepartment(string departmentId) - { - return this.departmentsCalendarsSettings.DepartmentsCalendars - .Where(x => x.DepartmentId == departmentId) - .Select(x => x.Calendar); - } - - #endregion } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs index 315d2f03b..0faa177aa 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.WorkHoursCredit/WorkHoursCredit.cs @@ -86,8 +86,8 @@ public async Task> GetCalendarEventsBy var events = await this.QueryCalendarEvents(ctx.Value.ChangeRequests, x => employeeIds.Any(id => id.Value == x.EmployeeId)) .ToArrayAsync(cancellationToken); - return events.GroupBy(x=> x.EmployeeId) - .ToDictionary(x => x.Key, x=> x.ToArray()); + return events.GroupBy(x => x.EmployeeId) + .ToDictionary(x => x.Key, x => x.ToArray()); } public async Task GetCalendarEventAsync(EmployeeId employeeId, Guid eventId, CancellationToken cancellationToken) From 6d12fe239093f474a833530d92bc2e2ed3e7dbe5 Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Thu, 13 Feb 2020 12:15:56 +0300 Subject: [PATCH 12/14] Fix by PR comment --- .../SharepointFieldsMapper.cs | 10 +++++----- .../SharepointDepartmentsCalendarsSettings.cs | 13 ++----------- .../Models/SharepointSynchronizationSettings.cs | 4 +++- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs index 306b3478a..7f6976dd1 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointFieldsMapper.cs @@ -53,11 +53,6 @@ public string GetSharepointField(Expression> property) return fieldName; } - private string? GetSharepointField(string propertyName) - { - return this.fieldsByPropertyName.TryGetValue(propertyName, out var field) ? field : null; - } - public static SharepointFieldMapping CreateMapping(Expression> property, string fieldName) { return new SharepointFieldMapping(property, fieldName); @@ -68,6 +63,11 @@ private string GetPropertyName(Expression> property) return new PropertyNameParser().GetName(property); } + private string? GetSharepointField(string propertyName) + { + return this.fieldsByPropertyName.TryGetValue(propertyName, out var field) ? field : null; + } + public struct SharepointFieldMapping { public SharepointFieldMapping(Expression> property, string field) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointDepartmentsCalendarsSettings.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointDepartmentsCalendarsSettings.cs index ee53f0169..346885edf 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointDepartmentsCalendarsSettings.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointDepartmentsCalendarsSettings.cs @@ -1,6 +1,5 @@ namespace Arcadia.Assistant.Sharepoint.Models { - using System; using System.Collections.Generic; using System.Fabric.Description; using System.Text.Json; @@ -9,16 +8,8 @@ public class SharepointDepartmentsCalendarsSettings : ISharepointDepartmentsCale { public SharepointDepartmentsCalendarsSettings(ConfigurationSection configurationSection) { - try - { - var val = configurationSection.Parameters["Value"].Value; - this.DepartmentsCalendars = JsonSerializer.Deserialize>(val); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } + var val = configurationSection.Parameters["Value"].Value; + this.DepartmentsCalendars = JsonSerializer.Deserialize>(val); } public IEnumerable DepartmentsCalendars { get; set; } diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointSynchronizationSettings.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointSynchronizationSettings.cs index f3f022a3a..624bae972 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointSynchronizationSettings.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Models/SharepointSynchronizationSettings.cs @@ -8,7 +8,9 @@ public class SharepointSynchronizationSettings : ISharepointSynchronizationSetti public SharepointSynchronizationSettings(ConfigurationSection configurationSection) { - this.SynchronizationIntervalMinutes = int.TryParse(configurationSection.Parameters["SynchronizationIntervalMinutes"].Value, out var interval) ? interval : this.DefaultSynchronizationIntervalMinutes; + this.SynchronizationIntervalMinutes = int.TryParse(configurationSection.Parameters["SynchronizationIntervalMinutes"].Value, out var interval) + ? interval + : this.DefaultSynchronizationIntervalMinutes; } public int SynchronizationIntervalMinutes { get; } From b0e4a9c3854deba4c73b57c634cc702959c9fc76 Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Thu, 13 Feb 2020 16:28:04 +0300 Subject: [PATCH 13/14] Fixed PR comments --- .../Contracts/SharepointRequest.cs | 41 +++++++++---------- .../SharepointStorage.cs | 3 +- .../StorageItemJsonConvertor.cs | 15 +++---- .../Sharepoint.cs | 8 ++-- .../SharepointSynchronizator.cs | 10 ++--- 5 files changed, 36 insertions(+), 41 deletions(-) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs index ba8703a4c..0565207f4 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/Contracts/SharepointRequest.cs @@ -16,7 +16,12 @@ public class SharepointRequest private const string IfMatchHeaderName = "IF-MATCH"; private const string XHttpMethodHeaderName = "X-HTTP-Method"; - private readonly List> headersInternal = new List>(); + /**** According rfc7230 3.2.2 p.2 + * A sender must not generate multiple header fields with the same field name in a message unless either the entire field + * value for that header field is defined as a comma-separated list [i.e., #(values)] or the header field is a well-known + * exception (as noted below). + */ + private readonly Dictionary> headersInternal = new Dictionary>(); private SharepointRequest(HttpMethod httpMethod, string url) { @@ -30,25 +35,14 @@ private SharepointRequest(HttpMethod httpMethod, string url) public HttpContent? Content { get; private set; } - public IReadOnlyList> Headers => this.headersInternal; - public static SharepointRequest Create(HttpMethod httpMethod, string url) { return new SharepointRequest(httpMethod, url); } - public SharepointRequest WithAcceptHeader(string value, bool update = true) + public SharepointRequest WithAcceptHeader(string value) { - if (update) - { - this.RemoveHeader(AcceptHeaderName); - } - - if (update || !this.headersInternal.Any(x => x.Item1 == AcceptHeaderName)) - { - this.AddHeader(AcceptHeaderName, value); - } - + this.AddHeader(AcceptHeaderName, value); return this; } @@ -95,8 +89,9 @@ public HttpRequestMessage GetHttpRequest() httpRequest.Content = this.Content; } - foreach (var (headerName, headerValue) in this.headersInternal) + foreach (var (headerName, headerValues) in this.headersInternal) { + var headerValue = string.Join(", ", headerValues); switch (headerName) { case AcceptHeaderName: @@ -128,12 +123,16 @@ public HttpRequestMessage GetHttpRequest() private void AddHeader(string name, string value) { - this.headersInternal.Add(Tuple.Create(name, value)); - } - - private void RemoveHeader(string name) - { - this.headersInternal.RemoveAll(x => x.Item1 == name); + if (!this.headersInternal.TryGetValue(name, out var headerValues)) + { + this.headersInternal.Add(name, new List() + { + value + }); + } else if (!headerValues.Contains(value)) + { + this.headersInternal[name].Add(value); + } } } } \ No newline at end of file diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs index 251bb0385..1a11c4bea 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs @@ -65,8 +65,7 @@ public async Task UpdateItem(string list, StorageItem item, CancellationToken ca var existingListItem = listItems.First(); - var updateItemUrl = this.GetListItemsUrl(list, false); - updateItemUrl += $"({existingListItem.Id})"; + var updateItemUrl = $"{this.GetListItemsUrl(list, false)}({existingListItem.Id})"; var listItemType = await this.GetListItemType(list, cancellationToken); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/StorageItemJsonConvertor.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/StorageItemJsonConvertor.cs index 33389cf3c..202441515 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/StorageItemJsonConvertor.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/StorageItemJsonConvertor.cs @@ -76,8 +76,8 @@ public string StorageItemToRequestJson(StorageItem item, string? listItemType) Id = this.GetJsonInt(item, this.FieldsMapper.GetSharepointField(x => x.Id)), Title = this.GetJsonString(item, this.FieldsMapper.GetSharepointField(x => x.Title)), Description = this.GetJsonString(item, this.FieldsMapper.GetSharepointField(x => x.Description)), - StartDate = this.GetJsonDate(item, this.FieldsMapper.GetSharepointField(x => x.StartDate), allDayEvent, DateTime.MinValue), - EndDate = this.GetJsonDate(item, this.FieldsMapper.GetSharepointField(x => x.EndDate), allDayEvent, DateTime.UtcNow), + StartDate = this.GetJsonDate(item, this.FieldsMapper.GetSharepointField(x => x.StartDate), DateTime.MinValue, allDayEvent), + EndDate = this.GetJsonDate(item, this.FieldsMapper.GetSharepointField(x => x.EndDate), DateTime.UtcNow, allDayEvent), Category = this.GetJsonString(item, this.FieldsMapper.GetSharepointField(x => x.Category)), AllDayEvent = allDayEvent, CalendarEventId = this.GetJsonString(item, this.FieldsMapper.GetSharepointField(x => x.CalendarEventId)) @@ -103,15 +103,10 @@ private bool GetJsonBool(JsonElement item, string propName, bool defaultValue = return item.TryGetProperty(propName, out var prop) && prop.ValueKind != JsonValueKind.Null ? prop.GetBoolean() : defaultValue; } - private DateTime GetJsonDate(JsonElement item, string propName, bool dateOnly, DateTime defaultValue) + private DateTime GetJsonDate(JsonElement item, string propName, DateTime defaultValue, bool dateOnly = false) { - var date = item.TryGetProperty(propName, out var prop) && prop.ValueKind != JsonValueKind.Null && prop.TryGetDateTime(out var val) ? val : defaultValue; - return dateOnly ? new DateTime(date.Year, date.Month, date.Day) : date; - } - - private DateTime GetJsonDate(JsonElement item, string propName, DateTime defaultValue) - { - return item.TryGetProperty(propName, out var prop) && prop.ValueKind != JsonValueKind.Null && prop.TryGetDateTime(out var val) ? val : defaultValue; + var date = item.TryGetProperty(propName, out var prop) && prop.ValueKind != JsonValueKind.Null && prop.TryGetDateTime(out var val) ? val : defaultValue; ; + return dateOnly ? date.Date : date; } #endregion diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs index 2e38d6d46..ceee1e392 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs @@ -31,7 +31,7 @@ public class Sharepoint : StatelessService private readonly ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings; private readonly IEmployees employees; private readonly Func externalStorageProvider; - private readonly ILogger? logger = null; // TO DO - initialize variable + private readonly ILogger? logger; private readonly IOrganization organizations; private readonly ISharepointSynchronizationSettings serviceSettings; private readonly ISickLeaves sickLeaves; @@ -47,7 +47,8 @@ public Sharepoint( IOrganization organizations, Func externalStorageProvider, ISharepointSynchronizationSettings serviceSettings, - ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings) + ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings, + ILogger logger) : base(context) { this.externalStorageProvider = externalStorageProvider; @@ -58,6 +59,7 @@ public Sharepoint( this.organizations = organizations; this.serviceSettings = serviceSettings; this.departmentsCalendarsSettings = departmentsCalendarsSettings; + this.logger = logger; } /// @@ -92,7 +94,7 @@ protected override async Task RunAsync(CancellationToken cancellationToken) } catch (Exception e) { - this.logger?.LogError(e, "Sharepoint items synchronization fail"); + this.logger.LogError(e, "Sharepoint items synchronization fail"); } await Task.Delay(TimeSpan.FromMinutes(this.serviceSettings.SynchronizationIntervalMinutes), cancellationToken); diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs index dbafcf75d..202428b98 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/SharepointSynchronizator.cs @@ -27,13 +27,13 @@ public sealed class SharepointSynchronizator private readonly IVacations vacations; private readonly IWorkHoursCredit workouts; private readonly ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings; - private readonly ILogger? logger; + private readonly ILogger logger; #endregion #region ctor - public SharepointSynchronizator(ISickLeaves sickLeaves, IVacations vacations, IWorkHoursCredit workouts, ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings, ILogger? logger) + public SharepointSynchronizator(ISickLeaves sickLeaves, IVacations vacations, IWorkHoursCredit workouts, ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings, ILogger logger) { this.sickLeaves = sickLeaves; this.vacations = vacations; @@ -111,7 +111,7 @@ private IEnumerable GetSharepointCalendarsByDepartment(string department private class EmployeeVacationsSynchronization : SharepointItemSynchronization { - public EmployeeVacationsSynchronization(IExternalStorage externalStorage, ILogger? logger = null) + public EmployeeVacationsSynchronization(IExternalStorage externalStorage, ILogger logger) : base(externalStorage, logger) { } @@ -131,7 +131,7 @@ protected override EmployeeId GetItemEmployeeId(VacationDescription item) private class EmployeeWorkoutsSynchronization : SharepointItemSynchronization { - public EmployeeWorkoutsSynchronization(IExternalStorage externalStorage, ILogger? logger = null) + public EmployeeWorkoutsSynchronization(IExternalStorage externalStorage, ILogger logger) : base(externalStorage, logger) { } @@ -151,7 +151,7 @@ protected override EmployeeId GetItemEmployeeId(WorkHoursChange item) private class EmployeeSickLeavesSynchronization : SharepointItemSynchronization { - public EmployeeSickLeavesSynchronization(IExternalStorage externalStorage, ILogger? logger = null) + public EmployeeSickLeavesSynchronization(IExternalStorage externalStorage, ILogger logger) : base(externalStorage, logger) { } From d2ba4c7cfa32ff813f5d980998140447ec154291 Mon Sep 17 00:00:00 2001 From: "sergey.shilov" <> Date: Thu, 13 Feb 2020 21:30:28 +0300 Subject: [PATCH 14/14] Fix PR comments --- .../SharepointStorage.cs | 3 +-- .../Arcadia.Assistant.Sharepoint/Sharepoint.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs index 1a11c4bea..533490ca8 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.ExternalStorages.SharepointOnline/SharepointStorage.cs @@ -90,8 +90,7 @@ public async Task DeleteItem(string list, string itemId, CancellationToken cance var existingListItem = listItems.First(); - var deleteItemUrl = this.GetListItemsUrl(list, false); - deleteItemUrl += $"({existingListItem.Id})"; + var deleteItemUrl = $"{this.GetListItemsUrl(list, false)}({existingListItem.Id})"; var request = SharepointRequest .Create(HttpMethod.Post, deleteItemUrl) diff --git a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs index ceee1e392..af256d7ea 100644 --- a/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs +++ b/server2/Arcadia.Assistant/Arcadia.Assistant.Sharepoint/Sharepoint.cs @@ -31,7 +31,7 @@ public class Sharepoint : StatelessService private readonly ISharepointDepartmentsCalendarsSettings departmentsCalendarsSettings; private readonly IEmployees employees; private readonly Func externalStorageProvider; - private readonly ILogger? logger; + private readonly ILogger logger; private readonly IOrganization organizations; private readonly ISharepointSynchronizationSettings serviceSettings; private readonly ISickLeaves sickLeaves;