From e6561308e573167ab3830a2ddba22a7ec2cc38f2 Mon Sep 17 00:00:00 2001 From: Mark van der Wal Date: Tue, 24 Mar 2020 23:26:02 +0100 Subject: [PATCH] Handle events. Calculate targetn. --- FarmmapsApi/FarmmapsApi.csproj | 1 + FarmmapsApi/Models/EventMessage.cs | 12 ++ FarmmapsApi/Models/ItemRequest.cs | 1 + FarmmapsApi/Models/TaskRequest.cs | 4 +- FarmmapsApi/Services/FarmmapsApiService.cs | 106 +++++++++++++++-- FarmmapsApiSamples/DefaultApp.cs | 53 --------- FarmmapsApiSamples/NbsApp.cs | 132 +++++++++++++++++++++ FarmmapsApiSamples/Program.cs | 9 +- 8 files changed, 251 insertions(+), 67 deletions(-) create mode 100644 FarmmapsApi/Models/EventMessage.cs delete mode 100644 FarmmapsApiSamples/DefaultApp.cs create mode 100644 FarmmapsApiSamples/NbsApp.cs diff --git a/FarmmapsApi/FarmmapsApi.csproj b/FarmmapsApi/FarmmapsApi.csproj index b75ff51..8719220 100644 --- a/FarmmapsApi/FarmmapsApi.csproj +++ b/FarmmapsApi/FarmmapsApi.csproj @@ -5,6 +5,7 @@ + diff --git a/FarmmapsApi/Models/EventMessage.cs b/FarmmapsApi/Models/EventMessage.cs new file mode 100644 index 0000000..eae53c4 --- /dev/null +++ b/FarmmapsApi/Models/EventMessage.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; + +namespace FarmmapsApi.Models +{ + public class EventMessage + { + public string EventType { get; set; } + public string ItemCode { get; set; } + public Dictionary Attributes { get; set; } + } +} \ No newline at end of file diff --git a/FarmmapsApi/Models/ItemRequest.cs b/FarmmapsApi/Models/ItemRequest.cs index 41bb7c7..3d76149 100644 --- a/FarmmapsApi/Models/ItemRequest.cs +++ b/FarmmapsApi/Models/ItemRequest.cs @@ -15,6 +15,7 @@ namespace FarmmapsApi.Models public string ItemType { get; set; } public string Name { get; set; } public DateTime? DataDate { get; set; } + public DateTime? DataEndDate { get; set; } public JObject Geometry { get; set; } public JObject Data { get; set; } public IList Tags { get; set; } diff --git a/FarmmapsApi/Models/TaskRequest.cs b/FarmmapsApi/Models/TaskRequest.cs index d690e4d..17d83ad 100644 --- a/FarmmapsApi/Models/TaskRequest.cs +++ b/FarmmapsApi/Models/TaskRequest.cs @@ -7,11 +7,11 @@ namespace FarmmapsApi.Models public string TaskType { get; set; } public string Delay { get; set; } - public Dictionary attributes { get; set; } + public Dictionary attributes { get; } public TaskRequest() { - this.attributes = new Dictionary(); + attributes = new Dictionary(); } } } \ No newline at end of file diff --git a/FarmmapsApi/Services/FarmmapsApiService.cs b/FarmmapsApi/Services/FarmmapsApiService.cs index 392985c..b422d89 100644 --- a/FarmmapsApi/Services/FarmmapsApiService.cs +++ b/FarmmapsApi/Services/FarmmapsApiService.cs @@ -1,15 +1,20 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Mime; using System.Security.Authentication; +using System.Text; using System.Threading.Tasks; +using System.Web; using FarmmapsApi.Models; +using IdentityModel; +using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using static IdentityModel.OidcConstants; namespace FarmmapsApi.Services { @@ -18,6 +23,10 @@ namespace FarmmapsApi.Services private readonly HttpClient _httpClient; private readonly OpenIdConnectService _openIdConnectService; private readonly Configuration _configuration; + + private HubConnection _hubConnection; + + public event Action EventCallback; public FarmmapsApiService(HttpClient httpClient, OpenIdConnectService openIdConnectService, Configuration configuration) @@ -34,7 +43,7 @@ namespace FarmmapsApi.Services { if (_httpClient.DefaultRequestHeaders.Authorization != null && _httpClient.DefaultRequestHeaders.Authorization.Scheme != - AuthenticationSchemes.AuthorizationHeaderBearer) + OidcConstants.AuthenticationSchemes.AuthorizationHeaderBearer) throw new AuthenticationException("Already seems to be authenticated"); var disco = await _openIdConnectService.GetDiscoveryDocumentAsync(); @@ -45,8 +54,25 @@ namespace FarmmapsApi.Services throw new AuthenticationException(token.Error); _httpClient.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue(AuthenticationSchemes.AuthorizationHeaderBearer, + new AuthenticationHeaderValue(OidcConstants.AuthenticationSchemes.AuthorizationHeaderBearer, token.AccessToken); + + await StartEventHub(token.AccessToken); + } + + private async Task StartEventHub(string accessToken) + { + var uri = new Uri(_configuration.Endpoint); + var eventEndpoint = $"{uri.Scheme}://{uri.Host}:{uri.Port}/EventHub"; + _hubConnection = new HubConnectionBuilder() + .WithUrl(eventEndpoint) + .WithAutomaticReconnect() + .AddJsonProtocol() + .Build(); + _hubConnection.On("Event", (EventMessage @event) => EventCallback?.Invoke(@event)); + + await _hubConnection.StartAsync(); + await _hubConnection.SendAsync("authenticate", accessToken); } public async Task GetCurrentUserCodeAsync() @@ -61,7 +87,7 @@ namespace FarmmapsApi.Services return json["code"].Value(); } - public async Task> GetCurrentUserRootsAsync() + public async Task> GetCurrentUserRootsAsync() { var response = await _httpClient.GetAsync(ResourceEndpoints.MYROOTS_RESOURCE); @@ -72,36 +98,95 @@ namespace FarmmapsApi.Services return JsonConvert.DeserializeObject>(jsonString); } + + public async Task GetItemAsync(string itemCode, string itemType = null, JObject dataFilter = null) + { + if (dataFilter == null) + return await GetItemSingleAsync(itemCode, itemType); - public async Task GetItemAsync(string itemCode) + var items = await GetItemsAsync(itemCode, itemType, dataFilter); + return items[0]; + } + + public async Task> GetItemsAsync(string itemCode, string itemType = null, JObject dataFilter = null) { var resourceUri = string.Format(ResourceEndpoints.ITEMS_RESOURCE, itemCode); + + var queryString = HttpUtility.ParseQueryString(string.Empty); + + if(itemType != null) + queryString["it"] = itemType; + + if(dataFilter != null) + queryString["df"] = dataFilter.ToString(Formatting.None); + + resourceUri = (queryString.Count > 0 ? $"{resourceUri}?" : resourceUri) + queryString; + var response = await _httpClient.GetAsync(resourceUri); if (!response.IsSuccessStatusCode) + { + if (response.StatusCode == HttpStatusCode.NotFound) + return null; + throw new Exception(response.ReasonPhrase); + } + + var jsonString = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject>(jsonString); + } + + private async Task GetItemSingleAsync(string itemCode, string itemType = null) + { + var resourceUri = string.Format(ResourceEndpoints.ITEMS_RESOURCE, itemCode); + + if (!string.IsNullOrEmpty(itemType)) + resourceUri = $"{resourceUri}/{itemType}"; + + var response = await _httpClient.GetAsync(resourceUri); + + if (!response.IsSuccessStatusCode) + { + if (response.StatusCode == HttpStatusCode.NotFound) + return null; + + throw new Exception(response.ReasonPhrase); + } var jsonString = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(jsonString); } - public async Task> GetItemChildrenAsync(string itemCode) + public async Task> GetItemChildrenAsync(string itemCode, string itemType = null, JObject dataFilter = null) { var resourceUri = string.Format(ResourceEndpoints.ITEMS_CHILDREN_RESOURCE, itemCode); + + var queryString = HttpUtility.ParseQueryString(string.Empty); + + if(itemType != null) + queryString["it"] = itemType; + + if(dataFilter != null) + queryString["df"] = dataFilter.ToString(Formatting.None); + + resourceUri = (queryString.Count > 0 ? $"{resourceUri}?" : resourceUri) + queryString; + var response = await _httpClient.GetAsync(resourceUri); if (!response.IsSuccessStatusCode) throw new Exception(response.ReasonPhrase); var jsonString = await response.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject>(jsonString); + return JsonConvert.DeserializeObject>(jsonString); } public async Task CreateItemAsync(ItemRequest itemRequest) { var jsonString = JsonConvert.SerializeObject(itemRequest); + + var content = new StringContent(jsonString, Encoding.UTF8,MediaTypeNames.Application.Json); var response = await _httpClient.PostAsync(ResourceEndpoints.ITEMS_CREATE_RESOURCE, - new StringContent(jsonString)); + content); if (!response.IsSuccessStatusCode) throw new Exception(response.ReasonPhrase); @@ -151,7 +236,8 @@ namespace FarmmapsApi.Services var resourceUri = string.Format(ResourceEndpoints.ITEMTASK_REQUEST_RESOURCE, itemCode); var jsonString = JsonConvert.SerializeObject(taskRequest); - var response = await _httpClient.PostAsync(resourceUri, new StringContent(jsonString)); + var content = new StringContent(jsonString, Encoding.UTF8,MediaTypeNames.Application.Json); + var response = await _httpClient.PostAsync(resourceUri, content); if (!response.IsSuccessStatusCode) throw new Exception(response.ReasonPhrase); @@ -162,7 +248,7 @@ namespace FarmmapsApi.Services return json["code"].Value(); } - public async Task> GetTasksStatusAsync(string itemCode) + public async Task> GetTasksStatusAsync(string itemCode) { var resourceUri = string.Format(ResourceEndpoints.ITEMTASKS_RESOURCE, itemCode); var response = await _httpClient.GetAsync(resourceUri); diff --git a/FarmmapsApiSamples/DefaultApp.cs b/FarmmapsApiSamples/DefaultApp.cs deleted file mode 100644 index fe9ba41..0000000 --- a/FarmmapsApiSamples/DefaultApp.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using FarmmapsApi.Services; -using Microsoft.Extensions.Logging; - -namespace FarmmapsApiSamples -{ - public class DefaultApp : IApp - { - private readonly ILogger _logger; - private readonly FarmmapsApiService _farmmapsApiService; - - public DefaultApp(ILogger logger, FarmmapsApiService farmmapsApiService) - { - _logger = logger; - _farmmapsApiService = farmmapsApiService; - } - - public async Task RunAsync() - { - try - { - await _farmmapsApiService.AuthenticateAsync(); - - _logger.LogInformation("Authenticated client credentials"); - - var user = await _farmmapsApiService.GetCurrentUserCodeAsync(); - _logger.LogInformation($"Usercode: {user}"); - - var roots = (await _farmmapsApiService.GetCurrentUserRootsAsync()).ToList(); - foreach (var userRoot in roots) - { - _logger.LogInformation($"{userRoot.Name} - {userRoot.Code}"); - } - - var myDriveRoot = roots.SingleOrDefault(r => r.Name == "My drive"); - if(myDriveRoot != null) - { - var items = await _farmmapsApiService.GetItemChildrenAsync(myDriveRoot.Code); - foreach (var item in items) - { - _logger.LogInformation($"{item.Name} - {item.ItemType}"); - } - } - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - } - } - } -} \ No newline at end of file diff --git a/FarmmapsApiSamples/NbsApp.cs b/FarmmapsApiSamples/NbsApp.cs new file mode 100644 index 0000000..ef74708 --- /dev/null +++ b/FarmmapsApiSamples/NbsApp.cs @@ -0,0 +1,132 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using FarmmapsApi.Models; +using FarmmapsApi.Services; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; + +namespace FarmmapsApiSamples +{ + public class NbsApp : IApp + { + private const string USERINPUT_ITEMTYPE = "vnd.farmmaps.itemtype.user.input"; + private const string VRANBS_TASK = "vnd.farmmaps.task.vranbs"; + + private readonly ILogger _logger; + private readonly FarmmapsApiService _farmmapsApiService; + + public NbsApp(ILogger logger, FarmmapsApiService farmmapsApiService) + { + _logger = logger; + _farmmapsApiService = farmmapsApiService; + + farmmapsApiService.EventCallback += OnEvent; + } + + private void OnEvent(EventMessage @event) + { + _logger.LogInformation(@event.EventType); + } + + public async Task RunAsync() + { + try + { + _logger.LogInformation("NBS sample app started"); + + await _farmmapsApiService.AuthenticateAsync(); + + _logger.LogInformation("Authenticated client credentials"); + + var roots = await _farmmapsApiService.GetCurrentUserRootsAsync(); + var myDriveRoot = roots.SingleOrDefault(r => r.Name == "My drive"); + + if (myDriveRoot != null) + { + // create or get a cropfield + var cropfieldItem = await GetCreateCropfieldItem(myDriveRoot.Code); + + // create or get target n item + + var targetNItems = await + _farmmapsApiService.GetItemChildrenAsync(myDriveRoot.Code, USERINPUT_ITEMTYPE); + + Item targetNItem; + if (targetNItems.Count == 0) + { + _logger.LogInformation("Creating targetN item"); + var itemRequest = CreateTargetNItemRequest(myDriveRoot.Code); + targetNItem = await _farmmapsApiService.CreateItemAsync(itemRequest); + } + else + { + targetNItem = targetNItems[0]; + } + + await CalculateTargetN(cropfieldItem, targetNItem, 60); + + + await Task.Delay(2000); + } + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + } + } + + private async Task GetCreateCropfieldItem(string parentItemCode) + { + var cropfieldItems = await + _farmmapsApiService.GetItemChildrenAsync(parentItemCode, "vnd.farmmaps.itemtype.cropfield"); + + if (cropfieldItems.Count > 0) + return cropfieldItems[0]; + + var currentYear = new DateTime(DateTime.UtcNow.Year, 1, 1); + var cropfieldItemRequest = new ItemRequest() + { + ParentCode = parentItemCode, + ItemType = "vnd.farmmaps.itemtype.cropfield", + Name = "cropfield for VRA", + DataDate = currentYear, + DataEndDate = currentYear.AddYears(1), + Data = JObject.FromObject(new + {startDate = currentYear, endDate = currentYear.AddYears(1)}), + Geometry = JObject.Parse( + @"{""type"":""Polygon"",""coordinates"":[[[6.09942873984307,53.070025028087],[6.09992507404607,53.0705617890585],[6.10036959220086,53.0710679529031],[6.10065149010421,53.0714062774307],[6.10087493644271,53.0716712354474],[6.10091082982487,53.0716936039203],[6.10165087441291,53.0712041549161],[6.10204994718318,53.0709349338005],[6.10263143118855,53.0705789370018],[6.10311578125011,53.0702657538294],[6.10331686552072,53.0701314102389],[6.103326530575,53.070119463569],[6.10309137950343,53.0699829669055],[6.10184241586523,53.0692902201371],[6.10168497998891,53.0691984306747],[6.10092987659869,53.0694894453514],[6.09942873984307,53.070025028087]]]}") + }; + + return await _farmmapsApiService.CreateItemAsync(cropfieldItemRequest); + } + + private async Task CalculateTargetN(Item cropfieldItem, Item targetNItem, int targetYield) + { + var nbsTargetNRequest = new TaskRequest {TaskType = VRANBS_TASK}; + nbsTargetNRequest.attributes["operation"] = "targetn"; + nbsTargetNRequest.attributes["inputCode"] = targetNItem.Code; + nbsTargetNRequest.attributes["inputType"] = "irmi"; + nbsTargetNRequest.attributes["purposeType"] = "consumption"; + nbsTargetNRequest.attributes["targetYield"] = targetYield.ToString(); + string itemTaskCode = await _farmmapsApiService.QueueTaskAsync(cropfieldItem.Code, nbsTargetNRequest); + + // poll task + + // get target N item again + + return targetNItem; + } + + private ItemRequest CreateTargetNItemRequest(string parentItemCode) + { + return new ItemRequest() + { + ParentCode = parentItemCode, + ItemType = USERINPUT_ITEMTYPE, + Name = "TargetN", + DataDate = DateTime.UtcNow + }; + } + } +} \ No newline at end of file diff --git a/FarmmapsApiSamples/Program.cs b/FarmmapsApiSamples/Program.cs index 23ab66f..5428c48 100644 --- a/FarmmapsApiSamples/Program.cs +++ b/FarmmapsApiSamples/Program.cs @@ -4,6 +4,7 @@ using FarmmapsApi.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; namespace FarmmapsApiSamples { @@ -19,10 +20,14 @@ namespace FarmmapsApiSamples var serviceProvider = new ServiceCollection() .AddLogging(opts => opts - .AddConsole() + .AddConsole(c => + { + c.IncludeScopes = false; + c.Format = ConsoleLoggerFormat.Default; + }) .AddFilter("System.Net.Http", LogLevel.Warning)) .AddFarmmapsServices(configuration) - .AddSingleton() + .AddSingleton() .BuildServiceProvider(); await serviceProvider.GetService().RunAsync();