From 3ee912354a5d67377e86b2f8f7ee1db440175acd Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Sat, 18 Nov 2023 22:03:47 +0100 Subject: [PATCH 01/28] CODE RUB: Clean up --- PlanetDotnet/Infrastructure/CombinedFeedSource.cs | 10 +++++----- PlanetDotnet/LoadFeedsFunction.cs | 12 ++++++------ PlanetDotnetAuthors/AuthorsLoader.cs | 6 +++--- PlanetDotnetAuthors/Models/Author.cs | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/PlanetDotnet/Infrastructure/CombinedFeedSource.cs b/PlanetDotnet/Infrastructure/CombinedFeedSource.cs index 7e77cb3..61b204f 100644 --- a/PlanetDotnet/Infrastructure/CombinedFeedSource.cs +++ b/PlanetDotnet/Infrastructure/CombinedFeedSource.cs @@ -1,8 +1,3 @@ -using Microsoft.Extensions.Logging; -using PlanetDotnet.Extensions; -using PlanetDotnetAuthors.Models; -using Polly; -using Polly.Retry; using System; using System.Collections.Generic; using System.Linq; @@ -12,6 +7,11 @@ using System.ServiceModel.Syndication; using System.Threading.Tasks; using System.Xml; +using Microsoft.Extensions.Logging; +using PlanetDotnet.Extensions; +using PlanetDotnetAuthors.Models; +using Polly; +using Polly.Retry; namespace PlanetDotnet.Infrastructure { diff --git a/PlanetDotnet/LoadFeedsFunction.cs b/PlanetDotnet/LoadFeedsFunction.cs index 3b8b51e..00451f0 100644 --- a/PlanetDotnet/LoadFeedsFunction.cs +++ b/PlanetDotnet/LoadFeedsFunction.cs @@ -1,15 +1,15 @@ -using Azure.Storage.Blobs; -using Azure.Storage.Blobs.Models; -using Microsoft.Azure.WebJobs; -using Microsoft.Extensions.Logging; -using PlanetDotnet.Infrastructure; -using PlanetDotnetAuthors; using System; using System.IO; using System.Linq; using System.ServiceModel.Syndication; using System.Threading.Tasks; using System.Xml; +using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Models; +using Microsoft.Azure.WebJobs; +using Microsoft.Extensions.Logging; +using PlanetDotnet.Infrastructure; +using PlanetDotnetAuthors; namespace PlanetDotnet { diff --git a/PlanetDotnetAuthors/AuthorsLoader.cs b/PlanetDotnetAuthors/AuthorsLoader.cs index 3648351..031840f 100644 --- a/PlanetDotnetAuthors/AuthorsLoader.cs +++ b/PlanetDotnetAuthors/AuthorsLoader.cs @@ -1,11 +1,11 @@ -using Newtonsoft.Json; -using PlanetDotnetAuthors.Models; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Newtonsoft.Json; +using PlanetDotnetAuthors.Models; namespace PlanetDotnetAuthors { diff --git a/PlanetDotnetAuthors/Models/Author.cs b/PlanetDotnetAuthors/Models/Author.cs index 0d1e683..6634bc5 100644 --- a/PlanetDotnetAuthors/Models/Author.cs +++ b/PlanetDotnetAuthors/Models/Author.cs @@ -1,7 +1,7 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; namespace PlanetDotnetAuthors.Models { From b2fd4b18a92060e0353274101e43dfe32e124b25 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Sat, 18 Nov 2023 22:07:46 +0100 Subject: [PATCH 02/28] CODE RUB: Clean up --- Authors/AlejandroRuiz.json | 19 -- Authors/AnbuMani27.json | 19 -- Authors/Cheesebaron.json | 19 -- Authors/DamianMehers.json | 19 -- Authors/DanRigby.json | 19 -- Authors/JesseLiberty.json | 19 -- Authors/JoeM-RP.json | 19 -- Authors/JonDouglas.json | 19 -- Authors/JuuCustodio.json | 19 -- Authors/LeomarisReyes.json | 19 -- Authors/LetsCreateSeries.json | 19 -- Authors/MarcBruins.json | 19 -- Authors/MartinZikmund.json | 19 -- Authors/Martynnw.json | 19 -- Authors/MergeConflictFM.json | 19 -- Authors/PieEatingNinjas.json | 19 -- Authors/Prin53.json | 19 -- Authors/Pujolsluis.json | 19 -- Authors/ReactiveUI.json | 19 -- Authors/Sankra.json | 19 -- Authors/StefanRiedmann.json | 19 -- Authors/TheFo2sh.json | 19 -- Authors/TheXamarinShow.json | 19 -- Authors/TomSoderling.json | 19 -- Authors/UdaraAlwis.json | 19 -- Authors/VicenteGuzman.json | 19 -- Authors/XamarinPodcast.json | 19 -- Authors/acaliaro.json | 19 -- Authors/ahoefling.json | 19 -- Authors/akamud.json | 19 -- Authors/alexsorokoletov.json | 19 -- Authors/almirvuk.json | 19 -- Authors/andreas-nesheim.json | 19 -- Authors/aritchie.json | 19 -- Authors/asfend.json | 19 -- Authors/aspnetde.json | 19 -- Authors/balivo.json | 19 -- Authors/banditoth.json | 19 -- Authors/basdecort.json | 19 -- Authors/bbenetskyy.json | 19 -- Authors/brminnick.json | 19 -- Authors/canbilgin.json | 19 -- Authors/chamons.json | 19 -- Authors/char0394.json | 19 -- Authors/cjlotz.json | 19 -- Authors/codemillmatt.json | 19 -- Authors/crswlls.json | 19 -- Authors/damienaicheh.json | 19 -- Authors/damiendoumer.json | 19 -- Authors/danielcauser.json | 19 -- Authors/danielmonettelli.json | 20 -- Authors/dansiegel.json | 19 -- Authors/davidbritch.json | 21 -- Authors/dhindrik.json | 19 -- Authors/divikiran.json | 19 -- Authors/dylanberry.json | 19 -- Authors/egbakou.json | 19 -- Authors/elbrinner.json | 19 -- Authors/felipebaltazar.json | 19 -- Authors/filipoff2.json | 19 -- Authors/framinosona.json | 19 -- Authors/gonemobilecast.json | 19 -- Authors/gptucci.json | 19 -- Authors/gshackles.json | 19 -- Authors/hnabbasi.json | 19 -- Authors/ionixjunior.json | 19 -- Authors/jamesmontemagno.json | 19 -- Authors/jesulink2514.json | 20 -- Authors/jfversluis.json | 19 -- Authors/jgiacomini.json | 19 -- Authors/jimbobbennett.json | 19 -- Authors/johnthiriet.json | 19 -- Authors/jssuthahar.json | 19 -- Authors/jsuarezruiz.json | 19 -- Authors/jtaubensee.json | 19 -- Authors/kent_boogaart.json | 19 -- Authors/kphillpotts.json | 19 -- Authors/kwlothrop.json | 19 -- Authors/logeshpalani98.json | 19 -- Authors/luismts.json | 20 -- Authors/mallibone.json | 19 -- Authors/marcofolio.json | 19 -- Authors/marcusts.json | 19 -- Authors/markolazic88.json | 19 -- Authors/martijn00.json | 19 -- Authors/mattleibow.json | 19 -- Authors/mfractor.json | 19 -- Authors/mike-grant.json | 19 -- Authors/mindofai.json | 19 -- Authors/mkieres.json | 19 -- Authors/msiccdev.json | 19 -- Authors/nickrandolph.json | 19 -- Authors/nigelferrissey.json | 19 -- Authors/officialdoniald.json | 19 -- Authors/peterfoot.json | 19 -- Authors/ramonesteban78.json | 19 -- Authors/rdavisau.json | 19 -- Authors/rdelrosario.json | 19 -- Authors/redth.json | 19 -- Authors/ricardoprestes.json | 19 -- Authors/rid00z.json | 19 -- Authors/robintschroeder.json | 19 -- Authors/roubachof.json | 19 -- Authors/saamerm.json | 19 -- Authors/sact1909.json | 19 -- Authors/samirgcofficial.json | 19 -- Authors/shirshov.json | 19 -- Authors/smstuebe.json | 19 -- Authors/sthewissen.json | 19 -- Authors/stvansolano.json | 19 -- Authors/susairajs.json | 19 -- Authors/syncfusion.json | 19 -- Authors/tbertuzzi.json | 19 -- Authors/telerik.json | 19 -- Authors/trailheadtechnology.json | 19 -- Authors/vulcanlee.json | 19 -- Authors/willsb.json | 19 -- Authors/winstongubantes.json | 19 -- Authors/wislon.json | 19 -- Authors/xablu.json | 19 -- Authors/xamarin.json | 19 -- Authors/xamarinhowto.json | 19 -- Authors/yuv4ik.json | 20 -- PlanetDotnet.sln | 23 -- .../Extensions/SyndicationItemExtensions.cs | 48 ---- .../Infrastructure/CombinedFeedSource.cs | 213 ------------------ PlanetDotnet/LoadFeedsFunction.cs | 87 ------- PlanetDotnet/PlanetDotnet.csproj | 24 -- .../Properties/serviceDependencies.json | 8 - .../Properties/serviceDependencies.local.json | 8 - PlanetDotnet/host.json | 11 - PlanetDotnet/local.settings.json | 12 - PlanetDotnetAuthors/AuthorsLoader.cs | 36 --- PlanetDotnetAuthors/Models/Author.cs | 40 ---- PlanetDotnetAuthors/Models/GeoPosition.cs | 20 -- .../PlanetDotnetAuthors.csproj | 16 -- author-schema.json | 88 -------- 137 files changed, 2977 deletions(-) delete mode 100644 Authors/AlejandroRuiz.json delete mode 100644 Authors/AnbuMani27.json delete mode 100644 Authors/Cheesebaron.json delete mode 100644 Authors/DamianMehers.json delete mode 100644 Authors/DanRigby.json delete mode 100644 Authors/JesseLiberty.json delete mode 100644 Authors/JoeM-RP.json delete mode 100644 Authors/JonDouglas.json delete mode 100644 Authors/JuuCustodio.json delete mode 100644 Authors/LeomarisReyes.json delete mode 100644 Authors/LetsCreateSeries.json delete mode 100644 Authors/MarcBruins.json delete mode 100644 Authors/MartinZikmund.json delete mode 100644 Authors/Martynnw.json delete mode 100644 Authors/MergeConflictFM.json delete mode 100644 Authors/PieEatingNinjas.json delete mode 100644 Authors/Prin53.json delete mode 100644 Authors/Pujolsluis.json delete mode 100644 Authors/ReactiveUI.json delete mode 100644 Authors/Sankra.json delete mode 100644 Authors/StefanRiedmann.json delete mode 100644 Authors/TheFo2sh.json delete mode 100644 Authors/TheXamarinShow.json delete mode 100644 Authors/TomSoderling.json delete mode 100644 Authors/UdaraAlwis.json delete mode 100644 Authors/VicenteGuzman.json delete mode 100644 Authors/XamarinPodcast.json delete mode 100644 Authors/acaliaro.json delete mode 100644 Authors/ahoefling.json delete mode 100644 Authors/akamud.json delete mode 100644 Authors/alexsorokoletov.json delete mode 100644 Authors/almirvuk.json delete mode 100644 Authors/andreas-nesheim.json delete mode 100644 Authors/aritchie.json delete mode 100644 Authors/asfend.json delete mode 100644 Authors/aspnetde.json delete mode 100644 Authors/balivo.json delete mode 100644 Authors/banditoth.json delete mode 100644 Authors/basdecort.json delete mode 100644 Authors/bbenetskyy.json delete mode 100644 Authors/brminnick.json delete mode 100644 Authors/canbilgin.json delete mode 100644 Authors/chamons.json delete mode 100644 Authors/char0394.json delete mode 100644 Authors/cjlotz.json delete mode 100644 Authors/codemillmatt.json delete mode 100644 Authors/crswlls.json delete mode 100644 Authors/damienaicheh.json delete mode 100644 Authors/damiendoumer.json delete mode 100644 Authors/danielcauser.json delete mode 100644 Authors/danielmonettelli.json delete mode 100644 Authors/dansiegel.json delete mode 100644 Authors/davidbritch.json delete mode 100644 Authors/dhindrik.json delete mode 100644 Authors/divikiran.json delete mode 100644 Authors/dylanberry.json delete mode 100644 Authors/egbakou.json delete mode 100644 Authors/elbrinner.json delete mode 100644 Authors/felipebaltazar.json delete mode 100644 Authors/filipoff2.json delete mode 100644 Authors/framinosona.json delete mode 100644 Authors/gonemobilecast.json delete mode 100644 Authors/gptucci.json delete mode 100644 Authors/gshackles.json delete mode 100644 Authors/hnabbasi.json delete mode 100644 Authors/ionixjunior.json delete mode 100644 Authors/jamesmontemagno.json delete mode 100644 Authors/jesulink2514.json delete mode 100644 Authors/jfversluis.json delete mode 100644 Authors/jgiacomini.json delete mode 100644 Authors/jimbobbennett.json delete mode 100644 Authors/johnthiriet.json delete mode 100644 Authors/jssuthahar.json delete mode 100644 Authors/jsuarezruiz.json delete mode 100644 Authors/jtaubensee.json delete mode 100644 Authors/kent_boogaart.json delete mode 100644 Authors/kphillpotts.json delete mode 100644 Authors/kwlothrop.json delete mode 100644 Authors/logeshpalani98.json delete mode 100644 Authors/luismts.json delete mode 100644 Authors/mallibone.json delete mode 100644 Authors/marcofolio.json delete mode 100644 Authors/marcusts.json delete mode 100644 Authors/markolazic88.json delete mode 100644 Authors/martijn00.json delete mode 100644 Authors/mattleibow.json delete mode 100644 Authors/mfractor.json delete mode 100644 Authors/mike-grant.json delete mode 100644 Authors/mindofai.json delete mode 100644 Authors/mkieres.json delete mode 100644 Authors/msiccdev.json delete mode 100644 Authors/nickrandolph.json delete mode 100644 Authors/nigelferrissey.json delete mode 100644 Authors/officialdoniald.json delete mode 100644 Authors/peterfoot.json delete mode 100644 Authors/ramonesteban78.json delete mode 100644 Authors/rdavisau.json delete mode 100644 Authors/rdelrosario.json delete mode 100644 Authors/redth.json delete mode 100644 Authors/ricardoprestes.json delete mode 100644 Authors/rid00z.json delete mode 100644 Authors/robintschroeder.json delete mode 100644 Authors/roubachof.json delete mode 100644 Authors/saamerm.json delete mode 100644 Authors/sact1909.json delete mode 100644 Authors/samirgcofficial.json delete mode 100644 Authors/shirshov.json delete mode 100644 Authors/smstuebe.json delete mode 100644 Authors/sthewissen.json delete mode 100644 Authors/stvansolano.json delete mode 100644 Authors/susairajs.json delete mode 100644 Authors/syncfusion.json delete mode 100644 Authors/tbertuzzi.json delete mode 100644 Authors/telerik.json delete mode 100644 Authors/trailheadtechnology.json delete mode 100644 Authors/vulcanlee.json delete mode 100644 Authors/willsb.json delete mode 100644 Authors/winstongubantes.json delete mode 100644 Authors/wislon.json delete mode 100644 Authors/xablu.json delete mode 100644 Authors/xamarin.json delete mode 100644 Authors/xamarinhowto.json delete mode 100644 Authors/yuv4ik.json delete mode 100644 PlanetDotnet/Extensions/SyndicationItemExtensions.cs delete mode 100644 PlanetDotnet/Infrastructure/CombinedFeedSource.cs delete mode 100644 PlanetDotnet/LoadFeedsFunction.cs delete mode 100644 PlanetDotnet/PlanetDotnet.csproj delete mode 100644 PlanetDotnet/Properties/serviceDependencies.json delete mode 100644 PlanetDotnet/Properties/serviceDependencies.local.json delete mode 100644 PlanetDotnet/host.json delete mode 100644 PlanetDotnet/local.settings.json delete mode 100644 PlanetDotnetAuthors/AuthorsLoader.cs delete mode 100644 PlanetDotnetAuthors/Models/Author.cs delete mode 100644 PlanetDotnetAuthors/Models/GeoPosition.cs delete mode 100644 PlanetDotnetAuthors/PlanetDotnetAuthors.csproj delete mode 100644 author-schema.json diff --git a/Authors/AlejandroRuiz.json b/Authors/AlejandroRuiz.json deleted file mode 100644 index 3c57034..0000000 --- a/Authors/AlejandroRuiz.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Alejandro", - "lastName": "Ruiz", - "tagOrBio": "Alejandro Ruiz is a Xamarin MVP, C# & Open Source Lover", - "stateOrRegion": "Guadalajara, Mexico", - "emailAddress": "alejandro@alejandroruizvarela.com", - "webSite": "https://alejandroruizvarela.blogspot.mx", - "feedUris": [ - "https://alejandroruizvarela.blogspot.mx/rss.xml" - ], - "twitterHandle": "alejandroruizva", - "gravatarHash": "35d0fff7dbc133e9fe2075aa14205a57", - "githubHandle": "AlejandroRuiz", - "position": { - "lat": 20.66682, - "lon": -103.39182 - }, - "languageCode": "es" -} \ No newline at end of file diff --git a/Authors/AnbuMani27.json b/Authors/AnbuMani27.json deleted file mode 100644 index 6927891..0000000 --- a/Authors/AnbuMani27.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Anbu", - "lastName": "Mani", - "tagOrBio": "is a Microsoft MVP who Blogger, Speaker, Founder & Organizer of XMonkeys360 Community – India.", - "emailAddress": "anbumani@xmonkeys360.com", - "twitterHandle": "anbu_mani27", - "gravatarHash": "f97650474e4aa5b9609a28dcfdb052d4", - "stateOrRegion": "Chennai, India", - "webSite": "https://www.xmonkeys360.com/", - "position": { - "lat": 12.902749, - "lon": 80.190846 - }, - "feedUris": [ - "https://xmonkeys360.com/feed/" - ], - "githubHandle": "AnbuMani27", - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/Cheesebaron.json b/Authors/Cheesebaron.json deleted file mode 100644 index f218416..0000000 --- a/Authors/Cheesebaron.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "feedUris": [ - "https://blog.ostebaronen.dk/feed.xml" - ], - "firstName": "Tomasz", - "lastName": "Cielecki", - "stateOrRegion": "Copenhagen, Denmark", - "emailAddress": "tomasz@ostebaronen.dk", - "tagOrBio": "Open Source all the things!", - "webSite": "https://blog.ostebaronen.dk", - "twitterHandle": "Cheesebaron", - "githubHandle": "Cheesebaron", - "gravatarHash": "f780d57997526876b0625e517c1e0884", - "position": { - "lat": 55.8019193, - "lon": 12.523124 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/DamianMehers.json b/Authors/DamianMehers.json deleted file mode 100644 index 78a0e5d..0000000 --- a/Authors/DamianMehers.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Damian", - "lastName": "Mehers", - "tagOrBio": "Independent Xamarin Developer", - "stateOrRegion": "Geneva, Switzerland", - "emailAddress": "damian@mehers.com", - "twitterHandle": "DamianMehers", - "gravatarHash": "d77613f4e20bfcae401a6bf0018a83d1", - "githubHandle": "DamianMehers", - "position": { - "lat": 46.3635288, - "lon": 6.1860801 - }, - "webSite": "https://damian.fyi/", - "feedUris": [ - "https://damian.fyi/feed/Xamarin.xml" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/DanRigby.json b/Authors/DanRigby.json deleted file mode 100644 index 7284a50..0000000 --- a/Authors/DanRigby.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Dan", - "lastName": "Rigby", - "tagOrBio": "is a Technical Solutions Professional", - "stateOrRegion": "Raleigh, North Carolina", - "emailAddress": "Dan.Rigby@Microsoft.com", - "twitterHandle": "DanRigby", - "githubHandle": "DanRigby", - "gravatarHash": "f025f772418fbcfd3a1e15a74bf0f8a4", - "position": { - "lat": 35.77959, - "lon": -78.638179 - }, - "webSite": "https://danrigby.com/", - "feedUris": [ - "https://feeds.feedburner.com/DanRigby" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/JesseLiberty.json b/Authors/JesseLiberty.json deleted file mode 100644 index 52dadc4..0000000 --- a/Authors/JesseLiberty.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Jesse", - "lastName": "Liberty", - "tagOrBio": "See http://jesseliberty.me", - "stateOrRegion": "Massachusetts", - "emailAddress": "jesseliberty@gmail.com", - "twitterHandle": "jesseliberty", - "gravatarHash": "78d5b6609fe5a80ce67e9f971833a6c3", - "githubHandle": "JesseLiberty", - "position": { - "lat": 42.4703963, - "lon": -71.4477468 - }, - "webSite": "http://jesseliberty.me", - "feedUris": [ - "https://feeds.feedburner.com/JesseLiberty-SilverlightGeek" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/JoeM-RP.json b/Authors/JoeM-RP.json deleted file mode 100644 index 0b3c109..0000000 --- a/Authors/JoeM-RP.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Joe", - "lastName": "Meyer", - "stateOrRegion": "Chicago, IL", - "twitterHandle": "iwritecodesmtms", - "emailAddress": "joseph.w.meyer@live.com", - "tagOrBio": "I write code sometimes", - "gravatarHash": "1431dd2c4749b0a178c7a3130e71831e", - "webSite": "https://iwritecodesometimes.net", - "githubHandle": "JoeM-RP", - "position": { - "lat": 41.92, - "lon": -87.65 - }, - "feedUris": [ - "https://iwritecodesometimes.net/feed/" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/JonDouglas.json b/Authors/JonDouglas.json deleted file mode 100644 index 98a9585..0000000 --- a/Authors/JonDouglas.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Jon", - "lastName": "Douglas", - "tagOrBio": "", - "stateOrRegion": "Utah", - "emailAddress": "", - "twitterHandle": "_jondouglas", - "webSite": "https://www.jon-douglas.com/", - "feedUris": [ - "https://www.jon-douglas.com/atom.xml" - ], - "gravatarHash": "83d67df0b9e002d1c55a2786aeeb0c1b", - "githubHandle": "JonDouglas", - "position": { - "lat": 39.32098, - "lon": -111.093731 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/JuuCustodio.json b/Authors/JuuCustodio.json deleted file mode 100644 index 1b6ac4d..0000000 --- a/Authors/JuuCustodio.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Juliano", - "lastName": "Custódio", - "stateOrRegion": "Alphaville - Barueri, Brasil", - "twitterHandle": "JuuCustodio", - "githubHandle": "JuuCustodio", - "tagOrBio": "Solutions Architect, Blogger and Speaker", - "emailAddress": "juliano.custodio@hotmail.com.br", - "gravatarHash": "71de9936f2ffbc93e9918066479331f1", - "position": { - "lat": -23.4880831, - "lon": -46.8496769 - }, - "webSite": "https://www.julianocustodio.com", - "feedUris": [ - "https://julianocustodio.com/tag/xamarin/rss/" - ], - "languageCode": "pt" -} \ No newline at end of file diff --git a/Authors/LeomarisReyes.json b/Authors/LeomarisReyes.json deleted file mode 100644 index 1b7b9fd..0000000 --- a/Authors/LeomarisReyes.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Leomaris", - "lastName": "Reyes", - "tagOrBio": "is a software engineer", - "stateOrRegion": "Dominican Republic", - "emailAddress": "reyes.leomaris@gmail.com", - "twitterHandle": "leomarisreyes11", - "gravatarHash": "ae78e84a683611c7b72c9ba829c125e0", - "githubHandle": "LeomarisReyes", - "position": { - "lat": 18.47088, - "lon": -69.911525 - }, - "webSite": "https://askxammy.com/", - "feedUris": [ - "https://askxammy.com/feed" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/LetsCreateSeries.json b/Authors/LetsCreateSeries.json deleted file mode 100644 index af8fdcd..0000000 --- a/Authors/LetsCreateSeries.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "LetsCreateSeries", - "lastName": "", - "stateOrRegion": "United States", - "emailAddress": "letscreate.roblox@gmail.com", - "tagOrBio": "Create Mobile Apps using Xamarin.Forms", - "webSite": "https://letscreateseries.com", - "twitterHandle": "LetsCre8Series", - "githubHandle": "LetsCreateSeries", - "gravatarHash": "10793693ff507eda06ff02e9855e774f", - "feedUris": [ - "https://letscreateseries.com/rss" - ], - "position": { - "lat": 40.6331, - "lon": 89.3985 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/MarcBruins.json b/Authors/MarcBruins.json deleted file mode 100644 index 917537f..0000000 --- a/Authors/MarcBruins.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Marc", - "lastName": "Bruins", - "tagOrBio": "is a native iOS/Android developer who fell in love with Xamarin", - "emailAddress": "marc@marcbruins.nl", - "twitterHandle": "MarcBruins", - "gravatarHash": "3795d2031be87499f76f6336ec5a3a45", - "stateOrRegion": "Utrecht, Netherlands", - "webSite": "https://www.marcbruins.nl", - "githubHandle": "MarcBruins", - "position": { - "lat": 53.219384, - "lon": 6.566502 - }, - "feedUris": [ - "https://www.marcbruins.nl/feed.xml" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/MartinZikmund.json b/Authors/MartinZikmund.json deleted file mode 100644 index 4d154cb..0000000 --- a/Authors/MartinZikmund.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Martin", - "lastName": "Zikmund", - "stateOrRegion": "Prague, Czech Republic", - "emailAddress": "martinzikmund@sphereline.com", - "tagOrBio": "is a mobile + cloud solutions developer, Microsoftie, Geocacher, regular squash player, foodie and Insanity & P90X fan", - "webSite": "https://blog.mzikmund.com/", - "twitterHandle": "MZetko", - "githubHandle": "MartinZikmund", - "gravatarHash": "d1a45c7ba013fbc3e9044ff6461f6acd", - "position": { - "lat": 50.124017, - "lon": 14.451934 - }, - "languageCode": "en", - "feedUris": [ - "https://blog.mzikmund.com/feed/?lang=en_us" - ] -} \ No newline at end of file diff --git a/Authors/Martynnw.json b/Authors/Martynnw.json deleted file mode 100644 index 0109f64..0000000 --- a/Authors/Martynnw.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Martyn", - "lastName": "Wiggins", - "tagOrBio": "Mobile Developer, Mountain Biker, Wannabe Adventurer", - "stateOrRegion": "Nottingham", - "emailAddress": "Martynnw@gmail.com", - "twitterHandle": "Martynnw", - "gravatarHash": "bf974dae53bdbf5018fbbbf928db0f4e", - "githubHandle": "Martynnw", - "position": { - "lat": 52.95, - "lon": -1.133333 - }, - "webSite": "https://martynnw.wordpress.com", - "feedUris": [ - "https://martynnw.wordpress.com/feed/" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/MergeConflictFM.json b/Authors/MergeConflictFM.json deleted file mode 100644 index 7dc4a35..0000000 --- a/Authors/MergeConflictFM.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Merge", - "lastName": "Conflict", - "stateOrRegion": "Seattle, WA", - "emailAddress": "mergeconflictfm@gmail.com", - "tagOrBio": "is a weekly development podcast hosted by Frank Krueger and James Montemagno.", - "webSite": "http://mergeconflict.fm", - "feedUris": [ - "https://feeds.fireside.fm/mergeconflict/rss" - ], - "twitterHandle": "MergeConflictFM", - "gravatarHash": "24527eb9b29a8adbfc4155db4044dd3c", - "githubHandle": "", - "position": { - "lat": 47.60621, - "lon": -122.332071 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/PieEatingNinjas.json b/Authors/PieEatingNinjas.json deleted file mode 100644 index eb0aa47..0000000 --- a/Authors/PieEatingNinjas.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Pieter", - "lastName": "Nijs", - "tagOrBio": "Senior .NET Software Engineer & Competence Leader Mobile @ Ordina Belgium. Passionate about programming, especially .NET, C#, XAML, Xamarin and UWP.", - "stateOrRegion": "Hasselt, Belgium", - "emailAddress": "pieternijs@live.be", - "twitterHandle": "nijspieter", - "gravatarHash": "61c9184b95820bdbbcd51764f3b9fb6e", - "githubHandle": "PieEatingNinjas", - "position": { - "lat": 50.93, - "lon": 5.3375 - }, - "webSite": "https://blog.pieeatingninjas.be/", - "feedUris": [ - "https://blog.pieeatingninjas.be/feed/rss" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/Prin53.json b/Authors/Prin53.json deleted file mode 100644 index 1d84a38..0000000 --- a/Authors/Prin53.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Denys", - "lastName": "Fiediaiev", - "stateOrRegion": "Ukraine", - "emailAddress": "fiediaiev@sbyte.dev", - "tagOrBio": "is a mobile developer specializing in Xamarin technology", - "webSite": "https://medium.com/@prin53", - "twitterHandle": "sbytedev", - "githubHandle": "Prin53", - "gravatarHash": "0d5df57543a53231787d7a34a9b79cd6", - "feedUris": [ - "https://medium.com/feed/@prin53" - ], - "position": { - "lat": 50.4547, - "lon": 30.5238 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/Pujolsluis.json b/Authors/Pujolsluis.json deleted file mode 100644 index bcac998..0000000 --- a/Authors/Pujolsluis.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Luis", - "lastName": "Pujols", - "stateOrRegion": "United States", - "emailAddress": "luispujolso@gmail.com", - "tagOrBio": "is a Software Engineer with a passion for Mobile Development and Software Architecture. Co-organizer of the .NET Community in the Dominican Republic and a Xamarin Lover.", - "webSite": "https://www.pujolsluis.com/", - "twitterHandle": "Pujolsluis1", - "githubHandle": "Pujolsluis", - "gravatarHash": "c91c0d654f4f06ca6e7a7e54699de85d", - "feedUris": [ - "https://www.pujolsluis.com/rss" - ], - "position": { - "lat": 26.0203048, - "lon": -80.115093 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/ReactiveUI.json b/Authors/ReactiveUI.json deleted file mode 100644 index 2547b64..0000000 --- a/Authors/ReactiveUI.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "ReactiveUI", - "lastName": "", - "stateOrRegion": "Internet", - "emailAddress": "hello@reactiveui.net", - "tagOrBio": "An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms", - "webSite": "https://reactiveui.net/", - "feedUris": [ - "https://reactiveui.net/rss" - ], - "twitterHandle": "ReactiveXUI", - "gravatarHash": "", - "githubHandle": "ReactiveUI", - "position": { - "lat": -13.6981464, - "lon": 37.3979239 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/Sankra.json b/Authors/Sankra.json deleted file mode 100644 index 5beca40..0000000 --- a/Authors/Sankra.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Runar Ovesen", - "lastName": "Hjerpbakk", - "tagOrBio": "Passionate, empathic and experienced developer, software architect and manager. My love for C# is only surpassed by my love for Xamarin and iOS.", - "stateOrRegion": "Trondheim, Norway", - "emailAddress": "runar@hjerpbakk.com", - "twitterHandle": "hjerpbakk", - "gravatarHash": "62b1d11eafee92745a51971d6cc21f85", - "githubHandle": "Sankra", - "position": { - "lat": 63.4305, - "lon": 10.3951 - }, - "webSite": "https://hjerpbakk.com/", - "feedUris": [ - "https://hjerpbakk.com/feed.xml" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/StefanRiedmann.json b/Authors/StefanRiedmann.json deleted file mode 100644 index 3e8cbb3..0000000 --- a/Authors/StefanRiedmann.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Stefan", - "githubHandle": "StefanRiedmann", - "lastName": "Riedmann", - "tagOrBio": "Growing software", - "stateOrRegion": "Mendoza, Argentina and Gemünden, Germany", - "emailAddress": "stefan.riedmann@ciclosoftware.com", - "twitterHandle": "CicloSoftware", - "webSite": "https://www.ciclosoftware.com", - "feedUris": [ - "https://www.ciclosoftware.com/feed/" - ], - "gravatarHash": "2781a55d04634584326bedfe08660537", - "position": { - "lat": -32.8886904, - "lon": -68.8481432 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/TheFo2sh.json b/Authors/TheFo2sh.json deleted file mode 100644 index bb08f0e..0000000 --- a/Authors/TheFo2sh.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Ahmed", - "lastName": "Fouad", - "stateOrRegion": "Vienna, Austria", - "emailAddress": "ahmed.fouad.net@hotmail.com", - "tagOrBio": "software engineer with 10 years experience with the .NET Framework living in Vienna, Austria.", - "webSite": "https://itnext.com/@csharpwriter", - "feedUris": [ - "https://medium.com/feed/@csharpwriter" - ], - "twitterHandle": "MCC_Ahmed", - "gravatarHash": "5727eb3df565991947d90ed140962472", - "githubHandle": "TheFo2sh", - "position": { - "lat": 48.20849, - "lon": 16.37208 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/TheXamarinShow.json b/Authors/TheXamarinShow.json deleted file mode 100644 index a52665a..0000000 --- a/Authors/TheXamarinShow.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "The Xamarin", - "lastName": "Show", - "stateOrRegion": "Channel 9", - "emailAddress": "", - "tagOrBio": "is a Weekly Developer Show for Xamarin Developers", - "webSite": "http://xamarinshow.com", - "feedUris": [ - "https://channel9.msdn.com/Shows/XamarinShow/feed" - ], - "twitterHandle": "TheXamarinShow", - "gravatarHash": "7a0c7da0279b4e90439e780fa01924e0", - "githubHandle": "", - "position": { - "lat": 47.645136, - "lon": -122.130939 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/TomSoderling.json b/Authors/TomSoderling.json deleted file mode 100644 index 823a6bc..0000000 --- a/Authors/TomSoderling.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Tom", - "lastName": "Soderling", - "stateOrRegion": "Minneapolis, MN", - "emailAddress": "", - "tagOrBio": "is a Sr. Mobile Developer, speaker, and open source contributor", - "twitterHandle": "tomsoderling", - "gravatarHash": "dd103f377899fc63b0b88c5bb62b15bd", - "position": { - "lat": 44.986656, - "lon": -93.258133 - }, - "webSite": "https://tomsoderling.github.io", - "feedUris": [ - "https://tomsoderling.github.io/feed.xml" - ], - "githubHandle": "TomSoderling", - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/UdaraAlwis.json b/Authors/UdaraAlwis.json deleted file mode 100644 index 0297db2..0000000 --- a/Authors/UdaraAlwis.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Udara", - "lastName": "Alwis", - "tagOrBio": "A mobile dev enthusiast. Xamarin Certified Developer in Singapore.", - "stateOrRegion": "Singapore", - "emailAddress": "udara.robotics@gmail.com", - "twitterHandle": "Udara_Alwis", - "gravatarHash": "125c4aaed98f2a88207dac78c17dd344", - "githubHandle": "UdaraAlwis", - "position": { - "lat": 1.284433, - "lon": 103.859609 - }, - "webSite": "https://theconfuzedsourcecode.wordpress.com/", - "feedUris": [ - "https://theconfuzedsourcecode.wordpress.com/feed/" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/VicenteGuzman.json b/Authors/VicenteGuzman.json deleted file mode 100644 index 7944023..0000000 --- a/Authors/VicenteGuzman.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Vicente", - "lastName": "Guzman", - "tagOrBio": "Vicente Guzman is a Community Member / Microsoft rMVP / Software Engineer", - "stateOrRegion": "Ciudad de México", - "emailAddress": "luciomsp@geeks.ms", - "webSite": "https://vicenteguzman.mx/", - "feedUris": [ - "https://vicenteguzman.mx/feed/" - ], - "twitterHandle": "luciomsp", - "gravatarHash": "72cce778aac0d6066a14225e90c30874", - "githubHandle": "VicenteGuzman", - "position": { - "lat": 19.432608, - "lon": -99.133209 - }, - "languageCode": "es" -} \ No newline at end of file diff --git a/Authors/XamarinPodcast.json b/Authors/XamarinPodcast.json deleted file mode 100644 index 2c6728d..0000000 --- a/Authors/XamarinPodcast.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "The Xamarin", - "lastName": "Podcast", - "stateOrRegion": "Internet", - "emailAddress": "hello@xamarin.com", - "tagOrBio": "is the official Xamarin podcast discussing all things Xamarin!", - "webSite": "http://www.xamarinpodcast.com", - "feedUris": [ - "https://feeds.fireside.fm/xamarinpodcast/rss" - ], - "twitterHandle": "XamarinPodcast", - "gravatarHash": "70148d964bb389d42547834e1062c886", - "githubHandle": "", - "position": { - "lat": -1337.0, - "lon": 42.0 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/acaliaro.json b/Authors/acaliaro.json deleted file mode 100644 index 4899ee5..0000000 --- a/Authors/acaliaro.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Alessandro", - "lastName": "Caliaro", - "tagOrBio": "I like Xamarin Forms and I like help other people to understand it", - "emailAddress": "acaliaro@libero.it", - "twitterHandle": "acaliaro", - "gravatarHash": "a7466eb1c467806f77bc692a4745d0f9", - "stateOrRegion": "Lissone, Italy", - "webSite": "https://acaliaro.wordpress.com", - "githubHandle": "acaliaro", - "position": { - "lat": 45.632783, - "lon": 9.227265 - }, - "feedUris": [ - "https://acaliaro.wordpress.com/feed/" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/ahoefling.json b/Authors/ahoefling.json deleted file mode 100644 index eb8adee..0000000 --- a/Authors/ahoefling.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Andrew", - "lastName": "Hoefling", - "stateOrRegion": "New York, United States", - "emailAddress": "andrew@hoefling.me", - "tagOrBio": "Microsoft MVP (Developer Technologies) Open Source developer who loves integrating Xamarin with other platforms", - "webSite": "https://www.andrewhoefling.com/", - "twitterHandle": "andrew_hoefling", - "githubHandle": "ahoefling", - "gravatarHash": "beab68478a5128e634590af5e4f01941", - "feedUris": [ - "https://www.andrewhoefling.com/feed.xml?category=xamarin&uno-platform" - ], - "position": { - "lat": 43.156578, - "lon": -77.608849 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/akamud.json b/Authors/akamud.json deleted file mode 100644 index ca11d7b..0000000 --- a/Authors/akamud.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Mahmoud", - "lastName": "Ali", - "stateOrRegion": "São Paulo, Brasil", - "emailAddress": "muddibr@gmail.com", - "tagOrBio": "Microsoft MVP", - "webSite": "https://www.lambda3.com.br/blog", - "twitterHandle": "akamud", - "githubHandle": "akamud", - "gravatarHash": "fc093b379c830c8105f8d15d9261a144", - "feedUris": [ - "https://www.lambda3.com.br/feed/" - ], - "position": { - "lat": -23.552339, - "lon": -46.661393 - }, - "languageCode": "pt" -} \ No newline at end of file diff --git a/Authors/alexsorokoletov.json b/Authors/alexsorokoletov.json deleted file mode 100644 index 5687f73..0000000 --- a/Authors/alexsorokoletov.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Alexandr", - "lastName": "Sorokoletov", - "tagOrBio": "when he's not developing mobile applications enjoys snowboarding, kitesurfing and travel", - "stateOrRegion": "Washington, D.C.", - "emailAddress": "", - "twitterHandle": "AlexSorokoletov", - "githubHandle": "alexsorokoletov", - "position": { - "lat": 38.905147, - "lon": -77.065189 - }, - "webSite": "https://sorokoletov.com", - "feedUris": [ - "https://sorokoletov.com/atom.xml" - ], - "gravatarHash": "b07fef8827dd03655303751e2fd5ca95", - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/almirvuk.json b/Authors/almirvuk.json deleted file mode 100644 index 7bae938..0000000 --- a/Authors/almirvuk.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Almir", - "lastName": "Vuk", - "tagOrBio": "Software Development Engineer & Microsoft MVP, crafting apps with ASP.NET Core and Xamarin", - "stateOrRegion": "Mostar, Bosnia and Herzegovina", - "emailAddress": "almir.vuk@outlook.com", - "twitterHandle": "almirvuk", - "gravatarHash": "d58b6fd6c2d9f949345e8d14d203a4b2", - "webSite": "https://almirvuk.com/", - "feedUris": [ - "https://almirvuk.com/rss/" - ], - "githubHandle": "almirvuk", - "position": { - "lat": 43.3395522, - "lon": 17.7862211 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/andreas-nesheim.json b/Authors/andreas-nesheim.json deleted file mode 100644 index 92955da..0000000 --- a/Authors/andreas-nesheim.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Andreas", - "lastName": "Nesheim", - "stateOrRegion": "Norway", - "emailAddress": "andreas.aronsen.nesheim@gmail.com", - "tagOrBio": ".NET developer with a passion for Xamarin, Azure DevOps and .NET Core.", - "webSite": "https://www.andreasnesheim.no/", - "twitterHandle": "AndreasNesheim", - "githubHandle": "andreas-nesheim", - "gravatarHash": "3f1d141d2809114debffb23277e91e3e", - "feedUris": [ - "https://andreasnesheim.no/feed" - ], - "position": { - "lat": 58.9540147, - "lon": 5.7259639 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/aritchie.json b/Authors/aritchie.json deleted file mode 100644 index cb30817..0000000 --- a/Authors/aritchie.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Allan", - "lastName": "Ritchie", - "stateOrRegion": "Toronto, Canada", - "emailAddress": "allan.ritchie@gmail.com", - "tagOrBio": "", - "webSite": "https://allancritchie.net", - "twitterHandle": "allanritchie911", - "githubHandle": "aritchie", - "gravatarHash": "5f22bd04ca38ed4d0a5225d0825e0726", - "position": { - "lat": 43.6425701, - "lon": -79.3892455 - }, - "languageCode": "en", - "feedUris": [ - "https://allancritchie.net/xamarin.rss" - ] -} \ No newline at end of file diff --git a/Authors/asfend.json b/Authors/asfend.json deleted file mode 100644 index 6a7e94c..0000000 --- a/Authors/asfend.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Asfend Yar", - "lastName": "Hamid", - "stateOrRegion": "Lahore, Pakistan", - "emailAddress": "asfend@hotmail.com", - "tagOrBio": "is a Technical Trainer, Author at Udemy and Passionate Community Member as well as Software Engineer", - "webSite": "https://xamarinui.blogspot.com/", - "feedUris": [ - "https://xamarinui.blogspot.com/feeds/posts/default" - ], - "twitterHandle": "asfend", - "gravatarHash": "1aa9a7436eec5ad5d0418a385d1bdbe0", - "githubHandle": "asfend", - "position": { - "lat": 31.5712, - "lon": 74.3646 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/aspnetde.json b/Authors/aspnetde.json deleted file mode 100644 index 190d444..0000000 --- a/Authors/aspnetde.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Thomas", - "lastName": "Bandt", - "stateOrRegion": "Munich, Germany", - "position": { - "lat": 48.1485869, - "lon": 11.5353743 - }, - "emailAddress": "", - "webSite": "https://thomasbandt.com/", - "tagOrBio": "Developer & Entrepreneur of Passion", - "twitterHandle": "asp_net", - "githubHandle": "aspnetde", - "gravatarHash": "32860557b42ace0afa72704e466e34f1", - "feedUris": [ - "https://thomasbandt.com/feed" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/balivo.json b/Authors/balivo.json deleted file mode 100644 index 19c993f..0000000 --- a/Authors/balivo.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Jefferson", - "lastName": "Balivo", - "stateOrRegion": "Jaú, São Paulo, Brazil", - "emailAddress": "jefferson@balivo.com.br", - "tagOrBio": "Xamarin Enthusiast, Technical Lead and Cross Platform Architect, Multi-Plataform Technical Audience Contributor (MTAC), Technical Writer and Speaker", - "webSite": "https://balivo.com.br/", - "twitterHandle": "jbalivo", - "githubHandle": "balivo", - "gravatarHash": "e5a95c365e8f06786d6439474bc733df", - "feedUris": [ - "https://balivo.com.br/rss" - ], - "position": { - "lat": -22.2997067, - "lon": -48.5931324 - }, - "languageCode": "pt" -} \ No newline at end of file diff --git a/Authors/banditoth.json b/Authors/banditoth.json deleted file mode 100644 index cca99ea..0000000 --- a/Authors/banditoth.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "András", - "lastName": "Tóth", - "tagOrBio": "also known as banditoth | Xamarin && C# .NET developer from Hungary.", - "stateOrRegion": "Budapest, Hungary", - "emailAddress": "", - "twitterHandle": "", - "githubHandle": "banditoth", - "position": { - "lat": 47.497913, - "lon": 19.040236 - }, - "webSite": "https://www.banditoth.hu", - "feedUris": [ - "https://www.banditoth.hu/feed/" - ], - "gravatarHash": "e11e3fd93c1cc4db5ec6f309bac0ff4d", - "languageCode": "hu" -} \ No newline at end of file diff --git a/Authors/basdecort.json b/Authors/basdecort.json deleted file mode 100644 index 2ef14fa..0000000 --- a/Authors/basdecort.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Bas", - "lastName": "de Cort", - "stateOrRegion": "Tilburg, The Netherlands", - "emailAddress": "", - "tagOrBio": "is a mobile developer with a great passion for Xamarin 🙉", - "webSite": "https://www.basdecort.com", - "twitterHandle": "basdecort", - "githubHandle": "basdecort", - "gravatarHash": "9cabafd38c21d9df358f7532ffa39153", - "position": { - "lat": 52.040222799999995, - "lon": 5.5349002999999994 - }, - "feedUris": [ - "https://www.basdecort.com/rss" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/bbenetskyy.json b/Authors/bbenetskyy.json deleted file mode 100644 index 4d0d0ce..0000000 --- a/Authors/bbenetskyy.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Bohdan", - "lastName": "Benetskyi", - "stateOrRegion": "Rzeszow, Poland", - "emailAddress": "benetskyybogdan@gmail.com", - "tagOrBio": "Xamarin Software Developer, co-organizer of Xamarin Local Events in Rzeszow, Local CSS at Xamarin Advocate", - "webSite": "https://medium.com/@benetskyybogdan/", - "twitterHandle": "bbenetskyy", - "githubHandle": "bbenetskyy", - "gravatarHash": "8bfa7db9239c2969b79468a58c8dd066", - "feedUris": [ - "https://medium.com/feed/@benetskyybogdan/" - ], - "position": { - "lat": 50.041187, - "lon": 21.999121 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/brminnick.json b/Authors/brminnick.json deleted file mode 100644 index 0359af3..0000000 --- a/Authors/brminnick.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Brandon", - "lastName": "Minnick", - "stateOrRegion": "San Francisco, CA", - "emailAddress": "brandon@codetraveler.io", - "tagOrBio": "works as a Developer Advocate at Microsoft. Formerly a Customer Success Engineer at Xamarin (before the Microsoft Acquisition), Brandon has a loves helping developers make 5-star apps!", - "webSite": "https://codetraveler.io", - "twitterHandle": "TheCodeTraveler", - "githubHandle": "brminnick", - "gravatarHash": "e03e28629383def59c31d54fb8bb3982", - "position": { - "lat": 37.77669, - "lon": -122.416644 - }, - "languageCode": "en", - "feedUris": [ - "https://codetraveler.io/tag/xamarin/rss" - ] -} \ No newline at end of file diff --git a/Authors/canbilgin.json b/Authors/canbilgin.json deleted file mode 100644 index 2ddfddf..0000000 --- a/Authors/canbilgin.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Can", - "lastName": "Bilgin", - "tagOrBio": "Seasoned microsoft stack developer with passion for mobile development. Microsoft MVP for Windows Platform Development", - "stateOrRegion": "Sarajevo", - "emailAddress": "can_bilgin@hotmail.com", - "twitterHandle": "can_bilgin", - "gravatarHash": "3659d530c25e6188f7ef4c98ed100dc8", - "webSite": "https://canbilgin.wordpress.com/", - "feedUris": [ - "https://canbilgin.wordpress.com/feed/" - ], - "githubHandle": "canbilgin", - "position": { - "lat": 43.8938256, - "lon": 18.3129519 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/chamons.json b/Authors/chamons.json deleted file mode 100644 index e26f390..0000000 --- a/Authors/chamons.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Chris", - "lastName": "Hamons", - "tagOrBio": "is the lead engineer for Xamarin.Mac", - "stateOrRegion": "Austin, Texas", - "emailAddress": "chris.hamons@xamarin.com", - "twitterHandle": "IfErrThrowBrick", - "webSite": "https://medium.com/@donblas", - "feedUris": [ - "https://medium.com/feed/@donblas" - ], - "gravatarHash": "8fb3e7f07ea1386cefe1326b48e0e21a", - "githubHandle": "chamons", - "position": { - "lat": 30.267153, - "lon": -97.743061 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/char0394.json b/Authors/char0394.json deleted file mode 100644 index f153a93..0000000 --- a/Authors/char0394.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Charlin", - "lastName": "Agramonte", - "tagOrBio": "Software Engineer", - "stateOrRegion": "Dominican Republic", - "emailAddress": "charlin@crossgeeks.com", - "twitterHandle": "Chard003", - "gravatarHash": "7db2bb2eed17e8df7e78b0d5461d90b0", - "githubHandle": "char0394", - "position": { - "lat": 18.4735438, - "lon": -69.9456919 - }, - "webSite": "https://xamgirl.com/", - "feedUris": [ - "https://xamgirl.com/rss" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/cjlotz.json b/Authors/cjlotz.json deleted file mode 100644 index 2a640c6..0000000 --- a/Authors/cjlotz.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Carel", - "lastName": "Lotz", - "tagOrBio": "is a Software Architect/Developer that loves to code", - "stateOrRegion": "Cape Town, South Africa", - "emailAddress": "carel.lotz@gmail.com", - "twitterHandle": "cjlotz", - "githubHandle": "cjlotz", - "gravatarHash": "0f692990601721c06f141f4fe860685e", - "position": { - "lat": -33.89635, - "lon": 18.70199 - }, - "webSite": "https://cjlotz.github.io", - "feedUris": [ - "https://cjlotz.github.io/feed.xml" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/codemillmatt.json b/Authors/codemillmatt.json deleted file mode 100644 index 8b8e421..0000000 --- a/Authors/codemillmatt.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "feedUris": [ - "https://codemilltech.com/feed/" - ], - "firstName": "Matthew", - "lastName": "Soucoup", - "stateOrRegion": "Madison, WI", - "emailAddress": "msoucoup@codemilltech.com", - "tagOrBio": "", - "webSite": "https://codemilltech.com", - "twitterHandle": "codemillmatt", - "gravatarHash": "df69069a0bffd2dae5a8700a1bef7bfd", - "githubHandle": "codemillmatt", - "position": { - "lat": 43.073052, - "lon": -89.40123 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/crswlls.json b/Authors/crswlls.json deleted file mode 100644 index 5f8fd20..0000000 --- a/Authors/crswlls.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Chris", - "lastName": "Williams", - "tagOrBio": "", - "emailAddress": "", - "twitterHandle": "crswlls", - "gravatarHash": "21e379df7ba9c57f167188e2fcb7dd75", - "stateOrRegion": "Bristol, UK", - "webSite": "https://crswlls.wordpress.com", - "feedUris": [ - "https://crswlls.wordpress.com/rss/" - ], - "githubHandle": "", - "position": { - "lat": 30.267153, - "lon": -97.743061 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/damienaicheh.json b/Authors/damienaicheh.json deleted file mode 100644 index 5459dfb..0000000 --- a/Authors/damienaicheh.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Damien", - "lastName": "Aicheh", - "stateOrRegion": "Rennes, France", - "emailAddress": "", - "tagOrBio": "is a passionate mobile developer also interrested about Azure, Azure DevOps and.NET Core.", - "webSite": "https://damienaicheh.github.io/", - "twitterHandle": "damienaicheh", - "githubHandle": "damienaicheh", - "gravatarHash": "b5cf688a9aa81b3ef752ecda4366a8e9", - "feedUris": [ - "https://damienaicheh.github.io/feed.xml" - ], - "position": { - "lat": 48.1113036, - "lon": -1.6801598 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/damiendoumer.json b/Authors/damiendoumer.json deleted file mode 100644 index 30a269c..0000000 --- a/Authors/damiendoumer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Damien", - "lastName": "Doumer", - "stateOrRegion": "Douala, Cameroon", - "emailAddress": "damientohin@gmail.com", - "tagOrBio": "Fresh .Net developer, who loves mobile app development.", - "webSite": "https://doumer.me/", - "feedUris": [ - "https://doumer.me/feed/" - ], - "twitterHandle": "Damien_Doumer", - "gravatarHash": "ecdd93df62c61daa04da17967f82d08d", - "githubHandle": "damiendoumer", - "position": { - "lat": 4.07316844239285, - "lon": 9.6842408186165585 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/danielcauser.json b/Authors/danielcauser.json deleted file mode 100644 index e7c0f22..0000000 --- a/Authors/danielcauser.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Daniel", - "lastName": "Causer", - "stateOrRegion": "Toronto, Canada", - "emailAddress": "danielcauser@gmail.com", - "tagOrBio": "is a Xamarin Certified Developer fan of Xamarin and Mobile Development.", - "webSite": "https://causerexception.com/", - "twitterHandle": "danielcauser", - "githubHandle": "danielcauser", - "gravatarHash": "2666956714a2c2d48c480a6bddac4071", - "feedUris": [ - "https://causerexception.com/feed" - ], - "position": { - "lat": 43.653103, - "lon": -79.383851 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/danielmonettelli.json b/Authors/danielmonettelli.json deleted file mode 100644 index 668e204..0000000 --- a/Authors/danielmonettelli.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "firstName": "Daniel Angel", - "lastName": "Monettelli L.", - "tagOrBio": "is a Software Engineer, Videoblogger, Xamarin Mobile Developer & UI/UX Architect.", - "stateOrRegion": "Arequipa, Perú", - "emailAddress": "danielmonetelli@hotmail.com", - "webSite": "https://danielmonettelli.github.io", - "feedUris": [ - "https://danielmonettelli.github.io/feed.xml", - "https://www.youtube.com/feeds/videos.xml?channel_id=UCTAlbAORoFvAHj7jA4DLSFQ" - ], - "twitterHandle": "DanielMonetelli", - "gravatarHash": "4b3d0e60019ad8fe1e4d7cd5c8850efb", - "githubHandle": "danielmonettelli", - "position": { - "lat": -16.4042643, - "lon": -71.5486383 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/dansiegel.json b/Authors/dansiegel.json deleted file mode 100644 index b626148..0000000 --- a/Authors/dansiegel.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Dan", - "lastName": "Siegel", - "stateOrRegion": "San Diego, CA", - "emailAddress": "dsiegel@avantipoint.com", - "tagOrBio": "is a Mobile App Consultant and Founder of AvantiPoint. He is an author of several OSS libraries, a maintainer of the Prism Library and a DevOps champion.", - "webSite": "https://dansiegel.net", - "feedUris": [ - "https://dansiegel.net/syndication.axd" - ], - "twitterHandle": "DanJSiegel", - "gravatarHash": "b65a519785f69fbe7236dd0fd6396094", - "githubHandle": "dansiegel", - "position": { - "lat": 32.726308, - "lon": -117.177746 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/davidbritch.json b/Authors/davidbritch.json deleted file mode 100644 index bda68ed..0000000 --- a/Authors/davidbritch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "firstName": "David", - "lastName": "Britch", - "Title": "Senior Developer/Writer", - "stateOrRegion": "UK", - "emailAddress": "", - "twitterHandle": "britchdavid", - "gravatarHash": "0ddca71a03a3591203b4a748fcdfb47a", - "Started": "2015-06-08T00:00:00", - "tagOrBio": "", - "githubHandle": "davidbritch", - "position": { - "lat": 53.4793, - "lon": -2.2479 - }, - "webSite": "https://www.davidbritch.com", - "feedUris": [ - "https://www.davidbritch.com/rss.xml" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/dhindrik.json b/Authors/dhindrik.json deleted file mode 100644 index 5a103ec..0000000 --- a/Authors/dhindrik.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Daniel", - "lastName": "Hindrikes", - "stateOrRegion": "Borlänge, Sweden", - "emailAddress": "daniel@hindrikes.se", - "tagOrBio": "is an architect and developer using Xamarin and Azure. Working as a Ninja at tretton37. Also recording \"The Code Behind\" podcast!", - "webSite": "https://danielhindrikes.se", - "twitterHandle": "hindrikes", - "githubHandle": "dhindrik", - "gravatarHash": "054db2cfd79654ec5d92e20c180f2d72", - "feedUris": [ - "https://danielhindrikes.se/index.php/feed/" - ], - "position": { - "lat": 60.48604, - "lon": 15.43391 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/divikiran.json b/Authors/divikiran.json deleted file mode 100644 index 870131c..0000000 --- a/Authors/divikiran.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Divikiran", - "lastName": "Ravela", - "stateOrRegion": "Melbourne, Australia", - "emailAddress": "divikiran1@gmail.com", - "tagOrBio": "Xamarin Consultant/Tech Lead", - "webSite": "https://xamlabs.com/", - "twitterHandle": "", - "githubHandle": "divikiran", - "gravatarHash": "", - "feedUris": [ - "https://xamlabs.com/feed/" - ], - "position": { - "lat": -37.8135511, - "lon": 144.9637748 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/dylanberry.json b/Authors/dylanberry.json deleted file mode 100644 index 808d8fd..0000000 --- a/Authors/dylanberry.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Dylan", - "lastName": "Berry", - "tagOrBio": "Genetically Predisposed Programmer", - "stateOrRegion": "Toronto, Canada", - "emailAddress": "dylanberry@gmail.com", - "twitterHandle": "dylbot9000", - "gravatarHash": "8ef85938904ff43397d50caa9b0eebed", - "githubHandle": "dylanberry", - "position": { - "lat": 43.653493, - "lon": -79.384095 - }, - "webSite": "https://www.dylanberry.com/", - "feedUris": [ - "https://www.dylanberry.com/feed/" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/egbakou.json b/Authors/egbakou.json deleted file mode 100644 index 886bb70..0000000 --- a/Authors/egbakou.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Kodjo Laurent", - "lastName": "Egbakou", - "stateOrRegion": "Lome, Togo", - "emailAddress": "laurent@lioncoding.com", - "tagOrBio": "is C#/.Net/Xamarin developer who learns and shares.", - "webSite": "https://lioncoding.com/", - "twitterHandle": "lioncoding", - "githubHandle": "egbakou", - "gravatarHash": "6e26a030d3b9495872b58d922bd86157", - "feedUris": [ - "https://lioncoding.com/feed.xml" - ], - "position": { - "lat": 6.2030129, - "lon": 1.1918434 - }, - "languageCode": "fr" -} \ No newline at end of file diff --git a/Authors/elbrinner.json b/Authors/elbrinner.json deleted file mode 100644 index 615211b..0000000 --- a/Authors/elbrinner.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Elbrinner", - "lastName": "da Silva Fernandes", - "stateOrRegion": "Madrid , Spain", - "emailAddress": "elbrinner@elbrinner.com", - "tagOrBio": "is a Xamarin consultant in everis Spain and organizer of the meetup group Xamarin Madrid.", - "webSite": "https://www.elbrinner.com/", - "twitterHandle": "elbrinner", - "githubHandle": "elbrinner", - "gravatarHash": "15e64690c0e4d5b2c692e9fc7de5e768", - "feedUris": [ - "https://www.elbrinner.com/rss" - ], - "position": { - "lat": 40.416775, - "lon": -3.70379 - }, - "languageCode": "es" -} \ No newline at end of file diff --git a/Authors/felipebaltazar.json b/Authors/felipebaltazar.json deleted file mode 100644 index 7ca680e..0000000 --- a/Authors/felipebaltazar.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Felipe", - "lastName": "Baltazar", - "tagOrBio": "Xamarin 🐒, ApnetCore 🌐, Windows 💻, Nerd 🤓, Gamer 🎮 and Father 👨‍👩‍👦", - "stateOrRegion": "Rio Grande do sul, Brasil", - "emailAddress": "felipe.dasilvabaltazar@gmail.com", - "twitterHandle": "FelippeBaltazar", - "githubHandle": "felipebaltazar", - "gravatarHash": "c4deac62305f590fbda80209628afd0e", - "position": { - "lat": -29.940163, - "lon": -51.088751 - }, - "webSite": "https://medium.com/@felipedasilvabaltazar", - "feedUris": [ - "https://medium.com/feed/@felipedasilvabaltazar" - ], - "languageCode": "pt" -} \ No newline at end of file diff --git a/Authors/filipoff2.json b/Authors/filipoff2.json deleted file mode 100644 index e3c9525..0000000 --- a/Authors/filipoff2.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Boguslaw", - "lastName": "Blonski", - "tagOrBio": "Generalist who is pragmatic and has delivered real code to real users in a variety of shapes.", - "stateOrRegion": "Lezajsk, Poland", - "emailAddress": "boguslawblonski@gmail.com", - "twitterHandle": "filipoff", - "gravatarHash": "d48c54ac95492ddadfc221b646d4c612", - "webSite": "https://medium.com/@boguslawblonski", - "feedUris": [ - "https://medium.com/feed/@boguslawblonski/" - ], - "githubHandle": "filipoff2", - "position": { - "lat": 50.2684647, - "lon": 22.3819053 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/framinosona.json b/Authors/framinosona.json deleted file mode 100644 index 4580e19..0000000 --- a/Authors/framinosona.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Francois", - "lastName": "Raminosona", - "stateOrRegion": "Norway", - "emailAddress": "framinosona@hotmail.fr", - "tagOrBio": "Passionate Xamarin/Microsoft technologies developer", - "webSite": "https://blog.francois.raminosona.com/", - "feedUris": [ - "https://blog.francois.raminosona.com/tag/xamarin/rss/" - ], - "twitterHandle": "framinosona", - "gravatarHash": "b3b91b8d4bd1e716982eef6e5228c92f", - "githubHandle": "framinosona", - "position": { - "lat": 58.9720089, - "lon": 5.7363974 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/gonemobilecast.json b/Authors/gonemobilecast.json deleted file mode 100644 index 290ca54..0000000 --- a/Authors/gonemobilecast.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Gone", - "lastName": "Mobile", - "stateOrRegion": "Internet", - "emailAddress": "hello@gonemobile.io", - "tagOrBio": "is a development podcast focused on mobile development hosted by Jon Dick and Greg Shackles.", - "webSite": "http://gonemobile.io", - "feedUris": [ - "https://feed.gonemobile.io/" - ], - "twitterHandle": "gonemobilecast", - "position": { - "lat": 51.253775, - "lon": -85.323214 - }, - "gravatarHash": "cb611c5ecd9a53b2af53a9d50d83c3c5", - "githubHandle": "", - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/gptucci.json b/Authors/gptucci.json deleted file mode 100644 index f9af534..0000000 --- a/Authors/gptucci.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "webSite": "https://www.informaticapressapochista.com/it", - "firstName": "Giampaolo", - "lastName": "Tucci", - "stateOrRegion": "Italia", - "emailAddress": "g.tucci@informaticapressapochista.com", - "tagOrBio": "L'IT come non l'avete mai letta", - "gravatarHash": "a8846caf48c8ccc9850ff201c1e2ad1d", - "feedUris": [ - "https://www.informaticapressapochista.com/it/?format=feed&type=rss" - ], - "twitterHandle": "GiampaoloTUCCI", - "githubHandle": "gptucci", - "position": { - "lat": 44.40138, - "lon": 8.93419 - }, - "languageCode": "it" -} \ No newline at end of file diff --git a/Authors/gshackles.json b/Authors/gshackles.json deleted file mode 100644 index 4a18581..0000000 --- a/Authors/gshackles.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Greg", - "lastName": "Shackles", - "tagOrBio": "knows a thing (or two) about mobile testing", - "emailAddress": "greg@gregshackles.com", - "twitterHandle": "gshackles", - "gravatarHash": "6d7b45031bf22823060849d494343a8c", - "stateOrRegion": "New York, NY", - "webSite": "https://gregshackles.com", - "feedUris": [ - "https://gregshackles.com/rss/" - ], - "githubHandle": "gshackles", - "position": { - "lat": 40.712784, - "lon": -74.005941 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/hnabbasi.json b/Authors/hnabbasi.json deleted file mode 100644 index ebcee56..0000000 --- a/Authors/hnabbasi.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Hussain", - "lastName": "Abbasi", - "stateOrRegion": "Houston, Texas", - "emailAddress": "hnabbasi@outlook.com", - "tagOrBio": "is a Blogger, Mobile Architect, and founder of intelliAbb.com. More at HussainAbbasi.com", - "webSite": "https://www.intelliabb.com/", - "twitterHandle": "HussainNAbbasi", - "githubHandle": "hnabbasi", - "gravatarHash": "6f415af725ae2d6b5b912a7e93b105b9", - "feedUris": [ - "https://www.intelliabb.com/feed" - ], - "position": { - "lat": 29.7656, - "lon": -95.3681 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/ionixjunior.json b/Authors/ionixjunior.json deleted file mode 100644 index 8d190e5..0000000 --- a/Authors/ionixjunior.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Ione", - "lastName": "Souza Junior", - "tagOrBio": "", - "stateOrRegion": "Blumenau, Brasil", - "emailAddress": "junior@ionixjunior.com.br", - "twitterHandle": "ionixjunior", - "githubHandle": "ionixjunior", - "gravatarHash": "790726f5b5613d61926dea2e2efd4da1", - "position": { - "lat": -26.914236, - "lon": -49.068776 - }, - "webSite": "https://www.ionixjunior.com.br", - "feedUris": [ - "https://www.ionixjunior.com.br/rss" - ], - "languageCode": "pt" -} \ No newline at end of file diff --git a/Authors/jamesmontemagno.json b/Authors/jamesmontemagno.json deleted file mode 100644 index a95e11c..0000000 --- a/Authors/jamesmontemagno.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "James", - "lastName": "Montemagno", - "stateOrRegion": "Seattle, WA", - "emailAddress": "", - "tagOrBio": "is a Principal Program Manager for Mobile Developer Tools", - "webSite": "https://montemagno.com", - "feedUris": [ - "https://montemagno.com/rss" - ], - "twitterHandle": "JamesMontemagno", - "gravatarHash": "5df4d86308e585c879c19e5f909d8bfe", - "githubHandle": "jamesmontemagno", - "position": { - "lat": 47.654177, - "lon": -122.35 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/jesulink2514.json b/Authors/jesulink2514.json deleted file mode 100644 index 3bc5320..0000000 --- a/Authors/jesulink2514.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "firstName": "Jesús", - "lastName": "Angulo", - "stateOrRegion": "Lima", - "emailAddress": "jesus.angulo@outlook.com", - "tagOrBio": "Microsoft MVP | Certified Xamarin Mobile Developer", - "webSite": "https://somostechies.com", - "twitterHandle": "jesulink2514", - "githubHandle": "jesulink2514", - "gravatarHash": "63359672e0ecb75e7ed261a358bf0478", - "feedUris": [ - "https://somostechies.com/rss/", - "https://www.youtube.com/feeds/videos.xml?channel_id=UCnqaA_ArZIT0nytKMAiurzw" - ], - "position": { - "lat": -12.0896427, - "lon": -77.0060778 - }, - "languageCode": "es" -} \ No newline at end of file diff --git a/Authors/jfversluis.json b/Authors/jfversluis.json deleted file mode 100644 index 7544e9f..0000000 --- a/Authors/jfversluis.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Gerald", - "lastName": "Versluis", - "stateOrRegion": "The Netherlands", - "emailAddress": "gerald@verslu.is", - "tagOrBio": "Software Engineer at Microsoft on the Xamarin.Forms team", - "webSite": "https://blog.verslu.is/", - "feedUris": [ - "https://blog.verslu.is/feed/" - ], - "twitterHandle": "jfversluis", - "gravatarHash": "f9d4d4211d7956ce3e07e83df0889731", - "githubHandle": "jfversluis", - "position": { - "lat": 50.889039, - "lon": 5.853717 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/jgiacomini.json b/Authors/jgiacomini.json deleted file mode 100644 index 104c5a0..0000000 --- a/Authors/jgiacomini.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Jérôme", - "lastName": "Giacomini", - "stateOrRegion": "Paris, France", - "emailAddress": "jerome.giacomini@gmail.com", - "tagOrBio": "is a Xamarin Enthusiast, co-author of a book on Xamarin", - "webSite": "https://jeromegiacomini.net/Blog/", - "twitterHandle": "jeromegiacomini", - "githubHandle": "jgiacomini", - "gravatarHash": "95e63961669a22586a1236fd6a7a494d", - "feedUris": [ - "https://jeromegiacomini.net/Blog/feed/" - ], - "position": { - "lat": 48.8704842, - "lon": 2.3449646 - }, - "languageCode": "fr" -} \ No newline at end of file diff --git a/Authors/jimbobbennett.json b/Authors/jimbobbennett.json deleted file mode 100644 index d97112e..0000000 --- a/Authors/jimbobbennett.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Jim", - "lastName": "Bennett", - "tagOrBio": "is the author of Xamarin in Action and a all-round nice guy", - "stateOrRegion": "Auckland, New Zealand", - "emailAddress": "jim@jimbobbennett.io", - "twitterHandle": "jimbobbennett", - "webSite": "https://jimbobbennett.io/", - "feedUris": [ - "https://www.jimbobbennett.io/rss" - ], - "gravatarHash": "", - "githubHandle": "", - "position": { - "lat": -36.84846, - "lon": 174.763332 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/johnthiriet.json b/Authors/johnthiriet.json deleted file mode 100644 index 21ab90a..0000000 --- a/Authors/johnthiriet.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "John", - "lastName": "Thiriet", - "stateOrRegion": "Paris, France", - "emailAddress": "johnthiriet@protonmail.com", - "tagOrBio": "is a Mobility Technical Manager, Trainer, Microsoft MVP, Xamarin MVP", - "webSite": "https://johnthiriet.com/", - "feedUris": [ - "https://johnthiriet.com/feed.xml" - ], - "twitterHandle": "johnthiriet", - "gravatarHash": "ed92222c19a155a929d9f2c12d39c3f4", - "githubHandle": "johnthiriet", - "position": { - "lat": 48.875485, - "lon": 2.311039 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/jssuthahar.json b/Authors/jssuthahar.json deleted file mode 100644 index 5195a63..0000000 --- a/Authors/jssuthahar.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Suthahar", - "lastName": "Jegatheesan", - "tagOrBio": "insatiable desire to keep learning keeps the dynamic blogger. I take keen interest in sharing my knowledge and solving my readers’ technology-related problems.", - "emailAddress": "", - "twitterHandle": "jssuthahar", - "gravatarHash": "2a34ebf4e9c4dca84eb7feee7217568f", - "stateOrRegion": "Bangalore, India", - "webSite": "https://www.msdevbuild.com/", - "position": { - "lat": 12.971599, - "lon": 77.594563 - }, - "feedUris": [ - "https://www.msdevbuild.com/feeds/posts/default" - ], - "githubHandle": "jssuthahar", - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/jsuarezruiz.json b/Authors/jsuarezruiz.json deleted file mode 100644 index 3361ef1..0000000 --- a/Authors/jsuarezruiz.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "feedUris": [ - "https://javiersuarezruiz.wordpress.com/feed/" - ], - "firstName": "Javier", - "lastName": "Suarez", - "stateOrRegion": "Seville, Spain", - "emailAddress": "javiersuarezruiz@hotmail.com", - "tagOrBio": "is a passionate software developer from Spain who enjoys learning, talk and help others", - "webSite": "https://javiersuarezruiz.wordpress.com", - "twitterHandle": "jsuarezruiz", - "gravatarHash": "", - "position": { - "lat": 37.389092, - "lon": -5.984459 - }, - "githubHandle": "jsuarezruiz", - "languageCode": "es" -} \ No newline at end of file diff --git a/Authors/jtaubensee.json b/Authors/jtaubensee.json deleted file mode 100644 index 22f5cbb..0000000 --- a/Authors/jtaubensee.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "John", - "lastName": "Taubensee", - "stateOrRegion": "Chicago, IL", - "emailAddress": "", - "tagOrBio": "is a developer focused on Microsoft technologies. Microsoft Azure alumni", - "webSite": "https://taubensee.net", - "feedUris": [ - "https://taubensee.net/rss" - ], - "twitterHandle": "jtaubensee", - "gravatarHash": "151bc535ca1581cb451eb4df1672b018", - "githubHandle": "jtaubensee", - "position": { - "lat": 41.8778143, - "lon": -87.6349955 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/kent_boogaart.json b/Authors/kent_boogaart.json deleted file mode 100644 index 42a1b1f..0000000 --- a/Authors/kent_boogaart.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Kent", - "lastName": "Boogaart", - "stateOrRegion": "Australia", - "emailAddress": "kent.boogaart@gmail.com", - "tagOrBio": "is a freelance software engineer working mainly in the mobile space", - "webSite": "https://kent-boogaart.com/", - "feedUris": [ - "https://kent-boogaart.com/atom.xml" - ], - "twitterHandle": "kent_boogaart", - "gravatarHash": "", - "githubHandle": "", - "position": { - "lat": -35.0004451, - "lon": 138.3309978 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/kphillpotts.json b/Authors/kphillpotts.json deleted file mode 100644 index c501dad..0000000 --- a/Authors/kphillpotts.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Kym", - "lastName": "Phillpotts", - "tagOrBio": "is one of the Xamarin University instructors", - "stateOrRegion": "Melbourne, Australia", - "emailAddress": "kphillpotts@gmail.com", - "twitterHandle": "kphillpotts", - "gravatarHash": "3218e66502c6f0836dfd0f02f210ba0b", - "webSite": "https://kymphillpotts.com/", - "feedUris": [ - "https://kymphillpotts.com/feed" - ], - "githubHandle": "kphillpotts", - "position": { - "lat": -37.813628, - "lon": 144.963058 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/kwlothrop.json b/Authors/kwlothrop.json deleted file mode 100644 index 8f89f76..0000000 --- a/Authors/kwlothrop.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Kerry W.", - "lastName": "Lothrop", - "tagOrBio": "", - "stateOrRegion": "Frankfurt, Germany", - "emailAddress": "", - "twitterHandle": "kwlothrop", - "webSite": "https://kerry.lothrop.de/", - "feedUris": [ - "https://kerry.lothrop.de/en/feed/" - ], - "gravatarHash": "250241b2800a1de895a75ce039bcfef4", - "githubHandle": "", - "position": { - "lat": 50.1307615, - "lon": 8.568736 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/logeshpalani98.json b/Authors/logeshpalani98.json deleted file mode 100644 index 4263567..0000000 --- a/Authors/logeshpalani98.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Logesh", - "lastName": "Palani", - "stateOrRegion": "Thiruvannamalai,Tamil Nadu, India", - "emailAddress": "logesh.01@hotmail.com", - "tagOrBio": "Keep Learning,.. and Keep Practicing,...", - "webSite": "https://logeshpalani.blogspot.com/", - "twitterHandle": "logeshpalani98", - "githubHandle": "logeshpalani98", - "gravatarHash": "14fd9ec21509b468c84abbed2e47384e", - "feedUris": [ - "https://logeshpalani.blogspot.com/feeds/posts/default" - ], - "position": { - "lat": 12.527056, - "lon": 79.098382 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/luismts.json b/Authors/luismts.json deleted file mode 100644 index 8e24113..0000000 --- a/Authors/luismts.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "firstName": "Luis", - "lastName": "Matos", - "stateOrRegion": "Dominican Republic", - "twitterHandle": "luismatosluna", - "emailAddress": "hello@luismts.com", - "tagOrBio": "Software Engineer, Entrepreneur", - "gravatarHash": "ac9ac28f6b1e05a310d622b37e8bc4be", - "webSite": "https://www.luismts.com/", - "feedUris": [ - "https://www.luismts.com/feed/", - "https://www.luismts.com/es/feed/" - ], - "githubHandle": "luismts", - "position": { - "lat": 18.4900563, - "lon": -69.8962411 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/mallibone.json b/Authors/mallibone.json deleted file mode 100644 index 49de03a..0000000 --- a/Authors/mallibone.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Mark", - "lastName": "Allibone", - "stateOrRegion": "Zurich, Switzerland", - "emailAddress": "", - "tagOrBio": "is a Microsoft MVP who blogs, talks, coaches and develops all around mobile development.", - "webSite": "https://mallibone.com", - "twitterHandle": "mallibone", - "githubHandle": "mallibone", - "gravatarHash": "4fa14971da4fafb96830960bc7c6733d", - "feedUris": [ - "https://mallibone.com/feed/" - ], - "position": { - "lat": 47.5056381, - "lon": 8.7241297 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/marcofolio.json b/Authors/marcofolio.json deleted file mode 100644 index b109554..0000000 --- a/Authors/marcofolio.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Marco", - "lastName": "Kuiper", - "tagOrBio": "is a Code monkey, incurably optimistic, loves great design and passionate about Xamarin.", - "stateOrRegion": "The Netherlands", - "emailAddress": "", - "twitterHandle": "marcofolio", - "gravatarHash": "5982941cf85cecc8254ec2a4f1c812ff", - "githubHandle": "marcofolio", - "position": { - "lat": 51.987642, - "lon": 5.904598 - }, - "webSite": "https://www.marcofolio.net/", - "feedUris": [ - "https://feeds2.feedburner.com/marcofolio" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/marcusts.json b/Authors/marcusts.json deleted file mode 100644 index 5380b47..0000000 --- a/Authors/marcusts.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Stephen", - "lastName": "Marcus", - "stateOrRegion": "Orange County, California", - "emailAddress": "marcus@marcusts.com", - "tagOrBio": "Certified Xamarin mobile app developer", - "webSite": "https://www.marcusts.com", - "feedUris": [ - "https://www.marcusts.com/category/Xamarin/feed/" - ], - "twitterHandle": "", - "gravatarHash": "a64074b2ab0ac9b5a27379670b259d6d", - "githubHandle": "marcusts", - "position": { - "lat": 33.6423, - "lon": -117.6977 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/markolazic88.json b/Authors/markolazic88.json deleted file mode 100644 index baa1fe1..0000000 --- a/Authors/markolazic88.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Marko", - "lastName": "Lazić", - "tagOrBio": "Software engineer, tech enthusiast, passionate traveler. 4+ years of active development in Xamarin.Forms", - "stateOrRegion": "Belgrade, Serbia", - "emailAddress": "marko.lazic88@gmail.com", - "twitterHandle": "markolazic88", - "gravatarHash": "5645586a7d29654e9b296b1409107014", - "webSite": "https://markolazic.com/", - "feedUris": [ - "https://markolazic.com/feed/" - ], - "githubHandle": "markolazic88", - "position": { - "lat": 44.7866, - "lon": 20.4489 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/martijn00.json b/Authors/martijn00.json deleted file mode 100644 index 576f3c1..0000000 --- a/Authors/martijn00.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Martijn", - "lastName": "van Dijk", - "stateOrRegion": "Amsterdam, Netherlands", - "emailAddress": "mhvdijk@gmail.com", - "tagOrBio": "is working at Baseflow.com on Apps and MvvmCross", - "webSite": "https://medium.com/@martijn00", - "feedUris": [ - "https://medium.com/feed/@martijn00" - ], - "twitterHandle": "mhvdijk", - "gravatarHash": "22155f520ab611cf04f76762556ca3f5", - "githubHandle": "martijn00", - "position": { - "lat": 52.370216, - "lon": 4.895168 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/mattleibow.json b/Authors/mattleibow.json deleted file mode 100644 index 2ee085e..0000000 --- a/Authors/mattleibow.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Matthew", - "lastName": "Leibowitz", - "tagOrBio": "An all-round software guy. I live and breathe C# and .NET on any platform.", - "emailAddress": "mattleibow@live.com", - "twitterHandle": "mattleibow", - "githubHandle": "mattleibow", - "gravatarHash": "365da45bdc71334831f228aff805738f", - "stateOrRegion": "Cape Town, South Africa", - "position": { - "lat": -34.02231, - "lon": 18.46716 - }, - "webSite": "https://dotnetdevaddict.co.za", - "feedUris": [ - "https://dotnetdevaddict.co.za/feed/" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/mfractor.json b/Authors/mfractor.json deleted file mode 100644 index 7dd9d23..0000000 --- a/Authors/mfractor.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "MFractor", - "lastName": "", - "stateOrRegion": "Brisbane, Australia", - "emailAddress": "matthew@mfractor.com", - "tagOrBio": "is a powerful productivity tool for Xamarin Developers.", - "webSite": "https://www.mfractor.com/", - "twitterHandle": "mfractor", - "githubHandle": "mfractor", - "gravatarHash": "35bac056166a67222ddcd48b57113a32", - "feedUris": [ - "https://www.mfractor.com/blogs/news.atom" - ], - "position": { - "lat": -27.470125, - "lon": 153.021072 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/mike-grant.json b/Authors/mike-grant.json deleted file mode 100644 index 667d7c9..0000000 --- a/Authors/mike-grant.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Mike", - "lastName": "Grant", - "stateOrRegion": "Leicestershire, England", - "emailAddress": "", - "tagOrBio": "Software Engineer", - "webSite": "https://www.mikegrant.org.uk", - "gravatarHash": "06c34ad3fb10ef6786a043c4522b6a5b", - "feedUris": [ - "https://mikegrant.org.uk/feeds/Xamarin.Forms.xml" - ], - "twitterHandle": "mike_grant_", - "githubHandle": "mike-grant", - "position": { - "lat": 52.663878, - "lon": -1.3052377 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/mindofai.json b/Authors/mindofai.json deleted file mode 100644 index 846d691..0000000 --- a/Authors/mindofai.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Bryan Anthony", - "lastName": "Garcia", - "stateOrRegion": "Manila, Philippines", - "githubHandle": "mindofai", - "twitterHandle": "mindofai", - "emailAddress": "bryananthonygarcia@live.com", - "tagOrBio": "Mobile .NET Developer who enjoys learning and writing about Mobile .NET stuff and has passion for football. Co-leads Mobile .NET Developers - Philippines", - "gravatarHash": "29e1cdab06c48322805220e33556c20c", - "webSite": "https://mindofai.github.io/", - "position": { - "lat": 14.668896, - "lon": 120.947204 - }, - "feedUris": [ - "https://mindofai.github.io/feed.xml" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/mkieres.json b/Authors/mkieres.json deleted file mode 100644 index f64ed2a..0000000 --- a/Authors/mkieres.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "emailAddress": "mikolaj.kieres@progrunning.net", - "languageCode": "en", - "feedUris": [ - "https://progrunning.net/rss/" - ], - "firstName": "Mikolaj", - "githubHandle": "mkieres", - "gravatarHash": "", - "lastName": "Kieres", - "position": { - "lat": -33.826623, - "lon": 151.195031 - }, - "tagOrBio": "Mobile development adventurer", - "stateOrRegion": "Sydney, Australia", - "twitterHandle": "mikolajkieres", - "webSite": "https://progrunning.net" -} \ No newline at end of file diff --git a/Authors/msiccdev.json b/Authors/msiccdev.json deleted file mode 100644 index f003737..0000000 --- a/Authors/msiccdev.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Marco", - "lastName": "Siccardi", - "tagOrBio": "cross platform developer, writing software for Desktop, Mobiles and IOT", - "stateOrRegion": "Switzerland", - "emailAddress": "msiccdev@hotmail.com", - "twitterHandle": "msicc", - "gravatarHash": "67aaa7c3b6357dbccc1167a70b0c73e3", - "githubHandle": "msiccdev", - "position": { - "lat": 47.4683, - "lon": 8.75727 - }, - "webSite": "https://msicc.net/category/devstories/xamarin/", - "feedUris": [ - "https://msicc.net/category/devstories/xamarin/feed/" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/nickrandolph.json b/Authors/nickrandolph.json deleted file mode 100644 index d3febff..0000000 --- a/Authors/nickrandolph.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Nick", - "lastName": "Randolph", - "stateOrRegion": "Sydney, Australia", - "emailAddress": "nick@builttoroam.com", - "tagOrBio": "Microsoft MVP and maintainer of @MvvmCross. Co-founder, Tech Lead at Built to Roam (@btroam), special forces in cross platform app and cloud solutions.", - "webSite": "https://builttoroam.com", - "feedUris": [ - "https://nicksnettravels.builttoroam.com/syndication.axd" - ], - "twitterHandle": "thenickrandolph", - "gravatarHash": "", - "githubHandle": "nickrandolph", - "position": { - "lat": -33.839559, - "lon": 151.2112434 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/nigelferrissey.json b/Authors/nigelferrissey.json deleted file mode 100644 index e893dd6..0000000 --- a/Authors/nigelferrissey.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Nigel", - "lastName": "Ferrissey", - "tagOrBio": "Mobile and front-end developer, Xamarin specialist", - "emailAddress": "n_ferrissey@hotmail.com", - "twitterHandle": "nferrissey", - "gravatarHash": "106ec2de3e2ea4e88e9fc431974f5d53", - "stateOrRegion": "Brisbane, Australia", - "webSite": "https://xamarininsider.com", - "feedUris": [ - "https://xamarininsider.com/rss/" - ], - "githubHandle": "nigelferrissey", - "position": { - "lat": -27.469657, - "lon": 153.025241 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/officialdoniald.json b/Authors/officialdoniald.json deleted file mode 100644 index 0b845e1..0000000 --- a/Authors/officialdoniald.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Bence", - "lastName": "Lenart", - "tagOrBio": "Xamarin developer and blogger.", - "stateOrRegion": "Szeged, Hungary", - "emailAddress": "bence960206@gmail.com", - "webSite": "ttps://officialdoniald.com", - "feedUris": [ - "https://officialdoniald.com/feed" - ], - "twitterHandle": "officialdoniald", - "gravatarHash": "4e467fcdbb65da5080d215bf303c442a", - "githubHandle": "officialdoniald", - "position": { - "lat": 46.231222, - "lon": 20.119167 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/peterfoot.json b/Authors/peterfoot.json deleted file mode 100644 index d9d9ca7..0000000 --- a/Authors/peterfoot.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Peter", - "lastName": "Foot", - "stateOrRegion": "United Kingdom", - "emailAddress": "peter@peterfoot.net", - "twitterHandle": "peterfoot", - "webSite": "https://inthehand.com/blog/", - "feedUris": [ - "https://inthehand.com/category/xamarin/feed/" - ], - "gravatarHash": "fa15aeeccc4b23e8a4677aeacb65b7bb", - "tagOrBio": "develops Xamarin and Windows applications at In The Hand Ltd", - "githubHandle": "peterfoot", - "position": { - "lat": 52.76872, - "lon": -2.37825 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/ramonesteban78.json b/Authors/ramonesteban78.json deleted file mode 100644 index 25bc0d4..0000000 --- a/Authors/ramonesteban78.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Ramon", - "lastName": "Esteban", - "tagOrBio": "Freelance Xamarin developer", - "stateOrRegion": "Malaga, Spain", - "emailAddress": "wantedforcode@outlook.com", - "twitterHandle": "ramonesteban78", - "gravatarHash": "2fc49c3e9095aece416ad4e147fa1452", - "githubHandle": "ramonesteban78", - "position": { - "lat": 36.5126395, - "lon": -4.6483388 - }, - "webSite": "https://ramonesteban78.github.io/", - "feedUris": [ - "https://ramonesteban78.github.io/feed.xml" - ], - "languageCode": "es" -} \ No newline at end of file diff --git a/Authors/rdavisau.json b/Authors/rdavisau.json deleted file mode 100644 index 51ef775..0000000 --- a/Authors/rdavisau.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "feedUris": [ - "https://ryandavis.io/rss/" - ], - "firstName": "Ryan", - "lastName": "Davis", - "stateOrRegion": "Brisbane, Australia", - "emailAddress": "ryandavis.au@gmail.com", - "tagOrBio": "knows how to 🎉", - "webSite": "https://ryandavis.io", - "twitterHandle": "rdavis_au", - "githubHandle": "rdavisau", - "gravatarHash": "d351762ec451e252b20ff860dfcded91d351762ec451e252b20ff860dfcded91", - "position": { - "lat": -27.469771, - "lon": 153.025124 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/rdelrosario.json b/Authors/rdelrosario.json deleted file mode 100644 index 4b067dd..0000000 --- a/Authors/rdelrosario.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Rendy", - "lastName": "Del Rosario", - "tagOrBio": "is a senior software developer with a passion for mobile development using Xamarin platform", - "stateOrRegion": "Dominican Republic", - "emailAddress": "rendy@crossgeeks.com", - "twitterHandle": "rdelrosario", - "gravatarHash": "4bece0ce1c33e65177110bcb95688c68", - "githubHandle": "rdelrosario", - "position": { - "lat": 18.486058, - "lon": -69.931212 - }, - "webSite": "https://xamboy.com/", - "feedUris": [ - "https://xamboy.com/rss" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/redth.json b/Authors/redth.json deleted file mode 100644 index 61f6a67..0000000 --- a/Authors/redth.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Jon", - "lastName": "Dick", - "tagOrBio": "", - "emailAddress": "jondick@gmail.com", - "twitterHandle": "redth", - "gravatarHash": "ad73e52d7e4d89e904e7c4cfd91fc2b9", - "stateOrRegion": "Ontario, Canada", - "webSite": "https://redth.codes", - "feedUris": [ - "https://redth.codes/feed/" - ], - "githubHandle": "redth", - "position": { - "lat": 51.253775, - "lon": -85.323214 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/ricardoprestes.json b/Authors/ricardoprestes.json deleted file mode 100644 index 902978e..0000000 --- a/Authors/ricardoprestes.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Ricardo", - "lastName": "Prestes", - "stateOrRegion": "Cornélio Procópio, Brasil", - "emailAddress": "ricardo.logan@hotmail.com", - "tagOrBio": "Tech lead mobile developer, Burger Monkeys Co-founder", - "webSite": "https://oficinadologan.wordpress.com/", - "twitterHandle": "ricardo_prestes", - "githubHandle": "ricardoprestes", - "gravatarHash": "9802e38d5bd2cd85db8b0720d5feed29", - "feedUris": [ - "https://oficinadologan.wordpress.com/rss" - ], - "position": { - "lat": -23.194571, - "lon": -50.7795215 - }, - "languageCode": "pt" -} \ No newline at end of file diff --git a/Authors/rid00z.json b/Authors/rid00z.json deleted file mode 100644 index eab1205..0000000 --- a/Authors/rid00z.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Michael", - "lastName": "Ridland", - "stateOrRegion": "Sydney, Australia", - "emailAddress": "michael@xam-consulting.com", - "tagOrBio": "Xamarin Contractor/Consultant | Founder XAM Consulting (xam-consulting.com) | Creator of FreshMvvm", - "webSite": "https://michaelridland.com", - "feedUris": [ - "https://michaelridland.com/feed/" - ], - "twitterHandle": "rid00z", - "gravatarHash": "3c07e56045d18f4f290eb4983031309d", - "githubHandle": "rid00z", - "position": { - "lat": -25.348875, - "lon": 131.035 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/robintschroeder.json b/Authors/robintschroeder.json deleted file mode 100644 index abbcb44..0000000 --- a/Authors/robintschroeder.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Robin", - "lastName": "Schroeder", - "tagOrBio": "Xamarin.Forms/UWP Software Consultant at MSCTek", - "stateOrRegion": "Illinois", - "emailAddress": "robin@msctek.com", - "twitterHandle": "RTSchroeder", - "gravatarHash": "1754cd9eee726fd3a5252a4718cbf108", - "githubHandle": "robintschroeder", - "position": { - "lat": 41.9136926, - "lon": -88.3148351 - }, - "webSite": "https://msctek.com/", - "feedUris": [ - "https://www.msctek.com/feed/" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/roubachof.json b/Authors/roubachof.json deleted file mode 100644 index cdddf25..0000000 --- a/Authors/roubachof.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Jean-Marie", - "lastName": "Alfonsi", - "tagOrBio": "is a singing software engineer who travels at the speed light and wanna make a supersonic man out of you.", - "stateOrRegion": "Paris, France", - "emailAddress": "jm.alfonsi@gmail.com", - "twitterHandle": "Piskariov", - "gravatarHash": "2a30d8bc59e2ccb6e83bb498d519394a", - "webSite": "https://www.sharpnado.com/", - "feedUris": [ - "https://www.sharpnado.com/rss/" - ], - "githubHandle": "roubachof", - "position": { - "lat": 48.8588377, - "lon": 2.2770206 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/saamerm.json b/Authors/saamerm.json deleted file mode 100644 index 592393a..0000000 --- a/Authors/saamerm.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "feedUris": [ - "https://medium.com/feed/@prototypemakers" - ], - "firstName": "Saamer", - "lastName": "Mansoor", - "stateOrRegion": "Toronto, Canada", - "emailAddress": "i@saamer.me", - "tagOrBio": "Top 5% Xamarin StackOverflow, Freelancer, Trainer", - "webSite": "https://medium.com/@prototypemakers", - "twitterHandle": "saamerm", - "githubHandle": "saamerm", - "gravatarHash": "66887acb0b76f2d3059255a1c53a3b22", - "position": { - "lat": 43.639728, - "lon": -79.380099 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/sact1909.json b/Authors/sact1909.json deleted file mode 100644 index 0e3de8b..0000000 --- a/Authors/sact1909.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Steven", - "lastName": "Checo", - "stateOrRegion": "United States", - "emailAddress": "steven.checo.19@gmail.com", - "tagOrBio": "is a C# ASP.Net and Xamarin Developer, I'd preferd back-end but I get along with the Front-End, I love help others with code and build new things with friends, I ♥ C#", - "webSite": "https://checox.com", - "twitterHandle": "steven1909", - "githubHandle": "sact1909", - "gravatarHash": "47ad7178de588bb9a0b5922a5c1364c8", - "feedUris": [ - "https://checox.com/feed/" - ], - "position": { - "lat": 40.837222, - "lon": -73.886111 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/samirgcofficial.json b/Authors/samirgcofficial.json deleted file mode 100644 index c55fd76..0000000 --- a/Authors/samirgcofficial.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Samir", - "lastName": "GC", - "tagOrBio": ".net lover, xamarin mobile developer | XamarinGuy Show Host | Trainer | Instructor", - "stateOrRegion": "Kathmandu/Nepal", - "emailAddress": "samirgcofficial@gmail.com", - "twitterHandle": "xamaringuy", - "githubHandle": "samirgcofficial", - "position": { - "lat": 27.700769, - "lon": 85.30014 - }, - "webSite": "https://xamaringuyshow.com/", - "feedUris": [ - "https://xamaringuyshow.com/feed/" - ], - "gravatarHash": "4b0b695b7711b0184ab2049e33f2cfd7", - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/shirshov.json b/Authors/shirshov.json deleted file mode 100644 index 9de2a03..0000000 --- a/Authors/shirshov.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Alex", - "lastName": "Shirshov", - "stateOrRegion": "Sydney, Australia", - "emailAddress": "shirshov@gmail.com", - "tagOrBio": "Software Craftsman", - "webSite": "https://omnitalented.com", - "twitterHandle": "omnitalented", - "githubHandle": "shirshov", - "gravatarHash": "", - "position": { - "lat": -33.88236, - "lon": 151.206588 - }, - "languageCode": "en", - "feedUris": [ - "https://omnitalented.com/rss-xamarin.xml" - ] -} \ No newline at end of file diff --git a/Authors/smstuebe.json b/Authors/smstuebe.json deleted file mode 100644 index 86aad08..0000000 --- a/Authors/smstuebe.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Sven-Michael", - "lastName": "Stübe", - "tagOrBio": "loves the cross platform approach, develops Xamarin plugins and organizes Xamarin Pizza & Beer Meetups in Munich", - "stateOrRegion": "Munich, Germany", - "emailAddress": "", - "twitterHandle": "stuebe2k14", - "webSite": "https://smstuebe.de/", - "feedUris": [ - "https://smstuebe.de/feed.xml" - ], - "githubHandle": "smstuebe", - "gravatarHash": "08b73d0a58fc120a8cc8dc561d83b3d6", - "position": { - "lat": 48.1373831, - "lon": 11.5063151 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/sthewissen.json b/Authors/sthewissen.json deleted file mode 100644 index 539b113..0000000 --- a/Authors/sthewissen.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Steven", - "lastName": "Thewissen", - "stateOrRegion": "The Netherlands", - "emailAddress": "", - "tagOrBio": " is a Xamarin Developer with a knack for Photoshop and a passion for soccer and cycling. He is also in love with his Xbox One", - "webSite": "https://www.thewissen.io/", - "feedUris": [ - "https://www.thewissen.io/feed/" - ], - "twitterHandle": "devnl", - "gravatarHash": "9f698e6f515cb54dbda305034b6823fc", - "position": { - "lat": 50.889039, - "lon": 5.853717 - }, - "githubHandle": "sthewissen", - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/stvansolano.json b/Authors/stvansolano.json deleted file mode 100644 index 28e2fee..0000000 --- a/Authors/stvansolano.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Esteban", - "lastName": "Solano", - "stateOrRegion": "Cartago, Costa Rica", - "emailAddress": "stvansolano@outlook.com", - "tagOrBio": "is a passionate software community guy from Costa Rica who enjoys learning, talk and help others to learn C# and Xamarin", - "webSite": "https://stvansolano.github.io/", - "feedUris": [ - "https://stvansolano.github.io/atom.xml" - ], - "twitterHandle": "stvansolano", - "gravatarHash": "d02d96057c4cd905d60d14549b00db0d", - "githubHandle": "stvansolano", - "position": { - "lat": 9.9322992, - "lon": -84.0815271 - }, - "languageCode": "es" -} \ No newline at end of file diff --git a/Authors/susairajs.json b/Authors/susairajs.json deleted file mode 100644 index 5a9d688..0000000 --- a/Authors/susairajs.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Delpin", - "lastName": "Susai Raj", - "stateOrRegion": "Chennai, India", - "emailAddress": "susairajs@outlook.com", - "tagOrBio": "Code with Monkeys", - "webSite": "https://xamarinmonkeys.blogspot.com/", - "twitterHandle": "susairajs18", - "githubHandle": "susairajs", - "gravatarHash": "3408967b9598e7bb107baf15d5a15454", - "feedUris": [ - "https://xamarinmonkeys.blogspot.com/feeds/posts/default" - ], - "position": { - "lat": 13.0783995, - "lon": 80.2886684 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/syncfusion.json b/Authors/syncfusion.json deleted file mode 100644 index f5deb50..0000000 --- a/Authors/syncfusion.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Syncfusion", - "lastName": "", - "stateOrRegion": "North Carolina, USA", - "emailAddress": "info@syncfusion.com", - "tagOrBio": "Syncfusion provides the best third-party UI components for Xamarin, Xamarin.Android and Xamarin.iOS", - "webSite": "https://www.syncfusion.com/xamarin-ui-controls", - "feedUris": [ - "https://www.syncfusion.com/blogs/feed" - ], - "twitterHandle": "Syncfusion", - "gravatarHash": "4291ef07481d300471113dbd4b4aabc2", - "githubHandle": "syncfusion", - "position": { - "lat": 35.855361, - "lon": -78.815751 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/tbertuzzi.json b/Authors/tbertuzzi.json deleted file mode 100644 index cc1c04b..0000000 --- a/Authors/tbertuzzi.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Thiago", - "lastName": "Bertuzzi", - "tagOrBio": "Web and Mobile Developer, Blogger, Speaker", - "stateOrRegion": "São Paulo, Brasil", - "emailAddress": "thiago.bertuzzi@gmail.com", - "twitterHandle": "tbertuzzi", - "githubHandle": "tbertuzzi", - "gravatarHash": "82d95125be475913cdc7a7fe319d0133", - "position": { - "lat": -23.5834337, - "lon": -46.6672048 - }, - "webSite": "https://medium.com/@bertuzzi/", - "feedUris": [ - "https://medium.com/feed/@bertuzzi" - ], - "languageCode": "pt" -} \ No newline at end of file diff --git a/Authors/telerik.json b/Authors/telerik.json deleted file mode 100644 index a4ca310..0000000 --- a/Authors/telerik.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Telerik", - "lastName": "", - "stateOrRegion": "Global", - "emailAddress": "telerikxamarinteam@progress.com", - "tagOrBio": "Progress Telerik UI libraries equip .NET ninjas with a full arsenal of weapons to help you create beautiful, modern and future-proof applications quickly and intuitively.", - "webSite": "https://www.telerik.com/", - "twitterHandle": "telerik", - "githubHandle": "telerik", - "gravatarHash": "", - "feedUris": [ - "https://feeds.telerik.com/blogs" - ], - "position": { - "lat": 42.512778, - "lon": -71.2515 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/trailheadtechnology.json b/Authors/trailheadtechnology.json deleted file mode 100644 index c613eed..0000000 --- a/Authors/trailheadtechnology.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Trailhead Technology", - "lastName": "", - "stateOrRegion": "Jenison, MI", - "twitterHandle": "@TrailheadTec", - "emailAddress": "info@trailheadtechnology.com", - "tagOrBio": "", - "gravatarHash": "0d6c200b64336790495f110332e1d7be", - "webSite": "https://www.trailheadtechnology.com/", - "feedUris": [ - "https://trailheadtechnology.com/feed" - ], - "githubHandle": "trailheadtechnology", - "position": { - "lat": 42.921436, - "lon": -85.806578 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/vulcanlee.json b/Authors/vulcanlee.json deleted file mode 100644 index 0067461..0000000 --- a/Authors/vulcanlee.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Vulcan", - "lastName": "Lee", - "stateOrRegion": "Taipei, Taiwan", - "twitterHandle": "vulcanlee", - "emailAddress": "vulcan.lee@gmail.com", - "tagOrBio": "Vulcan Lee is a Microsoft MVP who develops Xamarin at Doggy Ltd", - "gravatarHash": "f5de84ba365a15a05748624c07e70075", - "webSite": "https://mylabtw.blogspot.com/", - "feedUris": [ - "https://mylabtw.blogspot.com/feeds/posts/default?alt=rss" - ], - "githubHandle": "vulcanlee", - "position": { - "lat": 25.043847, - "lon": 121.525645 - }, - "languageCode": "zh" -} \ No newline at end of file diff --git a/Authors/willsb.json b/Authors/willsb.json deleted file mode 100644 index 16de042..0000000 --- a/Authors/willsb.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "William", - "lastName": "Barbosa", - "stateOrRegion": "Santos, Brasil", - "twitterHandle": "willdotnet", - "githubHandle": "willsb", - "tagOrBio": "Microsoft MVP, Blogger, Speaker, Monkey Nights Co-founder/Host and MvvmCross contributor", - "emailAddress": "heytherewill@gmail.com", - "gravatarHash": "e47d219e8ee5d6dd5a44940dc26585c4", - "position": { - "lat": -23.954821, - "lon": -46.3247136 - }, - "webSite": "https://willsb.github.io", - "feedUris": [ - "https://medium.com/feed/@heytherewill" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/winstongubantes.json b/Authors/winstongubantes.json deleted file mode 100644 index 0fd50d0..0000000 --- a/Authors/winstongubantes.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Winston", - "lastName": "Gubantes", - "tagOrBio": "a Xamarin Developer at Gluon Consulting", - "stateOrRegion": "Davao City, Philippines", - "emailAddress": "winston.gubantes@gmail.com", - "twitterHandle": "tony_fear", - "gravatarHash": "9e1aea174237384361fc260b33559d05", - "githubHandle": "winstongubantes", - "position": { - "lat": 7.1066271, - "lon": 125.6294976 - }, - "webSite": "https://winstongubantes.blogspot.com/", - "feedUris": [ - "https://winstongubantes.blogspot.com/feeds/posts/default?alt=rss" - ], - "languageCode": "es" -} \ No newline at end of file diff --git a/Authors/wislon.json b/Authors/wislon.json deleted file mode 100644 index 6e7fe33..0000000 --- a/Authors/wislon.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "John", - "lastName": "Wilson", - "stateOrRegion": "Brisbane, Australia", - "emailAddress": "wislon@hotmail.com", - "tagOrBio": "Head of Mobile, Tech Lead, Xamarin Specialist", - "webSite": "https://blog.wislon.io/", - "feedUris": [ - "https://blog.wislon.io/atom.xml" - ], - "twitterHandle": "wislon", - "gravatarHash": "86c9d925c04b4c79c98bfc839d949e15", - "githubHandle": "wislon", - "position": { - "lat": -27.3812513, - "lon": 152.7130138 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/xablu.json b/Authors/xablu.json deleted file mode 100644 index 0d8e711..0000000 --- a/Authors/xablu.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Xablu", - "lastName": "", - "stateOrRegion": "Amsterdam, the Netherlands", - "emailAddress": "hello@xablu.com", - "tagOrBio": "a 100% pure Xamarin company.", - "webSite": "https://www.xablu.com/", - "twitterHandle": "xabluhq", - "githubHandle": "xablu", - "gravatarHash": "508b1a99bb81e09c189e7487ecb69167", - "feedUris": [ - "https://www.xablu.com/tag/planet-xamarin/feed/" - ], - "position": { - "lat": 52.3702, - "lon": 4.8952 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/xamarin.json b/Authors/xamarin.json deleted file mode 100644 index 5ef90a8..0000000 --- a/Authors/xamarin.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "The Xamarin", - "lastName": "Blog", - "stateOrRegion": "Internet", - "emailAddress": "hello@xamarin.com", - "tagOrBio": "is your official source for Xamarin developer news.", - "webSite": "https://blog.xamarin.com", - "feedUris": [ - "https://devblogs.microsoft.com/xamarin/feed/" - ], - "twitterHandle": "xamarinhq", - "gravatarHash": "70148d964bb389d42547834e1062c886", - "githubHandle": "xamarin", - "position": { - "lat": 37.77493, - "lon": -122.419416 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/xamarinhowto.json b/Authors/xamarinhowto.json deleted file mode 100644 index 8b56c33..0000000 --- a/Authors/xamarinhowto.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "firstName": "Matt", - "lastName": "Crombie", - "tagOrBio": "Senior Xamarin Mobile Consultant/Architect", - "stateOrRegion": "Brisbane, Australia", - "emailAddress": "matt@xamarinhowto.com", - "twitterHandle": "xamarinhowto", - "gravatarHash": "3f1200c50911032b3558b7c0fc29c847", - "githubHandle": "xamarinhowto", - "position": { - "lat": -27.46845, - "lon": 153.024184 - }, - "webSite": "https://xamarinhowto.com/", - "feedUris": [ - "https://xamarinhowto.com/feed/" - ], - "languageCode": "en" -} \ No newline at end of file diff --git a/Authors/yuv4ik.json b/Authors/yuv4ik.json deleted file mode 100644 index 0055a33..0000000 --- a/Authors/yuv4ik.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "firstName": "Evgeny", - "lastName": "Zborovsky", - "stateOrRegion": "Estonia", - "emailAddress": "yuv4ik@gmail.com", - "tagOrBio": "blogs, speaks, helps others and is an enthusiastic Xamarin developer.", - "webSite": "https://evgenyzborovsky.com/", - "feedUris": [ - "https://evgenyzborovsky.com/tag/xamarin-forms/feed/", - "https://evgenyzborovsky.com/tag/xamarin/feed/" - ], - "twitterHandle": "ezborovsky", - "gravatarHash": "b8a0ab8445fafb38afdf26cb976df32f", - "githubHandle": "yuv4ik", - "position": { - "lat": 58.3750372, - "lon": 26.6625567 - }, - "languageCode": "en" -} \ No newline at end of file diff --git a/PlanetDotnet.sln b/PlanetDotnet.sln index 0ecf4f3..7d9a35a 100644 --- a/PlanetDotnet.sln +++ b/PlanetDotnet.sln @@ -3,30 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.32804.182 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlanetDotnet", "PlanetDotnet\PlanetDotnet.csproj", "{591E633A-5665-4D84-B4B6-02B627699AC4}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8FA44784-F2C3-4749-9272-38F4D8EA9398}" - ProjectSection(SolutionItems) = preProject - author-schema.json = author-schema.json - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlanetDotnetAuthors", "PlanetDotnetAuthors\PlanetDotnetAuthors.csproj", "{57C40CD9-62CF-48D2-9A5E-C4CDD1CE6082}" -EndProject Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {591E633A-5665-4D84-B4B6-02B627699AC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {591E633A-5665-4D84-B4B6-02B627699AC4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {591E633A-5665-4D84-B4B6-02B627699AC4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {591E633A-5665-4D84-B4B6-02B627699AC4}.Release|Any CPU.Build.0 = Release|Any CPU - {57C40CD9-62CF-48D2-9A5E-C4CDD1CE6082}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {57C40CD9-62CF-48D2-9A5E-C4CDD1CE6082}.Debug|Any CPU.Build.0 = Debug|Any CPU - {57C40CD9-62CF-48D2-9A5E-C4CDD1CE6082}.Release|Any CPU.ActiveCfg = Release|Any CPU - {57C40CD9-62CF-48D2-9A5E-C4CDD1CE6082}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection diff --git a/PlanetDotnet/Extensions/SyndicationItemExtensions.cs b/PlanetDotnet/Extensions/SyndicationItemExtensions.cs deleted file mode 100644 index 5cb476d..0000000 --- a/PlanetDotnet/Extensions/SyndicationItemExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Linq; -using System.ServiceModel.Syndication; - -namespace PlanetDotnet.Extensions -{ - public static class SyndicationItemExtensions - { - public static bool ApplyDefaultFilter(this SyndicationItem item) - { - if (item == null) - return false; - - var hasXamarinCategory = false; - var hasXamarinKeywords = false; - - if (item.Categories.Count > 0) - { - hasXamarinCategory = item.Categories.Any(category => - category.Name.ToLowerInvariant().Contains("xamarin")); - } - - if (item.ElementExtensions.Count > 0) - { - var element = item.ElementExtensions.FirstOrDefault(e => e.OuterName == "keywords"); - if (element != null) - { - var keywords = element.GetObject(); - hasXamarinKeywords = keywords.ToLowerInvariant().Contains("xamarin"); - } - } - - var hasXamarinTitle = item.Title?.Text.ToLowerInvariant().Contains("xamarin") ?? false; - - return hasXamarinTitle || hasXamarinCategory || hasXamarinKeywords; - } - - public static string ToHtml(this SyndicationContent content) - { - var textSyndicationContent = content as TextSyndicationContent; - if (textSyndicationContent != null) - { - return textSyndicationContent.Text; - } - - return content.ToString(); - } - } -} \ No newline at end of file diff --git a/PlanetDotnet/Infrastructure/CombinedFeedSource.cs b/PlanetDotnet/Infrastructure/CombinedFeedSource.cs deleted file mode 100644 index 61b204f..0000000 --- a/PlanetDotnet/Infrastructure/CombinedFeedSource.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.ServiceModel.Syndication; -using System.Threading.Tasks; -using System.Xml; -using Microsoft.Extensions.Logging; -using PlanetDotnet.Extensions; -using PlanetDotnetAuthors.Models; -using Polly; -using Polly.Retry; - -namespace PlanetDotnet.Infrastructure -{ - public class CombinedFeedSource - { - private static HttpClient _httpClient; - private static AsyncRetryPolicy _retryPolicy; - private readonly IEnumerable _authors; - private readonly ILogger _logger; - private readonly string _rssFeedTitle; - private readonly string _rssFeedDescription; - private readonly string _rssFeedUrl; - private readonly string _rssFeedImageUrl; - - public CombinedFeedSource( - IEnumerable authors, - ILogger logger, - string rssFeedTitle, - string rssFeedDescription, - string rssFeedUrl, - string rssFeedImageUrl) - { - EnsureHttpClient(); - - if (_retryPolicy == null) - { - // retry policy with max 2 retries, delay by x*x^1.2 where x is retry attempt - // this will ensure we don't retry too quickly - _retryPolicy = Policy.Handle() - .WaitAndRetryAsync(2, retry => TimeSpan.FromSeconds(retry * Math.Pow(1.2, retry))); - } - - _authors = authors; - _logger = logger; - _rssFeedTitle = rssFeedTitle; - _rssFeedDescription = rssFeedDescription; - _rssFeedUrl = rssFeedUrl; - _rssFeedImageUrl = rssFeedImageUrl; - } - - private void EnsureHttpClient() - { - if (_httpClient == null) - { - _httpClient = new HttpClient(); - _httpClient.DefaultRequestHeaders.UserAgent.Add( - new ProductInfoHeaderValue("PlanetDotnet", $"{GetType().Assembly.GetName().Version}")); - _httpClient.Timeout = TimeSpan.FromSeconds(15); - - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13; - } - } - - public async Task LoadFeed(int? numberOfItems, string languageCode = "mixed") - { - IEnumerable tamarins; - if (languageCode == null || languageCode == "mixed") // use all tamarins - { - tamarins = _authors; - } - else - { - tamarins = _authors.Where(t => t.FeedLanguageCode == languageCode); - } - - var feedTasks = tamarins.SelectMany(t => TryReadFeeds(t)).ToArray(); - - _logger?.LogInformation($"Loading feed for language: {languageCode} for {feedTasks.Length} authors"); - - var syndicationItems = await Task.WhenAll(feedTasks).ConfigureAwait(false); - var combinedFeed = GetCombinedFeed(syndicationItems.SelectMany(f => f), languageCode, tamarins, numberOfItems); - return combinedFeed; - } - - private IEnumerable>> TryReadFeeds(Author tamarin) - { - return tamarin.FeedUris.Select(uri => TryReadFeed(tamarin, uri.AbsoluteUri)); - } - - private async Task> TryReadFeed(Author tamarin, string feedUri) - { - try - { - return await _retryPolicy.ExecuteAsync(context => ReadFeed(feedUri), new Context(feedUri)).ConfigureAwait(false); - } - catch (FeedReadFailedException ex) - { - _logger.LogError(ex, $"{tamarin.FirstName} {tamarin.LastName}'s feed of {ex.Data["FeedUri"]} failed to load."); - } - - return new SyndicationItem[0]; - } - - private async Task> ReadFeed(string feedUri) - { - HttpResponseMessage response; - try - { - _logger?.LogInformation($"Loading feed {feedUri}"); - response = await _httpClient.GetAsync(feedUri).ConfigureAwait(false); - if (response.IsSuccessStatusCode) - { - using var feedStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - using var reader = XmlReader.Create(feedStream); - var feed = SyndicationFeed.Load(reader); - var filteredItems = feed.Items - .Where(item => item.ApplyDefaultFilter()); - - return filteredItems; - } - } - catch (HttpRequestException hex) - { - throw new FeedReadFailedException("Loading remote syndication feed failed", hex) - .WithData("FeedUri", feedUri); - } - catch (WebException ex) - { - throw new FeedReadFailedException("Loading remote syndication feed timed out", ex) - .WithData("FeedUri", feedUri); - } - catch (XmlException ex) - { - throw new FeedReadFailedException("Failed parsing remote syndication feed", ex) - .WithData("FeedUri", feedUri); - } - catch (TaskCanceledException ex) - { - throw new FeedReadFailedException("Reading feed timed out", ex) - .WithData("FeedUri", feedUri); - } - catch (OperationCanceledException opcex) - { - throw new FeedReadFailedException("Reading feed timed out", opcex) - .WithData("FeedUri", feedUri); - } - - throw new FeedReadFailedException("Loading remote syndication feed failed.") - .WithData("FeedUri", feedUri) - .WithData("HttpStatusCode", (int)response.StatusCode); - } - - private SyndicationFeed GetCombinedFeed(IEnumerable items, string languageCode, - IEnumerable tamarins, int? numberOfItems) - { - DateTimeOffset GetMaxTime(SyndicationItem item) - { - return new[] { item.PublishDate.UtcDateTime, item.LastUpdatedTime.UtcDateTime }.Max(); - } - - var orderedItems = items - .Where(item => - GetMaxTime(item) <= DateTimeOffset.UtcNow) - .OrderByDescending(item => GetMaxTime(item)); - - var feed = new SyndicationFeed( - _rssFeedTitle, - _rssFeedDescription, - new Uri(_rssFeedUrl), - numberOfItems.HasValue ? orderedItems.Take(numberOfItems.Value) : orderedItems) - { - ImageUrl = new Uri(_rssFeedImageUrl), - Copyright = new TextSyndicationContent("The copyright for each post is retained by its author."), - Language = languageCode, - LastUpdatedTime = DateTimeOffset.UtcNow - }; - - foreach (var tamarin in tamarins) - { - feed.Contributors.Add(new SyndicationPerson( - tamarin.EmailAddress, $"{tamarin.FirstName} {tamarin.LastName}", tamarin.WebSite.ToString())); - } - - return feed; - } - } - - public class FeedReadFailedException : Exception - { - public FeedReadFailedException(string message) - : base(message) - { - } - - public FeedReadFailedException(string message, Exception inner) - : base(message, inner) - { - } - } - - internal static class ExceptionExtensions - { - public static TException WithData(this TException exception, string key, object value) where TException : Exception - { - exception.Data[key] = value; - return exception; - } - } -} \ No newline at end of file diff --git a/PlanetDotnet/LoadFeedsFunction.cs b/PlanetDotnet/LoadFeedsFunction.cs deleted file mode 100644 index 00451f0..0000000 --- a/PlanetDotnet/LoadFeedsFunction.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.ServiceModel.Syndication; -using System.Threading.Tasks; -using System.Xml; -using Azure.Storage.Blobs; -using Azure.Storage.Blobs.Models; -using Microsoft.Azure.WebJobs; -using Microsoft.Extensions.Logging; -using PlanetDotnet.Infrastructure; -using PlanetDotnetAuthors; - -namespace PlanetDotnet -{ - public static class LoadFeedsFunction - { - [FunctionName("LoadFeedsFunction")] - public static async Task Run( - [TimerTrigger("0 0 */1 * * *", RunOnStartup = true)] TimerInfo myTimer, - ILogger log) - { - log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}"); - - var rssFeedTitle = GetEnvironmentVariable("RssFeedTitle"); - var rssFeedDescription = GetEnvironmentVariable("RssFeedDescription"); - var rssFeedUrl = GetEnvironmentVariable("RssFeedUrl"); - var rssFeedImageUrl = GetEnvironmentVariable("RssFeedImageUrl"); - - var authors = await AuthorsLoader.GetAllAuthors(); - var languages = authors.Select(author => author.FeedLanguageCode).Distinct().ToList(); - languages.Add("mixed"); - var feedSource = - new CombinedFeedSource( - authors, - log, - rssFeedTitle, - rssFeedDescription, - rssFeedUrl, - rssFeedImageUrl); - - var blobConnectString = GetEnvironmentVariable("FeedBlobStorage"); - var container = new BlobContainerClient(blobConnectString, "feeds"); - await container.CreateIfNotExistsAsync(); - await container.SetAccessPolicyAsync(PublicAccessType.Blob); - - foreach (var language in languages) - { - log.LogInformation($"Loading {language} combined author feed"); - var feed = await feedSource.LoadFeed(null, language); - using var stream = await SerializeFeed(feed); - await UploadBlob(container, stream, language, log); - } - } - - private static async Task UploadBlob(BlobContainerClient container, Stream feedStream, string language, ILogger log) - { - var feedName = $"feed.{language}.rss"; - var blob = container.GetBlobClient(feedName); - await blob.UploadAsync(feedStream, overwrite: true); - - log.LogInformation($"Uploaded {feedName} to {blob.Uri}"); - } - - private static async Task SerializeFeed(SyndicationFeed feed) - { - var memoryStream = new MemoryStream(); - using var xmlWriter = XmlWriter.Create(memoryStream, new XmlWriterSettings - { - Async = true - }); - - var rssFormatter = new Rss20FeedFormatter(feed); - rssFormatter.WriteTo(xmlWriter); - await xmlWriter.FlushAsync(); - - memoryStream.Seek(0, SeekOrigin.Begin); - - return memoryStream; - } - - private static string GetEnvironmentVariable(string name) - { - return Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process); - } - } -} diff --git a/PlanetDotnet/PlanetDotnet.csproj b/PlanetDotnet/PlanetDotnet.csproj deleted file mode 100644 index 1f7ca27..0000000 --- a/PlanetDotnet/PlanetDotnet.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - netcoreapp3.1 - v3 - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - Never - - - \ No newline at end of file diff --git a/PlanetDotnet/Properties/serviceDependencies.json b/PlanetDotnet/Properties/serviceDependencies.json deleted file mode 100644 index fcc92d1..0000000 --- a/PlanetDotnet/Properties/serviceDependencies.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "dependencies": { - "storage1": { - "type": "storage", - "connectionId": "AzureWebJobsStorage" - } - } -} \ No newline at end of file diff --git a/PlanetDotnet/Properties/serviceDependencies.local.json b/PlanetDotnet/Properties/serviceDependencies.local.json deleted file mode 100644 index 155d87e..0000000 --- a/PlanetDotnet/Properties/serviceDependencies.local.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "dependencies": { - "storage1": { - "type": "storage.emulator", - "connectionId": "AzureWebJobsStorage" - } - } -} \ No newline at end of file diff --git a/PlanetDotnet/host.json b/PlanetDotnet/host.json deleted file mode 100644 index bb3b8da..0000000 --- a/PlanetDotnet/host.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingExcludedTypes": "Request", - "samplingSettings": { - "isEnabled": true - } - } - } -} \ No newline at end of file diff --git a/PlanetDotnet/local.settings.json b/PlanetDotnet/local.settings.json deleted file mode 100644 index 6f0b5a7..0000000 --- a/PlanetDotnet/local.settings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "FUNCTIONS_WORKER_RUNTIME": "dotnet", - "RssFeedTitle": "Planet Xamarin", - "RssFeedDescription": "An aggregated feed from the Xamarin community", - "RssFeedUrl": "https://www.planetxamarin.com/feed", - "RssFeedImageUrl": "https://www.planetxamarin.com/Content/Logo.png", - "FeedBlobStorage": "" - } -} \ No newline at end of file diff --git a/PlanetDotnetAuthors/AuthorsLoader.cs b/PlanetDotnetAuthors/AuthorsLoader.cs deleted file mode 100644 index 031840f..0000000 --- a/PlanetDotnetAuthors/AuthorsLoader.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Newtonsoft.Json; -using PlanetDotnetAuthors.Models; - -namespace PlanetDotnetAuthors -{ - public static class AuthorsLoader - { - public static async Task> GetAllAuthors() - { - var assembly = Assembly.GetExecutingAssembly(); - var resourceNames = assembly.GetManifestResourceNames(); - var authorsResourceNames = resourceNames.Where(res => - res.StartsWith("PlanetDotnetAuthors", StringComparison.OrdinalIgnoreCase) && - res.EndsWith(".json", StringComparison.OrdinalIgnoreCase)); - - var authorsTasks = authorsResourceNames.Select(name => ReadAuthor(assembly, name)); - var authors = await Task.WhenAll(authorsTasks).ConfigureAwait(false); - return authors; - } - - private static async Task ReadAuthor(Assembly assembly, string authorResourceName) - { - using var stream = assembly.GetManifestResourceStream(authorResourceName); - using var reader = new StreamReader(stream); - var json = await reader.ReadToEndAsync().ConfigureAwait(false); - var author = JsonConvert.DeserializeObject(json); - return author; - } - } -} diff --git a/PlanetDotnetAuthors/Models/Author.cs b/PlanetDotnetAuthors/Models/Author.cs deleted file mode 100644 index 6634bc5..0000000 --- a/PlanetDotnetAuthors/Models/Author.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using Newtonsoft.Json; - -namespace PlanetDotnetAuthors.Models -{ - public class Author - { - [JsonProperty("firstName", Required = Required.DisallowNull)] - public string FirstName { get; set; } - [JsonProperty("lastName", Required = Required.DisallowNull)] - public string LastName { get; set; } - [JsonProperty("stateOrRegion", Required = Required.DisallowNull)] - public string StateOrRegion { get; set; } - [EmailAddress] - [JsonProperty("emailAddress", Required = Required.Always)] - public string EmailAddress { get; set; } - [JsonProperty("tagOrBio", Required = Required.DisallowNull)] - public string ShortBioOrTagLine { get; set; } - [Url] - [JsonProperty("webSite", Required = Required.Always)] - public Uri WebSite { get; set; } - [JsonProperty("twitterHandle", Required = Required.DisallowNull)] - public string TwitterHandle { get; set; } - [JsonProperty("githubHandle", Required = Required.Always)] - public string GitHubHandle { get; set; } - [JsonProperty("gravatarHash", Required = Required.DisallowNull)] - public string GravatarHash { get; set; } - [JsonProperty("feedUris", Required = Required.Always)] - public IEnumerable FeedUris { get; set; } - [JsonProperty("position", Required = Required.DisallowNull)] - public GeoPosition Position { get; set; } - - // In ISO 639-1, lowercase, 2 letters - // https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes - [JsonProperty("languageCode", Required = Required.Always)] - public string FeedLanguageCode { get; set; } - } -} diff --git a/PlanetDotnetAuthors/Models/GeoPosition.cs b/PlanetDotnetAuthors/Models/GeoPosition.cs deleted file mode 100644 index ccc81c2..0000000 --- a/PlanetDotnetAuthors/Models/GeoPosition.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Newtonsoft.Json; - -namespace PlanetDotnetAuthors.Models -{ - public class GeoPosition - { - public static GeoPosition Empty = new GeoPosition(-1337, 42); - - [JsonProperty("lat", Required = Required.Always)] - public double Lat { get; } - [JsonProperty("lon", Required = Required.Always)] - public double Lng { get; } - - public GeoPosition(double lat, double lng) - { - Lat = lat; - Lng = lng; - } - } -} diff --git a/PlanetDotnetAuthors/PlanetDotnetAuthors.csproj b/PlanetDotnetAuthors/PlanetDotnetAuthors.csproj deleted file mode 100644 index cb36460..0000000 --- a/PlanetDotnetAuthors/PlanetDotnetAuthors.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - netstandard2.0 - latest - - - - - - - - - - - diff --git a/author-schema.json b/author-schema.json deleted file mode 100644 index 4388d1e..0000000 --- a/author-schema.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - { - "definitions": { - "GeoPosition": { - "type": "object", - "properties": { - "lat": { - "type": "number" - }, - "lon": { - "type": "number" - } - }, - "required": [ - "lat", - "lon" - ] - } - }, - "type": "object", - "properties": { - "firstName": { - "description": "Author First Name", - "type": "string" - }, - "lastName": { - "description": "Author Last Name", - "type": "string" - }, - "stateOrRegion": { - "description": "Author State or Region", - "type": "string" - }, - "emailAddress": { - "description": "E-mail address", - "type": "string", - "format": "email" - }, - "tagOrBio": { - "description": "Tagline or bio", - "type": "string" - }, - "webSite": { - "description": "Author Web Site", - "type": "string", - "format": "uri" - }, - "feedUris": { - "description": "Feed URIs", - "type": "array", - "items": { - "type": [ - "string", - "null" - ], - "format": "uri" - } - }, - "twitterHandle": { - "description": "Author Twitter Handle", - "type": "string" - }, - "gravatarHash": { - "description": "Author Gravatar Hash", - "type": "string" - }, - "githubHandle": { - "description": "Author GitHub Handle", - "type": "string" - }, - "position": { - "description": "Author GeoPosition", - "$ref": "#/definitions/GeoPosition" - }, - "languageCode": { - "description": "Feed Language Code. ISO 639-1 format.", - "type": "string" - } - }, - "required": [ - "emailAddress", - "webSite", - "feedUris", - "githubHandle", - "languageCode" - ] - } -} \ No newline at end of file From 0629df002a4e4dfdb94199479b2bb3c136f0387d Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Sat, 18 Nov 2023 22:27:36 +0100 Subject: [PATCH 03/28] INFRA: Initialize Project --- .editorconfig | 26 ++ PlanetDotnet.sln | 12 + PlanetDotnet/.gitignore | 264 ++++++++++++++++++ PlanetDotnet/LoadFeedsFunction.cs | 40 +++ PlanetDotnet/PlanetDotnet.csproj | 22 ++ PlanetDotnet/Properties/launchSettings.json | 9 + .../Properties/serviceDependencies.json | 11 + .../Properties/serviceDependencies.local.json | 11 + PlanetDotnet/Startup.cs | 17 ++ PlanetDotnet/host.json | 12 + 10 files changed, 424 insertions(+) create mode 100644 .editorconfig create mode 100644 PlanetDotnet/.gitignore create mode 100644 PlanetDotnet/LoadFeedsFunction.cs create mode 100644 PlanetDotnet/PlanetDotnet.csproj create mode 100644 PlanetDotnet/Properties/launchSettings.json create mode 100644 PlanetDotnet/Properties/serviceDependencies.json create mode 100644 PlanetDotnet/Properties/serviceDependencies.local.json create mode 100644 PlanetDotnet/Startup.cs create mode 100644 PlanetDotnet/host.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..660f3ad --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +# To learn more about .editorconfig see https://aka.ms/editorconfigdocs + +# C# or VB files +[*.{cs,vb}] +guidelines = 120 + +#### Core EditorConfig Options #### + +#Formatting - header template +file_header_template = ---------------------------------------------------------------\nCopyright (c) 2023 Planet Dotnet. All rights reserved.\nLicensed under the MIT License.\nSee License.txt in the project root for license information.\n--------------------------------------------------------------- + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + diff --git a/PlanetDotnet.sln b/PlanetDotnet.sln index 7d9a35a..1eec153 100644 --- a/PlanetDotnet.sln +++ b/PlanetDotnet.sln @@ -3,7 +3,19 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.32804.182 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanetDotnet", "PlanetDotnet\PlanetDotnet.csproj", "{AA1CF7A0-FF81-40FA-ABB2-519AF44C2460}" +EndProject Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AA1CF7A0-FF81-40FA-ABB2-519AF44C2460}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA1CF7A0-FF81-40FA-ABB2-519AF44C2460}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA1CF7A0-FF81-40FA-ABB2-519AF44C2460}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA1CF7A0-FF81-40FA-ABB2-519AF44C2460}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection diff --git a/PlanetDotnet/.gitignore b/PlanetDotnet/.gitignore new file mode 100644 index 0000000..ff5b00c --- /dev/null +++ b/PlanetDotnet/.gitignore @@ -0,0 +1,264 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Azure Functions localsettings file +local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/PlanetDotnet/LoadFeedsFunction.cs b/PlanetDotnet/LoadFeedsFunction.cs new file mode 100644 index 0000000..8d9b79a --- /dev/null +++ b/PlanetDotnet/LoadFeedsFunction.cs @@ -0,0 +1,40 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace PlanetDotnet +{ + public static class LoadFeedsFunction + { + [FunctionName("LoadFeedsFunction")] + public static async Task Run( + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, + ILogger log) + { + log.LogInformation("C# HTTP trigger function processed a request."); + + string name = req.Query["name"]; + + string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); + dynamic data = JsonConvert.DeserializeObject(requestBody); + name = name ?? data?.name; + + string responseMessage = string.IsNullOrEmpty(name) + ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response." + : $"Hello, {name}. This HTTP triggered function executed successfully."; + + return new OkObjectResult(responseMessage); + } + } +} diff --git a/PlanetDotnet/PlanetDotnet.csproj b/PlanetDotnet/PlanetDotnet.csproj new file mode 100644 index 0000000..8b0fb18 --- /dev/null +++ b/PlanetDotnet/PlanetDotnet.csproj @@ -0,0 +1,22 @@ + + + net6.0 + v4 + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + diff --git a/PlanetDotnet/Properties/launchSettings.json b/PlanetDotnet/Properties/launchSettings.json new file mode 100644 index 0000000..7076a81 --- /dev/null +++ b/PlanetDotnet/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "PlanetDotnet": { + "commandName": "Project", + "commandLineArgs": "--port 7287", + "launchBrowser": false + } + } +} \ No newline at end of file diff --git a/PlanetDotnet/Properties/serviceDependencies.json b/PlanetDotnet/Properties/serviceDependencies.json new file mode 100644 index 0000000..df4dcc9 --- /dev/null +++ b/PlanetDotnet/Properties/serviceDependencies.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights" + }, + "storage1": { + "type": "storage", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/PlanetDotnet/Properties/serviceDependencies.local.json b/PlanetDotnet/Properties/serviceDependencies.local.json new file mode 100644 index 0000000..b804a28 --- /dev/null +++ b/PlanetDotnet/Properties/serviceDependencies.local.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights.sdk" + }, + "storage1": { + "type": "storage.emulator", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/PlanetDotnet/Startup.cs b/PlanetDotnet/Startup.cs new file mode 100644 index 0000000..779de6d --- /dev/null +++ b/PlanetDotnet/Startup.cs @@ -0,0 +1,17 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using Microsoft.Azure.Functions.Extensions.DependencyInjection; + +[assembly: FunctionsStartup(typeof(PlanetDotnet.Startup))] +namespace PlanetDotnet +{ + public class Startup : FunctionsStartup + { + public override void Configure(IFunctionsHostBuilder builder) + { } + } +} diff --git a/PlanetDotnet/host.json b/PlanetDotnet/host.json new file mode 100644 index 0000000..ee5cf5f --- /dev/null +++ b/PlanetDotnet/host.json @@ -0,0 +1,12 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + }, + "enableLiveMetricsFilters": true + } + } +} \ No newline at end of file From 52d0c7efe83653d10f4d6710d076b0e8b9ca827f Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Sat, 18 Nov 2023 22:30:12 +0100 Subject: [PATCH 04/28] INFRA: Initialize Unit Tests Project --- PlanetDotnet.Tests.Unit/DeleteMe.cs | 16 ++++++++++ .../PlanetDotnet.Tests.Unit.csproj | 29 +++++++++++++++++++ PlanetDotnet.sln | 6 ++++ 3 files changed, 51 insertions(+) create mode 100644 PlanetDotnet.Tests.Unit/DeleteMe.cs create mode 100644 PlanetDotnet.Tests.Unit/PlanetDotnet.Tests.Unit.csproj diff --git a/PlanetDotnet.Tests.Unit/DeleteMe.cs b/PlanetDotnet.Tests.Unit/DeleteMe.cs new file mode 100644 index 0000000..bdf1bb0 --- /dev/null +++ b/PlanetDotnet.Tests.Unit/DeleteMe.cs @@ -0,0 +1,16 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using Xunit; + +namespace PlanetDotnet.Tests.Unit +{ + public class DeleteMe + { + [Fact] + public void ShouldBeTrue() => Assert.True(true); + } +} \ No newline at end of file diff --git a/PlanetDotnet.Tests.Unit/PlanetDotnet.Tests.Unit.csproj b/PlanetDotnet.Tests.Unit/PlanetDotnet.Tests.Unit.csproj new file mode 100644 index 0000000..8180b84 --- /dev/null +++ b/PlanetDotnet.Tests.Unit/PlanetDotnet.Tests.Unit.csproj @@ -0,0 +1,29 @@ + + + + net7.0 + disable + disable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/PlanetDotnet.sln b/PlanetDotnet.sln index 1eec153..aa0ae85 100644 --- a/PlanetDotnet.sln +++ b/PlanetDotnet.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.4.32804.182 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanetDotnet", "PlanetDotnet\PlanetDotnet.csproj", "{AA1CF7A0-FF81-40FA-ABB2-519AF44C2460}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanetDotnet.Tests.Unit", "PlanetDotnet.Tests.Unit\PlanetDotnet.Tests.Unit.csproj", "{ED070F5D-3C55-4E8F-B23A-381E09931D1C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {AA1CF7A0-FF81-40FA-ABB2-519AF44C2460}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA1CF7A0-FF81-40FA-ABB2-519AF44C2460}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA1CF7A0-FF81-40FA-ABB2-519AF44C2460}.Release|Any CPU.Build.0 = Release|Any CPU + {ED070F5D-3C55-4E8F-B23A-381E09931D1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED070F5D-3C55-4E8F-B23A-381E09931D1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED070F5D-3C55-4E8F-B23A-381E09931D1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED070F5D-3C55-4E8F-B23A-381E09931D1C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 32ea601dfa70279bb928772f2bfc843a48e3f9a8 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Sat, 18 Nov 2023 22:47:37 +0100 Subject: [PATCH 05/28] INFRA: Initialize Authors Project --- Authors/AlejandroRuiz.json | 19 ++++++++ Authors/AnbuMani27.json | 19 ++++++++ Authors/Cheesebaron.json | 19 ++++++++ Authors/DamianMehers.json | 19 ++++++++ Authors/DanRigby.json | 19 ++++++++ Authors/JesseLiberty.json | 19 ++++++++ Authors/JoeM-RP.json | 19 ++++++++ Authors/JonDouglas.json | 19 ++++++++ Authors/JuuCustodio.json | 19 ++++++++ Authors/LeomarisReyes.json | 19 ++++++++ Authors/LetsCreateSeries.json | 19 ++++++++ Authors/MarcBruins.json | 19 ++++++++ Authors/MartinZikmund.json | 19 ++++++++ Authors/Martynnw.json | 19 ++++++++ Authors/MergeConflictFM.json | 19 ++++++++ Authors/PieEatingNinjas.json | 19 ++++++++ Authors/Prin53.json | 19 ++++++++ Authors/Pujolsluis.json | 19 ++++++++ Authors/ReactiveUI.json | 19 ++++++++ Authors/Sankra.json | 19 ++++++++ Authors/StefanRiedmann.json | 19 ++++++++ Authors/TheFo2sh.json | 19 ++++++++ Authors/TheXamarinShow.json | 19 ++++++++ Authors/TomSoderling.json | 19 ++++++++ Authors/UdaraAlwis.json | 19 ++++++++ Authors/VicenteGuzman.json | 19 ++++++++ Authors/XamarinPodcast.json | 19 ++++++++ Authors/acaliaro.json | 19 ++++++++ Authors/ahoefling.json | 19 ++++++++ Authors/akamud.json | 19 ++++++++ Authors/alexsorokoletov.json | 19 ++++++++ Authors/almirvuk.json | 19 ++++++++ Authors/andreas-nesheim.json | 19 ++++++++ Authors/aritchie.json | 19 ++++++++ Authors/asfend.json | 19 ++++++++ Authors/aspnetde.json | 19 ++++++++ Authors/balivo.json | 19 ++++++++ Authors/banditoth.json | 19 ++++++++ Authors/basdecort.json | 19 ++++++++ Authors/bbenetskyy.json | 19 ++++++++ Authors/brminnick.json | 19 ++++++++ Authors/canbilgin.json | 19 ++++++++ Authors/chamons.json | 19 ++++++++ Authors/char0394.json | 19 ++++++++ Authors/cjlotz.json | 19 ++++++++ Authors/codemillmatt.json | 19 ++++++++ Authors/crswlls.json | 19 ++++++++ Authors/damienaicheh.json | 19 ++++++++ Authors/damiendoumer.json | 19 ++++++++ Authors/danielcauser.json | 19 ++++++++ Authors/danielmonettelli.json | 20 ++++++++ Authors/dansiegel.json | 19 ++++++++ Authors/davidbritch.json | 21 +++++++++ Authors/dhindrik.json | 19 ++++++++ Authors/divikiran.json | 19 ++++++++ Authors/dylanberry.json | 19 ++++++++ Authors/egbakou.json | 19 ++++++++ Authors/elbrinner.json | 19 ++++++++ Authors/felipebaltazar.json | 19 ++++++++ Authors/filipoff2.json | 19 ++++++++ Authors/framinosona.json | 19 ++++++++ Authors/gonemobilecast.json | 19 ++++++++ Authors/gptucci.json | 19 ++++++++ Authors/gshackles.json | 19 ++++++++ Authors/hnabbasi.json | 19 ++++++++ Authors/ionixjunior.json | 19 ++++++++ Authors/jamesmontemagno.json | 19 ++++++++ Authors/jesulink2514.json | 20 ++++++++ Authors/jfversluis.json | 19 ++++++++ Authors/jgiacomini.json | 19 ++++++++ Authors/jimbobbennett.json | 19 ++++++++ Authors/johnthiriet.json | 19 ++++++++ Authors/jssuthahar.json | 19 ++++++++ Authors/jsuarezruiz.json | 19 ++++++++ Authors/jtaubensee.json | 19 ++++++++ Authors/kent_boogaart.json | 19 ++++++++ Authors/kphillpotts.json | 19 ++++++++ Authors/kwlothrop.json | 19 ++++++++ Authors/logeshpalani98.json | 19 ++++++++ Authors/luismts.json | 20 ++++++++ Authors/mabroukmahdhi.json | 19 ++++++++ Authors/mallibone.json | 19 ++++++++ Authors/marcofolio.json | 19 ++++++++ Authors/marcusts.json | 19 ++++++++ Authors/markolazic88.json | 19 ++++++++ Authors/martijn00.json | 19 ++++++++ Authors/mattleibow.json | 19 ++++++++ Authors/mfractor.json | 19 ++++++++ Authors/mike-grant.json | 19 ++++++++ Authors/mindofai.json | 19 ++++++++ Authors/mkieres.json | 19 ++++++++ Authors/msiccdev.json | 19 ++++++++ Authors/nickrandolph.json | 19 ++++++++ Authors/nigelferrissey.json | 19 ++++++++ Authors/officialdoniald.json | 19 ++++++++ Authors/peterfoot.json | 19 ++++++++ Authors/ramonesteban78.json | 19 ++++++++ Authors/rdavisau.json | 19 ++++++++ Authors/rdelrosario.json | 19 ++++++++ Authors/redth.json | 19 ++++++++ Authors/ricardoprestes.json | 19 ++++++++ Authors/rid00z.json | 19 ++++++++ Authors/robintschroeder.json | 19 ++++++++ Authors/roubachof.json | 19 ++++++++ Authors/saamerm.json | 19 ++++++++ Authors/sact1909.json | 19 ++++++++ Authors/samirgcofficial.json | 19 ++++++++ Authors/shirshov.json | 19 ++++++++ Authors/smstuebe.json | 19 ++++++++ Authors/sthewissen.json | 19 ++++++++ Authors/stvansolano.json | 19 ++++++++ Authors/susairajs.json | 19 ++++++++ Authors/syncfusion.json | 19 ++++++++ Authors/tbertuzzi.json | 19 ++++++++ Authors/telerik.json | 19 ++++++++ Authors/trailheadtechnology.json | 19 ++++++++ Authors/vulcanlee.json | 19 ++++++++ Authors/willsb.json | 19 ++++++++ Authors/winstongubantes.json | 19 ++++++++ Authors/wislon.json | 19 ++++++++ Authors/xablu.json | 19 ++++++++ Authors/xamarin.json | 19 ++++++++ Authors/xamarinhowto.json | 19 ++++++++ Authors/yuv4ik.json | 20 ++++++++ PlanetDotnet.Authors/Models/Authors/Author.cs | 47 +++++++++++++++++++ .../Models/GeoPositions/GeoPosition.cs | 26 ++++++++++ .../PlanetDotnet.Authors.csproj | 17 +++++++ PlanetDotnet.sln | 6 +++ 128 files changed, 2458 insertions(+) create mode 100644 Authors/AlejandroRuiz.json create mode 100644 Authors/AnbuMani27.json create mode 100644 Authors/Cheesebaron.json create mode 100644 Authors/DamianMehers.json create mode 100644 Authors/DanRigby.json create mode 100644 Authors/JesseLiberty.json create mode 100644 Authors/JoeM-RP.json create mode 100644 Authors/JonDouglas.json create mode 100644 Authors/JuuCustodio.json create mode 100644 Authors/LeomarisReyes.json create mode 100644 Authors/LetsCreateSeries.json create mode 100644 Authors/MarcBruins.json create mode 100644 Authors/MartinZikmund.json create mode 100644 Authors/Martynnw.json create mode 100644 Authors/MergeConflictFM.json create mode 100644 Authors/PieEatingNinjas.json create mode 100644 Authors/Prin53.json create mode 100644 Authors/Pujolsluis.json create mode 100644 Authors/ReactiveUI.json create mode 100644 Authors/Sankra.json create mode 100644 Authors/StefanRiedmann.json create mode 100644 Authors/TheFo2sh.json create mode 100644 Authors/TheXamarinShow.json create mode 100644 Authors/TomSoderling.json create mode 100644 Authors/UdaraAlwis.json create mode 100644 Authors/VicenteGuzman.json create mode 100644 Authors/XamarinPodcast.json create mode 100644 Authors/acaliaro.json create mode 100644 Authors/ahoefling.json create mode 100644 Authors/akamud.json create mode 100644 Authors/alexsorokoletov.json create mode 100644 Authors/almirvuk.json create mode 100644 Authors/andreas-nesheim.json create mode 100644 Authors/aritchie.json create mode 100644 Authors/asfend.json create mode 100644 Authors/aspnetde.json create mode 100644 Authors/balivo.json create mode 100644 Authors/banditoth.json create mode 100644 Authors/basdecort.json create mode 100644 Authors/bbenetskyy.json create mode 100644 Authors/brminnick.json create mode 100644 Authors/canbilgin.json create mode 100644 Authors/chamons.json create mode 100644 Authors/char0394.json create mode 100644 Authors/cjlotz.json create mode 100644 Authors/codemillmatt.json create mode 100644 Authors/crswlls.json create mode 100644 Authors/damienaicheh.json create mode 100644 Authors/damiendoumer.json create mode 100644 Authors/danielcauser.json create mode 100644 Authors/danielmonettelli.json create mode 100644 Authors/dansiegel.json create mode 100644 Authors/davidbritch.json create mode 100644 Authors/dhindrik.json create mode 100644 Authors/divikiran.json create mode 100644 Authors/dylanberry.json create mode 100644 Authors/egbakou.json create mode 100644 Authors/elbrinner.json create mode 100644 Authors/felipebaltazar.json create mode 100644 Authors/filipoff2.json create mode 100644 Authors/framinosona.json create mode 100644 Authors/gonemobilecast.json create mode 100644 Authors/gptucci.json create mode 100644 Authors/gshackles.json create mode 100644 Authors/hnabbasi.json create mode 100644 Authors/ionixjunior.json create mode 100644 Authors/jamesmontemagno.json create mode 100644 Authors/jesulink2514.json create mode 100644 Authors/jfversluis.json create mode 100644 Authors/jgiacomini.json create mode 100644 Authors/jimbobbennett.json create mode 100644 Authors/johnthiriet.json create mode 100644 Authors/jssuthahar.json create mode 100644 Authors/jsuarezruiz.json create mode 100644 Authors/jtaubensee.json create mode 100644 Authors/kent_boogaart.json create mode 100644 Authors/kphillpotts.json create mode 100644 Authors/kwlothrop.json create mode 100644 Authors/logeshpalani98.json create mode 100644 Authors/luismts.json create mode 100644 Authors/mabroukmahdhi.json create mode 100644 Authors/mallibone.json create mode 100644 Authors/marcofolio.json create mode 100644 Authors/marcusts.json create mode 100644 Authors/markolazic88.json create mode 100644 Authors/martijn00.json create mode 100644 Authors/mattleibow.json create mode 100644 Authors/mfractor.json create mode 100644 Authors/mike-grant.json create mode 100644 Authors/mindofai.json create mode 100644 Authors/mkieres.json create mode 100644 Authors/msiccdev.json create mode 100644 Authors/nickrandolph.json create mode 100644 Authors/nigelferrissey.json create mode 100644 Authors/officialdoniald.json create mode 100644 Authors/peterfoot.json create mode 100644 Authors/ramonesteban78.json create mode 100644 Authors/rdavisau.json create mode 100644 Authors/rdelrosario.json create mode 100644 Authors/redth.json create mode 100644 Authors/ricardoprestes.json create mode 100644 Authors/rid00z.json create mode 100644 Authors/robintschroeder.json create mode 100644 Authors/roubachof.json create mode 100644 Authors/saamerm.json create mode 100644 Authors/sact1909.json create mode 100644 Authors/samirgcofficial.json create mode 100644 Authors/shirshov.json create mode 100644 Authors/smstuebe.json create mode 100644 Authors/sthewissen.json create mode 100644 Authors/stvansolano.json create mode 100644 Authors/susairajs.json create mode 100644 Authors/syncfusion.json create mode 100644 Authors/tbertuzzi.json create mode 100644 Authors/telerik.json create mode 100644 Authors/trailheadtechnology.json create mode 100644 Authors/vulcanlee.json create mode 100644 Authors/willsb.json create mode 100644 Authors/winstongubantes.json create mode 100644 Authors/wislon.json create mode 100644 Authors/xablu.json create mode 100644 Authors/xamarin.json create mode 100644 Authors/xamarinhowto.json create mode 100644 Authors/yuv4ik.json create mode 100644 PlanetDotnet.Authors/Models/Authors/Author.cs create mode 100644 PlanetDotnet.Authors/Models/GeoPositions/GeoPosition.cs create mode 100644 PlanetDotnet.Authors/PlanetDotnet.Authors.csproj diff --git a/Authors/AlejandroRuiz.json b/Authors/AlejandroRuiz.json new file mode 100644 index 0000000..3c57034 --- /dev/null +++ b/Authors/AlejandroRuiz.json @@ -0,0 +1,19 @@ +{ + "firstName": "Alejandro", + "lastName": "Ruiz", + "tagOrBio": "Alejandro Ruiz is a Xamarin MVP, C# & Open Source Lover", + "stateOrRegion": "Guadalajara, Mexico", + "emailAddress": "alejandro@alejandroruizvarela.com", + "webSite": "https://alejandroruizvarela.blogspot.mx", + "feedUris": [ + "https://alejandroruizvarela.blogspot.mx/rss.xml" + ], + "twitterHandle": "alejandroruizva", + "gravatarHash": "35d0fff7dbc133e9fe2075aa14205a57", + "githubHandle": "AlejandroRuiz", + "position": { + "lat": 20.66682, + "lon": -103.39182 + }, + "languageCode": "es" +} \ No newline at end of file diff --git a/Authors/AnbuMani27.json b/Authors/AnbuMani27.json new file mode 100644 index 0000000..6927891 --- /dev/null +++ b/Authors/AnbuMani27.json @@ -0,0 +1,19 @@ +{ + "firstName": "Anbu", + "lastName": "Mani", + "tagOrBio": "is a Microsoft MVP who Blogger, Speaker, Founder & Organizer of XMonkeys360 Community – India.", + "emailAddress": "anbumani@xmonkeys360.com", + "twitterHandle": "anbu_mani27", + "gravatarHash": "f97650474e4aa5b9609a28dcfdb052d4", + "stateOrRegion": "Chennai, India", + "webSite": "https://www.xmonkeys360.com/", + "position": { + "lat": 12.902749, + "lon": 80.190846 + }, + "feedUris": [ + "https://xmonkeys360.com/feed/" + ], + "githubHandle": "AnbuMani27", + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/Cheesebaron.json b/Authors/Cheesebaron.json new file mode 100644 index 0000000..f218416 --- /dev/null +++ b/Authors/Cheesebaron.json @@ -0,0 +1,19 @@ +{ + "feedUris": [ + "https://blog.ostebaronen.dk/feed.xml" + ], + "firstName": "Tomasz", + "lastName": "Cielecki", + "stateOrRegion": "Copenhagen, Denmark", + "emailAddress": "tomasz@ostebaronen.dk", + "tagOrBio": "Open Source all the things!", + "webSite": "https://blog.ostebaronen.dk", + "twitterHandle": "Cheesebaron", + "githubHandle": "Cheesebaron", + "gravatarHash": "f780d57997526876b0625e517c1e0884", + "position": { + "lat": 55.8019193, + "lon": 12.523124 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/DamianMehers.json b/Authors/DamianMehers.json new file mode 100644 index 0000000..78a0e5d --- /dev/null +++ b/Authors/DamianMehers.json @@ -0,0 +1,19 @@ +{ + "firstName": "Damian", + "lastName": "Mehers", + "tagOrBio": "Independent Xamarin Developer", + "stateOrRegion": "Geneva, Switzerland", + "emailAddress": "damian@mehers.com", + "twitterHandle": "DamianMehers", + "gravatarHash": "d77613f4e20bfcae401a6bf0018a83d1", + "githubHandle": "DamianMehers", + "position": { + "lat": 46.3635288, + "lon": 6.1860801 + }, + "webSite": "https://damian.fyi/", + "feedUris": [ + "https://damian.fyi/feed/Xamarin.xml" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/DanRigby.json b/Authors/DanRigby.json new file mode 100644 index 0000000..7284a50 --- /dev/null +++ b/Authors/DanRigby.json @@ -0,0 +1,19 @@ +{ + "firstName": "Dan", + "lastName": "Rigby", + "tagOrBio": "is a Technical Solutions Professional", + "stateOrRegion": "Raleigh, North Carolina", + "emailAddress": "Dan.Rigby@Microsoft.com", + "twitterHandle": "DanRigby", + "githubHandle": "DanRigby", + "gravatarHash": "f025f772418fbcfd3a1e15a74bf0f8a4", + "position": { + "lat": 35.77959, + "lon": -78.638179 + }, + "webSite": "https://danrigby.com/", + "feedUris": [ + "https://feeds.feedburner.com/DanRigby" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/JesseLiberty.json b/Authors/JesseLiberty.json new file mode 100644 index 0000000..52dadc4 --- /dev/null +++ b/Authors/JesseLiberty.json @@ -0,0 +1,19 @@ +{ + "firstName": "Jesse", + "lastName": "Liberty", + "tagOrBio": "See http://jesseliberty.me", + "stateOrRegion": "Massachusetts", + "emailAddress": "jesseliberty@gmail.com", + "twitterHandle": "jesseliberty", + "gravatarHash": "78d5b6609fe5a80ce67e9f971833a6c3", + "githubHandle": "JesseLiberty", + "position": { + "lat": 42.4703963, + "lon": -71.4477468 + }, + "webSite": "http://jesseliberty.me", + "feedUris": [ + "https://feeds.feedburner.com/JesseLiberty-SilverlightGeek" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/JoeM-RP.json b/Authors/JoeM-RP.json new file mode 100644 index 0000000..0b3c109 --- /dev/null +++ b/Authors/JoeM-RP.json @@ -0,0 +1,19 @@ +{ + "firstName": "Joe", + "lastName": "Meyer", + "stateOrRegion": "Chicago, IL", + "twitterHandle": "iwritecodesmtms", + "emailAddress": "joseph.w.meyer@live.com", + "tagOrBio": "I write code sometimes", + "gravatarHash": "1431dd2c4749b0a178c7a3130e71831e", + "webSite": "https://iwritecodesometimes.net", + "githubHandle": "JoeM-RP", + "position": { + "lat": 41.92, + "lon": -87.65 + }, + "feedUris": [ + "https://iwritecodesometimes.net/feed/" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/JonDouglas.json b/Authors/JonDouglas.json new file mode 100644 index 0000000..98a9585 --- /dev/null +++ b/Authors/JonDouglas.json @@ -0,0 +1,19 @@ +{ + "firstName": "Jon", + "lastName": "Douglas", + "tagOrBio": "", + "stateOrRegion": "Utah", + "emailAddress": "", + "twitterHandle": "_jondouglas", + "webSite": "https://www.jon-douglas.com/", + "feedUris": [ + "https://www.jon-douglas.com/atom.xml" + ], + "gravatarHash": "83d67df0b9e002d1c55a2786aeeb0c1b", + "githubHandle": "JonDouglas", + "position": { + "lat": 39.32098, + "lon": -111.093731 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/JuuCustodio.json b/Authors/JuuCustodio.json new file mode 100644 index 0000000..1b6ac4d --- /dev/null +++ b/Authors/JuuCustodio.json @@ -0,0 +1,19 @@ +{ + "firstName": "Juliano", + "lastName": "Custódio", + "stateOrRegion": "Alphaville - Barueri, Brasil", + "twitterHandle": "JuuCustodio", + "githubHandle": "JuuCustodio", + "tagOrBio": "Solutions Architect, Blogger and Speaker", + "emailAddress": "juliano.custodio@hotmail.com.br", + "gravatarHash": "71de9936f2ffbc93e9918066479331f1", + "position": { + "lat": -23.4880831, + "lon": -46.8496769 + }, + "webSite": "https://www.julianocustodio.com", + "feedUris": [ + "https://julianocustodio.com/tag/xamarin/rss/" + ], + "languageCode": "pt" +} \ No newline at end of file diff --git a/Authors/LeomarisReyes.json b/Authors/LeomarisReyes.json new file mode 100644 index 0000000..1b7b9fd --- /dev/null +++ b/Authors/LeomarisReyes.json @@ -0,0 +1,19 @@ +{ + "firstName": "Leomaris", + "lastName": "Reyes", + "tagOrBio": "is a software engineer", + "stateOrRegion": "Dominican Republic", + "emailAddress": "reyes.leomaris@gmail.com", + "twitterHandle": "leomarisreyes11", + "gravatarHash": "ae78e84a683611c7b72c9ba829c125e0", + "githubHandle": "LeomarisReyes", + "position": { + "lat": 18.47088, + "lon": -69.911525 + }, + "webSite": "https://askxammy.com/", + "feedUris": [ + "https://askxammy.com/feed" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/LetsCreateSeries.json b/Authors/LetsCreateSeries.json new file mode 100644 index 0000000..af8fdcd --- /dev/null +++ b/Authors/LetsCreateSeries.json @@ -0,0 +1,19 @@ +{ + "firstName": "LetsCreateSeries", + "lastName": "", + "stateOrRegion": "United States", + "emailAddress": "letscreate.roblox@gmail.com", + "tagOrBio": "Create Mobile Apps using Xamarin.Forms", + "webSite": "https://letscreateseries.com", + "twitterHandle": "LetsCre8Series", + "githubHandle": "LetsCreateSeries", + "gravatarHash": "10793693ff507eda06ff02e9855e774f", + "feedUris": [ + "https://letscreateseries.com/rss" + ], + "position": { + "lat": 40.6331, + "lon": 89.3985 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/MarcBruins.json b/Authors/MarcBruins.json new file mode 100644 index 0000000..917537f --- /dev/null +++ b/Authors/MarcBruins.json @@ -0,0 +1,19 @@ +{ + "firstName": "Marc", + "lastName": "Bruins", + "tagOrBio": "is a native iOS/Android developer who fell in love with Xamarin", + "emailAddress": "marc@marcbruins.nl", + "twitterHandle": "MarcBruins", + "gravatarHash": "3795d2031be87499f76f6336ec5a3a45", + "stateOrRegion": "Utrecht, Netherlands", + "webSite": "https://www.marcbruins.nl", + "githubHandle": "MarcBruins", + "position": { + "lat": 53.219384, + "lon": 6.566502 + }, + "feedUris": [ + "https://www.marcbruins.nl/feed.xml" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/MartinZikmund.json b/Authors/MartinZikmund.json new file mode 100644 index 0000000..4d154cb --- /dev/null +++ b/Authors/MartinZikmund.json @@ -0,0 +1,19 @@ +{ + "firstName": "Martin", + "lastName": "Zikmund", + "stateOrRegion": "Prague, Czech Republic", + "emailAddress": "martinzikmund@sphereline.com", + "tagOrBio": "is a mobile + cloud solutions developer, Microsoftie, Geocacher, regular squash player, foodie and Insanity & P90X fan", + "webSite": "https://blog.mzikmund.com/", + "twitterHandle": "MZetko", + "githubHandle": "MartinZikmund", + "gravatarHash": "d1a45c7ba013fbc3e9044ff6461f6acd", + "position": { + "lat": 50.124017, + "lon": 14.451934 + }, + "languageCode": "en", + "feedUris": [ + "https://blog.mzikmund.com/feed/?lang=en_us" + ] +} \ No newline at end of file diff --git a/Authors/Martynnw.json b/Authors/Martynnw.json new file mode 100644 index 0000000..0109f64 --- /dev/null +++ b/Authors/Martynnw.json @@ -0,0 +1,19 @@ +{ + "firstName": "Martyn", + "lastName": "Wiggins", + "tagOrBio": "Mobile Developer, Mountain Biker, Wannabe Adventurer", + "stateOrRegion": "Nottingham", + "emailAddress": "Martynnw@gmail.com", + "twitterHandle": "Martynnw", + "gravatarHash": "bf974dae53bdbf5018fbbbf928db0f4e", + "githubHandle": "Martynnw", + "position": { + "lat": 52.95, + "lon": -1.133333 + }, + "webSite": "https://martynnw.wordpress.com", + "feedUris": [ + "https://martynnw.wordpress.com/feed/" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/MergeConflictFM.json b/Authors/MergeConflictFM.json new file mode 100644 index 0000000..7dc4a35 --- /dev/null +++ b/Authors/MergeConflictFM.json @@ -0,0 +1,19 @@ +{ + "firstName": "Merge", + "lastName": "Conflict", + "stateOrRegion": "Seattle, WA", + "emailAddress": "mergeconflictfm@gmail.com", + "tagOrBio": "is a weekly development podcast hosted by Frank Krueger and James Montemagno.", + "webSite": "http://mergeconflict.fm", + "feedUris": [ + "https://feeds.fireside.fm/mergeconflict/rss" + ], + "twitterHandle": "MergeConflictFM", + "gravatarHash": "24527eb9b29a8adbfc4155db4044dd3c", + "githubHandle": "", + "position": { + "lat": 47.60621, + "lon": -122.332071 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/PieEatingNinjas.json b/Authors/PieEatingNinjas.json new file mode 100644 index 0000000..eb0aa47 --- /dev/null +++ b/Authors/PieEatingNinjas.json @@ -0,0 +1,19 @@ +{ + "firstName": "Pieter", + "lastName": "Nijs", + "tagOrBio": "Senior .NET Software Engineer & Competence Leader Mobile @ Ordina Belgium. Passionate about programming, especially .NET, C#, XAML, Xamarin and UWP.", + "stateOrRegion": "Hasselt, Belgium", + "emailAddress": "pieternijs@live.be", + "twitterHandle": "nijspieter", + "gravatarHash": "61c9184b95820bdbbcd51764f3b9fb6e", + "githubHandle": "PieEatingNinjas", + "position": { + "lat": 50.93, + "lon": 5.3375 + }, + "webSite": "https://blog.pieeatingninjas.be/", + "feedUris": [ + "https://blog.pieeatingninjas.be/feed/rss" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/Prin53.json b/Authors/Prin53.json new file mode 100644 index 0000000..1d84a38 --- /dev/null +++ b/Authors/Prin53.json @@ -0,0 +1,19 @@ +{ + "firstName": "Denys", + "lastName": "Fiediaiev", + "stateOrRegion": "Ukraine", + "emailAddress": "fiediaiev@sbyte.dev", + "tagOrBio": "is a mobile developer specializing in Xamarin technology", + "webSite": "https://medium.com/@prin53", + "twitterHandle": "sbytedev", + "githubHandle": "Prin53", + "gravatarHash": "0d5df57543a53231787d7a34a9b79cd6", + "feedUris": [ + "https://medium.com/feed/@prin53" + ], + "position": { + "lat": 50.4547, + "lon": 30.5238 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/Pujolsluis.json b/Authors/Pujolsluis.json new file mode 100644 index 0000000..bcac998 --- /dev/null +++ b/Authors/Pujolsluis.json @@ -0,0 +1,19 @@ +{ + "firstName": "Luis", + "lastName": "Pujols", + "stateOrRegion": "United States", + "emailAddress": "luispujolso@gmail.com", + "tagOrBio": "is a Software Engineer with a passion for Mobile Development and Software Architecture. Co-organizer of the .NET Community in the Dominican Republic and a Xamarin Lover.", + "webSite": "https://www.pujolsluis.com/", + "twitterHandle": "Pujolsluis1", + "githubHandle": "Pujolsluis", + "gravatarHash": "c91c0d654f4f06ca6e7a7e54699de85d", + "feedUris": [ + "https://www.pujolsluis.com/rss" + ], + "position": { + "lat": 26.0203048, + "lon": -80.115093 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/ReactiveUI.json b/Authors/ReactiveUI.json new file mode 100644 index 0000000..2547b64 --- /dev/null +++ b/Authors/ReactiveUI.json @@ -0,0 +1,19 @@ +{ + "firstName": "ReactiveUI", + "lastName": "", + "stateOrRegion": "Internet", + "emailAddress": "hello@reactiveui.net", + "tagOrBio": "An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms", + "webSite": "https://reactiveui.net/", + "feedUris": [ + "https://reactiveui.net/rss" + ], + "twitterHandle": "ReactiveXUI", + "gravatarHash": "", + "githubHandle": "ReactiveUI", + "position": { + "lat": -13.6981464, + "lon": 37.3979239 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/Sankra.json b/Authors/Sankra.json new file mode 100644 index 0000000..5beca40 --- /dev/null +++ b/Authors/Sankra.json @@ -0,0 +1,19 @@ +{ + "firstName": "Runar Ovesen", + "lastName": "Hjerpbakk", + "tagOrBio": "Passionate, empathic and experienced developer, software architect and manager. My love for C# is only surpassed by my love for Xamarin and iOS.", + "stateOrRegion": "Trondheim, Norway", + "emailAddress": "runar@hjerpbakk.com", + "twitterHandle": "hjerpbakk", + "gravatarHash": "62b1d11eafee92745a51971d6cc21f85", + "githubHandle": "Sankra", + "position": { + "lat": 63.4305, + "lon": 10.3951 + }, + "webSite": "https://hjerpbakk.com/", + "feedUris": [ + "https://hjerpbakk.com/feed.xml" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/StefanRiedmann.json b/Authors/StefanRiedmann.json new file mode 100644 index 0000000..3e8cbb3 --- /dev/null +++ b/Authors/StefanRiedmann.json @@ -0,0 +1,19 @@ +{ + "firstName": "Stefan", + "githubHandle": "StefanRiedmann", + "lastName": "Riedmann", + "tagOrBio": "Growing software", + "stateOrRegion": "Mendoza, Argentina and Gemünden, Germany", + "emailAddress": "stefan.riedmann@ciclosoftware.com", + "twitterHandle": "CicloSoftware", + "webSite": "https://www.ciclosoftware.com", + "feedUris": [ + "https://www.ciclosoftware.com/feed/" + ], + "gravatarHash": "2781a55d04634584326bedfe08660537", + "position": { + "lat": -32.8886904, + "lon": -68.8481432 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/TheFo2sh.json b/Authors/TheFo2sh.json new file mode 100644 index 0000000..bb08f0e --- /dev/null +++ b/Authors/TheFo2sh.json @@ -0,0 +1,19 @@ +{ + "firstName": "Ahmed", + "lastName": "Fouad", + "stateOrRegion": "Vienna, Austria", + "emailAddress": "ahmed.fouad.net@hotmail.com", + "tagOrBio": "software engineer with 10 years experience with the .NET Framework living in Vienna, Austria.", + "webSite": "https://itnext.com/@csharpwriter", + "feedUris": [ + "https://medium.com/feed/@csharpwriter" + ], + "twitterHandle": "MCC_Ahmed", + "gravatarHash": "5727eb3df565991947d90ed140962472", + "githubHandle": "TheFo2sh", + "position": { + "lat": 48.20849, + "lon": 16.37208 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/TheXamarinShow.json b/Authors/TheXamarinShow.json new file mode 100644 index 0000000..a52665a --- /dev/null +++ b/Authors/TheXamarinShow.json @@ -0,0 +1,19 @@ +{ + "firstName": "The Xamarin", + "lastName": "Show", + "stateOrRegion": "Channel 9", + "emailAddress": "", + "tagOrBio": "is a Weekly Developer Show for Xamarin Developers", + "webSite": "http://xamarinshow.com", + "feedUris": [ + "https://channel9.msdn.com/Shows/XamarinShow/feed" + ], + "twitterHandle": "TheXamarinShow", + "gravatarHash": "7a0c7da0279b4e90439e780fa01924e0", + "githubHandle": "", + "position": { + "lat": 47.645136, + "lon": -122.130939 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/TomSoderling.json b/Authors/TomSoderling.json new file mode 100644 index 0000000..823a6bc --- /dev/null +++ b/Authors/TomSoderling.json @@ -0,0 +1,19 @@ +{ + "firstName": "Tom", + "lastName": "Soderling", + "stateOrRegion": "Minneapolis, MN", + "emailAddress": "", + "tagOrBio": "is a Sr. Mobile Developer, speaker, and open source contributor", + "twitterHandle": "tomsoderling", + "gravatarHash": "dd103f377899fc63b0b88c5bb62b15bd", + "position": { + "lat": 44.986656, + "lon": -93.258133 + }, + "webSite": "https://tomsoderling.github.io", + "feedUris": [ + "https://tomsoderling.github.io/feed.xml" + ], + "githubHandle": "TomSoderling", + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/UdaraAlwis.json b/Authors/UdaraAlwis.json new file mode 100644 index 0000000..0297db2 --- /dev/null +++ b/Authors/UdaraAlwis.json @@ -0,0 +1,19 @@ +{ + "firstName": "Udara", + "lastName": "Alwis", + "tagOrBio": "A mobile dev enthusiast. Xamarin Certified Developer in Singapore.", + "stateOrRegion": "Singapore", + "emailAddress": "udara.robotics@gmail.com", + "twitterHandle": "Udara_Alwis", + "gravatarHash": "125c4aaed98f2a88207dac78c17dd344", + "githubHandle": "UdaraAlwis", + "position": { + "lat": 1.284433, + "lon": 103.859609 + }, + "webSite": "https://theconfuzedsourcecode.wordpress.com/", + "feedUris": [ + "https://theconfuzedsourcecode.wordpress.com/feed/" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/VicenteGuzman.json b/Authors/VicenteGuzman.json new file mode 100644 index 0000000..7944023 --- /dev/null +++ b/Authors/VicenteGuzman.json @@ -0,0 +1,19 @@ +{ + "firstName": "Vicente", + "lastName": "Guzman", + "tagOrBio": "Vicente Guzman is a Community Member / Microsoft rMVP / Software Engineer", + "stateOrRegion": "Ciudad de México", + "emailAddress": "luciomsp@geeks.ms", + "webSite": "https://vicenteguzman.mx/", + "feedUris": [ + "https://vicenteguzman.mx/feed/" + ], + "twitterHandle": "luciomsp", + "gravatarHash": "72cce778aac0d6066a14225e90c30874", + "githubHandle": "VicenteGuzman", + "position": { + "lat": 19.432608, + "lon": -99.133209 + }, + "languageCode": "es" +} \ No newline at end of file diff --git a/Authors/XamarinPodcast.json b/Authors/XamarinPodcast.json new file mode 100644 index 0000000..2c6728d --- /dev/null +++ b/Authors/XamarinPodcast.json @@ -0,0 +1,19 @@ +{ + "firstName": "The Xamarin", + "lastName": "Podcast", + "stateOrRegion": "Internet", + "emailAddress": "hello@xamarin.com", + "tagOrBio": "is the official Xamarin podcast discussing all things Xamarin!", + "webSite": "http://www.xamarinpodcast.com", + "feedUris": [ + "https://feeds.fireside.fm/xamarinpodcast/rss" + ], + "twitterHandle": "XamarinPodcast", + "gravatarHash": "70148d964bb389d42547834e1062c886", + "githubHandle": "", + "position": { + "lat": -1337.0, + "lon": 42.0 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/acaliaro.json b/Authors/acaliaro.json new file mode 100644 index 0000000..4899ee5 --- /dev/null +++ b/Authors/acaliaro.json @@ -0,0 +1,19 @@ +{ + "firstName": "Alessandro", + "lastName": "Caliaro", + "tagOrBio": "I like Xamarin Forms and I like help other people to understand it", + "emailAddress": "acaliaro@libero.it", + "twitterHandle": "acaliaro", + "gravatarHash": "a7466eb1c467806f77bc692a4745d0f9", + "stateOrRegion": "Lissone, Italy", + "webSite": "https://acaliaro.wordpress.com", + "githubHandle": "acaliaro", + "position": { + "lat": 45.632783, + "lon": 9.227265 + }, + "feedUris": [ + "https://acaliaro.wordpress.com/feed/" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/ahoefling.json b/Authors/ahoefling.json new file mode 100644 index 0000000..eb8adee --- /dev/null +++ b/Authors/ahoefling.json @@ -0,0 +1,19 @@ +{ + "firstName": "Andrew", + "lastName": "Hoefling", + "stateOrRegion": "New York, United States", + "emailAddress": "andrew@hoefling.me", + "tagOrBio": "Microsoft MVP (Developer Technologies) Open Source developer who loves integrating Xamarin with other platforms", + "webSite": "https://www.andrewhoefling.com/", + "twitterHandle": "andrew_hoefling", + "githubHandle": "ahoefling", + "gravatarHash": "beab68478a5128e634590af5e4f01941", + "feedUris": [ + "https://www.andrewhoefling.com/feed.xml?category=xamarin&uno-platform" + ], + "position": { + "lat": 43.156578, + "lon": -77.608849 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/akamud.json b/Authors/akamud.json new file mode 100644 index 0000000..ca11d7b --- /dev/null +++ b/Authors/akamud.json @@ -0,0 +1,19 @@ +{ + "firstName": "Mahmoud", + "lastName": "Ali", + "stateOrRegion": "São Paulo, Brasil", + "emailAddress": "muddibr@gmail.com", + "tagOrBio": "Microsoft MVP", + "webSite": "https://www.lambda3.com.br/blog", + "twitterHandle": "akamud", + "githubHandle": "akamud", + "gravatarHash": "fc093b379c830c8105f8d15d9261a144", + "feedUris": [ + "https://www.lambda3.com.br/feed/" + ], + "position": { + "lat": -23.552339, + "lon": -46.661393 + }, + "languageCode": "pt" +} \ No newline at end of file diff --git a/Authors/alexsorokoletov.json b/Authors/alexsorokoletov.json new file mode 100644 index 0000000..5687f73 --- /dev/null +++ b/Authors/alexsorokoletov.json @@ -0,0 +1,19 @@ +{ + "firstName": "Alexandr", + "lastName": "Sorokoletov", + "tagOrBio": "when he's not developing mobile applications enjoys snowboarding, kitesurfing and travel", + "stateOrRegion": "Washington, D.C.", + "emailAddress": "", + "twitterHandle": "AlexSorokoletov", + "githubHandle": "alexsorokoletov", + "position": { + "lat": 38.905147, + "lon": -77.065189 + }, + "webSite": "https://sorokoletov.com", + "feedUris": [ + "https://sorokoletov.com/atom.xml" + ], + "gravatarHash": "b07fef8827dd03655303751e2fd5ca95", + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/almirvuk.json b/Authors/almirvuk.json new file mode 100644 index 0000000..7bae938 --- /dev/null +++ b/Authors/almirvuk.json @@ -0,0 +1,19 @@ +{ + "firstName": "Almir", + "lastName": "Vuk", + "tagOrBio": "Software Development Engineer & Microsoft MVP, crafting apps with ASP.NET Core and Xamarin", + "stateOrRegion": "Mostar, Bosnia and Herzegovina", + "emailAddress": "almir.vuk@outlook.com", + "twitterHandle": "almirvuk", + "gravatarHash": "d58b6fd6c2d9f949345e8d14d203a4b2", + "webSite": "https://almirvuk.com/", + "feedUris": [ + "https://almirvuk.com/rss/" + ], + "githubHandle": "almirvuk", + "position": { + "lat": 43.3395522, + "lon": 17.7862211 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/andreas-nesheim.json b/Authors/andreas-nesheim.json new file mode 100644 index 0000000..92955da --- /dev/null +++ b/Authors/andreas-nesheim.json @@ -0,0 +1,19 @@ +{ + "firstName": "Andreas", + "lastName": "Nesheim", + "stateOrRegion": "Norway", + "emailAddress": "andreas.aronsen.nesheim@gmail.com", + "tagOrBio": ".NET developer with a passion for Xamarin, Azure DevOps and .NET Core.", + "webSite": "https://www.andreasnesheim.no/", + "twitterHandle": "AndreasNesheim", + "githubHandle": "andreas-nesheim", + "gravatarHash": "3f1d141d2809114debffb23277e91e3e", + "feedUris": [ + "https://andreasnesheim.no/feed" + ], + "position": { + "lat": 58.9540147, + "lon": 5.7259639 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/aritchie.json b/Authors/aritchie.json new file mode 100644 index 0000000..cb30817 --- /dev/null +++ b/Authors/aritchie.json @@ -0,0 +1,19 @@ +{ + "firstName": "Allan", + "lastName": "Ritchie", + "stateOrRegion": "Toronto, Canada", + "emailAddress": "allan.ritchie@gmail.com", + "tagOrBio": "", + "webSite": "https://allancritchie.net", + "twitterHandle": "allanritchie911", + "githubHandle": "aritchie", + "gravatarHash": "5f22bd04ca38ed4d0a5225d0825e0726", + "position": { + "lat": 43.6425701, + "lon": -79.3892455 + }, + "languageCode": "en", + "feedUris": [ + "https://allancritchie.net/xamarin.rss" + ] +} \ No newline at end of file diff --git a/Authors/asfend.json b/Authors/asfend.json new file mode 100644 index 0000000..6a7e94c --- /dev/null +++ b/Authors/asfend.json @@ -0,0 +1,19 @@ +{ + "firstName": "Asfend Yar", + "lastName": "Hamid", + "stateOrRegion": "Lahore, Pakistan", + "emailAddress": "asfend@hotmail.com", + "tagOrBio": "is a Technical Trainer, Author at Udemy and Passionate Community Member as well as Software Engineer", + "webSite": "https://xamarinui.blogspot.com/", + "feedUris": [ + "https://xamarinui.blogspot.com/feeds/posts/default" + ], + "twitterHandle": "asfend", + "gravatarHash": "1aa9a7436eec5ad5d0418a385d1bdbe0", + "githubHandle": "asfend", + "position": { + "lat": 31.5712, + "lon": 74.3646 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/aspnetde.json b/Authors/aspnetde.json new file mode 100644 index 0000000..190d444 --- /dev/null +++ b/Authors/aspnetde.json @@ -0,0 +1,19 @@ +{ + "firstName": "Thomas", + "lastName": "Bandt", + "stateOrRegion": "Munich, Germany", + "position": { + "lat": 48.1485869, + "lon": 11.5353743 + }, + "emailAddress": "", + "webSite": "https://thomasbandt.com/", + "tagOrBio": "Developer & Entrepreneur of Passion", + "twitterHandle": "asp_net", + "githubHandle": "aspnetde", + "gravatarHash": "32860557b42ace0afa72704e466e34f1", + "feedUris": [ + "https://thomasbandt.com/feed" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/balivo.json b/Authors/balivo.json new file mode 100644 index 0000000..19c993f --- /dev/null +++ b/Authors/balivo.json @@ -0,0 +1,19 @@ +{ + "firstName": "Jefferson", + "lastName": "Balivo", + "stateOrRegion": "Jaú, São Paulo, Brazil", + "emailAddress": "jefferson@balivo.com.br", + "tagOrBio": "Xamarin Enthusiast, Technical Lead and Cross Platform Architect, Multi-Plataform Technical Audience Contributor (MTAC), Technical Writer and Speaker", + "webSite": "https://balivo.com.br/", + "twitterHandle": "jbalivo", + "githubHandle": "balivo", + "gravatarHash": "e5a95c365e8f06786d6439474bc733df", + "feedUris": [ + "https://balivo.com.br/rss" + ], + "position": { + "lat": -22.2997067, + "lon": -48.5931324 + }, + "languageCode": "pt" +} \ No newline at end of file diff --git a/Authors/banditoth.json b/Authors/banditoth.json new file mode 100644 index 0000000..cca99ea --- /dev/null +++ b/Authors/banditoth.json @@ -0,0 +1,19 @@ +{ + "firstName": "András", + "lastName": "Tóth", + "tagOrBio": "also known as banditoth | Xamarin && C# .NET developer from Hungary.", + "stateOrRegion": "Budapest, Hungary", + "emailAddress": "", + "twitterHandle": "", + "githubHandle": "banditoth", + "position": { + "lat": 47.497913, + "lon": 19.040236 + }, + "webSite": "https://www.banditoth.hu", + "feedUris": [ + "https://www.banditoth.hu/feed/" + ], + "gravatarHash": "e11e3fd93c1cc4db5ec6f309bac0ff4d", + "languageCode": "hu" +} \ No newline at end of file diff --git a/Authors/basdecort.json b/Authors/basdecort.json new file mode 100644 index 0000000..2ef14fa --- /dev/null +++ b/Authors/basdecort.json @@ -0,0 +1,19 @@ +{ + "firstName": "Bas", + "lastName": "de Cort", + "stateOrRegion": "Tilburg, The Netherlands", + "emailAddress": "", + "tagOrBio": "is a mobile developer with a great passion for Xamarin 🙉", + "webSite": "https://www.basdecort.com", + "twitterHandle": "basdecort", + "githubHandle": "basdecort", + "gravatarHash": "9cabafd38c21d9df358f7532ffa39153", + "position": { + "lat": 52.040222799999995, + "lon": 5.5349002999999994 + }, + "feedUris": [ + "https://www.basdecort.com/rss" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/bbenetskyy.json b/Authors/bbenetskyy.json new file mode 100644 index 0000000..4d0d0ce --- /dev/null +++ b/Authors/bbenetskyy.json @@ -0,0 +1,19 @@ +{ + "firstName": "Bohdan", + "lastName": "Benetskyi", + "stateOrRegion": "Rzeszow, Poland", + "emailAddress": "benetskyybogdan@gmail.com", + "tagOrBio": "Xamarin Software Developer, co-organizer of Xamarin Local Events in Rzeszow, Local CSS at Xamarin Advocate", + "webSite": "https://medium.com/@benetskyybogdan/", + "twitterHandle": "bbenetskyy", + "githubHandle": "bbenetskyy", + "gravatarHash": "8bfa7db9239c2969b79468a58c8dd066", + "feedUris": [ + "https://medium.com/feed/@benetskyybogdan/" + ], + "position": { + "lat": 50.041187, + "lon": 21.999121 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/brminnick.json b/Authors/brminnick.json new file mode 100644 index 0000000..0359af3 --- /dev/null +++ b/Authors/brminnick.json @@ -0,0 +1,19 @@ +{ + "firstName": "Brandon", + "lastName": "Minnick", + "stateOrRegion": "San Francisco, CA", + "emailAddress": "brandon@codetraveler.io", + "tagOrBio": "works as a Developer Advocate at Microsoft. Formerly a Customer Success Engineer at Xamarin (before the Microsoft Acquisition), Brandon has a loves helping developers make 5-star apps!", + "webSite": "https://codetraveler.io", + "twitterHandle": "TheCodeTraveler", + "githubHandle": "brminnick", + "gravatarHash": "e03e28629383def59c31d54fb8bb3982", + "position": { + "lat": 37.77669, + "lon": -122.416644 + }, + "languageCode": "en", + "feedUris": [ + "https://codetraveler.io/tag/xamarin/rss" + ] +} \ No newline at end of file diff --git a/Authors/canbilgin.json b/Authors/canbilgin.json new file mode 100644 index 0000000..2ddfddf --- /dev/null +++ b/Authors/canbilgin.json @@ -0,0 +1,19 @@ +{ + "firstName": "Can", + "lastName": "Bilgin", + "tagOrBio": "Seasoned microsoft stack developer with passion for mobile development. Microsoft MVP for Windows Platform Development", + "stateOrRegion": "Sarajevo", + "emailAddress": "can_bilgin@hotmail.com", + "twitterHandle": "can_bilgin", + "gravatarHash": "3659d530c25e6188f7ef4c98ed100dc8", + "webSite": "https://canbilgin.wordpress.com/", + "feedUris": [ + "https://canbilgin.wordpress.com/feed/" + ], + "githubHandle": "canbilgin", + "position": { + "lat": 43.8938256, + "lon": 18.3129519 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/chamons.json b/Authors/chamons.json new file mode 100644 index 0000000..e26f390 --- /dev/null +++ b/Authors/chamons.json @@ -0,0 +1,19 @@ +{ + "firstName": "Chris", + "lastName": "Hamons", + "tagOrBio": "is the lead engineer for Xamarin.Mac", + "stateOrRegion": "Austin, Texas", + "emailAddress": "chris.hamons@xamarin.com", + "twitterHandle": "IfErrThrowBrick", + "webSite": "https://medium.com/@donblas", + "feedUris": [ + "https://medium.com/feed/@donblas" + ], + "gravatarHash": "8fb3e7f07ea1386cefe1326b48e0e21a", + "githubHandle": "chamons", + "position": { + "lat": 30.267153, + "lon": -97.743061 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/char0394.json b/Authors/char0394.json new file mode 100644 index 0000000..f153a93 --- /dev/null +++ b/Authors/char0394.json @@ -0,0 +1,19 @@ +{ + "firstName": "Charlin", + "lastName": "Agramonte", + "tagOrBio": "Software Engineer", + "stateOrRegion": "Dominican Republic", + "emailAddress": "charlin@crossgeeks.com", + "twitterHandle": "Chard003", + "gravatarHash": "7db2bb2eed17e8df7e78b0d5461d90b0", + "githubHandle": "char0394", + "position": { + "lat": 18.4735438, + "lon": -69.9456919 + }, + "webSite": "https://xamgirl.com/", + "feedUris": [ + "https://xamgirl.com/rss" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/cjlotz.json b/Authors/cjlotz.json new file mode 100644 index 0000000..2a640c6 --- /dev/null +++ b/Authors/cjlotz.json @@ -0,0 +1,19 @@ +{ + "firstName": "Carel", + "lastName": "Lotz", + "tagOrBio": "is a Software Architect/Developer that loves to code", + "stateOrRegion": "Cape Town, South Africa", + "emailAddress": "carel.lotz@gmail.com", + "twitterHandle": "cjlotz", + "githubHandle": "cjlotz", + "gravatarHash": "0f692990601721c06f141f4fe860685e", + "position": { + "lat": -33.89635, + "lon": 18.70199 + }, + "webSite": "https://cjlotz.github.io", + "feedUris": [ + "https://cjlotz.github.io/feed.xml" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/codemillmatt.json b/Authors/codemillmatt.json new file mode 100644 index 0000000..8b8e421 --- /dev/null +++ b/Authors/codemillmatt.json @@ -0,0 +1,19 @@ +{ + "feedUris": [ + "https://codemilltech.com/feed/" + ], + "firstName": "Matthew", + "lastName": "Soucoup", + "stateOrRegion": "Madison, WI", + "emailAddress": "msoucoup@codemilltech.com", + "tagOrBio": "", + "webSite": "https://codemilltech.com", + "twitterHandle": "codemillmatt", + "gravatarHash": "df69069a0bffd2dae5a8700a1bef7bfd", + "githubHandle": "codemillmatt", + "position": { + "lat": 43.073052, + "lon": -89.40123 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/crswlls.json b/Authors/crswlls.json new file mode 100644 index 0000000..5f8fd20 --- /dev/null +++ b/Authors/crswlls.json @@ -0,0 +1,19 @@ +{ + "firstName": "Chris", + "lastName": "Williams", + "tagOrBio": "", + "emailAddress": "", + "twitterHandle": "crswlls", + "gravatarHash": "21e379df7ba9c57f167188e2fcb7dd75", + "stateOrRegion": "Bristol, UK", + "webSite": "https://crswlls.wordpress.com", + "feedUris": [ + "https://crswlls.wordpress.com/rss/" + ], + "githubHandle": "", + "position": { + "lat": 30.267153, + "lon": -97.743061 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/damienaicheh.json b/Authors/damienaicheh.json new file mode 100644 index 0000000..5459dfb --- /dev/null +++ b/Authors/damienaicheh.json @@ -0,0 +1,19 @@ +{ + "firstName": "Damien", + "lastName": "Aicheh", + "stateOrRegion": "Rennes, France", + "emailAddress": "", + "tagOrBio": "is a passionate mobile developer also interrested about Azure, Azure DevOps and.NET Core.", + "webSite": "https://damienaicheh.github.io/", + "twitterHandle": "damienaicheh", + "githubHandle": "damienaicheh", + "gravatarHash": "b5cf688a9aa81b3ef752ecda4366a8e9", + "feedUris": [ + "https://damienaicheh.github.io/feed.xml" + ], + "position": { + "lat": 48.1113036, + "lon": -1.6801598 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/damiendoumer.json b/Authors/damiendoumer.json new file mode 100644 index 0000000..30a269c --- /dev/null +++ b/Authors/damiendoumer.json @@ -0,0 +1,19 @@ +{ + "firstName": "Damien", + "lastName": "Doumer", + "stateOrRegion": "Douala, Cameroon", + "emailAddress": "damientohin@gmail.com", + "tagOrBio": "Fresh .Net developer, who loves mobile app development.", + "webSite": "https://doumer.me/", + "feedUris": [ + "https://doumer.me/feed/" + ], + "twitterHandle": "Damien_Doumer", + "gravatarHash": "ecdd93df62c61daa04da17967f82d08d", + "githubHandle": "damiendoumer", + "position": { + "lat": 4.07316844239285, + "lon": 9.6842408186165585 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/danielcauser.json b/Authors/danielcauser.json new file mode 100644 index 0000000..e7c0f22 --- /dev/null +++ b/Authors/danielcauser.json @@ -0,0 +1,19 @@ +{ + "firstName": "Daniel", + "lastName": "Causer", + "stateOrRegion": "Toronto, Canada", + "emailAddress": "danielcauser@gmail.com", + "tagOrBio": "is a Xamarin Certified Developer fan of Xamarin and Mobile Development.", + "webSite": "https://causerexception.com/", + "twitterHandle": "danielcauser", + "githubHandle": "danielcauser", + "gravatarHash": "2666956714a2c2d48c480a6bddac4071", + "feedUris": [ + "https://causerexception.com/feed" + ], + "position": { + "lat": 43.653103, + "lon": -79.383851 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/danielmonettelli.json b/Authors/danielmonettelli.json new file mode 100644 index 0000000..668e204 --- /dev/null +++ b/Authors/danielmonettelli.json @@ -0,0 +1,20 @@ +{ + "firstName": "Daniel Angel", + "lastName": "Monettelli L.", + "tagOrBio": "is a Software Engineer, Videoblogger, Xamarin Mobile Developer & UI/UX Architect.", + "stateOrRegion": "Arequipa, Perú", + "emailAddress": "danielmonetelli@hotmail.com", + "webSite": "https://danielmonettelli.github.io", + "feedUris": [ + "https://danielmonettelli.github.io/feed.xml", + "https://www.youtube.com/feeds/videos.xml?channel_id=UCTAlbAORoFvAHj7jA4DLSFQ" + ], + "twitterHandle": "DanielMonetelli", + "gravatarHash": "4b3d0e60019ad8fe1e4d7cd5c8850efb", + "githubHandle": "danielmonettelli", + "position": { + "lat": -16.4042643, + "lon": -71.5486383 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/dansiegel.json b/Authors/dansiegel.json new file mode 100644 index 0000000..b626148 --- /dev/null +++ b/Authors/dansiegel.json @@ -0,0 +1,19 @@ +{ + "firstName": "Dan", + "lastName": "Siegel", + "stateOrRegion": "San Diego, CA", + "emailAddress": "dsiegel@avantipoint.com", + "tagOrBio": "is a Mobile App Consultant and Founder of AvantiPoint. He is an author of several OSS libraries, a maintainer of the Prism Library and a DevOps champion.", + "webSite": "https://dansiegel.net", + "feedUris": [ + "https://dansiegel.net/syndication.axd" + ], + "twitterHandle": "DanJSiegel", + "gravatarHash": "b65a519785f69fbe7236dd0fd6396094", + "githubHandle": "dansiegel", + "position": { + "lat": 32.726308, + "lon": -117.177746 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/davidbritch.json b/Authors/davidbritch.json new file mode 100644 index 0000000..bda68ed --- /dev/null +++ b/Authors/davidbritch.json @@ -0,0 +1,21 @@ +{ + "firstName": "David", + "lastName": "Britch", + "Title": "Senior Developer/Writer", + "stateOrRegion": "UK", + "emailAddress": "", + "twitterHandle": "britchdavid", + "gravatarHash": "0ddca71a03a3591203b4a748fcdfb47a", + "Started": "2015-06-08T00:00:00", + "tagOrBio": "", + "githubHandle": "davidbritch", + "position": { + "lat": 53.4793, + "lon": -2.2479 + }, + "webSite": "https://www.davidbritch.com", + "feedUris": [ + "https://www.davidbritch.com/rss.xml" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/dhindrik.json b/Authors/dhindrik.json new file mode 100644 index 0000000..5a103ec --- /dev/null +++ b/Authors/dhindrik.json @@ -0,0 +1,19 @@ +{ + "firstName": "Daniel", + "lastName": "Hindrikes", + "stateOrRegion": "Borlänge, Sweden", + "emailAddress": "daniel@hindrikes.se", + "tagOrBio": "is an architect and developer using Xamarin and Azure. Working as a Ninja at tretton37. Also recording \"The Code Behind\" podcast!", + "webSite": "https://danielhindrikes.se", + "twitterHandle": "hindrikes", + "githubHandle": "dhindrik", + "gravatarHash": "054db2cfd79654ec5d92e20c180f2d72", + "feedUris": [ + "https://danielhindrikes.se/index.php/feed/" + ], + "position": { + "lat": 60.48604, + "lon": 15.43391 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/divikiran.json b/Authors/divikiran.json new file mode 100644 index 0000000..870131c --- /dev/null +++ b/Authors/divikiran.json @@ -0,0 +1,19 @@ +{ + "firstName": "Divikiran", + "lastName": "Ravela", + "stateOrRegion": "Melbourne, Australia", + "emailAddress": "divikiran1@gmail.com", + "tagOrBio": "Xamarin Consultant/Tech Lead", + "webSite": "https://xamlabs.com/", + "twitterHandle": "", + "githubHandle": "divikiran", + "gravatarHash": "", + "feedUris": [ + "https://xamlabs.com/feed/" + ], + "position": { + "lat": -37.8135511, + "lon": 144.9637748 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/dylanberry.json b/Authors/dylanberry.json new file mode 100644 index 0000000..808d8fd --- /dev/null +++ b/Authors/dylanberry.json @@ -0,0 +1,19 @@ +{ + "firstName": "Dylan", + "lastName": "Berry", + "tagOrBio": "Genetically Predisposed Programmer", + "stateOrRegion": "Toronto, Canada", + "emailAddress": "dylanberry@gmail.com", + "twitterHandle": "dylbot9000", + "gravatarHash": "8ef85938904ff43397d50caa9b0eebed", + "githubHandle": "dylanberry", + "position": { + "lat": 43.653493, + "lon": -79.384095 + }, + "webSite": "https://www.dylanberry.com/", + "feedUris": [ + "https://www.dylanberry.com/feed/" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/egbakou.json b/Authors/egbakou.json new file mode 100644 index 0000000..886bb70 --- /dev/null +++ b/Authors/egbakou.json @@ -0,0 +1,19 @@ +{ + "firstName": "Kodjo Laurent", + "lastName": "Egbakou", + "stateOrRegion": "Lome, Togo", + "emailAddress": "laurent@lioncoding.com", + "tagOrBio": "is C#/.Net/Xamarin developer who learns and shares.", + "webSite": "https://lioncoding.com/", + "twitterHandle": "lioncoding", + "githubHandle": "egbakou", + "gravatarHash": "6e26a030d3b9495872b58d922bd86157", + "feedUris": [ + "https://lioncoding.com/feed.xml" + ], + "position": { + "lat": 6.2030129, + "lon": 1.1918434 + }, + "languageCode": "fr" +} \ No newline at end of file diff --git a/Authors/elbrinner.json b/Authors/elbrinner.json new file mode 100644 index 0000000..615211b --- /dev/null +++ b/Authors/elbrinner.json @@ -0,0 +1,19 @@ +{ + "firstName": "Elbrinner", + "lastName": "da Silva Fernandes", + "stateOrRegion": "Madrid , Spain", + "emailAddress": "elbrinner@elbrinner.com", + "tagOrBio": "is a Xamarin consultant in everis Spain and organizer of the meetup group Xamarin Madrid.", + "webSite": "https://www.elbrinner.com/", + "twitterHandle": "elbrinner", + "githubHandle": "elbrinner", + "gravatarHash": "15e64690c0e4d5b2c692e9fc7de5e768", + "feedUris": [ + "https://www.elbrinner.com/rss" + ], + "position": { + "lat": 40.416775, + "lon": -3.70379 + }, + "languageCode": "es" +} \ No newline at end of file diff --git a/Authors/felipebaltazar.json b/Authors/felipebaltazar.json new file mode 100644 index 0000000..7ca680e --- /dev/null +++ b/Authors/felipebaltazar.json @@ -0,0 +1,19 @@ +{ + "firstName": "Felipe", + "lastName": "Baltazar", + "tagOrBio": "Xamarin 🐒, ApnetCore 🌐, Windows 💻, Nerd 🤓, Gamer 🎮 and Father 👨‍👩‍👦", + "stateOrRegion": "Rio Grande do sul, Brasil", + "emailAddress": "felipe.dasilvabaltazar@gmail.com", + "twitterHandle": "FelippeBaltazar", + "githubHandle": "felipebaltazar", + "gravatarHash": "c4deac62305f590fbda80209628afd0e", + "position": { + "lat": -29.940163, + "lon": -51.088751 + }, + "webSite": "https://medium.com/@felipedasilvabaltazar", + "feedUris": [ + "https://medium.com/feed/@felipedasilvabaltazar" + ], + "languageCode": "pt" +} \ No newline at end of file diff --git a/Authors/filipoff2.json b/Authors/filipoff2.json new file mode 100644 index 0000000..e3c9525 --- /dev/null +++ b/Authors/filipoff2.json @@ -0,0 +1,19 @@ +{ + "firstName": "Boguslaw", + "lastName": "Blonski", + "tagOrBio": "Generalist who is pragmatic and has delivered real code to real users in a variety of shapes.", + "stateOrRegion": "Lezajsk, Poland", + "emailAddress": "boguslawblonski@gmail.com", + "twitterHandle": "filipoff", + "gravatarHash": "d48c54ac95492ddadfc221b646d4c612", + "webSite": "https://medium.com/@boguslawblonski", + "feedUris": [ + "https://medium.com/feed/@boguslawblonski/" + ], + "githubHandle": "filipoff2", + "position": { + "lat": 50.2684647, + "lon": 22.3819053 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/framinosona.json b/Authors/framinosona.json new file mode 100644 index 0000000..4580e19 --- /dev/null +++ b/Authors/framinosona.json @@ -0,0 +1,19 @@ +{ + "firstName": "Francois", + "lastName": "Raminosona", + "stateOrRegion": "Norway", + "emailAddress": "framinosona@hotmail.fr", + "tagOrBio": "Passionate Xamarin/Microsoft technologies developer", + "webSite": "https://blog.francois.raminosona.com/", + "feedUris": [ + "https://blog.francois.raminosona.com/tag/xamarin/rss/" + ], + "twitterHandle": "framinosona", + "gravatarHash": "b3b91b8d4bd1e716982eef6e5228c92f", + "githubHandle": "framinosona", + "position": { + "lat": 58.9720089, + "lon": 5.7363974 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/gonemobilecast.json b/Authors/gonemobilecast.json new file mode 100644 index 0000000..290ca54 --- /dev/null +++ b/Authors/gonemobilecast.json @@ -0,0 +1,19 @@ +{ + "firstName": "Gone", + "lastName": "Mobile", + "stateOrRegion": "Internet", + "emailAddress": "hello@gonemobile.io", + "tagOrBio": "is a development podcast focused on mobile development hosted by Jon Dick and Greg Shackles.", + "webSite": "http://gonemobile.io", + "feedUris": [ + "https://feed.gonemobile.io/" + ], + "twitterHandle": "gonemobilecast", + "position": { + "lat": 51.253775, + "lon": -85.323214 + }, + "gravatarHash": "cb611c5ecd9a53b2af53a9d50d83c3c5", + "githubHandle": "", + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/gptucci.json b/Authors/gptucci.json new file mode 100644 index 0000000..f9af534 --- /dev/null +++ b/Authors/gptucci.json @@ -0,0 +1,19 @@ +{ + "webSite": "https://www.informaticapressapochista.com/it", + "firstName": "Giampaolo", + "lastName": "Tucci", + "stateOrRegion": "Italia", + "emailAddress": "g.tucci@informaticapressapochista.com", + "tagOrBio": "L'IT come non l'avete mai letta", + "gravatarHash": "a8846caf48c8ccc9850ff201c1e2ad1d", + "feedUris": [ + "https://www.informaticapressapochista.com/it/?format=feed&type=rss" + ], + "twitterHandle": "GiampaoloTUCCI", + "githubHandle": "gptucci", + "position": { + "lat": 44.40138, + "lon": 8.93419 + }, + "languageCode": "it" +} \ No newline at end of file diff --git a/Authors/gshackles.json b/Authors/gshackles.json new file mode 100644 index 0000000..4a18581 --- /dev/null +++ b/Authors/gshackles.json @@ -0,0 +1,19 @@ +{ + "firstName": "Greg", + "lastName": "Shackles", + "tagOrBio": "knows a thing (or two) about mobile testing", + "emailAddress": "greg@gregshackles.com", + "twitterHandle": "gshackles", + "gravatarHash": "6d7b45031bf22823060849d494343a8c", + "stateOrRegion": "New York, NY", + "webSite": "https://gregshackles.com", + "feedUris": [ + "https://gregshackles.com/rss/" + ], + "githubHandle": "gshackles", + "position": { + "lat": 40.712784, + "lon": -74.005941 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/hnabbasi.json b/Authors/hnabbasi.json new file mode 100644 index 0000000..ebcee56 --- /dev/null +++ b/Authors/hnabbasi.json @@ -0,0 +1,19 @@ +{ + "firstName": "Hussain", + "lastName": "Abbasi", + "stateOrRegion": "Houston, Texas", + "emailAddress": "hnabbasi@outlook.com", + "tagOrBio": "is a Blogger, Mobile Architect, and founder of intelliAbb.com. More at HussainAbbasi.com", + "webSite": "https://www.intelliabb.com/", + "twitterHandle": "HussainNAbbasi", + "githubHandle": "hnabbasi", + "gravatarHash": "6f415af725ae2d6b5b912a7e93b105b9", + "feedUris": [ + "https://www.intelliabb.com/feed" + ], + "position": { + "lat": 29.7656, + "lon": -95.3681 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/ionixjunior.json b/Authors/ionixjunior.json new file mode 100644 index 0000000..8d190e5 --- /dev/null +++ b/Authors/ionixjunior.json @@ -0,0 +1,19 @@ +{ + "firstName": "Ione", + "lastName": "Souza Junior", + "tagOrBio": "", + "stateOrRegion": "Blumenau, Brasil", + "emailAddress": "junior@ionixjunior.com.br", + "twitterHandle": "ionixjunior", + "githubHandle": "ionixjunior", + "gravatarHash": "790726f5b5613d61926dea2e2efd4da1", + "position": { + "lat": -26.914236, + "lon": -49.068776 + }, + "webSite": "https://www.ionixjunior.com.br", + "feedUris": [ + "https://www.ionixjunior.com.br/rss" + ], + "languageCode": "pt" +} \ No newline at end of file diff --git a/Authors/jamesmontemagno.json b/Authors/jamesmontemagno.json new file mode 100644 index 0000000..a95e11c --- /dev/null +++ b/Authors/jamesmontemagno.json @@ -0,0 +1,19 @@ +{ + "firstName": "James", + "lastName": "Montemagno", + "stateOrRegion": "Seattle, WA", + "emailAddress": "", + "tagOrBio": "is a Principal Program Manager for Mobile Developer Tools", + "webSite": "https://montemagno.com", + "feedUris": [ + "https://montemagno.com/rss" + ], + "twitterHandle": "JamesMontemagno", + "gravatarHash": "5df4d86308e585c879c19e5f909d8bfe", + "githubHandle": "jamesmontemagno", + "position": { + "lat": 47.654177, + "lon": -122.35 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/jesulink2514.json b/Authors/jesulink2514.json new file mode 100644 index 0000000..3bc5320 --- /dev/null +++ b/Authors/jesulink2514.json @@ -0,0 +1,20 @@ +{ + "firstName": "Jesús", + "lastName": "Angulo", + "stateOrRegion": "Lima", + "emailAddress": "jesus.angulo@outlook.com", + "tagOrBio": "Microsoft MVP | Certified Xamarin Mobile Developer", + "webSite": "https://somostechies.com", + "twitterHandle": "jesulink2514", + "githubHandle": "jesulink2514", + "gravatarHash": "63359672e0ecb75e7ed261a358bf0478", + "feedUris": [ + "https://somostechies.com/rss/", + "https://www.youtube.com/feeds/videos.xml?channel_id=UCnqaA_ArZIT0nytKMAiurzw" + ], + "position": { + "lat": -12.0896427, + "lon": -77.0060778 + }, + "languageCode": "es" +} \ No newline at end of file diff --git a/Authors/jfversluis.json b/Authors/jfversluis.json new file mode 100644 index 0000000..7544e9f --- /dev/null +++ b/Authors/jfversluis.json @@ -0,0 +1,19 @@ +{ + "firstName": "Gerald", + "lastName": "Versluis", + "stateOrRegion": "The Netherlands", + "emailAddress": "gerald@verslu.is", + "tagOrBio": "Software Engineer at Microsoft on the Xamarin.Forms team", + "webSite": "https://blog.verslu.is/", + "feedUris": [ + "https://blog.verslu.is/feed/" + ], + "twitterHandle": "jfversluis", + "gravatarHash": "f9d4d4211d7956ce3e07e83df0889731", + "githubHandle": "jfversluis", + "position": { + "lat": 50.889039, + "lon": 5.853717 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/jgiacomini.json b/Authors/jgiacomini.json new file mode 100644 index 0000000..104c5a0 --- /dev/null +++ b/Authors/jgiacomini.json @@ -0,0 +1,19 @@ +{ + "firstName": "Jérôme", + "lastName": "Giacomini", + "stateOrRegion": "Paris, France", + "emailAddress": "jerome.giacomini@gmail.com", + "tagOrBio": "is a Xamarin Enthusiast, co-author of a book on Xamarin", + "webSite": "https://jeromegiacomini.net/Blog/", + "twitterHandle": "jeromegiacomini", + "githubHandle": "jgiacomini", + "gravatarHash": "95e63961669a22586a1236fd6a7a494d", + "feedUris": [ + "https://jeromegiacomini.net/Blog/feed/" + ], + "position": { + "lat": 48.8704842, + "lon": 2.3449646 + }, + "languageCode": "fr" +} \ No newline at end of file diff --git a/Authors/jimbobbennett.json b/Authors/jimbobbennett.json new file mode 100644 index 0000000..d97112e --- /dev/null +++ b/Authors/jimbobbennett.json @@ -0,0 +1,19 @@ +{ + "firstName": "Jim", + "lastName": "Bennett", + "tagOrBio": "is the author of Xamarin in Action and a all-round nice guy", + "stateOrRegion": "Auckland, New Zealand", + "emailAddress": "jim@jimbobbennett.io", + "twitterHandle": "jimbobbennett", + "webSite": "https://jimbobbennett.io/", + "feedUris": [ + "https://www.jimbobbennett.io/rss" + ], + "gravatarHash": "", + "githubHandle": "", + "position": { + "lat": -36.84846, + "lon": 174.763332 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/johnthiriet.json b/Authors/johnthiriet.json new file mode 100644 index 0000000..21ab90a --- /dev/null +++ b/Authors/johnthiriet.json @@ -0,0 +1,19 @@ +{ + "firstName": "John", + "lastName": "Thiriet", + "stateOrRegion": "Paris, France", + "emailAddress": "johnthiriet@protonmail.com", + "tagOrBio": "is a Mobility Technical Manager, Trainer, Microsoft MVP, Xamarin MVP", + "webSite": "https://johnthiriet.com/", + "feedUris": [ + "https://johnthiriet.com/feed.xml" + ], + "twitterHandle": "johnthiriet", + "gravatarHash": "ed92222c19a155a929d9f2c12d39c3f4", + "githubHandle": "johnthiriet", + "position": { + "lat": 48.875485, + "lon": 2.311039 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/jssuthahar.json b/Authors/jssuthahar.json new file mode 100644 index 0000000..5195a63 --- /dev/null +++ b/Authors/jssuthahar.json @@ -0,0 +1,19 @@ +{ + "firstName": "Suthahar", + "lastName": "Jegatheesan", + "tagOrBio": "insatiable desire to keep learning keeps the dynamic blogger. I take keen interest in sharing my knowledge and solving my readers’ technology-related problems.", + "emailAddress": "", + "twitterHandle": "jssuthahar", + "gravatarHash": "2a34ebf4e9c4dca84eb7feee7217568f", + "stateOrRegion": "Bangalore, India", + "webSite": "https://www.msdevbuild.com/", + "position": { + "lat": 12.971599, + "lon": 77.594563 + }, + "feedUris": [ + "https://www.msdevbuild.com/feeds/posts/default" + ], + "githubHandle": "jssuthahar", + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/jsuarezruiz.json b/Authors/jsuarezruiz.json new file mode 100644 index 0000000..3361ef1 --- /dev/null +++ b/Authors/jsuarezruiz.json @@ -0,0 +1,19 @@ +{ + "feedUris": [ + "https://javiersuarezruiz.wordpress.com/feed/" + ], + "firstName": "Javier", + "lastName": "Suarez", + "stateOrRegion": "Seville, Spain", + "emailAddress": "javiersuarezruiz@hotmail.com", + "tagOrBio": "is a passionate software developer from Spain who enjoys learning, talk and help others", + "webSite": "https://javiersuarezruiz.wordpress.com", + "twitterHandle": "jsuarezruiz", + "gravatarHash": "", + "position": { + "lat": 37.389092, + "lon": -5.984459 + }, + "githubHandle": "jsuarezruiz", + "languageCode": "es" +} \ No newline at end of file diff --git a/Authors/jtaubensee.json b/Authors/jtaubensee.json new file mode 100644 index 0000000..22f5cbb --- /dev/null +++ b/Authors/jtaubensee.json @@ -0,0 +1,19 @@ +{ + "firstName": "John", + "lastName": "Taubensee", + "stateOrRegion": "Chicago, IL", + "emailAddress": "", + "tagOrBio": "is a developer focused on Microsoft technologies. Microsoft Azure alumni", + "webSite": "https://taubensee.net", + "feedUris": [ + "https://taubensee.net/rss" + ], + "twitterHandle": "jtaubensee", + "gravatarHash": "151bc535ca1581cb451eb4df1672b018", + "githubHandle": "jtaubensee", + "position": { + "lat": 41.8778143, + "lon": -87.6349955 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/kent_boogaart.json b/Authors/kent_boogaart.json new file mode 100644 index 0000000..42a1b1f --- /dev/null +++ b/Authors/kent_boogaart.json @@ -0,0 +1,19 @@ +{ + "firstName": "Kent", + "lastName": "Boogaart", + "stateOrRegion": "Australia", + "emailAddress": "kent.boogaart@gmail.com", + "tagOrBio": "is a freelance software engineer working mainly in the mobile space", + "webSite": "https://kent-boogaart.com/", + "feedUris": [ + "https://kent-boogaart.com/atom.xml" + ], + "twitterHandle": "kent_boogaart", + "gravatarHash": "", + "githubHandle": "", + "position": { + "lat": -35.0004451, + "lon": 138.3309978 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/kphillpotts.json b/Authors/kphillpotts.json new file mode 100644 index 0000000..c501dad --- /dev/null +++ b/Authors/kphillpotts.json @@ -0,0 +1,19 @@ +{ + "firstName": "Kym", + "lastName": "Phillpotts", + "tagOrBio": "is one of the Xamarin University instructors", + "stateOrRegion": "Melbourne, Australia", + "emailAddress": "kphillpotts@gmail.com", + "twitterHandle": "kphillpotts", + "gravatarHash": "3218e66502c6f0836dfd0f02f210ba0b", + "webSite": "https://kymphillpotts.com/", + "feedUris": [ + "https://kymphillpotts.com/feed" + ], + "githubHandle": "kphillpotts", + "position": { + "lat": -37.813628, + "lon": 144.963058 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/kwlothrop.json b/Authors/kwlothrop.json new file mode 100644 index 0000000..8f89f76 --- /dev/null +++ b/Authors/kwlothrop.json @@ -0,0 +1,19 @@ +{ + "firstName": "Kerry W.", + "lastName": "Lothrop", + "tagOrBio": "", + "stateOrRegion": "Frankfurt, Germany", + "emailAddress": "", + "twitterHandle": "kwlothrop", + "webSite": "https://kerry.lothrop.de/", + "feedUris": [ + "https://kerry.lothrop.de/en/feed/" + ], + "gravatarHash": "250241b2800a1de895a75ce039bcfef4", + "githubHandle": "", + "position": { + "lat": 50.1307615, + "lon": 8.568736 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/logeshpalani98.json b/Authors/logeshpalani98.json new file mode 100644 index 0000000..4263567 --- /dev/null +++ b/Authors/logeshpalani98.json @@ -0,0 +1,19 @@ +{ + "firstName": "Logesh", + "lastName": "Palani", + "stateOrRegion": "Thiruvannamalai,Tamil Nadu, India", + "emailAddress": "logesh.01@hotmail.com", + "tagOrBio": "Keep Learning,.. and Keep Practicing,...", + "webSite": "https://logeshpalani.blogspot.com/", + "twitterHandle": "logeshpalani98", + "githubHandle": "logeshpalani98", + "gravatarHash": "14fd9ec21509b468c84abbed2e47384e", + "feedUris": [ + "https://logeshpalani.blogspot.com/feeds/posts/default" + ], + "position": { + "lat": 12.527056, + "lon": 79.098382 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/luismts.json b/Authors/luismts.json new file mode 100644 index 0000000..8e24113 --- /dev/null +++ b/Authors/luismts.json @@ -0,0 +1,20 @@ +{ + "firstName": "Luis", + "lastName": "Matos", + "stateOrRegion": "Dominican Republic", + "twitterHandle": "luismatosluna", + "emailAddress": "hello@luismts.com", + "tagOrBio": "Software Engineer, Entrepreneur", + "gravatarHash": "ac9ac28f6b1e05a310d622b37e8bc4be", + "webSite": "https://www.luismts.com/", + "feedUris": [ + "https://www.luismts.com/feed/", + "https://www.luismts.com/es/feed/" + ], + "githubHandle": "luismts", + "position": { + "lat": 18.4900563, + "lon": -69.8962411 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/mabroukmahdhi.json b/Authors/mabroukmahdhi.json new file mode 100644 index 0000000..78d0165 --- /dev/null +++ b/Authors/mabroukmahdhi.json @@ -0,0 +1,19 @@ +{ + "firstName": "Mabrouk", + "lastName": "Mahdhi", + "stateOrRegion": "Darmstadt, Germany", + "emailAddress": "contact@mahdhi.com", + "tagOrBio": "is a Senior Technical Consultant who blogs, talks and develops all around mobile and web development.", + "webSite": "https://mahdhi.com", + "twitterHandle": "mabrouk_mahdhi", + "githubHandle": "mabroukmahdhi", + "gravatarHash": "1f5b179abb9b9f8a34a4a9799e205c96", + "feedUris": [ + "https://medium.com/feed/@mabroukmahdhi" + ], + "position": { + "lat": 49.873207, + "lon": 8.650779 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/mallibone.json b/Authors/mallibone.json new file mode 100644 index 0000000..49de03a --- /dev/null +++ b/Authors/mallibone.json @@ -0,0 +1,19 @@ +{ + "firstName": "Mark", + "lastName": "Allibone", + "stateOrRegion": "Zurich, Switzerland", + "emailAddress": "", + "tagOrBio": "is a Microsoft MVP who blogs, talks, coaches and develops all around mobile development.", + "webSite": "https://mallibone.com", + "twitterHandle": "mallibone", + "githubHandle": "mallibone", + "gravatarHash": "4fa14971da4fafb96830960bc7c6733d", + "feedUris": [ + "https://mallibone.com/feed/" + ], + "position": { + "lat": 47.5056381, + "lon": 8.7241297 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/marcofolio.json b/Authors/marcofolio.json new file mode 100644 index 0000000..b109554 --- /dev/null +++ b/Authors/marcofolio.json @@ -0,0 +1,19 @@ +{ + "firstName": "Marco", + "lastName": "Kuiper", + "tagOrBio": "is a Code monkey, incurably optimistic, loves great design and passionate about Xamarin.", + "stateOrRegion": "The Netherlands", + "emailAddress": "", + "twitterHandle": "marcofolio", + "gravatarHash": "5982941cf85cecc8254ec2a4f1c812ff", + "githubHandle": "marcofolio", + "position": { + "lat": 51.987642, + "lon": 5.904598 + }, + "webSite": "https://www.marcofolio.net/", + "feedUris": [ + "https://feeds2.feedburner.com/marcofolio" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/marcusts.json b/Authors/marcusts.json new file mode 100644 index 0000000..5380b47 --- /dev/null +++ b/Authors/marcusts.json @@ -0,0 +1,19 @@ +{ + "firstName": "Stephen", + "lastName": "Marcus", + "stateOrRegion": "Orange County, California", + "emailAddress": "marcus@marcusts.com", + "tagOrBio": "Certified Xamarin mobile app developer", + "webSite": "https://www.marcusts.com", + "feedUris": [ + "https://www.marcusts.com/category/Xamarin/feed/" + ], + "twitterHandle": "", + "gravatarHash": "a64074b2ab0ac9b5a27379670b259d6d", + "githubHandle": "marcusts", + "position": { + "lat": 33.6423, + "lon": -117.6977 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/markolazic88.json b/Authors/markolazic88.json new file mode 100644 index 0000000..baa1fe1 --- /dev/null +++ b/Authors/markolazic88.json @@ -0,0 +1,19 @@ +{ + "firstName": "Marko", + "lastName": "Lazić", + "tagOrBio": "Software engineer, tech enthusiast, passionate traveler. 4+ years of active development in Xamarin.Forms", + "stateOrRegion": "Belgrade, Serbia", + "emailAddress": "marko.lazic88@gmail.com", + "twitterHandle": "markolazic88", + "gravatarHash": "5645586a7d29654e9b296b1409107014", + "webSite": "https://markolazic.com/", + "feedUris": [ + "https://markolazic.com/feed/" + ], + "githubHandle": "markolazic88", + "position": { + "lat": 44.7866, + "lon": 20.4489 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/martijn00.json b/Authors/martijn00.json new file mode 100644 index 0000000..576f3c1 --- /dev/null +++ b/Authors/martijn00.json @@ -0,0 +1,19 @@ +{ + "firstName": "Martijn", + "lastName": "van Dijk", + "stateOrRegion": "Amsterdam, Netherlands", + "emailAddress": "mhvdijk@gmail.com", + "tagOrBio": "is working at Baseflow.com on Apps and MvvmCross", + "webSite": "https://medium.com/@martijn00", + "feedUris": [ + "https://medium.com/feed/@martijn00" + ], + "twitterHandle": "mhvdijk", + "gravatarHash": "22155f520ab611cf04f76762556ca3f5", + "githubHandle": "martijn00", + "position": { + "lat": 52.370216, + "lon": 4.895168 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/mattleibow.json b/Authors/mattleibow.json new file mode 100644 index 0000000..2ee085e --- /dev/null +++ b/Authors/mattleibow.json @@ -0,0 +1,19 @@ +{ + "firstName": "Matthew", + "lastName": "Leibowitz", + "tagOrBio": "An all-round software guy. I live and breathe C# and .NET on any platform.", + "emailAddress": "mattleibow@live.com", + "twitterHandle": "mattleibow", + "githubHandle": "mattleibow", + "gravatarHash": "365da45bdc71334831f228aff805738f", + "stateOrRegion": "Cape Town, South Africa", + "position": { + "lat": -34.02231, + "lon": 18.46716 + }, + "webSite": "https://dotnetdevaddict.co.za", + "feedUris": [ + "https://dotnetdevaddict.co.za/feed/" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/mfractor.json b/Authors/mfractor.json new file mode 100644 index 0000000..7dd9d23 --- /dev/null +++ b/Authors/mfractor.json @@ -0,0 +1,19 @@ +{ + "firstName": "MFractor", + "lastName": "", + "stateOrRegion": "Brisbane, Australia", + "emailAddress": "matthew@mfractor.com", + "tagOrBio": "is a powerful productivity tool for Xamarin Developers.", + "webSite": "https://www.mfractor.com/", + "twitterHandle": "mfractor", + "githubHandle": "mfractor", + "gravatarHash": "35bac056166a67222ddcd48b57113a32", + "feedUris": [ + "https://www.mfractor.com/blogs/news.atom" + ], + "position": { + "lat": -27.470125, + "lon": 153.021072 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/mike-grant.json b/Authors/mike-grant.json new file mode 100644 index 0000000..667d7c9 --- /dev/null +++ b/Authors/mike-grant.json @@ -0,0 +1,19 @@ +{ + "firstName": "Mike", + "lastName": "Grant", + "stateOrRegion": "Leicestershire, England", + "emailAddress": "", + "tagOrBio": "Software Engineer", + "webSite": "https://www.mikegrant.org.uk", + "gravatarHash": "06c34ad3fb10ef6786a043c4522b6a5b", + "feedUris": [ + "https://mikegrant.org.uk/feeds/Xamarin.Forms.xml" + ], + "twitterHandle": "mike_grant_", + "githubHandle": "mike-grant", + "position": { + "lat": 52.663878, + "lon": -1.3052377 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/mindofai.json b/Authors/mindofai.json new file mode 100644 index 0000000..846d691 --- /dev/null +++ b/Authors/mindofai.json @@ -0,0 +1,19 @@ +{ + "firstName": "Bryan Anthony", + "lastName": "Garcia", + "stateOrRegion": "Manila, Philippines", + "githubHandle": "mindofai", + "twitterHandle": "mindofai", + "emailAddress": "bryananthonygarcia@live.com", + "tagOrBio": "Mobile .NET Developer who enjoys learning and writing about Mobile .NET stuff and has passion for football. Co-leads Mobile .NET Developers - Philippines", + "gravatarHash": "29e1cdab06c48322805220e33556c20c", + "webSite": "https://mindofai.github.io/", + "position": { + "lat": 14.668896, + "lon": 120.947204 + }, + "feedUris": [ + "https://mindofai.github.io/feed.xml" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/mkieres.json b/Authors/mkieres.json new file mode 100644 index 0000000..f64ed2a --- /dev/null +++ b/Authors/mkieres.json @@ -0,0 +1,19 @@ +{ + "emailAddress": "mikolaj.kieres@progrunning.net", + "languageCode": "en", + "feedUris": [ + "https://progrunning.net/rss/" + ], + "firstName": "Mikolaj", + "githubHandle": "mkieres", + "gravatarHash": "", + "lastName": "Kieres", + "position": { + "lat": -33.826623, + "lon": 151.195031 + }, + "tagOrBio": "Mobile development adventurer", + "stateOrRegion": "Sydney, Australia", + "twitterHandle": "mikolajkieres", + "webSite": "https://progrunning.net" +} \ No newline at end of file diff --git a/Authors/msiccdev.json b/Authors/msiccdev.json new file mode 100644 index 0000000..f003737 --- /dev/null +++ b/Authors/msiccdev.json @@ -0,0 +1,19 @@ +{ + "firstName": "Marco", + "lastName": "Siccardi", + "tagOrBio": "cross platform developer, writing software for Desktop, Mobiles and IOT", + "stateOrRegion": "Switzerland", + "emailAddress": "msiccdev@hotmail.com", + "twitterHandle": "msicc", + "gravatarHash": "67aaa7c3b6357dbccc1167a70b0c73e3", + "githubHandle": "msiccdev", + "position": { + "lat": 47.4683, + "lon": 8.75727 + }, + "webSite": "https://msicc.net/category/devstories/xamarin/", + "feedUris": [ + "https://msicc.net/category/devstories/xamarin/feed/" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/nickrandolph.json b/Authors/nickrandolph.json new file mode 100644 index 0000000..d3febff --- /dev/null +++ b/Authors/nickrandolph.json @@ -0,0 +1,19 @@ +{ + "firstName": "Nick", + "lastName": "Randolph", + "stateOrRegion": "Sydney, Australia", + "emailAddress": "nick@builttoroam.com", + "tagOrBio": "Microsoft MVP and maintainer of @MvvmCross. Co-founder, Tech Lead at Built to Roam (@btroam), special forces in cross platform app and cloud solutions.", + "webSite": "https://builttoroam.com", + "feedUris": [ + "https://nicksnettravels.builttoroam.com/syndication.axd" + ], + "twitterHandle": "thenickrandolph", + "gravatarHash": "", + "githubHandle": "nickrandolph", + "position": { + "lat": -33.839559, + "lon": 151.2112434 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/nigelferrissey.json b/Authors/nigelferrissey.json new file mode 100644 index 0000000..e893dd6 --- /dev/null +++ b/Authors/nigelferrissey.json @@ -0,0 +1,19 @@ +{ + "firstName": "Nigel", + "lastName": "Ferrissey", + "tagOrBio": "Mobile and front-end developer, Xamarin specialist", + "emailAddress": "n_ferrissey@hotmail.com", + "twitterHandle": "nferrissey", + "gravatarHash": "106ec2de3e2ea4e88e9fc431974f5d53", + "stateOrRegion": "Brisbane, Australia", + "webSite": "https://xamarininsider.com", + "feedUris": [ + "https://xamarininsider.com/rss/" + ], + "githubHandle": "nigelferrissey", + "position": { + "lat": -27.469657, + "lon": 153.025241 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/officialdoniald.json b/Authors/officialdoniald.json new file mode 100644 index 0000000..0b845e1 --- /dev/null +++ b/Authors/officialdoniald.json @@ -0,0 +1,19 @@ +{ + "firstName": "Bence", + "lastName": "Lenart", + "tagOrBio": "Xamarin developer and blogger.", + "stateOrRegion": "Szeged, Hungary", + "emailAddress": "bence960206@gmail.com", + "webSite": "ttps://officialdoniald.com", + "feedUris": [ + "https://officialdoniald.com/feed" + ], + "twitterHandle": "officialdoniald", + "gravatarHash": "4e467fcdbb65da5080d215bf303c442a", + "githubHandle": "officialdoniald", + "position": { + "lat": 46.231222, + "lon": 20.119167 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/peterfoot.json b/Authors/peterfoot.json new file mode 100644 index 0000000..d9d9ca7 --- /dev/null +++ b/Authors/peterfoot.json @@ -0,0 +1,19 @@ +{ + "firstName": "Peter", + "lastName": "Foot", + "stateOrRegion": "United Kingdom", + "emailAddress": "peter@peterfoot.net", + "twitterHandle": "peterfoot", + "webSite": "https://inthehand.com/blog/", + "feedUris": [ + "https://inthehand.com/category/xamarin/feed/" + ], + "gravatarHash": "fa15aeeccc4b23e8a4677aeacb65b7bb", + "tagOrBio": "develops Xamarin and Windows applications at In The Hand Ltd", + "githubHandle": "peterfoot", + "position": { + "lat": 52.76872, + "lon": -2.37825 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/ramonesteban78.json b/Authors/ramonesteban78.json new file mode 100644 index 0000000..25bc0d4 --- /dev/null +++ b/Authors/ramonesteban78.json @@ -0,0 +1,19 @@ +{ + "firstName": "Ramon", + "lastName": "Esteban", + "tagOrBio": "Freelance Xamarin developer", + "stateOrRegion": "Malaga, Spain", + "emailAddress": "wantedforcode@outlook.com", + "twitterHandle": "ramonesteban78", + "gravatarHash": "2fc49c3e9095aece416ad4e147fa1452", + "githubHandle": "ramonesteban78", + "position": { + "lat": 36.5126395, + "lon": -4.6483388 + }, + "webSite": "https://ramonesteban78.github.io/", + "feedUris": [ + "https://ramonesteban78.github.io/feed.xml" + ], + "languageCode": "es" +} \ No newline at end of file diff --git a/Authors/rdavisau.json b/Authors/rdavisau.json new file mode 100644 index 0000000..51ef775 --- /dev/null +++ b/Authors/rdavisau.json @@ -0,0 +1,19 @@ +{ + "feedUris": [ + "https://ryandavis.io/rss/" + ], + "firstName": "Ryan", + "lastName": "Davis", + "stateOrRegion": "Brisbane, Australia", + "emailAddress": "ryandavis.au@gmail.com", + "tagOrBio": "knows how to 🎉", + "webSite": "https://ryandavis.io", + "twitterHandle": "rdavis_au", + "githubHandle": "rdavisau", + "gravatarHash": "d351762ec451e252b20ff860dfcded91d351762ec451e252b20ff860dfcded91", + "position": { + "lat": -27.469771, + "lon": 153.025124 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/rdelrosario.json b/Authors/rdelrosario.json new file mode 100644 index 0000000..4b067dd --- /dev/null +++ b/Authors/rdelrosario.json @@ -0,0 +1,19 @@ +{ + "firstName": "Rendy", + "lastName": "Del Rosario", + "tagOrBio": "is a senior software developer with a passion for mobile development using Xamarin platform", + "stateOrRegion": "Dominican Republic", + "emailAddress": "rendy@crossgeeks.com", + "twitterHandle": "rdelrosario", + "gravatarHash": "4bece0ce1c33e65177110bcb95688c68", + "githubHandle": "rdelrosario", + "position": { + "lat": 18.486058, + "lon": -69.931212 + }, + "webSite": "https://xamboy.com/", + "feedUris": [ + "https://xamboy.com/rss" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/redth.json b/Authors/redth.json new file mode 100644 index 0000000..61f6a67 --- /dev/null +++ b/Authors/redth.json @@ -0,0 +1,19 @@ +{ + "firstName": "Jon", + "lastName": "Dick", + "tagOrBio": "", + "emailAddress": "jondick@gmail.com", + "twitterHandle": "redth", + "gravatarHash": "ad73e52d7e4d89e904e7c4cfd91fc2b9", + "stateOrRegion": "Ontario, Canada", + "webSite": "https://redth.codes", + "feedUris": [ + "https://redth.codes/feed/" + ], + "githubHandle": "redth", + "position": { + "lat": 51.253775, + "lon": -85.323214 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/ricardoprestes.json b/Authors/ricardoprestes.json new file mode 100644 index 0000000..902978e --- /dev/null +++ b/Authors/ricardoprestes.json @@ -0,0 +1,19 @@ +{ + "firstName": "Ricardo", + "lastName": "Prestes", + "stateOrRegion": "Cornélio Procópio, Brasil", + "emailAddress": "ricardo.logan@hotmail.com", + "tagOrBio": "Tech lead mobile developer, Burger Monkeys Co-founder", + "webSite": "https://oficinadologan.wordpress.com/", + "twitterHandle": "ricardo_prestes", + "githubHandle": "ricardoprestes", + "gravatarHash": "9802e38d5bd2cd85db8b0720d5feed29", + "feedUris": [ + "https://oficinadologan.wordpress.com/rss" + ], + "position": { + "lat": -23.194571, + "lon": -50.7795215 + }, + "languageCode": "pt" +} \ No newline at end of file diff --git a/Authors/rid00z.json b/Authors/rid00z.json new file mode 100644 index 0000000..eab1205 --- /dev/null +++ b/Authors/rid00z.json @@ -0,0 +1,19 @@ +{ + "firstName": "Michael", + "lastName": "Ridland", + "stateOrRegion": "Sydney, Australia", + "emailAddress": "michael@xam-consulting.com", + "tagOrBio": "Xamarin Contractor/Consultant | Founder XAM Consulting (xam-consulting.com) | Creator of FreshMvvm", + "webSite": "https://michaelridland.com", + "feedUris": [ + "https://michaelridland.com/feed/" + ], + "twitterHandle": "rid00z", + "gravatarHash": "3c07e56045d18f4f290eb4983031309d", + "githubHandle": "rid00z", + "position": { + "lat": -25.348875, + "lon": 131.035 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/robintschroeder.json b/Authors/robintschroeder.json new file mode 100644 index 0000000..abbcb44 --- /dev/null +++ b/Authors/robintschroeder.json @@ -0,0 +1,19 @@ +{ + "firstName": "Robin", + "lastName": "Schroeder", + "tagOrBio": "Xamarin.Forms/UWP Software Consultant at MSCTek", + "stateOrRegion": "Illinois", + "emailAddress": "robin@msctek.com", + "twitterHandle": "RTSchroeder", + "gravatarHash": "1754cd9eee726fd3a5252a4718cbf108", + "githubHandle": "robintschroeder", + "position": { + "lat": 41.9136926, + "lon": -88.3148351 + }, + "webSite": "https://msctek.com/", + "feedUris": [ + "https://www.msctek.com/feed/" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/roubachof.json b/Authors/roubachof.json new file mode 100644 index 0000000..cdddf25 --- /dev/null +++ b/Authors/roubachof.json @@ -0,0 +1,19 @@ +{ + "firstName": "Jean-Marie", + "lastName": "Alfonsi", + "tagOrBio": "is a singing software engineer who travels at the speed light and wanna make a supersonic man out of you.", + "stateOrRegion": "Paris, France", + "emailAddress": "jm.alfonsi@gmail.com", + "twitterHandle": "Piskariov", + "gravatarHash": "2a30d8bc59e2ccb6e83bb498d519394a", + "webSite": "https://www.sharpnado.com/", + "feedUris": [ + "https://www.sharpnado.com/rss/" + ], + "githubHandle": "roubachof", + "position": { + "lat": 48.8588377, + "lon": 2.2770206 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/saamerm.json b/Authors/saamerm.json new file mode 100644 index 0000000..592393a --- /dev/null +++ b/Authors/saamerm.json @@ -0,0 +1,19 @@ +{ + "feedUris": [ + "https://medium.com/feed/@prototypemakers" + ], + "firstName": "Saamer", + "lastName": "Mansoor", + "stateOrRegion": "Toronto, Canada", + "emailAddress": "i@saamer.me", + "tagOrBio": "Top 5% Xamarin StackOverflow, Freelancer, Trainer", + "webSite": "https://medium.com/@prototypemakers", + "twitterHandle": "saamerm", + "githubHandle": "saamerm", + "gravatarHash": "66887acb0b76f2d3059255a1c53a3b22", + "position": { + "lat": 43.639728, + "lon": -79.380099 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/sact1909.json b/Authors/sact1909.json new file mode 100644 index 0000000..0e3de8b --- /dev/null +++ b/Authors/sact1909.json @@ -0,0 +1,19 @@ +{ + "firstName": "Steven", + "lastName": "Checo", + "stateOrRegion": "United States", + "emailAddress": "steven.checo.19@gmail.com", + "tagOrBio": "is a C# ASP.Net and Xamarin Developer, I'd preferd back-end but I get along with the Front-End, I love help others with code and build new things with friends, I ♥ C#", + "webSite": "https://checox.com", + "twitterHandle": "steven1909", + "githubHandle": "sact1909", + "gravatarHash": "47ad7178de588bb9a0b5922a5c1364c8", + "feedUris": [ + "https://checox.com/feed/" + ], + "position": { + "lat": 40.837222, + "lon": -73.886111 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/samirgcofficial.json b/Authors/samirgcofficial.json new file mode 100644 index 0000000..c55fd76 --- /dev/null +++ b/Authors/samirgcofficial.json @@ -0,0 +1,19 @@ +{ + "firstName": "Samir", + "lastName": "GC", + "tagOrBio": ".net lover, xamarin mobile developer | XamarinGuy Show Host | Trainer | Instructor", + "stateOrRegion": "Kathmandu/Nepal", + "emailAddress": "samirgcofficial@gmail.com", + "twitterHandle": "xamaringuy", + "githubHandle": "samirgcofficial", + "position": { + "lat": 27.700769, + "lon": 85.30014 + }, + "webSite": "https://xamaringuyshow.com/", + "feedUris": [ + "https://xamaringuyshow.com/feed/" + ], + "gravatarHash": "4b0b695b7711b0184ab2049e33f2cfd7", + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/shirshov.json b/Authors/shirshov.json new file mode 100644 index 0000000..9de2a03 --- /dev/null +++ b/Authors/shirshov.json @@ -0,0 +1,19 @@ +{ + "firstName": "Alex", + "lastName": "Shirshov", + "stateOrRegion": "Sydney, Australia", + "emailAddress": "shirshov@gmail.com", + "tagOrBio": "Software Craftsman", + "webSite": "https://omnitalented.com", + "twitterHandle": "omnitalented", + "githubHandle": "shirshov", + "gravatarHash": "", + "position": { + "lat": -33.88236, + "lon": 151.206588 + }, + "languageCode": "en", + "feedUris": [ + "https://omnitalented.com/rss-xamarin.xml" + ] +} \ No newline at end of file diff --git a/Authors/smstuebe.json b/Authors/smstuebe.json new file mode 100644 index 0000000..86aad08 --- /dev/null +++ b/Authors/smstuebe.json @@ -0,0 +1,19 @@ +{ + "firstName": "Sven-Michael", + "lastName": "Stübe", + "tagOrBio": "loves the cross platform approach, develops Xamarin plugins and organizes Xamarin Pizza & Beer Meetups in Munich", + "stateOrRegion": "Munich, Germany", + "emailAddress": "", + "twitterHandle": "stuebe2k14", + "webSite": "https://smstuebe.de/", + "feedUris": [ + "https://smstuebe.de/feed.xml" + ], + "githubHandle": "smstuebe", + "gravatarHash": "08b73d0a58fc120a8cc8dc561d83b3d6", + "position": { + "lat": 48.1373831, + "lon": 11.5063151 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/sthewissen.json b/Authors/sthewissen.json new file mode 100644 index 0000000..539b113 --- /dev/null +++ b/Authors/sthewissen.json @@ -0,0 +1,19 @@ +{ + "firstName": "Steven", + "lastName": "Thewissen", + "stateOrRegion": "The Netherlands", + "emailAddress": "", + "tagOrBio": " is a Xamarin Developer with a knack for Photoshop and a passion for soccer and cycling. He is also in love with his Xbox One", + "webSite": "https://www.thewissen.io/", + "feedUris": [ + "https://www.thewissen.io/feed/" + ], + "twitterHandle": "devnl", + "gravatarHash": "9f698e6f515cb54dbda305034b6823fc", + "position": { + "lat": 50.889039, + "lon": 5.853717 + }, + "githubHandle": "sthewissen", + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/stvansolano.json b/Authors/stvansolano.json new file mode 100644 index 0000000..28e2fee --- /dev/null +++ b/Authors/stvansolano.json @@ -0,0 +1,19 @@ +{ + "firstName": "Esteban", + "lastName": "Solano", + "stateOrRegion": "Cartago, Costa Rica", + "emailAddress": "stvansolano@outlook.com", + "tagOrBio": "is a passionate software community guy from Costa Rica who enjoys learning, talk and help others to learn C# and Xamarin", + "webSite": "https://stvansolano.github.io/", + "feedUris": [ + "https://stvansolano.github.io/atom.xml" + ], + "twitterHandle": "stvansolano", + "gravatarHash": "d02d96057c4cd905d60d14549b00db0d", + "githubHandle": "stvansolano", + "position": { + "lat": 9.9322992, + "lon": -84.0815271 + }, + "languageCode": "es" +} \ No newline at end of file diff --git a/Authors/susairajs.json b/Authors/susairajs.json new file mode 100644 index 0000000..5a9d688 --- /dev/null +++ b/Authors/susairajs.json @@ -0,0 +1,19 @@ +{ + "firstName": "Delpin", + "lastName": "Susai Raj", + "stateOrRegion": "Chennai, India", + "emailAddress": "susairajs@outlook.com", + "tagOrBio": "Code with Monkeys", + "webSite": "https://xamarinmonkeys.blogspot.com/", + "twitterHandle": "susairajs18", + "githubHandle": "susairajs", + "gravatarHash": "3408967b9598e7bb107baf15d5a15454", + "feedUris": [ + "https://xamarinmonkeys.blogspot.com/feeds/posts/default" + ], + "position": { + "lat": 13.0783995, + "lon": 80.2886684 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/syncfusion.json b/Authors/syncfusion.json new file mode 100644 index 0000000..f5deb50 --- /dev/null +++ b/Authors/syncfusion.json @@ -0,0 +1,19 @@ +{ + "firstName": "Syncfusion", + "lastName": "", + "stateOrRegion": "North Carolina, USA", + "emailAddress": "info@syncfusion.com", + "tagOrBio": "Syncfusion provides the best third-party UI components for Xamarin, Xamarin.Android and Xamarin.iOS", + "webSite": "https://www.syncfusion.com/xamarin-ui-controls", + "feedUris": [ + "https://www.syncfusion.com/blogs/feed" + ], + "twitterHandle": "Syncfusion", + "gravatarHash": "4291ef07481d300471113dbd4b4aabc2", + "githubHandle": "syncfusion", + "position": { + "lat": 35.855361, + "lon": -78.815751 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/tbertuzzi.json b/Authors/tbertuzzi.json new file mode 100644 index 0000000..cc1c04b --- /dev/null +++ b/Authors/tbertuzzi.json @@ -0,0 +1,19 @@ +{ + "firstName": "Thiago", + "lastName": "Bertuzzi", + "tagOrBio": "Web and Mobile Developer, Blogger, Speaker", + "stateOrRegion": "São Paulo, Brasil", + "emailAddress": "thiago.bertuzzi@gmail.com", + "twitterHandle": "tbertuzzi", + "githubHandle": "tbertuzzi", + "gravatarHash": "82d95125be475913cdc7a7fe319d0133", + "position": { + "lat": -23.5834337, + "lon": -46.6672048 + }, + "webSite": "https://medium.com/@bertuzzi/", + "feedUris": [ + "https://medium.com/feed/@bertuzzi" + ], + "languageCode": "pt" +} \ No newline at end of file diff --git a/Authors/telerik.json b/Authors/telerik.json new file mode 100644 index 0000000..a4ca310 --- /dev/null +++ b/Authors/telerik.json @@ -0,0 +1,19 @@ +{ + "firstName": "Telerik", + "lastName": "", + "stateOrRegion": "Global", + "emailAddress": "telerikxamarinteam@progress.com", + "tagOrBio": "Progress Telerik UI libraries equip .NET ninjas with a full arsenal of weapons to help you create beautiful, modern and future-proof applications quickly and intuitively.", + "webSite": "https://www.telerik.com/", + "twitterHandle": "telerik", + "githubHandle": "telerik", + "gravatarHash": "", + "feedUris": [ + "https://feeds.telerik.com/blogs" + ], + "position": { + "lat": 42.512778, + "lon": -71.2515 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/trailheadtechnology.json b/Authors/trailheadtechnology.json new file mode 100644 index 0000000..c613eed --- /dev/null +++ b/Authors/trailheadtechnology.json @@ -0,0 +1,19 @@ +{ + "firstName": "Trailhead Technology", + "lastName": "", + "stateOrRegion": "Jenison, MI", + "twitterHandle": "@TrailheadTec", + "emailAddress": "info@trailheadtechnology.com", + "tagOrBio": "", + "gravatarHash": "0d6c200b64336790495f110332e1d7be", + "webSite": "https://www.trailheadtechnology.com/", + "feedUris": [ + "https://trailheadtechnology.com/feed" + ], + "githubHandle": "trailheadtechnology", + "position": { + "lat": 42.921436, + "lon": -85.806578 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/vulcanlee.json b/Authors/vulcanlee.json new file mode 100644 index 0000000..0067461 --- /dev/null +++ b/Authors/vulcanlee.json @@ -0,0 +1,19 @@ +{ + "firstName": "Vulcan", + "lastName": "Lee", + "stateOrRegion": "Taipei, Taiwan", + "twitterHandle": "vulcanlee", + "emailAddress": "vulcan.lee@gmail.com", + "tagOrBio": "Vulcan Lee is a Microsoft MVP who develops Xamarin at Doggy Ltd", + "gravatarHash": "f5de84ba365a15a05748624c07e70075", + "webSite": "https://mylabtw.blogspot.com/", + "feedUris": [ + "https://mylabtw.blogspot.com/feeds/posts/default?alt=rss" + ], + "githubHandle": "vulcanlee", + "position": { + "lat": 25.043847, + "lon": 121.525645 + }, + "languageCode": "zh" +} \ No newline at end of file diff --git a/Authors/willsb.json b/Authors/willsb.json new file mode 100644 index 0000000..16de042 --- /dev/null +++ b/Authors/willsb.json @@ -0,0 +1,19 @@ +{ + "firstName": "William", + "lastName": "Barbosa", + "stateOrRegion": "Santos, Brasil", + "twitterHandle": "willdotnet", + "githubHandle": "willsb", + "tagOrBio": "Microsoft MVP, Blogger, Speaker, Monkey Nights Co-founder/Host and MvvmCross contributor", + "emailAddress": "heytherewill@gmail.com", + "gravatarHash": "e47d219e8ee5d6dd5a44940dc26585c4", + "position": { + "lat": -23.954821, + "lon": -46.3247136 + }, + "webSite": "https://willsb.github.io", + "feedUris": [ + "https://medium.com/feed/@heytherewill" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/winstongubantes.json b/Authors/winstongubantes.json new file mode 100644 index 0000000..0fd50d0 --- /dev/null +++ b/Authors/winstongubantes.json @@ -0,0 +1,19 @@ +{ + "firstName": "Winston", + "lastName": "Gubantes", + "tagOrBio": "a Xamarin Developer at Gluon Consulting", + "stateOrRegion": "Davao City, Philippines", + "emailAddress": "winston.gubantes@gmail.com", + "twitterHandle": "tony_fear", + "gravatarHash": "9e1aea174237384361fc260b33559d05", + "githubHandle": "winstongubantes", + "position": { + "lat": 7.1066271, + "lon": 125.6294976 + }, + "webSite": "https://winstongubantes.blogspot.com/", + "feedUris": [ + "https://winstongubantes.blogspot.com/feeds/posts/default?alt=rss" + ], + "languageCode": "es" +} \ No newline at end of file diff --git a/Authors/wislon.json b/Authors/wislon.json new file mode 100644 index 0000000..6e7fe33 --- /dev/null +++ b/Authors/wislon.json @@ -0,0 +1,19 @@ +{ + "firstName": "John", + "lastName": "Wilson", + "stateOrRegion": "Brisbane, Australia", + "emailAddress": "wislon@hotmail.com", + "tagOrBio": "Head of Mobile, Tech Lead, Xamarin Specialist", + "webSite": "https://blog.wislon.io/", + "feedUris": [ + "https://blog.wislon.io/atom.xml" + ], + "twitterHandle": "wislon", + "gravatarHash": "86c9d925c04b4c79c98bfc839d949e15", + "githubHandle": "wislon", + "position": { + "lat": -27.3812513, + "lon": 152.7130138 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/xablu.json b/Authors/xablu.json new file mode 100644 index 0000000..0d8e711 --- /dev/null +++ b/Authors/xablu.json @@ -0,0 +1,19 @@ +{ + "firstName": "Xablu", + "lastName": "", + "stateOrRegion": "Amsterdam, the Netherlands", + "emailAddress": "hello@xablu.com", + "tagOrBio": "a 100% pure Xamarin company.", + "webSite": "https://www.xablu.com/", + "twitterHandle": "xabluhq", + "githubHandle": "xablu", + "gravatarHash": "508b1a99bb81e09c189e7487ecb69167", + "feedUris": [ + "https://www.xablu.com/tag/planet-xamarin/feed/" + ], + "position": { + "lat": 52.3702, + "lon": 4.8952 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/xamarin.json b/Authors/xamarin.json new file mode 100644 index 0000000..5ef90a8 --- /dev/null +++ b/Authors/xamarin.json @@ -0,0 +1,19 @@ +{ + "firstName": "The Xamarin", + "lastName": "Blog", + "stateOrRegion": "Internet", + "emailAddress": "hello@xamarin.com", + "tagOrBio": "is your official source for Xamarin developer news.", + "webSite": "https://blog.xamarin.com", + "feedUris": [ + "https://devblogs.microsoft.com/xamarin/feed/" + ], + "twitterHandle": "xamarinhq", + "gravatarHash": "70148d964bb389d42547834e1062c886", + "githubHandle": "xamarin", + "position": { + "lat": 37.77493, + "lon": -122.419416 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/xamarinhowto.json b/Authors/xamarinhowto.json new file mode 100644 index 0000000..8b56c33 --- /dev/null +++ b/Authors/xamarinhowto.json @@ -0,0 +1,19 @@ +{ + "firstName": "Matt", + "lastName": "Crombie", + "tagOrBio": "Senior Xamarin Mobile Consultant/Architect", + "stateOrRegion": "Brisbane, Australia", + "emailAddress": "matt@xamarinhowto.com", + "twitterHandle": "xamarinhowto", + "gravatarHash": "3f1200c50911032b3558b7c0fc29c847", + "githubHandle": "xamarinhowto", + "position": { + "lat": -27.46845, + "lon": 153.024184 + }, + "webSite": "https://xamarinhowto.com/", + "feedUris": [ + "https://xamarinhowto.com/feed/" + ], + "languageCode": "en" +} \ No newline at end of file diff --git a/Authors/yuv4ik.json b/Authors/yuv4ik.json new file mode 100644 index 0000000..0055a33 --- /dev/null +++ b/Authors/yuv4ik.json @@ -0,0 +1,20 @@ +{ + "firstName": "Evgeny", + "lastName": "Zborovsky", + "stateOrRegion": "Estonia", + "emailAddress": "yuv4ik@gmail.com", + "tagOrBio": "blogs, speaks, helps others and is an enthusiastic Xamarin developer.", + "webSite": "https://evgenyzborovsky.com/", + "feedUris": [ + "https://evgenyzborovsky.com/tag/xamarin-forms/feed/", + "https://evgenyzborovsky.com/tag/xamarin/feed/" + ], + "twitterHandle": "ezborovsky", + "gravatarHash": "b8a0ab8445fafb38afdf26cb976df32f", + "githubHandle": "yuv4ik", + "position": { + "lat": 58.3750372, + "lon": 26.6625567 + }, + "languageCode": "en" +} \ No newline at end of file diff --git a/PlanetDotnet.Authors/Models/Authors/Author.cs b/PlanetDotnet.Authors/Models/Authors/Author.cs new file mode 100644 index 0000000..da6a3af --- /dev/null +++ b/PlanetDotnet.Authors/Models/Authors/Author.cs @@ -0,0 +1,47 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using PlanetDotnet.Authors.Models.GeoPositions; + +namespace PlanetDotnet.Authors.Models.Authors +{ + public class Author + { + [JsonProperty("firstName", Required = Required.DisallowNull)] + public string FirstName { get; set; } + [JsonProperty("lastName", Required = Required.DisallowNull)] + public string LastName { get; set; } + [JsonProperty("stateOrRegion", Required = Required.DisallowNull)] + public string StateOrRegion { get; set; } + [EmailAddress] + [JsonProperty("emailAddress", Required = Required.Always)] + public string EmailAddress { get; set; } + [JsonProperty("tagOrBio", Required = Required.DisallowNull)] + public string ShortBioOrTagLine { get; set; } + [Url] + [JsonProperty("webSite", Required = Required.Always)] + public Uri WebSite { get; set; } + [JsonProperty("twitterHandle", Required = Required.DisallowNull)] + public string TwitterHandle { get; set; } + [JsonProperty("githubHandle", Required = Required.Always)] + public string GitHubHandle { get; set; } + [JsonProperty("gravatarHash", Required = Required.DisallowNull)] + public string GravatarHash { get; set; } + [JsonProperty("feedUris", Required = Required.Always)] + public IEnumerable FeedUris { get; set; } + [JsonProperty("position", Required = Required.DisallowNull)] + public GeoPosition Position { get; set; } + + // In ISO 639-1, lowercase, 2 letters + // https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + [JsonProperty("languageCode", Required = Required.Always)] + public string FeedLanguageCode { get; set; } + } +} diff --git a/PlanetDotnet.Authors/Models/GeoPositions/GeoPosition.cs b/PlanetDotnet.Authors/Models/GeoPositions/GeoPosition.cs new file mode 100644 index 0000000..e5f6204 --- /dev/null +++ b/PlanetDotnet.Authors/Models/GeoPositions/GeoPosition.cs @@ -0,0 +1,26 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using Newtonsoft.Json; + +namespace PlanetDotnet.Authors.Models.GeoPositions +{ + public class GeoPosition + { + public static GeoPosition Empty = new GeoPosition(-1337, 42); + + [JsonProperty("lat", Required = Required.Always)] + public double Lat { get; } + [JsonProperty("lon", Required = Required.Always)] + public double Lng { get; } + + public GeoPosition(double lat, double lng) + { + Lat = lat; + Lng = lng; + } + } +} diff --git a/PlanetDotnet.Authors/PlanetDotnet.Authors.csproj b/PlanetDotnet.Authors/PlanetDotnet.Authors.csproj new file mode 100644 index 0000000..6b825fc --- /dev/null +++ b/PlanetDotnet.Authors/PlanetDotnet.Authors.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.1 + disable + + + + + + + + + + + + diff --git a/PlanetDotnet.sln b/PlanetDotnet.sln index aa0ae85..aaba29e 100644 --- a/PlanetDotnet.sln +++ b/PlanetDotnet.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanetDotnet", "PlanetDotne EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanetDotnet.Tests.Unit", "PlanetDotnet.Tests.Unit\PlanetDotnet.Tests.Unit.csproj", "{ED070F5D-3C55-4E8F-B23A-381E09931D1C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanetDotnet.Authors", "PlanetDotnet.Authors\PlanetDotnet.Authors.csproj", "{0518A91D-95CA-4C21-BACF-E1E7E56D8754}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {ED070F5D-3C55-4E8F-B23A-381E09931D1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {ED070F5D-3C55-4E8F-B23A-381E09931D1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED070F5D-3C55-4E8F-B23A-381E09931D1C}.Release|Any CPU.Build.0 = Release|Any CPU + {0518A91D-95CA-4C21-BACF-E1E7E56D8754}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0518A91D-95CA-4C21-BACF-E1E7E56D8754}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0518A91D-95CA-4C21-BACF-E1E7E56D8754}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0518A91D-95CA-4C21-BACF-E1E7E56D8754}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 75e93a1075c50bb196c453c1a4149fa19b9e2d2e Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Sat, 18 Nov 2023 22:49:57 +0100 Subject: [PATCH 06/28] FOUNDATIONS: Get All Authors --- .../Services/AuthorService.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 PlanetDotnet.Authors/Services/AuthorService.cs diff --git a/PlanetDotnet.Authors/Services/AuthorService.cs b/PlanetDotnet.Authors/Services/AuthorService.cs new file mode 100644 index 0000000..095b5fc --- /dev/null +++ b/PlanetDotnet.Authors/Services/AuthorService.cs @@ -0,0 +1,42 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Newtonsoft.Json; +using PlanetDotnet.Authors.Models.Authors; + +namespace PlanetDotnet.Authors.Services +{ + public static class AuthorService + { + public static async Task> GetAllAuthors() + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceNames = assembly.GetManifestResourceNames(); + var authorsResourceNames = resourceNames.Where(res => + res.StartsWith("PlanetDotnet.Authors", StringComparison.OrdinalIgnoreCase) && + res.EndsWith(".json", StringComparison.OrdinalIgnoreCase)); + + var authorsTasks = authorsResourceNames.Select(name => ReadAuthor(assembly, name)); + var authors = await Task.WhenAll(authorsTasks).ConfigureAwait(false); + return authors; + } + + private static async Task ReadAuthor(Assembly assembly, string authorResourceName) + { + using var stream = assembly.GetManifestResourceStream(authorResourceName); + using var reader = new StreamReader(stream); + var json = await reader.ReadToEndAsync().ConfigureAwait(false); + var author = JsonConvert.DeserializeObject(json); + return author; + } + } +} From 5110b567c0deac526ff47980b6fa749913aa3ab1 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Sat, 18 Nov 2023 23:41:28 +0100 Subject: [PATCH 07/28] FUNCTIONS: Load Feed --- .../Extensions/SyndicationItemExtensions.cs | 55 +++++ .../Infrastructure/CombinedFeedSource.cs | 219 ++++++++++++++++++ PlanetDotnet/LoadFeedsFunction.cs | 90 +++++-- PlanetDotnet/PlanetDotnet.csproj | 6 + 4 files changed, 355 insertions(+), 15 deletions(-) create mode 100644 PlanetDotnet/Extensions/SyndicationItemExtensions.cs create mode 100644 PlanetDotnet/Infrastructure/CombinedFeedSource.cs diff --git a/PlanetDotnet/Extensions/SyndicationItemExtensions.cs b/PlanetDotnet/Extensions/SyndicationItemExtensions.cs new file mode 100644 index 0000000..716ba08 --- /dev/null +++ b/PlanetDotnet/Extensions/SyndicationItemExtensions.cs @@ -0,0 +1,55 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.Linq; +using System.ServiceModel.Syndication; + +namespace PlanetDotnet.Extensions +{ + public static class SyndicationItemExtensions + { + public static bool ApplyDefaultFilter(this SyndicationItem item) + { + if (item == null) + return false; + + var hasXamarinCategory = false; + var hasXamarinKeywords = false; + + if (item.Categories.Count > 0) + { + hasXamarinCategory = item.Categories.Any(category => + category.Name.ToLowerInvariant().Contains("xamarin")); + } + + if (item.ElementExtensions.Count > 0) + { + var element = item.ElementExtensions.FirstOrDefault(e => e.OuterName == "keywords"); + if (element != null) + { + var keywords = element.GetObject(); + hasXamarinKeywords = keywords.ToLowerInvariant().Contains("xamarin"); + } + } + + var hasXamarinTitle = item.Title?.Text.ToLowerInvariant().Contains("xamarin") ?? false; + + return hasXamarinTitle || hasXamarinCategory || hasXamarinKeywords; + } + + public static string ToHtml(this SyndicationContent content) + { + var textSyndicationContent = content as TextSyndicationContent; + if (textSyndicationContent != null) + { + return textSyndicationContent.Text; + } + + return content.ToString(); + } + } + +} diff --git a/PlanetDotnet/Infrastructure/CombinedFeedSource.cs b/PlanetDotnet/Infrastructure/CombinedFeedSource.cs new file mode 100644 index 0000000..3b9d9e3 --- /dev/null +++ b/PlanetDotnet/Infrastructure/CombinedFeedSource.cs @@ -0,0 +1,219 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.ServiceModel.Syndication; +using System.Threading.Tasks; +using System.Xml; +using Microsoft.Extensions.Logging; +using PlanetDotnet.Authors.Models.Authors; +using PlanetDotnet.Extensions; +using Polly; +using Polly.Retry; + +namespace PlanetDotnet.Infrastructure +{ + public class CombinedFeedSource + { + private static HttpClient _httpClient; + private static AsyncRetryPolicy _retryPolicy; + private readonly IEnumerable _authors; + private readonly ILogger _logger; + private readonly string _rssFeedTitle; + private readonly string _rssFeedDescription; + private readonly string _rssFeedUrl; + private readonly string _rssFeedImageUrl; + + public CombinedFeedSource( + IEnumerable authors, + ILogger logger, + string rssFeedTitle, + string rssFeedDescription, + string rssFeedUrl, + string rssFeedImageUrl) + { + EnsureHttpClient(); + + if (_retryPolicy == null) + { + // retry policy with max 2 retries, delay by x*x^1.2 where x is retry attempt + // this will ensure we don't retry too quickly + _retryPolicy = Policy.Handle() + .WaitAndRetryAsync(2, retry => TimeSpan.FromSeconds(retry * Math.Pow(1.2, retry))); + } + + _authors = authors; + _logger = logger; + _rssFeedTitle = rssFeedTitle; + _rssFeedDescription = rssFeedDescription; + _rssFeedUrl = rssFeedUrl; + _rssFeedImageUrl = rssFeedImageUrl; + } + + private void EnsureHttpClient() + { + if (_httpClient == null) + { + _httpClient = new HttpClient(); + _httpClient.DefaultRequestHeaders.UserAgent.Add( + new ProductInfoHeaderValue("PlanetDotnet", $"{GetType().Assembly.GetName().Version}")); + _httpClient.Timeout = TimeSpan.FromSeconds(15); + + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13; + } + } + + public async Task LoadFeed(int? numberOfItems, string languageCode = "mixed") + { + IEnumerable tamarins; + if (languageCode == null || languageCode == "mixed") // use all tamarins + { + tamarins = _authors; + } + else + { + tamarins = _authors.Where(t => t.FeedLanguageCode == languageCode); + } + + var feedTasks = tamarins.SelectMany(t => TryReadFeeds(t)).ToArray(); + + _logger?.LogInformation($"Loading feed for language: {languageCode} for {feedTasks.Length} authors"); + + var syndicationItems = await Task.WhenAll(feedTasks).ConfigureAwait(false); + var combinedFeed = GetCombinedFeed(syndicationItems.SelectMany(f => f), languageCode, tamarins, numberOfItems); + return combinedFeed; + } + + private IEnumerable>> TryReadFeeds(Author tamarin) + { + return tamarin.FeedUris.Select(uri => TryReadFeed(tamarin, uri.AbsoluteUri)); + } + + private async Task> TryReadFeed(Author tamarin, string feedUri) + { + try + { + return await _retryPolicy.ExecuteAsync(context => ReadFeed(feedUri), new Context(feedUri)).ConfigureAwait(false); + } + catch (FeedReadFailedException ex) + { + _logger.LogError(ex, $"{tamarin.FirstName} {tamarin.LastName}'s feed of {ex.Data["FeedUri"]} failed to load."); + } + + return new SyndicationItem[0]; + } + + private async Task> ReadFeed(string feedUri) + { + HttpResponseMessage response; + try + { + _logger?.LogInformation($"Loading feed {feedUri}"); + response = await _httpClient.GetAsync(feedUri).ConfigureAwait(false); + if (response.IsSuccessStatusCode) + { + using var feedStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + using var reader = XmlReader.Create(feedStream); + var feed = SyndicationFeed.Load(reader); + var filteredItems = feed.Items + .Where(item => item.ApplyDefaultFilter()); + + return filteredItems; + } + } + catch (HttpRequestException hex) + { + throw new FeedReadFailedException("Loading remote syndication feed failed", hex) + .WithData("FeedUri", feedUri); + } + catch (WebException ex) + { + throw new FeedReadFailedException("Loading remote syndication feed timed out", ex) + .WithData("FeedUri", feedUri); + } + catch (XmlException ex) + { + throw new FeedReadFailedException("Failed parsing remote syndication feed", ex) + .WithData("FeedUri", feedUri); + } + catch (TaskCanceledException ex) + { + throw new FeedReadFailedException("Reading feed timed out", ex) + .WithData("FeedUri", feedUri); + } + catch (OperationCanceledException opcex) + { + throw new FeedReadFailedException("Reading feed timed out", opcex) + .WithData("FeedUri", feedUri); + } + + throw new FeedReadFailedException("Loading remote syndication feed failed.") + .WithData("FeedUri", feedUri) + .WithData("HttpStatusCode", (int)response.StatusCode); + } + + private SyndicationFeed GetCombinedFeed(IEnumerable items, string languageCode, + IEnumerable tamarins, int? numberOfItems) + { + DateTimeOffset GetMaxTime(SyndicationItem item) + { + return new[] { item.PublishDate.UtcDateTime, item.LastUpdatedTime.UtcDateTime }.Max(); + } + + var orderedItems = items + .Where(item => + GetMaxTime(item) <= DateTimeOffset.UtcNow) + .OrderByDescending(item => GetMaxTime(item)); + + var feed = new SyndicationFeed( + _rssFeedTitle, + _rssFeedDescription, + new Uri(_rssFeedUrl), + numberOfItems.HasValue ? orderedItems.Take(numberOfItems.Value) : orderedItems) + { + ImageUrl = new Uri(_rssFeedImageUrl), + Copyright = new TextSyndicationContent("The copyright for each post is retained by its author."), + Language = languageCode, + LastUpdatedTime = DateTimeOffset.UtcNow + }; + + foreach (var tamarin in tamarins) + { + feed.Contributors.Add(new SyndicationPerson( + tamarin.EmailAddress, $"{tamarin.FirstName} {tamarin.LastName}", tamarin.WebSite.ToString())); + } + + return feed; + } + } + + public class FeedReadFailedException : Exception + { + public FeedReadFailedException(string message) + : base(message) + { + } + + public FeedReadFailedException(string message, Exception inner) + : base(message, inner) + { + } + } + + internal static class ExceptionExtensions + { + public static TException WithData(this TException exception, string key, object value) where TException : Exception + { + exception.Data[key] = value; + return exception; + } + } +} diff --git a/PlanetDotnet/LoadFeedsFunction.cs b/PlanetDotnet/LoadFeedsFunction.cs index 8d9b79a..e919852 100644 --- a/PlanetDotnet/LoadFeedsFunction.cs +++ b/PlanetDotnet/LoadFeedsFunction.cs @@ -4,37 +4,97 @@ // See License.txt in the project root for license information. // --------------------------------------------------------------- +using System; using System.IO; +using System.Linq; +using System.ServiceModel.Syndication; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; +using System.Xml; +using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Models; using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; +using PlanetDotnet.Authors.Services; +using PlanetDotnet.Infrastructure; namespace PlanetDotnet { public static class LoadFeedsFunction { [FunctionName("LoadFeedsFunction")] - public static async Task Run( - [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, + public static async Task Run( + [TimerTrigger("0 0 */1 * * *", RunOnStartup = true)] TimerInfo myTimer, ILogger log) { - log.LogInformation("C# HTTP trigger function processed a request."); + log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}"); - string name = req.Query["name"]; + var rssFeedTitle = GetEnvironmentVariable("RssFeedTitle"); + var rssFeedDescription = GetEnvironmentVariable("RssFeedDescription"); + var rssFeedUrl = GetEnvironmentVariable("RssFeedUrl"); + var rssFeedImageUrl = GetEnvironmentVariable("RssFeedImageUrl"); - string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); - dynamic data = JsonConvert.DeserializeObject(requestBody); - name = name ?? data?.name; + var authors = await AuthorService.GetAllAuthors(); + var languages = authors.Select(author => author.FeedLanguageCode).Distinct().ToList(); + languages.Add("mixed"); + var feedSource = + new CombinedFeedSource( + authors, + log, + rssFeedTitle, + rssFeedDescription, + rssFeedUrl, + rssFeedImageUrl); - string responseMessage = string.IsNullOrEmpty(name) - ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response." - : $"Hello, {name}. This HTTP triggered function executed successfully."; + var blobConnectString = GetEnvironmentVariable("FeedBlobStorage"); + var container = new BlobContainerClient(blobConnectString, "feeds"); + await container.CreateIfNotExistsAsync(); + await container.SetAccessPolicyAsync(PublicAccessType.Blob); - return new OkObjectResult(responseMessage); + foreach (var language in languages) + { + try + { + log.LogInformation($"Loading {language} combined author feed"); + var feed = await feedSource.LoadFeed(null, language); + using var stream = await SerializeFeed(feed); + await UploadBlob(container, stream, language, log); + } + catch (Exception ex) + { + log.LogError(ex, ex.Message); + } + } + } + + private static async Task UploadBlob(BlobContainerClient container, Stream feedStream, string language, ILogger log) + { + var feedName = $"feed.{language}.rss"; + var blob = container.GetBlobClient(feedName); + await blob.UploadAsync(feedStream, overwrite: true); + + log.LogInformation($"Uploaded {feedName} to {blob.Uri}"); + } + + private static async Task SerializeFeed(SyndicationFeed feed) + { + var memoryStream = new MemoryStream(); + using var xmlWriter = XmlWriter.Create(memoryStream, new XmlWriterSettings + { + Async = true + }); + + var rssFormatter = new Rss20FeedFormatter(feed); + rssFormatter.WriteTo(xmlWriter); + await xmlWriter.FlushAsync(); + + memoryStream.Seek(0, SeekOrigin.Begin); + + return memoryStream; + } + + private static string GetEnvironmentVariable(string name) + { + return Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process); } } } diff --git a/PlanetDotnet/PlanetDotnet.csproj b/PlanetDotnet/PlanetDotnet.csproj index 8b0fb18..69d5a8c 100644 --- a/PlanetDotnet/PlanetDotnet.csproj +++ b/PlanetDotnet/PlanetDotnet.csproj @@ -7,8 +7,14 @@ + + + + + + From 2903e3ad898a34889d6d0e994b843191b2036f93 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Sat, 18 Nov 2023 23:57:26 +0100 Subject: [PATCH 08/28] BROKERS: Read Feed --- PlanetDotnet/Brokers/Feeds/FeedBroker.cs | 32 +++++++++++++++++++++++ PlanetDotnet/Brokers/Feeds/IFeedBroker.cs | 17 ++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 PlanetDotnet/Brokers/Feeds/FeedBroker.cs create mode 100644 PlanetDotnet/Brokers/Feeds/IFeedBroker.cs diff --git a/PlanetDotnet/Brokers/Feeds/FeedBroker.cs b/PlanetDotnet/Brokers/Feeds/FeedBroker.cs new file mode 100644 index 0000000..9bd9ccc --- /dev/null +++ b/PlanetDotnet/Brokers/Feeds/FeedBroker.cs @@ -0,0 +1,32 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.Collections.Generic; +using System.Net.Http; +using System.ServiceModel.Syndication; +using System.Threading.Tasks; +using System.Xml; + +namespace PlanetDotnet.Brokers.Feeds +{ + public class FeedBroker : IFeedBroker + { + private readonly HttpClient httpClient; + + public FeedBroker(HttpClient httpClient) => + this.httpClient = httpClient; + + public async ValueTask> ReadFeedAsync(string feedUri) + { + var response = await httpClient.GetAsync(feedUri).ConfigureAwait(false); + using var feedStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + using var reader = XmlReader.Create(feedStream); + var feed = SyndicationFeed.Load(reader); + + return feed.Items; + } + } +} diff --git a/PlanetDotnet/Brokers/Feeds/IFeedBroker.cs b/PlanetDotnet/Brokers/Feeds/IFeedBroker.cs new file mode 100644 index 0000000..d02dc52 --- /dev/null +++ b/PlanetDotnet/Brokers/Feeds/IFeedBroker.cs @@ -0,0 +1,17 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.Collections.Generic; +using System.ServiceModel.Syndication; +using System.Threading.Tasks; + +namespace PlanetDotnet.Brokers.Feeds +{ + public interface IFeedBroker + { + ValueTask> ReadFeedAsync(string feedUri); + } +} From 0dd64c55156423d222e62396788b2300cc485191 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Sun, 19 Nov 2023 01:10:31 +0100 Subject: [PATCH 09/28] BROKERS: GetAllAuthors --- PlanetDotnet/Brokers/Authors/AuthorBroker.cs | 19 ++++ PlanetDotnet/Brokers/Authors/IAuthorBroker.cs | 17 ++++ .../Brokers/DateTimes/DateTimeBroker.cs | 10 ++ .../Brokers/DateTimes/IDateTimeBroker.cs | 9 ++ PlanetDotnet/Brokers/Feeds/FeedBroker.cs | 7 +- PlanetDotnet/Brokers/Feeds/IFeedBroker.cs | 3 +- .../Brokers/Loggings/ILoggingBroker.cs | 14 +++ .../Brokers/Loggings/LoggingBroker.cs | 31 ++++++ PlanetDotnet/Services/Feeds/FeedService.cs | 98 +++++++++++++++++++ PlanetDotnet/Services/Feeds/IFeedService.cs | 17 ++++ PlanetDotnet/Startup.cs | 16 ++- 11 files changed, 234 insertions(+), 7 deletions(-) create mode 100644 PlanetDotnet/Brokers/Authors/AuthorBroker.cs create mode 100644 PlanetDotnet/Brokers/Authors/IAuthorBroker.cs create mode 100644 PlanetDotnet/Brokers/DateTimes/DateTimeBroker.cs create mode 100644 PlanetDotnet/Brokers/DateTimes/IDateTimeBroker.cs create mode 100644 PlanetDotnet/Brokers/Loggings/ILoggingBroker.cs create mode 100644 PlanetDotnet/Brokers/Loggings/LoggingBroker.cs create mode 100644 PlanetDotnet/Services/Feeds/FeedService.cs create mode 100644 PlanetDotnet/Services/Feeds/IFeedService.cs diff --git a/PlanetDotnet/Brokers/Authors/AuthorBroker.cs b/PlanetDotnet/Brokers/Authors/AuthorBroker.cs new file mode 100644 index 0000000..ad3d674 --- /dev/null +++ b/PlanetDotnet/Brokers/Authors/AuthorBroker.cs @@ -0,0 +1,19 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.Collections.Generic; +using System.Threading.Tasks; +using PlanetDotnet.Authors.Models.Authors; +using PlanetDotnet.Authors.Services; + +namespace PlanetDotnet.Brokers.Authors +{ + public class AuthorBroker : IAuthorBroker + { + public async ValueTask> GetAllAuthorsAsync() => + await AuthorService.GetAllAuthors(); + } +} diff --git a/PlanetDotnet/Brokers/Authors/IAuthorBroker.cs b/PlanetDotnet/Brokers/Authors/IAuthorBroker.cs new file mode 100644 index 0000000..f7dfafb --- /dev/null +++ b/PlanetDotnet/Brokers/Authors/IAuthorBroker.cs @@ -0,0 +1,17 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.Collections.Generic; +using System.Threading.Tasks; +using PlanetDotnet.Authors.Models.Authors; + +namespace PlanetDotnet.Brokers.Authors +{ + public interface IAuthorBroker + { + ValueTask> GetAllAuthorsAsync(); + } +} diff --git a/PlanetDotnet/Brokers/DateTimes/DateTimeBroker.cs b/PlanetDotnet/Brokers/DateTimes/DateTimeBroker.cs new file mode 100644 index 0000000..1a91c52 --- /dev/null +++ b/PlanetDotnet/Brokers/DateTimes/DateTimeBroker.cs @@ -0,0 +1,10 @@ +using System; + +namespace PlanetDotnet.Brokers.DateTimes +{ + public class DateTimeBroker : IDateTimeBroker + { + public DateTimeOffset GetCurrentDateTimeOffset() => + DateTimeOffset.UtcNow; + } +} \ No newline at end of file diff --git a/PlanetDotnet/Brokers/DateTimes/IDateTimeBroker.cs b/PlanetDotnet/Brokers/DateTimes/IDateTimeBroker.cs new file mode 100644 index 0000000..d771b78 --- /dev/null +++ b/PlanetDotnet/Brokers/DateTimes/IDateTimeBroker.cs @@ -0,0 +1,9 @@ +using System; + +namespace PlanetDotnet.Brokers.DateTimes +{ + public interface IDateTimeBroker + { + DateTimeOffset GetCurrentDateTimeOffset(); + } +} \ No newline at end of file diff --git a/PlanetDotnet/Brokers/Feeds/FeedBroker.cs b/PlanetDotnet/Brokers/Feeds/FeedBroker.cs index 9bd9ccc..d05fc13 100644 --- a/PlanetDotnet/Brokers/Feeds/FeedBroker.cs +++ b/PlanetDotnet/Brokers/Feeds/FeedBroker.cs @@ -4,7 +4,6 @@ // See License.txt in the project root for license information. // --------------------------------------------------------------- -using System.Collections.Generic; using System.Net.Http; using System.ServiceModel.Syndication; using System.Threading.Tasks; @@ -12,21 +11,21 @@ namespace PlanetDotnet.Brokers.Feeds { - public class FeedBroker : IFeedBroker + internal class FeedBroker : IFeedBroker { private readonly HttpClient httpClient; public FeedBroker(HttpClient httpClient) => this.httpClient = httpClient; - public async ValueTask> ReadFeedAsync(string feedUri) + public async ValueTask ReadFeedAsync(string feedUri) { var response = await httpClient.GetAsync(feedUri).ConfigureAwait(false); using var feedStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); using var reader = XmlReader.Create(feedStream); var feed = SyndicationFeed.Load(reader); - return feed.Items; + return feed; } } } diff --git a/PlanetDotnet/Brokers/Feeds/IFeedBroker.cs b/PlanetDotnet/Brokers/Feeds/IFeedBroker.cs index d02dc52..aa48450 100644 --- a/PlanetDotnet/Brokers/Feeds/IFeedBroker.cs +++ b/PlanetDotnet/Brokers/Feeds/IFeedBroker.cs @@ -4,7 +4,6 @@ // See License.txt in the project root for license information. // --------------------------------------------------------------- -using System.Collections.Generic; using System.ServiceModel.Syndication; using System.Threading.Tasks; @@ -12,6 +11,6 @@ namespace PlanetDotnet.Brokers.Feeds { public interface IFeedBroker { - ValueTask> ReadFeedAsync(string feedUri); + ValueTask ReadFeedAsync(string feedUri); } } diff --git a/PlanetDotnet/Brokers/Loggings/ILoggingBroker.cs b/PlanetDotnet/Brokers/Loggings/ILoggingBroker.cs new file mode 100644 index 0000000..bb02432 --- /dev/null +++ b/PlanetDotnet/Brokers/Loggings/ILoggingBroker.cs @@ -0,0 +1,14 @@ +using System; + +namespace PlanetDotnet.Brokers.Loggings +{ + public interface ILoggingBroker + { + void LogInformation(string message); + void LogTrace(string message); + void LogDebug(string message); + void LogWarning(string message); + void LogError(Exception exception); + void LogCritical(Exception exception); + } +} diff --git a/PlanetDotnet/Brokers/Loggings/LoggingBroker.cs b/PlanetDotnet/Brokers/Loggings/LoggingBroker.cs new file mode 100644 index 0000000..d5a6695 --- /dev/null +++ b/PlanetDotnet/Brokers/Loggings/LoggingBroker.cs @@ -0,0 +1,31 @@ +using System; +using Microsoft.Extensions.Logging; + +namespace PlanetDotnet.Brokers.Loggings +{ + public class LoggingBroker : ILoggingBroker + { + private readonly ILogger logger; + + public LoggingBroker(ILogger logger) => + this.logger = logger; + + public void LogInformation(string message) => + this.logger.LogInformation(message); + + public void LogTrace(string message) => + this.logger.LogTrace(message); + + public void LogDebug(string message) => + this.logger.LogDebug(message); + + public void LogWarning(string message) => + this.logger.LogWarning(message); + + public void LogError(Exception exception) => + this.logger.LogError(exception.Message, exception); + + public void LogCritical(Exception exception) => + this.logger.LogCritical(exception, exception.Message); + } +} diff --git a/PlanetDotnet/Services/Feeds/FeedService.cs b/PlanetDotnet/Services/Feeds/FeedService.cs new file mode 100644 index 0000000..6086b99 --- /dev/null +++ b/PlanetDotnet/Services/Feeds/FeedService.cs @@ -0,0 +1,98 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.ServiceModel.Syndication; +using System.Threading.Tasks; +using PlanetDotnet.Brokers.Authors; +using PlanetDotnet.Brokers.DateTimes; +using PlanetDotnet.Brokers.Feeds; +using PlanetDotnet.Brokers.Loggings; + +namespace PlanetDotnet.Services.Feeds +{ + internal partial class FeedService : IFeedService + { + private readonly IFeedBroker feedBroker; + private readonly IAuthorBroker authorBroker; + private readonly IDateTimeBroker dateTimeBroker; + private readonly ILoggingBroker loggingBroker; + + private const string RssFeedTitleKey = "RssFeedTitle"; + private const string RssFeedDescriptionKey = "RssFeedDescription"; + private const string RssFeedUrlKey = "RssFeedUrl"; + private const string RssFeedImageUrlKey = "RssFeedImageUrl"; + + public FeedService( + IFeedBroker feedBroker, + IAuthorBroker authorBroker, + IDateTimeBroker dateTimeBroker, + ILoggingBroker loggingBroker) + { + this.feedBroker = feedBroker; + this.authorBroker = authorBroker; + this.dateTimeBroker = dateTimeBroker; + this.loggingBroker = loggingBroker; + } + + public async ValueTask CombineFeedsAsync() + { + var authors = await this.authorBroker.GetAllAuthorsAsync(); + + var feedItems = new List(); + + foreach (var author in authors) + { + foreach (var feedUri in author.FeedUris) + { + var feed = await this.feedBroker.ReadFeedAsync(feedUri.AbsoluteUri); + + feedItems.AddRange(feed.Items); + } + } + + return CreateCombinedFeed( + items: feedItems, + lastUpdate: this.dateTimeBroker.GetCurrentDateTimeOffset()); + } + + private static SyndicationFeed CreateCombinedFeed( + List items, + DateTimeOffset lastUpdate, + string languageCode = "mixed") + { + var rssFeedTitle = Environment.GetEnvironmentVariable( + variable: RssFeedTitleKey, + target: EnvironmentVariableTarget.Process); + + var rssFeedDescription = Environment.GetEnvironmentVariable( + variable: RssFeedDescriptionKey, + target: EnvironmentVariableTarget.Process); + + var rssFeedUrl = Environment.GetEnvironmentVariable( + variable: RssFeedUrlKey, + target: EnvironmentVariableTarget.Process); + + var rssFeedImageUrl = Environment.GetEnvironmentVariable( + variable: RssFeedImageUrlKey, + target: EnvironmentVariableTarget.Process); + + var feed = new SyndicationFeed( + title: rssFeedTitle, + description: rssFeedDescription, + feedAlternateLink: new Uri(rssFeedUrl), + items: items); + + feed.ImageUrl = new Uri(rssFeedImageUrl); + feed.Copyright = new TextSyndicationContent("The copyright for each post is retained by its author."); + feed.Language = languageCode; + feed.LastUpdatedTime = lastUpdate; + + return feed; + } + } +} diff --git a/PlanetDotnet/Services/Feeds/IFeedService.cs b/PlanetDotnet/Services/Feeds/IFeedService.cs new file mode 100644 index 0000000..bdc8eb1 --- /dev/null +++ b/PlanetDotnet/Services/Feeds/IFeedService.cs @@ -0,0 +1,17 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.ServiceModel.Syndication; +using System.Threading.Tasks; + +namespace PlanetDotnet.Services.Feeds +{ + public interface IFeedService + { + ValueTask CombineFeedsAsync(); + ValueTask CombineFeedsAsync(); + } +} diff --git a/PlanetDotnet/Startup.cs b/PlanetDotnet/Startup.cs index 779de6d..eca6692 100644 --- a/PlanetDotnet/Startup.cs +++ b/PlanetDotnet/Startup.cs @@ -5,6 +5,13 @@ // --------------------------------------------------------------- using Microsoft.Azure.Functions.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using PlanetDotnet.Brokers.Authors; +using PlanetDotnet.Brokers.DateTimes; +using PlanetDotnet.Brokers.Feeds; +using PlanetDotnet.Brokers.Loggings; +using PlanetDotnet.Services.Feeds; [assembly: FunctionsStartup(typeof(PlanetDotnet.Startup))] namespace PlanetDotnet @@ -12,6 +19,13 @@ namespace PlanetDotnet public class Startup : FunctionsStartup { public override void Configure(IFunctionsHostBuilder builder) - { } + { + builder.Services.AddScoped>(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + } } } From 9ba3c0628e9cec40ec98fb43e39d47ecc526e60d Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Sun, 19 Nov 2023 01:38:32 +0100 Subject: [PATCH 10/28] BROKERS: SerializeFeed --- .../Serializations/ISerializationBroker.cs | 18 +++++++++ .../Serializations/SerializationBroker.cs | 38 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 PlanetDotnet/Brokers/Serializations/ISerializationBroker.cs create mode 100644 PlanetDotnet/Brokers/Serializations/SerializationBroker.cs diff --git a/PlanetDotnet/Brokers/Serializations/ISerializationBroker.cs b/PlanetDotnet/Brokers/Serializations/ISerializationBroker.cs new file mode 100644 index 0000000..815fcb4 --- /dev/null +++ b/PlanetDotnet/Brokers/Serializations/ISerializationBroker.cs @@ -0,0 +1,18 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.IO; +using System.ServiceModel.Syndication; +using System.Threading.Tasks; + +namespace PlanetDotnet.Brokers.Serializations +{ + public interface ISerializationBroker + { + ValueTask SerializeFeedAsync(SyndicationFeed feed); + ValueTask DeserializeFeedAsync(Stream feedStream); + } +} diff --git a/PlanetDotnet/Brokers/Serializations/SerializationBroker.cs b/PlanetDotnet/Brokers/Serializations/SerializationBroker.cs new file mode 100644 index 0000000..b03465e --- /dev/null +++ b/PlanetDotnet/Brokers/Serializations/SerializationBroker.cs @@ -0,0 +1,38 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.IO; +using System.ServiceModel.Syndication; +using System.Threading.Tasks; +using System.Xml; + +namespace PlanetDotnet.Brokers.Serializations +{ + internal class SerializationBroker : ISerializationBroker + { + public ValueTask DeserializeFeedAsync(Stream feedStream) + { + throw new System.NotImplementedException(); + } + + public async ValueTask SerializeFeedAsync(SyndicationFeed feed) + { + var memoryStream = new MemoryStream(); + using var xmlWriter = XmlWriter.Create(memoryStream, new XmlWriterSettings + { + Async = true + }); + + var rssFormatter = new Rss20FeedFormatter(feed); + rssFormatter.WriteTo(xmlWriter); + await xmlWriter.FlushAsync(); + + memoryStream.Seek(0, SeekOrigin.Begin); + + return memoryStream; + } + } +} From 9123a4ab99dd70d3ecc382aaf84b8dab12c4369c Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Sun, 19 Nov 2023 03:17:41 +0100 Subject: [PATCH 11/28] FUNCTIONS: Load Feeds --- .../Serializations/ISerializationBroker.cs | 2 +- .../Serializations/SerializationBroker.cs | 13 ++- .../Brokers/Storages/IStorageBroker.cs | 17 ++++ .../Brokers/Storages/StorageBroker.cs | 50 ++++++++++ PlanetDotnet/LoadFeedsFunction.cs | 83 +++-------------- PlanetDotnet/Services/Feeds/FeedService.cs | 91 +++++++++++++++++-- PlanetDotnet/Services/Feeds/IFeedService.cs | 5 +- PlanetDotnet/Startup.cs | 7 +- PlanetDotnet/host.json | 21 +++-- 9 files changed, 194 insertions(+), 95 deletions(-) create mode 100644 PlanetDotnet/Brokers/Storages/IStorageBroker.cs create mode 100644 PlanetDotnet/Brokers/Storages/StorageBroker.cs diff --git a/PlanetDotnet/Brokers/Serializations/ISerializationBroker.cs b/PlanetDotnet/Brokers/Serializations/ISerializationBroker.cs index 815fcb4..65a601d 100644 --- a/PlanetDotnet/Brokers/Serializations/ISerializationBroker.cs +++ b/PlanetDotnet/Brokers/Serializations/ISerializationBroker.cs @@ -13,6 +13,6 @@ namespace PlanetDotnet.Brokers.Serializations public interface ISerializationBroker { ValueTask SerializeFeedAsync(SyndicationFeed feed); - ValueTask DeserializeFeedAsync(Stream feedStream); + SyndicationFeed DeserializeFeed(Stream feedStream); } } diff --git a/PlanetDotnet/Brokers/Serializations/SerializationBroker.cs b/PlanetDotnet/Brokers/Serializations/SerializationBroker.cs index b03465e..6a4256a 100644 --- a/PlanetDotnet/Brokers/Serializations/SerializationBroker.cs +++ b/PlanetDotnet/Brokers/Serializations/SerializationBroker.cs @@ -4,6 +4,7 @@ // See License.txt in the project root for license information. // --------------------------------------------------------------- +using System; using System.IO; using System.ServiceModel.Syndication; using System.Threading.Tasks; @@ -13,9 +14,17 @@ namespace PlanetDotnet.Brokers.Serializations { internal class SerializationBroker : ISerializationBroker { - public ValueTask DeserializeFeedAsync(Stream feedStream) + public SyndicationFeed DeserializeFeed(Stream feedStream) { - throw new System.NotImplementedException(); + feedStream.Position = 0; + using var xmlReader = XmlReader.Create(feedStream, new XmlReaderSettings + { + Async = true + }); + + var feed = SyndicationFeed.Load(xmlReader); + + return feed; } public async ValueTask SerializeFeedAsync(SyndicationFeed feed) diff --git a/PlanetDotnet/Brokers/Storages/IStorageBroker.cs b/PlanetDotnet/Brokers/Storages/IStorageBroker.cs new file mode 100644 index 0000000..6294a48 --- /dev/null +++ b/PlanetDotnet/Brokers/Storages/IStorageBroker.cs @@ -0,0 +1,17 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.IO; +using System.Threading.Tasks; + +namespace PlanetDotnet.Brokers.Storages +{ + public interface IStorageBroker + { + ValueTask UploadBlobAsync(string language, Stream content); + ValueTask ReadBlobAsync(string language); + } +} diff --git a/PlanetDotnet/Brokers/Storages/StorageBroker.cs b/PlanetDotnet/Brokers/Storages/StorageBroker.cs new file mode 100644 index 0000000..b2bc85a --- /dev/null +++ b/PlanetDotnet/Brokers/Storages/StorageBroker.cs @@ -0,0 +1,50 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; +using System.IO; +using System.Threading.Tasks; +using Azure.Storage.Blobs; + +namespace PlanetDotnet.Brokers.Storages +{ + public class StorageBroker : IStorageBroker + { + private readonly BlobContainerClient blobContainerClient; + private const string BlobContainerName = "feeds"; + private const string FeedBlobStorageKey = "FeedBlobStorage"; + private const string BlobName = "newfeed.{0}.rss"; + + public StorageBroker() + { + var blobConnectString = Environment.GetEnvironmentVariable( + variable: FeedBlobStorageKey, + target: EnvironmentVariableTarget.Process); + + this.blobContainerClient = new BlobContainerClient( + connectionString: blobConnectString, + blobContainerName: BlobContainerName); + } + + public async ValueTask UploadBlobAsync(string language, Stream content) + { + var blobName = string.Format(BlobName, language); + + var blobClient = this.blobContainerClient.GetBlobClient(blobName); + + await blobClient.UploadAsync(content, overwrite: true); + } + + public async ValueTask ReadBlobAsync(string language) + { + var blobName = string.Format(BlobName, language); + var blobClient = this.blobContainerClient.GetBlobClient(blobName); + + var response = await blobClient.DownloadAsync(); + return response.Value.Content; + } + } +} diff --git a/PlanetDotnet/LoadFeedsFunction.cs b/PlanetDotnet/LoadFeedsFunction.cs index e919852..9abc778 100644 --- a/PlanetDotnet/LoadFeedsFunction.cs +++ b/PlanetDotnet/LoadFeedsFunction.cs @@ -5,96 +5,35 @@ // --------------------------------------------------------------- using System; -using System.IO; using System.Linq; -using System.ServiceModel.Syndication; using System.Threading.Tasks; -using System.Xml; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; using PlanetDotnet.Authors.Services; using PlanetDotnet.Infrastructure; +using PlanetDotnet.Services.Feeds; namespace PlanetDotnet { - public static class LoadFeedsFunction + public class LoadFeedsFunction { + private readonly IFeedService feedService; + + public LoadFeedsFunction(IFeedService feedService) => + this.feedService = feedService; + [FunctionName("LoadFeedsFunction")] - public static async Task Run( + public async Task Run( [TimerTrigger("0 0 */1 * * *", RunOnStartup = true)] TimerInfo myTimer, ILogger log) { - log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}"); - - var rssFeedTitle = GetEnvironmentVariable("RssFeedTitle"); - var rssFeedDescription = GetEnvironmentVariable("RssFeedDescription"); - var rssFeedUrl = GetEnvironmentVariable("RssFeedUrl"); - var rssFeedImageUrl = GetEnvironmentVariable("RssFeedImageUrl"); + log.LogInformation($"Load feeds Timer trigger function executed at: {DateTime.Now}"); - var authors = await AuthorService.GetAllAuthors(); - var languages = authors.Select(author => author.FeedLanguageCode).Distinct().ToList(); - languages.Add("mixed"); - var feedSource = - new CombinedFeedSource( - authors, - log, - rssFeedTitle, - rssFeedDescription, - rssFeedUrl, - rssFeedImageUrl); + await this.feedService.LoadFeedAsync(); - var blobConnectString = GetEnvironmentVariable("FeedBlobStorage"); - var container = new BlobContainerClient(blobConnectString, "feeds"); - await container.CreateIfNotExistsAsync(); - await container.SetAccessPolicyAsync(PublicAccessType.Blob); - - foreach (var language in languages) - { - try - { - log.LogInformation($"Loading {language} combined author feed"); - var feed = await feedSource.LoadFeed(null, language); - using var stream = await SerializeFeed(feed); - await UploadBlob(container, stream, language, log); - } - catch (Exception ex) - { - log.LogError(ex, ex.Message); - } - } - } - - private static async Task UploadBlob(BlobContainerClient container, Stream feedStream, string language, ILogger log) - { - var feedName = $"feed.{language}.rss"; - var blob = container.GetBlobClient(feedName); - await blob.UploadAsync(feedStream, overwrite: true); - - log.LogInformation($"Uploaded {feedName} to {blob.Uri}"); - } - - private static async Task SerializeFeed(SyndicationFeed feed) - { - var memoryStream = new MemoryStream(); - using var xmlWriter = XmlWriter.Create(memoryStream, new XmlWriterSettings - { - Async = true - }); - - var rssFormatter = new Rss20FeedFormatter(feed); - rssFormatter.WriteTo(xmlWriter); - await xmlWriter.FlushAsync(); - - memoryStream.Seek(0, SeekOrigin.Begin); - - return memoryStream; - } - - private static string GetEnvironmentVariable(string name) - { - return Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process); + log.LogInformation($"Load feeds Finished at: {DateTime.Now}"); } } } diff --git a/PlanetDotnet/Services/Feeds/FeedService.cs b/PlanetDotnet/Services/Feeds/FeedService.cs index 6086b99..b479100 100644 --- a/PlanetDotnet/Services/Feeds/FeedService.cs +++ b/PlanetDotnet/Services/Feeds/FeedService.cs @@ -6,12 +6,17 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; using System.ServiceModel.Syndication; using System.Threading.Tasks; +using PlanetDotnet.Authors.Models.Authors; using PlanetDotnet.Brokers.Authors; using PlanetDotnet.Brokers.DateTimes; using PlanetDotnet.Brokers.Feeds; using PlanetDotnet.Brokers.Loggings; +using PlanetDotnet.Brokers.Serializations; +using PlanetDotnet.Brokers.Storages; namespace PlanetDotnet.Services.Feeds { @@ -21,6 +26,8 @@ internal partial class FeedService : IFeedService private readonly IAuthorBroker authorBroker; private readonly IDateTimeBroker dateTimeBroker; private readonly ILoggingBroker loggingBroker; + private readonly ISerializationBroker serializationBroker; + private readonly IStorageBroker storageBroker; private const string RssFeedTitleKey = "RssFeedTitle"; private const string RssFeedDescriptionKey = "RssFeedDescription"; @@ -31,38 +38,95 @@ public FeedService( IFeedBroker feedBroker, IAuthorBroker authorBroker, IDateTimeBroker dateTimeBroker, - ILoggingBroker loggingBroker) + ILoggingBroker loggingBroker, + ISerializationBroker serializationBroker, + IStorageBroker storageBroker) { this.feedBroker = feedBroker; this.authorBroker = authorBroker; this.dateTimeBroker = dateTimeBroker; this.loggingBroker = loggingBroker; + this.serializationBroker = serializationBroker; + this.storageBroker = storageBroker; } - public async ValueTask CombineFeedsAsync() + public async ValueTask GetMixedFeedAsync() { var authors = await this.authorBroker.GetAllAuthorsAsync(); + return await GetCombinedFeedsAsync(authors, language: "mixed"); + } + + public async ValueTask LoadFeedAsync() + { + var authors = await this.authorBroker.GetAllAuthorsAsync(); + + this.loggingBroker.LogInformation($"Found {authors.Count()} author(s) globally..."); + + var languages = authors.Select(author => author.FeedLanguageCode).Distinct().ToList(); + + this.loggingBroker.LogInformation($"Available languages: {string.Join(",", languages)}."); + + foreach (var language in languages) + { + var languageAuthors = authors.Where(author => + author.FeedLanguageCode == language); + + this.loggingBroker.LogInformation($"{authors.Count()} author(s) are writing in '{language}'..."); + + var feed = await GetCombinedFeedsAsync(languageAuthors, language); + + var content = await this.serializationBroker.SerializeFeedAsync(feed); + + this.loggingBroker.LogInformation($"Blob for '{language}' is uploading..."); + + await this.storageBroker.UploadBlobAsync(language, content); + + this.loggingBroker.LogInformation($"Blob for '{language}' feed was uploaded successfully."); + } + } + + public async ValueTask RetrieveFeedAsync(string language) + { + var content = await this.storageBroker.ReadBlobAsync(language); + + return this.serializationBroker.DeserializeFeed(content); + } + + private async ValueTask GetCombinedFeedsAsync( + IEnumerable authors, + string language) + { var feedItems = new List(); foreach (var author in authors) { foreach (var feedUri in author.FeedUris) { - var feed = await this.feedBroker.ReadFeedAsync(feedUri.AbsoluteUri); - - feedItems.AddRange(feed.Items); + try + { + var feed = await this.feedBroker.ReadFeedAsync(feedUri.AbsoluteUri); + + feedItems.AddRange(feed.Items); + } + catch (Exception ex) + { + this.loggingBroker.LogError(ex); + } } } - return CreateCombinedFeed( + return CreateFeedInstance( items: feedItems, - lastUpdate: this.dateTimeBroker.GetCurrentDateTimeOffset()); + lastUpdate: this.dateTimeBroker.GetCurrentDateTimeOffset(), + constributers: authors, + languageCode: language); } - private static SyndicationFeed CreateCombinedFeed( + private static SyndicationFeed CreateFeedInstance( List items, DateTimeOffset lastUpdate, + IEnumerable constributers, string languageCode = "mixed") { var rssFeedTitle = Environment.GetEnvironmentVariable( @@ -92,7 +156,18 @@ private static SyndicationFeed CreateCombinedFeed( feed.Language = languageCode; feed.LastUpdatedTime = lastUpdate; + foreach (var constributer in constributers) + { + var item = new SyndicationPerson( + email: constributer.EmailAddress, + name: $"{constributer.FirstName} {constributer.LastName}", + uri: constributer.WebSite.ToString()); + + feed.Contributors.Add(item); + } + return feed; } + } } diff --git a/PlanetDotnet/Services/Feeds/IFeedService.cs b/PlanetDotnet/Services/Feeds/IFeedService.cs index bdc8eb1..df30fe1 100644 --- a/PlanetDotnet/Services/Feeds/IFeedService.cs +++ b/PlanetDotnet/Services/Feeds/IFeedService.cs @@ -11,7 +11,8 @@ namespace PlanetDotnet.Services.Feeds { public interface IFeedService { - ValueTask CombineFeedsAsync(); - ValueTask CombineFeedsAsync(); + ValueTask GetMixedFeedAsync(); + ValueTask LoadFeedAsync(); + ValueTask RetrieveFeedAsync(string language); } } diff --git a/PlanetDotnet/Startup.cs b/PlanetDotnet/Startup.cs index eca6692..4c615ee 100644 --- a/PlanetDotnet/Startup.cs +++ b/PlanetDotnet/Startup.cs @@ -11,6 +11,8 @@ using PlanetDotnet.Brokers.DateTimes; using PlanetDotnet.Brokers.Feeds; using PlanetDotnet.Brokers.Loggings; +using PlanetDotnet.Brokers.Serializations; +using PlanetDotnet.Brokers.Storages; using PlanetDotnet.Services.Feeds; [assembly: FunctionsStartup(typeof(PlanetDotnet.Startup))] @@ -20,9 +22,12 @@ public class Startup : FunctionsStartup { public override void Configure(IFunctionsHostBuilder builder) { - builder.Services.AddScoped>(); + builder.Services.AddLogging(); + builder.Services.AddScoped, Logger>(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/PlanetDotnet/host.json b/PlanetDotnet/host.json index ee5cf5f..24ef1a8 100644 --- a/PlanetDotnet/host.json +++ b/PlanetDotnet/host.json @@ -1,12 +1,15 @@ { - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - }, - "enableLiveMetricsFilters": true - } + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + }, + "enableLiveMetricsFilters": true } + }, + "logLevel": { + "PlanetDotnet.Brokers.Loggings.LoggingBroker": "Information" + } } \ No newline at end of file From db036a5f438f490cd26b4398165cb01cabacedcc Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 00:10:15 +0100 Subject: [PATCH 12/28] FOUNDATIONS: Combined Feed --- PlanetDotnet/Brokers/Feeds/FeedBroker.cs | 10 +- PlanetDotnet/Brokers/Feeds/IFeedBroker.cs | 2 +- .../Brokers/Loggings/ILoggingBroker.cs | 7 + .../Brokers/Loggings/LoggingBroker.cs | 9 + .../Brokers/Storages/IStorageBroker.cs | 1 + .../Brokers/Storages/StorageBroker.cs | 9 +- .../Extensions/ExceptionExtensions.cs | 23 ++ .../Infrastructure/CombinedFeedSource.cs | 219 ------------------ PlanetDotnet/LoadFeedsFunction.cs | 105 ++++++++- .../Feeds/Exceptions/FailedFeedException.cs | 21 ++ .../CombinedFeeds/CombinedFeedService.cs | 213 +++++++++++++++++ .../ICombinedFeedService.cs} | 8 +- PlanetDotnet/Services/Feeds/FeedService.cs | 173 -------------- PlanetDotnet/Startup.cs | 4 +- PlanetDotnet/host.json | 6 +- 15 files changed, 396 insertions(+), 414 deletions(-) create mode 100644 PlanetDotnet/Extensions/ExceptionExtensions.cs delete mode 100644 PlanetDotnet/Infrastructure/CombinedFeedSource.cs create mode 100644 PlanetDotnet/Models/Feeds/Exceptions/FailedFeedException.cs create mode 100644 PlanetDotnet/Services/CombinedFeeds/CombinedFeedService.cs rename PlanetDotnet/Services/{Feeds/IFeedService.cs => CombinedFeeds/ICombinedFeedService.cs} (61%) delete mode 100644 PlanetDotnet/Services/Feeds/FeedService.cs diff --git a/PlanetDotnet/Brokers/Feeds/FeedBroker.cs b/PlanetDotnet/Brokers/Feeds/FeedBroker.cs index d05fc13..94ad3c9 100644 --- a/PlanetDotnet/Brokers/Feeds/FeedBroker.cs +++ b/PlanetDotnet/Brokers/Feeds/FeedBroker.cs @@ -18,11 +18,17 @@ internal class FeedBroker : IFeedBroker public FeedBroker(HttpClient httpClient) => this.httpClient = httpClient; - public async ValueTask ReadFeedAsync(string feedUri) + public async Task ReadFeedAsync(string feedUri) { var response = await httpClient.GetAsync(feedUri).ConfigureAwait(false); using var feedStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - using var reader = XmlReader.Create(feedStream); + + var settings = new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Parse + }; + + using var reader = XmlReader.Create(feedStream, settings); var feed = SyndicationFeed.Load(reader); return feed; diff --git a/PlanetDotnet/Brokers/Feeds/IFeedBroker.cs b/PlanetDotnet/Brokers/Feeds/IFeedBroker.cs index aa48450..8da0272 100644 --- a/PlanetDotnet/Brokers/Feeds/IFeedBroker.cs +++ b/PlanetDotnet/Brokers/Feeds/IFeedBroker.cs @@ -11,6 +11,6 @@ namespace PlanetDotnet.Brokers.Feeds { public interface IFeedBroker { - ValueTask ReadFeedAsync(string feedUri); + Task ReadFeedAsync(string feedUri); } } diff --git a/PlanetDotnet/Brokers/Loggings/ILoggingBroker.cs b/PlanetDotnet/Brokers/Loggings/ILoggingBroker.cs index bb02432..485f6a2 100644 --- a/PlanetDotnet/Brokers/Loggings/ILoggingBroker.cs +++ b/PlanetDotnet/Brokers/Loggings/ILoggingBroker.cs @@ -1,3 +1,9 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + using System; namespace PlanetDotnet.Brokers.Loggings @@ -9,6 +15,7 @@ public interface ILoggingBroker void LogDebug(string message); void LogWarning(string message); void LogError(Exception exception); + void LogError(Exception exception, string message); void LogCritical(Exception exception); } } diff --git a/PlanetDotnet/Brokers/Loggings/LoggingBroker.cs b/PlanetDotnet/Brokers/Loggings/LoggingBroker.cs index d5a6695..5dd375e 100644 --- a/PlanetDotnet/Brokers/Loggings/LoggingBroker.cs +++ b/PlanetDotnet/Brokers/Loggings/LoggingBroker.cs @@ -1,3 +1,9 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + using System; using Microsoft.Extensions.Logging; @@ -25,6 +31,9 @@ public void LogWarning(string message) => public void LogError(Exception exception) => this.logger.LogError(exception.Message, exception); + public void LogError(Exception exception, string message) => + this.logger.LogError(exception, message); + public void LogCritical(Exception exception) => this.logger.LogCritical(exception, exception.Message); } diff --git a/PlanetDotnet/Brokers/Storages/IStorageBroker.cs b/PlanetDotnet/Brokers/Storages/IStorageBroker.cs index 6294a48..d52d8d4 100644 --- a/PlanetDotnet/Brokers/Storages/IStorageBroker.cs +++ b/PlanetDotnet/Brokers/Storages/IStorageBroker.cs @@ -11,6 +11,7 @@ namespace PlanetDotnet.Brokers.Storages { public interface IStorageBroker { + ValueTask InitializeAsync(); ValueTask UploadBlobAsync(string language, Stream content); ValueTask ReadBlobAsync(string language); } diff --git a/PlanetDotnet/Brokers/Storages/StorageBroker.cs b/PlanetDotnet/Brokers/Storages/StorageBroker.cs index b2bc85a..19424d9 100644 --- a/PlanetDotnet/Brokers/Storages/StorageBroker.cs +++ b/PlanetDotnet/Brokers/Storages/StorageBroker.cs @@ -8,6 +8,7 @@ using System.IO; using System.Threading.Tasks; using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Models; namespace PlanetDotnet.Brokers.Storages { @@ -16,7 +17,7 @@ public class StorageBroker : IStorageBroker private readonly BlobContainerClient blobContainerClient; private const string BlobContainerName = "feeds"; private const string FeedBlobStorageKey = "FeedBlobStorage"; - private const string BlobName = "newfeed.{0}.rss"; + private const string BlobName = "feed.{0}.rss"; public StorageBroker() { @@ -29,6 +30,12 @@ public StorageBroker() blobContainerName: BlobContainerName); } + public async ValueTask InitializeAsync() + { + await this.blobContainerClient.CreateIfNotExistsAsync(); + await this.blobContainerClient.SetAccessPolicyAsync(PublicAccessType.Blob); + } + public async ValueTask UploadBlobAsync(string language, Stream content) { var blobName = string.Format(BlobName, language); diff --git a/PlanetDotnet/Extensions/ExceptionExtensions.cs b/PlanetDotnet/Extensions/ExceptionExtensions.cs new file mode 100644 index 0000000..5ce0dbc --- /dev/null +++ b/PlanetDotnet/Extensions/ExceptionExtensions.cs @@ -0,0 +1,23 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; + +namespace PlanetDotnet.Extensions +{ + internal static class ExceptionExtensions + { + public static TException WithData( + this TException exception, + string key, + object value) + where TException : Exception + { + exception.Data[key] = value; + return exception; + } + } +} diff --git a/PlanetDotnet/Infrastructure/CombinedFeedSource.cs b/PlanetDotnet/Infrastructure/CombinedFeedSource.cs deleted file mode 100644 index 3b9d9e3..0000000 --- a/PlanetDotnet/Infrastructure/CombinedFeedSource.cs +++ /dev/null @@ -1,219 +0,0 @@ -// --------------------------------------------------------------- -// Copyright (c) 2023 Planet Dotnet. All rights reserved. -// Licensed under the MIT License. -// See License.txt in the project root for license information. -// --------------------------------------------------------------- - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.ServiceModel.Syndication; -using System.Threading.Tasks; -using System.Xml; -using Microsoft.Extensions.Logging; -using PlanetDotnet.Authors.Models.Authors; -using PlanetDotnet.Extensions; -using Polly; -using Polly.Retry; - -namespace PlanetDotnet.Infrastructure -{ - public class CombinedFeedSource - { - private static HttpClient _httpClient; - private static AsyncRetryPolicy _retryPolicy; - private readonly IEnumerable _authors; - private readonly ILogger _logger; - private readonly string _rssFeedTitle; - private readonly string _rssFeedDescription; - private readonly string _rssFeedUrl; - private readonly string _rssFeedImageUrl; - - public CombinedFeedSource( - IEnumerable authors, - ILogger logger, - string rssFeedTitle, - string rssFeedDescription, - string rssFeedUrl, - string rssFeedImageUrl) - { - EnsureHttpClient(); - - if (_retryPolicy == null) - { - // retry policy with max 2 retries, delay by x*x^1.2 where x is retry attempt - // this will ensure we don't retry too quickly - _retryPolicy = Policy.Handle() - .WaitAndRetryAsync(2, retry => TimeSpan.FromSeconds(retry * Math.Pow(1.2, retry))); - } - - _authors = authors; - _logger = logger; - _rssFeedTitle = rssFeedTitle; - _rssFeedDescription = rssFeedDescription; - _rssFeedUrl = rssFeedUrl; - _rssFeedImageUrl = rssFeedImageUrl; - } - - private void EnsureHttpClient() - { - if (_httpClient == null) - { - _httpClient = new HttpClient(); - _httpClient.DefaultRequestHeaders.UserAgent.Add( - new ProductInfoHeaderValue("PlanetDotnet", $"{GetType().Assembly.GetName().Version}")); - _httpClient.Timeout = TimeSpan.FromSeconds(15); - - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13; - } - } - - public async Task LoadFeed(int? numberOfItems, string languageCode = "mixed") - { - IEnumerable tamarins; - if (languageCode == null || languageCode == "mixed") // use all tamarins - { - tamarins = _authors; - } - else - { - tamarins = _authors.Where(t => t.FeedLanguageCode == languageCode); - } - - var feedTasks = tamarins.SelectMany(t => TryReadFeeds(t)).ToArray(); - - _logger?.LogInformation($"Loading feed for language: {languageCode} for {feedTasks.Length} authors"); - - var syndicationItems = await Task.WhenAll(feedTasks).ConfigureAwait(false); - var combinedFeed = GetCombinedFeed(syndicationItems.SelectMany(f => f), languageCode, tamarins, numberOfItems); - return combinedFeed; - } - - private IEnumerable>> TryReadFeeds(Author tamarin) - { - return tamarin.FeedUris.Select(uri => TryReadFeed(tamarin, uri.AbsoluteUri)); - } - - private async Task> TryReadFeed(Author tamarin, string feedUri) - { - try - { - return await _retryPolicy.ExecuteAsync(context => ReadFeed(feedUri), new Context(feedUri)).ConfigureAwait(false); - } - catch (FeedReadFailedException ex) - { - _logger.LogError(ex, $"{tamarin.FirstName} {tamarin.LastName}'s feed of {ex.Data["FeedUri"]} failed to load."); - } - - return new SyndicationItem[0]; - } - - private async Task> ReadFeed(string feedUri) - { - HttpResponseMessage response; - try - { - _logger?.LogInformation($"Loading feed {feedUri}"); - response = await _httpClient.GetAsync(feedUri).ConfigureAwait(false); - if (response.IsSuccessStatusCode) - { - using var feedStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - using var reader = XmlReader.Create(feedStream); - var feed = SyndicationFeed.Load(reader); - var filteredItems = feed.Items - .Where(item => item.ApplyDefaultFilter()); - - return filteredItems; - } - } - catch (HttpRequestException hex) - { - throw new FeedReadFailedException("Loading remote syndication feed failed", hex) - .WithData("FeedUri", feedUri); - } - catch (WebException ex) - { - throw new FeedReadFailedException("Loading remote syndication feed timed out", ex) - .WithData("FeedUri", feedUri); - } - catch (XmlException ex) - { - throw new FeedReadFailedException("Failed parsing remote syndication feed", ex) - .WithData("FeedUri", feedUri); - } - catch (TaskCanceledException ex) - { - throw new FeedReadFailedException("Reading feed timed out", ex) - .WithData("FeedUri", feedUri); - } - catch (OperationCanceledException opcex) - { - throw new FeedReadFailedException("Reading feed timed out", opcex) - .WithData("FeedUri", feedUri); - } - - throw new FeedReadFailedException("Loading remote syndication feed failed.") - .WithData("FeedUri", feedUri) - .WithData("HttpStatusCode", (int)response.StatusCode); - } - - private SyndicationFeed GetCombinedFeed(IEnumerable items, string languageCode, - IEnumerable tamarins, int? numberOfItems) - { - DateTimeOffset GetMaxTime(SyndicationItem item) - { - return new[] { item.PublishDate.UtcDateTime, item.LastUpdatedTime.UtcDateTime }.Max(); - } - - var orderedItems = items - .Where(item => - GetMaxTime(item) <= DateTimeOffset.UtcNow) - .OrderByDescending(item => GetMaxTime(item)); - - var feed = new SyndicationFeed( - _rssFeedTitle, - _rssFeedDescription, - new Uri(_rssFeedUrl), - numberOfItems.HasValue ? orderedItems.Take(numberOfItems.Value) : orderedItems) - { - ImageUrl = new Uri(_rssFeedImageUrl), - Copyright = new TextSyndicationContent("The copyright for each post is retained by its author."), - Language = languageCode, - LastUpdatedTime = DateTimeOffset.UtcNow - }; - - foreach (var tamarin in tamarins) - { - feed.Contributors.Add(new SyndicationPerson( - tamarin.EmailAddress, $"{tamarin.FirstName} {tamarin.LastName}", tamarin.WebSite.ToString())); - } - - return feed; - } - } - - public class FeedReadFailedException : Exception - { - public FeedReadFailedException(string message) - : base(message) - { - } - - public FeedReadFailedException(string message, Exception inner) - : base(message, inner) - { - } - } - - internal static class ExceptionExtensions - { - public static TException WithData(this TException exception, string key, object value) where TException : Exception - { - exception.Data[key] = value; - return exception; - } - } -} diff --git a/PlanetDotnet/LoadFeedsFunction.cs b/PlanetDotnet/LoadFeedsFunction.cs index 9abc778..3aab4ee 100644 --- a/PlanetDotnet/LoadFeedsFunction.cs +++ b/PlanetDotnet/LoadFeedsFunction.cs @@ -5,24 +5,35 @@ // --------------------------------------------------------------- using System; +using System.Globalization; +using System.IO; using System.Linq; +using System.ServiceModel.Syndication; using System.Threading.Tasks; -using Azure.Storage.Blobs; -using Azure.Storage.Blobs.Models; +using System.Xml; using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; -using PlanetDotnet.Authors.Services; -using PlanetDotnet.Infrastructure; -using PlanetDotnet.Services.Feeds; +using PlanetDotnet.Brokers.Authors; +using PlanetDotnet.Brokers.Storages; +using PlanetDotnet.Services.CombinedFeeds; namespace PlanetDotnet { public class LoadFeedsFunction { - private readonly IFeedService feedService; + private readonly ICombinedFeedService feedService; + private readonly IStorageBroker storageBroker; + private readonly IAuthorBroker authorBroker; - public LoadFeedsFunction(IFeedService feedService) => + public LoadFeedsFunction( + ICombinedFeedService feedService, + IStorageBroker storageBroker, + IAuthorBroker authorBroker) + { this.feedService = feedService; + this.storageBroker = storageBroker; + this.authorBroker = authorBroker; + } [FunctionName("LoadFeedsFunction")] public async Task Run( @@ -31,9 +42,87 @@ public async Task Run( { log.LogInformation($"Load feeds Timer trigger function executed at: {DateTime.Now}"); - await this.feedService.LoadFeedAsync(); + try + { + await this.storageBroker.InitializeAsync(); + + var authors = await this.authorBroker.GetAllAuthorsAsync(); + + var languages = authors.Select(author => author.FeedLanguageCode).Distinct().ToList(); + + var mainCulture = CultureInfo.CurrentCulture; + foreach (var language in languages) + { + try + { + CultureInfo.CurrentCulture = new CultureInfo(language); + log.LogInformation($"Loading {language} combined author feed"); + var feed = await feedService.LoadFeed(null, language); + using var stream = await SerializeFeed(feed); + await this.storageBroker.UploadBlobAsync(language, stream); + } + catch (Exception ex) + { + log.LogError(ex, "error"); + } + } + + CultureInfo.CurrentCulture = mainCulture; + } + catch (Exception ex) + { + log.LogError(ex, "error"); + } log.LogInformation($"Load feeds Finished at: {DateTime.Now}"); } + + private static async Task SerializeFeed(SyndicationFeed feed) + { + FixInvalidLastUpdatedTime(feed); + + var memoryStream = new MemoryStream(); + using var xmlWriter = XmlWriter.Create(memoryStream, new XmlWriterSettings + { + Async = true + }); + + var rssFormatter = new Rss20FeedFormatter(feed); + rssFormatter.WriteTo(xmlWriter); + await xmlWriter.FlushAsync(); + + memoryStream.Seek(0, SeekOrigin.Begin); + + return memoryStream; + } + + private static void FixInvalidLastUpdatedTime(SyndicationFeed feed) + { + foreach (var item in feed.Items) + { + // Check if both PublishDate and LastUpdatedTime are invalid + if (item.PublishDate == DateTimeOffset.MinValue && item.LastUpdatedTime == DateTimeOffset.MinValue) + { + item.PublishDate = DateTimeOffset.UtcNow; + item.LastUpdatedTime = DateTimeOffset.UtcNow; + } + else + { + // If only PublishDate is invalid, set it to LastUpdatedTime + if (item.PublishDate == DateTimeOffset.MinValue) + { + item.PublishDate = item.LastUpdatedTime; + } + + // If only LastUpdatedTime is invalid, set it to PublishDate + if (item.LastUpdatedTime == DateTimeOffset.MinValue) + { + item.LastUpdatedTime = item.PublishDate; + } + } + } + } + } + } diff --git a/PlanetDotnet/Models/Feeds/Exceptions/FailedFeedException.cs b/PlanetDotnet/Models/Feeds/Exceptions/FailedFeedException.cs new file mode 100644 index 0000000..0bf9409 --- /dev/null +++ b/PlanetDotnet/Models/Feeds/Exceptions/FailedFeedException.cs @@ -0,0 +1,21 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; + +namespace PlanetDotnet.Models.Feeds.Exceptions +{ + public class FailedFeedException : Exception + { + public FailedFeedException(string message) + : base(message) + { } + + public FailedFeedException(string message, Exception inner) + : base(message, inner) + { } + } +} diff --git a/PlanetDotnet/Services/CombinedFeeds/CombinedFeedService.cs b/PlanetDotnet/Services/CombinedFeeds/CombinedFeedService.cs new file mode 100644 index 0000000..f7789e0 --- /dev/null +++ b/PlanetDotnet/Services/CombinedFeeds/CombinedFeedService.cs @@ -0,0 +1,213 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.ServiceModel.Syndication; +using System.Threading.Tasks; +using System.Xml; +using PlanetDotnet.Authors.Models.Authors; +using PlanetDotnet.Brokers.Authors; +using PlanetDotnet.Brokers.Loggings; +using PlanetDotnet.Extensions; +using PlanetDotnet.Models.Feeds.Exceptions; +using Polly; +using Polly.Retry; + +namespace PlanetDotnet.Services.CombinedFeeds +{ + public class CombinedFeedService : ICombinedFeedService + { + private readonly HttpClient httpClient; + private readonly AsyncRetryPolicy _retryPolicy; + private readonly IAuthorBroker authorBroker; + private readonly ILoggingBroker loggingBroker; + + private const string RssFeedTitleKey = "RssFeedTitle"; + private const string RssFeedDescriptionKey = "RssFeedDescription"; + private const string RssFeedUrlKey = "RssFeedUrl"; + private const string RssFeedImageUrlKey = "RssFeedImageUrl"; + + public CombinedFeedService( + IAuthorBroker authorBroker, + ILoggingBroker loggingBroker, + HttpClient httpClient) + { + this.httpClient = httpClient; + EnsureHttpClient(); + + _retryPolicy ??= Policy.Handle() + .WaitAndRetryAsync(2, retry => TimeSpan.FromSeconds(retry * Math.Pow(1.2, retry))); + + this.authorBroker = authorBroker; + this.loggingBroker = loggingBroker; + } + + private void EnsureHttpClient() + { + this.httpClient.DefaultRequestHeaders.UserAgent.Add( + new ProductInfoHeaderValue("PlanetDotnet", $"{GetType().Assembly.GetName().Version}")); + + this.httpClient.Timeout = TimeSpan.FromSeconds(15); + + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13; + } + + public async Task LoadFeed(int? numberOfItems, string languageCode = "mixed") + { + var authors = await this.authorBroker.GetAllAuthorsAsync(); + + IEnumerable languageAuthors; + if (languageCode == null || languageCode == "mixed") // use all tamarins + { + languageAuthors = authors; + } + else + { + languageAuthors = authors.Where(t => t.FeedLanguageCode == languageCode); + } + + var feedTasks = languageAuthors.SelectMany(t => TryReadFeeds(t)).ToArray(); + + this.loggingBroker.LogInformation($"Loading feed for language: {languageCode} for {feedTasks.Length} authors"); + + var syndicationItems = await Task.WhenAll(feedTasks).ConfigureAwait(false); + var combinedFeed = GetCombinedFeed(syndicationItems.SelectMany(f => f), languageCode, languageAuthors, numberOfItems); + return combinedFeed; + } + + private IEnumerable>> TryReadFeeds(Author tamarin) + { + return tamarin.FeedUris.Select(uri => TryReadFeed(tamarin, uri.AbsoluteUri)); + } + + private async Task> TryReadFeed(Author tamarin, string feedUri) + { + try + { + return await _retryPolicy.ExecuteAsync(context => ReadFeed(feedUri), new Context(feedUri)).ConfigureAwait(false); + } + catch (FailedFeedException ex) + { + this.loggingBroker.LogError(ex, $"{tamarin.FirstName} {tamarin.LastName}'s feed of {ex.Data["FeedUri"]} failed to load."); + } + + return Array.Empty(); + } + + private async Task> ReadFeed(string feedUri) + { + HttpResponseMessage response; + try + { + this.loggingBroker.LogInformation($"Loading feed {feedUri}"); + + response = await httpClient.GetAsync(feedUri).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + using var feedStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + using var reader = XmlReader.Create(feedStream); + var feed = SyndicationFeed.Load(reader); + var filteredItems = feed.Items; + + return filteredItems; + } + } + catch (HttpRequestException hex) + { + throw new FailedFeedException("Loading remote syndication feed failed", hex) + .WithData("FeedUri", feedUri); + } + catch (WebException ex) + { + throw new FailedFeedException("Loading remote syndication feed timed out", ex) + .WithData("FeedUri", feedUri); + } + catch (XmlException ex) + { + throw new FailedFeedException("Failed parsing remote syndication feed", ex) + .WithData("FeedUri", feedUri); + } + catch (TaskCanceledException ex) + { + throw new FailedFeedException("Reading feed timed out", ex) + .WithData("FeedUri", feedUri); + } + catch (OperationCanceledException opcex) + { + throw new FailedFeedException("Reading feed timed out", opcex) + .WithData("FeedUri", feedUri); + } + + throw new FailedFeedException("Loading remote syndication feed failed.") + .WithData("FeedUri", feedUri) + .WithData("HttpStatusCode", (int)response.StatusCode); + } + + private SyndicationFeed GetCombinedFeed(IEnumerable items, string languageCode, + IEnumerable authors, int? numberOfItems) + { + + var rssFeedTitle = Environment.GetEnvironmentVariable( + variable: RssFeedTitleKey, + target: EnvironmentVariableTarget.Process); + + var rssFeedDescription = Environment.GetEnvironmentVariable( + variable: RssFeedDescriptionKey, + target: EnvironmentVariableTarget.Process); + + var rssFeedUrl = Environment.GetEnvironmentVariable( + variable: RssFeedUrlKey, + target: EnvironmentVariableTarget.Process); + + var rssFeedImageUrl = Environment.GetEnvironmentVariable( + variable: RssFeedImageUrlKey, + target: EnvironmentVariableTarget.Process); + + var orderedItems = items + .Where(item => + GetMaxTime(item) <= DateTimeOffset.UtcNow) + .OrderByDescending(item => GetMaxTime(item)); + + var feed = new SyndicationFeed( + rssFeedTitle, + rssFeedDescription, + new Uri(rssFeedUrl), + numberOfItems.HasValue ? orderedItems.Take(numberOfItems.Value) : orderedItems) + { + ImageUrl = new Uri(rssFeedImageUrl), + Copyright = new TextSyndicationContent("The copyright for each post is retained by its author."), + Language = languageCode, + LastUpdatedTime = DateTimeOffset.UtcNow + }; + + foreach (var author in authors) + { + feed.Contributors.Add(new SyndicationPerson( + author.EmailAddress, $"{author.FirstName} {author.LastName}", author.WebSite.ToString())); + } + + return feed; + } + + private static DateTimeOffset GetMaxTime(SyndicationItem item) + { + try + { + return new[] { item.PublishDate.UtcDateTime, item.LastUpdatedTime.UtcDateTime }.Max(); + } + catch + { + return item.PublishDate.UtcDateTime; + } + } + } +} \ No newline at end of file diff --git a/PlanetDotnet/Services/Feeds/IFeedService.cs b/PlanetDotnet/Services/CombinedFeeds/ICombinedFeedService.cs similarity index 61% rename from PlanetDotnet/Services/Feeds/IFeedService.cs rename to PlanetDotnet/Services/CombinedFeeds/ICombinedFeedService.cs index df30fe1..9d04163 100644 --- a/PlanetDotnet/Services/Feeds/IFeedService.cs +++ b/PlanetDotnet/Services/CombinedFeeds/ICombinedFeedService.cs @@ -7,12 +7,10 @@ using System.ServiceModel.Syndication; using System.Threading.Tasks; -namespace PlanetDotnet.Services.Feeds +namespace PlanetDotnet.Services.CombinedFeeds { - public interface IFeedService + public interface ICombinedFeedService { - ValueTask GetMixedFeedAsync(); - ValueTask LoadFeedAsync(); - ValueTask RetrieveFeedAsync(string language); + Task LoadFeed(int? numberOfItems, string languageCode = "mixed"); } } diff --git a/PlanetDotnet/Services/Feeds/FeedService.cs b/PlanetDotnet/Services/Feeds/FeedService.cs deleted file mode 100644 index b479100..0000000 --- a/PlanetDotnet/Services/Feeds/FeedService.cs +++ /dev/null @@ -1,173 +0,0 @@ -// --------------------------------------------------------------- -// Copyright (c) 2023 Planet Dotnet. All rights reserved. -// Licensed under the MIT License. -// See License.txt in the project root for license information. -// --------------------------------------------------------------- - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.ServiceModel.Syndication; -using System.Threading.Tasks; -using PlanetDotnet.Authors.Models.Authors; -using PlanetDotnet.Brokers.Authors; -using PlanetDotnet.Brokers.DateTimes; -using PlanetDotnet.Brokers.Feeds; -using PlanetDotnet.Brokers.Loggings; -using PlanetDotnet.Brokers.Serializations; -using PlanetDotnet.Brokers.Storages; - -namespace PlanetDotnet.Services.Feeds -{ - internal partial class FeedService : IFeedService - { - private readonly IFeedBroker feedBroker; - private readonly IAuthorBroker authorBroker; - private readonly IDateTimeBroker dateTimeBroker; - private readonly ILoggingBroker loggingBroker; - private readonly ISerializationBroker serializationBroker; - private readonly IStorageBroker storageBroker; - - private const string RssFeedTitleKey = "RssFeedTitle"; - private const string RssFeedDescriptionKey = "RssFeedDescription"; - private const string RssFeedUrlKey = "RssFeedUrl"; - private const string RssFeedImageUrlKey = "RssFeedImageUrl"; - - public FeedService( - IFeedBroker feedBroker, - IAuthorBroker authorBroker, - IDateTimeBroker dateTimeBroker, - ILoggingBroker loggingBroker, - ISerializationBroker serializationBroker, - IStorageBroker storageBroker) - { - this.feedBroker = feedBroker; - this.authorBroker = authorBroker; - this.dateTimeBroker = dateTimeBroker; - this.loggingBroker = loggingBroker; - this.serializationBroker = serializationBroker; - this.storageBroker = storageBroker; - } - - public async ValueTask GetMixedFeedAsync() - { - var authors = await this.authorBroker.GetAllAuthorsAsync(); - - return await GetCombinedFeedsAsync(authors, language: "mixed"); - } - - public async ValueTask LoadFeedAsync() - { - var authors = await this.authorBroker.GetAllAuthorsAsync(); - - this.loggingBroker.LogInformation($"Found {authors.Count()} author(s) globally..."); - - var languages = authors.Select(author => author.FeedLanguageCode).Distinct().ToList(); - - this.loggingBroker.LogInformation($"Available languages: {string.Join(",", languages)}."); - - foreach (var language in languages) - { - var languageAuthors = authors.Where(author => - author.FeedLanguageCode == language); - - this.loggingBroker.LogInformation($"{authors.Count()} author(s) are writing in '{language}'..."); - - var feed = await GetCombinedFeedsAsync(languageAuthors, language); - - var content = await this.serializationBroker.SerializeFeedAsync(feed); - - this.loggingBroker.LogInformation($"Blob for '{language}' is uploading..."); - - await this.storageBroker.UploadBlobAsync(language, content); - - this.loggingBroker.LogInformation($"Blob for '{language}' feed was uploaded successfully."); - } - } - - public async ValueTask RetrieveFeedAsync(string language) - { - var content = await this.storageBroker.ReadBlobAsync(language); - - return this.serializationBroker.DeserializeFeed(content); - } - - private async ValueTask GetCombinedFeedsAsync( - IEnumerable authors, - string language) - { - var feedItems = new List(); - - foreach (var author in authors) - { - foreach (var feedUri in author.FeedUris) - { - try - { - var feed = await this.feedBroker.ReadFeedAsync(feedUri.AbsoluteUri); - - feedItems.AddRange(feed.Items); - } - catch (Exception ex) - { - this.loggingBroker.LogError(ex); - } - } - } - - return CreateFeedInstance( - items: feedItems, - lastUpdate: this.dateTimeBroker.GetCurrentDateTimeOffset(), - constributers: authors, - languageCode: language); - } - - private static SyndicationFeed CreateFeedInstance( - List items, - DateTimeOffset lastUpdate, - IEnumerable constributers, - string languageCode = "mixed") - { - var rssFeedTitle = Environment.GetEnvironmentVariable( - variable: RssFeedTitleKey, - target: EnvironmentVariableTarget.Process); - - var rssFeedDescription = Environment.GetEnvironmentVariable( - variable: RssFeedDescriptionKey, - target: EnvironmentVariableTarget.Process); - - var rssFeedUrl = Environment.GetEnvironmentVariable( - variable: RssFeedUrlKey, - target: EnvironmentVariableTarget.Process); - - var rssFeedImageUrl = Environment.GetEnvironmentVariable( - variable: RssFeedImageUrlKey, - target: EnvironmentVariableTarget.Process); - - var feed = new SyndicationFeed( - title: rssFeedTitle, - description: rssFeedDescription, - feedAlternateLink: new Uri(rssFeedUrl), - items: items); - - feed.ImageUrl = new Uri(rssFeedImageUrl); - feed.Copyright = new TextSyndicationContent("The copyright for each post is retained by its author."); - feed.Language = languageCode; - feed.LastUpdatedTime = lastUpdate; - - foreach (var constributer in constributers) - { - var item = new SyndicationPerson( - email: constributer.EmailAddress, - name: $"{constributer.FirstName} {constributer.LastName}", - uri: constributer.WebSite.ToString()); - - feed.Contributors.Add(item); - } - - return feed; - } - - } -} diff --git a/PlanetDotnet/Startup.cs b/PlanetDotnet/Startup.cs index 4c615ee..bd1715d 100644 --- a/PlanetDotnet/Startup.cs +++ b/PlanetDotnet/Startup.cs @@ -13,7 +13,7 @@ using PlanetDotnet.Brokers.Loggings; using PlanetDotnet.Brokers.Serializations; using PlanetDotnet.Brokers.Storages; -using PlanetDotnet.Services.Feeds; +using PlanetDotnet.Services.CombinedFeeds; [assembly: FunctionsStartup(typeof(PlanetDotnet.Startup))] namespace PlanetDotnet @@ -30,7 +30,7 @@ public override void Configure(IFunctionsHostBuilder builder) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - builder.Services.AddScoped(); + builder.Services.AddScoped(); } } } diff --git a/PlanetDotnet/host.json b/PlanetDotnet/host.json index 24ef1a8..fbe7613 100644 --- a/PlanetDotnet/host.json +++ b/PlanetDotnet/host.json @@ -7,9 +7,9 @@ "excludedTypes": "Request" }, "enableLiveMetricsFilters": true + }, + "logLevel": { + "PlanetDotnet.Brokers.Loggings.LoggingBroker": "Information" } - }, - "logLevel": { - "PlanetDotnet.Brokers.Loggings.LoggingBroker": "Information" } } \ No newline at end of file From 43f73e129064060ae7f82ab08f61526effc29003 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 01:10:45 +0100 Subject: [PATCH 13/28] BROKERS: Serialize Feed --- .../Serializations/SerializationBroker.cs | 42 ++++++++- PlanetDotnet/LoadFeedsFunction.cs | 89 ++++++++++++------- .../Feeds}/CombinedFeedService.cs | 53 ++++++----- .../Feeds}/ICombinedFeedService.cs | 2 +- PlanetDotnet/Startup.cs | 2 +- 5 files changed, 120 insertions(+), 68 deletions(-) rename PlanetDotnet/Services/{CombinedFeeds => Foundations/Feeds}/CombinedFeedService.cs (84%) rename PlanetDotnet/Services/{CombinedFeeds => Foundations/Feeds}/ICombinedFeedService.cs (91%) diff --git a/PlanetDotnet/Brokers/Serializations/SerializationBroker.cs b/PlanetDotnet/Brokers/Serializations/SerializationBroker.cs index 6a4256a..5657951 100644 --- a/PlanetDotnet/Brokers/Serializations/SerializationBroker.cs +++ b/PlanetDotnet/Brokers/Serializations/SerializationBroker.cs @@ -6,6 +6,7 @@ using System; using System.IO; +using System.Linq; using System.ServiceModel.Syndication; using System.Threading.Tasks; using System.Xml; @@ -32,13 +33,46 @@ public async ValueTask SerializeFeedAsync(SyndicationFeed feed) var memoryStream = new MemoryStream(); using var xmlWriter = XmlWriter.Create(memoryStream, new XmlWriterSettings { - Async = true + Async = true, + Indent = true }); - var rssFormatter = new Rss20FeedFormatter(feed); - rssFormatter.WriteTo(xmlWriter); - await xmlWriter.FlushAsync(); + xmlWriter.WriteStartDocument(); + xmlWriter.WriteStartElement("rss"); + xmlWriter.WriteAttributeString("version", "2.0"); + xmlWriter.WriteStartElement("channel"); + xmlWriter.WriteElementString("title", feed.Title?.Text ?? string.Empty); + xmlWriter.WriteElementString("link", feed.Links.FirstOrDefault()?.Uri.AbsoluteUri ?? string.Empty); + xmlWriter.WriteElementString("description", feed.Description?.Text ?? string.Empty); + + if (feed.Language != null) + xmlWriter.WriteElementString("language", feed.Language); + + if (feed.LastUpdatedTime != DateTimeOffset.MinValue) + xmlWriter.WriteElementString("lastBuildDate", feed.LastUpdatedTime.ToString("R")); + + // Write items + foreach (var item in feed.Items) + { + xmlWriter.WriteStartElement("item"); + xmlWriter.WriteElementString("title", item.Title?.Text ?? string.Empty); + xmlWriter.WriteElementString("link", item.Links.FirstOrDefault()?.Uri.AbsoluteUri ?? string.Empty); + xmlWriter.WriteElementString("description", item.Summary?.Text ?? string.Empty); + + if (item.Id != null) + xmlWriter.WriteElementString("guid", item.Id); + + if (item.PublishDate != DateTimeOffset.MinValue) + xmlWriter.WriteElementString("pubDate", item.PublishDate.ToString("R")); + + xmlWriter.WriteEndElement(); // item + } + + xmlWriter.WriteEndElement(); // channel + xmlWriter.WriteEndElement(); // rss + + await xmlWriter.FlushAsync(); memoryStream.Seek(0, SeekOrigin.Begin); return memoryStream; diff --git a/PlanetDotnet/LoadFeedsFunction.cs b/PlanetDotnet/LoadFeedsFunction.cs index 3aab4ee..bafc578 100644 --- a/PlanetDotnet/LoadFeedsFunction.cs +++ b/PlanetDotnet/LoadFeedsFunction.cs @@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging; using PlanetDotnet.Brokers.Authors; using PlanetDotnet.Brokers.Storages; -using PlanetDotnet.Services.CombinedFeeds; +using PlanetDotnet.Services.Foundations.Feeds; namespace PlanetDotnet { @@ -79,47 +79,68 @@ public async Task Run( private static async Task SerializeFeed(SyndicationFeed feed) { - FixInvalidLastUpdatedTime(feed); - - var memoryStream = new MemoryStream(); - using var xmlWriter = XmlWriter.Create(memoryStream, new XmlWriterSettings + try { - Async = true - }); - var rssFormatter = new Rss20FeedFormatter(feed); - rssFormatter.WriteTo(xmlWriter); - await xmlWriter.FlushAsync(); + var memoryStream = new MemoryStream(); + using var xmlWriter = XmlWriter.Create(memoryStream, new XmlWriterSettings + { + Async = true, + Indent = true // Makes the output XML easier to read, optional + }); - memoryStream.Seek(0, SeekOrigin.Begin); + xmlWriter.WriteStartDocument(); + xmlWriter.WriteStartElement("rss"); + xmlWriter.WriteAttributeString("version", "2.0"); - return memoryStream; - } + xmlWriter.WriteStartElement("channel"); - private static void FixInvalidLastUpdatedTime(SyndicationFeed feed) - { - foreach (var item in feed.Items) - { - // Check if both PublishDate and LastUpdatedTime are invalid - if (item.PublishDate == DateTimeOffset.MinValue && item.LastUpdatedTime == DateTimeOffset.MinValue) - { - item.PublishDate = DateTimeOffset.UtcNow; - item.LastUpdatedTime = DateTimeOffset.UtcNow; - } - else + // Write channel elements + xmlWriter.WriteElementString("title", feed.Title?.Text ?? string.Empty); + xmlWriter.WriteElementString("link", feed.Links.FirstOrDefault()?.Uri.AbsoluteUri ?? string.Empty); + xmlWriter.WriteElementString("description", feed.Description?.Text ?? string.Empty); + + // Optional channel elements (add as needed) + if (feed.Language != null) + xmlWriter.WriteElementString("language", feed.Language); + + if (feed.LastUpdatedTime != DateTimeOffset.MinValue) + xmlWriter.WriteElementString("lastBuildDate", feed.LastUpdatedTime.ToString("R")); // RFC-822 format + + // More optional elements like image, categories, etc. can be added here + + // Write items + foreach (var item in feed.Items) { - // If only PublishDate is invalid, set it to LastUpdatedTime - if (item.PublishDate == DateTimeOffset.MinValue) - { - item.PublishDate = item.LastUpdatedTime; - } + xmlWriter.WriteStartElement("item"); - // If only LastUpdatedTime is invalid, set it to PublishDate - if (item.LastUpdatedTime == DateTimeOffset.MinValue) - { - item.LastUpdatedTime = item.PublishDate; - } + xmlWriter.WriteElementString("title", item.Title?.Text ?? string.Empty); + xmlWriter.WriteElementString("link", item.Links.FirstOrDefault()?.Uri.AbsoluteUri ?? string.Empty); + xmlWriter.WriteElementString("description", item.Summary?.Text ?? string.Empty); + + // Other optional elements for each item (guid, pubDate, etc.) + if (item.Id != null) + xmlWriter.WriteElementString("guid", item.Id); + + if (item.PublishDate != DateTimeOffset.MinValue) + xmlWriter.WriteElementString("pubDate", item.PublishDate.ToString("R")); // RFC-822 format + + // Additional elements for each item can be added here + + xmlWriter.WriteEndElement(); // item } + + xmlWriter.WriteEndElement(); // channel + xmlWriter.WriteEndElement(); // rss + + await xmlWriter.FlushAsync(); + memoryStream.Seek(0, SeekOrigin.Begin); + + return memoryStream; + } + catch (Exception ex) + { + throw; } } diff --git a/PlanetDotnet/Services/CombinedFeeds/CombinedFeedService.cs b/PlanetDotnet/Services/Foundations/Feeds/CombinedFeedService.cs similarity index 84% rename from PlanetDotnet/Services/CombinedFeeds/CombinedFeedService.cs rename to PlanetDotnet/Services/Foundations/Feeds/CombinedFeedService.cs index f7789e0..d53faae 100644 --- a/PlanetDotnet/Services/CombinedFeeds/CombinedFeedService.cs +++ b/PlanetDotnet/Services/Foundations/Feeds/CombinedFeedService.cs @@ -15,13 +15,14 @@ using System.Xml; using PlanetDotnet.Authors.Models.Authors; using PlanetDotnet.Brokers.Authors; +using PlanetDotnet.Brokers.Feeds; using PlanetDotnet.Brokers.Loggings; using PlanetDotnet.Extensions; using PlanetDotnet.Models.Feeds.Exceptions; using Polly; using Polly.Retry; -namespace PlanetDotnet.Services.CombinedFeeds +namespace PlanetDotnet.Services.Foundations.Feeds { public class CombinedFeedService : ICombinedFeedService { @@ -29,6 +30,7 @@ public class CombinedFeedService : ICombinedFeedService private readonly AsyncRetryPolicy _retryPolicy; private readonly IAuthorBroker authorBroker; private readonly ILoggingBroker loggingBroker; + private readonly IFeedBroker feedBroker; private const string RssFeedTitleKey = "RssFeedTitle"; private const string RssFeedDescriptionKey = "RssFeedDescription"; @@ -38,6 +40,7 @@ public class CombinedFeedService : ICombinedFeedService public CombinedFeedService( IAuthorBroker authorBroker, ILoggingBroker loggingBroker, + IFeedBroker feedBroker, HttpClient httpClient) { this.httpClient = httpClient; @@ -48,23 +51,15 @@ public CombinedFeedService( this.authorBroker = authorBroker; this.loggingBroker = loggingBroker; - } - - private void EnsureHttpClient() - { - this.httpClient.DefaultRequestHeaders.UserAgent.Add( - new ProductInfoHeaderValue("PlanetDotnet", $"{GetType().Assembly.GetName().Version}")); - - this.httpClient.Timeout = TimeSpan.FromSeconds(15); - - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13; + this.feedBroker = feedBroker; } public async Task LoadFeed(int? numberOfItems, string languageCode = "mixed") { - var authors = await this.authorBroker.GetAllAuthorsAsync(); + var authors = await authorBroker.GetAllAuthorsAsync(); IEnumerable languageAuthors; + if (languageCode == null || languageCode == "mixed") // use all tamarins { languageAuthors = authors; @@ -76,10 +71,12 @@ public async Task LoadFeed(int? numberOfItems, string languageC var feedTasks = languageAuthors.SelectMany(t => TryReadFeeds(t)).ToArray(); - this.loggingBroker.LogInformation($"Loading feed for language: {languageCode} for {feedTasks.Length} authors"); + loggingBroker.LogInformation($"Loading feed for language: {languageCode} for {feedTasks.Length} authors"); var syndicationItems = await Task.WhenAll(feedTasks).ConfigureAwait(false); + var combinedFeed = GetCombinedFeed(syndicationItems.SelectMany(f => f), languageCode, languageAuthors, numberOfItems); + return combinedFeed; } @@ -96,7 +93,7 @@ private async Task> TryReadFeed(Author tamarin, str } catch (FailedFeedException ex) { - this.loggingBroker.LogError(ex, $"{tamarin.FirstName} {tamarin.LastName}'s feed of {ex.Data["FeedUri"]} failed to load."); + loggingBroker.LogError(ex, $"{tamarin.FirstName} {tamarin.LastName}'s feed of {ex.Data["FeedUri"]} failed to load."); } return Array.Empty(); @@ -104,22 +101,13 @@ private async Task> TryReadFeed(Author tamarin, str private async Task> ReadFeed(string feedUri) { - HttpResponseMessage response; try { - this.loggingBroker.LogInformation($"Loading feed {feedUri}"); - - response = await httpClient.GetAsync(feedUri).ConfigureAwait(false); + loggingBroker.LogInformation($"Loading feed {feedUri}"); - if (response.IsSuccessStatusCode) - { - using var feedStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - using var reader = XmlReader.Create(feedStream); - var feed = SyndicationFeed.Load(reader); - var filteredItems = feed.Items; + var feed = await feedBroker.ReadFeedAsync(feedUri); - return filteredItems; - } + return feed.Items; } catch (HttpRequestException hex) { @@ -148,8 +136,7 @@ private async Task> ReadFeed(string feedUri) } throw new FailedFeedException("Loading remote syndication feed failed.") - .WithData("FeedUri", feedUri) - .WithData("HttpStatusCode", (int)response.StatusCode); + .WithData("FeedUri", feedUri); } private SyndicationFeed GetCombinedFeed(IEnumerable items, string languageCode, @@ -209,5 +196,15 @@ private static DateTimeOffset GetMaxTime(SyndicationItem item) return item.PublishDate.UtcDateTime; } } + + private void EnsureHttpClient() + { + httpClient.DefaultRequestHeaders.UserAgent.Add( + new ProductInfoHeaderValue("PlanetDotnet", $"{GetType().Assembly.GetName().Version}")); + + httpClient.Timeout = TimeSpan.FromSeconds(15); + + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13; + } } } \ No newline at end of file diff --git a/PlanetDotnet/Services/CombinedFeeds/ICombinedFeedService.cs b/PlanetDotnet/Services/Foundations/Feeds/ICombinedFeedService.cs similarity index 91% rename from PlanetDotnet/Services/CombinedFeeds/ICombinedFeedService.cs rename to PlanetDotnet/Services/Foundations/Feeds/ICombinedFeedService.cs index 9d04163..03bd1a9 100644 --- a/PlanetDotnet/Services/CombinedFeeds/ICombinedFeedService.cs +++ b/PlanetDotnet/Services/Foundations/Feeds/ICombinedFeedService.cs @@ -7,7 +7,7 @@ using System.ServiceModel.Syndication; using System.Threading.Tasks; -namespace PlanetDotnet.Services.CombinedFeeds +namespace PlanetDotnet.Services.Foundations.Feeds { public interface ICombinedFeedService { diff --git a/PlanetDotnet/Startup.cs b/PlanetDotnet/Startup.cs index bd1715d..f7b9671 100644 --- a/PlanetDotnet/Startup.cs +++ b/PlanetDotnet/Startup.cs @@ -13,7 +13,7 @@ using PlanetDotnet.Brokers.Loggings; using PlanetDotnet.Brokers.Serializations; using PlanetDotnet.Brokers.Storages; -using PlanetDotnet.Services.CombinedFeeds; +using PlanetDotnet.Services.Foundations.Feeds; [assembly: FunctionsStartup(typeof(PlanetDotnet.Startup))] namespace PlanetDotnet From 5e105f8faf8aaeb99c9654c69c1432f36d4532b0 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 01:42:25 +0100 Subject: [PATCH 14/28] FOUNDATIONS: Load Feed --- PlanetDotnet/LoadFeedsFunction.cs | 86 ++----------------- ...{CombinedFeedService.cs => FeedService.cs} | 6 +- ...CombinedFeedService.cs => IFeedService.cs} | 4 +- PlanetDotnet/Startup.cs | 2 +- 4 files changed, 15 insertions(+), 83 deletions(-) rename PlanetDotnet/Services/Foundations/Feeds/{CombinedFeedService.cs => FeedService.cs} (97%) rename PlanetDotnet/Services/Foundations/Feeds/{ICombinedFeedService.cs => IFeedService.cs} (76%) diff --git a/PlanetDotnet/LoadFeedsFunction.cs b/PlanetDotnet/LoadFeedsFunction.cs index bafc578..9edb1b4 100644 --- a/PlanetDotnet/LoadFeedsFunction.cs +++ b/PlanetDotnet/LoadFeedsFunction.cs @@ -6,14 +6,12 @@ using System; using System.Globalization; -using System.IO; using System.Linq; -using System.ServiceModel.Syndication; using System.Threading.Tasks; -using System.Xml; using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; using PlanetDotnet.Brokers.Authors; +using PlanetDotnet.Brokers.Serializations; using PlanetDotnet.Brokers.Storages; using PlanetDotnet.Services.Foundations.Feeds; @@ -21,18 +19,21 @@ namespace PlanetDotnet { public class LoadFeedsFunction { - private readonly ICombinedFeedService feedService; + private readonly IFeedService feedService; private readonly IStorageBroker storageBroker; private readonly IAuthorBroker authorBroker; + private readonly ISerializationBroker serializationBroker; public LoadFeedsFunction( - ICombinedFeedService feedService, + IFeedService feedService, IStorageBroker storageBroker, - IAuthorBroker authorBroker) + IAuthorBroker authorBroker, + ISerializationBroker serializationBroker) { this.feedService = feedService; this.storageBroker = storageBroker; this.authorBroker = authorBroker; + this.serializationBroker = serializationBroker; } [FunctionName("LoadFeedsFunction")] @@ -58,8 +59,8 @@ public async Task Run( { CultureInfo.CurrentCulture = new CultureInfo(language); log.LogInformation($"Loading {language} combined author feed"); - var feed = await feedService.LoadFeed(null, language); - using var stream = await SerializeFeed(feed); + var feed = await feedService.LoadFeedAsync(null, language); + using var stream = await this.serializationBroker.SerializeFeedAsync(feed); await this.storageBroker.UploadBlobAsync(language, stream); } catch (Exception ex) @@ -76,74 +77,5 @@ public async Task Run( } log.LogInformation($"Load feeds Finished at: {DateTime.Now}"); } - - private static async Task SerializeFeed(SyndicationFeed feed) - { - try - { - - var memoryStream = new MemoryStream(); - using var xmlWriter = XmlWriter.Create(memoryStream, new XmlWriterSettings - { - Async = true, - Indent = true // Makes the output XML easier to read, optional - }); - - xmlWriter.WriteStartDocument(); - xmlWriter.WriteStartElement("rss"); - xmlWriter.WriteAttributeString("version", "2.0"); - - xmlWriter.WriteStartElement("channel"); - - // Write channel elements - xmlWriter.WriteElementString("title", feed.Title?.Text ?? string.Empty); - xmlWriter.WriteElementString("link", feed.Links.FirstOrDefault()?.Uri.AbsoluteUri ?? string.Empty); - xmlWriter.WriteElementString("description", feed.Description?.Text ?? string.Empty); - - // Optional channel elements (add as needed) - if (feed.Language != null) - xmlWriter.WriteElementString("language", feed.Language); - - if (feed.LastUpdatedTime != DateTimeOffset.MinValue) - xmlWriter.WriteElementString("lastBuildDate", feed.LastUpdatedTime.ToString("R")); // RFC-822 format - - // More optional elements like image, categories, etc. can be added here - - // Write items - foreach (var item in feed.Items) - { - xmlWriter.WriteStartElement("item"); - - xmlWriter.WriteElementString("title", item.Title?.Text ?? string.Empty); - xmlWriter.WriteElementString("link", item.Links.FirstOrDefault()?.Uri.AbsoluteUri ?? string.Empty); - xmlWriter.WriteElementString("description", item.Summary?.Text ?? string.Empty); - - // Other optional elements for each item (guid, pubDate, etc.) - if (item.Id != null) - xmlWriter.WriteElementString("guid", item.Id); - - if (item.PublishDate != DateTimeOffset.MinValue) - xmlWriter.WriteElementString("pubDate", item.PublishDate.ToString("R")); // RFC-822 format - - // Additional elements for each item can be added here - - xmlWriter.WriteEndElement(); // item - } - - xmlWriter.WriteEndElement(); // channel - xmlWriter.WriteEndElement(); // rss - - await xmlWriter.FlushAsync(); - memoryStream.Seek(0, SeekOrigin.Begin); - - return memoryStream; - } - catch (Exception ex) - { - throw; - } - } - } - } diff --git a/PlanetDotnet/Services/Foundations/Feeds/CombinedFeedService.cs b/PlanetDotnet/Services/Foundations/Feeds/FeedService.cs similarity index 97% rename from PlanetDotnet/Services/Foundations/Feeds/CombinedFeedService.cs rename to PlanetDotnet/Services/Foundations/Feeds/FeedService.cs index d53faae..e4b7c49 100644 --- a/PlanetDotnet/Services/Foundations/Feeds/CombinedFeedService.cs +++ b/PlanetDotnet/Services/Foundations/Feeds/FeedService.cs @@ -24,7 +24,7 @@ namespace PlanetDotnet.Services.Foundations.Feeds { - public class CombinedFeedService : ICombinedFeedService + public class FeedService : IFeedService { private readonly HttpClient httpClient; private readonly AsyncRetryPolicy _retryPolicy; @@ -37,7 +37,7 @@ public class CombinedFeedService : ICombinedFeedService private const string RssFeedUrlKey = "RssFeedUrl"; private const string RssFeedImageUrlKey = "RssFeedImageUrl"; - public CombinedFeedService( + public FeedService( IAuthorBroker authorBroker, ILoggingBroker loggingBroker, IFeedBroker feedBroker, @@ -54,7 +54,7 @@ public CombinedFeedService( this.feedBroker = feedBroker; } - public async Task LoadFeed(int? numberOfItems, string languageCode = "mixed") + public async Task LoadFeedAsync(int? numberOfItems, string languageCode = "mixed") { var authors = await authorBroker.GetAllAuthorsAsync(); diff --git a/PlanetDotnet/Services/Foundations/Feeds/ICombinedFeedService.cs b/PlanetDotnet/Services/Foundations/Feeds/IFeedService.cs similarity index 76% rename from PlanetDotnet/Services/Foundations/Feeds/ICombinedFeedService.cs rename to PlanetDotnet/Services/Foundations/Feeds/IFeedService.cs index 03bd1a9..8218eb8 100644 --- a/PlanetDotnet/Services/Foundations/Feeds/ICombinedFeedService.cs +++ b/PlanetDotnet/Services/Foundations/Feeds/IFeedService.cs @@ -9,8 +9,8 @@ namespace PlanetDotnet.Services.Foundations.Feeds { - public interface ICombinedFeedService + public interface IFeedService { - Task LoadFeed(int? numberOfItems, string languageCode = "mixed"); + Task LoadFeedAsync(int? numberOfItems, string languageCode = "mixed"); } } diff --git a/PlanetDotnet/Startup.cs b/PlanetDotnet/Startup.cs index f7b9671..9355519 100644 --- a/PlanetDotnet/Startup.cs +++ b/PlanetDotnet/Startup.cs @@ -30,7 +30,7 @@ public override void Configure(IFunctionsHostBuilder builder) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - builder.Services.AddScoped(); + builder.Services.AddScoped(); } } } From 5ff708258b33691abf2505b037351c5b92c454a9 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 01:49:44 +0100 Subject: [PATCH 15/28] PROCESSINGS: Process Feed Loading --- PlanetDotnet/LoadFeedsFunction.cs | 31 +------- .../Feeds/FeedProcessingService.cs | 70 +++++++++++++++++++ .../Feeds/IFeedProcessingService.cs | 15 ++++ PlanetDotnet/Startup.cs | 2 + 4 files changed, 89 insertions(+), 29 deletions(-) create mode 100644 PlanetDotnet/Services/Processings/Feeds/FeedProcessingService.cs create mode 100644 PlanetDotnet/Services/Processings/Feeds/IFeedProcessingService.cs diff --git a/PlanetDotnet/LoadFeedsFunction.cs b/PlanetDotnet/LoadFeedsFunction.cs index 9edb1b4..4fc5f59 100644 --- a/PlanetDotnet/LoadFeedsFunction.cs +++ b/PlanetDotnet/LoadFeedsFunction.cs @@ -19,10 +19,7 @@ namespace PlanetDotnet { public class LoadFeedsFunction { - private readonly IFeedService feedService; - private readonly IStorageBroker storageBroker; - private readonly IAuthorBroker authorBroker; - private readonly ISerializationBroker serializationBroker; + public LoadFeedsFunction( IFeedService feedService, @@ -45,31 +42,7 @@ public async Task Run( try { - await this.storageBroker.InitializeAsync(); - - var authors = await this.authorBroker.GetAllAuthorsAsync(); - - var languages = authors.Select(author => author.FeedLanguageCode).Distinct().ToList(); - - var mainCulture = CultureInfo.CurrentCulture; - - foreach (var language in languages) - { - try - { - CultureInfo.CurrentCulture = new CultureInfo(language); - log.LogInformation($"Loading {language} combined author feed"); - var feed = await feedService.LoadFeedAsync(null, language); - using var stream = await this.serializationBroker.SerializeFeedAsync(feed); - await this.storageBroker.UploadBlobAsync(language, stream); - } - catch (Exception ex) - { - log.LogError(ex, "error"); - } - } - - CultureInfo.CurrentCulture = mainCulture; + } catch (Exception ex) { diff --git a/PlanetDotnet/Services/Processings/Feeds/FeedProcessingService.cs b/PlanetDotnet/Services/Processings/Feeds/FeedProcessingService.cs new file mode 100644 index 0000000..6a7e06d --- /dev/null +++ b/PlanetDotnet/Services/Processings/Feeds/FeedProcessingService.cs @@ -0,0 +1,70 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using PlanetDotnet.Brokers.Authors; +using PlanetDotnet.Brokers.Loggings; +using PlanetDotnet.Brokers.Serializations; +using PlanetDotnet.Brokers.Storages; +using PlanetDotnet.Services.Foundations.Feeds; + +namespace PlanetDotnet.Services.Processings.Feeds +{ + public class FeedProcessingService : IFeedProcessingService + { + private readonly IFeedService feedService; + private readonly IStorageBroker storageBroker; + private readonly IAuthorBroker authorBroker; + private readonly ISerializationBroker serializationBroker; + private readonly ILoggingBroker loggingBroker; + + public FeedProcessingService( + IFeedService feedService, + IStorageBroker storageBroker, + IAuthorBroker authorBroker, + ISerializationBroker serializationBroker, + ILoggingBroker loggingBroker) + { + this.feedService = feedService; + this.storageBroker = storageBroker; + this.authorBroker = authorBroker; + this.serializationBroker = serializationBroker; + this.loggingBroker = loggingBroker; + } + + public async ValueTask ProcessFeedLoadingAsync() + { + await this.storageBroker.InitializeAsync(); + + var authors = await this.authorBroker.GetAllAuthorsAsync(); + + var languages = authors.Select(author => author.FeedLanguageCode).Distinct().ToList(); + + var mainCulture = CultureInfo.CurrentCulture; + + foreach (var language in languages) + { + try + { + CultureInfo.CurrentCulture = new CultureInfo(language); + this.loggingBroker.LogInformation($"Loading {language} combined author feed"); + var feed = await feedService.LoadFeedAsync(null, language); + using var stream = await this.serializationBroker.SerializeFeedAsync(feed); + await this.storageBroker.UploadBlobAsync(language, stream); + } + catch (Exception ex) + { + this.loggingBroker.LogError(ex, "error"); + } + } + + CultureInfo.CurrentCulture = mainCulture; + } + } +} diff --git a/PlanetDotnet/Services/Processings/Feeds/IFeedProcessingService.cs b/PlanetDotnet/Services/Processings/Feeds/IFeedProcessingService.cs new file mode 100644 index 0000000..2d08eda --- /dev/null +++ b/PlanetDotnet/Services/Processings/Feeds/IFeedProcessingService.cs @@ -0,0 +1,15 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.Threading.Tasks; + +namespace PlanetDotnet.Services.Processings.Feeds +{ + public interface IFeedProcessingService + { + ValueTask ProcessFeedLoadingAsync(); + } +} diff --git a/PlanetDotnet/Startup.cs b/PlanetDotnet/Startup.cs index 9355519..e4a30e0 100644 --- a/PlanetDotnet/Startup.cs +++ b/PlanetDotnet/Startup.cs @@ -14,6 +14,7 @@ using PlanetDotnet.Brokers.Serializations; using PlanetDotnet.Brokers.Storages; using PlanetDotnet.Services.Foundations.Feeds; +using PlanetDotnet.Services.Processings.Feeds; [assembly: FunctionsStartup(typeof(PlanetDotnet.Startup))] namespace PlanetDotnet @@ -31,6 +32,7 @@ public override void Configure(IFunctionsHostBuilder builder) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); } } } From 75e192192c4a503d24f60e2a074d33d5d0f2f66b Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 01:55:25 +0100 Subject: [PATCH 16/28] FUNCTIONS: Load Feeds Function --- PlanetDotnet/LoadFeedsFunction.cs | 33 ++++++++++--------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/PlanetDotnet/LoadFeedsFunction.cs b/PlanetDotnet/LoadFeedsFunction.cs index 4fc5f59..7d96325 100644 --- a/PlanetDotnet/LoadFeedsFunction.cs +++ b/PlanetDotnet/LoadFeedsFunction.cs @@ -5,50 +5,37 @@ // --------------------------------------------------------------- using System; -using System.Globalization; -using System.Linq; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; -using PlanetDotnet.Brokers.Authors; -using PlanetDotnet.Brokers.Serializations; -using PlanetDotnet.Brokers.Storages; -using PlanetDotnet.Services.Foundations.Feeds; +using PlanetDotnet.Services.Processings.Feeds; namespace PlanetDotnet { public class LoadFeedsFunction { - + private readonly IFeedProcessingService feedProcessingService; - public LoadFeedsFunction( - IFeedService feedService, - IStorageBroker storageBroker, - IAuthorBroker authorBroker, - ISerializationBroker serializationBroker) - { - this.feedService = feedService; - this.storageBroker = storageBroker; - this.authorBroker = authorBroker; - this.serializationBroker = serializationBroker; - } + public LoadFeedsFunction(IFeedProcessingService feedProcessingService) => + this.feedProcessingService = feedProcessingService; [FunctionName("LoadFeedsFunction")] public async Task Run( [TimerTrigger("0 0 */1 * * *", RunOnStartup = true)] TimerInfo myTimer, ILogger log) { - log.LogInformation($"Load feeds Timer trigger function executed at: {DateTime.Now}"); - try { - + log.LogInformation($"Load feeds Timer trigger function executed at: {DateTime.Now}"); + + await this.feedProcessingService.ProcessFeedLoadingAsync(); + + log.LogInformation($"Load feeds Finished at: {DateTime.Now}"); } catch (Exception ex) { - log.LogError(ex, "error"); + log.LogError(ex, "Loading feeds could'nt be processed."); } - log.LogInformation($"Load feeds Finished at: {DateTime.Now}"); } } } From 802283f9e440bfe9b06ac07b2930af17799a2ea7 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 01:55:40 +0100 Subject: [PATCH 17/28] CODE RUB: Clean up --- PlanetDotnet/Brokers/DateTimes/DateTimeBroker.cs | 6 ++++++ PlanetDotnet/Brokers/DateTimes/IDateTimeBroker.cs | 6 ++++++ PlanetDotnet/Extensions/ExceptionExtensions.cs | 6 +++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/PlanetDotnet/Brokers/DateTimes/DateTimeBroker.cs b/PlanetDotnet/Brokers/DateTimes/DateTimeBroker.cs index 1a91c52..2109e7d 100644 --- a/PlanetDotnet/Brokers/DateTimes/DateTimeBroker.cs +++ b/PlanetDotnet/Brokers/DateTimes/DateTimeBroker.cs @@ -1,3 +1,9 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + using System; namespace PlanetDotnet.Brokers.DateTimes diff --git a/PlanetDotnet/Brokers/DateTimes/IDateTimeBroker.cs b/PlanetDotnet/Brokers/DateTimes/IDateTimeBroker.cs index d771b78..d4505c8 100644 --- a/PlanetDotnet/Brokers/DateTimes/IDateTimeBroker.cs +++ b/PlanetDotnet/Brokers/DateTimes/IDateTimeBroker.cs @@ -1,3 +1,9 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + using System; namespace PlanetDotnet.Brokers.DateTimes diff --git a/PlanetDotnet/Extensions/ExceptionExtensions.cs b/PlanetDotnet/Extensions/ExceptionExtensions.cs index 5ce0dbc..a563f30 100644 --- a/PlanetDotnet/Extensions/ExceptionExtensions.cs +++ b/PlanetDotnet/Extensions/ExceptionExtensions.cs @@ -11,9 +11,9 @@ namespace PlanetDotnet.Extensions internal static class ExceptionExtensions { public static TException WithData( - this TException exception, - string key, - object value) + this TException exception, + string key, + object value) where TException : Exception { exception.Data[key] = value; From ad0f059f61131ea0123140de2d978bd785747217 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 02:19:48 +0100 Subject: [PATCH 18/28] CODE RUB: Clean up --- .../Services/Foundations/Feeds/FeedService.cs | 37 +++++++++++-------- PlanetDotnet/Startup.cs | 18 ++++----- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/PlanetDotnet/Services/Foundations/Feeds/FeedService.cs b/PlanetDotnet/Services/Foundations/Feeds/FeedService.cs index e4b7c49..2929c59 100644 --- a/PlanetDotnet/Services/Foundations/Feeds/FeedService.cs +++ b/PlanetDotnet/Services/Foundations/Feeds/FeedService.cs @@ -27,7 +27,7 @@ namespace PlanetDotnet.Services.Foundations.Feeds public class FeedService : IFeedService { private readonly HttpClient httpClient; - private readonly AsyncRetryPolicy _retryPolicy; + private readonly AsyncRetryPolicy retryPolicy; private readonly IAuthorBroker authorBroker; private readonly ILoggingBroker loggingBroker; private readonly IFeedBroker feedBroker; @@ -38,25 +38,25 @@ public class FeedService : IFeedService private const string RssFeedImageUrlKey = "RssFeedImageUrl"; public FeedService( - IAuthorBroker authorBroker, - ILoggingBroker loggingBroker, + HttpClient httpClient, IFeedBroker feedBroker, - HttpClient httpClient) + IAuthorBroker authorBroker, + ILoggingBroker loggingBroker) { this.httpClient = httpClient; + this.feedBroker = feedBroker; + this.authorBroker = authorBroker; + this.loggingBroker = loggingBroker; + EnsureHttpClient(); - _retryPolicy ??= Policy.Handle() + this.retryPolicy ??= Policy.Handle() .WaitAndRetryAsync(2, retry => TimeSpan.FromSeconds(retry * Math.Pow(1.2, retry))); - - this.authorBroker = authorBroker; - this.loggingBroker = loggingBroker; - this.feedBroker = feedBroker; } public async Task LoadFeedAsync(int? numberOfItems, string languageCode = "mixed") { - var authors = await authorBroker.GetAllAuthorsAsync(); + var authors = await this.authorBroker.GetAllAuthorsAsync(); IEnumerable languageAuthors; @@ -71,7 +71,7 @@ public async Task LoadFeedAsync(int? numberOfItems, string lang var feedTasks = languageAuthors.SelectMany(t => TryReadFeeds(t)).ToArray(); - loggingBroker.LogInformation($"Loading feed for language: {languageCode} for {feedTasks.Length} authors"); + this.loggingBroker.LogInformation($"Loading feed for language: {languageCode} for {feedTasks.Length} authors"); var syndicationItems = await Task.WhenAll(feedTasks).ConfigureAwait(false); @@ -89,7 +89,7 @@ private async Task> TryReadFeed(Author tamarin, str { try { - return await _retryPolicy.ExecuteAsync(context => ReadFeed(feedUri), new Context(feedUri)).ConfigureAwait(false); + return await retryPolicy.ExecuteAsync(context => ReadFeed(feedUri), new Context(feedUri)).ConfigureAwait(false); } catch (FailedFeedException ex) { @@ -199,12 +199,17 @@ private static DateTimeOffset GetMaxTime(SyndicationItem item) private void EnsureHttpClient() { - httpClient.DefaultRequestHeaders.UserAgent.Add( - new ProductInfoHeaderValue("PlanetDotnet", $"{GetType().Assembly.GetName().Version}")); + this.httpClient.DefaultRequestHeaders.UserAgent.Add( + new ProductInfoHeaderValue( + productName: "PlanetDotnet", + productVersion: $"{GetType().Assembly.GetName().Version}")); - httpClient.Timeout = TimeSpan.FromSeconds(15); + this.httpClient.Timeout = TimeSpan.FromSeconds(15); - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13; + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Tls13 + | SecurityProtocolType.Tls12 + | SecurityProtocolType.Tls11; } } } \ No newline at end of file diff --git a/PlanetDotnet/Startup.cs b/PlanetDotnet/Startup.cs index e4a30e0..2b8b95a 100644 --- a/PlanetDotnet/Startup.cs +++ b/PlanetDotnet/Startup.cs @@ -24,15 +24,15 @@ public class Startup : FunctionsStartup public override void Configure(IFunctionsHostBuilder builder) { builder.Services.AddLogging(); - builder.Services.AddScoped, Logger>(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); + builder.Services.AddSingleton, Logger>(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); } } } From 7e7969cd6bc0be9116e5c5f340d2b1b7d1c9a51d Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 02:25:59 +0100 Subject: [PATCH 19/28] CODE RUB: Clean up --- .../{LoadFeedsFunction.cs => Functions/FeedFunctions.cs} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename PlanetDotnet/{LoadFeedsFunction.cs => Functions/FeedFunctions.cs} (84%) diff --git a/PlanetDotnet/LoadFeedsFunction.cs b/PlanetDotnet/Functions/FeedFunctions.cs similarity index 84% rename from PlanetDotnet/LoadFeedsFunction.cs rename to PlanetDotnet/Functions/FeedFunctions.cs index 7d96325..dc111aa 100644 --- a/PlanetDotnet/LoadFeedsFunction.cs +++ b/PlanetDotnet/Functions/FeedFunctions.cs @@ -10,13 +10,13 @@ using Microsoft.Extensions.Logging; using PlanetDotnet.Services.Processings.Feeds; -namespace PlanetDotnet +namespace PlanetDotnet.Functions { - public class LoadFeedsFunction + public class FeedFunctions { private readonly IFeedProcessingService feedProcessingService; - public LoadFeedsFunction(IFeedProcessingService feedProcessingService) => + public FeedFunctions(IFeedProcessingService feedProcessingService) => this.feedProcessingService = feedProcessingService; [FunctionName("LoadFeedsFunction")] @@ -28,7 +28,7 @@ public async Task Run( { log.LogInformation($"Load feeds Timer trigger function executed at: {DateTime.Now}"); - await this.feedProcessingService.ProcessFeedLoadingAsync(); + await feedProcessingService.ProcessFeedLoadingAsync(); log.LogInformation($"Load feeds Finished at: {DateTime.Now}"); } From 45f71a8e4f5727a420298474f2b48c5d731ba2e8 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 02:31:21 +0100 Subject: [PATCH 20/28] FOUNDATIONS: Retrieve All Authors --- PlanetDotnet.sln | 6 ++-- PlanetDotnet/Functions/AuthorFunctions.cs | 29 +++++++++++++++++++ .../Foundations/Authors/AuthorService.cs | 24 +++++++++++++++ .../Foundations/Authors/IAuthorService.cs | 17 +++++++++++ PlanetDotnet/Startup.cs | 2 ++ 5 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 PlanetDotnet/Functions/AuthorFunctions.cs create mode 100644 PlanetDotnet/Services/Foundations/Authors/AuthorService.cs create mode 100644 PlanetDotnet/Services/Foundations/Authors/IAuthorService.cs diff --git a/PlanetDotnet.sln b/PlanetDotnet.sln index aaba29e..550ffe7 100644 --- a/PlanetDotnet.sln +++ b/PlanetDotnet.sln @@ -3,11 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.32804.182 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanetDotnet", "PlanetDotnet\PlanetDotnet.csproj", "{AA1CF7A0-FF81-40FA-ABB2-519AF44C2460}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlanetDotnet", "PlanetDotnet\PlanetDotnet.csproj", "{AA1CF7A0-FF81-40FA-ABB2-519AF44C2460}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanetDotnet.Tests.Unit", "PlanetDotnet.Tests.Unit\PlanetDotnet.Tests.Unit.csproj", "{ED070F5D-3C55-4E8F-B23A-381E09931D1C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlanetDotnet.Tests.Unit", "PlanetDotnet.Tests.Unit\PlanetDotnet.Tests.Unit.csproj", "{ED070F5D-3C55-4E8F-B23A-381E09931D1C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlanetDotnet.Authors", "PlanetDotnet.Authors\PlanetDotnet.Authors.csproj", "{0518A91D-95CA-4C21-BACF-E1E7E56D8754}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlanetDotnet.Authors", "PlanetDotnet.Authors\PlanetDotnet.Authors.csproj", "{0518A91D-95CA-4C21-BACF-E1E7E56D8754}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/PlanetDotnet/Functions/AuthorFunctions.cs b/PlanetDotnet/Functions/AuthorFunctions.cs new file mode 100644 index 0000000..df5298e --- /dev/null +++ b/PlanetDotnet/Functions/AuthorFunctions.cs @@ -0,0 +1,29 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.Extensions.Logging; + +namespace PlanetDotnet.Functions +{ + public static class AuthorFunctions + { + [FunctionName("AuthorFunctions")] + public static async Task Run( + [HttpTrigger(AuthorizationLevel.Function, "get", Route = "authors")] HttpRequest req, + ILogger log) + { + log.LogInformation("C# HTTP trigger function processed a request."); + + + return new OkObjectResult(""); + } + } +} diff --git a/PlanetDotnet/Services/Foundations/Authors/AuthorService.cs b/PlanetDotnet/Services/Foundations/Authors/AuthorService.cs new file mode 100644 index 0000000..0d83ca4 --- /dev/null +++ b/PlanetDotnet/Services/Foundations/Authors/AuthorService.cs @@ -0,0 +1,24 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.Collections.Generic; +using System.Threading.Tasks; +using PlanetDotnet.Authors.Models.Authors; +using PlanetDotnet.Brokers.Authors; + +namespace PlanetDotnet.Services.Foundations.Authors +{ + public class AuthorService : IAuthorService + { + private readonly IAuthorBroker authorBroker; + + public AuthorService(IAuthorBroker authorBroker) => + this.authorBroker = authorBroker; + + public async ValueTask> RetrieveAllAuthorsAsync() => + await this.authorBroker.GetAllAuthorsAsync(); + } +} diff --git a/PlanetDotnet/Services/Foundations/Authors/IAuthorService.cs b/PlanetDotnet/Services/Foundations/Authors/IAuthorService.cs new file mode 100644 index 0000000..2d3cc4c --- /dev/null +++ b/PlanetDotnet/Services/Foundations/Authors/IAuthorService.cs @@ -0,0 +1,17 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.Collections.Generic; +using System.Threading.Tasks; +using PlanetDotnet.Authors.Models.Authors; + +namespace PlanetDotnet.Services.Foundations.Authors +{ + public interface IAuthorService + { + ValueTask> RetrieveAllAuthorsAsync(); + } +} diff --git a/PlanetDotnet/Startup.cs b/PlanetDotnet/Startup.cs index 2b8b95a..89b78b2 100644 --- a/PlanetDotnet/Startup.cs +++ b/PlanetDotnet/Startup.cs @@ -13,6 +13,7 @@ using PlanetDotnet.Brokers.Loggings; using PlanetDotnet.Brokers.Serializations; using PlanetDotnet.Brokers.Storages; +using PlanetDotnet.Services.Foundations.Authors; using PlanetDotnet.Services.Foundations.Feeds; using PlanetDotnet.Services.Processings.Feeds; @@ -32,6 +33,7 @@ public override void Configure(IFunctionsHostBuilder builder) builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); } } From 0a1efa3a7bf5b296dcb7c4aace5d5cb76fc7fcec Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 02:48:48 +0100 Subject: [PATCH 21/28] ShouldRetrieveAllAuthorsAsync -> PASS --- .../PlanetDotnet.Tests.Unit.csproj | 3 ++ .../AuthorServiceTests.Logic.GetAll.cs | 44 +++++++++++++++++++ .../Foundations/Authors/AuthorServiceTests.cs | 40 +++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Logic.GetAll.cs create mode 100644 PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.cs diff --git a/PlanetDotnet.Tests.Unit/PlanetDotnet.Tests.Unit.csproj b/PlanetDotnet.Tests.Unit/PlanetDotnet.Tests.Unit.csproj index 8180b84..969426d 100644 --- a/PlanetDotnet.Tests.Unit/PlanetDotnet.Tests.Unit.csproj +++ b/PlanetDotnet.Tests.Unit/PlanetDotnet.Tests.Unit.csproj @@ -10,7 +10,10 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Logic.GetAll.cs b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Logic.GetAll.cs new file mode 100644 index 0000000..9d7e2ab --- /dev/null +++ b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Logic.GetAll.cs @@ -0,0 +1,44 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Moq; +using PlanetDotnet.Authors.Models.Authors; +using Xunit; + +namespace PlanetDotnet.Tests.Unit.Services.Foundations.Authors +{ + public partial class AuthorServiceTests + { + [Fact] + public async Task ShouldRetrieveAllAuthorsAsync() + { + // given + IEnumerable randomAuthors = CreateRandomAuthors(); + IEnumerable storageAuthors = randomAuthors; + IEnumerable expectedAuthors = storageAuthors; + + this.authorBrokerMock.Setup(broker => + broker.GetAllAuthorsAsync()) + .ReturnsAsync(expectedAuthors); + + // when + var actualAuthors = + await this.authorService.RetrieveAllAuthorsAsync(); + + // then + actualAuthors.Should().BeEquivalentTo(expectedAuthors); + + this.authorBrokerMock.Verify(broker => + broker.GetAllAuthorsAsync(), + Times.Once()); + + this.authorBrokerMock.VerifyNoOtherCalls(); + } + } +} diff --git a/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.cs b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.cs new file mode 100644 index 0000000..a566ff7 --- /dev/null +++ b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.cs @@ -0,0 +1,40 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System.Collections.Generic; +using Moq; +using PlanetDotnet.Authors.Models.Authors; +using PlanetDotnet.Brokers.Authors; +using PlanetDotnet.Services.Foundations.Authors; +using Tynamix.ObjectFiller; + +namespace PlanetDotnet.Tests.Unit.Services.Foundations.Authors +{ + public partial class AuthorServiceTests + { + private readonly Mock authorBrokerMock; + private readonly IAuthorService authorService; + + public AuthorServiceTests() + { + this.authorBrokerMock = new Mock(); + + this.authorService = new AuthorService( + authorBroker: this.authorBrokerMock.Object); + } + + private static IEnumerable CreateRandomAuthors() => + CreateAuthorFiller().Create(count: GetRandomNumber()); + + private static Author CreateRandomAuthor() => + CreateAuthorFiller().Create(); + + private static Filler CreateAuthorFiller() => new(); + + private static int GetRandomNumber() => + new IntRange(min: 2, max: 15).GetValue(); + } +} From 32748a538cd00b250837e34f9f1ebe2c82872dc8 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 03:10:02 +0100 Subject: [PATCH 22/28] ShouldThrowCriticalDependencyExceptionOnRetrieveAllWhenAssemblyExceptionOccursAndLogIt -> FAIL --- .../Exceptions/AuthorDependencyException.cs | 17 ++++++ .../FailedAuthorStorageException.cs | 17 ++++++ .../AuthorServiceTests.Exceptions.GetAll.cs | 59 +++++++++++++++++++ .../Foundations/Authors/AuthorServiceTests.cs | 16 ++++- .../Foundations/Authors/AuthorService.cs | 9 ++- 5 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 PlanetDotnet.Authors/Models/Authors/Exceptions/AuthorDependencyException.cs create mode 100644 PlanetDotnet.Authors/Models/Authors/Exceptions/FailedAuthorStorageException.cs create mode 100644 PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs diff --git a/PlanetDotnet.Authors/Models/Authors/Exceptions/AuthorDependencyException.cs b/PlanetDotnet.Authors/Models/Authors/Exceptions/AuthorDependencyException.cs new file mode 100644 index 0000000..b4d01ab --- /dev/null +++ b/PlanetDotnet.Authors/Models/Authors/Exceptions/AuthorDependencyException.cs @@ -0,0 +1,17 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; + +namespace PlanetDotnet.Authors.Models.Authors.Exceptions +{ + public class AuthorDependencyException : Exception + { + public AuthorDependencyException(Exception innerException) : + base(message: "Author dependency error occurred, contact support.", innerException) + { } + } +} diff --git a/PlanetDotnet.Authors/Models/Authors/Exceptions/FailedAuthorStorageException.cs b/PlanetDotnet.Authors/Models/Authors/Exceptions/FailedAuthorStorageException.cs new file mode 100644 index 0000000..c41dc7f --- /dev/null +++ b/PlanetDotnet.Authors/Models/Authors/Exceptions/FailedAuthorStorageException.cs @@ -0,0 +1,17 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; + +namespace PlanetDotnet.Authors.Models.Authors.Exceptions +{ + public class FailedAuthorStorageException : Exception + { + public FailedAuthorStorageException(Exception innerException) + : base("Failed authors storage error occurred, contact support.", innerException) + { } + } +} diff --git a/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs new file mode 100644 index 0000000..702a8c5 --- /dev/null +++ b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs @@ -0,0 +1,59 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; +using System.Reflection; +using FluentAssertions; +using Moq; +using PlanetDotnet.Authors.Models.Authors.Exceptions; +using Xunit; + +namespace PlanetDotnet.Tests.Unit.Services.Foundations.Authors +{ + public partial class AuthorServiceTests + { + [Fact] + public void ShouldThrowCriticalDependencyExceptionOnRetrieveAllWhenAssemblyExceptionOccursAndLogIt() + { + // given + TargetInvocationException targetInvocationException + = GetTargetInvocationException(); + + var failedStorageException = + new FailedAuthorStorageException(targetInvocationException); + + var expectedAuthorDependencyException = + new AuthorDependencyException(failedStorageException); + + this.authorBrokerMock.Setup(broker => + broker.GetAllAuthorsAsync()) + .Throws(targetInvocationException); + + // when + Action retrieveAllAuthorsAction = () => + this.authorService.RetrieveAllAuthorsAsync(); + + AuthorDependencyException actualAuthorDependencyException = + Assert.Throws(retrieveAllAuthorsAction); + + // then + actualAuthorDependencyException.Should() + .BeEquivalentTo(expectedAuthorDependencyException); + + this.authorBrokerMock.Verify(broker => + broker.GetAllAuthorsAsync(), + Times.Once); + + this.loggingBrokerMock.Verify(broker => + broker.LogCritical(It.Is(SameExceptionAs( + expectedAuthorDependencyException))), + Times.Once); + + this.authorBrokerMock.VerifyNoOtherCalls(); + this.loggingBrokerMock.VerifyNoOtherCalls(); + } + } +} diff --git a/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.cs b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.cs index a566ff7..b3041b9 100644 --- a/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.cs +++ b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.cs @@ -4,10 +4,15 @@ // See License.txt in the project root for license information. // --------------------------------------------------------------- +using System; using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.Serialization; using Moq; using PlanetDotnet.Authors.Models.Authors; using PlanetDotnet.Brokers.Authors; +using PlanetDotnet.Brokers.Loggings; using PlanetDotnet.Services.Foundations.Authors; using Tynamix.ObjectFiller; @@ -16,14 +21,17 @@ namespace PlanetDotnet.Tests.Unit.Services.Foundations.Authors public partial class AuthorServiceTests { private readonly Mock authorBrokerMock; + private readonly Mock loggingBrokerMock; private readonly IAuthorService authorService; public AuthorServiceTests() { this.authorBrokerMock = new Mock(); + this.loggingBrokerMock = new Mock(); this.authorService = new AuthorService( - authorBroker: this.authorBrokerMock.Object); + authorBroker: this.authorBrokerMock.Object, + loggingBroker: this.loggingBrokerMock.Object); } private static IEnumerable CreateRandomAuthors() => @@ -36,5 +44,11 @@ private static Author CreateRandomAuthor() => private static int GetRandomNumber() => new IntRange(min: 2, max: 15).GetValue(); + + private static Expression> SameExceptionAs(Exception expectedException) => + actualException => actualException.Message == expectedException.Message; + + private static TargetInvocationException GetTargetInvocationException() => + (TargetInvocationException)FormatterServices.GetUninitializedObject(typeof(TargetInvocationException)); } } diff --git a/PlanetDotnet/Services/Foundations/Authors/AuthorService.cs b/PlanetDotnet/Services/Foundations/Authors/AuthorService.cs index 0d83ca4..da16073 100644 --- a/PlanetDotnet/Services/Foundations/Authors/AuthorService.cs +++ b/PlanetDotnet/Services/Foundations/Authors/AuthorService.cs @@ -8,15 +8,22 @@ using System.Threading.Tasks; using PlanetDotnet.Authors.Models.Authors; using PlanetDotnet.Brokers.Authors; +using PlanetDotnet.Brokers.Loggings; namespace PlanetDotnet.Services.Foundations.Authors { public class AuthorService : IAuthorService { private readonly IAuthorBroker authorBroker; + private readonly ILoggingBroker loggingBroker; - public AuthorService(IAuthorBroker authorBroker) => + public AuthorService( + IAuthorBroker authorBroker, + ILoggingBroker loggingBroker) + { this.authorBroker = authorBroker; + this.loggingBroker = loggingBroker; + } public async ValueTask> RetrieveAllAuthorsAsync() => await this.authorBroker.GetAllAuthorsAsync(); From 1c8461f71b1b75b97d39892ca79cf3411cf377e3 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 03:35:01 +0100 Subject: [PATCH 23/28] ShouldThrowCriticalDependencyExceptionOnRetrieveAllWhenAssemblyExceptionOccursAndLogIt -> PASS --- .../AuthorServiceTests.Exceptions.GetAll.cs | 14 +++--- .../Authors/AuthorService.Exceptions.cs | 46 +++++++++++++++++++ .../Foundations/Authors/AuthorService.cs | 6 +-- 3 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 PlanetDotnet/Services/Foundations/Authors/AuthorService.Exceptions.cs diff --git a/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs index 702a8c5..bdb70ff 100644 --- a/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs +++ b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs @@ -6,6 +6,7 @@ using System; using System.Reflection; +using System.Threading.Tasks; using FluentAssertions; using Moq; using PlanetDotnet.Authors.Models.Authors.Exceptions; @@ -16,7 +17,7 @@ namespace PlanetDotnet.Tests.Unit.Services.Foundations.Authors public partial class AuthorServiceTests { [Fact] - public void ShouldThrowCriticalDependencyExceptionOnRetrieveAllWhenAssemblyExceptionOccursAndLogIt() + public async Task ShouldThrowCriticalDependencyExceptionOnRetrieveAllWhenAssemblyExceptionOccursAndLogIt() { // given TargetInvocationException targetInvocationException @@ -33,15 +34,12 @@ TargetInvocationException targetInvocationException .Throws(targetInvocationException); // when - Action retrieveAllAuthorsAction = () => - this.authorService.RetrieveAllAuthorsAsync(); - - AuthorDependencyException actualAuthorDependencyException = - Assert.Throws(retrieveAllAuthorsAction); + var retrieveAllAuthorsTask = + this.authorService.RetrieveAllAuthorsAsync(); // then - actualAuthorDependencyException.Should() - .BeEquivalentTo(expectedAuthorDependencyException); + await Assert.ThrowsAsync(() => + retrieveAllAuthorsTask.AsTask()); this.authorBrokerMock.Verify(broker => broker.GetAllAuthorsAsync(), diff --git a/PlanetDotnet/Services/Foundations/Authors/AuthorService.Exceptions.cs b/PlanetDotnet/Services/Foundations/Authors/AuthorService.Exceptions.cs new file mode 100644 index 0000000..73a8b67 --- /dev/null +++ b/PlanetDotnet/Services/Foundations/Authors/AuthorService.Exceptions.cs @@ -0,0 +1,46 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; +using PlanetDotnet.Authors.Models.Authors; +using PlanetDotnet.Authors.Models.Authors.Exceptions; + +namespace PlanetDotnet.Services.Foundations.Authors +{ + public partial class AuthorService + { + private delegate ValueTask> ReturningAuthorsFunction(); + + private async ValueTask> TryCatch(ReturningAuthorsFunction returningAuthorsFunction) + { + try + { + return await returningAuthorsFunction(); + } + catch (TargetInvocationException targetInvocationException) + { + var failedAuthorStorageException = + new FailedAuthorStorageException(targetInvocationException); + + throw CreateAndLogDependencyException(failedAuthorStorageException); + } + } + + private AuthorDependencyException CreateAndLogDependencyException( + Exception exception) + { + var authorDependencyException = + new AuthorDependencyException(exception); + + this.loggingBroker.LogCritical(authorDependencyException); + + return authorDependencyException; + } + } +} diff --git a/PlanetDotnet/Services/Foundations/Authors/AuthorService.cs b/PlanetDotnet/Services/Foundations/Authors/AuthorService.cs index da16073..eead7f5 100644 --- a/PlanetDotnet/Services/Foundations/Authors/AuthorService.cs +++ b/PlanetDotnet/Services/Foundations/Authors/AuthorService.cs @@ -12,7 +12,7 @@ namespace PlanetDotnet.Services.Foundations.Authors { - public class AuthorService : IAuthorService + public partial class AuthorService : IAuthorService { private readonly IAuthorBroker authorBroker; private readonly ILoggingBroker loggingBroker; @@ -25,7 +25,7 @@ public AuthorService( this.loggingBroker = loggingBroker; } - public async ValueTask> RetrieveAllAuthorsAsync() => - await this.authorBroker.GetAllAuthorsAsync(); + public ValueTask> RetrieveAllAuthorsAsync() => + TryCatch(async ()=> await this.authorBroker.GetAllAuthorsAsync()); } } From ce6a1c93afc72cbf447fe9617c7955b649922420 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 03:45:35 +0100 Subject: [PATCH 24/28] ShouldThrowCriticalDependencyExceptionOnRetrieveAllIfDependencyErrorOccursAndLogIt -> FAIL --- .../AuthorServiceTests.Exceptions.GetAll.cs | 17 +++-- .../Foundations/Authors/AuthorServiceTests.cs | 62 +++++++++++++++++++ 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs index bdb70ff..c0a646c 100644 --- a/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs +++ b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs @@ -7,7 +7,6 @@ using System; using System.Reflection; using System.Threading.Tasks; -using FluentAssertions; using Moq; using PlanetDotnet.Authors.Models.Authors.Exceptions; using Xunit; @@ -16,22 +15,22 @@ namespace PlanetDotnet.Tests.Unit.Services.Foundations.Authors { public partial class AuthorServiceTests { - [Fact] - public async Task ShouldThrowCriticalDependencyExceptionOnRetrieveAllWhenAssemblyExceptionOccursAndLogIt() - { - // given - TargetInvocationException targetInvocationException - = GetTargetInvocationException(); + [Theory] + [MemberData(nameof(DependencyExceptions))] + public async Task ShouldThrowCriticalDependencyExceptionOnRetrieveAllIfDependencyErrorOccursAndLogIt( + Exception dependencyException) + { + // given var failedStorageException = - new FailedAuthorStorageException(targetInvocationException); + new FailedAuthorStorageException(dependencyException); var expectedAuthorDependencyException = new AuthorDependencyException(failedStorageException); this.authorBrokerMock.Setup(broker => broker.GetAllAuthorsAsync()) - .Throws(targetInvocationException); + .Throws(dependencyException); // when var retrieveAllAuthorsTask = diff --git a/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.cs b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.cs index b3041b9..f107424 100644 --- a/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.cs +++ b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.cs @@ -6,15 +6,20 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq.Expressions; +using System.Net.Http; using System.Reflection; using System.Runtime.Serialization; +using System.Web.Http; using Moq; +using Newtonsoft.Json; using PlanetDotnet.Authors.Models.Authors; using PlanetDotnet.Brokers.Authors; using PlanetDotnet.Brokers.Loggings; using PlanetDotnet.Services.Foundations.Authors; using Tynamix.ObjectFiller; +using Xunit; namespace PlanetDotnet.Tests.Unit.Services.Foundations.Authors { @@ -50,5 +55,62 @@ private static Expression> SameExceptionAs(Exception expec private static TargetInvocationException GetTargetInvocationException() => (TargetInvocationException)FormatterServices.GetUninitializedObject(typeof(TargetInvocationException)); + + private static Exception GetException() => + (Exception)FormatterServices.GetUninitializedObject(typeof(Exception)); + + private static string GetRandomString() => new MnemonicString().GetValue(); + + public static TheoryData DependencyExceptions() + { + string exceptionMessage = GetRandomString(); + + var exception = new Exception(exceptionMessage); + + var targetInvocationException = + new TargetInvocationException(exception); + + var argumentNullException = + new ArgumentNullException(); + + var invalidOperationException = + new InvalidOperationException(exceptionMessage); + + var aggregateException = + new AggregateException(); + + var operationCanceledException = + new OperationCanceledException(); + + var fileNotFoundException = + new FileNotFoundException(exceptionMessage); + + var directoryNotFoundException = + new DirectoryNotFoundException(exceptionMessage); + + var ioException = + new IOException(exceptionMessage); + + var jsonSerializationException = + new JsonSerializationException(); + + var jsonReaderException = + new JsonReaderException(); + + + return new TheoryData + { + targetInvocationException, + argumentNullException, + invalidOperationException, + aggregateException, + operationCanceledException, + fileNotFoundException, + directoryNotFoundException, + ioException, + jsonSerializationException, + jsonReaderException + }; + } } } From 049ca5c7386addf8ceee36efed589557556e861b Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 03:50:27 +0100 Subject: [PATCH 25/28] ShouldThrowCriticalDependencyExceptionOnRetrieveAllIfDependencyErrorOccursAndLogIt -> PASS --- .../Authors/AuthorService.Exceptions.cs | 69 ++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/PlanetDotnet/Services/Foundations/Authors/AuthorService.Exceptions.cs b/PlanetDotnet/Services/Foundations/Authors/AuthorService.Exceptions.cs index 73a8b67..6afce5d 100644 --- a/PlanetDotnet/Services/Foundations/Authors/AuthorService.Exceptions.cs +++ b/PlanetDotnet/Services/Foundations/Authors/AuthorService.Exceptions.cs @@ -6,8 +6,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Reflection; using System.Threading.Tasks; +using Newtonsoft.Json; using PlanetDotnet.Authors.Models.Authors; using PlanetDotnet.Authors.Models.Authors.Exceptions; @@ -23,6 +25,69 @@ private async ValueTask> TryCatch(ReturningAuthorsFunction r { return await returningAuthorsFunction(); } + catch (ArgumentNullException argumentNullException) + { + var failedAuthorStorageException = + new FailedAuthorStorageException(argumentNullException); + + throw CreateAndLogDependencyException(failedAuthorStorageException); + } + catch (InvalidOperationException invalidOperationException) + { + var failedAuthorStorageException = + new FailedAuthorStorageException(invalidOperationException); + + throw CreateAndLogDependencyException(failedAuthorStorageException); + } + catch (AggregateException aggregateException) + { + var failedAuthorStorageException = + new FailedAuthorStorageException(aggregateException); + + throw CreateAndLogDependencyException(failedAuthorStorageException); + } + catch (OperationCanceledException operationCanceledException) + { + var failedAuthorStorageException = + new FailedAuthorStorageException(operationCanceledException); + + throw CreateAndLogDependencyException(failedAuthorStorageException); + } + catch (FileNotFoundException fileNotFoundException) + { + var failedAuthorStorageException = + new FailedAuthorStorageException(fileNotFoundException); + + throw CreateAndLogDependencyException(failedAuthorStorageException); + } + catch (DirectoryNotFoundException directoryNotFoundException) + { + var failedAuthorStorageException = + new FailedAuthorStorageException(directoryNotFoundException); + + throw CreateAndLogDependencyException(failedAuthorStorageException); + } + catch (IOException ioException) + { + var failedAuthorStorageException = + new FailedAuthorStorageException(ioException); + + throw CreateAndLogDependencyException(failedAuthorStorageException); + } + catch (JsonSerializationException jsonSerializationException) + { + var failedAuthorStorageException = + new FailedAuthorStorageException(jsonSerializationException); + + throw CreateAndLogDependencyException(failedAuthorStorageException); + } + catch (JsonReaderException jsonReaderException) + { + var failedAuthorStorageException = + new FailedAuthorStorageException(jsonReaderException); + + throw CreateAndLogDependencyException(failedAuthorStorageException); + } catch (TargetInvocationException targetInvocationException) { var failedAuthorStorageException = @@ -35,9 +100,9 @@ private async ValueTask> TryCatch(ReturningAuthorsFunction r private AuthorDependencyException CreateAndLogDependencyException( Exception exception) { - var authorDependencyException = + var authorDependencyException = new AuthorDependencyException(exception); - + this.loggingBroker.LogCritical(authorDependencyException); return authorDependencyException; From 15cb28c6cc4aa5cb5a95c58d73881b83056aa31b Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 03:56:22 +0100 Subject: [PATCH 26/28] ShouldThrowServiceExceptionOnRetrieveAllIfServiceErrorOccursAndLogItAsync -> FAIL --- .../Exceptions/AuthorServiceException.cs | 16 +++++++++ .../AuthorServiceTests.Exceptions.GetAll.cs | 35 ++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 PlanetDotnet.Authors/Models/Authors/Exceptions/AuthorServiceException.cs diff --git a/PlanetDotnet.Authors/Models/Authors/Exceptions/AuthorServiceException.cs b/PlanetDotnet.Authors/Models/Authors/Exceptions/AuthorServiceException.cs new file mode 100644 index 0000000..e8e195f --- /dev/null +++ b/PlanetDotnet.Authors/Models/Authors/Exceptions/AuthorServiceException.cs @@ -0,0 +1,16 @@ +// --------------------------------------------------------------- +// Copyright (c) 2023 Planet Dotnet. All rights reserved. +// Licensed under the MIT License. +// See License.txt in the project root for license information. +// --------------------------------------------------------------- + +using System; + +namespace PlanetDotnet.Authors.Models.Authors.Exceptions +{ + public class AuthorServiceException : Exception + { + public AuthorServiceException(Exception innerException) + : base(message: "Author service error occurred, contact support.", innerException) { } + } +} diff --git a/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs index c0a646c..721acc1 100644 --- a/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs +++ b/PlanetDotnet.Tests.Unit/Services/Foundations/Authors/AuthorServiceTests.Exceptions.GetAll.cs @@ -5,7 +5,6 @@ // --------------------------------------------------------------- using System; -using System.Reflection; using System.Threading.Tasks; using Moq; using PlanetDotnet.Authors.Models.Authors.Exceptions; @@ -52,5 +51,39 @@ await Assert.ThrowsAsync(() => this.authorBrokerMock.VerifyNoOtherCalls(); this.loggingBrokerMock.VerifyNoOtherCalls(); } + + [Fact] + public async Task ShouldThrowServiceExceptionOnRetrieveAllIfServiceErrorOccursAndLogItAsync() + { + // given + var serviceException = new Exception(); + + var expectedAuthorServiceException = + new AuthorServiceException(serviceException); + + this.authorBrokerMock.Setup(broker => + broker.GetAllAuthorsAsync()) + .ThrowsAsync(serviceException); + + // when + var retrievedAuthorTask = + this.authorService.RetrieveAllAuthorsAsync(); + + // then + await Assert.ThrowsAsync(() => + retrievedAuthorTask.AsTask()); + + this.authorBrokerMock.Verify(broker => + broker.GetAllAuthorsAsync(), + Times.Once); + + this.loggingBrokerMock.Verify(broker => + broker.LogError(It.Is(SameExceptionAs( + expectedAuthorServiceException))), + Times.Once); + + this.authorBrokerMock.VerifyNoOtherCalls(); + this.loggingBrokerMock.VerifyNoOtherCalls(); + } } } From 88c7939b4a15f09be9cf59487976e5eeb963f48c Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 03:58:27 +0100 Subject: [PATCH 27/28] ShouldThrowServiceExceptionOnRetrieveAllIfServiceErrorOccursAndLogItAsync -> PASS --- .../Authors/AuthorService.Exceptions.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/PlanetDotnet/Services/Foundations/Authors/AuthorService.Exceptions.cs b/PlanetDotnet/Services/Foundations/Authors/AuthorService.Exceptions.cs index 6afce5d..b7e5318 100644 --- a/PlanetDotnet/Services/Foundations/Authors/AuthorService.Exceptions.cs +++ b/PlanetDotnet/Services/Foundations/Authors/AuthorService.Exceptions.cs @@ -95,6 +95,10 @@ private async ValueTask> TryCatch(ReturningAuthorsFunction r throw CreateAndLogDependencyException(failedAuthorStorageException); } + catch (Exception exception) + { + throw CreateAndLogServiceException(exception); + } } private AuthorDependencyException CreateAndLogDependencyException( @@ -107,5 +111,16 @@ private AuthorDependencyException CreateAndLogDependencyException( return authorDependencyException; } + + private AuthorServiceException CreateAndLogServiceException( + Exception exception) + { + var studentServiceException = + new AuthorServiceException(exception); + + this.loggingBroker.LogError(studentServiceException); + + return studentServiceException; + } } } From fb31653cdfdfea96c5ef0043fa40479bd4d22135 Mon Sep 17 00:00:00 2001 From: "Mabrouk.Mahdhi" Date: Wed, 29 Nov 2023 04:07:48 +0100 Subject: [PATCH 28/28] FUNCTIONS: Get All Authors --- PlanetDotnet/Functions/AuthorFunctions.cs | 37 ++++++++++++++++++----- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/PlanetDotnet/Functions/AuthorFunctions.cs b/PlanetDotnet/Functions/AuthorFunctions.cs index df5298e..6067cc5 100644 --- a/PlanetDotnet/Functions/AuthorFunctions.cs +++ b/PlanetDotnet/Functions/AuthorFunctions.cs @@ -5,25 +5,46 @@ // --------------------------------------------------------------- using System.Threading.Tasks; +using System.Web.Http; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; -using Microsoft.Extensions.Logging; +using PlanetDotnet.Brokers.Loggings; +using PlanetDotnet.Services.Foundations.Authors; namespace PlanetDotnet.Functions { - public static class AuthorFunctions + public class AuthorFunctions { - [FunctionName("AuthorFunctions")] - public static async Task Run( - [HttpTrigger(AuthorizationLevel.Function, "get", Route = "authors")] HttpRequest req, - ILogger log) + private readonly ILoggingBroker loggingBroker; + private readonly IAuthorService authorService; + + public AuthorFunctions( + ILoggingBroker loggingBroker, + IAuthorService authorService) + { + this.loggingBroker = loggingBroker; + this.authorService = authorService; + } + + [FunctionName("GetAllAuthors")] + public async Task GetAllAuthorsAsync( + [HttpTrigger(AuthorizationLevel.Function, "get", Route = "authors")] HttpRequest req) { - log.LogInformation("C# HTTP trigger function processed a request."); + try + { + this.loggingBroker.LogInformation("Started loading all authors."); + var authors = await this.authorService.RetrieveAllAuthorsAsync(); - return new OkObjectResult(""); + this.loggingBroker.LogInformation("Finished loading all authors."); + return new OkObjectResult(authors); + } + catch + { + return new InternalServerErrorResult(); + } } } }