From cd5ee2e68ba666a81c96909233f4c9b58936e467 Mon Sep 17 00:00:00 2001 From: Michael Tyson Date: Fri, 21 Jun 2024 11:52:44 -0400 Subject: [PATCH] chore: Update namespaces and interfaces for CentralPennIncidentsFunc --- .../CentralPennIncidentsFunc/API/Incidents.cs | 109 ++++++------ .../CentralPennIncidentsFunc/Dtos/Incident.cs | 39 ++--- .../CentralPennIncidentsFunc/Dtos/Location.cs | 15 +- .../Dtos/Mappings/IncidentProfile.cs | 13 +- .../Dtos/Mappings/LocationProfile.cs | 15 +- .../IncidentProviders/BaseIncidentProvider.cs | 10 +- .../LancasterIncidentProvider.cs | 21 +-- .../YorklIncidentProvider.cs | 21 +-- .../Interfaces/IDataCache.cs | 12 +- .../Interfaces/IEnvironmentProvider.cs | 15 +- .../Interfaces/ILocationRepository.cs | 11 +- .../Interfaces/ILocationService.cs | 9 +- .../Models/Incident.cs | 105 ++++++----- .../Models/LocationEntity.cs | 21 ++- .../Models/QuickType.cs | 163 +++++++++--------- .../src/CentralPennIncidentsFunc/Program.cs | 61 +++---- .../Providers/EnvironmentProvider.cs | 17 +- .../Repositories/LocationRepository.cs | 103 ++++++----- .../Schedules/EndPointKeepWarm.cs | 160 ++++++++--------- .../Services/FeedCache.cs | 32 ++-- .../Services/FeedService.cs | 18 +- .../Services/LocationCache.cs | 32 ++-- .../Services/LocationService.cs | 128 +++++++------- 23 files changed, 519 insertions(+), 611 deletions(-) diff --git a/apps/func/src/CentralPennIncidentsFunc/API/Incidents.cs b/apps/func/src/CentralPennIncidentsFunc/API/Incidents.cs index dc87600..ad269ad 100644 --- a/apps/func/src/CentralPennIncidentsFunc/API/Incidents.cs +++ b/apps/func/src/CentralPennIncidentsFunc/API/Incidents.cs @@ -9,77 +9,68 @@ using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; -namespace CentralPennIncidentsFunc.API +namespace CentralPennIncidentsFunc.API; + +public class Incidents( + IFeedService feedService, + ILocationService locationService, + ILogger log, + IMapper mapper +) { - public class Incidents - { - private readonly IFeedService _feedService; - private readonly ILocationService _locationService; - private readonly ILogger _log; - private readonly IMapper _mapper; + private readonly IFeedService _feedService = feedService; + private readonly ILocationService _locationService = locationService; + private readonly ILogger _log = log; + private readonly IMapper _mapper = mapper; - public Incidents( - IFeedService feedService, - ILocationService locationService, - ILogger log, - IMapper mapper - ) - { - _feedService = feedService; - _locationService = locationService; - _log = log; - _mapper = mapper; - } + [Function("incidents")] + public async Task Run( + [HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req + ) + { + _log.LogInformation( + "api/incidents - HTTP trigger function processed a request. {Request}", + req + ); - [Function("incidents")] - public async Task Run( - [HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req - ) + try { - _log.LogInformation( - "api/incidents - HTTP trigger function processed a request. {Request}", - req - ); + var incidents = await _feedService.GetIncidentsAsync(); - try - { - var incidents = await _feedService.GetIncidentsAsync(); + var incidentDtos = await Task.WhenAll( + incidents.Select(async i => + { + var dto = _mapper.Map(i); - var incidentDtos = await Task.WhenAll( - incidents.Select(async i => + if (string.IsNullOrWhiteSpace(i.Location)) { - var dto = _mapper.Map(i); - - if (string.IsNullOrWhiteSpace(i.Location)) - { - return dto; - } + return dto; + } - var locationEntity = await _locationService.GetLocationAsync( - i.Location, - i.Area - ); + var locationEntity = await _locationService.GetLocationAsync( + i.Location, + i.Area + ); - dto.GeocodeLocation = - (locationEntity?.Lat_VC.HasValue ?? false) - ? _mapper.Map(locationEntity) - : null; + dto.GeocodeLocation = + (locationEntity?.Lat_VC.HasValue ?? false) + ? _mapper.Map(locationEntity) + : null; - return dto; - }) - ); + return dto; + }) + ); - var response = req.CreateResponse(HttpStatusCode.OK); - response.Headers.Add("Content-Type", "application/json; charset=utf-8"); - await response.WriteStringAsync(JsonSerializer.Serialize(incidentDtos)); + var response = req.CreateResponse(HttpStatusCode.OK); + response.Headers.Add("Content-Type", "application/json; charset=utf-8"); + await response.WriteStringAsync(JsonSerializer.Serialize(incidentDtos)); - return response; - } - catch (Exception ex) - { - _log.LogError(ex, "Execution Error: {Exception}", ex); - throw; - } + return response; + } + catch (Exception ex) + { + _log.LogError(ex, "Execution Error: {Exception}", ex); + throw; } } } diff --git a/apps/func/src/CentralPennIncidentsFunc/Dtos/Incident.cs b/apps/func/src/CentralPennIncidentsFunc/Dtos/Incident.cs index dab6a95..d2c6798 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Dtos/Incident.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Dtos/Incident.cs @@ -1,32 +1,31 @@ using System; using System.Text.Json.Serialization; -namespace CentralPennIncidentsFunc.Dtos +namespace CentralPennIncidentsFunc.Dtos; + +public class Incident { - public class Incident - { - [JsonPropertyName("id")] - public string Id { get; set; } + [JsonPropertyName("id")] + public string Id { get; set; } - [JsonPropertyName("incident_dt")] - public DateTime IncidentDate { get; set; } + [JsonPropertyName("incident_dt")] + public DateTime IncidentDate { get; set; } - [JsonPropertyName("type")] - public string Type { get; set; } + [JsonPropertyName("type")] + public string Type { get; set; } - [JsonPropertyName("subType")] - public string SubType { get; set; } + [JsonPropertyName("subType")] + public string SubType { get; set; } - [JsonPropertyName("location")] - public string Location { get; set; } + [JsonPropertyName("location")] + public string Location { get; set; } - [JsonPropertyName("area")] - public string Area { get; set; } + [JsonPropertyName("area")] + public string Area { get; set; } - [JsonPropertyName("units_assigned")] - public string[] UnitsAssigned { get; set; } + [JsonPropertyName("units_assigned")] + public string[] UnitsAssigned { get; set; } - [JsonPropertyName("geocode_location")] - public Location GeocodeLocation { get; set; } - } + [JsonPropertyName("geocode_location")] + public Location GeocodeLocation { get; set; } } diff --git a/apps/func/src/CentralPennIncidentsFunc/Dtos/Location.cs b/apps/func/src/CentralPennIncidentsFunc/Dtos/Location.cs index ad5f448..ac44e2e 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Dtos/Location.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Dtos/Location.cs @@ -1,13 +1,12 @@ using System.Text.Json.Serialization; -namespace CentralPennIncidentsFunc.Dtos +namespace CentralPennIncidentsFunc.Dtos; + +public class Location { - public class Location - { - [JsonPropertyName("lat")] - public double Lat { get; set; } + [JsonPropertyName("lat")] + public double Lat { get; set; } - [JsonPropertyName("lng")] - public double Lng { get; set; } - } + [JsonPropertyName("lng")] + public double Lng { get; set; } } diff --git a/apps/func/src/CentralPennIncidentsFunc/Dtos/Mappings/IncidentProfile.cs b/apps/func/src/CentralPennIncidentsFunc/Dtos/Mappings/IncidentProfile.cs index 3daae94..f6c0c08 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Dtos/Mappings/IncidentProfile.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Dtos/Mappings/IncidentProfile.cs @@ -1,13 +1,12 @@ using AutoMapper; -namespace CentralPennIncidentsFunc.Dtos.Mappings +namespace CentralPennIncidentsFunc.Dtos.Mappings; + +public class IncidentProfile : Profile { - public class IncidentProfile : Profile + public IncidentProfile() { - public IncidentProfile() - { - CreateMap() - .ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.GlobalId.Uid)); - } + CreateMap() + .ForMember(dst => dst.Id, opt => opt.MapFrom(src => src.GlobalId.Uid)); } } diff --git a/apps/func/src/CentralPennIncidentsFunc/Dtos/Mappings/LocationProfile.cs b/apps/func/src/CentralPennIncidentsFunc/Dtos/Mappings/LocationProfile.cs index f97899e..165d1fe 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Dtos/Mappings/LocationProfile.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Dtos/Mappings/LocationProfile.cs @@ -1,14 +1,13 @@ using AutoMapper; -namespace CentralPennIncidentsFunc.Dtos.Mappings +namespace CentralPennIncidentsFunc.Dtos.Mappings; + +public class LocationProfile : Profile { - public class LocationProfile : Profile + public LocationProfile() { - public LocationProfile() - { - CreateMap() - .ForMember(d => d.Lat, opt => opt.MapFrom(src => src.Lat_VC)) - .ForMember(d => d.Lng, opt => opt.MapFrom(src => src.Lng_VC)); - } + CreateMap() + .ForMember(d => d.Lat, opt => opt.MapFrom(src => src.Lat_VC)) + .ForMember(d => d.Lng, opt => opt.MapFrom(src => src.Lng_VC)); } } diff --git a/apps/func/src/CentralPennIncidentsFunc/IncidentProviders/BaseIncidentProvider.cs b/apps/func/src/CentralPennIncidentsFunc/IncidentProviders/BaseIncidentProvider.cs index 413245e..68be758 100644 --- a/apps/func/src/CentralPennIncidentsFunc/IncidentProviders/BaseIncidentProvider.cs +++ b/apps/func/src/CentralPennIncidentsFunc/IncidentProviders/BaseIncidentProvider.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -7,14 +6,9 @@ namespace CentralPennIncidentsFunc.IncidentProviders; -public abstract class BaseIncidentProvider : IIncidentProvider +public abstract class BaseIncidentProvider(IEnvironmentProvider env) : IIncidentProvider { - private readonly IEnvironmentProvider _env; - - public BaseIncidentProvider(IEnvironmentProvider env) - { - _env = env; - } + private readonly IEnvironmentProvider _env = env; public string Source => _env.GetEnvironmentVariable($"IncidentSources__{Key}"); diff --git a/apps/func/src/CentralPennIncidentsFunc/IncidentProviders/LancasterIncidentProvider.cs b/apps/func/src/CentralPennIncidentsFunc/IncidentProviders/LancasterIncidentProvider.cs index 560142e..6ac0770 100644 --- a/apps/func/src/CentralPennIncidentsFunc/IncidentProviders/LancasterIncidentProvider.cs +++ b/apps/func/src/CentralPennIncidentsFunc/IncidentProviders/LancasterIncidentProvider.cs @@ -10,21 +10,14 @@ namespace CentralPennIncidentsFunc.IncidentProviders; -public class LancasterIncidentProvider : BaseIncidentProvider +public class LancasterIncidentProvider( + IEnvironmentProvider env, + IDataCache> feedCache, + IHttpClientFactory httpClientFactory +) : BaseIncidentProvider(env) { - private readonly IDataCache> _feedCache; - private readonly HttpClient _client; - - public LancasterIncidentProvider( - IEnvironmentProvider env, - IDataCache> feedCache, - IHttpClientFactory httpClientFactory - ) - : base(env) - { - _feedCache = feedCache; - _client = httpClientFactory.CreateClient(); - } + private readonly IDataCache> _feedCache = feedCache; + private readonly HttpClient _client = httpClientFactory.CreateClient(); public override string Key => "Lancaster"; diff --git a/apps/func/src/CentralPennIncidentsFunc/IncidentProviders/YorklIncidentProvider.cs b/apps/func/src/CentralPennIncidentsFunc/IncidentProviders/YorklIncidentProvider.cs index ab1d678..ac6d05a 100644 --- a/apps/func/src/CentralPennIncidentsFunc/IncidentProviders/YorklIncidentProvider.cs +++ b/apps/func/src/CentralPennIncidentsFunc/IncidentProviders/YorklIncidentProvider.cs @@ -10,21 +10,14 @@ namespace CentralPennIncidentsFunc.IncidentProviders; -public class YorklIncidentProvider : BaseIncidentProvider +public class YorklIncidentProvider( + IEnvironmentProvider env, + IDataCache> feedCache, + IHttpClientFactory httpClientFactory +) : BaseIncidentProvider(env) { - private readonly IDataCache> _feedCache; - private readonly HttpClient _client; - - public YorklIncidentProvider( - IEnvironmentProvider env, - IDataCache> feedCache, - IHttpClientFactory httpClientFactory - ) - : base(env) - { - _feedCache = feedCache; - _client = httpClientFactory.CreateClient(); - } + private readonly IDataCache> _feedCache = feedCache; + private readonly HttpClient _client = httpClientFactory.CreateClient(); public override string Key => "York"; diff --git a/apps/func/src/CentralPennIncidentsFunc/Interfaces/IDataCache.cs b/apps/func/src/CentralPennIncidentsFunc/Interfaces/IDataCache.cs index d685ca4..0f00a31 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Interfaces/IDataCache.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Interfaces/IDataCache.cs @@ -1,11 +1,7 @@ -using System.Collections.Generic; -using CentralPennIncidentsFunc.Models; +namespace CentralPennIncidentsFunc.Interfaces; -namespace CentralPennIncidentsFunc.Interfaces +public interface IDataCache { - public interface IDataCache - { - bool TryGetValue(TKey key, out TValue value); - void SaveValue(TKey key, TValue value); - } + bool TryGetValue(TKey key, out TValue value); + void SaveValue(TKey key, TValue value); } diff --git a/apps/func/src/CentralPennIncidentsFunc/Interfaces/IEnvironmentProvider.cs b/apps/func/src/CentralPennIncidentsFunc/Interfaces/IEnvironmentProvider.cs index 4020bdb..f477b25 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Interfaces/IEnvironmentProvider.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Interfaces/IEnvironmentProvider.cs @@ -1,12 +1,11 @@ using System; -namespace CentralPennIncidentsFunc.Interfaces +namespace CentralPennIncidentsFunc.Interfaces; + +public interface IEnvironmentProvider { - public interface IEnvironmentProvider - { - string GetEnvironmentVariable( - string variable, - EnvironmentVariableTarget target = EnvironmentVariableTarget.Process - ); - } + string GetEnvironmentVariable( + string variable, + EnvironmentVariableTarget target = EnvironmentVariableTarget.Process + ); } diff --git a/apps/func/src/CentralPennIncidentsFunc/Interfaces/ILocationRepository.cs b/apps/func/src/CentralPennIncidentsFunc/Interfaces/ILocationRepository.cs index abda1d5..0a7cb76 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Interfaces/ILocationRepository.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Interfaces/ILocationRepository.cs @@ -2,11 +2,10 @@ using CentralPennIncidentsFunc.Models; using Microsoft.Azure.Cosmos.Table; -namespace CentralPennIncidentsFunc.Interfaces +namespace CentralPennIncidentsFunc.Interfaces; + +public interface ILocationRepository { - public interface ILocationRepository - { - Task SaveAsync(ITableEntity locationEntity); - Task FindByAddressAsync(string location, string area); - } + Task SaveAsync(ITableEntity locationEntity); + Task FindByAddressAsync(string location, string area); } diff --git a/apps/func/src/CentralPennIncidentsFunc/Interfaces/ILocationService.cs b/apps/func/src/CentralPennIncidentsFunc/Interfaces/ILocationService.cs index bb5f9a8..9fde553 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Interfaces/ILocationService.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Interfaces/ILocationService.cs @@ -1,10 +1,9 @@ using System.Threading.Tasks; using CentralPennIncidentsFunc.Models; -namespace CentralPennIncidentsFunc.Interfaces +namespace CentralPennIncidentsFunc.Interfaces; + +public interface ILocationService { - public interface ILocationService - { - Task GetLocationAsync(string location, string area); - } + Task GetLocationAsync(string location, string area); } diff --git a/apps/func/src/CentralPennIncidentsFunc/Models/Incident.cs b/apps/func/src/CentralPennIncidentsFunc/Models/Incident.cs index eaa8bc3..fb093b3 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Models/Incident.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Models/Incident.cs @@ -2,70 +2,69 @@ using System.Linq; using System.Text; -namespace CentralPennIncidentsFunc.Models +namespace CentralPennIncidentsFunc.Models; + +public class Incident { - public class Incident + public Incident(GlobalId globalId) { - public Incident(GlobalId globalId) - { - GlobalId = globalId; - } - - public Incident(string globalId) - { - GlobalId = new GlobalId(globalId); - } + GlobalId = globalId; + } - public Incident(string key, string id) - { - GlobalId = new GlobalId(key, id); - } + public Incident(string globalId) + { + GlobalId = new GlobalId(globalId); + } - public GlobalId GlobalId { get; } - public DateTime IncidentDate { get; set; } - public string Type { get; set; } - public string SubType { get; set; } - public string Location { get; set; } - public string Area { get; set; } - public string[] UnitsAssigned { get; set; } + public Incident(string key, string id) + { + GlobalId = new GlobalId(key, id); } - public class GlobalId + public GlobalId GlobalId { get; } + public DateTime IncidentDate { get; set; } + public string Type { get; set; } + public string SubType { get; set; } + public string Location { get; set; } + public string Area { get; set; } + public string[] UnitsAssigned { get; set; } +} + +public class GlobalId +{ + public GlobalId(string globalId) { - public GlobalId(string globalId) - { - var splitId = DecodeFrom64(globalId).Split(':'); - var key = splitId.First(); - var id = splitId.Skip(1).First(); + var splitId = DecodeFrom64(globalId).Split(':'); + var key = splitId.First(); + var id = splitId.Skip(1).First(); - Key = key; - Id = id; - Uid = globalId; - } + Key = key; + Id = id; + Uid = globalId; + } - public GlobalId(string key, string id) - { - Key = key; - Id = id; - Uid = EncodeTo64($"{key}:{id}"); - } + public GlobalId(string key, string id) + { + Key = key; + Id = id; + Uid = EncodeTo64($"{key}:{id}"); + } - public string Key { get; } - public string Id { get; } - public string Uid { get; } + public string Key { get; } + public string Id { get; } + public string Uid { get; } - private static string DecodeFrom64(string encodedData) - { - var encodedDataAsBytes = Convert.FromBase64String(encodedData); - var returnValue = Encoding.ASCII.GetString(encodedDataAsBytes); - return returnValue; - } + private static string DecodeFrom64(string encodedData) + { + var encodedDataAsBytes = Convert.FromBase64String(encodedData); + var returnValue = Encoding.ASCII.GetString(encodedDataAsBytes); + return returnValue; + } - private static string EncodeTo64(string toEncode) - { - var toEncodeAsBytes = Encoding.ASCII.GetBytes(toEncode); - var returnValue = Convert.ToBase64String(toEncodeAsBytes); - return returnValue; - } + private static string EncodeTo64(string toEncode) + { + var toEncodeAsBytes = Encoding.ASCII.GetBytes(toEncode); + var returnValue = Convert.ToBase64String(toEncodeAsBytes); + return returnValue; } } diff --git a/apps/func/src/CentralPennIncidentsFunc/Models/LocationEntity.cs b/apps/func/src/CentralPennIncidentsFunc/Models/LocationEntity.cs index a4ec466..a9b05ee 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Models/LocationEntity.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Models/LocationEntity.cs @@ -1,19 +1,18 @@ using System.Net; using Microsoft.Azure.Cosmos.Table; -namespace CentralPennIncidentsFunc.Models +namespace CentralPennIncidentsFunc.Models; + +public class LocationEntity : TableEntity { - public class LocationEntity : TableEntity + public LocationEntity(string skey, string srow) { - public LocationEntity(string skey, string srow) - { - this.PartitionKey = skey; - this.RowKey = WebUtility.UrlEncode(srow); - } + this.PartitionKey = skey; + this.RowKey = WebUtility.UrlEncode(srow); + } - public LocationEntity() { } + public LocationEntity() { } - public double? Lat_VC { get; set; } - public double? Lng_VC { get; set; } - } + public double? Lat_VC { get; set; } + public double? Lng_VC { get; set; } } diff --git a/apps/func/src/CentralPennIncidentsFunc/Models/QuickType.cs b/apps/func/src/CentralPennIncidentsFunc/Models/QuickType.cs index 5a75a0b..a87ef31 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Models/QuickType.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Models/QuickType.cs @@ -1,86 +1,85 @@ // Generated by https://quicktype.io -namespace QuickType +namespace QuickType; + +using System.Text.Json; +using System.Text.Json.Serialization; + +public partial class Geocode +{ + [JsonPropertyName("results")] + public Result[] Results { get; set; } + + [JsonPropertyName("status")] + public string Status { get; set; } +} + +public partial class Result +{ + [JsonPropertyName("address_components")] + public AddressComponent[] AddressComponents { get; set; } + + [JsonPropertyName("formatted_address")] + public string FormattedAddress { get; set; } + + [JsonPropertyName("geometry")] + public Geometry Geometry { get; set; } + + [JsonPropertyName("place_id")] + public string PlaceId { get; set; } + + [JsonPropertyName("types")] + public string[] Types { get; set; } +} + +public partial class AddressComponent +{ + [JsonPropertyName("long_name")] + public string LongName { get; set; } + + [JsonPropertyName("short_name")] + public string ShortName { get; set; } + + [JsonPropertyName("types")] + public string[] Types { get; set; } +} + +public partial class Geometry +{ + [JsonPropertyName("location")] + public Location Location { get; set; } + + [JsonPropertyName("location_type")] + public string LocationType { get; set; } + + [JsonPropertyName("viewport")] + public Viewport Viewport { get; set; } +} + +public partial class Location +{ + [JsonPropertyName("lat")] + public double Lat { get; set; } + + [JsonPropertyName("lng")] + public double Lng { get; set; } +} + +public partial class Viewport +{ + [JsonPropertyName("northeast")] + public Location Northeast { get; set; } + + [JsonPropertyName("southwest")] + public Location Southwest { get; set; } +} + +public partial class Geocode +{ + public static Geocode FromJson(string json) => JsonSerializer.Deserialize(json); +} + +public static class Serialize { - using System.Text.Json; - using System.Text.Json.Serialization; - - public partial class Geocode - { - [JsonPropertyName("results")] - public Result[] Results { get; set; } - - [JsonPropertyName("status")] - public string Status { get; set; } - } - - public partial class Result - { - [JsonPropertyName("address_components")] - public AddressComponent[] AddressComponents { get; set; } - - [JsonPropertyName("formatted_address")] - public string FormattedAddress { get; set; } - - [JsonPropertyName("geometry")] - public Geometry Geometry { get; set; } - - [JsonPropertyName("place_id")] - public string PlaceId { get; set; } - - [JsonPropertyName("types")] - public string[] Types { get; set; } - } - - public partial class AddressComponent - { - [JsonPropertyName("long_name")] - public string LongName { get; set; } - - [JsonPropertyName("short_name")] - public string ShortName { get; set; } - - [JsonPropertyName("types")] - public string[] Types { get; set; } - } - - public partial class Geometry - { - [JsonPropertyName("location")] - public Location Location { get; set; } - - [JsonPropertyName("location_type")] - public string LocationType { get; set; } - - [JsonPropertyName("viewport")] - public Viewport Viewport { get; set; } - } - - public partial class Location - { - [JsonPropertyName("lat")] - public double Lat { get; set; } - - [JsonPropertyName("lng")] - public double Lng { get; set; } - } - - public partial class Viewport - { - [JsonPropertyName("northeast")] - public Location Northeast { get; set; } - - [JsonPropertyName("southwest")] - public Location Southwest { get; set; } - } - - public partial class Geocode - { - public static Geocode FromJson(string json) => JsonSerializer.Deserialize(json); - } - - public static class Serialize - { - public static string ToJson(this Geocode self) => JsonSerializer.Serialize(self); - } + public static string ToJson(this Geocode self) => JsonSerializer.Serialize(self); } diff --git a/apps/func/src/CentralPennIncidentsFunc/Program.cs b/apps/func/src/CentralPennIncidentsFunc/Program.cs index 24a681c..561c4d3 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Program.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Program.cs @@ -6,47 +6,42 @@ using CentralPennIncidentsFunc.Providers; using CentralPennIncidentsFunc.Repositories; using CentralPennIncidentsFunc.Services; -using CentralPennIncidentsFunc.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -namespace CentralPennIncidentsFunc +namespace CentralPennIncidentsFunc; + +public class Program { - public class Program + static async Task Main(string[] args) { - static async Task Main(string[] args) - { - var host = new HostBuilder() - .ConfigureFunctionsWorkerDefaults() - .ConfigureServices( - (context, services) => - { - services.AddMemoryCache(); - services.AddHttpClient(); + var host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults() + .ConfigureServices( + (context, services) => + { + services.AddMemoryCache(); + services.AddHttpClient(); - services.AddSingleton(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddSingleton< - IDataCache>, - FeedCache - >(); - services.AddSingleton< - IDataCache<(string, string), LocationEntity>, - LocationCache - >(); + services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddSingleton>, FeedCache>(); + services.AddSingleton< + IDataCache<(string, string), LocationEntity>, + LocationCache + >(); - // Incident Providers - services.AddScoped(); - services.AddScoped(); + // Incident Providers + services.AddScoped(); + services.AddScoped(); - services.AddAutoMapper(typeof(Program)); - } - ) - .Build(); + services.AddAutoMapper(typeof(Program)); + } + ) + .Build(); - await host.RunAsync(); - } + await host.RunAsync(); } } diff --git a/apps/func/src/CentralPennIncidentsFunc/Providers/EnvironmentProvider.cs b/apps/func/src/CentralPennIncidentsFunc/Providers/EnvironmentProvider.cs index beb7c68..2907f63 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Providers/EnvironmentProvider.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Providers/EnvironmentProvider.cs @@ -1,16 +1,15 @@ using System; using CentralPennIncidentsFunc.Interfaces; -namespace CentralPennIncidentsFunc.Providers +namespace CentralPennIncidentsFunc.Providers; + +public class EnvironmentProvider : IEnvironmentProvider { - public class EnvironmentProvider : IEnvironmentProvider + public string GetEnvironmentVariable( + string variable, + EnvironmentVariableTarget target = EnvironmentVariableTarget.Process + ) { - public string GetEnvironmentVariable( - string variable, - EnvironmentVariableTarget target = EnvironmentVariableTarget.Process - ) - { - return Environment.GetEnvironmentVariable(variable, target); - } + return Environment.GetEnvironmentVariable(variable, target); } } diff --git a/apps/func/src/CentralPennIncidentsFunc/Repositories/LocationRepository.cs b/apps/func/src/CentralPennIncidentsFunc/Repositories/LocationRepository.cs index f0a898d..12bb1b3 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Repositories/LocationRepository.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Repositories/LocationRepository.cs @@ -6,77 +6,70 @@ using CentralPennIncidentsFunc.Models; using Microsoft.Azure.Cosmos.Table; -namespace CentralPennIncidentsFunc.Repositories +namespace CentralPennIncidentsFunc.Repositories; + +public class LocationRepository : ILocationRepository { - public class LocationRepository : ILocationRepository - { - private readonly CloudTableClient _tableClient; - private readonly CloudTable _incidentTable; + private readonly CloudTableClient _tableClient; + private readonly CloudTable _incidentTable; - public LocationRepository(IEnvironmentProvider env) - { - var connectionString = env.GetEnvironmentVariable("AzureTableStorage"); + public LocationRepository(IEnvironmentProvider env) + { + var connectionString = env.GetEnvironmentVariable("AzureTableStorage"); - // Connect to the Storage account. - _tableClient = CloudStorageAccount.Parse(connectionString).CreateCloudTableClient(); + // Connect to the Storage account. + _tableClient = CloudStorageAccount.Parse(connectionString).CreateCloudTableClient(); - _incidentTable = _tableClient.GetTableReference("IncidentLocations"); - } + _incidentTable = _tableClient.GetTableReference("IncidentLocations"); + } - public async Task SaveAsync(ITableEntity locationEntity) + public async Task SaveAsync(ITableEntity locationEntity) + { + try { - try - { - await _incidentTable.CreateIfNotExistsAsync(); + await _incidentTable.CreateIfNotExistsAsync(); - var insertOperation = TableOperation.InsertOrReplace(locationEntity); - await _incidentTable.ExecuteAsync(insertOperation); - } - catch (Exception ex) - { - throw new SaveException(locationEntity.RowKey, ex); - } + var insertOperation = TableOperation.InsertOrReplace(locationEntity); + await _incidentTable.ExecuteAsync(insertOperation); } - - public async Task FindByAddressAsync(string location, string area) + catch (Exception ex) { - var address = $"{location}, {area}"; - - try - { - if (String.IsNullOrEmpty(location?.Trim())) - { - throw new ArgumentException($"{nameof(location)} is required."); - } - - var columns = new List { "Lat_VC", "Lng_VC" }; - var retrieveOperation = TableOperation.Retrieve( - area, - WebUtility.UrlEncode(address), - columns - ); + throw new SaveException(locationEntity.RowKey, ex); + } + } - await _incidentTable.CreateIfNotExistsAsync(); - var result = await _incidentTable.ExecuteAsync(retrieveOperation); + public async Task FindByAddressAsync(string location, string area) + { + var address = $"{location}, {area}"; - return (LocationEntity)result.Result; - } - catch (Exception ex) + try + { + if (String.IsNullOrEmpty(location?.Trim())) { - throw new FindByAddressException(address, ex); + throw new ArgumentException($"{nameof(location)} is required."); } - } - public class FindByAddressException : Exception - { - public FindByAddressException(string key, Exception ex) - : base($"Method: LocationRepository.FindByAddressAsync, RowKey: {key}", ex) { } - } + var columns = new List { "Lat_VC", "Lng_VC" }; + var retrieveOperation = TableOperation.Retrieve( + area, + WebUtility.UrlEncode(address), + columns + ); + + await _incidentTable.CreateIfNotExistsAsync(); + var result = await _incidentTable.ExecuteAsync(retrieveOperation); - public class SaveException : Exception + return (LocationEntity)result.Result; + } + catch (Exception ex) { - public SaveException(string key, Exception ex) - : base($"Method: LocationRepository.SaveAsync, RowKey: {key}", ex) { } + throw new FindByAddressException(address, ex); } } + + public class FindByAddressException(string key, Exception ex) + : Exception($"Method: LocationRepository.FindByAddressAsync, RowKey: {key}", ex) { } + + public class SaveException(string key, Exception ex) + : Exception($"Method: LocationRepository.SaveAsync, RowKey: {key}", ex) { } } diff --git a/apps/func/src/CentralPennIncidentsFunc/Schedules/EndPointKeepWarm.cs b/apps/func/src/CentralPennIncidentsFunc/Schedules/EndPointKeepWarm.cs index 860af89..bc58229 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Schedules/EndPointKeepWarm.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Schedules/EndPointKeepWarm.cs @@ -6,111 +6,103 @@ using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; -namespace CentralPennIncidentsFunc.Schedules +namespace CentralPennIncidentsFunc.Schedules; + +public class EndPointKeepWarm( + IHttpClientFactory httpClientFactory, + ILogger log, + IEnvironmentProvider env +) { - public class EndPointKeepWarm + private readonly HttpClient _httpClient = httpClientFactory.CreateClient(); + private readonly ILogger _log = log; + private readonly IEnvironmentProvider _env = env; + + // run every 15 minutes.. + [Function("EndPointKeepWarm")] + public async Task Run([TimerTrigger("0 */4 * * * *")] MyInfo timerInfo) { - private readonly HttpClient _httpClient; - private readonly ILogger _log; - private readonly IEnvironmentProvider _env; + _log.LogInformation( + "Function Ran. Next timer schedule = {TimerSchedule}", + timerInfo.ScheduleStatus.Next + ); - public EndPointKeepWarm( - IHttpClientFactory httpClientFactory, - ILogger log, - IEnvironmentProvider env - ) - { - _env = env; - _log = log; - _httpClient = httpClientFactory.CreateClient(); - } + _log.LogInformation( + "Run(): EndPointKeepWarm function executed at: {Now}. Past due? {PastDue}", + DateTime.Now, + timerInfo.IsPastDue + ); - // run every 15 minutes.. - [Function("EndPointKeepWarm")] - public async Task Run([TimerTrigger("0 */4 * * * *")] MyInfo timerInfo) + foreach (var endpointFunc in GetEndpoints()) { - _log.LogInformation( - "Function Ran. Next timer schedule = {TimerSchedule}", - timerInfo.ScheduleStatus.Next - ); - - _log.LogInformation( - "Run(): EndPointKeepWarm function executed at: {Now}. Past due? {PastDue}", - DateTime.Now, - timerInfo.IsPastDue - ); - - foreach (var endpointFunc in GetEndpoints()) + try { - try - { - var endpoint = endpointFunc(); - - _log.LogInformation("Run(): About to hit URL: '{Endpoint}'", endpoint); - _ = await HitUrl(endpoint); - } - catch (Exception ex) - { - _log.LogError(ex, "{Message}", ex.Message); - } - } - - _log.LogInformation($"Run(): Completed."); - } - - private IEnumerable> GetEndpoints() - { - var endPointsString = _env.GetEnvironmentVariable("EndPointUrls"); + var endpoint = endpointFunc(); - if (!string.IsNullOrEmpty(endPointsString)) - { - string[] endPoints = endPointsString.Split(';'); - foreach (string endPoint in endPoints) - { - yield return () => endPoint.Trim(); - } + _log.LogInformation("Run(): About to hit URL: '{Endpoint}'", endpoint); + _ = await HitUrl(endpoint); } - else + catch (Exception ex) { - yield return () => - throw new Exception( - $"Run(): No URLs specified in environment variable 'EndPointUrls'. Expected a single URL or multiple URLs " - + "separated with a semi-colon (;). Please add this config to use the tool." - ); + _log.LogError(ex, "{Message}", ex.Message); } } - private async Task HitUrl(string url) + _log.LogInformation($"Run(): Completed."); + } + + private IEnumerable> GetEndpoints() + { + var endPointsString = _env.GetEnvironmentVariable("EndPointUrls"); + + if (!string.IsNullOrEmpty(endPointsString)) { - HttpResponseMessage response = await _httpClient.GetAsync(url); - if (response.IsSuccessStatusCode) + string[] endPoints = endPointsString.Split(';'); + foreach (string endPoint in endPoints) { - _log.LogInformation("hitUrl(): Successfully hit URL: '{Url}'", url); + yield return () => endPoint.Trim(); } - else - { - _log.LogError( - "hitUrl(): Failed to hit URL: '{Url}'. Response: {StatusCode}:{ReasonPhrase}", - url, - (int)response.StatusCode, - response.ReasonPhrase + } + else + { + yield return () => + throw new Exception( + $"Run(): No URLs specified in environment variable 'EndPointUrls'. Expected a single URL or multiple URLs " + + "separated with a semi-colon (;). Please add this config to use the tool." ); - } - - return response; } + } - public class MyInfo + private async Task HitUrl(string url) + { + HttpResponseMessage response = await _httpClient.GetAsync(url); + if (response.IsSuccessStatusCode) { - public MyScheduleStatus ScheduleStatus { get; set; } - public bool IsPastDue { get; set; } + _log.LogInformation("hitUrl(): Successfully hit URL: '{Url}'", url); } - - public class MyScheduleStatus + else { - public DateTime Last { get; set; } - public DateTime Next { get; set; } - public DateTime LastUpdated { get; set; } + _log.LogError( + "hitUrl(): Failed to hit URL: '{Url}'. Response: {StatusCode}:{ReasonPhrase}", + url, + (int)response.StatusCode, + response.ReasonPhrase + ); } + + return response; + } + + public class MyInfo + { + public MyScheduleStatus ScheduleStatus { get; set; } + public bool IsPastDue { get; set; } + } + + public class MyScheduleStatus + { + public DateTime Last { get; set; } + public DateTime Next { get; set; } + public DateTime LastUpdated { get; set; } } } diff --git a/apps/func/src/CentralPennIncidentsFunc/Services/FeedCache.cs b/apps/func/src/CentralPennIncidentsFunc/Services/FeedCache.cs index b143a24..0d065c0 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Services/FeedCache.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Services/FeedCache.cs @@ -4,27 +4,21 @@ using CentralPennIncidentsFunc.Models; using Microsoft.Extensions.Caching.Memory; -namespace CentralPennIncidentsFunc.Services -{ - public class FeedCache : IDataCache> - { - private readonly IMemoryCache _memoryCache; +namespace CentralPennIncidentsFunc.Services; - public FeedCache(IMemoryCache memoryCache) - { - _memoryCache = memoryCache; - } +public class FeedCache(IMemoryCache memoryCache) : IDataCache> +{ + private readonly IMemoryCache _memoryCache = memoryCache; - public void SaveValue(string key, IEnumerable value) - { - using var entry = _memoryCache.CreateEntry(key); - entry.Value = value; - entry.AbsoluteExpiration = DateTime.UtcNow.AddSeconds(15); - } + public void SaveValue(string key, IEnumerable value) + { + using var entry = _memoryCache.CreateEntry(key); + entry.Value = value; + entry.AbsoluteExpiration = DateTime.UtcNow.AddSeconds(15); + } - public bool TryGetValue(string key, out IEnumerable value) - { - return _memoryCache.TryGetValue(key, out value); - } + public bool TryGetValue(string key, out IEnumerable value) + { + return _memoryCache.TryGetValue(key, out value); } } diff --git a/apps/func/src/CentralPennIncidentsFunc/Services/FeedService.cs b/apps/func/src/CentralPennIncidentsFunc/Services/FeedService.cs index 14e5ef5..1df56dd 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Services/FeedService.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Services/FeedService.cs @@ -7,19 +7,13 @@ namespace CentralPennIncidentsFunc.Services; -public class FeedService : IFeedService +public class FeedService( + IEnumerable incidentProviders, + ILogger logger +) : IFeedService { - private readonly IEnumerable _incidentProviders; - private readonly ILogger _logger; - - public FeedService( - IEnumerable incidentProviders, - ILogger logger - ) - { - _incidentProviders = incidentProviders; - _logger = logger; - } + private readonly IEnumerable _incidentProviders = incidentProviders; + private readonly ILogger _logger = logger; public async Task GetIncidentAsync(GlobalId globalId) { diff --git a/apps/func/src/CentralPennIncidentsFunc/Services/LocationCache.cs b/apps/func/src/CentralPennIncidentsFunc/Services/LocationCache.cs index cc665a5..40c7e4c 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Services/LocationCache.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Services/LocationCache.cs @@ -3,27 +3,21 @@ using CentralPennIncidentsFunc.Models; using Microsoft.Extensions.Caching.Memory; -namespace CentralPennIncidentsFunc.Services -{ - public class LocationCache : IDataCache<(string, string), LocationEntity> - { - private readonly IMemoryCache _memoryCache; +namespace CentralPennIncidentsFunc.Services; - public LocationCache(IMemoryCache memoryCache) - { - _memoryCache = memoryCache; - } +public class LocationCache(IMemoryCache memoryCache) : IDataCache<(string, string), LocationEntity> +{ + private readonly IMemoryCache _memoryCache = memoryCache; - public void SaveValue((string, string) key, LocationEntity value) - { - using var entry = _memoryCache.CreateEntry(key); - entry.Value = value; - entry.AbsoluteExpiration = DateTime.UtcNow.AddHours(24); - } + public void SaveValue((string, string) key, LocationEntity value) + { + using var entry = _memoryCache.CreateEntry(key); + entry.Value = value; + entry.AbsoluteExpiration = DateTime.UtcNow.AddHours(24); + } - public bool TryGetValue((string, string) key, out LocationEntity value) - { - return _memoryCache.TryGetValue(key, out value); - } + public bool TryGetValue((string, string) key, out LocationEntity value) + { + return _memoryCache.TryGetValue(key, out value); } } diff --git a/apps/func/src/CentralPennIncidentsFunc/Services/LocationService.cs b/apps/func/src/CentralPennIncidentsFunc/Services/LocationService.cs index 2caafc8..13c5aac 100644 --- a/apps/func/src/CentralPennIncidentsFunc/Services/LocationService.cs +++ b/apps/func/src/CentralPennIncidentsFunc/Services/LocationService.cs @@ -8,95 +8,85 @@ using Microsoft.Extensions.Logging; using QuickType; -namespace CentralPennIncidentsFunc.Services +namespace CentralPennIncidentsFunc.Services; + +public class LocationService( + ILocationRepository locationRepository, + IDataCache<(string, string), LocationEntity> locationCache, + IHttpClientFactory httpClientFactory, + IEnvironmentProvider env, + ILogger log +) : ILocationService { - public class LocationService : ILocationService + const string GEOCODE_API = "https://maps.googleapis.com/maps/api/geocode/json"; + + private readonly IDataCache<(string, string), LocationEntity> _locationCache = locationCache; + private readonly ILocationRepository _locationRepository = locationRepository; + private readonly HttpClient _client = httpClientFactory.CreateClient(); + private readonly ILogger _log = log; + private readonly string _googleApiKey = env.GetEnvironmentVariable("GOOGLE_API_KEY"); + + public async Task GetLocationAsync(string location, string area) { - const string GEOCODE_API = "https://maps.googleapis.com/maps/api/geocode/json"; - - private readonly IDataCache<(string, string), LocationEntity> _locationCache; - private readonly ILocationRepository _locationRepository; - private readonly HttpClient _client; - private readonly ILogger _log; - private readonly string _googleApiKey; - - public LocationService( - ILocationRepository locationRepository, - IDataCache<(string, string), LocationEntity> locationCache, - IHttpClientFactory httpClientFactory, - IEnvironmentProvider env, - ILogger log - ) + if (string.IsNullOrEmpty(location) || string.IsNullOrEmpty(area)) { - _locationCache = locationCache; - _locationRepository = locationRepository; - _log = log; - _client = httpClientFactory.CreateClient(); - _googleApiKey = env.GetEnvironmentVariable("GOOGLE_API_KEY"); + return null; } - public async Task GetLocationAsync(string location, string area) - { - if (string.IsNullOrEmpty(location) || string.IsNullOrEmpty(area)) - { - return null; - } + var key = (location, area); - var key = (location, area); + if (_locationCache.TryGetValue(key, out var locationEntity)) + { + return locationEntity; + } - if (_locationCache.TryGetValue(key, out var locationEntity)) - { - return locationEntity; - } + try + { + locationEntity = await _locationRepository.FindByAddressAsync(location, area); - try + if (locationEntity is null || !locationEntity.Lat_VC.HasValue) { - locationEntity = await _locationRepository.FindByAddressAsync(location, area); + var address = $"{location}, {area}"; - if (locationEntity is null || !locationEntity.Lat_VC.HasValue) + var geocode = await GetGeocodeAsync(address, _googleApiKey); + var geocodeLocation = geocode?.Results?.FirstOrDefault()?.Geometry?.Location; + if (geocodeLocation is null) { - var address = $"{location}, {area}"; - - var geocode = await GetGeocodeAsync(address, _googleApiKey); - var geocodeLocation = geocode?.Results?.FirstOrDefault()?.Geometry?.Location; - if (geocodeLocation is null) - { - return null; - } - - locationEntity = new LocationEntity(area, address) - { - Lat_VC = geocodeLocation.Lat, - Lng_VC = geocodeLocation.Lng - }; - - await _locationRepository.SaveAsync(locationEntity); + return null; } - _locationCache.SaveValue(key, locationEntity); + locationEntity = new LocationEntity(area, address) + { + Lat_VC = geocodeLocation.Lat, + Lng_VC = geocodeLocation.Lng + }; - return locationEntity; - } - catch (Exception ex) - { - _log.LogError(ex, "LocationService.GetLocationAsync"); + await _locationRepository.SaveAsync(locationEntity); } - return null; - } + _locationCache.SaveValue(key, locationEntity); - async Task GetGeocodeAsync(string address, string apiKey) + return locationEntity; + } + catch (Exception ex) { - var encodedAddress = WebUtility.UrlEncode(address); - var apiFetchUrl = - $"{GEOCODE_API}?address={encodedAddress}&key={apiKey}&_={DateTime.Now.Ticks}"; + _log.LogError(ex, "LocationService.GetLocationAsync"); + } - var res = await _client.GetAsync(apiFetchUrl); - res.EnsureSuccessStatusCode(); + return null; + } - var json = await res.Content.ReadAsStringAsync(); + async Task GetGeocodeAsync(string address, string apiKey) + { + var encodedAddress = WebUtility.UrlEncode(address); + var apiFetchUrl = + $"{GEOCODE_API}?address={encodedAddress}&key={apiKey}&_={DateTime.Now.Ticks}"; - return Geocode.FromJson(json); - } + var res = await _client.GetAsync(apiFetchUrl); + res.EnsureSuccessStatusCode(); + + var json = await res.Content.ReadAsStringAsync(); + + return Geocode.FromJson(json); } }